JAVA学习 DAY13 抽象类和接口【万字长文!一篇搞定!】

 本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。

点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励! 

本文篇幅较长,建议先收藏再食用!


 系列文章目录

JAVA学习 DAY1 初识JAVA

JAVA学习 DAY2 java程序运行、注意事项、转义字符

JAVA学习 DAY3 注释与编码规范讲解

JAVA学习 DAY4 DOS操作讲解及实例

JAVA学习 DAY5 变量&数据类型 [万字长文!一篇搞定!] 

JAVA学习 DAY6 运算符

JAVA学习 DAY7 程序逻辑控制【万字长文!一篇搞定!】

JAVA学习 DAY8 方法【万字长文!一篇搞定!】

JAVA学习 DAY9 数组【万字长文!一篇搞定!】

JAVA学习 DAY10 类和对象【万字长文!一篇搞定!】

JAVA学习 DAY11 类和对象_续1【万字长文!一篇搞定!】

JAVA学习 DAY12 继承和多态【万字长文!一篇搞定!】

JAVA学习 DAY13 抽象类和接口【万字长文!一篇搞定!】

深度剖析 Java 图书管理系统设计与实现:类、接口与对象的实战应用

JAVA学习 DAY15 Java String类

JAVA学习 DAY16 Java 异常

Java 基础全攻略:从语法到实战项目(简单总结)


 拓展文章

Sublime安装指导!只需四步!

图文详解汉诺塔问题:从递归思想到代码实现(零基础也能看懂)

Java避坑指南:千万别在构造方法中调用重写的方法!(附代码案例+执行流程全解析)

Java 接口学习核心难点深度解析

深入剖析 Java 中的深拷贝与浅拷贝:原理、实现与最佳实践


目录

 系列文章目录

前言

Java核心知识点精讲:抽象类、接口与Object类全解析(附实战案例)

第一章 抽象类(Abstract Class)

1.1 抽象类概念:为什么需要抽象类?

1.2 抽象类语法:如何定义和使用抽象类?

1.2.1 抽象类的定义语法

1.2.2 抽象类的使用语法

1.3 抽象类特性:必须掌握的8个核心特性

特性1:抽象类不能直接实例化对象

特性2:抽象方法不能是private修饰的

特性3:抽象方法不能被final和static修饰

特性4:抽象类必须被继承,子类需重写所有抽象方法(否则子类也为抽象类)

特性5:抽象类中不一定包含抽象方法,但有抽象方法的类一定是抽象类

特性6:抽象类中可以有构造方法,供子类初始化父类成员变量

特性7:抽象类可以实现接口

特性8:抽象类的子类可以是普通类或抽象类

1.4 抽象类的作用:为什么要使用抽象类?

作用1:提供统一的模板,实现代码复用和规范

作用2:提供编译器校验,预防出错

1.4.1 抽象类的使用场景总结

第二章 接口(Interface)

2.1 接口的概念:什么是接口?

2.2 接口的语法规则:如何定义和使用接口?

2.2.1 接口的定义语法

2.2.2 接口的命名规范(阿里编码规范)

2.2.3 接口的使用语法

2.3 接口的特性:必须掌握的10个核心特性

特性1:接口是引用数据类型,但不能直接实例化对象

特性2:接口中的抽象方法默认是public abstract,只能是public abstract

特性3:接口中的方法(抽象方法)不能在接口中实现,只能由实现类实现

特性4:重写接口中的抽象方法时,访问权限必须是public

特性5:接口中的变量默认是public static final,必须初始化且不能修改

特性6:接口中不能有构造方法和静态代码块

特性7:接口编译后生成的字节码文件后缀是.class

特性8:一个类可以实现多个接口(解决Java单继承问题)

总结


前言

小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!

Java核心知识点精讲:抽象类、接口与Object类全解析(附实战案例)

在开始正文之前,我们先明确一个核心逻辑:Java是一门纯面向对象的编程语言(OOP),而面向对象的三大特性是封装、继承、多态。抽象类和接口是实现多态的重要手段,它们定义了类的行为规范,实现了代码的解耦和复用;Object类是Java中所有类的根类,所有类都默认继承自Object类,其提供的核心方法是Java对象操作的基础。掌握这三者的用法和区别,是提升Java编程能力的关键一步。

第一章 抽象类(Abstract Class)

在面向对象的概念中,所有的对象都是通过类来描绘的,但并不是所有的类都能用来描绘具体的对象。有些类只是作为其他类的父类,本身并不包含足够的信息来创建具体的实例,这样的类就是抽象类。本章将从概念、语法、特性、作用四个方面,全面解析抽象类的相关知识。

1.1 抽象类概念:为什么需要抽象类?

我们先从两个生活中的例子入手,理解抽象类的核心价值:

例子1:图形类(Shape)与具体图形类(矩形、三角形、圆形)

在开发一个图形绘制程序时,我们会遇到多种具体的图形,比如矩形、三角形、圆形。这些图形都有一个共同的行为——绘制(draw)和计算面积(calcArea)。因此,我们可以抽象出一个父类Shape,用来封装这些共同的行为。但是,Shape类本身并不是一个具体的图形,我们无法确定它的绘制方式和面积计算方式(比如Shape的draw方法应该画什么?面积应该怎么算?)。也就是说,Shape类没有包含足够的信息来描绘一个具体的对象,这样的类就应该设计为抽象类。

例子2:动物类(Animal)与具体动物类(狗、猫)

动物类(Animal)具有“叫(bark)”的行为,但是不同的动物叫声不同——狗叫“汪汪汪”,猫叫“喵喵喵”。Animal类本身并不是一个具体的动物,我们无法确定它的叫声具体是什么,因此Animal类也应该设计为抽象类,而具体的叫声实现交给它的子类(Dog、Cat)来完成。

通过这两个例子,我们可以总结出抽象类的核心概念:

抽象类:一个类中如果没有包含足够的信息来描绘一个具体的对象,或者其核心方法无法给出具体实现(需要子类根据自身特性来实现),这样的类就是抽象类。抽象类中可以包含抽象方法(没有具体实现的方法),也可以包含普通方法和属性。

进一步来说,抽象方法是抽象类的核心特征之一。抽象方法:没有具体实现体的方法,它只定义了方法的名称、参数列表和返回值类型,具体的实现逻辑由子类重写。包含抽象方法的类,必须被声明为抽象类。

这里需要注意一个关键点:抽象类的本质是“模板”,它为子类提供了统一的行为规范,子类通过继承抽象类并实现其抽象方法,来完成具体的功能实现。这也是多态的核心体现——父类引用指向子类对象,调用方法时实际执行的是子类的实现。

1.2 抽象类语法:如何定义和使用抽象类?

在Java中,定义抽象类和抽象方法需要使用abstract关键字,具体语法规则如下:

1.2.1 抽象类的定义语法

使用abstract关键字修饰类,该类即为抽象类。抽象类中可以包含:抽象方法、普通方法、属性、构造方法(注意:构造方法不能用来实例化对象,只能供子类调用)。

// 抽象类:被abstract修饰的类
public abstract class Shape {
    // 抽象方法:被abstract修饰的方法,没有方法体({}省略)
    abstract public void draw(); // 公开的抽象方法
    abstract void calcArea();    // 默认访问权限的抽象方法(不推荐,建议显式声明权限)
    
    // 抽象类中可以包含普通方法(有具体实现体)
    public double getArea() {
        return area;
    }
    
    // 抽象类中可以包含属性
    protected double area; // 面积
    
    // 抽象类中可以包含构造方法(供子类初始化父类成员变量)
    public Shape() {
        this.area = 0.0;
    }
}

语法说明:

  • abstract关键字必须放在类名之前,用来标识该类是抽象类;

  • 抽象方法的定义:abstract关键字 + 方法返回值类型 + 方法名 + (参数列表) + ;(注意:没有方法体{});

  • 抽象类中可以包含普通方法和属性,这些成员的使用方式和普通类一致;

  • 抽象类中可以包含构造方法,子类创建对象时会先调用父类(抽象类)的构造方法,初始化父类的成员变量。

1.2.2 抽象类的使用语法

抽象类本身不能直接实例化对象(因为它没有足够的信息描绘具体对象),必须通过其子类来使用。使用抽象类的步骤如下:

  1. 定义子类,使用extends关键字继承抽象类;

  2. 子类必须重写抽象类中的所有抽象方法(如果子类不重写所有抽象方法,那么子类也必须被声明为抽象类);

  3. 创建子类的对象,通过子类对象调用方法(或通过多态方式,用抽象类引用指向子类对象)。

示例:定义Shape的子类Rect(矩形类)和Circle(圆类),并使用抽象类

// 矩形类:继承抽象类Shape,重写所有抽象方法
public class Rect extends Shape {
    private double length; // 长
    private double width;  // 宽
    
    // 子类构造方法:调用父类构造方法(默认调用无参构造,也可以显式调用有参构造)
    public Rect(double length, double width) {
        super(); // 调用父类Shape的无参构造
        this.length = length;
        this.width = width;
    }
    
    // 重写抽象方法draw()
    @Override
    public void draw() {
        System.out.println("绘制矩形:length=" + length + ", width=" + width);
    }
    
    // 重写抽象方法calcArea()
    @Override
    public void calcArea() {
        this.area = length * width; // 计算矩形面积:长×宽
    }
}

// 圆类:继承抽象类Shape,重写所有抽象方法
public class Circle extends Shape {
    private double r; // 半径
    private static final double PI = 3.1415926; // 圆周率(常量)
    
    public Circle(double r) {
        super();
        this.r = r;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制圆形:r=" + r);
    }
    
    @Override
    public void calcArea() {
        this.area = PI * r * r; // 计算圆形面积:πr²
    }
}

// 测试类:使用抽象类和子类
public class TestShape {
    public static void main(String[] args) {
        // 1. 多态方式:抽象类引用指向子类对象(推荐使用,体现多态特性)
        Shape shape1 = new Rect(3.0, 4.0);
        Shape shape2 = new Circle(2.0);
        
        // 调用方法:实际执行的是子类的实现
        shape1.draw();      // 输出:绘制矩形:length=3.0, width=4.0
        shape1.calcArea();  // 计算面积
        System.out.println("矩形面积:" + shape1.getArea()); // 输出:矩形面积:12.0
        
        shape2.draw();      // 输出:绘制圆形:r=2.0
        shape2.calcArea();  // 计算面积
        System.out.println("圆形面积:" + shape2.getArea()); // 输出:圆形面积:12.5663704
        
        // 2. 直接创建子类对象
        Rect rect = new Rect(5.0, 6.0);
        rect.draw();        // 输出:绘制矩形:length=5.0, width=6.0
        rect.calcArea();
        System.out.println("矩形面积:" + rect.getArea()); // 输出:矩形面积:30.0
    }
}

注意:如果子类没有重写抽象类中的所有抽象方法,那么子类必须被声明为抽象类。例如,定义三角形类Triangle,只重写draw方法,不重写calcArea方法,则Triangle必须是抽象类:

// 三角形类:继承Shape,只重写draw方法,未重写calcArea方法,因此必须声明为抽象类
public abstract class Triangle extends Shape {
    private double a; // 边a
    private double b; // 边b
    private double c; // 边c
    
    public Triangle(double a, double b, double c) {
        super();
        this.a = a;
        this.b = b;
        this.c = c;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制三角形:a=" + a + ", b=" + b + ", c=" + c);
    }
    
    // 未重写calcArea()方法,因此Triangle必须是抽象类
    // 三角形的面积计算需要根据具体类型(直角三角形、等腰三角形等)实现,因此可以进一步细化子类
}

// 直角三角形类:继承抽象类Triangle,重写calcArea方法
public class RightTriangle extends Triangle {
    private double base;   // 底边
    private double height; // 高
    
    public RightTriangle(double base, double height) {
        super(base, height, Math.hypot(base, height)); // 斜边长度:√(base²+height²)
        this.base = base;
        this.height = height;
    }
    
    @Override
    public void calcArea() {
        this.area = 0.5 * base * height; // 直角三角形面积:0.5×底×高
    }
}

1.3 抽象类特性:必须掌握的8个核心特性

抽象类的特性是面试中的高频考点,也是实际开发中容易出错的地方。我们结合语法和示例,逐一解析抽象类的核心特性:

特性1:抽象类不能直接实例化对象

抽象类本身没有足够的信息描绘具体对象,因此无法直接使用new关键字创建抽象类的实例。如果尝试实例化,编译器会直接报错。

// 错误示例:抽象类不能直接实例化
Shape shape = new Shape(); 
// 编译报错:Error:(30, 23) java: Shape是抽象的; 无法实例化

原因:抽象类中的抽象方法没有具体实现,实例化后调用抽象方法会导致程序异常,因此编译器禁止直接实例化抽象类。

特性2:抽象方法不能是private修饰的

抽象方法需要被子类重写才能使用,而private修饰的方法只能在当前类中访问,子类无法访问和重写。因此,抽象方法不能用private修饰,否则编译器报错。

// 错误示例:抽象方法不能是private
abstract class Shape {
    abstract private void draw(); // 编译报错
}
// 报错信息:Error:(4, 27) java: 非法的修饰符组合: abstract和private

注意:抽象方法的访问权限推荐使用public(默认也是public,除非显式声明其他权限,但不推荐使用默认权限),确保子类可以正常重写。

特性3:抽象方法不能被final和static修饰

- final修饰的方法不能被重写,而抽象方法必须被子类重写,因此abstract和final不能组合使用;

- static修饰的方法属于类级别的方法,不属于对象,而抽象方法需要通过对象调用(子类对象),且static方法无法被重写(只能被隐藏),因此abstract和static不能组合使用。

// 错误示例:抽象方法不能被final和static修饰
public abstract class Shape {
    abstract final void methodA();    // 错误:abstract和final冲突
    abstract public static void methodB(); // 错误:abstract和static冲突
}
// 编译报错:
// Error:(20, 25) java: 非法的修饰符组合: abstract和final
// Error:(21, 33) java: 非法的修饰符组合: abstract和static

特性4:抽象类必须被继承,子类需重写所有抽象方法(否则子类也为抽象类)

抽象类的核心作用是作为父类,为子类提供模板。因此,抽象类必须被继承才能使用。子类继承抽象类后,有两种选择:

  • 重写抽象类中的所有抽象方法,此时子类可以是普通类,能够实例化对象;

  • 只重写部分抽象方法(或不重写),此时子类必须被声明为抽象类,无法实例化对象。

示例:见1.2.2中的Triangle类和RightTriangle类,Triangle类继承Shape后未重写calcArea方法,因此被声明为抽象类;RightTriangle类继承Triangle后重写了calcArea方法,因此是普通类,可以实例化。

特性5:抽象类中不一定包含抽象方法,但有抽象方法的类一定是抽象类

这是一个容易混淆的知识点。抽象类的核心标识是abstract关键字,而不是是否包含抽象方法。也就是说,一个抽象类中可以没有抽象方法,只包含普通方法和属性。但是,只要一个类中包含了抽象方法,那么该类必须被声明为抽象类。

// 示例1:抽象类中没有抽象方法(合法)
public abstract class AbstractDemo {
    // 普通方法
    public void sayHello() {
        System.out.println("Hello, Abstract Class!");
    }
    
    // 属性
    private String name;
    
    // 构造方法
    public AbstractDemo(String name) {
        this.name = name;
    }
}

// 子类继承抽象类(无需重写抽象方法,因为父类没有抽象方法)
public class Demo extends AbstractDemo {
    public Demo(String name) {
        super(name);
    }
}

// 测试
public class TestAbstract {
    public static void main(String[] args) {
        Demo demo = new Demo("Java");
        demo.sayHello(); // 输出:Hello, Abstract Class!
    }
}

// 示例2:包含抽象方法的类必须是抽象类(否则编译报错)
// 错误示例:包含抽象方法但未声明为抽象类
public class Test {
    abstract public void method(); // 编译报错:类必须声明为抽象类
}

思考:为什么要定义没有抽象方法的抽象类?核心目的是禁止该类直接实例化。即使该类中都是普通方法,我们也不希望用户直接创建该类的对象,而是通过子类来使用,此时就可以将其声明为抽象类。

特性6:抽象类中可以有构造方法,供子类初始化父类成员变量

抽象类不能直接实例化,但可以有构造方法。这些构造方法的作用不是创建抽象类的对象,而是供子类调用,初始化父类中的成员变量(子类创建对象时,会先执行父类的构造方法)。

// 抽象类:包含构造方法
public abstract class Person {
    protected String name;
    protected int age;
    
    // 无参构造
    public Person() {
        this.name = "未知";
        this.age = 0;
    }
    
    // 有参构造
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 抽象方法
    abstract public void introduce();
}

// 子类:Student
public class Student extends Person {
    private String studentId; // 学号
    
    // 子类构造方法:调用父类的有参构造
    public Student(String name, int age, String studentId) {
        super(name, age); // 调用父类Person的有参构造,初始化name和age
        this.studentId = studentId;
    }
    
    @Override
    public void introduce() {
        System.out.println("我是" + name + ", 年龄" + age + ", 学号" + studentId);
    }
}

// 测试
public class TestPerson {
    public static void main(String[] args) {
        Student student = new Student("张三", 20, "2026001");
        student.introduce(); // 输出:我是张三, 年龄20, 学号2026001
    }
}

注意:子类的构造方法中,必须通过super()调用父类的构造方法(如果父类没有无参构造,子类必须显式调用父类的有参构造),否则编译器会默认调用父类的无参构造,如果父类没有无参构造,会编译报错。

特性7:抽象类可以实现接口

抽象类和接口之间是实现关系(使用implements关键字),抽象类实现接口后,可以不重写接口中的抽象方法(因为抽象类本身可以包含抽象方法),这些未重写的接口方法会成为抽象类的抽象方法,由抽象类的子类来重写。

// 定义接口
public interface IRunning {
    void run();
}

// 抽象类实现接口,不重写run方法
public abstract class Animal implements IRunning {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    // 接口中的run方法未重写,成为抽象类的抽象方法
    abstract public void eat(); // 抽象类自身的抽象方法
}

// 子类Dog继承Animal,必须重写所有抽象方法(run和eat)
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    
    // 重写接口IRunning中的run方法
    @Override
    public void run() {
        System.out.println(name + "正在用四条腿跑");
    }
    
    // 重写抽象类Animal中的eat方法
    @Override
    public void eat() {
        System.out.println(name + "正在吃骨头");
    }
}

特性8:抽象类的子类可以是普通类或抽象类

抽象类的子类如果重写了父类的所有抽象方法,那么子类是普通类,可以实例化;如果子类没有重写父类的所有抽象方法,那么子类必须是抽象类,无法实例化。

这一特性和特性4本质上是一致的,这里再强调一下,避免混淆。

1.4 抽象类的作用:为什么要使用抽象类?

很多初学者会有疑问:普通类也可以被继承,普通方法也可以被重写,为什么非要用抽象类和抽象方法呢?其实,抽象类的核心作用有两个:

作用1:提供统一的模板,实现代码复用和规范

抽象类可以封装子类的共同属性和方法,为子类提供统一的模板。子类继承抽象类后,无需重复编写共同的代码,只需重写抽象方法即可实现自身的个性化逻辑。这样既实现了代码复用,又保证了子类的行为一致性(符合父类的规范)。

例如,Shape类封装了所有图形的共同属性(area)和共同方法(draw、calcArea),子类Rect、Circle只需重写draw和calcArea方法,实现自身的绘制和面积计算逻辑,无需重复定义area属性和getArea方法。

作用2:提供编译器校验,预防出错

这是抽象类非常重要的一个作用。使用抽象类相当于多了一重编译器的校验,避免开发者误用父类(抽象类)创建对象,或者子类忘记重写抽象方法。

举个例子:如果我们把Shape类设计为普通类,draw方法提供一个空实现(比如只打印一句“绘制图形”),那么开发者可能会不小心创建Shape类的对象并调用draw方法,此时执行的是空实现,无法达到预期效果,而且编译器不会报错,问题只能在运行时发现。而如果把Shape类设计为抽象类,编译器会直接禁止创建Shape类的对象,强制开发者使用子类,从而避免了误用。

再比如,如果子类忘记重写抽象类中的抽象方法,编译器会直接报错,提醒开发者必须重写,而如果是普通方法,子类可以选择不重写(使用父类的实现),如果开发者不小心忘记重写,问题也只能在运行时发现。

这种“预防出错”的设计思想,在Java中很常见,比如final关键字(防止变量被修改、方法被重写、类被继承)、@Override注解(校验方法是否正确重写)等。充分利用编译器的校验,能在开发早期发现问题,提高代码的可靠性和可维护性。

1.4.1 抽象类的使用场景总结

在实际开发中,当满足以下场景时,建议使用抽象类:

  • 多个类具有共同的属性和方法,且这些方法的实现逻辑在子类中不同(需要子类个性化实现);

  • 不希望父类被直接实例化,只能通过子类来使用;

  • 需要为子类提供统一的行为规范,强制子类实现某些核心方法。

第二章 接口(Interface)

接口是Java中另一个核心的多态实现手段,它比抽象类更抽象,是一种“公共的行为规范标准”。接口定义了一组方法的签名(名称、参数列表、返回值类型),但不提供方法的实现(JDK8及以后允许有default方法和static方法)。本章将从概念、语法、特性、使用场景、与抽象类的区别等方面,全面解析接口的相关知识。

2.1 接口的概念:什么是接口?

我们先从生活中的例子入手,理解接口的本质:

例子1:笔记本电脑的USB接口

笔记本电脑上的USB接口,定义了一套公共的规范(比如接口的形状、数据传输协议、供电标准等)。只要符合这个规范的设备(U盘、鼠标、键盘、移动硬盘等),都可以插入USB接口使用。笔记本电脑不需要关心插入的具体是哪种设备,只需要按照USB规范与设备交互即可。

例子2:电源插座

家庭电路中的电源插座,定义了一套公共规范(比如插孔的形状、电压标准、电流标准等)。只要符合这个规范的电器(电脑、电视机、电饭煲、手机充电器等),都可以插入插座使用。插座不需要关心接入的具体是哪种电器,只需要提供符合规范的电力供应即可。

通过这两个例子,我们可以总结出接口的核心概念:

接口:是公共的行为规范标准,它定义了一组方法的签名(规范),但不关心方法的具体实现。任何类只要符合接口的规范(实现接口中的所有抽象方法),就可以使用接口的引用指向它,实现多态。

在Java中,接口是一种引用数据类型,它独立于类之外,不属于任何类的继承体系,但可以被类实现(implements)。接口的核心价值是“解耦”——将方法的定义(规范)与实现分离,让类的使用者只关注类是否具备某种能力(是否实现了某个接口),而不关注类的具体类型。

接口的本质是“has-a”的关系(具有某种能力),而抽象类和子类的关系是“is-a”的关系(是一种什么类型)。例如:

  • Cat是Animal的子类(is-a:猫是一种动物);

  • Cat实现IRunning接口(has-a:猫具有会跑的能力)。

2.2 接口的语法规则:如何定义和使用接口?

在Java中,定义接口使用interface关键字,具体语法规则如下:

2.2.1 接口的定义语法

接口的定义格式与类类似,将class关键字换成interface关键字即可。接口中可以包含:抽象方法(JDK8之前只能是抽象方法)、default方法(JDK8及以后)、static方法(JDK8及以后)、常量(默认是public static final)。

// 接口定义:使用interface关键字
public interface USB {
    // 1. 抽象方法:JDK8及以后,默认是public abstract(可以省略不写)
    void openDevice(); // 推荐写法:省略public abstract,代码更简洁
    public abstract void closeDevice(); // 完整写法
    public void transferData(); // 省略abstract,默认是abstract
    abstract void resetDevice(); // 省略public,默认是public
    
    // 2. 常量:默认是public static final(可以省略不写)
    double VERSION = 3.0; // 推荐写法:省略public static final
    public static final String TYPE = "USB 3.0"; // 完整写法
    
    // 3. default方法(JDK8及以后):有方法体,供子类调用或重写
    default void showInfo() {
        System.out.println("USB设备:版本" + VERSION + ", 类型" + TYPE);
    }
    
    // 4. static方法(JDK8及以后):有方法体,属于接口,通过接口名调用
    static void checkDevice() {
        System.out.println("检查USB设备是否符合规范...");
    }
}

语法说明(重要,面试高频考点):

  • 接口的访问权限:可以是public,也可以是默认权限(包访问权限)。如果是public,该接口可以被所有包中的类访问;如果是默认权限,只能被同一个包中的类访问。

  • 抽象方法:接口中的抽象方法默认被public abstract修饰,无论是否显式声明。因此,接口中的抽象方法不能使用private、protected、default、static、final等修饰符(与抽象类的抽象方法规则类似)。

  • 常量:接口中的变量默认被public static final修饰,无论是否显式声明。因此,接口中的变量必须初始化(因为final修饰的变量必须初始化),且不能被修改。变量名推荐使用全大写,多个单词之间用下划线分隔(规范)。

  • default方法:JDK8及以后新增,用于为接口添加新的方法,同时不影响已实现该接口的类(子类可以直接调用,也可以重写)。default方法必须有方法体,且只能被接口的实现类对象调用(不能通过接口名调用)。

  • static方法:JDK8及以后新增,属于接口本身,不属于实现类。static方法必须有方法体,只能通过接口名调用(不能通过实现类对象调用)。

  • 接口中不能包含构造方法和静态代码块:因为接口不是类,无法实例化,构造方法没有意义;静态代码块用于初始化类,接口也不需要。

2.2.2 接口的命名规范(阿里编码规范)

为了提高代码的可读性和规范性,阿里编码规范中对接口的命名有明确要求:

  • 接口的命名一般以大写字母“I”开头(区分于普通类),例如IRunning、ISwimming、IUSB;

  • 接口的命名一般使用“形容词”词性的单词,或者使用“动词+名词”的结构,体现接口的能力或规范,例如IRunnable(可运行的)、IFlyable(可飞行的)、IDataTransfer(数据传输);

  • 接口中的方法和属性不要加任何修饰符号(除了default和static方法),保持代码的简洁性。例如,抽象方法不要写public abstract,常量不要写public static final。

示例:符合阿里规范的接口定义

// 符合规范的接口:I开头,形容词词性
public interface IRunning {
    void run(); // 抽象方法,无修饰符
}

// 符合规范的接口:I开头,动词+名词结构
public interface IDataTransfer {
    void transfer(byte[] data); // 抽象方法,无修饰符
    String CHARSET = "UTF-8"; // 常量,无修饰符
    
    default void logTransfer() {
        System.out.println("数据传输中...");
    }
    
    static void checkData(byte[] data) {
        if (data == null || data.length == 0) {
            throw new IllegalArgumentException("数据不能为空");
        }
    }
}

2.2.3 接口的使用语法

接口不能直接实例化对象(因为它是抽象的,没有足够的信息描绘具体对象),必须通过“实现类”来使用。类与接口之间是“实现关系”,使用implements关键字。使用接口的步骤如下:

  1. 定义实现类,使用implements关键字实现接口(一个类可以实现多个接口,用逗号分隔);

  2. 实现类必须重写接口中的所有抽象方法(如果实现类是抽象类,可以不重写所有抽象方法);

  3. 创建实现类的对象,通过对象调用方法(或通过多态方式,用接口引用指向实现类对象)。

示例:实现USB接口的Mouse类和KeyBoard类,并使用接口

// USB接口(符合阿里规范)
public interface IUSB {
    // 抽象方法
    void openDevice();
    void closeDevice();
    
    // 常量
    String VERSION = "3.0";
    
    // default方法
    default void showVersion() {
        System.out.println("USB版本:" + VERSION);
    }
    
    // static方法
    static void checkUSB() {
        System.out.println("检查USB设备兼容性...");
    }
}

// 鼠标类:实现IUSB接口,重写所有抽象方法
public class Mouse implements IUSB {
    private String name;
    
    public Mouse(String name) {
        this.name = name;
    }
    
    // 重写接口的抽象方法openDevice
    @Override
    public void openDevice() {
        System.out.println("打开" + name + "鼠标");
    }
    
    // 重写接口的抽象方法closeDevice
    @Override
    public void closeDevice() {
        System.out.println("关闭" + name + "鼠标");
    }
    
    // 鼠标自身的方法
    public void click() {
        System.out.println(name + "鼠标点击");
    }
    
    // 可选:重写接口的default方法(不重写则使用接口的默认实现)
    @Override
    public void showVersion() {
        System.out.println(name + "鼠标:支持USB " + VERSION + " 接口");
    }
}

// 键盘类:实现IUSB接口,重写所有抽象方法
public class KeyBoard implements IUSB {
    private String brand;
    
    public KeyBoard(String brand) {
        this.brand = brand;
    }
    
    @Override
    public void openDevice() {
        System.out.println("打开" + brand + "键盘");
    }
    
    @Override
    public void closeDevice() {
        System.out.println("关闭" + brand + "键盘");
    }
    
    // 键盘自身的方法
    public void input() {
        System.out.println(brand + "键盘输入文字");
    }
}

// 笔记本电脑类:使用USB设备(依赖IUSB接口,不依赖具体实现类)
public class Computer {
    public void powerOn() {
        System.out.println("打开笔记本电脑");
    }
    
    public void powerOff() {
        System.out.println("关闭笔记本电脑");
    }
    
    // 多态:接收IUSB接口的引用,不关心具体是哪种USB设备
    public void useUSBDevice(IUSB usb) {
        IUSB.checkUSB(); // 调用接口的static方法(通过接口名调用)
        usb.openDevice(); // 调用实现类的方法
        usb.showVersion(); // 调用default方法(实现类重写了则执行重写后的逻辑,否则执行默认逻辑)
        
        // 向下转型:判断具体设备类型,调用特有方法
        if (usb instanceof Mouse) {
            Mouse mouse = (Mouse) usb;
            mouse.click();
        } else if (usb instanceof KeyBoard) {
            KeyBoard keyBoard = (KeyBoard) usb;
            keyBoard.input();
        }
        
        usb.closeDevice();
        System.out.println("------------------------");
    }
}

// 测试类:使用接口和实现类
public class TestUSB {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();
        
        // 创建USB设备对象
        IUSB mouse = new Mouse("罗技"); // 多态:接口引用指向Mouse对象
        IUSB keyBoard = new KeyBoard("机械革命"); // 多态:接口引用指向KeyBoard对象
        
        // 使用USB设备
        computer.useUSBDevice(mouse);
        computer.useUSBDevice(keyBoard);
        
        computer.powerOff();
    }
}

运行结果:

代码分析:

  • Computer类的useUSBDevice方法接收IUSB接口的引用,而不是具体的Mouse或KeyBoard对象,这样Computer类与具体的USB设备解耦——即使以后新增其他USB设备(比如U盘、移动硬盘),只要实现IUSB接口,就可以直接传入useUSBDevice方法使用,无需修改Computer类的代码(符合开闭原则);

  • Mouse类重写了接口的default方法showVersion,因此调用时执行的是Mouse类的重写逻辑;KeyBoard类没有重写showVersion方法,因此调用时执行的是接口的默认逻辑;

  • 接口的static方法checkUSB通过接口名IUSB调用,不能通过实现类对象调用(比如usb.checkUSB()会编译报错);

  • 通过instanceof关键字判断接口引用指向的具体实现类,然后向下转型,调用实现类的特有方法(比如Mouse的click方法、KeyBoard的input方法)。

2.3 接口的特性:必须掌握的10个核心特性

接口的特性与抽象类有相似之处,但也有很多独特的地方,是面试中的高频考点。我们结合语法和示例,逐一解析接口的核心特性:

特性1:接口是引用数据类型,但不能直接实例化对象

接口和类、数组一样,是Java中的引用数据类型,但接口没有构造方法,无法直接使用new关键字创建对象。如果尝试实例化,编译器会报错。

// 错误示例:接口不能直接实例化
IUSB usb = new IUSB(); 
// 编译报错:Error:(10, 19) java: IUSB是抽象的; 无法实例化

原因:接口中的抽象方法没有具体实现,实例化后调用抽象方法会导致程序异常,因此编译器禁止直接实例化接口。

特性2:接口中的抽象方法默认是public abstract,只能是public abstract

接口中的抽象方法无论是否显式声明,都会被隐式指定为public abstract。因此,抽象方法不能使用其他修饰符(private、protected、default、static、final等),否则编译器报错。

// 错误示例:接口抽象方法使用非法修饰符
public interface ITest {
    private void method1(); // 错误:private修饰符非法
    protected void method2(); // 错误:protected修饰符非法
    default void method3(); // 错误:default修饰符非法(抽象方法不能有default)
    static void method4(); // 错误:static修饰符非法(抽象方法不能有static)
    final void method5(); // 错误:final修饰符非法(抽象方法不能有final)
}
// 编译报错信息(部分):
// Error:(4, 18) java: 此处不允许使用修饰符private
// Error:(5, 19) java: 此处不允许使用修饰符protected
// Error:(6, 20) java: 接口抽象方法不能带有主体(default方法有主体,不能是抽象方法)

特性3:接口中的方法(抽象方法)不能在接口中实现,只能由实现类实现

接口只定义方法的规范(签名),不提供方法的实现。抽象方法没有方法体({}省略),必须由实现接口的类重写并实现;default方法和static方法有方法体,但它们不是抽象方法,default方法由实现类对象调用(可重写),static方法由接口名调用(不可重写)。

// 错误示例:接口中的抽象方法不能有方法体
public interface ITest {
    void method1() { // 错误:抽象方法不能有方法体
        System.out.println("method1");
    }
}
// 编译报错:Error:(5, 23) java: 接口抽象方法不能带有主体

特性4:重写接口中的抽象方法时,访问权限必须是public

接口中的抽象方法默认是public,子类重写方法时,访问权限不能低于父类方法的访问权限(Java中的重写规则)。因此,重写接口抽象方法时,必须显式声明为public(不能使用默认权限、protected、private),否则编译器报错。

// 错误示例:重写接口方法时使用非public权限
public interface IUSB {
    void openDevice(); // 默认是public abstract
}

public class Mouse implements IUSB {
    @Override
    void openDevice() { // 错误:访问权限低于public(默认权限)
        System.out.println("打开鼠标");
    }
}
// 编译报错:Error:(8, 10) java: 正在尝试分配更低的访问权限; 以前为public

正确写法:重写方法时显式声明为public

public class Mouse implements IUSB {
    @Override
    public void openDevice() { // 正确:public权限
        System.out.println("打开鼠标");
    }
}

特性5:接口中的变量默认是public static final,必须初始化且不能修改

接口中的变量无论是否显式声明,都会被隐式指定为public static final。因此:

  • 变量必须初始化(final修饰的变量必须初始化);

  • 变量不能被修改(final修饰);

  • 变量可以通过接口名直接访问(static修饰),也可以通过实现类名或实现类对象访问(但不推荐)。

public interface IUSB {
    String TYPE = "USB 3.0"; // 默认:public static final,必须初始化
    double VERSION = 3.0;
}

public class Test {
    public static void main(String[] args) {
        // 访问接口变量(推荐:通过接口名访问)
        System.out.println(IUSB.TYPE); // 输出:USB 3.0
        System.out.println(IUSB.VERSION); // 输出:3.0
        
        // 通过实现类名访问(允许,但不推荐)
        System.out.println(Mouse.TYPE);
        
        // 通过实现类对象访问(允许,但不推荐)
        Mouse mouse = new Mouse("罗技");
        System.out.println(mouse.TYPE);
        
        // 错误:尝试修改接口变量(final修饰,不能修改)
        IUSB.TYPE = "USB 2.0"; // 编译报错:Error:(12, 12) java: 无法为最终变量TYPE分配值
    }
}

特性6:接口中不能有构造方法和静态代码块

接口不是类,无法实例化,因此构造方法没有意义;静态代码块用于初始化类,接口也不需要,因此接口中不能包含构造方法和静态代码块,否则编译器报错。

// 错误示例:接口中包含构造方法和静态代码块
public interface ITest {
    // 错误:接口不能有构造方法
    public ITest() {
    }
    
    // 错误:接口不能有静态代码块
    static {
        System.out.println("静态代码块");
    }
    
    void method();
}
// 编译报错信息:
// Error:(5, 16) java: 接口中不能有构造方法
// Error:(9, 5) java: 接口中不能有静态初始化器

特性7:接口编译后生成的字节码文件后缀是.class

虽然接口不是类,但Java编译器会将接口编译成字节码文件(.class),与类的字节码文件格式相同。例如,IUSB.java编译后会生成IUSB.class文件。

这一特性说明,接口在Java底层是被当作一种特殊的类来处理的,但它不具备类的实例化能力。

特性8:一个类可以实现多个接口(解决Java单继承问题)

Java中类和类之间是单继承的(一个类只能有一个直接父类),但一个类可以实现多个接口(用逗号分隔多个接口名)。这是Java解决单继承局限性的重要手段,让类可以同时具备多种能力。

示例:青蛙(Frog)既能跑(实现IRunning接口),又能游(实现ISwimming接口)

// 定义接口:会跑的
public interface IRunning {
    void run();
}

// 定义接口:会游的
public interface ISwimming {
    void swim();
}

// 动物类(普通父类)
public class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void eat() {
        System.out.println(name + "正在吃东西");
    }
}

// 青蛙类:继承Animal类,同时实现IRunning和ISwimming接口
public class Frog extends Animal implements IRunning, ISwimming {
    public Frog(String name) {
        super(name);
    }
    
    // 重写IRunning接口的run方法
    @Override
    public void run() {
        System.out.println(name + "正在往前跳(跑)");
    }
    
    // 重写ISwimming接口的swim方法
    @Override
    public void swim() {
        System.out.println(name + "正在蹬腿游泳");
    }
}

// 测试类
public class TestFrog {
    public static void main(String[] args) {
        Frog frog = new Frog("小青蛙");
        frog.eat(); // 继承自Animal类的方法
        frog.run(); // 实现IRunning接口的方法
        frog.swim(); // 实现ISwimming接口的方法
        
        // 多态:接口引用指向实现类对象
        IRunning running = new Frog("跳跳蛙");
        running.run();
        
        ISwimming swimming = new Frog("游游蛙");
        swimming.swim();
    }
}

运行结果:

注意:一个类实现多个接口时,必须重写所有接口中的所有抽象方法,否则该类必须被声明为抽象类。

// 错误示例:实现多个接口但未重写所有抽象方法,且未声明为抽象类
public class Duck extends Animal


总结

以上就是今天要讲的内容,本文简单记录了JAVA学习笔记,大家根据注释理解,您的点赞关注收藏就是对小编最大的鼓励!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yvonne爱编码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值