Java学习之旅第二季-13:方法重写

13.1 方法重写的概念

如果子类需要对继承自父类的某些方法的默认逻辑进行修改,则可以在子类中对该方法进行定义,即是重写(Override)。类中的私有方法、构造方法与静态方法不能重写。

Java中方法重写的语法规则:

  1. 方法名相同、参数列表相同(数量,类型和顺序)
  2. 不能缩小访问权限
  3. 不能抛出比父类方法更多或范围更大的异常
  4. 子类方法的返回值与父类相同或是其子类
  5. 父类的非静态方法不能重写为静态方法

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 方法重写的调用规则

由于子类并不一定要重写父类的方法,所以在调用子类实例的方法时遵循的流程如下:

  1. 在本类中查找是否有该方法,有则直接调用,否则进入第2步

  2. 在父类中查找是否有该方法,有则直接调用,否则继续往上层的父类中找

  3. 直至在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类实例展示了如何自定义这些方法的比较逻辑,使对象比较更符合业务需求。

相关推荐
A懿轩A9 分钟前
【Maven 构建工具】从零到上手 Maven:安装配置 + IDEA 集成 + 第一个项目(保姆级教程)
java·maven·intellij-idea
野犬寒鸦18 分钟前
从零起步学习并发编程 || 第一章:初步认识进程与线程
java·服务器·后端·学习
科技林总22 分钟前
【系统分析师】6.3 企业信息化规划
学习
我爱娃哈哈22 分钟前
SpringBoot + Flowable + 自定义节点:可视化工作流引擎,支持请假、报销、审批全场景
java·spring boot·后端
XiaoFan01241 分钟前
将有向工作流图转为结构树的实现
java·数据结构·决策树
小突突突1 小时前
浅谈Java中的反射
java·开发语言
Anastasiozzzz1 小时前
LeetCode Hot100 295. 数据流的中位数 MedianFinder
java·服务器·前端
我真的是大笨蛋1 小时前
Redo Log详解
java·数据库·sql·mysql·性能优化
丝斯20111 小时前
AI学习笔记整理(67)——大模型的Benchmark(基准测试)
人工智能·笔记·学习
索荣荣2 小时前
Java动态代理实战:从原理到精通
java·开发语言