还记得在刚学习内部类时,经常对外部类以及各种内部类傻傻分不清楚,等到后来知道是怎么一回事后,又随着时间的流逝,再要说出个大概却是什么都回顾不起来了,因此本文就对内部类做个回顾。
关于内部类的定义就是可以将一个类的定义放在另一个类的定义内部
,内部类是一种非常有用的特性,它允许我们把一些逻辑相关的类组织在一起,并且可以控制位于内部的类的可视性。对于上面提到的各种内部类,我们可以大概分为下面张图片的分类:
内部类
从上图中可以看到,内部类分成静态内部类和非静态内部类,而非静态内部类又可以分为局部内部类和匿名内部类,同时我们把包裹内部类的类称之为外部类,就如下:
class OuterClass {
...
// 静态内部类
static class StaticInnerClass {
...
}
// 非静态内部类
class InnerClass {
...
}
}
外部类可以使用 public 或者默认包权限来修饰,而内部类则可以使用 private、protected、public 以及包权限进行修饰。使用内部类和我们平时使用普通类并没什么不同,此外当生成内部类对象的时候,内部类持有当前外部类的引用,通过这个引用它可以访问外部类所有的成员变量,包括私有变量
,也就是说实际上内部类和它所在的外部类实例对象是相关联的,它不能脱离外部类实例而独自存在,那么我们可能会好奇,它是怎么样和外部类进行相关联的呢?其实是编译器在生成 Java 字节码的时候通过给非静态内部类添加构造方法,使其在进行实例化时得到外部类的引用。
既然构建内部类时需要持有外部类对象的引用,那么要想创建内部类对象就要使用外部类的对象来创建该内部类对象,如下:
public class OuterClass {
public class Inner {}
public static void main(String[] args) {
OuterClass out = new OuterClass();
OuterClass.Inner in = out.new Inner();
// 也可以使用这种方式进行创建
Inner oin = out.new Inner();
}
}
静态内部类
当我们不想内部类对象与其外部类对象之间有联系的时候,我们可以将内部类声明为 static,这样它变成了静态内部类,既然没有和外部类对象有任何相关联,所以它也不需要再依赖与外部类对象,这样它也不能访问外部类的非静态成员,只能够访问外部类的静态成员,此外静态内部类和非静态内部类在创建时也稍有区别:
// OuterClass 为外部类,类含有静态内部类 S 以及非静态内部类 I
OuterClass out = new OuterClass();
// 创建静态内部类
out.S staticClass = out.S();
// 创建非静态内部类
out.I in = out.new I();
这里对静态内部类和非静态内部类做个总结:
- 静态内部类可以有静态成员,而非静态内部类则不能有静态成员;
- 静态内部类可以访问外部类的静态变量,而不可访问外部类的非静态变量;
- 非静态内部类的非静态成员可以访问外部类的非静态变量;
- 静态内部类的创建不依赖于外部类,而非静态内部类必须依赖于外部类的创建而创建;
局部内部类
如果一个内部类只在一个方法中使用,那么我们就可以将这个类定义在方法内部,这种内部类被称为局部内部类,其作用域也仅限于该方法区内,如下:
public class OuterClass {
public void show() {
System.out.println("外部类方法");
}
public void showFunctionClass() {
class FunctionClass {
private void show() {
System.out.println("我是局部内部类");
}
}
FunctionClass FunctionClass = new FunctionClass();
FunctionClass.show();
}
public static void main(String[] args) {
OuterClass out = new OuterClass();
out.show();
out.showFunctionClass();
}
}
// 运行结果:
//外部类方法
//我是局部内部类
此外,局部内部类注意事项如下:
- 局部内部类对外是隐藏的,只能通过创建这个类的方法中进行访问;
- 局部内部类不允许使用访问权限修饰符;
匿名内部类
匿名内部类是没有类名的局部类,匿名内部类使得类的定义和实例化同时进行,所以通常用来进行简化代码编写,而由于它没有类名也就不存在构造方法,下面通过两个小实验来学习匿名内部类的使用:
public interface Person {
public void eat();
}
public class Child implements Person {
@Override
public void eat() {
// TODO Auto-generated method stub
System.out.println("eat...");
}
public static void main(String[] args) {
Child child = new Child();
child.eat();
}
}
这是没有使用匿名内部类的普通写法,先实现接口再重写其中的方法,最后再实例化对象并调用方法,前面我们说到匿名内部类可以简化代码,那接下来看同样的例子用匿名内部类怎么来写:
public class Child {
public static void main(String[] args) {
new Person() {
@Override
public void eat() {
// TODO Auto-generated method stub
System.out.println("eat...");
}
}.eat();
}
}
这样引入匿名内部类直接就将接口中的方法进行重写,就省略了一个类的编写,所以只要是一个类是抽象类或者是一个接口,那么其子类方法或接口方法都可以使用匿名内部类来实现
,匿名内部类常用的典型场景是使用 Thread 类或者 Runnable 接口来实现多线程。此外,还需要注意的是,匿名内部类如果使用一个在其外部定义的对象时,编译器会要求该变量必须是 final,原因便是要保持参数的一致性。
为什么需要内部类
到这里,我们大概搞清楚内部类的相关知识点,其中在《Thinking in Java》一书中作者说到内部类最吸引人的原因是:
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
看到这儿,我想说的是大师就是大师,说的我都一脸懵逼了(太菜了),总结了下还是有下面几点:
- 内部类方法可以访问该类定义所在作用域中的数据,包括被 private 修饰的私有数据;
- 内部类可以对同一包中的其他类隐藏起来;
- 内部类可以解决 Java 单继承的缺陷;
- 对于一些有大量冗余代码的回调函数可以通过匿名内部类来实现;
参考资料: 《Thinking in Java》