Java中的内部类,看这一篇就够了

欢迎跳转到本文原文链接 Backend_Notes


内部类(inner class)是定义在另一个类中的类,需要使用内部类的原因有三个:

  • 内部类方法可以访问该类定义所在作用域中的数据,包括私有的数据
  • 内部类可以对同一个包中的其他类隐藏起来
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷

使用内部类访问对象状态

我们可以看以下的例子:

public class InnerClassTest2 {
    public static void main(String[] args) {
        Student student = new Student("Alice", 18);
        student.test();
    }
}

class Student {
    private String name;
    private int age;

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

    public void test() {
        InnerClass innerClass = new InnerClass();
        innerClass.print();
    }

    public class InnerClass {
        public void print() {
            System.out.println("name: " + name + ", age: " + age);
        }
    }
}

// output
// name: Alice, age: 18

观察上述代码的输出结果可知,内部类可以访问该类定义所在作用域中的数据,包括私有数据。内部类的对象有一个隐式引用,它引用了实例化该内部对象的外围类对,通过这个指针,可以访问外围类对象的全部状态,但是 static 内部类没有这种附加指针。

innerClass

外围类的引用在内部类的定义中是不可见的,我们可以将这个引用称为 outer,外围类的引用在构造器中设置,编译期修改了所有内部类的构造器,添加了一个外围类引用的构造函数,若没有定义该构造器,则编译器会为这个类生成一个默认的构造器,代码如下:

public class InnerClass {
    // automatically gengerated code
    public InnerClass(Student student) {
        outer = student;
    }
    
    public void print() {
        System.out.println("name: " + name + ", age: " + age);
    }
}

当我们调用 student.test() 方法时,编译器会将 this 引用传递给当前的语音时钟构造器。

InnerClass innerClass = new InnerClass(this); // parameter automatically added

注意:
InnerClass 可以声明为私有的,这样只有 Student 的方法可以构造 InnerClass 对象,只有内部类可以是私有类,其他常规类只可以是包可见性或公有可见性。

内部类的特殊语法规则

上文提到内部类有一个外围类的引用 outer,使用该引用的表达式为: OuterClass.this,上述代码我们可以改为:

public class InnerClass {
    public void print() {
        System.out.println("name: " + Student.this.name + ", age: " + Student.this.age);
    }
}

反过来我们也可以使用如下语法来更明确地编写内部对象的构造器:

outerObject.new InnerClass(construction parameters)

public void test() {
    InnerClass innerClass = this.new InnerClass();
    innerClass.print();
}

若内部类 Innerclass 是公有的,此时在外围类的作用域外可以这样创建内部类,在外围类的作用域之外,可以这样引用内部类: OuterClass.InnerClass

Student.InnerClass inner = student.new InnerClass();

注意:内部类不能有静态变量和静态方法。

局部内部类

我们也可以使用局部内部类,代码如下:

public class InnerClassTest3 {
    public static void main(String[] args) {
       Person p = new Person("Alice", 12);
       p.test();
    }
}

class Person {
    private String name;
    private int age;

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

    public void test() {
        class InnerClass {
            public void print() {
                System.out.println("name: " + name + ", age: " + age);
                System.out.println("name: " + Person.this.name + ", age: " + Person.this.age);
            }
        }

        InnerClass innerClass = new InnerClass();
        innerClass.print();
    }
}

// output
// name: Alice, age: 12
// name: Alice, age: 12

局部类不能使用 public 或 private 访问说明符进行声明,它的作用域被限定在声明这个局部类的块中,局部类有一个优势:即对外部世界可以完全隐藏起来,即时是 Person 类中的其他代码也不能访问它。除了 test 方法,没有任何方法知道它的存在

由外部方法访问变量

局部类不仅能够访问他们的外部类,还可以访问局部变量,不过这些局部变量必须事实上为 final, 这说明它们一旦被赋值就不会被改变,来看代码示例:

public class InnerClassTest4 {
    public static void main(String[] args) {
       Person2 p = new Person2("Alice", 12);
       p.test("hello");
    }
}

class Person2 {
    private String name;
    private int age;

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

    public void test(String parameter) {
        String world = "world";
        class InnerClass {
            public void print() {
                System.out.println(parameter);
                System.out.println(world);
            }
        }

        InnerClass innerClass = new InnerClass();
        innerClass.print();
    }
}

// output
// hello
// world

注意:

  • 为了使局部内部类中的方法可以正常使用,parameter 会使用 test 方法的局部域进行备份
  • 在 JDK8 之前,必须把局部类访问的局部变量声明为 final,JDK8 之后不用,但是通过反编译可以发现,编译器自动给变量加上了 final

匿名内部类

将局部内部类再进一步,此时我们可以使用匿名内部类,通用的语法格式为:

new SuperType(construction parameters) {
    // inner class methods and data
}

SuperType 可以是接口,那么内部类就实现这个接口, 也可以是类,那么内部类就要扩展(继承)它。由于构造器的名字必须和类名相同,而匿名类没有类名,所以,匿名类没有构造器,只能使用超类的构造器,尤其当内部类实现接口的时候不能有任何构造参数。

匿名类一般用在事件监听器与其他回调,如:

class TalkingClock
{
   /**
    * Starts the clock.
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public void start(int interval, boolean beep)
   {
      ActionListener listener = new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               System.out.println("At the tone, the time is " + new Date());
               if (beep) Toolkit.getDefaultToolkit().beep();
            }
         };
      Timer t = new Timer(interval, listener);
      t.start();
   }
}

JDK8 后,我们更多地使用 lambda 表达式,上述代码可以转化为:

class TalkingClock
{
   /**
    * Starts the clock.
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public void start(int interval, boolean beep)
   {
      Timer t = new Timer(interval, event -> {
         System.out.println("At the tone, the time is " + new Date());
         if (beep) Toolkit.getDefaultToolkit().beep();
      });
      t.start();
   }
}

双括号初始化(double brace initialization)

利用内部类可以实现集合的初始化,一般初始化集合时会使用如下代码:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
test(list);

如果集合以后不会用到,我们可以使用匿名列表:

print(new ArrayList<Integer>(){{
    add(1);
    add(2);
    add(3);
}});

注意这里用到了双括号,外层括号表示是建立了一个 ArrayList 的匿名类,里面一层括号为对象构造块,进行初始化。

静态内部类

有时候只是想要把一个类隐藏在一个类中,不需要内部类引用外部对象,此时可以使用静态内部类,取消外部对象的引用。

public class StaticInnerClassTest2 {
    public static void main(String[] args) {
        double[] nums = {1.1, 2.2, 3.3, 4.4, 5.5};
        ArrayAlgo.Pair p = ArrayAlgo.minmax(nums);
        System.out.println("Max: " + p.getSecond() + ", Min: " + p.getFirst());
    }

}

class ArrayAlgo {
    public static class Pair {
        private double first;
        private double second;

        public Pair(double first, double second) {
            this.first = first;
            this.second = second;
        }

        public double getFirst() {
            return first;
        }

        public double getSecond() {
            return second;
        }
    }

    public static Pair minmax(double[] values) {
        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;
        for (double v : values)
        {
            if (min > v) min = v;
            if (max < v) max = v;
        }
        return new Pair(min, max);
    }
}

// output
// Max: 5.5, Min: 1.1

注意:

  • 与常规内部类不同,静态内部类可以有静态域和静态方法
  • 声明在接口中的内部类自动成为 static 和 public

Reference

1.Java核心技术卷一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值