Simplistic answer
You can not do this in Java.
Impractical response
You could instrumentalize the Java classes in order to intercept the accesses and changes made to the objects.
Reasonable response
Implement an object access pattern to replicate the actions performed.
This reminds me of the architectural standard Event Sourcing , where operations performed on the data are stored as events, then play, undo, and navigate through the system event history.
Any change to the data must be made by a class that manages the data and will be represented by an object that contains the event data.
Example
I made a simple implementation based on the question code:
Class Pessoa
I have changed to be immutable, as it is a good practice.
public class Pessoa {
private final String nome;
private final int idade;
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
public String getNome() { return nome; }
public int getIdade() { return idade; }
@Override
public String toString() { return nome + " / " + idade; }
}
Event Interface
interface Event {
void apply(List<Pessoa> lista);
}
Event to add a person
public class AddPessoaEvent implements Event {
private final Pessoa p;
public AddPessoaEvent(Pessoa p) {
this.p = p;
}
@Override
public void apply(List<Pessoa> lista) {
System.out.println("* Adicionando " + p);
lista.add(p);
}
@Override
public String toString() { return "Add " + p.getNome(); }
}
Event to remove a person
public class RemovePessoaEvent implements Event {
private final int index;
public RemovePessoaEvent(int index) {
this.index = index;
}
@Override
public void apply(List<Pessoa> lista) {
System.out.println("* Removendo " + index);
lista.remove(index);
}
@Override
public String toString() { return "Remove " + index; }
}
Event to update a person's name
public class AtualizaNomePessoaEvent implements Event {
private final int index;
private final String nome;
AtualizaNomePessoaEvent(int index, String nome) {
this.index = index;
this.nome = nome;
}
@Override
public void apply(List<Pessoa> lista) {
System.out.println("* Atualizando " + index + " com nome " + nome);
lista.set(index, new Pessoa(nome, lista.get(index).idade));
}
@Override
public String toString() {
return "Atualiza " + index + " com nome " + nome;
}
}
People list management class
public class PessoasManager {
private final List<Pessoa> listaDePessoas = new ArrayList<>();
private final List<Event> eventos = new ArrayList<>();
public void add(Pessoa p) {
AddPessoaEvent e = new AddPessoaEvent(p);
eventos.add(e);
e.apply(listaDePessoas);
}
public void remove(int index) {
RemovePessoaEvent e = new RemovePessoaEvent(index);
eventos.add(e);
e.apply(listaDePessoas);
}
public void atualizaNome(int index, String nome) {
AtualizaNomePessoaEvent e = new AtualizaNomePessoaEvent(index, nome);
eventos.add(e);
e.apply(listaDePessoas);
}
public List<Pessoa> getListaDePessoas() { return listaDePessoas; }
public List<Event> getEventos() { return eventos; }
public void replay(List<Event> eventos) {
for (Event e : eventos) {
this.eventos.add(e);
e.apply(listaDePessoas);
}
}
}
Using the classes
The main code to perform the operations mentioned in the question would be the following:
PessoasManager pm = new PessoasManager();
pm.add(new Pessoa("Joao", 29));
pm.add(new Pessoa("Ana", 21));
pm.add(new Pessoa("Maria", 25));
pm.atualizaNome(0, "Jao Carlos");
pm.remove(2);
Each person manager method creates its event and applies it to the people list. Since each apply
method has a println
, this would print to the console:
Printing lists of people and events:
System.out.println("Pessoas " + pm.getListaDePessoas());
System.out.println("Eventos " + pm.getEventos());
We have the result:
People [Jao Carlos / 29, Ana / 21]
Events [Add Joao, Add Ana, Add Maria, Update 0 with name Jao Carlos, Remove 2]
Then, since we have the events, we can apply them again in another list, creating another people manager and using the replay
method:
PessoasManager pm2 = new PessoasManager();
pm2.replay(pm.getEventos());
The second line above will produce the exact same output on the referencing console, since the same events will be applied again. And if you print the new list of people it will also be the same as the previous one.
Something interesting in this pattern is that you could replicate events in another data structure by creating another method analogous to apply
. Instead of a list, it could be a connection to the bank, for example.
Disadvantages
The default, as applied above, can generate unnecessary events. Imagine a person's name updated several times.
So, if the history is not important to you, another alternative would be to store the initial list, create a copy of it, apply the changes, and in the end generate a diff , that is, a list of differences between the initial and the final state in order to perform the minimum number of operations possible when you apply them to your other data source.
Another simpler alternative, when the list is small and there are no dependencies, is simply not trying to replicate the process but replacing the target data with the new data. For example, I worked on a system a few years ago where the user could edit a table. Refreshing each field and record in the database according to the row and column of the screen was complicated, mainly because the user could arbitrarily include and exclude rows and columns. In this particular situation, we decided that the most viable solution at the time was simply to delete the existing records and recreate them all from the new values.