13.1 方法重写的概念
如果子类需要对继承自父类的某些方法的默认逻辑进行修改,则可以在子类中对该方法进行定义,即是重写(Override)。类中的私有方法、构造方法与静态方法不能重写。
Java中方法重写的语法规则:
- 方法名相同、参数列表相同(数量,类型和顺序)
- 不能缩小访问权限
- 不能抛出比父类方法更多或范围更大的异常
- 子类方法的返回值与父类相同或是其子类
- 父类的非静态方法不能重写为静态方法
13.2 实现方法重写
下面通过例子来说明这几个语法规则(暂不讨论第3点与异常相关的规则),比如下面有一个父类,其中声明了一个带 int 参数且返回Parent实例的方法:
java
public class Parent {
public Parent method1(int num) {
System.out.println("Parent method1");
return new Parent();
}
}
声明子类Child,在其中重写 method1 方法:
java
public class Child extends Parent {
@Override
public Parent method1(int num) {
System.out.println("Child method1");
return new Parent();
}
}
其中第2行出现的 @Override 是一个注解,用于表示下面的方法是合法重写,如果没有这个注解也不影响代码实现,但是有这个注解,一旦它修饰的方法违反了方法重写的语法,IDEA会报错。
第3行开始的方法声明与父类中的方法声明是完全一样的,所以这是一个合法的方法重写。
但是如果将方法名或参数列表改为与父类方法不同,则就不是合法重写:
java
public Parent method2(int num) { // method2是一个合法的方法,但不是合法的重写方法
System.out.println("Child method2");
return new Parent();
}
public Parent method1() { // 没有参数,与父类方法参数类别不同,不是合法重写
System.out.println("Child method1");
return new Parent();
}
如果将父类方法的public改为其他更小的访问控制修饰符也是不成立的:
java
Parent method1(int num) {
System.out.println("Child method1");
return new Parent();
}
上面的方法声明的是默认访问控制级别,比父类方法的 public 范围小,也不是合法重写。
对于第4点,在开发中到是并不常见,比如:
java
public Child method1(int num) {
System.out.println("Child method1");
return new Child();
}
这种将方法返回值类改成父类方法返回值类型的子类型的写法也是合法的重写。
对于第5点规则,也好理解,就是在重写方法前使用static修饰也是不成立的:
java
public static Child method1(int num) { // 不是合法重写
System.out.println("Child method1");
return new Child();
}
在实际开发中,基本上方法重写就是子类声明的方法与父类声明的方法完全保持一致即可满足要求。
13.3 方法重写的调用规则
由于子类并不一定要重写父类的方法,所以在调用子类实例的方法时遵循的流程如下:
-
在本类中查找是否有该方法,有则直接调用,否则进入第2步
-
在父类中查找是否有该方法,有则直接调用,否则继续往上层的父类中找
-
直至在java.lang.Object中查找
比如下面的父类与子类的声明:
java
public class Parent {
public void method() {
System.out.println("Parent method");
}
}
public class Child extends Parent {
}
就算Child类没有重写Parent的方法method,也是可以继承下来从而调用到的
java
Child child=new Child();
child.method();
此时在子类中没有重写method方法,也能访问到继承自父类的methid方法。结果是:
Parent method
如果子类重写了method方法,则直接调用即可:
java
public class Child extends Parent {
public void method() {
System.out.println("Child method");
}
}
那么运行的结果就是子类重写方法的实现:
Child method
13.4 重写Object中的方法
由于 Object 类是所有类的"老祖宗",所有的类,包括数组都继承了 Object 的部分方法,很多情况下,默认的实现并不能满足开发需求,一般都要在子类中重写部分方法。本小节主要关注 toString,equals及hashCode方法的重写。
重写toString
toString 方法会将对象的信息以字符串形式返回,在字符串拼接和控制台输出时会自动调用该方法。
在Object类中默认的实现如下,返回的是全限定类名@哈希码的16进制字符串表示:
java
public String toString() {
return getClass().getName() + "@" +Integer.toHexString(hashCode());
}
还是以Dog类为例,之前声明的Dog类是没有重写 ToString 方法的:
java
public class Dog {
private int age; // 年龄
private float weight; // 体重
private String breed; // 品种
private String color; // 毛色
// 省略 setter 与 getter
}
实例化后直接输出:
java
Dog erHa1=new Dog();
erHa1.setAge(3);
erHa1.setWeight(2.4F);
erHa1.setBreed("西伯利亚哈士奇");
erHa1.setColor("灰色");
System.out.println(erHa1);
结果如下,格式就是 Object 类 toString 方法返回的字符串:
com.laotan.article13.Dog@f6f4d33
一般在开发中,会重写toString方法以返回更有意义的信息:
java
@Override
public String toString() {
return "年龄:" + age + "月,体重:" + weight + "千克,品种:" + breed + ",毛色:" + color;
}
再次运行测试代码,输入结果就大为不同:
年龄:3月,体重:2.4千克,品种:西伯利亚哈士奇,毛色:灰色
重写equals
equals方法用于比较两个对象是否相等,其参数是 Object,返回的结果是boolean。方法体内实现比较的逻辑就是使用 == 运算符判断地址是否相等,即两个对象是否是同一个:
java
public boolean equals(Object obj) {
return (this == obj);
}
不过在实际开发中,大部分场景会对两个对象在逻辑以上是否相等更感兴趣,也就是只要两个对象在自定义的相等的逻辑下时相等的就可以,不需要是同一个对象,此时重写equals就很有必要。比如下面两个实例:
java
Dog erHa1=new Dog();
erHa1.setAge(3);
erHa1.setWeight(2.4F);
erHa1.setBreed("西伯利亚哈士奇");
erHa1.setColor("灰色");
Dog erHa2=new Dog();
erHa2.setAge(3);
erHa2.setWeight(2.4F);
erHa2.setBreed("西伯利亚哈士奇");
erHa2.setColor("灰色");
它们的四个属性值都相同,但是默认情况下调用equlas结果会是false:
true
System.out.println(erHa1.equals(erHa2)); // 输出 false
如果对equals重写,我们就可以指定自己的比较规则,比如:
java
@Override
public boolean equals(Object obj) {
if (obj == null) { // 与 null 比,直接返回 false
return false;
}
if (obj instanceof Dog) { // 判断传如的比较参数是否是 DOg 类型
Dog d = (Dog) obj; // 涉及到向下转型
if (this.age != d.age) {
return false;
}
if (this.weight != d.weight) {
return false;
}
if (this.breed == null && d.breed != null) {
return false;
}
if (this.breed != null && !this.breed.equals(d.breed)) {
return false;
}
if (this.color == null && d.color != null) {
return false;
}
if (this.color != null && !this.color.equals(d.color)) {
return false;
}
return true;
}
return false;
}
再次进行equals调用,就是访问到子类重写的equals方法了,结果为 true。
equals方法重写约定
- 自反性(reflexive):任意非 null 的 x,x.equals(x) 总是返回 true
- 对称性(symmetric):任意非 null 的 x, y,x.equals(y) 与 y. equals(x) 结果相等
- 传递性(transitive):任意非 null 的 x, y, z,如果 x.equals(y) 为 true,y.equals(z) 为 true,则 x.equals(z) 就为 true
- 一致性(consistent):任意非 null 的 x, y,如果用于 equals 的信息没被修改,多次调用 equals 方法结果不变
- 任何非 null 的 x, x.equals(null) 总是false
- 重写 equals 方法时永远要重写 hashCode 方法
重写hashCode
哈希码是指使用哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,在基于哈希存储数据的集合容器(如:HashSet)中,能通过哈希码高效获取数据,通常建议equals与hashCode同时重写。
在Object类中的,该方法是一个native(本地)方法,意味着它的具体实现并不是Java实现的。
java
public native int hashCode();
默认实现是返回对象在内存中的地址转换之后的整数,我们可以根据特定的算法根据类中的属性实现获取hashCode的逻辑,不过JDK中提供了一个方法可以帮我们实现计算:
java
public int hashCode() {
return Objects.hash(age, weight, breed, color);
}
该方法内部实现如下,可以了解下:
java
public static int hashCode(Object[] a) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
hashCode的重写规则:
- 如果两个对象使用equals比较的结果相等,则其hashCode返回值也相等,反过来不一定
- 同一个对象hashCode方法在一个程序中调用多次,应该返回相同的值
另外,在开发中也可以使用IDEA同时自动生成equals与hashCode方法;或者借助Lombok开源包简化代码
13.5 小结
本小节介绍了Java中方法重写的概念与实现规则。方法重写是指子类修改继承自父类方法的默认逻辑,需遵循方法名、参数列表相同,不能缩小访问权限,返回值类型相同或更具体等规则。文章详细讲解了重写的语法规则和调用流程,并提供代码示例说明。特别强调了Object类中toString()、equals()等常用方法的重写必要性,通过Dog类实例展示了如何自定义这些方法的比较逻辑,使对象比较更符合业务需求。