zoukankan      html  css  js  c++  java
  • [java学习笔记]继承和组合

    继承

    OOP的三大特性之一,也是经常使用到的一特性。可以很容易的实现类的重用;但是利弊总是相伴的。它带来的一个最大的坏处就是破坏封装。相比之下,组合也是实现类重用的重要方式,而采用组合方式来实现重用则能提供更好的封装性。

    子类扩展(extends)父类时,可以从父类集成得到属性和方法。如果访问权限允许(即不是private的声明),子类可以直接访问父类的属性和方法。but,子类同样可以重写(override)父类的属性和方法;

    那么问题来了,这样的话就是说儿子可以随便干掉老子辛辛苦苦奋斗得来的东西;然后自己按照自己的喜好随便折腾一番。

    父类:

    public class father {
    
        public String companyName = "father's company.";
    
        public father() {
            System.out.println("father's companyName:" + companyName);
        }
    
    
        public void myMoney() {
            System.out.println("老子辛辛苦苦半辈子,赚了一个亿,熊孩子不争气,就给他5000W吧。");
        }
    }

    子类:

    public class Son extends father {
    
        public String companyName = "son's company.";
    
        public void myMoney() {
            super.myMoney();
            System.out.println("----end father's info----
    ");
            System.out.println("这老头死抠,我要篡改信息:my father 给我留了一个亿");
        }
    
        public static void main(String[] args) {
            Son son = new Son();
            son.myMoney(); // 这老头死抠,我要篡改信息:my father 给我留了一个亿
            System.out.println(son.companyName); //  son's company.
            System.out.println("都是我的了,O(∩_∩)O哈哈哈~");
        }
    }

    输出:

    father's companyName:father's company.
    老子辛辛苦苦半辈子,赚了一个亿,熊孩子不争气,就给他5000W吧。
    ----end father's info----
    
    这老头死抠,我要篡改信息:my father 给我留了一个亿
    son's company.
    都是我的了,O(∩_∩)O哈哈哈~
    
    Process finished with exit code 0

    熊孩子不省事,瞬间就能把老子给气吐血了。

    ========================================

    总结:

    为了保证父类良好的封装性,不会被子类随意改变,设计父类通常应该遵循以下规则:

    1、尽量隐藏父类的内部数据,尽量把父类的所有属性设置为private,不用让子类直接访问父类属性;

    2、不让子类可以随意访问、修改父类的方法。父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰。如果父类方法必须外部类调用,必须以public修饰符。如果不想让子类随便重写方法,可以声明方法为final。如果想让子类重写但是又不希望被别的类自由访问,则使用protected 来修饰方法。(亲儿子可以随便,养子啥的,呵呵)

    3、不用在父类构造器中调用被子类重写的方法;

    比如father构造函数中,调用了可被子类重写的方法:

    public father() {
            System.out.println("father's companyName:" + companyName);
            test();
        }
    
        public void test(){
            System.out.println("将被子类重写的方法。");
        }

    子类:

    public class Son extends father {
    
        private String name="Son";
    
        public  void test(){
            System.out.println("son.test().name.length="+name.length());
        }
    
        public static void main(String[] args) {
            Son son = new Son(); // java.lang.NullPointerException
        }
    }

    为什么会出现NullPointerException呢?

    因为在实例化Son son=new Son()时,会先初始化父类 father;而因为father.test()方法又被子类重写了;所以在实例化father()无参构造函数时,里面调用的test()方法并非father.test()而是 Son.test();

    此时,由于没有执行到Son类中属性的赋值阶段;所以此时的name = null,只是声明了属性变量而已,并没有开辟空间、赋值;

    所以,name.length() ===> null.length(),就会报错了。

     向上转型:即由子类---->父类的转换,将会把子类中已扩展的并且父类中不存在的方法和属性都会过滤掉;

            father father = new Son();// 子类向上转型,将去除掉子类自定义扩展的一些属性、方法;
            father.test(); // son.test().name.length=3
            father.sonSelfMethod();// 提示找不到方法,编译时IDE提示cannot resolved method "sonSelfMethod"

    组合:

     对于继承而言,子类可以直接获得父类的public方法,程序使用子类时,将可以直接访问子类从父类哪里继承的方法;而组合则是把其他类作为新类的的属性嵌入进来,用于辅助新类实现功能;用户看到的是新类的方法,而隐藏了嵌入类的方法。因此,嵌入类在声明时,需要指定为private的访问修复符;。这样新类与旧类之间存在了一个“has a”的关系;比如:新类person有Arm、Leg;

    /**
     * Created by hager.
     * 组合,主要是 has-a的关系;而继承则是is a的关系;
     */
    public class CombinePerson {
        private Arm myArm;// 使用private,隐藏嵌入类,防止使用新类的场景中乱用嵌入类。
        private Leg myLeg; // 同上
    
        public CombinePerson(Arm myArm, Leg myLeg) {
            this.myArm = myArm;
            this.myLeg = myLeg;
        }
    
        public void buildaPerson() {
            System.out.println("开始造人...");
            String arms = myArm.buildArm();
            String legs = myLeg.buildLeg();
    
            System.out.println(String.format("one person has %s,%s",arms,legs));
        }
    
    }

    Arm、Leg类:

    public class Arm {
        public String buildArm(){
            return "两只胳膊";
        }
    }
    
    // Leg类
    public class Leg {
        public String buildLeg(){
            return "两条腿";
        }
    }

    结果:

     public static void main(String[] args) {
    
            Arm myArm = new Arm();
            Leg myLeg = new Leg();
    
            CombinePerson person = new CombinePerson(myArm, myLeg);
    
            person.buildaPerson();
    
            System.out.println("造人完毕");
    
        }
    
    // 输出内容
    // 开始造人...
    // one person has 两只胳膊,两条腿
    // 造人完毕
    
    // 

    其实,在实际场景中,如果是组合方式,一般也是基于接口的构造注入。这样面向接口的编程,配合IOC,相对后续复杂系统实现来说更加灵活一些;

    ==============================

    扩展:到底输出6,还是9 呢?

    /**
     * Created by Administrator-xierfly on 2016/11/27.
     * 属性初始化顺序测试
     * 普通初始化块、声明实例属性指定的默认值都可以认为是对象的初始化代码,他们的执行顺序与源程序中的排列顺序相同。
     * 初始化块,只在创建java对象时隐式执行,而且是在构造器之前执行
     */
    public class InitOrderTest {
        /**
         * 以下示例,是先执行int a 并分配默认值为0;然后再执行初始化块,赋值 a = 6;
         * 接着,执行a = 9 的属性赋值;所以最终输出是9;
         *
         * 如果把int a = 9 放到初始化块之前,那么输出的值将是 6 ;
         *
         * 所以,初始化块和属性执行顺序是跟声明顺序有关系的;
         *
         */
        {
            a = 6;
        }
    
        int a = 9;
    
    
        public static void main(String[] args) {
            System.out.println("a="+new InitOrderTest().a);//a = 9
        }
    }

    参考:《李刚疯狂java讲义》

  • 相关阅读:
    IIS 备份
    Windows Service 的注册和卸载
    算法测试(课上测试)
    《Unix/Linux系统编程》第十四章学习笔记20191304商苏赫
    mystat
    实验四 Web服务器1socket编程
    《Unix/Linux系统编程》第十二章学习笔记20191304商苏赫
    整数范围与类型转换
    《Unix/Linux系统编程》第十三章学习笔记20191304商苏赫
    实验四 Web服务器2
  • 原文地址:https://www.cnblogs.com/hager/p/6106350.html
Copyright © 2011-2022 走看看