ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【继承】具体作用及深层逻辑便利

【继承】具体作用及深层逻辑便利 继承·引出自我理解老生常谈的是Java纯面向 对象面向对象便存在一个问题确保代码的精简性即如何让代码每一步有着精确的作用简洁的代码长度。而不是代码冗长些许代码似是复制让一行甚至多行代码如重复使用般执行相同功能。此时则不是每一步有自己该有的功能而是相同几步有着如出一辙的目的整个项目中代码的体系庞大精简性若是缺失“新四军过绑带又臭又长又难挨”便是最好的形象描述那些 大同小异的代码回归代码在面向 对象 的思想下树状图式步骤把功能的实现一步步分开这其中一步甚至几步似是别无二致。如何归结为一步 归结一步之后要使用便声明一下则是对代码的极大精简在这个方面静态关键字static即是代码归结为一步的方式之一。那么static归结为一步在代码中的作用范围是什么显而易见static修饰的成员属于类促成了全局共享的特性。同类中直接用其名字便可调用不同类需看其所修饰的访问限定符是否支持其他类调用其所在类名.名字也可调用static在“多步归结为一步”上同类之中最为方便不受对访问限定符的限制其他类也可调用但有个前提static每次只能修饰 一个 成员变量或方法这即是static的缺点如果调用静态成员较多在内外类方面同类还好不同类就不好了不止在调用时需每个要申请一下便出现了多行代码都是声明的情况 —— 别致的“声明”风景而如果所需调用静态成员的分布在多类光在找其对应类名就会心烦意躁简称脑大心想好像知道在哪创建过你但我创建了那么多哪知道每个对应的类是谁啊还要我去翻着找烦死了哼找的时间还不如自己再写一遍。 这样就回归原始了复杂粘贴static便失去了让代码精简性的效果上方说找其对应类名似乎忽略了一个问题就是在类中每个的成员 的封装性修饰的访问限定符是哪个 就是我们所要调用的其是否支持其他类调用。 倘若耐着性子憋着脑大找到了其对应的是哪个类天塌的发现其又被private的修饰夸张抡起锤子砸电脑的想法油然而生这其中,如果依赖调用静态成员则需过度用static修饰成员回归本质static修饰的成员过多也并不好①复杂不言而喻的就是多次调用static修饰的时代码潜移默化的就会混乱起来在调试时每一 步的运行在整个项目中跳来跳去目不暇接另外static有着全局共享的特性任何地方都可修改在检查或维护代码时追踪其来源便会眼花缭乱即变得复杂②破坏封装性上面提过创建的成员要么为了逻辑紧密防止为修改可以用private封装起来要么不怕被修改值公开其他类调用如果 追求 static修饰的被其他类调用就得选择减少封装会过度暴露类的内部细节会让代码整体设计不佳③不支持多态此处可以总结的是面对多个类中有某些不多且不会破坏封装性的情况下成员变量和方法被多次用到可以用static修饰这样调用时声明简单更方便快捷。而如果某个类中所需调用其他类的成员过多如何不用static不用多次声明声明一次就可以访问另一个类的随意成员且可以不破坏另一个类封装性的情况下即private修饰的其他类依旧不能访问——继承继承定义继承通俗而言就是一个类把另一个类全部承接过来随时可选择调用承接过来那个类的任何成员继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段它允许程序员在保持原有类特 性的基础上进行扩展增加新功能这样产生新的类称派生类。继承呈现了面向对象程序设计的层次结构 体现了由简单到复杂的认知过程。继承和static有相似的特性共性的抽取实现代码复用如下方演示代码// Animal.java ——父类classAnimal{Stringname;intage;privateintz;publicvoideat(){System.out.println(name正在吃饭);}publicvoidsleep(){System.out.println(name正在睡觉);}}// Dog.java ——子类1classDogextendsAnimal{voidbark(){System.out.println(name汪汪汪~~~);eat();}z10;//报错无法访问}// Cat.Java ——子类2classCatextendsAnimal{voidmew(){System.out.println(name喵喵喵~~~);}z10;// 同样如此报错无法访问}可知子类和父类要表示继承关系需在子类中加以关键字 —— extends 即继承语法修饰符 class 子 · 类名 extends 父 · 类名 {…}通过以上代码可知子类继承父类之后其父类成员就是子类成员子类调用父类成员的调用区别和同类下调用无区别但每个类都有自己的封装性若是父类中有的成员没有被其他类访问的权限即使是子类也无法访问即任何类无法访问不会被随意改变其成员数据这样会极大程度保护每个类的逻辑安全性 避免debug减少使程序运行错误的概率// TestExtend.java ——测试类classTestExtend{publicstaticvoidmain(String[]args){DogdognewDog();// dog类中并没有定义任何成员变量name和age属性肯定是从父类Animal中继承下来的System.out.println(dog.name);System.out.println(dog.age);// dog访问的eat()和sleep()方法也是从Animal中继承下来的dog.eat();dog.sleep();dog.bark();}}继承的“合理” 行为合法没理的代码设计继承关系通过extends关键字可确立即1类的子类是2类2类的子类是3类3类的子类是4类4类的子类是5类……A —— B —— C —— D —— E …… 因而继承关系可 一直继承下去但继承过多就不合理了过深的继承意味着“代码异味”那么多过深的继承就反映了一个问题即代码设计有问题就相当于一个功能基本可以在三步之内完成非要在五步之外完成继承层次多父类如果改动某个地方子类就会可能出现问题继承这种依赖关系就很隐蔽排查难以发现维护代码调试debug调用栈极长在整个项目代码里跳来跳去整个逻辑模糊面对那些“祖宗十八代”想分清的念头都没有以此可以规定的是继承关系尽量控制在 三层及以内第二如果子类相似于父类就是没有该子类自己异于父类的成员那么这种继承关系有何作用干脆要实现的功能都在父类执行就可免得节外生枝创建子类这样代码错误几率也少。就相当于子类什么不做不创建自己的成员就依赖父类。要知道父类是对子类共性的抽取说明子类有对应不是共性的成员即子类应有自己的“特点”继承方式单层继承 —— 所谓的 ”单层“ 就是一层继承。 有个父类其子类只有一个 A —— B多层继承 —— ”多层“就是多层继承关系就是父类有子类子类还有子类还有子类等A —— B —— C —— ……在前面分析过多继承适合 三层继承过多层的继承虽合法但不合理代码维护会出现问题不同类继承一个父类 —— 表面含义即为该父类具有多个子类需继承的成员从而父类出现有多个子类。 A —— B C —— B ……一个类继承多个父类 —— ”多 继承“ ”多“就是一个子类有多个父类。 这种多继承在 Java中并不合法但可以间接多继承 即通过“接口”形式在后续的“接口”博客会分析到地址的传递子类与父类 没有重名的成员 变量classBase1{inta;intb;}classDerivedextendsBase1{intc;publicvoidmethod(){a10;// 访问从父类中继承下来的ab20;// 访问从父类中继承下来的bc30;// 访问子类自己的c}}//子类与父类 没有重名的成员 方法classBase2{publicvoidmethodA(){System.out.println(Base中的methodA());}}classDerived2extendsBase2{publicvoidmethodB(){System.out.println(Derived中的methodB()方法);}publicvoidmethodC(){methodB();// 访问子类自己的methodB()methodA();// 访问父类继承的methodA()// methodD(); // 编译失败在整个继承体系中没有发现方法methodD()}}//成员方法没有同名时在子类方法中或者通过子类对象访问方法时则优先访问自己的//自己没有时再到父类中找如果父类中也没有则报错//相同变量 —— 子类和父类都创建有相同成员变量classBase3{inta;intb;intc;}classDerived3extendsBase3{inta;// 与父类中成员a同名且类型相同charb;// 与父类中成员b同名但类型不同publicvoidmethod(){a100;// 访问子类自己新增的ab101;// 访问子类自己新增的bc102;// 子类没有c访问从父类继承下来的c}}//相同方法 —— 即 子类和父类都创建有相同成员方法classBase4{publicvoidmethodA(){System.out.println(Base中的methodA());}publicvoidmethodB(){System.out.println(Base中的methodB());}}classDerived4extendsBase4{publicvoidmethodA(inta){//方法的重载System.out.println(Derived中的method(int)方法);}publicvoidmethodB(){System.out.println(Derived中的methodB()方法);}publicvoidmethodC(){methodA();// 没有传参访问父类中的methodA()methodA(20);// 传递int参数访问子类中的methodA(int)methodB();// 直接访问则永远访问到的都是子类中的methodB()父类的无法访问到}}在子类方法中 或者 通过子类对象访问成员时如果访问的成员变量子类中有优先访问自己的成员变量。如果访问的成员变量子类中无则访问父类继承下来的如果父类也没有定义则编译报错。如果访问的成员变量与父类中成员变量同名则优先访问自己的。成员变量访问遵循就近原则自己有优先自己的如果没有则向父类中找通过子类对象访问父类与子类中不同名方法时优先在子类中找找到则访问否则在父类中找找到则访问否则编译报错通过派生类对象访问父类与子类同名方法时如果父类和子类同名方法的参数列表不同(重载)根据调用方法适传递的参数选择合适的方法访问没有则报错关键字 —— super子类中存在与父类中相同的成员时会自动调用子类中的成员,那如何在子类中访问父类相同名称的成员呢不一般调用如何把 调用的类 确定为父类 由关键字确定 ——super“super”字面意思即是在子类方法中访问父类的成员。在要调用的成员前加上“super.”此时调用的便是父类与子类重名的成员classBase5{inta;intb;publicvoidmethodA(){System.out.println(Base5中的methodA());}publicvoidmethodB(){System.out.println(Base5中的methodB());}}classDerived5extendsBase5{inta;// 与父类中成员变量同名且类型相同charb;// 与父类中成员变量同名但类型不同publicvoidmethodA(inta){// 与父类中methodA()构成重载System.out.println(Derived5中的method()方法);}// 与父类中methodB()构成重写publicvoidmethodB(){System.out.println(Derived5中的methodB()方法);}publicvoidmethodC(){a100;// 等价于 this.a 100;b101;// 等价于 this.b 101;super.a200;super.b201;// 父类和子类中构成重载的方法直接可以通过参数列表区分清访问父类还是子类方法methodA();// 没有传参访问父类中的methodA()methodA(20);// 传递int参数访问子类中的methodA(int)methodB();// 直接访问则永远访问到的都是子类中的methodA()父类的无法访问到super.methodB();// 访问父类的methodB()}}分析子类和父类出现相同成员默认调子类本质就是没有针对性的传递父类地址。创建针对子类的对象时通过对象引用相同成员时在子类中若有就会直接把子类的地址传递过去若没编译器才会去父类查找即是 就近原则区别 —— super thissuper和this有相同地方也有不同在地址传递方面就都是给相同名字的变量传递地址而super不同的就多了。super变量和方法的地址都可传递且只能在继承关系时类与类之间为子类传递父类相同成员的地址在格式或特性上基本相同① 都是传递地址super同样不能在静态成员中直接传递地址。② 在构造方法方面this必须在第一条语句super也是一样由于只能在第一条语句因而super和this犹如一山不能容二虎即是不能同时存在总结【相同点】都是Java中的关键字只能在类的非静态方法中使用用来访问非静态成员方法和字段在构造方法中调用时必须是构造方法中的第一条语句并且不能同时存在【不同点】this是当前对象的引用当前对象即调用实例方法的对象super相当于是子类对象中从父类继承下来部分成员的引用在非静态成员方法中this用来访问本类的方法和属性super用来访问父类继承下来的方法和属性在构造方法中this(…)用于调用本类构造方法super(…)用于调用父类构造方法两种调用不能同时在构造方法中出现构造方法中一定会存在super(…)的调用用户没有写编译器也会增加但是this(…)用户不写则没有不同点 —— 初始化在【代码块】博客时总结了所有初始化方式那在继承关系上初始化是否有不同 由于没什么新增的只是关系上更复杂了即 初始化方式不变既然继承在关系上更复杂了【代码块】模块时总结了各种方法的执行顺序关系变复杂了自然各种方法执行顺序也变了对各种初始化方法的分析可·不·看在“this引用”内容模块时知晓在本类中创建无参数构造方法如果没创建编译器会自动创建 空 的构造方法在主类的main方法中针对某类创建对象时如果不主动调用其他构造方法编译器会自动调用无参数的构造方法如果手动调用构造方法则是在创建该类对象时 同时在括号里输入 想要某调用构造方法的参数对构造方法而言基本是整个代码最初执行的静态代码块也是如此虽是比构造方法更早执行即是对该类的数据的一种规划数据的初始化更好利用数据。面对这些数据而创建的一些初始化方式就是为了更好管理利用调用数据换句话说类中数据的如何初始化就是创造方法静态代码块等规定的没有这些规定就相当于在main中创建对象时只是调用数据不能在调用的同时进行初始化需单独初始化代码利用率较低且数据之间是有逻辑关系的单独初始化会破坏逻辑性有人说在创建对象调用类的成员时某些数据就是单独初始化的呀。对此我的理解是创造方法和静态代码块是对一些数据 逻辑性之间的整合然后逻辑编写好之后提供接口 让在调用时进行初始化。而那些可以单独初始化的则是对那些有逻辑性的数据 影响不大的。就相当于一些数据的初始化为了逻辑性必须在调用这些数据之前进行初始化。 并不是这些初始化的值后续不能改而是这些具有逻辑性的数据提供接口初始化之后而是尽量不要改这些接口已经就数据逻辑之间针对性的规划如何初始化。 这些数据身具逻辑性可能在类中创建这些数据时 就进行封装性设计了即使想要单独初始化修改也是不能直接初始化如何间接 ——set get因此静态代码块实例化代码块构造方法这些代码中只要有就相当于官方定义每次创建对象时都是 必须调用的只是调用方式 调用次数不一样静态代码块和实例化代码块是每次创建对象自动在编译器自动调用的但静态代码块 只能在最开始创建 该类 第一个对象时 调用一次 另外类中如果有多个静态和实例化代码块都会把所有的自动调用构造方法就不一样了需手动调用如有多个可选择性的调用哪个在继承关系上静态和实例化代码块都是自动调用因此如果父类有此类代码块在子类中也都是自动调用的无关调用格式的区别而构造方法也是对某些类成员的初始化所以在子类中是需强制性调用父类构造方法由于是两个类之间且是继承关系调用父类的构造方法的方式也有所不同这也是为什么在继承关系上初始化单独分析构造方法多变上方可知父类有构造方法时子类必须调用子类调用时必须在子类的构造方法中 调用父类的构造方法即使子类构造方法中没有想要初始化的成员也必须在子类构造方法里声明一行代码调用父类构造方法构造方法也是父类的成员所以通过super在子类中调用publicclassBase{publicBase(){System.out.println(Base());}}publicclassDerivedextendsBase{publicDerived(){//super();// 注意子类构造方法中默认会调用基类的无参构造方法super(),// 用户没有写时,编译器会自动添加而且super()必须是子类构造方法中第一条语句// 并且只能出现一次//如果调用父类有参数构造方法在括号里输入对应参数即可System.out.println(Derived());}}publicclassTest{publicstaticvoidmain(String[]args){DeriveddnewDerived();}}结果打印Base()Derived()//上方代码剖析可知调用父类构造方法的 子类构造方法中声明调用的那一行代码必须在 第一行中。 而在this内容模块时一个类中可以多个构造方法在一个构造方法中可通过this调用另一个构造方法其this()也必须在第一行。 因而super和this不能同时出现运行结果可可看出先运行父类的构造方法再运行子类的构造方法继承关系与否 初始化的不变之处是继承关系和不是继承关系在一个类中创建另一个类的对象 调用·构造方法仍有相同 —— 性质方面如果父类有构造方法子类同时必须调用父类的一个构造方法否则报错。而非继承关系换个说法也是如此在创建某类的对象时必须在括号里输入参数调用该类的一个构造方法不然同样报错非继承关系中所创建对象的类中如果没有构造方法那么在该对象创建时 在编译器会自动生成一个 空无参数 的构造方法。 如果有构造方法有参数创建对象时没有输入参数调用构造方法就相当于调用无参数构造方法编译器此时不会自动生成无参数构造方法便会报错继承关系里就是——换言之 父类没有构造方法子类有构造方法时编译器运行时 会自动生成一行代码 —— super() 即自动为父类生成 空 的构造方法 并 为子类自动调用父类的空构造方法 如果父类有构造方法有参数 没有无参数而在子类的构造方法敲有super()编译器此时同样 不会自动为父类 生成无参数构造方法也会报错回归这块“初始化”内容开始继承关系上各种初始化方式的执行顺序是什么静态代码块 —— 实例化代码块 —— 创造方法 —— 其他方法get set 等更多“私人定制”的方法 ——无继承关系 的执行顺序classPerson{publicStringname;publicintage;publicPerson(Stringname,intage){this.namename;this.ageage;System.out.println(Person构造方法执行);}{System.out.println(Person实例代码块执行);}static{System.out.println(Person静态代码块执行);}}classStudentextendsPerson{publicStudent(Stringname,intage){super(name,age);System.out.println(Student构造方法执行);}{System.out.println(Student实例代码块执行);}static{System.out.println(Student静态代码块执行);}}/* 运行结果 Person静态代码块执行 Student静态代码块执行 Person实例代码块执行 Person构造方法执行 Student实例代码块执行 Student构造方法执行 Person实例代码块执行 Person构造方法执行 Student实例代码块执行 Student构造方法执行 **/可看出总体执行顺序还是 静态代码块 —— 实例化代码块 —— 创造方法 且静态代码块只在 该类的第一个对象运行细节上父类先执行子类后执行特殊上实例化代码块和构造方法先实例化再构造 父类和子类一同执行 且虽是两个一起但还是先父类再子类一致上和是否为继承关系 没影响即静态代码块只执行一次执行顺序为父类静态块 —— 子类静态块 —— 父类实例块 —— 父类构造方法 —— 子类实例块 —— 子类构造方法 —— 其他方法访问限定符 —— protectedprotected同一家族和不同家族的亲戚能看的到 —— 最大访问范围不同包子类//extend01包中publicclassB{privateinta;protectedintb;publicintc;intd;}// extend01包中// 同一个包中的子类publicclassDextendsB{publicvoidmethod(){// super.a 10; 编译报错父类private成员在相同包子类中不可见super.b20;super.c30;super.d40;}}// 父类中protected成员在相同包子类中可以直接访问// 父类中public成员在相同包子类中可以直接访问// 父类中默认访问权限修饰即default的成员在相同包子类中可以直接访问// extend02包中// 不同包中的 子类publicclassCextendsB{publicvoidmethod(){// super.a 10; // 编译报错父类中private成员在不同包子类中不可见super.b20;// 父类中protected修饰的成员在不同包子类中可以直接访问super.c30;// 父类中public修饰的成员在不同包子类中可以直接访问//super.d 40; // 父类中默认访问权限修饰default的成员在不同包子类中不能直接访问}}publicclassTestC{publicstaticvoidmain(String[]args){CcnewC();c.method();// System.out.println(c.a); // 编译报错父类中private成员在不同包其他类中不可见// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问System.out.println(c.c);// 父类中public成员在不同包其他类中可以直接访问// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问}}总结private —— default —— protected —— public 访问权限逐步增大即使privatedefault修饰的在不同包子类中无法直接访问但还是继承到子类中了可通过创建public修饰的方法间接访问与set get 方法 同理子类与父类的 权限问题 —— 即是在多态中向上转型子类权限必须大于父类权限在后续 多态 中分析如何挑选访问限定符?创建类时要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者。 因而在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就应尽量不要用 public此外,有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 但 这种方式属于是对访问权限的滥用 即应仔细斟酌该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)再选择合适的访问限定符修饰protected 的双胞胎 且反目成仇的 兄弟 ——— final访问限定符是为了隐藏 类的内部 细节而达成 封装性。在继承关系上不超过三层为最适。是否会存在以下的情况有多个类并存在多个继承关系在创建继承关系时在第三层时有意识的规定着不能再创建第四层继承关系了但往后敲其他代码时在根据实际要求时创建一个类并且想要继承另一个类如果继承的那另一个类 再继承一次就刚好是第四次继承了面对庞大的代码体系可能此时有几层继承关系并不清楚那么如何避免出现四层甚至五层的继承关系即是觉得该继承关系达到最高层数大多为三层根据情况可有四层的出现时通过某种声明表示改类拒绝再次被其他类继承拒绝其他类继承不仅在 避免多层继承 方面上另一个要避免的是 封装性 的破坏 即使是有两层继承但在第二层继承的类上此类的细节并不合适公开再被继承就像被private修饰的成员一样防止破坏类 里面的封装性此时上升到了整个类的封装性此时为此类通过声明 拒绝继承即使是A —— B,这种单层继承B这个类不想再被继承可声明 拒绝继承;甚至本身没有继承即没有创建继承关系此时不想被继承也可通过声明 拒绝继承.这种情况就是 可极大程度保护整个类的封装性可以分析的是需要一个声明来拒绝被继承避免上方两种情况的发生。 即在创建类时加上 关键字 ——final访问限定符是为了隐藏 类的内部 细节而达成 封装性。 而final虽然不属于 访问限定符 的定义但作用上相差无几即final是为了隐藏 整个类 细节而达成 封装性finalpublicclassAnimal{...}publicclassBirdextendsAnimal{...}// 编译出错// Error:(3, 27) java: 无法从最终com.bit.Animal进行继承上面 分析的是final对 类的作用但不是指final只能修饰类也可修饰类里面的成员变量作用有异曲同工, 甚至修饰成员方法作用不同但我觉得有千丝万缕的联系是防止该方法被重写final修饰类是不被其他类继承主要是不被其他类修改里面的成员数据就是封装性的破坏。而final修饰成员变量也是为了改成员变量后续不被修改 即把变量 变成常量无法修改其数据finalinta10;// 修饰变量或字段表示常量(即不能修改)a20;// 编译出错可总结的是final就像是个拒绝命令被修饰的类成员变量成员方法就是为了防止 被其他东西 给修改其数据保护封装性结语以上结论带有我自己对该内容理解而总结得出可能分析不全用词不适总结不当接受批评欢迎指出
返回列表