Java中的抽象类和接口

目录

1. 抽象类

1.1 抽象类概念

1.2 抽象类语法 

1.3 抽象类需要注意的点

1.4 抽象类的作用

2. 接口 

2.1 接口的概念

2.2 语法规则

2.3 接口使用 

2.4 接口特性 

2.5 实现多个接口 

2.6 接口间的继承 

2.7 接口使用实例

2.8 Clonable接口,浅拷贝和深拷贝

2.9 抽象类和接口的区别 

3. Object类

3.1 获取对象信息 

3.2 对象比较equals方法 

3.3 hashcode方法 


1. 抽象类

1.1 抽象类概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果 一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 比如:

abstract class Shape {
    public abstract void draw();//抽象方法
}


class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("🟢");
    }
}

class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("🖼️");
    }
}
public class Test2 {
    
    public static void drawMap(Shape shape) {
        shape.draw();
    }
    public static void main(String[] args) {
        drawMap(new Rect());
        drawMap(new Cycle());
    }
}

在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为抽象类(abstract class).

1.2 抽象类语法 

在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用 给出具体的实现体。

// 抽象类:被abstract修饰的类
public abstract class Shape {
    // 抽象方法:被abstract修饰的方法,没有方法体
    abstract public void draw();
    abstract void calcArea();
    // 抽象类也是类,也可以增加普通方法和属性
    public double getArea(){
        return area;
    }
    protected double area; // 面积
}

注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

1.3 抽象类需要注意的点

1. 抽象类和抽象方法都是使用abstract修饰的

2. 抽象类不能实例化,但是普通类可以

3. 抽象类中不一定包含抽象方法,但包含抽象方法的类一定是抽象类

4. 抽象类中可以定义成员变量和成员方法

5. 当一个普通类继承了抽象类,那么普通类中一定要重写抽象类中的抽象方法

6. 抽象类存在的最大的意义就是被继承

7. 当一个抽象类A继承了抽象类B,此时抽象类A不需要重写抽象类B中的抽象方法,但是当一个普通类C继承了抽象类A,此时就要重写所有的抽象方法

8. 抽象方法不能被final,static,private修饰,因为抽象方法要被子类重写

9. final关键字与abstract关键字不可能同时作用在一个方法或者类上

10. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

1.4 抽象类的作用

抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.

有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?

确实如此. 但是使用抽象类相当于多了一重编译器的校验. 使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类 了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.

很多语法存在的意义都是为了 "预防出错", 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们. 充分利用编译器的校验, 在实际开发中是非常有意义的.

2. 接口 

2.1 接口的概念

接口其实就是一种公共的行为规范标准,可以算抽象类的进一步抽象,大家在实现时,只要符合这个标准的,就都可以用这个接口.

在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2.2 语法规则

接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。

public interface 接口名称{
    // 抽象方法
    public abstract void method1(); // public abstract 是固定搭配,可以不写
    public void method2();
    abstract void method3();
    void method4();
// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}

提示:

1. 创建接口时, 接口的命名一般以大写字母 I 开头.

2. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性 

2.3 接口使用 

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。

public class 类名称 implements 接口名称{
// ...
}

注意:子类和父类之间是 extends 继承关系,类与接口之间是 implements 实现关系。 

2.4 接口特性 

1. 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)

public interface IShape {
    // Error:(4, 18) java: 此处不允许使用修饰符private
    private void darw();
    void draw1();
}

2. 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现 

public interface IShape {

    void darw();
    // 编译失败:因为接口中的方式默认为抽象方法
    // Error:(5, 23) java: 接口抽象方法不能带有主体
    void draw1() {
        System.out.println("画图形!");
    }
}

3. 但是在接口中的方法有两个特例

3.1 在接口中,静态方法可以有具体的实现 

interface IShape {
    // int age3 = 1;
    void draw();
    
    public static void draw1() {
        System.out.println("1234");
    }
}

3.2 被default关键字修饰的方法可以有具体的实现(从1.8开始Java引入了这个特性)

interface IShape {
    // int age3 = 1;
    void draw();
    default void draw4() {
        System.out.println("1234");
    }
    public static void draw1() {
        System.out.println("1234");
    }
}

4. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量

interface IShape {
    int age3 = 1;
    void draw();
}

public class Test {
    public static void main(String[] args) {
        System.out.println(IShape.age3);  // 可以直接通过接口名访问,说明是静态的
 
        IShape.age3 = 3; // 编译报错:Error:(12, 12) java: 无法为最终变量brand分配值
                         // 说明age3具有final属性
    }
}

5. 接口类型是一种引用类型,但是不能直接new接口的对象 

public class Test {
    public static void main(String[] args) {
        IShape shape = new IShape();
    }
}

// Error:(10, 19) java: day20210915.USB是抽象的; 无法实例化

6. 当一个类实现了一个接口后,这个类必须重写这个接口当中的抽象方法

7. 当接口中,存在default方法,可以选择重写,也可以选择不重写,具体看需求

interface IShape {
    void draw();
    default void draw4() {
        System.out.println("1234");
    }
}
class Cycle implements IShape {
    @Override
    public void draw() {
        System.out.println("🟢");
    }

    @Override
    public void draw4() {
        System.out.println("我觉得父接口的这个默认方法不好,我自己重写!");
    }
}

8. 不管是接口还是抽象类,它们仍是可以发生向上转型. 

(接口)

interface IShape {
    void draw();
    /*default void draw4() {
        System.out.println("1234");
    }*/
}
class Cycle implements IShape {
    @Override
    public void draw() {
        System.out.println("🟢");
    }

    /*@Override
    public void draw4() {
        System.out.println("我觉得父接口的这个默认方法不好,我自己重写!");
    }*/
}
class Rect implements IShape {
    @Override
    public void draw() {
        System.out.println("🖼️");
    }
}

public class Test {
    public static void drawMap(IShape shape) {
        shape.draw();
    }

    public static void main(String[] args) {
        IShape iShape = new Cycle();
        IShape iShape1 = new Rect();
        drawMap(iShape);
        drawMap(iShape1);
    }

}

(抽象类) 

abstract class Shape {
    public abstract void draw();//抽象方法
}

class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("🟢");
    }
}

class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("🖼️");
    }
}
public class Test {

    public static void drawMap(Shape shape) {
        shape.draw();
    }
    public static void main(String[] args) {
        Shape shape = new Cycle();
        Shape shape1 = new Rect();
        drawMap(shape);
        drawMap(shape1);
    }
}

9. 子类重写方法的时候,这个方法一定要是public修饰的

interface IShape {
    void draw();
}

class Rect implements IShape {
    // 编译报错,重写IShape中draw方法时,不能使用默认修饰符
    // 必须要是public
    @Override
    void draw() {
        System.out.println("🖼️");
    }
}

10. 接口中不能有代码块和构造方法 

interface IShape {
    void draw();
    // 编译失败
    {
        
    }
    // 编译失败
    public IShape() {
        
    }
    
}

11. 一个类如果不想实现接口中的方法,这个类可以被定义为抽象类

12. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class 

 

2.5 实现多个接口 

一个类实现多个接口,可以解决Java中多继承的问题

语法是一定要先继承类在实现接口

interface IFlying {
    void fly();
}

interface ISwimming {
    void swim();
}

interface IRunning {
    void run();
}


abstract class Animal {

    private String name;

    private int age;

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

    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 abstract void eat();
}

class Dog extends Animal implements IRunning, ISwimming {


    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void swim() {
        System.out.println(this.getName() + "正在狗刨!");
    }

    @Override
    public void run() {
        System.out.println(this.getName() + "正在跑步!");
    }

    @Override
    public void eat() {
        System.out.println(this.getName() + "正在吃狗粮!");
    }
}

class Bird extends Animal implements IFlying {

    public Bird(String name, int age) {
        super(name, age);
    }

    @Override
    public void fly() {
        System.out.println(this.getName() + "正在用翅膀飞!");
    }

    @Override
    public void eat() {
        System.out.println(this.getName() + "正在吃鸟量!");
    }
}

class Robot implements IRunning {

    @Override
    public void run() {
        System.out.println("机器人在跑!");
    }
}


public class Test {

    public static void test1(Animal animal) {
        animal.eat();
    }

    public static void testFly(IFlying iFlying) {
        iFlying.fly();
    }

    public static void testRun(IRunning running) {
        running.run();
    }

    public static void testSwim(ISwimming iSwimming) {
        iSwimming.swim();
    }


    public static void main(String[] args) {
        testFly(new Bird("小鸟", 2));
        testRun(new Dog("小狗", 1));
        testSwim(new Dog("小狗", 1));
        testRun(new Robot());
    }

    public static void main1(String[] args) {
        test1(new Dog("小狗", 1));
        test1(new Bird("小鸟", 2));
    }
}

通过上述代码,我们可能会有如下疑惑

为什么不能将三个接口都写成在Animal类的方法呢?

因为有的动物不会飞

为什么不能把三个接口写成三个类呢?

因为Java当中,只支持单继承. 

继承表达的含义是 is - a 语义, 而接口表达的含义是具有 xxx 特性.

时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.比如在上述代码中,不需要是动物,机器人也可以跑.

2.6 接口间的继承 

接口和接口之间也是存在关系的,用extends关键字来关联,此时认为这个意思是扩展接口功能的意思

interface A {
    void testA();
}

interface B {
    void testB();
}
//扩展功能!
interface C extends A,B {
    void testC();
}

class D implements C {

    @Override
    public void testA() {

    }

    @Override
    public void testB() {

    }

    @Override
    public void testC() {

    }
}

可以看到在D类当中要重写A,B接口中的抽象方法.

接口间的继承相当于把多个接口合并在一起.

2.7 接口使用实例

给对象数组排序

class Student {
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
    
}

public class Test {

    public static void main(String[] args) {
        Student[] array = new Student[3];
        array[0] = new Student("zhangsan",89,39.9);
        array[1] = new Student("lisi",69,59.9);
        array[2] = new Student("wangwu",39,68.4);
        System.out.println("排序前:" + Arrays.toString(array));
        Arrays.sort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }
}

我们知道Arrays类中的sort这个方法是可以给数组进行排序的,但是给自定义类型排序时则不行了.

我们可以点进ComparableTimeSort错误,去看源代码.

可以看出数组的内容被强转成了Comparable类型,那Comparable又是什么呢?是一个接口,这个接口当中还有一个compareTo方法,那么我们的学生对象怎么能和这个Comparable接口产生联系呢?

用学生类实现这个接口即可,然后再重写接口中的compareTo方法就可以达到给对象数组排序的效果了(代码是按照年龄排序的).

class Student implements Comparable<Student>{
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }


    @Override
    public int compareTo(Student o) {
        /*if(this.age - o.age > 0) {
            return 1;
        }else if(this.age == o.age) {
            return 0;
        }else {
            return -1;
        }*/
        return this.age - o.age;
    }
}

public class Test {

    public static void main(String[] args) {
        Student[] array = new Student[3];
        array[0] = new Student("zhangsan",89,39.9);
        array[1] = new Student("lisi",69,59.9);
        array[2] = new Student("wangwu",39,68.4);
        System.out.println("排序前:" + Arrays.toString(array));
        Arrays.sort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }

}

为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序)

class Student implements Comparable<Student>{
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }


    @Override
    public int compareTo(Student o) {
        /*if(this.age - o.age > 0) {
            return 1;
        }else if(this.age == o.age) {
            return 0;
        }else {
            return -1;
        }*/
        return this.age - o.age;
    }
}

public class Test {

    public static void main(String[] args) {
        Student[] array = new Student[3];
        array[0] = new Student("zhangsan",89,39.9);
        array[1] = new Student("lisi",69,59.9);
        array[2] = new Student("wangwu",39,68.4);
        System.out.println("排序前:" + Arrays.toString(array));
        //Arrays.sort(array);
        bubbleSort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }

    public static void bubbleSort(Comparable[] comparables) {
        for (int i = 0; i < comparables.length-1; i++) {
            for (int j = 0; j < comparables.length-1-i; j++) {
                if(comparables[j].compareTo(comparables[j+1]) > 0){
                    Comparable tmp = comparables[j];
                    comparables[j] = comparables[j+1];
                    comparables[j+1] = tmp;
                }
            }
        }
    }

}

但是我们会发现 Comparable接口中的compareTo方法被写死了,如果我不按照年龄来排序,那该怎么办呢?我现在来按照成绩排序,还有一个Comparator接口,这个接口更加的灵活.

class Student {
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}
class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
class ScoreComparator implements Comparator<Student> {

    public int compare(Student o1, Student o2) {
        return (int)(o1.score - o2.score);
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan",19,30.9);
        Student student2 = new Student("lisi",9,80.9);

        AgeComparator ageComparator = new AgeComparator();
        int ret = ageComparator.compare(student1,student2);
        System.out.println(ret);
        System.out.println("=====================");

        ScoreComparator scoreComparator = new ScoreComparator();
        int ret2 = scoreComparator.compare(student1,student2);
        System.out.println(ret2);
    }
}

2.8 Clonable接口,浅拷贝和深拷贝

class Student {
    public int age;

    public Student(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }

}

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student(10);
        Student student2 = (Student) student1.clone(); // 编译报错
    }
}

这个clone方法student1这个引用没有访问权限 

class Student implements Cloneable{
    public int age;

    public Student(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }

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

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student(10);
        Student student2 = (Student) student1.clone(); // 编译失败
    }
}

此时,出现了异常,我们只需要处理这个异常就ok了 

这个Cloneable接口,里面什么都没有,它的作用是什么呢?

这个接口被叫做标记接口,只要实现了该接口,就说明当前的类是可以被克隆的.

class Student implements Cloneable{
    public int age;

    public Student(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }

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

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student student1 = new Student(10);
        Student student2 = (Student) student1.clone();
        System.out.println(student1);
        System.out.println(student2);
    }
}

此时证明克隆成功了.

接下来我们在Student类中新增一个对象

class Money {
    public double money;
}

class Student implements Cloneable{
    public int age;
    
    public Money m = new Money();

    public Student(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }

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

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student(10);
        student1.m.money = 19.9;
        Student student2 = (Student) student1.clone(); // 此时还是编译报错  还按照上面的步骤 
                                                       // 就可以解决
    }
}

改正后的代码 

class Money implements Cloneable{
    public double money;

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

class Student implements Cloneable{
    public int age;

    public Money m = new Money();

    public Student(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }

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

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException{
        Student student1 = new Student(10);
        student1.m.money = 19.9;
        Student student2 = (Student) student1.clone();
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
        
    }
}

class Money implements Cloneable{
    public double money;

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

class Student implements Cloneable{
    public int age;

    public Money m = new Money();

    public Student(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }

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

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException{
        Student student1 = new Student(10);
        student1.m.money = 19.9;
        Student student2 = (Student) student1.clone();
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);

        System.out.println("=================");
        student1.m.money = 29.9;
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
        
    }
}

 

为什么我只动改了student1引用指向的对象中的属性,怎么student2引用指向的对象中的属性也跟着改了呢?

首先,我们的代码,先是将student1引用指向的对象拷贝一份,student2这个引用指向了这个拷贝的对象,但是在new Student()对象中,我们还有一个m引用指向的对象没有被拷贝,此时,m引用在student2这个引用指向的对象中,m引用里面的地址值没有改变,所以对student1引用指向的对象中的money属性改动,student2中的也没有变.这种没有彻底将所有对象拷贝的方式就叫做浅拷贝

那么,深拷贝就是将所有对象都拷贝了.

把上面的代码改成深拷贝

只要将上面Student类中的clone方法内容重写就行了.

2.9 抽象类和接口的区别 

1. 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中 不能包含普通方法, 子类必须重写所有的抽象方法.

2. 使用extends关键字继承抽象类,使用关键字implements来实现接口

3. 一个子类只能继承一个抽象类,但是一个子类可以实现多个接口

4. 一个抽象类可以实现多个接口,但是接口不能继承抽象类,接口只能继承接口.

3. Object类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类

class Person{}
class Student{}
public class Test {
    public static void main(String[] args) {
        function(new Person());
        function(new Student());
    }
    public static void function(Object obj) {
        System.out.println(obj);
    }
}

所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。如下:

3.1 获取对象信息 

如果要打印对象中的内容,可以直接重写Object类中的toString()方法,之前已经讲过了,此处不再累赘。

// Object类中的toString()方法实现:
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

3.2 对象比较equals方法 

在Java中,==进行比较时:

a.如果==左右两侧是基本类型变量,比较的是变量中值是否相同

b.如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同

c.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:

// Object类中的equals方法
    public boolean equals(Object obj) {
        return (this == obj); // 使用引用中的地址直接来进行比较
    }
class Person{
    private String name ;
    private int age ;
    public Person(String name, int age) {
        this.age = age ;
        this.name = name ;
    }
}
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("gaobo", 20) ;
        Person p2 = new Person("gaobo", 20) ;
        int a = 10;
        int b = 10;
        System.out.println(a == b); // 输出true
        System.out.println(p1 == p2); // 输出false
        System.out.println(p1.equals(p2)); // 输出false
    }
}

Person类重写equals方法后,然后比较:

class Person{
...
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false ;
        }
        if(this == obj) {
            return true ;
        }
// 不是Person类对象
        if (!(obj instanceof Person)) {
            return false ;
        }
        Person person = (Person) obj ; // 向下转型,比较属性值
        return this.name.equals(person.name) && this.age==person.age ;
    }
}

结论:比较对象中内容是否相同的时候,一定要重写equals方法。

3.3 hashcode方法 

我们看到了hashCode()这个方法,他帮我算了一个具体的对象位置,

 

该方法是一个native方法,底层是由C/C++代码写的。我们看不到。 我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,我们可以来看示例 代码: 

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("gaobo", 20) ;
        Person per2 = new Person("gaobo", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
//执行结果
460141958
1163157884

注意事项:两个对象的hash值不一样.

像重写equals方法一样,我们也可以重写hashcode()方法。此时我们再来看看。 

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("gaobo", 20) ;
        Person per2 = new Person("gaobo", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
//执行结果
460141958
460141958

注意事项:哈希值一样。

结论:

1、hashcode方法用来确定对象在内存中存储的位置是否相同

2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的 散列码,进而确定该对象在散列表中的位置。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/172902.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用pytorch利用神经网络原理进行图片的训练(持续学习中....)

1.做这件事的目的 语言只是工具,使用python训练图片数据,最终会得到.pth的训练文件,java有使用这个文件进行图片识别的工具,顺便整合,我觉得Neo4J正确率太低了,草莓都能识别成为苹果,而且速度慢,不能持续识别视频帧 2.什么是神经网络?(其实就是数学的排列组合最终得到统计结果…

算法分析与设计课后练习23

求下面的0-1背包问题 &#xff08;1&#xff09;N5,M12,(p1,p2,…,p5)(10,15,6,8,4),(w1,w2,…,w5)(4,6,3,4,2) &#xff08;2&#xff09;N5,M15,(p1,p2,…,p5)(w1,w2,…,w5)(4,4,5,8,9)

深入理解JSON及其在Java中的应用

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;每天一个知识点 ✨特色专栏&#xff1a…

日常办公:批处理编写Word邮件合并获取图片全路径

大家在使用Word邮件合并这个功能&#xff0c;比如制作席卡、贺卡、准考证、员工档案、成绩单、邀请函、名片等等&#xff0c;那就需要对图片路径进行转换处理&#xff0c;此脚本就是直接将图片的路径提取出来&#xff0c;并把内容放到txt格式的文本文档里&#xff0c;打开Excel…

netty整合websocket(完美教程)

websocket的介绍&#xff1a; WebSocket是一种在网络通信中的协议&#xff0c;它是独立于HTTP协议的。该协议基于TCP/IP协议&#xff0c;可以提供双向通讯并保有状态。这意味着客户端和服务器可以进行实时响应&#xff0c;并且这种响应是双向的。WebSocket协议端口通常是80&am…

Redis:抢单预热

前言 在当今的互联网时代&#xff0c;抢单活动已经成为了电商平台、外卖平台等各种电子商务平台中常见的营销手段。通过抢单活动&#xff0c;商家可以吸引大量用户参与&#xff0c;从而提高销量和知名度。然而&#xff0c;抢单活动所带来的高并发请求往往会给系统带来巨大的压…

opencv-形态学处理

通过阈值化分割可以得到二值图&#xff0c;但往往会出现图像中物体形态不完整&#xff0c;变的残缺&#xff0c;可以通过形态学处理&#xff0c;使其变得丰满&#xff0c;或者去除掉多余的像素。常用的形态学处理算法包括&#xff1a;腐蚀&#xff0c;膨胀&#xff0c;开运算&a…

Spring-IOC-@Import的用法

1、Car.java package com.atguigu.ioc; import lombok.Data; Data public class Car {private String cname; }2、 MySpringConfiguration2.java package com.atguigu.ioc; import org.springframework.context.annotation.Bean; import org.springframework.context.annotatio…

一、防火墙-基础知识

学习防火墙之前&#xff0c;对路由交换应要有一定的认识 1、什么是防火墙2、防火墙的发展史3、安全区域3.1.接口、网络和安全区域的关系3.2.报文在安全区域之间流动方向3.3.安全区域的配置安全区域小实验 3.4.状态检测和会话机制3.4.1.状态检测3.4.2.会话 3.5.状态检测和会话机…

c语言-数据结构-链式二叉树

目录 1、二叉树的概念及结构 2、二叉树的遍历概念 2.1 二叉树的前序遍历 2.2 二叉树的中序遍历 2.3 二叉树的后序遍历 2.4 二叉树的层序遍历 3、创建一颗二叉树 4、递归方法实现二叉树前、中、后遍历 4.1 实现前序遍历 4.2 实现中序遍历 4.3 实现后序遍历 5、…

《算法通关村——最长公共前缀问题解析》

《算法通关村——最长公共前缀问题解析》 14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 示例 1&#xff1a; 输入&#xff1a;strs ["flower","flow","flight…

腾讯云代金券怎么领取(腾讯云代金券在哪领取)

腾讯云代金券是可抵扣费用的优惠券&#xff0c;领券之后新购、续费、升级腾讯云相关云产品可以直接抵扣订单金额&#xff0c;节省购买腾讯云的费用&#xff0c;本文将详细介绍腾讯云代金券的领取方法和使用教程。 一、腾讯云代金券领取 1、新用户代金券【点此领取】 2、老用户…

Unity中Shader的PBR的基础知识与理论

文章目录 前言一、什么是PBR二、什么是PBS在这里插入图片描述 三、PBS的核心理论1、物质的光学特性&#xff08;Substance Optical Properties&#xff09;2、微平面理论&#xff08;Microfacet Theory&#xff09;3、能量守恒&#xff08;Energy Conservation&#xff09;4、菲…

90%的测试工程师是这样使用Postman做接口测试的...

一&#xff1a;接口测试前准备 接口测试是基于协议的功能黑盒测试&#xff0c;在进行接口测试之前&#xff0c;我们要了解接口的信息&#xff0c;然后才知道怎么来测试一个接口&#xff0c;如何完整的校验接口的响应值。 那么问题来了&#xff0c;那接口信息从哪里获取呢&…

金山云2023年Q3财报:持续向好!

11月21日&#xff0c;金山云公布了2023年第三季度业绩。 财报显示&#xff0c;金山云Q3营收16.3亿元&#xff0c;调整后毛利率达12.1%再创历史新高&#xff0c;调整后毛利额同比上涨57.5%。今年第三季度&#xff0c;公有云实现收入10.2亿元&#xff0c;毛利率达到4.7%&#xf…

STM32出现 Invalid Rom Table 芯片锁死解决方案

出现该现象的原因为板子外部晶振为25M&#xff0c;而程序软件上以8M为输入晶振频率&#xff0c;导致芯片超频锁死&#xff0c;无法连接、下载。 解决方案 断电&#xff0c;将芯片原来通过10k电阻接地的BOOT0引脚直接接3.3V&#xff0c;硬件上置1上电&#xff0c;连接目标板&am…

Redis跳跃表

前言 跳跃表(skiplist)是一种有序数据结构&#xff0c;它通过在每一个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。 跳跃表支持平均O(logN)&#xff0c;最坏O(N)&#xff0c;复杂度的节点查找&#xff0c;还可以通过顺序性来批量处理节点…

ROS2中Executors对比和优化

目录 SingleThreadExecutorEventExecutor SingleThreadExecutor 执行流程 EventExecutor 通信图

局域网文件共享神器:Landrop

文章目录 前言解决方案Landrop软件界面手机打开效果 软件操作 前言 平常为了方便传文件&#xff0c;我们都是使用微信或者QQ等聊天软件&#xff0c;互传文件。这样传输有两个问题&#xff1a; 必须登录微信或者QQ聊天软件。手机传电脑还有网页版微信&#xff0c;电脑传手机比…

MT8735/MTK8735安卓核心板规格参数介绍

MT8735核心板是一款高性能的64位Cortex-A53四核处理器&#xff0c;设计用于在4G智能设备上运行安卓操作系统。这款多功能核心板支持LTE-FDD/LTE-TDD/WCDMA/TD-SCDMA/EVDO/CDMA/GSM等多种网络标准&#xff0c;同时还具备WiFi 802.11a/b/g/n和BT4.0LE等无线通信功能。此外&#x…