重生之小比特头像
关注
【Java SE】抽象类和接口封面图

【Java SE】抽象类和接口

目录

本节学习目标

第一部分 抽象类 abstract

1.1 抽象类概念

1.2 抽象类语法

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

1.4 抽象类的作用

第二部分 接口 interface

2.1 接口概念

2.2 接口定义语法

2.3 接口实现 implements

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

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

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

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

第三部分 内部类

3.1 静态内部类 static class

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

3.3 局部内部类

3.4 匿名内部类(高频)

第四部分 Object 根类

4.1 toString()

4.2 equals () 方法

4.3 hashCode()

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 抽象类语法

// 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 实例化对象
Shape shape = new Shape();
//编译报错:Shape是抽象的; 无法实例化
  1. 抽象方法不能用private修饰 抽象方法目的就是让子类重写,private 子类看不到,矛盾。
abstract private void draw(); //编译报错:非法修饰符组合 abstract和private
  1. 抽象方法不能使用final、static修饰
  • final方法不能被重写,抽象方法必须被子类重写,冲突。
  • static属于类,静态方法不能被子类重写。
abstract final void methodA(); //报错
abstract public static void methodB(); //报错
  1. 子类继承抽象类,必须重写全部抽象方法;如果不全部实现,子类也必须是 abstract 抽象类
//矩形子类,实现全部抽象方法,可以是普通类
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。

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

编码小提示:

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

2.3 接口实现 implements

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

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 设备示例:

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

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

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

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 具备某种能力;猫具备跑的能力

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

//只要实现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可以同时继承多个其他接口,实现接口合并。

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 classinterface
成员变量普通成员变量,可以有各种修饰符默认public static final常量,不能普通变量
方法可以普通方法、抽象方法、构造方法JDK8 前只能抽象方法;JDK8 支持 default、static 方法;没有构造方法
继承 / 实现extends,类只能单继承implements,类可以多实现;接口可以多继承接口
语义is‑a 是一个事物具备某一种能力、规范

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


第三部分 内部类

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

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

3.1 静态内部类 static class

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

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。每一个外部类对象,对应一份内部类对象。

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 局部内部类

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

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 实例对象;经常用于接口、抽象类,只使用这一次。 语法:

new 父类/接口(){
    //重写方法
};

示例,实现接口:

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()

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

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

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 源码:

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

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

示例:

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 返回相同数值。
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

4.4 Cloneable 克隆接口与浅拷贝

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

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

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‑a;implements具备某种能力。
  3. 内部类
    • 静态内部类:只能直接访问外部 static 成员,创建不需要外部类实例。
    • 实例内部类:依赖外部类对象,可以访问外部所有成员;同名成员使用外部类名.this。
    • 匿名内部类:接口 / 抽象类一次性使用,定义同时实例化。
  4. Object
    • 所有类的父类。
    • ==:基本类型比值,引用类型比地址;原生equals也是比地址;比较对象内容要重写equals,配套重写hashCode()。
    • clone()默认浅拷贝。

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

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

Q:==和equals()区别?

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

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/wuqingyigexiaoba/article/details/165481029

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--