文章目录
- 一、继承的概述
- (1)生活中的继承
- (2) Java中的继承
- 1、角度一:从上而下
- 2、角度二:从下而上
- (3)继承的好处
- (4)总结
- 二、继承的语法与应用举例
- (1)继承的语法
- 1、继承中的语法格式
- 2、继承中的基本概念
- 3、引入
- 4、举例
- 5、注意
- 6、总结
- (2)继承性的细节说明
- 三、 练习
- (1)练习1
- (2)练习2
一、继承的概述
(1)生活中的继承
- 财产继承
- 绿化:前人栽树,后人乘凉
- 样貌
- 继承之外,还可以"
进化
"
继承有延续(下一代延续上一代的基因、财富)、扩展(下一代和上一代又有所不同)的意思。
父类中的一些功能,子类可以直接继承过来使用,同样子类也可以定义自己特有的功能。
(2) Java中的继承
1、角度一:从上而下
为描述和处理个人信息,定义类Person
:
!
为描述和处理学生信息,定义类Student
:
“个人”与“学生”有这样包含的关系:
可以通过继承,简化Student类的定义:
让Student通过关键字extends
继承于Person类,继承之后,在Person类中声明的属性(比如name,age,birthDate)在Student类中不用再写了,Student类可以直接获取Person类里面的属性,包括方法。
说明:Student类继承了父类Person的所有属性和方法,并增加了一个属性school。Person中的属性和方法,Student都可以使用。
2、角度二:从下而上
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成继承关系
。如图所示:
“猫”与“狗”之间的关系:
再举例:
(3)继承的好处
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了
is-a
的关系,为多态的使用提供了前提。 (比如Student–子类 is a Person–父类)- 继承描述事物之间的所属关系,这种关系是:
is-a
的关系。可见,父类更通用、更一般,子类更具体。
- 继承描述事物之间的所属关系,这种关系是:
注意:不要仅为了获取其他类中某个功能而去继承!比如猫能吃喝,人也能吃喝,不要让人继承了猫,根本不是一个种类!!!
(4)总结
继承性的理解
①生活上:财产的继承、颜值的继承
②代码层面:
<1> 自上而下:定义了一个类A,在定义另一个类B时,发现类B的功能与类A相似,考虑类B–子类继承于类A–父类(B->A),先有父类,后有子类。
<2> 自下而上:定义了类B,C,D等,发现B、C、D有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,封装到类A中,让类B、C、D继承于类A,同时类B、C、D中的相似的功能就可以删除了。
二、继承的语法与应用举例
(1)继承的语法
1、继承中的语法格式
通过 extends
关键字,可以声明一个类B继承另外一个类A,定义格式如下:
[修饰符] class 类A {
...
//属性、方法
}
[修饰符] class 类B extends 类A { //B继承了A(B是A的子类)
...
}
2、继承中的基本概念
①类A
,称为父类、超类、基类(base class)、SuperClass
②类B
,称为子类、派生类(derived class)、SubClass
3、引入
之前写代码都是这样:
【Person.java】
public class Person {
//属性
String name;
int age;
//方法
public void eat(){
System.out.println("人吃饭");
}
public void sleep(){
System.out.println("人睡觉");
}
}
【Student.java】
public class Student {
//属性
String name;
int age;
String school;
//方法
public void eat(){
System.out.println("人吃饭");
}
public void sleep(){
System.out.println("人睡觉");
}
public void study(){
System.out.println("学生学习");
}
}
【ExtendsTest.java】
public class ExtendsTest {
public static void main(String[] args) {
Person p1=new Person();
p1.name="Tom";
p1.eat();
Student s1=new Student();
s1.name="Jack";
s1.eat();
}
}
若此时将Student类中的name、age属性和eat()、sleep()方法注释调,那么在测试类里面就会报错,如下:
此时将Student继承于Person类即可:
运行也不会有什么问题:
4、举例
1.父类
package com.atguigu.inherited.grammar;
/*
* 定义动物类Animal,做为父类
*/
public class Animal {
// 定义name属性
String name;
// 定义age属性
int age;
// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的"
+ name + "在吃东西");
}
}
2.子类
package com.atguigu.inherited.grammar;
/*
* 定义猫类Cat 继承 动物类Animal
*/
public class Cat extends Animal {
int count;//记录每只猫抓的老鼠数量
// 定义一个猫抓老鼠的方法catchMouse
public void catchMouse() {
count++;
System.out.println("抓老鼠,已经抓了"
+ count + "只老鼠");
}
}
3.测试类
package com.atguigu.inherited.grammar;
public class TestCat {
public static void main(String[] args) {
// 创建一个猫类对象
Cat cat = new Cat();
// 为该猫类对象的name属性进行赋值
cat.name = "Tom";
// 为该猫类对象的age属性进行赋值
cat.age = 2;
// 调用该猫继承来的eat()方法
cat.eat();
// 调用该猫的catchMouse()方法
cat.catchMouse();
cat.catchMouse();
cat.catchMouse();
}
}
5、注意
1.通过让子类(Student)继承于父类(Person),子类就获取到了父类中声明的属性和方法。
2.子类可以调用父类中的构造器,不认为直接在子类中加载父类的构造器,因为构造器要与类同名,在子类Person中声明的构造器叫Person是不合适的。
3.引入继承之后,Student类里面虽然只声明了一个属性school,但是在创建Student对象时。堆空间仍然会有三个属性(包括父类Person类的属性)。如下:
4.【调试】
我们也可以调试看一下,
首先在Student s1=new Student();
这一行左边点一下,出现一个红色的点(断点);然后在上方点击“调试”:(不想要断点可以再点击一下红点位置)
然后:
往下执行一行:
这里显示了属性,可以看到有三个属性:
5.封装性问题
私有的属性只能在本类中使用,子类中不能使用
有了继承,封装性不会被打破。
比如此时让Person类(父类)的age属性私有化,在Student类(子类)中就不能直接调用这个私有属性了,如下:
但是Student子类还是拥有了父类的所有属性,只是不能直接调用而已,可以调试一下看看,还是有age属性的。
如下:
当然,p1自己也不能调用这个属性,都受到封装性的影响:
若想使用这个私有属性,那就只能提供它的set/get方法:
有了封装性可以拿到属性,只是不能访问而已。
6.默认的父类
Java中声明的类,如果没有显式的声明其父类时,则默认继承于java.lang.Object
比如调用p1的方法,可以看到还可以调用不在Person类里面的方法:(红色部分)
可以调用一下toString
返回一个字符串,默认打印对象的地址,如下:
toString
方法在Person类中并没有被定义,既然这里能够调用,那么一定来自父类。
想要知道这个方法来自哪里,可以按住Ctrl
键点击这个方法,然后就会跳转到这个类中:
所以Person类的父类是Object。
当然,也可以验证一下。
以Student类为例,获取它所属的类:s1.getClass();
然后再去获取它的父类:s1.getClass().getSuperclass();
(就是Student类的父类是谁)
可以发现,父类就是Person类:
还可以看一下Person类的父类:p1.getClass().getSuperclass();
如下:
6、总结
有了继承性以后:
①子类就获取到了父类中声明的所有的属性和方法。
但是,由于封装性的影响,可能子类不能直接调用父类中声明的属性或方法。
②子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法)
extends:延展、扩展、延伸
③子类和父类的理解,要区别于集合和子集。
④不要为了继承而继承。在继承之前,判断一下是否有is a
的关系。
(2)继承性的细节说明
1、子类会继承父类所有的实例变量和实例方法
从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。
- 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
- 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循
从下往上
找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。
所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。
2、子类不能直接访问父类中私有的(private)的成员变量和方法
子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。
如图所示:
3、在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”
子类在继承父类以后,还可以定义自己特有的方法,这就可以看做是对父类功能上的扩展。
4、Java支持多层继承(继承体系)
Student是Graduate的直接父类,Person是Graduate的间接父类。
- Java是支持多层继承。(可以有子类,子类也可以再有父类)
- 概念:直接父类、间接父类
- Java中的子父类的概念是相对的,Student相对于Person是子类,相对于Graduate是父类。
- Java中一个父类可以声明多个子类。反之,一个子类只能有一个父类(Java的单继承性–类的层面,后边接口可以有多个父接口,关键字也是extends)
- 类–单继承,接口–多继承
class A{}
class B extends A{}
class C extends B{}
说明:
- 子类和父类是一种相对的概念
- 顶层父类是Object类。所有的类默认继承Object,作为父类。
- 一个父类可以被多个子类所继承,但是一个子类不能有多个父类
5、一个父类可以同时拥有多个子类
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
6、Java只支持单继承,不支持多重继承
public class A{}
class B extends A{}
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} //ok
class C extends A,B... //error
三、 练习
(1)练习1
🌋题目描述
(1)定义一个ManKind类,包括
- 成员变量int sex和int salary;
- 方法void manOrWoman():根据sex的值显示“man”(
sex==1
)或者“woman”(sex==0
); - 方法void employeed():根据salary的值显示“no job”(
salary==0
)或者“ job”(salary!=0
)。
(2)定义类Kids继承ManKind,并包括
- 成员变量int yearsOld;
- 方法printAge()打印yearsOld的值。
(3)定义类KidsTest,在类的main方法中实例化Kids的对象someKid,用该对象访问其父类的成员变量及方法。
🤺代码
父类中声明的属性和方法都被继承到子类了,构造器就不提了。后边提super关键字的时候会提到,在子类当中调用父类中的构造器。
【 Mankind.java】
package yuyi05;
/**
* ClassName: Mankind
* Package: yuyi05
* Description:
* (1)定义一个ManKind类,包括
* 成员变量int sex和int salary;
* - 方法void manOrWoman():根据sex的值显示“man”(sex==1)或者“woman”(sex==0);
*
* - 方法void employeed():根据salary的值显示“no job”(salary==0)或者“ job”(salary!=0)。
* @Author 雨翼轻尘
* @Create 2023/10/30 0030 10:32
*/
public class Mankind {
//属性
private int sex;
private int salary;
//方法
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;
}
public void manOrWoman(){
if(sex==1){
System.out.println("man");
} else if (sex==0) {
System.out.println("woman");
}
}
public void employeed(){
if(salary==0){
System.out.println("no job");
} else {
System.out.println("job");
}
}
//构造器
public Mankind() {
}
public Mankind(int sex, int salary) {
this.sex = sex;
this.salary = salary;
}
}
【Kids.java】
package yuyi05;
/**
* ClassName: Kids
* Package: yuyi05
* Description:
* (2)定义类Kids继承ManKind,并包括
* - 成员变量int yearsOld;
* - 方法printAge()打印yearsOld的值。
* @Author 雨翼轻尘
* @Create 2023/10/30 0030 10:56
*/
public class Kids extends Mankind { //父类中声明的属性和方法都被继承到子类了,构造器就不提了。后边提super关键字的时候会提到,在子类当中调用父类中的构造器
private int yearOld;
public int getYearOld() {
return yearOld;
}
public void setYearOld(int yearOld) {
this.yearOld = yearOld;
}
public void printAge(){
System.out.println("I am "+yearOld+" years old");
}
//构造器
public Kids(){
}
public Kids(int yearOld){
this.yearOld=yearOld;
}
//把父类中的属性也做一个赋值,包括自己的属性
public Kids(int sex, int salary,int yearOld){
this.yearOld=yearOld;
//sex、salary两个 属性是父类继承过来的,怎么给他们赋值?
setSex(sex);
setSalary(salary);
}
}
【KidsTest.java】
package yuyi05;
/**
* ClassName: KidsTest
* Package: yuyi05
* Description:
*(3)定义类KidsTest,在类的main方法中实例化Kids的对象someKid,用该对象访问其父类的成员变量及方法。
* @Author 雨翼轻尘
* @Create 2023/10/30 0030 10:58
*/
public class KidsTest {
public static void main(String[] args) {
Kids someKid=new Kids();
someKid.setSex(1);
someKid.setSalary(100);
someKid.setYearOld(12);
//Kids类自己声明的方法
someKid.printAge();
//来自于父类中声明的方法
someKid.manOrWoman();
someKid.employeed();
}
}
👻运行结果
⚡注意
1.父类中声明的属性和方法都被继承到子类了,构造器就不提了。后边提super关键字的时候会提到,在子类当中调用父类中的构造器怎么调用。
目前只能通过ManKind类中的方法去调用ManKind类中的属性,如下:
public class Kids extends Mankind {
private int yearOld;
//...
//构造器
//把父类中的属性也做一个赋值,包括自己的属性
public Kids(int sex, int salary,int yearOld){
this.yearOld=yearOld;
//sex、salary两个 属性是父类继承过来的,怎么给他们赋值?
setSex(sex);
setSalary(salary);
}
}
2.赋值过程
public class KidsTest {
public static void main(String[] args) {
Kids someKid=new Kids();
someKid.setSex(1);
someKid.setSalary(100);
someKid.setYearOld(12);
}
}
对象刚创建好:
执行结束:
(2)练习2
🌋题目描述
根据下图实现相关的类。
在CylinderTest类中创建Cylinder类的对象,设置圆柱的底面半径和高,并输出圆柱的体积。
🤺代码
【Circle.java】
package yuyi06;
/**
* ClassName: Circle
* Package: yuyi06
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/31 0031 10:07
*/
public class Circle {
//属性
private double radius; //半径
//方法
public void setRadius(double radius){
this.radius=radius;
}
public double getRadius(){
return radius;
}
//求圆的面积
public double findArea(){
return Math.PI*radius*radius;
}
//构造器
public Circle(){
radius=1;
}
}
【Cylinder.java】
package yuyi06;
/**
* ClassName: Cylinder
* Package: yuyi06
* Description:
* 圆柱类
* @Author 雨翼轻尘
* @Create 2023/10/31 0031 10:19
*/
public class Cylinder extends Circle{
//属性
private double length; //高
//方法
public void setLength(double length){
this.length=length;
}
public double getLength(){
return length;
}
//求圆柱的体积
public double findVolume(){
//return Math.PI*getRadius()*getRadius()*getLength();
return findArea()*getLength();
}
//构造器
public Cylinder(){
length=1;
}
}
【CylinderTest.java】
package yuyi06;
/**
* ClassName: CylinderTest
* Package: yuyi06
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/31 0031 10:29
*/
public class CylinderTest {
public static void main(String[] args) {
Cylinder cy=new Cylinder();
cy.setRadius(2.3);
cy.setLength(1.4);
System.out.println("圆柱的体积为: "+cy.findVolume());
System.out.println("圆柱的底面积为: "+cy.findArea());
}
}
👻运行结果
⚡注意
调试:
Cylinder是我们声明的构造器,这个构造器里面,我们将高length设置为了1,如下:
public Cylinder(){
length=1;
}
而在内存中,可以发现半径radius也是1.0。这里肯定调用了Circle构造器。
public Circle(){
radius=1;
}
Cylinder是子类对象,子类对象在创建的时候,要调用父类的构造器,这个后边再说。
然后通过set属性给两个属性赋值: