ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-07-01 16:53:36  阅读:186  来源: 互联网

标签:InnerClass Java 内部 void class println new public 第七


 

同学们,这一次讲座,我们讲一下Inner Class内部类。
我们平时写的程序是由一个个类构成的,这些类之间是相互独立的关系。我们说过,这种思路源自对现实世界的模拟,拉近了“问题空间”和“解决空间”。因此简化了系统的设计。
而Inner class 内部类是指一个类是另一个类的内部成员,定义在某个类的内部的,对外可能可见也可能不可见。

基本形式还是蛮简单的,我们看一个例子:

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里面用下面两行创建了内部类并调用方法:

        InnerClass innerClass=new InnerClass();
        innerClass.display();

而在内部类里面,我们直接使用了外部内的属性。这一段演示了外部类内部类的互操作。编译器会做一个处理,对上面的内部类方法:

public void displayOuter(){
            System.out.println(outerName);
        }  

编译之后的代码会变成:

OuterClass.this.outerName。

好奇的人一般会去bin目录下看一眼编译之后的结果,生成了两个class文件,OuterClass.class和OuterClass

除了上述的基本型内部类,内部类还可以定义成静态的,或者在一个方法体内进行定义,如:

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

 

 

标签:InnerClass,Java,内部,void,class,println,new,public,第七
来源: https://blog.51cto.com/u_14602625/2965346

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有