8-抽象类和接口

一.抽象类

1.1 抽象类的概念

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

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

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

8-抽象类和接口_第1张图片

动物选手1号

8-抽象类和接口_第2张图片

动物选手2号

8-抽象类和接口_第3张图片

动物选手3号

8-抽象类和接口_第4张图片

潜力选手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 抽象类的特性

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

8-抽象类和接口_第5张图片

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

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

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

4.抽象类不能实例化

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

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

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

8-抽象类和接口_第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 接口的概念

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

8-抽象类和接口_第7张图片

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

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

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

2.2 接口的语法规则

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

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

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

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

2.3 接口的特性

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

8-抽象类和接口_第8张图片

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.如果实现接口的类不想全部重写接口的抽象方法,就必须定义成抽象类

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

8-抽象类和接口_第9张图片
public class Phone implements IChargeUSB{

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


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

}

下面来定义电脑

8-抽象类和接口_第10张图片
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());
}
}

输出结果

8-抽象类和接口_第11张图片

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

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

2.4 实现多个接口

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

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

8-抽象类和接口_第12张图片
8-抽象类和接口_第13张图片

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

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

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

来拿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);
}

运行结果

8-抽象类和接口_第14张图片

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

我猜你肯定会问到:

为啥还要多增添两个接口?直接把它们的功能给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的抽象方法全部重写

8-抽象类和接口_第15张图片

你肯定又要问了

为什么不直接把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{
    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;i0) {
                   Comparable ret=array[j];
                   array[j]=array[j+1];
                   array[j+1]=ret;
               }
           }
       }
    }

2.6.2 对象比较Comparator

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

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

8-抽象类和接口_第16张图片

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

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

//按照成绩排序
class scoreComparator implements Comparator {
@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

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

8-抽象类和接口_第17张图片

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

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

看图解

8-抽象类和接口_第18张图片

因为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方法

8-抽象类和接口_第19张图片

它的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;
}

用图来说话就是这样子的

8-抽象类和接口_第20张图片

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

下面是完整的代码

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类提供了很多方法供我们使用

8-抽象类和接口_第21张图片

其中的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快捷生成就行了

8-抽象类和接口_第22张图片

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

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方法的时候见过它,只是我没有提

8-抽象类和接口_第23张图片

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就好了

8-抽象类和接口_第24张图片

三.抽象类VS接口

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

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

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

抽象类

接口

成员组成

可以有各种成员

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

成员权限

可以有各种权限

只能是public权限

定义方式

abstract class

interface

被继承关键字

extends

implements

被继承个数

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

一个类可以实现多个接口

能否实例化

不能

不能

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

8-抽象类和接口_第25张图片

你可能感兴趣的:(JavaSE,java,开发语言)