【JAVA抽象类和接口】

发布于:2024-12-07 ⋅ 阅读:(160) ⋅ 点赞:(0)

](https://img-home.csdnimg.cn/images/20220524100510.png#pic_center)
🌈个人主页: Aileen_0v0
🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法
💫个人格言:“没有罗马,那就自己创造罗马~”

  • 在父类中的方法可以创建,但是无需实现,但是如果不在这个方法里面实现,代码会发生报错,所以为了简化代码,于是Java中提供了abstract这个关键字用于创建抽象方法和抽象类。
    在这里插入图片描述
    在这里插入图片描述
抽象方法书写格式:public abstract void draw();
  • 抽象类的使用规则

    • 1.包含抽象方法的类也必须用abstract这个修饰符修饰这个类,这个类叫做抽象类。
    • 2.抽象类不能被实例化
      在这里插入图片描述
    • 3.若一个普通类继承了一个抽象类继承了一个抽象类,那么这个普通类必须重写这个抽象方法。
      在这里插入图片描述
    • 4.抽象类和普通类的区别·:
      • ①可以和普通方法一样:有成员变量,成员方法。
  • 在这里插入图片描述

      • ②抽象类多了抽象方法(抽象类不一定包含抽象方法,但是有抽象方法的类一定是抽象类)
      • ③抽象类不能进行实例化
      1. 何时设为抽象类:如果这个类不能描述一个具体的对象,那么就可以设置为抽象类,如:Animal
      1. 抽象方法不能被private,finalstatic修饰
      1. 如果一个类不想重写抽象类的方法,那么当前类也要改写成抽象类。不过这个抽象类里面可以用抽象方法也可以不用抽象方法。
        在这里插入图片描述

      1. 如果一个类继承的父类都是抽象类,则这个类里面要重写之前父类里面的所有抽象方法。如下代码所示:
package demo4;

abstract class Shape{
//    public  static  int count = 0 ;
//    public  int size = 0;
//    public  void test(){
//    }
    public  abstract  void  draw();
}
 abstract class A extends Shape{
    public abstract void testA();
}

class B extends A{
    @Override
    public void testA() {
        
    }

    @Override
    public void draw() {
        
    }
}


      1. 抽象类里面可以有构造方法让其可以通过子类构造方法去调用它去进行变量初始化,如下代码所示:
package demo4;

abstract  class Person{
    public  String name;
    public  int  age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Student extends  Person{
    public Student(){
        super("Aileen",21);
    }
}

package demo3;
abstract class Shape{
    //抽象方法
    public abstract void draw();
}

class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("○");
    }
}

class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("矩形");
    }
}

public class Test {
    //定义一个drawing方法,接受Shape类型的参数
    public static  void drawing(Shape myshape){
        //调用传入对象的draw方法
        myshape.draw();
    }

    public static void main(String[] args) {
        //向上转型,将Circle对象赋值给Shape类型的引用
        Shape shape1 = new Cycle();
        //向上转型,将Rect对象赋值给Sgape类型的引用
        Shape shape2 = new Rect();
        //调用drawing方法传入shape1
        drawing(shape1);
        //调用drawing方法传入shape2
        drawing(shape2);
    }
}

抽象类的书写格式:abstract class 类名();

抽象类:一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就叫做抽象类。

package demo4;

abstract class Shape{
//    public  static  int count = 0 ;
//    public  int size = 0;
//    public  void test(){
//    }
    public  abstract  void  draw();
}
class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("画矩形");
    }
}
class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("画一个○");
    }
}
class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

public class Test {
    public static void func(Shape shape){
        shape.draw();
    }
    public static void main(String[] args) {
        //Shape shape = new Shape();
        //如果上面的func是静态的,那么我们可以在这个静态的main方法里面直接调用func
        func(new Cycle());
        func(new Flower());
        func(new Rect());
//******************************************************************************
        //静态方法不能调用非静态,如果非要调用非静态则需要对象的引用去调用。
        //如果上面的func是非静态的(static去掉),则可以按照下面的方法去调用func;
        Test test = new Test();
        test.func(new Flower());

    }
}

抽象类的作用:

  • 抽象类本身不能进行实例化,要想使用,只能创建抽象类的子类,让后让子类重写抽象类中的抽象方法。
  • 抽象类在继承的时候子类必须重写其对应的抽象方法,这就使得抽象类多了一重编译器的校验。

接口

接口:多个类的公共规范,是一种引用数据类型,不能直接new接口对象。

在这里插入图片描述

  • 定义接口用关键字interface来定义
  • 接口当中的成员变量默认都是public static final的,所以可以省略不写,如下面的变量a。
  • 接口当中的方法若未被实现,那它默认就是一个抽象方法(如下面的draw方法,其前面的public abstract可以省略不写),即接口中的每一个方法都是public的抽象方法,会被隐式指为public abstract,其他修饰符都会报错。
  • 接口当中的方法不能有具体的实现,如果需要则此方法需要加上关键字defaultstatic修饰。如下面的test和func方法的实现。
package demo5;

interface  IShape{
   int a = 10;
   void  draw();
   
   public default void test(){
       System.out.println("cool");
   }
   
   public static void func(){
       
   }
}
  • 接口中的方法不能在接口中实现,只能由实现接口的类来实现。(即谁实现implement它的接口,谁就对这个接口的方法进行重写。⚠️但记得一点重写的方法要加public修饰符否则会报错,因为在接口中我们虽然不用加修饰符可以直接写成 void 方法名(),那是因为编译器已经隐式给它加了public abstract,只是没展示而已。如下所示:)
    在这里插入图片描述

  • 接口当中不能有静态代码块和构造方法
    在这里插入图片描述
    -

  • 类和接口的关系通过implements来来关联。(接口里面的所有抽象方法都要被重写)

  • 如果类没有实现接口中的所有抽象方法,则类必须设置为抽象类(但这个类就不能进行实例化了)。在这里插入图片描述

  • 接口虽然不是类,但是也是可以产生字节码文件的

  • 一个类可以继承一个抽象类/普通类同时还可以实现这个接口(必须先继承后实现)。
    在这里插入图片描述

接口的使用

  • 接口不能直接使用,必须要有一个“实现类”来“实现”该接口,实现接口中所有抽象的方法。
  • 子类与父类是extends的关系,类与接口是implements的实现关系。
抽象类的书写格式:public class 类名称 implements 接口名称{ ...... };
  • 创建接口时,接口一般以大写字母I开头。
  • 阿里编码规范中约定,接口的方法和属性不要加任何的修饰符号,保持代码的整洁性。
package demo5;

interface  IShape{

    void  draw();
}

class Rect implements IShape{
    @Override
    public void draw() {
        System.out.println("画矩形");
    }
}

class Cycle implements IShape{
    @Override
    public void draw() {
        System.out.println("○");
    }
}

class Flower implements IShape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
}
public class Test {
    public static void func(IShape myShape){
        myShape.draw();
    }
    public static void main(String[] args) {
//        IShape iShape = new IShape();
        //写法1:
        func(new Cycle());
        func(new Flower());
        func(new Rect());
        System.out.println("--------------------------");
        //写法2:
        IShape iShape1 = new Rect();
        IShape iShape2 = new Cycle();
        IShape iShape3 = new Flower();
        func(iShape1);
        func(iShape2);
        func(iShape3);
    }
}

运行结果:
在这里插入图片描述

接口执行类的过程演示

在这里插入图片描述

  • 我们将接口IUSB的引用指向Mouse的对象,然后通过computer调用其对应的useDevice方法,将Mouse这个对象传入,由于Mouse这个接口里面有重写computer里面的openDevice和closeDevice的方法,所以他会调用Mouse自己的而不是Computer的,然后根据判断语句执行对应的click方法。我们可以看到接口的实现本质也是一种继承。

在这里插入图片描述

  • 我们将接口IUSB的引用指向KeyBoard的对象,然后通过computer调用其对应的useDevice方法,将KeyBoard这个对象传入,由于KeyBoard这个接口里面有重写computer里面的openDevice和closeDevice的方法,所以他会调用KeyBoard自己的而不是Computer的,然后根据判断语句执行对应的input方法。最后会根据useDevice方法里面的调用顺序,首先执行KeyBoard的openDevice方法,然后是执行判断继承以后的input方法,最后执行closeDevice方法。打印结果为:打开键盘->敲键盘->关闭键盘。

在这里插入图片描述

  • 因为现实中不是所有的动物都会游泳,或者都会跑,或者都会飞,所以我们不能直接把这三个方法写到同一个Animal类中,而是把这三个方法分别写到三个不同的接口中。
  • 此外,这三个方法也不能写成类,因为Java当中不能实现多继承,它只能进行单继承,但它可以通过类去实现多个接口。
  • 在这里插入图片描述

在这里插入图片描述
由于Dog没有实现飞的接口所以不能调用。

在这里插入图片描述

package demo2;

public class Roboot implements IRun{


    @Override
    public void run() {
        System.out.println("机器人在用两条腿跑~");
    }
}

public class Test {
    public static void testRun(IRun iRun){
        iRun.run();
    }
    
    public static void main(String[] args) {
        testRun(new Roboot());

    }
  • 上面的代码我们通过Roboot去调用IRun的接口,无需关注Roboot是不是Animal。

接口间的继承

  • Java中,类与类之间是单继承的,一个类可以实现多个接口,接口和接口之间可以多继承。在Java中我们可以通过接口实现多继承。
  • 接口可以继承接口,达到复用的效果,使用extends关键字。
    在这里插入图片描述

抽象类和接口的区别

NO 区别 抽象类(abstract) 接口(Interface)
1 结构组成 普通类+抽象方法 抽象方法+全局常量
2 权限 各种权限 public
3 子类使用 使用extends关键字继承抽象类 使用implements关键字实现接口
4 关系 一个抽象类可以实现若干个接口 接口不能继承抽象类,但是接口可使用extends关键字继承多个父接口
5 子类限制 一个子类只能继承一个抽象类 一个子类可以实现多个接口
  • 抽象类和接口最重要的区别
    • 1.抽象类中可以包含和普通类一样的成员变量和成员方法,但是接口当中的成员变量只能是public static final的,方法只能是public abstract的。
    • 2.一个类只能继承一个抽象类,但是能同时实现多个接口,所以解决了Java当中不能多继承的特性。

Object 类

在Java中Object类是所有类的父类。默认它们都继承了Object这个父类,所有类的引用都可以用Object的引用来进行接收。
在这里插入图片描述
通过上面的代码我们可以知道Object作为所有类的父类,可以发生向上转型。
在这里插入图片描述
我们通过ALT+7可以看到所有Object的方法,这意味着我们可以在其它类中调用它的这些方法。

Object几个比较重要的方法

1.toString方法的使用

在这里插入图片描述
在这里插入图片描述

  • Step1: 首先将person这个子类作为对象传给父类Object引用x【向上转型】
  • Step2: 然后调用valueOf这个方法,将刚刚传递的参数传入(由于obj这个引用引用的对象不一样,其表现的行为就会不一样【多态】)。
  • Step3: 判断其是否为空,如果不为空,我们通过父类引用obj调用toString方法(由于子类Person中重写了objtoString方法,则会先调用子类的,否则调用父类的【动态绑定】)

2.equals方法的使用

package demo3;

class Person{
    public String name ;

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

    @Override
    public String toString() {
        return "name:" + name;
    }
}

public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("张三");
        Person person2 = new Person("李四");
        System.out.println(person1 == person2);
    }
}

运行结果:
在这里插入图片描述
根据运行结果我们可以知道person1person2为引用数据类型变量,它们所指向的是各自的地址,所以不能直接比较。

下面是Object类中equals方法的默认实现:
在这里插入图片描述

  • 在Java中,Object类是所有类的根类,提供了一些通用的方法,其中就包括equals方法。在Object类的原始定义中,equals方法被实现为检查两个对象引用是否相同,即是否指向内存中的同一个地址。他这种比较和我们直接用==比较的效果是一样的。
    在这里插入图片描述

package demo3;
class Person{
    public String name ;
    public  Person(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return "name:" + name;
    }

}

public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("张三");
        Person person2 = new Person("张三");
        System.out.println(person1.equals(person2));
        //因为person里面没有equals方法所以会调用object的equals方法
    }
}

运行结果:
在这里插入图片描述

我们知道在Java中基本数据类型int,char,double判断是否相等是用==号,而引用数据类型(String)是用equals,但是上面代码中我们比较Person的引用里面传的参数name是否相等的时候用了equals编译器运行结果却是false,这是为什么呢?

  • 这是因为在Pesrson这个类中并没有equals这个方法,所以它只能调用默认父类类Object里面的equals方法,但是在默认类里面的equals方法是一个假的方法,因为根据上面的学习我们可以知道,这个equals方法是用来判断这两个值的地址是否相等而非用来判断他们的值是否相等。所以我们需要在子类Person中将equals方法进行重写。此外,由于我们要比较的name这个属性是Person这个类的它的类型是person,而equals方法传递的参数类型是Object类型的,所以我们需要通过向下转型将Object类型的对象转为Person类型,这样才能调用Personname属性去判断这两个值是否相等。
  • 修改后的代码及运行结果如下所示:
package demo3;
class Person{
    public String name ;

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

    @Override
    public String toString() {
        return "name:" + name;
    }

    @Override
    public boolean equals(Object obj) {
        Person tmp = (Person) obj;
        return tmp.name.equals(this.name);
    }
}

public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("张三");
        Person person2 = new Person("张三");
        System.out.println(person1.equals(person2));
        //因为person里面没有equals方法所以会调用object的equals方法
         }
}

在这里插入图片描述
思考:为啥我们下面的代码无需重写也能比较值?

        String str1 = "Aileen";
        String str2 = "Aileen";
        System.out.println(str1.equals(str2));

Java中,String类是Java标准库中的类,而上面的Person类是我们自定义的类。在Java标准库中的类已经重写了equals方法,String作为标准库中的一个类,Java开发团队已经为它重写了equals方法,这是因为在字符串的比较中,内容的相等性比引用的相等性更常用。


3.hashcode方法的使用

 public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

回忆上面的toString方法的源码里有一个hashCode()方法,如上所示,他帮我们算了一个具体对象的位置,它是一个内存地址,它通过调用Integer.toHexString()方法,将这个地址以16进制输出。
在这里插入图片描述
我们通过调用hashCode可以发现,对于person我们调用的是Object的hashCode,按照常理来说,内容一样指向的内存地址按道理来说也应该一样,但是我们打印出来的结果却是不一样的,所以我们需要在person里面重写hashCode这个方法。
在这里插入图片描述

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }

运行结果:
在这里插入图片描述
根据上面的代码,我们通过编译器自动生成重写的equals和hashCode方法,运行以后打印的地址就会变成一摸一样的,这也说明了,我们两个内容相同的值指向的是同一个地址。

](https://img-home.csdnimg.cn/images/20220524100510.png#pic_center)
](https://img-home.csdnimg.cn/images/20220524100510.png#pic_center)