共计 6464 个字符,预计需要花费 17 分钟才能阅读完成。
前言
说到 java 外部类,想必大家首先会想到比拟罕用的“匿名外部类”,但实际上,这只是外部类的其中一种应用形式而已。外部类的应用形式实际上总共包含:成员外部类,办法部分类,匿名外部类,上面,我就给大家来一一介绍:
为什么要应用外部类
有的时候你可能有这样一种需要:对一个类(假如它为 MyClass.java)创立一个和它相干的类(假如它是 Part.java),但因为 Part.java 和 MyClass 之间的分割“严密”且“繁多”,导致咱们在这种状况下,不心愿像上面这样减少一个额定的兄弟类
├─MyClass
└─Part
而心愿能将 Part.java 的数据暗藏在 MyClass.java 外部,于是这个时候外部类就堂而皇之地呈现了
那么,这个不请自来的外部类到底给咱们上述的场面造成了怎么的扭转呢?让咱们来看看:
减少一个额定的兄弟类 Part:
- 对一些没有关联的类可见(如果 protected 则对同一包内类可见,如果 public 则对所有类可见)
- 不能齐全自在的拜访 MyClass 中的公有数据(必须通过拜访器办法)
- 新增了一个 java 文件
应用外部类,将 Part 类的定义写入 MyClass 外部
- 能够缩小多余的可见性,例如可把 Part 在 MyClass 外部定义为公有,这样对同一包内其余类也不可见了
- 外部类(Part)能够自在拜访外围类的所有数据(MyClass),包含公有数据
- 缩小了一个 java 文件,使得类构造更简洁
成员外部类
故名思议,成员外部类嘛~ 应用当然和成员变量很类似咯
你能够像
private String data
这样定义一个“平行的”成员外部类:
private class Inner
具体看上面的例子:
Outter.java:
public class Outter {
// 成员变量 data
private String data = "内部数据";
// 定义一个外部类
private class Inner {public void innerPrint () {System.out.println(data);
}
}
// 外部类的办法,new 一个外部类的实例并调用其 innerPrint 办法
public void outterPrint () {Inner i = new Inner();
i.innerPrint();}
}
Test.java:
public class Test {public static void main (String [] args) {Outter o = new Outter();
o.outterPrint();}
}
后果输入:
内部数据
看来这还是能达到咱们预期的成果的:因为将 Inner 外部类设为 private,它变得只对咱们以后的外部类 Outter 类可见,咱们胜利地把它 ” 暗藏 ” 在了 Outter 类外部,与此同时,它还自在地拜访到了 Outter 类的公有成员变量 data
两个 this
尽管下面的例子看起来挺简略的,但实际上外部类的作用机制还是比较复杂的。
首先要思考的是“this”的问题,外部类和外部类各有一个 this,关键在于外部类中咱们如何对这两个 this 作出辨别:
咱们假如下面的例子中的 Inner 类外部有一个办法 fn:
private class Inner {public void fn () {
Outter.this // 指向 Outter 实例对象的 this 援用
this // 指向 Inner 实例对象的 this 援用
}
}
在这个办法 fn 里,Outter.this 是指向 Outter 实例对象的 this 的援用,而 this 是指向 Inner 实例对象的 this 的援用
咱们拜访类中成员变量有两种形式:隐式拜访(不加 this)和显式拜访(加 this)
隐式拜访类中成员变量
让咱们对下面的 Outter.java 做一些改变,减少一行代码:
public class Outter {
// 成员变量 data
private String data = "内部数据";
// 定义一个外部类
private class Inner {
// 减少 Inner 类对 data 成员变量的申明
private String data = "外部数据"
public void innerPrint () {System.out.println(data);
}
}
// 外部类的办法,new 一个外部类的实例并调用其 innerPrint 办法
public void outterPrint () {Inner i = new Inner();
i.innerPrint();}
}
后果输入:
外部数据
如此可见,外部类内申明的数据会笼罩外部类的同名数据。或者说, 在上述例子中,对于 data 成员变量,它会首先在 Inner 的 this 中查找有无这个成员变量,而后没有,那么就再在 Outter.this 中查找
显式拜访类中成员变量
但有的时候咱们心愿既能拜访外部类的成员变量,同时也能拜访外部类的成员变量,这个时候咱们就要应用到 this 了,然而如何辨别外部类和外部类的 this 呢?你能够这样:
以上述例子为例:
拜访外部类定义的成员变量:Outter.this.data
拜访外部类定义的成员变量:this.data
如下图所示
public class Outter {
// 外部类的成员变量 data
private String data = "内部数据";
// 定义一个外部类
private class Inner {
// 外部类的成员变量 data
private String data = "外部数据";
public void innerPrint () {System.out.println(Outter.this.data);
System.out.println(this.data);
}
}
// 外部类的办法,new 一个外部类的实例并调用其 innerPrint 办法
public void outterPrint () {Inner i = new Inner();
i.innerPrint();}
}
部分外部类
部分外部类是外部类的第二种模式,它让外部类的“暗藏”得更深一层——写在外部类的办法外部,而不是处于和外部类办法平行的地位。
让咱们对下面成员外部类解决的场景做些思考:咱们的 Inner 外部类仅仅只在 outterPrint 办法中应用了一次:
public void outterPrint () {Inner i = new Inner();
i.innerPrint();}
那么咱们能不能把 Inner 外部类间接定义在 outterPrint 的外部呢?这样的话,它就能更好地暗藏起来,即便是类 Outter 中除 outterPrint 外的办法,也不能拜访到它:
当初的 Outter 的类看起来像这样:
public class Outter {public void outterPrint () {// 外部类办法
class LocalInner { // 部分外部类
public void innerPrint () {}
}
LocalInner i = new LocalInner(); // 实例化部分外部类
i.innerPrint();}
}
相比于成员外部类,部分外部类多了一项能拜访的数据,那就是局部变量(由外部类办法提供)
成员外部类:外部类数据,外部类数据
部分外部类:外部类数据,外部类数据,部分数据
具体示例如下:
Outter.java
public class Outter {
private String data = "内部数据"; // 外部类数据
public void outterPrint (final String localData) { // 部分数据
class LocalInner {
private String data = "外部数据"; // 外部类数据
public void innerPrint () {System.out.println(Outter.this.data); // 打印外部类数据
System.out.println(this.data); // 打印外部类数据
System.out.println(localData); // 打印部分数据
}
}
LocalInner i = new LocalInner();
i.innerPrint();}
}
Test.java:
public class Test {public static void main (String [] args) {Outter o = new Outter();
o.outterPrint("部分数据");
}
}
后果输入:
内部数据
外部数据
部分数据
部分类所应用的外部类办法的形参必须用 final 润饰
这里要留神一点,部分类所应用的外部类办法的形参必须用 final 润饰,否则会编译不通过,也就是说传入后不许扭转
为什么这个办法形参肯定要用 final 润饰?
(仅集体了解,如有不同的意见或者更好的了解欢送在评论区探讨)
如果不必 final 润饰会怎么?且听我缓缓道来:
首先要说一下:
1. 外部类和外部类在编译之后模式上是一样的,不会有内外之分
2. 部分外部类对于应用的内部办法的值会用构造函数做一个拷贝(编译后)
例如对于上面 outterPrint 办法中的 LocalInner
public void outterPrint (final String data) {
class LocalInner {public void innerPrint () {// 应用 data}
}
}/./* 欢送退出 java 交换 Q 君样:909038429 一起吹水聊天
编译之后大略长这样:
public class Outter$LocalInner{public LocalInner(String data){this.LocalInner$data = data; // 对于应用的 data 做了一次拷贝}
public void innerPrint (){ /* 应用 data */}
}
这里要留神的是:
- 编译后,LocalInner 并非间接应用 data,而是用结构器拷贝一份后再应用
- java 是值传递的,所以包裹 LocalInner 的内部办法 outterPrint 也会对传入的 data 参数做一次拷贝(根本类型数据拷贝正本,对象等则拷贝援用)
OK,当初的状况是:
办法内的部分类对 data 拷贝了两次:内部办法 outterPrint 值传递时的拷贝,和 LocalInner 构造函数的拷贝
办法内除了部分类外的作用域只拷贝了 data 一次:内部办法 outterPrint 值传递时的拷贝
拷贝两次和拷贝一次,导致在 outterPrint 办法外部,部分类外部的 data 和部分类内部的 data 是不同步的!也即你在部分类外部改了 data 不影响部分类内部的 data,在部分类内部改了 data 也不影响部分类外部的 data(留神一个前提,值是根本类型的,如果是对象的话因为拷贝的是援用依然能够“同步”)
图示一:
图示二:
于是 java 说:哎呀妈呀,这都 data 都不同步了,要是让你批改这还了得!!! 于是就强行要求咱们加上 final
【留神】所谓的不同步次要是针对根本类型来说的,如果是对象之类的话因为拷贝的是援用所以依然能够“同步”
如何冲破必须用 final 的限度
咱们下面说到,部分外部类所应用的办法形参必须用 final 润饰的限度。
例如
public void outterPrint (String data) {// 没加上 final
class LocalInner {public void changeData () {data = "我想批改 data 的值"; // 在这一行编译报错}
}
}
提醒:
Cannot refer to a non-final variable data inside an inner class defined in a different method
那么,如果咱们有对该形参必须能批改的硬性需要怎么办?
你能够通过一种乏味的形式绕开它:应用一个单元素数组。因为用 final 润饰的根本类型的变量不容许批改值,然而却容许批改 final 润饰的单元素数组里的数组元素,因为寄存数组的变量的值只是一个援用,咱们批改数组元素的时候是不会批改援用指向的地址的,在这点上 final 并不会障碍咱们:
Outter.java
public class Outter {public void outterPrint (final String [] data) {
class LocalInner {public void innerPrint () {data[0] = "堂而皇之地批改它!!"; // 批改数据
System.out.print(data[0]); // 输入批改后的数据
}
}
LocalInner i = new LocalInner();
i.innerPrint();}
}
Test.java:
public class Test {public static void main (String [] args) {Outter o = new Outter();
String [] data = new String [1];
data[0] = "我是数据";
o.outterPrint(data); // 批改数据并且输入
}
}
后果输入:
堂而皇之地批改它!!
【留神】部分类不能用 public 或 private 拜访符进行申明!!
匿名外部类
假使咱们再把部分外部类再深入一下,那就是匿名外部类
匿名外部类的应用形式
new [超类 / 接口] {/* 类体 */}
让咱们看看上面这个例子:
Other.java:
public class Other {}
Outter.java:
public class Outter {public void outterPrint (String data) {Other o = new Other() {}; // 匿名外部类}
}
何谓之匿名?
“诶,不是说好的匿名吗?那么为什么还有个 Other 的类名呢?”
Other o = new Other() { /* 匿名外部类的类体 */};
实际上,这里的 Other 并不是咱们的匿名外部类,而是咱们匿名外部类的超类,下面一行代码其实相当于(用成员外部类来示意的话)
// annoymous 翻译为匿名
public class Outter {private class annoymous extends Other{}
public void outterPrint () {Other a = new annoymous();
}
}
同时要留神,咱们在应用匿名外部类的形式,是在定义一个外部类的同时实例化该外部类:
new Other() { /* 匿名外部类的类体 */}; // new 操作和定义类的代码是紧紧联合在一起的
匿名函数的作用
用匿名函数的作用在于在一些特定的场景下写起来很简略,例如事件监听器:
ActionListener listener = new ActionListener() {public void actionPerformed(ActionEvent e) {}};
防止了再创立另外一个类文件
讲的有点乱,对匿名外部类做个总结:
- 省略被定义的类的类名
- 必须联合超类或者接口应用,即 new [超类 / 接口] {/ 类体 / }
- 在定义该匿名类的同时实例化该匿名类
- 在一些场景下能简化代码
【留神】匿名类不能有结构器,因为结构器和类同名,而匿名类没有类名,所以匿名类不能有结构器
文章总结
咱们应用外部类的起因次要有三点:
1. 实现数据暗藏,防止多余的可见性
2. 自在拜访外部类的变量
- 在应用监听器等场景的时候应用匿名外部类,防止减少的大量代码
对于成员外部类,办法部分类,匿名外部类的关系
从成员外部类,办法部分类到匿名外部类是一个不断深入的关系,成员外部类进一步暗藏可见性就成为了办法部分类,办法部分类省去类名,并将类的定义和实例化操作合并到一起,就是匿名外部类。因而,匿名外部类因循了成员外部类和办法部分类的根本特个性
外部类的一些非凡的要求
1. 部分类不能用 public 或 private 拜访符进行申明
2. 部分类所应用的外部类办法的形参必须用 final 润饰
- 匿名外部类不能有结构器
最新 2020 整顿收集的一些高频面试题(都整顿成文档),有很多干货,蕴含 mysql,netty,spring,线程,spring cloud、jvm、源码、算法等具体解说,也有具体的学习规划图,面试题整顿等,须要获取这些内容的敌人请加 Q 君样:909038429
/./* 欢送退出 java 交换 Q 君样:909038429 一起吹水聊天