一、内部类概述
1. 内部类的基本概念
- 内部类:一个类的内部又完整地嵌套了另一个类结构,则被嵌套的类成为内部类(inner class),嵌套其他类的类称为外部类(outer class)。
- 一个类中有五大成员:属性、方法、构造器、代码块、内部类。
- 内部类最大的特点就是可以直接访问外部类的私有属性,并且可以体现类与类之间的包含关系。
- 内部类是面向对象学习的重难点,底层源码中包含大量的内部类。
2. 基本语法
class Outer {
// 外部类
class Inner {
// 内部类
}
}
class Other {
// 外部其他类
}
3. 内部类的分类
(1)定义在外部类的成员位置上:
- 成员内部类(没有 static 修饰)
- 静态内部类(使用 static 修饰)
(2)定义在外部类的局部位置上(方法和代码块中):
- 局部内部类(有类名)
- 匿名内部类(无类名)重点!!!
二、局部内部类
- 局部内部类定义在外部类的局部位置(即方法和代码块中),并且具有类名。
1. 局部内部类的用法与细节
(1)局部内部类本质上还是类;但同时又相当一个局部变量,因此不能用任何访问修饰符和 static 来修饰局部内部类,可以使用 final 修饰。
(2)局部内部类的作用域只在定义它的方法或者代码块中。
(3)局部内部类可以直接访问外部类的所有成员,包括私有的(不需要创建外部类对象)。
(4)外部类的其他成员不能直接访问局部内部类的成员,只能通过在定义了局部内部类的方法中用局部内部类的对象调用其成员,然后再调用该方法,实现间接使用局部内部类的成员。
(5)外部其他类不能访问局部内部类。
- 把局部内部类看做是一个方法中的局部变量,便容易理解了。
(6)如果外部类中的成员和局部内部类中的成员重名时,内部类访问该成员默认遵循就近原则;在局部内部类中想访问外部类的同名成员,使用 外部类名.this.成员 访问(外部类名.this 就相当于外部类的对象)。
2. 细节代码演示
public class LocalInnerClass {
//
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02的hashcode=" + outer02);
}
}
class Outer02 {
// 外部类
private int n1 = 100;// 外部类的属性
// 外部类私有方法
private void m2() {
System.out.println("Outer02 m2()");
}
// 外部类普通方法
public void m1() {
// 局部内部类是定义在外部类的局部位置,方法和代码块中;
// 不能添加访问修饰符和 static ,但是可以使用 final 修饰;
// 作用域 : 仅仅在定义它的方法或代码块中;
class Inner02 {
// 局部内部类(本质仍然是一个类,同时也是方法的局部变量)
private int n1 = 800;// 局部内部类的私有属性(与外部类的同名)
public void f1() {
// 局部内部类可以直接访问外部类的所有成员,比如下面 外部类n1 和 m2();
// 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,使用 外部类名.this.成员 去访问;
// Outer02.this 本质就是外部类的对象, 即哪个对象调用了m1, Outer02.this就是哪个对象;
System.out.println("n1=" + n1 + " 外部类的n1=" + Outer02.this.n1);
// 打印Outer02.this 的哈希值,与main 方法中的对象 outer02比较,两者相同;
System.out.println("Outer02.this hashcode=" + Outer02.this);
m2(); // 直接访问外部类的私有方法;
}
} // 局部内部类结束
// 外部类在m1方法中,可以创建一个Inner02 对象,
// 这样就可以调用m1 方法,自动创建Inner02 对象,使用其成员了;
// 注意:不能在外部类的其他方法中创建m1 方法中的局部内部类对象;
// 因为局部内部类的作用域只在定义它的方法或者代码块中。
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
三、匿名内部类(重要!!!)
- 匿名内部类定义在外部类的局部位置(即方法和代码块中),并且不具有类名。
1. 匿名内部类的用法和细节
- 基本语法
// 第一种:实现接口的匿名内部类
接口名 接口的引用 = new 接口名() {
重写接口的抽象方法;
};
// 第二种:继承父类的匿名内部类(父类可以是抽象类或普通类)
父类类型 父类的引用 = new 父类(形参列表) {
重写父类的抽象方法/方法;
};
(1)匿名内部类本质上还是类,但同时它本身还是一个对象。
(2)匿名内部类就是实现了接口的一个类,或者是继承了父类的一个子类;匿名内部类只会被创建一次,之后便被销毁;其对象也只会被创建一次,但是该对象可以被一直使用。
- 代码解释:
// 实现接口的匿名内部类
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer {
//外部类
// 外部类的普通方法
public void method() {
// 实现接口的匿名内部类
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}; // 内部类创建完毕
// 查看匿名内部类的类名是否为 Outer$1
System.out.println("tiger的运行类型=" + tiger.getClass());
tiger.cry();// tiger对象 可以一直使用
tiger.cry();
tiger.cry();
}
}
// 接口
interface IA {
public void cry();
}
分析:
1.需求: 想实现IA接口,并创建对象
2.传统方式,是写一个实现接口的类,实现该接口,并创建该类对象
3.可要求是:该类只是使用一次,后面再不使用;
4. 可以使用匿名内部类来简化开发
5. tiger的编译类型是: IA接口
6. tiger的运行类型是: 就是匿名内部类 Outer$1 (jdk分配的类名,不能使用)
我们看底层分配 类名 Outer$1 ,如下:
class Outer$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
7. jdk底层在创建匿名内部类 Outer$1,并立即就创建了 Outer$1 实例对象,
并且把对象地址返回给 tiger;
8. 匿名内部类 Outer$1 使用一次后,就不能再使用,但其对象 tiger可以一直使用。
// 继承父类的匿名内部类
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer {
//外部类
// 外部类的普通方法
public void method() {
// 继承父类的匿名内部类
Father father = new Father("jack"){
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
};
System.out.println("father对象的运行类型=" + father.getClass()); // 输出 Outer$2
father.test();
// 继承抽象类的匿名内部类
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("小狗吃骨头...");
}
};
animal.eat();
}
}
// 父类
class Father {
// 构造器
public Father(String name) {
System.out.println("接收到name=" + name);
}
//方法
public void test() {
}
}
// 抽象类
abstract class Animal {
abstract void eat();
}
分析:
1. father编译类型 Father
2. father运行类型 Outer$2
3. 底层会创建匿名内部类,如下
class Outer$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
}
4. 同时也直接返回了 匿名内部类 Outer$2的对象
5. 注意("jack") 参数列表会传递给 构造器。
(3)匿名内部类既是一个类的定义,同时本身也是一个对象,又是一个局部变量。因此它可以不返回对象地址,通过自身直接调用其内部的成员;但这样该匿名内部类的对象便只能使用一次。
(4)匿名内部类的作用域只在定义它的方法或者代码块中;匿名内部类不能添加访问修饰符 和 static,可以用 final 修饰,因为它的地位就是一个局部变量。
(5)匿名内部类可以直接访问外部类的所有成员,包括私有的(不需要创建外部类对象)。
(6)外部类的其他成员不能直接访问匿名内部类的成员,只能通过在定义了匿名内部类的方法中用匿名内部类的对象调用其成员,然后再调用该方法,实现间接使用匿名内部类的成员。
(7)外部其他类不能访问匿名内部类及其成员。
(8)如果外部类中和成员和匿名内部类中的成员重名时,内部类访问该成员默认遵循就近原则;在匿名内部类中想访问外部类的同名成员,使用 外部类名.this.成员 访问。
- 代码解释:
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
System.out.println("main outer05 hashcode=" + outer05);
}
}
class Outer05 {
private int n1 = 99;// 外部类的属性
public void f1() {
// 创建一个基于类的匿名内部类;
// 不能添加访问修饰符,因为它的地位就是一个局部变量;
// 作用域 : 仅仅在定义它的方法或代码块中
Person p = new Person() {
private int n1 = 88;// 匿名内部类的属性,与外部类的同名
@Override
public void hi() {
// 匿名内部类可以直接访问外部类的所有成员,包含私有的;
// 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则;
// 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问;
System.out.println("匿名内部类重写了 hi方法 n1=" + n1 +
" 外部内的n1=" + Outer05.this.n1 );
// Outer05.this 就是调用 f1的 对象;
System.out.println("Outer05.this hashcode=" + Outer05.this);
}
};// 匿名内部类定义结束
p.hi();// 动态绑定, 运行类型是 Outer05$1
// 匿名内部类也可以直接调用其成员, 匿名内部类本身也是一个对象
new Person() {
@Override
public void hi() {
System.out.println("匿名内部类重写了 hi方法,哈哈...");
}
@Override
public void ok(String str) {
super.ok(str);
}
}.ok("jack");
}
}
// 父类
class Person {
public void hi() {
System.out.println("Person hi()");
}
public void ok(String str) {
System.out.println("Person ok() " + str);
}
}
2. 匿名内部类的最佳实践
- 匿名内部类可以当做实参直接传递给方法,简洁高效。
案例如下:
public class InnerClassExercise01 {
public static void main(String[] args) {
// 传统方法:传入一个实现接口的对象,浪费资源;
f1(new Picture());
// 新方法:把匿名内部类当做实参直接传递,简洁高效。
f1(new IL() {
@Override
public void show() {
System.out.println("这是一副名画~~...");
}
});
}
// 静态方法,形参是接口类型
public static void f1(IL il) {
il.show();
}
}
//接口
interface IL {
void show();
}
//类->实现IL => 编程领域 (硬编码)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画XX...");
}
}
四、成员内部类
- 成员内部类是定义在外部类成员位置的类,并且没有 static 关键字修饰。
成员内部类的用法与细节
(1)成员内部类实质上就是一个类;同时它也是一个成员变量,因此它可以用任意的访问修饰符来修饰(public、protected、默认、private),也可以用 final 来修饰,但不用 static 修饰(用 static 修饰的成员内部类叫做静态内部类)。
(2)成员内部类的作用域是整个类体;它可以直接访问外部类的所有成员,包含私有的、静态的(不需要创建外部类对象);注意:成员内部类中不能定义静态成员。
(3)外部类的其他成员想访问成员内部类中的非静态成员,需要先创建成员内部类的对象,再通过该对象来访问(成员内部类中没有静态成员)。
(4)外部其他类的成员访问成员内部类的成员(前提是满足访问权限),有两种方式,如下:
// 首先需要先创建一个外部类的对象;
Outer08 outer08 = new Outer08();
// 第一种方式: 直接在外部其他类中创建成员内部类对象;
// 这就是一个语法,不要特别的纠结
Outer08.Inner08 inner08 = outer08.new Inner08();
// outer08.new Inner08(); 相当于把 new Inner08() 作为整体当做是 outer08 成员
inner08.say(); // 这时便可以使用成员内部类的成员了
// 第二方式: 在外部类中,编写一个方法,可以返回 Inner08 对象;
// public Inner08 getInner08Instance(){ return new Inner08(); }
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();// 这时便可以使用成员内部类的成员了
(5)如果外部类中的成员和成员内部类中的成员重名时,内部类访问该成员默认遵循就近原则;在成员内部类中想访问外部类的同名成员,使用 外部类名.this.成员 访问。
五、静态内部类的用法与细节
- 静态内部类是定义在外部类成员位置的类,并且使用 static 关键字修饰。
静态内部类的用法与细节
(1)静态内部类实质上就是一个类;同时它也是一个成员变量,因此它可以用任意的访问修饰符来修饰(public、protected、默认、private),也可以用 final 来修饰,而必须使用 static 修饰。
(2)静态内部类的作用域是整个类体;它可以直接访问外部类的所有静态成员,包含私有的(不需要创建外部类对象);注意:静态内部类中可以定义非静态成员和静态成员(自己尝试)。
(3)外部类的其他成员想访问静态内部类中的非静态成员,需要先创建静态内部类的对象,再通过该对象来访问;外部类的其他成员想访问静态内部类中的静态成员,则只需要使用静态内部类名.成员 便可直接访问。
(4)外部其他类的成员访问成员内部类的成员(前提是满足访问权限),有两种方式,如下:
// 首先需要先创建一个外部类的对象;
Outer outer = new Outer();
// 第一种方式: 直接在外部其他类中创建静态内部类对象;
// 这就是一个语法,不要特别的纠结
Outer.Inner inner = new Outer.Inner();
// 因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
inner.say(); // 这时便可以使用静态内部类的成员了
// 第二方式: 在外部类中,编写一个方法,可以返回 Inner 对象;
// public Inner getInnerInstance(){ return new Inner(); }
Outer.Inner innerInstance = outer.getInnerInstance();
innerInstance.say();// 这时便可以使用静态内部类的成员了
(5)如果外部类中的成员和静态内部类中的成员重名时,内部类访问该成员默认遵循就近原则;在静态内部类中想访问外部类的同名成员,使用 外部类名.成员 访问(静态内部类中不能使用 this 关键字)。
总结
- 本文是小白博主在学习B站韩顺平老师的Java网课时整理总结的学习笔记,在这里感谢韩顺平老师的网课,如有有兴趣的小伙伴也可以去看看。
- 本文详细解释了 内部类 的概念与使用,并深入介绍了 4种内部类 的注意事项和细节。这一部分的知识非常的多且琐碎,而且想要理解好需要较好的面向对象的基础。小白博主已经尽力整理了,希望小伙伴们看后能有所收获!
- 最后,如果本文有什么错漏的地方,欢迎大家批评指正!一起加油!!我们下一篇博文见吧!