引言
重出江湖了,各位少侠多多关照
内部类多而繁杂,互访情况下,不管是内访外,还是外访内,静态(类,方法,成员变量)与非静态(类,方法,成员变量)之间的访问也错综复杂。还有各种令人头疼的编译问题,匿名内部类使用的形参为何必须为final修饰等都是面试喜欢问的点。如果您对内部类还有疑惑,读完本文,希望能加深理解。
文章导读
- 内部类的种类
- 命名规则
- 匿名内部类编译
- 内外互访
一、内部类的种类
内部类分为4种:
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类
成员内部类
与成员变量类似,写在类里。如果内部类持有外部类的引用,可能导致内部类没有执行完,外部类也无法释放,有内存泄漏风险。想要避免这种现象,需要在外部类对象生命周期结束时手动将内部类对象生命周期结束。除非延迟回收会成为系统的运维瓶颈,一般不需要特别关注,GC机制通常足以应付。
public class Outter {
String s;
class Inner{
}
}
局部内部类
局部内部类是定义在一个方法或者一个作用域里的类。Inner2和Inner都是Outter的内部类。
public class Outter {
public Inner getInner2(){
class Inner2 extends Inner{
String s = "Inner2";
}
return new Inner2();
}
}
class Inner{
String s;
}
静态内部类
好吧,就是成员内部类前面加了static。声明为static的类不会持有外部类的引用,可以通过软引用的方式保存外部类的了引用,只有静态内部类不可能造成内存泄漏。
public class Outter {
String s;
static class Inner{
}
}
匿名内部类
匿名内部类应该是我们用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。
public class Outter {
void test() {
new Thread(new Runnable(){
String S;
@Override
public void run(){}
}).start();
}
}
二、内部类编译后的命名规则
非匿名内部类类名规则为 OutClass$InnerClass(外部类类名与内部类类名用$连接) 匿名内部类类名则为OutClass$数字(OutClass$1,OutClass$2,OutClass$3)
public class A {
C c = new C(){
String s="i am c";
@Override
public void demo() {
}
};
C c2 = new C(){
String s="i am c2";
@Override
public void demo() {
}
};
}
interface C{
public void demo();
}
编译之后:显然,符合上述命名规则
三、关于匿名内部类的编译问题
先看如下代码:
public class Test {
public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
}
}.start();
}
}
- 当方法执行完成后,局部变量a声明周期结束,此时Thread对象的声明周期可能没有结束,在run方法中继续访问a变量变成不可能,怎么解决?
java采用的是拷贝的套路。如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造方法传参的方式来对拷贝进行初始化赋值(可以反编译查看,发现Test$1的构造方法有两参数,一个是指向外部类对象的引用,一个是int型变量。很显然,int型变量就是Test成员变量a的拷贝。) - 在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?
会造成数据不一致性。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),数据不一致的问题就解决了。
这就是必须使用final修饰的原因。
四、内部类,外部类互访
- 内部类访问外部类
里面的可以自由访问外面的,访问非静态成员变量时必须先创建对象。
以下从内部类的状态开始阐述。
1.非静态内部类的非静态方法
直接访问
public class Outter {
int i = 5;
static String word = "Hello";
class Inner {
void Test() {
System.out.println(i);
System.out.println(word);
}
}
}
2.非静态内部类的静态方法
非静态内部类不能有静态方法,编译报错。
3.静态内部类的非静态方法
访问非静态成员变量必须先new外部类。
public class Outter {
int i = 5;
static String word = "Hello";
static class Inner {
void Test() {
System.out.println(new Outter().i);
System.out.println(word);
}
}
}
4.静态内部类的静态方法
和第三种情况类似,静态方法访问外部类非静态成员变量必须先new外部类。
public class Outter {
int i = 5;
static String word = "Hello";
static class Inner {
static void Test() {
System.out.println(new Outter().i);
System.out.println(word);
}
}
}
- 外部类访问内部类
可以将内部类理解为外部类的一个普通成员,所以外面的访问里面的需先new一个对象。
Outter.Inner inner = new Outter.Inner();
1.非静态方法访问非静态内部类成员
先new内部类
public class Outter {
void Test() {
System.out.println(new Inner().i);
}
class Inner {
int i = 5;
// static String word = "hello"; 编译报错!
}
}
2.非静态方法访问静态内部类的成员
先new内部类,再访问非静态的成员变量
静态成员变量可外部类.内部类.变量名直接访问
public class Outter {
void Test() {
Outter.Inner inner = new Outter.Inner();
System.out.println(inner.i);
System.out.println(inner.string);
System.out.println(Outter.Inner.string);
}
static class Inner {
int i = 5;
static String string = "Hello";
}
}
3.静态方法访问非静态内部类的成员
public class Outter {
static void Test() {
System.out.println(new Outter().new Inner().i);
}
class Inner {
int i = 5;
// static String word = "Hello"; 编译报错!
}
}
4.静态方法访问静态内部类的成员
访问非静态成员需先new 内部类
new Inner().i
与第2种情况相似
public class Outter {
static void Test() {
Outter.Inner inner = new Outter.Inner();
System.out.println(new Inner().i);
System.out.println(inner.i);
System.out.println(Outter.Inner.word);
}
static class Inner {
int i = 5;
static String word = "Hello";
}
}
5.匿名内部类
匿名内部类访问外部成员变量时,成员变量前应加final关键字。
至此,内部类完结,感觉身体被掏空~
如果喜欢我的文章欢迎关注我的专栏~
参考博文【Java】内部类与外部类的互访使用小结,匿名内部类 类名规则 定位$1