教你精通JavaSE语法之第九章、抽象类和接口

简介: Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。范例:使用Object接收所有类的对象。

 目录

一、抽象类

1.1抽象类的概念

1.2抽象类的语法

1.3抽象类的特性

1.4抽象类的作用

1.5抽象类注意事项

二、接口

2.1接口的概念

2.2语法规则

2.3接口使用

2.3.1实现接口

2.3.2接口使用

2.4接口特性

2.5实现多个接口

2.6接口间的继承

2.7接口的注意事项

2.8接口使用实例

2.8.1给对象数组排序

接口 Comparable

2.8.2冒泡排序

2.8.3优化代码实现对分数比较(比较器)

2.9Clonable 接口和深拷贝

2.9.1如何进行对象的拷贝

2.9.3浅拷贝

2.9.4深拷贝

2.9.5深拷贝的实现

三、Object类

3.1Object类介绍

3.2获取对象信息

3.3获取类型信息

3.4对象比较equals方法

3.4.1equals源码

3.4.2equals方法的重写

3.4.3equlas方法的完善

3.5hashcode方法

3.5.1作用

3.5.2hashCode方法的使用


一、抽象类

1.1抽象类的概念

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

image.gif编辑

像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为抽象类(abstract class)

1.2抽象类的语法

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

public abstract class Animal {
    //父类:动物类
    public String name;
    public int age;
    //内部可以有构造方法
    public Animal(){
        System.out.println("父类无参构造");
    }
    public Animal(String name,int age){
        this.name=name;
        this.age=age;
    }
    //抽象方法不给出方法体的实现
    public abstract void eat();
    public abstract void bark();
}

image.gif

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

1.3抽象类的特性

1.抽象类不能直接实例化对象

Shape shape = new Shape();
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化

image.gif

2.抽象方法不能是 private 的

abstract class Shape {
    abstract private void draw();
} 
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private

image.gif

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

public abstract class Shape {
    abstract final void methodA();
    abstract public static void methodB();
}
//编译报错:
// Error:(20, 25) java: 非法的修饰符组合: abstract和final
// Error:(21, 33) java: 非法的修饰符组合: abstract和static

image.gif

4.抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰

(1.)继承后子类重写父类抽象方法

image.gif编辑

(2.)继承后子类没有重写抽象方法image.gif编辑

(3.)继承后子类没有重写抽象方法,子类也需要用abstract修饰

image.gif编辑

(4.)抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类

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

1.4抽象类的作用

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

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

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

1.5抽象类注意事项

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

2.抽象类不能进行实例化,普通类可以进行实例化。

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

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

5.当一个普通类继承了抽象类,那么必须在子类重写抽象类的抽象方法。

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

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

8.满足重写的要求。

9.final关键字不可能同时作用在一个方法或者变量中。

10抽象类中可以存在构造方法,通过子类实例化时会帮助父类成员进行初始化。


二、接口

2.1接口的概念

在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。

通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2.2语法规则

将class关键字换成 interface 关键字,就定义了一个接口。

public interface IUSB {
    public abstract void connect();
    public void open();
    public void close();
}

image.gif

注意:

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

2.接口的命名一般使用 "形容词" 词性的单词

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

2.3接口使用

2.3.1实现接口

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

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

image.gif

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

2.3.2接口使用

实现笔记本电脑使用USB鼠标、USB键盘的例子

1. USB接口:包含打开设备、关闭设备功能

2. 笔记本类:包含开机功能、关机功能、使用USB设备功能

3. 鼠标类:实现USB接口,并具备点击功能

4. 键盘类:实现USB接口,并具备输入功能

public interface IUSB {
    public void open();
    public void close();
}
public class Mouse implements IUSB{
    //重写父类抽象方法
    public void open(){
        System.out.println("打开鼠标!");
    }
    public void close(){
        System.out.println("关闭鼠标!");
    }
    //子类特有的方法
    public void click(){
        System.out.println("点击鼠标!");
    }
}
public class KeyBoard implements IUSB{
    //重写父类的方法
    public void open(){
        System.out.println("打开键盘!");
    }
    public void close(){
        System.out.println("关闭键盘!");
    }
    //子类特有的方法
    public void inPut(){
        System.out.println("键盘输入信息!");
    }
}
public class Computer implements IUSB{
    public void open(){
        System.out.println("打开电脑!");
    }
    public void close(){
        System.out.println("关闭电脑!");
    }
    public void useDevice(IUSB USB){
        //上转型
        USB.open();
        if(USB instanceof Mouse){
            //无法访问子类的成员方法,通过下转型实现
            // USB.click();
            ((Mouse) USB).click();
            ((Mouse) USB).close();
        }
        else if(USB instanceof KeyBoard){
            ((KeyBoard) USB).inPut();
            ((KeyBoard) USB).close();
        }
    }
}
public class TestCompuer {
    public static void main(String[] args) {
        Computer computer=new Computer();
        computer.open();
        computer.useDevice(new Mouse());
        computer.useDevice(new KeyBoard());
        computer.close();
    }
}
/*
打开电脑!
打开鼠标!
点击鼠标!
关闭鼠标!
打开键盘!
键盘输入信息!
关闭键盘!
关闭电脑!
*/

image.gif

2.4接口特性

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

public class TestUSB {
    public static void main(String[] args) {
    USB usb = new USB();
    }
} 
// Error:(10, 19) java: day20210915.USB是抽象的; 无法实例化

image.gif

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

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

image.gif

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

public interface USB {
void openDevice();
    // 编译失败:因为接口中的方式默认为抽象方法
    // Error:(5, 23) java: 接口抽象方法不能带有主体
    void closeDevice(){
    System.out.println("关闭USB设备");
    }
}

image.gif

4.重写接口中方法时,不能使用默认的访问权限

public class Mouse implements USB {
    @Override
    void openDevice() {
        System.out.println("打开鼠标");
    } // ...
} // 编译报错,重写USB中openDevice方法时,不能使用默认修饰符
// 正在尝试分配更低的访问权限; 以前为public

image.gif

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

 

public interface USB {
    double brand = 3.0; // 默认被:final public static修饰
    void openDevice();
    void closeDevice();
}
    public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.brand); // 可以直接通过接口名访问,说明是静态的
        // 编译报错:Error:(12, 12) java: 无法为最终变量brand分配值
        USB.brand = 2.0; // 说明brand具有final属性
    }
}

image.gif

6.接口中不能有静态代码块和构造方法

 

public interface USB {
    // 编译失败
    public USB(){
    } 
// 编译失败
    void openDevice();
    void closeDevice();
}

image.gif

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

8.如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类

9.jdk8中:接口中还可以包含default方法。

2.5实现多个接口

在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口

class Animal {
    protected String name;
    public Animal(String name) {
    this.name = name;
    }
}
interface IFlying {
    void fly();
} 
interface IRunning {
    void run();
} 
interface ISwimming {
    void swim();
}
class Cat extends Animal implements IRunning {
    public Cat(String name) {
    s    uper(name);
} 
@Override
public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}
class Fish extends Animal implements ISwimming {
    public Fish(String name) {
        super(name);
    } @Override
    public void swim() {
        System.out.println(this.name + "正在用尾巴游泳");
    }
}
class Frog extends Animal implements IRunning, ISwimming {
    public Frog(String name) {
        super(name);
    } 
    @Override
    public void run() {
        System.out.println(this.name + "正在往前跳");
    } @
    Override
    public void swim() {
        System.out.println(this.name + "正在蹬腿游泳");
    }
}

image.gif

注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。

还有一种神奇的动物, 水陆空三栖, 叫做 "鸭子":代码功能的拓展

class Duck extends Animal implements IRunning, ISwimming, IFlying {
    public Duck(String name) {
        super(name);
    }
    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飞");
    }
    @Override
    public void run() {
        System.out.println(this.name + "正在用两条腿跑");
    }
    @Override
    public void swim() {
        System.out.println(this.name + "正在漂在水上");
    }
}

image.gif

上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.

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

猫是一种动物, 具有会跑的特性.

青蛙也是一种动物, 既能跑, 也能游泳

鸭子也是一种动物, 既能跑, 也能游, 还能飞

时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,
而只关注某个类是否具备某种能力.

拓展代码的功能:

public static void walk(IRunning running) {
    System.out.println("我带着伙伴去散步");
    running.run();
}
//-------------------------------------------
Cat cat = new Cat("小猫");
walk(cat);
Frog frog = new Frog("小青蛙");
walk(frog);
// 执行结果
我带着伙伴去散步
小猫正在用四条腿跑
我带着伙伴去散步
小青蛙正在往前跳

image.gif

2.6接口间的继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。

接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字

interface IRunning {
    void run();
} interface ISwimming {
        void swim();
        } // 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
...
}

image.gif

通过接口继承创建一个新的接口 IAmphibious 表示 "两栖的". 此时实现接口创建的 Frog 类, 就继续要实现 run 方法, 也需要实现 swim 方法.

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

2.7接口的注意事项

1.接口是通过interface来定义的

2.接口中不能有实现的方法,但是静态方法可以有具体实现的方法。

3.可以被default方法修饰

4.接口中的方法默认被public abstract修饰

5.接口中的成员变量默认被public static final修饰

6.接口不能通过关键字 new 来创建对象

7.接口和类之间用关键字interface来进行关联

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

9.当接口中存在default关键字修饰的方法时,可以进行重写,也可以不重写。具体看业务需求。

10.一个类实现多个接口,可以实现多继承。

11.不管是接口还是抽象类,都可以发生向上转型

12.子类实现接口的方法的时候,这个方法一定要用public修饰

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

14.一个类不想实现接口的方法,可以定义为抽象类。

15.接口只能被public修饰

16.接口可以被abstract修饰

image.gif编辑

2.8接口使用实例

2.8.1给对象数组排序

class Student {
    private String name;
    private int score;
public Student(String name, int score) {
    this.name = name;
    this.score = score;
}
@Override
public String toString() {
    return "[" + this.name + ":" + this.score + "]";
    }
}
//对这个对象数组中的元素进行排序(按分数降序).
Student[] students = new Student[] {
new Student("张三", 95),
new Student("李四", 96),
new Student("王五", 97),
new Student("赵六", 92),
};

image.gif

仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学对象的大小关系怎么确定? 需要我们额外指定

Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable

image.gif

让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法

1.Comparable接口

接口 Comparable<T>

此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。

int compareTo(T o):比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。


2.Arrays.sort();

public class Test {
    public static void main(String[] args) {
        Student stu2=new Student("李四",5);
        Student stu3=new Student("赵六",45);
        Student stu1=new Student("张三",21);
        Student[] students={stu1,stu2,stu3};
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

image.gif

image.gif编辑

image.gif编辑

查看Comparable源码--->接口,内部含有compareTo方法同时查看源码

image.gif编辑

image.gif编辑

也就是说:通过将数组内容强转为Comparable接口后调用其中的compareTo方法

此时我们需要实现Comparable接口,同时重写内部的compareTo方法,才能调用该方法。

class Student implements Comparable{
    public int compareTo(Object o) {
    }
}

image.gif

3.重写compareTo接口

class Student implements Comparable {
    //类实现Comparable接口
    private String name;
    private int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    @Override
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }
    @Override
    public int compareTo(Object o) {
        //祖先类,强制类型转换,向下转型
        //根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
        Student s = (Student)o;
        if (this.score > s.score) {
            return -1;
        } else if (this.score < s.score) {
            return 1;
        } else {
            return 0;
        }
        //return this.age-o.age;
    }
}
public class Test {
    public static void main(String[] args) {
        Student stu2=new Student("李四",5);
        Student stu3=new Student("赵六",45);
        Student stu1=new Student("张三",21);
        Student[] students={stu1,stu2,stu3};
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
        /*
        通过实现接口调用compareTo方法
        int ret=stu1.compareTo(stu2);
        */
    }
}

image.gif

当我们对自定义类型进行比较的时候,一定要实现一个可以比较的接口!

2.8.2冒泡排序

通过自己实现sort方法对对象进行排序。方法的形参为接口数组,由于每个数组元素都实现接口,所以内部通过compareTo方法进行比较。

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] > comparables[j+1]) {
                if(comparables[j].compareTo(comparables[j+1]) > 0) {
                    //交换!
                    Comparable tmp =  comparables[j];
                    comparables[j] = comparables[j+1];
                    comparables[j+1] = tmp;
                }
            }
        }
    }

image.gif

2.8.3优化代码实现对分数比较(比较器

如果实现年龄比较的同时实现对分数的比较,需要另外一个接口comparator接口。

image.gif编辑

通过定义一个类实现该接口的compare方法

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

image.gif

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

image.gif

代码实现:

import java.util.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);
    }
}

image.gif

2.9Clonable 接口和深拷贝

Java 中内置了一些很有用的接口, Clonable 就是其中之一.

Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.

2.9.1如何进行对象的拷贝

class Student{
    public int age;
    public Student(int age){
        this.age=age;
    }
}
public class Test {
    public static void main(String[] args) {
        Student stu1=new Student(15);
        Student stu2=stu1.clone();
    }
}

image.gif

当我们想对对象进行拷贝时,发现clone方法报错,此时我们想要实现这个功能需要对clone方法重写。

image.gif编辑

image.gif编辑  

接下来通过查看clone方法的源码查看具体实现细节:

image.gif编辑

1.首先我们知道:在Java中所有的类默认继承Object类 。所以Student类默认继承Object类,属于不同包的子类中,可以通过super访问。

2.native底层由C/C++实现,会提升程序运行速度

3.返回值为Object类,Object类是所有类的祖先

4.throws CloneNotSupportedException表示抛出异常,需要我们处理这个异常

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student stu1=new Student(15);
        Student stu2=(Student)stu1.clone();
    }
}

image.gif

image.gif编辑

image.gif编辑

此时再重写clone()方法:通过super访问protected修饰的方法

class Student{
    public int age;
    public Student(int age){
        this.age=age;
    }
    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student stu1=new Student(15);
        Student stu2=(Student)stu1.clone();
    }
}

image.gif

同样:此时通过重写toString方法输出对象:

class Student{
    public int age;
    public Student(int age){
        this.age=age;
    }
    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student stu1=new Student(15);
        Student stu2=(Student)stu1.clone();
    }
}

image.gif

image.gif编辑

当我们运行时还会报错,报错原因为不让使用重写的clone()方法,此时引入接口

2.9.2标记接口

接口内部什么东西都没有,当实现该接口表明证明当前的类可以用于某某功能。

Cloneable接口:当某类实现该接口证明该类可以被克隆

image.gif编辑

class Student implements Cloneable{
    public int age;
    public Student(int age){
        this.age=age;
    }
    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student stu1=new Student(15);
        Student stu2=(Student)stu1.clone();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
        //输出结果:
        //Student{age=15}
        //Student{age=15}
    }
}

image.gif

当我们的对象的成员变量中存在对象时:修改代码如下

class Money{
    public double money;
}
class Student implements Cloneable{
    public int age;
    Money m=new Money();
    public Student(int age){
        this.age=age;
    }
    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", m=" + m.money +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student stu1=new Student(15);
        stu1.m.money=19.9;
        Student stu2=(Student)stu1.clone();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
        //输出结果
        //Student{age=15, m=19.9}
        //Student{age=15, m=19.9}
    }
}

image.gif

当我们修改stu1的对象成员变量时,会出现如下问题:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student stu1=new Student(15);
        stu1.m.money=19.9;
        Student stu2=(Student)stu1.clone();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
        stu1.m.money=29.9;
        System.out.println("==========");
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
    }
}

image.gif

image.gif编辑

提问:既然实现的功能是拷贝,为什么我之前已经将stu1的内容拷贝到str2中,修改stu1的值,输出stu2的值也会被更改呢?

2.9.3浅拷贝

image.gif编辑

浅拷贝:对引用数据类型进行拷贝,但是内部如果存在引用数据类型,拷贝的同时地址值不会被完全拷贝,导致指向同一块空间,这就叫做浅拷贝

2.9.4深拷贝

如果对引用数据类型进行拷贝,内部如果存在引用数据类型,拷贝的同时地址值会被完全拷贝,不会指向同一块空间,这就叫做深拷贝。

思路:每一个对象都要被拷贝,而不是直接拿来用。

注意:深浅拷贝实现的代码不一样,实现的原理也不一样

2.9.5深拷贝的实现

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
    protected Object clone() throws CloneNotSupportedException {
        Student tmp = (Student)super.clone();
        tmp.m=(Money)this.m.clone();
        return tmp;
        //return super.clone();
    }
    //@Override
    /*protected Object clone() throws CloneNotSupportedException {
        Student tmp = (Student)super.clone();
        tmp.m = (Money) this.m.clone();
        return tmp;
        //return super.clone();
    }*/
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", m=" + m.money +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Student stu1=new Student(15);
        stu1.m.money=19.9;
        Student stu2=(Student)stu1.clone();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
        stu1.m.money=29.9;
        System.out.println("==========");
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
    }
}

image.gif

image.gif编辑

三、Object类

3.1Object类介绍

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

范例:使用Object接收所有类的对象

class Student{
    public int age;
    public Student(int age) {
        this.age = age;
    }
}
public class Test {
    public static void main(String[] args) {
        Object obj1=new Student(12);
        Object obj2=new Student(15);
        System.out.println(obj1);
        System.out.println(obj2);
        //ByteDemo.Student@4554617c
        //ByteDemo.Student@74a14482
    }
}

image.gif

祖先类Object定义好的方法:

image.gif编辑

3.2获取对象信息

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

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

image.gif

3.3获取类型信息

class Student{
    public int age;
    public Student(int age) {
        this.age = age;
    }
}
public class Test {
    public static void main(String[] args) {
        Object obj1=new Student(12);
        System.out.println(obj1.getClass());
        //输出结果:
        //class ByteDemo.Student---->ByteDemo包下的Student类
    }
}

image.gif

3.4对象比较equals方法

在Java中,== 进行比较时:
a.如果==左右两侧是基本类型变量,比较的是变量中值是否相同
b.如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
c.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:

3.4.1equals源码

public boolean equals(Object obj) {
        //this表示调用该方法的对象的引用
        return (this == obj);
    }

image.gif

3.4.2equals方法的重写

image.gif编辑

class Student{
    public int age;
    public Student(int age) {
        this.age = age;
    }
    public boolean equals(Object obj) {
       Student stu=(Student)obj;
       return this.age==stu.age;
    }
}
public class Test {
    public static void main(String[] args) {
        Student stu1=new Student(10);
        Student stu2=new Student(10);
        System.out.println(stu1.equals(stu2));
        System.out.println(stu1==stu2);
        //true
        //false
    }
}

image.gif

3.4.3equlas方法的完善

class Person{
    private String name ;
    private int age ;
    public Person(String name, int age) {
        this.age = age ;
        this.name = name ;
    }
@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 ;
    }
}

image.gif

3.5hashcode方法

3.5.1作用

       :算一个具体的对象位置

hashcode方法源码:

public native int hashCode();

image.gif

该方法是一个native方法,底层是由C/C++代码写的。我们看不到

3.5.2hashCode方法的使用

我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写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("张三", 20) ;
            Person per2 = new Person("李四", 20) ;
            System.out.println(per1.hashCode());
            System.out.println(per2.hashCode());
        }
    }
}
//执行结果
//460141958
//1163157884

image.gif

注意事项:两个对象的hash值不一样。重写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(age);
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("张三", 20) ;
        Person per2 = new Person("李四", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
/*执行结果:常量在栈区地址值相同
51
51
*/

image.gif

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);
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("张三", 20) ;
        Person per2 = new Person("李四", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
/*执行结果:引用数据类型在堆区地址值不同
774920
842092
*/

image.gif

结论:

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

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

目录
相关文章
|
Java 程序员
【javaSE】 面向对象程序三大特性之继承(一)
【javaSE】 面向对象程序三大特性之继承(一)
|
Java 编译器
【javaSE】 面向对象程序三大特性之继承(二)
【javaSE】 面向对象程序三大特性之继承(二)
|
Java 编译器
【javaSE】 面向对象程序三大特征之多态(一)
【javaSE】 面向对象程序三大特征之多态
|
6月前
|
Python
Python面向对象进阶:深入解析面向对象三要素——封装、继承与多态
Python面向对象进阶:深入解析面向对象三要素——封装、继承与多态
|
6月前
|
设计模式 Java
JavaSE 面向对象程序设计进阶 抽象类和接口 2024年详解
JavaSE 面向对象程序设计进阶 抽象类和接口 2024年详解
43 0
|
6月前
|
Java
JavaSE 面向对象程序设计高级 方法引用 2024详解
JavaSE 面向对象程序设计高级 方法引用 2024详解
25 0
|
6月前
|
Java
JavaSE——面向对象高级二(1/4)-面向对象三大特征之三-多态(认识多态、使用多态的好处、多态下的类型转换问题)
JavaSE——面向对象高级二(1/4)-面向对象三大特征之三-多态(认识多态、使用多态的好处、多态下的类型转换问题)
40 0
|
6月前
|
Java
JavaSE——面向对象高级一(3/4)-面向对象三大特征之二:继承(初步认识继承、了解继承的好处)
JavaSE——面向对象高级一(3/4)-面向对象三大特征之二:继承(初步认识继承、了解继承的好处)
30 0
|
7月前
|
Java
Java面向对象编程,解释封装、继承和多态的概念。
Java面向对象编程,解释封装、继承和多态的概念。
83 2
|
安全 Java
【javaSE】 面向对象程序三大特征之多态(二)
【javaSE】 面向对象程序三大特征之多态(二)