Dúvida: Objetos e referência de memória

Estou estudando um o comportamento de referências de memória em Java.
A ideia desse exemplo é bem simples:

Crio 2 pessoas: P1 = Lovelace e P2 = Tesla e depois de executar o método SWAP eu quero inverter isso que P1 = Tesla e P2 = Lovelace

Tenho 2 perguntas pra voces:

  • Como faço para o método swap_clone se comportar da mesma forma que o swap_correct?
  • Voces fariam um refactor nessa classe? Se sim, porque?
package com.examples.app;

import com.examples.model.Person;

public class app {

	public static void main(String[] args) throws CloneNotSupportedException {
		
		Person p1 = new Person("Lovelace", 18);
		Person p2 = new Person("Tesla", 30);		
		
		
		System.out.println("======Begin Swap_Incorrect=====");
		System.out.println("Main Method - P1_Name: " + p1.hashCode()  + p1.getName());
		System.out.println("Main Method - P2_Name: " + p2.hashCode() + p2.getName());		
		
		p1.swap_incorrect(p1, p2);
		
		System.out.println("Main Method - P1_Name: " + p1.hashCode() + p1.getName());
		System.out.println("Main Method - P2_Name: " + p2.hashCode() + p2.getName());
		System.out.println("=======End Swap_Incorrect======");
		
		
		
		
		
		System.out.println("");
		System.out.println("======Begin Swap_Correct=====");
		System.out.println("Main Method - P1_Name: " + p1.hashCode()  + p1.getName());
		System.out.println("Main Method - P2_Name: " + p2.hashCode() + p2.getName());		
		
		p1.swap_correct(p1, p2);
		
		System.out.println("Main Method - P1_Name: " + p1.hashCode() + p1.getName());
		System.out.println("Main Method - P2_Name: " + p2.hashCode() + p2.getName());
		System.out.println("=======End Swap_Correct======");
		
		
		
		
		System.out.println("");
		System.out.println("======Begin Swap_Clone=====");
		System.out.println("Main Method - P1_Name: " + p1.hashCode()  + p1.getName());
		System.out.println("Main Method - P2_Name: " + p2.hashCode() + p2.getName());		
		
		p1.swap_clone(p1, p2);
		
		System.out.println("Main Method - P1_Name: " + p1.hashCode() + p1.getName());
		System.out.println("Main Method - P2_Name: " + p2.hashCode() + p2.getName());
		System.out.println("=======End Swap_Clone======");	
		
	}

}

package com.examples.model;

public class Person implements Cloneable {
	private String name;
	private int age;
	private Person p;
	
	private Person getP() {
		return p;
	}

	private void setP(Person p) {
		this.p = p;
	}

	public Person() {}
	
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	private void setName(String name) {
		this.name = name;
	}

	private void setAge(int age) {
		this.age = age;
	}		
	
	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}
	
	public void swap_incorrect(Person p1, Person p2) {
		Person p0 = p1;
		p1 = p2;
		p2 = p0;
		
		System.out.println("Class Method - P1_Name: " + p1.hashCode() + p1.getName());
		System.out.println("Class Method - P2_Name: " + p2.hashCode() + p2.getName());		
		
	}
	
	public void swap_correct(Person p1, Person p2) {
		Person p0 = new Person();
		p0.setAge(p1.getAge());
		p0.setName(p1.getName());
		
		p1.setAge(p2.getAge());
		p1.setName(p2.getName());
		
		p2.setAge(p0.getAge());
		p2.setName(p0.getName());
		
		p2 = p0;
		
		System.out.println("Class Method - P1_Name: " + p1.hashCode() + p1.getName());
		System.out.println("Class Method - P2_Name: " + p2.hashCode() + p2.getName());		
		
	}
	
	
	public void swap_clone(Person p1, Person p2) throws CloneNotSupportedException {
		Person p0 = new Person();	
		p0.setP(p1.clone());		
		p1.setP(p2.clone());
		p2.setP(p0.clone());
		
		System.out.println("Class Method - P1_Name: " + p1.hashCode() + p1.getName());
		System.out.println("Class Method - P2_Name: " + p2.hashCode() + p2.getName());	
		
	}
	
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }	

	
	
}

Resposta 1

Para que se comportem da mesma forma vc deve modificar seu método setP para ficar assim:

private void setP(Person p) {
    this.name = p.name;
    this.age = p.age;
}

Obs: Você pode remover o atributo p e o método getP, eles não são necessários.

Resposta 2

Sim, eu faria, porque…

  1. Não vejo sentido para o atributo p existir.
  2. Não entendo qual a utilidade do swap_clone.

A minha proposta de refatoração é essa incluir sobreescrever o método hashCode e o método equals.

import java.util.Objects;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(Person p) {
        this.name = p.name;
        this.age = p.age;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Person)) return false;
        return this.age == ((Person) o).age && Objects.equals(this.name, ((Person) o).name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.name, this.age);
    }

    public static void swap(Person a, Person b) {
        Person temp = new Person(a);
        a.name = b.name;
        a.age = b.age;
        b.name = temp.name;
        b.age = temp.age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Lovelace", 18);
        Person p2 = new Person("Tesla", 30);

        System.out.printf("AFTER:\n%d %s\n%d %s\n", p1.hashCode(), p1.getName(), p2.hashCode(), p2.getName());
        Person.swap(p1, p2);
        System.out.printf("BEFORE:\n%d %s\n%d %s\n", p1.hashCode(), p1.getName(), p2.hashCode(), p2.getName());
    }
}

Oi wldomiciano, muito obrigado pela sua resposta e pelo seu tempo.

Eu sei que a ideia é nao ter classes “muito grandes” mas imagine uma classe com muitos atributos, eu teria que escrever “this.abritubo = p.atributo;” inumeras vezes.
Se a classe tiver 20 atributos, tenho que ficar repetindo codigo.

A intencao do swap_clone era passar o objeto inteiro de uma vez so, ao inves de codificar atributo por atributo. E se eu precisasse adicionar um novo atributo na classe, eu precisaria lembrar de adiciona-lo nos metodos. Para evitar essas duas situacoes eu estou tentando criar o swap_clone.
Como eu poderia fazer isso?

Mais uma vez, muito obrigado.

Se você utilizar a interface Cloneable, exemplo:

public class People implements Cloneable {
    private int id;
    private String name;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }    
    public People Clone() throws CloneNotSupportedException
    {
        return (People) super.clone();
    }   
}

Código:

People p0 = new People();
p0.setId(1);
p0.setName("Guj");

People p1 = p0.Clone();

System.out.println(p0.getId() + " " + p0.getName());
System.out.println(p1.getId() + " " + p1.getName());       

p1.setId(2);
p1.setName("Duvida");

System.out.println(p0.getId() + " " + p0.getName());
System.out.println(p1.getId() + " " + p1.getName()); 

Saída:

run:
1 Guj
1 Guj
1 Guj
2 Duvida
BUILD SUCCESSFUL (total time: 0 seconds)

Ou seja, uma cópia rasa.

Referencia: Clone() method in Java

Oi Dragoon,

Obrigado pelo exemplo, mas acho que ainda nao é bem isso…

No meu exemplo de estudo eu crio 2 objetos:
“P1 = Lovelace” e “P2 = Tesla”
e depois de executar o método SWAP eu quero inverter isso
“P1 = Tesla” e “P2 = Lovelace”

Seria como “trocar de lugar as informacoes”.

No meu primeiro exemplo eu ja tinha conseguido atraves do medoto “swap_correct”
mas nesse exemplo eu tenho que referenciar atributo por atributo. Existe alguma forma que nao seja fazer a referencia atributo por atributo?

Muito obrigado =)

Eu acho que você está bem perdidinho…

Sua citação: “Existe alguma forma que não seja fazer a referencia atributo por atributo?”

Minha resposta é para não fazer isso. Talvez você precisa escreve algum código em especifico …

Preste atenção nisso cara …!

Se você precisa interver valores é só guardar um deles e depois clonar para o outro …

Ou seja, precisa de mais um objeto …!

1 curtida

Vc não vai conseguir escapar de copiar atributo por atributo.

Imagine que além de ter 20 atributos, sua classe possui atributos mais complexos.

O método clone padrão faz apenas copias simples e classes mais complexas requerem que vc sobreescreva este método para fazer uma clonagem adequada.

Vc teria que especificar no clone como clonar cada atributo.

Acredito uma possível forma de automatizar o processo seja usando Reflection.

Além disso, no Java, é impossível alterar a identidade de um objeto. Imagine o seguinte exemplo hipotético:

public static void main(String... args) {
    Person p1 = new Person();
    Person p2 = new Person();
    
    Person temp1 = p1;
    Person temp2 = p2;
   
    System.out.println(temp1 == p1); // true
    System.out.println(temp2 == p1); // false
    
    System.out.println(temp1 == p2); // false
    System.out.println(temp2 == p2); // true
    
    Person.swap(p1, p2);
 
    System.out.println(temp1 == p1); // continua true
    System.out.println(temp2 == p2); // continua true
}

Usando o exemplo acima, para obter uma saida diferente da esperada, vc precisaria de uma forma de modificar o conteúdo das variáveis p1 e p2 de dentro do método swap e Java não nos fornece este acesso.

Em C ou C++ esse tipo de coisa é possível com ponteiros para ponteiro, mas em Java não.

Este experimento, vc está se baseando em quê para realizá-lo? Tem algum exemplo de uso de real ou algo assim? Se vc explicar melhor, talvez haja uma saida mais adequada.

1 curtida

Java manipula objetos por referência e todas as variáveis não-primitivas são referências.
Entretanto Java não passa argumentos de métodos por referência e sim por valor, na verdade ele passa uma cópia da referência, por isso é considerado passagem de parâmetro por valor (referência aqui).

Então você não vai conseguir escrever um método que troque a referência de dois objetos passados por parâmetro.
Vais ter que fazer o swap dessa forma:

public class Application {

    public static void main(String[] args) throws CloneNotSupportedException {

        Person p1 = new Person("Lovelace", 18);
        Person p2 = new Person("Tesla", 30);

        System.out.println("======Before Swap=====");
        System.out.println("Main Method - P1_Name: " + p1.hashCode() + " " + p1.getName());
        System.out.println("Main Method - P2_Name: " + p2.hashCode() + " " + p2.getName());

        System.out.println("======Begin Swap=====");

        Person tmp = p1;
        p1 = p2;
        p2 = tmp;

        System.out.println("Main Method - P1_Name: " + p1.hashCode() + " " + p1.getName());
        System.out.println("Main Method - P2_Name: " + p2.hashCode() + " " + p2.getName());
        System.out.println("=======End Swap======");
    }
}

Entretanto se você se sente incomodado em escrever essas três linhas abaixo:

Person tmp = p1;
p1 = p2;
p2 = tmp;

Existe uma alternativa não muito bonita para você escrever menos código, você pode criar um método assim:

public static <T> T swap(T a, T b) {
    return a;
}

Dessa forma você conseguirá fazer o swap em uma única linha, mas de uma forma não muito legiva:

public class Application {

    public static void main(String[] args) throws CloneNotSupportedException {

        Person p1 = new Person("Lovelace", 18);
        Person p2 = new Person("Tesla", 30);

        System.out.println("======Before Swap=====");
        System.out.println("Main Method - P1_Name: " + p1.hashCode() + " " + p1.getName());
        System.out.println("Main Method - P2_Name: " + p2.hashCode() + " " + p2.getName());

        System.out.println("======Begin Swap=====");

        p1 = swap(p2, p2 = p1); // Uma única linha, mas é feio pra caramba

        System.out.println("Main Method - P1_Name: " + p1.hashCode() + " " + p1.getName());
        System.out.println("Main Method - P2_Name: " + p2.hashCode() + " " + p2.getName());
        System.out.println("=======End Swap======");
    }

    public static <T> T swap(T a, T b) {
        return a;
    }
}
2 curtidas

Opa, muito obrigado pela explicaçao. Agora ficou muito mais claro.

Nao é um caso de uso real, apenas estudos mesmo.
Talvez o Reflection seja uma saida, apesar da complexidade de implementacao para um caso tao simples desse vale o aprendizado. =)

Obrigado pelo exemplo staroski =)

Obrigado mais uma vez galera.
Topico encerrado e duvida esclarecida =)

1 curtida

Perfeito, toda classe em Java e C# são passados por referencia. (não sei outras linguagens por não conhecer a base em outras)

No C# não sei, mas no C e no C++ existem as duas formas, vai depender da assinatura dos parâmetros do método, se houver um & antes do identificador do parâmetro, então a passagem será por referência.

No Java na verdade é passado uma cópia da referência, por isso que consideram ser passagem por valor. Os parâmetros são variáveis diferentes das passadas por argumento, mas que apontam para a mesma referência.

Exemplo

void executar() {
    Objeto a = new Objeto("1"); // a aponta para "1"
    Objeto b = new Objeto("2"); // b aponta para "2"
    passar(a, b); // passamos a e b ao metodo
}

void passar(Objeto x, Objeto y) { // x e y são duas novas variáveis, mas apontam para a mesma referência que a e b
    Objeto tmp = x; // tmp aponta para a mesma referência que x ("1")
    x = y; // agora x aponta para a mesma referência que y ("2")
    y = tmp; // agora y aponta para a mesma referência que tmp ("1")

    // Agora x aponta pra "2" e y pra "1"
    // Mas x e y são apenas cópias da referência
    // então lá no método executar
    // a continua apontando pra "1" e b continua apontando pra "2"
}

Eu afirmei que em C# e com complemento da sua resposta pensei que Java também fosse!

C# é passado por referencia.

1 curtida