Java关于继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
关于继承我们先看看高手们是怎么理解的吧。
“面向对象的程序设计扩展了基于对象的程序设计,可以提供类型/子类型的关系。这是通过一种被称为继承(inheritance)的机制而获得的。子类不需要重新实现所有的特征,而是继承了其父类的数据成员和成员方法。Java通过一种被称为类派生的机制来支持继承。被继承的类称为基类(base class)或父类,而新的类被称为派生类(derived class)或子类。一般把基类和派生类实例的集合称作类继承层次结构。“
——上文摘自《Java开发技术大全》(清华大学出版社) 刘新 等编著
上文可以看到“基类”、“派生类"等关键名词,也基本讲清楚了基类与派生类之间的基本关系——
基类→派生→派生类,或者,派生类→继承于→基类。
extends 的意思是”拓展“。子类是父类的拓展。
类的继承关系:B属于A
面向对象程序攻计强调更“贴近人类思维习惯和模式”的思想。比如:学生→大学生→本科生。他们之间存在着“从属关系”。
从“外延"和"内涵"的角度考察可以发现:→左边的概念在外延上更大,内涵却相对简单;→右边的概念在内涵上更丰富,外延却相对更小。“本科生属于大学生”是两者最本质的关系。
“学生”是“大学生"的父类、基类;“大学生”是"学生”的子类、派生类;“大学生"是“本科生”的父类;“本科生”是“大学生”的子类。更细致地说,“大学生”具有“本科生”的所有属性和行为。
如果在建立了描述“大学生”的类之后可以发现,属于“本科生”的众多成员和方法都已经在“大学生"类中存在了,“本科生”类可以直接从“大学生"类中继承这些成员和方法,而不必重新编码。
这就是“代码复用”的直接体现,同时也是继承机制的终极目的之一!
子类会自动拥有父类部分或全部的属性和方法,同时可以继续定义子类自己的属性和方法,使得子类信息更详细、功能更明确。
继承中权限修饰符总结:
public和 private相对简单,是两个极端:从”保护“功能讲,public最弱,private最强;
protected 修饰的成员和方法,无论是否在同一个包下,子类都可以继承;且,同一包下的类,可以通过其对象引用。
无修饰的成员和方法,在同一包下的子类可以继承,不在同一包下的子类不能继承;且,不在同一包下的其非派生类,不能引用。
使用技巧:
**1、凡是打算为子类继承的成员和方法,用protected修饰;**
2、不打算被包外的类引用的成员和方法,不写任何修饰符;
无修饰的成员和方法,是为了打包后(*.jar文件)不被外面的类所引用。
关于super()
无论子类执行的构造方法是无参还是带参的,在默认情况下,JVM只调基类的无参构造方法!
那么,什么不是“默认情况”呢?
对了,就是用super (…)非常明确指明父类的具体哪一种构造方法!与 this()一样,super()也有两个严格的要求:
1、super只能出现在子类的方法或者构造方法中;
2、如果super()调用父类的构造方法,那它必须是构造方法的第一条语句!
3、super和 this 不能同时调用构造方法!
super VS this
1、代表的对象不同:
this:本身调用者这个对象;
super:代表对象的应用;
2、前提:
this:没有继承也可以使用;
super:只能在继承条件下才可以使用;
3、构造方法:
this():本类的构造;
super():父类的构造!
沿袭传统,还是锐意改革—成员、方法的覆盖
继承,可以使得子类拥有父类所拥有的成员与方法,子类也可以更改父类所提供的这些成员和方法,以适应子类不同于父类的环境和特殊要求。这使得工具不但能继承,还能“与时俱进",变化更新!
在面向对象程序设计中,上面思想的实现手段是:覆盖。
方法的覆盖一—主要的覆盖手段
方法的覆盖是对已有工具的改良、改革,是非常实用的技术手段。
首先对方法的覆盖进行语法和规则上的说明:
1、方法的覆盖只存在于有继承关系的类之间;
2、子类方法名称,参数个数和类型,必须与被覆盖的父类方法一致;
3、第2条成立,子类方法返回值类型必须与被覆盖的父类方法一致;
4、第2条成立,子类方法的修饰符不能“低于”被覆盖的分类方法;
5、违反第2项,其实质是实现了方法的重载,而非覆盖。
Java 继承实现 Animal 类
public class Animal {
private String kind;
public Animal() {}
public Animal(String kind) {
this.kind = kind;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public void cry() {
System.out.println("动物的叫声!");
}
}
Animal 类的意思就是”动物“,它有一个 kind 成员,并且不打算让这个成员被子类继承;但其Getter与Setter方法可以被继承,所以该被成员可以被子类间接继承。
public class Cat extends Animal{
public Cat(String kind) {
super(kind);
}
@Override
public void cry() {
System.out.println("小猫的叫声:喵喵喵~~~");
}
}
Cat类从 Animal 类派生,其仅仅覆盖了 Animal 类的 cry()方法。
public class Dog extends Animal{
public Dog(String kind) {
super(kind);
}
@Override
public void cry() {
System.out.println("小狗的叫声:汪汪汪~~~");
}
}
Dog 类与 Cat 类 类似;
关于 instanceof 是运算符,当然也是 Java 关键字。其直译过来就是”是实例否“。其作用是判别一个对象是否是某个类的对象。
System.out.println(cat instanceof Cat);
System.out.println(dog instanceof Dog);
System.out.println(animal instanceof Animal);
其输出都是true;
System.out.println(cat instanceof Animal);
System.out.println(dog instanceof Animal);
其输出也都是true;
这说明,派生类的对象被看成基类的对象!
关于 多态
多态,即同一方法可以根据发送对象的不同而采用多种不同的行为方式。
多态存在的条件:
有继承关系;
子类重写父类方法;
父类引用指向子类对象;
注意:多态是方法的多态,属性没有多态性。
一切类的共同基类——Object
Object类是Java所提供的一个类类型,且是所有类的基类。
在上图中,我们看到了以前在其它类中看到的,不知道哪里来的方法。这是因为所有的类都继承于 Object 类。所以,Object 类的方法自然成为其他类的方法!这些方法很多都可以覆盖。
覆盖 toString() 方法
先看下面代码及其执行结果:
输出结果很奇怪:com.mec.polymorphic.core.Cat@1c4af82c
前面的是 cat 对象的类型,后面的是一串十六进制数字(其实就是cat对象所指向的实例的首地址),用@连接。这样的结果并不是我们想要的。这时候我们就需要通过覆盖 Object 类的 toString() 方法实现。
public class Cat extends Animal{
public Cat(String kind) {
super(kind);
}
public void cry() {
System.out.println("小猫的叫声:喵喵喵~~~");
}
@Override
public String toString() {
return "我是" + getName();
}
}
这次输出就是我们想要的了。
覆盖 equals() 方法
equals 的意思是“相等”,相等比较的意思。
众所周知,关于八大基本类型数值的大小比较,可以通过关系运算符,即 >、<、>=、<=、== 和 != 这些运算符进行。但是,复杂数据类型,而类的对象其本质是首地址,所以对于类对象的内容大小比较是不能用关系运算符的。
Object类定义的equals()和==的作用是相同的,比较俩地址值是否相同。
看下面代码:
public class Point {
public static final int MIN_ROW = 1;
public static final int MIN_COL = 1;
public static final int MAX_ROW = 25;
public static final int MAX_COL = 80;
public static final int DEFAULT_ROW = 1;
public static final int DEFAULT_COL = 1;
private int row;
private int col;
public Point() {
this(DEFAULT_ROW,DEFAULT_COL);
}
public Point(int row, int col) {
setRow(row);
setCol(col);
}
public Point(int row) {
setRow(row);
setCol(DEFAULT_COL);
}
public Point(Point point) {
setRow(point.row);
setCol(point.col);
}
public int getRow() {
return row;
}
public void setRow(int row) {
if (row < MIN_ROW || row > MAX_ROW) {
row = DEFAULT_ROW;
}
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
if (col < MIN_COL || col > MAX_COL) {
col = DEFAULT_COL;
}
this.col = col;
}
@Override
public String toString() {
return "(" + row + "," + col "")";
}
}
增加了toString() 方法的覆盖,下面是测试类:
public class Test {
public static void main(String[] args) {
Point pointOne = new Point(4,2);
Point pointTwo = new Point(4,2);
System.out.println(pointOne);
System.out.println(pointTwo);
System.out.println(pointOne == pointTwo);
}
}
输出为:
(4,2)
(4,2)
false
假如我们在最后紧接着输出一句:
System.out.println(pointOne.equals(pointTwo);
则输出结果依旧是:false
所以 Object 所提供的 equals() 方法比较的本质就是地址比较!
通常情况下我们自定义类如果使用 equals() 的话,也是比较“实体内容”,所以我们需要对equals()进行重写。