继承性
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。如图所示:
其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。例如,图中兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中, 那么多个类无需再定义这些属性和行为,只要继承那个类即可。
- 此处的多个类称为子类(派生类),单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类”
类继承语法规则:
通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
作用:
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
注意:不要仅为了获取其他类中某个功能而去继承
- 子类继承了父类,就继承了父类的方法和属性。
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
- 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集, 而是对父类的“扩展”。
举例
定义父类
public class Creature {
public void breath() {
System.out.println("呼吸");
}
}
定义Creature的子类Person类
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() {
sleep();
}
private void sleep() {
System.out.println("睡觉");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
定义Person类的子类
public class Student extends Person{
String major;
public Student(){
}
public Student(String name,int age,String major){
this.name = name;
setAge(age);
this.major = major;
}
public void study(){
System.out.println("学习");
}
public void show(){
System.out.println("name:" + name + ",age:" + getAge());
}
}
定义测试类
/*
* 面向对象的特征之二:继承性 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) {
//创建父类对象,不能访问子类中的属性和方法
Creature c = new Creature();
//Person类相对Student类是父类
Person p1 = new Person();
//访问父类中的方法
p1.breath();//呼吸
//Student类即是Person的子类又是Creature的子类
Student s1 = new Student();
s1.eat();//睡觉
s1.breath();//呼吸
}
}
关于继承的规则:
- 子类不能直接访问父类中私有的(private)的成员变量和方法
注意事项:
- Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2...//error
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
- 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。意味着,所有的java类具有java.lang.Object类声明的功能。
练习题
定义父类
package demo02;
/*
定义一个父类ManKind类,包括
成员变量int sex和int salary;
方法void manOrWoman():根据sex的值显示“man”(sex==1)或者“woman”(sex==0);
方法void employeed():根据salary的值显示“no job”(salary==0)或者“ job”(salary!=0)。
*/
public class ManKind {
private int sex;//性别
private int salary;//薪资
public ManKind() {
}
public ManKind(int sex, int salary) {
this.sex = sex;
this.salary = salary;
}
public void manOrWoman(){
if(sex == 1){
System.out.println("man");
}else if(sex == 0){
System.out.println("woman");
}
}
public void employeed(){
String jobInfo = (salary == 0)? "no job" : "job";
System.out.println(jobInfo);
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
}
定义子类
package demo02;
/*
*
定义类Kids继承ManKind,并包括
成员变量int yearsOld;
方法printAge()打印yearsOld的值。
*/
public class Kids extends ManKind {
private int yearsOld;
public Kids() {
}
public Kids(int yearsOld) {
this.yearsOld = yearsOld;
}
public void printAge() {
System.out.println("I am " + yearsOld + " years old.");
}
public int getYearsOld() {
return yearsOld;
}
public void setYearsOld(int yearsOld) {
this.yearsOld = yearsOld;
}
}
定义测试类
package demo02;
/*
* 定义类KidsTest,在类的main方法中实例化Kids的对象someKid,用该对象访问其父类的成员变量及方法。
*/
public class KidsTest {
public static void main(String[] args) {
//创建子类对象
Kids someKid = new Kids(12);
//调用本类的方法
someKid.printAge();
//调用父类的方法
someKid.setSalary(0);
someKid.setSex(1);
someKid.employeed();
someKid.manOrWoman();
}
}
方法的重写(override/overwrite)
定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称 为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求:
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
- 子类方法抛出的异常不能大于父类被重写方法的异常
注意:
- 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
方法重写的好处
- 子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。
举例:
定义父类
package java1;
public class Person {
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
void eat(){
System.out.println("吃饭");
}
public void walk(int distance){
System.out.println("走路,走的距离是:" + distance + "公里");
show();
eat();
}
private void show(){
System.out.println("我是一个人");
}
}
定义子类
package java1;
public class Student extends Person{
String major;
public Student(){
}
public Student(String major){
this.major = major;
}
public void study(){
System.out.println("学习。专业是:" + major);
}
//对父类中的eat()进行了重写
public void eat(){
System.out.println("学生应该多吃有营养的食物");
}
public void show(){
System.out.println("我是一个学生");
}
public void walk(int distance) {
System.out.println("重写的方法"+distance);
}
}
定义测试类
package java1;
/*
* 方法的重写(override / overwrite)
*
* 1.重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
*
* 2.应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
*
* 3. 重写的规定:
* 方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
* //方法体
* }
* 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
* ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
* ② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
* >特殊情况:子类不能重写父类中声明为private权限的方法
* ③ 返回值类型:
* >父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
* >父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
* >父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
* ④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
* **********************************************************************
* 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
*
* 面试题:区分方法的重载与重写
*/
public class PersonTest {
public static void main(String[] args) {
Student s = new Student("计算机科学与技术");
s.eat();//学生应该多吃有营养的食物
s.walk(10);//重写的方法10
s.study();//学习。专业是:计算机科学与技术
Person p1 = new Person();
p1.eat();//吃饭
}
}
总结
继承后的特点——成员变量
当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?
创建父类对象
- 只能使用只能使用父类的成员,不能使用任何子类内容
创建子类对象
- 如果子类父类中出现不重名的成员变量,这时我们可以根据成员变量名称访问,本类和从父类中继承下来的成员变量。
- 如果子类父类中出现重名的成员变量,这时候我们的访问有2种方式:
- 直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找。
- 间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找
继承后的特点——成员方法
当类之间产生了关系,其中各类中的成员方法,又产生了哪些影响呢?
创建父类对象
- 只能使用只能使用父类的成员,不能使用任何子类内容
创建子类对象
- 如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
-
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
注意事项:
- 无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。
复习下权限修饰符
关键字super
super :代表父类的存储空间标识(可以理解为父亲的引用)。
在Java类中使用super来调用父类中的指定操作:
- 在子类的成员方法中,访问父类的成员变量。格式:super.成员变量名
- 在子类的成员方法中,访问父类的成员方法。格式:super.成员方法名()
- 在子类的构造方法中,访问父类的构造方法。格式:super(...)
注意:
- 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存 空间的标识
举例
定义父类
package demo09;
public class Fu {
int num = 30;
}
定义子类
package demo09;
public class Zi extends Fu {
int num = 20;
public Zi() {
//在本类的构造方法中,访问本类的另一个构造方法。
this(123); // 本类的无参构造,调用本类的有参构造
}
public Zi(int n) {
}
public void showNum() {
int num = 10;
// 访问局部变量
System.out.println(num);
//在本类的成员方法中,访问本类的成员变量。
System.out.println(this.num); // 本类中的成员变量
//在本类的成员方法中,访问父类中的成员变量
System.out.println(super.num);
}
public void methodA() {
System.out.println("AAA");
}
public void methodB() {
//在本类的成员方法中,访问本类的另一个成员方法。
this.methodA();
System.out.println("BBB");
}
}
调用父类的构造器
- 子类中所有的构造器默认都会访问父类中空参数的构造器
- 当父类中没有空参数的构造器时,子类的构造器必须通过this(参 数列表)或者super(参数列表)语句指定调用本类或者父类中相应的 构造器。同时,只能”二选一”,且必须放在构造器的首行
- 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又 没有无参的构造器,则编译出错
this和super的区别
子类对象的实例化过程
子类对象实例化的全过程
- 从结果上来看:(继承性)子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上来看:当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,...直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。