[Thinking in Java] - No.3 Java内部类

本篇文章的标题是,Java内部类。其实这样称呼是不对的。因为我们想要表达的其实是Java nested class。而Java nested class 包含有一个分支叫做 non-static nested class 或者 叫 Inner class。所以说,只是我觉得在Thinking in Java翻译中我觉得可能产生puzzle的地方。

正如我们之前提到的,nested class(应该翻译为嵌套类,但是我们的Java 中又会把嵌套类对应为 static nested class,所以我觉得这里不妥)一共分为四种类型:

1. static nested class 

2. non-static nested class / inner class includes following three types:

2.1) member class

2.2) local class

2.3)anonymous class

在本篇文章中,我只会对四个nested class 做最基本的解释,non-nested class 中的.this 和 .new 语法使用,讲解我们使用nested class 来实现Java 中的闭包和回调的示例,同时会涉及到nested class 的继承和覆盖的一部分内容。

1.基本概念:

non-static nested class / inner class:非静态内部类(在Thinking in Java 中译作 内部类)。其包含上述三种类型:

a. member class:成员类,也就是我们最直观的理解的内部类。在外围类的内部直接声明。在我们声明一个内部类对象的时候,此对象与制造他的外部对象之间就存在这一种特殊关系。内部类可以无条件的使用外部类的所有元素的访问权,包括private等。我们可以通过.this来生成对外部类对象的引用,也可以通过.new来生成内部类对象。

public class A {
    void sayA(){
        System.out.println("A");
    }
    public class B{
        public A getA(){
            return A.this;
        }
    }
    public B getB(){
        return new B();
    }
    public static void  main(String args []){
        A a = new A();
        A.B b = a.getB();
        b.getA().sayA();
    }
}
我们还可以使用.new对上面的代码进行小小改动:

public class A {
    void sayA(){
        System.out.println("A");
    }
    public class B{
        public A getA(){
            return A.this;
        }
    }
    public B getB(){
        return new B();
    }
    public static void  main(String args []){
        A a = new A();
        A.B b = a.new B();
        b.getA().sayA();
    }
}
注意:我们在任何时候构造一个内部类(这里指的是我们这种成员类)的时候,都要确定首先创建外围类对象,然后才能创建内部类。下面的代码是错误的:


b.local class:定义在方法内、代码块的内部类。区别于匿名类。

c.匿名类:匿名类指的是我们不像之前的成员类那样给内部类命名,而是以匿名的形式来创建类:如下所示:

 public interface Content{ void say();}
    
    public Content c = new Content() {
        @Override
        public String toString() {
            return super.toString();
        }
        
        public void say(){System.out.print("hello");}
        
        private int content;
    };
上面的生成content对象的方式就是匿名类,我们直接在后面的大括号内完成类的声明。注意,如果在匿名内部类中使用到外部对象,要保证外部对象是final的。

d.static nested class:静态嵌套类(在Thinking in Java中直接译作嵌套类),意为我们在我们的nested class 前添加static标识符。所以我们的静态嵌套类不能使用外围类的非静态对象、变量。并且不得使用.this 和.new 语法。因为静态嵌套类和外围类之间的相互联系的关系是不存在的。

2.实现Java闭包和回调:

首先我们来解释一下什么叫闭包和回调:

闭包:一种可以被调用的对象,该对象保留了创建其的作用域中的信息。对于非静态内部类而言,它保留了一个对其 外围类的引用,通过它可以访问外部类的私有成员,因此可以把非静态内部类当成面向对象领域的闭包。

回调:我们给出维基百科中最准确的解释。回调就类似于,我们不去主动调用一些函数,而是将参数传递出去并且等待其他程序在某个时刻可以来调用我们。我们给出Thinking in Java中的例子:

import java.util.Objects;

/**
 * Created by YanMing on 2017/3/6.
 */
// innerclasses/Callbacks.java
// Using inner classes for callbacks

interface Incrementable {
    void increment();
}



// Very simple to just implement the interface
class Callee1 implements Incrementable {
    private int i = 0;
    public void increment() {
        i++;
        System.out.println(i);
    }
}



class MyIncrement {
    public void increment() {System.out.println("Other operation");}
    static void f(MyIncrement mi) {mi.increment();}
}


// If your class must implement increment() in
// some other way, you must use an inner class:
class Callee2 extends MyIncrement {
    private int i=0;
    public void increment() {
        super.increment();
        i++;
        System.out.println(i);
    }
    private class Closure implements Incrementable {
        public void increment() {
            // Specify outer-class method, otherwise
            // you'd get an infinite recursion
            Callee2.this.increment();
        }
    }
    Incrementable getCallbackReference() {
        return new Closure();
    }
}



class Caller {
    private Incrementable callbackReference;
    Caller(Incrementable cbh) {callbackReference = cbh;}
    void go() {callbackReference.increment();}
}



public class Callbacks {
    public void print(Object o){
        System.out.println(o.toString());
    }
    public static void main(String[] args) {
        Callee1 c1 = new Callee1();
        Callee2 c2 = new Callee2();
        MyIncrement.f(c2);

        Caller caller1 = new Caller(c1);
        Caller caller2 = new Caller(c2.getCallbackReference());
        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
    }
}

执行结果:


下面我们来解释一下这个代码。Callee1是最简单的方法,直接实现了Increment接口中的increment()函数。Callee2继承了MyIncrement,并且在MyIncrement()中含有increment()方法,于是我们创建了一个内部类closure来实现Increment中的increment函数。我们可以从结果看出,2,3均来自我们的caller2,也就是利用c2创建了一个闭包。虽然我们没有再使用c2中的i,但是由于caller2利用了c2的闭包,像是一个hook一样。回调体现在哪里呢?我们通过传递了一个Increment类型的参数,可以使得caller对象不断地回调callee类。

回调更多的体现在很多的动态语言中,我们在javascript中也可以经常使用到回调。

3.内部类的继承和覆盖:

a.继承:

首先我们来看一个例子:

class Withinner{
    class Inner{}
}
public class InheritInner extends Withinner.Inner {
    InheritInner(Withinner wi){
        wi.super();
    }
}
在可以看出,我们的Inherit类继承了Withinner中的Inner。注意我们的构造函数,没有想往常一样是某人构造函数或者能够正常的含参构造函数,而是我们传入了我们的基类的外部类对象,并且调用super函数。

b.覆盖:

/**
 * Created by YanMing on 2017/3/6.
 */
class Egg2{
    protected class Yolk{
        public Yolk(){
            System.out.println("1: Egg2.Yolk");
        }
        public void f(){
            System.out.println("2: Egg2.Yolk.f");
        }
    }

    private Yolk y  = new Yolk();
    public Egg2(){System.out.println("3: New Egg2");}

    public void insertYolk(Yolk yy){y = yy;}
    public void  g(){y.f();}
}

public class BigEgg2 extends Egg2{
    public class Yolk extends Egg2.Yolk{
        public Yolk(){System.out.println("4: BigEgg2.Yolk");}
        public void f(){System.out.println("5: BigEgg2.Yolk.f");}
    }

    public BigEgg2(){insertYolk(new Yolk());}

    public static void main(String args[]){
        Egg2 e = new BigEgg2();
        e.g();

    }
}

我们来分析一下,上述代码的运行过程:

首先我们在实例化一个BigEgg2的对象。我们给出Java实例化的规则:

a.首先实例化内部对象。

b.含有继承关系,要首先实例化基类。

1.所以,在这里我们在调用BigEgg2的构造函数之前,首先实例化Egg2。在调用Egg2的构造函数之前,我们先实例化了Egg.Yolk内部变量。打印Egg2.Yolk。

2.调用Egg2的构造函数,打印New Egg2

3.调用BigEgg2构造函数,返回一个Yolk对象(这里是BigEgg中的Yolk)。该Yolk继承自Egg2.Yolk,所以首先实例化基类,打印Egg2.Yolk。

4.调用BigEgg2的Yolk构造函数,打印BigEgg2.Yolk。

5.调用g()函数,然后调用e对象中的y对象的f方法。在这里,我们的BigEgg2.Yolk被向上转型为Egg2.Yolk,同时由于f()在BigEgg2.Yolk中被覆盖,所以打印的是Bigegg2.Yolk.f。


P.S. 文章不妥之处还望指正




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值