Java核心-你真的知道Object吗(Object:所有类的超类)

作者:逍遥Sean
简介:一个主修Java的Web网站\游戏服务器后端开发者
主页:https://blog.csdn.net/Ureliable
觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言!

前言
今天来聊一聊对象(bushi)!!!

众所周知,Java每个类都是对象,Object类是Java中所有类的始祖,在Java中每个类都扩展了Object。但是并不需要这样写:

public class Employee extends Object

如果没有明确的指出超类,Object就被认为是这个类的超类。由于在Java中每个类都是由Object扩展而来的。所以熟悉这个类提供的所有服务十分重要。文章将介绍一些基本的内容,没有提到的内容大多是涉及并发操作的,后续将有文章介绍哦~

Object:所有类的超类

  • 1 Object类型的变量
  • 2 equals方法
  • 3 相等测试与继承
  • 4 hashCode方法
  • 5 toString方法

1 Object类型的变量

可以使用Object类型的变量引用任何类型的对象

Object obj = new String("Hello");

Object可以用来声明一个指向任何对象的引用变量,以及作为方法的参数和返回类型。例如:

Object obj = new String("Hello");
obj = new Integer(10);
obj = new MyClass();

当然,Object只能是作为各种值的一个泛型容器。想要对其中的内容进行具体的操作,还是需要清楚对象的原始类型,并进行相应的强制转换:

Object obj = new MyClass();
MyClass mc = (MyClass) obj;

在这里,obj变量被声明为Object类型,因此可以先将它指向一个String对象,然后又改为指向一个Integer对象,最后又指向一个自定义的MyClass对象。注意,当obj变量指向不同类型的对象时,需要进行类型转换才能访问其特定的方法和属性。

在Java中,只有基本数据类型不是对象

所有的数组结构也继承了Object
例如:

MyClass[] mcs = new MyClass[];
Object obj = mcsl; //OK

2 equals方法

Java的equals方法是一个用于比较两个对象是否相等的方法。它是Object类中定义的方法,并且可以被任何类继承和重写。
在Java中,如果两个对象的内容相同,则它们被认为是相等的。因此在比较两个对象是否相等时,需要考虑对象的内容而不是对象的引用。
例如,假设我们有一个名为Employee 的类。我们可以通过如下的方式实现equals方法:

public class Employee {
    // 省略属性声明
	...
	public boolean equals(Object otherObject)
	{
		// a quick test to see if the objects are identical
		if (this == otherObject) return true;
		// must return false if the explicit parameter is null
		if (otherObject == null) return false;
		// if the classes don't match, they can't be equal
		if (getClass() != otherObject.getClass())
		return false;
		// now we know otherObject is a non-null Employee
		Employee other = (Employee) otherObject;
		// test whether the fields have identical values
		return name.equals(other. name)
		&& salary = other.salary
		&& hireDay. equals(other,hireDay):
    }
}

这个equals方法首先检查传递进来的对象是否为空或者是否指向当前对象,如果是则直接返回true或false。接下来,它检查传递进来的对象是否是Employee类的实例。如果不是,则返回false。最后,它将传递进来的对象转换成Employee对象,并比较它们的name和salary字段是否相等。如果这两个字段相等,则返回true,否则返回false。

注:上述代码中,为了防备name可能为null的情况,需要使用Object.equals方法,如果两个参数都是null,Object.equals会返回true;如果其中一个参数都是null,Object.equals会返回false;如果两个参数都不是null,Object.equals会调用a.equals(b)

在子类中定义 equals 方法时, 首先调用超类的 equals。如果检测失败, 对象就不可能相等。如果超类中的域都相等, 就需要比较子类中的实例域。

public class Manager extends Employee {
	public boolean equals(Object otherObject){
		if (!super.equals(otherObject)) return false;
		Manager other = (Manager) otherObject;
		return bonus == other.bonus;
	}
}

3 相等测试与继承

如果隐式和显式的参数不属于同一个类, equals 方法将如何处理呢? 这是一个很有争议的问题。 在前面的例子中, 如果发现类不匹配, equals 方法就返冋 false。但是, 许多程序员却喜欢使用 instanceof 进行检测:

if ( !(otherObject instanceof Employee)) return false;

这样做不但没有解决 otherObject 是子类的情况,并且还有可能会招致一些麻烦。这就是建议不要使用这种处理方式的原因所在。
Java 语言规范要求 equals 方法具有下面的特性:

  1. 自反性:对于任何非空引用 x, x.equals(x) 应该返回 true;
  2. 对称性: 对于任何引用 x 和 y, 当且仅当 y.equals(x) 返回 true , x.equals(y) 也应该返回 true;
  3. 传递性: 对于任何引用 x、 y 和 z, 如果 x.equals(y) 返回 true, y.equals(z) 返回 true,x.equals(z) 也应该返回 true;
  4. 一致性: 如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果;
  5. 非空性:对于任意非空引用 x, x.equals(null) 应该返回 false。

这些规则十分合乎情理,从而避免了类库实现者在数据结构中定位一个元素时还要考虑调用 x.equals(y), 还是调用 y.equals(x) 的问题。
然而, 就对称性来说, 当参数不属于同一个类的时候需要仔细地思考一下。请看下面这个调用:

e.equals(m)

这里的 e 是一个 Employee 对象, m 是一个 Manager 对象, 并且两个对象具有相同的姓名、薪水和雇佣日期。 如果在 Employee.equals 中用 instanceof 进行检测, 则返回 true. 然而这意味着反过来调用:

m.equals(e)

也需要返回 true。但对称性不允许这个方法调用返回 false,或者抛出异常。
这就使得 Manager 类受到了束缚。 这个类的 equals 方法必须能够用自己与任何一个 Employee 对象进行比较, 而不考虑经理拥有的那部分特有信息! 猛然间会让人感觉instanceof 测试并不是完美无瑕。
某些开发者认为不应该利用 getClass 检测, 因为这样不符合置换原则有一个应用AbstractSet 类的 equals 方法的典型例子,它将检测两个集合是否有相同的元素。AbstractSet类有两个具体子类: TreeSet 和HashSet, 它们分别使用不同的算法实现查找集合元素的操作。无论集合采用何种方式实现,都需要拥有对任意两个集合进行比较的功能。
然而, 集合是相当特殊的一个例子, 应该将 AbstractSetequals 声明为 final , 这是因为没有任何一个子类需要重定义集合是否相等的语义(事实上,这个方法并没有被声明为 final。这样做, 可以让子类选择更加有效的算法对集合进行是否相等的检测)。
下面可以从两个截然不同的情况看一下这个问题:

  • 如果子类能够拥有自己的相等概念, 则对称性需求将强制采用 getClass 进行检测。
  • 如果由超类决定相等的概念,那么就可以使用 imtanceof进行检测, 这样可以在不同子类的对象之间进行相等的比较。

在雇员和经理的例子中, 只要对应的域相等, 就认为两个对象相等。如果两个 Manager对象所对应的姓名、 薪水和雇佣日期均相等, 而奖金不相等, 就认为它们是不相同的, 因此,可以使用 getClass 检测。
但是,假设使用雇员的 ID 作为相等的检测标准,并且这个相等的概念适用于所有的子类, 就可以使用 instanceof 进行检测, 并应该将 Employee.equals 声明为 final。

4 hashCode方法

散列码( hash code ) 是由对象导出的一个整型值。散列码是没有规律的。如果 x 和 y 是两个不同的对象, x.hashCode( ) 与 y.hashCode( ) 基本上不会相同。 在下表中列出几个通过调用 String 类的 hashCode 方法得到的散列码。
String 类使用下列算法计算散列码:

int hash = 0;
for (int i = 0; i < length0;i++)
hash = 31 * hash + charAt(i)

由于 hashCode方法定义在 Object 类中, 因此每个对象都有一个默认的散列码,其值为对象的存储地址。来看下面这个例子。

String s = "Ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());
String t = new String("Ok");
StringBuilder tb = new StringBuilder(t);
System.out.println(t.hashCode() + " "+ tb.hashCode());

Java核心-你真的知道Object吗(Object:所有类的超类)_第1张图片
请注意, 字符串 s 与 t 拥有相同的散列码, 这是因为字符串的散列码是由内容导出的。而字符串缓冲 sb 与 tb却有着不同的散列码, 这是因为在 StringBuffer 类中没有定义hashCode 方法,它的散列码是由 Object 类的默认 hashCode 方法导出的对象存储地址。
如果重新定义 equals方法,就必须重新定义 hashCode 方法, 以便用户可以将对象插入到散列表中。
hashCode 方法应该返回一个整型数值(也可以是负数), 并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
例如, 下面是 Employee 类的 hashCode 方法。

public class Employee
{
	public int hashCode()
	{
		return 7 * name.hashCode0
			+ 11 * new Double(salary).hashCode0
			+ 13 * hireDay.hashCode();
	}
	...
}

不过,还可以做得更好。首先, 最好使用 null 安全的方法Objects. hashCode。 如果其参数为 null,这个方法会返回 0, 否则返回对参数调用 hashCode 的结果。
另外,使用静态方法 Double.hashCode 来避免创建 Double 对象:

public int hashCode()
{
	return 7 * Objects.hashCode(name)
	+ 11 * Double.hashCode(salary)
	+ 13 * Objects.hashCode(hireDay);
}

还有更好的做法,需要组合多个散列值时,可以调用 Objects.hash 并提供多个参数。这个方法会对各个参数调用 Objects.hashCode, 并组合这些散列值。这样 Employee.hashCode 方法可以简单地写为:

public int hashCodeO
{
return Objects.hash(name, salary, hireDay);
}

equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true, 那么 x.hashCode( ) 就必须与 y.hashCode( ) 具有相同的值。例如,如果用定义的 Employee.equals 比较雇员的 ID,那么 hashCode 方法就需要散列 ID,而不是雇员的姓名或存储地址。

如果存在数组类型的域, 那么可以使用静态的 Arrays.hashCode 方法计算一个散列,这个散列码由数组元素的散列码组成。

5 toString方法

在 Object 中还有一个重要的方法, 就是 toString方法, 它用于返回表示对象值的字符串。下面是一个典型的例子。Point 类的 toString方法将返回下面这样的字符串:

java.awt.Point[x=10,y=20]

绝大多数(但不是全部)的 toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值。下面是 Employee 类中的 toString 方法的实现:

public String toStringO
{
return "Employee[name=" + name
+ ", salary=" + salary
+ ", hireDay=" + hireDay
}

toString方法也可以供子类调用。
当然,设计子类的程序员也应该定义自己的 toString 方法,并将子类域的描述添加进去。如果超类使用了 getClass( ).getName( ), 那么子类只要调用 super.toString( )就可以了。例如,下面是 Manager 类中的 toString 方法:

public class Manager extends Employee {
	public String toString {
		return super.toString() + "[bonus=" + bonus+ "]";
	}
}

现在,Manager 对象将打印输出如下所示的内容:

Manager[name=...,salary=...,hireDay=...][bonus=...]

随处可见 toString方法的主要原因是:只要对象与一个字符串通过操作符
“+” 连接起来,Java 编译就会自动地调用 toString方法,以便获得这个对象的字符串描述。例如:

Point p = new Point(10, 20);
String message = "The current position is " + p;

注:在调用 x.toString() 的地方可以用 “”+x 替代。这条语句将一个空串与 x 的字符串表示相连接。这里的 x 就是 x.toString() 。与 toString 不同的是,如果 x 是基本类型,这条语句照样能够执行

你可能感兴趣的:(#,Java核心,java,开发语言)