How do I know which ArrayList objects are being modified or deleted?

3

I have a ArrayList that is preloaded with some objects of the type of my Person class, see:

ArrayList<Pessoa> listaDePessoas = new ArrayList<>();
listaDePessoas.add(new Pessoa("Joao", 29));
listaDePessoas.add(new Pessoa("Ana", 21));
listaDePessoas.add(new Pessoa("Maria", 25));

Let's suppose that at some point I change one of these objects contained in ArrayList listaDePessoas see:

listaDePessoas.get(0).setNome("Jao carlos");

And then at another time I remove one of these objects contained in ArrayList listaDePessoas see below:

listaDePessoas.remove(listaDePessoas.get(2));

I removed the object that is in the third position in the list.

Person class structure:

public class Pessoa {    
    private String nome;
    public String getNome() { return nome; }
    public void setNome(String nome) { this.nome = nome; }
    private int idade;
    public int getIdade() { return idade; }
    public void setIdade(int idade) { this.idade = idade; }

    public Pessoa(String nome, int idade) {
        this.nome = nome;
        this.idade = idade;
    }

    public Pessoa() { }
}

Question

So, I wonder how I could know which objects are being modified and which ones are being removed from the list? So that in the future the changes applied to these change and delete objects will be applied elsewhere.

    
asked by anonymous 20.10.2016 / 23:22

2 answers

5

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:

  
  • Adding Joao / 29

  •   
  • Adding Ana / 21

  •   
  • Adding Mary / 25

  •   
  • Updating 0 with name Jao Carlos

  •   
  • Removing 2

  •   

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.

    
21.10.2016 / 07:19
4

Since you know what object you want, just make a copy of it before doing any operations. It is a basic technique, there is no "magic".

Change

Pessoa pessoaAlterada = listaDePessoas.get(0).clone();
listaDePessoas.get(0).setNome("Jao carlos");

I did not make a simple copy. As the type is by reference the copy would be the reference, so pessoaAlterada would point to the same memory location where the object is pointing to the list, then the change would be seen in that copy. The solution was to clone the object. So I had to implement the interface Cloneable in the class. You can even do it by hand, but it's not ideal.

With cloning the copy will point to a new object with the same data as it will change next. Because it is an independent object, changing the object referenced in the list will not affect the cloned object.

It is only important to understand that this is a shallow cloning, so all bits of the object will be copied, including the references contained in those objects. We conclude that the new object will point to the same objects that make it up as the original object is pointing to. If the original object changes these composite objects, the cloned object will see the changes. To avoid this, you would need a deep cloning that clones the entire tree of objects, making everything independent. You need to see if that's what you want.

If you want deep cloning, or the data type ( Pessoa ), you must have a clone() method that does this for you, or you will have to create an algorithm in the code to do all the cloning of the whole tree in the hand . I did not do it because it does not seem to be necessary in this case, but it could be if I put new attributes. Even the String which is a type by reference has type semantics by value and is immutable, so it does not have this problem, a change would create a new object.

This kind of copy is dangerous if you do not know what you are doing.

I talk about shallow copy and deep copy in Class Copy in C # . It's very much like Java.

Removal

Pessoa pessoaRemovida = listaDePessoas.remove(2);

Notice that you do not need to get the object if you already know the index. And this method overload returns the object itself (the reference), then you do not even need to make a previous copy.

To remove you do not need to make any copies of the object itself, after all you removed it from the list, then it will no longer be accessible in the list, so you can use it yourself. Of course if you have any other reason to clone the object you can also do the same as the previous example.

See working on ideone and on CodingGound .

Notification

It's not clear yet whether or not you need notification, you can do this in the class, but you would need to see how this will be consumed. You have some questions about it:

The basic idea is to put in Pessoa an array :

private List<Observer> observers = new ArrayList<Observer>();

One way to subscribe:

public void subscribe(Observer observer) {
    observers.add(observer);       
}

Notifiers:

public void notifyAllObservers() {
    for (Observer observer : observers) {
        observer.update(this);
    }
}

When the property is modified you need to trigger the notification:

public void setNome(String nome) {
    this.nome = nome;
    notifyAllObservers();
}

The Observer interface would look something like this:

public interface ObserverPessoa {
    public void update(Pessoa pessoa);
}

And consumers would look something like this:

public class Consumidor extends Observer {
    @Override
    public void update(Pessoa pessoa) {
        //faz o que quiser aqui 
    }
}

It's an idea, there are several others to do. This I did without thinking much, without knowing the real case. It has advantages and disadvantages. I just posted to give you an idea, does not mean I would do that.

If you need the notification in the list you need to see where the list is and mount something similar to the above in this class. You can have general or specialized notifications.

    
21.10.2016 / 01:12