zoukankan      html  css  js  c++  java
  • [01] 继承


    1、继承的声明

    继承,是指一个类的定义可以基于另一个已经存在的类,即子类基于父类,从而实现父类代码的重用,子类能吸收父类的属性和行为,并扩展新的能力。Java中的继承是单继承,即最多只能有一个父类。

    所谓 “龙生龙,凤生凤,老鼠的儿子会打洞”,这句话简单明白地阐述了继承:
    • 子类基于父类,也意味着是 “is-a” 的关系
    • 子类拥有父类的能力,也就是代码得到了复用

    继承的声明也很简单,直接使用extends关键字即可:
    【访问权限修饰符】【修饰符】子类名 extends 父类名 { 子类类体 } 



    2、构造方法

    子类能复用的是父类的属性和方法,但是这里并不包括构造方法,因为构造方法是不能被继承的。但是可以在子类构造方法中通过super关键字调用父类构造方法,super只能在构造方法的首行。
    //父类
    public class Animal {
        private String name;
    
        public Animal(String name) {
            this.name = name;
        }
    }
    
    //子类
    public class Cat extends Animal{
        public Cat(String name) {
            super(name);
        }
    }

    如果你不想调用父类的构造函数,那么也必须保证父类有一个无参的构造函数,子类在调用自身构造函数时会默认调用父类无参构造函数(相当于默认在构造方法首行插入super( )方法)。

    也即使是说,无论如何,要保证父类的构造函数,因为子类的构造方法总是先调用父类的构造方法,再调用自己的构造方法。毕竟,没有老子哪来的儿子?



    3、this和super

    this关键字代表自身,主要用在:
    • 在构造方法中引用自身类的其他构造方法
    • 用this代表自身类的对象(例如用this引用成员变量或方法)
    public class Animal {
    
        private String name;
        private int age;
    
        public Animal(String name) {
            this.name = name;
        }
    
        public Animal(String name, int age) {
            this(name);
            this.age = age;
        }
    
        public String run() {
            return "run";
        }
    
        public String runQuickly() {
            return this.age + " " + this.name + " " + this.run() + " quickly";
        }
    
        public static void main(String[] args) {
            Animal animal = new Animal("diudiu", 12);
            System.out.println(animal.run()); //输出 run
            System.out.println(animal.runQuickly()); //输出 12 diudiu run quickly
        }
    
    }

    super和this类似,但是super是只有在继承关系里才会使用到的关键字,this是在本类中调用本类,而super则是在本类中调用父类:
    • 调用父类的构造方法(super语句只能在子类构造函数的第一行)
    • 调用父类的属性或方法
    public class Cat extends Animal{
    
        public Cat(String name, int age) {
            super(name, age);
        }
    
        public String catRun() {
            return "cat " + super.runQuickly();
        }
    
        public static void main(String[] args) {
            Cat cat = new Cat("huahua", 9);
            System.out.println(cat.catRun()); //输出 cat 9 huahua run quickly
        }
    
    }



    4、private和继承

    假如父类的某个属性权限设置为private,子类继承父类以后,却是无法直接调用该属性的,所以private的属性无法继承吗?

    但是假如该属性有一个public的get方法,子类却可以通过get获取到当初无法直接访问的属性的值,所以private的属性实际上得到了继承吗?

    实际上,继承的这个关系有点微妙,在我的理解看来,每次我们获取到一个子类对象时,实际上它包含了 “子类对象和其父类对象”,也就是说,每次我们实例化一个子类对象时,表面上看只有一个对象,实际上背后调用涉及的是一个子类加父类的组合。

    我们看看子类实例化的过程和顺序是这样的:
    • 初始化父类的静态代码
    • 初始化子类的静态代码
    • 初始化父类的非静态代码
    • 初始化父类构造函数
    • 初始化子类非静态代码
    • 初始化子类构造函数

    但是两者并不是相互交融的,域还是各自的域,但相互又有些关联,所以所谓继承下来的东西,只是让我们看起来像子类的了。而实际上是,当调用子类对象的方法或属性时,先看子类是否有,如果没有,就到父类对象(且权限允许的情况下)去调用。这也是为何子类与父类同名的属性和方法,可以将父类的覆盖掉的原因。

    所以开始的private的那个问题也就可以理解了,private属性实际上也是有的,在对应的父类对象里,但是因为没有权限,所以无法直接访问。

    最后一个示例,配合食用更佳:
    //父类
    public class Animal {
    
        public String name = "animal";
    
        public String getName() {
            return this.name;
        }
        
    }
    
    //子类
    public class Cat extends Animal{
    
        public String name = "cat";
    
    }
    
    //测试类
    public class Test {
        public static void main(String[] args) {
            Cat cat = new Cat();
            System.out.println(cat.name); //输出cat
            System.out.println(cat.getName()); //输出animal
        }
    }



    5、protected引发的权限修饰符认知纠偏

    先来回顾一下权限访问修饰符的控制范围:
    权限访问修饰符定义权限针对范围
    public    公共权限    可以被任意类访问属性、方法、类
    protected    受保护的权限同包类可以访问,或者非同包的该类子类可访问属性、方法
    default(即默认不写)同包权限只能被同包的类访问属性、方法、类
    private    私有权限    只能在本类中访问使用    属性、方法

    一直以来我个人都错误地理解了权限修饰符,应该说,把如上表中的权限和针对范围部分混淆了。

    在上个标题《4、private和继承》中已经从权限的一部分去说明了和继承的关系,当有一次用到protected权限时(注意看包位置):
    //父类
    package temp.animal;
    
    public class Animal {
        public void eat() {
            System.out.println("Animal eat!");
        }
        protected void run() {
            System.out.println("Animal run!");
        }
    }
    
    //子类
    package temp.animal;
    
    public class Cat extends Animal{
    }
    
    //测试类
    package temp.test;
    import temp.animal.Cat;
    
    public class Test {
        public static void main(String[] args) {
            Cat cat = new Cat();
            cat.eat(); // compile ok
            cat.run(); // compile error
        }
    }

    Cat是Animal的子类,是继承关系。当我在一个测试类中,新建了一个Cat类对象,试图调用Cat类继承下来的protected修饰的方法run()时,编译不通过。

    这里的变量cat,是Cat类吗?是的。Cat类是Animal的子类吗?是的。Cat类继承了Animal的protected方法run()吗?继承了。那为什么cat不能调用run()?

    我说了,我的认知把权限和针对范围混淆了,这里的权限修饰符表示的权限始终是针对类的,也就是说,变量cat能否调用protected的run()方法,主要在于它在哪个类里,cat是在Test类中调用的,但是Test显然和Animal不同包,Test也不是Animal子类,所以是没有权限的,也就无法调用。相反,如果在Cat类中新建变量cat,就可以调用run(),或者Test和Animal同包,也是可以调用run()的。

    好了,大概就是这样,记录于此,纠偏认知。



    6、方法覆盖

    在private和继承的关系中我们已经提到,当调用子类对象的方法或属性时,先看子类是否有,如果没有,就到父类对象(且权限允许的情况下)去调用。

    这意味着,如果出现和父类同名的方法,调用时会执行子类的方法,而非父类的方法,这个叫方法覆盖,发生在继承关系中。当然,这要求同名、同参、同返回值,且访问权限不能缩小

    另外在方法覆盖和异常抛出上,也有一定的限制:
    • 不可以增加新的异常,即使这个新的异常是父类方法声明中的任何一个异常的子类也不行
    • 不可以抛出 "被覆盖方法抛出异常" 的父类异常

    就像一个修理家电的人,他能够修理冰箱,电脑,洗衣机,电视机。 一个年轻人从他这里学的技术,就只能修理这些家电,或者更少。你不能要求他教出来的徒弟用从他这里学的技术去修理直升飞机。

    假如我有Cat子类继承Animal父类,父类有run方法,为何子类要覆盖run方法,而不是另外写catRun方法呢?很简单,如果不使用方法覆盖,那么在调用子类时实际其父类的方法也存在,即也可以调用,这不符合面向对象的封装性,这也是方法覆盖的意义所在。



    7、Object类

    Object类是所有类的父类,位于java.lang包中,任何类的对象,都可以调用Object类中的方法,包括数组对象。

    7.1 toString

    toString方法可以将任何一个对象转换为字符串返回,返回值的算法为:
    getClass().getName() + '@' + Integer.toHexString(hashCode())

    即“包名+类名+@+16进制数”,实际上System.out.print( Object obj )则默认调用了toString方法。另外,通常我们某些类的toString需要重写为我们需要的样式。

    7.2 equals

    equals其实本质上和“==”是一样的,都是比较的对象的虚地址,但是“==”是不能修改的,所以为了按照特定的方式进行两个对象的对比,比如我们希望两个对象的某个属性相同就视为相同的话,就可以采用重写equals方法的方式。

    实际上Java中很多提供的类也重写了equals,比如String的equals就是比较两个字符串的内容,而非虚地址。

    7.3 hashCode

    hashCode方法是获取对象的哈希码,结果是16进制。

    (哈希码:哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同)

    我们在重写equals,往往要连同把hashCode方法进行重写,要求:
    • 如果两个对象用equals返回true,则它们的hashCode必须相同
    • 如果两个对象用equals返回false,那么它们的hashCode值不一定不同

    为什么要重写hashCode?假如我们有Book类,两本同名书,但不同的出版社,现在我们希望只要名字相同就视为同一本书,我们重写equals,同名书返回了true,但是如果不重写hashCode,在涉及到Set集合时,比如存入Set集合中,会因为hashCode不同,而无法实现去重。


  • 相关阅读:
    几个常用myeclipse快捷键
    5G layer
    customize the entry point of pod runtime
    关于JS Pormise的认识
    修改 /etc/pam.d/login, linux 本地账号密码无法登陆,一直返回 登陆的login界面
    Java支付宝PC网站支付功能开发(详细教程)
    支付宝PC支付功能异步通知签名验证失败解决方案
    提交代码出现 Push to origin/master was rejected 错误解决方法
    易语言连接RCON详细教程实例(演示连接Unturned服务器RCON)
    易语言调用外部DLL详细实例教程
  • 原文地址:https://www.cnblogs.com/deng-cc/p/7461887.html
Copyright © 2011-2022 走看看