[翻译]List为什么实现了那么多接口?

本文解释了List<T>为何实现多个接口的原因,包括接口继承的特性、冗余声明的作用及其实现细节,并探讨了不同工具如何展示这些接口。

[翻译]List<T>为什么实现了那么多接口?

原文地址:http://blogs.msdn.com/b/ericlippert/archive/2011/04/04/so-many-interfaces.aspx

原作者:Eric Lippert

Eric Lippert是微软员工,C#编译器的主要开发人员之一。

 

今天,我在StackOverflow上回答了一个问题。按照以往的习惯,我把它以对话体的形式整理成一篇博客。

MSDN的文档中说List<T>是这样声明的:

public class List : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable

 

那么List真的实现了这么多接口吗?

是的。

为什么会有这么多接口呢?

比如说IList<T>派生自IEnumerable<T>,那么派生程度较高的接口的实现者就必须要实现派生程度较低的接口。接口继承就是这样的;如果你要满足派生程度较高的类型的契约的话,你就必须要满足派生程度较低的类型。

那么就是说一个class或者一个struct必须要实现其传递闭包上所有接口定义的所有方法吗?

非常正确。

一个实现了派生程度较高的接口的class或者struct必须要在其基类型列表中声明它实现了那些派生程度较低的接口吗?

不是。

那会被禁止声明吗?

不会。

那么就是说派生程度较低的接口是否被声明在基类列表中是可选的了?

是的。

总是这样的吗?

基本上总是这样的:

interface I1 {}
interface I2 : I1 {}
interface I3 : I2 {}

 

I3是否显式指明它实现了I1是可选的。

class B : I3 {}

 

I3的实现者必须实现I2和I1,但不一定要显式指明。是否指明是可选的。

class D : B {}

派生类可以指明它实现了其基类的接口,但是并不是一定要这么做。

下面有一个例子:

class C<T> where T : I3
{
  public virtual void M<U>() where U : I3 {}
}

 

 

T和U所对应的类型参数必须要实现I2和I1,但是T的U的泛型类型约束中却不一定要显式指明出来。

分布类中是否重新声明它所实现的接口也是可选的:

partial class E : I3 {}
partial class E {}

 

类型E的第二部分可以声明它实现了I3,I2或I1,但并不是一定要这么做。

好吧,我懂了;这是可选的。那怎么会有人想去声明一个并不是一定要声明的接口呢?

可能是因为他们认为这么做会让代码更易读更具有自说明性吧。

有可能某个开发者会写出如下的代码:

interface I1 {}
interface I2 {}
interface I3 : I1, I2 {}

 

然后他突然发觉I2应该继承自I1。干嘛非得要求开发者在把I2修改为继承自I1之后再回头去在I3的声明中删除掉对I1的继承呢?我找不到任何理由去强制开发人员删除掉冗余的声明信息。

除了易读性和易懂性之外,在基类列表中显式声明一个接口和虽不声明但实现这个接口会有什么技术上的区别吗?

通常没有,但是有一种情况下会有一点细微的差别。假设有个类型D,它的基类B实现了某些接口。D通过B也就自动实现了那些接口。如果你在D的基类列表中显式声明了这些接口的话,那么C#编译器将会做一次接口重新实现。其细节有些隐晦;如果你对此有兴趣的话那我建议你去仔细阅读C# 4语言规范的13.4.6。基本上来说,编译器会“重新开始”并找出哪个方法实现了哪个接口。

List<T>的源代码中真的显式声明了那么多接口吗?

不是的。真正的源代码是这样的:

public class List<T> : IList<T>, System.Collections.IList

为什么虽然源代码没有显式声明所有接口,但是MSDN还是把所有的接口都列出来了呢?

因为MSDN是文档,它应该提供尽可能多的信息。在一页文档中给出完整的信息总比让你去翻阅十页文档才能找到一个类型实现的全部接口好一些。

为什么有些工具,比如说Reflector或者对象浏览器会把所有接口都显示出来呢?

这些工具不知道源代码是什么样子的。它们只能从元数据入手。因为显式声明全部接口只是可选的,所以这些工具根本就无从得知源代码到底有没有显式声明所有接口。反正怎么显示都有可能是错的,那这些工具还不如就把信息罗列的全面一些。这些工具给你显示了有可能比实际情况更多的信息,而不是向你隐藏一些你有可能会需要的信息是因为它们想要帮助你。

我发现IEnumerable<T>继承自IEnumerable,但是IList<T>并没有继承自IList,这是怎么回事呢?

这和IEnumerable<T>是协变的而IList<T>不是协变的是一个原因。通过装箱,我们可以把一个整数序列当做一个object的序列来对待。但是一个可读可写的整数列表却不可以被当做一个可读可写的object的列表来对待,因为你有可能给可读可写的object列表中加入一个String。一个实现IEnumerable<T>的类型可以很容易的满足IEnumerable,只要加一个装箱的helper方法就行了。但是实现IList<T>的类型却不一定能够满足IList,所以IList没有继承自IList。

那为什么List<T>又去实现了IList呢?

这个有点奇怪,因为除了T为object时之外,其他情况下List<T>都不满足的IList的要求。这有可能是因为想要给升级老的C# 1.0代码的人行个方便吧,好让他们更容易的用到泛型。那些想要升级代码的人可能已经确保了只向列表中加入正确类型的对象。而且多数情况下,当你把一个IList当做参数传递出去的时候,被调用的方法也只是会去按索引访问列表,而不是去向列表中加入一些任意类型的对象。

<think>首先,用户的问题是:“我想询问Java中List接口的实现类是否支持索引访问 请问Java List接口的实现类是否支持索引访问”。这翻译成英文是:“I want to ask if the implementation classes of the Java List interface support index access. Do the implementation classes of the Java List interface support index access?” 关键点是:用户想知道Java List接口的实现类是否支持索引访问。 现在,根据系统级指令: 1. 所有行内数学表达式必须使用$...$格式。但这个问题是关于Java编程的,没有数学表达式,所以可能不需要数学格式。但指令说“包括变量、不等式、方程等”,所以如果有任何数学相关的内容,才需要。这里没有,所以我可以忽略。 2. 独立公式必须使用$$...$$格式并单独成段。同样,没有公式。 3. LaTeX语法正确:不适用,因为没有数学。 4. 使用中文回答:整个回答必须用中文。 5. 生成相关问题:在回答结束时,生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识:用户提供了引用,我需要引用它们。 用户提供了三个引用: - 引用[1]: 关于List接口的实现类,如ArrayList和LinkedList。 - 引用[2]: 关于List接口的实现类,如ArrayList。 - 引用[3]: 关于List接口的实现类,包括ArrayList、LinkedList、CopyOnWriteArrayList等。 用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但在这个对话中,这是第一个用户消息,所以没有“上一次的问题”。我需要基于这些引用回答问题。 用户的问题是:“Java List接口的实现类是否支持索引访问?” 从Java知识中,我知道List接口定义了索引访问的方法,如get(int index)。所以,实现类应该支持索引访问,但效率可能不同。 查看引用: - 引用[1]: 提到ArrayList和LinkedList实现List接口,可以调用Collection和List的方法。 - 引用[2]: 同样提到ArrayList作为实现类。 - 引用[3]: 详细列出了各种实现类,如ArrayList、LinkedList、CopyOnWriteArrayList等,并讨论了它们的性能,包括索引访问。 具体从引用[3]:它说“高频随机访问ArrayList O(1)索引访问”,意思是ArrayList支持索引访问,时间复杂度为O(1)。对于LinkedList,它说“频繁增删操作LinkedList O(1)头尾插入/删除”,但索引访问在LinkedList中是O(n),因为它需要遍历。 所以,所有List接口的实现类都支持索引访问,因为List接口要求实现get(int index)方法。但实现方式不同: - ArrayList:基于数组,索引访问是O(1)。 - LinkedList:基于链表,索引访问是O(n)。 - 其他如CopyOnWriteArrayList也支持索引访问。 因此,答案是:是的,所有List接口的实现类都支持索引访问,但性能取决于具体实现。 现在,构建回答: - 结构清晰:逐步解决问题。 - 帮助用户:解释清楚。 - 使用中文。 - 在引用段落末尾添加引用标识,如[^1]。 引用用户提供的引用: - 引用[1]: [^1] - 引用[2]: [^2] - 引用[3]: [^3] 我需要自然地引用它们。 回答结构: 1. 直接回答问题:是的,支持。 2. 解释为什么:因为List接口定义了索引访问的方法。 3. 给出具体实现类的例子:如ArrayList和LinkedList。 4. 讨论性能差异:基于引用[3]。 5. 结束时生成相关问题。 生成相关问题:基于指令“生成相关问题”,我需要创建2-3个相关问题。 例如: - 哪些List实现类在索引访问上效率最高? - LinkedList在索引访问时性能如何? - 等等。 最后,确保回答真实可靠:基于Java标准库和引用。 完整回答: - 开头:直接回应用户问题。 - 主体:解释List接口和实现类。 - 引用:在适当位置引用。 - 结尾:生成相关问题。</think>### Java List接口的实现类是否支持索引访问? 是的,Java中List接口的所有实现类都支持索引访问。这是因为List接口本身定义了索引访问的方法,如`get(int index)`,用于获取指定位置的元素,以及`set(int index, E element)`用于修改元素。这些方法是List接口的强制性要求,任何实现类都必须提供这些功能。下面我将逐步解释原因、具体实现类的表现,以及性能差异。 #### 1. **为什么支持索引访问?** - List接口是Java集合框架的一部分,它扩展了Collection接口,并添加了基于索引的操作[^1]。这意味着所有实现List接口的类(如ArrayList、LinkedList等)都必须实现`get(int index)`方法,以支持通过索引访问元素。 - 例如,在代码中,你可以直接使用`list.get(0)`来获取第一个元素,无论list是ArrayList还是LinkedList的实例。这体现了Java接口的“契约”特性:实现类必须遵守接口定义的方法规范[^2]。 #### 2. **主要实现类及其索引访问特性** 虽然所有实现类都支持索引访问,但不同实现类的底层数据结构影响了访问效率和适用场景。以下是常见实现类的分析: - **ArrayList**:基于动态数组实现,索引访问的时间复杂度为$O(1)$(常数时间),因为它直接通过数组下标定位元素。这使得ArrayList在随机访问(如频繁读取)场景中非常高效[^3]。 - 示例代码: ```java List<String> arrayList = new ArrayList<>(); arrayList.add("A"); arrayList.add("B"); System.out.println(arrayList.get(0)); // 输出 "A",索引访问 ``` - **LinkedList**:基于双向链表实现,索引访问的时间复杂度为$O(n)$(线性时间),因为需要从链表头或尾遍历到指定位置。虽然支持索引访问,但在随机访问时性能较差,更适合频繁插入/删除操作[^3]。 - 示例代码: ```java List<String> linkedList = new LinkedList<>(); linkedList.add("X"); linkedList.add("Y"); System.out.println(linkedList.get(1)); // 输出 "Y",索引访问 ``` - **其他实现类**: - **CopyOnWriteArrayList**:线程安全的实现类,索引访问也是$O(1)$,但写操作会复制整个数组,适合读多写少的并发场景[^3]。 - **Collections.synchronizedList**:包装类,提供同步的索引访问,性能取决于底层实现(如ArrayList或LinkedList)[^3]。 - **Arrays.ArrayList**(来自`Arrays.asList()`):固定大小的列表,索引访问为$O(1)$,但不支持结构修改[^3]。 总之,索引访问是List实现类的标准功能,但效率差异显著:ArrayList类在随机访问上最优,而LinkedList类在索引访问上可能较慢[^3]。 #### 3. **性能注意事项** - 在需要高频索引访问的场景(如遍历或随机读取),推荐使用ArrayList,因为它的$O(1)$访问时间能提升性能[^3]。 - 如果涉及频繁的增删操作,LinkedList的$O(1)$头尾插入/删除可能更合适,但需避免在大型列表中使用索引访问,以免$O(n)$开销导致性能下降[^3]。 - 引用[3]中的性能对比表总结了不同场景下的推荐实现类: | 场景需求 | 推荐实现类 | 索引访问优势 | |------------------------|---------------------|-------------------| | 高频随机访问 | ArrayList | $O(1)$,高效 | | 频繁增删操作 | LinkedList | $O(n)$,避免使用 | | 多线程读多写少 | CopyOnWriteArrayList| $O(1)$,线程安全 | | 只读或固定大小数据 | Arrays.ArrayList | $O(1)$,零开销 | #### 4. **总结** Java List接口的实现类都支持索引访问,这是由接口规范保证的。然而,选择哪个实现类取决于具体应用:ArrayList适合快速随机访问,LinkedList适合动态修改,而线程安全类(如CopyOnWriteArrayList)用于并发环境[^1][^2][^3]。在实际开发中,建议根据性能需求测试和选择。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值