面向对象进阶一:继承
第三章 继承
3.1 概述
3.1.1 引入
假如我们要定义如下类:
学生类,老师类和工人类,分析如下。
-
学生类
属性:姓名,年龄
行为:吃饭,睡觉 -
老师类
属性:姓名,年龄,薪水
行为:吃饭,睡觉,教书 -
班主任
属性:姓名,年龄,薪水
行为:吃饭,睡觉,管理
如果我们定义了这三个类去开发一个系统,那么这三个类中就存在大量重复的信息(属性:姓名,年龄。行为:吃饭,睡觉)。这样就导致了相同代码大量重复,代码显得很臃肿和冗余,那么如何解决呢?
假如多个类中存在相同属性和行为时,我们可以将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。如图所示:
其中,多个类可以称为子类,单独被继承的那一个类称为父类、超类(superclass)或者基类。
3.1.2 继承的含义
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。例如,兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承:就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
3.1.3 继承的好处
- 提高代码的复用性(减少代码冗余,相同代码重复利用)。
- 使类与类之间产生了关系。
3.1.4 继承的格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
需要注意:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。
3.2 继承案例
各位同学,学习完继承的快速入门之后,接下来我们学习一下继承的好处,以及它的应用场景。
我们通过一个案例来学习
观察代码发现,我们会发现Teacher类中和Consultant类中有相同的代码;其实像这种两个类中有相同代码时,没必要重复写。
我们可以把重复的代码提取出来,作为父类,然后让其他类继承父类就可以了,这样可以提高代码的复用性。改造后的代码如下:
接下来使用继承来完成上面的案例,这里只演示People类和Teacher类,然后你尝试自己完成Consultant类。
- 先写一个父类 People,用来设计Teacher和Consultant公有的成员。
public class People{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
}
- 再写两个子类Teacher继承People类,同时在子类中加上自己特有的成员。
public class Teacher extends People{
private String skill; //技能
public String getSkill(){
return skill;
}
public void setSkill(String skill){
this.skill=skill;
}
public void printInfo(){
System.out.println(getName()+"具备的技能:"+skill);
}
}
- 最后再写一个测试类,再测试类中创建Teacher、Consultant对象,并调用方法。
public class Test {
public static void main(String[] args) {
// 目标:搞清楚继承的好处。
Teacher t = new Teacher();
t.setName("播仔");
t.setSkill("Java、Spring");
System.out.println(t.getName());
System.out.println(t.getSkill());
t.printInfo();
}
}
执行代码,打印结果如下:
关于继承的好处我们只需要记住:继承可以提高代码的复用性
3.3 权限修饰符
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,我们之前已经学习过了public 和 private,接下来我们研究一下protected和默认修饰符的作用。
-
public:公共的,所有地方都可以访问。
-
protected:本类 ,本包,其他包中的子类都可以访问。
-
默认(没有修饰符):本类 ,本包可以访问。
注意:默认是空着不写,不是default
-
private:私有的,当前类可以访问。
public > protected > 默认 > private
public | protected | 默认 | private | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中的类 | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包中的无关类 | √ |
可见,public具有最大权限。private则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private
,隐藏细节。 - 构造方法使用
public
,方便创建对象。 - 成员方法使用
public
,方便调用方法。
小贴士:不加权限修饰符,就是默认权限
3.4 单继承 Object
刚才我们写的代码中,都是一个子类继承一个父类,那么有同学问到,一个子类可以继承多个父类吗?
Java语言只支持单继承,不支持多继承,但是可以多层继承。就像家族里儿子、爸爸和爷爷的关系一样:一个儿子只能有一个爸爸,不能有多个爸爸,但是爸爸也是有爸爸的。
public class Test {
public static void main(String[] args) {
// 目标:掌握继承的两个注意事项事项。
// 1、Java是单继承的:一个类只能继承一个直接父类;
// 2、Object类是Java中所有类的祖宗。
A a = new A();
B b = new B();
ArrayList list = new ArrayList();
list.add("java");
System.out.println(list.toString());
}
}
class A {} //extends Object{}
class B extends A{}
// class C extends B , A{} // 报错
class D extends B{}
3.5 方法重写
各位同学,学习完继承之后,在继承的基础之上还有一个很重要的现象需要给大家说一下。
叫做方法重写。为了让大家能够掌握方法重写,我们先认识什么是方法重写,再说一下方法的应用场景。
什么是方法重写
当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
注意:重写后,方法的访问遵循就近原则。下面我们看一个代码演示
例如:我们定义了一个动物类代码如下:
public class Animal {
public void run(){
System.out.println("动物跑的很快!");
}
public void cry(){
System.out.println("动物都可以叫~~~");
}
}
然后定义一个猫类,猫可能认为父类cry()方法不能满足自己的需求
代码如下:
public class Cat extends Animal {
public void cry(){
System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
}
}
public class Test {
public static void main(String[] args) {
// 创建子类对象
Cat ddm = new Cat();
// 调用父类继承而来的方法
ddm.run();
// 调用子类重写的方法
ddm.cry();
}
}
执行代码,我们发现真正执行的是Cat类中的cry方法
知道什么是方法重写之后,还有一些注意事项,需要和大家分享一下。
- 1.重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法
- 2.子类复写父类方法时,访问权限必须大于或者等于父类方法的权限
public > protected > 缺省
- 3. 重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小
- 4. 私有方法、静态方法不能被重写,如果重写会报错。
关于这些注意事项,同学们其实只需要了解一下就可以了。实际上我们实际写代码时,只要和父类写的一样就可以( 总结起来就8个字:声明不变,重新实现)
方法重写的应用场景
学习完方法重写之后,接下来,我们还需要大家掌握方法重写,在实际中的应用场景。方法重写的应用场景之一就是:子类重写Object的toString()方法,以便返回对象的内容。
比如:有一个Student类,这个类会默认继承Object类。
public class Student extends Object{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
其实Object类中有一个toString()方法,直接通过Student对象调用Object的toString()方法,会得到对象的地址值。
public class Test {
public static void main(String[] args) {
Student s = new Student("播妞", 19);
// System.out.println(s.toString());
System.out.println(s);
}
}
但是,此时不想调用父类Object的toString()方法,那就可以在Student类中重新写一个toSting()方法,用于返回对象的属性值。
package com.itheima.extends05;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 快捷键 toS 回车
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
重新运行测试类,结果如下
好了,到这里方法什么是方法重写,以及方法重写的应用场景我们就学习完了。
package com.itheima.extends06;
public class Dog {
public void eat(){
System.out.println("吃狗粮");
}
public void drink(){
System.out.println("狗舔水..");
}
public void lookDoor(){
System.out.println("狗子看家");
}
}
package com.itheima.extends06;
public class EnZuo extends Dog{
//不重写
//特有功能
public void chaiJia(){
System.out.println("二哈拆家");
}
}
package com.itheima.extends06;
public class PiPi extends Dog{
@Override
public void eat() {
super.eat();//调用父类的 eat方法
System.out.println("吃骨头");
}
}
package com.itheima.extends06;
public class BaJie extends Dog{
@Override
public void eat() {
System.out.println("吃剩饭..");
}
}
package com.itheima.extends06;
public class Demo {
public static void main(String[] args) {
PiPi pp = new PiPi();
pp.eat();
pp.drink();
pp.lookDoor();
}
}
3.6 继承后的特点—子类中访问成员的特点
各位同学,刚才我们已经学习了继承,我们发现继承至少涉及到两个类,而每一个类中都可能有各自的成员(成员变量、成员方法),就有可能出现子类和父类有相同成员的情况,那么在子类中访问其他成员有什么特点呢?
- 原则:在子类中访问其他成员(成员变量、成员方法),是依据就近原则的
定义一个父类,代码如下
package com.itheima.extends07;
public class Fu {
int num = 100 ;
public void method(){
System.out.println("父method");
}
}
再定义一个子类,代码如下。有一个同名的num成员变量,有一个同名的method成员方法;
package com.itheima.extends07;
public class Zi extends Fu{
int num = 200;
public void show(){
int num = 300;
System.out.println(num);//300
System.out.println(this.num);//200
System.out.println(super.num);//100
//访问method
this.method();
//父类的method
super.method();
}
@Override
public void method() {
System.out.println("子method");
}
}
接下来写一个测试类,观察运行结果,我们发现都是调用的子类变量、子类方法。
package com.itheima.extends07;
/*
理解
1:子类方法中访问成员的时候,遵循就近原则。
变量--局部位置找--成员位置--父类成员位置
方法--当前类中找---父类中找
2:
this表示当前对象,
通过它可以访问成员变量 成员方法
super 父类的标识
通过它可以访问 父类的成员变量 成员方法
*/
public class Demo {
public static void main(String[] args) {
//创建子类对象
Zi z = new Zi();
z.show();
}
}
- 如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分。
3.7 继承后的特点—构造方法
3.7.1 引入
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子)
继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法
3.7.2 案例演示
代码如下:
package com.itheima.extends08;
public class Fu {
public Fu(){
System.out.println("fu的无参构造~");
}
public Fu(String name){
System.out.println("父的有参构造");
}
}
package com.itheima.extends08;
public class Zi extends Fu{
public Zi(){
// 先执行了父类的无参构造...
// super();
// 调用父类的带参构造
super("haha");
System.out.println("子的无参构造");
}
public Zi(String name){
// 先执行了父类的无参构造
System.out.println("子的带参构造");
}
}
package com.itheima.extends08;
public class Demo {
/*
理解
1:子类构造执行时,默认会先执行父类的无参构造。
子类构造的第一行 默认是 super()
访问父类的构造
为什么有这个情况,因为我们想在子类对象构建的时候
先初始化父类成员。
所以在执行子类构造的时候,
调用了父类的无参构造。
2: 怎么调用父类的有参构造呢?
super() 无参构造
super(...)
*/
public static void main(String[] args) {
Zi zi1 = new Zi();
Zi zi2 = new Zi("jack");
}
}
3.7.3 小结
- 子类构造方法执行的时候,都会在第一行默认先调用父类无参数构造方法一次。
- 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。
3.8 super(…)和this(…)
3.8.1 引入
请看上节中的如下案例:
package com.itheima.thisuper;
public class Fu {
private String name;
public Fu(String name){
this.name = name;
}
}
package com.itheima.thisuper;
public class Zi extends Fu{
public Zi(){
//super() 访问父类有参
// super("哈哈");
this("哈哈");
}
public Zi(String name){
super(name);
}
}
我们发现,通过super(…)可以访问父类带参构造,this(…)可以访问本类的构造.
3.8.2 super和this的用法格式
super和this完整的用法如下,其中this,super访问成员我们已经接触过了。
this.成员变量 -- 本类的
super.成员变量 -- 父类的
this.成员方法名() -- 本类的
super.成员方法名() -- 父类的
接下来我们使用调用构造方法格式:
super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认
3.8.3 super(…)案例图解
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造七调用时,一定先调用父类的构造方法。理解图解如下:
3.8.4 小结
-
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
-
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
-
super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。
-
super(…)可以调用父类构造方法初始化继承自父类的成员变量的数据。
-
this(…)可以调用本类中的其他构造方法。
4. 关于今天知识的小结:
会写一个继承结构下的标准Javabean即可
-
经理
成员变量:工号,姓名,工资,管理奖金
成员方法:工作(管理其他人),吃饭(吃米饭)
-
厨师
成员变量:工号,姓名,工资
成员方法:工作(炒菜),吃饭(吃米饭)
分享书写技巧:
1.在大脑中要区分谁是父,谁是子
2.把共性写到父类中,独有的东西写在子类中
3.开始编写标准Javabean(从上往下写)
4.在测试类中,创建对象并赋值调用
代码示例:
员工类
package com.itheima.test;
/*
员工类 作为父类出现
*/
public class Employee {
private String id;//工号
private String name;//姓名
private double salary;//工资
//生成满参空参构造
public Employee() {
}
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
//生成set get方法
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//工作方法 吃饭方法
public void work(){
System.out.println("员工都在干活....");
}
public void eat(){
System.out.println("员工都在吃米饭!!!");
}
}
管理类
package com.itheima.test;
/*
经理类 继承 员工类
*/
public class Manager extends Employee {
//特有成员变量
private double bonus;
//写构造
// 子父类--产生了基础 父类还有成员变量
// 构造 子类无参调用父类无参 子类带参调用父类带参
// 只有这样才能实现数据的初始化
//alt+insert 找到构造 选择 空参的 select none
public Manager() {
}
//alt+insert 找到构造 选择带参 后面 ok
public Manager(String id, String name, double salary, double bonus) {
super(id, name, salary);//三个给父类
this.bonus = bonus;//一个给自己
}
//set get
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
//重写 work
@Override
public void work() {
System.out.println(getName()+"正在管理其他人..."+"管理奖金:"+getBonus());
}
}
厨师类
package com.itheima.test;
/*
作为子类 继承自 父类员工
*/
public class Cooker extends Employee{
//无参和带参
public Cooker() {
}
public Cooker(String id, String name, double salary) {
super(id, name, salary);//调用父类带参构造
}
@Override
public void work() {
System.out.println("工号为"+getId()+"厨师:"+getName()+"正在炒菜...."+"工资是:"+getSalary());
}
}
测试类
package com.itheima.test;
public class Test {
public static void main(String[] args) {
Cooker cooker = new Cooker("9527","华安",1000);
cooker.work();
//测试 经理
Manager m = new Manager("001", "吴宇航", 100000, 1000000);
m.work();
}
}