继承和多态

继承

继承 (inheritance) 机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性 的基础上进行扩展,增加新功能 ,这样产生新的类,称 派生类。(共性的抽取,实现代码复用 。)
语法格式:
Java 中不支持多继承 。即一个孩子有多个父亲
在Java中,当子类继承父类时,它会继承父类的所有 private成员变量和方法,子类继承父类之后,虽然可以继承父类私有的数据,但是由于private访问修饰符的问题,子类没有办法直接访问该数据。这是因为继承主要影响的是行为(方法)而不是状态(数据)。所以在子类中访问父类被private修饰的成员时,会 编译出错

父类成员访问

//创建Animal类(父类)
public class Animal {
    public String name="小胖";;
    public int age=5;
    public void eat(){
        System.out.println("去吃饭");
    }
}
//创建dog类(子类)
class Dog extends Animal{
    public String color="yellow";

    //public String name="黑虎";//与父类中成员变量name同名
  
//与父类中成员方法eat()同名
//    public void eat(){
//        System.out.println("吃狗粮");
//    }

    public void method(){
        //父类和子类成员同名时,根据就近原则,优先访问自己的成员变量
        System.out.println(name);//访问从父类继承下来的name 
        System.out.println(age);//访问从父类继承下来的age
        System.out.println(color);//访问子类自己的color
        eat();//访问从父类继承下来eat()方法
        //通过super关键字获取到子类从基类继承下来的部分
        System.out.println(super.name);
        super.eat();
    }
}
//实现
class Test{
    public static void main(String[] args) {
        Dog dog=new Dog();
        dog.method();
    }
}

子类中访问父类的成员

1. 子类和父类不存在同名成员

2. 子类和父类成员同名

 

在子类方法中 或者 通过子类对象访问成员时 如果访问的成员变量与父类中成员变量同名,成员变量访问遵循 就近原则:
1. 如果访问的成员子类中有,则优先访问自己的成员。
2. 如果访问的成员子类中无,则访问父类继承下来的。
3. 如果父类也没有定义,则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同 ( 重载 ) ,根据调用方法适传递的参数选择合适的方法访问,如果没有则报错

super 关键字

作用:当子类和父类成员同名时,可以在子类方法中或者通过子类对象访问成员时,通过super关键字,优先访问父类的成员。

1. 只能在非静态方法中使用
2. 在子类方法中,只能访问 直接 父类的成员变量和方法,不能访问父类的父类。
class Plant{
    public Plant(){
        System.out.println("这是Plant类的构造方法");
    }
}
class Animal{
    Plant plant = new Plant();//使用new关键字来实例化Plant类的对象
    public Animal(){
        System.out.println("这是父类的构造方法");
    }
    static {
        System.out.println("这是父类静态代码块");
    }
    {
        System.out.println("这是父类实例代码块");
    }
}
class Dog extends Animal{
    Plant plant = new Plant();//使用new关键字来实例化Plant类的对象
    static {
        System.out.println("这是子类静态代码块");
    }
    {
        System.out.println("这是子类实例代码块");
    }
    public Dog() {
        super();
        System.out.println("这是子类构造方法");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog1=new Dog();
        System.out.println("==================");
        Dog dog2 =new Dog();
    }
}

通过分析执行结果,得出以下结论:
1 、父类静态代码块优先于子类静态代码块执行,且是最早执行
2 、父类实例代码块和父类构造方法紧接着执行
3 、子类的实例代码块和子类构造方法紧接着再执行
4 、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
即执行顺序为:
父类静态代码 > 子类静态代码 > 父类实例化代码 > 父类构造方法 > 子类实例化代码 > 子类构造方法

子类构造方法

子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法,( 将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整)
在Java中,子类的构造方法不一定需要有与父类构造方法中相同数量的参数。子类构造方法的参数数量可以 大于、小于或等于父类构造方法中的参数个数。然而,如果子类的构造方法参数列表与父类的构造方法 不匹配,那么子类构造方法必须 显式地调用父类的构造方法( 使用super关键字)来确保父类的成员变量得到正确初始化
注意:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法 第一行默认 有隐含的 super() 调用,即调用父类构造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义与父类参数相同的构造方法,并在子类构造方法中的使用 super(参数...) 调用父类构造方法,且必须是子类构造方法中 第一条语句 ,否则编译失败。
3. super(...) 只能在子类构造方法中出现一次,并且不能和 this 同时出现
this 和 super
相同点
1. 都是 Java 中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和变量
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点
2. 在非静态成员方法中, this可以 用来访问父类和子类的方法和属性,且当使用this访问父类和子类同名的成员时,子类优先被访问。 super 用来访问父类继承下来的方法和属性
3. 在构造方法中: this(...) 用于调用本类构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在 super(...) 的调用,无参或默认的构造方法中用户没有写编译器也会增加,但是 this(...) 用户不写则没有

protected 关键字

访问权限:

1. 同一包中的同一类

2. 同一包中的不同类

3. 不同包中的子类

package com.example;

public class Animal {
    protected String name="黑虎";
}
package com.example2;

import com.example.Animal;

public class Dog extends Animal {
    public void print(){
        System.out.println(super.name);//super只能在非静态方法中使用
    }
    public static void main(String[] args) {
        Dog dog=new Dog();
        dog.print();
    }
}

final关键字

1. 修饰变量或字段,表示常量 ( 即不能修改 )
 final int age=10;
 age=20;//err
final int[] arr1=new int[]{1,2,3,4,5};
arr1=new int[10];//err

不能修改arr1本身的内容即地址,即不能再指向另一个数组,但可以修改arr1指向的内容

2. 修饰类:表示此类不能被继承
一般我们不希望出现超过三层的继承关系
final public class Animal{
    ...
}

class Dog extends Anmial{
    ...
}

//编译失败 java: 无法从最终com.example.Animal进行继承
//Animal被final修饰,不能被继承
3. 修饰方法:表示该方法不能被重写

组合

组合通常指的是**面向对象编程(OOP)**中的一种关系,即一个类包含另一个类的对象作为其成员变量。这种关系被称为“组合”或“has-a”关系,表示一个对象由其他对象组成。

汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是由这些部件组成的。

组合的特点

  1. has-a 关系

    • 一个类中包含另一个类的对象。

    • 例如:Car 类中包含 Engine 类的对象,表示“汽车有一个引擎”。

  2. 强依赖关系

    • 组合中的成员对象通常与包含它的对象有强关联性。

    • 如果包含对象被销毁,成员对象通常也会被销毁。

  3. 灵活性

    • 组合允许在运行时动态地替换成员对象。

    • 例如:可以通过 setEngine() 方法更换汽车的引擎。

  4. 代码复用

    • 通过组合,可以复用其他类的功能,而不需要继承。

//轮胎类
class Tire{
    public void print1(){
        System.out.println("这是轮胎类");
    }
}
//发动机类
class Engine{
    public void print2(){
        System.out.println("这是发动机类");
    }
}
//组合
public class Car {
    //tire和engine没有被初始化,它们默认为null
    public Tire tire;//Car has-a Tire
    public Engine engine;//Car has-a Engine
}
class Benz extends Car{
    public void print3(){
        tire.print1();
        engine.print2();
    }
}
class Test2{
    public static void main(String[] args) {
        Benz benz =new Benz();
        //确保了在调用print3方法之前,tire和engine已经被正确初始化
        benz.tire=new Tire();
        benz.engine=new Engine();
        benz.print3();
    }
}

多态

概念:去完成某个行为,当不同的对象去完成时会产生出不同 的状 态。
java 中要实现多态,必须要满足如下几个条件,缺一不可:
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

重写:覆盖 覆写

需满足以下条件,缺一不可:

1. 方法名必须相同

2. 参数列表必须相同(类型,个数,顺序)

3. 子类中重写的方法必须返回与父类中被重写方法相同的类型,或者是其子类型(协变返回类型)。

方法重写的规则:

1. 被private修饰的方法不能被重写,因为该方法只能在当前类中被使用

2. 被static修饰的方法不能被重写,但可以被隐藏。如果子类有一个与父类静态方法同名的方法,它将隐藏父类的方法,而不是重写它。静态方法在编译时绑定,而重写方法在运行时绑定,

3. 被final修饰的方法不能被重写,此时这个方法叫密封方法

4. 子类中重写的方法不能拥有比父类访问权限更低的访问修饰符。private < default < protected < public

5. 构造方法不能被重写

6. 可以使用@Override注解来明确指出一个方法是重写了父类的方法。如果一个方法被标记为@Override,但并没有重写父类的方法,编译器将报错。

class Anmial{
    public String name;
    public int age;
    public String color;
    public String eat(String name){
        System.out.println("吃饭");
        this.name=name;
        return this.name;
    }
}
class Dog extends Anmial{
    public void run(){
        System.out.println("用狗腿跑步");
    }

    //重写eat()方法
    @Override
    public String eat(String name){
        System.out.println("吃狗粮");
        this.color="yellow";
        return this.color;
    }
}
class Cat extends Anmial{
    public void run(){
        System.out.println("用猫腿跑步");
    }

    //重写eat()方法
    @Override
    public String eat(String name){
        System.out.println("吃猫粮");
        this.name=name;
        return this.name;
    }
}

重写与重载的区别:

区别点重写

重载

参数列表不能修改必须修改
返回类型不能修改(除非可以构成父子关系)可以修改
访问限定符子类中重写的方法不能拥有比父类访问权限更低的访问修饰符可以修改
方法重载是一个类的多态性表现 , 而方法重写是子类与父类的一种多态性表现
静态绑定 :也称为前期绑定 ( 早绑定 ) ,即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
方法重载,在编译的时候,我们通过方法的参数等,就能确定它调用的是哪个方法了。
动态绑定 :也称为后期绑定 ( 晚绑定 ) ,即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

向上转型

将子类对象赋给父类引用的过程称为 向上转型。(也可以将子类B的子类A对象赋给子类B的父类C引用,虽然C不是A的直接父类,但A仍继承了C的内容,因此可以发生向上转型)C->A->B

向上转型和成员访问:

  1. 父类成员:向上转型后,可以访问父类中定义的所有成员变量和方法

  2. 子类重写方法:如果子类重写了父类的方法,通过父类引用调用时,实际上是调用的是子类中的实现。这是动态绑定或多态性的体现。

  3. 子类特有成员:不能直接访问子类中特有的成员变量和方法,因为这些成员在父类中不存在。

语法格式:
使用场景:
1. 直接赋值
Anmial anmial = new Cat();//向上转型
// //实际写法
// Anmial anmial=new Anmial();
// anmial=new Cat();
System.out.println(anmial.eat("大胖"));//通过父类引用调用这个父类和子类重写的方法

2. 方法传参

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        print(cat);
    }
    //向上转型
    public static void print(Anmial anmial){
        System.out.println(anmial.eat("大胖"));
    }
}

3. 方法返回

public class Test {
    public static void main(String[] args) {
        Anmial anmial = func();//向上转型
        System.out.println(anmial.eat("大胖"));
    }
    public static Anmial func(){
        return new Cat();
    }
}

anmial.eat("大胖");在编译的时候无法确定调用父类还是子类的eat方法,在运行时,子类重写父类的eat方法,可以确定调用子类的eat方法,这个过程我们就称为动态绑定或运行时绑定

 向下转型

将一个子类对象经过向上转型之后当成父类使用,但父类引用无法直接访问子类特有的成员变量和方法,如果需要访问子类特有的成员,必须进行向下转型(Downcasting)回子类类型。

进行转型:使用(子类类型)将父类引用转型为子类引用。 

instanceof 是一个关键字,它用于检查对象是否是特定类的实例

instanceof表达式返回一个布尔值,如果对象是指定类的实例,则返回true;否则返回false。

instanceof操作符通常用在需要确定对象的类型时,特别是在执行向下转型之前,需要确定对象实际类型,因为向下转型不安全。

public class Test {
    public static void main(String[] args) {
          Anmial anmial1 = new Cat();//向上转型
          //向下转型前确定对象是否为Cat类型
          if(anmial1 instanceof Cat){
              Cat cat = (Cat) anmial1;//向下转型
              cat.run();//访问子类特有的方法
          }
    }
}
多态的优缺点:
1. 能够降低代码的 " 圈复杂度 ", 避免使用大量的 if - else
什么叫 " 圈复杂度 " ?
圈复杂度是一种描述一段代码复杂程度的方式 . 一段代码如果平铺直叙 , 那么就比较简单容易理解 . 而如
果有很多的条件分支或者循环语句 , 就认为理解起来更复杂 .
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数 , 这个个数就称为 " 圈复杂度 ".
如果一个方法的圈复杂度太高 , 就需要考虑重构 .
不同公司对于代码的圈复杂度的规范不一样 . 一般不会超过 10
如果我想打印 吃狗粮,吃猫粮,吃狗粮,吃狗粮 要怎么打印
  1. 使用 if...else 语句
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        String[] shapes = {"吃狗粮", "吃猫粮", "吃狗粮", "吃狗粮"};
        for (String shap : shapes) {
            if (shap.equals("吃狗粮")) {
                dog.eat("大黄");
            } else if (shap.equals("吃猫粮")) {
                cat.eat("大黄");
            }
        }
    }
}

2. 使用多态

public class Test {
    public static void main(String[] args) {
        Anmial[] shapes={new Dog(),new Cat(),new Dog(), new Dog()};
        for(Anmial shap:shapes){
            shap.eat("大黄");
        }
    }
}

2. 可扩展能力更强

如果我们想要新增一种新的eat方法并打印, 使用多态的方式代码改动成本比较低,只要创建一个新的实例就可以了。而不使用多态的情况,就需要改动if...else 语句,成本更高

多态缺陷:代码的运行效率降低
1. 属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
2. 构造方法没有多态性
class Animal{
    public Animal(){
        eat();
    }
   public void eat(){
       System.out.println("Anmial.eat()");
   }
}
class Dog extends Animal{
    public int age=10;
    public Dog(){
        super();
        eat();
    }
    public void eat(){
        System.out.println("Dog.eat()"+age);
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

构造 Dog  对象的同时 , 会调用父类  Anmial  的构造方法 .
父类Anmial  的构造方法中调用了  eat  方法 , 此时会触发动态绑定 , 会调用到 Dog  中的  eat
此时 Dog  对象自身还没有构造 , age   处在未初始化的状态 , 值为 0. 如果具备多态性,age 的值应该是 10.
父类Animal对象构造完成后,才开始调用Dog自身的构造方法,此时age初始化为10.
所以在构造函数内,尽量避免使用实例方法,除了 final private 方法。
结论 : " 用尽量简单的方式使对象进入可工作状态 ", 尽量不要在构造器中调用方法 ( 如果这个方法被子类重写 , 就会触发动态绑定, 但是此时子类对象还没构造完成 ), 可能会出现一些隐藏的但是又极难发现的问题 .

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值