浅析Java中的内部类

149 阅读5分钟

内部类

1. 概念和使用场景

如果一个事物内部包含另一个事物,那么这就是一个类内部包含另一个类。因此,内部类就是定义在一个类或方法内部的类。促使我们使用内部类的原因有两个:

  • 内部类可以对同一个包中的其他类隐藏,即如果我们希望一个类只能被某一个具体的类使用,那么就可以将其定义在那个类内部作为内部类使用
  • 内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据。如果一个普通类类想访问某个类的私有成员属性,那么只能通过类的getter()setter()访问,而内部类可以直接访问它外围类的私有成员属性

2. 分类

2.1 成员内部类

2.1.1 定义格式
// 外部类
修饰符 class 类名称{
    // 内部类
    修饰符 class 类名称{
        ...
    }
   ...
}

只有类为内部类,它的修饰符才有可能是private

2.1.2 数据字段访问

内部类可以访问自身的数据字段,也可以访问创建它的外部类对象的数据字段,即内部类可以直接通过属性名方法访问外部类的数据字段(这种情况建立在内部类中没有和其重名的变量存在),如

public class Body {
    private String name = "body";
    private String state = "healthy";

    public String getName() {
        return name;
    }

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

    public void setState(String state) {
        this.state = state;
    }
    
    public void methodBody(){
        System.out.println("outer class...");
        new Heart().beat();
    }

    public class Heart{
        public void beat(){
            System.out.println(state);
            System.out.println("beat...");
        }
    }
}

public class InterClassTest {
    public static void main(String[] args) {
        Body body = new Body();
        body.methodBody(); // outer class...  healthy  beat...
    }
}

在内部类Heart的beat()可直接使用,而不必通过getState()使用.
在这里插入图片描述

外部用内部,需要内部类对象。外部类使用内部类对象有两种方式:

  • 间接方式:在外部类的方法中使用内部类,然后main()中只调用外部类方法,如上所示

  • 直接方式:使用代码格式为:

    外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
    

    例如直接在main()直接创建内部类对象,通过内部类对象的方法直接方法外部类数据字段

    class Body {
        private String name = "body";
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void methodBody(){
            System.out.println("outer class...");
            // 间接调用内部类
            new Heart().beat();
        }
    
        //成员内部类
        public class Heart{
            public void beat(){
                System.out.println("beat...");
            }
        }
    }
    
    public class InterClassTest {
        public static void main(String[] args) {
            Body.Heart heart = new Body().new Heart();
            heart.beat(); // beat...
        }
    }
    

2.2 局部内部类

如果一个类是定义在一个方法内部,那么它就是一个局部内部类;只有当前所属方法才能使用它,出了这个方法的范围就不能再用。格式:

 修饰符 class 外部类名称{
     修饰符 返回值类型 外部类名称(参数列表){
         class 局部内部类名称{
             ...
         }
         ...
     }
     ...
 }

声明局部内部类时不能有访问控制符(即public或private)

代码示例:

class Person{
    public void show(){
        class Teacher{
            public void show(){
                System.out.println("teacher...");
            }
        }
        Teacher t = new Teacher();
        t.show();
    }
}

public class LocalInterClass {
    public static void main(String[] args) {
        Person p = new Person();
        p.show();  // teacher...
    }
}

局部内部类如果希望访问所在方法的局部变量,那么这个变量必须是有效final的

2.3 匿名内部类

如果接口的实现类(或父类的子类)只需要使用唯一的一次,则可以省略该类的定义,而改为使用匿名内部类。格式:

接口名称 对象名 = new 接口名称(){
	//覆写所有抽象方法
};

注意事项:对于上面创建匿名内部类的语句来说:

  • new代表创建对象的动作
  • 接口名称是匿名内部类需要实现那个接口
  • {…}是匿名内部类的内容

匿名内部类和匿名对象:

  • 匿名内部类在创建对象的时候,只能使用唯一的一次。如果希望使用多次,则需多次创建。如果每次使用的类的内容完全一样,更好的方式是使用接口的单独实现类
  • 匿名内部类是省略了实现类/子类名称,但匿名对象是省略了对象名称

2.4 静态内部类

内部类希望只有创建它的外部类使用,对包内其他类具有不可见性。如果同时又希望使用内部类只是将其隐藏在外部类内部,并不需要内部类有外部类对象的一个引用,就可以将内部类声明为static,此时内部类就是静态内部类。

例如,创建一个类ArrayGetMaxMin实现获取一次性获取数组中的最大值和最小值,为了保存最值结果,在ArrayGetMaxMin中创建内部类pair实现结果保存。由于Pair并不需要有ArrayGetMaxMin对象的引用,因此最好定义为一个静态内部类。

import java.util.Arrays;
import java.util.Random;

public class ArrayGetMaxMin {
    public static Pair getMaxMin(int[] arr, int bound){
        int min = arr[0];
        int max = arr[0];

        for(int i = 1; i < arr.length; i++){
            if (arr[i]< min){
                min = arr[i];
            }
            if (arr[i] > max){
                max = arr[i];
            }
        }
        return new Pair(min, max);
    }
    public static class Pair{
        private int first;
        private int second;

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

        public int getFirst() {
            return first;
        }

        public int getSecond() {
            return second;
        }
    }

    public static void main(String[] args) {
        int[] array = new int[10];
        int bound = 10;
        for (int i = 0; i < array.length; i++) {
            array[i] = new Random().nextInt(bound);
        }
        System.out.println(Arrays.toString(array));

        Pair maxMin = ArrayGetMaxMin.getMaxMin(array, bound);
        System.out.println("min number is: " + maxMin.getFirst() + " and ma x number is: " + maxMin.getSecond());
    }
}

2.5 变量重名问题

通过关键字指定访问的是哪一个变量:

  • 如果是当前方法的变量则直接访问
  • 如果是当前类成员变量,需使用this关键字指定访问
  • 如果是外部类的成员变量,需使用外部类名称.this.成员变量名称访问
class Body {
    private String name = "body";

    //成员内部类
    public class Heart{
        String name = "HEART";
        
        public void show(){
            String name = "heart";
            // 解决重名变量问题
            System.out.println(name);
            System.out.println(this.name);
            System.out.println(Body.this.name);
        }
    }
}

public class InterClassTest {
    public static void main(String[] args) {
        Body.Heart heart = new Body().new Heart();
        heart.show();
        /*
         * heart
         * HEART
         * body
         */
    }
}

2.6 访问控制修饰符问题

包含有外部类、内部类的访问控制修饰符的使用:

  • 外部类:public、(default)
  • 成员内部类::public / protected / (default) / private
  • 局部内部类:什么都不能写