Java 外部类和内部类


1 外部类

在 Java 中, 创建的 .java 文件 (源文件) 时, 文件名必须和里面定义的类名一样。
而这个我们创建的类, 就是外部类, 也叫顶级类。

做一点引申:

  1. 在一个源文件里面, 可以有很多个顶级类, 但是这些顶级类只有和文件名一样的顶级类才能被 public 修饰, 只有一个, 其他的都只能用默认 (什么都不加) 进行修饰
  2. 同一个源文件里面的多个顶级类, 因为只有一个可以被 public 修饰, 所以导致其他的顶级类, 只能在当前包路径下使用
  3. 《Effective Java, Third Edition》第 25 条建议: 每个源文件只对应一个顶级类

2 内部类

又称嵌套类, 是在类中在定义另外一个类, 这个类可以在顶级类里面的任意地方定义。

2.1 静态内部类

public class OutClass {
    public static class InnerClass {  
    }
}

直接在顶级类的内部声明, 通过 static 进行修饰的类

特点:

  1. 所有的权限修饰符都可以进行修饰
  2. 能够在类中定义静态属性和方法
  3. 只能使用外部类的静态属性和方法
  4. 在其他类可以直接声明 (静态内部类与外部类没有关系, 在编译后, 就是 2 个普通的类)
OutClass.StaticInnerClass innerClass = new OutClass.StaticInnerClass();

2.2 非静态内部类

2.2.1 成员内部类

public class OutClass {
    public class InnerClass {
    }
}

定义在顶级类的直接内部, 和类的属性同级

特点:

  1. 所有的权限修饰符都可以进行修饰
  2. 不能够在类内定义静态属性和方法
  3. 能够访问外部类的所有属性和方法, 包括静态的
  4. 需要依靠所在类的外部类才能声明 (成员内部类对象会隐式的引用一个外部类对象, 也就是 2 者之间有联系)
OutClass outClass = new OutClass();
// 需要先创建出外部类的实例, 通过实例才能创建内部类
OutClass.InnerClass innerClass = outClass.new InnerClass();

2.2.2 局部内部类

public class OutClass {
   public void fn() {
       class InnerClass {
       }
   }
}

声明在顶级类的某个作用域内(方法体内, if 判断里面等)

特点:

  1. 这个类只能使用默认权限修饰符, 不能被其他的权限修饰符修饰
  2. 不能够在类内定义静态属性和方法
  3. 可以访问到外部类的属性和方法, 包括静态的
  4. 在其他类是无法创建声明的
  5. 局部类只能在对应的作用域起作用, 超出了对应的作用域就没了, 所以无法被其他的类调用

2.2.3 匿名内部类

public class OutClass {
    public void fn() {
       /**
        *     创建格式:
        *     new 父类/接口(参数列表) {
        *     }
        */
        new Thread(new Runnable() {
          @Override
          public void run() {
          }
        });
    }
}

在顶级类的某个作用域内, 直接创建使用, 没有进行具体的声明

特点:

  1. 匿名内部类为局部内部类的特例, 具备局部内部类的特点

3 作用

3.1 封装性, 隐藏具体的细节, 不让外部类知道

/**
 * 加密器接口
 */
public interface Encrypter {

    /**
     * 对内容进行加密
     * @param content 加密的内容
     * @return 加密后的内容
     */
    String encrypt(String content);
}

/**
 * 外部类
 */
public class OutClass {

    /**
     * 隐藏起来的加密器
     */
    private class MyEncrypter implements Encrypter {
        @Override
        public String encrypt(String content) {
            // 在需要加密的内容后面加上 encrypt
            return content + "encrypt";
        }
    }

    /**
     * 获取加密器
     * @return
     */
    public Encrypter getEncrypter() {
        return new MyEncrypter();
    }
}

/**
 * 调用类
 */
public class Main {

    public static void main(String[] args) {

        String str = "需要加密的内容";

        // 取到加密器, 此处我们只知道通过外部类就能获得加密器, 但是加密器的加密过程我们是不知道的
        OutClass outClass = new OutClass();
        Encrypter encrypter = outClass.getEncrypter();

        // 加密内容
        String encryptedContent = encrypter.encrypt(str);
        System.out.println(encryptedContent);
    }
}

3.2 间接实现多重继承(声明多个内部类, 每个内部类继承一个类)

/**
 * 加密器
 */
 public class Encrypter {

     /**
      * 对内容进行加密
      * @param content 加密的内容
      * @return 加密后的内容
      */
     public String encrypt(String content) {
         // 在需要加密的内容后面加上 encrypt
         return content + "encrypt";
     }
 }

/**
 * 解密器
 */
 public class Decrypter {

    /**
     * 对内容进行解密
     * @param content 需要解密的内容
     * @return 解密后的内容
     */
    public String decrypt(String content) {

        // 在需要加密的内容后面加上 encrypt
        int length = "encrypt".length();
        // 解密的内容不为空, 同时长度需要大于 length
        if (content == null || "".equals(content) || content.length() < length) {
            return content;
        }
        return content.substring(0, content.length() - length );
    }
}


/**
 * 外部类, 此时我们的外部类具备了编码和解码的功能
 */
public class OutClass {

  private class MyEncrypter extends Encrypter{
  }

  private class MyDecrypter extends Decrypter{
  }

  public String encrypt(String content) {
      return new MyEncrypter().encrypt(content);
  }

  public String decrypt(String content) {
      return new MyDecrypter().decrypt(content);
  }
}

3.3 方便编写事件驱动程序 (如安卓里面的按钮事件等)

/** 匿名内部类 */
myBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
       //这里写代码
    }
});

4 补充

4.1 内部类如何访问外部类的同名方法/属性

内部类的属性, 方法和外部类的重名了, 在调用时, 优先使用内部类的, 同时可以通过外部类.this.方法/属性, 访问到外部类的同名方法/属性

4.2 局部内部类访问方法体内入参/变量的条件

  1. 局部内部类可以访问方法内的局部变量和入参, 但是前提是这个变量和入参在这个方法体内是被 final 修饰的
  2. 在 Java 8 中, 可以直接声明后使用, 只要你保证你的入参/变量满足 Effectively final

注: Effectively final 是 Java 8 新增的一个特性。
对于一个变量, 如果没有给它加 final 修饰, 而且没有对它的二次赋值, 那么这个变量就是 effectively final (有效的不会变的)。
简单的来说, 我们在方法 fn 里面声明了一个变量 int a = 1; 在这个方法体内, 除了第一次声明外, 没有别的地方对其进行了修改, 他就是 effectively final, 而只要做了一次变更, 就会破坏掉这个状态。

Effectively final 内部类和 Lambda 时会经常碰到, 一旦在他们内部做外部的一个变量做了变更就会编译失败, 但是单纯的读是支持的。


  目录