• 《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();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    圆形,正方形,三角形都重写了父类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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

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

    🌍1.4抽象类特性

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

      Shape s=new Shape();
    
    • 1

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

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

    public abstract class Shape {
        private abstract void draw();
            
    }
    
    • 1
    • 2
    • 3
    • 4

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

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

    1. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
    public abstract class Shape {
       public  final abstract void draw();
    
    }
    
    public abstract class Shape {
       public  static abstract void draw();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    三角形定义为抽象类并继承图形类是可以不重写图形类中的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() {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

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

    接口语法规则

    🌍接口的定义与特点

    接口用关键字interface来定义

    public interface 接口名 {// 常量// 抽象方法
    
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    代码示例

    public interface Fly {
       //public abstract void fly();
       //public static final int a=90;
         void fly();
        int a=90;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    注意

    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();
        }
    }
    运行结果
    //使用鼠标
    //鼠标点击
    //不使用鼠标
    //打开键盘
    //键盘输入
    //关闭键盘
    //关闭笔记本电脑
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    🌍2.5接口特性

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

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

    public class Text2 {
        public static void main(String[] args) {
            IUSB i=new IUSB();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    public interface IUSB {
        private abstract  void openDevice();
       private abstract void closeDevice();
    }
    
    • 1
    • 2
    • 3
    • 4

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

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

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

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

    public interface IUSB {
      //public  static final int a=90;
      int a=90;
    }
    
    • 1
    • 2
    • 3
    • 4

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

    public interface USB {
    	// 编译失败
    	public USB(){
    }
    	{
    	
    	} // 编译失败
    	void openDevice();
    	void closeDevice();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

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

    public interface IUSB {
        default void print(){
            System.out.println("我是USB接口");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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("键盘输入");
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    实现类中重写的方法的访问权限需大于等于接口中,而接口中方法的访问权限修饰符默认是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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    public interface IFly {
        void fly();
    }
    
    public interface IRunning {
        void run();
    }
    
    public interface ISwimming {
        void swim();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    猫只能凭借4条腿跑

    public class Cat extends Animal implements IRunning{
        public Cat(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            System.out.println(name+"四条腿跑的快");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    鸟是会飞的

    public class Bird extends Animal implements IFly{
    
        public Bird(String name) {
            super(name);
        }
    
        @Override
        public void fly() {
            System.out.println(name+"用翅膀飞");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    青蛙既能游泳又能跑

    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+"游泳");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    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+"游泳");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上述代码都是一个类继承一个类,同时实现多个接口。继承体现类与类之间的关系是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();
        }
    
    }
    //运行结果
    小鸟会用爪子跑
    小猫四条腿跑的快
    青蛙跳着走
    大黄鸭用大脚趾跑
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    上述代码等价于

        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();
        }
    }
    //运行结果
    小鸟会用爪子跑
    小猫四条腿跑的快
    青蛙跳着走
    大黄鸭用大脚趾跑
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    接口间的继承

    在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("青蛙游泳");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    通过接口继承创建了一个新的接口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 +
                    '}';
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    学生测试类

    public class StudentText {
        public static void main(String[] args) {
           Student s=new Student("叶秋涵",19,100);
            Student s2=new Student("叶子秋",15,99);
          
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    既然要对引用类型的数据进行排序,那么是否是运算符来比较大小呢?肯定是不可以的,对象与对象的比较,如同人与人之间的比较,总要具体到某一件事上才能比较,好比家人总是说你张三成绩比你好,这就是比较了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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    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");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接口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));
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    代码无法编译过去,因为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}]
    
    • 1

    🌍2.9 自定义比较器( Comparator)

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

    自定义年龄比较器

    ublic class AgeComparator implements Comparator<Student> {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getAge()-o2.getAge();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    自定义分数比较器

    class ScoreComparator implements Comparator<Student> {
        @Override
        public int compare(Student o1, Student o2) {
            return Double.compare(o1.getScore(), o2.getScore());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    自定义姓名比较器

    public class NameComparator implements Comparator<Student> {
        @Override
        public int compare(Student o1, Student o2) {
           return o1.getName().compareTo(o2.getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    补充字符串的排序比较

    使用字符串的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}]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    🌍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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    上述代码内存解析

    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  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    浅拷贝内存图

    在这里插入图片描述

    🌍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}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    深拷贝内存图

    在这里插入图片描述

    为了完成深拷贝,只需将引用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    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    🌍4.1获取对象信息

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

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

    只需用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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    重写了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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    🌍4.3hashcode方法

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

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

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

    hashcode方法源码:

    public native int hashCode();
    
    • 1

    该方法是一个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    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    注意事项:两个对象的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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    注意事项:哈希值一样。

    结论:

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

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

    最后的话

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

    在这里插入图片描述

  • 相关阅读:
    双指针——复写零
    基于光流的视频插帧算法 TOFlow 解读教程
    聊聊并发编程——并发容器和阻塞队列
    Sentinel 热点规则 (ParamFlowRule)
    第1讲 Android Camera Native Framework 课程介绍
    JAVA 单例模式
    UE4 将蓝图写在Actor类里面 实现复用
    水库大坝安全监测方案,筑牢水库安全防线!
    基于MYSQL的新闻发布系统数据库设计项目实战
    修正两个shapefile之间的数字化错误
  • 原文地址:https://blog.csdn.net/qq_60308100/article/details/124903735