热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[JAVA冷知识]JAVA居然支持多继承?让我们用内部类去实现吧

写在前面JAVA冷知识,今天和小伙伴分享的是通过内部类的方式实现JAVA的多继承一个Demo和JDK源码中的具体场景部分内容参考《编写高质量代码(改善Java程序的151个建议)》

写在前面




  • JAVA冷知识,今天和小伙伴分享的是
  • 通过内部类的方式实现JAVA的多继承
  • 一个DemoJDK源码中的具体场景
  • 部分内容参考
    • 《编写高质量代码(改善Java程序的151个建议)》
    • 《Effective Java》中文版第3版
  • 博文理解有误的地方小伙伴留言私信一起讨论

与亲近之人不要说气话,不要说反话,不要不说话。——烽火戏诸侯 《剑来》


众多周知,对于面向对象语言来讲,JAVA是不支持多继承的,只支持单继承,但是提供了接口来补偿。

在实际的项目中,接口更多的用于行为的委托,把类本身一些是共性但又是特定的行为委托给一个接口的具体实现,当然接口也可以用于属性的委托,对象结构型的设计模式大都采用接口的方式来实现对对象内部组成的注册和操作

如果实现java的多继承,其实很简单,关键是对于内部类的特征的掌握,内部类可以继承一个与外部类无关的类,保证了内部类天然独立性,根据这个特性从而实现一个类可以继承多个类的效果

下面我们看一个Demo,声明父母两个接口,实现父母两个类,看如何通过内部类来继承父母类,而不是通过,接口委托的方式,


一个Demo

父亲接口

package com.liruilong;/*** @Project_name: workspack* @Package: com.liruilong* @Description: 父亲接口* @Author: 1224965096@qq.com* @WeChat_Official_Accounts: 山河已无恙* @blog: https://liruilong.blog.csdn.net/* @Date: 2022/2/12 2:48*/
public interface Father {/*** @return: int* @Description 强壮的行为* @author LiRuilong* @date 2022/2/12 2:49**/int strong();
}

父亲实现类

package com.liruilong;/*** @Project_name: workspack* @Package: com.liruilong* @Description: 父亲类* @Author: 1224965096@qq.com* @WeChat_Official_Accounts: 山河已无恙* @blog: https://liruilong.blog.csdn.net/* @Date: 2022/2/12 2:51*/
public class FatherImpl implements Father {static public String height = "身体超高";/*** @return: int* @Description 强壮值* @author LiRuilong* @date 2022/2/12 2:51**/@Overridepublic int strong() {return 8;}
}

母亲接口

package com.liruilong;/*** @Project_name: workspack* @Package: com.liruilong* @Description: 母亲接口* @Author: 1224965096@qq.com* @WeChat_Official_Accounts: 山河已无恙* @blog: https://liruilong.blog.csdn.net/* @Date: 2022/2/12 2:50*/
public interface Mother {/*** @return: int* @Description 温柔的行为* @author LiRuilong* @date 2022/2/12 2:50**/int Kind();
}

母亲实现类

package com.liruilong;/*** @Project_name: workspack* @Package: com.liruilong* @Description: 母亲类* @Author: 1224965096@qq.com* @WeChat_Official_Accounts: 山河已无恙* @blog: https://liruilong.blog.csdn.net/* @Date: 2022/2/12 2:51*/
public class MotherImpl implements Mother{static public String pretty = "脸蛋特别漂亮";/*** @return: int* @Description 温柔值* @author LiRuilong* @date 2022/2/12 2:51**/@Overridepublic int Kind() {return 8;}
}

OK,准备工作做好了, 看我们如何实现。

package com.liruilong;import java.util.logging.Logger;/*** @Project_name: workspack* @Package: com.liruilong* @Description: 孩子类* @Author: 1224965096@qq.com* @WeChat_Official_Accounts: 山河已无恙* @blog: https://liruilong.blog.csdn.net/* @Date: 2022/2/12 13:16*/
public class Son extends FatherImpl implements Mother {static Logger logger = Logger.getAnonymousLogger();MotherSpecial motherSpecial = new MotherSpecial();@Overridepublic int strong() {return super.strong() + 1;}@Overridepublic int Kind() {return motherSpecial.Kind();}@Overridepublic String toString() {return "Son{" +"height=" + height +"," +"pretty=" + MotherSpecial.pretty +'}';}public class MotherSpecial extends MotherImpl {@Overridepublic int Kind() {return super.Kind() - 1;}}public static void main(String[] args) {Son son = new Son();logger.info(son.toString());logger.info(son.strong()+"");logger.info(son.Kind()+"");}}

我们用内部类继承一个外部类无关的类,实现了Son类的多继承

Bad level value for property: .level
Bad level value for property: java.util.logging.ConsoleHandler.level
Can''t set level for java.util.logging.ConsoleHandler
二月 12, 2022 2:02:06 下午 com.liruilong.Son main
信息: Son{height=身体超高,pretty=脸蛋特别漂亮}
二月 12, 2022 2:02:06 下午 com.liruilong.Son main
信息: 9
二月 12, 2022 2:02:06 下午 com.liruilong.Son main
信息: 7Process finished with exit code 0

这里只是讨论这样的写法,我个人认为,这种方法有些鸡肋。这种方式实现的多继承,完全可以通组合的方式来实现,我们简单分析一下优缺点

优缺点分析


优点:

通过内部类的方式,把继承关系控制在类的内部,理论上比通过组合的方式更加安全,代码可读性要好一点。

更符合设计原则中的迪米特法则,又称最少知道原则(Demeter Principle),一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

缺点:

首先通过继承的方式实现,打破了类的封装性,子类依赖于其超类中特定功能的实现细节。 超类的实现有可能会随着发行版本的不同而有所变化,如果真的发生了变化,即使子类的代码完全没有改变,但是子类可能会遭到破坏因而,子类必须要跟着其超类的更新而演变,除非超类是专门为了扩展而设计的,并且具有很好的文挡说明

其次,通过这样的方式实现的,不符合常态思想,尤其内部类同名的情况,容易被忽略某些特性(见JDK源码)。而且不满足合成复用原则(Composite Reuse Principle),尽量使用合成/聚合的方式,而不是使用继承。

JDK源码中的运用

关于通过内部类来实现java多继承JDK场景,我们简单分析一下

asList

List<Integer> integers &#61; Arrays.asList(1, 2, 3);

这个代码小伙伴们一定不陌生&#xff0c;这里通过Arrays工具类来生成一个List&#xff0c;但是这里的List并不是真正的ArrayList&#xff0c;而是在Arrays工具类内部定义的一个继承了AbstractList的静态内部类ArrayList,这里java通过内部类的方式巧妙的实现了。

.......&#64;SafeVarargs&#64;SuppressWarnings("varargs")public static <T> List<T> asList(T... a) {return new ArrayList<>(a);}/*** &#64;serial include*/private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable{private static final long serialVersionUID &#61; -2764017481108945198L;private final E[] a;ArrayList(E[] array) {a &#61; Objects.requireNonNull(array);}.................

但是这里同样需要注意的是通过内部类实现多继承要考虑其类的特殊性&#xff1a;

这样生成的List调用add方法会抛不支持的操作的异常&#xff0c;基于ArraysArrayList是一个静态私有内部类&#xff0c;除了Arrays能访问以外&#xff0c;其他类都不能访问&#xff0c;正常的ArrayList中add方法是ArrayList父类提供&#xff0c;Arrays的内部类ArrayList没有覆写add方法。

下面源码为ArrayList静态内部类实现的个方法。

/*** &#64;serial include*/private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable{private static final long serialVersionUID &#61; -2764017481108945198L;private final E[] a;ArrayList(E[] array) {a &#61; Objects.requireNonNull(array);}&#64;Overridepublic int size() {return a.length;}&#64;Overridepublic Object[] toArray() {return a.clone();}&#64;Override&#64;SuppressWarnings("unchecked")public <T> T[] toArray(T[] a) {int size &#61; size();if (a.length < size)return Arrays.copyOf(this.a, size,(Class<? extends T[]>) a.getClass());System.arraycopy(this.a, 0, a, 0, size);if (a.length > size)a[size] &#61; null;return a;}&#64;Overridepublic E get(int index) {return a[index];}&#64;Overridepublic E set(int index, E element) {E oldValue &#61; a[index];a[index] &#61; element;return oldValue;}&#64;Overridepublic int indexOf(Object o) {E[] a &#61; this.a;if (o &#61;&#61; null) {for (int i &#61; 0; i < a.length; i&#43;&#43;)if (a[i] &#61;&#61; null)return i;} else {for (int i &#61; 0; i < a.length; i&#43;&#43;)if (o.equals(a[i]))return i;}return -1;}&#64;Overridepublic boolean contains(Object o) {return indexOf(o) !&#61; -1;}&#64;Overridepublic Spliterator<E> spliterator() {return Spliterators.spliterator(a, Spliterator.ORDERED);}&#64;Overridepublic void forEach(Consumer<? super E> action) {Objects.requireNonNull(action);for (E e : a) {action.accept(e);}}&#64;Overridepublic void replaceAll(UnaryOperator<E> operator) {Objects.requireNonNull(operator);E[] a &#61; this.a;for (int i &#61; 0; i < a.length; i&#43;&#43;) {a[i] &#61; operator.apply(a[i]);}}&#64;Overridepublic void sort(Comparator<? super E> c) {Arrays.sort(a, c);}}

即没有实现addremove方法&#xff0c;所以asList返回的为一个长度不可变的列表&#xff0c;数组为多长转换为列表为多长&#xff0c;即不在保持列表动态变长的特性

subList

嗯&#xff0c;不多讲&#xff0c;直接上代码

ArrayList arrayList &#61; new ArrayList();LinkedList linkedList &#61; new LinkedList();Vector vector &#61; new Vector();linkedList.subList(2,3);arrayList.subList(2,3);vector.subList(2,3);

List提供一个subList方法,与StringsubString有点类似,这里的List通过subList生成子list方式也是通过内部类继承方式的多继承实现的。

当然这里&#xff0c;具体需要分析&#xff0c;ArrayList其他List的实现的方式略有不同

ArrayList是自己定义的内部类SubList继承AbstractList实现的

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{.......public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList(this, 0, fromIndex, toIndex);}
.....private class SubList extends AbstractList<E> implements RandomAccess {private final AbstractList<E> parent;private final int parentOffset;private final int offset;int size;.........

LinkedListsubList方法是由AbstractList实现的&#xff0c;它会根据是不是随机存储提供不同的实现方法&#xff0c;subList返回的类也是AbstractList的子类SubList

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {........public List<E> subList(int fromIndex, int toIndex) {return (this instanceof RandomAccess ?new RandomAccessSubList<>(this, fromIndex, toIndex) :new SubList<>(this, fromIndex, toIndex));}class SubList<E> extends AbstractList<E> {...}class RandomAccessSubList<E> extends SubList<E> implements RandomAccess{.......}........
}

这里需要注意的是&#xff0c;不管是ArrayList还是LinkedList等其他List&#xff0c;通过SubList内部类生成的List&#xff0c;其所有的方法(get,add,set,remove等)都是在原始列表上操作的&#xff0c;它自身并没有生成一个数组或是链表&#xff0c;也就是子列表只是原列表的一个视图(View&#xff09;,所有的修改都反映在原列表上。


推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
author-avatar
sensor
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有