12初级 - 面向对象:组合与继承

291 阅读5分钟

继承的本质是避免重复

  • 经典面试题,面向对象的三大特张"封装 继承 多态"
  • 什么是继承,继承之后就会获得父类的所有东西
package com.github.hcsp;

public class Dog extends Animal {
    public void wang() {

    }
}

Java的继承体系与Object中的常用方法

  • java是单根继承,java所有的类都继承了Object

    • 看两个重要的equals和toString
  • equals默认比较两个地址是否相同,一般我们都要根据实际的场景覆盖

    • 自己手写覆盖equals方法比较麻烦,一般都是自动生成,自动生成的时候同时要自动生成hash code
  • == 和 equals的区别载于==比较的是内存中的地址的值而equals比较的是对象的实际内容是否一样

  • 案例order.java,通过id比较订单是否相同

package com.github.hcsp;

import java.util.Objects;

public class Order extends Object{
    Integer id;
    String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Order order = (Order) o;
        return id.equals(order.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

Object的String方法

  • 作用对一个对象提供一个字符串表示
package com.github.hcsp;

public class Animal {
    String name;

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                '}';
    }

    public void sayName() {
        System.out.println("我的名字是" + name);
    }
}
  • 单根继承的好处就是保证所有对象都有某种行为
  • Java中是单一继承,多重继承存在的问题,菱形继承父亲拥有相同的方法不知道继承哪一个

继承中的类结构与初始化顺序

  • 子类继承父类的结构图 2
  • animal父亲的构造器先于cat被调用
  • animal.java
package com.github.hcsp;

![2.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec583897c431425da3eb9402b5fc1012~tplv-k3u1fbpfcp-watermark.image?)
![1.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f07a1ba4ddb54651972423c955c4a55b~tplv-k3u1fbpfcp-watermark.image?)
public class Animal {
    String name;

    // 编译器偷偷摸摸帮你生成的构造器
//    public Animal () {
//
//    }

    // 自己生成的构造器
    public Animal(String name) {
        this.name = name;
    }

    public void sayName() {
        System.out.println("我的名字是" + name);
    }
}
  • cat.java
package com.github.hcsp;

public class Cat extends Animal {
   int age;

    public Cat(String name) {
        super(name);
    }

    // 这是编译器偷偷摸摸帮你生成的,如果此时父类生成了一个构造器就会有问题
//   public Cat() {
//       super();
//   }
   public void meow (){

   }
}
  • 初始化过程,严格遵从自顶向下

实例方法的覆盖

  • 一个类可以通过重写,要加@override,防止手残,@override可以理解成一个注释,提示编译器这里覆盖了一个方法,帮我检查一下有没有覆盖
  • 举个例子:String.equals

设计模式实战:模板方法

  • BookWriter.java案例
package com.github.hcsp;

// 模板方法
public class BookWriter {
    // 写书
    public void writeBook() {
        writeTitle();
        writeContent();
        writeEnding();
    }

    public void writeTitle() {
        System.out.println("标题");
    }

    public void writeContent() {
        System.out.println("内容");
    }

    public void writeEnding() {
        System.out.println("谢谢大家");
    }

}
  • 使用模板方法案例MyBookWriter.java
package com.github.hcsp;

public class MyBookWriter extends BookWriter {

    // 为模板方法的某一个方法做改变,而改变了整个流程
    @Override
    public void writeContent() {
        System.out.println("我的内容");
    }

    public static void main(String[] args) {
        new MyBookWriter().writeBook();
    }
}
  • spring的初始化方法就是模板方法,定义了一些初始化的方法,自定义的实现覆盖了某个方法,从而达到了实现某个应用行为的目的 3
  • 如果需要在子类自定义方法之前将原先的父类模板方法先执行可以通过super.writeContent()实现
package com.github.hcsp;

![3.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6a8e35a2cb3940389c620ebf41b263a9~tplv-k3u1fbpfcp-watermark.image?)
public class MyBookWriter extends BookWriter {

    // 为模板方法的某一个方法做改变,而改变了整个流程
    @Override
    public void writeContent() {
        super.writeContent()
        System.out.println("我的内容");
    }

    public static void main(String[] args) {
        new MyBookWriter().writeBook();
    }
}

向上_向下转型

  • 用instanceof 判断是不是某个类的实例
  • null instance of 某个对象,永远是false
  • 当你需要一个父类animal的时候,永远可以传递一个子类cat给他
  • 当方法需要一个父类对象的时候,永远可以传递一个子类
  • 向上转型是安全的可以自动进行,反之不安全必须使用强制类型转换 int b = (int) i;
int a = 1;
long i = a; // int 比 long小,小的东西放到大的东西里
int b = (int) i;

final关键字与单例模式

  • final 最常用的是设置变量是不变的
  • final 的好处是线程安全,保证多线程不会带来问题
package com.github.hcsp;

public class Main {
    final int i;
    public Main(int i) {
        this.i = i;
        final int local = 1234;
    }  
}
  • 另外一个用法就是放到参数里,等价声明局部变量
    private void f(final int i) {

    }
  • final 后面跟对象,表示指向的对象的地址不变
  • 声明常量,常量全大写+下划线
public static final double PI = 3.1415926

单例模式

  • 只创建一个对象
  • final 在修饰变量的demo
package com.github.hcsp;

public class World {
    public static final World SINGLETON_INSTANCE = new World();

    // 工厂方法
    public static World getInstance() {
        return SINGLETON_INSTANCE;
    }

    private World() {

    }
}
  • final修饰class的时候,从此这个类就不可继承了,这是最终的一个类,根据effective java的19条如果使用继承则设计,并文档说明,否则不该使用
package com.github.hcsp;

public final class World {
    
}
  • final 在类声明上的使用:禁止继承此类
    • 继承提供了灵活性,也埋下了隐患
    • JDK很多类都被设计成了final
  • final 如果在方法前面,则表示方法不能被继承
  • 面试题:final的好处:final保证了这个类不被继承,因此没人可以通过继承破坏类的约定。运行的时候这个方法是确定的,不会有多态的情况,虚拟机运行的时候可以把方法内连起来,从而可以提高性能。

组合和继承

package com.github.hcsp;

public class Main {

    class Driver {
        void drive() {
            // 100行复杂逻辑
        }

        void fixCar() {
            // 100行复杂逻辑
        }
    }

    class Doctor {
        void fixPatient() {
            // 100行复杂逻辑
        }
    }

    class DoctorDriver {
        Driver driver;
        Doctor doctor;

        void drive(){
            driver.drive();
        }

        void fix(){
            driver.fixCar();
        }

        void fixPatient() {
            doctor.fixPatient();
        }
    }

}

课后练习题 countingSet的例子

  • 继承引发了多态,造成了多次调用add方法,如果用组合就会避免这个问题
  • 这里继承不好,因为考虑到了父类的实现细节,正确做法用组合
public class CountingSet {
    HashSet<Object> set = new HashSet<>();
    
    public boolean add(Object obj) {
        count++;
        return set.add(Obj)
    }

}
  • is-a用继承,has-a用组合