Java中级——Object和Objects

Object和Objects

  • Object和Objects是什么?
  • Object源码
  • Objects源码
  • 多态
  • Object.equals()方法和 ==
  • Object.hashcode()方法
  • Object.clone()方法
  • Object.toString()方法

Object和Objects是什么?

Object是所有Java类的父类,一个类若没有明确指出父类,Object就默认为此类的父类

Objects是工具类,用于实现基本方法

Object源码

不同源码的Object可能不同,如安卓原源码里有其他方法,以下按照空格分为:Natives()、getClass()、hashCode()和equals()、clone()、toString()、notify()和wait()、finalize()

public class Object {

	private static native void registerNatives();
	static {
    	registerNatives();
	}
	
	public final native Class getClass();
	
	public native int hashCode();
	public boolean equals(Object obj) {
    	return (this == obj);
	}
	
	
	protected Object clone() throws CloneNotSupportedException {
   	 	if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException("Class " + getClass().getName() + " doesn't implement Cloneable");
    	}
    	return internalClone();
	}
	private native Object internalClone();
	
	public String toString() {
    	return getClass().getName() + "@" + Integer.toHexString(hashCode());
	}
	
	public final native void notify();
	public final native void notifyAll();
	
	public final void wait() throws InterruptedException {
    	wait(0);
	}
	public final void wait(long timeout, int nanos) throws InterruptedException {
   	 	if (timeout < 0) {
        	throw new IllegalArgumentException("timeout value is negative");
    	}
    	if (nanos < 0 || nanos > 999999) {
        	throw new IllegalArgumentException("nanosecond timeout value out of range");
    	}
    	if (nanos > 0) {
        	timeout++;
    	}
    	wait(timeout);
	}
	public final native void wait(long timeout) throws InterruptedException;
	
	protected void finalize() throws Throwable { }
}

Tips:

  • Object中的Native方法都是非Java实现,故看不到源码
  • 可能需要重写的有hashCode()和equals()、clone()、toString()、finalize()

Objects源码

以下按照空格分为:私有构造、equals()和deepEquals()、hashCode()和hash()、toString()、compare()、requireNonNull()、isNull()和nonNull()

public final class Objects {

	private Objects() {
        throw new AssertionError("No java.util.Objects instances for you!");
    }
    
    public static boolean equals(Object a, Object b) {
    	return (a == b) || (a != null && a.equals(b));
	}
	public static boolean deepEquals(Object a, Object b) {
    	if (a == b)
        	return true;
    	else if (a == null || b == null)
        	return false;
    	else
        	return Arrays.deepEquals0(a, b);
	}
	
	public static int hashCode(Object o) {
   	 	return o != null ? o.hashCode() : 0;
	}
	public static int hash(Object... values) {
   		return Arrays.hashCode(values);
	}
	
	public static String toString(Object o) {
    	return String.valueOf(o);
	}
	public static String toString(Object o, String nullDefault) {
   	 	return (o != null) ? o.toString() : nullDefault;
	}
	
	public static  int compare(T a, T b, Comparator c) {
   	 	return (a == b) ? 0 :  c.compare(a, b);
	}
	
	public static  T requireNonNull(T obj) {
    	if (obj == null)
        	throw new NullPointerException();
    	return obj;
	}	
	public static  T requireNonNull(T obj, String message) {
    	if (obj == null)
        	throw new NullPointerException(message);
    	return obj;
	}
	public static  T requireNonNull(T obj, Supplier messageSupplier) {
    	if (obj == null)
        	throw new NullPointerException(messageSupplier.get());
   	 	return obj;
	}
	
	public static boolean isNull(Object obj) {
    	return obj == null;
	}
	public static boolean nonNull(Object obj) {
    	return obj != null;
	}

Tips:

  • 会经常用到的方法为equals()和deepEquals()、hashCode()和hash()、toString()、compare()
  • requireNonNull()、isNull()和nonNull()是类库设计者用到的,我们一般直接判空

多态

所有Object类型的变量可以引用除基本数据类型外的各种对象

class Person {

}

但要对其操作还得转为对应的数据类型

Object o = new Person();
Person p = (Person) o;

Object类型变量还可引用所有数组

Object o = new Object();
Person[] people = new Person[10];
o = people;
o = new int[10];

Object.equals()方法和 ==

  • == 比较地址是否相同
  • Object.equals()未重写时等价于 ==(源码就是调用==)
  • Object.equals()重写后比较规则由设计者给出

以String中的equals()为例,当两个字符串地址相等时,肯定是同一字符串,当地址不等时,若字符串中每个字符相等也认为是同一字符串,故比较规则由设计者给出(若设计者想在一半相等就返回true也是可以的)

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = length();
        if (n == anotherString.length()) {
            int i = 0;
            while (n-- != 0) {
                if (charAt(i) != anotherString.charAt(i))
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Object.equals()经常用来判断两个对象状态是否相等(是否有相同的域)

对于非空的x、y、z,重写Object.equals()应该保证:

  • 自反性: x.equals(x) == true
  • 对称性:x.equals(y) == true && y.equals(x) == true
  • 传递性:x.equals(y) == true && y.equals(z) == true && x.equals(z) == true
  • 一致性:当x、y未改变时,对于多次调用总是 x.equals(y) == true
  • 非空性:x.equals(null) == false

在effective java中给出了编写Object.equals()的规则

  • 使用 == 比较参数是否为这个对象的引用
  • 使用 instanceof 比较参数是否为正确的类型
  • 把参数转换成正确的类型
  • 比较参数中的域与该对象中对应的域是否相等

标准写法如下,(obj == null)判断不是必须的,当obj=null,在instanceof判断时也会返回false

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        /*if (obj == null) {
            return false;
        }*/
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(this.name, other.name);
    }
}

为什么是instanceof判断而不是getClass()判断?

  • 父类的equals()同时适用于子类,就用instanceof,如AbstractMap中的equals()适用于HashMap、TreeMap等
  • 用instanceof方便以后的扩展
  • 若父类的equals()无法满足子类的比较,则子类需重写equals()

如下给出一个使用getClass()判断的反例

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(this.name, other.name);
    }
}

class Man extends Person {

    public Man(String name) {
        super(name);
    }
}

针对上面的继承结构,若想判断name="song"的人是否存在于List,如下代码输出true和false,显然后者的false并不符合实际要求

List list = new ArrayList<>();
list.add(new Person("song"));
System.out.println(list.contains(new Person("song")));
System.out.println(list.contains(new Man("song")));

此外继承和equals()的性质是相违背的,对于如下继承结构

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(this.name, other.name);
    }
}

class Man extends Person {
    private int age;

    public Man(String name, int age) {
        super(name);
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Man)) {
            return false;
        }
        Man other = (Man) obj;
        return super.equals(obj) && this.age == other.age;
    }
}

如下代码输出true、false,表示Person可Man比较,而Man跟Person比较始终为false,明显违背对称性

System.out.println(new Person("song").equals(new Man("song", 18)));
System.out.println(new Man("song", 18).equals(new Person("song")));

通过修改Man的equals进行混合比较可满足上述对称性,如下

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(this.name, other.name);
    }
}

class Man extends Person {
    private int age;

    public Man(String name, int age) {
        super(name);
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        if (!(obj instanceof Man)) {
            return obj.equals(this);
        }
        Man other = (Man) obj;
        return super.equals(obj) && this.age == other.age;
    }
}

但是修改后的代码不满足对称性,如下输出true、true、false,因为前两个比较只考虑了name属性,而最后的比较多了age属性,同时上面的代码可能会导致无限递归调用(两个子类比较时都调用对方的equals)

Man m1 = new Man("song", 18);
Person p1=new Person("song");
Man m2 = new Man("song", 19);

System.out.println(m1.equals(p1));
System.out.println(p1.equals(m2));
System.out.println(m1.equals(m2));

故无法在继承时增加比较域又保持equals的性质,而解决办法是把继承改为组合,并在子类中提供一个返回父类的公有视图(修改后将不再允许父类和子类比较)

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(this.name, other.name);
    }
}

class Man {
    private Person person;
    private int age;

    public Man(String name, int age) {
        person = new Person(name);
        this.age = age;
    }

    public Person asPerson() {
        return person;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Man)) {
            return false;
        }
        Man other = (Man) obj;
        return Objects.equals(this.person, other.person) && this.age == other.age;
    }
}

Tips:

  • 对于float和double,使用Float.compare()和Double.compare()比较
  • 对于其他基本数据类型,使用 == 比较
  • 对于引用数据类型,使用Objects.equals(a,b)比较
  • 对于数组,使用Arrays.equals比较
  • 比较顺序会影响equals()方法性能,应优先比较最有可能不相等的域或开销最低的域

Object.hashcode()方法

重写equals()方法就必须重写hashcode()方法,需要保证

  • x.equals(y) == true,必有x.hashcode() == y.hashcode()
  • x.hashcode() != y.hashcode(),必有x.equals(y) == false
  • x.hashcode() == y.hashcode(),x.equals(y)不确定

先看不重写hashcode()会怎样,对于如下代码

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(this.name, other.name);
    }
}

如下将一个实例存储到hashmap再取出,打印为null,原因是

  • 未重写hashcode时,其值为地址,每次new出来的对象地址不一样
  • 所以导致put()把对象存放在一个散列桶,而get()却在另一个散列桶内查找
HashMap map = new HashMap<>();
map.put(new Person("song"), 1);
System.out.println(map.get(new Person("song")));

effective java给出的编写规则为:将第一个关键域的hashCode()作为result,其乘以31再累加其他关键域的hashCode(),其他关键域的计算方法如下:

  • 若为基本数据类型,则调用其装箱类型的hashCode()方法
  • 若为引用数据类型,并且该数据在equals中递归调用equals,则在hashcode也递归调用hashcode
  • 若为数组中的某个元素,则将其单独拿出来按照上面规则计算,若整个数组都为关键域则调用Arrays.hashCode()
class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return this.name.equals(other.name) && this.age == other.age;
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + Integer.hashCode(age);
        return result;
    }
}

若觉得上面太麻烦,可通过Objects.hash()生成hashcode,但其性能不如上面

class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(this.name, other.name) && this.age == other.age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

若一个类经常用于散列,可定义一个域将hashcode缓存起来(可选择创建时初始化或延迟初始化)

Object.clone()方法

赋值 = 对于基本数据类型和不可变对象类型(如String)是拷贝数据,对于可变对象则是拷贝引用

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

对于如下代码,b的改变不会影响a,但p2的改变会影响p1

int a = 1;
int b = a;
b = 2;

Person p1 = new Person("tom", 1);
Person p2 = p1;
p2.setName("john");

要想获得一个与调用者一样的对象实例,则需要clone方法,如下p2的修改不会再影响p1

Person p1 = new Person("tom", 1);
Person p2 = (Person) p1.clone();
p2.setName("john");

上述代码是运行不了的,因为clone()是protect方法,想要调用须实现Cloneable接口并重写clone()

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

如果类包含可变对象域,super.clone()会让两个实例中对象域的引用相等,如Person新增Clothe域

class Person {
    private String name;
    private int age;
    private Clothe clothe;

    public Person(String name, int age, Clothe clothe) {
        this.name = name;
        this.age = age;
        this.clothe = clothe;
    }

    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 Clothe getClothe() {
        return clothe;
    }

    public void setClothe(Clothe clothe) {
        this.clothe = clothe;
    }

    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Clothe {
    private String Coat;

    public Clothe(String coat) {
        Coat = coat;
    }

    public void setCoat(String coat) {
        Coat = coat;
    }
}

对于以下代码,p1、p2的Clothe域引用同一个对象,对p2的Clothe域修改会影响p1

Person p1 = new Person("tom", 1, new Clothe("裤子"));
Person p2 = (Person) p1.clone();
p2.getClothe().setCoat("裙子");

故还需要对类内部可变对象域进行拷贝,让Clothe也实现Cloneable重写clone()

class Clothe implements Cloneable{
    private String Coat;

    public Clothe(String coat) {
        Coat = coat;
    }

    public void setCoat(String coat) {
        Coat = coat;
    }

    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

然后在Person的clone()内部完成对可变实例域(Clothe)的拷贝,即递归拷贝

protected Object clone() throws CloneNotSupportedException {
    Person tempPerson= (Person) super.clone();
    tempPerson.clothe= (Clothe) clothe.clone();
    return tempPerson;
}

现在p1、p2的Person引用的对象不一样,其内部Clothe域引用的对象也不一样

Person p1 = new Person("tom", 1, new Clothe("裤子"));
Person p2 = (Person) p1.clone();
p2.getClothe().setCoat("裙子");

Tips:

  • clone()不能用于final类型的可变对象域
  • 复写的clone()方法可修改返回值(省去类型转换)、去掉异常并申明为public供外部调用
  • 递归拷贝容易出错,更好的方式是提供拷贝构造器或拷贝工厂(里面调用addAll)

Object.toString()方法

默认打印类名@十六进制Hashcode,通常重写为对域的遍历打印(会自动生成)

class Person {
    private String name;
    private int age;
    
	@Override
	public String toString() {
    	return "Person{" +
           		"name='" + name + '\'' +
             	", age=" + age +
            	'}';
	}
}

打印结果为

Person{name='null', age=0}

你可能感兴趣的:(#,Java中级,java)