5.1 子类与父类

举一个不恰当的例子,你在给别人做自我介绍时,有些东西不用全部说。
我叫张三,我有俩眼睛,一个鼻子一个嘴巴。

这时候你就要想了,这不明摆着废话吗? 雀氏这样,那么我们深入思考下,为什么这些基本情况不用从头说起?

这时候就有人要说了,你听听你说的是话吗?

关键点还真就在这个上面。
前面张三的自我介绍,这就是一个人的基本特征,这些都是人类所共同拥有的,这些东西就不用说。


那么当我们在编写一个类时,发现某个类有我们所需要的行为和特征时。我们再次编写这样会造成代码冗余,为了解决这个问题,我们要考虑代码复用性问题

我能不能把之前已经写好的类拿过来直接用呢,或者说将以前写过的类当做一个模板,直接套用呢?

能!!!! 这个黑魔法在Java的世界中称之为继承

继承是用已有类创建新的类的一种机制。

在使用继承前,我们要先把共同特征抽象出来,比如说不论是男人还是女人,在正常情况下有两条腿,
我们又知道不管是男人还是女人同属于人,那么综上所述,我们可以将父类 抽象出。

人:有两条腿

男人女人同继承自,所以男人女人都有两条腿。

那么我们来梳理下上面的关系

人(被继承者):叫做父类/超类/基类
男/女人(继承者):称之为子类/派生类

5.1.1 子类

上面我们已经知道了,子类是继承者。在Java中使用关键字extends来表示继承。

语法如下

  1. // 父类:人
  2. class People{
  3. }
  4. // 子类:男人 继承自 人
  5. class Man extends People{
  6. }
  7. // 子类: 女人 继承自 人
  8. class Woman extends People{
  9. }

如果你看到一个类,上面带有**extends**关键字,那么这个类一定是一个子类

5.1.2 类的树形结构

Java中类的集是以树形组织的。

如果你看一个类没有使用 extends 关键字 那么他同样是 Object的子类
因为
Object类是Java中所有类的父类

那么上面的代码就可以描述为

  1. - Object
  2. - People
  3. - Man
  4. - Woman

5.2 子类的继承性

类可以有两种重要的成员:成员变量方法
子类成员有一部分是子类自己声明,定义的,另一部分是从父类继承过来的。

什么叫继承呢?

父类中的成员变量或者方法,可以直接在子类中去使用,这就是继承。

5.2.1 子类和父类在同一包中的继承性

如果子类和父类在同一个包中,那么子类继承了父类中不是**private**的成员变量和成员方法,作为自己的成员变量,成员方法。

  1. package base.ch5;
  2. class People {
  3. private int legs = 2;
  4. public void walk() {
  5. System.out.println(this + ":我用" + legs + "条腿走路");
  6. }
  7. void drink(){
  8. System.out.println("喝水水");
  9. }
  10. private void test(){
  11. System.out.println("Hello,World!");
  12. }
  13. }
  14. class Man extends People {
  15. }
  16. class Woman extends People {
  17. }
  18. public class ExtendsTest {
  19. public static void main(String[] args) {
  20. new People().walk();
  21. new Man().walk();
  22. // 友好方法可以被继承
  23. new Man().drink();
  24. new Woman().walk();
  25. // new Woman().test()
  26. }
  27. }
  1. base.ch5.People@60e53b93:我用2条腿走路
  2. base.ch5.Man@5e2de80c:我用2条腿走路
  3. 喝水水
  4. base.ch5.Woman@1d44bcfa:我用2条腿走路

看这里的子类继承了父类的walk,还有drink方法。 但是父类的 private方法子类却无法继承。

5.2.2 子类和父类不在同一包中的继承性

子类和父类不在同一个包中时,子类只能继承父类**protected****public**访问权限成员变量/方法,作为子类的成员变量/方法。

这里要注意的是 子类和父类不在同一个包中 protected 方法虽然可以被子类调用,但是不可以被子类实例化对象直接调用,需要在子类通过super关键字间接调用。

5.2.3 继承关系的UML图

如果一个类是另一个类的子类,那么UML中通过**实线**连接两个类的UML图,终点用**空心三角结束**

5.2.4 protected 的进一步说明

  • 当子类和父类在同一个包中:子类可以直接访问父类的protected方法
  • 当子类和父类不在同一个包中: 子类可以通过super关键字来访问父类的protected方法

5.3 子类与对象

instanceof运算符

instanceof运算符是Java独有的双目运算符,左边操作元是对象,右边操作元是类

当左边操作元的对象是右边操作元的**类或者其子类**创建时表达式值为**true**

  1. class Main {
  2. public static void main(String[] args) {
  3. System.out.println(new Man() instanceof People);
  4. }
  5. }

5.4 成员变量的隐藏和方法重写

5.4.1 成员变量的隐藏

当子类的成员变量和继承自父类的成员变量名字相同时(类型可以不同),子类会隐藏掉继承自父类的成员变量

  • 子类自己定义的方法操作与父类同名的成员变量是子类重新生成的成员变量
  • 子类调用从父类继承的成员方法操作与父类同名的成员变量一定是被子类隐藏的成员变量(父类的成员变量)

示例代码

  1. public class Animal {
  2. public int legs =2;
  3. void info(){
  4. System.out.println("[super] legs:"+legs);
  5. }
  6. }
  7. class Rabbit extends Animal{
  8. // 和父类成员变量同名
  9. int legs = 4;
  10. // 子类自己的方法
  11. void intro(){
  12. System.out.println("[child] legs:"+legs);
  13. }
  14. }
  15. class AnimalTest{
  16. public static void main(String[] args) {
  17. Rabbit rabbit = new Rabbit();
  18. // 子类调用继承自父类的成员方法 访问的是隐藏的成员变量
  19. rabbit.info();
  20. // 子类调用自己的成员方法 访问的是 子类的成员变量
  21. rabbit.intro();
  22. }
  23. }
  1. [super] legs:2
  2. [child] legs:4

5.4.2 方法重写

同样的子类可以通过重写来隐藏继承自父类的方法,这个过程称之为重写 (Override)

方法重写的规则

如果子类有权继承自父类的某个方法,那么子类就有权利重写这个方法。方法重写就是子类中定义一个和父类方法**名字****参数列表****返回值** 一模一样的方法来”替换”掉父类方法

方法重写的目的

那就举个栗子叭,我们都知道兔子和老虎都属于动物,那么我们就可以说 兔子和老虎都继承自动物,接下来抽取共同特性,动物都会吃,所以父类中定义一个eat方法。

那么根据Java中继承的规则,兔子和老虎都继承了这个eat的方法。

我们要思考这里面存在的问题 :兔子是食草动物,老虎是食肉动物


所以我们要在兔子这个类中用一个新的方法去”替换”掉父类中写的eat,让兔子去食草。

同理我们也要在老虎中用一个新的方法去”替换”掉eat方法,让老虎食肉。

需要注意的一点是这里的”替换”,是要满足上面方法重写规则呦,我们把这个替换的过程称之为方法的重写

  1. public class Animal {
  2. void eat(){
  3. System.out.println("动物会吃");
  4. }
  5. }
  6. class Rabbit extends Animal{
  7. @Override
  8. void eat() {
  9. System.out.println("兔子食草");
  10. }
  11. }
  12. class Tiger extends Animal{
  13. @Override
  14. void eat() {
  15. System.out.println("老虎食肉");
  16. }
  17. }
  18. class OverrideTest{
  19. public static void main(String[] args) {
  20. Animal animal = new Animal();
  21. animal.eat();
  22. Rabbit rabbit = new Rabbit();
  23. rabbit.eat();
  24. Tiger tiger = new Tiger();
  25. tiger.eat();
  26. }
  27. }

这时候你就要问了 这里的 @Override 是什么呀?

其实这里的@Override是Java的一个注解,暂时不用做过多的研究,你只需要知道 在你重写的方法上面加上这个,他就会对你的方法进行重写规则检查,在这里起一个检查的作用。

重写时注意的事项

在重写父类方法时,不允许降低方法的访问权限,但是可以提高访问权限。

来复习下 什么是访问权限修饰符?

访问权限从高到低依次是什么?

分别代表什么作用?

5.5 super关键字

5.5.1 用super关键字操作被隐藏的成员变量和方法

子类隐藏了父类的成员变量,那么子类创建的悐就不再拥有该成员变量,如果我就是要访问咋办呢? 可以使用 super 关键字

当子类想要调用父类中被隐藏的成员变量/方法时,可以使用**super**关键字

  1. class AnimalI{
  2. int legs = 2;
  3. void info(){
  4. System.out.println("[super] legs:"+legs);
  5. }
  6. }
  7. class Frog extends AnimalI{
  8. int legs = 4;
  9. @Override
  10. void info() {
  11. System.out.println("[child] legs:"+legs);
  12. super.info();
  13. System.out.println("[super-legs]:"+super.legs);
  14. }
  15. }
  16. public class SuperTest {
  17. public static void main(String[] args) {
  18. new AnimalI().info();
  19. System.out.println("---------");
  20. new Frog().info();
  21. }
  22. }
  1. [super] legs:2
  2. ---------
  3. [child] legs:4
  4. [super] legs:2
  5. [super-legs]:2

5.5.2 用super调用父类的构造方法

当子类构造方法创建一个子类对象时,会首先调用父类的构造方法。

  1. class Parent{
  2. public Parent() {
  3. System.out.println("Parent:无参构造被触发");
  4. }
  5. }
  6. class Child extends Parent{
  7. public Child() {
  8. System.out.println("Child:无参构造被触发");
  9. }
  10. }
  11. public class ConstructorTest {
  12. public static void main(String[] args) {
  13. new Child();
  14. }
  15. }
  1. Parent:无参构造被触发
  2. Child:无参构造被触发

由于子类不能继承父类构造方法,因此子类在构造方法内要通过super关键字来调用父类构造方法。super()并且super必须是子类构造方法中的第一条语句

即便你没有写Java也会默认生成一个 super() 即默认无参构造方法

  1. public class Student {
  2. protected String name;
  3. protected int age;
  4. protected String no;
  5. public Student(String name, int age, String no) {
  6. this.name = name;
  7. this.age = age;
  8. this.no = no;
  9. }
  10. @Override
  11. public String toString() {
  12. return "Student{" +
  13. "name='" + name + '\'' +
  14. ", age=" + age +
  15. ", no='" + no + '\'' +
  16. '}';
  17. }
  18. }
  19. class StudentA extends Student{
  20. protected String level;
  21. public StudentA(String name, int age, String no, String level) {
  22. // 调用父类构造方法
  23. super(name, age, no);
  24. this.level = level;
  25. }
  26. @Override
  27. public String toString() {
  28. return "StudentA{" +
  29. "name='" + name + '\'' +
  30. ", age=" + age +
  31. ", no='" + no + '\'' +
  32. ", level='" + level + '\'' +
  33. '}';
  34. }
  35. }
  36. class StudentTest{
  37. public static void main(String[] args) {
  38. Student lisi = new Student("李四", 21, "00002");
  39. System.out.println(lisi);
  40. StudentA studentA = new StudentA("张三", 22, "0001", "大三");
  41. System.out.println(studentA);
  42. }
  43. }
  1. Student{name='李四', age=21, no='00002'}
  2. StudentA{name='张三', age=22, no='0001', level='大三'}

这里有亿点点细节

  • 如果你没写构造方法那么Java会给你生成一个默认的无参构造方法
  • 如果你写了构造方法,那么Java不会为你生成默认无参构造方法

如果父类中没有默认无参构造方法,那么子类中一定要通过 super 来调用父类的有参构造方法

5.6 final关键字

final 是一个关键字,可以用来修饰,成员变量/方法局部变量

5.6.1 final类

被 final关键字修饰的类,不能够被继承

5.6.2 final 方法

如果父类中一个方法被 final关键字修饰那么子类中无法重写该方法

5.6.3 常量

如果一个类的成员变量或者局部变量被final关键字修饰,那么它将成为一个常量

何为常量?

常量顾名思义就是 常数量 是不可以被修改的。

5.7 对象的上转型对象

我们经常说,“老虎是动物”, 这里的老虎是子类,动物是父类,那么二者之间的关系就是 “is-a” 关系。

当我们说老虎是动物时,老虎将丧失掉老虎独有的属性和功能。

我们总不能说老虎会爬树,所有的动物都会爬树吧,显然太过于绝对。

当我们使用这种上溯思维时,子类看做父类时,子类将丧失自己的行为和特征

  1. public class Animal {
  2. protected void eat(){
  3. System.out.println("动物会吃");
  4. }
  5. }
  6. class Tiger extends Animal{
  7. protected void climb(){
  8. System.out.println("老虎会爬树");
  9. }
  10. }
  11. class Test{
  12. public static void main(String[] args) {
  13. Tiger tiger = new Tiger();
  14. tiger.eat(); // 老虎是动物所以会吃
  15. tiger.climb(); // 老虎独有特性:会爬树
  16. System.out.println("--------------");
  17. Animal animal = tiger; // 将老虎看做动物,老虎将丧失爬树技能
  18. animal.eat();
  19. // animal.climb(); // 错误:方法不存在
  20. }
  21. }

将一个子类的引用传递给父类子类将丢失自己的特性,这个过程称之为对象的上转型

那么上转型对象都有哪些特性呢?

  • 上转型对象不能操作子类新增的成员变量/方法 (丢失掉的特性)
  • 上转型对象可以访问子类继承或隐藏的成员变量,也可以调用子类继承的方法,或者子类重写的方法
  • 不要将父类创建的对象和子类对象的上转型混淆
  • 可以将对象的上转型对象再强制转换到一个子类对象,此时子类对象又具备了子类的所有属性和功能。
  • 如果子类重写了父类的静态方法,那么子类对象的上转型对象不能调用子类重写的静态方法,只能调用父类的静态方法(因为JVM静态方法区原因)
  1. public class Apes {
  2. protected int foot =2;
  3. public void walk(){
  4. System.out.println("我是类人猿我用"+foot+"条腿走路");
  5. }
  6. protected static void sayHello(){
  7. System.out.println("wo~wo~wo~");
  8. }
  9. }
  10. class People extends Apes{
  11. @Override
  12. public void walk() {
  13. System.out.println("我是人我用"+foot+"条腿走路");
  14. }
  15. protected static void sayHello(){
  16. System.out.println("你好啊,我是佩奇!");
  17. }
  18. }
  19. class ApesTes{
  20. public static void main(String[] args) {
  21. Apes apes = new Apes();
  22. People people = new People();
  23. apes.walk();
  24. people.walk();
  25. // ----------上转型对象 -------
  26. apes = people;
  27. // 上转型对象调用的是父类的静态方法,而不是子类重写的静态方法
  28. apes.sayHello();
  29. // 上转型对象可以强制转换为子类对象,此时丢失的特性恢复
  30. People p =(People) apes;
  31. p.walk();
  32. p.sayHello();
  33. }
  34. }
  1. 我是类人猿我用2条腿走路
  2. 我是人我用2条腿走路
  3. wo~wo~wo~
  4. 我是人我用2条腿走路
  5. 你好啊,我是佩奇!

上面代码可能理解起来有些困难,建议多读几遍

  1. Apes p = new People(); // 这也是上转型对象

5.8 继承与多态

其实在前面的上转型对象中我们就已经接触到了多态。

老虎食肉,兔子食草,他们都是继承自动物,并且可通过重写eat方法实现,那么我们将这两个子类的引用放到父类对象中,也就是我们说的上转型对象,就实现了多态


同样是eat方法,根据对象上转型的规则,当我们将老虎赋值给动物时,调用eat方法时调用的是老虎自己的eat方法,那么兔子也是如此。

这个过程中产生了多种状态,所以称之为多态

那么多态的具体概念是什么呢?

父类么某个方法被子类重写时,可以产生自己的功能行为。

当老虎重写父类的eat方法时,老虎食肉

当兔子重写父类的eat方法时,兔子食草

  1. public class Animal {
  2. protected void eat(){
  3. System.out.println("动物会吃");
  4. }
  5. }
  6. class Tiger extends Animal{
  7. @Override
  8. protected void eat() {
  9. System.out.println("老虎食肉");
  10. }
  11. }
  12. class Rabbit extends Animal{
  13. @Override
  14. protected void eat() {
  15. System.out.println("兔子食草");
  16. }
  17. }
  18. class Test{
  19. public static void main(String[] args) {
  20. Animal tiger = new Tiger();
  21. Animal rabbit = new Rabbit();
  22. tiger.eat();
  23. rabbit.eat();
  24. System.out.println("------------");
  25. // 这时候你就要问了 这里即便不使用上转型对象也可以实现啊 为啥还要上转型对象呢?
  26. // 我们将代码稍微变动下,这样是不是提高了代码复用率呢,没错多态就是这么的神奇
  27. invoke(tiger);
  28. invoke(rabbit);
  29. }
  30. public static void invoke(Animal animal){
  31. animal.eat();
  32. }
  33. }
  1. 老虎食肉
  2. 兔子食草
  3. ------------
  4. 老虎食肉
  5. 兔子食草

多态是一个重点+难点一定要掌握牢

5.9 abstract类与abstract方法

用关键字abstract修饰的类称之为abstract类抽象类

  1. abstract class A{
  2. }

用关键字abstract修饰的方法称之为abstract方法抽象方法

  1. abstract void eat();
  • 对于abstract方法,只允许声明,不允许实现,也就是说abstract方法,没有方法体,{}
  • 不允许使用static修饰abstract方法,abstract方法必须是实例方法
  • 不允许使用 final 和 abstract方法同时修饰一个方法或者类(思考下为什么不允许?)

abstract类中可以有非abstract方法

和普通类相比,abstract类中,可以有abstract方法,也可以有非abstract方法。

  1. public abstract class Calculate {
  2. // abstract 方法 (没有方法体)
  3. abstract double add(double a,double b);
  4. // 非 abstract 方法 (有方法体)
  5. double sub(double a,double b){
  6. return a-b;
  7. }
  8. }

abstract类中也可以没有abstract 方法。

abstract类不能使用**new** 运算符创建对象

对于abstract类,不能使用new运算符创建该类的对象,如果一个非抽象类是某个抽象类的子类,那么他必须重写父类的抽象方法,这就是为什么不允许使用final和abstract同时修饰同一个方法或类的原因。

abstract类的子类

  • 如果一个非抽象类是一个抽象类的子类,他必须重父类的抽象方法。
  • 如果一个抽闲类是一个抽象类的子类,他可以重写父类的抽象方法,也可以继承父类的抽象方法。
  1. public abstract class Animal {
  2. // 抽象方法不需要方法体
  3. abstract void eat();
  4. // 抽象类中可以有非抽象方法
  5. void walk(){
  6. System.out.println("动物会跑");
  7. }
  8. }
  9. abstract class Cats extends Animal{
  10. // 重写父类抽象方法
  11. @Override
  12. void eat() {
  13. System.out.println("猫科动物都食肉");
  14. }
  15. // 继承了 walk 方法
  16. }
  17. class Cat extends Cats{
  18. @Override
  19. void walk() {
  20. System.out.println("猫会跑");
  21. }
  22. }
  23. class Test{
  24. public static void main(String[] args) {
  25. Cat cat = new Cat();
  26. cat.eat();
  27. cat.walk();
  28. }
  29. }
  1. 猫科动物都食肉
  2. 猫会跑

abstract类的对象作为上转型对象

可以使用abstract类声明对象,尽管不能使用 new运算符创建该对象,但该对象可以成为其子类对象的上转型对象。

理解abstract类

  • 抽象类可以抽象出 重要的行为标准,该行为用抽象方法表示。 也可以说 抽象类定义了子类**必须**要有的行为准则
  • 抽象类声明的对象可以成为其子类的对象的上转型对象,调用子类重写的方法,体现了子类根据抽象类行为准则”约束自己”

加深理解抽象类

男孩找女朋友,对女孩提取一些”约束要求”,例如 会做饭,勤俭持家,等一些行为标准。

根据这些行为标准,我们写出抽象类

  1. public abstract class GirlFriend {
  2. abstract boolean cooking(); // 是否会做饭
  3. abstract boolean husband(); // 是否勤俭持家
  4. }
  5. class GirlA extends GirlFriend {
  6. @Override
  7. boolean cooking() {
  8. System.out.println("A:我会做饭");
  9. return true;
  10. }
  11. @Override
  12. boolean husband() {
  13. System.out.println("A:胡吃海喝,我妈说彩礼30W");
  14. return false;
  15. }
  16. @Override
  17. public String toString() {
  18. return "GirlA";
  19. }
  20. }
  21. class GirlB extends GirlFriend {
  22. @Override
  23. boolean cooking() {
  24. System.out.println("B:会做饭");
  25. return true;
  26. }
  27. @Override
  28. boolean husband() {
  29. System.out.println("B:勤俭持家,相夫教子");
  30. return true;
  31. }
  32. @Override
  33. public String toString() {
  34. return "GirlB";
  35. }
  36. }
  37. class GirlC extends GirlFriend {
  38. @Override
  39. boolean cooking() {
  40. System.out.println("C:不会做饭");
  41. return false;
  42. }
  43. @Override
  44. boolean husband() {
  45. System.out.println("B:天天酒吧,KTV");
  46. return false;
  47. }
  48. @Override
  49. public String toString() {
  50. return "GirlC";
  51. }
  52. }
  53. class Boy {
  54. // 结婚
  55. public boolean tryMarried(GirlFriend gf) {
  56. if ((!gf.cooking() || !gf.husband())) {
  57. System.out.println("Boy:不合适哦," + gf);
  58. return false;
  59. }
  60. System.out.println("Boy:" + gf + ",真是一个贤妻良母");
  61. return true;
  62. }
  63. public static void main(String[] args) {
  64. Boy boy = new Boy();
  65. boy.tryMarried(new GirlA());
  66. boy.tryMarried(new GirlB());
  67. boy.tryMarried(new GirlC());
  68. }
  69. }

5.10 面向抽象编程

在设计程序时,经常会用到abstract类,原因是 抽象类只需要关系操作,无需关系实现细节,它实现了对行为的约束,从而让开发者把精力放到程序设计上,而不是纠结内部实现。(将这些细节留给子类的设计者)。

使用多态进行程序设计核心技术之一:使用上转型对象,即将abstract类声明的对象作为其子类的上转型对象。


所谓面相抽象编程指的是当设计某一个重要的类时,不让该类指向对应的类,而是应当指向抽象类,那么前面的动物-老虎-兔子,我们就可以改写成抽象类。

  1. public abstract class Animal {
  2. abstract void eat(); // 动物会吃
  3. abstract void run(); // 动物也会跑
  4. }
  5. class Cat extends Animal{
  6. @Override
  7. void eat() {
  8. System.out.println("猫食肉");
  9. }
  10. @Override
  11. void run() {
  12. System.out.println("猫会爬树");
  13. }
  14. }
  15. class Rabbit extends Animal{
  16. @Override
  17. void eat() {
  18. System.out.println("兔子食草");
  19. }
  20. @Override
  21. void run() {
  22. System.out.println("兔子会跳跃");
  23. }
  24. }
  25. class Test{
  26. public static void invoke(Animal an){
  27. an.eat();
  28. an.run();
  29. }
  30. public static void main(String[] args) {
  31. invoke(new Cat());
  32. invoke(new Rabbit());
  33. }
  34. }

看吧这里我们用一个方法就完成了多态的实现

同一个方法,作用在不同的对象上,导致了不同的结果

通过面向抽象编程目的是为了应对用户需求的编号,比如说上面的例子中我们加一个FIsh他也同样满足动物的特征,以不变应万变

  1. class Fish extends Animal{
  2. @Override
  3. void eat() {
  4. System.out.println("我是食草/肉动物");
  5. }
  6. @Override
  7. void run() {
  8. System.out.println("我会游");
  9. }
  10. }
  11. class Test{
  12. public static void invoke(Animal an){
  13. an.eat();
  14. an.run();
  15. }
  16. public static void main(String[] args) {
  17. invoke(new Fish());
  18. }
  19. }

5.11 开闭原则

所谓开闭原则,就是说对设计的系统 扩展开放,修改关闭

当我们给系统新增模块时不应当修改现有模块,从而提高系统稳定性

先看一个不使用开闭原则的例子

  1. public abstract class Pen {
  2. abstract void write(); // 笔的特性: 写
  3. }
  4. class BluePen extends Pen{
  5. @Override
  6. void write() {
  7. System.out.println("我能书写蓝色笔迹");
  8. }
  9. }
  10. class RedPen extends Pen{
  11. @Override
  12. void write() {
  13. System.out.println("我能书写红色笔迹");
  14. }
  15. }
  16. class Test{
  17. public static void main(String[] args) {
  18. Pen bluePen = new BluePen();
  19. Pen redPen = new RedPen();
  20. bluePen.write();
  21. redPen.write();
  22. }
  23. }
  1. 我能书写蓝色笔迹
  2. 我能书写红色笔迹

明显这个系统不易维护,如果我们要写一个 紫色的笔,我们就要写一个新的类,。。。。。 然后陷入了无限的循环,那这样我们的笔就成了一个一次性的笔

为什么说上面的代码不满足开闭原则,因为我们每次新添加一个类,都要修改 write 方法,显然这样是不合乎常理的。

那么如何改善这个问题呢?

我们深入思考下,笔里面是有笔芯的,笔芯可替换,那么我们要根据开闭原则,来更换笔芯,而不是增加笔。

  1. public abstract class Refill {
  2. protected String color;
  3. abstract void write(); // 笔芯的特性书写
  4. // 笔芯构造方法
  5. public Refill(String color) {
  6. this.color = color;
  7. }
  8. }
  9. // 默认笔芯
  10. class DefaultRefill extends Refill {
  11. @Override
  12. void write() {
  13. System.out.println("我能书写:" + color);
  14. }
  15. public DefaultRefill(String color) {
  16. super(color);
  17. }
  18. }
  19. class Pen {
  20. private Refill refill;
  21. // 更换笔芯
  22. public void setRefill(Refill refill) {
  23. this.refill = refill;
  24. }
  25. public void write() {
  26. refill.write();
  27. }
  28. }
  29. class Test {
  30. public static void main(String[] args) {
  31. Pen pen = new Pen();
  32. pen.setRefill(new DefaultRefill("红色"));
  33. pen.write();
  34. pen.setRefill(new DefaultRefill("蓝色"));
  35. pen.write();
  36. }
  37. }
  1. 我能书写:红色
  2. 我能书写:蓝色

这样在笔写的时候,调用的是笔芯的write方法,即便我们换了笔芯,也不用修改pen 里面的东西。这就是开闭原则。

这样的抽象能力不是一下子就能学会的,需要多练习,多思考,从事物本质去思考。

5.12 小结

  • 继承是一种由现有类创建新的类的机制。
  • 子类继承父类后,父类的一些 可被允许 的成员方法/变量,会被子类继承。(访问权限修饰符允许的范围内)
  • 子类继承父类的方法,只能操作子类继承和隐藏的成员变量
  • 子类重写或新增的方法,能操作子类继承和声明的成员变量,但能直接操作被隐藏的成员变量(需要使用super关键字)
  • 多态是面向对象编程的一个重要特性,子类可以实现多态。子类根据需要重写父类方法。
  • 在设计多态程序时,要熟练使用面相抽象编程的思想,以便体现”开闭原则”