8-抽象类和接口

一.抽象类

1.1 抽象类的概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的, 如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

比如,动物类都有自己的name,有age,都会eat,但是根据这些成员我们能判断出动物是什么吗?

是一条狗?一只猫?还是一条鱼?又或者四不像?

动物选手1号

动物选手2号

动物选手3号

潜力选手4号

我们先给动物类定义一些属性和行为

class Animal {
//定义成员变量
String name;
int age;

//定义吃饭行为
public void eat() {
System. out.println(this.name+"在吃饭!");
}
}

再来定义继承动物的狗类和猫类

class Dog extends Animal {
public void eat() {
System. out.println(this.name+"在啃骨头!");
}
}

class Cat extends Animal {
public void eat() {
System. out.println(this.name+"在摸鱼!");
}
}

观察上面的代码就会发现,猫类和狗类都把eat方法重写了,说明Animal类里面的eat方法不需要具体的实现代码,可以改成这样

class Animal {
String name;
int age;

public void eat() {
}
}

为了让代码更简洁,可不可以不要'{}',而是直接在这个方法的后面加上' ; '呢

实际上是可以的,但是需要借助abstract关键字,表示这是一个抽象方法

public abstract void eat() ;

像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类

1.2 抽象类的特性

下面的内容会很多,各位看官一定要耐心!

1.抽象类和抽象方法前必须要有abstract修饰

2.抽象类中可以有普通成员变量或方法

3.抽象类可以没有抽象方法,有抽象方法的类一定是抽象类

4.抽象类不能实例化

5.如果一个普通类继承了抽象类,这个普通类就要重写抽象类中的所有未被重写的抽象方法

6.如果一个类不想全部重写抽象类的抽象方法,这个类也要被定义成抽象类

如果觉得有点绕,不防看看下图图解

先是定义了一个抽象类Animal,类体里面有抽象方法eat和sleep

定义了Fish类来继承Animal,但是这个类只是重写了sleep方法,所以Fish也必须是抽象类

定义了GoldFish类继承鱼类,如果想让GoldFish实例化出一个金鱼,就必须把它定义成普通类,所以它必须重写未被重写的抽象方法eat

总而言之,所有被普通类继承的抽象方法至少要被重写一次

你以为这就完了?其实还有两条规则😆

7.抽象类方法要满足被重写的要求(不能被private,static,final修饰,不能是构造方法)

8.抽象类中可以存在构造方法,在子类实例化时帮助父类成员初始化

为了便于更好的理解上面的规则,我们上代码看一下

1.3 抽象类举例

拿我们的固定嘉宾Animal举例吧

下面是Animal类的定义

abstract class Animal {
private String name;
private int age;

//定义Animal类的构造方法
Animal(String name,int age) {
this.name=name;
this.age=age;
}

//定义抽象方法eat和play
public abstract void eat();
public abstract void play();
}

接下来定义一直陪伴我们的朋友---

class Dog extends Animal {

Dog(String name,int age) {
super(name,age);
}

@Override
public void eat() {
System. out.println("汪汪汪~要啃骨头");
}

@Override
public void play() {
System. out.println("汪汪汪~在拿耗子");
}
}
class Cat extends Animal {

Cat(String name, int age) {
super(name, age);
}

@Override
public void eat() {
System. out.println("喵喵喵~在吃鱼");
}

@Override
public void play() {
System. out.println("喵喵喵~在玩毛球");
}
}

结合前面学过的继承和多态,来看看下面的test代码会运行出什么结果吧

public class Test {
public static void func(Animal animal) {
animal.eat();
animal.play();
}
public static void main(String[] args) {
func(new Dog("小白",2));
System. out.println("************************");
func(new Cat("小黑",1));
}
}

运行结果

看到这里,是不是还有疑问?既然普通类可以被继承,也能被重写,为啥非要把Animal定义成抽象类捏?不急,接着往下翻

1.4 抽象类的作用

因为抽象类并不能完整地描述一个实体,对比普通类,抽象类本身不能被实例化,只能创建子类,让子类实例化出对象。可以说抽象类存在的最大意义就是被继承。

所以,定义一个抽象类相当于多了一层编译器的校验。

很多功能不应该由父类去完成,而是应该让子类自己定义,在这种情况下,如果我们不小心new了一个抽象父类,编译器不会袖手旁观。普通父类并没有这个特权。

很多语法存在的意义都是为了 "预防出错", 例如我们曾经用过的 final 也是类似. 创建的变量不允许用户去修改,但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.

二.接口

2.1 接口的概念

在现实生活中,接口的例子比比皆是,比如下面这个插排

如果你拿着一个两头的充电器,就不能使用这个插排

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

在Java中,接口可以看成是多个类的公共规范

2.2 接口的语法规则

把class换成interface,就可以定义一个接口

public interface 接口名称{
抽象方法
}

接口的定义一般有以下规则(不强制)

1.接口名称前面一般加上'I'修饰
2.接口中的属性和方法一般不加修饰符,保持代码简洁性

2.3 接口的特性

规则也有很多,请冲一杯咖啡慢慢理解

1.接口内的方法都是抽象方法,除了被defult修饰的方法(JDK1.8后引入的)和静态方法

2.接口内的方法默认被public abstract修饰

3.接口内的成员变量都默认被public static final修饰,在定义时要初始化

4.接口不能new出对象

5.接口不能有代码块和构造方法

比如,定义一个插排的充电接口

public interface IChargeUSB {

int capacity=10;//定义一个成员表示容量
private String color="red";//error,不允许有除了public,static,final的其他修饰符

static {
capacity=100;
}//error,接口内部不允许有代码块


//定义一个可以实现的方法
//方法前面不允许添加除了public,abstract以外的修饰符
default void open() {
System. out.println("已经连通,可以充电了!");
}

//定义一个抽象方法
void close();

//定义一个静态方法获取容量
static int getCapacity() {
return capacity;
}
}

既然有了充电接口这个标准,想要使用它就必须与类联系到一起,看看语法是怎么规定的吧

6.一个类想要实现一个接口,需要用implements关联

7.普通类实现了一个接口,就必须重写接口内所有的抽象方法

8.接口内被default修饰的方法可重写可不重写,被static修饰的方法不能被重写

9.如果实现接口的类不想全部重写接口的抽象方法,就必须定义成抽象类

来定义你手上的手机或者面前的电脑实现这个接口吧

public class Phone implements IChargeUSB{

@Override
public void open() {
System. out.println("您的手机已充上电!");
}//被default修饰的方法,可重写可不重写


@Override
public void close() {
System. out.println("您的手机已断开充电线!");
}//抽象方法,必须重写

}

下面来定义电脑

public class Computer implements IChargeUSB{

@Override
public void close() {
System. out.println("您的电脑已断开电源");
}
}

接口和抽象类一样,都可以通过引用子类对象向上转型,从而实现多态,来体会一下吧

public class Test {

public static void func(IChargeUSB usb) {
usb.open();
usb.close();
}

public static void main(String[] args) {
System. out.println("插排容量为:"+IChargeUSB. getCapacity());
func(new Phone());
System. out.println("*********************");
func(new Computer());
}
}

输出结果

别看接口不能和类一样实例化,每个接口也都有自己对应的字节码文件

建议每个java文件里只放一个接口或者一个类

2.4 实现多个接口

在继承和多态里面提到,Java中是没有多继承的,但是现实生活中一个类仅仅继承一个父类是不够的

比如Animal类,只能给它定义全部动物的共同属性---name,age,只能给它定义全部动物的共同行为---eat,sleep

但是狗和猫除了会吃饭睡觉,还会撒娇,会捉老鼠,他们还有很多共同的功能...

实现多个接口就是为了解决多继承的问题

一个类只能继承一个基类,可以实现多个接口

来拿Animal举例

abstract public class Animal {
//定义成员变量
private String name;
private int age;

//定义抽象方法
public abstract void eat();
public abstract void sleep();

//定义构造方法
Animal(String name,int age) {
this.name=name;
this.age=age;
}

//定义public方法获取成员变量
public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

然后我们再定义狗类和猫类的共同功能

//解锁捉耗子功能
public interface ICatch {
void catchMouse();
}
//解锁玩耍功能
public interface IPlay {
void play();
}

这样就可以完善狗和猫了

注意:必须先继承类才能实现接口,也就是说extends必须放在implements的前面

//狗类继承了Animal抽象类,并且实现了ICatch和IPlay接口
public class Dog extends Animal implements ICatch,IPlay{
//帮助完成父类成员初始化
Dog(String name, int age) {
super(name, age);
}

@Override
public void eat() {
System. out.println(getName()+"正在吃狗粮");
}

@Override
public void sleep() {
System. out.println("汪汪汪~"+getName()+"在睡觉");
}

@Override
public void catchMouse() {
System. out.println("汪汪汪~"+getName()+"在拿耗子");
}

@Override
public void play() {
System. out.println("汪汪汪~"+getName()+"在接飞盘");
}
}

public class Cat extends Animal implements ICatch,IPlay{

//帮助完成父类成员初始化
Cat(String name, int age) {
super(name, age);
}

@Override
public void eat() {
System. out.println(getName()+"在吃鱼");
}

@Override
public void sleep() {
System. out.println("喵喵喵~"+getName()+"在晒太阳睡大觉");
}

@Override
public void catchMouse() {
System. out.println("喵喵喵~"+getName()+"在看旺财拿耗子");
}

@Override
public void play() {
System. out.println("喵喵喵~"+getName()+"在玩毛球");
}
}

如果我们想要实现动物的功能或者获取动物的属性,可以接收Animal类的引用,如果想要实现ICatch和IPlay的功能,可以接收ICatch和IPlay的引用

public static void funcAnimal(Animal animal) {
System. out.println("Hello,I'm"+animal.getName()+"我今年"+animal.getAge()+"岁了");
animal.eat();
animal.sleep();
}
public static void funcICatch(ICatch icatch) {
icatch.catchMouse();
}
public static void funcIPlay(IPlay iplay) {
iplay.play();
}

现在到了收获结果的时候了

public static void main(String[] args) {
Dog dog=new Dog("旺财",2);
funcAnimal(dog);
Cat cat=new Cat("小白",1);
funcICatch(cat);
funcIPlay(cat);
}

运行结果

不论是抽象类还是接口,都可以通过向上转型实现多态

我猜你肯定会问到:

为啥还要多增添两个接口?直接把它们的功能给Animal类然后继承不就完事嘛!
答曰:不是所有的动物都会多管闲事去拿耗子,博主就对耗子不感兴趣

使用接口可以让我们忽略类,只要某个对象有这个接口,它就可以实现这个接口的方法

比如再定义一个模型火车

public class Toy implements IPlay{
@Override
public void play() {
System. out.println("呜呜~我会沿着轨道跑");
}
}

这时候只要调用funcIPlay方法,这个火车也可以用来逗隔壁邻居家的傻儿子玩

public static void main(String[] args) {
funcIPlay(new Toy());
}

2.5 接口之间的继承

一个接口可以继承多个接口,从而实现这个接口功能的扩展

接口之间的继承要用到关键字extends,语法规则如下

interface A extends B,C {
}

上面的代码让接口A继承了接口B和接口C,如果有某个类想要实现接口A,就要把接口B和接口C的抽象方法全部重写

你肯定又要问了

为什么不直接把testA,B,C三个功能直接给接口C?还在这里脱裤子放屁
答曰:通过这种接口间相互继承的方式,可以实现代码的低耦合。如果更改了C接口,实现A接口和B接口的类不会受到影响

2.6 常用的接口

下面的接口全部是针对自定义类型

2.6.1 数组排序Comparable

先定义一个学生类

class Students {
//定义学生类成员变量,姓名,身高,成绩
String name;
int height;
double score;

//定义构造方法
public Students(String name, int height, double score) {
this.name = name;
this.height = height;
this.score = score;
}
}

现在全班有20个学生,他们班主任面前有个两难的问题,究竟是按身高还是按成绩给学生排座呢?

先不管班主任的问题,你有没有想过这些学生能排序吗?

对于类型元素是基本数据类型的数组,可以用Arrays.sort()实现排序

public static void main(String[] args) {
int[] array={2,5,9,3,0};
Arrays. sort(array);
System. out.println(Arrays. toString(array));
}
输出结果
[0, 2, 3, 5, 9]

对于一个学生数组,Arrays还能帮助排序吗?

public class Test {
public static void main(String[] args) {
Students[] st=new Students[3];
st[0]=new Students("喜羊羊",125,98);
st[1]=new Students("美羊羊",115,80);
st[2]=new Students("懒羊羊",120,60);
Arrays. sort(st);
for(Students e:st) {
System. out.println(e);
}
}
}

运行结果就是:

点开异常,就会发现sort其实是把数组元素转换成了Comparable接口,并且调用了这个接口的compareTo方法,所以只要学生类实现了这个接口并且重写了compareTo方法,就可以实现排序

所以重新定义一下学生类,让它可以按照身高排序

我们先使用这种规则重写compareTo方法

如果自己的身高大于要对比对象的身高,返回一个大于0的整形数字

//实现Comparable接口
class Students implements Comparable<Students>{
    String name;
    int height;
    double score;
    public Students(String name, int height, double score) {
        this.name = name;
        this.height = height;
        this.score = score;
    }

//重写toString方法
    @Override
    public String toString() {
        return "Students{" +
                "name='" + name + '\'' +
                ", height=" + height +
                ", score=" + score +
                '}';
    }

//重写compareTo方法    
    @Override
    public int compareTo(Students o) {
        return this.height-o.height;//此处如果按照成绩排序,需要将结果强转成int
                                  //(int)(this.score-o.score)
    }
}

来看一下运行结果

从运行结果也可以看出,上面的运行规则可以让学生按照身高的升序排列,也可以推断出compareTo的使用方式

1.如果当前对象应排在参数对象之前, 返回小于 0 的数字;
2.如果当前对象应排在参数对象之后, 返回大于 0 的数字;
2.如果当前对象和参数对象不分先后, 返回 0;

为了更好地理解Comparable接口,来用冒泡排序实现一下sort过程

public static void main(Comparable[] array) {
       for(int i=0;i<array.length-1;i++) {
           for(int j=0;j<array.length-i-1;j++) {
               if(array[j].compareTo(array[j+1])>0) {
                   Comparable ret=array[j];
                   array[j]=array[j+1];
                   array[j+1]=ret;
               }
           }
       }
    }

2.6.2 对象比较Comparator

前面的Comparable接口可以让我们实现排序,但也固定了学生排序的方式只能是按照身高,如果慢羊羊村长在上课的时候不想看见打鼾的懒羊羊,就要按照成绩进行排序

如果想实现一个类的多种排序方式,要用到Comparator接口

这时候就可以定义两个类分别实现compare方法

//按照身高排序
class heightComparator implements Comparator<Students> {
@Override
public int compare(Students o1, Students o2) {
return o1.height-o2.height;
}
}

//按照成绩排序
class scoreComparator implements Comparator<Students> {
@Override
public int compare(Students o1, Students o2) {
return (int)(o1.score-o2.score);
}
}

接下来分别按照身高和成绩比较美羊羊和懒羊羊

public class Test {
    public static void main(String[] args) {
        Students st1=new Students("懒羊羊",120,60);
        Students st2=new Students("美羊羊",115,80);
        heightComparator hc=new heightComparator();

        //定义身高比较器
        int ret=hc.compare(st1,st2);
        System.out.print("按照身高排序:");
        if(ret>0) {
            System.out.println(st1.name+">"+st2.name);
        } else if(ret<0) {
            System.out.println(st1.name+"<"+st2.name);
        } else System.out.println(st1.name+"="+st2.name);

        //定义分数比较器
        scoreComparator sc=new scoreComparator();
        ret=sc.compare(st1,st2);
        System.out.print("按照成绩排序:");
        if(ret>0) {
            System.out.println(st1.name+">"+st2.name);
        } else if(ret<0) {
            System.out.println(st1.name+"<"+st2.name);
        } else System.out.println(st1.name+"="+st2.name);
    }
}

2.6.3 对象克隆Cloneable

现在有一个手机对象

public class Phone {
//手机的成员变量
String color;
String brand;
Detail dt;

//手机的构造方法
public Phone(String color, String brand, Detail video) {
this.color = color;
this.brand = brand;
this.dt = video;
}
}

class Detail {
//手机大小
int size=10;
//手机重量
int height=10;
}

这部手机是属于你妈妈的,你正好想换新手机了,觉着妈妈的手机用着不错,想要克隆一部出来

这时候就要使用clone方法

public static void main(String[] args) {
Phone mPhone=new Phone("white","huawei",new Detail());
Phone myPhone=mPhone.clone();
}

上面这串代码是不能使用的,想要使用clone方法就要先了解它的规则

可以看到,clone是Object类下的一个方法,会返回一个Object对象(Object是所有类的祖先,即使这个类没有显式定义继承了Object),这个方法由protected限制,可以被不同包的子类访问,native表示它的底层是由C/C++实现的,后面的抛异常以后再详细解释(目前我也不知道它的语法)

所以我们的目的就明确了,重写clone方法

public class Phone {
    String color;
    String brand;
    Detail dt;

    public Phone(String color, String brand, Detail video) {
        this.color = color;
        this.brand = brand;
        this.dt = video;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Detail {
    int size=10;
    int height=10;
}
class test {
    //仔细看main后面的内容,这是处理异常的方式
    public static void main(String[] args) throws CloneNotSupportedException {
      Phone mPhone=new Phone("white","huawei",new Detail());
      
      Phone myPhone=(Phone)mPhone.clone();//返回类型是Object,如果使用Phone接收要向下转型
    }
}

运行一下发现程序抛出了异常

翻译过来就是不支持克隆,解决方法:在克隆的类后面实现Cloneable接口

public class Phone implements Cloneable

实际上这个接口啥也没有实现...

Cloneable接口只是一个标志接口,表示这个类型的对象可以被克隆

所以你拥有了妈妈同款手机

public static void main(String[] args) throws CloneNotSupportedException {
Phone mPhone=new Phone("white","huawei",new Detail());
Phone myPhone=(Phone)mPhone.clone();
System. out.println("妈妈的手机:"+mPhone);
System. out.println("我的新手机:"+myPhone);//Phone和Detail类要先实现toString的重写,
//下文会给出完整代码
}

你又发现自己的idol王一博换了一个带有摩托图案的手机壳,既然买不起他的手机,就买同款手机壳吧,于是你手机的height属性变重了

myPhone.dt.height=11;
System. out.println("妈妈的手机:"+ mPhone.dt);
System. out.println("我的手机:"+myPhone.dt);

你妈妈奇怪的发现,诶,我手机咋也变沉了?

看图解

因为dt成员是个引用变量,所以它存储的是对象的地址,clone的时候只是把地址拷贝过去了,所以你的手机和妈妈的手机捆绑在一块了,这显然是不合理的

要进行深拷贝,就是把所有的对象都拷贝一遍!

//让手机里的Detail类型的成员进行拷贝,就是让Detail类重写clone方法
class Detail implements Cloneable{
    int size=10;
    int height=10;
    @Override
    public String toString() {
        return "Detail{" +
                "size=" + size +
                ", height=" + height +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

这也是不管用的,问题出在了哪里呢?

看Phone类的clone方法

它的clone方法只是调用了父类的clone,没有做其他的改变

应该改成下面这样

protected Object clone() throws CloneNotSupportedException {
Phone tmp=(Phone)super.clone();//使用Phone变量接收this对象通过父类克隆的对象
tmp.dt=(Detail)this.dt.clone();//拷贝dt并更改tmp指向的dt对象
return tmp;
}

用图来说话就是这样子的

我们再来打印输出刚才的结果

下面是完整的代码

public class Phone implements Cloneable{
    String color;
    String brand;
    Detail dt;

    public Phone(String color, String brand, Detail video) {
        this.color = color;
        this.brand = brand;
        this.dt = video;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "color='" + color + '\'' +
                ", brand='" + brand + '\'' +
                ", dt=" + dt +
                '}';
    }


    @Override
    protected Object clone() throws CloneNotSupportedException {
        Phone tmp=(Phone)super.clone();
        tmp.dt=(Detail)this.dt.clone();
        return tmp;
    }
}
class Detail implements Cloneable{
    int size=10;
    int height=10;
    @Override
    public String toString() {
        return "Detail{" +
                "size=" + size +
                ", height=" + height +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class test {
    public static void main(String[] args) throws CloneNotSupportedException {
      Phone mPhone=new Phone("white","huawei",new Detail());
      Phone myPhone=(Phone)mPhone.clone();
      System.out.println("妈妈的手机:"+mPhone);
      System.out.println("我的新手机:"+myPhone);
      myPhone.dt.height=11;
      System.out.println("妈妈的手机:"+ mPhone.dt);
      System.out.println("我的手机:"+myPhone.dt);
    }
}

2.7 Object类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。
所有类的对象都可以使用Object的引用进行接收。

也就是说,如果一个类没有显式继承某个基类,它也会默认继承Object类,如果它继承了某个基类,因为这个基类也是Object的子类,所以这个基类的子类多层继承了Object类

Object类提供了很多方法供我们使用

其中的toString和clone已经讲述过,接下来看getClass,hashCode和equals方法(其余的以后会提到)

2.7.1 getClass方法的使用

这个方法就是用来告知类的信息,也不能被重写,没有其他的功能

来看看它的使用

public static void main(String[] args) throws CloneNotSupportedException {
Phone mPhone=new Phone("white","huawei",new Detail());
Class<?> c=mPhone.getClass();
System. out.println(c);
}

输出结果

2.7.2 equals方法

在Java中,如果使用==判断两个对象,实际上比较的使它们的地址

public static void main(String[] args) throws CloneNotSupportedException {
Phone mPhone=new Phone("white","huawei",new Detail());
//克隆妈妈的手机
Phone myPhone=(Phone)mPhone.clone();

System. out.println(mPhone);//此时Phone类还未定义toString方法
System. out.println(myPhone);

System. out.println(myPhone==mPhone);
}

运行结果

但是我的手机在换手机壳之前是克隆的妈妈的手机呀,想更改默认的比较逻辑,让它们相等怎么办?

这时候要在类内部重写equals方法

 @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Phone phone = (Phone) o;
        return Objects.equals(color, phone.color) && Objects.equals(brand, phone.brand) && Objects.equals(dt, phone.dt);
    }

当然了,在自定义类型Detail里也要实现equals的重写,String类有自己的equals方法,调用就管

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Detail detail = (Detail) o;
        return size == detail.size && height == detail.height;
    }

你以为这些方法逻辑那么强,还要判断形参是不是null类型,博主考虑地也太周到了吧(嘿嘿,虽然最后一句话是我杜撰的,这个方法也不是我写的,就自夸一下下吧)

用idea快捷生成就行了

这样我们就可以看看根据我们的逻辑,两个手机是否相同了

public static void main(String[] args) throws CloneNotSupportedException {
      Phone mPhone=new Phone("white","huawei",new Detail());
      Phone myPhone=(Phone)mPhone.clone();
      System.out.println(myPhone.equals(mPhone));
}

2.7.3 hashCode方法

实际上我们在重写toString方法的时候见过它,只是我没有提

hashCode方法会返回这个对象的地址

class test {
public static void main(String[] args) throws CloneNotSupportedException {
Phone mPhone=new Phone("white","huawei",new Detail());
Phone myPhone=(Phone)mPhone.clone();
System. out.println(mPhone.hashCode());
System. out.println(myPhone.hashCode());
}
}
运行结果
460141958
1163157884

如果我们想让这两部手机的hash值一样,也可以重写hashCode方法

像生成equals方法一样用idea生成就行了(所有类型都要生成),然后你就会得到下面的代码

Phone类

@Override
public int hashCode() {
return Objects. hash(color, brand, dt);
}

Detail类

@Override
public int hashCode() {
return Objects. hash(size, height);
}

这时候再打印两部手机的hashCode值,就会发现他们是一样的

至于为啥非得重写它们的hashCode方法

事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

建议所有自定义类型都把equals和hashCode方法重写一遍,反正用idea就好了

三.抽象类VS接口

下面的部分实际上是前面内容的简单总结

有些类不能完整地描绘出一个实体,就需要用到抽象类

只要有共同的功能,就可以实现同一个接口,接口可以打破类的限制

抽象类

接口

成员组成

可以有各种成员

只能有public static final修饰的成员变量,只能有static或default修饰的方法或抽象方法

成员权限

可以有各种权限

只能是public权限

定义方式

abstract class

interface

被继承关键字

extends

implements

被继承个数

一个类只能继承一个抽象类

一个类可以实现多个接口

能否实例化

不能

不能

总而言之,接口是比抽象类还抽象的类型...over!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不 会敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值