1.finalize()方法
垃圾回收器只能回收通过new创建的对象的内存空间,但由于Java可以调用本地方法,本地方法中有可能通过c语言的malloc()方法来分配内存,所以垃圾回收器会执行一次finalize()方法来调用C语言的free()方法(finalize()方法需要自己去编写代码去调用本地方法)来释放内存
2.封装、继承和多态
面向对象编程的三大特性:封装、继承和多态,其中封装和继承是多态的基础,多态的实现依赖数据抽象和继承。
以下所说的接口是指能被其他类调用的方法定义,例如public,protected方法,请求即客户端程序的方法调用请求。
在Java中,extends关键字可以解释为继承和扩展,但继承和扩展是两个不同的概念。继承是指子类的接口与基类的接口一致,基类可以接收子类的任何消息,而客户端程序不需要知道任何关于子类的确切类型,视为is-a的关系。扩展则与继承不同,扩展是指在基类已有接口的基础上,在子类增加额外的新特性,此时如果不知道子类的确切类型,是没办法使用子类的特性的,视为is-like-a的关系。
通过继承,对象具有自由向上转型的能力,所以客户端程序可以通过与基类的接口交互,实现与具体子类的解耦。基类的接口是所有子类的行为的抽象,接口封装具体子类的实现。
总结:封装使客户端程序能只通过基类接口与具体实现类交互,分离了不变的客户端程序与可变的子类实现。继承则对所有的子类的共性抽象出来,并让基类能代替子类接收所有请求(向上转型)。
此外,扩展和组合是两种复用类的方式,他们分别在什么情况下使用,在重构一书中,如果子类不会使用父类的所有成员,则不应该使用继承,而在Java编程思想中有一个更有说服力的理由,是否需要向上转型,因为扩展和组合最大的区别就是能否向上转型。
3.?通配符
?通配符的作用是让不具有协变的泛型,能向上转型为对应的泛型类。通常是用在变量的定义上,可以是成员变量,局部变量,方法形参。?通配符只有2种形式,一种是? extends T,只能获取不能添加对象,一种是? super T,可以获取也可以添加T类及其子类,所以在extends时,可以接收通配符定义的变量,因为不管入参如何,方法内都只能获取而不能设置元素;但是super可以设置元素,如果入参是通配符表示,此时并不清楚入参的具体类型,不允许设置元素,所以当把通配符设置到super的入参时,就编译报错不允许调用。
?通配符表示一个具有某种特定类型的非原生类型(原生类型则是任何类型),与之对应的是一个具体的动态参数类型,此参数类型对应一个具体的类型,可以在返回值类型,变量类型中定义。具体类型只有T extends MyClass,而不能使用T extends MyClass(即只有上界没有下界),主要原因是Java拥有多态性,不管该参数类型是什么,都能自动向上转型为上界,但没办法自动向下转型,此时使用下界并不是类型安全的。
举个反例,假如可以定义T super SubClass,然后在代码中定义T为SuperClass,然而在用到这个T的类中,T类型的对象的实际类型不一定是SubClass,这时会出现类型转换异常,泛型的宗旨是在编译期就解决类型安全问题,所以T super SubClass不应该被使用。而?通配符是可以的,因为我们并不能用这个?来定义一个对象的类型,也就不存在类型转换的问题了。
4.导致死锁的可能
(1)错失的信号p706,如果对条件谓词的判断和wait()/notify()代码段没有被锁保护起来,当线程2判断感兴趣的条件时是true,此时可能会切换到线程1并调用notify(),这是线程2再去调用wait(),由于notify()已经执行过了,所以wait()实际上处于无限等待的死锁,因为并没有其他线程调用notify()来唤醒他。
这里与Java并发编程14.2.2节p267的最后一条准则是对应的。同时与14.2.3丢失的信号不是同一种情况。
(2)使用notify()而不是notifyAll(),是必须每个任务都等待相同的条件,如果如果唤醒了不恰当的任务,而条件判断后再次wait(),这时会因为没有线程可以调用notify()而导致死锁