Java performance programming, Part 2: The cost of casting

Reduce overhead and execution errors through type-safe code

1 2 Page 2
Page 2 of 2

This illustration shows related abstract base classes implementing some shared behavior, where specialized subclasses are used to extend the shared implementation for a specific instance. The base class for Gorph objects, BaseGorph, tracks the Widget object associated with each Gorph and implements some shared behavior using BaseWidget, the Widget base class.

With this approach, we need to use a cast when a Gorph subclass makes use of some specific feature of the Widget subclass with which it's associated, as shown in the doSubWidgetSomething() call within SubGorph. As a more general issue, this approach also requires that methods included in the base class definition, such as otherGorph(), need to return instances of the base class, even though we may have a design that requires that otherGorph() always returns a Gorph of the same type on which it's used. This makes it necessary to cast the returned result, as shown in the otherGorph() call within SubGorph. We'll first consider ways around the Widget problem, then get back to the more general issue of casting results even when we know the type.

Redundant references

For the first type of casting operation there are some workarounds, though they can be messy. The most direct is to have each Gorph subclass keep an appropriately typed reference to its own Widget. The value of the reference in the base class is duplicated, except that proper typing is added:

    // Gorph subclass using a Widget subclass
    public class SubGorph {
        // the SubWidget associated with this SubGorph
        private SubWidget subWidget;
        ...
    }

This works, but it creates another problem: you must make sure that the two references -- one in the base class, one in the subclass -- are always updated together. We might want to handle this by overriding the base class's set() method:

    // Gorph subclass using a Widget subclass
    public class SubGorph {
        // the SubWidget associated with this SubGorph
        private SubWidget subWidget;
        ...
        // set the SubWidget associated with this SubGorph
        protected void setWidget(SubWidget widget) {
            subWidget = widget;
            super.setWidget(widget);
        }
    }

Unfortunately, the setWidget() call defined in this manner does not actually override the base-class method, because the method signature is different -- this version takes a SubWidget parameter, while the base-class method by the same name takes a BaseWidget parameter. The net effect is that setWidget(SubWidget) is only defined for the SubGorph class, and if setWidget(BaseWidget) were used with a SubGorph instance, it would bypass this method and access the base-class method directly. We can avoid this problem with an actual override of the base-class method:

    // Gorph subclass using a Widget subclass
    public class SubGorph {
        // the SubWidget associated with this SubGorph
        private SubWidget subWidget;
        ...
        // set the SubWidget associated with this SubGorph
        protected void setWidget(SubWidget widget) {
            subWidget = widget;
            super.setWidget(widget);
        }
        // override the base class method to make sure we don't miss any changes
        //  will throw ClassCastException if called with the wrong type of
        // object
        protected void setWidget(BaseWidget widget) {
            setWidget((SubWidget) widget);
        }
    }

This is a pretty messy solution for avoiding some casting, though. It's also a source of potential problems for multithreaded code, since the duplicated references will not be changed in an atomic operation. Solving this multithreading problem would require the use of synchronization, itself a more costly operation than casting (as we'll discuss in a future article in this series). In summary, this approach can be made to work, but it's not very adaptable.

Callouts to subclasses

An alternative approach to the use of redundant references is to simply avoid storing the reference in the base class at all. Instead, the base class can use callout methods to access the value from the subclass when needed:

    public abstract class BaseGorph {
        ...
        // get the Widget associated with this Gorph
        public abstract BaseWidget getBaseWidget();
        ...
    }
    // Gorph subclass using a Widget subclass
    public class SubGorph {
        // the SubWidget associated with this SubGorph
        private SubWidget subWidget;
        ...
        // get the BaseWidget associated with this SubGorph
        public BaseWidget getBaseWidget() {
            return subWidget;
        }
        // get the SubWidget associated with this SubGorph
        public SubWidget getSubWidget() {
            return subWidget;
        }
    }

This approach can lead to awkward class definitions when carried to extremes, but used in moderation it's a fairly clean solution. The main drawback is that it's strictly a one-level approach, applicable only with a single level of concrete subclasses. If you wanted to allow subclasses of SubGorph to have their own associated subclasses of SubWidget, you'd be confronted with the original problem again. Still, this situation can often be avoided in practice, and callouts have the advantage of simplicity while avoiding the problems of redundant references.

Type-specific returns

The more general casting problem in our original code was that base class methods cannot return types specific to a subclass. One route around this problem -- currently only a theoretical route, as we'll discuss in a moment -- would be to define overrides for the base-class methods with type-specific returns, as can be done for virtual methods in C++:

    public abstract class BaseGorph {
        ...
        // return a Gorph with some relation to this Gorph
        //  this will always be the same type as it's called on, but we can only
        //  return an instance of our base class
        public abstract BaseGorph otherGorph() {
            ...
        }
    }
    // Gorph subclass using a Widget subclass
    public class SubGorph {
        ...
        // return a SubGorph with some relation to this SubGorph
        public SubGorph otherGorph() {
            ...
        }
    }

This would allow the base-class method to return the most specific type of which it is aware, while a subclass method could return an even more specific result. Since the result returned by the subclass method would always be an instance of the type returned by the base method, the contract defined by the method would still be valid. Within the subclass (or in other code specifically using an instance of the subclass), the more specific return value would be available without the need for a cast operation.

Unfortunately, the Java language definition does not currently allow this type of (intended) override. Bug 4144488 (see Resources) in the Java Developer Connection database addresses this issue with the specific example of the clone() method, one of the cases in which it would be especially useful to use type-specific overrides. It doesn't sound like we're likely to see this fixed any time soon, even though it would apparently only require a compiler change -- but adding your vote for it couldn't hurt!

Lacking the ability to use type-specific overrides, the only way to avoid casting the result in this situation is to provide a separate method specific to the subclass, implementing the method defined in the base class using this new specific method:

    // Gorph subclass using a Widget subclass
    public class SubGorph {
        ...
        // return a SubGorph with some relation to this SubGorph
        public SubGorph otherSubGorph() {
            ...
        }
        // implement the generic method defined in the base class
        public Gorph otherGorph() {
            return otherSubGorph();
        }
    }

This adds clutter and confusion, but at least allows for type-specific usage without the need for casting.

Conclusion

So far we've covered the basics of object and reference types in Java, along with the related issues of casting and polymorphism. After a detailed look at the performance costs of some of these operations, we've also discussed ways of structuring code to reduce the need for casting in one situation that often encourages it -- the linkage between subclasses and base classes.

Hopefully, this discussion of different approaches of achieving this goal -- and the limitations of some of these approaches -- will get you to think about ways to eliminate some of the unnecessary casting in your code and clean up the structure at the same time. Properly applied, structural techniques that reduce casting produce code that not only performs better than the alternatives, but is also cleaner and less prone to runtime errors.

Next month, we'll look into casting as it relates to the different ways of handling collections in Java. Starting with the generic collections support included in the original Java class libraries, we'll discuss the enhancements provided with Java 2 and the most efficient and effective ways of using these standard implementations. We'll also discuss the changes that may be coming in this area with the proposed addition of support for generic types in Java. Finally, we'll look at using custom type-safe collections, especially as applied to primitive types where we can eliminate both casting and object-creation overhead.

Don't forget to check back then for the full scoop on this aspect of Java performance!

Dennis Sosnoski is a software professional with over 25 years experience in developing for a wide range of platforms and application areas. An early adoptee of C++, he was just as quick to convert to using Java when it became available, and for the last three years it's been his preferred development platform. Dennis is the president and senior consultant of Sosnoski Software Solutions Inc., a Java consulting and contract software development firm collocated with another well-known software company in Redmond, Wash.

Learn more about this topic

Related:
1 2 Page 2
Page 2 of 2