[effective Java Reading Notes] A common method for all objects (3)

Posted by DeathRipple on Wed, 05 Jun 2019 23:14:49 +0200

1. Careful coverage of clone

1. Common object overrides 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 + "]";
	}
	//Add clone method
	@Override
	protected PhoneNumber clone() throws CloneNotSupportedException {
		return (PhoneNumber) super.clone();
	}
}
Method of implementation:

@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)));
	}
Implementation results:

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

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

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

It seems that a new cloned object can be created by a simple strong transformation.

2. Objects contain references to variable objects and override clones. Simple clones can cause problems.

Look at another example:

Stack2

//Adding generic constraints to classes
public class Stack2<E> implements Cloneable{
	//Array of generic definition classes
	private E[] elements;
	private int size = 0;
	private static final int DEFAULT_CAPACITY=16;
	@SuppressWarnings("unchecked")
	public Stack2(){
		//Here, because the array must be of a specific type, it needs to be overridden
		elements = (E[]) new Object[DEFAULT_CAPACITY];
	}
	
	//push pushes into elements of generic classes
	public void push(E e){
		ensureCapacity();
		elements[size++] = e;
		System.out.println("Stack size"+size);
	}
	
	private void ensureCapacity() {
		// TOD ensures self-increasing size
		if (elements.length==size) {
			elements = Arrays.copyOf(elements, 2*size+1);
			System.out.println("Stack Length"+elements.length);
		}
	}

	//Pop-up Generic Result Set
	public E pop(){
		if(isEmpty()){
			throw new EmptyStackException();
		}
		E result = elements[--size];
		System.out.println("Stack size"+size);
		//Pop-up element empty
		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();
	}
}
Execute code:

@Test
	public void test1(){
		Stack2<String> stack2 = new Stack2<>();
		stack2.push("hello0");
		stack2.push("hello1");
		try {
			//Careful coverage of 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();
		}
	}
Implementation results:

Stack size 1

Stack size 2

Stack size 1

hello1

Stack size 1

null

I found a strange problem: common elements.

E[]elements

Other attributes, such as size, have been copied successfully. Causes me to pop one object, another object to pop the current location object again, take out empty.

In fact, the clone method is another constructor that must ensure that the original object is not harmed. And ensure that the constraints of cloned objects are created correctly.

@Override
	protected Stack2<E> clone() throws CloneNotSupportedException {
		Stack2<E> result = (Stack2<E>) super.clone();
		result.elements = elements.clone();
		return result;
	}
Execution results after modification:

Stack size 1

Stack size 2

Stack size 1

hello1

Stack size 1

hello1

3. If the elements domain is final

The code is as follows:

public class Stack2<E> implements Cloneable{
	//Array of generic definition classes
	private final E[] elements;
	...slightly...
	
	@Override
	protected Stack2<E> clone() throws CloneNotSupportedException {
		Stack2<E> result = (Stack2<E>) super.clone();
		//This is not allowed if the elements domain is final
		result.elements = elements.clone();
		return result;
	}
}

4. Shallow and deep copies:

4.1. What is shallow copy?

Shallow copy is a bit-by-bit copy of an object, which creates a new object with an exact copy of the original object's attribute values. If the attribute is a basic type, the copy is the value of the basic type; if the attribute is a memory address (reference type), the copy is a memory address, so if one of the objects changes this address, it will affect the other object.

4.2. What is Deep Copy

Deep copy copies all attributes and copies the dynamically allocated memory that attributes point to. A deep copy occurs when an object is copied with the object it refers to. Deep copy is slower and more expensive than shallow copy.


The book takes the example of HashTable as a deep copy, just as we can look at the code of 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);
        }
    }


Suggestion: Because deep copy is needed in general, and the matters needing attention in deep copy, such as final domain, such as internal traversal, etc. So it's better to expose a copy constructor's static factory directly to the public. Similar to the following:

//Through Static Factory Processing
	public static PhoneNumber newInstance(PhoneNumber pn){
		return new PhoneNumber(pn.areaCode,pn.prefix,pn.lineNumber);
	}

2. Consider Implementing Comparable Interface

Look at a date comparison class I wrote to implement the Comparable interface

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);
        // For example, cc_time=1291778220
        long lcc_time = Long.valueOf(cc_time);
        re_StrTime = sdf.format(new Date(lcc_time * 1000L));

        return re_StrTime;

    }
}
Execute code:

@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());
	}
Implementation results:

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

The compareTo method returns results: Returns - 1, 0, 1 according to the negative, zero and positive values of the expression, and must satisfy symmetry, transitivity and reflexivity as equals; besides TreeSet and TreeMap, it can also be used in Arrays.sort(a) method to sort the array of objects.

The following code:

@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());
		}
	}
Implementation results:

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


Cautious optimization: Since the return value only needs positive and negative numbers or zero, and does not require size, the optimization efficiency can be appropriately processed by subtraction of two values.

But we need to consider the boundary problem. For example, if the difference between the maximum and minimum values is greater than the boundary, it will overflow and return a negative value, indicating an error.

















Topics: Attribute