【effective Java读书笔记】对于所有对象都通用的方法(三)

时间:2022-12-03 16:02:02

一、谨慎的覆盖clone

1、普通对象覆盖clone

PhoneNumber

public class PhoneNumber implements Cloneable{
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(short areaCode, short prefix, short lineNumber) {
super();
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
public PhoneNumber(int i, int j, int k) {
this.areaCode = (short)i;
this.prefix = (short)j;
this.lineNumber = (short)k;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber) obj;
return pn.areaCode==areaCode&&pn.prefix==prefix&&pn.lineNumber==lineNumber;
}

@Override
public int hashCode() {
return 42;
}
@Override
public String toString() {
return "PhoneNumber [areaCode=" + areaCode + ", prefix=" + prefix + ", lineNumber=" + lineNumber + "]";
}
//添加clone方法
@Override
protected PhoneNumber clone() throws CloneNotSupportedException {
return (PhoneNumber) super.clone();
}
}
执行方法:

@Test
public void test() throws CloneNotSupportedException {
PhoneNumber pn = new PhoneNumber(1, 2, 3);
PhoneNumber pn2 = pn.clone();
System.out.println("pn.clone()!=pn2-----"+(pn.clone()!=pn2));
System.out.println("pn.clone().getClass()==pn2.getClass()-----"+(pn.clone().getClass()==pn2.getClass()));
System.out.println("pn.clone().equals(pn2)-----"+(pn.clone().equals(pn2)));
}
执行结果:

pn.clone()!=pn2-----true

pn.clone().getClass()==pn2.getClass()-----true

pn.clone().equals(pn2)-----true

看似好像只需要做一个简单的强转就可以实现新建一个克隆对象出来。

2、对象包含引用可变对象,覆盖clone,简单的clone就会出现问题。

再看一个例子:

Stack2

//添加类的泛型约束
public class Stack2<E> implements Cloneable{
//泛型定义类的数组
private E[] elements;
private int size = 0;
private static final int DEFAULT_CAPACITY=16;
@SuppressWarnings("unchecked")
public Stack2(){
//此处由于数组必须是具体的某种类型,需要强转
elements = (E[]) new Object[DEFAULT_CAPACITY];
}

//push压入泛型类的元素
public void push(E e){
ensureCapacity();
elements[size++] = e;
System.out.println("栈的大小"+size);
}

private void ensureCapacity() {
// TOD确保大小自增
if (elements.length==size) {
elements = Arrays.copyOf(elements, 2*size+1);
System.out.println("栈的长度"+elements.length);
}
}

//弹出泛型的结果集
public E pop(){
if(isEmpty()){
throw new EmptyStackException();
}
E result = elements[--size];
System.out.println("栈的大小"+size);
//弹出的元素置空
elements[size] = null;
return result;
}

public boolean isEmpty() {
// TODO Auto-generated method stub
return size==0;
}

@Override
protected Stack2<E> clone() throws CloneNotSupportedException {
return (Stack2<E>) super.clone();
}
}
执行代码:

@Test
public void test1(){
Stack2<String> stack2 = new Stack2<>();
stack2.push("hello0");
stack2.push("hello1");
try {
//谨慎的覆盖clone
Stack2<String> stack22 = stack2.clone();
System.out.println(stack22.pop());
//System.out.println(stack22.pop());

System.out.println(stack2.pop());
//System.out.println(stack2.pop());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
执行结果:

栈的大小1

栈的大小2

栈的大小1

hello1

栈的大小1

null

发现一个很奇怪的问题:公用了elements,

E[]elements

而自身的size等其他属性被成功拷贝了。导致我对一个对象pop后,另一个对象再次去pop当前位置对象的时候,取出为空了。

实际上clone方法就是另一个构造器,必须确保不能伤害原对象。并确保正确的创建被克隆对象的约束条件。

@Override
protected Stack2<E> clone() throws CloneNotSupportedException {
Stack2<E> result = (Stack2<E>) super.clone();
result.elements = elements.clone();
return result;
}
修改完之后的执行结果:

栈的大小1

栈的大小2

栈的大小1

hello1

栈的大小1

hello1

3、如果elements域是final的

代码如下:

public class Stack2<E> implements Cloneable{
//泛型定义类的数组
private final E[] elements;
...略...

@Override
protected Stack2<E> clone() throws CloneNotSupportedException {
Stack2<E> result = (Stack2<E>) super.clone();
//如果elements域是final的则不被允许这么处理
result.elements = elements.clone();
return result;
}
}

4、浅拷贝 和 深拷贝:

4.1、什么是浅拷贝

   浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

4.2、什么是深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。


书中拿hashTable的例子表示深拷贝,正好我们也可以拿HashTable的代码看看:

public synchronized Object clone() {
try {
Hashtable<?,?> t = (Hashtable<?,?>)super.clone();
t.table = new Entry<?,?>[table.length];
for (int i = table.length ; i-- > 0 ; ) {
t.table[i] = (table[i] != null)
? (Entry<?,?>) table[i].clone() : null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}


建议:由于一般情况都需要深拷贝,而深拷贝需要注意的事项,例如final域,例如内部遍历等问题。所以不如直接对外公开一个拷贝构造器的静态工厂。类似如下:

//通过静态工厂处理
public static PhoneNumber newInstance(PhoneNumber pn){
return new PhoneNumber(pn.areaCode,pn.prefix,pn.lineNumber);
}

二、考虑实现Comparable接口

看看我写的一个日期比较类,实现了Comparable接口

public class DateBean implements Serializable,Comparable<DateBean>{
private int year;
private int month;
private int date;

public DateBean(int year, int month, int date) {
this.year = year;
this.month = month;
this.date = date;
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDate() {
return date;
}

public void setDate(int date) {
this.date = date;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

DateBean dateBean = (DateBean) o;

if (year != dateBean.year) return false;
if (month != dateBean.month) return false;
return date == dateBean.date;

}

@Override
public int hashCode() {
int result = year;
result = 31 * result + month;
result = 31 * result + date;
return result;
}

@Override
public int compareTo(DateBean dateBean) {
// TODO Auto-generated method stub
if (dateBean.getYear()>getYear()){
return -1;
}else if (dateBean.getYear()<getYear()){
return 1;
}else {
if (dateBean.getMonth()>getMonth()){
return -1;
}else if (dateBean.getMonth()<getMonth()){
return 1;
}else {
if (dateBean.getDate()>getDate()){
return -1;
}else if (dateBean.getDate()<getDate()){
return 1;
}else {
return 0;
}
}
}
}

@Override
public String toString() {
String time = getStrTime(getTime(year+"-"+month+"-"+date,"yyyy-M-dd"),"yyyy-MM-dd");
return time;
}

public static String getTime(String user_time, String type) {
String re_time = null;
SimpleDateFormat sdf = new SimpleDateFormat(type);
Date d;
try {

d = sdf.parse(user_time);
long l = d.getTime();
String str = String.valueOf(l);
re_time = str.substring(0, 10);

} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return re_time;
}

public static String getStrTime(String cc_time, String type) {
String re_StrTime = null;

SimpleDateFormat sdf = new SimpleDateFormat(type);
// 例如:cc_time=1291778220
long lcc_time = Long.valueOf(cc_time);
re_StrTime = sdf.format(new Date(lcc_time * 1000L));

return re_StrTime;

}
}
执行代码:

@Test
public void test() {
Set<DateBean> dateBeans = new TreeSet<>();
dateBeans.add(new DateBean(2017, 5, 1));
dateBeans.add(new DateBean(2017, 4, 1));
dateBeans.add(new DateBean(2017, 5, 20));
dateBeans.add(new DateBean(2015, 3, 20));
dateBeans.add(new DateBean(2015, 4, 20));
System.out.println(dateBeans.toString());
}
执行结果:

[2015-03-20, 2015-04-20, 2017-04-01, 2017-05-01, 2017-05-20]

compareTo方法返回结果:根据表达式的值为负值、零、正值,分别返回-1,0,1,而且与equals一样必须满足对称性,传递性、自反性;除了TreeSet,TreeMap,还可以用在Arrays.sort(a)方法,对对象的数组的排序。

如下代码:

@Test
public void test1() {
DateBean[] dateBeans =new DateBean[5];
dateBeans[0]=new DateBean(2017, 5, 1);
dateBeans[1]=new DateBean(2017, 4, 1);
dateBeans[2]=new DateBean(2017, 5, 20);
dateBeans[3]=new DateBean(2015, 3, 20);
dateBeans[4]=new DateBean(2015, 4, 20);
for (DateBean b:dateBeans) {
System.out.println(b.toString());
}
Arrays.sort(dateBeans);
System.out.println("---------");
for (DateBean b:dateBeans) {
System.out.println(b.toString());
}
}
执行结果:

2017-05-01

2017-04-01

2017-05-20

2015-03-20

2015-04-20

---------

2015-03-20

2015-04-20

2017-04-01

2017-05-01

2017-05-20


慎用优化:由于返回值只需要正负数或者零,并没有要求大小,可以适当通过两个值做减法处理优化效率。

但是需要考虑,边界问题。例如最大和最小值的差大于边界,那么会溢出,并返回一个负值,表示错误。