UnsupportedOperationException nas interfaces da estrutura de coleções java

12

Examinando o Java Collections Framework, notei que algumas das interfaces têm o comentário (optional operation). Esses métodos permitem a implementação de classes através de um UnsupportedOperationExceptionse eles simplesmente não querem implementar esse método.

Um exemplo disso é o addAllmétodo no Set Interface.

Agora, conforme declarado nesta série de perguntas, as interfaces são um contrato definidor do que o uso pode esperar.

As interfaces são importantes porque separam o que uma classe faz e como ela faz. O contrato que define o que um cliente pode esperar deixa o desenvolvedor livre para implementá-lo da maneira que escolher, desde que cumpra o contrato.

e

Uma interface é uma descrição das ações que um objeto pode executar ... por exemplo, quando você liga um interruptor de luz, a luz acende, você não se importa como, exatamente o que faz. Na Programação Orientada a Objetos, uma Interface é uma descrição de todas as funções que um objeto deve ter para ser um "X".

e

Eu acho que a abordagem baseada em interface é significativamente melhor. Você pode zombar de suas dependências bem e tudo é basicamente menos acoplado.

Qual é o objetivo de uma interface?

O que são interfaces?

Interface + Extensão (mixin) vs Classe Base

Dado que o objetivo das interfaces é definir um contrato e tornar suas dependências fracamente acopladas, alguns métodos não prejudicam UnsupportedOperationExceptiono objetivo? Isso significa que eu não posso mais passar Sete usar addAll. Em vez disso, tenho que saber qual implementação Setfoi aprovada, para que eu possa saber se posso usar addAllou não. Isso me parece inútil.

Então, qual é o sentido UnsupportedOperationException? Ele está apenas compensando o código legado e eles precisam limpar suas interfaces? Ou tem um objetivo mais sensitivo que estou sentindo falta?

MirroredFate
fonte
Eu não sei qual JRE que você está usando, mas a minha versão Oracle 8 não define addAllem HashSet. Adia para a implementação padrão na AbstractCollectionqual certamente não é lançada UnsupportedOperationException.
@Snowman Você está certo. Eu li mal os documentos. Vou editar minha pergunta.
MirroredFate
1
Eu gosto de iniciar o Eclipse e olhar o código-fonte, pulando referências e definições de código para garantir que eu esteja certo. Contanto que o JRE esteja vinculado, src.zipele funciona muito bem. Ajuda a saber exatamente qual código o JRE está executando algumas vezes e não adiar para o JavaDoc, que pode ser um pouco detalhado.

Respostas:

12

Veja as seguintes interfaces:

Todas essas interfaces declaram métodos de mutação como opcionais. Isso documenta implicitamente o fato de que a classe Collections é capaz de retornar implementações daquelas interfaces que são imutáveis: ou seja, essas operações de mutação opcionais têm garantia de falha. No entanto, de acordo com o contrato no JavaDoc, todas as implementações dessas interfaces devem permitir as operações de leitura. Isso inclui as implementações "normais", como HashSete LinkedListassim como os invólucros imutáveis Collections.

Contraste com as interfaces da fila:

Essa interface não especifica nenhuma operação opcional: uma fila, por definição, é projetada para oferecer e pesquisar elementos de maneira FIFO. Uma fila imutável é tão útil quanto um carro sem rodas.


Uma ideia comum que surge repetidamente é ter uma hierarquia de herança que possui objetos mutáveis ​​e imutáveis. No entanto, todos estes têm desvantagens. A complexidade turva as águas sem realmente resolver o problema.

  • Um hipotético Setpode ter as operações de leitura e uma subinterface MutableSetpode ter as operações de gravação. Liskov nos diz que a MutableSetpode ser passado para qualquer coisa que precise de Set. No início, isso parece bom, mas considere um método que espera que o conjunto subjacente não seja modificado durante a leitura: seria possível que dois encadeamentos usassem o mesmo conjunto e viole a invariância do conjunto. Isso pode causar um problema, por exemplo, se um método lê um elemento do conjunto duas vezes e ele existe na primeira vez, mas não na segunda.

  • Setpoderia não ter implementações diretas, ao invés disso, ter MutableSete ImmutableSetcomo subinterfaces que são usadas para implementar classes. Isso tem o mesmo problema que acima: em algum momento da hierarquia, uma interface possui invariantes conflitantes. Um diz que "este conjunto deve ser mutável" e o outro diz "este conjunto não pode mudar".

  • Pode haver duas hierarquias completamente separadas para estruturas de dados mutáveis ​​e imutáveis. Isso adiciona uma tonelada de complexidade extra ao que acaba sendo muito pouco ganho. Isso também tem a fraqueza específica de métodos que não se preocupam com a mutabilidade (por exemplo, eu só quero iterar uma lista) agora devem suportar duas interfaces separadas. Como o Java é estaticamente digitado, isso significa métodos extras para lidar com as duas hierarquias da interface.

  • Poderíamos ter uma única interface e permitir que as implementações gerassem exceções se um método não fosse aplicável a ela. Essa é a rota que Java seguiu e faz mais sentido. O número de interfaces é reduzido ao mínimo e não há invariáveis ​​de mutabilidade porque a interface documentada não garante garantias de mutabilidade de qualquer maneira . Se uma imutabilidade invariável for necessária, use os invólucros Collections. Se um método não precisar alterar uma coleção, simplesmente não a altere. A desvantagem é que um método não pode garantir que uma coleção não seja alterada em outro segmento se for fornecida uma coleção de fora, mas isso é uma preocupação do método de chamada (ou de seu método de chamada) de qualquer maneira.


Leitura relacionada: Por que o Java 8 não inclui coleções imutáveis?

Comunidade
fonte
1
Mas se os métodos são opcionais, que lugar eles têm na interface? Não deveria haver uma interface separada contendo os métodos opcionais, por exemplo MutableCollection?
MirroredFate
Não. Não há como ter objetos mutáveis ​​e imutáveis ​​na mesma hierarquia de maneira significativa. Houve uma pergunta recente que tinha um bom diagrama mostrando a complexidade e uma explicação de por que essa é uma má ideia, mas foi excluída. Talvez alguém saiba de uma pergunta para ajudar a explicar isso, não consigo encontrar nada. Mas atualizarei minha resposta para explicar um pouco.
Essa é uma espécie de declaração ampla sobre filas imutáveis. Eu usei um apenas alguns dias atrás para resolver este problema .
Karl Bielefeldt
@ Snowman Mas isso parece fazer com que objetos mutáveis ​​e imutáveis ​​sejam opostos um ao outro. Eu acho que objetos imutáveis ​​são realmente apenas objetos sem a capacidade de sofrer mutação. Honestamente, a maneira como está agora é mais complexa e confusa, porque você precisa descobrir o que é uma implementação mutável e o que não é. Parece-me que a única diferença entre colocar todos os métodos em uma interface em vez de dividir os métodos mutáveis ​​é de clareza.
MirroredFate
@MirroredFate leu minha edição mais recente.
2

É basicamente YAGNI. Todas as coleções concretas na biblioteca padrão são mutáveis, implementando ou herdando as operações opcionais. Eles não se importam com coleções imutáveis ​​de uso geral e nem a grande maioria dos desenvolvedores de Java. Eles não criarão uma hierarquia de interface inteira apenas para coleções imutáveis ​​e não incluirão nenhuma implementação.

Por outro lado, existem alguns valores para fins especiais ou coleções "virtuais" que podem ser muito úteis como imutáveis, como conjunto vazio e nCopies . Além disso, existem coleções imutáveis ​​de terceiros (como as de Scala), que podem querer chamar código Java existente, portanto deixaram a possibilidade de coleções imutáveis ​​abertas da maneira menos disruptiva.

Karl Bielefeldt
fonte
Ok, isso faz algum sentido. Ainda parece que ele aborda o problema da direção errada, no entanto. Por que não começar definindo interfaces de coleção imutáveis ​​e depois definir as interfaces mutáveis ​​para as implementações de coleções mutáveis?
MirroredFate