Skip to content

一、抽象类

1.1 什么是抽象类

有 abstract 修饰的类,就是抽象类。

语法格式:

java
【其他修饰符】 abstract class 抽象类名{

}

1.2 为什么要用抽象类

首先,父类是代表众多子类的共同的特征。

其次,当类的层次结构多了之后,越往上的类就变得越抽象,越往下的子类就变的越具体。

当定义上层的父类时,某些方法无法给出具体的实现了,只能声明其方法头,方法体写不了,这样的方法就需要被定义为抽象方法。

Java 中规定,凡是类中有抽象方法,这个类必须是抽象类。

方法 = 方法头 + 方法体

【修饰符】 返回值类型 方法名(【形参列表】) {

​ 方法体

}

【修饰符】 返回值类型 方法名(【形参列表】) ==> 方法头

抽象方法的语法格式:

java
【其他修饰符】 abstract  返回值类型 抽象方法名(【形参列表】) ;  //直接;结束,没有{方法体}

两句话:

  • 凡是类中有抽象方法,这个类必须是抽象类
  • 如果一个类中没有抽象方法,这个类也可以是抽象类

1.3 抽象类与普通类有什么不同

  • 抽象类不能直接 new 对象

image-20250708093918212

  • 子类继承抽象类后,必须重写抽象父类的所有抽象方法。否则,子类也得是抽象类。实现(或重写)抽象方法的快捷键是 Ctrl + I。(这里用 Ctrl + O 也可以,只是 Ctrl + O 包含父类抽象和非抽象方法)

image-20250708094115763

问:抽象类有构造器吗?有

抽象类的构造器是给儿子用的。因为子类必须调用父类的构造器,默认调用父类的无参构造。

java
package com.atguigu.chouxiang;

/*
Graphic它也有父类,默认是Object。父类可以不是抽象的。
 */
public abstract class Graphic {//图形类
    private String name;

    public Graphic() {
    }

    public Graphic(String name) {
        this.name = name;
    }

    //求面积
    public abstract double area();
}
java
package com.atguigu.chouxiang;

public class Rectangle extends Graphic{//矩形
    private double length;//长
    private double width;//宽

    public Rectangle() {
        super();
    }

    public Rectangle(double length, double width) {
        super();
        this.length = length;
        this.width = width;
    }
    public Rectangle(String name,double length, double width) {
        super(name);
        this.length = length;
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public String toString() {
        return "长:" + length +
                ",宽:" + width +
                ",面积:" + area();
    }

    @Override
    public double area() {
        return length * width;
    }
}
java
package com.atguigu.chouxiang;

public class TestGraphic {
    public static void main(String[] args) {
//        Graphic g = new Graphic();//错误
        Rectangle g = new Rectangle(8,6);
        double area = g.area();
        System.out.println("面积:" + area);
        System.out.println(g);
    }
}

二、接口

2.1 为什么要用接口?

接口是对类进行补充的一种类型。它接口解决:

  • 解决类的单继承的限制问题。
  • 它可以给多个不是同一类事物的类,提供相同方法的抽取,表示它们遵循同一个标准。例如:Plane、Bird、Kite 有相同的操作方法 fly,它们遵循同一标准。

image-20250708103820565

2.2 如何声明接口(重点)

语法格式:

java
【修饰符】 interface  接口名{

}

image-20250708104118681

2.3 类如何实现接口(重点)

语法格式:

java
【修饰符】 class 类名extends 父类】 implements 接口们{ //接口们代表可以是多个接口,接口1,接口2,接口3

}

//这个类可以被称为接口的子类,也可以被称为实现类(后者更多)
  • 类与接口之间可以多实现
  • 当类实现接口时,如果这个类本身不是抽象类,那么这个类就必须实现接口的所有抽象方法
  • 如果子类同时要继承父类,又要实现父接口,要遵循先继承父类再实现接口

Java 的类与类之间是单继承,类与接口之间是多实现。

比喻:子类与父类比喻为亲儿子和亲生父亲,亲生父亲只有 1 个;

​ 子类与父接口比喻为干儿子和干爹,干爹可以有多个。

2.4 接口与接口之间可以是什么关系?(了解)

语法格式:

java
【修饰符】 interface 子接口 extends 父接口们{

}
  • 接口与接口之间是多继承

2.5 示例代码

2.5.1 示例代码 1

java
package com.atguigu.jiekou;

//声明为abstract,可以避免创建Animal的对象
public abstract class Animal {//动物
}
java
package com.atguigu.jiekou;

//接口默认就是抽象的,不需要加abstract
public  interface Flyable {
    //在接口中,抽象方法默认都是public abstract
    //可以省略public abstract
//    public abstract void fly();
    void fly();
}
java
package com.atguigu.jiekou;

public interface MyRunnable {
    void run();
}
java
package com.atguigu.jiekou;

public class Bird extends Animal implements Flyable,MyRunnable {//鸟
    @Override
    public void fly(){
        System.out.println("我要展翅高飞!");
    }

    @Override
    public void run() {
        System.out.println("我会蹦蹦跳跳跑");
    }
}
java
package com.atguigu.jiekou;

public class Plane implements Flyable{//飞机
    @Override
    public void fly(){
        System.out.println("我要飞入云霄!");
    }
}
java
package com.atguigu.jiekou;

public class Kite implements Flyable{//风筝
    @Override
    public void fly(){
        System.out.println("我飞的再高,线也在你手里!");
    }
}
java
package com.atguigu.jiekou;

public class TestFlyableAndMyRunnable {
    public static void main(String[] args) {
        Bird b = new Bird();
        b.fly();
        b.run();

        Plane p = new Plane();
        p.fly();

        Kite k = new Kite();
        k.fly();

//        Flyable f = new Flyable();//错误,不能直接创建接口的对象,只能创建接口的实现类的对象
    }
}

2.5.2 示例代码 2

java
package com.atguigu.jiekou;

public interface A {
    void a();
}
java
package com.atguigu.jiekou;

public interface B {
    void b();
}
java
package com.atguigu.jiekou;

//接口与接口之间实现多实现
public interface C extends A,B{
    void c();
}
java
package com.atguigu.jiekou;

public class D implements C{
    @Override
    public void c() {

    }

    @Override
    public void a() {

    }

    @Override
    public void b() {

    }
}

2.6 接口的成员

2.6.1 JDK8 之前(重点)

接口的成员只能是:

  • 公共的静态的 final 的常量:public static final,可以省略
  • 公共的抽象方法:public abstract,可以省略,它没有方法体。非抽象子类必须重写。

接口中没有构造器、普通的成员变量、普通的成员方法。

2.6.2 JDK8(含)之后(能看懂即可)

在之前的基础上,增加了如下两类成员:

  • 公共的静态方法:public static ,其中 public 可以省略,static 不可以省略,它有方法体。子类不能重写。

    • 解释:因为静态方法本身与类或接口的对象无关,按照原来的版本,接口中不允许出现静态方法,那么如果有静态方法的需求,就需要单独找一个类装这个静态方法,就增加了维护的成本,因为你需要同时维护接口 和 这个类 。现在接口中允许定义静态方法,代码更紧凑了,遵循了高内聚的开发原则。
  • 公共的默认方法:public default,其中 public 可以省略,default 不可以省略,它也有方法体。子类可以重写,可以不重写。

    • 解释:(1)某个接口的抽象方法,在多个子类中实现的方法体是基本一致的,那么这个方法实现就可以抽取到父接口中,这样避免冗余的重复代码。(2)早期的很多接口,里面只有抽象方法。当版本更新之后,这些接口有可能也要更新(增加新成员),如果按照之前的原则,接口更新只能增加抽象方法,这会导致之前这个接口的所有实现类都报错,都必须重新实现这个新的抽象方法,那么维护的成本就非常高。所以把这个新的抽象方法定为默认方法,旧是实现类可以选择重新实现,也可以选择不实现,比较灵活。

image-20250708112317141

image-20250708112623217

父类的方法不是所有方法都重写的。

(1)final 不能重写

(2)private 不能重写

(3)static 不能重写

2.6.3 JDK9(含)之后(了解)

接口又增加了一个成员:

  • 私有的方法:private ,其中 private 不能省略,私有方法可以是静态的,可以是非静态的,但是私有方法不能是 abstract,也不能是 default。
    • 解释:按照 JDK8 版本的升级,接口中的静态方法和默认方法有方法体了,多个方法之间就可能存在相同的方法实现,这些是冗余的重复代码,需要抽取出来一个方法,这个方法又只是接口的内部使用的,所以应该定义为 private。

image-20250708113308878

2.6.4 接口的成员演示

java
package com.atguigu.jiekou;

public interface Drivable {//可以开的
    //public static final
    int MAX_SPEED = 500;//公共的静态的常量

    void drive();//公共的抽象方法,省略了public abstract

    static void show(){//公共的静态方法,省略了public
        display();
        System.out.println("我开.....心起来");
    }

    default void on(){//公共的默认方法,省略了public
        display();
        System.out.println("启动....");
    }

    private static void display(){//私有静态方法
        System.out.println("我是一个接口");
    }
}
java
package com.atguigu.jiekou;

public class Car implements Drivable{
    @Override
    public void drive() {//必选
        System.out.println("我是su7,我跑起来");
    }

    @Override
    public void on() {//可选
        System.out.println("start....");
    }
}
java
package com.atguigu.jiekou;

public class TestDrivable {
    public static void main(String[] args) {
        //调用接口的公共的静态的常量
        System.out.println("时速的最大值:" + Drivable.MAX_SPEED);

        //调用接口的抽象方法
        Car car = new Car();
        car.drive();

        //调用接口的静态方法
        Drivable.show();

        //调用接口的默认方法
        car.on();

        //调用接口的私有方法
//        Drivable.display();//错误。private不可以跨类使用

    }
}

2.6.5 冲突问题(了解)

1、父类的成员变量与父接口的公共的静态常量重名了

image-20250708114511374

java
package com.atguigu.problem;

public class Father {
    int MAX_VALUE = 1;
}
java
package com.atguigu.problem;

public interface DryFather {//dry 干燥
    int MAX_VALUE = 2;//它隐藏了 public static final
}
java
package com.atguigu.problem;

public class Son extends Father implements DryFather{
    public void show(){
        System.out.println("父类的MAX_VALUE:" + super.MAX_VALUE);
        System.out.println("父接口的MAX_VALUE:" + DryFather.MAX_VALUE);
    }
}
java
package com.atguigu.problem;

public class TestSon {
    public static void main(String[] args) {
        Son s = new Son();
        s.show();
    }
}

2、父类的成员方法与父接口的默认方法冲突了

遵循:默认亲爹优先原则

当然子类也可以主动选择,进行重写。

java
package com.atguigu.problem;

public class Fu {
    public void show(){
        System.out.println("我是父类的方法(1)");
    }
}
java
package com.atguigu.problem;

public interface DryFu {
    default void show(){
        System.out.println("我是父接口的方法(2)");
    }
}
java
package com.atguigu.problem;

public class Sub extends Fu implements DryFu{
    @Override
    public void show() {
//        super.show();//父类的
//        DryFu.super.show();//父接口的
        System.out.println("我自己来");
    }
}
java
package com.atguigu.problem;

public class TestSub {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.show();
    }
}

3、当两个父接口的默认方法冲突了

遵循:必须做出选择的原则,即重写

image-20250708115332400

java
package com.atguigu.problem;

public interface DryDieOne {
    public default void show(){
        System.out.println("我是第1个干爹的方法");
    }
}
java
package com.atguigu.problem;

public interface DryDieTwo {
    public default void show(){
        System.out.println("我是第2个干爹的方法");
    }
}
java
package com.atguigu.problem;

public class ErZi implements DryDieOne,DryDieTwo{
    @Override
    public void show() {
//        DryDieOne.super.show();
//        DryDieTwo.super.show();
        System.out.println("我学习");
    }
}
java
package com.atguigu.problem;

public class TestErZi {
    public static void main(String[] args) {
        ErZi z = new ErZi();
        z.show();
    }
}

2.7 抽象类与接口的区别

相同点:

  • 它们都不能直接 new 对象
  • 它们都可能包含抽象方法

抽象类与接口是面向对象编程中两个重要的概念,尤其在 Java 等语言中使用广泛。它们都用于实现抽象和多态,但有以下主要区别:

区别点抽象类(Abstract Class)接口(Interface)
方法实现可以包含抽象方法和具体方法的实现在 Java 8 前只能包含抽象方法;Java 8 及之后可以包含默认方法 (default) 和静态方法
构造器有构造器,可以被子类调用没有构造器
成员变量可以定义普通成员变量只能是 public static final 的常量
继承关系子类通过 extends 继承抽象类,且只能单继承类通过 implements 实现接口,一个类可以实现多个接口
抽象访问修饰符方法可以使用publicprotected、缺省修饰符方法默认是 public abstract,不能使用其他访问修饰符
设计目的表示“是什么(is -a)”的关系,强调的是类的本质表示“具有什么能力(has-a)”的关系,强调行为规范

总结来说:

  • 如果你需要共享代码或维护状态(这里的状态是指各种属性值),优先选择抽象类;
  • 如果你需要定义行为契约,并允许多个不相关的类实现该契约,优先选择接口。

关键点:

(1)单继承和多实现

(2)成员的情况不同

(3)子类与它的关系是什么?is -a 还是 has-a

三、关键字

回忆咱们已经学过哪些关键字?

txt
基本数据类型:byte,short,int,long,float,double,char,boolean(8种)
空类型:void
引用数据类型:interface(定义接口)、class(定义类)
流程控制语句结构:if、else、switch、case、default、for、do、while、continue、return、break、
对象:new、this、super
关系:extends、implements
包:import、package
修饰符:abstract、static、public、private、final、protected
txt
上下文关键字:yield

特殊值:

java
true, false, null

保留字:

java
gotoconst、_

3.1 native(了解)

native:本地的,原生的

在 Java 中用于修饰方法,这个方法在 Java 层面没有方法体,它的方法体实现在 C/C++底层代码中。

image-20250708144119578

image-20250708144142282

对于 native 的方法,能重写的正常重写,该怎么调用怎么调用,除了方法体代码,与 Java 方法没有什么区别。

3.2 Object 类的方法

1、hashCode:

public native int hashCode();用于产生一个哈希值,它类似于是对象的身份证号码。

image-20250708144524925

2、equals 方法

java
public boolean equals(Object obj):用于比较两个对象是否“相等”。

3、hashCode 和 equals 方法一起重写

image-20250708144810884

image-20250708145007103

image-20250708145159599

image-20250708145307566

java
package com.atguigu.keyword;

import java.util.Objects;

public class Student {
    //实例变量,私有化
    private String name;
    private int score;

    //无参构造,有参构造
    public Student() {
    }

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    //get/set方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }
    //重写toString等

    @Override
    public String toString() {
        return "Student{" +
                "score=" + score +
                ", name='" + name + '\'' +
                '}';
    }

    //重写hashCode方法的快捷键 (1)Ctrl+O (2)Alt+Insert(推荐)
    @Override
    public boolean equals(Object o) {//形参是Object类型,但是实际上,实参应该是Student等对象
        /*
        测试类中 s1.equals(s2)
        this是当前对象,表示调用equals方法的对象。针对上面这句代码,this是s1
        o是()中接收的实参对象。。针对上面这句代码,o是s2
         */
        if (this == o) return true; //如果this==o,等价于 s1==s2,它们的地址相同

        if (o == null || getClass() != o.getClass()) return false;
        /*
        如果 o == null ,就直接返回false。因为如果能进入到这个方法中,说明s1一定不是null,如果s1是null,就发生空指针异常NullPointerException。
                        既然s1不为null,o为null,肯定不相等。

        如果getClass() != o.getClass() ,就直接返回false。
                getClass() 它其实是 this.getClass() 表示的是返回this对象的类型,即s1对象的类型
                o.getClass() 它就是返回o对象的类型,即s2对象的类型
                如果两个对象的类型不同,那么就是false
         */

        Student student = (Student) o; //强制转换
        return score == student.score && Objects.equals(name, student.name);
        //     this.score == student.score 且  Objects.equals(this.name, student.name)(比较的是名字)
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, score);
    }
}
java
package com.atguigu.keyword;

public class TestStudent {
    public static void main(String[] args) {
        Student s1 = new Student("张三",99);
        Student s2 = new Student("张三",99);

        System.out.println(s1 == s2);//false
        //因为Student是类,s1,s2是引用数据类型的变量
        //s1,s2中就存储的是对象的首地址
        //两次new的对象地址值肯定不一样的

        System.out.println(s1.equals(s2));//true
        //如果Student类没有重写equals方法,那么s1.equals(s2)等价于 s1==s2

        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        /*
        Java中规定:
        (1)两个相等的对象,它们的hashCode(哈希值)一定相同
        A:==相等(绝对相同,地址都一样,物理上就是同一个对象
        B:equals相等(相对相同,里面的属性值相同)
        (2)如果两个对象的hashCode(哈希值)不同,那么它们一定不相等
        (3)如果两个对象的hashCode(哈希值)相同,那么它们可能相等,可能不同
            可能存在两个不同的人,身份证号码一样

            因为hashCode也是根据某个公式算出来一个int值
            y=f(x),两个不同的x,可能得到相同的y
         */

        String str1 = "Aa";
        String str2 = "BB";
        System.out.println(str1.equals(str2));//false
        System.out.println(str1.hashCode());//2112
        System.out.println(str2.hashCode());//2112
        /*
        到目前为止哈希值对于我们来说,还没有什么意义。
        等到咱们哈希结构的集合等容器时,才有意义,例如:HashSet、HashMap
         */
    }
}