Today I spent some time debugging an issue with hibernate, simplified example would look like:
Map<Cat, Owner> catsMap = new HashMap();
List<Owner> owners = ownerRepo.getOwners();
for (Owner owner : owners) {
// cat is Lazy, according to its nature :)
catsMap.put(owner.getCat(), owner);
}
Cat cat = catRepo.findOne("meow");
Owner meowOwner = catsMap.get(cat);
at this moment meowOwner is null because it is not found in catsMap keyset. It took sometime to figure out why because in debug window I see that the Cat with name 'meow' exists in keyset of catsMap, moreover, if I write an expression
catsMap.keySet().iterator().next().equals(cat)
it returns true, hashcodes are the same, same values, though
catsMap.get(cat)
still returns null in the same expressions window.
At last I called
catsMap.keySet().iterator().next().getClass()
and finally found out that it is long.path.to.package.Cat_$$_jvstaea_41, so it is a proxy and equals fails on the step when it checks class equality.
The solution is, of course, obvious, but the question is why do I have
catsMap.keySet().iterator().next().equals(cat)
returning true? I tried also reversed case
cat.equals(catsMap.keySet().iterator().next())
and this one returns false, which is breaking the equals() convention of transitivity.
PS: in all the examples I assume that currently there is only one cat and one owner in DB
Cat cat = catRepo.findOne("meow") should return the same instance, unless your Map is outside of the initial transaction. If you want to store an Entity outside of a transaction, make sure to unproxy it before storing it.
public T unproxy(T proxied)
{
T entity = proxied;
if (entity instanceof HibernateProxy) {
Hibernate.initialize(entity);
entity = (T) ((HibernateProxy) entity)
.getHibernateLazyInitializer()
.getImplementation();
}
return entity;
}
You have to make sure you use the right syntax for your equals and hashCode overrides. This is an example of implementation:
#Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof MyEntityClass))
return false;
MyEntityClass other = (MyEntityClass) obj;
return Objects.equals(getId(), other.getId());
}
#Override
public int hashCode() {
return Objects.hash(getId());
}
Note: Do not use the fields directly this.id, prefer the getter to allow for Hibernate's proxy to resolve the entity when necessary. Also, prefer instanceof to getClass() != obj.getClass(), the first will handle implementations and extends correctly, not the second.
Related
please is there a way how to make the code more concise -
to avoid repeating similar/same actions -
I have a method doing object validations, but most parameters
are similar/same, like using some sort of lambda expression for that,
but objects are not from the same tree.
My use-case is like this:
validate.( car );
and somewhere else I do:
validate.( person );
Right now I am doing the validation like this:
public boolean validate( Object obj ) {
if ( obj instanceof Car ) {
Car car = (Car) obj;
if ( car.getAge() <= 0 ) return false;
// many other checks which are repeated below
} else if ( obj instanceof Person ) {
Person person = (Person) obj;
if ( person.getAge() <= 0 ) return false;
// many other check which are repeating those above
}
// here I would like to do checks for both objects, but objects are from different roots
return true;
}
You can use method overloading :
public boolean validate(Car car) {
if ( car.getAge() <= 0 ) return false;
return validate((Master)car);
}
public boolean validate(Person p) {
if ( p.getAge() <= 0 ) return false;
return validate((Master)p);
}
public boolean validate(Master m) {
// common validations
return true;
}
Master is the common parent of Person and Car (I assume there is a common parent interface/class).
"Do not"s:
First of all, I would strictly suggest you to try your best to avoid designing your method as you are doing it now;
Person and Car have nothing in common from the logical, modelling, or conceptual perspective. Nor they do share any similar characteristics in the real life;
Do not accept Object as an argument in your method. You will have hard times of managing your bugs and/or maintaining unpredictable behaviour.
"Do"s:
Define your method by following Single Responsibility Principle;
Consider from your model object perspective - what your method should be as a behaviour? what ONE problem it should solve?
Try to separate concerns in their respective classes/files/block-units and try not to overlap them.
If you want to your method to behave based on what is the actual instance of the argument, best way is to use instanceof checks.
What you can do instead, however, is to design some proper inheritance and accept a supertype, as a method argument. Then polymorphism would kick in and you will only have one logic in your method.
public class Bus{
private int seats;
private List<People> currentPeople;
public Bus(int seats){
this.seats = seats;
}
public void passengers(List<People> boarders, List<People> deboarders){
this.currentPeople.remove(deboarders);
this.currentPeople.add(boarders);
}
I'm trying to create a list of people on the bus. I'm trying to remove deboarders(alighting passengers) from the list and add the boarders(boarding passengers). But Java won't let me add the boarders to the currentPeople list. People is a class which stores the name and ticket number of the passanger. Any help on this? I've been trying to solve this for hours.
To add a collection of objects to a list you need to use addAll:
this.currentPeople.addAll(boarders);
To remove a collection of objects from a list you need to use removeAll:
this.currentPeople.removeAll(deboarders);
Be careful, in order to compare and remove the right elements from the list, removeAll uses equals method. So you should implement equals method in your People class.
EDIT:
equals method for People class:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
People people = (People) o;
if (ticketNumber != people.ticketNumber) return false;
return name != null ? name.equals(people.name) : people.name == null;
}
Don't forget to initialize currentPeople list, otherwise you will get NullPointerException.
Try removeAll() and addAll() methods because you are passing a Collection as a parameter.
Do not forget " ; " at the end of the line inside your constructor.
this.currentPeople.removeAll(deboarders);
this.currentPeople.addAll(boarders);
Change add to addAll and likewise for remove.
As Federico points out in comments, assuming People has equals method implemented.
I want to find a LegalEntity object in an ArrayList. The object can possibly be a different instance. I'm only interested in whether they represent the same value, i.e. they have the same primary key. All LegalEntity instances are created from database values by EJB:
List<LegalEntity> allLegalEntities = myEJB.getLegalEntityfindAll());
LegalEntity currentLegalEntity = myEJB.getLegalEntityfindById(123L);
My first naive idea never finds matches:
if (allLegalEntities.contains(currentLegalEntity)) {
}
I then thought that perhaps I need to create my own equals() method:
public boolean equals(LegalEntity other) {
return legalEntityId.equals(other.legalEntityId);
}
But this method is not even being invoked. Is there a way to find an object in a list that doesn't involve looping?
I'm learning Java so it might easily be some foolish misunderstanding on my side.
Your approach is correct, but you need to override the method equals that accepts an Object:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LegalEntity other = (LegalEntity) obj;
// check if equals based one some properties
}
However you also need to override hashCode:
#Override
public int hashCode() {
// return a unique int
}
So this might not be the easiest solution.
Another approach is to use filter:
LegalEntity myLegalEntity = myEJB.getLegalEntityfindAll().stream()
.filter(legalEntity -> legalEntity.getProperty().equals("someting"))
.findAny()
.orElse(null);
More info here
If you're using Java 8 you can use streams:
List<LegalEntity> allLegalEntities = myEJB.getLegalEntityfindAll());
LegalEntity currentLegalEntity = allLegalEntities.stream().filter(entity -> entity.getId() == 123L).findFirst();
Ok, see this User class
public class User{
private String userName;
public User(String userName){
this.userName=userName;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof User))
return false;
User user = (User) obj;
return user.userName.equals(this.userName);
}
#Override
public int hashCode() {
return userName.hashCode() ;
}
}
Now we got a Map
Map<User, List<String>> map=new ConcurrentHashMap<User, List<String>>();
now wee got these codes:
User tom=new User("Tom");
List<String> tomList=new ArrayList<String>();
tomList.add("Test");
User mary=new User("Mary");
map.put(tom,tomList);
map.put(mary,new ArrayList<String>());
Now, we create a new object Tom
User tom2=new User("Tom");
List<String> tomList=map.get(tom2);
My question is that tomList is null or not null
If it is null, then can we make it not null. That means instead of try to find the exact object we can just create object with same userName and we can do map.get(user);
Map keys are matched using key.equals(k).
Or formally:
If the Map already contains a mapping from a key 'k' to a value 'v' such that key.equals(k), then get(key) method returns v, otherwise it returns null.
In your case:
User.equals() matches for userName String property, so map.get(tom2) is not null.
All User Objects named "Tom" will be considered as having the same key.
The behaviur you are asking is actually controlled by your code via the equals() (and hashcode()) function. Let's take a look at the equals():
#Override
public boolean equals(Object obj) {
//two references are considered equal to each other,
//if they are pointing to the same Object
//This is the default equals implementation.
if (this == obj)
return true;
//Two objects cannot be equal if even their types do not match.
if (!(obj instanceof User))
return false;
//If their types matched, let's start comparing them field-by-field.
//If all fields match, then (altough they are different objects
//in memory) they represent the same real-world entity
//in this case user Tom.
User user = (User) obj;
return user.userName.equals(this.userName);
}
By removing the last part from your equals() and the corresponding part from hashcode() you can change the behaviur of the map to expect a reference to the very same object.
while working with my application I've encountered a problem while trying to remove object from the java collection ( Set pulled from database with EclipseLink ).
The object which I want to remove in an entity class which has overriden equals method.
I've even checked whether any of the objects in the collection is eqauls to the one I want to remove with the following code:
for(AlbumEntity entity : deleteGroup.getAlbums()){
System.out.println("VAL: " + deleteAlbum.equals(entity));
}
In this case, one of the values returned is true. However, if I do:
boolean result = deleteGroup.getAlbums().remove(deleteAlbum);
the value of result is false and the size of collection stays the same.
Thanks for your help in advance
edit:
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof AlbumEntity)) {
return false;
}
AlbumEntity other = (AlbumEntity) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
A few possibilities:
1) There is a problem with the implementation of id's equals or hashCode methods. In this case, you could have id1.equals(id2) but id1.hashCode() != id2.hashCode(). This would cause inconsistency between equals and hashCode() for the album objects and could cause the symptoms you're seeing.
2) The id for one or more albums changes at some point after the for loop that checks deleteAlbum.equals(entity) for each album in the Set. If an id changes for an album, the remove() method may not be able to find it. An id could change from null to some non null number if got saved to the database - EclipseLink might do this for you without you explicitly asking it to.
3) Because of EclipseLink's meddling, deleteGroup might not actually be a HashSet when you run your code. The docs for EclipseLink suggest it will give you an "indirection object" instead of the java.util.Set (or java.util.HashSet I presume) declared in your class, depending on how it is configured. In that case, the contains and remove methods might not do what you expect them to.
See Overriding equals and hashCode in Java for more details on these and other possible problems involving equals and hashCode, which can cause bizarre behavior with Sets.
Okay let's try a bit of testing:
1:
Iterator<AlbumEntity> it = deleteGroup.getAlbums().iterator();
while(it.hasNext()){
AlbumEntity entity = it.next();
Assert.assertTrue(deleteGroup.getAlbums().contains(entity))
}
Does this test run successfully?