我的其他笔记可以查看JAVA学习记录总贴
内部类
基础知识
内部类的类的第五大成员。
五大成员分别是:属性、方法、构造器、代码块、内部类。
定义:一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为“内部类”,在外面的类称为“外部类”,内部类最大的特点就是可以访问私有属性,并且可以体现类与类之间的包含关系。
语法
class Outer{//外部类
class Inter{//内部类
}
}
class OtherOuter{//外部其他类
}
PS:内部类是OOP的重难点,底层源码有大量的内部类,必须要要下来这一块。
分类
-
定义在外部类局部位置(比如方法内)
-
局部内部类(有类名)
-
匿名内部类(无类名,重点!!!!!!!)
-
-
定义在外部类的成员位置上
-
成员内部类(没用static)
-
静态内部类(使用static)
-
class OuterClass {
//成员内部类
class MemberInnerClass {}
//静态内部类
static class StaticInnerClass{}
//外部方法
public void outerMethod() {
//局部内部类
class LocalInnerClass {}
//匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类");
}
};
runnable.run();
}
}
局部内部类
细节
局部内部类定义在外部类的局部位置,比如在方法中,并且有类名。(代码块中也行,但罕见)
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,因为局部内部类本质上就是一个局部变量,局部变量不能使用修饰符,同理它也不能,但它可以使用final,因为局部变量也可以用final,顺带一提abstract也可以。
-
局部内部类是可以被继承的。
-
作用域:仅仅作用在定义它的方法或代码块中
-
局部内部类想访问外部类的成员,直接访问即可
-
外部类想访问局部内部类的成员,可以在作用域内实例化局部内部类,但是注意,必须在定义它之后再new
-
外部其他类不可能访问局部内部类,这个挺好理解,因为局部内部类本质局部变量,不在它作用域内。
-
如果外部类和局部内部类变量重名,则会遵循就近原则,优先访问到局部内部类的变量,如果想要访问外部类的成员,则用如下语法
外部类类名.this.成员名,ps:顺带一提,如果你不嫌脱裤子放屁,其实外部类类名.this.成员名这种语法,可以在类的所有地方精确调用到本类成员 -
再顺带一提,其实所有内部类都可以在内部继续写内部类,这也是它复杂的原因
class OuterClass {
private int n1 = 10;
private void m2() {}
public void m1() {
class InnerClass {
public void show() {
// 可以直接访问外部类的所有成员,包括私有类型,包括方法
System.out.println("n1 = " + n1);
m2();
}
}
class A extends InnerClass{
}
InnerClass innerClass = new InnerClass();
}
}
建议对着上面的代码,每一条细节都去自己实践一遍,看看违反了会报什么错。
匿名内部类
重点中的重点,这部分所有代码建议自己真的看完书去手敲一下,不要指望看一遍能懂,更不要单纯相信遇到的时候让AI来解释就好,没那么轻松。
特点
-
本质是类,底层会有独立的class字节码文件
-
属于内部类,定义在外部类/代码块,这类局部位置中
-
没有类名,其实底层是有的,但是程序员不关心,因而匿名
-
同时也是一个对象,类定义好的同时就已经被创建了
基本语法
new 父类/接口名(构造参数列表){
// 类体:重写父类/实现接口的抽象方法,也可以新增自定义成员
};
基于接口的匿名内部类
class Outer04 {
private int n1 = 10;
public void method() {
// 基于接口的匿名内部类
// 1. 需求:使用IA接口,并创建对象
// 2. 传统方法是写一个类,实现接口,创建对象,并调用方法
// 3. 但如果我们的需求是,这个类只用一次,那么定义出来就有些浪费了,所以我们可以使用匿名内部类来简化开发
// inner的编译类型是IA,而inner的运行类型是匿名内部类
/*其实这里的底层含义是
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("匿名内部类实现了cry方法");
}
}
*/
// 4. jdk底层创建了匿名内部类Outer04$1,然后创建了实例,并且把地址返回给inner
// 5. 匿名内部类只能使用一次,不能再次使用
IA inner = new IA(){
@Override
public void cry() {
System.out.println("匿名内部类实现了cry方法");
}
};
inner.cry();
System.out.println(inner.getClass());
IA inner1 = new IA(){
@Override
public void cry() {
System.out.println("匿名内部类实现了cry方法");
}
};
System.out.println(inner1.getClass());
}
}
interface IA {
public void cry();
}
值得说的细节在注释里都已经写明了,我在此补充一点
- Outer04$1 就是 JVM 自动为第一个匿名内部类分配的名字,数字表示该类在外部类中出现的顺序。
基于类的匿名内部类
class Outer04 {
private int n1 = 10;
public void method() {
Father fa = new Father("张三");
Father fa1 = new Father("张三"){};
// 证明匿名类创建的不是Father类,而是匿名内部类
System.out.println(fa.getClass());
System.out.println(fa1.getClass());
Father fa2 = new Father("张三"){
@Override
public void show() {
System.out.println("匿名内部类重写show()");
}
};
fa2.show();
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("匿名内部类重写eat()");
}
};
animal.eat();
}
}
class Father {
private String name;
public Father(String name) {
this.name = name;
}
public void show() {
System.out.println("show()");
}
}
abstract class Animal{
public abstract void eat();
}
简单总结一些要点
-
匿名内部类可以重写方法
-
父类的构造器如果有参数,则你也要提供对应参数(如果有多个构造器,那你就提供符合其中一个的就行)
-
基于抽象类(接口)的匿名内部类,必须实现其中所有抽象方法
一些细节
-
匿名内部类,既有定义一个类的特性,也有创建对象的特性,是一个凉面派
-
可以直接访问外部类的所有成员,包括外部私有成员
-
不能添加访问修饰符(直觉看上去也没办法提交orz),因为它本质是局部变量
-
作用域仅在定义它的方法or代码块中
-
外部其他类也不能访问匿名内部类,因为其本质局部变量
-
如果外部类和匿名内部类变量重名,则会遵循就近原则,优先访问到局部内部类的变量,如果想要访问外部类的成员,则用如下语法
外部类类名.this.成员名
匿名内部类练习
题目1:写一个基于接口的匿名内部类,并把它作为方法参数传入
public class AnonymousClass {
public static void main(String[] args) {
// 方式1:直接在参数位置编写匿名内部类
f1(new AA() {
@Override
public void show() {
System.out.println("匿名类实现接口");
}
});
// 方式2:先创建匿名内部类对象,再传入参数
AA aa = new AA() {
@Override
public void show() {
System.out.println("匿名类实现接口");
}
};
f1(aa);
}
public static void f1(AA aa){
aa.show();
}
}
interface AA{
void show();
}
题目2:
-
定义一个铃声接口 Bell,接口中包含 ring() 方法。
-
定义一个手机类 Cellphone,类中包含闹钟功能方法 alarmclock(Bell bell),方法的参数类型为 Bell。
-
测试手机类的闹钟功能:通过匿名内部类创建 Bell 接口的实现对象,作为参数传入 alarmclock 方法,调用 ring() 方法时打印:懒猪起床了。
-
再传入另一个匿名内部类对象,调用 ring() 方法时打印:小伙伴上课了。
public class AnonymousClass {
public static void main(String[] args) {
new Cellphone().alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
new Cellphone().alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell {
void ring();
}
class Cellphone {
public void alarmclock(Bell bell){
bell.ring();
}
}
重点:一定要写一写感受一下,这部分涉及:多态、继承、动态绑定、内部类,多个知识点混杂在一起,需要认真练习
成员内部类
成员内部类定义在外部类的成员位置,并且没有static修饰
-
可以访问外部类的所有成员
-
可以添加任意的修饰符,因为其地位相当于一个成员
-
成员内部类的作用域和外部类的其他成员一样,都是整个类体。
-
成员内部类可以调用外部类的所有成员,包括私有。
-
外部类可以访问成员内部类的所有成员,包括私有成员,不过,必须要创建实例才能访问。
-
外部其他类想要访问成员内部类,有两种方法,分别标注在代码里了
-
如果外部类和内部类成员同名,则内部类访问时采取就近原则,如果一定要访问外部类的同名成员,则采用
外部类.this.成员名的方式
public class AnonymousClass {
public static void main(String[] args) {
// 外部其他类访问成员内部类的两种方式
// 方式一 通过外部类对象访问成员内部类对象,下面的两种写法本质上是等价的
Outer.Inner inner = new Outer().new Inner(); // 写法1
Outer outer = new Outer(); // 写法2
Outer.Inner inner1 = outer.new Inner();
// 方式二 在外部类中编写一个写法,返回成员内部类对象
Outer.Inner inner2 = outer.getInner();
}
}
class Outer{
private int n1 = 10;
private String name = "张三";
public Inner getInner(){
return new Inner();
}
// 成员内部类
// 成员内部类是定义在外部类的成员位置上
public class Inner{
private int n2 = 20;
public void say(){
System.out.println("n1 = " + n1 + " name = " + name);
}
}
public void show(){
Inner inner = new Inner();
// 即使是类内部的私有成员也可以被访问,因为本质上它也是类的一部分。
System.out.println(inner.n2);
}
}
静态内部类
静态内部类定义在外部类的成员位置,并且有 static 修饰符。
-
静态内部类可以直接访问外部类的所有静态成员(包括私有的),但是不能直接访问非静态成员;可以访问本类的所有成员,不管是静态还是非静态
-
解释:
-
静态内部类是外部类的一部分,因而可以访问外部类的所有静态成员。
-
对于外部类的非静态成员,可以在静态内部类中实例化,然后访问。
-
-
可以添加任意访问修饰符,因为它的地位就是一个成员
-
作用域和其他的成员一样,是整个类体内部。
-
静态内部类可以在不依赖外部类的前提下被实例化
-
外部其他类访问静态内部类、静态内部类访问外部类、外部类访问静态内部类的逻辑都写在了代码里,如下所示
-
外部类和静态内部类成员重名时,静态内部类如果想要访问,则默认就近原则,如果想要访问外部类的同名成员,则需要
外部类名.成员
P.S. 内部类可以是静态的,但是顶层类不能是静态的。
public class AnonymousClass {
public static void main(String[] args) {
// 展示外部其它类访问静态内部类的方式
// 方式1,直接new
Outer.Inner inner = new Outer.Inner();
inner.sayHello();
// 方式2,编写一个方法,返回静态内部类的实例。
Outer.Inner inner1 = new Outer().getInnerInstance("java");
inner1.sayHello();
// 方式2的补充,可以在外部类中写静态方法,返回静态内部类的实例,没有本质区别
Outer.Inner inner2 = Outer.getInnerInstance_("HSP");
inner2.sayHello();
}
}
class Outer {
private int n1 = 10;
private static int n2 = 20;
// 静态内部类
public static class Inner {
private String name;
private static int count = 0;
private static int n2 = 30;
// 静态内部类可以有构造器
Inner(String name) {
this.name = name;
}
Inner() {
}
// 静态类里可以有普通方法
public void sayHello() {
// 展示静态内部类访问外部类的方式。
// System.out.println(n1); 不能直接访问外部类非静态成员变量
System.out.println(new Outer().n1); // 但是可以通过创建外部类实例来访问外部类非静态变量
System.out.println(Outer.n2); // 可以直接访问外部类静态变量
System.out.println(n2); // 直接访问遵循就近原则
System.out.println("Hello, I'm " + this.name); // 也可以访问本类非静态变量
System.out.println("Total count: " + count); // 也可以访问本类静态变量
}
}
// 接下来展示外部类如何访问静态内部类
public void accessInner() {
Inner inner = new Inner("HSP"); // 创建静态内部类对象
System.out.println(inner.name); // 访问静态内部类成员变量
System.out.println(Inner.count); // 访问静态内部类静态成员变量
}
public Inner getInnerInstance(String name) {
return new Inner(name); // 创建静态内部类对象并返回给调用方
}
public static Inner getInnerInstance_(String name) {
return new Inner(name); // 创建静态内部类对象并返回给调用方
}
}
综合练习
练习1
当前代码会不会报错?为什么?
public class Test {
class Inner {
public int a = 5;
}
public static void main(String[] args) {
Inner r = new Inner();
}
}
答案:
会报错,因为成员内部类在创建时依赖外部类的实例而存在,需要一个外部类作为容器,传统初始化方法为 “外部类实例.new 成员内部类名”
main方法是静态的,静态方法中没有this关键字,因而报错,下面这样做就可以
public class Outer {
public class Inner { }
// 实例方法(非静态方法)
public void show() {
Inner inner = new Inner(); // ✅ 可以!
// 等价于:this.new Inner();
// 因为实例方法中,this 指向当前 Outer 对象
}
}
练习2
下面这段代码会输出什么?
public class Test {
public Test() {
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
System.out.println(s2.a);
}
class Inner {
public int a = 5;
}
public static void main(String[] args) {
Test t = new Test();
Inner r = t.new Inner();
System.out.println(r.a);
}
}
答案:
5
5
分析:
Test构造器里初始化的 S1 和 S2 分别是两个不同的成员内部类实例,而 main 方法里初始化的 r 也是一个不同的成员内部类实例。三个方法中的 a 是独立的
1 个帖子 - 1 位参与者