Identidade e mutabilidade de objetos

8

Eu estava lendo uma proposta para tipos de valor em Java e me deparei com esta frase: "A identidade do objeto serve apenas para suportar a mutabilidade, onde o estado de um objeto pode ser alterado, mas permanece o mesmo objeto intrínseco".

Pelo que entendi (ainda que provisoriamente), a identidade do objeto é a idéia de sua variável agir como um ponteiro ou referência a um objeto localizado em outro lugar na memória (como objetos instanciados no heap em Java ou C #). Então, o que isso teria a ver com a mutabilidade de objetos? Isso implica que, por exemplo, objetos instanciados na pilha em C ++ são imutáveis? Estou tendo problemas para visualizar o link aqui.

Drazen Bjelovuk
fonte
"Isso significa que, por exemplo, objetos instanciados na pilha em C ++ são imutáveis?" Eu acho que o que torna um objeto imutável é como ele é projetado e o que ele permite, não para onde é instanciado ou sua identidade. O que você acha?
Jordan #
Existe um fórum de discussão associado a essa proposta? Eu sugeriria que um tipo de valor deveria ser considerado como um monte de variáveis ​​unidas com fita adesiva. Dada struct Point {public int x,y;}, uma declaração Point ptdeve efetivamente declarar duas variáveis ​​chamadas pt.xe pt.y. Um parâmetro de método do tipo Pointdeve ser dois intparâmetros. Sob essas regras, os únicos aspectos que exigiriam qualquer alteração no tempo de execução seriam matrizes e valores de retorno da função. Arrays contendo apenas primitivos ou apenas objetos podem ser manipulados ... #
308
... usando matrizes primitivas ou matrizes de objetos e elementos de intercalação. Matrizes contendo uma mistura de primitivos e valores poderiam ser acomodadas como uma matriz de objetos, cujo primeiro item era uma referência a uma matriz de valores. Se o número de objetos ou primitivos em uma estrutura for limitado, os retornos da função poderão ser manipulados sem alterações no tempo de execução, adicionando membros Threadpara mantê-los.
Supercat
1
@ supercat da maneira que o JCP funciona, se alguém quiser na linguagem principal e gritar alto o suficiente para que "Java esteja morto se não conseguir isso", seja incluído.
Jwenting
Você pode encontrar este artigo útil, sobre este mesmo assunto: objetos devem ser imutáveis
yegor256

Respostas:

10

Antes de abordar a identidade, vamos definir o que entendemos por igualdade um pouco mais precisamente. Dizemos que duas coisas são iguais se, e somente se, não podemos diferenciá-las (ver: Identidade dos indiscerníveis ). Isso significa que se duas coisas são iguais ou não, depende dos meios que temos para inspecioná-las.

Vamos pensar um pouco mais sobre isso em termos de programação. Vamos deixar nossos preconceitos à porta e supor que estamos trabalhando em uma linguagem desconhecida totalmente nova, onde todas as variáveis ​​e valores são imutáveis. Pela definição de cima, dois valores Ae Bsão iguais se e apenas se existem programas de NO na língua que produzem resultados diferentes quando Aé usado em lugar de B, ou vice-versa. Digamos Ae Bsomos (IEEE 754) flutuadores, e quando substituído na expressão _ + 1.0, o resultado é 1.0para ambos Ae B. Certamente Ae Bsão ambos zero. Eles são iguais? Isso depende - a linguagem fornece alguma função que me permita determinar o sinal do zero? Se não, eles são iguais; se isso acontecer, eles podem não estar.

Portanto, dois valores são iguais sempre que fornecem os mesmos resultados para todas as combinações possíveis de operações suportadas. Valores imutáveis, em particular, não produzem resultados diferentes, dependendo de quais operações foram aplicadas anteriormente a eles. Por esse motivo, não nos importamos se duas variáveis ​​apontam para duas cópias do mesmo valor ou se ambas apontam para a mesma cópia.

O que isso tem a ver com mutabilidade? Mutabilidade implica que nossa linguagem tenha alguma noção de uma célula de memória cujo conteúdo possa ser substituído. Digamos que adicionemos suporte a células de memória mutáveis ​​ao nosso idioma:

  • ref <value>cria uma nova célula de memória, diferente de todas as outras, inicializada em <value>.
  • <variable> := <value> substitui o conteúdo de uma célula de referência.
  • !<variable> retorna o valor atualmente armazenado em uma célula de referência.

Agora vamos pensar sobre o que igualdade significa para células de memória. Suponha A = ref 0e B = A. Considere este programa:

A := 1
print(!_)

Substituindo Aimpressões em branco 1e substituindo por Bimpressões 1também. Agora suponha A = ref 0e B = ref 0. Nesse caso, a substituição no programa acima será impressa 1e 0, desde agora, Ae Bapontará para células de memória distintas.

Portanto, importa para nós se duas referências apontam para a mesma célula de memória ou células de memória diferentes. Como isso importa, seria útil ter uma maneira eficiente e geral de distinguir duas referências. Nosso método atual de comparar os valores que eles mantêm e, se eles são iguais a uma mutação, é problemático por vários motivos:

  • Depende da capacidade de comparar os valores armazenados nas células de memória para obter igualdade. Igualdade não faz sentido para todos os tipos - por exemplo, geralmente não faz sentido para funções, porque não existe um método geral para determinar se duas funções desconhecidas são iguais (isso está se aventurando no território do Problema de Colocação). Portanto, dadas duas referências às células de memória que armazenam funções, não podemos comparar as funções que elas possuem para a igualdade.
  • Depende de ter algum valor que possamos atribuir a uma das duas referências. Portanto, mesmo que a igualdade faça sentido para todos os tipos no idioma, ainda precisamos acessar um valor para cada tipo que queremos comparar. E se a construção de um valor desse tipo tiver efeitos colaterais?
  • O valor de referência que usamos para alterar uma das referências deve ser diferente do valor que a célula de memória já possui; portanto, precisamos de dois valores.
  • O código para comparar referências de tipos diferentes será exatamente o mesmo, exceto pelos dois valores que usamos.
  • Precisamos fazer backup e restaurar o valor da referência que modificamos para evitar alterar o significado do programa.

Portanto, seria útil para o idioma fornecer uma operação para verificar diretamente se duas referências apontam para a mesma célula de memória mutável. Essa função é inútil para valores imutáveis; na verdade, eu diria que é absolutamente prejudicial. Se existe uma maneira de saber se dois 1s estão armazenados em lugares diferentes da memória, pode haver programas que se importam se eu passo um 1ou outro. Eu realmente não quero me preocupar se tenho "o certo 1"; a matemática já é difícil o suficiente! Portanto, fica claro que poder verificar a igualdade de memória é útil principalmente para tipos mutáveis.

Doval
fonte
5

Imagem que você tem dois objetos, por exemplo, para instâncias da classe List. Ambas as listas têm o mesmo conteúdo. Agora você está lendo as listas e calcula e produz algo com base em seu conteúdo. Importa qual lista você usa? Não porque eles têm o mesmo conteúdo.

Mas e se uma das listas for alterada? Agora faz importa o que você escolher para a leitura e outputing algo. Portanto, você precisa ser capaz de distinguir entre eles e deseja, mesmo em algumas situações, verificar se duas variáveis ​​apontam para o mesmo objeto (ou lista aqui).

Se, no entanto, você não conseguir alterar o conteúdo de uma lista, não precisará nem ter dois objetos de lista com o mesmo conteúdo, porque não poderá alterá-los. Se você deseja "alterar" o conteúdo, em vez disso, criará um novo objeto de lista com conteúdo diferente - que é como eu disse um novo objeto. Portanto, deste ponto de vista, a identidade não importa. Somente o conteúdo faz e somente o conteúdo deve ser usado para comparar duas listas.

Observe também que o compilador pode apontar para o mesmo objeto de lista, mesmo se você declarar dois objetos, porque ele só precisa armazenar seu conteúdo uma vez, pois não pode ser alterado.

valenterry
fonte
0

A identidade do objeto não existe "apenas" para suportar a mutabilidade, mas também tem outros usos [na verdade, uma instância do Objecttipo é útil apenas como um token de identidade, pois não possui atributos observáveis ​​mutáveis!]. Em vez disso, a identidade geralmente é um - característica indesejada que um objeto mutável adquirirá sempre que existirem várias referências ao objeto, elas não serão todas controladas por um único proprietário e entidades externas ao proprietário poderão - sem o conhecimento do proprietário - fazer ou ver alterações nesse objeto .

Suponha que algum campo estático Xcontenha uma referência à instância de elemento único de int[]e X [0] = 5. O campo estático Ytambém mantém um elemento em uma instância de elemento único de int[], e Y [0] = 5. Se o código já chama IdentityHashCase()em Xe Y, ou se as análises de código X==Y, ele será capaz de discernir se Xe Yidentificar a mesma instância int[]ou instâncias diferentes. Da mesma forma, se o código faz algo parecido X[0]+=1; Y[0]+=2;, o comportamento dependerá se Xe Yidentificar a mesma instância ou instâncias diferentes. Se, no entanto, o código nunca testar Xe Yexplicitamente a igualdade de referência, nem verificar seu código de hash de identidade ou fizer qualquer outra coisa "relacionada a referência", e se X[0]eY[0]nunca são modificados Xe Yserão equivalentes, independentemente de identificarem as mesmas matrizes ou matrizes diferentes.

Uma das coisas desagradáveis ​​sobre identidade em Java ou .NET é que a identidade de um objeto depende fundamentalmente do paradeiro de todas as entidades do universo que podem fazer ou ver alterações nesse objeto. Se uma referência a um objeto for exposta livremente a códigos externos, o proprietário do objeto perderá o controle sobre ele e nunca poderá recuperá-lo.

Os tipos de valor, diferentemente dos objetos, nunca podem ser observados ou modificados, exceto pelo objeto ou método em que são declarados. Portanto, um objeto que contém um campo de valor não precisa se preocupar em perder o controle sobre ele, pois é semanticamente impossível que isso aconteça. No .NET (embora não seja Java), é possível transmitir uma variável, campo, elemento da matriz ou outro local de armazenamento como um "parâmetro de referência". Fazer isso temporariamente permitirá que o método observe ou modifique o conteúdo do local de armazenamento passado pela duração de sua execução, mas qualquer "byref" passado (o termo técnico para o que é passado) será destruído antes que o método retorne, garantindo assim que o chamador mantenha controle sobre os dados nele contidos,identidade indesejada .

supercat
fonte
Em Java, Objecttem um atributo mutável muito importante, a exclusão mútua associada!
Jan Hudec
@ JanHudec: Eu diria que os bloqueios de monitor em Java e .NET são usados Objectcomo um token de identidade. Na medida em que eles são vistos como encapsulando um estado mutável em um objeto, não existe um objeto imutável. É irônico que Java às vezes seja visto como uma "linguagem de ensino", uma vez que um objetivo de design mais significativo era simplificar o tempo de execução usando um único tipo de referência; de uma perspectiva semântica, seria melhor distinguir tipos que têm uma identidade daqueles que não têm. O principal benefício dos tipos imutáveis ​​é ... #
308
... que instâncias imutáveis ​​que encapsulam o mesmo estado podem ser usadas de forma intercambiável, mas não há como um tipo garantir isso. Se o código for usado new String("Hello");como chave em um IdentityHashMap, a string se parecerá com qualquer outra string que encapsule esses caracteres a qualquer código que não conheça esse mapa, mas não seria substituível porque seu objeto subjacente teria um token de identidade que era diferente de qualquer outro objeto em todo o universo.
Supercat
Não importa se o bloqueio está tecnicamente contido ou armazenado na estrutura de dados externa; semanticamente, ele se comporta como propriedade mutável que permite distinguir a identidade do objeto. E tenho quase certeza de que vi a descrição de baixo nível do Java Object, onde claramente incluía o bloqueio. Um dos motivos da extrema ineficiência de memória do Java.
Jan Hudec
@JanHudec: Semântica, nenhum objeto pode ser mais imutável do que Object. Qualquer definição de imutabilidade em nível de classe que seria satisfeita por, por exemplo, Integerseria igualmente satisfeita por Object. Além disso, em geral, quando se diz que um objeto encapsula um estado mutável, é possível copiar significativamente esse estado de uma instância para outra. Eu não acho que haja alguma maneira de copiar o estado de bloqueio de um objeto para outro.
Supercat