JAVA类的继承与多态
包含的内容不全面,前面一些简单的继承语法就跳过了,主要记录了一些不懂的地方,参考书籍:《JAVA核心技术 卷Ⅰ》
1. 正确理解多态
一个对象变量可以指示多种实际类型的现象称为多态(polymorphism),在运行时能够自动地选择适当的方法,称为动态绑定(dynamic binding)。
多态有三要素:
- 要有继承关系
- 要有子类重写父类成员方法
- 要有父类数据类型引用子类
多态后父类数据类型不能使用子类特有的属性和方法。
举个栗子,Manager
类是 employee
类的一个子类,重写了 getSalary
方法,于是作为 employee
类型的 staff[0]
调用 getSalary
方法就是多态的一个表现,但是 staff[0]
不能调用 Manager
类特有的方法。
1 | public class helloworld { |
关于覆盖(重写)值得注意的是,子类方法不能低于超类方法的可见性!
比如超类方法是public,子类方法必须也要声明为public。
2. 强制类型转换
每一个对象变量都有一个类型,当一个值存入变量时,编译器将检查你是否承诺过多。
如果将一个子类的引用赋值给一个超类变量,编译器时允许的(比如上文多态的栗子),但是将一个超类的引用赋值给一个子类变量时,就承诺过多了,必须进行强制类型转换。
有两点需要注意:
- 只能在继承层次内进行强制类型转换
- 强制类型转换可能会失败,因此在进行强制类型转换前需要使用
instanceof
操作符进行检测
1 | public class helloworld { |
3. final类和方法
有时候,我们不希望某个类定义子类,这种不允许扩展的类称为final类,我们只需要在定义类时使用final修饰符即可。
同样的,如果不希望类中的方法被重写,我们也可以在定义方法时使用final修饰符,但是这样的话,就会破坏多态性(当然许多程序员觉得用不到多态性)。
4. 抽象类
一般来说,位于上层的类更具有一般性,可能更加抽象,从某种角度看,祖先类更具有一般性,人们一般只将它作为派生其他类的基类,而不是用来构造想要的特定实例,这就是抽象类!
比如说我们为人建立一个抽象类 Person
,我们将一些共有属性就放在高层次抽象类中,比如姓名 name
;然后我们再增加一个 getDescription
方法,用于获得一个人的描述,但是考虑到具体的子类需要采用不同的描述形式,我们可以不用定义这个方法,让子类重写该方法,这时就需要使用 abstract
关键词。
1 | public abstract class Person { |
并且,包含一个即以上的抽象方法的类本身必须被声明为 abstract
然后我们就能继承该抽象类定义子类了(注意,必须重载所有的 abstract
方法,否则子类仍是抽象的!)
1 | public class Employee extends Person { // 员工类 |
1 | public class Student extends Person{ // 学生类 |
这样我们就基于抽象类定义了两个具体的子类!
抽象类不能创建对象实例,但是我们可以定义抽象类变量引用非抽象子类的对象,比如这样:
1 | public class helloworld { |
5. 所有类的超类:Object
Object类是Java所有类的超类
5.1 equals方法
Object类中的equals方法用于检测一个对象与是否等于另一个对象,其实现的方法是确定两个对象的引用是否相等。但是我们经常需要基于状态检测对象的相等性,如果两个对象状态相同,就认为他们是相等的。
因此我们就需要重写由Object继承下来的equals方法。不过在此之前,我要需要区分 ==
与 euquals
的区别。
- 对于基本类型(char,boolean,byte,short,int,long,float,double)来说,它不是对象,不能使用
equals
方法,其存储在常量池里,使用==
进行判断时时根据其值进行判断的。 - 对于一般对象而言,其存储在堆中,当使用
==
进行判断时,是判断其在内存堆地址是否相等,而当使用equals
进行判断时,将调用类中定义的equals
方法来判断,如果类中没有重写equals
方法,将会调用其超类的equals
方法,对于所有类型的超类:Object,其equals
方法就是==
,下面是其源码。(一些预定义类,比如String,已经实现好了基于状态的equals
方法)
- 对于基本类型的包装类,它可能存储在堆中,也可能存储在常量池中,是由其大小决定的,以
Integer
为例,其在常量池的范围是[-128,127],超出这个范围就存储在堆中,我们看下面这个栗子就非常的清楚。
1 | public class helloworld { |
理解了 ==
与 equals
的区别之后,我们就可以给我们的类重写 equals
方法了,主要是下面这样一个思路(已一个Employee类为例)。
1 | ... |
我们简单测试一下:
1 | public class helloworld { |
大功告成~
5.2 hashCode方法
获取散列码的方法
hashCode方法定义在Object中,其值是由其存储地址得出的,而我们通常需要的散列值是希望基于其内容导出的,所有我们在创建类时需要重写它。
在一些预定义类中已经重写好了hashCode方法,比如String类:
1 | int hash = 0; |
那对于我们自己定义的类呢?
我们的自定义的类一般都是由基本数据类型以及一些预定义类组成的,于是我们只需要将他们的hashCode组合起来即可。
直接看如下代码,还是以Employee类为例:
1 | ... |
然后试试我们实现的hashCode
1 | public class helloworld { |
great!
5.3 toString方法
Object中还有一个非常重要的方法:toString
,如下是其默认方法:
1 | public String toString() { |
当我们想打印一个LocalDate类时,我们很容易就写出如下代码:
1 | LocalDate now = LocalDate.now(); |
我们知道只有字符串String在能简单的进行 “+” 操作,但为什么LocalDate也可以?
因为只要一个对象通过操作符 “+” 和字符串连接起来时,Java编译器就会自动的调用其 toString
方法,使用起来就非常的方便。
对于这些预定义类,已经重写好了 toString
方法,对于我们自定的类就需要我们自己重写了,绝大多数的 toString
方法都遵循着这样的格式:类的名字,随后时一对方括号括起来的字段值。
上代码,还是以Employee类为例:
1 | ... |
试试看~
1 | public class helloworld { |
输出结果:
1 | class com.panfeng.Employee[name=Bob,salary=2000.0] |
不错不错~