为什么需要继承?
//大学生类
public class UnStudent{
//定义属性
String name;
Int ID;
Int age;
//定义方法
public void study(){
System.out.println("学习!");
}
public void sleep(){
System.out.println("睡觉!");
}
}
//高中生
public class SenoirStudent{
//定义属性
String name;
Int ID;
Int age;
//定义方法
public void study(){
System.out.println("学习!");
}
public void sleep(){
System.out.println("睡觉!");
}
}
从这里我们可以看出来,高中生类和大学生类拥有相同的属性和方法,如果此时再多出一个初中生类,和一个小学生类呢?难道我们还要再去拷贝三份代码吗?显然我们是拒绝的(因为程序员都比较懒),此时我们想最大限度的利用重复的代码。于是很容易就想到了,能不能写一个模板,让其他要实现的类具体化一下它呢?
引子
“复用代码是Java众多引人注目的功能之一。但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情。在这句话中最引人注目的是“复用代码”,尽可能的复用代码使我们程序员一直在追求的,现在我来介绍一种复用代码的方式,也是java三大特性之一---继承。”
--《Thanking in java》
继承的概念
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
生活中的继承
想象我们的生活中无处不存在这继承。动物和植物都是属于生物的范畴,而狗和鸟都是属于动物的范畴。所以说动物和植物都是继承生物,而狗和鸟都是继承动物。
此时,想必大家都明白了吧,对于被继承类来说更加的通用一点,而对于继承类来说更加具体一点。
IS-A关系
如果有两个对象A和B,若可以描述为“A是B”,则可以表示A继承B,其中B是被继承者称之为父类或者超类,A是继承者称之为子类或者派生类(很容易理解说动物是生物)。
继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字
java中类与类之间的继承都是单继承,也就是说一个类只能继承另一个类(注意与c++的区别,c++中一个类可以继承多个类)
public class A extends B{
}
implements关键字
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
interface A{
}
interface B{
}
public interface C implements A,B{
}
此时再来看看上面的问题
//公共的学生类
public class Student{
//定义属性
String name;
Int studentId;
Int age;
//定义方法
public void study(){
System.out.println("学习!");
}
public void sleep(){
System.out.println("睡觉!");
}
}
public class UnStudent{
}
public class SenoirStudent{
}
此时可以看出来,代码简洁来许多,同时我们接着向扩充初中生和小学生类也非常容易。
继承的特性
- 子类拥有父类非private的属性,方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- java的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如A类继承B类,B类继承C类,所以按照关系就是C类是B类的父类,B类是A类的父类,这是java继承区别于C++继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系)。
使用继承的时候有下面几个值得注意的问题
一 构造器
通过前面我们知道子类可以继承父类的属性和方法,除了那些private的外还有一样是子类继承不了的—构造器。对于构造器而言,它只能够被调用,而不能被继承。但是父类的构造器带有参数的(没有无参构造器的情况下),则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。
如果父类有无参构造器,则在子类的构造器中用super调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
对于子类而已,其构造器的正确初始化是非常重要的,构造器的初始化顺序有如下规则:
先初始化父类的构造器,在初始化子类的构造器。看下面一个例子.
public class SuperClass {
private int n;
public SuperClass(int n) {
this.n = n;
System.out.println("SuperClass Constructor");
}
}
public class SubClass extends SuperClass{
public SubClass(int n) {
//注意此处不显示调用父类的构造器编译器会报错bride
//如果不想显示调用可以在父类中加一个无参构造器
super(n);
System.out.println("SubClass Constructor");
}
public static void main(String[] args) {
SubClass subClass = new SubClass(100);
}
}
Ouput:
SuperClass Constructor
SubClass Constructor
二 protected权限修饰符
当我们使用private关键字的时候,对于封装来说确实很好,因为它对外隐藏了自己。但是我们有些时候可能会有这样的需求:某些事物尽可能地对这个世界隐藏,但是对于自己的子类来说是可以访问的。这个时候就使用到了protected关键字。
对于protected而言,它指明就类用户而言,他是private,但是对于任何继承与此类的子类而言或者其他任何位于同一个包的类而言,他却是可以访问的。(不在当前类的包下,当前类对象不可以调用protected访问权限的方法和属性,但是子类却可以调用protected修饰的方法和属性,即使子类和父类不在同一个包下)。
public class Student {
protected String name;
protected String getName() {
return name;
}
}
public class SeniorStudent extends Student {
public SeniorStudent() {
name = "小明";
System.out.println("my name is:"+getName());
}
public static void main(String[] args) {
SeniorStudent stu = new SeniorStudent();
}
}
Output:
my name is:小明
注意:只有当我们需要子类可以访问父类的属性或者方法的时候才使用protected修饰符,切不可滥用(因为会破坏面向对象封装的特性)。
三 向上转型
因为继承是is-a的相互关系,从一个简单的例子来看,鸟继承动物,所以我们可以说鸟是动物。我们说鸟是动物就是向上转型 。
public class Animal {
public void display(){
System.out.println("Animal");
}
public static void show(Animal animal){
animal.display();
}
}
public class Bird extends Animal {
public static void main(String[] args) {
Bird bird = new Bird();
Animal.show(bird);
}
}
Output:
Animal
我们可以看到我们传入show方法中去的是一个Bird,而我们的形式参数是一个Animal,所以我们就可以把Bird看作一个Animal。
将子类转换成父类,在继承关系上面是向上移动的,所以一般称之为向上转型。由于向上转型是从一个叫专用类型向较通用类型转换,所以它总是安全的,唯一发生变化的可能就是属性和方法的丢失。这就是为什么编译器在“未曾明确表示转型”活“未曾指定特殊标记”的情况下,仍然允许向上转型的原因。
关于继承类的对象创建过程和内存分析
以前面提到的学生为例子:
//公共的学生类
public class Student{
//定义属性
String name;
Int studentId;
Int age;
public Student(){
super();
System.out.println("Student");
}
//定义方法
public void study(){
System.out.println("学习!");
}
public void sleep(){
System.out.println("睡觉!");
}
}
public class SenoirStudent{
//班级编号
private String classNumber;
public SenoirStudent(){
super();
System.out.println("SenoirStudent");
}
public void nationExam(){
System.out.println("高考!");
}
public static void main(String[] args){
SenoirStudent stu = new SenoirStudent();
}
}
当创建一个SenoirStudent实例的时候构造器执行的流程:
再来看看堆内存:
首先在方法区加载Object类的信息,然后再在堆区创建一个Object对象
接着加载在方法区加载Student类信息,然后再在堆区创建了一个Student对象
(此时可以清楚的看到每一个student对象中的实例方法都含有两个隐式的关键词this和super。this指向对象自己本身,而super执行父类对象。)
最后,在方法区加载SenoirStudent类信息,然后再在堆区创建一个SenoirStudent对象
结语
问一问自己是否需要从子类向父类进行向上转型。如果必须向上转型,则继承是必要的,但是如果不需要,则应当好好考虑自己是否需要继承。
---《Thinking in java》
本文深入探讨了Java中的继承概念,解释了为什么需要继承,并通过具体的代码示例展示了如何使用继承简化代码,提高代码复用率。文章还介绍了继承的关键字、特性和注意事项。

1300

被折叠的 条评论
为什么被折叠?



