Java中的类型列表与类型ArrayList

关于在Java中使用List接口与ArrayList的具体实现,讨论了两者在灵活性、接口解耦、性能优化等方面的选择。文章指出,通常推荐使用List接口以保持代码的灵活性,但在某些特定场景下,如依赖ArrayList的特定行为或性能需求时,直接使用ArrayList更为合适。此外,还提到了序列化和集合类型在接口与实现之间的选择对代码的影响。
摘要由CSDN通过智能技术生成
(1) List<?> myList = new ArrayList<?>();

(2) ArrayList<?> myList = new ArrayList<?>();

我了解使用(1),可以交换List接口的实现。 似乎(1)通常在应用程序中使用,而不需要它(我自己总是使用它)。

我想知道是否有人使用(2)?

另外,这种情况实际上需要多久(并且我可以举个例子),实际上需要使用(1)而不是(2)(即,其中(2)不足以..除了编码接口最佳实践等之外)。


#1楼

如果代码是列表的“所有者”,则使用(2)。 例如,对于仅局部变量,则为true。 没有理由使用抽象类型List而不是ArrayList 。 另一个显示所有权的示例:

public class Test {

    // This object is the owner of strings, so use the concrete type.
    private final ArrayList<String> strings = new ArrayList<>();

    // This object uses the argument but doesn't own it, so use abstract type.
    public void addStrings(List<String> add) {
        strings.addAll(add);
    }

    // Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
    public List<String> getStrings() {
        return Collections.unmodifiableList(strings);
    }

    // Here we create a new list and give ownership to the caller. Use concrete type.
    public ArrayList<String> getStringsCopy() {
        return new ArrayList<>(strings);
    }
}

#2楼

HashSetTreeSet的引用存储在Set类型的变量中被认为一种很好的样式

Set<String> names = new HashSet<String>();

这样,如果您决定改用TreeSet ,则只需更改一行。

另外,对集合进行操作的方法应指定Set类型的参数:

public static void print(Set<String> s)

然后, 该方法可用于所有set实现

从理论上讲,我们应该对链接列表提出相同的建议,即将LinkedList引用保存在List类型的变量中。 但是,在Java库中,List接口是ArrayListLinkedList类所共有的。 特别是,它具有用于随机访问的get和set方法,即使这些方法对于链表来说效率很低。

如果您不知道随机访问是否有效, 就无法编写高效的代码

在标准库中,这显然是一个严重的设计错误,因此,我不建议使用List接口。

要查看该错误的binarySearch程度,请查看Collections类的binarySearch方法的源代码。 该方法采用List参数,但是二进制搜索对于链接列表没有任何意义。 然后,代码笨拙地尝试发现列表是否为链接列表,然后切换到线性搜索!

Set界面和Map界面设计合理,应该使用它们。


#3楼

当您编写List ,您实际上告诉您该对象仅实现List接口,但没有指定该对象所属的类。

编写ArrayList ,您指定您的对象类是可调整大小的数组。

因此,第一个版本使您的代码将来更加灵活。

查看Java文档:

ArrayList -大小可变数组实现的List界面。

接口List -有序集合(也称为序列)。 该界面的用户可以精确控制列表中每个元素的插入位置。

Array包含固定数量的单一类型值的容器对象。


#4楼

ArrayListList几乎总是首选,因为例如, List可以转换为LinkedList而不影响其余代码库。

如果使用ArrayList而不是List ,则很难将ArrayList实现更改为LinkedList因为在代码库中使用了ArrayList特定的方法,这也需要进行重组。

您可以在此处阅读有关List实现的信息

您可以从ArrayList开始,但是很快发现另一个实现是更合适的选择。


#5楼

我会说首选1,除非

  • 您取决于ArrayList中可选行为*的实现,在这种情况下,显式使用ArrayList更清晰
  • 您将在需要ArrayList的方法调用中使用ArrayList,可能是出于可选的行为或性能特征

我的猜测是,在99%的情况下,您可以通过List获得,这是首选。

  • 例如removeAlladd(null)

#6楼

我想知道是否有人使用(2)?

是。 但很少有合理的理由(IMO)。

人们之所以被烧死是因为他们本应使用List时使用ArrayList

  • 诸如Collections.singletonList(...)Arrays.asList(...)类的实用方法不会返回ArrayList

  • List API中的方法不能保证返回相同类型的列表。

例如,有人被烧死,在https://stackoverflow.com/a/1481123/139985中 ,海报的“切片”出现了问题,因为ArrayList.sublist(...)不返回ArrayList ...设计他的代码以将ArrayList用作所有列表变量的类型。 他最终通过将子列表复制到新的ArrayList来“解决”该问题。

通过使用RandomAccess标记接口,可以很大程度上解决需要了解List行为的参数。 是的,它有点笨拙,但替代方案更糟。

而且,这种情况实际需要多久使用一次(1)而不是(2)(即,其中(2)不足以..除了“接口编码”和最佳实践等之外)

问题的“频率”部分在客观上是无法回答的。

(我可以举个例子)

有时,应用程序可能会要求您使用ArrayList API中List API中使用的方法。 例如, trimToSize() ensureCapacity(int)trimToSize()removeRange(int, int) 。 (最后一个只有在您创建了ArrayList的子类型并将该方法声明为public时才会出现。)

这是对类而不是接口IMO进行编码的唯一合理原因。

(从理论上讲,在某些情况下……在某些平台上,您可能会在性能上稍有提高,但是除非您真的需要最后0.05%,否则这样做是不值得的。合理的理由,IMO。)


如果您不知道随机访问是否有效,就无法编写高效的代码。

这是有道理的。 但是,Java提供了更好的方法来解决该问题。 例如

public <T extends List & RandomAccess> void test(T list) {
    // do stuff
}

如果使用未实现RandomAccess的列表进行调用,则会出现编译错误。

如果静态类型过于尴尬,您还可以使用instanceof ...动态测试。 您甚至可以编写代码以使用(动态)不同的算法,具体取决于列表是否支持随机访问。

请注意, ArrayList不是唯一实现RandomAccess列表类。 其他包括CopyOnWriteListStackVector

我见过人们对Serializable提出相同的争论(因为List没有实现它)……但是上面的方法也解决了这个问题。 (在某种程度上,使用运行时类型都可以解决 。如果任何元素不可序列化, ArrayList都会失败。)


#7楼

(3)集合myCollection = new ArrayList();

我通常使用这个。 而且只有在我需要List方法时,我才会使用List。 与ArrayList相同。 您始终可以切换到更多的“窄”界面,但不能切换到更多的“宽”界面。


#8楼

唯一的情况下,我知道在哪里(2) 可以得到更好的使用GWT时,因为它减少了应用程序的足迹(不是我的主意,但谷歌Web Toolkit的团队是这么说的)。 但是对于在JVM(1)中运行的常规Java来说,它总是更好。


#9楼

我认为使用(2)的人不了解Liskov替换原则Dependency Inversion原则 。 或者他们真的必须使用ArrayList


#10楼

例如,您可能确定LinkedList是您的应用程序的最佳选择,但后来出于性能原因,决定ArrayList可能是更好的选择。

采用:

List list = new ArrayList(100); // will be better also to set the initial capacity of a collection 

代替:

ArrayList list = new ArrayList();

以供参考:

在此处输入图片说明

(主要发布于收藏图)


#11楼

以下两个中的一个:

(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();

首先通常是首选。 因为您将仅使用List接口的方法,所以它为您提供了以后使用List其他实现的自由,例如LinkedList 。 因此,它使您脱离了特定的实现。 现在有两点值得一提:

  1. 我们应该始终编写接口程序。 这里更多。
  2. 您几乎总是会在LinkedList最终使用ArrayList这里更多。

我想知道是否有人使用(2)

有时候是(很少读)。 当我们需要方法是ArrayList的实现的一部分而不是接口List一部分时。 例如, ensureCapacity

另外,这种情况实际需要多久(我可以举个例子),使用(1)而不是(2)

几乎总是您喜欢选项(1)。 这是OOP中的经典设计模式,在该模式中,您始终尝试将代码从特定的实现和程序分离到接口。


#12楼

List是一个接口,它没有方法。 当您在List引用上调用method时。 实际上,在两种情况下都将调用ArrayList的方法。

将来,您可以将List obj = new ArrayList<>更改为List obj = new LinkList<>或其他实现List接口的类型


#13楼

实际上,有时(2)不仅是首选而且是强制性的,我很惊讶,在这里没有人提及。

序列化!

如果您有一个可序列化的类并且希望它包含一个列表,则必须声明该字段为ArrayList类的具体且可序列化的类型,因为List接口不会扩展java.io.Serializable

显然,大多数人不需要序列化,而不必理会。

一个例子:

public class ExampleData implements java.io.Serializable {

// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();

#14楼

有人再次询问(重复),这使我对这个问题更深入了。

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");

    ArrayList<String> aList = new ArrayList<String>();
    aList.add("a");
    aList.add("b");

}

如果我们使用字节码查看器(我使用的是http://asm.ow2.org/eclipse/index.html ),则列表片段将看到以下内容(仅列表初始化和赋值):

   L0
    LINENUMBER 9 L0
    NEW ArrayList
    DUP
    INVOKESPECIAL ArrayList.<init> () : void
    ASTORE 1
   L1
    LINENUMBER 10 L1
    ALOAD 1: list
    LDC "a"
    INVOKEINTERFACE List.add (Object) : boolean
    POP
   L2
    LINENUMBER 11 L2
    ALOAD 1: list
    LDC "b"
    INVOKEINTERFACE List.add (Object) : boolean
    POP

对于清单

   L3
    LINENUMBER 13 L3
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 2
   L4
    LINENUMBER 14 L4
    ALOAD 2
    LDC "a"
    INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP
   L5
    LINENUMBER 15 L5
    ALOAD 2
    LDC "b"
    INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP

区别在于list最终调用INVOKEINTERFACE,aList调用INVOKEVIRTUAL 。 根据Bycode Outline插件参考,

invokeinterface用于调用在Java接口中声明的方法

在调用虚拟时

调用除接口方法(使用invokeinterface的方法),静态方法(使用invokestatic的方法)以及invokespecial处理的一些特殊情况以外的所有方法。

总之,invokevirtual将objectref从堆栈中弹出,而invokeinterface

解释器将'n'个项目弹出操作数堆栈,其中'n'是从字节码中获取的8位无符号整数参数。 这些项目中的第一个是objectref,这是对被调用其方法的对象的引用。

如果我正确理解这一点,则本质上的区别在于每种方式如何检索objectref


#15楼

List接口具有几个不同的类ArrayListLinkedListLinkedList用于创建索引集合,而ArrayList用于创建排序列表。 因此,您可以在参数中使用其中的任何一个,但是您可以允许其他使用您的代码,库等的开发人员使用不同类型的列表,而不仅仅是您使用的列表,因此,在此方法中

ArrayList<Object> myMethod (ArrayList<Object> input) {
   // body
}

您只能将其与ArrayList ,而不能与LinkedList一起使用,但可以允许在使用该方法的其他地方使用任何List类,这只是您的选择,因此使用接口可以允许它:

List<Object> myMethod (List<Object> input) {
   // body
}

在此方法参数中,您可以使用要使用的任何List类:

List<Object> list = new ArrayList<Object> ();

list.add ("string");

myMethod (list);

结论:

尽可能在任何地方使用接口,不要限制您或其他人使用他们想要使用的不同方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值