Por que as coleções Java foram implementadas com “métodos opcionais” na interface?

69

Durante minha primeira implementação estendendo a estrutura de coleta Java, fiquei surpreso ao ver que a interface de coleta contém métodos declarados como opcionais. O implementador deverá lançar UnsupportedOperationExceptions se não for suportado. Isso imediatamente me pareceu uma má escolha de design da API.

Depois de ler muito do excelente livro "Effective Java", de Joshua Bloch, e depois de saber que ele pode ser responsável por essas decisões, ele não pareceu se harmonizar com os princípios adotados no livro. Eu acho que declarar duas interfaces: Collection e MutableCollection, que estende a coleção com os métodos "opcionais", levaria a um código de cliente muito mais sustentável.

Há um excelente resumo dos problemas aqui .

Houve uma boa razão pela qual os métodos opcionais foram escolhidos em vez da implementação das duas interfaces?

    
por glenviewjeff 20.05.2011 / 16:18
fonte

4 respostas

28

A FAQ fornece a resposta. Em suma, eles viram uma potencial explosão combinatória de interfaces necessárias com visão modificável e não modificável, somente exclusão, somente adição, comprimento fixo, imutável (para encadeamento) e assim por diante para cada possível conjunto de métodos de opção implementados.

    
por 20.05.2011 / 17:09
fonte
10

Parece-me que o Interface Segregation Principle não era tão bem explorado como é agora; essa maneira de fazer as coisas (ou seja, sua interface inclui todas as operações possíveis e você tem métodos "degenerados" que lançam exceções para aqueles que você não precisa) era popular antes que o SOLID eo ISP se tornassem o padrão de fato para código de qualidade. / p>     

por 20.05.2011 / 17:20
fonte
4

Embora algumas pessoas possam detestar "métodos opcionais", elas podem, em muitos casos, oferecer uma semântica melhor do que as interfaces altamente segregadas. Entre outras coisas, elas permitem as possibilidades de que um objeto possa ganhar habilidades ou características em seu tempo de vida, ou que um objeto (especialmente um objeto wrapper) possa não saber quando é construído quais habilidades exatas devem reportar.

Embora eu dificilmente chame os exemplos de bom design de classes de coleções Java, sugiro que uma boa estrutura de coleções inclua em sua fundação um grande número de métodos opcionais juntamente com formas de perguntar a uma coleção sobre suas características e habilidades . Esse design permitirá que uma única classe de wrapper seja usada com uma grande variedade de coleções sem habilidades de obscurecimento acidental que a coleção subjacente possa possuir. Se os métodos não fossem opcionais, seria necessário ter uma classe de wrapper diferente para cada combinação de recursos que as coleções pudessem suportar ou que alguns wrappers ficassem inutilizáveis em algumas situações.

Por exemplo, se uma coleção suporta escrever um item por índice, ou acrescentar itens no final, mas não suporta inserir itens no meio, então o código que deseja encapsulá-lo no wrapper que registraria todas as ações executadas nele precisa de uma versão do wrapper de logging que forneça a combinação exata de habilidades suportadas ou, se nenhuma estiver disponível, teria que usar um wrapper que suportasse append ou write-by-index, mas não ambos. Se, no entanto, uma interface de coleta unificada fornecesse todos os três métodos como "opcionais", mas incluísse métodos para indicar quais dos métodos opcionais seriam utilizáveis, então uma única classe de invólucro poderia manipular coleções que implementassem qualquer combinação de recursos. Quando perguntado sobre quais recursos ele suporta, um wrapper pode simplesmente relatar o que a coleção encapsulada suporta.

Observe que a existência de "habilidades opcionais" pode, em alguns casos, permitir que coleções agregadas implementem determinadas funções de maneiras muito mais eficientes do que seria possível se as habilidades fossem definidas pela existência de implementações. Por exemplo, suponha que um método concatenate foi usado para formar uma coleção composta de dois outros, o primeiro dos quais passou a ser uma ArrayList com 1.000.000 elementos e o último dos quais era uma coleção de vinte elementos que só poderia ser iterada de o começo. Se a coleção composta fosse solicitada para o elemento 1.000.013 (índice 1.000.012), ele poderia perguntar à ArrayList quantos itens ela continha (ou seja, 1.000.000), subtrair essa do índice requisitado (rendendo 12), ler e pular doze elementos da segunda. coleção e, em seguida, retornar o próximo elemento.

Em tal situação, mesmo que a coleção composta não tivesse uma maneira instantânea de retornar um item por índice, pedir a coleção composta pelo item 1.000.013 ainda seria muito mais rápido do que ler 1.000.013 itens individualmente e ignorar todos os itens. mas o último.

    
por 04.10.2014 / 23:10
fonte
-1

Eu atribuí-lo aos desenvolvedores originais simplesmente não sabendo melhor naquela época. Nós já percorremos um longo caminho no design OO desde 1998, quando o Java 2 e o Collections foram lançados pela primeira vez. O que parece ser um mau design óbvio agora não era tão óbvio nos primeiros dias da OOP.

Mas isso pode ter sido feito para evitar o vazamento extra. Se fosse uma segunda interface, você teria que converter suas instâncias de coleções para chamar esses métodos opcionais, o que também é um pouco feio. Como é agora, você pegaria uma UnsupportedOperationException imediatamente e consertaria seu código. Mas se houvesse duas interfaces, você precisaria usar instanceof e casting em todo o lugar. Talvez eles consideraram que uma troca válida. Também nos primórdios do Java 2 dias a instância foi strongmente desaprovada devido ao seu desempenho lento, eles podem ter tentado evitar o uso excessivo dela.

Claro que isso é tudo especulação selvagem, eu duvido que poderíamos responder isso com certeza a menos que um dos arquitetos de coleções originais coincide.

    
por 20.05.2011 / 16:40
fonte