【Java SE】抽象类和接口

目录

本节学习目标

[第一部分 抽象类 abstract](#第一部分 抽象类 abstract)

[1.1 抽象类概念](#1.1 抽象类概念)

[1.2 抽象类语法](#1.2 抽象类语法)

[1.3 抽象类全部特性(高频考点)](#1.3 抽象类全部特性(高频考点))

[1.4 抽象类的作用](#1.4 抽象类的作用)

[第二部分 接口 interface](#第二部分 接口 interface)

[2.1 接口概念](#2.1 接口概念)

[2.2 接口定义语法](#2.2 接口定义语法)

[2.3 接口实现 implements](#2.3 接口实现 implements)

[2.4 接口重要语法约束(必背)](#2.4 接口重要语法约束(必背))

[2.5 一个类实现多个接口(弥补 Java 单继承)](#2.5 一个类实现多个接口(弥补 Java 单继承))

[2.6 接口和接口之间可以多继承](#2.6 接口和接口之间可以多继承)

[2.7 抽象类 vs 接口(面试高频)](#2.7 抽象类 vs 接口(面试高频))

[第三部分 内部类](#第三部分 内部类)

[3.1 静态内部类 static class](#3.1 静态内部类 static class)

[3.2 实例内部类(普通成员内部类,没有 static)](#3.2 实例内部类(普通成员内部类,没有 static))

[3.3 局部内部类](#3.3 局部内部类)

[3.4 匿名内部类(高频)](#3.4 匿名内部类(高频))

[第四部分 Object 根类](#第四部分 Object 根类)

[4.1 toString()](#4.1 toString())

[4.2 equals () 方法](#4.2 equals () 方法)

[4.3 hashCode()](#4.3 hashCode())

[4.4 Cloneable 克隆接口与浅拷贝](#4.4 Cloneable 克隆接口与浅拷贝)

📔本篇核心考点总结


本节学习目标

  1. 掌握抽象类、抽象方法的语法、全部特性,理解抽象类存在的意义
  2. 掌握interface接口语法、接口成员规则、implements实现接口
  3. 掌握多实现、接口之间多继承,理解接口表达 "具备某种能力"
  4. 理解四种内部类:实例内部类、静态内部类、局部内部类、匿名内部类
  5. 吃透Object根类:toString()equals()hashCode(),区分==equals
  6. 了解Cloneable克隆接口、浅拷贝概念

第一部分 抽象类 abstract

1.1 抽象类概念

并不是所有的类都可以用来实例化对象。 有些父类只用来描述通用行为,行为没有办法写出具体实现 ,比如动物bark()叫:动物都会叫,但是普通动物不知道怎么叫,狗、猫这些具体子类才知道怎么叫。

抽象方法 :只有方法声明,没有方法体,没有具体实现。 抽象类 :包含抽象方法的类,被abstract修饰。

示例图形场景:Shape图形父类,draw()画图形,不同图形绘制逻辑完全不一样,父类写不出通用实现,可以把draw()定义为抽象方法。

1.2 抽象类语法

java 复制代码
// abstract修饰抽象类
public abstract class Shape {
    //抽象方法:abstract修饰,没有{}方法体,直接分号结束
    abstract public void draw();
    abstract void calcArea();

    //抽象类也是普通类,可以拥有普通成员变量、普通成员方法
    protected double area;

    public double getArea(){
        return area;
    }
}

✔抽象类可以包含:普通成员变量、普通方法、构造方法、抽象方法。

1.3 抽象类全部特性(高频考点)

  1. 抽象类不能直接 new 实例化对象
java 复制代码
Shape shape = new Shape();
//编译报错:Shape是抽象的; 无法实例化
  1. 抽象方法不能用private修饰 抽象方法目的就是让子类重写,private 子类看不到,矛盾。
java 复制代码
abstract private void draw(); //编译报错:非法修饰符组合 abstract和private
  1. 抽象方法不能使用finalstatic修饰
  • final方法不能被重写,抽象方法必须被子类重写,冲突。
  • static属于类,静态方法不能被子类重写。
java 复制代码
abstract final void methodA(); //报错
abstract public static void methodB(); //报错
  1. 子类继承抽象类,必须重写全部抽象方法;如果不全部实现,子类也必须是 abstract 抽象类
java 复制代码
//矩形子类,实现全部抽象方法,可以是普通类
public class Rect extends Shape {
    private double length;
    private double width;
    Rect(double length, double width){
        this.length = length;
        this.width = width;
    }
    @Override
    public void draw(){
        System.out.println("矩形: length= "+length+" width= " + width);
    }
    @Override
    public void calcArea(){
        area = length * width;
    }
}

//圆子类
public class Circle extends Shape{
    private double r;
    final private static double PI = 3.14;
    public Circle(double r){
        this.r = r;
    }
    @Override
    public void draw(){
        System.out.println("圆:r = "+r);
    }
    @Override
    public void calcArea(){
        area = PI * r * r;
    }
}

//三角形:没有实现calcArea抽象方法,自己必须也是抽象类
public abstract 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);
    }
    //没有重写calcArea,本类必须abstract
}
  1. 抽象类不一定包含抽象方法;但是只要有抽象方法,这个类一定是抽象类

可以写一个没有抽象方法的 abstract 类,目的:禁止直接 new 这个类。

  1. 抽象类存在构造方法!

抽象类不能 new 对象,但是构造方法提供给子类super()调用,用来初始化父类继承的成员变量。

1.4 抽象类的作用

核心作用:利用编译器做语法校验,防止错误实例化父类

普通父类,程序员不小心直接 new 父类对象,编译器不会报错,运行才发现逻辑不对。 抽象类直接禁止实例化,编译阶段就拦截错误。

很多语法设计目的:提前在编译期发现 bug,而不是等到运行时


第二部分 接口 interface

2.1 接口概念

现实类比:USB 接口、电源插座。 接口是一套公共行为规范、标准。设备只要遵守 USB 接口规范,就可以插到 USB 口使用。

Java 中接口是引用类型,代表一组行为规范。

2.2 接口定义语法

使用interface关键字,代替class

java 复制代码
public interface USB {
    // public abstract 可以省略,接口里面抽象方法默认 public abstract
    void openDevice();
    void closeDevice();
}

编码小提示:

  1. 接口命名习惯:一般大写I开头,例如IRunning;多用形容词。
  2. 阿里规范:接口方法不要手写修饰符,保持简洁。

2.3 接口实现 implements

类和接口不是继承extends,是实现implements

java 复制代码
public class Mouse implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }
    public void click(){
        System.out.println("鼠标点击");
    }
}

public class KeyBoard implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }
    public void inPut(){
        System.out.println("键盘输入");
    }
}

笔记本使用 USB 设备示例:

java 复制代码
public class Computer {
    public void powerOn(){
        System.out.println("打开笔记本电脑");
    }
    public void powerOff(){
        System.out.println("关闭笔记本电脑");
    }
    //接口引用接收任意实现了USB规范的对象
    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();
    }
}

2.4 接口重要语法约束(必背)

  1. JDK8 之前,接口中方法默认public abstract抽象方法,不能写方法体
java 复制代码
public interface USB {
    void openDevice();
    // void closeDevice(){ System.out.println(); } //编译报错,不能有方法体
}
  1. 实现接口重写方法,访问权限必须是 public,不能缩小权限。
java 复制代码
//接口方法默认public
public interface USB {
    void openDevice();
}
class Mouse implements USB{
    @Override
    void openDevice(){
        //编译报错!重写不能使用默认包访问,权限降低
    }
}
  1. 接口里面定义的变量,会被编译器隐式加上 public static final是常量,不能修改
java 复制代码
public interface USB {
    double brand = 3.0; //等价 public static final double brand =3.0;
}
public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.brand);
        // USB.brand = 2.0; //编译报错 final常量不能赋值
    }
}
  1. 接口不能包含构造方法、静态代码块
java 复制代码
public interface USB {
    // public USB(){} //构造方法,编译报错
    // {} //实例代码块,编译报错
}
  1. 接口虽然不是类,编译之后同样生成.class字节码文件。
  2. 如果实现类没有实现接口全部抽象方法 ,这个类必须定义为abstract抽象类。
  3. ✨JDK8 新特性:接口中可以写default默认方法,带方法体;还可以有 static 静态方法。

2.5 一个类实现多个接口(弥补 Java 单继承)

Java 类和类只能单继承extends;但是一个类可以implements多个接口,逗号隔开。

java 复制代码
class Animal {
    protected String name;
    public Animal(String name) {
        this.name = name;
    }
}

//定义多个能力接口
interface IFlying { void fly(); }
interface IRunning { void run(); }
interface ISwimming { void swim(); }

//猫:是动物,具备跑的能力
class Cat extends Animal implements IRunning {
    public Cat(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}

//青蛙:继承动物,同时实现跑、游泳两个接口
class Frog extends Animal implements IRunning, ISwimming {
    public Frog(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(this.name + "正在往前跳");
    }
    @Override
    public void swim() {
        System.out.println(this.name + "正在蹬腿游泳");
    }
}

//鸭子,同时实现三个接口
class Duck extends Animal implements IRunning, ISwimming, IFlying {
    public Duck(String name) {
        super(name);
    }
    @Override
    public void fly() { System.out.println(this.name + "正在用翅膀飞"); }
    @Override
    public void run() { System.out.println(this.name + "正在用两条腿跑"); }
    @Override
    public void swim() { System.out.println(this.name + "正在漂在水上"); }
}

语义区分:

  • extends继承:is‑a 是一个;猫是动物
  • implements接口:has‑ability 具备某种能力;猫具备跑的能力

多态用法示例,只关心能力,不关心具体是什么类:

java 复制代码
//只要实现IRunning接口,具备跑的能力,就可以传入,不管是动物还是机器人
public static void walk(IRunning running) {
    System.out.println("我带着伙伴去散步");
    running.run();
}

//机器人不是Animal,但是实现IRunning接口,也可以传进去
class Robot implements IRunning {
    private String name;
    public Robot(String name) { this.name = name; }
    @Override
    public void run() {
        System.out.println(this.name + "正在用轮子跑");
    }
}

2.6 接口和接口之间可以多继承

接口使用extends可以同时继承多个其他接口,实现接口合并。

java 复制代码
interface IRunning { void run(); }
interface ISwimming { void swim(); }

//两栖接口,同时继承跑和游泳两个接口
interface IAmphibious extends IRunning, ISwimming{
}

//实现IAmphibious,就必须实现run()和swim()两个方法
class Frog implements IAmphibious{
    @Override
    public void run() {}
    @Override
    public void swim() {}
}

2.7 抽象类 vs 接口(面试高频)

表格

对比项 抽象类 abstract class 接口 interface
关键字 abstract class interface
成员变量 普通成员变量,可以有各种修饰符 默认public static final常量,不能普通变量
方法 可以普通方法、抽象方法、构造方法 JDK8 前只能抽象方法;JDK8 支持 default、static 方法;没有构造方法
继承 / 实现 extends,类只能单继承 implements,类可以多实现;接口可以多继承接口
语义 is‑a 是一个事物 具备某一种能力、规范

开发经验:优先用接口定义能力;有一部分通用成员逻辑复用,再考虑抽象类。


第三部分 内部类

把一个类写在另外一个类的内部,叫内部类。 分类:

  1. 实例内部类(成员内部类,无 static)
  2. 静态内部类(static 修饰成员内部类)
  3. 局部内部类(写在方法里面,极少用)
  4. 匿名内部类(重点,经常使用)

3.1 静态内部类 static class

被 static 修饰,属于外部类,不属于外部类的对象。

java 复制代码
public class OutClass {
    public int a; //实例成员
    public static int b; //静态成员

    //静态内部类
    static class InnerClass{
        public void methodInner(){
            // a = 100; //❌不能直接访问外部类的实例成员a
            b = 200; //✅可以访问外部类静态成员
        }
    }

    public static void main(String[] args) {
        //创建静态内部类对象,不需要外部类实例对象
        OutClass.InnerClass inner = new OutClass.InnerClass();
        inner.methodInner();
    }
}

要点:

  1. 静态内部类只能直接访问外部类 static 静态成员;访问外部实例成员,必须传入外部类对象引用。
  2. 创建对象语法:外部类.静态内部类 变量 = new 外部类.静态内部类();不需要 new 外部类对象

3.2 实例内部类(普通成员内部类,没有 static)

写在外部类的成员位置,不加 static。每一个外部类对象,对应一份内部类对象

java 复制代码
public class OutClass {
    public int a;
    public static int b;
    public int c = 100;

    //实例内部类
    class InnerClass{
        int c = 200;
        public void methodInner(){
            //可以直接访问外部类所有成员(private也可以)
            System.out.println(a);
            System.out.println(b);
            System.out.println(c); //优先内部自己的c →200
            //访问外部类同名成员:外部类名.this.成员
            System.out.println(OutClass.this.c); //取外部类c →100
        }
    }

    public static void main(String[] args) {
        OutClass out = new OutClass();
        //实例内部类必须依赖外部类对象才能创建
        OutClass.InnerClass inner1 = out.new InnerClass();
        inner1.methodInner();

        //也可以一行写完
        OutClass.InnerClass inner2 = new OutClass().new InnerClass();
    }
}

重点规则:

  1. 实例内部类可以直接访问外部类全部成员,包含 private。
  2. 内外类成员重名:内部优先访问自己;访问外部同名成员:外部类名.this.变量名
  3. 创建实例内部类对象,必须先有外部类对象
  4. 外部类不能直接访问内部类成员,必须先 new 内部类对象。

3.3 局部内部类

定义在方法内部,只能当前方法里面使用,几乎不写业务代码,了解即可。

java 复制代码
public class OutClass {
    int a = 10;
    public void method(){
        //局部内部类,方法里面定义
        class InnerClass{
            public void func(){
                System.out.println(a);
            }
        }
        InnerClass inner = new InnerClass();
        inner.func();
    }
}

限制:不能使用public static等修饰符;只在本方法作用域有效。

3.4 匿名内部类(高频)

没有类名字,定义类的同时直接 new 实例对象;经常用于接口、抽象类,只使用这一次。 语法:

java 复制代码
new 父类/接口(){
    //重写方法
};

示例,实现接口:

java 复制代码
interface Greeting {
    void greet();
}

public class Test {
    public static void main(String[] args) {
        //匿名内部类,没有类名,直接new接口并且重写方法
        Greeting greeting = new Greeting() {
            @Override
            public void greet() {
                System.out.println("Hello!");
            }
        };
        greeting.greet();
    }
}

适用场景:这个实现类只使用一次,不想单独写一个.java文件。


第四部分 Object 根类

⚠️Java 中所有类,直接或者间接继承 Object 。任何对象,都可以用Object引用接收。 Object 是所有类的祖先。

4.1 toString()

java 复制代码
//Object原生toString实现
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

直接打印对象,默认调用 toString,输出类名@十六进制哈希码想要打印对象属性内容,需要重写 toString ()

java 复制代码
class Person{
    String name;
    int age;
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{name="+name+",age="+age+"}";
    }
}

4.2 equals () 方法

==equals()核心区分(面试必考题)

  1. 如果==两边是基本数据类型 :比较值是否相等
  2. 如果==两边是引用类型 :比较两个引用保存的内存地址是否是同一个对象

Object 原生 equals 源码:

java 复制代码
public boolean equals(Object obj) {
    return (this == obj); //原生equals也是比较地址!
}

如果业务需求是比较对象里面的内容属性是否相等 ,必须重写 equals ()

示例:

java 复制代码
class Person{
    String name;
    int age;
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }
}

4.3 hashCode()

返回对象 int 哈希码。

  • 原生实现:根据对象内存地址生成哈希值。
  • 如果重写了equals,业务上认为两个对象内容相等,强烈建议同步重写 hashCode (),保证相等对象 hashCode 返回相同数值
java 复制代码
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

4.4 Cloneable 克隆接口与浅拷贝

Cloneable是标记接口,接口内部没有抽象方法。 实现Cloneable,才可以调用super.clone()做对象拷贝。

clone 默认做浅拷贝:拷贝出来新对象,引用类型成员还是指向原来的堆对象。

java 复制代码
class Money {
    public double m = 99.99;
}

class Person implements Cloneable{
    public Money money = new Money();
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        //浅拷贝,两个person的money引用指向同一个Money对象
        person2.money.m = 13.6;
        System.out.println(person1.money.m); //输出13.6!person1也被修改
    }
}

浅拷贝:基本类型直接复制值;引用类型复制地址,共用同一个子对象。 深拷贝:递归把引用指向的对象也完整复制一份,互不干扰,需要自己手写。


📔本篇核心考点总结

  1. 抽象类 abstract
    • 有抽象方法一定是抽象类;抽象类不一定有抽象方法;不能 new。
    • 子类继承抽象类,必须重写全部抽象方法,否则子类 abstract;抽象类有构造方法供子类 super 调用。
  2. 接口 interface
    • 接口方法默认public abstract;变量默认public static final常量;没有构造方法。
    • implements实现接口;一个类可以多实现;接口之间可以多继承。
    • 语义:extends is‑aimplements具备某种能力。
  3. 内部类
    • 静态内部类:只能直接访问外部 static 成员,创建不需要外部类实例。
    • 实例内部类:依赖外部类对象,可以访问外部所有成员;同名成员使用外部类名.this
    • 匿名内部类:接口 / 抽象类一次性使用,定义同时实例化。
  4. Object
    • 所有类的父类。
    • ==:基本类型比值,引用类型比地址;原生equals也是比地址;比较对象内容要重写equals,配套重写hashCode()
    • clone()默认浅拷贝。

面试简答 Q:抽象类和接口区别?

A:抽象类用 abstract class,可以有普通成员变量、构造方法;接口 interface 变量只能是常量,不能有构造;类单继承抽象类,类可以多实现接口;语义上抽象类代表 is‑a,接口代表具备某种能力。
Q:==equals()区别?

A:==基本类型比较数值;引用类型比较对象地址。Object 原生 equals 等价于 ==;需要比较对象内部属性内容,要手动重写 equals 方法。

相关推荐
传奇开心果编程1 小时前
【Rust入门知识点学与练】第33课:异步编程入门(async/await)强化
开发语言·学习·rust
Lyyaoo.1 小时前
【设计模型】策略模式
java
譕痕1 小时前
Idea 集成 智能体开发工具“千问(qwen)”
java·ide·spring·语言模型
传奇开心果编程1 小时前
【Rust入门知识点学与练】第34课:所有权与借用
开发语言·学习·rust
用户8181870627461 小时前
第27章 消息丢失/重复消费的全链路排查(生产者→Broker→消费者)
java·后端
索隆zoro1 小时前
Army 对 jOOQ
java·后端
二炮手亮子2 小时前
java责任链模式
java
529宝宝起名网2 小时前
用 Python 开发历史名字查询与起名灵感工具:从古籍人物数据库到名字文化故事生成
开发语言·前端·python
AI深栈2 小时前
第 10 章 · Embedding、VectorStore 与 RAG
java·人工智能