1 外部类
在 Java 中, 创建的 .java
文件 (源文件) 时, 文件名必须和里面定义的类名一样。
而这个我们创建的类, 就是外部类, 也叫顶级类。
做一点引申:
- 在一个源文件里面, 可以有很多个顶级类, 但是这些顶级类只有和文件名一样的顶级类才能被 public 修饰, 只有一个, 其他的都只能用默认 (什么都不加) 进行修饰
- 同一个源文件里面的多个顶级类, 因为只有一个可以被 public 修饰, 所以导致其他的顶级类, 只能在当前包路径下使用
- 《Effective Java, Third Edition》第 25 条建议: 每个源文件只对应一个顶级类
2 内部类
又称嵌套类, 是在类中在定义另外一个类, 这个类可以在顶级类里面的任意地方定义。
2.1 静态内部类
public class OutClass {
public static class InnerClass {
}
}
直接在顶级类的内部声明, 通过 static 进行修饰的类
特点:
- 所有的权限修饰符都可以进行修饰
- 能够在类中定义静态属性和方法
- 只能使用外部类的静态属性和方法
- 在其他类可以直接声明 (静态内部类与外部类没有关系, 在编译后, 就是 2 个普通的类)
OutClass.StaticInnerClass innerClass = new OutClass.StaticInnerClass();
2.2 非静态内部类
2.2.1 成员内部类
public class OutClass {
public class InnerClass {
}
}
定义在顶级类的直接内部, 和类的属性同级
特点:
- 所有的权限修饰符都可以进行修饰
- 不能够在类内定义静态属性和方法
- 能够访问外部类的所有属性和方法, 包括静态的
- 需要依靠所在类的外部类才能声明 (成员内部类对象会隐式的引用一个外部类对象, 也就是 2 者之间有联系)
OutClass outClass = new OutClass();
// 需要先创建出外部类的实例, 通过实例才能创建内部类
OutClass.InnerClass innerClass = outClass.new InnerClass();
2.2.2 局部内部类
public class OutClass {
public void fn() {
class InnerClass {
}
}
}
声明在顶级类的某个作用域内(方法体内, if 判断里面等)
特点:
- 这个类只能使用默认权限修饰符, 不能被其他的权限修饰符修饰
- 不能够在类内定义静态属性和方法
- 可以访问到外部类的属性和方法, 包括静态的
- 在其他类是无法创建声明的
- 局部类只能在对应的作用域起作用, 超出了对应的作用域就没了, 所以无法被其他的类调用
2.2.3 匿名内部类
public class OutClass {
public void fn() {
/**
* 创建格式:
* new 父类/接口(参数列表) {
* }
*/
new Thread(new Runnable() {
@Override
public void run() {
}
});
}
}
在顶级类的某个作用域内, 直接创建使用, 没有进行具体的声明
特点:
- 匿名内部类为局部内部类的特例, 具备局部内部类的特点
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 局部内部类访问方法体内入参/变量的条件
- 局部内部类可以访问方法内的局部变量和入参, 但是前提是这个变量和入参在这个方法体内是被 final 修饰的
- 在 Java 8 中, 可以直接声明后使用, 只要你保证你的入参/变量满足
Effectively final
注: Effectively final
是 Java 8 新增的一个特性。
对于一个变量, 如果没有给它加 final 修饰, 而且没有对它的二次赋值, 那么这个变量就是 effectively final (有效的不会变的)。
简单的来说, 我们在方法 fn 里面声明了一个变量 int a = 1; 在这个方法体内, 除了第一次声明外, 没有别的地方对其进行了修改, 他就是 effectively final, 而只要做了一次变更, 就会破坏掉这个状态。
Effectively final 内部类和 Lambda 时会经常碰到, 一旦在他们内部做外部的一个变量做了变更就会编译失败, 但是单纯的读是支持的。