前言:我们继续书接上回讲解内部类。我们今天这篇文章就彻底把内部类这一大板块完结了,大家看完一定会有很大的启发的!!!
希望给小编一个免费的赞鼓励一下(点个关注我们一起进步!!!!)
正式开始之前,我们先回顾一下之前学习了哪些内容。
第一篇文章:
①咱们通过 官方术语 和 个人比喻 的方式对比着讲述了内部类和人们口中常说的哪个类的区别再哪里?
②具体在5个角度(定义位置,访问权限,生命周期,内存开销,使用场景)和 使用举代码例子 进行了全方位拆解,让大家在实践中,更加深刻地理解。
③比较粗略的讲述了,内部类这个概念是怎么提出来的?优化或者解决了生活实际应用中的的哪些问题?
④通过内部类分类的简单 思维导图 ,引入第二篇文章我们需要讲解的细节。
第二篇文章:
①通过内部类分类的 思维导图 细致讲解了内部类的前一半内容(成员内部类)
我们采取的是 人话拆解 和 代码实例 的形式细致讲解了怎么使用每一个小的知识点。
第三篇文章:
我们继续将思维导图的后半部分(局部内部类),通过 人话拆解 和 代码实例 的形式细致拆解。
局部内部类:
概述:
局部内部类分为: 非匿名局部内部类 和 匿名局部内部类。
对比局部内部类 和 成员内部类:
开始之前,我们先简单对 局部内部类 和 成员内部类 做一个对比理解!!
成员内部类
①我把官方对于这个概念的专业术语总结一些特点:
- 成员内部类是定义在另一个类的内部,但不在方法或代码块中的类。
- 它们默认拥有对外部类所有成员的访问权限。
- 每个成员内部类的对象都隐含地保存了一个引用,指向创建它的外部类对象。
- 成员内部类可以声明为public、private、protected或具有包级可见性,也可以使用static修饰符来创建静态内部类。
②我对这些概念特点的人话拆解:
- 成员内部类就像是一个“家庭成员”,它属于这个家庭(外部类),并且知道家庭里所有的秘密(可以访问外部类的所有成员)。
- 它总是伴随着家庭的存在而存在,只要家庭还在,它就可以被创建。
- 这个家庭成员有明确的身份(访问修饰符),比如它可以是公开的、私有的等。
局部内部类 :
①我把官方对于这个概念的专业术语总结一些特点:
- 局部内部类是在方法、构造器或初始化块中定义的类。
- 它只能在定义它的方法或代码块内实例化。
- 该类不能用访问修饰符(如public、private、protected)来修饰,因为它不是类的一部分,而是方法或代码块的一部分。
- 局部内部类可以访问定义它的方法或代码块中的final或有效final(从Java 8开始)的局部变量。
- 局部内部类同样可以访问外部类的所有成员,包括私有成员。
②我对这些概念特点的人话拆解:
- 局部内部类更像是一个“临时工”或者“访客”,它只在一个特定的任务(方法或代码块)中出现。
- 它的工作范围非常有限,只有在任务需要的时候才会被雇佣(实例化),任务完成后,它就不再有用。
- 由于它是临时的,所以不需要长期的身份标识(没有访问修饰符)。
- 这个临时工可以接触到任务相关的所有信息(方法或代码块中的final或有效final局部变量),以及家庭的秘密(外部类的所有成员)。
对比总结一下:
如果把外部类比作一家公司:
成员内部类 就像是公司的正式员工,他们有固定的岗位和职责,可以接触公司内部的各种资源;
而 局部内部类 则像是为了完成某个特定项目临时聘请的顾问,项目结束后他们的任务也就完成了。
举个代码例子理解一下!!
public class OuterClass {
private int outerField = 10;
// 成员内部类
public class MemberInnerClass {
public void printOuterField() {
System.out.println("Member inner: " + outerField);
}
}
// 方法,包含局部内部类
public void someMethod(final String param) {
// 局部内部类
class LocalInnerClass {
public void printOuterAndParam() {
System.out.println("Local inner: " + outerField + ", " + param);
}
}
// 实例化局部内部类并调用其方法
new LocalInnerClass().printOuterAndParam();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// 使用成员内部类
OuterClass.MemberInnerClass memberInner = outer.new MemberInnerClass();
memberInner.printOuterField();
// 使用局部内部类
outer.someMethod("Parameter");
}
}
代码 解释 和 提取 :
①MemberInnerClass是一个成员内部类,可以在main方法中创建它的实例,并且它能够访问外部类的outerField字段。
②另一方面,LocalInnerClass是someMethod方法内的局部内部类,它只能在someMethod方法内实例化,并且它不仅能够访问外部类的outerField字段,还能访问传递给someMethod方法的param参数。
ok,现在我们 对比 了 成员内部类 和 局部内部类 之后,正式开始讲解 局部内部类 的细节内容部分!!!
局部内部类——非匿名局部内部类
语法格式:
[修饰符] class 外部类{
[修饰符] 返回值类型 方法名(形参列表){
[final/abstract] class 内部类{
}
}
}
特点 和 使用规则:
• 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、 编号。
• 和成员内部类不同的是,它前面不能有权限修饰符等
• 局部内部类如同局部变量一样,有作用域
• 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法
大家大多时候对于这些专业的术语不太能理解清楚,就是因为一句话里面 最关键的那个名词 没理解清楚吧。所以,小编我对紫色标注的关键词给大家解释一下,大家就可以无师自通了。
这个方法对于以后学习任何其他的知识都有帮助!!!
人话拆解:
假如说你正在组织一场大型活动(外部类 Event),你需要雇佣一些工作人员来帮助你完成不同的任务。对于某些特定的任务,你可能会临时雇佣一些特别的助手(局部内部类 Helper)。这些助手只在特定的任务中工作,并且一旦任务完成,他们就不再需要了。
独立的字节码文件:
当活动结束后,每个特别助手的工作都被记录下来,存放在一个单独的文件里,但文件名会包含活动的名字、一个特殊符号 $ 以及一个编号。比如,如果你有两个不同任务中都叫 Helper 的助手,他们的记录文件可能是 Event$1Helper.class 和 Event$2Helper.class。
没有权限修饰符:这些特别助手的身份是临时的,所以他们不需要长期的身份标识(如 public 或 private);他们只是为了一项具体任务而存在。
作用域:特别助手只能在他们被雇佣的那个任务中工作,不能参与其他任务。一旦任务结束,他们就离开了,这意味着你不能在其他地方再次调用他们。
访问外部类成员:特别助手可以访问活动(外部类)的所有资源(非静态成员),因为他们是在活动中工作的。但是,如果他们在某个特定任务(方法)中工作,他们能访问的外部资源取决于该任务本身是否允许。例如,如果任务是在一个特定的会议室(非静态方法)中进行的,那么助手就可以访问这个会议室里的所有东西。但如果任务是在整个活动场地(静态方法)上进行的,那助手就只能访问那些对所有人开放的资源(静态成员)。
举个栗子!!!!
public class Event {
private String eventName = "Annual Conference"; // 非静态成员
static int attendeeCount = 500; // 静态成员
// 成员内部类
public class Staff {
void showEventDetails() {
System.out.println("Event name: " + eventName);
}
}
// 方法,包含局部内部类
public void setupRoom(final String roomName) {
// 局部内部类
class Helper {
void prepareRoom() {
System.out.println("Preparing " + roomName + " for the " + eventName);
}
}
// 实例化局部内部类并调用其方法
new Helper().prepareRoom();
}
// 静态方法,包含局部内部类
public static void countAttendees() {
// 局部内部类
class Counter {
void printCount() {
// 不能直接访问非静态成员eventName,因为这里是一个静态上下文
// System.out.println(eventName); // 这行代码会导致编译错误
System.out.println("Total attendees: " + attendeeCount);
}
}
// 实例化局部内部类并调用其方法
new Counter().printCount();
}
public static void main(String[] args) {
Event event = new Event();
// 使用成员内部类
Event.Staff staff = event.new Staff();
staff.showEventDetails();
// 使用局部内部类 (setupRoom)
event.setupRoom("Main Hall");
// 使用局部内部类 (countAttendees)
Event.countAttendees();
}
}
代码解释和总结:
①Staff 是一个成员内部类,它可以自由地访问 Event 类的非静态成员 eventName。
②setupRoom 方法中的 Helper 是一个局部内部类,它同样可以访问 Event 类的非静态成员 eventName 以及传递给 setupRoom 方法的参数 roomName。
③然而,在静态方法 countAttendees 中定义的 Counter 局部内部类只能访问 Event 类的静态成员 attendeeCount,因为它自己位于一个静态上下文中。
④当你编译这段代码时,编译器会产生多个 .class 文件,包括 Event.class、Event$Staff.class、Event$1Helper.class 和 Event$2Counter.class。
⑤其中 Event$1Helper.class 和 Event$2Counter.class 分别对应 setupRoom 和 countAttendees 方法中的局部内部类,它们的名字包含了外部类名 Event、分隔符 $ 以及一个表示顺序的数字,这是因为两个局部内部类有相同的名字 Helper 和 Counter。
局部内部类——匿名内部类:
概述:
①匿名内部类(Anonymous Inner Class)是Java中的一种特殊的内部类,它没有名字,并且是在创建对象的同时定义的。
②匿名内部类通常用于只需要创建一次的对象,特别是当我们需要实现接口或继承抽象类,并且只需要一个实例时。
③匿名内部类可以直接调用方法,但需要注意的是,它们只能在定义它们的地方使用。
因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名 字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类 命名的问题。
我们通过据三个实际应用的例子来让大家更直观的了解清楚匿名内部类!!!
1)使用匿名内部类的对象直接调用方法:
2)通过父类或父接口的变量多态引用匿名内部类的对象
3)匿名内部类的对象作为实参
1)使用匿名内部类的对象直接调用方法:举个栗子:
// 定义一个接口
interface Speaker {
void speak();
}
public class AnonymousInnerClassExample {
// 一个使用匿名内部类的方法
public void useSpeaker() {
// 创建匿名内部类并立即调用其方法
new Speaker() {
@Override
public void speak() {
System.out.println("Hello from the anonymous inner class!");
}
}.speak(); // 直接调用speak方法
}
public static void main(String[] args) {
AnonymousInnerClassExample example = new AnonymousInnerClassExample();
example.useSpeaker(); // 调用useSpeaker方法,这将触发匿名内部类的speak方法
}
}
输出结果:
Hello from the anonymous inner class!
代码解释和总结:
接口 Speaker:我们首先定义了一个名为 Speaker 的接口,它包含一个名为 speak 的抽象方法。这个接口代表了可以“说话”的能力,任何实现了这个接口的类都必须提供 speak 方法的具体实现。
useSpeaker 方法:在这个方法中,我们创建了一个匿名内部类来实现 Speaker 接口。匿名内部类的定义和实例化是在同一行完成的,即 new Speaker() { ... }。在这个大括号内,我们重写了 speak 方法,提供了具体的实现逻辑。
直接调用 speak 方法:在创建匿名内部类实例后,我们立刻在其后面加上 .speak() 来直接调用该方法。这是因为我们在创建匿名内部类的时候已经知道了我们需要调用的方法,所以可以在创建对象的同时调用它。
main 方法:这是程序的入口点。在这里,我们创建了 AnonymousInnerClassExample 类的一个实例,并调用了它的 useSpeaker 方法。当 useSpeaker 方法被调用时,它会创建匿名内部类的实例并调用 speak 方法,从而输出一条消息到控制台。
2)通过父类或父接口的变量多态引用匿名内部类的对象举个栗子!!
通过父类或父接口的变量来多态引用匿名内部类的对象,是Java中实现多态性的一种常见方式。这种方式允许我们使用一个统一的接口或抽象类来处理不同类型的对象,而这些对象可能是在运行时动态创建的。
// 定义一个接口
interface Animal {
void makeSound();
}
public class PolymorphicAnonymousInnerClassExample {
// 一个使用匿名内部类并返回其引用的方法
public Animal getAnimal(String type) {
if ("Dog".equalsIgnoreCase(type)) {
return new Animal() { // 创建匿名内部类
@Override
public void makeSound() {
System.out.println("Woof!");
}
};
} else if ("Cat".equalsIgnoreCase(type)) {
return new Animal() { // 创建另一个匿名内部类
@Override
public void makeSound() {
System.out.println("Meow!");
}
};
} else {
return new Animal() { // 默认的匿名内部类
@Override
public void makeSound() {
System.out.println("Unknown animal sound");
}
};
}
}
public static void main(String[] args) {
PolymorphicAnonymousInnerClassExample example = new PolymorphicAnonymousInnerClassExample();
// 使用父接口的变量来引用匿名内部类的对象
Animal dog = example.getAnimal("Dog");
Animal cat = example.getAnimal("Cat");
Animal unknown = example.getAnimal("Bird");
// 调用方法
dog.makeSound(); // 输出: Woof!
cat.makeSound(); // 输出: Meow!
unknown.makeSound();// 输出: Unknown animal sound
}
}
运行结果:
Woof!
Meow!
Unknown animal sound
代码解释和总结:
接口 Animal:我们定义了一个名为 Animal 的接口,它包含一个名为 makeSound 的抽象方法。这个接口代表了所有动物都能发出声音的能力,任何实现了这个接口的类都必须提供 makeSound 方法的具体实现。
getAnimal 方法:在这个方法中,我们根据传入的参数 type 来决定返回哪种动物的声音。这里我们使用了三个不同的匿名内部类来分别实现 Dog、Cat 和默认未知动物的行为。每个匿名内部类都实现了 Animal 接口,并重写了 makeSound 方法以提供特定的实现逻辑。
多态引用:在 main 方法中,我们创建了 PolymorphicAnonymousInnerClassExample 类的一个实例,并调用了它的 getAnimal 方法。getAnimal 方法返回的是 Animal 接口的引用,但实际上指向的是匿名内部类的对象。这意味着我们可以使用同一个类型(即 Animal)的变量来引用不同类型的对象,这就是所谓的多态性。
调用 makeSound 方法:我们通过 Animal 接口的变量 dog、cat 和 unknown 来调用 makeSound 方法。尽管它们都是 Animal 类型的变量,但实际调用的是各自匿名内部类中重写的 makeSound 方法,因此会输出相应动物特有的声音。
3)匿名内部类的对象作为实参 举个栗子!!!
匿名内部类的对象作为实参传递给方法或构造器,是Java中一种常见且灵活的编程技巧。它允许我们在调用方法或创建对象时直接提供一个实现,而无需事先定义一个具体的类。这种方式特别适合于那些只需要一次性的、简单的实现,比如事件监听器、线程启动等。
// 定义一个接口
interface Task {
void execute();
}
// 定义一个接收Task接口作为参数的方法
class TaskExecutor {
public void runTask(Task task) {
System.out.println("Executing the task...");
task.execute(); // 调用传入的任务
System.out.println("Task completed.");
}
}
public class AnonymousInnerClassAsArgumentExample {
public static void main(String[] args) {
TaskExecutor executor = new TaskExecutor();
// 将匿名内部类的对象作为实参传递给runTask方法
executor.runTask(new Task() { // 创建匿名内部类
@Override
public void execute() {
System.out.println("This is an anonymous task being executed!");
}
});
// 另一个匿名内部类作为实参传递
executor.runTask(new Task() {
@Override
public void execute() {
System.out.println("This is another anonymous task with different behavior.");
}
});
}
}
运行结果:
Executing the task...
This is an anonymous task being executed!
Task completed.
Executing the task...
This is another anonymous task with different behavior.
Task completed.
代码解释和总结:
接口 Task:我们定义了一个名为 Task 的接口,它包含一个名为 execute 的抽象方法。这个接口代表了可以执行的任务,任何实现了这个接口的类都必须提供 execute 方法的具体实现逻辑。
TaskExecutor 类:这是一个辅助类,它有一个名为 runTask 的方法,该方法接收一个 Task 接口类型的参数。runTask 方法的作用是打印一些信息,然后调用传入的 task 对象的 execute 方法来执行任务,最后再打印一条完成的信息。
main 方法:这是程序的入口点。在这里,我们创建了 TaskExecutor 类的一个实例 executor。然后,我们两次调用了 executor 的 runTask 方法,每次都将一个新的匿名内部类的对象作为实参传递给它。
匿名内部类作为实参:
在第一次调用 runTask 时,我们创建了一个匿名内部类,它实现了 Task 接口,并重写了 execute 方法。在这个方法中,我们定义了任务的具体行为——打印一条消息。
在第二次调用 runTask 时,我们又创建了另一个匿名内部类,它同样实现了 Task 接口,但这次 execute 方法的行为不同——打印另一条不同的消息。
ok,小编今天的分享到此为止,我相信一定会对大家有帮助的。
——键盘敲烂 ,月薪过万!!!!