java内部类的相关知识点

Java的内部类

1. 内部类的介绍

依据《Java核心技术》中所说,内部类即是一个定义在另一个类中的类,使用内部类的主要原因在于:

  • 内部类的方法可以访问该类定义所在的作用域中的数据,包括私有的数据
  • 内部类可以对同一个包中的其他类进行隐藏起来
  • 如果我们想要定义一个回调函数,而又不想编写大量的代码,那么我们可以使用匿名内部类比较便捷

我们首先来看一个简单的内部类的例子

package com.feng;

public class OuterClass {
    private int age = 10;
    private String name = "feng";
    private boolean flag = true;

    public static void main(String[] args) {
        
    }


    public  class InnerClass {

        //可以定义构造函数
        public InnerClass() {

        }
        
        //定义非静态方法
        public void func2() {
            if (flag) {
                System.out.println("内部类的非静态方法");
            }
        }
    }
}

通过上面的代码我们可以看出:

  • 内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域,即我们在InnerClass中使用到了OuterClass的成员变量

  • 内部类中的对象总是存在一个隐式引用,它指向了创建它的外部类的对象,其中是编译器修改了所有的内部类的构造器,添加了外部类引用的参数

  • 其中我们的内部类可以定义为私有的,这样只有我们的外部类才能访问私有的内部类,但是也只有内部类可以是私有类,我们的常规类只可以是包可见性或者公有可见性

2. 内部类特殊语法规则

在我们的外部类中,可以使用以下方式来创建一个内部类的实例:

public static void main(String[] args) {
    OuterClass outerClass = new OuterClass();  //先创建一个外部类的对象实例
    InnerClass innerClass = outerClass.new InnerClass(); //使用外部类引用.new 内部类();
}
场景题:我们在内部类中声明的所有静态域都必须是final类型,为什么?

原因很简单,因为一个静态域只会存在一个实例,但是对于每一个外部对象而言,会分别有一个独立的内部类的实例,此时如果这个域不是final类型的,就不能保证它的唯一性

内部类是否可用和安全

首先我们说内部类是一种编译器现象,与我们的虚拟机无关。编译器将会把内部类翻译成**$(美元符号)**分隔外部类名和内部类名的常规类文件,而虚拟机对此是一无所知的。

3. 局部内部类

局部内部类不能使用public或者private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

局部内部类的优势在于:可以对外部世界完全的隐藏起来,同时,局部内部类不仅能够访问包含他们的外部类,还可以访问局部变量。不过这些局部变量事实上为final类型的。他们一旦赋值将不会被改变。

public class OuterClass {
    private static int a;
    private int b;

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        outerClass.partTest(5);
    }

    public void partTest(final int c) {
        final int d = 1;
        class PartClass {
            public void print() {
                System.out.println(c);
            }
        }
        System.out.println(d);
        PartClass partClass = new PartClass();
        partClass.print();
    }
}

输出:

1
5
JDK8之前必须将局部类访问的局部变量声明为final,为什么?

见下面的回答,答案是一致的!!!

4. 成员内部类

成员内部类,一方面作为外部类的成员:

  • 可以调用外部类中的结构
  • 可以被static修饰,即下面的静态内部类
  • 可以被四种不同的权限修饰符进行修饰(private,public,protected,默认default)

另一方面,它作为一个类:

  • 可以定义属性,方法,构造器等
  • 可以被final修饰,表示此类不能被继承
  • 可以被abstract修饰
public class OuterClass {
    private int a = 10;
    private static int b = 20;
    private FieldInnerClass fieldInnerClass = new FieldInnerClass();

    public static void main(String[] args) {
        System.out.println(FieldInnerClass.b);
        OuterClass outerClass = new OuterClass();
        //利用外部类的对象进行内部类实例的创建
        FieldInnerClass fieldClass = outerClass.new FieldInnerClass();

    }

    public class FieldInnerClass {
        private int a = 1;
        private final static int b = 30;  //作为常量存在了...

        //可以定义构造函数
        public FieldInnerClass() {

        }

        //定义非静态方法
        public void func2() {
            System.out.println("静态内部类的非静态方法");
        }
    }
}

注意:

  • 在成员内部类中不能定义静态方法和静态变量,其中final修饰的除外,即就是上面我们的结论:成员内部类中声明的所有静态域都必须是final类型
  • 成员内部类必须通过外部类的实例对象进行内部类对象的实例化

5. 静态内部类

当我们向将一个类隐藏在另一个类的内部,并不需要内部类引用外围对象,对此我们可以将该内部类声明为static

其中:

  • 静态内部类只能访问外部类的静态变量和静态方法,在静态内部类内部可以定义静态变量,方法以及构造函数等等
  • 与常规的内部类不同的是,静态内部类中可以有静态域和静态方法
  • 我们可以采取自己new的方式进行静态内部类的对象的创建,同样可以使用外部类.静态内部类的方式调用静态方法,但是不能调用非静态方法
  • 声明在接口中内部类会自动成为static和public类
public class OuterClass {
    private int a = 10;
    private static int b = 20;

    public static class StaticInnerClass {
        private static int b = 30;

        //可以定义构造函数
        public StaticInnerClass() {

        }

        //定义静态方法
        public static void func1() {
            System.out.println("静态内部类的静态方法");
        }

        //定义非静态方法
        public void func2() {
            System.out.println("静态内部类的非静态方法");
        }
    }
}
public static void main(String[] args) {
    //创建静态内部类的第一种方式:自己new
    StaticInnerClass class1 = new StaticInnerClass();
    class1.func2();
    class1.func1();
}
public static void main(String[] args) {
    //采用外部类.内部类
    OuterClass.StaticInnerClass.func1();
    OuterClass.StaticInnerClass.func2();   //编译不通过
}

6. 匿名内部类

匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写

但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String run() {
        return "Person类的run()方法执行...";
    }
}
public class OuterClass {

    public void test(Person person) {
        System.out.println(person.getName() + ":" + person.getAge() + "," + person.run());
    }


    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        outerClass.test(new Person("feng",20) {
            @Override
            public String run() {
                return "匿名方法执行...";
            }
        });
    }
}

其中,关于匿名内部类:

  • 匿名内部类可以访问外部类的所有成员

  • 匿名内部类不能访问外部类未加final修饰的局部变量(注意:JDK1.8即使没有用final修饰也可以访问

  • 属性屏蔽,与内嵌类相同,匿名内部类定义的类型(如变量)会屏蔽其作用域范围内的其他同名类型(变量)

  • 匿名内部类可以有常量属性(final修饰的属性)

  • 匿名内部类内部可以定义属性

  • 匿名内部类内部可以定义额外的方法(父接口,父类中没有的方法)

  • 匿名内部类中可以继续定义内部类

  • 匿名内部类中可以对其他类进行实例化

  • 匿名内部类没有类名,不能在内部有构造器

JDK8之前,匿名内部类不能访问外部类未加final修饰的局部变量,为什么?

通过final来保证数据的一致性

底层会将我们的局部变量拷贝一份,并将其传入我们的匿名内部类中,并且以匿名内部类的成员变量的形式存在,这个值的传递过程是通过我们的匿名内部类的构造器来完成的,那么此时就是存在一定的问题!!!如果此时外部的局部变量发生了变化,我们的匿名内部类是不知道的,因为它只是拷贝了局部变量的值而已,此时就会造成数据的不一致性,故我们通过加final来保证数据的一致性

在JDK8以后,如果我们不加,也是可以访问的,其实在底层它还是为我们主动加上了final关键字

7. Java内部持有外部类的引用分析

Java中我们的内部类一般分为成员内部类和匿名内部类,他们的对象都会持有外部对象的引用,从而影响外部类对象的回收。因为我们的GC垃圾回收器回收没有被引用或者根集合不可达的对象,此时内部类在生命周期内始终持有外部类的对象的引用,就会造成外部类的对象始终不满足GC的回收条件,从而造成了内存泄露问题。

我们通常的解决方式是:

  • 将内部类定义为static
  • 使用static静态变量引用匿名内部类的实例

静态内部类实例,static引用的匿名内部类的实例没有引用外部类的实例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
匿名内部类是指在声明一个类的同时实例化它,而不需要为该类命名。在Java中,匿名内部类通常用于实现接口或继承抽象类。 下面是一些关于匿名内部类知识点: 1. 声明匿名内部类:可以在创建对象的同时定义匿名内部类,使用new关键字后紧跟接口或抽象类的定义,并重写其中的方法。例如: ```java InterfaceName obj = new InterfaceName() { // 匿名内部类的实现 // 重写接口中的方法 }; ``` 2. 实现接口:匿名内部类常用于实现接口,可以直接在创建对象时实现接口的方法,省去了单独创建一个实现类的步骤。 ```java Runnable runnable = new Runnable() { @Override public void run() { // 实现Runnable接口的run方法 } }; ``` 3. 继承抽象类:匿名内部类也可以继承抽象类,并重写其中的抽象方法。 ```java AbstractClass obj = new AbstractClass() { @Override public void method() { // 实现抽象类中的方法 } }; ``` 4. 访问外部变量:匿名内部类可以访问外部类中的成员变量,但需要将其声明为final或实际上是final的(Java 8之后,如果变量未被后续代码修改,可以不显式声明为final)。 ```java int num = 10; InterfaceName obj = new InterfaceName() { @Override public void method() { System.out.println(num); // 访问外部变量 } }; ``` 需要注意的是,匿名内部类没有名称,因此无法通过名称直接创建多个对象。每次使用都需要重新定义匿名内部类。匿名内部类通常用于简单的场景,如果需要复杂的逻辑或多次使用,建议单独定义一个具名的内部类

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值