How is possible the Map will find the right element, when the HasCode() of that element has changed? - spring

From my previous question: Hibernate: Cannot fetch data back to Map<>, I was getting NullPointerException after I tried to fetch data back. I though the reason was the primary key (when added to Map as put(K,V), the primary key was null, but after JPA persist, it created the primary key and thus changed the HashMap()). I had this equals and hashCode:
User.java:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(id, user.id) && Objects.equals(username, user.username) && Objects.equals(about, user.about) && Objects.equals(friendships, user.friendships) && Objects.equals(posts, user.posts);
}
#Override
public int hashCode() {
return Objects.hash(id, username, about, friendships, posts);
}
-> I used all fields in the calculation of hash. That made the NullPointerException BUT not because of id (primary key), but because of collections involved in the hash (friends and posts). So I changed both functions to use only database equality:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (id == null) return false;
if (!(o instanceof User)) return false;
User user = (User) o;
return this.id.equals(user.getId());
}
#Override
public int hashCode() {
return id == null ? System.identityHashCode(this) :
id.hashCode();
So now only the id field is involved in the hash. Now, it didn't give me NullPointerException for fetched data. I used this code to test it:
(from User.java):
public void addFriend(User friend){
Friendship friendship = new Friendship();
friendship.setOwner(this);
friendship.setFriend(friend);
this.friendships.put(friend, friendship);
}
DemoApplication.java:
#Bean
public CommandLineRunner dataLoader(UserRepository userRepo, FriendshipRepository friendshipRepo){
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
User f1 = new User("friend1");
User f2 = new User("friend2");
User u1 = new User("user1");
System.out.println(f1);
System.out.println(f1.hashCode());
u1.addFriend(f1);
u1.addFriend(f2);
userRepo.save(u1);
User fetchedUser = userRepo.findByUsername("user1");
System.out.println(fetchedUser.getFriendships().get(f1).getFriend());
System.out.println(fetchedUser.getFriendships().get(f1).getFriend().hashCode());
}
};
}
You can see I am
puting the f1 User into friendship of user1 (owner of the friendship). The time when the f1.getId() == null
saving the user1. The time when the f1 id gets assign its primary key value by Hibernate (because the friendship relation is Cascade.All so including the persisting)
Fetching the f1 User back by geting it from the Map, which does the look-up with the hashCode, which is now broken, because the f1.getId() != null.
But even then, I got the right element. The output:
User{id=null, username='friend1', about='null', friendships={}, posts=[]}
-935581894
...
User{id=3, username='friend1', about='null', friendships={}, posts=[]}
3
As you can see: the id is null, then 3 and the hashCode is -935581894, then 3... So how is possible I was able to get the right element?

Not all Map implementation use the hashCode (for example a TreeMap implementation do not use it, and rather uses a Comparator to sort entries into a tree).
So i would first check that hibernate is not replacing the field :
private Map<User, Friendship> friendships = new HashMap<>();
with its own implementation of Map.
Then, even if hibernate keeps the HashMap, and the hashcode of the object changed, you might be lucky and both old and new hashcodes gives the same bucket of the hashmap.
As the object is the same (the hibernate session garantees that), the equals used to find the object in the bucket will work. (if the bucket has more than 8 elements, instead of the bucket being a linked list, it will be a b-tree ordered on hashcode, in that case it won't find your entry, but the map seems to have only 2-3 elements so it can't be the case).

Now I understood your question.
Looking at the Map documentation we read the following:
Note: great care must be exercised if mutable objects are used as map
keys. The behavior of a map is not specified if the value of an object
is changed in a manner that affects equals comparisons while the
object is a key in the map.
It looks like there is no definitive answer for this and as #Thierry already said it seems that you just got lucky. The key takeaway is "do not use mutable objects as Map keys".

Related

HashMap null check in Merge Operation

Why HashMap merge is doing null check on value. HashMap supports null key and null values.So can some one please tell why null check on merge is required?
#Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null)
throw new NullPointerException();
if (remappingFunction == null)
throw new NullPointerException();
Due to this I am unable to use Collectors.toMap(Function.identity(), this::get) to collect values in a Map
The behavior is mandated by the Map.merge contract:
Throws:
…
NullPointerException - if the specified key is null and this map does not support null keys or the value or remappingFunction is null
Note that using Map.merge for Collectors.toMap without a merge function is an implementation detail; it not only disallows null values, it does not provide the desired behavior for reporting duplicate keys, the Java 8 implementation wrongly reports one of the two values as key when there are duplicate keys.
In Java 9, the implementation has been completely rewritten, it does not use Map.merge anymore. But the new implementation is behavioral compatible, now having code explicitly throwing when the value is null. So the behavior of Collectors.toMap not accepting null values has been fixed in the code and is not an artifact of using Map.merge anymore. (Still speaking of the toMap collector without a merge function only.)
Unfortunately, the documentation does not tell.
Because internally for Collectors.toMap, Map#merge is used - you can't really do anything about it. Using the static Collectors.toMap is not an option (which by the way is documented to throw a NullPointerException).
But spinning a custom collector to be able to do what you want (which you have not shown) is not that complicated, here is an example:
Map<Integer, Integer> result = Arrays.asList(null, 1, 2, 3)
.stream()
.collect(
HashMap::new,
(map, i) -> {
map.put(i, i);
},
HashMap::putAll);
As a workaround for mentioned problems with null values in toMap and merge
you can try to use a custom collector in the following manner:
public static <T, R> Map<T, R> mergeTwoMaps(final Map<T, R> map1,
final Map<T, R> map2,
final BinaryOperator<R> mergeFunction) {
return Stream.of(map1, map2).flatMap(map -> map.entrySet().stream())
.collect(HashMap::new,
(accumulator, entry) -> {
R value = accumulator.containsKey(entry.getKey())
? mergeFunction.apply(accumulator.get(entry.getKey()), entry.getValue())
: entry.getValue();
accumulator.put(entry.getKey(), value);
},
HashMap::putAll);
}

Tranversing and filtering a Set comparing its objects' getters to an Array using Stream

I've got some working, inelegant code here:
The custom object is:
public class Person {
private int id;
public getId() { return this.id }
}
And I have a Class containing a Set<Person> allPersons containing all available subjects. I want to extract a new Set<Person> based upon one or more ID's of my choosing. I've written something which works using a nested enhanced for loop, but it strikes me as inefficient and will make a lot of unnecessary comparisons. I am getting used to working with Java 8, but can't quite figure out how to compare the Set against an Array. Here is my working, but verbose code:
public class MyProgram {
private Set<Person> allPersons; // contains 100 people with Ids 1-100
public Set<Person> getPersonById(int[] ids) {
Set<Person> personSet = new HashSet<>() //or any type of set
for (int i : ids) {
for (Person p : allPersons) {
if (p.getId() == i) {
personSet.add(p);
}
}
}
return personSet;
}
}
And to get my result, I'd call something along the lines of:
Set<Person> resultSet = getPersonById(int[] intArray = {2, 56, 66});
//resultSet would then contain 3 people with the corresponding ID
My question is how would i convert the getPersonById method to something using which streams allPersons and finds the ID match of any one of the ints in its parameter array? I thought of some filter operation, but since the parameter is an array, I can't get it to take just the one I want only.
The working answer to this is:
return allPersons.stream()
.filter(p -> (Arrays.stream(ids).anyMatch(i -> i == p.getId())) )
.collect(Collectors.toSet());
However, using the bottom half of #Flown's suggestion and if the program was designed to have a Map - it would also work (and work much more efficiently)
As you said, you can introduce a Stream::filter step using a Stream::anyMatch operation.
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
if (ids.length == 0) {
return Collections.emptySet();
}
return allPersons.stream()
.filter(p -> IntStream.of(ids).anyMatch(i -> i == p.getId()))
.collect(Collectors.toSet());
}
If the method is called more often, then it would be a good idea to map each Person to its id having a Map<Integer, Person>. The advantage is, that the lookup is much faster than iterating over the whole set of Person.Then your algorithm may look like this:
private Map<Integer, Person> idMapping;
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
return IntStream.of(ids)
.filter(idMapping::containsKey)
.mapToObj(idMapping::get)
.collect(Collectors.toSet());
}

Parse: how to determine if any given user is anonymous?

If we are dealing with the current user, it is easy by doing:
ParseAnonymousUtils.isLinked(ParseUser.getCurrentUser());
But what if I just have bunch of usernames? How can I determine which ones are anonymous? I have tried querying the User table like this:
ParseQuery<ParseUser> queryUser = new ParseUser().getQuery();
queryUser.whereEqualTo("User", usernameString);
queryUser.setLimit(1);
queryUser.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> users, ParseException e) {
if (e == null) {
if (users != null) {
for (ParseUser user : users) {
// do a check
}
}
}
}
}
And tried different ways to do the check but nothing seems to work. For example:
ParseAnonymousUtils.isLinked(user)
always returns false. I have also tried accessing the authData as String:
String authData = user.getString("authData");
if (authData == null) Log.i("authData", "null");
else Log.i("authData", authData);
but always get null. I have also tried casting it as JSONObject hoping to see if I can determine if an anonymous field exists in the JSON:
JSONObject authDataJson = (JSONObject) user.get("authData");
but also always get null.
How can I determine if any given username belongs to an anonymous user? Thanks

Validation Error: value is not valid when using a custom converter [duplicate]

This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 6 years ago.
NetBeans 7.1.1 JSF2.1
When using converter="convK" attribute in h:selectManyCheckBox it all works well. But I tried to use #FacesConverter(forClass=className.class) form and it keeps giving me "Validation is not Valid" errors. I've tried changing it to forClass=packageName.className.class but no help.
This is converter:
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
#FacesConverter( "convK")
public class KorisnikConverter implements Converter{
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value==null) return value;
if (value.isEmpty()) return value;
for (int i=0; i<Arhiva.getSviKor().size(); i++) {
if (Arhiva.getSviKor().get(i).getUsername().equals(value)) {
return Arhiva.getSviKor().get(i);
}
}
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value==null) return "";
if (value instanceof Korisnik) return ((Korisnik)value).getUsername();
return "";
}
}
I have a class called Korisnik which has couple text fields, username is unique one. In my main managing bean I have couple arrayList of those objects. Goal is to use selectManyCheckBox to chose just some of users and put them in a separate arraylist for some other uses. I wanted to push entire objects around (I can always easily work with strings and have object creation and management in my controler beans but wanted to try custom converters to get selectItems to work with objects)
In my class I've overridden equals and hashCode (as there is a lot of talk about custom converters giving blah blah Validation is not valid errors).
#Override
public boolean equals (Object obj) {
if (obj==null) return false;
if (!(obj instanceof Korisnik)) return false;
Korisnik k = (Korisnik)obj;
return (this.username==k.username);
}
#Override
public int hashCode() {
return this.username.hashCode();
}
Edit. When I'm using it as named converter and using said converter only in that one instance with selectManyCheckbox it works fine even without overriding equals and hashCode.
This is checkbox code
<h:selectManyCheckbox value="#{kontrolg.izabrAut}" layout="pageDirection" converter="convK" >
<f:selectItems value="#{kontrolg.moguciAut}" var="it" itemLabel="# {it.ime} #{it.prezime}" itemValue="#{it}"/>
</h:selectManyCheckbox>
What I don't know is whether I'm failing to properly use forClass="whatever" in converter annotation or my converter actually works ok with that one selectManyCheckbox, but when I specify it in forClass form it gets used for all instances of that object and causes some other code that worked nice before adding custom converters to now give "validation is not valid" error?
The value is not valid validation error will be thrown when the equals() method on the selected item has not returned true for any of the available items.
And indeed, your equals() method is broken. The following line is wrong:
return (this.username==k.username);
I'll assume that username is a String, which is an Object. The == compares Objects by reference, not by their value. In other words, when performing == on two Objects, you're basically testing if they point to exactly the same instance. You're not checking if they represent the same value (say, the Object instance's internal representation). You should be using the Object's equals() method instead, the String#equals() method, here's an extract of relevance from its javadoc:
equals
public boolean equals(Object anObject)
Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object.
The == is only applicable when comparing primitives like boolean, int, long, etc or when testing for null.
So, to fix your problem, replace the wrong line by the following line:
return username.equals(k.username);
Or, when they can possibly be null:
return (username == null) ? (k.username == null) : username.equals(k.username);
See also:
Right way to implement equals contract

Linq and retrieving primary key

This code works, but i dont understand why. With DeferredLoadingEnabld = false, I would expect it not to return the primary key. Can someone explain what I am missing?
public void SaveOrder (Order order)
{
using (DataContext dc= new DataContext)
{
dc.DeferredLoadingEnabled = false;
...
order.Total= total;
dc.order.InsertOnSubmit(order);
dc.SubmitChanges();
}
}
IN ORDER SERVICE:
public void ServiceSaveOrder(Order order)
{
Order order= new Order();
SaveOrder(order);
Print(order.ID); //ID= unique primary key
}
DeferredLoadingEnabled property is simply used for populating other relationships across foreign keys and not for returning IDs back after inserts. Your keys will always be populated. With DeferredLoadingEnabled set to true, any parent or child relationships will not automatically populated.
More information is available at the MSDN article.

Resources