Home > Articles > Programming > Java

Java Binary Compatibility Part 3: The Technical Details

  • Print
  • + Share This
Joshua Engel explains how Java classes can change and still work together without recompiling other classes. This can save you a lot of time in development, save megabytes of distribution, and help you understand how to evolve code without breaking dependencies.
Like this article? We recommend

To understand in detail how binary compatibility works, it helps to understand exactly how Java programs are represented in class files, and what class files mean—since the class files are the actual implementation of your Java program. This installment looks at how Java class files work, and in particular how late binding is implemented.

Late Binding by Example

Here's a piece of code to figure out what wine I should drink with dinner tonight, and at what temperature the wine should be served (so that I can begin chilling it on time). Let's start by assuming some wine-oriented classes:

class Sommelier {
  Wine recommend(String meal) { ... }

abstract class Wine {
  // Make a recommendation for appropriate wine serving temperature
  abstract float temperature();

class RedWine extends Wine {
  // Reds in general are served warmer than whites
  float temperature() { return 63; }

class WhiteWine extends Wine {
  float temperature() { return 47; }

// And so on for a variety of wines
class Bordeaux extends RedWine {
  float temperature() { return 64; }

class Riesling extends WhiteWine {
  // Inherit the temperature from the WhiteWine class
We'll use these classes to make some recommendations for dinner:
void example1() {
  Wine wine = sommelier.recommend("duck");
  float temp = wine.temperature();

In the second call in example1, the only thing we know about the wine object is that it's a Wine. It could be a Bordeaux or a Riesling or something else. We know it can't be an instance Wine itself, since that class is abstract.

When you compile the call to wine.temperature(), the class file contains a line that looks like this:

invokevirtual Wine/temperature ()F


Notice that the class file contains a binary representation of this code, rather than this actual text. There is no one standard textual representation of Java class files. I'm using one called Oolong. You can read more about it in my book Programming for the Java Virtual Machine.

This is a method call—a regular (virtual) method call, as opposed to a static method call—that calls the temperature on a Wine object. The argument on the right, ()F, is called the signature. This signature indicates a method with no arguments (that's what the empty parentheses mean) and returns a floating-point value (the F).

When the Java Virtual Machine (JVM) reaches this statement, it won't necessarily invoke the definition of temperature in Wine. In this case, it couldn't anyway, because the method is abstract. Rather, the JVM looks at the class of the object and seeks a method with the exact name and signature given in the invokevirtual statement. If none exists, it looks at the superclass, the super-superclass, and so on until an implementation is found.

Mostly this is just like Java method calls. However, this is somewhat simpler, in that it's looking for a simple string match on the name and signature. It doesn't consider subtypes and supertypes of the classes mentioned; only an exact match will do. In this example, the method signature only mentions the built-in type float, so there wouldn't be subclasses to consider, but we'll get to a more complicated example shortly.

In this case, if the object is a Bordeaux, the JVM calls the temperature()F method in that class, which returns 64. If the object is a Riesling, it seeks the method and doesn't find one, so it looks in the WhiteWine class. It finds a temperature()F method there and invokes it, which returns 47.

  • + Share This
  • 🔖 Save To Your Account