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

69

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

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

Há um excelente resumo dos problemas aqui .

Havia um bom motivo para a escolha de métodos opcionais em vez da implementação das duas interfaces?

glenviewjeff
fonte
OMI, não há uma boa razão. O C ++ STL foi criado por Stepanov no início dos anos 80. Embora sua usabilidade tenha sido limitada pela estranha sintaxe do modelo C ++, é um modelo de consistência e usabilidade quando comparada às classes de coleção Java.
Kevin cline
Havia um C ++ STL 'portando' para Java. Mas era 5 vezes maior na contagem de turmas. Com essa mente, não acredito que a maior seja mais consistente e utilizável. docs.oracle.com/javase/6/docs/technotes/guides/collections/…
m3th0dman
@ m3th0dman É muito maior em Java porque Java não tem nada equivalente em potência aos modelos C ++.
kevin Cline
Talvez seja apenas um estilo estranho que desenvolvi, mas meu código tende a tratar todas as coleções como "somente leitura" (mais precisamente, apenas algo que você conta ou sobre o qual itera), exceto pelos poucos métodos que realmente criam as coleções. Provavelmente boas práticas de qualquer maneira (especialmente para simultaneidade). E os métodos opcionais que muitos reclamam nunca foram um problema real para mim. Nem os pesadelos dos Genéricos com "super" e "estende" jamais foram (muito) um problema. Apenas querendo saber se outros usam essa prática geral?
User949300

Respostas:

28

O FAQ fornece a resposta. Em resumo, 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 segmentação) e assim por diante para cada conjunto possível de métodos de opção implementados.

catraca arrepiante
fonte
6
Tudo isso seria evitado se Java tivesse uma constpalavra - chave como C ++.
Etienne de Martel
@ Etienne: Melhor seria metaclasses como Python. Em seguida, você pode criar programaticamente o número combinatório de interfaces. O problema com const é que ele só dá-lhe uma ou duas dimensões: vector<int>, const vector<int>, vector<const int>, const vector<const int>. Até aí tudo bem, mas em seguida, tentar gráficos implementação, e que pretende tornar a estrutura gráfico constante, mas atribui o nó modificável, etc.
Neil G
2
@ Etienne, mais uma razão para incluir o "Learn Scala" na minha lista de tarefas!
Glenviewjeff
2
O que eu não entendo é: por que eles não criaram um canmétodo que testaria se uma operação é possível? Manteria a interface simples e rápida.
Mehrdad 21/05
3
@ Etienne de Martel Bobagem. Como isso ajudaria na explosão combinatória?
Tom Hawtin - defina
10

Parece-me que o Interface Segregation Principlenão era tão bem explorado naquela época 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 aquelas que você não precisa) era popular antes de o SOLID e o ISP se tornarem o padrão de fato para o código de qualidade.

Wayne Molina
fonte
2
Por que um voto negativo? Alguém não gosta do ISP?
Wayne Molina
Também é importante notar que em estruturas que suportam variação, há uma enorme vantagem em segregar os aspectos de uma interface que podem ser covariantes ou contravariantes daqueles que são fundamentalmente invariantes. Mesmo sem esse suporte, em estruturas que não usam apagamento de tipo, seria importante segregar aspectos de uma interface que são independentes de tipo (por exemplo, permitir que se obtenha Countuma coleção sem ter que se preocupar com os tipos de itens que ela contém), mas em estruturas baseadas em apagamento de tipo como Java, esse não é um problema.
Supercat
4

Enquanto algumas pessoas podem odiar "métodos opcionais", em muitos casos podem oferecer semântica melhor do que interfaces altamente segregadas. Entre outras coisas, eles permitem as possibilidades de que um objeto possa adquirir habilidades ou características durante sua vida útil, ou que um objeto (especialmente um objeto de invólucro) talvez não saiba quando é construído quais habilidades exatas devem ser relatadas.

Embora eu dificilmente chame as classes de coleções Java de modelos de bom design, sugiro que uma boa estrutura de coleções inclua em sua base um grande número de métodos opcionais, além de maneiras 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 ocultar acidentalmente as habilidades 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 possam suportar, ou então alguns wrappers podem ser inutilizados em algumas situações.

Por exemplo, se uma coleção suportar escrever um item por índice ou anexar itens no final, mas não suportar a inserção de itens no meio, o código que deseja encapsulá-lo no wrapper que registraria todas as ações executadas nela precisaria de uma versão do wrapper de log que fornecia a combinação exata de habilidades suportadas ou, se não houvesse nenhum disponível, seria necessário usar um wrapper que suporta anexar ou gravar por índice, mas não ambos. Se, no entanto, uma interface de coleção unificada fornecesse todos os três métodos como "opcional", mas incluísse métodos para indicar quais métodos opcionais seriam utilizáveis, uma única classe de wrapper poderia manipular coleções que implementam qualquer combinação de recursos. Quando perguntado sobre quais recursos ele suporta, um wrapper pode simplesmente relatar o que a coleção encapsulada suportar.

Observe que a existência de "habilidades opcionais" pode, em alguns casos, permitir que coleções agregadas implementem certas 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 concatenatemétodo tenha sido usado para formar uma coleção composta de duas outras, a primeira sendo uma ArrayList com 1.000.000 elementos e a última uma coleção de vinte elementos, que só poderia ser iterada desde o início. Se a coleção composta fosse solicitada para o 1.000.013 ° elemento (índice 1.000.012), ele poderia perguntar à ArrayList quantos itens continha (ou seja, 1.000.000), subtrair isso do índice solicitado (com 12), ler e pular doze elementos do segundo coleção e, em seguida, retorne 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, solicitar à coleção composta pelo 1.000.013º item ainda seria muito mais rápido do que ler 1.000.013 itens individualmente e ignorar todos, exceto o último 1.

supercat
fonte
@kevincline: Você não concorda com o texto citado? Eu consideraria o design dos meios pelos quais as implementações de interface podem descrever suas características e habilidades essenciais como um dos aspectos mais importantes do design de interface, mesmo que muitas vezes não receba muita atenção. Se diferentes implementações de uma interface tiverem maneiras ótimas diferentes de realizar determinadas tarefas comuns, deve haver um meio para os clientes que se preocupam com o desempenho selecionarem a melhor abordagem nos cenários em que isso importaria.
Supercat
11
desculpe, comentário incompleto. Eu estava prestes a dizer que "'maneiras de perguntar sobre uma coleção' ..." move o que deveria ser uma verificação em tempo de compilação para o tempo de execução.
kevin Cline
@kevincline: nos casos em que um cliente precisa ter uma determinada capacidade e não pode viver sem ela, as verificações em tempo de compilação podem ser úteis. Por outro lado, existem muitas situações em que as coleções podem ter habilidades além daquelas que podem ter garantia de tempo de compilação. Em alguns casos, pode fazer sentido ter uma interface derivada cujo contrato especifica que todas as implementações legítimas da interface derivada devem oferecer suporte a métodos específicos opcionais na interface base, mas nos casos em que o código poderá manipular algo, independentemente de ser ou não ele tem algum recurso ... #
1113
... mas se beneficiará do recurso existente, é melhor que o código pergunte ao objeto se ele suporta o recurso do que perguntar ao sistema de tipos se o tipo do objeto promete dar suporte ao recurso. Se uma interface "específica" específica for amplamente usada, pode-se incluir um AsXXXmétodo na interface base que retornará o objeto sobre o qual é invocado se implementar essa interface, retornar um objeto wrapper que suporte essa interface, se possível, ou lançar uma exceção se não. Por exemplo, uma ImmutableCollectioninterface pode exigir, por contrato ... #
3013
2
Há um problema com sua proposta: um padrão "pergunte e faça" tem um problema de simultaneidade se os recursos de um objeto puderem mudar durante sua vida útil. (Se você está indo ter que arriscar uma exceção de qualquer maneira, você pode muito bem não se preocupe em perguntar ...)
jhominal
-1

Eu atribuiria isso aos desenvolvedores originais, simplesmente não sabendo melhor naquela época. Percorremos um longo caminho no design de OO desde 1998, quando o Java 2 e o Collections foram lançados. O que parece um projeto ruim óbvio agora não era tão óbvio nos primeiros dias da OOP.

Mas isso pode ter sido feito para impedir a transmissão extra. Se fosse uma segunda interface, você teria que converter suas instâncias de coleções para chamar esses métodos opcionais, que também são feios. Agora, você pega uma UnsupportedOperationException imediatamente e corrige seu código. Mas se houvesse duas interfaces, você teria que usar instanceof e casting em todo o lugar. Talvez eles considerassem isso uma troca válida. Também no início do Java 2 dias, a instância de era bastante desaprovada devido ao seu desempenho lento, eles poderiam estar tentando impedir o uso excessivo dele.

É claro que tudo isso é especulação selvagem, duvido que possamos responder isso com certeza, a menos que um dos arquitetos das coleções originais entre em cena.

Jberg
fonte
7
Qual elenco? Se um método retorna um Collectione não um MutableCollection, é um sinal claro de que ele não deve ser modificado. Não sei por que alguém precisaria lançá-los. Ter interfaces separadas significa que você obterá esse tipo de erro no tempo de compilação, em vez de obter uma exceção no tempo de execução. Quanto mais cedo você receber o erro, melhor.
Etienne de Martel
11
Como isso é muito menos flexível, um dos maiores benefícios para a biblioteca de coleções é que você pode retornar as interfaces de alto nível em todos os lugares e não se preocupar com a implementação real. Se você estiver usando duas interfaces, agora está mais acoplado. Na maioria dos casos, você simplesmente deseja retornar List, e não ImmutableList, porque geralmente deseja deixar isso para a classe de chamada para determinar.
Jberg 20/05
6
Se eu lhe der uma coleção somente leitura, é porque não quero que ela seja modificada. Lançar uma exceção e confiar na documentação parece um remendo. No C ++, eu simplesmente retornaria um constobjeto, informando instantaneamente ao usuário que o objeto não pode ser modificado.
Etienne de Martel
7
1998 não era "os primeiros dias do design de OO".
quant_dev 21/05