Java抽象类和接口

1,抽象类

所谓抽象就是不是特指某个对象,是个很宽泛的概念。如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

1.1 抽象类语法

在 Java 中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法。

  • 抽象方法不能给具体的实现体
  • 如果有抽象方法的类一定是抽象类,但抽象类里面不一定有抽象方法。
java 复制代码
// 抽象类:被abstract修饰的类
 
public abstract class Shape {
    // 抽象⽅法:被abstract修饰的⽅法,没有⽅法体 
    abstract public void draw();
    abstract void calcArea();

    // 抽象类也是类,也可以增加普通⽅法和属性
    public double getArea(){
        return area;
    }
    protected double area;// ⾯积
}
    

抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

1.2 抽象类的特性

  1. 抽象类无法实例化,抽象类可以帮助构建对象。

  2. 抽象方法不能被 private,final,static修饰,因为抽象方法要被子类重写

    java 复制代码
    abstract class Shape {
        abstract private void draw();
    }
    
    public abstract class Shape {
        abstract final void methodA();
        abstract public static void methodB();
    }
    // 编译出错
     
  3. 抽象类必须被继承,并且继承后子类要重写父类的抽象方法,子类也是抽象类就不用重写,但是后续有非抽象类继承,需要把所有的抽象方法都重写。

    java 复制代码
    package demo1;
    
    public abstract class Animal {
        abstract void birth();
    }
    
    package demo1;
    //哺乳类
    public abstract class Mammals extends Animal {
        abstract void viviparity();//胎生
    }
    
    package demo1;
    
    public class Dog extends Mammals{
    //重写两个抽象方法
        @Override
        void viviparity() {
            System.out.println("胎生");
        }
    
        @Override
        void birth() {
            System.out.println("出生");
        }
    }
  4. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量,就和继承的子类创建必须先构造父类一样

1.3 抽象类的作用

抽象类不能实例化,只能继承给其他类帮助其他类创建对象,如果实例化了抽象类就会报错。

2,接口

2.1 接口

接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。

在Java中,接口可以看成是多个类的公共规范,是一种引用数据类型

2.2 语法规则

接口的定义格式与定义类的格式相同,将class关键字换成 interface 关键字,就定义了一个接口

java 复制代码
public interface 接⼝名称{
    // 抽象⽅法
    public abstract void method1();   // public abstract 是固定搭配,可以不写
    public void method2();
    abstract void method3();
    void method4();
    // 注意:在接⼝中上述写法都是抽象⽅法,跟推荐⽅式4,代码更简洁
}
  1. 创建接口时,接口的命名一般以大写字母 i 开头
  2. 接口的命名一般使用"形容词"词性的单词
  3. 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号
  4. 接口当中的成员变量默认是:public static final

2.3 接口的使用

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法

类比抽象类

java 复制代码
public  class 类名称 implements 接⼝名称{
    // ...
} 

类和类是继承 extends 的关系,类和接口是 implements 实现的关系

实现笔记本电脑使⽤ USB ⿏标、 USB 键盘的例子

  1. USB 接⼝:包含打开设备、关闭设备功能

  2. 笔记本类:包含开机功能、关机功能、使⽤ USB 设备功能

  3. ⿏标类:实现 USB 接⼝,并具备点击功能

  4. 键盘类:实现 USB 接⼝,并具备输⼊功能

java 复制代码
// USB接⼝
 
public interface USB {
    void openDevice();
    void closeDevice();
}
// ⿏标类,实现USB接⼝
 
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("⿏标点击");
    }
}
// 键盘类,实现USB接⼝
 
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("关闭笔记本电脑");
    }
    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();
    }
}

// 测试类:
public class TestUSB {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();

    // 使⽤⿏标设备
    computer.useDevice(new Mouse());

    // 使⽤键盘设备
    computer.useDevice(new KeyBoard());
    computer.powerOff();
    }
}

2.4 接口特性

1.接口是一种引用类型,但是不能直接new接口的对象

java 复制代码
public class TestUSB {
    public static void main(String[] args) {
        USB usb = new USB();
    }
}
// Error:(10, 19) java: day20210915.USB
//是抽象的; ⽆法实例化

2.接口中每一个方法都是public的抽象方法,隐式类型为 public abstract (只能是 public abstract,其他修饰符都会报错)

java 复制代码
public interface USB {
// Error:(4, 18) java: 此处不允许使⽤修饰符private 
    private void openDevice();
    void closeDevice();
}

3.接口中的方法不能有主体,因为是抽象方法,jdk8中:接⼝中还可以包含default⽅法

java 复制代码
public interface IUSB {
    default void closeDevice(){
        System.out.println("关闭USB设备");
    }
}

4.重写接口中方法时,不能使用默认的访问权限,要带public

java 复制代码
public interface USB {
    void openDevice();    // 默认是public的
    void closeDevice();   // 默认是public的
 
}
public class Mouse implements USB {
    @Override
    void openDevice() {
        System.out.println("打开⿏标");
    }
// ...
}
// 编译报错,重写USB中openDevice⽅法时,不能使⽤默认修饰符
 
// 正在尝试分配更低的访问权限; 以前为public

5.接口中的成员变量默认是 public static final 修饰

java 复制代码
public interface USB {
    double brand = 3.0;  // 默认被:final public static修饰
 
    void openDevice();
    void closeDevice();
}
public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.brand);   // 可以直接通过接⼝名访问,说明是静态的
// 编译报错:Error:(12, 12) java: ⽆法为最终变量brand分配值
    USB.brand = 2.0;       // 说明brand具有final属性
    }
}
 

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

java 复制代码
public interface USB {
// 编译失败
 
    public USB(){
    }
    {}    // 编译失败
 
    void openDevice();
    void closeDevice();
}

7.接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是 .class

8.如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类

2.5 实现多接口

我们都知道 Java没有多继承,但是可以用接口实现多继承的效果

java 复制代码
interface IRunning {
    void run();
}
interface ISwimming {
    void swim();
}
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 + "正在蹬腿游泳");
    }
}

实现多个接口时,每个接口的抽象方法都要实现

IDEA使用 ctrl + i 快速实现接口

2.6 接口与接口的继承

java 复制代码
interface IRunning {
    void run();
}
interface ISwimming {
    void swim();
}
// 两栖的动物, 既能跑, 也能游
 
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
    ...
}

接口间的继承相当于把多个接口合并在一起

2.7 接口使用例子

对象之间进行大小关系比较

  1. 使用 Comparable 接口 让我们的Student类实现Comparable接口,并实现其中的compareTo⽅法

    java 复制代码
    class Student implements Comparable {
        private String name;
        private int score;
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
        @Override
        public String toString() {
            return "[" + this.name + ":" + this.score + "]";
        }
        @Override
        public int compareTo(Object o) {
             Student s = (Student)o;
            if (this.score > s.score) {
                return -1;
            } else if (this.score < s.score) {
                return 1;
            } else {
                return 0;
            }
        }
    }
    java 复制代码
    public class Test {
        public static void main(String[] args) {
            Student s1 = new Student("zhangsan",10);
            Student s2 = new Student("lisi",20);
            System.out.println(s1.compareTo(s2) );
        }
    }

    如果s1⼤于s2那么返回⼤于0的数字,如果相同返回0,否则返回小于0的数字,这种方法写了之后就很难改变了。

  2. 使用Comparator接口

java 复制代码
class Student {
    public String name;
    public int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    @Override
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }
}
class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.score-o2.score;
    }
}
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}
java 复制代码
public class Test  {
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan",10);
        Student s2 = new Student("lisi",20);
        //根据分数进⾏⽐较
        ScoreComparator scoreComparator = new ScoreComparator();
        System.out.println(scoreComparator.compare(s1, s2));

        //根据姓名进⾏⽐较
        NameComparator nameComparator = new NameComparator();
        System.out.println(nameComparator.compare(s1, s2));
    }
}

2.8 Clonable 接口和深拷贝

Object类中存在⼀个clone⽅法,调⽤这个⽅法可以创建⼀个对象的"拷⻉".但是要想合法调⽤clone ⽅法,必须要先实现Clonable接⼝,否则就会抛出CloneNotSupportedException异常

java 复制代码
class Animal implements Cloneable {
    private String name;
    @Override
    public Animal clone() {
        Animal o = null;
//异常捕获的知识
        try {
            o = (Animal)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Animal animal2 = animal.clone();
        System.out.println(animal == animal2);
    }
}
// 输出结果
// false

浅拷贝

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();
        System.out.println("通过person2修改前的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        person2.money.m = 13.6;
        System.out.println("通过person2修改后的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}
// 执⾏结果
通过person2修改前的结果
99.99
99.99
通过person2修改后的结果
13.6
13.6

深拷贝

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

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

public class TestDemo3 {
    public static void main(String[] args) throws 
CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        System.out.println("通过person2修改前的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        person2.money.m = 13.6;
        System.out.println("通过person2修改后的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}

2.9 抽象类和接口的区别

相同点

  • 都不能实例化

不同点

  • 结构组成:抽象类:普通类+抽象方法。接口:抽象方法+全局常量
  • 权限:抽象类:各种权限。接口:public
  • 子类使用:抽象类:extends继承。接口:implements实现
  • 关系:抽象类:可以实现多个接口。接口:可以继承多个父类接口
  • 子类限制:抽象类:只能继承一个子类。接口:可以继承多个接口

3.内部类

将一个类定义在另一个类或者方法的内部,叫做内部类

java 复制代码
public class OutClass {
    class InnerClass{
    }
}
// OutClass是外部类
// InnerClass是内部类

内部类和外部类共用一个Java源文件,但是经过编译之后,内部类会形成单独的字节码文件

3.1 内部类的分类

  1. 静态内部类
  2. 实例内部类
  3. 局部内部类
  4. 匿名内部类

静态内部类

被 static 修饰的内部成员类称为静态内部类

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

    // 静态内部类:被static修饰的成员内部类
    static class InnerClass{
        public void methodInner(){
    // 在内部类中只能访问外部类的静态成员
    // a = 100;      // 编译失败,因为a不是类成员变量
 
            b =200;
        }
    }
    public static void main(String[] args) {
        // 静态内部类对象创建 & 成员访问
 
        OutClass.InnerClass innerClass = new OutClass.InnerClass();
        innerClass.methodInner();
    }
}
  1. 在静态内部类中只能访问外部类中的静态成员
  2. 创建静态内部对象时,不需要先创建外部类对象

实例内部类

未被 strtic 修饰的成员内部类

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

    //  实例内部类:未被static修饰
    class InnerClass{
        int c;
        public void methodInner(){
            // 在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
            a = 100;
            b =200;
            // 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类⾃⼰的
            c = 300;
            System.out.println(c);
            // 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
            OutClass.this.c = 400;
            System.out.println(OutClass.this.c);
        }
    }
java 复制代码
public static void main(String[] args) {
// 外部类:对象创建以及成员访问
 
    OutClass outClass = new OutClass();
    System.out.println(outClass.a);
    System.out.println(OutClass.b);
    System.out.println(outClass.c);
    System.out.println("=============实例内部类的访问=============");
    // 要访问实例内部类中成员,必须要创建实例内部类的对象
 
    // ⽽普通内部类定义与外部类成员定义位置相同,
    //因此创建实例内部类对象时必须借助外部类
 
    // 创建实例内部类对象
 
    OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
    // 上述语法⽐较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象
 
    OutClass.InnerClass innerClass2 = outClass.new InnerClass();
    innerClass2.methodInner();
    }
}
  1. 外部类中的任何成员都可以在实例内部类方法中直接访问
  2. 实例内部类所处的位置与外部类成员位置相同,因此也受public,private等访问限定符的约束
  3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须使用:外部类名称.this.同名成员 来访问
  4. 实例内部类对象必须在先有外部对象的前提下才能创建
  5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
  6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。

局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使⽤,⼀般使用的⾮常少

java 复制代码
public class OutClass {
    int a = 10;
    public void method(){
        int b = 10;
    // 局部内部类:定义在⽅法体内部
    // 不能被public、static等访问限定符修饰
 
    class InnerClass{
        public void methodInnerClass(){
            System.out.println(a);
            System.out.println(b);
        }
    }

    // 只能在该⽅法体内部使⽤,其他位置都不能⽤
 
    InnerClass innerClass = new InnerClass();
    innerClass.methodInnerClass();
}   
    public static void main(String[] args) {
    // OutClass.InnerClass innerClass = null; 编译失败
    }
}
  1. 局部内部类只能在所定义的方法体内部使用
  2. 不能被public,static等修饰
  3. 编译器也有自己独立的字节码文件,命名格式:外部类$数字内部类名字.class
  4. 几乎不会使用

匿名内部类

没有类名的内部类

java 复制代码
new SuperType(constructor-arguments) {     
  //类体
};

SuperType可以是接口,抽象类或具体类

java 复制代码
interface Greeting {
    void greet();
}
public class Test  {
    public static void main(String[] args) {
        Greeting greeting = new Greeting() {
            @Override
            public void greet() {
                System.out.println("Hello!");
            }
        };
        greeting.greet();
    }
}

匿名内部类当中可以定义和正常类一样的成员变量,但是和正常类一样都不能直接包含执行语句

4.Object类

object是祖先类,一般不写继承的都继承object类

使用Object接受所有类的对象

java 复制代码
class Person{}
class Student{}
public class Test {
    public static void main(String[] args) {
        function(new Person());
        function(new Student());
    }
    public static void function(Object obj) {
        System.out.println(obj);
    }
}
//执⾏结果:
 
Person@1b6d3586
Student@4554617c

发生了向上转型

4.1 获取对象信息

如果要打印对象中的内容,就可以直接重写Object中的toString()方法

java 复制代码
// Object类中的toString()⽅法实现: 
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

4.2 对象比较equals方法

比较相同或者不同

Java中比较相同:

  1. 如果==左右两侧是基本类型变量,比较的是变量中值是否相同
  2. 如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
  3. 要比较就要重写Object中的equals方法,因为equals方法默认也是按照地址比较的
java 复制代码
// Object类中的equals⽅法
 
public boolean equals(Object obj) {
    return (this == obj);   // 使⽤引⽤中的地址直接来进⾏⽐较
}
java 复制代码
class Person{
    private String name; 
    private int age; 
    public Person(String name, int age) {
        this.age = age; 
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("cyy", 20); 
        Person p2 = new Person("cyy", 20); 
        int a = 10;
        int b = 10;
        System.out.println(a == b);            // 输出true 
        System.out.println(p1 == p2);          // 输出false 
        System.out.println(p1.equals(p2));     // 输出false 
    }
}

重写equals方法

java 复制代码
class Person{
    ...
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false ; 
        }
        if(this == obj) {
            return true ; 
        }
        // 不是Person类对象
        if (!(obj instanceof Person)) {
            return false ; 
        }
        Person person = (Person) obj ; // 向下转型,⽐较属性值
 
        return this.name.equals(person.name) && this.age==person.age ; 
    }
}

4.3 hashcode方法

hashcode方法源码

java 复制代码
public native int hashCode();

该方法是一个native方法,底层是由C/C++代码写的看不到。

我们认为两个名字相同,年龄相同的对象,将存储在同⼀个位置,如果不重写hashcode()方法,我们求出的hash值不一样

重写hashcode()方法

java 复制代码
class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("cyy", 20) ;
        Person per2 = new Person("cyy", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
//执⾏结果
//3070322
//3070322

hashcode方法用来确定对象在内存中存储的位置是否相同

相关推荐
小马爱打代码1 小时前
Spring源码 第七篇:Spring Boot 自动配置原理深度拆解
java·spring boot·spring
日取其半万世不竭1 小时前
给 Docker 容器设置 CPU 和内存限制,避免单个服务拖垮整机
java·docker·容器
铁皮哥1 小时前
【agent 开发】Claude Code 的 Skill 是怎么被加载的?从 name/description 到 SKILL.md 再到资源文件
java·服务器·数据库·python·gitee·github·软件工程
小糯米6011 小时前
C语言 自定义类型:结构体 与 联合体
c语言·开发语言·数据结构
jieyucx1 小时前
Go 语言 JSON 序列化与反序列化
开发语言·golang·json·序列化
白宇横流学长1 小时前
基于SpringBoot实现的校园失物招领平台设计与实现【源码+文档】
java·spring boot·后端
罗超驿1 小时前
6.Java多线程详解:Thread类、线程属性与start()方法深度解析
java·开发语言·面试·java-ee
苦逼的猿宝2 小时前
IT技术交流和分享平台的设计与实现(源码+论文)
java·毕业设计·springboot·计算机毕业设计
摇滚侠2 小时前
IDEA 需要修改的配置 开发工具
java·ide·intellij-idea