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

一、谨慎的覆盖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


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

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

















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Effective Java》是一本由Joshua Bloch撰写的Java编程实践指南,其第版是最新的版本。这本书从Java编程语言的角度,提供了许多有关编写高质量、高性能和可维护代码的实用建议。 第版的《Effective Java》相比前两版进行了更新和扩充,以适应现代Java编程的需求。它包含了许多最佳实践和设计原则,以帮助读者写出更有效、更优雅的代码。这本书的内容涵盖了Java编程中的很多方面,包括类设计、接口、泛型、枚举、注解、Lambda表达式等主题。 与前两版不同,第版在面对现代编程环境和新版本的Java时进行了更新和修订。它还引入了新的编程概念,如函数式编程和Lambda表达式,以及新的Java特性和库,如Stream API、Optional类等。这使得它成为一本与时俱进的Java编程指南,可以帮助读者更好地利用新的编程工具和技术。 第版的《Effective Java》以清晰、简洁和易于理解的方式进行了组织和阐述。每个条目都包含了一个特定的编程问题或原则,并给出了解决或应用该原则的最佳方法。书中的实例代码和说明非常详细,读者可以通过阅读和理解这些例子,更好地理解作者的观点和建议。 总的来说,第版的《Effective Java》是一本对于Java程序员来说不可或缺的参考书。它不仅提供了编写高质量代码的指导,还帮助读者理解和应用Java编程语言的最佳实践。无论是初学者还是有经验的开发者,都可以从中受益,并提高他们的编程技能和代码质量。因此,如果你是一名Java开发者,我强烈推荐你阅读和学习《Effective Java》第版。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值