Java中的几种内部类

在看Java核心技术卷I 6.4小节内部类时有些混乱,故整理些笔记方便查看。

什么是内部类?
将一个类的定义放在另一个类的定义内部,这就是内部类。

为什么要使用内部类?
1、内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据,所以与常规类比较起来功能更加强大。
2、内部类可以对同一个包中的其他类隐藏起来。
3、当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。

Java中的几种内部类:
成员内部类:作为外部类的一个成员存在,与外部类的属性、方法并列。当某个类除了他的外部类,不会被其他类使用时应该选择使用成员内部类。

package com.ww.innerclass;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

/**
 * 成员内部类:
 * (1)可以有静态最终的变量,不可以有静态方法;
 * (2)可以使用public、protected、private进行修饰。
 * <p>
 * 优点:
 * 1、内部类可以访问外围类的私有数据,跟常规类比较起来功能更加强大。
 * 2、用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
 *
 * @author: Sun
 * @create: 2020-02-25 14:16
 * @version: v1.0
 */
public class MemberInnerClass {

    private int interval;
    private boolean beep;

    public MemberInnerClass(int interval, boolean beep) {
        this.interval = interval;
        this.beep = beep;
    }

    public void start() {
        MemberInnerClass.TimePrinter timePrinter = this.new TimePrinter();

        Timer timer = new Timer(interval, timePrinter);
        timer.start();
    }

    // 成员内部类
    public class TimePrinter implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("At the one, the time is " + new Date());

            // 内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域 (使用外围类引用的正规语法 OuterClass.this)
            if (beep) {
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }
}

局部内部类:局部内部类定义在外部类的某个代码块或方法块中。如果只会在某个方法或块中创建这个类的对象,就可以使用局部内部类。

package com.ww.innerclass;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

/**
 * 局部内部类:
 * (1)可以有静态最终的变量,不可以有静态方法;
 * (2)不能用public、protected、private访问说明符进行声明。
 * <p>
 * 优点:
 * 1、局部内部类有一个优势,即对外部世界可以完全地隐藏起来。除了在声明局部内部类的块中知道该类的存在,其他任何地方都不知道该类的存在。
 * 通过内部类和接口可以达到强制的弱耦合,用局部内部类来实现接口,并在方法中返回接口类型,使局部内部类不可见,屏蔽实现类的可见性。
 *
 * @author: Sun
 * @create: 2020-02-26 11:44
 * @version: v1.0
 */
public class LocalInnerClass {

    public void start(int interval, boolean beep) {
        // 局部内部类
        class TimePrinter implements ActionListener {
        
            @Override
            public void actionPerformed(ActionEvent event) {
                System.out.println("At the one, the time is " + new Date());
                /**
                 * 编译器必须检测对局部变量的访问,为每一个变量在局部内部类中建立相应的数据域,
                 * 并将局部变量拷贝到构造器中,以便将这些内部类中的数据域初始化为局部变量的副本。
                 */
                if (beep) {
                    Toolkit.getDefaultToolkit().beep();
                }
            }
        }

        // 调用内部类TimePrinter无参构造器
        TimePrinter listener = new TimePrinter();
        Timer timer = new Timer(interval, listener);
        timer.start();
    }
}

匿名内部类:匿名内部类一般定义在需要传递接口或回调的的地方,一个匿名内部类一定是在new的后面,用其隐含实现一个接口或继承一个类。假如只需要创建这个类的一个对象不需要知道其实际类型(不需要使用到类名),那么就可以使用匿名内部类。

package com.ww.innerclass;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Date;

/**
 * 匿名内部类:
 * (1)由于构造器的名字必须与类名相同,而匿名类没有类名,所以匿名类不能有构造器;
 * 取而代之的是:匿名内部类在继承类的时候将构造器参数传递给超类(superclass)构造器;在实现接口的时候,不能有任何构造参数。
 * 如果构造方法的闭小括号后面跟一个开大括号,那么就是在定义了一个匿名内部类。(无论new后面跟的是类还是接口。如果是类,内部类就要扩展它;如果是接口,内部类就要实现它);
 * (2)可以有静态最终的变量,不可以有静态方法。
 * <p>
 * 优点:
 * 1、在编写事件监听或回调的代码时匿名内部类不但方便,而且使代码更加容易维护
 * 
 * @author: Sun
 * @create: 2020-02-26 13:55
 * @version: v1.0
 */
public class AnonymousInnerClass {

    public void start(int interval, boolean beep) {
        // 匿名内部类
        ActionListener listener = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent event) {
                System.out.println("At the one, the time is " + new Date());
                if (beep) {
                    Toolkit.getDefaultToolkit().beep();
                }
            }

        };
        
        Timer timer = new Timer(interval, listener);
        timer.start();
    }

    public static void main(String[] args) {
        /**
         * 双括号初始化:实际上是利用了内部类语法
         * 外层大括号建立了ArrayList的一个匿名子类
         * 内层大括号是一个对象构造块
         */
        ArrayList<Integer> integers = new ArrayList<Integer>() {{
            add(1);
            add(2);
            add(3);
        }};
    }
}

静态内部类:和成员内部类一样,作为外部类的一个成员存在,与外部类的属性、方法并列,只不过在声明类的时候加入了static关键字。有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。这时可以使用静态内部类,以便取消产生对外部类的引用。

package com.ww.innerclass;

import java.util.ArrayList;

/**
 * 静态内部类:
 * (1)静态内部类可以有静态域和方法;
 * (2)可以使用public、protected、private进行修饰。
 * <p>
 * 优点:
 * 1、在没有外部类对象的情况下,可以创建静态内部类的对象。
 *
 * @author: Sun
 * @create: 2020-02-26 14:30
 * @version: v1.0
 */
public class StaticInnerClass {

    // 静态内部类
    public static class Pair {

        private double first;
        private double second;

        public Pair(double f, double s) {
            first = f;
            second = s;
        }

        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);
    }

    // 如果不使用内部类Pair,那么需要调用两次同一个方法进行两次数组循环才能得到数组中最大和最小的两个数字。
    public static void main(String[] args) {
        double[] d = new double[20];
        for (int i = 0; i < d.length; i++)
            d[i] = 100 * Math.random();
        StaticInnerClass.Pair p = StaticInnerClass.minmax(d);
        System.out.println("min = " + p.getFirst());
        System.out.println("max = " + p.getSecond());
    }
}

几个小疑问:
为什么内部类中不可以有静态方法?
因为内部类并不会随着外部类的加载而加载(无论是内部类还是静态内部类),如果一个非static的内部类如果具有static的属性或者方法,那么就会出现一种情况:内部类未加载,但是却试图在内存中加载并使用static的属性和方法,这当然是错误的。
静态内部类中可以允许有静态方法,但只能访问外围类的静态域和方法。

为什么内部类不能有静态变量但是可以有静态常量?
(1)Java类加载顺序:首先加载类,执行static变量初始化,接下来执行对象的创建。如果我们要初始化内部类中的变量static int a,那么必须先执行加载外部类并且创建出外部类的对象,再加载内部类,最后初始化内部类静态变量a。
问题就出在加载内部类上面,我们可以把内部类看成外部类的非静态成员,它的初始化必须在外部类对象创建后以后进行,要加载内部类必须在实例化外部类对象之后完成,JVM要求所有的静态变量必须在对象创建之前完成,这样便产生了矛盾。
而Java常量放在内存中常量池,它的机制与变量是不同的,编译时加载常量是不需要加载类的,所以就没有上面那种矛盾。
(2)我们希望一个静态域只有一个实例,不过对于每个外部对象,会分别有一个单独的内部类实例。如果这个域不是final,它可能就不是唯一的。

静态内部类会随着外部类的加载而加载吗?
不会的。静态内部类其实和外部类的静态变量,静态方法一样,只要被调用了都会让外部类的被加载。不过当只调用外部类的静态变量、方法时是不会让静态内部类被加载的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值