1. 什么是内部类
1.1 内部类的定义
所谓内部类,就是在某个类的内部定义的类,结构如下所示:
//外部类
class ClassA {
//内部类
class ClassB {
....
}
....
}
1.2 内部类的优缺点
优点:
- 1.内部类和外部类可以相互访问对方所有的成员变量和方法(包括private)。
- 2.内部类可以对外界的其他类隐藏起来。
- 3.通过内部类可以实现类似多继承的效果。
缺点:
- 结构相对比较复杂。
2. 内部类的种类
2.1 成员内部类
成员内部类是最为普通的一种内部类结构,内部类就像是外部类的成员变量一样进行定义,但二者之间还是有很多差别的。
public class OutterClass {
int outNumOne = 1;
private int outNumTwo = 2;
public static int outStaticNum = 10;
public OutterClass() {
InnerClass innerClass = new InnerClass();
System.out.println("内部类 innerNumOne 值为: " + innerClass.innerNumOne);
System.out.println("内部类 innerNumTwo 值为: " + innerClass.innerNumTwo);
test();
}
public static void test() {
System.out.println("其内部类的 static 字段的值为: " + InnerClass.staticNum);
}
public class InnerClass {
int innerNumOne = -1;
private int innerNumTwo = -2;
public static final int staticNum = 0;
public InnerClass() {
System.out.println("创建" + this.getClass().getSimpleName() + "对象");
System.out.println("外部类 outNumOne 值为: " + outNumOne);
System.out.println("外部类 outNumTwo 值为: " + outNumTwo);
System.out.println("外部类 static 字段值为: " + outStaticNum);
}
}
}
上述代码在OutterClass中定义了一个成员内部类InnerClass,如代码中所示,内部类和外部类之间可以互相访问对方所有访问权限的字段和方法,但区别是内部类访问外部字段时(例如outNumOne
)可以直接进行访问,而外部类访问内部类的字段时需要先构造出内部类的对象,然后通过对象进行调用。
这与构造出来的外界其他类的对象有什么区别呢?
例如:
public OutterClass() {
//Person是定义在外部的其他类
Peron peron = new Person();
System.out.println("name 的值为: " + person.name);
}
class Person {
private String name = "person";
}
运行一下就会发现,程序在执行person.name
时报错了,因为无法访问Person类的私有属性,而创建的内部类对象却可以访问所有控制权限的属性。
另外需要注意的是:
- 内部类中声明的所有静态域都必须是final的,这是因为成员内部类依托于外部类的对象,而对于每个外部对象都分别有一个单独的内部类实例,如果这个域不是final,则会出现不唯一的情况。
- 内部类中不允许声明静态方法。
在外界其他类中构造成员内部类
publi class InnerClassTest {
public static void main(String[] args) {
OutterClass outterClass = new OutterClass();
OutterClass.InnerClass innerClass = outterClass.new InnerClass();
}
}
通过上述代码可以总结出,在外界其他类中构造成员内部类的通用格式为:所在外部类.内部类 对象名 = 所在外部类对象.new 内部类();
如果不想单独创建外部类对象,则可以使用以下的简略形式:
publi class InnerClassTest {
public static void main(String[] args) {
OutterClass.InnerClass innerClass = new OutterClass().new InnerClass();
}
}
2.2 静态内部类
静态内部类不依托于外部类的实例而存在,就像类中的静态变量和方法一样,是独立于所有对象的,只需要通过类名.静态成员或方法名
调用即可。同理,静态内部类的创建也无需依赖外部类对象。例如:
public class OutterClass {
int outNumOne = 1;
private int outNumTwo = 2;
public static int outStaticNum = 10;
public OutterClass() {
InnerClass innerClass = new InnerClass();
System.out.println("内部类 innerNumOne 值为: " + innerClass.innerNumOne);
System.out.println("内部类 innerNumTwo 值为: " + innerClass.innerNumTwo);
test();
}
public static void test() {
System.out.println("其内部类的 static 字段的值为: " + InnerClass.staticNum);
}
public static class StaticInnerClass {
int innerNumOne = -1;
private int innerNumTwo = -2;
public static final int staticNum = 0;
public StaticInnerClass() {
System.out.println("创建" + StaticInnerClass.class.getSimpleName() + "对象。");
System.out.println("其外部类的 static 字段的值为: " + outStaticNum);
}
}
}
需要注意的是静态内部类无法调用其外部类的非静态成员属性或方法,而外部类依旧像访问普通成员内部类一样可以访问静态内部类对象的所有访问权限的成员。
但在静态内部类的类中可以定义任意的成员变量和方法,这一点也和普通成员内部类一致。
在外界其他类中构造静态内部类
由于静态内部类的构造不需要依赖外部类的对象,所以可以通过如下形式创建:
publi class InnerClassTest {
public static void main(String[] args) {
OutterClass.StaticInnerClass innerClass = new OutterClass.StaticInnerClass();
}
}
2.3 匿名内部类
有时候可能只是需要一个接口的实例化对象,这时单独创建一个内部类则显得有些复杂,例如:
public interface IInfo {
String getInfo();
}
内部类如下:
public class OutterClass {
public Class InnerInfo implements IInfo {
public String getInfo() {
return "info......";
}
}
public void show() {
InnerInfo info = new InnerInfo();
System.out.println(info.getInfo());
}
}
我们可以通过匿名内部类的方式简化以上代码,例如:
public class OutterClass {
public void show() {
//将匿名内部类赋给了IInfo接口对象info,并实现了接口中的方法。
IInfo info = new IInfo() {
public String getInfo() {
return "info......";
}
};
System.out.println(info.getInfo());
}
}
匿名内部类不只可以创建接口的实例化对象,还可以扩展已有的类,继承某个类/抽象类,并扩展某些方法,例如:
public class OutterClass {
public void show(int num) {
//创建了一个继承自Object类型的对象,并重写了类中的toString方法。
Object obj = new Object() {
@Override
public String toString() {
return "test : " + num;
}
};
System.out.println(obj);
}
}
需要注意的是,匿名内部类和普通成员内部类一样可以访问其外部类的所有成员,但由于匿名内部类没有类名,所以外部类无法构造出额外的匿名内部类对象来访问匿名内部类中的成员变量和方法。
还有一点和普通成员内部类不同的是,匿名内部类可以访问它所在方法的局部变量,例如上面代码中show方法的参数num,但要注意的是所访问的局部变量必须为最终的值,即final修饰的变量(JDK1.8后可去掉final修饰符)。也就是说作为被匿名内部类访问的局部变量,num的值无论是在匿名内部类的内部还是方法的其他任意地方都不可再进行修改。
2.4 局部内部类
局部内部类的应用场景相对较少,它与匿名内部类相似,可以在方法或代码块中定义,但局部内部类的使用范围仅在其定义的方法或代码块中有效,例如:
public class OutterClass {
int outNumOne = 1;
private int outNumTwo = 2;
public static int outStaticNum = 10;
public void test(int num) {
class InnerClassA {
public InnerClassA() {
System.out.println("创建" + this.getClass().getSimpleName() + "对象");
System.out.println("外部类 outNumOne 值为: " + outNumOne);
System.out.println("外部类 outNumTwo 值为: " + outNumTwo);
System.out.println("外部类 static 字段值为: " + outStaticNum);
System.out.println("局部变量 num 的值为: " + num);
}
}
InnerClassA classA = new InnerClassA();
if(true) {
class InnerClassB {
public InnerClassB() {
System.out.println("创建" + this.getClass().getSimpleName() + "对象");
System.out.println("外部类 outNumOne 值为: " + outNumOne);
System.out.println("外部类 outNumTwo 值为: " + outNumTwo);
System.out.println("外部类 static 字段值为: " + outStaticNum);
System.out.println("局部变量 num 的值为: " + num);
}
}
InnerClassB classB = new InnerClassB();
}
//InnerClassB classB = new InnerClassB(); //编译错误:局部内部类InnerClassB只在其定义的if代码块内有效,所以在此处找不到该类。
}
}
局部内部类同匿名内部类一样可以访问外部类所有控制权限的字段和方法,但外部类却不能访问局部内部类中定义的成员属性和方法,因为其只在定义它的代码块里有效,一旦离开这个代码块其定义就已经失效了,所以局部内部类也不能定义为public。
还有一点,局部内部类和匿名内部类一样可以访问局部变量,同样要求局部变量为最终值。