背八股的时候,没怎么见到过关于Java内部类的知识。有几次在牛客刷题的时候刷到了,发现自己很多都不清楚,因此本文应运而生。本文将从什么是内部类、内部类的种类、怎么使用内部类等方向进行讲解,希望能够帮到你!
一、什么是内部类?
简单的来说,就是“定义在另一个类中的类”。这只是最基础的说法,其实定义在接口中的另一个类,也可以称为内部类,这将在后面讲解。前面主要提及的便是在类中的内部类。
二、内部类的分类
按照是否被 static 修饰,或者说,是否需要一个指向外部类的引用,可以分为 “普通的”内部类和嵌套类。当然,这个普通的是我自己命名的,它泛指,一般的内部类(不被 static 修饰)。
对于,“是否需要一个指向外部类的引用”的这个引用是什么。它是这样的:当创建一个内部类时,这个内部类的对象中会隐含一个链接,指向用于创建该对象的外围对象。通过该链接,无需任何特殊条件,内部类对象就可以访问外围对象的成员。
如果再细分,那么内部类大致可以分为四种:
1、成员内部类
2、局部内部类
3、匿名内部类
4、嵌套类(静态内部类)
其中,嵌套类其实是可以划分到成员内部类中的,这里为了讲解的方便,特意划分出来。
三、成员内部类
3.1 创建成员内部类
public class Outer {
private int num1 = 10;
private static int num2 = 100;
private int getNum1() {
return this.num1;
}
class Inner {
private int num1 = 12;
public void f1() {
System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
System.out.println("Inner的私有成员变量 num1: " + num1);
System.out.println("Outer的静态成员变量 num2: " + num2);
System.out.println("调用Outer的方法:" + getNum1());
}
}
public static void main(String[] args) {
// 创建内部类实例
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.f1();
}
}
如上面的代码所示,这是一个简单的成员内部类的实例。可以看到,内部类可以“无条件”的访问外部类的成员和方法,包括私有的。
3.2 外部类的引用
如果我们想要访问外部类的成员或方法,我们就需要一个指向外部类的引用,这个引用可以用 外部类的名称.this
来生成。
当然,如果内部类没有这个字段或方法,你也可以直接使用外部类的变量名或方法名来使用。不过还是建议加上 外部类名称.this 的方式,这样更容易明白调用的是谁的成员。
3.3 创建内部类实例
想要创建内部类的实例,就必须先创建外部类的实例。然后通过 外部类实例.new
来创建内部类实例。
如果你直接 Outer.Inner inner = new Inner();
那么,编译器会报 “找不到类Inner”。
当然,你也不能 Outer.Inner inner = new Outer.Inner();
,编译器会提示你需要一个 Outer的实例。
同时,对于普通的类,我们知道:只能给他们声明为 public 或 包访问权限(即不写),无法被声明为 private 和 protected的。但,内部类不一样,它可以被四种访问修饰符所修饰。
四、局部内部类
4.1 什么是局部内部类?
即在一个方法内或者任何一个作用域内。
4.2 创建局部内部类
4.2.1 方法内
public class Outer {
private int num1 = 10;
private static int num2 = 100;
private int getNum1() {
return this.num1;
}
public void test() {
class Inner {
private int num1 = 12;
public void f1() {
System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
System.out.println("Inner的私有成员变量 num1: " + num1);
System.out.println("Outer的静态成员变量 num2: " + num2);
System.out.println("调用Outer的方法:" + getNum1());
}
}
Inner inner = new Inner();
inner.f1();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
4.2.2 作用域内
public class Outer {
private int num1 = 10;
private static int num2 = 100;
private int getNum1() {
return this.num1;
}
public void test(boolean flag) {
if (flag) {
class Inner {
private int num1 = 12;
public void f1() {
System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
System.out.println("Inner的私有成员变量 num1: " + num1);
System.out.println("Outer的静态成员变量 num2: " + num2);
System.out.println("调用Outer的方法:" + getNum1());
}
}
Inner inner = new Inner();
inner.f1();
}
// Inner inner = new Inner();
// inner.f1();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test(true);
}
}
值得注意的是:局部内部类的有效范围,就是这个作用域的范围。譬如,如果把上面的代码的注释去掉,就会报错,因为已经超过定义其的作用域之外了。
五、匿名内部类
5.1 什么是匿名内部类?
顾名思义,即没有名字的内部类。
5.2 匿名内部类示例
public class Animal {
private int num1 = 10;
private static int num2 = 100;
private int getNum1() {
return this.num1;
}
public Dog running() {
return new Dog(){
@Override
public void run() {
System.out.println("*********");
super.run();
System.out.println("Animal的私有成员变量 num1: " + num1);
System.out.println("Animal的静态成员变量 num2: " + num2);
System.out.println("调用Animal的方法:" + getNum1());
}
};
}
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = animal.running();
dog.run();
}
}
Dog类
public class Dog {
public void run(){
System.out.println("小狗 run run run");
}
}
除此之外,我们也可以把匿名内部类运用在方法的参数上,也可以直接在方法内部直接 new。
public class Animal {
public void test2(Thread thread) {
System.out.println("test2*****");
}
public static void main(String[] args) {
Animal animal = new Animal();
new Thread() {
@Override
public void run() {
super.run();
}
}.start();
animal.test2(new Thread() {
@Override
public void run() {
super.run();
}
});
}
}
六、嵌套类(静态内部类)
6.1 什么是嵌套类?
如果不需要内部类对象和外部类对象之间的连接,可以将内部类设置为 static 的。如果一个内部类被设置为嵌套类,那么就意味着:
1、不需要一个外部类对象来创建嵌套类对象;
2、无法从嵌套类对象内部访问非 static 的外部类对象。
对于普通的内部类,其内部不能有 static 数据、static 字段,也不能有嵌套类。但是嵌套类可以拥有这些。
6.2 创建嵌套类
public class Outer {
private int num1 = 10;
private static int num2 = 100;
private int getNum1() {
return this.num1;
}
static class Inner {
private int num1 = 12;
private static int num2 = 666;
public void f1() {
// System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
System.out.println("Inner的私有成员变量 num1: " + num1);
System.out.println("Outer的静态成员变量 num2: " + num2);
// System.out.println("调用Outer的方法:" + getNum1());
}
}
}
6.3 对于非嵌套类是否能有静态成员
public class Outer {
class Inner {
private int num1 = 12;
static int num2 = 666;
public static void f2() {
System.out.println("我是Inner的静态方法 f2()");
}
}
public static void main(String[] args) {
int a = Outer.Inner.num2;
Outer.Inner.f2();
System.out.println(a);
}
}
实测貌似是可以的?这里的Java版本是 JDK17。
6.4 接口中的类
嵌套类也可以是接口的一部分,当类被放到接口中,都会被加上 public 和 static。因此接口中的类都是嵌套类,也就是说,类放在接口中,这是将其本身放到了这个接口的“命名空间”中。你甚至可以在嵌套类中实现这个接口。
public interface ClassInInterface {
void say();
class Test implements ClassInInterface{
@Override
public void say() {
System.out.println("我是接口中的类");
}
}
public static void main(String[] args) {
new Test().say();
}
}
七、常见问题
7.1 为什么需要内部类?
每一个内部类都可以独立地继承一个实现。外部类是否已经继承了某个实现,对于内部类来说是没有限制的。通过这一点,我们可以利用内部类来实现“多继承”。
7.2 继承内部类
需要在继承类的构造方法中传入一个外部类的实例,然后调用外部实例的构造方法。
public class A extends Outer.Inner{
A(Outer outer) {
outer.super();
}
}
class Outer {
class Inner {
}
}
7.3 内部类的重写?
你不能通过继承外部类的方式,然后“重写”内部类中的方法。你通过继承“重写”的内部类和被继承类的内部类,其实是两个独立的实体——他们分别存在不同的命名空间中。如果你想重写,你必须显示的在内部类也 extends 被继承类的内部类。
7.4 内部类标识符
外部类名称+ $ + 内部类名称。
如果内部类是匿名的,那么编译器会以数字作为内部类的标识符。