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...

相关推荐
考虑考虑1 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261351 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊2 小时前
Java学习第22天 - 云原生与容器化
java
渣哥4 小时前
原来 Java 里线程安全集合有这么多种
java
间彧4 小时前
Spring Boot集成Spring Security完整指南
java
间彧5 小时前
Spring Secutiy基本原理及工作流程
java
Java水解6 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆8 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学8 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole8 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端