文章目录
PPT链接:点击这里
1、面向对象特征之二:继承性
1.1、继承性的理解
为什么要有继承
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么,多个类无需再定义这些属性和行为,只要继承那个类即可
继承性的好处
- 减少了代码的冗余,提高了代码的复用性
- 便于功能的扩展
- 为之后多态性的使用,提供了前提
继承性的格式
class A extends B{
}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
-
体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的结构:属性、方法。特别的,父类中声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只有因为封装性的影响,使得子类不能直接调用父类的结构而已
-
子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展
-
子类和父类的关系:不同于子集与集合的关系
Java 中关于继承性的规定
-
一个类可以被多个类继承
-
Java 中类的单继承性:一个类只能有一个父类
-
子父类是相对的概念
-
子类直接继承的父类,称为直接父类。间接继承的父类,称为间接父类
-
子类继承父类后,就获取了直接父类以及所有间接父类中声明的属性和方法
说明
- 如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object 类
- 所有的 java 类(除 java.long.Object 类之外)都直接或间接地继承于 java.lang.Object 类
- 意味着,所有的 java 类具有 java.lang.Object 类声明的功能
继承的规则
![image-20220802184254889](https://i-blog.csdnimg.cn/blog_migrate/02be894011b6e0a1ce6a878708baf95b.png)
单继承与多层继承
![](https://i-blog.csdnimg.cn/blog_migrate/7348a434d1507f47619b874060b1b577.png)
1.2、练习
(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,用该对象访问其父类的成员变量及方法
【代码实现】
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(){
// if(salary == 0){
// System.out.println("no job");
// }else{
// System.out.println("job");
// }
//或
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;
}
}
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;
}
/*
* 修改练习中定义的类Kids,在Kids中重新定义employeed()方法,
* 覆盖父类ManKind中定义的employeed()方法,输出“Kids should study and no job.”
*/
public void employeed() {//方法重写
System.out.println("Kids should study and no job.");
}
}
public class KidsTest {
public static void main(String[] args) {
Kids someKid = new Kids(12);
someKid.printAge();//I am 12 years old.
someKid.setSalary(0);
someKid.setSex(1);
someKid.employeed();//Kids should study and no job.
someKid.manOrWoman();//man
}
}
2、方法的重写(override)
- 重写:子类继承父类以后,可以对父类中的方法进行覆盖操作
- 应用:重写以后,当创建子类对象以后,通过子类对象去调用子父类中同名同参数方法时,执行的是子类重写父类的方法。即在程序执行时,子类的方法将覆盖父类的方法
重写的规定
-
方法的声明:
//1.直接将父类的方法的第一行粘过来,直接写方法体 public void walk(int distance){ System.out.println("重写的方法"); } //2.直接输入父类的方法名,Alt + /,选择即可生成 @Override public void walk(int distance) { System.out.println("自动生成"); }
-
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
注意事项
-
子类重写的方法的方法名和形参列表必须和父类被重写的方法的方法名和形参列表相同
-
子类重写的方法使用的访问权限 大于等于 父类被重写的方法的访问权限(权限:子类 >= 父类)
特殊情况:子类不能重写父类中声明为 private 权限的方法
-
返回值类型
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值类型如果是基本数据类型(比如double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须是double)
-
子类方法抛出的异常 小于等于 父类被重写的方法抛出的异常(异常:子类 <= 父类)
注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法 ----- 要想被继承,父类方法中就不能用final、private、static来修饰
面试题:区分方法的重载与重写
- 方法重载(Overloading)是一个类中定义了多个方法名相同,而他们的参数的数量不同 或 数量相同而类型和次序不同
- 方法重写(Overriding)是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法
- 方法的重写和重载是java多态性的不同表现,方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
3、四种访问权限修饰符
Java 权限修饰符 public、protected、default(缺省)、private
置于类的成员定义前,用来限定对象对该类成员的访问权限
对于 class类 的权限修饰只可以用 public 和 default(缺省)
-
public 类可以在任意地方被访问
-
default 类只可以被同一个包内部的类访问
package com.atguigu.java2;
public class Order {
private int orderPrivate;
int orderDefault;
protected int orderProtected;
public int orderPublic;
private void methodPrivate(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
void methodDefault(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
protected void methodProtected(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
public void methodPublic(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
}
同一个包的不同类
package com.atguigu.java2;
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
order.orderDefault = 1;
order.orderProtected = 2;
order.orderPublic = 3;
order.methodDefault();
order.methodProtected();
order.methodPublic();
//同一个包中的其他类,不可以调用Order类中私有的属性、方法
// order.orderPrivate = 4;
// order.methodPrivate();
}
}
不同的包下的子类
package com.atguigu.java3;
import com.atguigu.java2.Order;//用不同的包下的类
public class SubOrder extends Order {
public void method(){
orderProtected = 1;
orderPublic = 2;
methodProtected();
methodPublic();
//在不同包的子类中,不能调用Order类中声明为private和缺省权限的属性、方法
// orderDefault = 3;
// orderPrivate = 4;
// methodDefault();
// methodPrivate();
}
}
不同的包下的非子类
package com.atguigu.java3;
import com.atguigu.java2.Order;
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
order.orderPublic = 1;
order.methodPublic();
//不同包下的普通类(非子类)要使用Order类,不可以调用声明为private、缺省、protected权限的属性、方法
// order.orderPrivate = 2;
// order.orderDefault = 3;
// order.orderProtected = 4;
// order.methodPrivate();
// order.methodDefault();
// order.methodProtected();
}
}
4、super关键字
4.1、super的使用
- super理解为:父类的
- super可以用来调用:属性、方法、构造器
super的使用
- 我们可以在子类的方法或构造器中,通过"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯去省略这个"super."
- 特殊情况1:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性
- 特殊情况2:当子类重写了父类中的方法后,我们想在子类的方法中调用父类中被重写的方法时,必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法
super调用构造器
- 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- "super(形参列表)"的使用,必须声明在子类构造器的首行
- 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
- 在构造器的首行,既没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认的调用的是父类中的空参构造器:super()
- 在类的多个构造器中,至少有一个类的构造器使用了"super(形参列表)",调用父类中的构造器
注意:当子类继承父类时,因为子类默认有一个空参构造器,而子类的空参构造器中默认调用super() (父类的空参构造器),如果父类中只有带参构造器而没有空参构造器,则会报错
- 解决1:父类中写空参构造器
- 解决2:子类中写有参构造器,并且不用super(),而是让其调用指定的父类构造器super(形参1,形参2)
public class Person {
String name;
int age;
int id = 1001;
public Person(){
System.out.println("我无处不在");
}
public Person(String name){
this.name = name;
}
public Person(String name,int age){
this(name);
this.age = age;
}
public void eat(){
System.out.println("人,吃饭");
}
public void walk(){
System.out.println("人,走路");
}
}
public class Student extends Person{
String major;
int id = 1002;//学号
public Student(){
}
public Student(String name,int age,String major){
// this.age = age;
// this.name = name;
super(name,age);//调用父类的指定的构造器
this.major = major;
}
public Student(String major){
this.major = major;
}
public void eat(){
System.out.println("学生多吃有营养的食物");
}
public void Study(){
System.out.println("学生,学习知识。");
this.eat();
super.eat();//调用父类中被重写的方法,不调用子类中的方法
super.walk();//子父类中未重写的方法,用"this."或"super."调用都可以
}
public void show(){
System.out.println("name = " + this.name + ",age = " + super.age);
System.out.println("id = " + this.id);
System.out.println("id = " + super.id);
}
}
this和super的区别
this.
:先在子类找属性,找不到时再从父类或间接父类中找属性
super.
:先在父类找属性,找不到时再从其父类或间接父类中找属性
4.2、练习
【代码实现】
/*
* 写一个名为Account的类模拟账户。该类的属性和方法如下图所示。
* 该类包括的属性:账号id,余额balance,年利率annualInterestRate;
* 包含的方法:访问器方法(getter和setter方法),
* 返回月利率的方法getMonthlyInterest(),
* 取款方法withdraw(),存款方法deposit()。
*/
public class Account {
private int id; //账号
private double balance; //余额
private double annualInterestRate; //年利率
public Account(int id, double balance, double annualInterestRate) {
super();
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public double getAnnualInterestRate() {
return annualInterestRate;
}
public void setAnnualInterestRate(double annualInterestRate) {
this.annualInterestRate = annualInterestRate;
}
public double getMonthlyInterest(){ //返回月利率的方法
return annualInterestRate / 12;
}
public void withdraw (double amount){ //取款方法
if(balance >= amount){
balance -= amount;
return;
}
System.out.println("余额不足");
}
public void deposit (double amount){ //存款方法
if(amount > 0){
balance += amount;
}
}
}
/*
* 写一个用户程序测试Account类。在用户程序中,
* 创建一个账号为1122、余额为20000、年利率4.5%的Account对象。
* 使用withdraw方法提款30000元,并打印余额。再使用withdraw方法提款2500元,
* 使用deposit方法存款3000元,然后打印余额和月利率。
*/
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account(1122,20000,0.045);
acct.withdraw(30000);
System.out.println("你的账户余额为:" + acct.getBalance());
acct.withdraw(2500);
System.out.println("你的账户余额为:" + acct.getBalance());
acct.deposit(3000);
System.out.println("你的账户余额为:" + acct.getBalance());
System.out.println("月利率为: " + (acct.getAnnualInterestRate() * 100) + "%");
}
}
/*
* 创建Account类的一个子类CheckAccount代表可透支的账户,该账户中定义一个属性overdraft代表可透支限额。
* 在CheckAccount类中重写withdraw方法,其算法如下:
* 如果(取款金额<账户余额),可直接取款
* 如果(取款金额>账户余额),计算需要透支的额度,判断可透支额overdraft是否足够支付本次透支需要
* 如果可以
* 将账户余额修改为0,冲减可透支金额
* 如果不可以
* 提示用户超过可透支额的限额
*/
public class CheckAccount extends Account{
private double overdraft; //代表可透支限额
public CheckAccount(int id, double balance, double annualInterestRate,double overdraft){
// this.id = id;
// this.balance = balance;
// this.annualInterestRate = annualInterestRate;
super(id, balance, annualInterestRate);//只能用super调用父类构造器这样写,上面写法错误,因为3个变量已经私有化
this.overdraft = overdraft;
}
public double getOverdraft() {
return overdraft;
}
public void setOverdraft(double overdraft) {
this.overdraft = overdraft;
}
@Override
public void withdraw(double amount) {
if(getBalance() >= amount){//余额足够消费
//方式一
// setBalance(getBalance() - amount);
//方式二
super.withdraw(amount);//理解:用super.withdraw()调用父类中的取钱方法,直接改余额
}else if(overdraft >= amount - getBalance()){//余额不够,能透支
overdraft -= (amount - getBalance());//注意:setBalance()写下面,否则getBalance()是0
// setBalance(0);
//或
super.withdraw(getBalance());//取光钱
}else{//超过可透支限额
System.out.println("超过可透支限额!");
}
}
}
/*
* 写一个用户程序测试CheckAccount类。在用户程序中,
* 创建一个账号为1122、余额为20000、年利率4.5%,
* 可透支限额为5000元的CheckAccount对象。
* 使用withdraw方法提款5000元,并打印账户余额和可透支额。
* 再使用withdraw方法提款18000元,并打印账户余额和可透支额。
* 再使用withdraw方法提款3000元,并打印账户余额和可透支额。
*
*/
public class CheckAccountTest {
public static void main(String[] args) {
CheckAccount cat = new CheckAccount(1122,20000,0.045,5000);
cat.withdraw(5000);
System.out.println("您的账户余额为: " + cat.getBalance());
System.out.println("您的可透支额度为: " + cat.getOverdraft());
cat.withdraw(18000);
System.out.println("您的账户余额为: " + cat.getBalance());
System.out.println("您的可透支额度为: " + cat.getOverdraft());
cat.withdraw(3000);
System.out.println("您的账户余额为: " + cat.getBalance());
System.out.println("您的可透支额度为: " + cat.getOverdraft());
}
}
5、子类对象实例化过程
从结果上看(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法
- 创建子类的对象中,在堆空间中,就会加载所有父类中声明的属性
从过程上看
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类结构,所以才可以看到内存中有父类中的结构,子类对象可以考虑进行调用
明确:虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象
物竞天择,适者生存,加油吧!!!