Java语言十五讲(第七讲 InnerClass)

 

同窗们,这一次讲座,咱们讲一下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起步于一个简单的模型,基本内容很简单,可是一步步深刻,就会牵扯出高级的话题。这就有点像推倒多米诺骨牌,第一下只是很小的一个动做,后面却又大动做等着。不少技术都是这样的,越研讨越深,不断发现新挑战,这也是一种乐趣。正如咱们探索未知的山川,层峦叠嶂,接应不暇,这山望见那山高,前路永远有精彩的风景。也正如咱们进入桃园洞口,刚开头武陵人只是缘溪捕鱼,到了半道,忽逢桃花林,复前行,林尽水源却得一山,从口入,行数十步,豁然开朗,得此桃源仙境。学问上这种探索的乐趣,王国维先生曾言:众里寻他千百度,慕然回首,那人却在,灯火阑珊处。咦!微斯人,吾谁与归

相关文章
相关标签/搜索