zoukankan      html  css  js  c++  java
  • 类的族谱

      最近刚把《java编程思想》第十章内部类看完,有关类的介绍也就告一段落,而我脑子里始终萦绕的是那些继承、接口、内部类以及它们各自琐碎的语法,让我不知如何是好。我决定抛开这些细枝末节,重构一下我对对象组成的新理解,把以上那些名词编制成网,方便记忆和理解。其中若有不对的地方,欢迎指正,谢谢大家。同时这篇文章我决定换一种轻松明朗的书写风格,让大家看的愉快。

    1.类的理解。

      从我开始学Java时,知道类被作为一种数据类型,可以自己随意造类时,我觉得惊讶不已。因为我觉得程序中只能包含26个字母以及10个数字,加上一些运算符号,这些才是计算机所能识别的语言,要不然电脑怎么了解你要传递的信息呢。而Java通过编写一个类,往这些类里面塞变量、方法,这个类被编译执行,最终表现形式也还是转化为以上提到的那些符号,来跟虚拟机交流。所以类应该算是一个中间层的角色。它封装着我们对事物的理解,在这个层面上,我们用Java提供的语法写类,就像我们用熟悉的词语、语法写一篇文章。虚拟机会把我们的“文章”用它熟悉的语言翻译一遍,在这个过程中,我们之前的一行行代码都被拆解,最终如一个完整的n*n拼图被打乱一样。我们很难从零乱的拼图中窥见完整图形的原貌,但实际上这是虚拟机按自己的思维又重新拼的图案。虚拟机得到了信息之后它就知道自己该干什么,接下来的事我们也就无需过问了。毕竟我们最最重要的事怎么写出一个个类来准确表达信息

      通常我们用许多属性来描述一件事物。古人说:一阴一阳之谓道。如果把阴理解为“静”,阳理解为“动”,那么万事万物都具有动静两种属性。例如我们人都具备的眼睛、鼻子、手、脚,这些都属于静态属性,它成了人的特征;此外人还具有动态属性,能吃喝打闹,蹦蹦跳跳,它成了人的行为。静态属性对应的就是类的成员变量,动态属性对用的则是方法。于是按这种思想把一种事物抽象成类,就具有了实实在在的意义。

    2.继承

      有很多伟大的科学家取得重要成就时,总会谦虚地说:我是站在巨人的肩膀上的。说明很多成功都是利用现成的资源,没必要自己去重新走老路造轮子。我对继承的理解同此,如果一个初始类类用一个圆来表示,继承的使命就是扩展初始类,也就是让这个圆变大,可以包含更多属性。它通过在初始圆的外层再画一个圆,把旧圆包含进去,达到目的。此外还有一种常用方法(组合)来扩展类:建一个新类,将新类的成员变量为设置初始类的引用。也就是新类存着一把可以访问初始类的钥匙。类似这样的钥匙越多,这个类可以访问的属性也就越多,也达到了扩展类的目的。而这两种扩展类的方式的使用场景是怎样的呢?一言以蔽之:根据两个类的所属关系。类A -交通工具,类B表示汽车,类C表示发电机。汽车是这一种特殊的交通工具,即B is A,所以B extends A;汽车里有发电机,即B has C,所以C c可作为B的成员变量。

    3.多态

      因为继承,所以多态。(这里的继承也包括implements)多态给人印象最深的还是它的向上转型加动态绑定。写了这么多字,还是来一段“名言”吧:

    class Pig{
        public void getInfo() {
            
        }
    }
    //麦兜
    class MaiDou extends Pig{
        public void getInfo() {
            System.out.println("两情若是久长时,又岂在猪猪肉肉");
        }
    }
    //佩奇
    class PeiQi extends Pig{
        public void getInfo() {
            System.out.println("小猪佩奇身上纹,掌声送给社会人");
        }
    }
    
    public class Test {
        public static void show(Pig pig) {
            pig.getInfo();
        }
        
        public static void main(String[] args) {
            Pig md = new MaiDou();
            Pig pq = new PeiQi();
            show(md);
            show(pq);
        }
    }
    //output:
    //    两情若是久长时,又岂在猪猪肉肉
    //    小猪佩奇身上纹,掌声送给社会人

      这段代码中只要理解Pig md = new MaiDou(),以及show()的调用过程就行。首先,创建麦兜对象,将这个对象引用向上转型赋给Pig,说明麦兜是猪,就像海水是水一样合乎情理。后面show()方法接受pig类型的参数,然后getInfo()调用了我们期盼的结果,为什么会这样。这源于Java语言的动态性。Java中有个名词:绑定。意思是:方法的调用与方法所在的类进行关联。绑定分为:静态绑定,动态绑定。静态绑定是指:在编译Java文件时就已经知道方法对应的是哪个类。常见的的私有方法、final 方法、静态方法、构造方法都是静态绑定。这四种方法的共同点就是,要么不能被继承(如私有方法、final方法、构造方法),要么被继承也不能被重写(如静态方法),于是这些方法就只和声明它的那个类文件进行了绑定。动态绑定则有趣的多。来我们分析一下pig.getInfo():

      1.在编译的时候:编译器对这个方法只做一个很简单的判断:pig(及其它的父类、父父类。。)有没有getInfo()方法。没有就报错,有就通过编译。

      2.在运行期,找到md实际引用的那个对象,也就是麦兜。老规矩,查看麦兜(及其它的父类、父父类。。)中的所有getInfo()方法,哪个方法参数与当前这个方法参数最相符,就调用哪个。

      多态就是基于动态绑定实现的。了解了这个过程,多态运行的结果也就理所应当。

    4.接口

      如果你想扩展你的类,不能总是依赖继承,继承让你的对象像滚雪球一样越滚越大,其中还掺杂着你可能不需要的属性方法。例如:你想飞,那就继承鸟吧,顺便给你一层厚厚的羽毛,以及捕虫子的方法-囧。所以我们需要一种轻巧的扩充类的方法,这就是接口的意义。接口的实现其实与继承的意思相差无几,所以实现接口也可以说继承接口。一个类可以继承一个父类,但可以实现多个接口,简单的可以直接理解为多重继承。另外接口体现在多态上的范围要比继承在多态上的范围还要宽广。举个例子:当你的方法参数为一个鸟类时,你传递的参数只能是鸟类和鸟类的导出类,但如果你的方法参数为一个飞行接口,任何继承(实现)这个接口的类都可以传,那我可以传鸟类和它的导出类,还可以传飞机类和它的导出类。

      关于接口,还有一点值得一提。上面说到接口作为一种更轻巧的方式扩充类,我们常见的情况是接口最常见的是只定义抽象方法,而很少定义变量,即使定义了变量,也必须把它声明为一个初始化的常量。这个原因网上很多说的比较明白,这里我简单回顾一下:接口是为了定制统一的规约,它要达到这个目的,你只能改我允许你改的部分,其余的部分你只能用我的。接口已经把方法扩展的权利拱手相让了,如果它里面的变量都还可以被实现类修改,那它个规约的所有地方都能被修改,那算什么规约!所以有了static让接口的变量被接口所公有,而不是每个实现类独占一份;有了final关闭了实现类修改的权利。还有一个原因是我自己琢磨的:多态是在方法调用上体现出来的,而成员变量并不具备多态的特征。

    class Father{
        int age = 45;
    }
    class Son extends Father{
        int age = 21;
    }
    
    class Daughter extends Father{
        int age = 24;
    }
    
    public class Test {
        public void showAge(Father f) {
            System.out.println(f.age);
        }
        
        public static void main(String[] args) {
            Son son = new Son();
            Daughter daughter = new Daughter();
            Test t = new Test();
            t.showAge(son);
            t.showAge(daughter);
        }
    }
    // output
    // 45   45

      f.age()并没有如我们期望那样,找到儿子、女儿对象,分别显示他们的年龄,也就是多态没有体现出来。所以接口中的变量不能在实现过程中不能表现出多态,如果继承不是为了多态,那么继承毫无意义!那还不如就声明为常量。当然这个理由,并没有前面的有力,只当自己的一种看法与大家探讨。

    5.内部类

      内部类作为最后的大佬总算要登场了!内部类的定义很简单,在类的里面再定义一个类,里面的那个类就是内部类。根据内部类所处的位置以及修饰符,于是有了位于成员变量同一级别的成员内部类,位于方法中(也可以说是代码块中)的局部内部类,用static修饰的嵌套类,以及大名鼎鼎的匿名内部类。在介绍在四种内部类之前,我们先看看为什么要有内部类,总得找个让人信服的理由。一句话概括就是:内部类的出现就是为了完善java的多重继承(再次重申一次,全文中的继承都可以理解为继承+实现)机制。怎么个完善法呢?先前用接口时,我们是这样扩充类的:A extends B implements C,D,E... ,这样A 的肚子了就装下了B,C,D,E。但实际运用过程中,会发现有些情况需要的继承结构更细致精确。举个例子:一个女孩觉得自己的鼻子不好看,想把鼻子整个容,这时候,没必要整个人全身上下都整,她仅仅只需要整的是她这个对象的某个成员变量(鼻子)。也就是对她的鼻子实现整容接口,而不是让她整个人实现整容接口。还有一些情况,如:太空飞船实现了航天接口,为了飞向宇宙探险,而飞船中有一个逃生舱,它也实现了航天接口,对同一接口,它的行为是为了返回地球。类与它的成员都要实现同一个接口,这种情况也只能使用内部类实现。从这两个例子可以看出使用继承多个接口的方式还是过于粗犷,内部类将继承关系拆解到类的里面,从而达到更准确更有针对性的继承。而且内部类还提供了相应的技术支持,那就是在定义内部类的时候,我们可以在内部类里面访问外部类的所有成员,不管你是方法还是成员变量,不管你是私有还是公开。外部类对它而言就是打开自家冰箱。在这里,顺便提一点我经常会犯的错误。书中提到:当生成一个内部类对象时,它能访问其外围对象的所有成员。我当时的理解是:

    public class Test {
        private int a =10;
        class Father{
            public int getVar() {
                return a;
            }
        }
        public Father getFather() {
            return new Father();
        }
        public static void main(String[] args) {
            Test t = new Test();
            Test.Father f = t.getFather();
            f.a//f根本不能访问外围类的变量a
        }
    }

      我想当然的理解为内部类对象可以直接点点点,父类的成员。后来才知道,Father与Test是两个独立的class文件,class定义的时候,压根就没有a,怎么能访问呢?书中的那句话我的理解是,它可以畅通无阻的访问外围类的所有成员是在定义class的时候,也就是我们在写Test这个类时,所有的成员都在这同一个类里,根据内能访问外的规律,所以内部类可以外围类的所有成员。而在实际运行过程中,外围对象跟内部类对象关系也很紧密,因为内部类可以访问修改外部类。只要你要编写内部类的时候,写了相应的方法就行,如上面代码中的getVar(),我用f.getVar()还是可以拿到a 的。这里注意一下:就算你外部类的所有的方法变量都是私有的,只要你的内部类里提供了访问外部类成员的方法,那么只要得到了内部类对象,也就得到了打开外部类的钥匙。这就是内部类被称作闭包的原因吧。

      另外,四种不同的内部类,实际用的最多的应属匿名内部类和成员内部类了,

    interface Fly{
        void fly();
    }
    public class Test {
        class Bird implements Fly{
            @Override
            public void fly() {
                System.out.println("成员内部类实现了接口");
                
            }
        }
        public Fly getBird() {
            return new Bird();
        }
        
        public void show(Fly f) {
            f.fly();
        }
        
        public static void main(String[] args) {
            Test t = new Test();
            Fly bird = t.getBird();
            t.show(bird);
            t.show(new Fly() {
                @Override
                public void fly() {
                    System.out.println("匿名内部类实现了接口");    
                }
            });
            
        }
    }
    //output
    //成员内部类实现了接口
    //匿名内部类实现了接口

       匿名内部类是浓缩版的成员内部类,它的显著特征是没有构造器。所以如果你想要继续创造内部类实现对象,那就用成员内部类好了;如果只需要一个继承接口(抽象类)的对象,那么匿名内部类更简洁。另外还有局部内部类和静态内部类,用的少我就不多讲了,而且当你理解了java方法的动态绑定特征后,理解内部类的实际调用情况简直so easy!

      

      

      

  • 相关阅读:
    F版本SpringCloud1—大白话为啥要有微服务?啥是微服务?SpringCloud为什么有那么多组件?
    Java已五年1—二本物理到前端实习生到Java程序员「回忆贴」
    SpringBoot图文教程17—上手就会 RestTemplate 使用指南「Get Post」「设置请求头」
    SpringBoot图文教程15—项目异常怎么办?「跳转404错误页面」「全局异常捕获」
    SpringBoot图文教程14—SpringBoot集成EasyExcel「上」
    SpringBoot图文教程12—SpringData Jpa的基本使用
    SpringBoot图文教程11—从此不写mapper文件「SpringBoot集成MybatisPlus」
    SpringBoot图文教程10—模板导出|百万数据Excel导出|图片导出「easypoi」
    SpringBoot图文教程9—SpringBoot 导入导出 Excel 「Apache Poi」
    SpringBoot图文教程8 — SpringBoot集成MBG「代码生成器」
  • 原文地址:https://www.cnblogs.com/shu94/p/9547740.html
Copyright © 2011-2022 走看看