Java面向对象
编辑时间:2021/03/03
读完本节:大概花费37分钟,共3768词
1.面向对象之继承
-
继承性的优点:
①减少了代码的冗余,提高了代码的复用性
②便于子类功能的扩展
③为之后的多态性的使用提供了基础
-
继承性的格式
class A extends B{}
-
子类与父类:子类、派生类、subclass;父类、超类、基类、superclass
-
继承性的体现:一旦子类A继承了父类B以后,子类A中就获取了父类B中声明的所有属性和方法;
-
在父类中声明为private的属性和方法,子类继承了父类以后,仍然认为获取了父类中的私有结构,只是因为封装性的影响,使得子类不能直接调用这些被封装的属性和方法。
-
子类继承父类以后,还可以声明子类自己所特有的属性或方法,实现功能的拓展。子类与父类的关系不能等同于数学中子集与集合的概念
-
Java类只支持单继承、多层继承,不允许多重继承;一个子类只有一个直接父类;一个父类可以派生出多个子类;
-
子父类是相对的关系,子类直接继承的类,叫直接父类;子类间接继承的类叫间接父类。如上图父类1是子类1.1.1的间接父类。子类继承了父类以后就获取了直接父类以及所有间接父类中声明的属性和方法
-
一个类如果没有显示声明一个类作为父类,则这个类会默认继承于java.lang.Object类。
-
所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类,换句话来说,所有类都有父类。
2.方法的重写(Override、Overwrite)
-
定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也成为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
-
重写的要求:
-
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
-
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型,即父类被重写方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类;父类被重写方法的返回值类型是void,则子类重写方法的返回值类型只能是void;父类被重写方法的返回值类型是基本数据类型时,子类重写的方法的返回值类型必须时相同的基本数据类型。
-
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限,子类不能重写父类中声明为private权限的方法
-
子类方法抛出的异常不能大于父类被重写方法的异常
-
子类与父类中同名参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写),即不能重写声明为static的方法。因为static方法时属于类的,子类无法覆盖父类的方法。
-
方法的声明
权限修饰符 返回值类型 方法名(参数列表) throw 异常类型{
方法体
}
-
-
应用:
- 重写父类的方法后,当创建子类对象后,通过子类对象调用与父类中同名同参数的方法时,实际执行的是子类重写父类的方法。
3.super关键字
-
super理解为父类的;super可以用来调用:属性、方法;构造器
-
super的使用:调用属性和方法
- 在子类的方法或者构造器中,通过使用“super.属性”或“super.方法”的方式,显示的调用父类中声明的属性或方法。但是通常情况下,习惯省略“super.”
- 特殊情况,当子类和父类中定义了同名的属性时,想要在子类中调用父类中声明的属性,则必须是显示的使用”super.属性“**的方式,表明调用的是父类中声明的属性。
- 特殊情况,当子类和父类中定义了同名的方法时,想要在子类中调用父类中声明的方法,则必须是**显示的使用”super.方法“**的方式,表明调用的是父类中声明的方法。
-
super的使用:调用构造器
-
可以在子类的构造器中显示的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器
-
“super(形参列表)”的使用,必须声明在子类构造器的首行,同this关键字调用构造器类似
-
在类的构造器中,针对“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现
-
在构造器的首行,没有显示的声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中空参的构造器——super()
-
在类的多个构造器中,至少有一个类的构造器使用了“super(形参列表)”,调用父类中的构造器
-
super(形参列表):调用父类中指定的构造器
this(形参列表):本类重载的其他的构造器
-
-
练习:
package classoverwrite; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/4 17:01 * @Description: */ public class Account { private int id; //账号 private double balance;//账户余额 private double annualInterestRate;//年利率 public Account(int id, double balance, double annualInterestRate) { 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 getAnnualInterestRate() / 12; } //取款 public void withdraw(double amount){ if(amount > balance){ System.out.println("余额不足"); }else{ balance -= amount; } } //存款 public void deposit(double amount){ balance -= amount; } }
package classoverwrite; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/4 18:09 * @Description: */ public class CheckAccount extends Account { 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; } public void setOverdraft(double overdraft) { this.overdraft = overdraft; } @Override public void withdraw(double amount) { if(getBalance() >= amount){ //方式1 // setBalance(getBalance() - amount); //方式2 super.withdraw(amount); }else if(overdraft >= amount - getBalance()){ overdraft -= (amount - getBalance()); //方式1 // setBalance(0); //方式2 super.withdraw(getBalance()); }else{ System.out.println("超过可透支额度"); } } }
package classoverwrite; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/4 18:30 * @Description: */ public class CheckAccountTest { public static void main(String[] args){ CheckAccount checkAccount = new CheckAccount(1122, 20000, 0.00045, 5000); checkAccount.withdraw(5000); System.out.println("账户余额为:" + checkAccount.getBalance()); System.out.println("账户可透支额为:" + checkAccount.getOverdraft()); checkAccount.withdraw(18000); System.out.println("账户余额为:" + checkAccount.getBalance()); System.out.println("账户可透支额为:" + checkAccount.getOverdraft()); checkAccount.withdraw(3000); System.out.println("账户余额为:" + checkAccount.getBalance()); System.out.println("账户可透支额为:" + checkAccount.getOverdraft()); } }
4.子类对象实例化的过程
-
从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法
创建子类的对象,在堆空间中就会加载所有父类中声明的属性
-
从过程上来看:
当通过子类的构造器创建子类对象时,一定会直接或间接调用父类的构造器,进而调用父类的父类,它的构造器,直到调用了java.lang.Object类中的空参构造器为止。正因为加载过所有父类的结构,所以才可以看到内存中有父类的结构,子类才可以考虑进行调用。
-
虽然创建子类对象时,调用了父类的构造器,但是自始自终就创建了一个对象,即为new的子类对象。
-
子类对象实例化过程
super(参数列表)和this(参数列表)不能出现在同一个构造器中?
为什么super(参数列表)和this(参数列表)调用语句只能作为构造器的第一句出现?
java规定,在执行构造函数之前必须执行父类的构造函数,直到这个类是java.lang.Object类的构造函数。
然而函数的入口是子类构造函数,因此任何构造函数第一句,必须是执行父类构造函数,如果没有添加super关键字,那么编译器会为该构造函数第一句添加一个super()语句(你可以这么理解,当然编译以后并不是这样)。如果有super关键字显示的调用父类构造函数,就是用指定的那个父类构造函数,否则使用默认的无参构造函数。
也有一种情况例外,就是存在this(),调用本类其它构造函数,但是按照递归调用,最终还是会调用父类构造函数;如果this()和super()都存在,那么就会出现:初始化父类两次的不安全操作,因为当super()和this()同时出现的时候,在调用完了super()之后 还会执行this(),而this()中又会自动调用super(),这就造成了调用两次super()的结果。
如果你继承的父类没有无参数构造函数,那么你这个类第一句必须显示的调用super关键字,来调用父类对应的有参构造函数,否则不能通过编译。
5.面向对象之多态
-
多态性可以浅显的理解为一个事物的多种形态;
-
对象的多态性:父类的引用指向子类的对象或者称为子类的对象赋给父类的引用;可以直接应用在抽象类和接口上
-
多态的使用:虚拟方法的调用(Virtual Method Invocation)
正常方法的调用
Person e = new Person();
e.getInfo();
Strudent e = new Student();
e.getInfo();
虚拟方法的调用(多态情况下)
子类中定义了于父类同名的同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类过呢根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期时无法确定的;使用多态的情况下,无法调用子类所特有的方法和属性。
Person e = new Student();
e.getInfo();//调Student类的getInfo()方法
编译时类型和运行时类型
编译时e为Person类,而方法的调用时在运行时确定的。所以调用的时Student类的getInfo()方法,这样子的过程叫动态绑定
有了对象的多态性以后,在编译期只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。
Java引用变量有两个类型:编译时类型和运行时类型,编译时类型有声明该变量的时使用的类型决定,运行时类型由实际赋给该变量的对象决定。若编译时类型和运行时类型不一致,就出现了对象的多态性(polymorphism)
SuperClass sp = new SubClass;
可以理解为编译时看等号左边,运行时,看等号右边
-
多态性使用的前提:①类的继承关系;②方法的重写
-
对象的多态性只适用方法,不适用于属性。属性可以理解为编译个和运行时都看等号的左边。
-
举例1:如果Java中没有多态性,抽象类和接口的存在就没有任何的意义,因为抽象类无法造对象,接口也不能造对象;开发中用抽象类和接口就是提供它子类的对象或者说它实现类的对象,这就体现为多态性。类是单继承的,通过接口实现多继承的这种特点。
举例2:方法的参数是类类型的,则不光适用于列表中类的类型的对象,还适用于该类类型的子类对象,如果没有多态性,使用方法的通用性极大降低,方法的参数列表不能够填子类对象,就意味着要造大量重载的方法
6.方法的重载和重写的区别
-
重载的定义:20210301Java面向对象 4.方法的重载
在同一个类中,只要方法名相同,方法的参数列表不同,编译器就认为方法构成了重载
重写的定义:20210303Java面向对象 2.方法的重写(本篇)
子类继承父类以后,子类中可以对父类中同名、同参数列表的方法进行方法体的改造,这称为重写
-
重载的规则:重载跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系,通过对象调用方法时只能根据方法名和参数列表来判断
重写的规则:子类重写方法的权限修饰符不小于被重写的方法、子类重写的方法返回值类型不大于被重写方法的返回值类型(引用类型“子类返回值类型可以是被重写方法返回值同类或它的子类”、void和基本数据类型“相同”)
-
从编译和运行的角度看重写构成多态性、而重载不构成多态性:
重载,指的是允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数列表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以对于重载而言,在方法调用之前,编译器就已经确定了索要调用的具体方法,这称为**“早绑定”或“静态绑定”**;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,而这称为**“晚绑定”或“动态绑定”**
子类”、void和基本数据类型“相同”)
-
从编译和运行的角度看重写构成多态性、而重载不构成多态性:
重载,指的是允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数列表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以对于重载而言,在方法调用之前,编译器就已经确定了索要调用的具体方法,这称为**“早绑定”或“静态绑定”**;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,而这称为**“晚绑定”或“动态绑定”**
Bruce Eckel: 如果不是晚绑定,就不是多态