继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。如图所示:
其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。可以理解为:“子类 is a 父类。例如,图中兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
为什么要有继承?
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中, 那么多个类无需再定义这些属性和行为,只要继承那个类即可。
定义
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
继承的格式
通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
继承演示,代码如下:
定义父类
package demo01; // 定义动物类,里面定义的是所有动物都具有的方法 public class Creature { public void breath(){ System.out.println("呼吸"); } }
定义子类继承父类
package demo01; //子类继承了父类,就继承了父类的方法和属性。 public class Person extends Creature { String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println("吃饭"); sleep(); } private void sleep() { System.out.println("睡觉"); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
定义测试类
package demo01; /* * 面向对象的特征之二:继承性 why? * * 一、继承性的好处: * ① 减少了代码的冗余,提高了代码的复用性 * ② 便于功能的扩展 * ③ 为之后多态性的使用,提供了前提 * * * 二、继承性的格式: * class A extends B{} * A:子类、派生类、subclass * B:父类、超类、基类、superclass * * 2.1体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。 * 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。 * 只有因为封装性的影响,使得子类不能直接调用父类的结构而已。 * 2.2 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。 * 子类和父类的关系,不同于子集和集合的关系。 * extends:延展、扩展 * * 三、Java中关于继承性的规定: * 1.一个类可以被多个子类继承。 * 2.Java中类的单继承性:一个类只能有一个父类 * 3.子父类是相对的概念。 * 4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类 * 5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法 * * * 四、 1. 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类 * 2. 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类 * 3. 意味着,所有的java类具有java.lang.Object类声明的功能。 */ public class ExtendsTest { public static void main(String[] args) { //创建子类对象 Person p1 = new Person(); //调用子类中的属性和方法 System.out.println(p1.name); p1.eat(); //调用父类中的方法 p1.breath(); } }
好处
- 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
- 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
-
继承的作用中除了可以让代码复用之外,还有非常重要的两个作用,那就是有了继承之后才会衍生出方法的覆盖和多态机制。
注意:不要仅为了获取其他类中某个功能而去继承
子类的特点
- 子类继承了父类,就继承了父类的方法和属性。
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
- 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
继承的规则:
- B类继承 A类,则称 A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类。
- 子类不能直接访问父类中私有的(private)的成员变量和方法。
Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
class SubDemo extends Demo{ } //ok class SubDemo extends Demo1,Demo2...//error
java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中直接访问。可以通过间接的手段来访问。)java 中的类没有显示的继承任何类,则默认继承 Object类,Object类是 java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有 Object类型中所有的特征。
继承的弊端
- 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
继承的应用场景
- 使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承。is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类
继承中成员方法的访问特点
本质上,子类继承父类之后,是将父类继承过来的方法归为自己所有。实际上调用的也不是父类的方法,是他子类自己的方法(因为已经继承过来了就属于自己的)。
通过子类对象访问一个方法
- 先去子类成员范围找
- 在去父类成员范围找
- 如果都没有就报错(不考虑父亲的父亲…)
继承中变量的访问特点
在子类方法中访问一个变量,采用的是就近原则。
- 先去子类局部范围找
- 在去子类成员范围找
- 之后去父类成员范围找
- 如果都没有就报错(不考虑父亲的父亲…)
继承中构造方法的访问特点
- 子类中所有的构造方法默认都会访问父类中无参的构造方法
- 子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的
- 对象在堆内存中,会单独存在一块super区域,用来存放父类的数据
问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
- 通过使用super关键字去显示的调用父类的带参构造方法
- 在父类中自己提供一个无参构造方法
方法覆盖 Override
概念
- 子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)就发生了方法覆盖。
方法重写的应用场景
- 只有当从父类中继承过来的方法无法满足当前子类业务需求的时候,需要将父类中继承过来的 方法进行覆盖。换句话说,父类中继承过来的方法已经不够用了,子类有必要将这个方法重新 再写一遍,所以方法覆盖又被称为方法重写。当该方法被重写之后,子类对象一定会调用重写之后的方法。
方法重写的条件
- 方法覆盖发生在具有继承关系的父子类之间,这是首要条件
- 覆盖之后的方法与原方法具有相同的返回值类型、相同的方法名、相同的形式参数列表
代码示例
package com.wrg; /* * 方法的重写(override / overwrite) * * 1.重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作 * * 2.应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。 * * 3. 重写的规定: * 方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ * //方法体 * } * 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法 * ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同 * ② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符 * >特殊情况:子类不能重写父类中声明为private权限的方法 * ③ 返回值类型: * >父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void * >父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类 * >父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值 类型必须是相同的基本数据类型(必须也是double) * ④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲) * ********************************************************************** * 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。 * * 面试题:区分方法的重载与重写 */ public class Test { public static void main(String[] args) { // 创建子类对象 NewPhone np = new NewPhone(); // 调用父类继承而来的方法 np.call("张三"); // 调用子类重写的方法 np.showNum(); } } //父类 class Phone { //父类的方法 public void sendMessage() { System.out.println("发短信"); } public void call(String name) { System.out.println("给" + name + "打电话"); } public void showNum() { System.out.println("来电显示号码"); } } //智能手机类,子类 class NewPhone extends Phone { //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能 public void showNum() { //调用父类已经存在的功能使用super关键字 super.showNum(); //增加自己特有显示姓名和图片功能 System.out.println("显示来电姓名"); System.out.println("显示头像"); } }
另外,在使用方法覆盖的时候,需要有哪些注意事项呢?
- 由于覆盖之后的方法与原方法一模一样,建议在开发的时候采用复制粘贴的方式,不建议手写
- 私有的方法不能被继承,所以不能被覆盖
- 构造方法不能被继承,所以也不能被覆盖
- 覆盖之后的方法不能比原方法拥有更低的访问权限,可以更高
- 覆盖之后的方法不能比原方法抛出更多的异常,可以相同或更少
- 方法覆盖只是和方法有关,和属性无关
- 静态方法不存在覆盖,方法覆盖只能针对实例方法。
类和类继承之后的代码执行顺序,请看程序:
public class Test { public static void main(String[] args) { new H2(); } } class H1 { { System.out.println("父类代码块"); } public H1() { System.out.println("父类构造"); } static { System.out.println("父类静态代码块"); } } class H2 extends H1 { static { System.out.println("子类静态代码块"); } { System.out.println("子类代码块"); } public H2() { System.out.println("子类构造"); } }
我们对以上的程序进行分析,子类 H2 继承 H1,new H2()执行的时候,会先进行类加载, 先加载 H2 的父类 H1,所以 H1 当中的静态代码块先执行,然后再执行 H2 中的静态代码块, 静态代码块执行结束之后,不会马上执行构造方法,代码块会先执行,Java 中有一条规则:子 类构造方法执行前先执行父类的构造方法(学习 super 之后大家就知道了),所以父类 H1 的 代码块先执行,再执行 H1 的构造方法,然后再执行 H2 的代码块,最后执行 H2 的构造方法。 我们对以上程序进行编译并运行,请看下图结果:
方法重载和方法覆盖有什么区别?
- 方法重载发生在同一个类当中。
- 方法覆盖是发生在具有继承关系的父子类之间。
- 方法重载是一个类中,方法名相同,参数列表不同。
- 方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致:方法名一致、参数列表一致、返回值类型一致。
子类对象的实例化过程
子类对象实例化的全过程
- 从结果上来看:(继承性)子类继承父类以后,就获取了父类中声明的属性或方法。 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上来看:当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,...直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
- 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。