目录

一:抽象类

1.抽象类概念

2.抽象类语法

3.抽象类特性

4.抽象类的作⽤

二:接⼝

1.接⼝的概念

2.语法规则

3.接⼝使⽤

4.接⼝特性

5.实现多个接⼝

6.接⼝间的继承

7.接⼝使⽤实例

8.Clonable接⼝和深拷⻉

9.抽象类和接⼝的区别


内容大纲:
这篇博客聚焦 Java 的三大抽象机制:
抽象类——提供模板结构,可含抽象/具体方法,子类必须实现抽象方法;
接口——定义行为契约(JDK 8+ 支持 default 方法),支持多实现,解耦能力强;

一:抽象类

1.抽象类概念

在⾯向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是⽤来描绘 对象的,如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。⽐如:

在打印图形例⼦中,我们发现,⽗类Shape中的draw⽅法好像并没有什么实际⼯作,主要的绘制图形都 是由Shape的各种⼦类的draw⽅法来完成的.像这种没有实际⼯作的⽅法,我们可以把它设计成⼀个 抽象⽅法(abstractmethod),包含抽象⽅法的类我们称为抽象类(abstractclass)

2.抽象类语法

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

        //抽象类,被abstract修饰的类
abstract class Shape{
        //抽象方法,被abstract修饰的方法,没有方法体
        //抽象成员方法
        abstract public void draw();
        abstract void  calcArea();

        //抽象类也是类,是类就可以包括成员方法,成员变量,还有构造方法
        //成员方法
        public double getArea(){
            return area;
        }
        //成员变量
        protected double area;

}

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

3.抽象类特性

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

public class Test {
    Shape shape = new Shape();
}

原因:

2.抽象⽅法不能是private的

abstract public class Shape {
    //成员方法
    abstract private void drawn();
}

原因:

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

abstract public class Shape {
    abstract final void dff();
    abstract public static void ddd();
}

原因:

4. 抽象类必须被继承,并且继承后⼦类要重写⽗类中的抽象⽅法,否则⼦类也是抽象类,必须要使⽤ abstract 修饰

Shape(父类)

abstract public class Shape {
    //抽象方法
    abstract public void draw();
    abstract void  calcArea();

    //成员变量
    protected double area;
}

Rect

public class Rect extends Shape{
    //成员方法
    private double length;
    private double  width;

    //构造方法
    public Rect(double length, double width) {
        this.length = length;
        this.width = width;
    }

    //成员方法
    public void draw(){
        System.out.println(" 矩形: length= "+length+" width= " + width);
    }
    public void calcArea(){
        area = length * width;
    }

}

Circle

public class Circle extends Shape{
    //成员变量
    private double r;
    final private static double PI = 3.14;

    //构造方法
    public Circle(double r) {
        this.r = r;
    }

    //成员方法
    public void draw(){
        System.out.println(" 圆: r = "+r);
    }
    public void calcArea(){
        area = PI * r * r;
    }

}

Triangle

abstract public class Triangle extends Shape{
    private double a;
    private double b;
    private double c;

    //重写父类的抽象方法
    @Override
    public void draw() {
        System.out.println(" 三⻆形: a = "+a + " b = "+b+" c = "+c);
    }
 }

此时我们的Triangle是被abstract修饰的,重写父类的抽象方法就可以从子类调用父类里面的抽象方法,这是没有任何问题的

如果Triangle不是被abstract修饰的,则会报错

 public class Triangle extends Shape{
    private double a;
    private double b;
    private double c;
 }

原因:

解决方法:

第一个就是加上我们的abstract修饰子类,这样就可以继承抽象的父类

第二个就是实现父类的抽象方法,注意父类的抽象方法都要实现

 public class Triangle extends Shape{
    private double a;
    private double b;
    private double c;

     @Override
     public void draw() {
         
     }

     @Override
     void calcArea() {

     }
 }

此时我们的代码就是没有任何问题的

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

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

4.抽象类的作⽤

抽象类本⾝不能被实例化,要想使⽤,只能创建该抽象类的⼦类.然后让⼦类重写抽象类中的抽象⽅法

有些同学可能会说了,普通的类也可以被继承呀,普通的⽅法也可以被重写呀,为啥⾮得⽤抽象类和抽 象⽅法呢

确实如此.但是使⽤抽象类相当于多了⼀重编译器的校验.

使⽤抽象类的场景就如上⾯的代码,实际⼯作不应该由⽗类完成,⽽应由⼦类完成.那么此时如果不⼩⼼ 误⽤成⽗类了,使⽤普通类编译器是不会报错的.但是⽗类是抽象类就会在实例化的时候提⽰错误,让我 们尽早发现问题

很多语法存在的意义都是为了"预防出错",例如我们曾经⽤过的final也是类似.创建的变量⽤⼾不去 修改,不就相当于常量嘛?但是加上final能够在不⼩⼼误修改的时候,让编译器及时提醒我们

充分利⽤编译器的校验,在实际开发中是⾮常有意义的.

二:接⼝

1.接⼝的概念

在现实⽣活中,接⼝的例⼦⽐⽐皆是,⽐如:笔记本上的USB⼝,电源插座等

电脑的USB⼝上,可以插:U盘、⿏标、键盘...所有符合USB协议的设备

电源插座插孔上,可以插:电脑、电视机、电饭煲...所有符合规范的设备

通过上述例⼦可以看出接⼝就是公共的⾏为规范标准,⼤家在实现时,只要符合规范标准,就可以 通⽤。

在Java中,接⼝可以看成是:多个类的公共规范,是⼀种引⽤数据类型。

2.语法规则

接⼝的定义格式与定义类的格式基本相同将class关键字换成 interface 关键字,就定义了⼀个接 ⼝。

public interface 接⼝名称{

// 抽象⽅法

public abstract void method1(); // public abstract 是固定搭配,可以不写

public void method2();

abstract void method3();

void method4();

// 注意:在接⼝中上述写法都是抽象⽅法,推荐⽅式 4 ,代码更简洁

}

提⽰:

1. 创建接⼝时,接⼝的命名⼀般以⼤写字⺟ I 开头.

2. 接⼝的命名⼀般使⽤"形容词"词性的单词.

3. 阿⾥编码规范中约定,接⼝中的⽅法和属性不要加任何修饰符号,保持代码的简洁性.

3.接⼝使⽤

接⼝不能直接使⽤,必须要有⼀个"实现类"来"实现"该接⼝实现接⼝中的所有抽象⽅法

public  class  类名称  implements  接⼝名称 {

// ...

}

注意:⼦类和⽗类之间是extends继承关系,类与接⼝之间是implements实现关系

实现笔记本电脑使⽤ USB ⿏标、 USB 键盘的例⼦

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

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

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

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

代码实现:
USB(接口)

//实现一个接口
public interface USB {
    //抽象方法
    void openDevice();
    void closeDevice();
}

Mouse(继承接口)

public class Mouse implements USB{

    //接口的实现
    @Override
    public void openDevice() {

    }

    @Override
    public void closeDevice() {

    }


    //成员方法
    public void click(){
        System.out.println("⿏标点击 ");
    }
}

KeyBoard (继承接口)

public class KeyBoard implements USB{
    @Override
    public void openDevice() {

    }

    @Override
    public void closeDevice() {

    }

    //成员方法
    public void inPut(){
        System.out.println(" 键盘输⼊ ");
    }
}

Computer

public class Computer {
    //成员方法
    public void powerOn(){
        System.out.println(" 打开笔记本电脑 ");
    }
    public void powerOff(){
        System.out.println(" 关闭笔记本电脑 ");
    }
    //带有参数的成员方法
    public void useDevice(USB 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();
    }

}

Test

public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();
        //打开电脑
        computer.powerOn();

        //使⽤⿏标设备
        computer.useDevice(new Mouse());

        //使⽤键盘设备
        computer.useDevice(new KeyBoard());

        //关闭电脑
        computer.powerOff();
    }
}

4.接⼝特性

1.接⼝类型是⼀种引⽤类型,但是不能直接new接⼝的对象

public class Test {
    public static void main(String[] args) {
    USB usb = new USB();
}
}

原因:

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

//实现一个接口
public interface USB {
    private void openDevice();
}

原因:

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

//实现一个接口
public interface USB {
    void closeDevice(){
        System.out.println("11");
    }
}

原因:

4.重写接⼝中⽅法时,不能使⽤默认的访问权限

public class Mouse implements USB{
    //接口的实现
    @Override
    void openDevice() {

    }
}

原因:

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

public class Test {
    public static void main(String[] args) {
        System.out.println(USB.brand);
        USB.brand = 3.1;
    }
}

可以输出3.0可以说明接口是被Static修饰的

想要修改接口里面的变量

原因:

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

//实现一个接口
public interface USB {

    //接口不能有构造方法
    public USB(){

    }
    
    //接口不能有静态代码块
    {
        
    }

}

原因:

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

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

9.jdk8中:接⼝中还可以包含default⽅法。

5.实现多个接⼝

在Java中,类和类之间是单继承的,⼀个类只能有⼀个⽗类,即Java中不⽀持多继承,但是⼀个类可 以实现多个接⼝。下⾯通过类来表⽰⼀组动物.

Aninmal(父类)

public class Animal {
    protected String name;

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

另外我们再提供⼀组接⼝,分别表⽰"会⻜的","会跑的","会游泳的".

IFlying(接口)

public interface IFlying {
    void fly();
}

IRunning(接口)

public interface IRunning {
    void run();
}

 ISwimming (接口)

public interface ISwimming {
    void swim();
}

接下来我们创建⼏个具体的动物

猫,是会跑的

public class Cat extends Animal implements IRunning{
    //实现接口方法
    @Override
    public void run() {
        System.out.println(this.name + " 正在⽤四条腿跑 ");
    }

    //构造父类方法
    public Cat(String name) {
        super(name);
    }
}

⻥,是会游的.

public class Fish extends Animal implements ISwimming{
    public Fish(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(this.name + " 正在⽤尾巴游泳 ");
    }
}

⻘蛙,既能跑,⼜能游(两栖动物)

public class Frog extends Animal implements ISwimming,IRunning{
    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + " 正在往前跳 ");
    }

    @Override
    public void swim() {
        System.out.println(this.name + " 正在蹬腿游泳 ");
    }
}

Test

public class Test {
    public static void main(String[] args) {
        Cat cat= new Cat("小三");
        cat.run();

        Fish fish=new Fish(" 小鱼");
        fish.swim();

        Frog frog = new Frog("小蛙");
        frog.run();
        frog.swim();
    }
}

输出:

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

提⽰,IDEA中使⽤ctrl+i快速实现接⼝

上⾯的代码展⽰了Java⾯向对象编程中最常⻅的⽤法:⼀个类继承⼀个⽗类,同时实现多种接⼝.

继承表达的含义是 is - a 语义,⽽接⼝表达的含义是 具有 xxx 特性

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

⻘蛙也是⼀种动物,既能跑,也能游泳

这样设计有什么好处呢?时刻牢记多态的好处,让程序猿忘记类型.有了接⼝之后,类的使⽤者就不必关 注具体类型,⽽只关注某个类是否具备某种能⼒

6.接⼝间的继承

在Java中,类和类之间是单继承的,⼀个类可以实现多个接⼝,接⼝与接⼝之间可以多继承。即:⽤接⼝可以达到多继承的⽬的

接⼝可以继承⼀个接⼝,达到复⽤的效果. 使⽤extends关键字

public interface IAmphibious extends IRunning,ISwimming{
    
}

接⼝间的继承相当于把多个接⼝合并在⼀起

7.接⼝使⽤实例

对象之间进⾏⼤⼩关系⽐较

Student

public class Student {
    public String name;
    public int score;

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

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

}

Test

public class Test {
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan",10);
        Student s2 = new Student("lisi",20);
        System.out.println(s1 > s2 );
    }
}

输出:

此时程序会编译报错,并没有指定根据分数还是什么进⾏⼤⼩⽐较。所以,应该指定以什么样的⽅式 进⾏⽐较??

不太好的点是,只要按照该⽅式⽐较后,不灵活。⽆法按照其他⽅式进⾏⽐较

⽅式⼀:使⽤Comparable接⼝

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

Student

public class Student implements Comparable{
    public String name;
    public int score;

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

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

    @Override
    public int compareTo(Object o) {
        Student s = (Student)o;
        if (this.score > s.score) {
            return -1;
        } else if (this.score < s.score) {
            return 1;
        } else {
            return 0;
        }
    }

}

Test

public class Test {
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan",10);
        Student s2 = new Student("lisi",20);
        System.out.println(s1.compareTo(s2));
    }
}

输出:

如果s1⼤于s2那么返回⼤于0的数字,如果相同返回0,否则返回⼩于0的数字。

⽅式⼆:使⽤Comparator接⼝

ScoreComparator

import java.util.Comparator;

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

NameComparator

import java.util.Comparator;

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

Test1

public class Test1 {
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan",10);
        Student s2 = new Student("lisi",20);

        //分数比较
        ScoreComparator scoreComparator = new ScoreComparator();
        System.out.println(scoreComparator.compare(s1, s2));

        //名字比较
        NameComparator nameComparator = new NameComparator();
        System.out.println(nameComparator.compare(s1, s2));
    }
}

8.Clonable接⼝和深拷⻉

Java中内置了⼀些很有⽤的接⼝,Clonable就是其中之⼀

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

Animal

public class Animal implements Cloneable{
    private String name;

    @Override
    public Animal clone() {
        Animal o = null;
        try {
            o = (Animal) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

Test1

public class Test1 {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Animal animal2 = animal.clone();
        System.out.println(animal == animal2);
    }
}

输出:

浅拷⻉VS深拷⻉

Cloneable 拷⻉出的对象是⼀份"浅拷⻉

观察以下代码:

Person

public class Person implements Cloneable{
    public Money money = new Money();

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

Money

public class Money {
    public double m = 99.99;
}

Test

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        System.out.println(" 通过 person2 修改前的结果 ");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        
        person2.money.m = 13.6;
        System.out.println(" 通过 person2 修改后的结果 ");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}

输出:

如上代码,我们可以看到,通过clone,我们只是拷⻉了Person对象。但是Person对象中的Money对 象,并没有拷⻉。通过person2这个引⽤修改了m的值后,person1这个引⽤访问m的时候,值也发⽣了改变。这⾥就是发⽣了浅拷⻉。那么同学们想⼀下如何实现深拷⻉呢?

9.抽象类和接⼝的区别

抽象类和接⼝都是Java中多态的常⻅使⽤⽅式.都需要重点掌握.同时⼜要认清两者的区别(重要!!!常 ⻅⾯试题).

核⼼区别:抽象类中可以包含普通⽅法和普通字段,这样的普通⽅法和字段可以被⼦类直接使⽤(不必重写), ⽽接⼝中不能包含普通⽅法,⼦类必须重写所有的抽象⽅法.

如之前写的Animal例⼦.此处的Animal中包含⼀个name这样的属性,这个属性在任何⼦类中都是存 在的.因此此处的Animal只能作为⼀个抽象类,⽽不应该成为⼀个接⼝.

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

再次提醒:

抽象类存在的意义是为了让编译器更好的校验,像Animal这样的类我们并不会直接使⽤,⽽是使⽤它 的⼦类.万⼀不⼩⼼创建了Animal的实例,编译器会及时提醒我们

后面还有部分内容,我们放到下一章!!!!

更多推荐