可以将一个类的定义放在另一个类的定义内部,这就是内部类 –《Java编程思想》
Note1: 顶级类只能用 public 或 default 来修饰。而内部类可以使用 static , public , default , protected 和 private 进行修饰。
Note2: 内部类是一个编译时的概念,一旦编译成功,将会按照定义生成多个class文件。所以内部类的成员变量/方法名可以和外部类相同。
TestA.java
public class TestA {
class TestB {
}
}
结果如图所示
再看另外一个例子
TestA.java
public class TestA {
class TestB {
class TestC {
}
}
}
结果如图所示:
java内部类一般分为四种类型:成员内部类,局部内部类,嵌套内部类和匿名内部类。
1.成员内部类
成员内部类,就是作为外部类的成员。当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而且不需要任何特殊条件。
不过当成员内部类拥有和外部类有同名的成员变量或方法时,就会发生隐藏现象,默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要采用下列形式:
外部类.this.成员变量
外部类.this.成员方法
虽然成员内部类可以无条件的访问外部类的所有成员,但是如果外部类要访问内部类的成员变量,必须先创建一个成员内部类的对象,然后通过这个对象来访问。但是成员内部类是依附外部类而存在的,如果创建成员内部类的对象,前提必须存在一个外部类的对象。
代码实例如下所示:
TestA.java
public class TestA
private TestB testB = null ;
/**
* 获得成员内部类TestB的实例
*/
public TestB getTestBInstance () {
return new TestB();
}
/**
* 内部类
*/
class TestB {
}
public static void main (String[] args) {
TestA testA1 = new TestA();
TestA.TestB testB1 = testA1.new TestB();
TestA testA2 = new TestA();
TestA.TestB testB2 = testA2.getTestBInstance();
}
}
此外,还需要注意的是,成员内部类不能含有static的变量和方法。因为成员内部类需要先创建外部类,才能创建自己。还需要说明一点的是,局部内部类看以看作变量,所以有访问权限。所以如果内部类由private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。
2.局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或该作用域内。因为局部内部类就像是方法里面的一个局部变量一样,所以是不能有public、protected、private以及static修饰符的。
People.java
public class People {
public void showMe () {
System.out.println("我是一个人" );
}
public static People getNewPeople (int select) {
if (select == 1 ) {
class Man extends People {
@Override
public void showMe () {
System.out.println("我是一个男人" );
}
}
return new Man();
}else {
class Woman extends People {
@Override
public void showMe () {
System.out.println("我是一个女人" );
}
}
return new Woman();
}
}
public static void main (String[] args) {
People onePerson = People.getNewPeople(1 );
onePerson.showMe();
}
}
结果如图所示:
3.匿名内部类
匿名内部类是平常使用最多的,在编写事件监听代码时使用匿名内部类不但方便,而且容易维护。
MainActivity.java
Button readButton = (Button) findViewById(R.id.activity_main_read);
readButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick (View v) {
Intent intent = new Intent(MainActivity.this , SMSListActivity.class);
startActivity(intent);
}
});
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
4.静态内部类(嵌套类)
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
TestA.java
public class TestA {
/**
* 静态内部类
*/
static class TestB {
}
public static void main (String[] args) {
TestA.TestB testB = new TestA.TestB();
}
}
以上是对Java内部类的基础知识,下面就对Java内部类做一些深入的研究
一.局部内部类和匿名内部类访问方法的局部变量时,该变量必须是final类型。
Test.java
public class Test {
public static void main (String[] args) {
final int count = 10 ;
class TestInner{
public void show () {
System.out.println("局部内部类,数字是:" + count);
}
}
TestInner testInner = new TestInner();
testInner.show();
new Thread(new Runnable() {
@Override
public void run () {
System.out.println("匿名内部类,数字是:" + count);
}
}).start();
System.out.println("java版本是:" + System.getProperty("java.version" ));
}
}
在代码中,定义了一个局部内部类 TestInner 和匿名内部类 Runnable ,他们共同访问了方法中的局部变量 count 并打印出来结果,注意:局部变量 count 是final类型的。
如果设置布局 count 不是 final 类型的,看一下 IDEA 给的结果
IDEA 报错了,我们用编译器验证了局部内部类和匿名内部类访问的局部变量必须是 final 类型的。
但是,我为什么打印出 Java 版本,嘿嘿,原因在下面。
我们将 Java 版本设置为 Java8
看一下代码
没报错,Why?
运行一下
也是正常的,为什么?
因为 Java8 加了新特性:Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。lambda 式其实就是匿名内部类,所以不用显示将变量声明为 final 类型,但是这个变量还是不能更改的。
我们在局部内部类中更改了 count 值,所以就会报错。
那为什么局部内部类和匿名内部类访问的局部变量为什么必须是 final 类型的?
1. 原因是编译器实现上的困难:内部类对象的生命周期很有可能会超过局部变量的生命周期。
2. 局部变量的生命周期很短,方法调用完局部变量就会“死亡”。而内部类对象生命周期与其它类对象一样:自创建一个内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才有可能会“死亡”(被 JVM 垃圾回收)。所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但对象仍然活着。
3. 所以要求内部类的对象“活着”时,该局部变量也必须“活着”。
4. 解决方法:内部类对象可以访问同一个方法中被定义为 final 类型的局部变量。定义为 final 后,编译器会把内部类对象要访问的final 类型的局部变量,都拷贝一份作为该对象的成员变量。这样,即使栈中局部变量已经死亡,内部类对象照样可以拿到该局部变量的值,因为它自己拷贝了一份,且与原局部变量的值始终保持一致( final 类型不可变)。