内部类,又叫做类中的类,在Java中有四种,根据作用域不同分为三类:成员内部类、静态内部类和局部内部类。而局部内部类里又有一种特殊的匿名内部类。
成员内部类
成员内部类首先有这么一个特征,多个内部实例,对应外部类一个实例。我举个例子,假设有以下外部类和内部类:
public class University {
private String name;
public University(String name) {
this.name = name;
}
private class College {
private String name;
public College(String name) {
this.name = name;
}
@Override
public String toString() {
return University.this.name + name;
}
}
}
再创建实例
final University university = new University("家里蹲大学");
final College college1 = university.new College("文法学院");
final College college2 = university.new College("外语学院");
可以看到两个College对象共享一个University实例。但是不推荐这种共享对象的用法。为什么?因为使用组合(composition)更适合这种场景。
因为共享实例的原因,枚举只能作为嵌套类,不能作为成员内部类,内部枚举编译器会自动加上static关键字,而不是不加static就报错。
成员内部类的出现,其实是为了解决多继承问题的。因为外部类可以继承一个类,而内部类又可以继承第二个类,并且可以访问外部类的所有属性和方法,相当于内部类继承了两个类了。假如要继承第三个类,那么就在内部类里再写一个内部类。我举个斜杠青年如何诞生的例子:
interface Skill {
String skill();
}
class Mother implements Skill {
@Override
public String skill() {
return "踢足球";
}
}
class Other implements Skill {
@Override
public String skill() {
return "打篮球";
}
}
public class Father implements Skill {
@Override
public String skill() {
return "下棋";
}
public class Child extends Mother implements Skill {
public class Grandchild extends Other {
@Override
public String skill() {
return Father.this.skill() + "/" + Child.this.skill() + "/" + super.skill();
}
}
}
public static void main(String[] args) {
final Father father = new Father();
final Child child = father.new Child();
final Child.Grandchild grandchild = child.new Grandchild();
System.out.println(grandchild.skill());
}
}
测试结果:
下棋/踢足球/打篮球
在语法上,内部类是不能有静态字段和静态方法的。如果有这个需求,那么只能使用嵌套类,也就是静态内部类。
静态内部类
静态内部类,也叫做嵌套类Nested Class。在JDK的集合类源码中,经常看到静态内部类。前面说过成员内部类不能有静态字段和静态方法,但是静态内部类是可以用非静态字段和方法的。这说明了静态内部类一个普遍的用法是用在数据类Data Class的属性装配上,而这种属性所属的类还是专有的,JDK的Map中的Entry就是这样的,其他的类没有Entry, Entry是专属于Map的。这句话说得有点拗口,我举个例子吧,以下是我举的一个例子:
package com.youngthing.demo;
/**
* 12/5/2022 1:54 AM 创建
*
* @author 花书粉丝
*/
public class Hero {
/**
* 名字
*/
private String name;
/**
* 等级
*/
private int level;
/**
* 武器
*/
private Weapon weapon;
public static class Weapon {
/**
* 名字
*/
private String name;
/**
* 等级
*/
private int level;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
我建的类叫英雄,是游戏里常见的,然后把武器作为英雄的嵌套类。当然这个例子并不恰当,实际的项目中武器比较重要,代码非常复杂,所以一般是不会用嵌套类来实现。
局部内部类
局部内部类是在方法内部定义的类,一般比较少用,反而是它的匿名形式,匿名内部类比较常见。比如要实现一个字符串按长度比较,可以写一个局部内部类来实现,如以下代码:
package com.youngthing.demo;
import java.util.Arrays;
import java.util.Comparator;
/**
* 12/5/2022 2:02 AM 创建
*
* @author 花书粉丝
*/
public class SortDemo {
public static void main(String[] args) {
String[] strings = {"Abandon", "Zoo", "Park", "Union"};
class MyComparator implements Comparator<String> {
@Override
public int compare(String s, String t1) {
return s.length()-t1.length();
}
}
Arrays.sort(strings, new MyComparator());
System.out.println(Arrays.toString(strings));
}
}
局部内部类有一些限制,不能有静态方法和静态字段。
匿名内部类
上面的例子中,局部内部类只用一次,所以可以改成匿名内部类,代码可以更简洁一些,如下:
package com.youngthing.demo;
import java.util.Arrays;
import java.util.Comparator;
/**
* 12/5/2022 2:02 AM 创建
*
* @author 花书粉丝
*/
public class StringSortDemo {
public static void main(String[] args) {
String[] strings = {"Abandon", "Zoo", "Park", "Union"};
Arrays.sort(strings, new Comparator<>() {
@Override
public int compare(String s, String t1) {
return 0;
}
});
System.out.println(Arrays.toString(strings));
}
}
同局部内部类一样,匿名内部类不能有静态方法和静态字段。
作用域内部类和匿名内部类只能使用final的局部变量的问题。这个问题的原因是,局部变量传递到内部类时,已经是另一个变量了。如果在内部类中修改这个变量,并不会修改外部的局部变量的值。为了避免开发人员写出错误的代码,编译器就强制要求变量为final。但是如果要传递一个可能动态改变的值怎么办呢?可以定义一个长度为1的final数组,将值传递过去。