Uma vantagem da linguagem Java é que ela cancela o conceito de ponteiros, mas também leva muitos programadores a ignorar frequentemente a diferença entre objetos e referências na programação. Este artigo tentará esclarecer esse conceito. E como o Java não pode resolver o problema de copiar objetos por meio de atribuição simples, durante o processo de desenvolvimento, geralmente é necessário usar o método clone () para copiar objetos. Este artigo permitirá que você entenda o que são clones de sombra e clone profundo e entenderão suas diferenças, vantagens e desvantagens.
Você está um pouco confuso quando vê este título: o idioma Java afirma claramente que os ponteiros são cancelados, porque os ponteiros geralmente são convenientes e também a causa raiz da insegurança do código, e também torna o programa muito complexo e difícil de entender. O código escrito por abuso de ponteiros não é menos do que usar a já infame declaração "Goto". É absolutamente sábio para Java desistir do conceito de ponteiros. Mas isso é apenas que não existe uma definição clara de ponteiro no idioma Java. Em essência, toda nova declaração retorna uma referência de ponteiro. No entanto, na maioria dos casos, o Java não precisa se preocupar com como operar esse "ponteiro", muito menos ficar tão assustado quanto ao operar ponteiros em C ++. A única coisa a se preocupar mais é ao passar objetos para as funções.
pacote com.zoer.src; classe pública objref {obj aobj = new obj (); int aint = 11; public void ChangeObj (obj inobj) {inobj.str = "alteração valor"; } public void ChangePri (int inint) {inint = 22; } public static void main (string [] args) {objref oref = new objref (); System.out.println ("Antes de Chamada Mudança () Método:" + Oref.aobj); oref.changeobj (oref.aobj); System.out.println ("Após a chamada Mudança () Método:" + oref.aobj); System.out.println("=============================================================================================== ==================================================================================================================== ==================================================================================================================== ==================================================================================================================== Chamada de alteraçãoPRI () Método: " + Oref.aint); }} pacote com.zoer.src; classe pública obj {string str = "init valor"; public string tostring () {return str; }} A parte principal deste código chama dois métodos muito semelhantes, ChangeObj () e ChangePri (). A única diferença é que um toma o objeto como o parâmetro de entrada, e o outro leva o tipo básico int em Java como o parâmetro de entrada. E os parâmetros de entrada são alterados dentro de ambos os corpos da função. O método aparentemente mesmo, mas os resultados da saída do programa são diferentes. O método ChangeObj () realmente altera os parâmetros de entrada, enquanto o método ChangePri () não altera os parâmetros de entrada.
A partir deste exemplo, Java processa objetos e tipos de dados básicos de maneira diferente. Como C, quando os tipos de dados básicos de Java (como int, char, dupla etc.) são passados para o corpo da função como parâmetros de entrada, os parâmetros passados se tornam variáveis locais dentro do corpo da função. Esta variável local é uma cópia dos parâmetros de entrada. Todas as operações dentro do corpo da função são operações para esta cópia. Após a conclusão da execução da função, a variável local concluirá sua missão e não afetará as variáveis como parâmetros de entrada. Este método de passagem de parâmetros é chamado de "passagem de valor". Em Java, o passe usando um objeto como um parâmetro de entrada é "passe de referência", o que significa que apenas uma "referência" do objeto é passada. O conceito dessa "referência" é o mesmo que a referência do ponteiro na linguagem C. Quando a variável de entrada é alterada dentro do corpo da função, é essencialmente uma operação direta no objeto.
Exceto pelo "passe de referência" ao passar o valor da função, "passe de referência" ao atribuir valores às variáveis de objeto usando "=". É semelhante a dar a uma variável outro alias. Ambos os nomes apontam para o mesmo objeto na memória.
Na programação real, geralmente encontramos essa situação: existe um objeto A que contém alguns valores válidos em um determinado momento. No momento, um novo objeto B pode ser necessário exatamente o mesmo que a, e quaisquer alterações em B não afetarão o valor em A. Ou seja, a e B são dois objetos independentes, mas o valor inicial de B é determinado pelo objeto A. No idioma java, o uso de instruções simples de atribuição não pode atender a esse requisito. Embora existam muitas maneiras de atender a essa necessidade, a maneira mais simples e eficiente de implementar o método clone () está entre eles.
Todas as classes Java herdam a classe java.lang.Object por padrão, e há um clone de método () na classe java.lang.Object. A documentação da API JDK explica que esse método retornará uma cópia do objeto. Há dois pontos a serem explicados: primeiro, o objeto de cópia retorna um novo objeto, não uma referência. Segundo, a diferença entre copiar um objeto e um novo objeto retornado com o novo operador é que esta cópia já contém algumas informações sobre o objeto original, em vez das informações iniciais do objeto.
Como aplicar o método clone ()?
Um código de chamada muito típico para clone () é o seguinte:
classe pública cloneclass implementa clonável {public int aint; public Object clone () {CLONECLASS O = NULL; tente {o = (CLONECLASS) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retornar o; }} Há três pontos que vale a pena notar. Primeiro, a classe CLONECLASS que espera implementar a função clone implementa a interface clonável. Esta interface pertence ao pacote java.lang. O pacote java.lang foi importado para a classe padrão, por isso não precisa ser escrito como java.lang.clonable. Outra coisa que vale a pena notar é que o método clone () está sobrecarregado. Finalmente, o Super.Clone () é chamado no método clone (), que também significa que, independentemente da estrutura da herança da classe de clone, Super.Clone () chama diretamente ou indiretamente o método clone () da classe Java.lang.Object. Vamos explicar esses pontos em detalhes abaixo.
Deve -se dizer que o terceiro ponto é o mais importante. Se você observar cuidadosamente um método nativo do clone da classe de objeto (), a eficiência do método nativo geralmente é muito maior que a dos métodos não nativos em Java. Isso também explica por que devemos usar o método clone () no objeto em vez de primeiro a primeira classe e atribuir as informações do objeto original ao novo objeto, embora isso também implemente a função clone. Para o segundo ponto, também devemos observar se o clone () na classe de objeto é um método com uma propriedade protegida. Isso também significa que, se você deseja aplicar o método clone (), deve herdar a classe de objeto. Em Java, todas as classes herdam a classe de objeto por padrão, para que você não precise se preocupar com isso. Em seguida, sobrecarregue o método clone (). Outra coisa a considerar é que, para que outras classes chamassem o método clone () desta classe de clone, após a sobrecarga, os atributos do método clone () devem ser definidos como público.
Então, por que a classe clone ainda implementa a interface clonável? Observe que a interface clonável não contém nenhum método! De fato, essa interface é apenas um sinalizador, e esse sinalizador é apenas para o método clone () na classe de objeto. Se a classe clone não implementar a interface clonável e chamar o método clone () do objeto (ou seja, o método super.clone () é chamado), o método clone () do objeto lançará uma exceção de clonenotsupportEdException.
Os acima são as etapas mais básicas do clone. Se você deseja concluir um clone bem -sucedido, também precisa entender o que é "clone das sombras" e "clone profundo".
O que é Shadow Clone?
pacote com.zoer.src; classe Unidonea {private int i; public ulonee (int ii) {i = ii; } public void DoubleValue () {i *= 2; } public string tostring () {return integer.toString (i); }} classe cloneb implementa clonável {public int aint; public uncone unda = novo uncloea (111); public Object clone () {cloneb o = null; tente {o = (cloneb) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retornar o; }} classe pública objref {public static void main (string [] a) {cloneb b1 = new cloneb (); b1.aint = 11; System.out.println ("Antes do clone, b1.aint =" + b1.aint); System.out.println ("Antes do clone, b1.unca =" + b1.unca); Cloneb b2 = (cloneb) b1.clone (); b2.aint = 22; b2.unca.doubleValue (); System.out.println("======================================================================================================== ================================================================ ================================================================= ================================================================ ================================================================= ================================================================ ================================================================= clone, b2.aint = " + b2.aint); System.out.println ("Após o clone, b2.unca =" + b2.unca); }} Resultado da saída:
Antes do clone, b1.aint = 11 também clone, b1.unca = 111 =========================================================== =============================================================== ============================================================== =============================================================== ============================================================== =============================================================== ============================================================== ===============================================================
Os resultados da saída mostram que a variável int AINT e o resultado do clone do objeto de instância Uncá de Uncloea são inconsistentes. O tipo int é realmente clone porque a variável AINT em B2 não tem efeito no AINT de B1. Ou seja, b2.aint e b1.aint já ocupam diferentes espaços de memória, b2.aint é uma cópia real do B1.aint. Pelo contrário, a alteração para B2.unca muda simultaneamente para B1.unca, e é óbvio que B2.uncA e B1.uncA são referências diferentes que apenas apontam para o mesmo objeto! A partir disso, podemos ver que o efeito de chamar o método clone () na classe de objeto é: primeiro abra um espaço na memória que é o mesmo que o objeto original e depois copie o conteúdo do objeto original como ele é. Para os tipos de dados básicos, essas operações não são problemáticas, mas para variáveis de tipo não primitivas, sabemos que elas apenas salvam referências a objetos, o que também causa as variáveis de tipo não primitivo após o clone apontar para o mesmo objeto e as variáveis correspondentes no objeto original.
Na maioria das vezes, os resultados desse clone geralmente não são os resultados que esperamos, e esse clone também é chamado de "clone das sombras". Para fazer com que o B2.unca aponte para um objeto diferente de B2.UNCA, e B2.uNCA também contém informações em B1.unca como informações iniciais, você deve implementar um clone de profundidade.
Como realizar clone profundo?
É muito simples alterar o exemplo acima para um clone profundo, e duas alterações são necessárias: uma é permitir que a classe desconhecida também implemente a mesma função clone que a classe CloneB (implemente a interface clonável e sobrecarregam o método clone ()). O segundo é adicionar uma frase o.unca = (uncloea) uncA.clone () ao método clone () do cloneb;
pacote com.zoer.src; classe Unidonee implementa clonável {private int i; public ulonee (int ii) {i = ii; } public void DoubleValue () {i *= 2; } public string tostring () {return integer.toString (i); } public Object clone () {uncloea o = null; tente {o = (uncloea) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retornar o; }} classe cloneb implementa clonável {public int aint; public uncone unda = novo uncloea (111); public Object clone () {cloneb o = null; tente {o = (cloneb) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } o.unca = (uncloea) unda.clone (); retornar o; }} classe pública clonemain {public static void main (string [] a) {cloneb b1 = new cloneb (); b1.aint = 11; System.out.println ("Antes do clone, b1.aint =" + b1.aint); System.out.println ("Antes do clone, b1.unca =" + b1.unca); Cloneb b2 = (cloneb) b1.clone (); b2.aint = 22; b2.unca.doubleValue (); System.out.println("================================================================================ ==================================================================================================== ==================================================================================================== ============================================================================================================= dúvida clone, b1.aint = " + b1.aint); System.out.println ("Após o clone, b1.unca =" + b1.unca); System.out.println("=================================================================================================== ============================================================== ============================================================== ============================================================== ============================================================== ============================================================== ============================================================== Resultado da saída:
Antes do clone, b1.aint = 11 também clone, b1.unca = 111 =========================================================== =============================================================== ============================================================== =============================================================== ============================================================== =============================================================== ============================================================== ===============================================================
Pode -se observar que a mudança atual de B2.unca não tem efeito no B1.uncA. Neste momento, B1.unca e b2.unca apontam para duas instâncias diferentes da soléia, e B1 e B2 têm o mesmo valor no momento em que o cloneb b2 = (cloneb) b1.clone (); é chamado aqui, b1.i = b2.i = 11.
Você deve saber que nem todas as classes podem implementar clones profundos. Por exemplo, se você alterar a variável do tipo de desconto na classe CloneB acima para o tipo StringBuffer, observe a descrição sobre StringBuffer na API JDK. O StringBuffer não sobrecarrega o método clone () e o que é mais sério é que o StringBuffer ainda é uma classe final, o que significa que não podemos implementar indiretamente o clone do StringBuffer usando herança. Se uma classe contiver um objeto de tipo StringBuffer ou um objeto semelhante ao StringBuffer, temos duas opções: apenas o clone de sombra pode ser implementado ou adicionar uma frase ao método clone () da classe (assumindo que é um objeto sringbuffer, e o nome da variável ainda é UncA): O.uncA = novo stringbuffer (Unc.OSSSTRING ( // O original é: O.unca = (uncloea) unda.clone ();
Deve -se notar também que, além dos tipos de dados básicos que podem implementar automaticamente clones profundos, o objeto String é uma exceção. Seu desempenho após o clone também parece implementar clones profundos. Embora isso seja apenas uma ilusão, facilita muito nossa programação.
A diferença entre String e StringBuffer no clone deve ser explicada que isso não é um foco na diferença entre String e StringBuffer, mas, a partir deste exemplo, também podemos ver alguns recursos exclusivos da classe String.
O exemplo a seguir inclui duas classes. A classe CLONEC contém uma variável de tipo de string e uma variável do tipo StringBuffer e implementa o método clone (). A variável do tipo CLONEC C1 é declarada na classe strclone e, em seguida, o método clone () de C1 é chamado para gerar uma cópia do C1 C2. Depois de alterar as variáveis do tipo String e StringBuffer em C2, o resultado é impresso:
pacote com.zoer.src; classe CLONEC implementa clonável {public string str; public stringbuffer strbuff; public Object clone () {CLONEC O = NULL; tente {o = (CLONEC) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retornar o; }} classe pública strclone {public static void main (string [] a) {CLONEC C1 = new CLONEC (); c1.str = new String ("InitializEST"); c1.strbuff = new StringBuffer ("InitializestBuff"); System.out.println ("Antes do clone, c1.str =" + c1.str); System.out.println ("Antes do clone, c1.strbuff =" + c1.strbuff); CLONEC C2 = (CLONEC) C1.CLONE (); c2.str = c2.str.substring (0, 5); c2.strbuff = c2.strbuff.append ("altere o clone strbuff"); System.out.println ("============================================== ==================================================================== ==================================================================== ===================================================================== ==================================================================== ==================================================================== ==================================================================== ===================================================================== System.out.println ("Após o clone, c2.strbuff =" + c2.strbuff); Resultados da execução:
<span style = "font-family: 'Microsoft yahei';"> <span style = "font-size: 16px;"> antes do clone, c1.str = inicializest antes do clone, c1.strbuff = inicializestBuff ============================================================================================================== ============================================================================================================== ============================================================================================================== ============================================================================================================== clone, c2.str = inicializestBuff alteração strbuff clone </span> </span>
O resultado impresso mostra que as variáveis do tipo de string parecem ter implementado o clone de profundidade, porque as alterações para C2.STR não afetaram C1.STR! Java considera a classe sring um tipo de dados básico? Na verdade, esse não é o caso. Há um pequeno truque aqui. O segredo está na declaração c2.str = c2.str.substring (0,5)! Em essência, C1.STR e C2.STR ainda são referências quando clone, e ambos apontam para o mesmo objeto String. Mas quando c2.str = c2.str.substring (0,5), é equivalente a gerar um novo tipo de string e, em seguida, atribuí -lo novamente a C2.str. Isso ocorre porque a string é escrita como uma classe imutável pelos engenheiros da Sun, e as funções em todas as classes de string não podem alterar seus próprios valores.
O acima é tudo sobre este artigo. Espero que seja útil que todos entendam replicação profunda e superficial em Java.