同窗们,这一次讲座,咱们讲一下Inner Class内部类。
咱们平时写的程序是由一个个类构成的,这些类之间是相互独立的关系。咱们说过,这种思路源自对现实世界的模拟,拉近了“问题空间”和“解决空间”。所以简化了系统的设计。
而Inner class 内部类是指一个类是另外一个类的内部成员,定义在某个类的内部的,对外可能可见也可能不可见。java
基本形式仍是蛮简单的,咱们看一个例子:编程
public class OuterClass {
private String outerName;
public void display(){
System.out.println("OuterClass display...");
System.out.println(outerName);
}
public class InnerClass{
private String innerName;
InnerClass(){
innerName = "inner class";
}
public void display(){
System.out.println("InnerClass display...");
System.out.println(innerName);
}
}
}
如上面的代码所示,这样在OuterClass里面就定义了一个InnerClass类。使用的时候能够这么用:安全
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.display();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
}
从代码能够看出,内部类跟一个普通属性同样使用,只要在外部类建立好的状况下,就能够去使用,能够明显看出这种被包含的关系。
并且,外部的程序能这么用的一个前提是内部类声明为public,这样才会为外部程序所见,这个跟普通属性也是同样的。固然,这个public不是约束包含的那个外部类的,不管把内部类声明为public仍是private,对包含它的外部类都是可见的。闭包
外部类和内部类的这种包含关系,决定了互相能够调用。代码以下(OuterClass.java):异步
public class OuterClass {
private String outerName;
public OuterClass(){
outerName="OurClass Default Name";
}
public void display(){
System.out.println("OuterClass display...");
System.out.println(outerName);
}
public void displayInner(){
InnerClass innerClass=new InnerClass();
innerClass.display();
}
private class InnerClass{
private String innerName;
InnerClass(){
outerName="outer class new name";
innerName = "inner class default name";
}
public void displayOuter(){
System.out.println(outerName);
}
public void display(){
System.out.println("InnerClass display...");
System.out.println(innerName);
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.display();
outerClass.displayInner();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
innerClass.displayOuter();
}
}
咱们在OuterClass里面用下面两行建立了内部类并调用方法:ide
InnerClass innerClass=new InnerClass();
innerClass.display();
而在内部类里面,咱们直接使用了外部内的属性。这一段演示了外部类内部类的互操做。编译器会作一个处理,对上面的内部类方法:函数式编程
public void displayOuter(){
System.out.println(outerName);
}
编译以后的代码会变成:函数
OuterClass.this.outerName。
好奇的人通常会去bin目录下看一眼编译以后的结果,生成了两个class文件,OuterClass.class和OuterClassthis
除了上述的基本型内部类,内部类还能够定义成静态的,或者在一个方法体内进行定义,如:spa
void method1() {
class InnerClass {
public void print() {
System.out.println("method inner class.");
}
}
InnerClass inner = new InnerClass();
inner.print();
}
这个简单,这里再也不完整举例。下面举一个静态内部类的例子,代码以下(Employee.java):
public class Employee{
public String empName;
public Company company;
public Employee(String empName){
this.empName = empName;
}
public static class Company{
public String compName;
public String compRegion;
public Company(String compName,String compRegion){
this.compName = compName;
this.compRegion = compRegion;
}
}
public static void main(String[] args) {
Company company = new Employee.Company("Sun/Oracle", "China");
Employee alice = new Employee("Alice");
Employee bob = new Employee("Bob");
alice.company = company;
bob.company = company;
}
}
上面的Employee里面包含一个静态内部类Company,这样跟一个普通类相似了,外部程序能够直接建立这个内部类。并且多个外部类employee能够共享同一个内部类company。要注意的是,这个时候,外部类能够访问内部类,而内部类访问不了外部类。大家能够本身动手试一下,在Employee中增长一个test(),而后试着在Company中调用,会有编译错误:
Cannot make a static reference to the non-static method test() from the type Employee。
有了内部类的这些基础知识,下面咱们要讨论进阶一点的内容了。
有一种颇有用的场合时匿名内部类。它看起来就是一个常规的内部类,可是不显式起名,所以叫匿名类。这个场景,定义和实例化是写在一块儿同时的。当咱们须要实现一个接口或者抽象类的时候,咱们常常这么用。
咱们看一个监听器响应事件触发的例子。代码以下:
先定义一个Listener接口:
public interface ClickListener {
void onClick();
}
再定义一个Button类,里面包含一个Listener匿名内部类,代码以下(Button.java):
public class Button {
public void click(){
new ClickListener(){
public void onClick(){
System.out.println("click ...");
}
}.onClick();
}
public static void main(String[] args) {
Button button=new Button();
button.click();
}
}
仔细看看上面的代码,按照常规,咱们应该在click()方法中定义这个内部类,而后new一个实例,再调用方法。而使用匿名内部类,咱们用一句话一鼓作气:
new ClickListener(){
public void onClick(){
System.out.println("click ...");
}
}.onClick();
之因此这么用,是由于其实咱们只是想实现onClick()方法,至于这个类叫什么名字,咱们并不关心,因此就匿名了。这种需求在些事件响应程序式会常常用到。这个匿名能够当作一种简写,对编译器来说,他仍是规规矩矩地生成了一个内部类的class文件ButtonInnerClass.class。为何须要内部类?用两个外部类不是同样的吗?从程序功能上来讲,确实是同样的。我我的理解的是,采用内部类技术,隐藏细节和内部结构,封装性更好,让程序结构更加合理优雅。在现实世界里,一个事物内部都由不少部件组成,每一个部件也还可能包含子部件,这些子部件不须要暴露出来。内部类的思想就是借鉴了这个现实世界,概念的同一性让它很好理解。这种思想更贴近现实世界,“问题空间”与“解决空间”更接近了。
除了上述的基本型内部类,内部类还能够定义成静态的,或者在一个方法体内进行定义,如:¨G6G这个简单,这里再也不完整举例。下面举一个静态内部类的例子,代码以下(Employee.java):¨G7G上面的Employee里面包含一个静态内部类Company,这样跟一个普通类相似了,外部程序能够直接建立这个内部类。并且多个外部类employee能够共享同一个内部类company。要注意的是,这个时候,外部类能够访问内部类,而内部类访问不了外部类。大家能够本身动手试一下,在Employee中增长一个test(),而后试着在Company中调用,会有编译错误:¨G8G有了内部类的这些基础知识,下面咱们要讨论进阶一点的内容了。有一种颇有用的场合时匿名内部类。它看起来就是一个常规的内部类,可是不显式起名,所以叫匿名类。这个场景,定义和实例化是写在一块儿同时的。当咱们须要实现一个接口或者抽象类的时候,咱们常常这么用。
咱们看一个监听器响应事件触发的例子。代码以下:先定义一个Listener接口:¨G9G再定义一个Button类,里面包含一个Listener匿名内部类,代码以下(Button.java):¨G10G仔细看看上面的代码,按照常规,咱们应该在click()方法中定义这个内部类,而后new一个实例,再调用方法。而使用匿名内部类,咱们用一句话一鼓作气:¨G11G之因此这么用,是由于其实咱们只是想实现onClick()方法,至于这个类叫什么名字,咱们并不关心,因此就匿名了。这种需求在些事件响应程序式会常常用到。这个匿名能够当作一种简写,对编译器来说,他仍是规规矩矩地生成了一个内部类的class文件Button1.class。
咱们要的是这个方法,而接口里面也只有着一个方法,这种场景叫函数式接口,Java8以后能够用函数式编程进一步简化上面的代码:
public void click(){
ClickListener listener = ()->{System.out.println("click ...");};
listener.onClick();
}
函数式接口与匿名类是不一样的实现方式。编译器并不会生成一个内部类class文件。
把匿名类当成方法的参数是常见的使用方式。
咱们写一个程序,一个事件处理器响应事件的发生,响应完以后发一个消息通知。
先定义一个发消息的接口:
public interface IMessenger {
void sendMessage(String string);
}
再定义事件处理器,代码以下(Handler.java):
public class Handler {
public void handleEvent(IMessenger messenger) {
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
messenger.sendMessage("SUCCESS.");
}
}).start();
}
}
这个处理器的主体方法是handleEvent(IMessenger),异步处理事件花费两秒钟,以后发消息。
下面咱们使用这个事件处理器,代码以下(HandlerTest.java):
public class HandlerTest {
public static void main(String[] args) {
Handler handler = new Handler();
handler.handleEvent(new IMessenger() {
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
}
});
System.out.println("------event handling test--------");
}
}
注意上面代码的主体部分:
handler.handleEvent(new IMessenger() {
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
}
});
这里就是生成了一个实现IMessenger接口的匿名类,而后传给handleEvent()方法做为参数使用。天然,编译器会自动生成一个匿名类class文件。
再一次说起函数式编程,由于这个IMessenger是一个只有惟一一个方法的接口,因此此处咱们能够再次用函数式编程简化代码:
public static void main(String[] args) {
Handler handler = new Handler();
handler.handleEvent((msg) ->{System.out.println("event handled. " + msg);});
System.out.println("------event handling test--------");
}
匿名内部类来自外部闭包环境的自由变量必须是final的。这一点让人费解,不少人在编译器提示错误的时候就自动修改一下,并不深究。这个与Java对Closure闭包的实现有关,咱们来初步探究一下。这是比进阶更加高级的课题了。
闭包是包含自由变量的函数,这些变量是在定义函数的环境中定义的,而函数自己也当成一个参数进行传递和返回,返回后,这个自由变量还同函数一同存在。在JavaScript和别的语言中流行。
你们也一直但愿Java实现闭包。Java是经过内部类实现的,可是实现的不完整。
先看现象。咱们在上面的HandlerTest程序的主体部分增长一个自由变量,代码以下:
int i = 0;
test.handleEvent(new IMessenger() {
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
int j=0;
j = i+j;
}
});
在JDK7以前,会有编译错误:Local variable i defined in an enclosing scope must be final。这个错误出如今j=i+j;这一行。必须让外面的这个自由变量i定义成final。(JDK8以后不会出错。可是实际上仍是有这个限制,你试着把上面的代码修改一下,给i进行一个赋值就会看出来了。)
把i定义成final以后,意味着咱们在匿名类中不能给自由变量i赋值了。
这儿在类里面定义了内部类,而内部类又引用了外部的自由变量,这就构成了典型的闭包。Java并无彻底实现闭包,在生成匿名类的时候,它把外部自由变量i的value传入,其实是一个拷贝,所以内部其实不能修改外部的自由变量了。Java团队此处又偷了一个懒,干脆就规定外部的这个自由变量为final。
咱们能够反编译这个类,看看编译器处理以后的代码,为:
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
int j = 0;
j = this.val$i + j;
}
注意编译器把j=i+j;变成了j = this.val$i + j;,只是一个外部i的拷贝。对于普通的内部类,为何又不用呢?由于普通的内部类有构造函数,在构造函数过程当中把外部类传进来。可是匿名内部类是没有构造函数的。Java这样处理,引发了多年的争论。我我的仍是比较赞同Java团队的作法的,这样虽然限制不少,可是代码安全,而且预留了之后彻底支持闭包的可能性。之后我会有专题讲解闭包,此次讲解只是就着匿名内部类带一下。大家看到了,Inner Classs起步于一个简单的模型,基本内容很简单,可是一步步深刻,就会牵扯出高级的话题。这就有点像推倒多米诺骨牌,第一下只是很小的一个动做,后面却又大动做等着。不少技术都是这样的,越研讨越深,不断发现新挑战,这也是一种乐趣。正如咱们探索未知的山川,层峦叠嶂,接应不暇,这山望见那山高,前路永远有精彩的风景。也正如咱们进入桃园洞口,刚开头武陵人只是缘溪捕鱼,到了半道,忽逢桃花林,复前行,林尽水源却得一山,从口入,行数十步,豁然开朗,得此桃源仙境。学问上这种探索的乐趣,王国维先生曾言:众里寻他千百度,慕然回首,那人却在,灯火阑珊处。咦!微斯人,吾谁与归