> 文档中心 > 《JavaSE-第十章》之抽象类与接口

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

前言

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

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

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

共勉:talk is cheap, show me the code

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

文章目录

    • @[TOC]
  • 🌍1.1抽象类
    • 🌍什么是抽象类?
    • 🌍1.2为什么要使用抽象类?
      • 🌍1.3抽象类的语法
        • 🌍1.4抽象类特性
          • **1.5🌍抽象类的使用总结与注意事项**
      • 🌍1.6final和abstract是什么关系?
  • 🌍2.1接口
    • 🌍2.2什么是接口?
    • 🌍2.3为什么要使用接口?
      • 🌍2.4接口使用
        • 🌍2.5接口特性
        • 🌍2.6注意事项
      • 🌍2.7接口使用实例
        • 🌍2.8给引用类型的数据排序
        • 🌍2.9 自定义比较器( Comparator)
      • 🌍3.0Clonable 接口和深拷贝
        • 🌍3.1Clonable的使用
        • 🌍3.2浅拷贝
        • 🌍3.3深拷贝
          • 🌍3.4抽象类和接口的区别
    • 🌍4.1Object类
        • 🌍4.1获取对象信息
        • 🌍4.2equals()
        • 🌍4.3hashcode方法

🌍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*/{    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

上述代码内存解析

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

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@1b6d3586clonable.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));    }运行结果truefalsefalse

重写了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));//比较对象的属性    }     运行结果 falsetrue

🌍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());    }}运行结果4601419581163157884    

注意事项:两个对象的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());    }}运行结果3800360138003601

注意事项:哈希值一样。

结论:

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

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

最后的话

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

在这里插入图片描述

51银饰网