Java(六)——抽象类与接口

文章目录

抽象类和接口

抽象类

抽象类的概念

Java使用类实例化对象来描述现实生活中的实体,不过,不是所有的类都能描述实体的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类(准确来说,我们可以将这样的类设计成抽象类)。

以画图形为例,有如下代码:

java 复制代码
//Shape.java
public class Shape {
    public void draw() {
        System.out.println("画一个图形");
    }
}

//Pentagram.java
public class Pentagram extends Shape {
    @Override
    public void draw() {
        System.out.println("五角星☆");
    }
}

//Rotundity.java
public class Rotundity extends Shape {
    @Override
    public void draw() {
        System.out.println("圆形⭕");
    }
}

我们在实现Shape类的draw方法时,由于Shape是一个宽泛的概念,不是一个具体的图形,所以我们实际上是没办法实现的,上代码中的实现也没有实际的意义,因此,我们可以将Shape类设计成抽象类 ,而对于Shape类中的draw方法,我们可以将它设计成抽象方法.


抽象类的语法

Java中,被 abstract修饰的类称为抽象类 ,抽象类中被 abstract修饰的方法称作抽象方法

抽象方法 不用给出具体的实现。(这是强制的,不能给出方法体)

将上面的Shape类设计成抽象类,draw方法设计成抽象方法:

java 复制代码
//Shape.java
public abstract class Shape {
    public abstract void draw();
}

注意: 抽象类也是类,抽象类也可以有普通的成员方法、成员变量或构造方法。

抽象类与普通类的区别是抽象类有abstract修饰,且可以存在抽象方法(不存在也可以)。


抽象类的特性

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

    java 复制代码
    public class Test {
        public static void main(String[] args) {
            Shape shape = new Shape();//实例化抽象类,报错
        }
    }

抽象类虽然不能实例化,但是抽象类的引用可以引用其子类的对象

java 复制代码
//Shape.java
public abstract class Shape {
    public abstract void draw();
}

//Pentagram.java
public class Pentagram extends Shape {
    @Override
    public void draw() {
        System.out.println("五角星☆");
    }
}

//Rotundity.java
public class Rotundity extends Shape {
    @Override
    public void draw() {
        System.out.println("圆形⭕");
    }
}

//Test.java
public class Test {
    public static void func(Shape shape) {
        shape.draw();
    }

    public static void main(String[] args) {
        Pentagram pentagram = new Pentagram();
        Rotundity rotundity = new Rotundity();

        Shape[] shapes = {pentagram, rotundity};

        //向上转型
        for(Shape shape : shapes){
            func(shape);
        }
    }
}
  1. 抽象类必须被继承,且继承抽象类的子类必须重写抽象类中的抽象方法,如果不想重写抽象方法,需要加abstract修饰。不过,一旦这个类被一个普通的类继承,则此普通类必须重写所有的抽象方法。

    java 复制代码
    public abstract class Test {
        public abstract void func1();
    }
    
    abstract class A extends Test {
        public abstract void func2();
    }
    
    class B extends A {
        @Override
        public void func1() {
            System.out.println("重写抽象类Test的抽象方法");
        }
    
        @Override
        public void func2() {
            System.out.println("重写抽象类A的抽象方法");
        }
    } 

    ​ 抽象类A继承了抽象类Test,普通类B继承了A,那么普通类B必须重写A 类和Test类中的所有抽象方法。

  2. 由于抽象方法必须被重写,所以抽象方法不能被privatefinalstatic修饰

  3. 抽象类中可以不包含抽象方法,但抽象方法必须在抽象类中

    java 复制代码
    public class Test {
        public abstract void func();
    }
  4. 抽象类中可以存在构造方法,用于子类实例化时初始化父类的成员

    java 复制代码
    //Shape.java
    public abstract class Shape {
        public int a;
        //抽象类的构造方法
        public Shape(int a) {
            this.a = a;
        }
        public abstract void draw();
    }
    
    //Pentagram.java
    public class Pentagram extends Shape {
    
    
        public Pentagram(int a) {
            super(a);
        }
        @Override
        public void draw() {
            System.out.println("五角星☆");
        }
    }
    
    //Rotundity.java
    public class Rotundity extends Shape {
    
        public Rotundity(int a) {
            super(a);
        }
        @Override
        public void draw() {
            System.out.println("圆形⭕");
        }
    }
    
    //Test.java
    public class Test {
        public static void main(String[] args) {
            Shape shape = new Pentagram(20);
            System.out.println(shape.a);
        }
    }

    打印结果是:


抽象类的意义

我们可能会有一个疑问,普通类也可以被继承,普通的成员方法也可以被重写,为什么要用抽象类和抽象方法呢?

抽象类中的抽象方法如果没有被重写,编译器会报错。 是的,使用抽象类和抽象方法的好处就是多了一层编译器的校验,能帮助我们检查抽象方法是否被重写。

抽象类不能被实例化。 很多引用场景,比如上面的画图形场景,其实际工作不应该由父类完成,而应由子类完成,此时误用父类编译器会报错。

总的来说,使用抽象类就是为了预防出错,并让我们尽早发现问题,充分利用编译器的校验,在实际开发中是非常有意义的。


接口

接口的概念

接口 (英文:Interface),在JAVA编程语言中是一个抽象(引用)类型,是抽象方法的集合。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

我们很容易联想到实际生活中电脑的USB接口,它是一个标准,符合这个标准,就可以与电脑交互(可以插U盘、鼠标等)。

Java中的接口也是公共行为的规范,我们在实现时,只要符合标准,就可以通用。所以,接口可以看作是:多个类的公共规范,是一种引用类型


接口的语法

接口的定义与类的定义类似,只需要把关键字class换成interface

接口只能被publicabstract修饰,abstract默认不显性显示,如果省略public则使用默认的访问权限

java 复制代码
public interface 接口名 {
    //...
}
  • 接口的命名一般是以I开头
  • 接口的命名一般使用形容词词性的单词,这样就能显示出接口的标准

接口的特性

  1. 接口是一种引用类型,但是不能直接实例化对象(接口实际上比抽象类更加抽象),不过接口可以引用实现该接口的类的对象(向上转型)

    java 复制代码
        public static void main(String[] args) {
            ITest iTest = new ITest();//报错,不能直接实例化接口
            }
    java 复制代码
    interface ITest {
        //待实现的方法
        void func();
    }
    
    class A implements ITest {
        @Override
        public void func() {
            System.out.println("类A实现的func");
        }
    }
    
    class NewTest {
        public static void main(String[] args) {
            //接口引用了A类对象
            ITest iTest = new A();
        }
    }
  2. 接口可以定义变量,且接口的成员变量都是publicstaticfinal修饰的,且接口定义的变量必须手动初始化

    即使我们不写修饰符,成员变量也默认被publicstaticfinal修饰

    java 复制代码
    public abstract interface ITest {
        //下面定义的变量都是由 public static final 修饰的(不论是否显性书写或写全)
        public int a = 1;
        static int b = 2;
        final int c = 3;
        int d = 4;
        public static int e = 5;
        static final int f = 6;
        public final int g = 7;
        public static final int h = 8;
    }
  3. 接口可以定义方法,接口的方法默认是publicabstract修饰的,属于抽象方法,不需要方法体。如果想要在接口中具体实现一个方法,那么它必须是由defaultstatic修饰

    java 复制代码
    public interface ITest {
        //以下写法的修饰符都是 public abstract(无论是否写或写全)
        void func0();
        public abstract void func1();
        public void func2();
        abstract void func3();
        
        //被default或static修饰的方法有具体的实现
        default void func4() {
            System.out.println("接口中被default修饰的方法有方法体");
        }
    
        static void func5() {
            System.out.println("接口中被static修饰的方法有方法体");
        }
    }
  4. 实现接口的类,必须重写接口中的所有抽象方法,如果此类不想重写接口中的抽象方法,则它必须由abstract修饰,一旦此类被另一个非抽象类继承,那么这个非抽象类必须重写所有未被重写的抽象方法

    java 复制代码
    interface IA {
        //接口抽象方法
        void abfunc1();
    }
    
    abstract class A implements IA {
        public void myA() {
            System.out.println("A类特有的方法");
        }
        //A类抽象方法
        abstract void abfunc2();
    }
    
    class B extends A {
        @Override
        public void abfunc1() {
            System.out.println("重写IA接口中的抽象方法");
        }
    
        @Override
        void abfunc2() {
            System.out.println("重写B类中的抽象方法");
        }
    }
  5. 接口中不能有静态代码块、构造代码块或构造方法

  6. 由于接口的成员方法默认都有public修饰,所以重写后的方法必须是public修饰(重写后的方法的访问权限要大于等于父类)

  7. 上述表述的重写严格来说不准确,不过,语法要求与重写一致(这里不必纠结,只要记住抽象方法要被重写即可)

  8. 接口文件的文件名必须与接口名相同,且接口文件编译后也是.class为后缀的字节码文件


接口的使用

接口不能直接实例化对象,接口的使用需要一个 "实现类" 来 实现 接口中的方法,要用到关键字implements

java 复制代码
public class 类 implements 接口 {
    //...
}

接口与类之间implements是实现关系,父类和子类之间extends是继承关系

java 复制代码
interface ITest {
    //待实现的方法
    void func();
}

class A implements ITest {
    @Override
    public void func() {
        System.out.println("类A实现的func");
    }
}

继承表达的是is A的关系,如狗是动物;接口表达的含义是具有xxx特性,如狗会跑


实现多个接口

Java中不支持多继承,不过一个类可以实现多个接口,这个类需要实现所有接口的抽象方法:

java 复制代码
//ISwimming.java
public interface ISwimming {
    void swim();
}

//IRunning.java
public interface IRunning {
    void run();
}

//Dog.java
public class Dog implements IRunning, ISwimming {
    public String name;
    public int age;

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

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

    @Override
    public void swim() {
        System.out.println(this.name + "正在游泳");
    }
}
  • 一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类

一个类可以继承一个父类并同时实现多个接口:

java 复制代码
//Animal.java
public class Animal {
    public String name;
    public int age;
}
//IRunning.java
public interface IRunning {
    void run();
}

//ISwimming.java
public interface ISwimming {
    void swim();
}

//Dog.java
public class Dog extends Animal implements IRunning, ISwimming {

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

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

    @Override
    public void swim() {
        System.out.println(this.name + "正在游泳");
    }
}
  • 继承与实现都存在时,必须先写extends继承,后写implements实现
  • 对于上面的代码,理解一点:只要一个类实现了如IRunning接口,它就可以run(),而不必是动物。(有些废话,主要是强调接口使用)

接口与多态

Java中,接口可以实现多态,看如下代码:

java 复制代码
//IEat.java
public interface IEat {
    void eat();
}

//Animal.java
public class Animal {
    public String name;
    public int age;
}

//Cat.java
public class Cat extends Animal implements IEat {

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public void eat() {
        System.out.println(this.name + "正在吃猫粮...");
    }
}

//Dog.java
public class Dog extends Animal implements IEat {

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public void eat() {
        System.out.println(this.name + "正在吃狗粮...");
    }
}

//Test.java
public class Test {
    
    //体现多态
    public static void testEat(IEat iEat) {
        iEat.eat();
    }
    
    public static void main(String[] args) {
        testEat(new Dog("大哈", 2));//匿名对象
        testEat(new Cat("小猫", 3));
    }
}

对于上述代码,我们给同一个方法testEat传入不同的对象CatDog,打印结果不同:


接口间的继承

接口的继承相当于把所有的接口合并在一起。

接口间可以实现继承

java 复制代码
//IExcellentQuality.java
public interface IExcellentQuality {
    void func1();
}

//IKind.java
public interface IKind extends IExcellentQuality {
    void func2();
}

上述代码逻辑上就是:善良的 是 品质优良的

  • 一个接口继承了另一个接口,子接口不实现父接口的抽象方法,而当一个非抽象类实现了子接口,那么这个类要重写子接口和父接口所有的抽象方法

    java 复制代码
    public class Person implements IKind {
        @Override
        public void func2() {
            System.out.println("kind");
        }
    
        @Override
        public void func1() {
            System.out.println("excellent quality");
        }
    }

Java中,类与类之间不可以实现多继承,但接口与接口之间可以实现多继承

比如以下例子,两栖动物既可以游泳又可以行走:

java 复制代码
//IRunning.java
public interface IRunning {
    void run();
}

//ISwimming.java
public interface ISwimming {
    void swim();
}

//IAmphibious.java
public interface IAmphibious extends ISwimming, IRunning {
    void func();
}

【总结】

  • 一个接口继承了另一个接口,子接口不实现父接口的抽象方法,而当一个非抽象类实现了子接口,那么这个类要重写子接口和父接口所有的抽象方法
  • 接口与接口之间可以实现多继承

抽象类和接口的区别

抽象类中可以包含普通的方法和字段,这些字段和方法可以被子类直接使用(普通方法不需要被重写),而接口中不能定义普通的字段和方法,子类必须重写接口中的所有抽象方法。

几点具体的区别:

    1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行(除非被defaultstatic修饰)。
    1. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
    1. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。(从 Java8 开始接口中可以定义静态方法)
    1. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

另外,我们补充一下实现接口与继承的区别:

1.不同的关键字,即实现接口(implements),继承(extends);

2.(一个类只能继承一个父类,而一个方法可以实现多个接口) 在面向对象编程的时候只能是单继承,但是实现接口可以有多个,简单点说,就是实现接口可以有好多个,但是继承的中父类只能只有一个,因为父亲只有一个,这说明了继承在Java中具有单根性,子类只能去继承一个父类;

3.在接口中只能定义全局变量和抽象方法,而在继承中可以定义普通的属性、方法等等...

4.(接口中的抽象方法必须被重写,而继承中子类可以选择不重写父类的方法(不考虑父类是抽象类)) 当某个接口被实现的时候,在类中一定要重写接口中的抽象方法,而继承中子类可以选择是否重写父类的方法,不是强制的


以上就是对Java中抽象类和接口的所有介绍了,希望能给大家带来帮助!
接下来将会发布不少博客:Java实现简单的图书管理系统、介绍内部类、介绍String...

相关推荐
木头没有瓜4 分钟前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
奋斗的老史4 分钟前
Spring Retry + Redis Watch实现高并发乐观锁
java·redis·spring
high20116 分钟前
【Java 基础】-- ArrayList 和 Linkedlist
java·开发语言
1nullptr9 分钟前
lua和C API库一些记录
开发语言·lua
Jerry Nan10 分钟前
Lua元表
开发语言·lua
老马啸西风13 分钟前
NLP 中文拼写检测纠正论文 C-LLM Learn to CSC Errors Character by Character
java
?3333316 分钟前
CTFHub Web进阶-PHP-Bypass disable_function攻略
开发语言·安全·web安全·php
所以经济危机就是没有新技术拉动增长了16 分钟前
二、javascript的进阶知识
开发语言·javascript·ecmascript
Bubluu27 分钟前
浏览器点击视频裁剪当前帧,然后粘贴到页面
开发语言·javascript·音视频
Cosmoshhhyyy35 分钟前
LeetCode:3083. 字符串及其反转中是否存在同一子字符串(哈希 Java)
java·leetcode·哈希算法