- 一、内部类是什么?
- 二、内部类的共性
- 三、静态内部类和非静态内部类的区别
- 四、四种内部类的用法
- 1.成员内部类
- 2.局部内部类
- 3.匿名内部类
- 4.静态内部类
- 五、理解内部类
- 六、内部类的使用场景和好处
一、内部类是什么?
Java类中不仅可以定义变量和方法,还可以定义类,这样在类内部定义的类被称为内部类。内部类可以分为:静态内部类(嵌套类)和非静态内部类。非静态内部类又可以分为:成员内部类、方法内部类、匿名内部类。内部类拥有外部类的所有元素的访问权限。
二、内部类的共性- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号。
- 内部类不能以普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否为 private 的。
- 内部类声明成静态的,就不能随便访问外部类的成员变量,仍然是只能访问外部类的静态成员变量。
- 外部类不能直接访问内部类的的成员,但可以通过内部类对象来访问。
- 静态内部类可以有静态成员,而非静态内部类则不能有静态成员。
- 静态内部类可以访问外部类的静态变量,而不可访问外部类的非静态变量。
- 非静态内部类的非静态成员可以访问外部类的非静态变量。
- 静态内部类的创建不依赖于外部类,而非静态内部类必须依赖于外部类的创建而创建。
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
class OuterClass{
public void print(){
System.out.println("OuterClass");
}
//内部类
class InnerClass{
public void print(){
System.out.println("InnerClass");
}
}
}
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。例:
public class TestInnerClass {
public int outField1 = 1;
protected int outField2 = 2;
int outField3 = 3;
private int outField4 = 4;
public TestInnerClass() {
InnerClass innerClass = new InnerClass();
//在外部类对象内部,直接通过new InnerClass() 创建内部类对象
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
System.out.println("其内部类的 filed1 字段的值为:" + innerClass.field1);
System.out.println("其内部类的 filed2 字段的值为:" + innerClass.field2);
System.out.println("其内部类的 filed3 字段的值为:" + innerClass.field3);
System.out.println("其内部类的 filed4 字段的值为:" + innerClass.field4);
}
public class InnerClass{
public int field1 = 5;
protected int field2 = 6;
int field3 = 7;
private int field4 = 8;
//static int field5 = 5; // 编译错误!普通内部类中不能定义 static 属性
public InnerClass() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
System.out.println("其外部类的 outFiled1 字段的值为:" + outField1);
System.out.println("其外部类的 outFiled2 字段的值为:" + outField2);
System.out.println("其外部类的 outFiled3 字段的值为:" + outField3);
System.out.println("其外部类的 outFiled4 字段的值为:" + outField4);
}
}
public static void main(String[] args) {
TestInnerClass outerClass = new TestInnerClass();
//若不在外部类内部,使用外部类对象.new 内部类构造器(); 的方式创建内部类对象
//InnerClass innerClass = outerClass.new InnerClass();
}
}
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。运行结果如下:
2.局部内部类局部内部类使用的比较少,局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。例:
public class InnerClassTest {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
public InnerClassTest() {
System.out.println("创建 "+ this.getClass().getSimpleName() + " 对象");
}
private void localInnerClassTest() {
//局部内部类a,只能在当前方法中使用
class A {
//static int field = 1; //编译错误!局部内部类中不能定义static字段
public A() {
System.out.println("创建 "+ A.class.getSimpleName() + " 对象");
System.out.println("其外部类的 filed1 字段的值为:" + field1);
System.out.println("其外部类的 filed2 字段的值为:" + field2);
System.out.println("其外部类的 filed3 字段的值为:" + field3);
System.out.println("其外部类的 filed4 字段的值为:" + field4);
}
}
A a = new A();
if (true) {
//局部内部类B,只能在当前代码块中使用
class B {
public B() {
System.out.println("创建 "+ B.class.getSimpleName() + " 对象");
System.out.println("其外部类的 filed1 字段的值为:" + field1);
System.out.println("其外部类的 filed2 字段的值为:" + field2);
System.out.println("其外部类的 filed3 字段的值为:" + field3);
System.out.println("其外部类的 filed4 字段的值为:" + field4);
}
}
B b = new B();
}
//B b1 = new B(); 编译错误!不在类B的定义域内,找不到类B
}
public static void main(String[] args) {
InnerClassTest outObj = new InnerClassTest();
outObj.localInnerClassTest();
}
}
同样的,在局部内部类里面可以访问外部类对象的所有访问权限的字段,而外部类却不能访问局部内部类中定义的字段,因为局部内部类的定义只在其特定的方法体 / 代码块中有效,一旦出了这个定义域,那么其定义就失效了,就像代码注释中描述的那样,即外部类不能获取局部内部类的对象,因而无法访问局部内部类的字段。运行结果:
匿名内部类有多种形式,其中最常见的一种形式莫过于在方法参数中新建一个接口对象 / 类对象,并且实现这个接口声明 / 类中原有的方法了:
- 匿名内部类中是没有访问修饰符的。
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类中不能存在任何静态成员或方法。
- 匿名内部类是没有构造方法的,因为它没有类名。
例:
public class InnerClassTest2 {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
public InnerClassTest2() {
System.out.println("创建 InnerClassTest 对象");
}
//自定义接口
interface OnClickListener {
void onClick(Object obj);
}
private void anonymousClassTest() {
//在这个过程中会创建一个匿名内部类对象
//这个匿名内部类实现类OnClickListener接口并重写onClick方法
OnClickListener listener = new OnClickListener() {
//可以在内部类中定义属性,但是只能在当前内部类中使用,
//无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名,
//也就无法创建匿名内部类的对象
int field = 1;
@Override
public void onClick(Object obj) {
System.out.println("对象 " + obj + " 被点击");
System.out.println("其外部类的 filed1 字段的值为:" + field1);
System.out.println("其外部类的 filed2 字段的值为:" + field2);
System.out.println("其外部类的 filed3 字段的值为:" + field3);
System.out.println("其外部类的 filed4 字段的值为:" + field4);
}
};
//new Object() 过程会创建一个匿名内部类,继承与Object类,
//并重写了toString()方法
listener.onClick(new Object() {
@Override
public String toString() {
return "obj1";
}
});
}
public static void main(String[] args) {
InnerClassTest2 outObj = new InnerClassTest2();
outObj.anonymousClassTest();
}
}
运行结果如下:
上面的代码中展示了常见的两种使用匿名内部类的情况:
1、直接 new 一个接口,并实现这个接口声明的方法,在这个过程其实会创建一个匿名内部类实现这个接口,并重写接口声明的方法,然后再创建一个这个匿名内部类的对象并赋值给前面的 onClickListener 类型的引用;
2、new 一个已经存在的类 / 抽象类,并且选择性的实现这个类中的一个或者多个非 final 的方法,这个过程会创建一个匿名内部类对象继承对应的类 / 抽象类,并且重写对应的方法。
同样的,在匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,因为是匿名内部类,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。
4.静态内部类静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。创建一个类的静态内部类对象不需要依赖其外部类对象,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。例:
public class InnerClassTest3 {
public int field = 1;
public InnerClassTest3() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
//创建静态内部类对象
StaticClass innerObj = new StaticClass();
System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
System.out.println("其内部类的 field5 字段的值为: " + innerObj.field5);
}
static class StaticClass {
public int field2= 2;
protected int field3 = 3;
int field4 = 4;
private int field5 = 5;
//静态内部类中可以定义static属性
static int field6 = 6;
public StaticClass() {
System.out.println("创建 " + StaticClass.class.getSimpleName() + " 对象");
//编译报错; Non-static field 'field' cannot be referenced from a static context
//System.out.println("其外部类的 filed1 字段的值为:" + field);
}
}
public static void main(String[] args) {
//无需依赖外部类对象,直接创建内部类对象
InnerClassTest3 outObj = new InnerClassTest3();
}
}
运行结果:
为什么成员内部类可以无条件访问外部类的成员?
在此之前,我们已经讨论过了成员内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是Outter.java的代码:
public class Outer {
private Inner inner = null;
public Outer() {
}
public Inner getInnerInstance() {
if(inner == null) {
inner = new Inner();
}
return inner;
}
protected class Inner {
public Inner() {
}
}
}
编译之后,出现了两个字节码文件:
为什么在Java中需要内部类?总结一下主要有以下四点:
1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
3.方便编写事件驱动程序
4.方便编写线程代码
第一点是最重要的原因之一,内部类的存在使得Java的多继承机制变得更加完善。



