1

I am trying to generically get vehicles by tags. How come I can't use the generic Collection type to get vehicles? The CarTypeTags and TruckTypeTags are hashmaps of CarType class and TruckType class which extend from VehicleType indexed by string (tag). Is there a way I can get the cars or trucks assigned to the types collection and return it?

Bonus: Does clazz.getClass() return the class of the caller to this method?

public <T> Collection<T> getVehicleByTag(String tag) {
        T clazz;
        Collection<T> types;
        if (clazz.getClass() == Car.class) {
            types = (CarTypeTags.get(tag)); // error here: type mismatch
        } else if (clazz.getClass() == Truck.class) {
            types.addAll(TruckTypeTags.get(tag)); // error here too
        } 
        if (types != null) {
            return types;
    }
    return new ArrayList<T>();
}
Joe F
  • 4,174
  • 1
  • 14
  • 13
Stephen D
  • 2,836
  • 4
  • 27
  • 40
  • I am under the assumption the compiler takes the class of the caller. Is this the case? – Stephen D Jun 17 '13 at 19:46
  • 3
    Java generics can't do what you want here. **Type erasure** means that types is a `Collection`. There is nothing available _at compile time_ to help it figure out the correct types. Incidentally, this type of design with `if` on a class type usually has a better solution with inheritance. It's a code smell. – Andrew Lazarus Jun 17 '13 at 19:48
  • 3
    I don't see how `clazz.getClass()` would do anything other than throw a NPE... Does any of this actually compile? I think you need to spend some time learning how generics are _supposed_ to work. – jahroy Jun 17 '13 at 19:51
  • Aside from the clazz issue you brought up, which would probably be a NPE, is there a way for a Collection to be set to any other type of collection? ie Collection types = TruckTypeTags.get(tag); can it be done without casting? – Stephen D Jun 17 '13 at 19:54
  • I think you would be interested in getting generic type http://stackoverflow.com/questions/1942644/get-generic-type-of-java-util-list – Grzegorz Żur Jun 17 '13 at 19:55
  • 1
    Generally a generic method has an argument that allows the compiler to infer the return type... – jahroy Jun 17 '13 at 19:58
  • I don't know exactly what's going on with your code, but it looks like this method should return a collection of type T where T is either a common superclass to Cars and Trucks **OR** an interface that is implemented by both Cars and Trucks. – jahroy Jun 17 '13 at 20:02
  • It is the former. Cars and Trucks extend Vehicle. For the collection to be the correct type, should it be declared as public Collection getVehiclesByTag(String tag) { ? – Stephen D Jun 17 '13 at 20:09
  • Why do you need `T`? This method does not look like it should even be generic. It should just return a Collection of Vehicles, in which case there is no need for generics (unless I'm missing something). – jahroy Jun 17 '13 at 20:12
  • I am using 'T' because I want to only return the type that calls the method. If a car type uses the method, then it should return a list of collection of cars. They are all collections. It is separate than trucks. If a trucks type calls this method, only the truck types should be returned. – Stephen D Jun 17 '13 at 20:17
  • That suggests that the Car and Truck classes should override the method differently (assuming it is part of the Vehicle class). Where is this method actually defined/implemented? I think if we knew more about how your application was structured, we could suggest a better solution. It would be helpful to see the implementation of whatever class contains the method `getVehicleByTag()`... – jahroy Jun 17 '13 at 20:22
  • 1
    Instead of using a String, why not use a type-safe token of some sort? I'm not sure how the rest of your code is structured, but this is definitely a design smell. If you want to retrieve objects from a collection by their class, you should identify them by their class. – millimoose Jun 17 '13 at 20:35
  • 1
    Also also: please consider editing the additional information in your comments explaining what you're trying to accomplish into your question if you can - it's easier than piecingit together from the discussion. – millimoose Jun 17 '13 at 20:38
  • 1
    Agreed... Please consider re-writing the question to include all of the info you've revealed in comments **plus** some description of where this method is defined and how you expect to use it. Example client code would be great. – jahroy Jun 17 '13 at 20:39
  • Thanks for your help guys. Closing this question and I accepted an answer. – Stephen D Jun 18 '13 at 20:08

2 Answers2

2

Taking a wild guess at what you're roughly going for, you want to use some sort of type token somewhere:

class Vehicle {
    String tag;
}

class Car extends Vehicle { ... }

class Truck extends Vehicle { ... }

class Tag<T> {
    Class<T> tagType;
    String tagName;
}


List<Vehicle> vehicles = new ArrayList<Vehicle>();

vehicles.add(new Car("tag1"));
vehicles.add(new Truck("tag1"));
vehicles.add(new Car("tag2"));
vehicles.add(new Truck("tag2"));

public <T> List<T> getVehiclesByTag(Tag<T> tag) {
    List<T> result = new ArrayList<>();
    for (Vehicle vehicle : vehicles) {
        if (tag.tagType.isInstance(vehicle) && tag.tagName.equals(vehicle.tag))) {
            result.add(tag.tagType.cast(vehicle));
        }
    }
    return result;
}

(Constructors, properties, etc omitted for brevity.)

The above will filter both on the tag name and the expected return type. If a single tag is only supposed to map to one vehicle type, you can leave out the isInstance() check.

Obviously the code is more of a sample of what functions you'll need to use - since I have no idea what CarTypeTags.get() etc. are supposed to do I can't reproduce your use case exactly.

As far as your bonus question goes: no. You can get the class of the caller by either poking at the stack trace (slow and ugly and fragile), or by passing it in explicitly in the token.

millimoose
  • 39,073
  • 9
  • 82
  • 134
1

It's hard to know what's going on without seeing more code...

But it looks like your method should do something like this:

public Collection<Vehicle> getVehicleByTag(String tag) {
    if (carTypeTags.containsKey(tag)) {
        return carTypeTags.get(tag);
    }
    else if (truckTypeTags.containsKey(tag)) {
        return truckTypeTags.get(tag);
    }
    else {
        return new ArrayList<Vehicle>();
    }
}

In the above code, Vehicle should be one of two things:

  1. a class that both Cars and Trucks extend
  2. an interface implemented by both Cars and Trucks

As I wrote this example, it occurred to me that the type mismatch error you're seeing probably has to do with the contents of your CarTypeTags array. It looks like your TruckTag map contains collections of Trucks, whereas you CarTag map contains only single Cars. If types is a Collection and CarTypeTags contains Cars (not collections of Cars) then that line will not work.

Either way, it's clear from your example code that you don't quite understand how generics are meant to be used in Java. You should never be comparing classes or using the instanceof method in a generic method (with very very very rare exceptions).

Also...

You should begin the names of all objects with lowercase letters in Java!

jahroy
  • 22,322
  • 9
  • 59
  • 108