0

I'm trying to create a function that takes in an instance of a parameterized class with one type parameter T, and then uses reflection to look at fields of its enveloping class, checks if those fields are of type T, and then casts and does something with them if they are. Here's what I have so far:

 private <T> void someFunction(SomeClass<T> instance) throws IllegalAccessException {
            for(Field field : this.getClass().getDeclaredFields()){
                Object obj = field.get(this);
                // TODO: get parameter T from instance and store it in a variable named 'myClass'
                if(myClass.isInstance(obj)){
                    doSomething(instance, myClass.cast(obj))
                }
            }
        }

From what I understand, in order to do this at runtime you need to declare a variable of type Class and store the class of T in there so you can then use the isInstance() and cast() functions, but I am unsure how to properly do this.

jipthechip
  • 157
  • 1
  • 13
  • `to do this at runtime you need to declare a variable of type Class and store the class of T in there so you can then use the isInstance() and cast() functions` You'll need to clarify, because this is too vague. Do you have a source for that statement? – markspace Feb 15 '21 at 06:08
  • 2
    `T` is a type parameter. There is no “class of T”. The actual argument for `T` can be something like `? extends Comparable&CharSequence`. No `Class` object can represent this. – Holger Feb 15 '21 at 14:25
  • @Holger Oh ok that makes sense, but looking at another [post](https://stackoverflow.com/questions/1570073/java-instanceof-and-generics), in the answers I found this code snippet: `ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass(); clazz = (Class>)pt.getActualTypeArguments()[0];` Though when I tried to do that, I got the error `java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class`. Is it possible to do something similar to get a single class from the type parameter? – jipthechip Feb 15 '21 at 17:49
  • 2
    That `Type` may be a `Class` in some scenarios, but it isn’t in others. When you have a class of the form `class Sub extends Super`, then `getGenericSuperclass().getActualTypeArguments()[0]` can’t be a `Class`, as the type argument for `Super` is the `T` type variable declared by `Sub`, not a concrete class. Since `someFunction` is generic, the caller of that function will decide which type argument to use. Nothing in `SomeClass` can tell you what the immediate caller of `someFunction` did. – Holger Feb 16 '21 at 07:22

1 Answers1

1

Short Story -- Missing Piece

private <T extends SomeClass> void someFunction(SomeClass<T> instance) throws IllegalAccessException {
    for (Field field : this.getClass().getDeclaredFields()) {
        Object obj = field.get(this);

        Class<?> objClass = obj.getClass();
        if (objClass.isInstance(instance))
            doSomething(instance, (T) obj);
    }
  • Where we get and store the Class from the Object, then check that against the instance we want to match with.
  • We use the wildcard ? to signify an unknown Type, which saves us from having to figure out, or specify types.
  • Then we assure the doSomething Method accepts the same generic type argument to allow for similar work
    • Therefore we only need to cast to T

As a Whole, With A Working Example of Animals

(Yes this is a reduction :))

Animal Class as a Base

public abstract class Animal {

    protected Object color;

    protected Animal(Object color) {
        this.color = color;
    }

    protected Animal() {}

}

Various Colors of Animal (To Simplify)

No Color Animal (A Ghost?!)
public class NoColorAnimal extends Animal {

    // A null color
    public NoColorAnimal() {}

}
A Single Color Animal
public class SingleColorAnimal extends Animal {

    // A single String of color
    public SingleColorAnimal(String color) {
        super(color);
    }

}
A Multi-colored Animal
public class MultiColorAnimal extends Animal {

    // An Array of colors
    public MultiColorAnimal(String... colors) {
        super(colors);
    }

}

A Zoo With Viewable Animals as Your Enclosing Class

public class ZooAnimals {

    public MultiColorAnimal tiger = new MultiColorAnimal("black", "orange");
    public SingleColorAnimal dolphin = new SingleColorAnimal("grey");


    public <T extends Animal> void viewAnimalIfPresent(Animal<T> animal) throws IllegalAccessException {
        for (Field field : this.getClass().getDeclaredFields()) {
            Object obj = field.get(this);

            Class<?> objClass = obj.getClass();
            if (objClass.isInstance(animal))
                view(animal, (T) obj, field.getName());
        }
    }

    private <T extends Animal> void view(Animal<T> animal, T obj, String name) {
        // TODO - Do something useful with these, but simply log them for now :)
        System.out.printf("Viewed a %s; the %s%n", obj.getClass().getSimpleName(), name);
    }

}

A Simple Test

ZooAnimals zoo = new ZooAnimals();

    @Test
    public void test() throws IllegalAccessException {
        zoo.viewAnimalIfPresent(zoo.tiger);
        zoo.viewAnimalIfPresent(zoo.dolphin);
        zoo.viewAnimalIfPresent(new NoColorAnimal());
    }

Result of Test

The Console Ouput for Test Showing Proper Type Shifting
Viewed a MultiColorAnimal; the tiger
Viewed a SingleColorAnimal; the dolphin

Where no field matched a colorless Animal, and therefore nothing was printed.

Phacops
  • 76
  • 7
  • 4
    The type parameter of your `Animal` class has no relevance to the code you’ve posted. You never check that an object matches the actual type of `T`, it’s just a coincidence that the classes always declare the actual type argument to be matching. I could easily add another class that does not match its type argument for `T`, and the test would not detect that they don’t match, but it has no consequence anyway. – Holger Feb 15 '21 at 14:36
  • Fair point. I thought to add it as demonstration of how one can directly utilize sub-classes from super-classes, but only did it to this stage so as not to make it overly complex, then left the comment. As such, you're right though that it adds nothing if it shows/affects nothing. – Phacops Feb 15 '21 at 15:30