《JavaSE-第十章》之抽象类与接口

前言

前面我们已经学了继承,在继承中我们会发现,子类重写了父类的方法,最终使用的子类的方法,而父类中方法的方法体没啥用,那么是否能把它剩略呢?另一个问题就是类不能多继承,子类的功能是不方便的扩展与维护的。这两个问题都会随着本章所讲解的抽象类与接口所回答,接下来就让我们带着问题进入本章的阅读。

博客主页:KC老衲爱尼姑的博客主页

博主的github,平常所写代码皆在于此

共勉:talk is cheap, show me the code

作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!

🌍1.1抽象类

🌍什么是抽象类?

在java中类是用来描述对象的信息,当一个类没有包含足够的信息去描述一个具体的对象,那么这样的类被称为抽象类。比如

在这里插入图片描述

圆形,正方形,三角形都属于图形,因此和图形的关系是继承关系。而图形类中虽然有draw()方法,但是图形类不是一个具体的形状,所以无法去画一个具体的图形。又因为图形类本身就是通过对圆形,正方形,三角形等图形进一步抽象而来的类,导致其方法是无法具体实现,所以可以将该类称之为抽象类。

🌍1.2为什么要使用抽象类?

先来看一段代码

public class Shape {
    public void draw(){
        System.out.println("画一个图形");
    }

}

public class Circular extends Shape{
    public void draw(){
        System.out.println("画一个圆形");
    }
}

public class Square  extends Shape{
    public void draw(){
        System.out.println("画一个正方形");
    }
}

public class Triangle  extends Shape{
    public void draw(){
        System.out.println("画一个三角形");
    }
}

public class Text {
    public static void main(String[] args) {
        Shape c = new Circular();
        c.draw();
        Shape t = new Triangle();
        t.draw();
        Shape s = new Square();
        s.draw();
    }
}

圆形,正方形,三角形都重写了父类Shape中的draw()方法,最终执行的是子类中的方法,而父类中draw();方法体中的内容一点用都没有。此时就可以将该方法定义为抽象方法,而包含抽象方法得到类则定义为抽象类。

🌍1.3抽象类的语法

在java中,如果一个类被abstract修饰则称之为抽象类,抽象类中的倍abstract修饰的方法被称为抽象方法,而抽象方法是可以不写具体的方法体。

代码示例

public abstract class Shape {
    public int area;
   private double height;
    public  static final int width =90;

    public Shape(int area, double height) {
        this.area = area;
        this.height = height;
    }

    public abstract void draw();

    public void  print(){
        System.out.println("我是一个实例方法");
    }

    public static void method(){
        System.out.println("我是一个静态方法");
    }

    public int getArea() {
        return area;
    }

    public void setArea(int area) {
        this.area = area;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public static int getWidth() {
        return width;
    }
}

抽象类与普通类最大的区别就是抽象类被abstract,以及该类中可以定义抽象方法。在其它方面基本与普通类差不多,如抽象类可以定义实例成员变量,可以提供构造器,可以定义实例方法与静态方法。

🌍1.4抽象类特性

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

  Shape s=new Shape();

创建对象是通过一个具体的类,描述出一个具体的对象。而图形本身就是抽象出来的概念,将它实例出来的图形还是图形,那么图形是啥呢?而实例化对象是获得一个具体对象,所以抽象类不能直接实例化对象。

2.抽象方法不能是 private 修饰的

public abstract class Shape {
    private abstract void draw();
        
}

private修饰的成员只能在本类中访问,而抽象方法被其它子类访问并重写的。

注意:抽象方法没有加访问限定符时,默认是public.

  1. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
public abstract class Shape {
   public  final abstract void draw();

}

public abstract class Shape {
   public  static abstract void draw();

}

final修饰方法都不能被重写,static修饰的方法也不能被继承。

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

代码示例

public abstract class Shape {
   public   abstract void draw();
    public abstract void rotating();
}


public  abstract class Triangle  extends Shape{
   public abstract void print();
}

public class LsoscelesTriangle extends Triangle {

    @Override
    public void draw() {
        System.out.println("画一个等腰三角形");
    }

    @Override
    public void print() {
        System.out.println("我是一个等腰三角形");
    }

    @Override
    public void rotating() {
        System.out.println("等腰三角形旋转90度");
    }
}


public class Text {
    public static void main(String[] args) {
        LsoscelesTriangle l=new LsoscelesTriangle();
        l.draw();
        l.print();
    }
}

三角形定义为抽象类并继承图形类是可以不重写图形类中的draw();方法,而等腰三角形类继承了三角形类,所以它要重写图形类和三角形类中的所有的抽象方法。

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

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

1.5🌍抽象类的使用总结与注意事项
  1. 抽象类可以理解成类的不完整设计图,是用来被子类继承的。
  2. 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
  3. 不能用abstract修饰变量、代码块、构造器。
  4. 有得有失: 得到了抽象方法,失去了创建对象的能力。

🌍1.6final和abstract是什么关系?

互斥关系

  1. abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
  2. 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。

🌍2.1接口

🌍2.2什么是接口?

接口在我们现实中随处可见,比如笔记本电脑上的USB接口,它可以插U盘,鼠标,键盘,所有符合USB协议的设备,比如电源插座上的插孔,可以插符合插孔规范的电脑,电视机等设备,比如寝室们的锁孔,凡是符合这把锁的钥匙都能插,并且打开门。通过这些例子可以得出一个结论:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。 在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

🌍2.3为什么要使用接口?

先来看一段代码

public abstract class Animal {
    abstract void eat();
    abstract void run();
    abstract void Swimming();
    abstract void fly();
}

public class Bird extends Animal{
    @Override
    void eat() {
        System.out.println("鸟吃虫子");
    }

    @Override
    void run() {
        System.out.println("鸟用小爪子跳着走");
    }

    @Override
    void Swimming() {

    }

    @Override
    void fly() {
        System.out.println("鸟用翅膀飞");
    }
}

public class Dog extends Animal {
    @Override
    void eat() {
        System.out.println("狗吃肉");
    }

    @Override
    void run() {
        System.out.println("狗用4条腿跑");
    }

    @Override
    void Swimming() {

    }

    @Override
    void fly() {

    }
}

public class Fish extends Animal{
    @Override
    void eat() {
        System.out.println("鱼吃微生物");
    }

    @Override
    void run() {

    }

    @Override
    void Swimming() {
        System.out.println("鱼在水里游泳");
    }

    @Override
    void fly() {

    }
}

定义一个抽象的动物类,并提供抽象方法,然后让狗,鱼,鸟三个动物继承并重写该类中的方法后,就会产生一个问题,那就是狗是不会游泳的,是不会飞的,同理其它物种也是一样,这与实际不符合,此时就需要接口来解决和这个问题,将某些行为定义成接口,然后让子类实现接口即可。

接口语法规则

🌍接口的定义与特点

接口用关键字interface来定义

public interface 接口名 {// 常量// 抽象方法

} 

代码示例

public interface Fly {
   //public abstract void fly();
   //public static final int a=90;
     void fly();
    int a=90;
}

在接口中默认的方法是抽象方法,变量默认是常量。所以修饰符可以直接省略。

注意

  1. 创建接口时, 接口的命名一般以大写字母 I 开头.
  2. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
  3. 接口的命名一般使用 “形容词” 词性的单词

🌍2.4接口使用

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

代码示例

public interface IUSB {
    void openDevice();
    void closeDevice();
}

public class KeyBoard implements IUSB{
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }
    public void inPut(){
        System.out.println("键盘输入");
    }

}

public class Mouse implements IUSB{
    @Override
    public void openDevice() {
        System.out.println("使用鼠标");
    }

    @Override
    public void closeDevice() {
        System.out.println("不使用鼠标");
    }

    public void click(){
        System.out.println("鼠标点击");
    }

}

public class Computer  {
    public void powerOn(){
        System.out.println("打开笔记本电脑");
    }
    public void powerOff(){
        System.out.println("关闭笔记本电脑");
    }


    public void useDevice(IUSB usb){
        usb.openDevice();
        if(usb instanceof Mouse){
            Mouse mouse = (Mouse)usb;
            mouse.click();
        }else if(usb instanceof KeyBoard){
            KeyBoard keyBoard = (KeyBoard)usb;
            keyBoard.inPut();
        }
        usb.closeDevice();
    }
}

public class Text {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();
        // 使用鼠标设备
        computer.useDevice(new Mouse());
        // 使用键盘设备
        computer.useDevice(new KeyBoard());
        computer.powerOff();
    }
}
运行结果
//使用鼠标
//鼠标点击
//不使用鼠标
//打开键盘
//键盘输入
//关闭键盘
//关闭笔记本电脑

🌍2.5接口特性

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

接口是抽象类进一步的抽象,凡是抽象的都不能实例化对象

public class Text2 {
    public static void main(String[] args) {
        IUSB i=new IUSB();
    }
}

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

public interface IUSB {
    private abstract  void openDevice();
   private abstract void closeDevice();
}

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

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

接口中的方法是抽象方法是被实现类所所重写的,不能在接口中实现。

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

public interface IUSB {
  //public  static final int a=90;
  int a=90;
}

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

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

因为接口是进一步的抽象,在接口里面所有定义的变量都默认是常量,而常量必须就地初始化,因此没有必要提供构造器或者代码块来初始化成员变量。

6.使用default关键字定义的方法是接口默认的方法

public interface IUSB {
    default void print(){
        System.out.println("我是USB接口");
    }
}

7.重写接口中方法时,不能使用default访问权限修饰

public interface IUSB {
   void openDevice();
    void closeDevice();
}

public class KeyBoard implements IUSB{
    @Override
   default void openDevice() {
        System.out.println("打开键盘");//编译报错
    }

    @Override
    default void closeDevice() {
        System.out.println("关闭键盘");//编译报错 
    }
    public void inPut(){
        System.out.println("键盘输入");
    }

}

实现类中重写的方法的访问权限需大于等于接口中,而接口中方法的访问权限修饰符默认是public,所以实现类中所重写的方法必须用public修饰。

🌍2.6注意事项

  1. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
  2. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
  3. dk8中:接口中还可以包含default方法。

实现多个接口

java中不能多继承只能单继承也就是不能同时拥有多了亲爹,但是java支持类有多个干爹,这个干爹就是接口。

代码示例

定义一个动物类

public class Animal {
    protected String name;

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

实现会飞,会游泳,会跑等是三个接口

public interface IFly {
    void fly();
}

public interface IRunning {
    void run();
}

public interface ISwimming {
    void swim();
}

猫只能凭借4条腿跑

public class Cat extends Animal implements IRunning{
    public Cat(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(name+"四条腿跑的快");
    }
}

鸟是会飞的

public class Bird extends Animal implements IFly{

    public Bird(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(name+"用翅膀飞");
    }
}

青蛙既能游泳又能跑

public class Frog extends Animal implements IRunning,ISwimming {

    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(name+"跳着走");
    }

    @Override
    public void swim() {
        System.out.println(name+"游泳");
    }
}

鸭子既能跑,又能游泳,更能飞。

public class Duck extends Animal implements IFly,IRunning,ISwimming{

    public Duck(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(name+"用鸭翅膀飞");
    }

    @Override
    public void run() {
        System.out.println(name+"用大脚趾跑");
    }

    @Override
    public void swim() {
        System.out.println(name+"游泳");
    }
}

上述代码都是一个类继承一个类,同时实现多个接口。继承体现类与类之间的关系是is -a关系,接口是体现了事物xxx的特征。

比如猫是一只动物,具有跑的特征,青蛙是一只动物,既能跑,也能游泳。

有了接口后,类的实现者不必关心具体的类型,而只要关注类是否具备某种能力。

比如,现在实现一个吃东西的方法

public class Text {
    public static void main(String[] args) {
        Bird b = new Bird("小鸟");
        Cat c = new Cat("小猫");
        Frog f = new Frog("青蛙");
        Duck d = new Duck("大黄鸭");
        run(b);
        run(c);
        run(f);
        run(d);
    }

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

}
//运行结果
小鸟会用爪子跑
小猫四条腿跑的快
青蛙跳着走
大黄鸭用大脚趾跑

上述代码等价于

    public class Text {
    public static void main(String[] args) {
        IRunning b = new Bird("小鸟");
        IRunning c = new Cat("小猫");
        IRunning f = new Frog("青蛙");
        IRunning d = new Duck("大黄鸭");
        b.run();
        c.run();
        f.run();
        d.run();
    }
}
//运行结果
小鸟会用爪子跑
小猫四条腿跑的快
青蛙跳着走
大黄鸭用大脚趾跑

实现接口可以看做特殊的继承,上述的代码就是多态的体现,父类引用指向子类对象,最终调用的是具体子类的方法。

接口间的继承

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

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

public interface IRunning {
    void run();
}

public interface ISwimming {
    void swim();
}

public interface IAmphibious extends IRunning,ISwimming{
}


public class Frog2 implements IAmphibious {
    @Override
    public void run() {
        System.out.println("青蛙跳着走");
    }

    @Override
    public void swim() {
        System.out.println("青蛙游泳");
    }
}

通过接口继承创建了一个新的接口IAmphibious,而frog2实现了这个接口,就要重写该接口里面的swim()方法以及run()方法。

🌍2.7接口使用实例

🌍2.8给引用类型的数据排序

####🌍2.9 接口Comparable的使用

首先准备2个学生类。

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

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

    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 double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

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


学生测试类

public class StudentText {
    public static void main(String[] args) {
       Student s=new Student("叶秋涵",19,100);
        Student s2=new Student("叶子秋",15,99);
      
    }
}

既然要对引用类型的数据进行排序,那么是否是运算符来比较大小呢?肯定是不可以的,对象与对象的比较,如同人与人之间的比较,总要具体到某一件事上才能比较,好比家人总是说你张三成绩比你好,这就是比较了2对象之间的成绩。所以不是拿对象那本身来互相比较,而是具体到对象的某一属性来比较。

为了指定到对象的某一属性来进行比价,我们让Student实现Comparable接口,并实现其中的compareTo接口。

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

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

    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 double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

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

    @Override
    public int compareTo(Student o) {//按年龄比较
        return this.age-o.age;
    }
}

Studnet测试类

public class StudentText {
    public static void main(String[] args) {
        Student s=new Student("叶秋涵",11,100);
        Student s2=new Student("叶子秋",15,99);
        if(s.compareTo(s2)>0){
            System.out.println("s>s2");
        }else if(s.compareTo(s2)<0){
            System.out.println("s<s2");
        }else{
            System.out.println("s==s2");
        }
    }
}

接口compareTo()的比较规则

  1. 如果认为左边数据大于右边数据则返回正整数
  2. 如果认为左边数据小于右边数据则返回负整数
  3. 如果认为左边数据等于右边数据则返回负整数
  4. 默认是升序,若要降序将比价的2个对象的参数互换即可。即o2-o1.

使用Arrays.sort();对引用类型的数组进行排序。

示例代码一

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

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

    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 double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

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

//    @Override
//    public int compareTo(Student o) {
//        return this.age-o.age;
//    }
}

public class StudentText {
    public static void main(String[] args) {
        Student [] arr=new Student[3];
        arr[0]=new Student("叶秋涵",11,100);
        arr[1]=new Student("叶子秋",15,99);
        arr[2]=new Student("老衲爱尼姑",13,39);
//        if(arr[0].compareTo(arr[1])>0){
//            System.out.println("s>s2");
//        }else if(arr[0].compareTo(arr[1])<0){
//            System.out.println("s<s2");
//        }else{
//            System.out.println("s==s2");
//        }
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}


代码无法编译过去,因为sort();会将传入的对象强制转换为Comparable,而此时的Student没有实现Comparable是无法强转的,同时sort()方法会自动调用compareTo()方法,也就是意味着对引用类型的数组进行排序也要事先指定排序规则。

Student实现了Comparable并重写了ompareTo(),最后成功对该数组排序。

[Studnet{name='叶秋涵', age=11, score=100.0}, Studnet{name='老衲爱尼姑', age=13, score=39.0}, Studnet{name='叶子秋', age=15, score=99.0}]

🌍2.9 自定义比较器( Comparator)

如果我们需要按分数对学生排序,那么按照上述的代码,我们还需修改compareTo()中的排序规则,某一天又需要对年龄比较又要修改回来,这样就非常麻烦此时需要另一个接口Comparator(),来解决此问题。

自定义年龄比较器

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

自定义分数比较器

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

自定义姓名比较器

public class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
       return o1.getName().compareTo(o2.getName());
    }
}

补充字符串的排序比较

使用字符串的compareTo();接口来比较。
int compareTo(String anotherString) ,虽然返回值是Int,但是实际比较的两个字符串的ascii码值。返回的值(正数、负数、0)有三种情况:

  1. 如果第一个字符和参数的第一个字符不等,结束比较,返回与第一个字符的ASCII码差值;
  2. 如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,直到不同字符返回差值;
  3. 如果两个字符串为子串关系,则返回两个字符串的长度差值。

使用比较器对学生数组进行排序

public class Text {
    public static void main(String[] args) {
        Student [] arr=new Student[3];
        arr[0]=new Student("叶秋涵",11,100);
        arr[1]=new Student("子秋",15,99);
        arr[2]=new Student("老衲爱尼姑",13,39);
        System.out.println("按年龄排序");
        AgeComparator ageComparator=new AgeComparator();
        Arrays.sort(arr,ageComparator);
        System.out.println(Arrays.toString(arr));
        System.out.println("按分数排序");
        ScoreComparator scores=new ScoreComparator();
        Arrays.sort(arr,scores);
        System.out.println(Arrays.toString(arr));
        System.out.println("按字典序排序");
        NameComparator name=new NameComparator();
        Arrays.sort(arr,name);
        System.out.println(Arrays.toString(arr));
    }
}
运行结果
按年龄排序
[Studnet{name='叶秋涵', age=11, score=100.0}, Studnet{name='老衲爱尼姑', age=13, score=39.0}, Studnet{name='子秋', age=15, score=99.0}]
按分数排序
[Studnet{name='老衲爱尼姑', age=13, score=39.0}, Studnet{name='子秋', age=15, score=99.0}, Studnet{name='叶秋涵', age=11, score=100.0}]
按字典序排序
[Studnet{name='叶秋涵', age=11, score=100.0}, Studnet{name='子秋', age=15, score=99.0}, Studnet{name='老衲爱尼姑', age=13, score=39.0}]

🌍3.0Clonable 接口和深拷贝

java 中内置了一些很有用的接口, Clonable 就是其中之一. Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要 先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。要拷贝的类实现了Clonable();该接口是个空接口,也就是接口里面没有任何的抽象方法,实现该接口只是为了标记要拷贝的对象,以便能成功拷贝。

🌍3.1Clonable的使用

public class Person implements Cloneable{
        public  int age;

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

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

    public static void main(String[] args) throws Exception {
        Person p=new Person();
        p.age=90;
        System.out.println(p);
        Person p2=(Person) p.clone();
        System.out.println(p2.age);
        System.out.println("============");
        p2.age=99;
        System.out.println(p2.age);
    }
}

运行结果
Person{age=90}
90
============
99

上述代码内存解析

clone方法克隆了一份对象p并返回一个Object的对象,由于该方法的返回值为Object,所以需将其强转为Person在赋值给Person。当我们通过p2引用修改age可以知道不会修改引用p所指向对象中的age,因为这是两份内存,是互不干扰的。

🌍3.2浅拷贝

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向 原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.

浅拷贝代码示例

public class Person implements Cloneable{
        public  int age;
        Wallet w=new Wallet();
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

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

}

public class Wallet {
    double money = 99;
}

public class WalletText {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p = new Person();
        Person p1 = (Person) p.clone();
        System.out.println(p1.w.money);
        System.out.println("修改后");
        p1.w.money=12;
        System.out.println(p1.w.money);
    }
}

运行结果
//99.0
//修改后
//12.0  

浅拷贝内存图

在这里插入图片描述

🌍3.3深拷贝

被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被 复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都 复制了一遍

深拷贝代码示例

public class Wallet implements Cloneable{
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    double money = 99;
    @Override
    public String toString() {
        return "Wallet{" +
                "money=" + money +
                '}';
    }
}

public class Person implements Cloneable {
    public int age;
    public Wallet w = new Wallet();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person tmp = (Person) super.clone();
        tmp.w = (Wallet) this.w.clone();
        return tmp;
    }

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

  
}

public class WalletText2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p=new Person();
        p.age=90;
        System.out.println(p);
        System.out.println("修改后");
        Person p1=(Person) p.clone();
        p1.age=90;
        p1.w.money=900;
        System.out.println(p1);
    }
}
运行结果
Person{age=90, w=Wallet{money=99.0}}
修改后
Person{age=90, w=Wallet{money=900.0}}

深拷贝内存图

在这里插入图片描述

为了完成深拷贝,只需将引用p所指的对象先拷贝,后将p对象的w引用所指向的对象再拷贝一份,最后修改p1中引用w的地址即可。为了完成这个过程需要拷贝Wallet,故该类也需实现Cloneable并重写clone()方法,还需一个中间变量tmp去修改p1中引用w的地址。

🌍3.4抽象类和接口的区别

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

普通区别

  1. 从组成来说,抽象类是由普通类加抽象方法组成,而接口是抽象方法+全局常量。
  2. 从访问权限来说,抽象类可以使用各种权限修饰符,而接口只能用public
  3. 从子类角度来说,子类使用extends继承抽象类,对于实现接口则是用implements
  4. 从类与类之间的关系来说,一个抽象类可以实现多个接口,但是接口不能继承抽象类,但能使用extends关键字继承多个接口。
  5. 从子类角度来说,一个子类只能继承一个抽象类,但是能实现多个接口。

🌍4.1Object类

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

代码示例使用Object接收其它类的引用

public class Text {
    public static void main(String[] args) {
        fun(new Person());
        fun(new Wallet());
    }

    private static void fun(Object o) {
        System.out.println(o);
    }
}

运行结果
clonable.Person@1b6d3586
clonable.Wallet@4554617c    

🌍4.1获取对象信息

如果要打印对象中的内容,可以直接重写Object类中的toString()方法,不重写打印的地址。

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

只需用idea重写toString()即可,打印对象的信息。

🌍4.2equals()

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

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

代码示例

没有重写equals

public static void main(String[] args) {
        Person p=new Person();
        p.age=90;
        Person p1=new Person();
        p1.age=90;
        System.out.println(p.age == p1.age);
        System.out.println(p == p1);
        System.out.println(p.equals(p1));
    }

运行结果
true
false
false

重写了equals

public static void main(String[] args) {
        Person p=new Person();
        p.name="叶秋涵";
        Person p1=new Person();
        p1.name="叶秋涵";
        System.out.println(p == p1);
        System.out.println(p.name.equals(p1.name));//比较对象的属性
    }
    
 运行结果 
false
true

🌍4.3hashcode方法

回忆刚刚的toString方法的源码:

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

调用hashCode();返回哈希值,然后通过Integer.toHexString以16进制打印出来。

hashcode方法源码:

public native int hashCode();

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

没有重写hashCode()l;

public class Person {
    public String name;
    public int age;

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

public class Text {
    public static void main(String[] args) {
        Person p=new Person("阿茂",19);
        Person p2=new Person("阿茂",19);
        System.out.println(p.hashCode());
        System.out.println(p2.hashCode());
    }
}
运行结果
460141958
1163157884    

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

重写hashCode();后

public 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 Text {
    public static void main(String[] args) {
        Person p=new Person("阿茂",19);
        Person p2=new Person("阿茂",19);
        System.out.println(p.hashCode());
        System.out.println(p2.hashCode());
    }
}

运行结果
38003601
38003601

注意事项:哈希值一样。

结论:

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

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

最后的话

各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!

在这里插入图片描述

  • 63
    点赞
  • 39
    收藏
  • 打赏
    打赏
  • 55
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论 55

打赏作者

KC老衲爱尼姑

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值