目录
前言
学习继承和多态,可以让我们的代码更加的整洁明了。
一、为什么会出现继承?
我们所生活的世界是错综复杂的,每一个事物之间可能有着千丝万缕的联系,也有可能毫无关联,而Java的存在就是对世界中的实体进行描述的,类实例化对象,对象就可以来描述生活中的实体,都说C生物,拿Java也是一样的。例如自行车与摩托车,他们都是车。下面来定义这两个类:
class Bicycle{
public String name;
public int price;
public String model;
public void RideCar(){
System.out.println("骑车");
}
}
class Auto{
public String name;
public int price;
public String model;
public void RideBike(){
System.out.println("开车");
}
}
从上面的代码可知,自行车和汽车都有共同的成员变量,并且每一个对象都会为他们开辟空间,为了提高代码的复用性,所以出现了继承。
二、继承详解
2.1 继承的概念
继承 (inheritance) 机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性 的基础上进行扩展,增加新功能 ,这样产生新的类,称 派生类 。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用(就好似自行车和汽车一样,他们都有着一些共同的特性,我们可以对共性的内容进行共性抽取,然后通过继承的思想来实现。)
2.2 继承的使用
class Car{
public String name;
public int price;
public String model;
public double weight;
public int a = 6;
public void func(){
System.out.println("父类的方法");
}
}
class Bicycle extends Car{
public double weight;
public int a = 3;
public void RideCar(){
System.out.println("骑车");
System.out.println("访问相同名的成员变量:"+super.a);
super.func();//父类的方法
func();//子类的方法
}
public void func(){
System.out.println("子类的方法");
}
@Override//前面类和对象已解释
public String toString() {
return "Bicycle{" +
"name='" + name + '\'' +
", price=" + price +
", model='" + model + '\'' +
", weight=" + weight +
'}';
}
}
class Auto extends Car{
public String colour;
public void RideBike(){
System.out.println("开车");
}
}
public class Test {
public static void main(String[] args) {
Bicycle bicycle = new Bicycle();
bicycle.name = "自行车";
bicycle.weight = 36.3;
System.out.println(bicycle);//打印Bicycle{name='自行车', price=0, model='null', weight=36.3}
System.out.println(bicycle.name);
}
}
下面我们根据上面的代码可以提出相应的问题
(1)如何继承?
Java中通过extends来实现继承。
(2)继承后,子类有什么变化?
子类会将父类中的成员变量或者成员方法继承到子类中了,子类相当于扩容,多了成员变量和方法
class Car{
public String name;
public int price;
public Car(String name, int price) {
this.name = name;
this.price = price;
}
}
class Bicycle extends Car{
public double weight;
/*public Bicycle(){//默认的,父类没有自定义构造函数时
super();
}*/
public Bicycle(String name, int price, double weight) {
super(name, price);//调用父类的构造函数,来初始化子类从父类继承过来的属性,只能在第一行
this.weight = weight;
}
}
(3)如何访问子类和父类的成员变量和方法?
子类继承父类后,通过子类实例化对象,根据对象的引用来访问。访问时根据就近原则,先访问子类,再去访问父类的,如果子类和父类都没有方法或变量时,代码会出错。
(4)父类与子类出现相同的方法名或变量时,又是怎么访问父类的变量和方法?
子类与父类同时出现相同类名的变量或方法时,子类的访问就是正常的对象的引用,访问父类则需要super关键字,super.变量、super.方法。
(5)什么是super?
super可以理解为从父类继承过来的父类那一块成员的地址,可以说super里面存了父类成员的地址;Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。
2.3 子类构造方法
什么是子类构造方法,与类一样,编译器都会默认的提供构造方法,但是呢,当我们自定义构造方法的时候,编译器就不会提供了。子类继承父类的时候,是将它的成员继承过来,对应着构造方法也会继承过来。子类对象构造完成之前(就是代码块{}走完),是先要帮助父类的成员进行初始化。如下代码:
class Car{
public String name;
public int price;
public Car(String name, int price) {
this.name = name;
this.price = price;
}
}
class Bicycle extends Car{
public double weight;
/*public Bicycle(){//默认的,父类没有自定义构造函数时
super();
}*/
public Bicycle(String name, int price, double weight) {
super(name, price);//调用父类的构造函数,来初始化子类从父类继承过来的属性,只能在第一行
this.weight = weight;
}
}
2.4 父类子类中的代码块
class Car{
public Car() {
System.out.println("父类的构造代码块");
}
{
System.out.println("父类的实例代码块");
}
static{
System.out.println("父类的静态代码快");
}
}
class Bicycle extends Car{
public Bicycle() {
System.out.println("子类的构造方法");
System.out.println("==============");
}
{
System.out.println("子类的实例代码块");
}
static{
System.out.println("子类的静态代码块");
}
}
public class Test {
public static void main(String[] args) {
Bicycle bicycle = new Bicycle();
Bicycle bicycle1 = new Bicycle();
}
}
//打印结果
父类的静态代码快
子类的静态代码块
父类的实例代码块
父类的构造代码块
子类的实例代码块
子类的构造方法
==============
父类的实例代码块
父类的构造代码块
子类的实例代码块
子类的构造方法
Process finished with exit code 0
由上面的代码可以知道继承的类也是遵循代码块的执行顺序。(类和对象有详解),总的一句话,有父及子,静态先行
2.5 protected关键字详解
//同一个包下不同的类访问
class Student{
protected int age;
public void func(){
System.out.println(age);
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.func();//打印默认值0
}
}
//同一个类访问
public class Test {
protected int age;
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.age);//打印0
}
}
//不同包的子类
//inherit包下的子类
public class Test {
protected int age;
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.age);//打印0
}
}
//demo包下的子类
public class Test extends inherit.Test {
public void func(){
System.out.println(super.age);//不能直接的访问,需要super才行
}
public static void main(String[] args) {
//inherit.Test test = new inherit.Test();
//test.age = 10;err
Test test1 = new Test();
test1.func();
}
}
访问限定符的使用要看运用的场景。我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者。
2.6 继承的方式
分为三种,看代码:
//单继承
class Student{}
class Student1 extends Student{}
//不同的类继承同一个类
class Student{}
class Student1 extends Student{}
class Student2 extends Student{}
//多个类继承
class Student{}
class Student1 extends Student{}
class Student2 extends Student1{}
//不能一个子类继承多个父类
class Student{}
class Student1{}
class Student2 extends Student1,Student{}
我们并不希望类之间的继承层次太复杂. 一般不超过三层的继承关系.。如果继承层次太多, 就需要考虑对代码进行重构了。如果想从语法上进行限制继承, 就可以使用 final 关键字对类进行密封,密封后这个类就不能继承了。
2.7 final关键字详解
final class Student{}//final修饰类后,类就会被密封,不能被继承
//class Student1 extends Student{} err
public class Test {
public final int a = 10;//final修饰后,变量就不可以更改了
//a = 20; err
}
//final修饰方法后,这个方法不能重写了
2.8 继承组合
简单的说明:继承就是谁是谁的关系,汽水是水,开水也是水;而组合呢:就是多个细节组合起来,比如电脑由内存、显示器、鼠标平键盘...等组成
三、多态
3.1 什么是多态
什么是多态呢?实例化出来的对象去完成某一件事(行为)的时候,不同的对象会产生不同的结果。下面的动物就很好的诠释了多态,兔子和老虎都是动物,但是呢吃的东西是完全不一样的。
3.2 满足多态的条件
简单的说有三个条件:
🚀发生向上转型
🚀重写父类的方法
🚀通过父类的引用调用子类中重写父类的方法(动态绑定,方法的传参,返回值)
class Animal{
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){//public Animal eat() 父子类关系
System.out.println(name+"吃东西");
}
}
class Tiger extends Animal{
public Tiger(String name, int age) {
super(name, age);
}
public void running(){
System.out.println(name+"正在飞快的跑");
}
@Override
public void eat() {//public Tiger eat() 父子类关系
System.out.println(name+"吃肉");
}
}
public class Test {
public static void func(Animal animal){}
public static Animal func1(){
return new Tiger("老虎",6);
}
public static void main(String[] args) {
//Animal animal = new Tiger("老虎",6);
Tiger tiger = new Tiger("老虎",6);
Animal animal = tiger;//父类的引用 引用了 子类引用的对象
animal.eat();//发生了动态绑定,没有重写eat()方法的时候会打印老虎吃东西
//animal.running();//不能调用了,只能调用父类自己独有的方法
animal.eat();//重写了父类的eat();这时调用就会以子类的方法为主,打印老虎吃肉
func(tiger);//方法的传参-动态绑定
func1();//返回值-动态绑定
}
}
根据上面的代码,我们可以提出以下问题:
1.什么是向上转型,转型后有什么影响?何为动态绑定
向上转型:Animal animal = new Tiger("老虎",6);就是将子类的对象赋值父类的引用;发生向上转型后,父类的引用(animal)只能调用自己独有的方法了,不能调用子类的,是因为发生了动态绑定;动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法,编译的时候还是调用父类,但是执行的时候就会发生动态绑定。(静态绑定,在编译的时候就已经确定了,重载)
2.如果子类重写了父类的方法后会发生什么呢?什么又是重写?
重写:就是在子类中重写父类的方法,重写的规则有:返回值相同(父子类的关系也可以,协变类型) ;参数的类型、个数,顺序相同 ;方法名相同(上面就重写了eat()方法);static和private修饰的方法不能被重写(子类的访问限定符要大于等于父类的访问限定符);重写后父类的引用就可以调用子类的重写父类后的方法了。如果你不想这个方法重写的话,加上final就行(密封)。
3.3 再次了解多态
class Animal{
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
//eat();也可以发生动态绑定,引用子类的对象,super调用父类的构造函数,打印子类的eat();
}
public void eat(){
System.out.println(name+"吃东西");
}
}
class Tiger extends Animal{
public Tiger(String name, int age) {
super(name, age);
}
public void running(){
System.out.println(name+"正在飞快的跑");
}
@Override
public void eat() {
System.out.println(name+"吃肉");
}
}
class Rabbit extends Animal{
public Rabbit(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"吃草");
}
}
public class Test {
public static void func(Animal animal){
animal.eat();//打印老虎吃肉
}
public static void func1(Animal animal){
animal.eat();//打印兔子吃草
}
public static void main(String[] args) {
func(new Tiger("老虎",6));//方法的传参-动态绑定
func1(new Rabbit("兔子",3));
}
}
父类的引用引用了子类的对象,通过父类的引用调用子类重写后的方法,而上面的代码就只写了一个父类的引用,父类的引用通过调用同一个重写后方法(动态绑定),出现了不同的变现形式,这种就叫做多态。一个多种对象同事去做一个行为的时候,表现会有所不同。
3.4 向下转型
🚀将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。🚀向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。 Java 中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为 true ,则可以安全转换。
//tiger 和 rabbit这个两个类接上面的代码,这里就不重复写了
public class Test {
public static void func(Animal animal){
//animal是否引用了Rabbit的对象
if(animal instanceof Rabbit){//通过instanceof来判断animal是不是Rabbit这个类的成员
Rabbit rabbit = (Rabbit) animal;
rabbit.eat();//会出错
}
}
public static void main(String[] args) {
/*Animal animal = new Tiger("老虎",6);
Rabbit rabbit = (Rabbit) animal;
rabbit.eat();//编译能通过,执行会错;因为兔子不会吃肉,只吃草*/
func(new Rabbit("兔子",3));
}
}
总结
this与super的区别
相同点:
🚀只能在类的非静态方法中使用,用来访问非静态成员方法和字段🚀在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在不同点:
🚀this是当前对象的引用,super相当于是子类对象中从父类继承下来部分成员的引用
🚀在非静态成员方法中, this 用来访问本类的方法和属性, super 用来访问父类继承下来的方法和属性🚀this 是非静态成员方法的一个隐藏参数, super 不是隐藏的参数🚀在构造方法中: this(...) 用于调用本类带参数的构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现(而且两者都只能出现在第一行)🚀构造方法中一定会存在 super(...) 的调用,用户没有写编译器也会增加,但是 this(...) 用户不写则没有
Java中的swap()函数是需要在堆上实现;不同类里面的方法(继承后)也可以形成重载
多态的优点:可以降低代码的复杂度;扩展能力强