ArrayList继承了AbstractList,为什么还要实现List接口呢?

1,827 阅读2分钟

查看ArrayList源码的时候,我突然发现ArrayList继承了AbstractList并且实现了List接口,但是AbstractList已经实现了List接口了!

于是我就很疑惑,这个ArrayList实现List接口的意义在哪?

最后在Stack Overflow找到了一个比较信服的答案。

附上原文:Why does LinkedHashSet<E> extend HashSet<e> and implement Set<E>

其中一个回复,来自google的员工Kevin Bourrillion:

I've asked Josh Bloch, and he informs me that it was a mistake. He used to think, long ago, that there was some value in it, but he since "saw the light". Clearly JDK maintainers haven't considered this to be worth backing out later.

意思就是,这是一个错误,Josh Bloch(Java集合框架的创始者)当时以为这样做有用。

另一个回答

我之所以将这个回答贴出来的原因是:虽然这个回答还是不足以让人信服,但是它揭示的一个问题还是存在价值的。

package com.example;

import java.io.Serializable;
import java.util.Arrays;

public class Test {

    public static interface MyInterface {
        void foo();
    }

    public static class BaseClass implements MyInterface, Cloneable, Serializable {
        @Override
        public void foo() {
            System.out.println("BaseClass.foo");
        }
    }

    public static class Class1 extends BaseClass {
        @Override
        public void foo() {
            super.foo();
            System.out.println("Class1.foo");
        }
    }

    static class Class2 extends BaseClass implements MyInterface, Cloneable, Serializable {
        @Override
        public void foo() {
            super.foo();
            System.out.println("Class2.foo");
        }
    }

    public static void main(String[] args) {
        showInterfacesFor(BaseClass.class);
        showInterfacesFor(Class1.class);
        showInterfacesFor(Class2.class);
    }

    private static void showInterfacesFor(Class<?> clazz) {
        System.out.printf("%s --> %s\n", clazz, Arrays.toString(clazz.getInterfaces()));
    }
}

输出如下:

class example.Test$BaseClass --> [interface example.Test$MyInterface, interface java.lang.Cloneable, interface java.io.Serializable]
class example.Test$Class1 --> []
class example.Test$Class2 --> [interface example.Test$MyInterface, interface java.lang.Cloneable, interface java.io.Serializable]

我们看到Class1没有显式接口定义,所以Class#getInterfaces()没有包含这些接口,而Class2包含了这些接口。它的使用在下面这个程序才会清晰:

package example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import example.Test.BaseClass;
import example.Test.Class1;
import example.Test.Class2;

public class Test2 extends Test {

    public static void main(String[] args) {

        MyInterface c1 = new Class1();
        MyInterface c2 = new Class2();

        //注意顺序
        MyInterface proxy2 = createProxy(c2);
        proxy2.foo();
        //这个会因为unchecked exception而失败
        MyInterface proxy1 = createProxy(c1);
        proxy1.foo();
    }

    /**
     * 创建obj的代理类
     * @param obj 要被代理的对象
     * @return
     */
    private static <T> T createProxy(final T obj) {

        final InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.printf("要调用 %s 的 %s方法\n", obj, method.getName());
                return method.invoke(obj, args);
            }
        };

        return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}

输出如下:

要调用 example.Test$Class2@578ceb 的 foo方法
BaseClass.foo
Class2.foo
Exception in thread "main" java.lang.ClassCastException: $Proxy1 cannot be cast to example.Test$MyInterface
 at example.Test2.main(Test2.java:23)

虽然Class1隐式的实现了MyInterface,但是不能创建代理对象。

因此,如果我们想要获取具有隐式接口继承的对象的所有接口,从而来创建动态代理。那么唯一的办法就是通过反射遍历类的继承,并找出超类的所有接口。通过显式的声明接口,可以更容易获得该类继承的所有接口。

这个观点认为这样设置的原因是因为这样做能够方便动态创建代理对象。但我存疑的原因是:Collection框架是在jdk1.2的时候引入的,而Proxy是jdk1.3才加入的。这个回答只能解释了为什么这种形式会被保留下来,而不能说明作者当初为什么这么写。