Por que a comparação do valor da string do operador == não chegou ao Java?


50

Todo programador Java competente sabe que você precisa usar String.equals () para comparar uma sequência, em vez de == porque == verifica a igualdade de referência.

Quando estou lidando com seqüências de caracteres, na maioria das vezes estou verificando a igualdade de valor em vez de referenciar a igualdade. Parece-me que seria mais intuitivo se o idioma permitisse a comparação dos valores de string usando apenas ==.

Como comparação, o operador de C # == verifica a igualdade de valor da string s. E se você realmente precisou verificar a igualdade de referência, pode usar String.ReferenceEquals.

Outro ponto importante é que as Strings são imutáveis, portanto, não há danos a serem causados ​​ao permitir esse recurso.

Existe alguma razão específica para isso não ser implementado em Java?


12
Você pode olhar para Scala, onde ==é a igualdade de objetos e eqé a igualdade de referência ( ofps.oreilly.com/titles/9780596155957/… ).
Giorgio

Assim como uma nota, e isso não pode ajudá-lo, mas, tanto quanto me lembro, você pode comparar strings literais com um '=='
Kgrover

10
@Kgrover: você pode, mas isso é apenas um subproduto conveniente da igualdade de referência e como o Java otimiza agressivamente os literais de correspondência de strings em referências ao mesmo objeto. Em outras palavras, funciona, mas pelas razões erradas.
tdammers

11
@aviv o ==operador mapeia apenas Equalsse o ==operador foi implementado dessa maneira. O comportamento padrão para ==é o mesmo que ReferenceEquals(na verdade, ReferenceEqualsé definida como a versão de objeto de ==)
Patrick Huizinga

3
É uma conseqüência das decisões de design que fazem muito sentido em muitos outros cenários. Mas como você parece saber disso e está fazendo isso de qualquer maneira, sinto-me compelido a responder à sua pergunta: por que tem que haver um caso de uso para comparação de referência de String?
Jacob Raihle

Respostas:


90

Eu acho que é apenas consistência, ou "princípio de menor espanto". String é um objeto, portanto, seria surpreendente se ele fosse tratado de maneira diferente de outros objetos.

Na época em que o Java foi lançado (~ 1995), apenas ter algo parecido Stringera um luxo total para a maioria dos programadores que estavam acostumados a representar strings como matrizes com terminação nula. StringO comportamento de agora é o que era naquela época, e isso é bom; alterar sutilmente o comportamento posteriormente pode ter efeitos surpreendentes e indesejados nos programas de trabalho.

Como uma observação lateral, você pode usar String.intern()para obter uma representação canônica (interna) da sequência, após a qual as comparações podem ser feitas ==. A internação leva algum tempo, mas depois disso, as comparações serão muito rápidas.

Além disso: ao contrário de algumas respostas sugeridas, não se trata de oferecer suporte à sobrecarga do operador . O +operador (concatenação) funciona em Strings, mesmo que o Java não suporte a sobrecarga do operador; é simplesmente tratado como um caso especial no compilador, resolvendo para StringBuilder.append(). Da mesma forma, ==poderia ter sido tratado como um caso especial.

Então, por que surpreender com um caso especial, +mas não com ==? Porque +simplesmente não é compilado quando aplicado a Stringobjetos que não são objetos, o que é rapidamente aparente. O comportamento diferente de ==seria muito menos aparente e, portanto, muito mais surpreendente quando atingir você.


8
Casos especiais acrescentam espanto.
Blrfl

17
As cordas eram um luxo em 1995? Realmente?? Veja o histórico das linguagens de computador. O número de idiomas que tinham algum tipo de string na época superaria em muito o número dos que não possuíam. Quantos idiomas além de C e seus descendentes usavam matrizes terminadas nulas?
precisa

14
@ WarrenT: Claro, algumas (se não a maioria) das línguas tinham algum tipo de string, mas as strings coletadas por lixo com capacidade para Unicode eram uma novidade em 1995, eu acho. Por exemplo, o Python introduziu seqüências de caracteres Unicode com a versão 2.0, ano de 2000. A escolha da imutabilidade também era uma escolha controversa na época.
Joonas Pulakka

3
@JoonasPulakka Então talvez você deva editar sua resposta para dizer isso. Porque, como está, a parte do "luxo total" da sua resposta está completamente errada.
precisa

11
A internação tem um custo: você recebe uma sequência que nunca será desalocada. (Bem, a não ser que você use seu próprio motor de Internar que você pode jogar fora.)
Donal Fellows

32

James Gosling , o criador do Java, explicou isso em julho de 2000 :

Eu deixei de fora a sobrecarga do operador como uma opção bastante pessoal, porque eu tinha visto muitas pessoas abusarem dela em C ++. Nos últimos cinco a seis anos, passei muito tempo pesquisando pessoas sobre a sobrecarga de operadores e é realmente fascinante, porque você divide a comunidade em três partes: provavelmente cerca de 20 a 30% da população considera a sobrecarga de operadores como a desova do diabo; alguém fez algo com sobrecarga de operador que realmente os marcou, porque eles usaram like + para inserção de lista e isso torna a vida muito, muito confusa. Muito desse problema decorre do fato de haver apenas meia dúzia de operadores que você pode sobrecarregar de maneira sensata e, no entanto, existem milhares ou milhões de operadores que as pessoas gostariam de definir - então você precisa escolher,


50
Ah, sim, o velho "deixa de embotar a ferramenta pontiaguda para que os imbecis não se machucem" desculpa.
Blrfl

22
@ Blrfl: Se uma ferramenta cria mais problemas do que resolve, não é uma boa ferramenta. Obviamente, decidir se esse é o caso da sobrecarga do operador pode se transformar em uma discussão muito longa.
Giorgio

15
-1. Isso não responde à pergunta. Java faz tem sobrecarga de operadores. O ==operador está sobrecarregado para objetos e primitivas. O +operador é sobrecarregado para byte, short, int, long, float, double, Stringe, provavelmente, um par de outros que eu esqueci. Teria sido perfeitamente possível sobrecarga ==para Stringbem.
Jörg W Mittag

10
@ Jorg - não, não. A sobrecarga do operador é impossível de definir no nível do usuário. De fato, há alguns casos especiais em que o compilador, mas que dificilmente se qualifica
AZ01

9
@Blrfl: Eu não me importo com as imbecis se machucando. É quando eles acidentalmente apontam meus olhos que eu fico irritado.
Jonas19

9

Consistência no idioma. Ter um operador que age de maneira diferente pode ser surpreendente para o programador. Java não permite que os usuários sobrecarregem os operadores - portanto, igualdade de referência é o único significado razoável ==entre objetos.

Dentro do Java:

  • Entre tipos numéricos, ==compara igualdade numérica
  • Entre tipos booleanos, ==compara igualdade booleana
  • Entre objetos, ==compara a identidade de referência
    • Use .equals(Object o)para comparar valores

É isso aí. Regra simples e simples para identificar o que você deseja. Tudo isso é coberto na seção 15.21 do JLS . Compreende três subseções fáceis de entender, implementar e raciocinar.

Depois de permitir a sobrecarga== , o comportamento exato não é algo que você pode procurar no JLS, colocar um dedo em um item específico e dizer "é assim que funciona", o código pode se tornar difícil de raciocinar. O comportamento exato de ==pode ser surpreendente para um usuário. Toda vez que você o vê, precisa voltar e verificar o que realmente significa.

Como o Java não permite sobrecarregar os operadores, é necessário ter um teste de igualdade de valor do qual você possa substituir a definição básica. Assim, foi determinado por essas opções de design. ==em Java testa numérico para tipos numéricos, igualdade booleana para tipos booleanos e igualdade de referência para todo o resto (que pode substituir .equals(Object o)para fazer o que quiserem para igualdade de valor).

Não se trata de "existe um caso de uso para uma conseqüência específica dessa decisão de design", mas "é uma decisão de design para facilitar essas outras coisas, é uma consequência disso".

String interning , é um exemplo disso. De acordo com o JLS 3.10.5 , todos os literais de cadeia de caracteres são internados. Outras strings são internadas se alguém as invocar .intern(). Isso "foo" == "foo"é uma consequência das decisões de design tomadas para minimizar o espaço ocupado pela memória ocupado pelos literais String. Além disso, a internação de strings é algo que está no nível da JVM que tem um pouco de exposição ao usuário, mas na grande maioria dos casos, não deve ser algo que preocupa o programador (e os casos de uso para programadores não eram algo que estava no topo da lista para os designers ao considerar esse recurso).

As pessoas apontam isso +e +=estão sobrecarregadas com o String. No entanto, isso não é aqui nem ali. Continua sendo o caso que, se ==tiver um significado de igualdade de valor para String (e apenas String), seria necessário um método diferente (que só existe em String) para a igualdade de referência. Além disso, isso desnecessariamente complicaria os métodos que levam o Object e esperam ==se comportar de uma maneira e .equals()se comportar de outra, exigindo que os usuários criem casos especiais de todos esses métodos para String.

O contrato consistente para ==on Objects é que ele é apenas igualdade de referência e .equals(Object o)existe para todos os objetos que devem testar a igualdade de valor. Complicar isso complica muitas coisas.


obrigado por reservar um tempo para responder. Essa seria uma ótima resposta para as outras perguntas às quais vinculei. Infelizmente, isso não é adequado para esta pergunta. Vou atualizar o OP com esclarecimentos com base nos comentários. Estou procurando mais os casos de uso em que um usuário de linguagem gostaria de ter os falsos negativos ao comparar seqüências de caracteres. A linguagem fornece esse recurso como consistência. Agora, gostaríamos de dar um passo adiante. Talvez pensando nisso pelo designer de novos idiomas, é necessário? (infelizmente, não lang-design.SE)
Anonsage

3
@ Anonsage não é um falso negativo. Eles não são o mesmo objeto. Isso é tudo o que está dizendo. Devo também salientar que no Java 8, new String("foo") == new String("foo")pode ser verdade (consulte Desduplicação de String ).

11
Quanto ao design do idioma, o CS.SE anuncia que ele pode estar no tópico de lá.

ooh obrigado! Vou postar minhas futuras perguntas sobre design de idiomas aqui. :) E, sim, infelizmente 'falso-negativo' não é a maneira mais precisa de descrever minha pergunta e o que estou procurando. Preciso escrever mais palavras para que as pessoas não precisem adivinhar o que sou. tentando dizer.
Anonsage

2
"Consistência no idioma" também ajuda com genéricos
Brendan

2

Java não suporta sobrecarga de operador, o que significa que ==se aplica apenas a tipos ou referências primitivas. Qualquer outra coisa requer a invocação de um método. Por que os designers fizeram isso é uma pergunta que apenas eles podem responder. Se eu tivesse que adivinhar, provavelmente é porque a sobrecarga do operador traz complexidade que eles não estavam interessados ​​em adicionar.

Eu não sou especialista em C #, mas os designers dessa linguagem parecem configurá-la de tal maneira que todo primitivo é um structe todo structé um objeto. Como o C # permite a sobrecarga do operador, esse arranjo facilita muito a qualquer classe, e não apenas String, a trabalhar da maneira "esperada" com qualquer operador. C ++ permite a mesma coisa.


11
"Java não suporta sobrecarga de operador, o que significa == se aplica apenas a tipos ou referências primitivas. Qualquer outra coisa requer a invocação de um método.": Pode-se acrescentar que, se ==quisesse igualdade de string, precisaríamos de outra notação para igualdade de referência.
Giorgio

@Giorgio: Exatamente. Veja meu comentário sobre a resposta de Gilad Naaman.
Blrfl 02/04

Embora isso possa ser resolvido por um método estático que compara as referências de dois objetos (ou um operador). Como em C #, por exemplo.
Gilad Naaman

@ GiladNaaman: Esse seria um jogo de soma zero, porque causa o problema oposto ao que o Java tem agora: a igualdade estaria em um operador e você teria que invocar um método para comparar as referências. Além disso, você teria que impor o requisito de que todas as classes implementem algo que possa ser vinculado ==. Isso adiciona efetivamente a sobrecarga do operador, o que teria implicações tremendas na maneira como o Java é implementado.
Blrfl

11
@Blrfl: Na verdade não. Sempre haverá uma maneira definida de comparar reference ( ClassName.ReferenceEquals(a,b)) e um ==operador e Equalsmétodo padrão apontados para ambos ReferenceEquals.
Gilad Naaman 02/02

2

Isso foi diferenciado em outros idiomas.

No Object Pascal (Delphi / Free Pascal) e C #, o operador de igualdade é definido para comparar valores, não referências, ao operar em strings.

Particularmente em Pascal, string é um tipo primitivo (uma das coisas que eu realmente amo em Pascal, obter NullreferenceException apenas por causa de uma string não inicializada é simplesmente irritante) e ter semântica de copiar na gravação, tornando (na maioria das vezes) operações de string muito barato (em outras palavras, só é perceptível quando você começa a concatenar cadeias de vários megabytes).

Portanto, é uma decisão de design de linguagem para Java. Quando eles projetaram a linguagem, seguiram o caminho C ++ (como Std :: String), de modo que as strings são objetos, o que é um truque para compensar C sem um tipo de string real, em vez de torná-las primitivas (como são).

Portanto, por um motivo, posso apenas especular que eles facilitaram o processo e não codificaram o operador, fazendo uma exceção no compilador para seqüências de caracteres.


Como isso responde à pergunta?
mosquito

Veja a última frase (que eu separei em um parágrafo apropriado em uma edição).
Fabricio Araujo

11
IMHO, Stringdeveria ter sido um tipo primitivo em Java. Diferente de outros tipos, o compilador precisa conhecer String; além disso, as operações nele serão suficientemente comuns para que, para muitos tipos de aplicativos, elas possam representar um gargalo de desempenho (que poderia ser facilitado pelo suporte nativo). Um típico string[minúsculo] teria um objeto alocado no heap para manter seu conteúdo, mas nenhuma referência "normal" a esse objeto existiria em qualquer lugar; poderia, portanto, ser um indireto único, Char[]ou Byte[]melhor, do que ter que ser Char[]indireto através de outro objeto.
23714

1

Em Java, não há sobrecarga de operador, e é por isso que os operadores de comparação são sobrecarregados apenas para os tipos primitivos.

A classe 'String' não é uma primitiva, portanto, não possui uma sobrecarga para '==' e usa o padrão de comparar o endereço do objeto na memória do computador.

Não tenho certeza, mas acho que no Java 7 ou 8 o oracle fez uma exceção no compilador para reconhecer str1 == str2comostr1.equals(str2)


"Não tenho certeza, mas acho que no Java 7 ou 8 o oracle fez uma exceção no compilador ao reconhecer str1 == str2 como str1.equals (str2)": não ficaria surpreso: o Oracle parece menos preocupado com minimalismo do que a Sun era.
Giorgio

2
Se for verdade, é um truque muito feio, pois significa que agora existe uma classe em que a linguagem trata diferente de todas as outras e quebra o código que compara as referências. : - @
Blrfl

11
@WillihamTotland: Considere o caso oposto. Atualmente, se eu criar duas cordas, s1e s2e dar-lhes o mesmo conteúdo, eles passam a igualdade ( s1.equals(s2)comparação), mas não a mesma referência ( ==comparação), porque eles são dois objetos diferentes. Alterar a semântica de ==para significar igualdade causaria s1 == s2avaliar trueonde costumava avaliar false.
Blrfl

2
@Brlfl: Embora isso seja verdade, isso soa como uma coisa excepcionalmente ruim de se confiar, pois as strings são objetos imutáveis ​​e internáveis.
Williham Totland 02/04

2
@Giorgio: "O Java 7 ou 8 oracle fez uma exceção no compilador ao reconhecer str1 == str2 como str1.equals (str2)" Não, a Java Language Specification diz: Operadores de igualdade de referência : "Se os operandos de um operador de igualdade são do tipo de referência ou do tipo nulo, a operação é igualdade de objetos ". Isso é tudo, pessoal. Também não encontrei nada novo sobre isso no rascunho inicial do Java 8 .
David Tonhofer

0

O Java parece ter sido projetado para manter uma regra fundamental de que o ==operador deve ser legal a qualquer momento em que um operando possa ser convertido no tipo do outro e deve comparar o resultado dessa conversão com o operando não convertido.

Esta regra dificilmente é exclusiva do Java, mas tem alguns efeitos de longo alcance (e lamentável a IMHO) no design de outros aspectos da linguagem relacionados ao tipo. Teria sido mais claro especificar os comportamentos ==em relação a combinações particulares de tipos de operandos e proibir combinações dos tipos X e Y onde x1==y1e x2==y1não implicariam x1==x2, mas as línguas raramente fazem isso [sob essa filosofia, double1 == long1teriam que indicar se double1não é uma representação exata de long1, ou se recusa a compilar;int1==Integer1deve ser proibido, mas deve haver um meio conveniente e eficiente de não arremessar se um objeto é um número inteiro em caixa com valor específico (a comparação com algo que não é um número inteiro em caixa deve simplesmente retornar false)].

Com relação à aplicação do ==operador às seqüências de caracteres, se o Java proibisse comparações diretas entre operandos do tipo Stringe Object, poderia ter evitado surpresas no comportamento de ==, mas não há comportamento que possa ser implementado para essas comparações que não seriam surpreendentes. Ter duas referências de string mantidas no tipo Objectse comporta de maneira diferente das referências mantidas no tipo Stringteria sido muito menos surpreendente do que ter um desses comportamentos diferentes do de uma comparação legal de tipo misto. Se String1==Object1for legal, isso implicaria que o único caminho para os comportamentos String1==String2e Object1==Object2para corresponder String1==Object1seria que eles se correspondessem.


Devo estar faltando alguma coisa, mas o IMHO ==em objetos deve simplesmente chamar igual a (nulo-seguro) igual e outra coisa (por exemplo, ===ou System.identityEqual) deve ser usada para a comparação de identidade. A mistura de primitivos e objetos seria inicialmente proibida (não havia autobox antes da 1.5) e, em seguida, alguma regra simples poderia ser encontrada (por exemplo, unbox com segurança nula, depois converter e comparar).
Maaartinus 17/11/2014

@maaartinus: Um bom design de linguagem deve usar operadores de igualdade separados para igualdade de valor e referência. Embora eu concorde que, conceitualmente, seria possível um int==Integeroperador retornar falsese Integernulo e, caso contrário, comparar valores, essa abordagem teria sido diferente do comportamento de ==todas as outras circunstâncias, em que coagiu incondicionalmente ambos os operandos ao mesmo tipo antes comparando-os. Pessoalmente, eu me pergunto se auto-unboxing foi posto em prática em um esforço para permitir que int==Integerpara ter um comportamento que não era absurda ...
supercat

... desde que autoboxing inte fazer uma comparação de referência teria sido bobo [mas nem sempre falhava]. Caso contrário, não vejo razão para permitir uma conversão implícita que possa falhar com um NPE.
Supercat

Eu acho que minha ideia é consistente. Apenas lembre-se de que, no mundo melhor, ==não tem nada a ver identityEquals. +++ "operadores de igualdade separados para igualdade de valor e referência" - mas quais? Eu consideraria tanto primitivo ==e equalscomo fazê valor comparação no sentido de que equalsolha para o valor da referência. +++ Quando isso ==significa equals, então int==IntegerDEVE fazer autoboxing e comparar as referências usando valores nulos e seguros. +++ Receio que minha idéia não seja realmente minha, mas apenas o que Kotlin faz.
Maaartinus

@maaartinus: Se ==nunca testou a igualdade de referência, seria sensato realizar um teste de igualdade de valores com segurança nula. O fato de que ele faz igualdade de referência de teste, no entanto, limita severamente como ele pode lidar com comparações de referência / valor mistos sem inconsistência. Observe também que Java é fixo na noção de que os operadores promovem ambos os operandos para o mesmo tipo, em vez de gerar comportamentos especiais com base nas combinações de tipos envolvidos. Por exemplo, 16777217==16777216.0fretornos trueporque ele executa uma conversão com perdas do primeiro operando a float, enquanto um ...
supercat

0

Em geral, há boas razões para querer testar se duas referências de objeto apontam para o mesmo objeto. Já tive muitas vezes que escrevi

Address oldAddress;
Address newAddress;
... populate values ...
if (oldAddress==newAddress)
... etc ...

Eu posso ou não ter uma função igual nesses casos. Se sim, a função equals pode comparar todo o conteúdo de ambos os objetos. Frequentemente, apenas compara algum identificador. "A e B são referências ao mesmo objeto" e "A e B são dois objetos diferentes com o mesmo conteúdo" são, obviamente, duas idéias muito diferentes.

Provavelmente é verdade que, para objetos imutáveis, como Strings, isso é menos problemático. Com objetos imutáveis, tendemos a pensar no objeto e no valor como sendo a mesma coisa. Bem, quando digo "nós", quero dizer "eu", pelo menos.

Integer three=new Integer(3);
Integer triangle=new Integer(3);
if (three==triangle) ...

Claro que isso retorna falso, mas posso ver alguém pensando que deveria ser verdade.

Mas quando você diz que == compara alças de referência e não o conteúdo de Objetos em geral, criar um caso especial para Strings seria potencialmente confuso. Como alguém aqui disse, e se você quisesse comparar as alças de dois objetos String? Haveria alguma função especial para fazê-lo apenas para Strings?

E sobre ...

Object x=new String("foo");
Object y=new String("foo");
if (x==y) ...

Isso é falso porque são dois objetos diferentes ou verdadeiro porque são Strings cujo conteúdo é igual?

Então, sim, eu entendo como os programadores ficam confusos com isso. Eu já fiz isso, quero dizer write if myString == "foo" quando eu quis dizer if myString.equals ("foo"). Mas, além de redesenhar o significado do operador == para todos os objetos, não vejo como resolvê-lo.


Observe que outras linguagens modernas na JVM, como Scala, costumam ==significar "seqüências iguais".
Andres F.Mar

@AndresF. (encolher de ombros) Em Java, "<" significa "menor que", enquanto em XML "abre uma tag". No VB "=" pode significar "é igual a", enquanto no Java é usado apenas para atribuição. Etc. Não surpreende que idiomas diferentes usem símbolos diferentes para significar a mesma coisa, ou o mesmo símbolo para significar coisas diferentes.
Jay

Não foi uma escavação na sua resposta. Foi apenas um comentário. Eu estava apenas apontando que uma linguagem mais moderna que Java aproveitou a chance de redesenhar o significado ==, exatamente como você mencionou no final.
Andres F.

@AndresF. E minha resposta não foi um comentário sobre o seu comentário, apenas dizendo que idiomas diferentes abordam esses problemas de maneiras diferentes. :-) Eu realmente gosto da maneira como o VB lida com isso ... pausa para os assobios e vaias dos inimigos do VB ... "=" sempre compara o valor (para tipos definidos pelo sistema), seja primitivo ou objeto. "Is" compara as alças de dois objetos. Isso parece mais intuitivo para mim.
Jay

Certo. Mas Scala está muito mais próximo do Java do que do Visual Basic. Eu gosto de pensar que os designers da Scala perceberam que o uso de Java ==era propenso a erros.
Andrés F.

0

Esta é uma pergunta válida para Strings, e não apenas para cordas, mas também para outros objetos imutáveis que representam cerca de "valor", por exemplo Double, BigIntegere mesmo InetAddress.

Para tornar o ==operador utilizável com Strings e outras classes de valor, vejo três alternativas:

  • Faça com que o compilador conheça todas essas classes de valor e a maneira de comparar seu conteúdo. Se fossem apenas algumas classes do java.langpacote, eu consideraria isso, mas isso não cobre casos como o InetAddress.

  • Permitir sobrecarga do operador para que uma classe defina seu ==comportamento de comparação.

  • Remova os construtores públicos e tenha métodos estáticos retornando instâncias de um pool, sempre retornando a mesma instância pelo mesmo valor. Para evitar vazamentos de memória, você precisa de algo como SoftReferences no pool, que não existia no Java 1.0. E agora, para manter a compatibilidade, os String()construtores não podem mais ser removidos.

A única coisa que ainda poderia ser feita hoje seria introduzir a sobrecarga do operador e, pessoalmente, não gostaria que o Java seguisse esse caminho.

Para mim, a legibilidade do código é mais importante, e um programador Java sabe que os operadores têm um significado fixo, definido na especificação da linguagem, enquanto os métodos são definidos por algum código, e seu significado deve ser pesquisado no Javadoc do método. Eu gostaria de permanecer com essa distinção, mesmo que isso signifique que as comparações de String não poderão usar o ==operador.

Há apenas um aspecto das comparações de Java que é irritante para mim: o efeito do boxe automático e da caixa unificada. Oculta a distinção entre o tipo primitivo e o tipo de invólucro. Mas quando você os compara ==, eles são MUITO diferentes.

    int i=123456;
    Integer j=123456;
    Integer k=123456;
    System.out.println(i==j);  // true or false? Do you know without reading the specs?
    System.out.println(j==k);  // true or false? Do you know without reading the specs?
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.