目录
一. 抽象类
上述代码: 在Dog中重写了Animal中的eat(), 在Bird中重写了Animal中的eat()
而我们从来没有用到过Animal中的eat(), 因为Animal不指代任何一种动物, 所以eat()无意义.
那么我们是否可以将Animal中的eat()方法简化呢?下面就引出了我们的抽象方法和抽象类
1.1 抽象类概念
如果 一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类 。
例如:
例如上述代码中的Animal中的eat(), 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类Animal我们称为 抽象类(abstract class).
1.2 抽象类语法
在 Java 中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
例如上述代码,可修改为:
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
1.3 抽象类特性
1. 抽象类不能直接实例化对象
2. 抽象方法不能是 private 的
3. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
4. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
5. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
6. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
1.4 抽象类的作用
抽象类本身不能被实例化 , 要想使用 , 只能创建该抽象类的子类 . 然后让子类重写抽象类中的抽象方法. 使用抽象类相当于多了一重编译器的校验 .
使用抽象类的场景就如上面的代码 , 实际工作不应该由父类完成 , 而应由子类完成 . 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的 . 但是父类是抽象类就会在实例化的时候提示错误 , 让我们尽早发现问题 .
二. 接口
2.1 接口的概念
在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。
电脑的USB口上,可以插:U盘、鼠标、键盘...所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲...所有符合规范的设备
通过上述例子可以看出:
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用 。
在 Java 中,接口可以看成是:多个类的公共规范,是一种 引用数据类型。
2.2 语法规则
1. 接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface 关键字,就定义了一个接口。
提示 :
1. 创建接口时 , 接口的命名一般以大写字母 I 开头 .
2. 接口的命名一般使用 " 形容词 " 词性的单词 .
2. 接口中的方法如果没有被实现, 那么默认为一个抽象方法
3. 接口当中的定义的方法, 默认都是public abstract修饰的(只能是public abstract,其他修饰符都会报错), 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现 .
4. 如果想要有具体的实现, 必须由default或static修饰
5. 接口当中定义的成员变量, 默认是public static final修饰的
6. 接口类型是一种引用类型,但是不能直接new接口的对象
7. 接口中不能有静态代码块和构造方法
8. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是 .class
9. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
2.3 接口的使用
接口不能直接使用,必须要有一个 " 实现类 " 来 " 实现 " 该接口,实现接口中的所有抽象方法。
需要使用implements来关联
注: 一个类只能继承一个普通类/抽象类, 但是可以实现多个接口(有什么特征就实现什么接口)
接下来我们举个例子:
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .
鸟是一种动物 , 具有会跑,会飞的特性 .
狗也是一种动物 , 既能跑 , 也能游泳
鸭子也是一种动物 , 既能跑 , 也能游 , 还能飞
这样设计有什么好处呢 ? 时刻牢记多态的好处 , 让程序猿 忘记类型 . 有了接口之后 , 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.甚至参数可以不是 " 动物 ", 只要会跑 !
只要该对象具有这种性质, 就可以实现对应的接口.
2.4 接口间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口 , 达到复用的效果 . 使用 extends 关键字 .
接口间的继承相当于把多个接口合并在一起.
2.5 抽象类和接口的区别
抽象类和接口都是 Java 中多态的常见使用方式 . 都需要重点掌握 . 同时又要认清两者的区别.
核心区别 :
1. 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写),
而接口中不能包含普通方法, 子类必须重写所有的抽象方法
2. 一个类只能继承一个抽象类, 但是可以同时实现多个接口, 解决了java中不能多继承的特性
如之前写的 Animal 例子 . 此处的 Animal 中包含一个 name 这样的属性 , 这个属性在任何子类中都是存在的 . 因此此处的 Animal 只能作为一个抽象类 , 而不应该成为一个接口
三. Object类
Object 是 Java 默认提供的一个类。 Java 里面除了 Object 类,所有的类都是存在继承关系的。 所有类默认会继承Object父类。 即所有类的对象都可以使用Object 的引用进行接收。
例如:在传参时, 我们可以用Object接收
所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法,在子类中可以进行使用。
对于整个 Object 类中的方法需要实现全部掌握。
本篇博客当中,我们主要来熟悉这几个方法: toString() 方法, equals() 方法, hashcode() 方法
3.1 toString() 方法
看下述代码:
打印结果为:
所以:
我们直接拿来用的这一行代码 System.out.println(son); ,实际上是发生了向上转型, 通过父类也就是Object类中的toString方法实现的.
那么如果我们在子类中自己定义一个toString方法,是否可以发生重写呢?
此时输出的结果为:
答案是肯定的, 即在子类中自己定义一个toString方法,可以重写Object中的toString方法.
3.2 对象比较equals()方法
在 Java 中, == 进行比较时:
a. 如果 == 左右两侧是基本类型变量,比较的是变量中值是否相同
b. 如果 == 左右两侧是引用类型变量,比较的是引用变量地址是否相同
c. 如果要比较对象中内容,必须重写 Object 中的 equals 方法,因为 equals 方法默认也是按照地址比较的:
例如:
Person类重写equals方法后,然后比较:
结论:比较对象中内容是否相同的时候,一定要重写equals方法
3.3 hashcode方法
该方法是一个 native 方法,底层是由 C/C++ 代码写的。我们看不到。
我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写 hashcode() 方法,我们可以来看示例代码:
两个对象的 hash 值不一样。
像重写 equals 方法一样,我们也可以重写 hashcode() 方法。此时我们再来看看。
哈希值一样
结论:
1 、 hashcode 方法用来确定对象在内存中存储的位置是否相同
2 、事实上 hashCode() 在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。