多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。例如,兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承定义
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
子类自动的共享父类中定义的数据和方法,被继承的类称为父类或超类,而经继承产生的类称为子类或派生类。根据继承机制,派生类继承了超类的所有属性和方法,并相应地增加了自己的一些新的方法和属性。
若一个子类只允许继承一个父类,称为单继承;若允许继承多个父类,称为多继承。目前许多面向对象程序设计语言不支持多继承。而Java语言通过接口(interface)的方式来弥补由于Java不支持多继承而带来的子类不能享用多个父类的成员的缺憾。
根据访问权限修饰符的不同,子类可以继承父类中某些成员变量和方法,提高了代码的重用性,子类也可以添加新的成员变量和方法 :如果类被final修饰,则该类不能被继承:
继承好处:
面向对象程序设计中的继承机制,大大增强了程序代码的可复用性,提高了软件的开发效率,降低了程序产生错误的可能性,也为程序的修改扩充提供了便利。而且类与类之间产生了关系,是多态的前提。
通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
语法如下:class <子类> extends <父类>
class 父类 { class 子类 extends 父类 {
... ...
} }
继承演示,代码如下:
/* 定义员工类Employee,做为父类 */
class Employee {
String name; // 定义name属性
// 定义员工的工作方法
public void work() {
System.out.println(this.name + "尽心尽力地工作");
}
}
/* 定义讲师类Teacher 继承 员工类Employee*/
class Teacher extends Employee {
// 定义一个打印name的方法
public void printName() {
System.out.println("name=" + name);
}
}
/*定义测试类*/
public class ExtendDemo01 {
public static void main(String[] args) {
Teacher t = new Teacher();// 创建一个讲师类对象
t.name = "小明";// 为该员工类的name属性进行赋值
t.printName(); // 调用该员工的printName()方法:name = 小明
t.work(); //调用Teacher类继承来的work()方法:尽心尽力地工作
}
}
继承is-a 与组合has–a的关系
class C { public int name; } | class A { C c = new C(); public int id; } | class B extends A { int getID() { return id; } } |
Is-a:代表类于类之间的继承关系;
has-a代表的是组成关系也就类及其成员的从属关系。例如在class A中声明了一个class C的变量 public C c,那么就是A has-a C,在使用has-a的时候,可以理解为xx有一个xx,这样在使用的时候就更容易了。
A has-a C, B has-a C
继承父类的由来:
其实是由多个类不断向上抽取共性内容而来的。
Object类是所有类的直接父类或间接父类。
多继承为什么不支持呢?
因为父类中的方法中存在方法体,当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪一个呢?
但是java支持多重继承。A继承B B继承C C继承D。
多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。
简单说:对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。
继承什么时候使用呢?
细节一类与类之间存在着所属关系时:
当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。狼是犬科中的一种。所属关系:"is a "
注意:不要仅仅为了获取其他类中的已有成员进行继承。所以判断所属关系,可以简单看,如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。
细节二:在方法覆盖时,注意两点:
1:子类覆盖父类时,必须要保证,子类方法的权限必须大于等于父类方法权限可以实现继承。否则,编译失败。
2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)
继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。
这时如何解决问题呢?介绍一个关键字,final:最终。
final特点:
1:这个关键字是一个修饰符,可以修饰类,方法,变量。
2:被final修饰的类是一个最终类,不可以被继承。
3:被final修饰的方法是一个最终方法,不可以被覆盖。
4:被final修饰的变量是一个常量,只能赋值一次。
其实这样的原因的就是给一些固定的数据起个阅读性较强的名称。
不加final修饰不是也可以使用吗?那么这个值是一个变量,是可以更改的。加了final,程序更为严谨。常量名称定义时,有规范,所有字母都大写,如果由多个单词组成,中间用 _ 连接。
继承后的特点:
1:成员变量。
当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?
成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:
class Fu { // Fu中的成员变量 int num = 5; } | class Zi extends Fu { int num2 = 6; // Zi中的成员变量 public void show() {// Zi中的成员方法 //继承而来,所以直接访问父类中的num, System.out.println("Fu num="+num); // 访问子类中的num2 System.out.println("Zi num2="+num2); } } | public class ExtendDemo02 { public static void main(String[] args) { // 创建子类对象 Zi z = new Zi(); // 调用子类中的show方法 z.show(); } } 演示结果:Fu num = 5 Zi num2 = 6 |
成员变量重名
如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:
class Fu { // Fu中的成员变量 int num = 5; } | class Zi extends Fu { int num = 6; // Zi中的成员变量 public void show() {// Zi中的成员方法 //继承而来,所以直接访问父类中的num, System.out.println("Fu num="+num); // 访问子类中的num System.out.println("Zi num="+num); } } | public class ExtendDemo02 { public static void main(String[] args) { // 创建子类对象 Zi z = new Zi(); // 调用子类中的show方法 z.show(); } } 演示结果:Fu num = 6 Zi num = 6 |
当子类成员变量和父类成员变量同名时,对子类来讲,父类的成员变量不能被子类继承(即子类的成员变量覆盖了父类的成员变量),此时称子类的成员变量隐藏了父类的成员变量。
如果要在子类非static修饰的代码块或方法中使用被隐藏的父类成员变量可以通过super关键字实现。
This:代表是本类类型的对象引用。Super:代表是子类所属的父类中的内存空间引用。
使用格式:super.父类成员变量名
class Fu { // Fu中的成员变量 int num = 5; } | class Zi extends Fu { int num = 6; // Zi中的成员变量 public void show() {// Zi中的成员方法 //继承而来,所以直接访问父类中的num, System.out.println("Fu num="+ super.num); // 访问子类中的num System.out.println("Zi num="+ this.num); } } | public class ExtendDemo02 { public static void main(String[] args) { // 创建子类对象 Zi z = new Zi(); // 调用子类中的show方法 z.show(); } } 演示结果:Fu num = 5 Zi num = 6 |
注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。
Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。
2:成员方法。
成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下
class Fu{ public void show(){ System.out.println("Fu类中的show执行"); } } class Zi extends Fu{ public void show2(){ System.out.println("Zi类中的show2执行"); } } | public class ExtendsDemo04{ public static void main(String[] args) { //子类中没有show方法,但是可以找到父类方法去执行 Zi z = new Zi(); z.show(); z.show2(); } } |
成员方法重名——重写(Override)
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。
有时从父类继承的方法在子类中必须进行修改以适应新类的需要,这种对父类方法进行改写或改造的现象称为方法重写或方法覆盖。父类方法在子类中重写使继承更加灵活。
子类重写了父类的方法,则使用子类创建的对象调用该方法时,调用的是重写后的方法,即子类中的方法:
如果要在子类非static修饰的代码块或方法中调用父类被重写的方法可以通过super关键字实现。
当子父类中出现了一模一样的方法时,建立子类对象会运行子类中的方法。好像父类中的方法被覆盖掉一样。所以这种情况,是方法的另一个特性:Override (复写,重写)
什么时候使用Override呢?当一个类的功能内容需要修改时,可以通过Override来实现。
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
class Fu{ public void show(){ System.out.println("Fu类中的show方法执行"); } } class Zi extends Fu{ public void show(){ System.out.println("Zi类中的show2方法执行"); } } | public class ExtendsDemo04{ public static void main(String[] args) { // 子类中有show方法,只执行重写后的show方法 Zi z = new Zi(); z.show(); // Zi show } } |
重写的应用
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:
class Phone { public void sendMessage(){ System.out.println("发短信"); } public void call(){ System.out.println("打电话"); } public void showNum(){ System.out.println("来电显示号码"); } } //智能手机类 class NewPhone extends Phone { //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能 public void showNum(){ //调用父类已经存在的功能使用super super.showNum(); //增加自己特有显示姓名和图片功能 System.out.println("显示来电姓名"); System.out.println("显示头像"); } } | public class ExtendsDemo06 { public static void main(String[] args) { // 创建子类对象 NewPhone np = new NewPhone(); // 调用父类继承而来的方法 np.call(); // 调用子类重写的方法 np.showNum(); } } |
这里重写时,用到super.父类成员方法,表示调用父类的成员方法。
子类重写父类方法需要满足的条件:
1、子类重写的方法和父类被重写的方法在方法名和参数列表方面相同;
2、返回值类型:
a、如果父类被重写的方法没有返回值类型或者返回值类型为基本数据类型,则要求子类重写的方法的返回值类型和父类被重写方法的返回值类型相同;
b、如果父类被重写的方法返回值类型为引用数据类型,则要求子类重写的方法的返回值类型和父类被重写方法的返回值类型相同或是其子类。
3、子类重写的方法不能缩小父类被重写方法的访问权限,子类重写方法的访问权限必须大于等于父类被重写方法的访问权限;
4、父类中静态方法可以被子类继承,但却不能被子类重写;
5、父类中被final关键字修饰的方法不能被子类重写
3:构造方法。
发现子类构造方法运行时,都会默认访问父类中的空参数的构造方法,因为子类的所有构造方法中的第一行,其实都有一条隐身的语句super();
如果父类中没有空参数的构造方法,那么子类的构造方法内,必须通过super语句指定要访问的父类中的构造方法。
如果子类构造方法中用this来指定调用子类自己的构造方法,那么被调用的构造方法也一样会访问父类中的构造方法。
super(参数): 表示父类的构造方法,并会调用于参数相对应的父类中的构造方法。而super():是在调用父类中空参数的构造方法。
因为子类继承父类,会继承到父类中的数据。所以子类在进行对象初始化时,先调用父类的构造方法,看父类是如何对自己的数据进行初始化的,这就是子类的实例化过程。
注意: 首先我们要回忆两个事情,构造方法的定义格式和作用。
1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如下:
class Fu { private int n; Fu(){ System.out.println("Fu()"); } } | class Zi extends Fu { Zi(){ // super(),调用父类构造方法 super(); System.out.println("Zi()"); } } | public class ExtendsDemo07{ public static void main (String args[]){ Zi zi = new Zi(); } } 输出结果:Fu() Zi() |
super和this
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。
super和this的含义
super :代表父类的存储空间标识(可以理解为父亲的引用)。
this :代表当前对象的引用(谁调用就代表谁)。
super和this的用法
访问成员
this.成员变量 ‐‐ 本类的 this.成员方法名() ‐‐ 本类的 | super.成员变量 ‐‐ 父类的 super.成员方法名() ‐‐ 父类的 |
用法演示,代码如下:
class Animal { public void eat() { System.out.println("animal : eat"); } } class Cat extends Animal { public void eat() { System.out.println("cat : eat"); } public void eatTest() { this.eat(); // this 调用本类的方法 super.eat(); // super 调用父类的方法 } } | public class ExtendsDemo08 { public static void main(String[] args) { Animal a = new Animal(); a.eat(); Cat c = new Cat(); c.eatTest(); } } 输出结果为: cat : eat cat : eat animal : eat |
访问构造方法
this(...) ‐‐ 本类的构造方法
super(...) ‐‐ 父类的构造方法
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
因为super()或者this()都是调用构造方法,构造方法用于初始化,所以初始化的动作要先完成。
super使用原则:
super关键字可以调用父类的成员变量(super.属性)和方法(super.父类方法(([参数列表]))。
子类构造方法中可以使用super关键字调用父类的构造方法:super([参数列表]);
super 不能用于静态方法或静态代码块中。