今天接触了一天的 Java 内部类,这个东西给我感觉一点就是:变化多端。之所以说这个东西变化多端,后面我们会用一些例子来证明,通过不同的形式来实现同一个方法。
内部类,可以分为以下四种
- 成员内部类:创建在类内方法外,和成员变量及成员方法类似。
- 局部内部类:创建在方法内部,类似局部代码块。
-
静态内部类:用
static
修饰的内部类。 - 匿名内部类:也叫匿名子类对象,最特殊也是最常用的一种类型;一般用在方法参数的位置,匿名子类对象的类内方法也叫做 闭包。
1.创建内部类类对象格式
- 如果我们想访问一个内部类时,我们需要通过
外部类名.内部类名
来找到这个内部类。 - 在我们创建下面👇这个内部类对象的格式是这样的:
外部类.内部类 对象名 = 外部类对象.内部类对象
class Outer {
class Inner {
public void method() {
System.out.println("Hello Inner");
}
}
}
class Sample_InnerClass01 {
public static void main(String[] args) {
// 外部类.内部类 对象名 = 外部类对象.内部类对象;
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}
Tip👉:直接通过这种方式在创建内部类对象是不常用、也不推荐的方式,下面我们会介绍其他调用内部类的方式。
2.成员内部类私有的使用
- 我们知道面向对象一个重要原则是封装,就像我们会私有化成员变量一样,在一般情况下内部类也是需要进行私有化,并提供外部访问接口。
- Sample 中我们私有化了
Inner
内部类,提供了print
方法供外部调用。
class Outer {
private int num = 10;
private class Inner {
public void method() {
System.out.println("Print: " + num);
}
}
public void print() {
Inner in = new Inner();
in.method();
}
}
class Sample_InnerClass02 {
public static void main(String[] args) {
Outer o = new Outer();
o.print();
}
}
Thinking💁♂️:本例中为什么内部类能访问到外部类的成员变量?
3.静态成员内部类
- 与创建非静态内部类对象不同的是,创建内部类对象格式有些不同。
- 静态内部类对象创建格式:
外部类.内部类 对象名 = 外部类名.内部类对象
class Outer {
private static int num = 10;
static class Inner {
public void method() {
System.out.println("Print: " + num);
}
}
}
class Sample_InnerClass03 {
public static void main(String[] args) {
// 外部类.内部类 对象名 = 外部类名.内部类对象;
Outer.Inner oi = new Outer.Inner();
oi.method();
}
}
Reminder👨💻:静态内部类中只能访问本类的静态成员。
4.成员内部类的小练习🐣
- 内部类之所以能获取到外部类的成员,是因为它能获取到外部类的引用
外部类名.this
。
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
// 内部类之所以能获取到外部类的成员,是因为它能获取到外部类的引用 外部类名.this
System.out.println(Outer.this.num); // 10
}
}
}
class Sample_InnerClass04 {
public static void main(String[] args) {
new Outer().new Inner().show();
}
}
5.局部内部类访问局部变量
- 在内存中
num
会随着 method 方法的出栈而释放,而内部类在堆区还没有消失。JVM 默认为局部变量添加final
,其实是将num
复制 了一份提供给局部内部类来访问,而之前真正的局部变量num
已经被释放,所以此时将局部变量用final
修饰为一个常量。 - 如果在内部类中改变
num
的值就会出现这样的错误提示:错误: 从内部类引用的本地变量必须是最终变量或实际上的最终变量
。
class Outer {
public void method() {
int num = 10; // 在 JDK1.8之后 JVM 会自动加上 final 修饰符
class Inner {
public void print() {
System.out.println(num);
}
}
Inner in = new Inner();
in.print();
}
}
class Sample_InnerClass05 {
public static void main(String[] args) {
new Outer().method();
}
}
6.匿名子类对象概述
- 概述:实际就是内部类的简化写法。
- 前提:存在一个 类 或 接口 ,这里的类可以是具体类也可以是抽象类。
- 格式:
new 类名或接口() {
重写方法;
}
- 本质:是一个继承了该类 或 者实现了该接口的 匿名子类对象。
interface Inter {
public abstract void print1();
}
class Outer {
public void method() {
Inter in = new Inter() {
public void print1() {
System.out.println("Print1");
}
};
in.print1();
}
}
class Sample_InnerClass06 {
public static void main(String[] args) {
new Outer().method();
}
}
7.匿名子类对象重写多个方法的调用
- 使用原则:匿名子类对象只针对重写一个方法的时候使用。
- 弊端:不能定义、调用子类特有的方法,且没有子类类名。
interface Inter {
public void show1();
public void show2();
}
class Outer {
public void method() {
Inter in = new Inter() { // 父类引用指向子类对象(多态)
public void show1() {
System.out.println("show1");
}
public void show2() {
System.out.println("show2");
}
};
in.show1();
in.show2();
System.out.println(in);
}
}
class Sample_InnerClass07 {
public static void main(String[] args) {
new Outer().method();
}
}
Discussion🤡:
Inter
父类指向匿名子类对象,其实是一个多态,如果在子类对象中定义了父类中不存在的方法,则编译会报错。而如果创建一个子类对象来实现接口方法的话,就可以扩展子类特有的功能了。
8.接口对象作为参数传递的多种实现形式
interface Inter {
void print();
}
class Demo {
public static void method(Inter i) {
i.print();
}
}
class InterClass implements Inter {
public void print() {
System.out.println("Hello World1!");
}
}
class Test {
private static final Inter i3;
static {
i3 = new Inter() {
public void print() {
System.out.println("Hello World3!");
}
};
}
public static void main(String[] args) {
// 1.创建一个实现接口的子类对象。
InterClass i1 = new InterClass();
Demo.method(i1);
// 2.在方法中创建一个匿名子类并返回。
Inter i2 = method();
Demo.method(i2);
// 3.定义一个 Inter类型的 static 成员变量,并在构造代码块中用赋值它的子类对象。
Demo.method(i3);
// 4.在成员方法的参数位置创匿名子类对象,并重写父类方法。(推荐方式)
Demo.method(new Inter() {
public void print() {
System.out.println("Hello World4!");
}
});
}
public static Inter method() {
return new Inter() {
public void print() {
System.out.println("Hello World2!");
}
};
}
}
Discussion🤡:可以看到,通过子类实现 抽象类 与 接口 的方式千变万化,语法的掌握是基础,但最终目的是为了提高代码的内聚性、减少耦合性并简化代码,只有从这个目的出发去使用我们的内部类才能让我们的编程能力得到提高。
9.悄悄话
- 这是我个人发表的第 2 篇简书 Blog,第一篇文章 JavaSE 基础(六)构造函数 试着投了首页和几个关于 Java 的专题,都顺利通过了,甚至还被专题推荐了文章,真的是没想到的。今天写这篇技术 Blog 完全是受着大家的鼓励而写出来的。
- 因为这段时间本人正在 JavaSE 的闭关修行中,没有太多时间去查阅相关资料和做文章优化,所以有些内容可能会有差错,也请读者们在评论中批评并指出问题。
-
今天开通了简书专题 JavaSE 成长之路,主要为一样正在 JavaSE 修行中的简友们提供了技术交流的平台,希望大家多多投稿交流互动。
网友评论