Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if a list is changed?

Tags:

java

hash

mutable

I have a List field in a class managed by a little-known proprietary framework.

The annotation @BindMagic is managed by the framework, so the underlying list mutates sometimes: it could be recreated or its elements could change.

class SharedEntity{

  @BindMagic // this annotation does a magic that we cannot control
  private List<Map<String,Object>> values;

  public boolean isChangedSincePreviousCall(){
    // check if "values" have changed since the previous call of this method          
  }
}

I'm agree that it is a poor design, but let's suppose there's no possibility to affect it.

Time to time (not on every mutation) it's needed to check if the list is changed. For instance, I want do it with the method isChangedSincePreviousCall. Probably, something like a hash sum would be good. But I'm curious are there better ways.

What is the best practice to detect if the list is changed?

like image 257
diziaq Avatar asked Jun 19 '19 06:06

diziaq


People also ask

How do you check if the elements of a list are same in Java?

distinct() Let's look at one particular solution making use of the distinct() method. If the count of this stream is smaller or equal to 1, then all the elements are equal and we return true.

How do you check if all items in a list are in another list Java?

The containsAll() method of List interface in Java is used to check if this List contains all of the elements in the specified Collection.

How do you check if something is already in a list Java?

Contains() method uses indexOf() method to determine if a specified element is present in the list or not. So we can also directly use the indexOf() method to check the existence of any supplied element value.


2 Answers

Using a hash is not definitive, because the same hash can be produced from different inputs, albeit with a very small chance.

"Being changed" and "being different" mean different things. Consider an entry in one of the maps that is changed from "A" -> 1 to "A" -> 2 then back to "A" -> 1 again between calls to your method - it was changed but isn't different. I'll assume you mean "different".

Make a copy when checking and compare that with the current state. Assuming that the map values are immutable:

class SharedEntity {

    @BindMagic
    private List<Map<String, Object>> values;
    private List<Map<String, Object>> valuesCopy;

    public boolean isChangedSincePreviousCall() {
        newCopy = new ArrayList<>(values);
        boolean result = !Objects.equals(valuesCopy, newCopy);
        valuesCopy = newCopy;
        return result;
    }
}

If the Map values are (or contain) mutable objects, you'll have to make a deep copy of them when creating the copy.

FYI Objects#equals() returns true if both parameters are null.

like image 185
Bohemian Avatar answered Oct 15 '22 00:10

Bohemian


The problem is probably the Thread accessing the list. It is most likely not supposed to be caught up in some kind of Listener-resolution, which is why there is no proper way of attaching a listener to the list.

However, if you have control over the SharedEntiry class, you could 'hack' into the list's access by using synchronized. However you expressly stated, that the list could be recreated, so I assume the instance stored behind values can actually be replaced.

Basically you have three cases:

1 The values-List is replaced by a new List:

Solve this by making a second reference on List:

private List<Map<String,Object>> valuesPrevious;

Whenever you check for change, check for identity of the lists first. If they are a mismatch, you can be sure the list changed (at least the instance, if not the content).

if (values != valuesPrevious) {
    // handle change.
}

Yes, you still need to periodically check, but an identity-comparison is relatively cheap, and therefore an affordable thread to run in the background.

2 The values-List is replaced by a new List (of a type you did not set):

If that occures, move all values from the API's list to an instance of your observable list (described below), set values to that instance and wait for the next change to occure.

3 The values changed, but the instance is the same:

Solve this by using an ObservableList (if you are implementing in Java10+ https://docs.oracle.com/javase/10/docs/api/javafx/collections/ObservableList.html) or by implementing such a List yourself (probably by extending an existing List type).

Then, that listener only sets a 'dirty' flag, and your method knows that a change occured (and resets the flag).

In any way, my suggestion would be to ensure, that the Thread handling the change only triggers another Thread to handle the change, rather than lock the accessing thread, since I suspect, that your @BindMagic-API has some sort of runtime-relevant factor (for example, it is a network or database related shadow of something). If you simply lock the thread, until you have handled your reaction, you might get weird effects, disconnects or end up accidentally blocking the server you are accessing.

like image 36
TreffnonX Avatar answered Oct 14 '22 22:10

TreffnonX