内部类:定义在另一个类中的类。分为成员内部类(定义在成员位置)、局部内部类(定义在外部类方法中)、匿名内部类(没有类名,只用一次)、静态内部类(static修饰)
1、内部类特性
- 内部类可以被写在外部类的任意位置,如成员位置、方法内。
- 内部类可以用多个实例,每个实例都有自己的状态信息,与其他外部对象的信息相互独立(内部类与外部类没有is-a关系)
- 当想要定义一个回调函数又不想编写大量代码时,可以使用匿名内部类(现在多用lambda表达式)
- 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
- 内部类和普通类一样可以重写Object类的方法,如toString方法;并且有构造函数(匿名内部类没有构造函数),执行顺序依旧是先初始化属性,再执行构造函数
- 内部类既能访问自身的数据域,还能访问创建它的外部类对象的数据域:内部类的对象有指向外部类对象的隐式引用,这个外部类的引用在内部类的构造器中设置,编译器修改所有内部类的构造器,添加了外部类引用的参数Outter this&0,也正是因为这个原因创建内部类对象之前要创建外部类对象。(常规类要访问某个类的域只能访问public域,或者通过public方法访问private域)
- 编译器编译完后,会出现(外部类.class)和(外部类﹩内部类.class)两个类文件名。
- 局部和匿名内部类只能访问方法中的局部final变量:因为局部变量和局部/匿名内部类对象生存周期不同,如果局部变量的值在编译期间就可以确定,则编译器默认会在局部/匿名内部类的常量池中添加一个内容相对的字面量或直接将相应的字节码嵌入到执行字节码中,实际局部/匿名内部类使用的是另一个”复制的“局部变量,只不过值与方法中的局部变量值相等;若变量的值无法在编译期间确定,则通过内部类构造器传参的方式来对内部类中的”复制局部变量“进行初始化。(当变量是final是,若是基本数据类型,由于其值不变,因而复制品与原始的变量是一样的;若是引用类型,由于其值不变,即所指对象不变,因而复制品与原始引用变量指向同一个对象。非final就不能保证复制品与原值一致了)
- 关于成员内部类的继承问题:一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:1)成员内部类的引用方式必须为 Outter.Inner。2)构造器中必须有指向外部类对象的引用,并通过这个引用调用super()。这段代码摘自《Java编程思想》
2、语法规则
- 对外部类的引用:OuterClass.this
- 在外部类的作用域之外引用内部类:OuterClass.InnerClass
- 成员位置的内部类(除局部和匿名)被修饰为private时只对本类可见(一般是private);无修饰符(默认)时只对包可见;被修饰为protected时对本包和所有子类可见;被修饰为public时对所有类可见;被修饰为static时为静态内部类,没有this
- 当内部类声明为private类时,只有外部类的方法能够构造内部类的实例对象。
- 当内部类为public类时,外部类的方法和实例都可以构造内部类的实例对象。
- 内部类中声明的所有静态域必须是final,保证静态域的唯一性
- 内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成用“OuterClass$InnerClass”类名的常规类文件,而虚拟机对此一无所知。编译器为了引用外部类,生成了一个附加的实例域“this$0”
内部类对象的建立
1、在同包其他类 以及main方法中(前提要内部类没有被修饰成private,private内部类只能被外部类的方法访问)
- 1)先创建外部类对象,再通过外部类对象创建内部类
- Out outer = new Out();
- Out.In inner = outer.new In(); //第一个Out是为了说明该内部类到底属于哪个外部类
- 2)通过匿名外部类创建内部类
- Out.In inner = new Out().new In();
-
2、在外部类里
- 可直接创建对象,如 In inner = new In(); 或者直接new In();
内部类的访问
- 重名时:对外部类的静态成员通过 外部类名.静态成员变量名 访问;内部类访问自己的非静态成员通过this.变量名。访问外部类的非静态成员通过 外部类名.this.变量名 访问 。
- 不重名时:无论静态非静态,内部类直接通过变量名访问外部成员变量。
外部类的访问
- 内部类为非静态时:外部类访问内部类,必须建立内部类对象。
- 内部类为静态时:外部类访问非静态成员,通过(外部类对象名.内部类名.方法名)访问;外部类访问静态成员时,直接通过(外部类名.内部类名.方法名)
- PS:当内部类中定义了静态成员时,内部类必须是静态的;当外部静态方法访问内部类时,内部类也必须是静态的才能访问。
3、成员内部类
- 引用方式为Outter.Inner
- 最普通的内部类,定义在外部类的成员(域/方法)位置
- 可以像类成员一样被private、protected、public修饰,从而拥有对应的可见性
- 可以访问外部类所有成员,不论何种修饰
- 成员内部类依附外部类而存在,对象的建立依赖于外部类对象(外部类对象直接或通过外部方法调用内部类构造函数)
4、局部内部类
- 只在某个方法中创建某类对象
- 局部类不能用private、protected、public、static修饰
- 访问权限被限定在声明这个局部类的作用域中,即使外部类的方法也不能访问,对外界完全隐藏
- 局部类不仅可以访问其外部类,还可以访问局部变量(final的局部变量)
- 编译器会检测局部内部类对局部变量的访问,为每一个要访问的变量建立相应的局部内部类数据域,将局部变量拷贝到局部内部类的构造器中,以便将其数据域初始化为局部变量的副本
5、匿名内部类
- 一般用于继承其他类或实现接口,不需要增加额外方法,只需要创建一个对象并重写或实现继承的方法,因此不必给类命名
- 匿名内部类必须继承一个外部类或者实现接口,因为匿名内部类没有类名,而构造器又必须和类名相同,所以匿名类不能有构造器,只能将构造器参数传递给超类构造器,如果匿名类实现的是接口,则没有构造参数()不过还是要提供括号
- 如果构造参数的括号 () 后面跟一个大括号 {},则定义的是匿名内部类
- 匿名内部类可以访问局部final变量
双括号初始化:
invite(new ArrayList<String>() { {add(“Harry”); add(“Tony”);} } ); //构建并初始化匿名数组列表传递给invite()方法,外层{}:new ArrayList<String>(){};建立ArrayList的匿名子类;内层括号{}是一个对象构造块(即第四章的初始化块,在构造类对象的时候执行)
6、静态内部类
- 不依赖于外部类,在没有外部类对象的情况下也可以创建静态内部类的对象
- 不能使用外部类的非static成员(因为非static成员要依赖于具体的对象)
- 在接口中声明的内部类自动成为static和public类
- 若内部类的对象是在静态方法中构造的,则必须使用静态内部类
- 只有内部类可声明为static
静态内部类与非静态内部类的区别:
- 非静态内部类没有static域和方法;只有静态内部类可以有静态域和方法
- 非静态内部类可以调用外部类的任何成员,不论是否静态;静态内部类不能使用外部类的非static域和非static方法
- 非静态内部类在编译完成后会隐含的保存指向创建它的外部类的引用,但静态内部类没有这个引用,这意味着非静态内部类必须依赖于外部类的创建而创建,静态内部类的创建不依赖于外部类
编程练习
// InnerClassTest.java
public class InnerClassTest {
public static void main(String args[]) {
OuterClass outer = new OuterClass("Outer", 0);
OuterClass.InnerClass inner = outer.new InnerClass(); //外部类作用域之外引用内部类
inner.display();
outer.getPrivateInnerClass(); //private内部类,只能由外部类的方法new实例对象
System.out.println("LocalInnerClass: compare int " + "1 and 2 = " + outer.func().compare(1, 2));
String s1 = "Hello";
String s2 = "Java";
outer.compare2String(s1, s2);
OuterClass.StaticInnerClass s_inner = new OuterClass.StaticInnerClass(); //对比创建inner
s_inner.display();
}
}
//OuterClass.java
import java.time.LocalDate;
import java.util.Comparator;
public class OuterClass {
private String str; //final
private int num;
OuterClass(String str, int num) {
this.str = str;
this.num = num;
}
public class InnerClass {
public void display() {
System.out.println("InnerClass: visit OuterClass field str is " + str);
}
}
private class PrivateInnerClass {
public void display() {
System.out.println("PrivateInnerClass: visit OuterClass field num is " + OuterClass.this.num);
}
}
public void getPrivateInnerClass() {
PrivateInnerClass p_inner = new PrivateInnerClass();
p_inner.display();
}
public Comparator func() {
class LocalInnerClass implements Comparator<Integer> {
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
}
return new LocalInnerClass();
}
public void compare2String(String s1, String s2) {
Comparator<String> cmp = new Comparator<String>() { //匿名内部类必须是某个已经存在的接口或超类的子类
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
}; //不要忘了这里的分号
System.out.println("AnonymousInnerClass: compare String " + s1 + " and " + s2 + " = " + cmp.compare(s1, s2));
}
public static class StaticInnerClass {
public void display() {
System.out.println("StaticInnerClass: display()");
}
}
}
输出: