Java的接口是支持多继承的。Java是不支持实现上的多继承。
2. 避免interfaces 中的函数发生冲突。
如果两个接口具有相同的方法名字,但是其返回类型不一样,那么其实现类不能够同时实现这两个接口,编译会报错。
如果两个接口的具有相同的方法名字,并且返回类型也一致,那么其实现类可以同时实现这两个接口,编译不会报错。
如果两个接口声明的变量是一模一样的话,那么在实现类中调用变量的时候,需要指定是哪个接口的变量,或者编译不会通过。
3. 如需提供部分实现(partial implementation),请使用abstract classes(抽象类)。
所谓abstract class 是一种本身不能被具现化(instantiated)的class,但允许为其内声明的函数提供实现代码。不同与interface(接口),接口是不能够有实现。
4. 区分interface、abstract class和concrete class。
|
Interface |
Abstract class |
Concrete class |
描述 |
一种契约(contract)表示法,不 带实现 |
一种契约(contract)表示法,带有部分实现 |
一种具象(具体)实现,经常是某个interface 或者某个abstract class 的实现 |
使用时机 |
当你想要支持单一或多个interface 继承,或为了确定某一marker interface(标识接口) |
当你打算提供带有部分实现的class |
当你打算提供一份完 整的具象实现 |
内容限制 |
Public 函数以及public static final 常量 |
无任何限制 |
无任何限制 |
实现 |
不允许实现 |
允许部分实现 |
全部实现 |
interface、abstract classes 和concrete classes 的总结
|
支持多重继承 |
支持抽象函数 |
允许实现 |
允许创建实体 |
允许部分实现 |
|
Interface |
Y |
Y |
N |
N |
N |
|
abstract classes |
N |
Y |
Y |
N |
Y |
|
concrete classes |
N |
N |
Y |
Y |
N |
|
interface 的所有函数都暗自为abstract。
5. 审慎地定义和实现immutable classes (不可变类)。
不可变对象是一种一点构建好就不再变化的object,在其生存期间不可被改变内容,不可变类通常用来表示字符串、颜色和数字。由于不可变对象永远不会改变本身数据,所以没有必要对他们同步控制。
Class的定义和实现多方面合作才可能造就不变性,以下几个步骤是必须的:
a. 将class中的所有数据声明为private;(如果声明为其他修饰符的话,实例类则可以修改数据信息)
b. 只提供取值方法(getter),不允许存在设置方法(setter);(如果提供了setter方法,那么其数据信息肯定会被修改掉)
c. 将class声明为final(即使提供了getter方法,但是在子类中也覆盖父类方法的话,仍然有可能是修改数据,因此需要将类声明为不能够被继承);
d. 从获取器返回reference to mutable object之前,先克隆(cloning)那些mutable objects;
e. 将[传递给构造函数之reference to mutable object]先克隆(clone);
f. 在构造函数中设定class 内含的所有数据。(由于数据信息不能够被修改,所以其赋值只能够是在构造函数)
6. 欲传递或接收mutable objects(可变对象)之object references时,请实施clone()。
实现immutable class(不可变类)时,必须对传入或传出之mutable objects(可变对象)进行克隆(clone)动作。如果一个object及其指涉(引用)所有objects都不会被改变,该object便是为恒常不变的(immutable)。如果不实施克隆动作,你的对象不变性(immutability)就得不到保证。这是因为他处可能保留了这个immutable object 内的某个object 的reference,并对其进行了修改,从而冲破了不变性的[界限]。
就是说,如果一个对象A中还引用了其他对象的引用B,那么引用B中的数据是可以被修改掉。如下代码:
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public final class DiskInfo {
private String disk;
private User user;
public DiskInfo(String disk, User user) {
this.disk = disk;
this.user = user;
}
public User getUser() {
return user;
}
public String disk() {
return disk;
}
public static void main(String[] args) {
User u = new User();
u.setAge(1);
u.setName("test1");
DiskInfo d = new DiskInfo("test", u);
User u1 = d.getUser();
System.out.println("-----" + u1.getName());
u1.setName("test2");
User u2 = d.getUser();
System.out.println("-----" + u2.getName());
}
}
其输入结果:
-----test1
-----test2
可以看到上述的结论是正确的。因此为了保持数据的不变性,需要将mutable对象进行克隆。
克隆:Shallow Cloning(浅层克隆)。此技术对对象进行逐位拷贝(bitwise copy)。如果cloned object 包含了一些object references,那么新的object 将内含与[cloned object 所含之object reference]完全一致的副本。所以new object 和cloned object 依然共享数据。使用了克隆的代码如下:
package com.study6;
public class User implements Cloneable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
}
package com.study6;
public final class DiskInfo {
private String disk;
private User user;
public DiskInfo(String disk, User user) {
this.disk = disk;
this.user = (User)user.clone();
}
public User getUser() {
return (User)user.clone();
}
public String disk() {
return disk;
}
public static void main(String[] args) {
User u = new User();
u.setAge(1);
u.setName("test1");
DiskInfo d = new DiskInfo("test", u);
User u1 = d.getUser();
System.out.println("-----" + u1.getName());
u1.setName("test2");
User u2 = d.getUser();
System.out.println("-----" + u2.getName());
}
}
输出结果:
-----test1
-----test1
Deep Cloning(深层克隆):缺省情况下,Vector clone()执行的是浅层克隆(shallow clone)。Vector的值域driveShare其实是个object reference,因此当代码对Vector进行克隆时,虽然建立了一个新副本,其内容(references to User object)却没有被克隆。因此需要深层克隆。
在实现—个immutable class(不可变类)时,请遵循下列规则:
a.声明这个class 为final。
b.声明所有数据为private。
c.只提供取值函数(getter),不提供设值函数(setter)。
d.在构造函数中设置所有instance 数据。
e. 如果函数返回reference to mutable objects,请克隆那些mutable objects。
f.如果函数接受reference to mutable objects,请克隆那些mutable objects。
g.如果缺省之浅层克隆(shallow clone)不能符合immutable object 的正常行为请实现出深层克隆(deep clone)。
7. 使用继承(inheritance)或委托(delegation)来定义immutable classes(不可变类)。
另外三个你可以用来定义immutable classes 的技术:
a.immutable interface(不可变接口)。
b.公共接口或基类(common interface or base class)。
c.immutable delegation class (不可变的委托类)。
技术 |
优点 |
缺点 |
Immutable inference 不可变(恒常)接口 |
简单易行。无需付出性能上的代价 |
有破绽,可被破坏。(转型会破坏不变性) |
Common interface or base calss 公共接口或基类 |
没有破绽。清晰地将 mutable object 和immutable objects 分离 |
需实现更多的classes,完成更深的classes 继承系(一个接口必须有多个实现类) |
Immutable delegation class不可变(恒常)委托类 |
没有破绽。当你无法修改既 有的mutable object 源码 时,此法可用 |
需付出性能上的代价(不可变类中含有一个可变类的引用,造成性能下降) |
8.实现clone()时记得调用super.clone()。
clone ()中首先必须调用super.clone(),可确保java.long.Object 中的clone会被调用,因而使得克隆件得以被正确建构出来。java.1ang.Object 的clone()函数会创建一个正确型别的新对象,并执行浅层克隆(shallow clone,即从[被克隆物]复制所有位域(fields)到[新对象]身上)。即使需要深层克隆(deep clone),仍然斋要调用java.1ang.Object 的clone()函数,才能创建对象的正确型别。
任何classes 如果支持克隆操作,就必须实现Cloneable interface——这是一个标识接口(marker interface),也就是说它并没有实现任何函数,任何classes 如果实现Cloneable,就是宣称它们支持克隆操作。
9. 别只依赖finalize()清理non—memory(内存以外)的资源
在垃圾问收器(garbage collector)回收某个class object 之前,JVM 会调用这个class 的finalize()。这个函数有时被宣扬为[在回收某个对象的内存之前确保释放non-memory 资源]的有效方法。由于垃圾回收器仅仅释放对象内存,finalize()因此提供了一种释放其他资源的办法。
对象的finalize()函数只有在垃圾回收器释放对象占用的空间之前才会被调用。
当垃圾回收器执行时,可能并非所有符合回收条件的对象都被回收(实际情况取决于垃圾回收机制所采用之算法),此外亦无法保证finalize()在可预期的时间上执行。这是由于[终结(finalization)动作[与垃圾回收]之间的异步(asynchronous)本性造成的。由此推断,finalize()未必在程序结束前被执行。这意味即使你正确撰写了finalize()来释放non-memory 资源,你的程序结束前仍有可能耗尽这些资源。
避免这个问题的可能办法之一是使用System.runFinalization()。这个函数要求JVM 在垃圾回收器下次运行之前,对所有标记为[可终结(finalizable)]对象调用其finalize()。是否能够做到这一点,取决于垃圾回收器和这个函数所采用的算法。但不能保证调用该函数就一定会执行起finalize()。
早先的另一个可用函数是System.runFinalization(),现已作废。这个函数仅仅保证所有对象的finalize()将在JVM 退出之前被执行。这意味等到finalize()执行时,你可能已经耗尽了资源。通常你会希望finalize()在你程序运行期间被执行,而不是等到最后JVM退出时刻。这个函数被认为是不安全的,因此在Java2 中被废除了。
因此不能把希望单单寄托于[finalize()被调用]这件事情上,需要自己编写清理no-memory资源的函数,并且结合finalize方法一起使用。可以自己编写一个清理函数,并且该函数是public的,同时要被finalize函数所调用(确定JVM调用finalize时可以调用到清理函数)。将清理函数声明为public,是为了可以再任何时刻都可以清理资源。将清理函数被声明为synchronized,这可以保证多个线程(threads)不会针对相同的对象同时进入这个函数。
所有finalize()都应当调用super.finalize()以确保任superclass 的finalize()被调用。由于无法保证finalize()是否被调用、何时被调用,所以你应当提供一个Public 专员函数,用以执行non-momory 资源清理工作;该函数应当由class 的finalize()调用之。
10. 在构造函数内调用non—final函数时要当心
当新建一个对象的时候,构造方法能够被调用。
package com.study6;
public class NoFinalTest {
/**
* @param args
*/
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.getNum());
}
}
class Base {
private int value;
public Base() {
System.out.println("base");
value = lookUp();
}
public int lookUp() {
System.out.println("base lookUp");
return 5;
}
public int getNum() {
return value;
}
}
class Sub extends Base {
private int num = 10;
public int lookUp() {
System.out.println("Sub lookUp " + num);
return num;
}
}输出结果为:
base
Sub lookUp 0
0
.这不是我们想要的结果,不过从上可以看出来num并还没有赋值。
分析:该函数返回instance变量num,它在instance 变量初始化时被赋值为10.事实真象是,当Sub的1ookup()开始执行时,其instance变量初始化工作还未来得及进行呢。
当构造函数调用non-final 函数时,就有可能发生这种错误。如果该函数被一个derived class 覆写,而是该函数返回一个[instance 变量初始化期间]被初始化的值,就会产生问题。