理解 Java 的三大特性之继承
•引言
在《Think in java》中有这样一句话:复用代码是Java众多引人注目的功能之一。
但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情。
在这句话中最引人注目的是 “复用代码” ,尽可能的复用代码是我们程序员一直在追求的;
现在我来介绍一种复用代码的方式,也是 Java 三大特性之一:继承。
•通过代码深入了解继承
在讲解之前我们先看一个例子,该例子是前篇博文的。
Husband.java
public class Husband { /* * 对属性的封装 * 一个人的姓名、性别、年龄、妻子都是这个人的私有属性 */ private String name ; private String sex ; private int age ; private Wife wife; }Wife.java
public class Wife { private String name; private int age; private String sex; private Husband husband; }从这里我们可以看出,Husband、Wife 两个类除了各自的 wife、husband 外其余部分全部相同;
作为一个想最大限度实现复用代码的我们是不能够忍受这样的重复代码;
果再来一个小三、小四、小五……(扯远了)我们是不是也要这样写呢?
那么我们如何来实现这些类的可复用呢?利用继承!!
首先我们先离开软件编程的世界,从常识中我们知道丈夫、妻子、小三、小四…,他们都是人;
而且都有一些共性,有名字、年龄、性别等等,而且他们都能够吃东西、走路、说话等等共同的行为;
所以从这里我们可以发现他们都拥有人的属性和行为,同时也是从人那里继承来的这些属性和行为的。
从上面我们就可以基本了解了继承的概念了,继承是使用已存在的类的定义作为基础建立新类的技术;
新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
新建一个 Person 类;
Person.java
public class Person { private String name ; private String sex ; private int age ; }让 Husband类 和 Wife类 继承这个类:
public class Husband extends Person{ private Wife wife; }public class Wife extends Person{ private Husband husband; }在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的;
对于 Husband、Wife 使用继承后,除了代码量的减少我们还能够非常明显的看到他们的关系。
继承所描述的是 “is-a” 的关系;
如果有两个对象 A 和 B,若可以描述为 “A是B”;
则可以表示 A 继承 B ,其中 B 是被继承者称之为父类或者超类,A 是继承者称之为子类或者派生类。
实际上继承者(A) 是被继承者(B) 的特殊化,它除了拥有被继承者(B) 的特性外,还拥有自己独有得特性。
例如猫有抓老鼠、爬树等其他动物没有的特性。
同时在继承关系中,继承者(A) 完全可以替换被继承者(B),反之则不可以;
例如我们可以说猫是动物,但不能说动物是猫就是这个道理,其实对于这个我们将其称之为 “向上转型”,下面介绍。
诚然,继承定义了类如何相互关联,共享特性。
对于若干个相同或者相似的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类(或者超类);
然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
同时在使用继承时需要记住三句话:
1、子类拥有父类非private的属性和方法。
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。(以后介绍)。
综上所述,使用继承确实有许多的优点,除了将所有子类的共同属性放入父类,实现代码共享,避免重复外;
还可以使得修改扩展继承而来的实现比较简单。
诚然,讲到继承一定少不了这三个东西:构造器、protected关键字、向上转型。
•构造器
通过前面我们知道子类可以继承父类的属性和方法,除了那些 private 的外还有一样是子类继承不了的---构造器。
对于构造器而言,它只能够被调用,而不能被继承。 调用父类的构造方法我们使用 super() 即可。
对于子类而已,其构造器的正确初始化是非常重要的;
而且当且仅当只有一个方法可以保证这点:在构造器中调用父类构造器来完成初始化;
而父类构造器具有执行父类初始化所需要的所有知识和能力。
Person.java
public class Person { private String name ; private String sex ; private int age ; Person(){ System.out.println("Person Constrctor!"); } }Husband.java
public class Husband extends Person{ private Wife wife; Husband(){ super();//调用父类的构造方法 System.out.println("Husband Constructor!"); } public static void main(String[] args) { Husband husband = new Husband(); } }运行结果
将 Husband() 方法中的 super() 去掉:
public class Husband extends Person{ private Wife wife; Husband(){ // super();//调用父类的构造方法 System.out.println("注释掉super():"+"Husband Constructor!"); } public static void main(String[] args) { Husband husband = new Husband(); } }运行结果
通过这个示例可以看出,构建过程是从父类 “向外” 扩散的;
也就是在创建子类对象的时候,Java 虚拟机是先创建父类的对象;
然后在这个父类对象的基础上加上子类特有的方法和属性,从而创建出子类对象;
而且我们并没有显示的引用父类的构造器,这就是 Java 的聪明之处:编译器会默认给子类调用父类的构造器 super() 方法。
但是,这个默认调用父类的构造器是有前提的:父类有默认构造器(无参构造方法)。
如果父类没有默认构造器,我们就要必须显示的使用 super() 来调用父类构造器;
否则编译器会报错:无法找到符合父类形式的构造器。
修改 Person.java 中的代码,如下所示:
public class Person { private String name ; private String sex ; private int age ; Person(String name){ System.out.println("Person Constrctor!"+"name : "+name); } }修改 Husband.java 中的代码,如下所示:
public class Husband extends Person{ private Wife wife; Husband(String name){ super(name); System.out.println("Husband Constructor!"+"name : "+name); } public static void main(String[] args) { Husband husband = new Husband("隔壁老王"); } }运行结果:
所以综上所述:对于继承而已,子类会默认调用父类的构造器;
但是如果没有默认的父类构造器,子类必须要显示的指定父类的构造器,而且必须是在子类构造器中做的第一件事(第一行代码)。
•protected关键字
private 访问修饰符,对于封装而言,是最好的选择,但这个只是基于理想的世界;
有时候我们需要这样的需求:我们需要将某些事物尽可能地对这个世界隐藏,但是仍然允许子类的成员来访问它们。
这个时候就需要使用到protected。
对于protected而言,它指明就类用户而言,他是private;
但是对于任何继承与此类的子类而言或者其他任何位于同一个包的类而言,他却是可以访问的。
Person.java
public class Person { private String name; private int age; private String sex; protected void setName(String name) { this.name = name; } protected String getName() { return name; } public String toString(){ return "this name is " + name; } /** 省略其他setter、getter方法 **/ }Husband.java
public class Husband extends Person{ private Wife wife; public String toString(){ setName("XXX"); //调用父类的 setName(); return super.toString(); //调用父类的 toString() 方法 } public static void main(String[] args) { Husband husband = new Husband(); System.out.println(husband.toString()); } }运行结果
从上面示例可以看书子类Husband可以明显地调用父类Person的setName()。
诚然尽管可以使用protected访问修饰符来限制父类属性和方法的访问权限;
但是最好的方式还是将属性保持为private;
通过protected方法来控制类的继承者的访问权限。
PS:父类私有的方法和对象是子类无法直接访问的,子类仅仅是拥有但访问不到!
•向上转型
在上面的继承中我们谈到继承是 is-a 的相互关系;
猫继承与动物,所以我们可以说猫是动物,或者说猫是动物的一种。
这样将猫看做动物就是向上转型。如下:
Person.java
public class Person { public void display(){ System.out.println("Display Person..."); } static void display(Person person){ person.display(); } }Husband.java
public class Husband extends Person{ public static void main(String[] args) { Husband husband = new Husband(); Person.display(husband); //向上转型 } }在这我们通过 Person.display(husband) 这句话可以看出 husband 是person类型。
将子类转换成父类,在继承关系上面是向上移动的,所以一般称之为向上转型。
由于向上转型是从一个叫专用类型向较通用类型转换,所以它总是安全的,唯一发生变化的可能就是属性和方法的丢失。
这就是为什么编译器在“未曾明确表示转型”活“未曾指定特殊标记”的情况下,仍然允许向上转型的原因。
•继承类型
需要注意的是 Java 不支持多继承,但支持多重继承。
继承的特性
子类拥有父类非 private 的属性、方法。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
•补充说明
父类中的final属性和方法以及static属性和方法子类是能够继承并直接访问的;
但是final的属性子类无法修改,final的方法子类无法重写,说明final修饰的方法并不具备动态绑定;
而static的属性和方法会存在隐藏现象,即当子类中出现与父类static变量或者方法同名的变量或者方法时,子类的成员变量和静态方法会被隐藏,
如:父类中有 public static int num = 1 ,而子类中有 public static int num = 2 ,
这时候用向上转型的对象,比如 Father father = new Son() ,其中 Father 是父类,Son是子类;
去输出 num属性会发现输出为1!
static方法的隐藏理论同属性一致,如果你想调用子类的 static 方法和属性,你需要定义明确子类的对象去调用,如
Son son = new Son()
从这里看出static方法和属性也不支持动态绑定!
所以子类并不能重写父类的static方法。
知识点扩展
Java中的动态绑定,是实现多态的本质,它只针对普通方法,而且仅仅是方法,不包括成员变量那些!
所以说如果你用一个向上转型的父类对象去调用父类对象中的非final、非static属性,
即使它对应的子类中有一样的属性,最终输出还是父类对象的属性!
最后补充一点,Java中除了final、static、private修饰的方法以及构造器(构造器默认为static)为静态绑定之外,其他方法皆为动态绑定。
•声明
本文转载自:【java提高篇(二)-----理解java的三大特性之继承】
参考链接:【Java 继承 | 菜鸟教程】