Java 之旅

初级08 - 面向对象:继承

2019-08-01  本文已影响0人  晓风残月1994

组合与继承是人类在计算机世界中对客观事物的描述。


面向对象的三大特征:

1. 继承和 Java

继承的本质是提炼出公用代码,避免重复。
Java 的继承体系是单根继承,所有类都继承自Object类,重要的方法比如equals()toString()
C++ 这种多重继承带来的问题就是当两个父类有相同的东西时,子类无法做出选择。

例如,将属性name 和方法sayMyName 抽离到公共的Animal类。

public class Animal {
    String name;
    public void sayMyName() {
        System.out.println("我的名字是" + name);
    }
}
public class Cat extends Animal {
    public Cat(String name) {
        this.name = name;
    }

    public void meow() {
        System.out.println("喵" + name);
    }
}
public class Dog extends Animal {
    public Dog(String name) {
        this.name = name;
    }

    public void wang() {
        System.out.println("汪" + name);
    }
}
public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat("ABC");
        cat.meow();
        cat.sayMyName();

        Dog dog = new Dog("BCD");
        dog.wang();
        dog.sayMyName();
    }
}

2. 类的结构与初始化顺序

上面例子中,如果父类和子类都没有显式的声明构造器,那么编译器会自动给父类和子类添加默认的构造器:

public class Animal {
    //...
    public Animal() {
        // 一个空的构造器
    }
    //...
}
public class Cat extends Animal {
    public Cat() {
        super(); // 超类的构造器
    }
    // ...
}

如代码中所示,super 关键字代表的就是父类的构造器,子类来源于父类,如此,当调用Cat类的构造器创建Cat实例时,内部先调用super创建出父类的成员,然后再创建自身的成员。

还是上面的例子,如果父类一开始就自定义了构造器,那么子类中就要拥有匹配的构造器,编译器提供的默认构造器是没有参数的,这不匹配,所以需要子类的构造器中调用父类,并传入参数:

public class Animal {
    String name;
    // 父类自定义了构造器
    public Animal(String name) {
        this.name = name;
    }
    public void sayMyName() {
        System.out.println("我的名字是" + name);
    }
}
public class Cat extends Animal {
    // 创建 Cat 实例时,会先创建 Animal 实例
    // 所以要给父类的构造器 super 中传递 new Cat 时的参数
    public Cat(String name) {
        super(name);
    }
    // ...
}

3. 实例方法的 Override

又称为覆盖/重写,发生在继承时对父类方法的覆盖,且返回类型、方法名以及参数列表保持一致才算是覆盖。
注意和重载(Overload)的区别,重载是在同一个类或者子类与父类中定义名字相同,但参数不同的方法(两同一不同),可以实现方法的默认值等功能,这里有提到重载。
永远使用@Override注解来防止手残,善用 IDEA 中的提示。

在 Java 中随处可见 Override,比如String.equals

public class User {
    private Integer id;
    private String name;

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public static void main(String[] args) {
        System.out.println(new User(1, "user1") == new User(1, "user1"));
        System.out.println(new User(1, "user1").equals(new User(1, "user1")));
        System.out.println(new User(1, "user1").equals(new User(2, "user2")));
    }

    // 请在这里覆盖equals方法,使得两个相同ID的用户equals返回true
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            return this.id.equals(((User)obj).id);
        } else {
            return false;
        }
    }
}

4. 设计模式实战:模板方法模式

提供一个“模板”类,使用可以覆盖模板的全部或部分。
如下所示,Story 是模板,MonsterStory继承自前者,属于模板的消费者:

public class MonsterStory extends Story {
    // 请补全本类,使得main方法可以输出以下内容:
    //
    // 开始讲故事啦
    // 从前有个老妖怪
    // 故事讲完啦
    // 你还想听吗
    public static void main(String[] args) {
        new MonsterStory().tellStory();
    }

    @Override
    public void story() {
        System.out.println("从前有个老妖怪");
    }

    @Override
    public void endStory() {
        super.endStory(); // 部分覆盖时,先保留父类的实现
        System.out.println("你还想听吗");  // 再在此基础上增加自己的实现
    }
}
public class Story {
    public final void tellStory() { // final 定义的方法可以防止被 Override
        startStory();
        story();
        endStory();
    }

    public void startStory() {
        System.out.println("开始讲故事啦");
    }

    public void story() {
        System.out.println("从前有个老和尚");
    }

    public void endStory() {
        System.out.println("故事讲完啦");
    }

    public static void main(String[] args) {
        new Story().tellStory();
    }
}

5. 向上/向下转型

long i = a;
int b = (int) i;

6. final关键字

在 Java 中,声明类、变量和方法(和方法的参数)时,可使用关键字 final 来修饰,查一下单词,final代表 最终的决定性的不可更改的,所以特点如下:

7. 设计模式实战:单例模式(Singleton pattern)

单例模式限制类的实例化和个数,提供全局的方法来获取唯一实例。

public class World {
    // 创建唯一的实例对象
    private static final World SINGLETON_INSTANCE = new World();
    // 将构造器私有化,这样该类就不会(轻易)再被实例化
    private World() {
    }
    // 暴露出获取可用对象的方法(就像工厂模式一样)
    public static World getInstance() {
        return SINGLETON_INSTANCE;
    }
}

8. 组合(有坑待填)

上一篇 下一篇

猜你喜欢

热点阅读