Java内部类

偶然看到了关于Java内部类的面试题,感觉对Java内部类掌握的并不好,所以对Java内部类做了些回顾,整理了该篇文章。

和外部类的区别

内部类可以比外部类多使用三个修饰符:private,protected,static。

非静态内部类不能持有静态的成员变量和方法。

内部类的类型

  • 成员内部类
    • 非静态内部类
    • 静态内部类
      • 局部内部类(实际开发用的很少)
      • 匿名内部类(适合创建只需要使用一次的类)

在JDK 1.8之前,被匿名内部类访问的局部变量必须使用final修饰符修饰,但是JDK 1.8对匿名内部类做了改进,被匿名内部类访问的局部变量可以不再使用final修饰,在JDK 1.8中,如果一个局部变量被匿名内部类访问了,那么该局部变量相当于自动使用了final修饰符号修饰。

成员内部类和外部类的关系

非静态内部类和外部类的关系

1. 非静态内部类持有外部类的引用,所以可以访问外部类的成员变量和方法。

2. 外部类不能使用非静态内部类的成员变量和方法。如果外部类想要访问非静态内部类的成员,必须显示的创建非静态内部类的实例,来访问其成员。

原因分析:
因为非静态内部类并不会随着外部类的加载或者对象的创建而创建,所以当我们创建了一个外部类的实例的时候,非静态内部类是不存在的,如果这个时候访问非静态内部类的成员,就会出错。

3. 外部类静态成员中不能使用非静态内部类,即使是通过创建非静态内部的实例的方式。

为什么不允许外部类通过创建非静态内部类的实例的方法,来访问非静态内部类的成员变量和方法?
因为非静态内部类持有外部类的引用,可以访问外部类的成员变量和方法,如果在静态方法中创建了非静态内部类的实例,但是非静态内部类访问了外部类的非静态的成员,而这时候外部类的实例并不存在,这个时候就出错了,所以不允许在外部类的静态方法中通过创建非静态内部类的实例的方式来访问非静态内部类。

4. 非静态内部类不能持有静态的成员变量和方法。

原因分析:
非静态内部类的成员变量和方法都是依赖于非静态内部类的实例的,当访问非静态内部类的成员变量和方法的时候,必须借助于外部类的实例来创建一个非静态内部类的实例,也就是说当访问非静态内部类的成员变量和方法的时候,非静态内部类的实例肯定是存在的,而静态成员变量和方法是类相关的,依赖于类而不是依赖于实例,不用借助于实例就可以访问,这和前面讲的正好矛盾。

简单点说,就是静态成员变量和方法,可以直接用类来访问,但是对于非静态内部类,如果访问他的成员变量和方法,他的实例必须存在,所以非静态的成员变量和方法,没有存在的意义。

静态内部类和外部类的关系

1. 静态内部类是类相关的,依赖于外部类,而不是外部类的的实例。

2. 静态内部类不能访问外部的类的非静态的成员变量和方法,甚至是静态内部类的非静态的方法也不能访问外部类的非静态的成员变量和方法。

原因分析:
因为静态内部类是类相关的,依赖于外部类而不是外部类的实例,当通过外部类创建了静态内部类的实例的时候,外部类的实例可能是不存在的,调用创建的静态内部类对象的非静态方法,如果非静态方法访问了外部类的的非静态成员变量,由于外部类的实例不存在,此时就会报错,所以静态内部类不能访问外部类的非静态的成员变量和方法。

3. 外部类不能直接访问静态内部类的内部的成员变量和方法。原因同非静态内部类。

使用内部类

在外部类内部使用内部类

和使用普通的类没有什么区别,唯一的区别是不要在外部类的静态成员中使用非静态内部类,因为静态成员不能访问非静态的成员。

在外部类以外使用非静态内部类

修饰符

内部类可以使用public,protected,default,private修饰。

  • private修饰的内部类只能在外部类内部使用
  • default修饰的内部类只能在和外部类同一个包下的类里面使用
  • protected修饰的内部类只能在和外部类同一个包下的类或者是和外部类具有继承关系的子类中使用
  • public修饰的内部类 在所有的包中都可以使用

创建对象

非静态内部类依赖于外部类的实例,创建非静态内部类的实例的时候,必须借助于外部类的实例。或者说非静态内部类的构造器必须使用外部类对象来调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Out {

public static void main(String[] args) {

Out.In in = new Out().new In("你好");
}

class In{
public In(String msg) {
System.out.println("内部类接收到-" + msg + "-被创建");
}
}
}

创建子类

在外部类以外的地方创建非静态内部类的子类,更需要注意前面提到的:非静态内部类的构造器必须通过外部类对象来调用。当创建一个子类的时候,子类的构造器总是会调用父类的构造器,因此在创建非静态内部类的子类的时候,必须保证子类构造器能够调用非静态内部类的构造器,调用非静态内部类的构造器的时候,必须存在一个外部类对象。

1
2
3
4
5
public class SubClass extends Out.In {
public SubClass(Out out) {
out.super("hello");
}
}

SubClass是非静态内部类的子类,非静态内部类In对象里面必须持有一个对Out对像的引用,其子类SubClass对象里也应该持有对Out对象的引用。当创建SubClass对象时候传给该构造器的Out对像,就是SubClass对象里Out对象引用所指向的对象。

非静态内部类的In对象和SubClass对象都必须持有指向Outer对象的引用,区别是创建两种对象的时候传入Outer的方法不同:当创建非静态内部类In的对象的时候,必须通过Outer对象来调用new关键字;当创建SubClass对象的时候,必须使用Outer对象作为调用者来调用In的构造器。

总之一句话,有一个非静态内部类的子类存在,就肯定有一个外部类的实例存在。

外部类以外使用静态内部类

创建实例

静态内部类是和类相关的,所以创建静态内部类的对象的时候,无需创建外部类对象。语法如下:

1
new OuterClassName.InterClassConstructor();

创建子类

创建静态内部类的子类也很简单,语法如下所示:

1
2
public class StaticSubClass extends OuterClassName.StaticInterClassName{
}

补充的问题

内部类是外部类的成员,但是并不能通过创建外部类的子类,在子类中定义一个内部类来重写父类中的内部类。愿因是因为内部类的类名不是简单的由内部类的类名构成的,他实际上还把外部类的类名作为一个命名空间,作为内部类类名的限制。因此在子类中的内部类和父类中的内部类不可能完全同名,即使二者所包含的内部类的类名相同,但是因为他们所处的外部类空间的不同,所以他们不可能完全的重名,所以也就不可能重写。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器