Minecraft Modding: Singletons, Instances, and Literals

Background


When writing code we frequently have to compare things. In Java there are three common comparison approaches: using the == operator, using the instanceof operator, and using the equals() method.

In this tutorial I try to explain which method is appropriate for each type of comparison you might encounter in Minecraft modding.

As a general rule (applies to all Java programming) remember:

  • The == operator checks that two object instances are the exact same instance. Copies will fail.
  • The instanceof operator checks that two things are the same type (or type hierarchy).
  • The equals() method checks that two object instances have the same value. For custom classes it is up to the programmer to create an equals() method for this if the one inherited from the Object class isn't suitable.

Only Use == For Singletons


A "singleton" is a class where only one instance is ever created. So then it is appropriate to use the == because all references will be to the exact same instance.

In Minecraft, there are several things which are singletons. The most commonly encountered ones are Block and Item instances. For example, all bone items in the game are Items.BONE.

Key Point: For items, don't get confused between Item and ItemStack. Item instances are singletons but ItemStacks are not. But an ItemStack always contains an Item singleton. So:
  • Good: myItemStack.getItem() == Items.BONE is a valid comparison.
  • Good: myItemStack.getItem().equals(Items.BONE) is valid but not as commonly used.
  • Bad: myItemStack.getItem().getUnlocalizedName() == Items.BONE.getUnlocalizedName() is not valid because Java strings always need to be compared by value (using the equals() method).
  • Caution: myItemStack == myStackOfBones is likely not a valid comparison even if myItemStack also contains bones.

Use instanceof For Comparing Non-Singleton Instances


In Minecraft there are several things which are not singletons. The most commonly encountered ones are Entity and TileEntity instances. So:
  • Good: myEntity instanceof EntityZombie is a valid comparison
  • Bad: myEntity.equals(new EntityZombie(world)) is not a valid comparison
  • Bad: myEntity == new EntityZombie(world) is not a valid comparison
  • Caution: myEntity == aZombieEntity may be valid if you're looking for a specific zombie instance (like one you found in an AABB).

Use equals() For Comparing All Values


Warning: In Java, people often make the mistake of using == when when comparing string values. It is dangerous because it sometimes works but isn't guaranteed to work. The reason is that if the two strings were created in different locations in memory the comparison will fail even if they contain the same characters. Always use equals() for string comparisons.

Besides strings, in Minecraft, equals() should be only be used when you have different instances but want to check that everything about them is the same.

For example, ItemStack instances contain information about the Item singleton, the quantity, the metadata and the NBT. So if you want to see if all that is the same then it is valid to use equals(). But you need to use it carefully as it is not valid in many cases and it is entirely dependent on the implementation.

Important: The ItemStack class equals() function was designed to aid HashMap entries and is not good to use for general comparison. So ItemStack class provides some static helper methods to make comparisons about parts of the information. So you should usually use one of the following:
  • ItemStack.isItemStackEqual()
  • ItemStack.isItemEqual()
  • ItemStack.isItemEqualIgnoreDurability()
  • ItemStack.isItemStackEqualUsingNBTShareTag()

Conclusion


It is very important to choose the right comparison method in your coding. Understanding whether you're comparing for exact same instance, for same value, or for same type will greatly help you get it right. Otherwise, you will get some very weird bugs that are hard to understand.

Hope this helps!

No comments:

Post a Comment