美文网首页Java 之旅
初级08 - 面向对象:继承

初级08 - 面向对象:继承

作者: 晓风残月1994 | 来源:发表于2019-08-01 19:27 被阅读0次

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

    • 组合
    • 继承
    • 继承中的初始化流程
    • 向上/向下转型
    • final关键字

    面向对象的三大特征:

    • 封装
    • 继承
    • 多态

    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. 类的结构与初始化顺序

    • 子类拥有父类的一切数据和行为
    • 父类先于子类进行初始化
    • 必须拥有匹配的构造器
      • super 关键字

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

    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. 向上/向下转型

    • 一个子类类型的对象永远也是一个父类类型的对象
      • instanceof 判断类型
      • null instanceof ??? 永远是 false
      • null 永远可以赋值给一个对象
    • 因此,当需要一个父类型时,总可以传递一个子类型
    • 但是有时必须进行一些转型,转型是不安全的,所以一定要使用进行强制类型转换:
    long i = a;
    int b = (int) i;
    

    6. final关键字

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

    • final修饰的变量是常量,初始化后不能再被赋值(非static成员变量可以晚点再初始化);
    • final修饰的方法不能被子类 Override;
    • 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. 组合(有坑待填)

    相关文章

      网友评论

        本文标题:初级08 - 面向对象:继承

        本文链接:https://www.haomeiwen.com/subject/dvrwrctx.html