前言
随着工作经验的增加,越发感觉到基础的重要性,所以最近上下班地铁上都在看一些基础的东西,前几天看了拭心大哥讲的内部类,由于自己对概念的理解有误,造成了一些困惑,自己查了一上午才弄明白(正好早上开无聊的周例会,我就在那自己查资料,还拉着大学同学一起讨论),所以决定抽空把这部分再梳理一下,做个分享,也可以加深自己对这部分的理解。
正文
定义在一个类中或者方法中的类称为内部类。
内部类可以分为以下四类:
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类
我们来逐个分解:
-
成员内部类
最普通的内部类,它定义在一个类的内部中,就如同一个成员变量一样。
代码形式如下:
public class OuterClass {
class InnerClass {
private void method() {
System.out.println("innerMethod");
}
}
}
调用方式:
OuterClass out = new OuterClass();
OuterClass.InnerClass in = out.new InnerClass();
in.method();
注意:不是直接 new outClass.InnerClass()
成员内部类可以无条件的访问外部类的成员属性和成员方法(包括 private 和 static 类型的成员),这是因为在成员内部类中,隐式地持有了外部类的引用。
使用场景:
当类 A 需要使用类 B ,同时 B 需要访问 A 的成员/方法时,可以将 B 作为 A 的成员内部类。同时我们可以利用 private 内部类禁止其他类访问该内部类,从而做到将具体的实现细节完全隐藏。
-
局部内部类
在代码块或者方法中创建的类。局部内部类的作用域只能在其所在的代码块或者方法内,在其它地方无法创建该类的对象。可以把局部内部类理解为作用域很小的成员内部类。
代码形式如下:
public void test() {
class PartInnerClass {
private void method() {
System.out.println("partInnerMethod");
}
}
new PartInnerClass().method();
}
使用场景:
局部内部类只用于当前方法或者代码块中创建、使用,属于一次性产品,使用场景比较少。
-
静态内部类
使用 static 关键字修饰的内部类就是静态内部类,静态内部类不持有外部类的引用,可以看作是和外部类平级的类。
代码形式如下:
public class OuterClass {
static class StaticInnerClass {
private void method() {
System.out.println("staticInnerMethod");
}
}
}
我们想在静态内部类中访问外部类的成员只能 new 一个外部类的对象,否则只能访问外部类的静态属性和静态方法。
使用场景:
1.当类 A 需要使用类 B,而 B 不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类。
2.单例的实现:
public class Singleton {
private Singleton() {}
private static class InnerClass {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return InnerClass.INSTANCE;
}
}
由于InnerClass是一个内部类,只在外部类Singleton的getInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。初始化的时候由ClassLoader来保证同步,确保 INSTANCE 变量只能初始化一次,使INSTANCE成为一个真·单例。
-
匿名内部类
匿名内部类需要重点说一下,之前就是因为对它理解不到位,才造成了困惑。
概括一下匿名内部类的特点:
1.必须继承一个父类或实现一个接口,并不需要增加额外的方法,只是对继承方法的实现或是覆盖。
2.只是为了获得一个对象实例,并不需要知道其实际的类型。
3.匿名内部类的匿名指的是没有类名,而不是没有引用指向它。
4.匿名内部类不能有构造方法,只能有初始化代码块。因为编译器会帮我们生成一个构造方法然后调用。
5.匿名内部类中使用到的参数是需要声明为 final 的,否则编译器会报错。
我之前理解出现偏差就是在第3条,好吧,其实第3条是我自己补充的,为了避免大家出现跟我一样的错误(之前和几个同学讨论的时候,大家对这一点的理解都很模糊)。
代码形式如下:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
我们常用的按钮点击监听用到的就是匿名内部类。
上面这个例子很好理解,我们再来看看下面这个:
@SuppressLint("HandlerLeak")
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
...
}
};
没错,这个new出来的 Handler 也是一个匿名内部类,我之前看到这个的时候懵逼了,判断不出它究竟是哪种内部类。所以记住,匿名内部类的匿名指的是没有类名,而不是没有引用指向它。你可别告诉我它的类型就是Handler啊,它只是Handler的一个子类,一个没有类名的子类。
顺便再说一下,这样的写法可能会造成内存泄露,因为这个匿名内部类会隐式地持有外部类的引用。举个例子:
Message message = Message.obtain();
message.what = 233;
mHandler.sendMessageDelayed(message, 1000 * 60 * 10);
finish();
我们发送了一个延迟10分钟的Message,在Message发送出去之前,我们结束了当前Activity。Message在消息队列中延迟了10分钟,然后才处理该消息。而这个Message引用了Handler,Handler又隐性地持有了Activity的引用,当发生GC时因为 Message – Handler – Acitivity 的引用链导致Activity无法被回收,所以发生了内存泄露。
解决办法就是使用弱引用WeakReference或者干脆将 Handler 设计为静态内部类。
那么为什么匿名内部类中使用参数需要声明为final呢?
因为匿名内部类是创建一个对象并返回,这个对象的方法被调用的时机不确定,方法中有修改参数的可能,如果在匿名内部类中修改了参数,外部类中的参数是否需要同步修改呢?Java 为了避免这种问题,限制匿名内部类访问的变量需要使用 final 修饰,这样可以保证访问的变量不可变。
使用场景:
需要实现一个接口,但不需要持有它的引用。
补充
除了静态内部类,剩下的成员内部类、局部内部类、匿名内部类 都会默认隐式地持有外部类的引用。
再来两道典型题目融会贯通一下:
在?处填入合适的代码,使程序在控制台输出30,20,10。
public class OuterClass {
private int num = 10;
class InnerClass {
int num = 20;
void show() {
int num = 30;
System.out.println(?);
System.out.println(?);
System.out.println(?);
}
}
public static void main(String[] args) {
InnerClass in = new OuterClass().new InnerClass();
in.show();
}
}
.
.
.
.
.
答案:
1.num
2.this.num
3.OuterClass.this.num
补全代码 ,要求在控制台输出"HelloWorld"
interface InterfaceTest {
void show();
}
public class TestClass {
//补全代码
public static void main(String[] args) {
TestClass.method().show();
}
}
.
.
.
.
.
答案:
interface InterfaceTest {
void show();
}
public class TestClass {
//补全代码
public static InterfaceTest method() {
return new InterfaceTest() {
@Override
public void show() {
System.out.println("HelloWorld");
}
};
}
public static void main(String[] args) {
TestClass.method().show();
}
}
结语
学海无涯,我们不仅要向前迈进,还得时常回过头去,看看来时的路。
文中多有借鉴拭心大哥的文章,附上链接:
http://blog.csdn.net/d29h1jqy3akvx
网友评论