目录
1、什么是内部类?
简单的说就是在一个类的内部又定义了一个类,这个类就称之为内部类(Inner Class)。
内部类的使用场景及好处
-
(1). 每个内部类都能独立的继承一个接口的实现,弥补了java单继承的缺陷,使得多继承的方案变的完整;
-
(2). 将有一定逻辑的类组织在一起,又可以对外界隐藏;
-
(3). 静态内部类是采用延时懒加载,第一次使用的时候加载,不使用就不加载,所以可以很好的利用内存空间;
-
(4). 方便编写线程程序;
2、内部类的实现
2.1、成员内部类
-
类似于成员变量,对象的创建依赖于外部类;
-
内部类可以访问其所在类的属性(包括所在类的私有属性),内部类创建自身对象需要先创建其所在类的对象;
代码实现:
public class MyClass extends FatherF {
//非静态成员内部类传入了一个外部类的引用的指针;
public class InnerClassA extends FatherFF { //内部类使得java的单继承缺点变得完善,同时继承了FatherF和FatherFF两个类;
public void methodA(){
//taskA();
}
}
public static void main(String[] args) {
//内部类的对象创建分为2步:
//第一步:创建外部类的对象;
MyClass myClass = new MyClass();
//第二步:创建内部类的对象;
MyClass.InnerClassA innerClassA = myClass.new InnerClassA();
//调用内部类的方法;
innerClassA.methodA();
}
}
编译该文件会生成如下两个文件:
-
MyClass.class
-
MyClass$InnerClassA.class
jad反编译MyClass$InnerClass
A.class文件:
public class MyClass$InnerClassA {
//内部类传入了一个外部类的引用
public MyClass$InnerClassA(MyClass paramMyClass ){
}
public void methodA(){
taskA();
}
}
思考、为什么内部类可以肆无忌惮的访问外部类?
-
内部类在编译的时候会编译成两个class;
-
内部类的创建依赖于外部类;
由于编译器会默认给内部类添加一个:
final com.king.MyClass.this$0; 外部类的引用指针Myclass paramMyClass,所以内部类可以肆无忌惮的访问外部类的成员变量。
2.2、成员内部接口类
在类内部可以定义一个接口,且可以定义另外一个内部类来实现这个接口。
代码实现:
public class InnerInterfaceDemo {
//内部成员接口interface;
public interface Play {
void doPlay();
};
//内部成员接口interface的实现类;
public class PeoplePlay implements Play{
@Override
public void doPlay() {
System.out.println("children is playing~");
}
}
public static void main(String[] args) {
InnerInterfaceDemo innerInterfaceDemo = new InnerInterfaceDemo();
InnerInterfaceDemo.Play peoplePlay =innerInterfaceDemo.new PeoplePlay();
peoplePlay.doPlay();
}
}
2.3、局部内部类
定义在方法中的内部类,作用域只能在局部范围。
-
(1)、内部类不能被public、private、static修饰;
-
(2)、在外部类中不能创建内部类的实例;
-
(3)、创建内部类的实例只能在包含他的方法中;
-
(4)、内部类访问包含他的方法中的变量必须有final修饰;
-
(5)、外部类不能访问局部内部类,只能在方法体中访问局部内部类,且访问必须在内部类定义之后。
代码实现:
public class MethodOuterClass {
private String str1_outer = "str1_outer in Outer";
private String str2_outer = "str2_outer in Outer";
private String method1() {
return "method1 in Outer";
}
private String method2() {
return "method2 in Outer";
}
public void method3() {
final String s_method = "s_method in method3";
//局部内部类;
class MethodClassInner {
private String s1 = "str1_outer in MethodClassInner";
public void method1() {
// 内部类访问外部方法的变量,需要有final修饰
System.out.println(s_method);
// 局部内部类可直接访问外部类的变量,即使是私有的
System.out.println(str2_outer);
// 内部类和外部类有同名变量和方法时,访问作用域最近的变量
System.out.println(s1);
// 访问外部类的变量需要指明作用域;
System.out.println(MethodOuterClass.this.str1_outer);
System.out.println(method2());
// 访问外部类的方法时也需要指明作用域;
System.out.println(MethodOuterClass.this.method2());
}
private String method2() {
return "method2 in MethodClassInner";
}
}
MethodClassInner methodClassInner = new MethodClassInner();
methodClassInner.method1();
}
public static void main(String[] args) {
MethodOuterClass outerClass = new MethodOuterClass();
//方法的调用
outerClass.method3();
}
}
2.4、匿名内部类
-
大部分的匿名内部类用于接口的回掉;继承其他类或者实现其他的接口,不需要增加额外的方法,只是对继承方法的实现和重写;
只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现,最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口等。
代码实现:
public interface AnonymousClass {
public void play();
public class Demo {
public static void main(String[] args) {
//匿名内部类
AnonymousClass cat = new AnonymousClass() {
@Override
public void play() {
System.out.println("cat is playing");
}
};
cat.play();
}
}
}
思考、为什么局部内部类和匿名内部类只能访问局部final变量?
编译器会为匿名内部类和内部类生成一个新的对象,OuterX.class文件
如果不用final修饰会出现两个问题:
-
(1)、 生命周期不一样,局部变量方法执行完就结束了,但是线程可能还没有结束,访问了一个不存在的值;采用复制的手段可以解决。 如果局部变量在编译期间能确定值,则直接进行拷贝;如果不能确定值则使用构造传参的方式进行拷贝初始化赋值;
-
(2)、拷贝后解决了生命周期的问题,但是 数据的一致性又存在问题,所以使用final不允许对这个变量进行修改。 解决了二义性;
2.5、静态内部类
Java文档中是这样描述static内部类的:一旦内部类使用static修饰,那么此时这个内部类就升级为顶级类。也就是说,除了写在一个类的内部以外,static内部类具备所有外部类的特性。
-
(1).静态内部类不依赖外部类对象,成员内部类依赖外部对象;
-
(2).静态内部类也是定义在一个类里面,不持有外部类的引用,不依赖于外部类,不能访问外部的非静态成员变量。
-
(3).完全属于外部类,不属于任何一个实例对象;
-
(4).不可以访问外部类的实例变量;
-
(5).外部不可以声明为静态的,java只有一种静态类,那就是静态内部类;
代码实现:
public class StaticInnerClass {
//静态内部类会生成一个全新的类;
public static class InnerClass{
public void methodA(){
System.out.println("static inner method");
}
}
public static void main(String[] args) {
//static内部类对象的创建:静态内部类不依赖外部类对象,成员内部类依赖外部对象;
StaticInnerClass.InnerClass innerClass = new StaticInnerClass.InnerClass();
innerClass.methodA();
}
}
编译该文件会生成如下两个文件:
-
MyClass.class
-
MyClass$InnerClassB.class
反编译MyClass$InnerClassB.class,可以发现该类与非静态内部类相比,少了外部类的引用指针;
public class MyClass$InnerClassB { //静态类部类是一个全新的class,没有外部类的引用
public MyClass$InnerClassB( ){
}
public void methodB(){
}
}
3、内部类的内存泄漏问题
因为内部类和外部类的生命周期不一致,
长生命周期的内部类引用了短生命周期的外部类的引用就会导致内存泄漏。非静态内部类的对象会隐式强引用其外围对象,所以在内部类未释放时,外围对象也不会被释放,从而造成内存泄漏。
实例如下:
public class InnerClassMemLeak extend FatherF {
private String data;
//非静态内部类传入了一个外部类的引用的指针导致外部类FatherF长时间不能被jvm回收;
public class InnerClassA {
public void methodA() {
new Thread() {
@Override
public void run() {
while (true) {
//todo something;
}
}
}.start();
}
}
public static void main(String[] args) {
//内部类的对象创建分为2步:
//第一步:创建外部类的对象;
InnerClassMemLeak leakClass = new InnerClassMemLeak();
//第二步:创建内部类的对象;
InnerClassMemLeak.InnerClassA innerClassA = leakClass.new InnerClassA();
//调用内部类的方法;
innerClassA.methodA();
}
}
此时我们就要注意了,如果在一个InnerClassA里有一个长时间的任务methodA,这个thread其实也是一个局部内部类,这时候如果外部类MyClass要销毁的时候线程还没有结束,就会出现内存的泄漏;因为InnerClassA类会存在一个InnerClassMemLeak和FatherF类的强引用,使其占用内存不会被回收。
如果想避免可以使用:
-
静 态内部类-static;
-
弱引用-WeakReference;
代码实现:
public class OuterServcie extends FatherF {
private String data;
//静态内部类不会强引用外部类的指针;
public static class InnerClassA {
//弱引用不会影响父类的回收;
private final WeakReference<OuterServcie> outWeakReference;
public InnerClassA(OuterServcie outerServcie) {
this.outWeakReference = new WeakReference<OuterServcie>(outerServcie);
}
public void methodA() {
OuterServcie outerServcie = outWeakReference.get();
if (outerServcie != null) { //需要判空,outerServcie对象可能被jvm回收了;
new Thread() {
@Override
public void run() {
while (true) {
//todo something; 耗时任务
}
}
}.start();
}
}
}
public static void main(String[] args) {
//内部类的对象创建分为2步:
//第一步:创建外部类的对象;
InnerClassMemLeak leakClass = new InnerClassMemLeak();
//第二步:创建内部类的对象;
InnerClassMemLeak.InnerClassA innerClassA = leakClass.new InnerClassA();
//调用内部类的方法;
innerClassA.methodA();
}
}
4、静态内部类实现单例模式
通过静态内部类高效实现单例模式
public class SingletonDemo {
//private 防止调用默认的构造函数来创建对象
private SingletonTest(){
}
//当SingletonTest初始化的时候,静态内部类SingletonHolder并没有被加载到内存;
private static class SingletonHolder{
private static final SingletonTest singleTon = new SingletonTest();
}
//在调用内部类SingletonHolder的时候才会加载初始化singleTon这个对象;并且jvm保证只初始化一次;
public static SingletonTest getSingleton(){
return SingletonHolder.singleTon;
}
}
过程分析:
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getSingleton()方法从而触发内部类SingletonHolder的加载,此时初始化,并且 JVM 能确保 INSTANCE 只被实例化一次。
优点:
-
这种方式不仅具有延迟初始化的好处,懒加载,节约空间;
-
而且由 JVM 提供了对线程安全的支持, jvm保证单例,调用时只会被加载一次;
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。