1.15.2 Problem with hashcode

Discussion in 'Spigot Plugin Development' started by eirikh1996, Mar 5, 2020.

  1. Hello. I am making a plugin which contains a custom object storing the world name as a string and chunk coordinates as integers

    Here is how this object look like:
    Code (Java):
    package io.github.eirikh1996.nationcraft.core.territory;

    import io.github.eirikh1996.nationcraft.api.nation.Nation;
    import io.github.eirikh1996.nationcraft.api.nation.NationManager;
    import io.github.eirikh1996.nationcraft.api.objects.NCLocation;
    import io.github.eirikh1996.nationcraft.api.objects.NCVector;
    import io.github.eirikh1996.nationcraft.api.settlement.Settlement;
    import io.github.eirikh1996.nationcraft.api.settlement.SettlementManager;
    import io.github.eirikh1996.nationcraft.core.utils.Direction;
    import org.jetbrains.annotations.NotNull;
    import org.jetbrains.annotations.Nullable;

    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Objects;
    import java.util.UUID;

    public final class Territory implements Comparable<Territory> {
        private final int x, z;
        private final String world;

        public Territory(String world, int x, int z){
            this.world = world;
            this.x = x;
            this.z = z;
        }

        /**
         * Gets the x value of the territory chunk
         * @return x chunk coordinate
         */

        public int getX() {
            return x;
        }

        /**
         * Gets the z value of the territory chunk
         * @return z chunk coordinate
         */

        public int getZ() {
            return z;
        }

        /**
         * Gets the world this territory chunk is located in
         * @return the world this territory chunk is located in
         */

        @NotNull
        public String getWorld() {
            return world;
        }

        public boolean contains(String world, int x, int z) {
            return this.world.equals(world) && x >> 4 == this.x && z >> 4 == this.z;
        }

        public boolean contains(NCLocation loc) {
            return contains(loc.getWorld(), loc.getBlockX(), loc.getBlockZ());
        }

        public boolean matches(@NotNull Territory territory){
            return territory == this;
        }

        /**
         * Returns a territory chunk at the relative offset of this territory chunk
         * @param dx distance in x direction
         * @param dz distance in z direction
         * @return territory chunk at given relative offset
         */

        public Territory getRelative(int dx, int dz){
            return new Territory(world, x + dx, z + dz);
        }

        public Territory getRelative(Direction direction){
            if (direction == null){
                throw new IllegalArgumentException("Direction parameter cannot be null!");
            }
            switch (direction){
                case NORTH:
                    return new Territory(world, x, z - 1);
                case NORTH_WEST:
                    return new Territory(world, x - 1, z - 1);
                case WEST:
                    return new Territory(world, x - 1, z);
                case SOUTH_WEST:
                    return new Territory(world, x - 1, z + 1);
                case SOUTH:
                    return new Territory(world, x, z + 1);
                case SOUTH_EAST:
                    return new Territory(world, x + 1, z + 1);
                case EAST:
                    return new Territory(world, x + 1, z);
                case NORTH_EAST:
                    return new Territory(world, x + 1, z - 1);
            }
            return null;
        }

        @Override
        public boolean equals(@Nullable Object obj) {
            if (!(obj instanceof Territory)){
                return false;
            }
            Territory other = (Territory) obj;

            return other.getWorld().equals(getWorld()) && other.getX() == getX() && other.getZ() == getZ();
        }

        /**
         * Gets the nation that have laid claim to this territory, or null if no nation has claimed it
         * @return the nation holding the territory. Null if unclaimed
         */


        @Nullable
        public Nation getNation() {
            return NationManager.getInstance().getNationAt(this);
        }

        /**
         * Gets the settlement that have laid claim to this territory, or null if no nation has claimed it
         * @return the settlement holding the territory. Null if unclaimed
         */


        @Nullable
        public Settlement getSettlement() {
            return SettlementManager.getInstance().getSettlementAt(this);
        }

        /**
         * Gets the territory chunks adjacent and surrounding this territory
         * @return surrounding and adjacent territories
         */


        @NotNull
        public Collection<Territory> getSurroundings() {
            final HashSet<Territory> surroundings = new HashSet<>();
            for (int x = this.x - 1; x <= this.x - 1; x += 2) {
                surroundings.add(new Territory(world, x, z));
            }
            for (int z = this.z - 1; z <= this.z - 1; z += 2) {
                surroundings.add(new Territory(world, x, z));
            }
            return surroundings;
        }

        @Override
        public int hashCode() {
            return Objects.hash(world,x,z);
        }

        @NotNull
        public final NCVector toVector(){
            return new NCVector(x,0,z);
        }

        @Override
        public String toString() {
            return "[" + world.toString() + ", " + x + ", " + z + "]";
        }

        @Override
        public int compareTo(@NotNull Territory o) {
            int i = 0;
            i += (getX() - o.getX());
            i += (getZ() - o.getZ());
            i += (getWorld().compareTo(o.getWorld()));
            return i;
        }
    }
     

    As one can see, it overrides both the equals and hashcode methods. However, I do have a problem

    When Spigot loads the plugin, it will also load instances of Territory from its representation by world name, chunk x and chunk z coordinates stored in configuration files. In another part of the code, run when a command is executed, it will create a new instance with the name of the world the player is in and the x and z coordinate of the chunk the player is in. And here is when the problem is: This instance created is checked in a contains() method in a hashmap containing these objects, And where contains should be true due to containing a Territory instance with the same world, x and z values, it returns false instead, causing key functions in the plugin to break

    Any idea what is causing this?
     
  2. Values in Map (HashMap respectively) shouldn't necessarily implement equals() and hashCode(). The problem is the Map key, that needs equals() and hashCode() implemented.