当前位置: 首页 > news >正文

继承和多态的详解

文章目录

  • 1. 继承
    • 1.1 继承的概念
    • 1.3 继承的语法
    • 1.3 父类成员访问
      • 1.3.1 子类中访问父类的成员变量
      • 1.3.2 子类中访问父类的成员方法
    • 1.4 子类构造方法
  • 2.super关键字
    • 2.1 super关键字的概念
    • 2.2 super和this的区别
  • 3. 在继承中访问限定符的可见性
  • 4. 继承方式的分类
  • 5. 多态
    • 5.1 多态的概念
    • 5.2 多态实现条件
    • 5.3 重写
      • 5.3.1 重写的概念
      • 5.3.2 重写和重载的区别
    • 5.4 向上转移和向下转型
      • 5.4.1 向上转型
      • 5.4.2 向下转型
    • 5.5 多态的优缺点
    • 5.6 避免在构造方法中调用重写的方法
  • 6.final 关键字
  • 7.继承与组合
    • 7.1 组合的概念
    • 7.2 继承与组合的区别

1. 继承

1.1 继承的概念

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。

在这里插入图片描述
上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

1.3 继承的语法

在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类 extends 父类 {// ...  
}

举个例子:

// Animal.java
public class Animal{String name;int age;public void eat(){System.out.println(name + "正在吃饭");}public void sleep(){System.out.println(name + "正在睡觉");}
}// Dog.java
public class Dog extends Animal{    void bark(){System.out.println(name + "汪汪汪~~~");}
}// TestExtend.java
public class TestExtend {public static void main(String[] args) {Dog dog = new Dog();// dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的System.out.println(dog.name);System.out.println(dog.age);// dog访问的eat()和sleep()方法也是从Animal中继承下来的dog.eat();dog.sleep();dog.bark();}
}

注意:

1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

1.3 父类成员访问

1.3.1 子类中访问父类的成员变量

  1. 子类和父类不存在同名成员变量
public class Base {int a;int b;
}public class Derived extends Base{int c;public void method(){a = 10;    // 访问从父类中继承下来的ab = 20;    // 访问从父类中继承下来的bc = 30;    // 访问子类自己的c}
}
  1. 子类和父类成员变量同名
public class Base {int a;int b;int c;
}public class Derived extends Base{int a;              // 与父类中成员a同名,且类型相同char b;             // 与父类中成员b同名,但类型不同public void method(){a = 100;        // 访问父类继承的a,还是子类自己新增的a?b = 101;        // 访问父类继承的b,还是子类自己新增的b?c = 102;        // 子类没有c,访问的肯定是从父类继承下来的c// d = 103;     // 编译失败,因为父类和子类都没有定义成员变量b}
}

在子类方法中 或者 通过子类对象访问成员时:

  • 如果访问的成员变量子类中有,优先访问自己的成员变量。
  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
  • 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。

1.3.2 子类中访问父类的成员方法

  1. 成员方法名字不同
public class Base {public void methodA(){System.out.println("Base中的methodA()");}
}public class Derived extends Base{public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodB();         // 访问子类自己的methodB()methodA();         // 访问父类继承的methodA()// methodD();     // 编译失败,在整个继承体系中没有发现方法methodD()}
}

总结:

成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。

  1. 成员方法名字相同
public class Base {public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}
}public class Derived extends Base{public void methodA(int a) {System.out.println("Derived中的method(int)方法");}public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodA();      // 没有传参,访问父类中的methodA()methodA(20);    // 传递int参数,访问子类中的methodA(int)methodB();      // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到}
}

【说明】

  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
  • 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;

1.4 子类构造方法

在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
1.父类显式定义无参或者默认的构造方法时

public class Base {public Base(){System.out.println("Base()");}
}public class Derived extends Base{public Derived(){// super();   // 注意子类构造方法中默认会调用基类的无参构造方法:super(),// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,// 并且只能出现一次System.out.println("Derived()");}
}public class Test {public static void main(String[] args) {Derived d = new Derived();}
}结果打印:
Base()
Derived(

2.父类显式定义构造方法是带有参数的时

public class Base {public int a;public Base(int a){this.a = a;}
}class Derived extends Base{public Derived(int a) {super(a);//必须要调用父类的构造方法,初始化父类,否则编译报错}}class Test {public static void main(String[] args) {Derived d = new Derived(12);}
}

总结:

1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
4. super(…)只能在子类构造方法中出现一次,并且不能和this同时出现

2.super关键字

Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员

2.1 super关键字的概念

1.super.XXX 访问父类的成员变量

public class Base {int a;int b;
}public class Derived extends Base{int a;    // 与父类中成员变量同名且类型相同char b;   // 与父类中成员变量同名但类型不同}public void methodC(){// 对于同名的成员变量,直接访问时,访问的都是子类的a = 100;   // 等价于: this.a = 100;b = 101;   // 等价于: this.b = 101;// 注意:this是当前对象的引用// 访问父类的成员变量时,需要借助super关键字// super是获取到子类对象中从基类继承下来的部分super.a = 200;super.b = 201;}
}
  1. super.XXX()访问父类的成员方法
public class Base {public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}
}class Derived extends Base{// 与父类中methodA()构成重载public void methodA(int a) {System.out.println("Derived中的method()方法");}// 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法methodA();      // 没有传参,访问父类中的methodA()methodA(20);    // 传递int参数,访问子类中的methodA(int)// 如果在子类中要访问重写的基类方法,则需要借助super关键字methodB();      // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到super.methodB(); // 访问基类的methodB()}
}
  1. super(XXX,XXX)调用父类的构造方法
public class Base {int a;int b;public Base(int a,int b) {this.a = a;this.b = b;}
}class Derived extends Base{int a;   char b; public Derived(int a,char b) {super(1,4);//调用基类的构造方法this.a = a;this.b = b;} 
}

【注意事项】

1. 只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法。

2.2 super和this的区别

super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句.

【相同点】

1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

【不同点】

1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
在这里插入图片描述
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造 方法中出现
4. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有

3. 在继承中访问限定符的可见性

在这里插入图片描述

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {private int a;protected int b;public int c;int d;
}// extend01包中
// 同一个包中的子类
public class D extends B{public void method(){// super.a = 10;     // 编译报错,父类private成员在相同包子类中不可见super.b = 20;         // 父类中protected成员在相同包子类中可以直接访问super.c = 30;         // 父类中public成员在相同包子类中可以直接访问super.d = 40;         // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问}
}// extend02包中
// 不同包中的子类
public class C extends B {public void method(){// super.a = 10;     // 编译报错,父类中private成员在不同包子类中不可见super.b = 20;        // 父类中protected修饰的成员在不同包子类中可以直接访问super.c = 30;        // 父类中public修饰的成员在不同包子类中可以直接访问//super.d = 40;     // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问}
}// extend02包中
// 不同包中的类
public class TestC {public static void main(String[] args) {C c = new C();c.method();// System.out.println(c.a);   // 编译报错,父类中private成员在不同包其他类中不可见// System.out.println(c.b);   // 父类中protected成员在不同包其他类中不能直接访问System.out.println(c.c);      // 父类中public成员在不同包其他类中可以直接访问// System.out.println(c.d);   // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问}
}

注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了

4. 继承方式的分类

在现实生活中,事物之间的关系是非常复杂,灵活多样,比如:

在这里插入图片描述
Java中只支持以下几种继承方式:
在这里插入图片描述
注意:Java中不支持多继承。

5. 多态

5.1 多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
在这里插入图片描述
如上图所示,同一件事情,发生在不同对象身上,就会产生不同的结果。

5.2 多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

代码示例:

public class Animal {String name;int age;    public Animal(String name, int age){        this.name = name;        this.age = age;   }    public void eat(){        System.out.println(name + "吃饭");   } 
} 
public class Cat extends Animal{    public Cat(String name, int age){        super(name, age);   }    @Override    public void eat(){        System.out.println(name+"吃鱼~~~");   } 
} 
public class Dog extends Animal {    public Dog(String name, int age){        super(name, age);   }  @Override    public void eat(){        System.out.println(name+"吃骨头~~~");   } 
} ///分割线// 
public class TestAnimal {    // 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法    // 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法    // 注意:此处的形参类型必须时父类类型才可以    public static void eat(Animal a){a.eat();}public static void main(String[] args) {Cat cat = new Cat("元宝",2);Dog dog = new Dog("小七", 1);eat(cat);eat(dog);}
}运行结果:
元宝吃鱼~~~
小七吃骨头~~~

在上述代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的.

父类的引用的子类对象不一样时,调用重写的方法所表现出来的行为是不同的,这种思想叫做多态。如上面代码所示,调用eat()方法时,参数的类型为Animal(父类),此时在该方法内部并不知道当前的a引用指向的是哪个类型(哪个子类)的实例. 此时 a这个引用调用 eat方法可能会有多种不同的表现(和 a 引用的实例相关),这种行为就称为多态。

在这里插入图片描述

5.3 重写

5.3.1 重写的概念

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

【方法重写的规则】

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 父类被final修饰的方法不能被重写
  • 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验.

代码示例:

public class Animal {String name;public void eat(){        System.out.println(name + "吃饭");   } 
} public class Cat extends Animal{    @Override    public void eat(){        System.out.println(name+"吃鱼~~~");   } 
} public class Dog extends Animal {    @Override    public void eat(){        System.out.println(name+"吃骨头~~~");   } 
} 

在子类Cat和Dog类中,重写了父类的eat()方法

5.3.2 重写和重载的区别

区别点重写(override)重载(override)
参数列表一定不能修改必须修改
返回类型一定不能修改【除非可以构成父子类关系】可以修改
访问限定符一定不能做更严格的限制(可以降低限制)可以修改

即:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

在这里插入图片描述

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。典型代表函数重写。

5.4 向上转移和向下转型

5.4.1 向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:

父类类型 对象名 = new 子类类型()

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。

在这里插入图片描述

【使用场景】

1.直接赋值:子类对象赋值给父类对象

public class TestAnimal {public static void main(String[] args) {Animal cat = new Cat("元宝",2);   // 1. 直接赋值:子类对象赋值给父类对象Dog dog = new Dog("小七", 1);    } 
}

2. 方法传参:形参为父类型引用,可以接收任意子类的对象

public class TestAnimal {// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象public static void eatFood(Animal a){a.eat();    }public static void main(String[] args) {Animal cat = new Cat("元宝",2);   Dog dog = new Dog("小七", 1);eatFood(cat);eatFood(dog);    } 
} 

3. 方法返回值:返回任意子类对象

public class TestAnimal {// 3. 作返回值:返回任意子类对象public static Animal buyAnimal(String var){if("狗".equals(var) ){return new Dog("狗狗",1);}else if("猫" .equals(var)){return new Cat("猫猫", 1);}else{return null;}    }public static void main(String[] args) {Animal animal = buyAnimal("狗");animal.eat();animal = buyAnimal("猫");animal.eat();    } 
} 

向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。

5.4.2 向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
在这里插入图片描述

public class TestAnimal {public static void main(String[] args) {Cat cat = new Cat("元宝",2);Dog dog = new Dog("小七", 1);// 向上转型Animal animal = cat;animal.eat();animal = dog;animal.eat();// 编译失败,编译时编译器将animal当成Animal对象处理// 而Animal类中没有bark方法,因此编译失败// animal.bark();// 向上转型// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastExceptioncat = (Cat)animal;cat.mew();// animal本来指向的就是狗,因此将animal还原为狗也是安全的   dog = (Dog)animal;dog.bark();}
}

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof关键字 ,如果该表达式为true,则可以安全转换。

instanceof关键字
在 Java 中可以使用 instanceof 关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例,语法格式如下所示。

boolean result = obj instanceof Class

其中,obj 是一个对象,Class 表示一个类或接口。obj 是 class 类(或接口)的实例或者子类实例时,结果 result 返回 true,否则返回 false。

public class TestAnimal {public static void main(String[] args) {Cat cat = new Cat("元宝",2);Dog dog = new Dog("小七", 1);// 向上转型Animal animal = cat;animal.eat();animal = dog;animal.eat();if(animal instanceof Cat){//更安全cat = (Cat)animal;cat.mew();}if(animal instanceof Dog){dog = (Dog)animal;dog.bark();}}
}

5.5 多态的优缺点

【优点】

假设有如下代码:

class Shape {//属性....public void draw() {System.out.println("画图形!");}
}
class Rect extends Shape{@Overridepublic void draw() {System.out.println("♦");}
}
class Cycle extends Shape{@Overridepublic void draw() {System.out.println("●");}
}
class Flower extends Shape{@Overridepublic void draw() {System.out.println("❀");}
}public class Test {public static void drawShapes() {Rect rect = new Rect();Cycle cycle = new Cycle();Flower flower = new Flower();String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};for (String shape : shapes) {if (shape.equals("cycle")) {cycle.draw();} else if (shape.equals("rect")) {rect.draw();} else if (shape.equals("flower")) {flower.draw();}}}
}

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单.

public static void drawShapes() {// 我们创建了一个 Shape 对象的数组. Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()};for (Shape shape : shapes) {shape.draw();}
}
  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

什么叫"圈复杂度" ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解.而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.如果一个方法的圈复杂度太高, 就需要考虑重构.

  1. 可扩展能力更强
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
class Triangle extends Shape {@Overridepublic void draw() {System.out.println("△");}
}

对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低.而对于不用多态的情况, 就要把 drawShapes 中的 if - else 进行一定的修改, 改动成本更高.

【缺点】:代码的运行效率降低。

  1. 属性没有多态性
    当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
  2. 构造方法没有多态性

5.6 避免在构造方法中调用重写的方法

一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func

class B {public B() {// do nothingfunc();}public void func() {System.out.println("B.func()");}
}class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}
}public class Test {public static void main(String[] args) {D d = new D();}
}// 执行结果
D.func() 0
  • 构造 D 对象的同时, 会调用 B 的构造方法.
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
  • 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
  • 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

6.final 关键字

final关键可以用来修饰变量、成员方法以及类。

  1. 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20;  // 编译出错final int a;
a= 10;

final修饰变量或字段只能初始化一次

  1. 修饰类:表示此类不能被继承
final public class Animal {...
}public class Bird extends Animal {...
}// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承

我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承.

在这里插入图片描述

  1. 修饰方法:表示该方法不能被重写
public class Animal {final public void eat() {System.out.println("Animal:eat()");}
}class Bird extends Animal {public void eat() {  //编译出错System.out.println("Animal:eat()");}
}

7.继承与组合

7.1 组合的概念

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

继承(Inheritance)是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
在这里插入图片描述
组合表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在。组合表示对象之间是has-a的关系,比如:汽车和方向盘,轮胎在这里插入图片描述

组合(Composition)体现的是整体与部分、拥有的关系,即has-a的关系。例如:汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的。

在这里插入图片描述

// 轮胎类
class Tire{// ...
}// 发动机类
class Engine{// ...
}// 车载系统类
class VehicleSystem{// ...
}class Car{private Tire tire;          // 可以复用轮胎中的属性和方法private Engine engine;      // 可以复用发动机中的属性和方法private VehicleSystem vs;   // 可以复用车载系统中的属性和方法// ...
}// 奔驰是汽车
class Benz extend Car{// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

注意:组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议,能用组合尽量用组合。

7.2 继承与组合的区别

组 合 关 系继 承 关 系
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
优点:具有较好的可扩展性缺点:支持扩展,但是往往以增加系统结构的复杂度为代价
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象缺点:不支持动态继承。在运行时,子类无法选择不同的父类
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口缺点:子类不能改变父类的接口
缺点:整体类不能自动获得和局部类同样的接口优点:子类能自动继承父类的接口
缺点:创建整体类的对象时,需要创建所有局部类的对象优点:创建子类的对象时,无须创建父类的对象

相关文章:

继承和多态的详解

文章目录 1. 继承1.1 继承的概念1.3 继承的语法1.3 父类成员访问1.3.1 子类中访问父类的成员变量1.3.2 子类中访问父类的成员方法 1.4 子类构造方法 2.super关键字2.1 super关键字的概念2.2 super和this的区别 3. 在继承中访问限定符的可见性4. 继承方式的分类5. 多态5.1 多态的…...

【Unity】UniTask(异步工具)快速上手

UniTask(异步工具) 官方文档:https://github.com/Cysharp/UniTask/blob/master/README_CN.md URL:https://github.com/Cysharp/UniTask.git?pathsrc/UniTask/Assets/Plugins/UniTask 优点:0GC,可以在任何地方使用 为Unity提供一个高性能&…...

k8s三种常用的项目发布方式

k8s三种常用的项目发布方式 1、 蓝绿发布 2、 金丝雀发布(灰度发布):使用最多 3 、滚动发布 应用程序升级,面临的最大问题是新旧业务之间的切换。 项目的生命周期:立项----定稿----需求发布----开发----测试-----发布 最后测试之后上线。再…...

Nodejs搭配axios下载图片

新建一个文件夹,npm i axios 实测发现只需保留node_modules文件夹,删除package.json不影响使用 1.纯下载图片 其实该方法不仅可以下载图片,其他的文件都可以下载 const axios require(axios) const fs require(fs) var arrPic [https:…...

强化学习在生成式预训练语言模型中的研究现状简单调研

1. 绪论 本文旨在深入探讨强化学习在生成式预训练语言模型中的应用,特别是在对齐优化、提示词优化和经验记忆增强提示词等方面的具体实践。通过对现有研究的综述,我们将揭示强化学习在提高生成式语言模型性能和人类对话交互的关键作用。虽然这些应用展示…...

python_selenium_安装基础学习

目录 1.为什么使用selenium 2.安装selenium 2.1Chrome浏览器 2.2驱动 2.3下载selenium 2.4测试连接 3.selenium元素定位 3.1根据id来找到对象 3.2根据标签属性的属性值来获取对象 3.3根据xpath语句来获取对象 3.4根据标签的名字获取对象 3.5使用bs4的语法来获取对象…...

面试宝典进阶之关系型数据库面试题

D1、【初级】你都使用过哪些数据库? (1)MySQL:开源数据库,被Oracle公司收购 (2)Oracle:Oracle公司 (3)SQL Server:微软公司 (4&#…...

Agisoft Metashape 地面点分类参数设置

Agisoft Metashape 点云分类之地面点分类参数设置 文章目录 Agisoft Metashape 点云分类之地面点分类参数设置前言一、分类地面点参数二、农村及城区有房屋地区二、植被区域分类三、侵蚀半径(Erosion radius)参数设置前言 Agisoft Metashape提供了自动检测地面点的功能,减少…...

计算机科学速成课【学习笔记】(4)——二进制

本集课程B站链接: 4. 二进制-Representing Numbers and Letters with Binary_BiliBili_哔哩哔哩_bilibili4. 二进制-Representing Numbers and Letters with Binary_BiliBili是【计算机科学速成课】[40集全/精校] - Crash Course Computer Science的第4集视频&…...

数据库开发工具Navicat Premium 15 mac软件特色

Navicat Premium 15 mac版是一款数据库开发工具,Navicat Premium 15 Mac版可以让你以单一程序同時连接到 MySQL、MariaDB、SQL Server、SQLite、Oracle 和 PostgreSQL 数据库。 Navicat Premium mac软件特色 无缝数据迁移 数据传输,数据同步和结构同步…...

从零开始构建区块链:我的区块链开发之旅

1.引言 1.区块链技术的兴起和重要性 区块链技术,作为数字化时代的一项颠覆性创新,已经成为当今世界最令人瞩目的技术之一。自比特币的问世以来,区块链技术已经从仅仅支持加密货币发展成为一种具有广泛应用前景的分布式账本技术。其核心优势…...

c JPEG编码,但有错误

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/videodev2.h> //v4l2 头文件 #include <strin…...

二级C语言备考1

一、单选 共40题 &#xff08;共计40分&#xff09; 第1题 &#xff08;1.0分&#xff09; 题号:6923 难度:较易 第1章 以下叙述中正确的是 A:C语言规定必须用main作为主函数名,程序将从此开始执行 B:可以在程序中由用户指定任意一个函数作为主函数…...

【2024系统架构设计】 系统架构设计师第二版-嵌入式系统架构设计理论与实践

目录 一 嵌入式系统软件架构的原理 二 嵌入式系统软件架构的设计方法 三 案例分析 一 嵌入式系统软件架构的原理 🚀嵌入式系统的典型架构可以分为...

用python提取word中的所有图片

使用word中提取的方式图片会丢失清晰度&#xff0c;使用python写一个脚本&#xff0c;程序运行将弹出对话框选择一个word文件&#xff0c;然后在弹出一个对话框选择一个文件夹保存word中的文件。将该word中的所有图片都保存成png格式&#xff0c;并命名成image_i的样式。 程序…...

医疗器械分类及是否需要临床

1、医疗器械的分类&#xff1a; 在中国&#xff0c;医疗器械的管理分为一类、二类和三类&#xff0c;这是根据《医疗器械监督管理条例》的规定划分的。不同类别的医疗器械受到不同的监督和管理&#xff0c;包括注册审批、生产质量监督、市场监管等方面。 一类医疗器械&#x…...

AI人工智能虚拟现实行业发展分析

AI人工智能和虚拟现实是当今科技领域最受关注和研究的两个领域。这两项技术的迅速发展给各行各业带来了巨大的变革和机遇。在过去的几年里&#xff0c;AI和虚拟现实已经取得了显著的进展&#xff0c;并且有着广阔的发展前景。 AI人工智能作为一种模拟人类智能的技术&#xff0…...

3. SPSS数据文件的基本加工和处理

如何获取SPSS自带的案例数据文件&#xff1f; 首先找到SPSS的安装目录&#xff0c;然后找到Samples文件夹 可以看到有不同语言版本&#xff0c;选择简体中文 就能看到很多.sav文件 数据文件的整理 个案排序 单值排序 例&#xff1a;对于下面的数据集&#xff0c;将工资按…...

Ubuntu20二进制方式安装nginx

文章目录 1.下载nginx安装包2.安装nginx3.安装出现的问题及解决方案错误1&#xff1a;错误2&#xff1a;错误3&#xff1a; 4.常用命令5.知识扩展&#xff1a; 1.下载nginx安装包 nginx官网&#xff1a;http://nginx.org/en/download.html 选择稳定的nginx版本下载。 2.安装ngi…...

window mysql5.7 搭建主从同步环境

window 搭建mysql5.7数据库 主从同步 主节点 配置文件my3308.cnf [mysql] # 设置mysql客户端默认字符集 default-character-setutf8mb4[mysqld] server-id8 #server-uuidbc701be9-ac71-11ee-9e35-b06ebf511956 log-binD:\mysql_5.7.19\mysql-5.7.19-winx64\mysql-bin binlog-…...

MCU、MPU、SOC简介

文章目录 前言一、MCU二、MPU三、SOC总结 前言 随着处理器技术的不断发展&#xff0c;CPU(Central Processing Unit)的发展逐渐出现三种分支&#xff0c;分别是MCU(Micro Controller Unit&#xff0c;微控制器单元) 和MPU&#xff08;Micro Processor Unit&#xff0c;微处理器…...

Kubernetes那点事儿——配置存储:ConfigMap、Secret

配置存储&#xff1a;ConfigMap、Secret 前言ConfigMapSecret 前言 前面介绍过的各种存储主要都是做数据的持久化&#xff0c;本节介绍的ConfigMap和Secret主要用于配置文件存储&#xff0c;或者环境变量的配置。 ConfigMap 创建ConfigMap后&#xff0c;数据实际会存储在K8s中…...

小白向:搭建企业培训APP的完整技术指南

当下&#xff0c;许多企业转向了现代化的培训方法&#xff0c;其中一个关键的工具就是企业培训APP。本文将为你提供搭建企业培训APP的完整技术指南&#xff0c;助你在数字化时代更好地满足企业培训的需求。 一、需求分析与功能规划 在开始开发之前&#xff0c;首先需要明确企…...

CVE-2023-36025 Windows SmartScreen 安全功能绕过漏洞

CVE-2023-36025是微软于11月补丁日发布的安全更新中修复Windows SmartScreen安全功能绕过漏洞。攻击者可以通过诱导用户单击特制的URL来利用该漏洞&#xff0c;对目标系统进行攻击。成功利用该漏洞的攻击者能够绕过Windows Defender SmartScreen检查及其相关提示。该漏洞的攻击…...

分析一个项目(微信小程序篇)二

目录 首页&#xff1a; 发现&#xff1a; 购物车&#xff1a; 我的&#xff1a; 分析一个项目讲究的是如何进行对项目的解析分解&#xff0c;进一步了解项目的整体结构&#xff0c;熟悉项目的结构&#xff0c;能够知道每个组件所处在哪个位置&#xff0c;发挥什么作用。 接…...

每日论文推送(有中文摘或源码地址或项目地址)

***VX搜索“晓理紫”并关注就可以每日获取最新论文***标题: “It’s not like Jarvis, but it’s pretty close!” – Examining ChatGPT’s Usage among Undergraduate Students in Computer Science作者: Ishika Joshi, Ritvik Budhiraja, Harshal D Akolekar摘要: Large lan…...

【漏洞复现】锐捷EG易网关login.php命令注入漏洞

Nx01 产品简介 锐捷EG易网关是一款综合网关&#xff0c;由锐捷网络完全自主研发。它集成了先进的软硬件体系架构&#xff0c;配备了DPI深入分析引擎、行为分析/管理引擎&#xff0c;可以在保证网络出口高效转发的条件下&#xff0c;提供专业的流控功能、出色的URL过滤以及本地化…...

Nginx安装http2和ssl模块

Nginx安装http2和ssl模块 Nginx在执行默认安装命令的时候&#xff0c;并不会编译启用ngx_http_v2_module模块。故在修改Nginx配置文件启用http2.0协议的时候会报错。 一.检查Nginx安装了哪些模块 #进入Nginx的安装目录 cd /usr/local/nginx #执行命令查看安装了哪些模块 ./sbi…...

Android 8.1 隐藏设置中定位功能

Android 8.1 隐藏设置中定位功能 最近接到客户反馈&#xff0c;需要隐藏设备设置中的定位功能&#xff0c;具体修改参照如下&#xff1a; /vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/SecuritySettings.java mLocationcontroller.displ…...

无线网卡怎么连接台式电脑?正确操作步骤分享!

“我在使用电脑时经常都需要用到网络&#xff0c;请问大家在使用无线网卡时怎么将它与台式电脑进行连接的呢&#xff1f;” 使用电脑的用户在进行网上冲浪时都需要先连接网络。如果不想使用网线&#xff0c;无线网卡不仅可以为用户提供网络服务&#xff0c;在使用时该更加灵活和…...

聚道云软件连接器助力某贸易公司实现付款流程自动化

客户介绍&#xff1a; 某贸易公司是一家集进出口贸易、国内贸易、电子商务等业务于一体的综合性贸易企业。公司业务遍及全球多个国家和地区&#xff0c;拥有庞大的供应商网络和采购需求。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 客户痛点&#…...

第六讲_css盒子模式

css盒子模型 1. 长度单位2. 盒子模型的组成2.1 盒子模型内容2.2 盒子模型内边距2.3 盒子模型边框2.4 盒子模型外边距 1. 长度单位 px&#xff1a;像素em&#xff1a;相对于当前元素或父元素的 font-size 的倍数 <style>.parent {height: 500px;width: 500px;background…...

【WSL】Win10 使用 WSL2 进行 Linux GPU 开发

1. GPU 驱动 先安装 驱动 参考 https://docs.nvidia.com/cuda/wsl-user-guide/index.html 使用 https://www.nvidia.com/Download/index.aspx 提供的兼容 GeForce 或 NVIDIA RTX/Quadro 显卡在系统上安装 NVIDIA GeForce Game Ready 或 NVIDIA RTX Quadro Windows 11 显示驱动…...

什么是博若莱新酒节?

在红酒圈儿里混&#xff0c;一定不能不知道博若莱新酒节&#xff0c;这是法国举世闻名的以酒为主题的重要节日之一。现已成为世界范围内庆祝当年葡萄收获和酿制的节日&#xff0c;被称为一年一度的酒迷盛会。 云仓酒庄的品牌雷盛红酒LEESON分享博若莱位于法国勃艮第南部&#x…...

Centos7下升级gcc/g++版本(简单 + 避坑)

在 Centos7 下&#xff0c;使用 yum 安装高版本的 gcc/g 最简单&#xff1a; yum -y install centos-release-scl yum -y install devtoolset-11-gcc devtoolset-11-gcc-c devtoolset-11-binutils需要安装哪个个版本的gcc/g&#xff0c;就把数字替换成对应的版本号。例如上面代…...

PyTorch项目源码学习(1)

PyTorch PyTorch是一个开源的深度学习框架&#xff0c;项目地址 https://github.com/pytorch/pytorch 在学术工作中的使用频率较高。 其主体由C开发&#xff0c;PyTorch是Torch的Python调用版本。学习PyTorch项目源代码的目的在于学习其中的实现方法与技巧&#xff0c;增加训练…...

adb 使用的出现 adb server is out of date. killing

我知道你们肯定看到这种播客 像这种解决问题的方法是暂时的 , 因为我们Android studio 中 , 你在查看后台的时候发现 你的Android studio 也占用了一端口 , 就是说你把 Android studio 杀掉了只要打开 Android studio 打开就还是 关闭 cmd adb 看到一个新的方法 , win 10 中…...

数据结构之二叉搜索树(Binary Search Tree)

数据结构可视化演示链接&#xff0c;也就是图片演示的网址 系列文章目录 数据结构之AVL Tree 数据结构之B树和B树 数据结构之Radix和Trie 文章目录 系列文章目录示例图定义二叉搜索树满足的条件应用场景 示例图 二叉 线形(顺序插入就变成了线性树&#xff0c;例如插入顺序为&…...

Spring Boot自定义启动Banner在线生成工具

记录一下&#xff0c;后续有更新添加 链接: Spring Boot自定义启动Banner在线生成工具...

Android Studio导入项目 下载gradle很慢或连接超时,提示:Read timed out---解决方法建议收藏!

目录 前言 一、报错信息 二、解决方法 三、更多资源 前言 一般来说&#xff0c;使用Android Studio导入项目并下载gradle的过程应该是相对顺利的&#xff0c;但是有时候会遇到下载速度缓慢或连接超时的问题&#xff0c;这可能会让开发者感到头疼。这种情况通常会出现在网络…...

汽车标定技术(十五)--FETK如何帮助Aurix实现快速原型、标定测量功能(1)

目录 1.概述 2.ETK描述 2.1 产品概述 2.2 FETK-T1.1产品描述 2.3 FETK-T1.1内部构造 3.小结...

linux项目部署(jdk,tomcat,mysql,nginx,redis)

打开虚拟机&#xff0c;与连接工具连接好&#xff0c;创建一个文件夹 cd /tools 把jdk,tomcat安装包放入这个文件夹里面 jdk安装 #解压 tar -zxvf apache-tomcat-8.5.20.tar.gz #解压jdk tar -zxvf jdk-8u151-linux-x64.tar.gz 编辑jdk文件以及测试jdk安装 第一行代码路径…...

Unity | 渡鸦避难所-6 | 有限状态机控制角色行为逻辑

1 有限状态机简介 有限状态机&#xff08;英语&#xff1a;finite-state machine&#xff0c;缩写&#xff1a;FSM&#xff09;&#xff0c;简称状态机&#xff0c;是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型 在游戏开发中应用有限状态机&#xff…...

数据库参数 PGA_AGGREGATE_LIMIT 限制进程大小

在自动化 PGA 内存管理模式下&#xff0c;Oracle 数据库通过动态控制分配到工作区的 PGA 内存量来尝试遵从 PGA_AGGREGATE_TARGET 值。但是&#xff0c;有时因为以下原因&#xff0c;PGA 内存使用量可能会超过 PGA_AGGREGATE_TARGET 设置&#xff1a; PGA_AGGREGATE_TARGET 设置…...

208.【2023年华为OD机试真题(C卷)】停车场车辆统计(贪心算法实现-JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-停车场车辆统计二.解题思路三.题解代码Python题…...

JS 作用域和预解析

作用域 通常来说&#xff0c;一段程序代码中所用到的名字并不总是有效和可用的&#xff0c;而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性&#xff0c;增强了程序的可靠性&#xff0c;减少了名字冲突。 作用域分为全局作用域和局…...

各种锁的概述

乐观锁与悲观锁 悲观锁指对数据被外界修改持保守态度&#xff0c;认为数据很容易就会被其他线程修改&#xff0c;所以在数据被处理前先对数据进行加锁&#xff0c;并在整个数据处理过程中&#xff0c;使数据处于锁定状态。 悲观锁的实现往往依靠数据库提供的锁机制&#xff0…...

【docker笔记】Docker容器数据卷

Docker容器数据卷 卷就是目录或者文件&#xff0c;存在于一个或多个容器中&#xff0c;由docker挂载到容器&#xff0c;但不属于联合文件系统&#xff0c;因此能够绕过Union File System提供一些用于持续存储或共享数据的特性 卷的设计目的就是数据的持久化&#xff0c;完全独…...

大前端nestjs入门教程系列(四):如何nestjs整合mysql数据库

经过前面的几篇文章&#xff0c;想必大家已经对nestjs有了基础的了解&#xff0c;那么这篇文章就带大家玩玩数据库&#xff0c;学会了这篇&#xff0c;就离大前端又进了一步 Nest与数据库无关&#xff0c;使你可以轻松地与任何 SQL 或 NoSQL 数据库集成。 根据你的喜好&#xf…...

Android studio环境配置

1.搜索android studio下载 Android Studio - Download 2.安装 3.配置环境 配置gradle&#xff0c;gradle参考网络配置。最后根据项目需求选择不同的jdk。...