Tipo de lista vs tipo ArrayList em Java

(1) List myList = new ArrayList(); (2) ArrayList myList = new ArrayList(); 

Eu entendo que com (1), implementações da interface List podem ser trocadas. Parece que (1) é normalmente usado em um aplicativo, independentemente da necessidade (eu sempre uso isso).

Eu estou querendo saber se alguém usa (2)?

Além disso, com que frequência (e posso por favor obter um exemplo) a situação realmente requer o uso de (1) sobre (2) (ou seja, onde (2) não seria suficiente … além de codificar para interfaces e melhores práticas etc.)

Quase sempre o primeiro é preferido em relação ao segundo. O primeiro tem a vantagem de que a implementação da List pode mudar (para uma LinkedList por exemplo), sem afetar o resto do código. Esta será uma tarefa difícil de fazer com um ArrayList , não só porque você precisará alterar ArrayList para LinkedList todos os lugares, mas também porque você pode ter usado methods específicos do ArrayList .

Você pode ler sobre as implementações de List aqui . Você pode começar com um ArrayList , mas logo depois descobre que outra implementação é mais apropriada.

Eu estou querendo saber se alguém usa (2)?

Sim. Mas raramente por um bom motivo.

E às vezes as pessoas se queimam porque usaram ArrayList quando deveriam ter usado List :

  • Métodos utilitários como Collections.singletonList(...) ou Arrays.asList(...) não retornam um ArrayList .

  • Os methods na API List não garantem a devolução de uma lista do mesmo tipo.

Por exemplo, em https://stackoverflow.com/a/1481123/139985, o cartaz teve problemas com o “slicing” porque ArrayList.sublist(...) não retorna um ArrayList … e ele projetou seu código para use ArrayList como o tipo de todas as suas variables ​​de lista. Ele acabou “resolvendo” o problema copiando a sub-lista para uma nova ArrayList .

O argumento de que você precisa saber como o List se comporta é amplamente tratado usando a interface do marcador RandomAccess . Sim, é um pouco desajeitado, mas a alternativa é pior.

Além disso, com que frequência a situação realmente requer o uso de (1) sobre (2) (ou seja, onde (2) não seria suficiente … além de ‘codificar para interfaces’ e melhores práticas etc.)

A parte “com que frequência” da pergunta é objetivamente irrespondível.

(e posso por favor obter um exemplo)

Ocasionalmente, o aplicativo pode exigir que você use methods na API ArrayList que não estão na API List . Por exemplo, ensureCapacity(int) , trimToSize() ou removeRange(int, int) . (E o último só surgirá se você tiver criado um subtipo de ArrayList que declara o método como public ).

Esse é o único motivo sólido para codificar a class em vez da interface, IMO.

(É teoricamente possível que você tenha uma leve melhora no desempenho … sob certas circunstâncias … em algumas plataformas … mas a menos que você realmente precise dos últimos 0,05%, não vale a pena fazer isso. Isso não é razão de som, IMO.)


Você não pode escrever código eficiente se não souber se o access random é eficiente ou não.

Esse é um ponto válido. No entanto, o Java fornece melhores maneiras de lidar com isso; por exemplo

 public  void test(T list) { // do stuff } 

Se você chamar isso com uma lista que não implementa o RandomAccess você receberá um erro de compilation.

Você também pode testar dinamicamente … usando instanceof … se a tipagem estática é muito embaraçosa. E você poderia até mesmo escrever seu código para usar algoritmos diferentes (dinamicamente) dependendo se uma lista suportava access random ou não.

Note que ArrayList não é a única class de lista que implementa o RandomAccess . Outros incluem CopyOnWriteList , Stack e Vector .

Eu vi pessoas fazerem o mesmo argumento sobre Serializable (porque List não o implementa) … mas a abordagem acima resolve esse problema também. (Na medida em que é solúvel em todos os tipos de tempo de execução. Um ArrayList falhará serialização se algum elemento não é serializável.)

Por exemplo, você pode decidir que uma LinkedList é a melhor escolha para seu aplicativo, mas depois decide que o ArrayList pode ser uma escolha melhor por motivo de desempenho.

Usar:

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

Ao invés de:

 ArrayList list = new ArrayList(); 

Para referência:

insira a descrição da imagem aqui

(postado principalmente para o diagrama de coleção)

É considerado bom estilo armazenar uma referência a um HashSet ou TreeSet em uma variável do tipo Set.

Set names = new HashSet();

Dessa forma, você precisa alterar apenas uma linha se decidir usar um TreeSet .

Além disso, os methods que operam em conjuntos devem especificar parâmetros do tipo Set:

public static void print(Set s)

Então o método pode ser usado para todas as implementações definidas .

Em teoria, devemos fazer a mesma recomendação para listas vinculadas, ou seja, salvar referências LinkedList em variables ​​do tipo List. No entanto, na biblioteca Java, a interface List é comum à class ArrayList e LinkedList . Em particular, ele obtém e define methods para access random, embora esses methods sejam muito ineficientes para listas vinculadas.

Você não pode escrever código eficiente se não souber se o access random é eficiente ou não.

Isso é claramente um grave erro de design na biblioteca padrão, e não posso recomendar o uso da interface List por esse motivo.

Para ver o quão embaraçoso é esse erro, dê uma olhada no código-fonte do método binarySearch da class Collections . Esse método usa um parâmetro List, mas a pesquisa binária não faz sentido para uma linked list. O código então tenta descobrir se a lista é uma linked list e, em seguida, alterna para uma pesquisa linear!

A interface Set e a interface Map , são bem projetadas, e você deve usá-las.

Eu uso (2) se o código é o “dono” da lista. Isso é, por exemplo, verdadeiro para variables ​​somente locais. Não há razão para usar o tipo abstrato List vez de ArrayList . Outro exemplo para demonstrar a propriedade:

 public class Test { // This object is the owner of strings, so use the concrete type. private final ArrayList strings = new ArrayList<>(); // This object uses the argument but doesn't own it, so use abstract type. public void addStrings(List 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 getStrings() { return Collections.unmodifiableList(strings); } // Here we create a new list and give ownership to the caller. Use concrete type. public ArrayList getStringsCopy() { return new ArrayList<>(strings); } } 

Eu acho que as pessoas que usam (2) não conhecem o princípio da substituição de Liskov ou o princípio da inversão de dependência . Ou eles realmente precisam usar o ArrayList .

Quando você escreve List , você diz que seu object implementa apenas a interface List , mas você não especifica a que class seu object pertence.

Ao escrever ArrayList , você especifica que sua class de object é um array redimensionável.

Portanto, a primeira versão torna seu código mais flexível no futuro.

Veja os documentos do Java:

Classe ArrayList – Implementação de array redimensionável da interface List .

List Interface – Uma coleção ordenada (também conhecida como uma sequência). O usuário dessa interface tem controle preciso sobre onde na lista cada elemento é inserido.

Array – object contêiner que contém um número fixo de valores de um único tipo.

Na verdade, há ocasiões em que (2) não é apenas preferido, mas obrigatório e estou muito surpreso, que ninguém menciona isso aqui.

Serialização!

Se você tem uma class serializável e deseja que ela contenha uma lista, então você deve declarar o campo como sendo um tipo concreto e serializável como ArrayList porque a interface List não estende java.io.Serializable

Obviamente, a maioria das pessoas não precisa de serialização e esquece isso.

Um exemplo:

 public class ExampleData implements java.io.Serializable { // The following also guarantees that strings is always an ArrayList. private final ArrayList strings = new ArrayList<>(); 

(3) Coleção myCollection = new ArrayList ();

Eu estou usando isso normalmente. E somente se eu precisar de methods List, usarei List. O mesmo com ArrayList. Você sempre pode alternar para uma interface mais “estreita”, mas não pode alternar para mais “ampla”.

Dos dois seguintes:

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

Primeiro é geralmente preferido. Como você estará usando methods apenas da interface List , ele fornece a liberdade de usar alguma outra implementação de List por exemplo, LinkedList no futuro. Por isso, dissocia-lo da implementação específica. Agora há dois pontos que vale a pena mencionar:

  1. Devemos sempre programar para interface. Mais aqui .
  2. Você quase sempre acabará usando ArrayList sobre LinkedList . Mais aqui .

Eu estou querendo saber se alguém usa (2)

Sim, às vezes (leia raramente). Quando precisamos de methods que fazem parte da implementação do ArrayList mas não fazem parte da List interfaces. Por exemplo, ensureCapacity .

Além disso, com que frequência (e posso por favor obter um exemplo) a situação realmente requer o uso de (1) sobre (2)

Quase sempre você prefere a opção (1). Este é um padrão de design clássico em OOP, onde você sempre tenta dissociar seu código de uma implementação e programa específicos para a interface.

O único caso que eu sei de onde (2) pode ser melhor é quando eu uso o GWT, porque ele reduz o tamanho do aplicativo (não é minha ideia, mas a equipe do kit de ferramentas do google web diz isso). Mas para java regular rodando dentro da JVM (1) é provavelmente sempre melhor.

Lista é uma interface. Não tem methods. Quando você chama o método em uma referência de lista. Na verdade, chama o método de ArrayList em ambos os casos.

E para o futuro você pode mudar a List obj = new ArrayList<> para List obj = new LinkList<> ou outro tipo que implemente a interface List.

Alguém perguntou isso de novo (duplicado), o que me fez ir um pouco mais fundo sobre esta questão.

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

Se usarmos um visualizador de bytecode (eu usei http://asm.ow2.org/eclipse/index.html ), veremos o seguinte (apenas lista de boot e atribuição) para o nosso snippet de lista :

  L0 LINENUMBER 9 L0 NEW ArrayList DUP INVOKESPECIAL ArrayList. () : 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 

e para alist :

  L3 LINENUMBER 13 L3 NEW java/util/ArrayList DUP INVOKESPECIAL java/util/ArrayList. ()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 

A diferença é que a lista acaba chamando INVOKEINTERFACE enquanto aList chama INVOKEVIRTUAL . Codificando para a referência de plug-in do Bycode Outline,

invokeinterface é usado para invocar um método declarado dentro de uma interface Java

enquanto invokevirtual

invoca todos os methods, exceto methods de interface (que usam invokeinterface), methods estáticos (que usam invokestatic) e os poucos casos especiais tratados pelo invokespecial.

Em resumo, invokevirtual pops objectref fora da pilha enquanto para invokeinterface

o interpretador insere ‘n’ itens da pilha de operandos, onde ‘n’ é um parâmetro inteiro sem sinal de 8 bits tirado do bytecode. O primeiro desses itens é objectref, uma referência ao object cujo método está sendo chamado.

Se eu entendi isso corretamente, a diferença é basicamente como cada maneira recupera objectref .

Eu diria que 1 é o preferido, a menos que

  • você está dependendo da implementação do comportamento opcional * em ArrayList, nesse caso explicitamente usando ArrayList é mais claro
  • Você estará usando o ArrayList em uma chamada de método que requer ArrayList, possivelmente para comportamento opcional ou características de desempenho

Meu palpite é que, em 99% dos casos, você pode obter com o List, que é o preferido.

  • por exemplo removeAll ou add(null)

Interface de List tem várias classs diferentes – ArrayList e LinkedList . LinkedList é usado para criar uma coleção indexada e ArrayList – para criar listas ordenadas. Assim, você pode usar qualquer um deles em seus argumentos, mas você pode permitir que outros desenvolvedores que usam seu código, biblioteca, etc. usem tipos diferentes de listas, não apenas as que você usa, portanto, neste método

 ArrayList myMethod (ArrayList input) { // body } 

você pode usá-lo somente com ArrayList , não com LinkedList , mas você pode permitir usar qualquer uma das classs List em outros lugares onde o método está sendo usado, é apenas sua escolha, então usar uma interface pode permitir:

 List myMethod (List input) { // body } 

Neste método argumentos você pode usar qualquer uma das classs de List que você deseja usar:

 List list = new ArrayList (); list.add ("string"); myMethod (list); 

CONCLUSÃO:

Use as interfaces em todos os lugares, quando possível, não restrinja você ou outras pessoas a usar methods diferentes que eles querem usar.