oop特征之二三:继承性、多态性
目录1、继承性的好处:
①减少了代码的冗余
②便于功能的扩展
③为了之后多态性的使用提供了前提
2、继承性的格式:class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
①体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有属性、方法。
特别的,父类中声明为private的属性和方法,子类继承父类以后,仍认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已,可以用父类get、set方法来调用
②子类继承父类以后,还可以声明自己特有的属性和方法,实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。
3、Java中关于继承性的规定:
①一个类可以被多个子类继承。
②Java中类的单继承性:一个类只能有一个父类。
③子父类是相对的概念,可以多重继承。
④子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
⑤子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
4、①如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
②所有的java类(出java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
③意味着,所有的java类具有java.lang.Object类声明的功能。
在子类当中可以根据需要对从父类中继承来的方法进行改造,也成为方发的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
方法的声明:权限修饰符 返回值类型 方法名(形参列表){}
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表;
子类重写的方法返回值不能大于父类方法被重写的方法的返回值类型
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(比如double),则子类重写的方法的返回值类型必须是相同的
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
注:子类不能重写父类中声明为private权限的方法
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
我们可以在子类的方法或构造器中。通过使用“super.属性”或”super.方法“的方式,显式的调用父类中声明的属性或方法。但通常情况下,我们习惯省略“super.”
特殊情况下,当子类和父类中定义了同名的属性或重写了父类方法时,我们要想在子类中调用父类中声明的属性或被重写方法,则必须显式的使用“super.属性”和“super.方法"的方式,声明调用的是父类中声明的属性或被重写方法。
只能写在构造器的第一行
因为无论通过哪个构造器创建子类对象,需要保证先初始化父类。
目的:当子类继承父类后,“继承”父类中所有属性的方法,因此子类有必要知道父类如何为对象进行初始化
从结果上看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
从过程来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接地调用父类的构造器,进而调用父类的父类的构造器....以此类推,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类的结构,子类对象才可以考虑调用。
虽然创建子类对象时调用了父类的构造器,但自始至终就创建了一个对象,即子类的对象。
package com.atguigu.exer2; /** * @Auther: zgq * @Date: 2021/10/21 - 10 - 21 -14:33 * @Description: com.atguigu.exer2 * @version: 1.0 * */ public class Account { private int id;//账号 private double balance;//余额 private double annualInterestRate;//年利率 public Account(int id,double balance,double annualInterestRate){ super();//即便不显式的写出super(),构造器第一行也必然会非显式的存在一个 this.id = id; this.balance = balance; this.annualInterestRate = annualInterestRate; } public int getId() { return id; } public double getBalance() { return balance; } public double getAnnualInterestRate() { return annualInterestRate; } public void setId(int id) { this.id = id; } public void setBalance(double balance) { this.balance = balance; } 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; return; } System.out.println("输入有误!"); } }
package com.atguigu.exer2; /** * @Auther: zgq * @Date: 2021/10/21 - 10 - 21 -14:58 * @Description: com.atguigu.exer2 * @version: 1.0 * 创建Account类的一个子类CheckAccount代表可透支的账户,定义属性overdraft代表可透支限额 * 在子类中重写Account方法如下: * 如取款额<余额,直接取款 * 如取款额>余额,计算透支额度 * 判断可透支额overdraft是否足够支付本次透支需要,如可以 * 将账户余额改为0,冲减可透支金额 * 如不可以 * 提示用户超过可透支的限额 */ public class CheckAccount extends Account{ //父类Account没有定义空参构造器,所以会报错(因为默认的super()为空参) // 解决办法1、在父类中定义一个空参构造器 // 解决办法2、调用父类中的有参构造器,这里使用方法2 private double overdraft;//可透支额度 public CheckAccount(int id,double balance,double annualInterestRate,double overdraft){ super(id,balance,annualInterestRate); this.overdraft = overdraft; } public double getOverdraft() { return overdraft; } @Override public void withdraw(double amount) { //余额足够时,扣除余额 if(getBalance() >= amount){ //方法一 //setBalance(getBalance() - amount); //方法二 super.withdraw(amount); } //余额不足,透支额度加上余额足够时,扣除余额,扣除透支额度 else if((overdraft + getBalance()) >= amount) { amount -= getBalance(); overdraft -= amount; super.withdraw(getBalance()); } else{ System.out.println("超过可透支限额!"); } } }
package com.atguigu.exer2; import com.sun.jmx.snmp.internal.SnmpOutgoingRequest; /** * @Auther: zgq * @Date: 2021/10/21 - 10 - 21 -15:34 * @Description: com.atguigu.exer2 * @version: 1.0 * 创建一个账户为1122、余额为2000、年利率4.5%,可透支额度为5000元的CheckAccount对象。 * 使用withdraw方法提款5000元,并打印余额和可透支额度 * 使用withdraw方法提款18000元,并打印余额和可透支额度 * 使用withdraw方法提款3000元,并打印余额和可透支额度 */ public class CheckAccountTest { public static void main(String[] args) { CheckAccount acct = new CheckAccount(1122,20000,0.045,5000); acct.withdraw(5000); System.out.println("余额为: " + acct.getBalance() + "元\t" + "可透支额度为:" + acct.getOverdraft() + "元"); acct.withdraw(18000); System.out.println("余额为: " + acct.getBalance() + "元\t" + "可透支额度为:" + acct.getOverdraft() + "元"); acct.withdraw(3000); System.out.println("余额为: " + acct.getBalance() + "元\t" + "可透支额度为:" + acct.getOverdraft() + "元"); } }
可以理解为一个事物的多种形态。
对象的多态性:父类的引用指向子类的对象(子类的对象赋给父类的引用)
Person p1 = new Men();//父类Person,子类Men
①类的继承关系②方法的重写
以例1为例,Person类中属性id=1;Men类中属性id=2,当我们在test类中调用p1.id,对象的值应该是Person类中id的值1,而非子类中的值2。
当我们使用了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的方法,子类特有的属性和方法不能调用。
那么如何才能使用子类中特有的属性和方法呢?
这里就需要使用强制类型转换符,即向下转型。
以例一为例:
Man m2 = (Man)p1;
将p1强转为Man,将地址赋给m2,此时p2就可以调用子类Man中特有的属性和方法。
向下转型只能将父类转为子类,如果两个类不存在子父类关系或将子类强转为父类都会在运行时出现ClassCastException的异常。
例如:
Woman w3 = (Woman)p1;
instanceof的使用
a instanceof A:判断对象a是否是类A的实例。如果是,返回ture,否则返回false。
使用情景:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回ture,就进行向下转型;返回false,不进行向下转型。
如果a instanceof A返回ture,且类B是类A的父类,则a instanceof B也返回ture。
package com.atguigu.exer3; /** * @Auther: zgq * @Date: 2021/10/22 - 10 - 22 -14:58 * @Description: com.atguigu.exer3 * @version: 1.0 * 1、若子类重写了父类的方法,就意味着子类中定义的方法彻底覆盖 * 了父类例的同名方法,系统不可能把父类里的方法转移到子类中。(编译看左边,运行看右边) * 2、对于实例变量则不存在这样的现象,即使子类中定义了与父类完 * 全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。(编译运行都看左边) */ public class FieldMethodTest { public static void main(String[] args) { Sub s = new Sub(); System.out.println(s.count);//20 s.display();//20 Base b = s; System.out.println(b == s);//ture,地址值一致 System.out.println(b.count);//10,多态性只适用于方法,不适用于属性 b.display();//20 } } class Base{ int count = 10; public void display(){ System.out.println(this.count); } } class Sub extends Base{ int count = 20; public void display(){ System.out.println(this.count); } }
public class InterviewTest { public static void main(String[] args) { Base1 base = new Sub1(); base.add(1,2,3);//Sub_1 Sub1 s = (Sub1)base; s.add(1,2,3);//Sub_2 } } class Base1{ public void add(int a, int... arr){ System.out.println("Base1"); } } class Sub1 extends Base1{ public void add(int a, int[] arr){//int[] arr与int... arr是一样的,所有构成重写 System.out.println("Sub_1"); } public void add(int a, int b, int c){//与int... arr不构成重写 System.out.println("Sub_2"); } }