当前位置:首页 » 《休闲阅读》 » 正文

JavaSE之抽象类和接口

9 人参与  2024年10月27日 11:20  分类 : 《休闲阅读》  评论

点击全文阅读


文章目录

抽象类抽象类概念抽象类语法抽象类要点总结 接口接口的概念接口实例接口间的多继承接口要点总结Comparable接口Comparator接口Clonable接口深拷贝的代码实现 抽象类和接口的区别
        在这里插入图片描述

                                           给个关注叭
      在这里插入图片描述

  个人主页

  JavaSE专栏

前言:本篇文章主要整理了抽象类的概念及语法、抽象类要点总结.接口的概念,通过实例进一步理解接口、接口间的多继承、接口要点总结、Comparable接口、Comparator接口、Cloneable接口、浅拷贝和深拷贝代码实现及其堆栈图、抽象类和接口的区别。

抽象类

抽象类概念

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

Animal类是动物类,其内部有一个bark方法,每个动物都有不同叫的方法,但是由于Animal类不是一个具体的动物,因此其内部bark()方法无法具体实现Dog是狗类,与Animal类是继承关系,其次狗是一种具体的动物,其bark方法有具体的叫法,“汪汪汪”Cat是猫类,与Animal类是继承关系,其次猫是一种具体的动物,其bark方法有具体的叫法,“喵喵喵”所以这种Animal类中的bark()方法不需要有具体的实现,那么可以把Animal类设置为抽象类,只需让其具体的子类根据需要去重写这个抽象方法

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


抽象类语法

  抽象类需要用abstract的关键字来修饰类,同时abstract关键字也可以修饰方法,被abstract修饰的方法称为抽象方法,抽象方法可以没有具体的实现。代码示例如下:

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

抽象类首先是一个类,和普通类一样,可以有普通方法和变量,甚至构造方法,可以被继承,唯一不同的是抽象类不能实例化对象

抽象类要点总结

abstract修饰的类称为抽象类,被abstract修饰的方法称为抽象方法,抽象方法可以没有具体的实现抽象类和普通类一样,可以定义普通类中的成员变量和成员方法,构造方法抽象类不能实例化对象如果一个类中包含抽象方法,那么这个类一定是抽象类(被abstract修饰)抽象类就是用来被继承的,继承后,子类一定要重写抽象类中的所有抽象方法,不然会有红线

在这里插入图片描述
在子类B中要重写抽象类A中的所有抽象方法,代码示例如下:

   @Override   public void func1() {        }
如果不想重写抽象类的抽象方法,那么可以同样用abstract修饰子类;但是“出来混迟早是要还的”,如果再有一个类继承了这个类(已经继承了抽象类的类),那么必须重写这两个类中所有的抽象方法。

在这里插入图片描述
这里虽然B类使用abstract关键字修饰来避免了重写父类A中的抽象方法,但是C类继承了B类,C类还是要把A类和B类中的所有抽象方法全部重写
代码示例如下:

class C extends B {   @Override   public void func1() {          }   @Override   public void func2() {   }}
抽象方法既然要被重写,就要遵循重写的规则,抽象方法不能被final、private、static修饰 被private关键字修饰后,说明这个变量或方法只能在当前类中被访问。有关private关键字的更多介绍,请移步 封装.访问限定符被static关键字修饰后,说明这个变量或方法是当前类的变量或方法,不属于某一个具体的对象,是所有对象共享的;随类的加载而创建,随类的销毁而卸载;而且只随类的创建加载一次,再次加载类或者new一个对象时,不会再次加载。有关static关键字的更多介绍,请移步 封装.static

接口

接口的概念

  接口类似一种功能,只要某个事物具备这样的功能,都可以实现这个接口。(公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。)
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。


接口实例

  实例一、
     定义IShpe接口,Rect、Triangle、Cycle三个类实现IShape接口

public interface IShape {    void draw();//默认被public abstract修饰,建议不加,代码更简洁}
public class Rect implements IShape{    @Override    public void draw() {        System.out.println("画一个矩形...");    }}
public class Triangle implements IShape{    @Override    public void draw() {        System.out.println("画一个三角形...");    }}
public class Cycle implements  IShape{    @Override    public void draw() {        System.out.println("画一个圆...");    }}
public class Test {    public static void drawShape(IShape shape) {        shape.draw();    }    public static void main(String[] args) {        IShape rect = new Rect();        IShape triangle = new Triangle();        IShape cycle = new Cycle();        drawShape(rect);        drawShape(triangle);        drawShape(cycle);    }}

运行结果:
画一个矩形…
画一个三角形…
画一个圆…

结果表明,向上转型也可以体现在类实现接口的关系上(同时也体现出了动态绑定和多态)

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

USB接口:包含打开设备、关闭设备功能
public interface IUsb {    void openDevice();    void closeDevice();}
鼠标类:实现USB接口,并具备点击功能
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("点击鼠标...");    }}
键盘类:实现USB接口,并具备输入功能
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("输入数据...");    }}
笔记本类:包含开机功能、关机功能、使用USB设备功能
public class Computer {    public void openPower() {        System.out.println("打开电脑...");    }    public void closePower() {        System.out.println("关闭电脑...");    }    public void IUsbService(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 Test {    public static void main(String[] args) {        Computer computer = new Computer();        computer.openPower();        //使用鼠标        computer.IUsbService(new Mouse());        //使用键盘        computer.IUsbService(new KeyBoard());        computer.closePower();    }}

运行结果:
打开电脑…
连接鼠标…
点击鼠标…
关闭鼠标…
连接键盘…
输入数据…
关闭键盘…
关闭电脑…

  实例三、
    一个类实现多个接口,定义Animal、Dog、Bird、Duck类,定义IRunable、ISwimable、IFlyable接口。让这些动物类实现合适的一个或多个接口
接口定义:

public interface IRunning {    void run();}public interface ISwimming {    void swim();}public interface IFlying {    void fly();}

各种类的定义

public class Animal {    String name;    int age;    public Animal(String name, int age) {        this.name = name;        this.age = age;    }    public void eat() {        System.out.println("正在吃...");    }}

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

public class Dog extends Animal implements IRunning,ISwimming{    public Dog(String name,int age) {        super(name,age);    }    @Override    public void run() {        System.out.println(this.name + " 正在跑...");    }    @Override    public void swim() {        System.out.println(this.name + " 正在游...");    }}
public class Bird extends Animal implements IFlying{    public Bird(String name,int age) {        super(name,age);    }    @Override    public void fly() {        System.out.println(this.name + " 正在飞...");    }}
public class Duck extends Animal implements IRunning,ISwimming,IFlying{    public Duck(String name,int age) {        super(name,age);    }    @Override    public void run() {        System.out.println(this.name + " 正在跑...");    }    @Override    public void swim() {        System.out.println(this.name + " 正在游...");    }    @Override    public void fly() {        System.out.println(this.name + " 正在飞...");    }}

测试类

public class Test {    public static void main(String[] args) {        Dog dog = new Dog("旺财",3);        Bird bird =  new Bird("小飞飞",1);        Duck duck = new Duck("唐老鸭",5);        walk(dog);        walk(duck);        fly(bird);        fly(duck);    }    public static void walk(IRunning iRunning) {        iRunning.run();    }    public static void fly(IFlying iFlying) {        iFlying.fly();    }}

运行结果:
旺财 正在跑…
唐老鸭 正在跑…
小飞飞 正在飞…
唐老鸭 正在飞…

run方法和fly方法,使用接口这种引用类型作为参数,可以不用关注类的调用者的具体类型,只要这个类具备这个功能特性(实现了这个接口),就可以使用


接口间的多继承

  在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的.比如接口C 继承接口A和接口B,那C就具A和B的功能特性,当一个类实现这个接口C时,就要重写A、B、C接口中所有的方法

interface A {    void testA();}interface B {    void testB();}interface C extends A,B{    void testC();}public class Test implements C{    @Override    public void testA() {    }    @Override    public void testB() {    }    @Override    public void testC() {    }}

接口要点总结

接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口创建接口时, 接口的命名一般以大写字母 I 开头.接口的命名一般使用 “形容词” 词性的单词.阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性接口中不能定义构造方法,也不能定义有具体实现的方法(否则会有红线)接口中如果要定义有具体实现的方法,要用static 或 default 关键字修饰接口中可以定义变量,默认被public static final 修饰,为静态常量(如果用默认值以外的修饰符修饰,会有红线)接口中可以定义没有具体实现的方法,默认被public abstract修饰,为抽象方法(如果用默认值以外的修饰符修饰,会有红线)接口中变量 或 方法可以不加任何限定修饰符,它们都有自己的默认修饰符接口和抽象类一样,不能直接实例化对象,接口是用来被类实现的一个类实现接口,使用 implements关键字,代表类实现了接口实现接口的类,要在类中重写接口中所有的(抽象)方法如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类一个类可以实现多个接口,中间用逗号隔开接口与接口之间也可以存在继承关系,接口与接口之间可以多继承

Comparable接口

在Comparable接口源码中,有一个compareTo方法
在这里插入图片描述
  现有一个学生类,需要对两个学生对象 按照年龄 进行比较。不能使用直接使用 >、<或== 来比较两个学生引用。
  应该让Student类实现Comparable接口,然后在Student中 按照比较需求 重写这个接口中的compareTo方法。代码示例如下:

public class Student implements Comparable<Student>{    String name;    int age;    public Student(String name,int age) {        this.name = name;        this.age = age;    }    @Override    public int compareTo(Student o) {        if(this.age > o.age) {            return 1;        }else if(this.age == o.age) {            return 0;        }else {            return -1;        }    }//可以把上面一堆代码是换成 return this.age-o.age}

测试类:

public class Test {    public static void main(String[] args) {        Student student1 = new Student("zhangSan",18);        Student student2 = new Student("liSi",17);        int ret = student1.compareTo(student2);        if(ret > 0) {            System.out.println("student1 > student2");        }else if(ret == 0) {            System.out.println("student1 == student2");        }else {            System.out.println("student1 < student2");        }    }}

运行结果:
student1 > student2


  使用Arrays.sort() 对学生类数组students进行排序,下图是Arrays.sort() 的源码,可以看出Arrays.sort() 的底层也是使用了comparable接口,所以Student类必须要实现comparable接口,并根据比较需求重写compareTo方法。
【students数组中的每个元素都是学生对象,比较时也是通过冒泡排序的方式,源码中要对学生对象强转为comparable接口,并且使用compareTo方法,所以Student类必须实现comparable接口】
在这里插入图片描述
Student 类实现comparable接口,重写compareTo方法,使用Arrays.sort(students)对学生数组按照年龄排序,代码示例如下:

public class Student implements Comparable<Student>{    String name;    int age;    public Student(String name,int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }    @Override    public int compareTo(Student o) {        return this.age - o.age;    }}
public class Test {    public static void main(String[] args) {        Student student1 = new Student("zhangSan",18);        Student student2 = new Student("liSi",17);        Student student3 = new Student("aBao",13);        Student[] students = new Student[3];        students[0] = student1;        students[1] = student2;        students[2] = student3;        Arrays.sort(students);        System.out.println(Arrays.toString(students));    }}

运行结果:
[Student{name=‘aBao’, age=13}, Student{name=‘liSi’, age=17}, Student{name=‘zhangSan’, age=18}]

Student 类实现comparable接口,重写compareTo方法,使用Arrays.sort(students)对学生数组按照姓名排序,代码示例如下:

public class Student implements Comparable<Student>{    String name;    int age;    public Student(String name,int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }    @Override    public int compareTo(Student o) {        return this.name.compareTo(o.name);    }}

运行结果:
[Student{name=‘aBao’, age=13}, Student{name=‘liSi’, age=17}, Student{name=‘zhangSan’, age=18}]
只需改动compareTo方法的比较方式,测试类Test不变
【点进String类的源码,其实String类中也实现了Comparable接口,所以String类也重写了compareTo方法,因此对于String这种类的引用是可以调用compareTo方法,来比较两个引用类型的大小的】

使用冒号排序BubbleSort()实现 Arrays.sort(),按照姓名进行比较排序,代码示例如下:

public class Student implements Comparable<Student>{    String name;    int age;    public Student(String name,int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }    @Override    public int compareTo(Student o) {        return this.name.compareTo(o.name);    }}
public class Test {    public static void main(String[] args) {        Student student1 = new Student("zhangSan",18);        Student student2 = new Student("liSi",17);        Student student3 = new Student("aBao",13);        Student[] students = new Student[3];        students[0] = student1;        students[1] = student2;        students[2] = student3;        bubbleSort(students);        System.out.println(Arrays.toString(students));            public static void bubbleSort(Student[] students) {        for (int i = 0; i < students.length-1; i++) {            for (int j = 0; j < students.length-1-i; j++) {                if(students[j].compareTo(students[j+1]) > 0) {                    Student tmp = students[j];                    students[j] = students[j+1];                    students[j+1] = tmp;                }            }        }    }}

运行结果:
[Student{name=‘aBao’, age=13}, Student{name=‘liSi’, age=17}, Student{name=‘zhangSan’, age=18}]
students[j].compareTo(students[j+1]能够使用compareTo()比较的原因是,数组的每个元素都是Student类对象,而学生类实现了comparable接口,同时重写了compareTo方法

使用冒号排序BubbleSort()实现 Arrays.sort(),按照年龄从小到大进行比较排序,只需更改compareTo方法里面的比较方式。代码示例如下:

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

如果想要按照年龄从大到小排序,只需将 return this.age - o.age改为return o.age - this.age


Comparator接口

Comparable接口的缺点:如果一个类已经实现了Comparable接口,并且根据需要已经重写了compareTo抽象方法,那么一般这个已经改写的抽象方法就不会再变了,比如说之前的业务都是使用年龄age来比较,但是如果确实需要通过姓名比较,也不能把原来已经重写好的方法再次改写按照姓名比较,这样一来以前使用年龄age比较的所有业务都会出错。那么我们可以使用一个新的接口Comparator接口,可以设置两种不同比较方式的类,让两个类都实现Comparator接口,分别按照比较需求重写Comparator中的抽象方法。

以下是comparator接口的源码,可以看到有两个抽象方法

这个接口里面有两个抽象方法,但是一个类实现了这个接口后只重写compare()方法就可以了,也不会报错。这是为啥子呢?解释:我们知道Object类是所有类的父类,其实在Object类中就有equals()方法的具体实现,而作为子类的AgeComparator也实现了Comparator接口,可以认为子类已经从父类继承了equals方法,近似认为已经重写了equals()方法,所以不需要再次重写equals()方法。
在这里插入图片描述

使用AgeComparator类中重写的compare()方法按照年龄比较,代码示例如下:

public class AgeComparator implements Comparator<Student> {    @Override    public int compare(Student o1, Student o2) {        return o1.age - o2.age;    }}
public class Test {    public static void main(String[] args) {        Student student1 = new Student("zhangSan",18);        Student student2 = new Student("liSi",17);        AgeComparator ageComparator = new AgeComparator();        int ret = ageComparator.compare(student1,student2);        if(ret > 0) {            System.out.println("student1 > student2");        }else if(ret == 0) {            System.out.println("student1 == student2");        }else {            System.out.println("student1 < student2");        }    }}

运行结果:
student1 > student2

使用NameComparator类中重写的compare()方法按照姓名比较,代码示例如下:

public class NameComparator implements Comparator<Student> {    @Override    public int compare(Student o1, Student o2) {        return o1.name.compareTo(o2.name);    }}
public class Test {    public static void main(String[] args) {        Student student1 = new Student("zhangSan",18);        Student student2 = new Student("liSi",17);        NameComparator nameComparator = new NameComparator();        int ret = nameComparator.compare(student1,student2);        if(ret > 0) {            System.out.println("student1 > student2");        }else if(ret == 0) {            System.out.println("student1 == student2");        }else {            System.out.println("student1 < student2");        }    }

运行结果:
student1 > student2


Clonable接口

现通过一个实例总结使用clone()的注意事项;有一个Goods类,代码示例如下:

class Money {    public double money = 9.9;}public class Goods {    public String name;    public Money money;    public Goods(String name) {        this.name = name;        this.money = new Money();    }}

现需要创建一个Goods对象,并克隆这个对象。在Test类中,创建好了一个goods对象,当用这个对象的引用通过 点 访问clone()方法时,但实际 点 不出来clone()方法,此时可以在子类Goods中重写clone()方法,这时候就能 点出来了。这是因为Object类中已经实现了clone()方法,所以作为子类的Goods类,重写这个方法后一定可以被访问。
但是我们发现还是会有红线,此时阅读Cloneable的源代码可以发现:
在这里插入图片描述

1 . 返回值是Object,所以要把Object类型的赋值给左边Goods类型的,必须要对右边进行强转为(Goods)。

    public static void main(String[] args) {        Goods goods = new Goods("苹果");        Goods goods1 = (Goods)goods.clone();    }
强转之后发现还会有红线,这是因为throw CloneNotSuppurtedException是一个不支持克隆的异常,需要在编译时解决异常,解决异常的方法是:main()方法后 加throw CloneNotSuppurtedException
    public static void main(String[] args) throws CloneNotSupportedException {        Goods goods = new Goods("苹果");        Goods goods1 = (Goods)goods.clone();    }
解决当前类是否能被克隆,编译通过但运行时报错,是因为没有实现Cloneable接口,实现Cloneable接口后才能真正实现拷贝,

阅读Cloneable接口的源码发现,其内部没有任何方法,
那么实现这个接口有什么用呢?它只是用来标记当前类是可以被拷贝的

完整代码示例如下:

class Money {    public double money = 9.9;}public class Goods implements Cloneable{    public String name;    public Money money;    public Goods(String name) {        this.name = name;        this.money = new Money();    }    @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }}
public class Test {    public static void main(String[] args) throws CloneNotSupportedException {        Goods goods = new Goods("苹果");        Goods goods1 = (Goods)goods.clone();    }}

public class Test {    public static void main(String[] args) throws CloneNotSupportedException {        Goods goods = new Goods("苹果");        Goods goods1 = (Goods)goods.clone();        System.out.println("改变前goods money = " +goods.money.money );        System.out.println("改变前goods1 money = " +goods1.money.money );        goods1.money.money = 66.6;        System.out.println("改变后goods money = " +goods.money.money );        System.out.println("改变后goods1 money = " +goods1.money.money );    }}

运行结果:
改变前goods money = 9.9
改变前goods1 money = 9.9
改变后goods money = 66.6
改变后goods1 money = 66.6

通过以上测试类的结果说明,Money这个类的对象并没有被真正拷贝,新拷贝的goods1中的money这个引用还是指向了原来的地址,这是浅拷贝

使用堆栈图更能清晰的表示:

在这里插入图片描述
此时的goods1 和 goods 中的money引用所指向的对象是同一个对象(引用所存放的地址相同),所以在通过goods.money.money = 66.6这条语句改变money的值时,goods中的money也会改变,money引用指向的地址都是同一个地址

深拷贝的代码实现

以下是实现深拷贝的具体代码实现:

class Money implements Cloneable{    public double money = 9.9;    @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }}public class Goods implements Cloneable{    public String name;    public Money money;    public Goods(String name) {        this.name = name;        this.money = new Money();    }    @Override    protected Object clone() throws CloneNotSupportedException {        Goods tmp = (Goods)super.clone();        tmp.money = (Money)this.money.clone();        return tmp;    }}
public class Test {    public static void main(String[] args)            throws CloneNotSupportedException {        Goods goods = new Goods("苹果");        Goods goods1 = (Goods)goods.clone();        System.out.println("改变前goods money = " +goods.money.money );        System.out.println("改变前goods1 money = " +goods1.money.money );        goods1.money.money = 66.6;        System.out.println("改变后goods money = " +goods.money.money );        System.out.println("改变后goods1 money = " +goods1.money.money );    }}

运行结果:
改变前goods money = 9.9
改变前goods1 money = 9.9
改变后goods money = 9.9
改变后goods1 money = 66.6

操作的堆栈图:

在这里插入图片描述先拷贝一份goods 给 tmp,然后再对money这个引用的对象进行拷贝,把拷贝好的money对象的地址 给 tmp.money。这里money引用也进行拷贝,所以Money也要实现Cloneable接口,重写clone()方法。


注意:从代码层次上,浅拷贝或深拷贝 是由具体的代码实现来决定的,不能说某个clone()方法是浅拷贝或深拷贝


抽象类和接口的区别

抽象类中可以有普通成员变量和普通成员方法,构造方法,也可以有抽象方法;接口中可以定义变量,不能定义构造方法 和 有具体实现的方法(如果想有具体实现,使用static或default修饰),接口中的方法默认是被public abstract 修饰(抽象方法),变量默认是被 public static final 修饰(静态常量)抽象类使用 abstract 关键字;接口使用 interface 关键字抽象类 用来被继承,,关键字 extends;接口用来被实现,关键字 implements类与类之间不支持多继承;接口可以实现多继承,一个类可以实现多个接口,接口与接口之间也可以继承,甚至继承多个接口

点击全文阅读


本文链接:http://zhangshiyu.com/post/178467.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1