内部类总结

内部类:一种在类内定义的类,具有很多小派别如:静态嵌套类、非静态内部类(成员类)、局部类、匿名类。

文章目录

    • 静态嵌套类
    • 非静态内部类(成员类)
    • 局部类
    • 匿名类
      • 示例
      • 对比lambda
    • 命名规则+使用方式对比表
    • 思考题
      • 为什么要有内部类呢?
      • 为什么局部类和匿名类只能访问final局部变量?
      • 为什么说使用内部类可能造成内存泄漏?

静态嵌套类

带有static修饰符的类。它不依赖于外部类,是独立的类(不持有外部类的引用)可以直接使用,注意其只能访问外部类的静态变量,故而,个人不将其判定为成员类。
示例:
外部类Outer+静态嵌套类StaticInner

public class Outer {
     
    private static final int staticFinalInt = 0;
    private static int staticInt = staticFinalInt + 1;
    private int var;

    public static void main(String[] args) {
     

    }

    static class StaticInner extends Object {
     
        void print() {
     
            System.out.println(staticInt);
        }
    }
}

使用javac编译后得到两个class文件: Outer.class+Outer$StaticInner.class

public class Outer {
     
    private static final int staticFinalInt = 0;
    private static int staticInt = 1;
    private int var;

	// 编译自动生成的构造函数
    public Outer() {
     
    }

    public static void main(String[] var0) {
     
    }
}
class Outer$StaticInner {
     
    // 编译自动生成的构造函数
    Outer$StaticInner() {
     
    }

    void print() {
     
        // 直接访问外部类的staticInt变量  编译后会如下加入外部类名字
        System.out.println(Outer.staticInt);
    }
}

使用javac编译后得到两个class文件: Outer.class+Outer$StaticInner.class

非静态内部类(成员类)

个人理解为定义在类中(不包含块,方法),依赖于外部类,就像非静态成员变量一样,声明时需要有外部类实例(持有外部类的引用)。且可以被外部类不能有的多种修饰符(protected和private)修饰。
示例:
外部类Outer+非静态内部类(成员类)GlobalInner

public class Outer {
     
    private static final int staticFinalInt = 0;
    private static int staticInt = staticFinalInt + 1;
    private int var;

    public static void main(String[] args) {
     

    }

     class GlobalInner extends Object {
     
        void print() {
     
            System.out.println(var);
            System.out.println(staticInt);
        }
    }
}

经javac编译得到Outer$GlobalInner.class,外部类Outer的class文件如上。

class Outer$GlobalInner {
     
    Outer$GlobalInner(Outer var1) {
     
        this.this$0 = var1;
    }

    void print() {
     
        System.out.println(this.this$0.var);
        System.out.println(Outer.staticInt);
    }
}

继承成员类示例:

class CC extends Outer.GlobalInner {
     
        public CC(Outer outer) {
     
            outer.super();
        }
    }

因为成员类依赖于外部类,所以继承成员类一定要声明构造函数,且传入外部类实例,调用成员类构造方法。

局部类

定义在方法或块里面的类。由于是局部作用域,所以不能使用static等修饰符修饰。
示例:
外部类Outer+局部类LocalInner

public class Outer {
     
    private static final int staticFinalInt = 0;
    private static int staticInt = staticFinalInt + 1;
    private int var;

    public static void main(String[] args) {
     
        int partVar = 0;
        class LocalInner {
     
            {
     
                System.out.println(partVar);
            }
        }
    }
}

经javac编译得到Outer$GlobalInner.class,外部类Outer的class文件如上。

class Outer$1LocalInner {
     
    Outer$1LocalInner(int var1) {
     
        this.val$partVar = var1;
        System.out.println(this.val$partVar);
    }
}

突然想到一个问题,这个类名中的数字序号是和啥有关系呢?
做个小实验,如下:

public class Outer {
     
    private static final int staticFinalInt = 0;
    private static int staticInt = staticFinalInt + 1;
    private int var;

    void printt() {
     
        int var = 0;
        class LocalInner {
     
            {
     
                System.out.println(var);
                System.out.println(staticFinalInt);
            }
        }
        LocalInner localInner = new LocalInner();
    }

    public static void main(String[] args) {
     
        int partVar = 0;
        class LocalInner {
     
            {
     
                System.out.println(partVar);
            }
        }
    }

    void print() {
     
        class LocalInner {
     
            {
     
                System.out.println(var);
            }
        }
    }
	void printAA() {
     
        class AALocalInner {
     
            {
     
                System.out.println(var);
            }
        }
	    }
	}

经javac得四个局部类class文件,分别是Outer$1AALocalInner.class,Outer$1LocalInner.class,Outer$2LocalInner.class,Outer$3LocalInner.class。根据名字我们可以发现,序号只是在类名重复时递增,如果类名不重复,序号永远是1。
那重名类之间的序号是怎样递增呢?接下来看看具体的class文件:

class Outer$1LocalInner {
     
    Outer$1LocalInner(Outer var1, int var2) {
     
        this.this$0 = var1;
        // 这个是赋局部变量var值
        this.val$var = var2;
        System.out.println(this.val$var);
        // final变量staticFinalInt 直接替换成0
        System.out.println(0);
    }
}
class Outer$2LocalInner {
     
    Outer$2LocalInner(int var1) {
     
        // 这个是赋局部变量partVar值
        this.val$partVar = var1;
        System.out.println(this.val$partVar);
    }
}
class Outer$3LocalInner {
     
    Outer$3LocalInner(Outer var1) {
     
        this.this$0 = var1;
        System.out.println(this.this$0.var);
    }
}

根据上面的变量赋值过程,我们可以得知局部类的非final变量是靠构造函数传递的,final变量是直接替换成值的。而数字序号可以很明显发现,和定义局部类的顺序有关,越靠前则序号越小。

匿名类

顾名思义,是一种没有名字的内部类,所以匿名类是没有构造器。同局部类一样,是不可以用static等修饰符修饰。一般直接new 类名/接口名 来定义一个匿名类,匿名类只是重写或实现一些方法,而不能自定义一些方法。

示例

外部类Outer+匿名类

public class Outer {
     
    private static final int staticFinalInt = 0;
    private static int staticInt = staticFinalInt + 1;
    private int var;

    void printt() {
     
        int var = 0;
        new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(var);
            }
        };
    }

    public static void main(String[] args) {
     
        int partVar = 0;
        new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(partVar);
            }
        };
    }

    void print() {
     
        new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(var);
            }
        };
    }
}

经javac编译得3个匿名类class文件,Outer$1.class,Outer$2.class,Outer$3.class。

class Outer$1 implements Runnable {
     
    Outer$1(Outer var1, int var2) {
     
        this.this$0 = var1;
        // 这个是赋局部变量var值
        this.val$var = var2;
    }

    public void run() {
     
        System.out.println(this.val$var);
    }
}
class Outer$2 implements Runnable {
     
    Outer$2(int var1) {
     
        // 这个是赋局部变量partVar值
        this.val$partVar = var1;
    }

    public void run() {
     
        System.out.println(this.val$partVar);
    }
}
class Outer$3 implements Runnable {
     
    Outer$3(Outer var1) {
     
        this.this$0 = var1;
    }

    public void run() {
     
        System.out.println(this.this$0.var);
    }
}

对比lambda

初学java的时候,就觉得lambda表达式与匿名类长得很像,还以为他们是亲戚。其实,他们是完完全全的两个东西。匿名类,虽然没有名字,但是编译器编译的时候会给他一个名字,格式:{外部类类名$数字序号},总而言之,它依旧是一个类。而lambda表达式,它用于简化代码,还没有成为一个类。
示例:
对比匿名类实现Runnable的run方法

new Thread(()->{
     
    System.out.println("实现runnable");
}); 

new Thread(new Runnable() {
     
    @Override
    public void run() {
     
        System.out.println("实现runnable");
    }
});

可以看出来使用lambda表达式确实更简洁。

命名规则+使用方式对比表

类型 类名格式 使用方式
静态嵌套类 外部类类名 $ 静态类类名 Outer.StaticInner staticInner = new Outer.StaticInner()
非静态内部类(成员类) 外部类类名 $ 成员类类名 Outer.GlobalInner globalInner = new Outer().new GlobalInner()
局部类 外部类类名$数字序号局部类类名 LocalInner localInner = new LocalInner();注意只能在定义类的作用域内使用局部类。
匿名类 外部类$数字序号 在定义处,则使用。如:Runnable runnable = new Runnable() {@Override public void run() {System.out.println(var);} }

思考题

为什么要有内部类呢?

1.通过改变类的修饰符或者定义内部类的位置,来控制类的访问范围。

2.解决类的单继承问题。内部类可以访问外部类的成员变量和方法。

3.匿名类使得代码更简单。

为什么局部类和匿名类只能访问final局部变量?

1.当访问非final变量,则以构造器的入参传递给局部类/匿名类;当使用final变量,则直接以值替换变量;
2.简单解释:由于局部类和匿名类的作用域限制,比如一个方法结束了,但是局部类和匿名类还是存在的,他们还需要正常运转,也就意味里面的变量就要能使用,如果变量可改变,就不知道什么时候改变,所以只能访问final变量。
3.java8其实已经不限定只能使用final变量了,只要不对局部变量赋值操作,也能使用。

为什么说使用内部类可能造成内存泄漏?

这是因为非静态内部类始终持有外部类的引用,一旦非静态内部类被其他类使用时,相当于外部类的指针还存在,即便我们没有自己声明使用外部类,外部类的引用依旧被持有,自然可能造成内存泄漏。

你可能感兴趣的:(java,java,class,类)