0

Summary Question: Do different instances of a sub-class inherit the same parent class instance?

I would have thought that two instances of a sub-class also have different parent class instances, but perhaps I am not understanding something about inheritance. Hopefully someone can explain why I am seeing this behavior.

Here is the class where I see the "problem":

@Entity
@Table(name="inventory.parts_fstnr_capscrews")
public class FastenerCapScrew implements PartInterface {
    ...
    private Dimension length;
    private Dimension threadLength;
    ...
    @ManyToOne
    @JoinColumn(name="fk_lengthid")
    @JsonView(View.CommodityPartPOView.class)
    public Dimension getLength() {
        return length;
    }

    public void setLength(Dimension length) {
        this.length = length;
    }

    @ManyToOne
    @JoinColumn(name="fk_threadlengthid")
    @JsonView(View.CommodityPartPOView.class)
    public Dimension getThreadLength() {
        return threadLength;
    }

    public void setThreadLength(Dimension threadLength) {
        this.threadLength = threadLength;
    }

    @Override
    @Transient
    public List<FiltersInterface> getFilters() {
        List<FiltersInterface> filters = new ArrayList<>();
        LOGGER.debug(filters.toString());
        LOGGER.debug(length.toString());
        LOGGER.debug(threadLength.toString());
        if (length!=null) {
            length.setDbColumnName("FK_LengthID");
            filters.add(length);
        }
        LOGGER.debug(filters.toString());
        LOGGER.debug(length.toString());
        LOGGER.debug(threadLength.toString());
        if (threadLength!=null) {
            threadLength.setDbColumnName("FK_ThreadLengthID");
            filters.add(threadLength);
        }
        LOGGER.debug(filters.toString());
        LOGGER.debug(length.toString());
        LOGGER.debug(threadLength.toString());
        return filters;
    }
}

And here is the Dimension class:

@Entity
@Table(name="utilities.dimensions")
public class Dimension extends FiltersExtension implements FiltersDimensionInterface {
    ...
}

And the extended class:

public class FiltersExtension {
    protected String dbColumnName;

    public String getDbColumnName() {
        return dbColumnName;
    }

    public void setDbColumnName(String dbColumnName) {
        this.dbColumnName = dbColumnName;
    }
}

When I call the getFilters() method in FastenersCapScrew, the initial output for length and threadLength is as expected, and both have dbColumnName=null. Then it runs length.setDbColumnName("FK_LengthID");, but both length and threadLength are changed and both show dbColumnName=FK_LengthID. Then it runs threadLength.setDbColumnName("FK_ThreadLengthID");, and again both items are changed so that dbColumnName=FK_ThreadLengthID.

Initially, I thought it must have something to do with the hashCode and equals methods in Dimension, so I changed them to include dbColumnName as below:

@Override
public int hashCode() {
    LOGGER.debug("First compare hashCode with dbColumnName="+this.dbColumnName);
    int hash = 3;
    hash = 37 * hash + this.dimID;
    hash = 37 * hash + Objects.hashCode(this.dbColumnName);
    return hash;
}

@Override
public boolean equals(Object obj) {
    LOGGER.debug("Now compare equals with dbColumnName="+this.dbColumnName);
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final Dimension other = (Dimension) obj;
    if (this.dimID != other.dimID) {
        return false;
    }
    LOGGER.debug("Now compare the column name: "+this.dbColumnName+" vs. "+other.dbColumnName);
    if (!Objects.equals(this.dbColumnName,other.dbColumnName)) {
        return false;
    }
    return true;
}

Can anyone explain to me why changing one Dimension instance changes the other one as well? And what would be the way to fix this so that I do have two totally separate instances? Thanks!

For what it is worth, I am using Java 8 and Spring Boot 2.0.3 with Hibernate, but I don't think that has any bearing on this problem.

2
  • 2
    Where are you initializing those Dimension objects, i.e. how are you calling setLength() and setThreadLength()? Commented Aug 1, 2018 at 23:41
  • @MickMnemonic The Dimension objects are initialized as part of the Spring Data JPA / Hibernate. So, for a given FastenerCapScrew database record, it finds the corresponding Dimension database records and builds the objects. Commented Aug 2, 2018 at 14:19

1 Answer 1

2
+50

Definitely two instances of a sub-class do not share memory for their parent fields. Maybe the cause of this behavior is just a Hibernate's cache. Hibernate does create a new instance of Dimension for one of the fields of FastenerCapScrew class loading it from cache instead. Try to enable logging of SQL-queries to investigate what happens when you call getFilters method.

EDIT The simplest way to get different instances of essentially the same entity is to use defensive copying in setters. As long as you do not apply this technique to collections Hibernate should still be able to perform dirty checking since it compares objects by value. In contrast collections are compared by identity and dirty checking will not work for them.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your feedback. It does appear to be something along the lines of what you suggested. In the case where I noticed the behavior, the length and the threadLength happened to be the same. In that case, I only see one place in the logs where a Dimension object is built. If I use a case where the length and threadLength are different, then the logs show two Dimension objects being built. So how do I force it to use two distinct objects even if initially the value is the same?
Brilliant. I had never heard of defensive copying. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.