首页 > 代码库 > java学习笔记 6

java学习笔记 6

Interface

  • 与继承不同,interface为一个类指明的是what it does而不是what it is
  • Java中的多重继承多用implement多个interface代替(逻辑上来讲,一个is a很多的类的类通常用can do很多interface来表达会更合理)
  • interface起名通常用-able的形容词,所以谓语应该是is,写起来的时候用implements(注意s)来继承,用逗号分开多个interface。
  • interface中的所有方法都是public的,并且方法的实现不能直接写在interface本身的定义里(和abstract class一样)。在interface的定义里,方法的声明不需要写上public,编译器会默认给interface的所有方法加上public。但是因为类里的方法默认得到的是package visibility,不写的话就相当于从public降级了,编译器不会给过,所以在继承这个interface的类里必须给override的方法写上public。
  • interface可以有方法和常数,但不能有instance field,在interface里声明的变量(只需要类型)会自动变成public static final(变成常量),interface也可以只有常量,没有方法。
  • 因此,interface可以看成所有方法都是public并且不能有instance field的abstract class
  • 为什么需要用interface,不直接添加方法?因为这样可以得到类似多态的效果,而且interface可以让编译器确定一个类是否有这个方法而不是瞎猜然后到运行时挂掉。
  • 为什么用interface而不是abstract class? 因为继承abstract class的话只能继承一个
  • 和abstract class一样,interface不能被实例化,不能写 x = new Comparable(...)
    • 但是可以写x = new Comparable(...) {...},花括号里实现对应interface的方法,这本质上是实现了一个implement Comparable的匿名新类然后new了再给x,被实例化的不是Comparable而是这个新类
  • 多态和instanceof的用法同abstract class
  • interface可以相互继承

举例:Comparable

  • 只有compareTo()这一个方法
  • Java SE 5.0以后开始,compareTo用上了泛型,参数不再是Object而是对应的任意类(不过依然可以用Object做参数类型,只不过用的时候需要手动cast回来)
  • 使用泛型的写法

    public class A implements Comparable<B> {    public int compareTo(B other) {...}}
  • 使用raw type的写法(注意implements后面的Comparable没有带上类型参数)

    public class A implements Comparable {    public int compareTo(Object obj) {        B other = (B) obj;        ...    }}
  • 在实现compareTo的时候可以利用类似Double.compare这些原始类型的wrapper的compareTo来帮忙
  • 返回值需要是int,并且保证不能溢出
  • 标准库的Arrays.sort的实现是接收一个Object[](不是Comparable[]),然后在实现里手动cast再调用compareTo = =!!! 像这样

    if (((Comparable) a[i]).compareTo(a[j]))  // 好多括号啊

    所以如果传进去的数组本来就没implement Comparable,编译器也查不出来,要到运行时才会杯具……

  • Comparable的API里指明了这个interface的implementation必须保证
    • sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
    • 当且仅当 x.compareTo(y) 会抛出异常时, y.compareTo(x)会抛出异常
  • 当涉及继承的时候就会变得更加蛋疼

    • 父类已经implement过一个interface之后,子类就不能再implement了。所以如果你用的是泛型的implement,那么子类的compareTo接收到的参数依然只能是父类类型的。
    • 为了遵循标准里给出的“当且仅当 x.compareTo(y) 会抛出异常时, y.compareTo(x)会抛出异常”这一要求,在子类的compareTo里不能将那个父类的参数cast成子类(这样如果参数不是子类的话,cast会抛异常,但如果反过调用父类的compareTo又会没事)
    • 作者的建议是分成两种情况

      1. 如果子类对相等的定义和父类不同, 那么在所有的compareTo开头都加上这样一句

        if (getClass() != other.getClass())    throw new ClassCastException();

        就可以保证父类的compareTo也会按照标准说的那样抛异常,然后你就可以放心cast了

      2. 如果两者对相等的定义一样,那么直接只给父类实现compareTo并且设成final就行。

Clone

  • 和JS一样,如果不用clone的话直接将一个对象赋值给一个变量,两者指向同一块内存
  • Object自带的clone可以创建shallow copy。一个直接继承自Object(默认)的类可以调用自己继承自Object的clone(返回的是Object)然后cast成自己这个类。如果被拷贝的对象里有成员是对象(引用),那么被拷贝过去的也只是一个引用,两者间依然存在依赖。
    • 当然,如果那个引用指的是一个immutable的对象(比如String),两者间的依赖是没什么关系的
  • 注意clone在Object里是protected的,所以在没有override的情况下,只有直接继承自Object的类能够调用自己的clone,它的子类和同一package的其他类(两者都不在Object所在的java.lang下)都不能用(不在同一package时,protected的成员只能通过直接继承得到)。
  • 如果需要让其他类能够调用自己的clone,或者实现deep copy,则需要自己实现clone。这个时候需要implement Cloneable,不通过这个方法自己写clone的话,调用的时候会报异常(好吧虽然书上是这么写的不过我一试OK啊 = =)。因为通过interface继承,此时clone一定是public的。在Java SE 5.0前clone返回的都是Object,但是现在可以返回同类的类型。
  • 如果只是想把clone改成public的让其他类也调用,保留原来的浅拷贝特性不变,写起来大概是这样的,注意要标注异常(凡是用了clone()的函数都要标注这个或者用try...catch,一般建议只有final class来用try..catch处理,其他class标注这个异常即可)

    class A implements Cloneable {    public A clone() throws CloneNotSupportedException {        return (A) super.clone();    }}

    这段代码除了把继承自Object的clone改成了public以外啥都没干……(注意Object的clone是会复制子类实际的field的,估计用了reflection)。如果还需要干点其他的,比如进一步拷贝里面的引用,一般在clone里会先调用Object的clone,将原始类型拷贝过来,然后再对引用类型逐个调用clone:

    A cloned = (A) super.clone();cloned.b = (B) this.b.clone();...return cloned;
  • Object将clone定为protected的原因是因为子类直接调用父类的clone可能会出现各种各样奇怪的问题,不如逼程序员自己重新写clone。此外clone操作其实不是很常见,标准库里只有大约5%的类有实现clone而已,很多时候直接复制引用就够了。
  • 数组全部自带clone,每个元素都会被浅拷贝,参见Array.clone的文档

Callbacks

  • Java里的callback通常使用传一个对象而不是传一个函数来实现。此时调用callback的函数还可以利用这个对象里携带的额外信息。
  • Java里的Method类似C++的function pointer,但是效率不高,也不好用,一般来说在Java里如果想用function pointer,不如考虑implement一个interface然后调用这个interface带上的方法,这样才能在编译时做检查。
  • 导入Core Java的timer代码的时候直接Run会报editor does not conatin a main type,因为这份代码的开头有一个package timer,Eclipse导入的时候不会智能创建一个叫timer的package,所以要手动建好,把代码移进去= =||
  • Java中类似setTimeout的功能是(使用awt和swing)
    1. 写一个implements ActionListener的类,实现public void actionPerformed(ActionEvent event)
    2. 要用的时候新建一个Timer,传入秒数和实现了actionPerformed的类的对象
    3. 调用Timer的start,它会每隔指定的时间调用传入的对象的actionPerformed。要停下调用stop
  • 和JS的区别大约就是JS喜欢直接传函数进来,Java喜欢把函数包在一个对象里传进来

Inner Class

  • 用inner class主要有几个原因
    1. inner class可以访问outer class的private成员,这是同类以外的对象访问private成员的唯一办法(Java是没有friend class的)
    2. 非public的inner class可以完全隐藏在outer class内,即使是同一package的其他类也不能看到它(如果放在和outer class的同一块空间,那么同一package的类都能看到它),因此它的public成员只有outer class和其他inner class能够访问
    3. 匿名的inner class可以用一点点代码就能创建一个新类,还可以implement其他interface,写callback的时候很方便。
  • 另外,其他类可以用Outer.Inner这样的类型来使用public的inner class。
  • inner class可以访问outer class的所有成员
  • Java里只有inner class能够成为真正的private class,其他的class只能有两种visibility:public或者package(后者不能被其他package的代码访问)
  • 在inner class里也可以显式访问outer class,假设outer class叫做Outer,有个叫a的成员,那么Outer.this.a就可以得到这个成员。
  • 在outer class里可以显式地构造inner class的对象,语法是 this.new Inner()。虽然看上去好像很多余,但是这种写法可以让inner class利用其他同类对象的成员。假设这个inner class的构造函数里需要用到outer class的成员a,有另外一个outer class的对象叫x,那么x.new Inner()用到的就不是this.a而是x.a。
  • 注意inner class和outer class的机制是编译器玩的花样,和虚拟机无关。inner class在编译之后依然只是一个普通的类,它之所以能够访问outer class的private成员,是因为编译器会特意给outer class加上一个暴露private成员的public函数给它用,这个public函数通常叫access$0之类的,所以普通的java代码文本里不能调用它,因为它在代码里不是合法的名字(有$)。但是如果你能手写bytecode的话……就可以用它了呵呵呵。
  • private的inner class会被编译成一个package-visible的class,构造函数被变异成private的,然后编译器会给它加上一个package-visible的构造函数去调用这个private的构造函数。

Local inner class

  • inner class的定义不光可以在类定义里写,还可以在函数里面写= =!!
  • local的inner class不能写public,它的scope一定在这个函数里,出去了就没了
  • local inner class可以获得函数里的数据,但是只能通过final的引用获得(常量),而且这个引用必须在类定义前就指向要指的地方。因为Java在这里的实现机制是编译器探测到inner class用了外部函数的引用就给这个inner class加多一个field,然后把引用拷贝过去,函数执行完之后,拷贝给inner class的引用还在,所以如果是callback还可以再用。因此必须在类定义前就知道这个引用指向哪里,而且不能再改指向的地方,所以必须是final。(JS是可以再改,所以经常坑到人2333)
  • 其实这个final的限制可以有一个workaround:final的是引用(指向的内存),不是引用内的数据,所以你可以将数据包在一个对象里,把引用给inner class用,但是后来还可以修改引用的内容。最常见的就是包在长度为1的数组里了。(在inner class刚刚进入java的时候编译器会自动做这个转换,但是由于有些人觉得这样编译器背着他们瞎在堆上创建新的数组好怕怕,所以就改成了要手动去创建新数组,不然就得final= =)

Anonymous inner class

  • 语法大概是

    new SuperType(...) {...}

    花括号里是inner class的定义,一般会override一些方法啥的。这句做的事包括

    1. 写了一个继承SuperType(或者implement了SuperType,如果它是interface)的新类
    2. 在花括号里可能override了一些方法,加了一些field之类
    3. 用new给这个新类实例化了一个对象(这样定义的类只能有这一个实例), SuperType后面的括号里的参数传给了SuperType的构造函数 SuperType可以是interface也可以是一个类。注意这样的匿名inner class不能写构造函数,人家是匿名的,没有名字你写个啥= = 所以SuperType后面的括号里给的参数其实是给SuperType的构造函数的。
  • 用来实现callback的类一般这样写

    public class A implements ActionListener {    // 这里可能有个构造函数啥的    public A(...) {...}    // 实现actionPerformed    public void actionPerformed(ActionEvent event) {        // ....    }}// 用法ActionListener a = new A(...);// 然后把a传给需要callback对象的函数

    匿名的inner class的话就这样写

    ActionListener a = new ActionListener() {    // 木有构造函数,呵呵    public void actionPerformed(ActionEvent event) {        // ....    }}
  • 虽然不能有构造函数,但是匿名inner class依然可以有initialization block,而且利用initialization block还可以快速初始化ArrayList或者其他数据结构,像这样

    ArrayList<String> a = new ArrayList<String>() {{ add("1"); add("2");}}

    然后用的时候自动起用多态2333

  • 匿名inner class的另一种神奇用法:在static方法里用reflection获取类的信息。因为直接调用getClass()实际上需要this指针,static方法又没有this指针,所以可以临时创建一个inner class,用getClass().getEnclosingClass()来获取这个类的信息,像这样:

    new Object(){}.getClass().getEnclosingClass();

Static inner class

  • 如果inner class不需要outer class的引用(也就是让两者互相独立存在),就可以将inner class声明为static,这样可以省内存
  • static的inner class可以有non static的方法,它的static仅仅是指它相对于outer class static而已。
  • 在interface里声明的inner class是自动public static的

Proxies

  • 又是黑魔法……
  • 粗略看了一下,主要用途是拿来实现“编译时不知道要implement多少个interface的class”
  • 需要有一个implement了InvocationHandler的类
  • 需要用Proxy来创建新对象
  • 用鸭子类型的语言的话根本就没这些乱七八糟的玩意儿吧………设计模式果然是用来补足语言的缺陷的,真蛋疼啊OTZ