2

I have a class library, that i didn't write, which defines several classes and subclasses, which have static methods. A very much stripped down example:

public class Vehicle {
    static String getName() { return "unknown"; }
}

public class Car extends Vehicle {
    static String getName() { return "car"; }
}

public class Train extends Vehicle {
    static String getName() { return "train"; }
}

Now, i have an object, which is a Vehicle, may be a Car or a Train, and want to call it's getName() function. Again, very much stripped down:

public class SMCTest {
    public static void main(String[] args) {
        Vehicle vehicle=new Car();
        System.out.println(vehicle.getName());
    }
}

This prints "unknown", not "car", as the JVM doesn't need, or use, the object to call a static method, it just uses the class.

If that was my code, i'd rewrite vehicle library to use singletons, and non-static methods, but as it isn't my code, i'd rather not touch it.

Is there any way to call the static method of the "real" class of the object, preferable without using reflection? If it helps, i could change the vehicle in the above example to a Class <? extends Vehicle> variable and use that, but i don't see how that helps me to avoid reflection.

Guntram Blohm
  • 9,667
  • 2
  • 24
  • 31
  • Yes, there is. You can simple downcast the object `vehicle` since you already know its concrete class: `Car vehicle = new Car();`. Then `vehicle.getName()` would return *car*. Nevertheless, doing this would result in a warning since you shouldn't access a static method through an instance. – António Ribeiro Mar 06 '16 at 13:16
  • 5
    You seem to want a static method to behave in a polymorphic way. That is not possible. Using the object to call a static method shows a design problem that should be solved. The static method should be an instance method. – JB Nizet Mar 06 '16 at 13:19
  • @aribeiro: In this stripped down example, i know the object is, really, a Car. In what i want to do in the real world, i get an object passed from somewhere else, and i do NOT know what it really is. So i can't just use the Car class. I wanted to keep the example as short/easy as possible. – Guntram Blohm Mar 06 '16 at 13:25
  • @JBNizet: I know there is a design problem. But as the "Vehicle" class library is not mine, i'd rather circumvent it, than spend weeks fighting with the creator of that library to get a fixed version, or spend days fixing it myself and creating a fork that i can't get updated. – Guntram Blohm Mar 06 '16 at 13:30
  • Yeah Reflections seems to be the only way. @Joe gave an answer on how to do it. – Buddha Mar 06 '16 at 13:33

2 Answers2

5

preferable without using reflection?

Drop that requirement, and:

vehicle.getClass().getMethod("getName").invoke(null);

would solve the problem as asked.

(However, you should fix the code.)

Joe
  • 29,416
  • 12
  • 68
  • 88
1

There is no need for reflection if you know all of the classes involved. With java 8 (create the necessary interface and (anonymous) classes for lower java versions):

public class VehicleUtil {

    private static final Map<Class<? extends Vehicle>, Supplier<String>> map = createMap();

    private static Map<Class<? extends Vehicle>, Supplier<String>> createMap() {
        Map<Class<? extends Vehicle>, Supplier<String>> result = new HashMap<>();
        result.put(Vehicle.class, Vehicle::getName);
        result.put(Car.class, Car::getName);
        result.put(Train.class, Train::getName);
        return result;
    }

    public static String getName(Vehicle vehicle) {
        return map.get(vehicle.getClass()).get();
    }

    public static void main(String[] args) {
        System.out.println(VehicleUtil.getName(new Vehicle()));
        System.out.println(VehicleUtil.getName(new Car()));
        System.out.println(VehicleUtil.getName(new Train()));
    }
}

The above is just a more elegant way of doing something like:

public static String getName(Vehicle vehicle) {
    return Vehicle.class.equals(vehicle.getClass()) ? Vehicle.getName()
            : Car.class.equals(vehicle.getClass()) ? Car.getName()
            : Train.class.equals(vehicle.getClass()) ? Train.getName()
            : null;
}
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110