写点什么

[JAVA 冷知识]JAVA 居然可以多继承吗?让我们用内部类去实现吧!

作者:山河已无恙
  • 2022 年 2 月 13 日
  • 本文字数:6595 字

    阅读完需:约 22 分钟

写在前面


  • 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    **/    @Override    public 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    **/    @Override    public 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();

    @Override    public int strong() {        return super.strong() + 1;    }
    @Override    public int Kind() {        return motherSpecial.Kind();    }

    @Override    public String toString() {        return "Son{" +                "height=" + height +"," +                "pretty=" + MotherSpecial.pretty +                '}';    }

    public class MotherSpecial extends MotherImpl {        @Override        public 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: .levelBad level value for property: java.util.logging.ConsoleHandler.levelCan''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信息: 7
Process finished with exit code 0

复制代码

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

优缺点分析

优点:

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

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

缺点:

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

其次,通过这样的方式实现的,不符合常态思想,尤其内部类同名的情况,容易被忽略某些特性(见 JDK 源码)。

同时不符合面向对象软件设计原则中的合成复用原则

JDK 源码中的运用

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

asList

List<Integer> integers = Arrays.asList(1, 2, 3);
复制代码

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

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

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


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

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

    /**     * @serial include     */    private static class ArrayList<E> extends AbstractList<E>        implements RandomAccess, java.io.Serializable    {        private static final long serialVersionUID = -2764017481108945198L;        private final E[] a;
        ArrayList(E[] array) {            a = Objects.requireNonNull(array);        }
        @Override        public int size() {            return a.length;        }
        @Override        public Object[] toArray() {            return a.clone();        }
        @Override        @SuppressWarnings("unchecked")        public <T> T[] toArray(T[] a) {            int size = 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] = null;            return a;        }
        @Override        public E get(int index) {            return a[index];        }
        @Override        public E set(int index, E element) {            E oldValue = a[index];            a[index] = element;            return oldValue;        }
        @Override        public int indexOf(Object o) {            E[] a = this.a;            if (o == null) {                for (int i = 0; i < a.length; i++)                    if (a[i] == null)                        return i;            } else {                for (int i = 0; i < a.length; i++)                    if (o.equals(a[i]))                        return i;            }            return -1;        }
        @Override        public boolean contains(Object o) {            return indexOf(o) != -1;        }
        @Override        public Spliterator<E> spliterator() {            return Spliterators.spliterator(a, Spliterator.ORDERED);        }
        @Override        public void forEach(Consumer<? super E> action) {            Objects.requireNonNull(action);            for (E e : a) {                action.accept(e);            }        }
        @Override        public void replaceAll(UnaryOperator<E> operator) {            Objects.requireNonNull(operator);            E[] a = this.a;            for (int i = 0; i < a.length; i++) {                a[i] = operator.apply(a[i]);            }        }
        @Override        public void sort(Comparator<? super E> c) {            Arrays.sort(a, c);        }    }
复制代码

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

subList

嗯,不多讲,直接上代码

        ArrayList arrayList = new ArrayList();        LinkedList linkedList = new LinkedList();        Vector vector = new Vector();        linkedList.subList(2,3);        arrayList.subList(2,3);        vector.subList(2,3);
复制代码

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

当然这里,具体需要分析,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实现的,它会根据是不是随机存储提供不同的实现方法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{    .......    }    ........ }
复制代码

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

发布于: 2022 年 02 月 13 日阅读数: 64
用户头像

CSDN博客专家,华为云云享专家,RHCE/CKA认证 2022.01.04 加入

Java 后端一枚,技术不高,前端、Shell、Python 也可以写一点.纯种屌丝,不热爱生活,热爱学习,热爱工作,喜欢一直忙,不闲着。喜欢篆刻,喜欢吃好吃的,喜欢吃饱了晒太阳。

评论 (2 条评论)

发布
用户头像
连续看到2篇文章中有package com.liruilong,作者的文章更多的是标题党,基本只需要一句话就可以说清楚的内容,没有深度,没有延展,没有自己的思考。

嗯,可能我的要求稍微高了那么一点点吧。
17 分钟前
回复
就拿这篇文章来说吧,这种方式实现,是多继承么?和内部类有什么直接关系?
本质上,就是单继承+实现接口而已。没有任何的人把这种做法叫做「多继承」。


如果你那interface的default method来举例,并且提到多继承的弊端「菱形继承/diamond inheritance」问题,并且说明在java中是怎么避免的,或许是最靠近「多继承」概念的。


例如:
interface A {
default int get() { return 1; }
}


interface B {
default int get() { return 2; }
}


interface C extends B {
default int get() { return 3; }
}


interface D {
int get();
}


class X1 implements A, B { }
class X2 implements A, C { }
class X3 implements A, B { }


以上X1、X2、X3的get()方法,在「多重继承」的实现中,有什么问题,遵循什么原则,编译器会不会告警等。
展开
刚刚
回复
没有更多了
[JAVA冷知识]JAVA居然可以多继承吗?让我们用内部类去实现吧!