java内部类详解

目录

什么是内部类?

为什么要使用内部类

1内部类基础

1.1成员内部类

1.2 静态内部类

1.3 局部内部类

1.4 匿名内部类

2. 内部类进阶

2.1 内部类持有的外部类引用

2.2 匿名内部类中为什么只能访问final变量


什么是内部类?

Java中的内部类就是定义在类里面的类。

为什么要使用内部类

简单来说,内部类最大的优点就是可以解决多重继承的问题。也就是每个内部类可以独立的继承一个接口的实现,而不用去受外部类继承的实现的制约。

1内部类基础

内部类一般分为四类

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

1.1成员内部类

成员内部类就是最普通的“定义在一个类里面的”类。

class Circle{
    private double radius = 0;
    public Circle(double radius){
        this.radius = radius;
    }
    //Draw就是一个成员内部类
    class Draw{
        public void drawSahpe() {
            //成员内部类可以访问外部类的所有成员属性和成员方法,无论private或static
            System.out.println(radius);
        }
    }
}

使用成员内部类时的注意事项

  1. 成员内部类中不能有静态成员属性和方法,然而,如果有一个变量是基本数据类型或String类型且用static final修饰时,编译可以通过

    • 不能有静态成员属性和方法的原因是,非静态成员内部类要依赖外部类的存在而存在
    • 可以有static final的基本数据类型或String类型成员变量是因为,这些变量是放在常量池中的,不涉及类的加载问题
  2. 成员内部类可以访问外部类的所有成员属性和成员方法,无论private或static

  3. 外部类也可以访问成员内部类的所有成员属性和成员方法,但要先创建一个成员内部类的对象,再通过这个对象引用访问内部类的成员

  4. 成员内部类和外部类的成员变量或方法发生冲突时,外部类的成员会被隐藏。如果此时要在内部类中访问外部类的成员,可以通过以下方式访问

    • 外部类.this.成员变量
    • 外部类.this.成员方法
  5. 内部类可以修饰访问权限

    • private,只能在外部类中访问
    • protected,只能在同一个包下或者继承了外部类的类中访问
    • public,任何地方都可以访问
    • 默认在同一个包下访问

1.2 静态内部类

静态内部类和成员内部类相似,只是有static修饰符。

class OuterClass{
    private int a = 1;
    static private int b = 0;
    public class static InnerClass{
        //这一句编译出错
        System.out.println(a);
        //这一句正确
        System.out.println(b);
    }
}

使用静态内部类时的注意事项

  1. 静态内部类中允许有static成员变量和方法,而非静态内部类中不允许定义static成员变量和方法,除非是static final的基本数据类型或String

  2. 静态类不能使用任何外部类中不是static的成员变量和方法。因为Java中可以在不创建外部类对象的情况下创建静态内部类的对象,而外部类的非static成员变量和方法是依附于外部类对象存在的,这二者相互矛盾

  3. 静态内部类和非静态内部类的最大差别在于,非静态内部类在编译完成后始终持有一个指向创建他的外部类对象的引用,而静态内部类没有也不需要这个引用。因此,静态内部类对象的创建不需要依附于外部类

1.3 局部内部类

局部内部类是定义在一个方法或作用域中的类,局部内部类的访问权限仅在该方法或作用域内。

class OuterClass{
    public void function(){
        class InnerClass{
            public void print(){
                System.out.println("This is local inner class!");
            }
        }
    }
}

使用局部内部类时的注意事项

  1. 局部内部类的地位类似于外部方法的局部变量,因此只要出了这个方法就无法访问到局部内部类了

  2. 局部内部类不能使用访问修饰符,包括private、protected、public、static,根本原因就在于第1条——出了方法就无法访问,那么访问修饰符就没有意义了

  3. 可以访问外部类的成员变量,如果局部内部类被定义在static方法中,那么就只能访问外部类的static变量

  4. 可以使用final或abstract修饰

1.4 匿名内部类

匿名内部类就是没有名字的类,主要优点在于编写方便,但是有个前提,内部类必须继承或者实现一个外部类或者接口

public class OuterClass {
    public InnerClass getInnerClass(final int num,String str2){
        return new InnerClass(){
            int number = num + 3;
            public int getNumber(){
                return number;
            }
        };
    }
    public static void main(String[] args) {
        OuterClass out = new OuterClass();
        InnerClass inner = out.getInnerClass(2, "chenssy");
        System.out.println(inner.getNumber());
    }
}
//此处要有接口的声明
interface InnerClass {
    int getNumber();
}

使用匿名内部类时的注意事项

  1. 匿名内部类不能用修饰符,都没地方写

  2. new一个匿名内部类时必须有这个类或者接口的声明

  3. 匿名内部类没有构造方法

  4. 当所在方法的形参需要被在匿名内部类中使用时,这个形参必须为final,此处的final是指在匿名内部类中不能修改这个使用的变量的值,虽然在Java8中不需要声明为final了,但在匿名内部类中仍然是不能修改的。究其根本,Java对于匿名内部类传递变量的实现是基于构造器传参的,如果允许在匿名内部类中修改值,那么实际上修改的只是这个变量的副本而已,实际上并没有对外部类的这个变量产生什么影响,这就会对程序产生影响——修改了,但没完全修改

2. 内部类进阶

2.1 内部类持有的外部类引用

public class Demo {
    public class DemoRunnable implements Runnable() {
        public void run() {
        }
    }
}

编译后生成了两个字节码文件

  • Demo.class
  • Demo$DemoRunnable.class
    其中Demo$Runnable.class文件就是内部类编译出的字节码文件,内部类编译后的文件命名为外部类名$内部类名,此时反编译后的代码如下
package inner

public class Demo$Runnable implements Runnable() {
        public Demo$Runnable (Demo var1)) {
            this.this$0 = var1;
        }
        public void run() {
        }
    }

也就是说外部类对象的引用是通过作为内部类的构造函数的参数传进内部类对象的

2.2 匿名内部类中为什么只能访问final变量

我们以三种情况,分类查看反编译后内部类变量的字节码

  • 非final局部变量
  • final局部变量
  • 外部类成员变量

非final局部变量

public void run() {
    int age = 10;
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            int myAge = age + 1;
            System.out.println(myAge);
        }
    };
}

此时反编译后的内部类变量字节码为

package inner;

public class Demo$Runnable implements Runnable() {
    public Demo$Runnable (Demo var1, int var2)) {
        this.this$0 = var1;
        this.this$age = var2;
    }
    public void run() {
        int var1 = this.val$age + 1;
        System.out.println(var1);
    }
}

可以发现非final变量是以形参方式传入

final局部变量

public void run() {
    final int age = 10;
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            int myAge = age + 1;
            System.out.println(myAge);
        }
    };
}

此时反编译后的内部类变量字节码为

package inner;

public class Demo$Runnable implements Runnable() {
    public Demo$Runnable (Demo var1)) {
        this.this$0 = var1;
    }
    public void run() {
        byte var1 = 11;
        System.out.println(var1);
    }
}

可以发现final变量在编译期间就被直接优化成一个byte值了,而当这个变量无法在编译期间确定时,会和非final变量一样作为形参传入

外部类变量

public class Demo {
    int age = 10;
    public void run() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int myAge = age + 1;
                System.out.println(myAge);
                age = 20;
            }
        };
    }
}

此时反编译后的内部类变量字节码为

package Demo;

public class Demo$1 implements Runnable() {
    public Demo$1(Demo var1)) {
        this.this$0 = var1;
    }
    public void run() {
        int var1 = this.this$0.age + 1;
        System.out.println(var1);
        this.this$0.age = 20;
    }
}

可以发现此时是通过外部类的引用来操作外部类变量

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