Casting an Object into a Class of type T

Tags: ,



Let’s say I have an empty class called ClsA(), and a subclass of it called ClsB() like so:

public static class ClsA {}

public static class ClsB extends ClsA {}

Supposing that we now have an ArrayList of ClsA type objects, I want to be able to count how many elements in the ArrayList are actually of type ClsA or ClsB. Based on some searching, I found out that the following function works wonders:

   public static <T> int countInstances(ArrayList<?> list, Class<T> cls) {
        int count = 0;
        for(Object o : list) {
            if(cls.isInstance(o)) {
                count++;
            }
        }
        return count;
    }

Indeed, the following sample main method does give the correct output.

public static void main(String[] args) {
    // write your code here
    ArrayList<ClsA> test = new ArrayList<>();
    test.add(new ClsA());
    test.add(new ClsB());
    test.add(new ClsA());
    test.add(new ClsB());
    int result = countInstances(test,ClsB.class);
    System.out.println(result);
}

However, let’s say that ClsB is now defined as followed:

public static class ClsB extends ClsA {
    private final String type;
    public ClsB(String type) {
        this.type = type;
    }

    public String getType() {return type;}
}

I now want to count how many ClsB instances of a specific type are present in the given ArrayList. After checking in my countInstances() that an element is of the given class (in this example ClsB), I want to be able to also check if the type given to the method matches the type of the element. Is there any way to actually cast Object o into an instance of the given class since the compiler doesn’t really know its actual type?

So far I’ve gotten to this point:

public static void main(String[] args) {
        // write your code here
        ArrayList<ClsA> test = new ArrayList<>();
        test.add(new ClsA());
        test.add(new ClsB("t1"));
        test.add(new ClsA());
        test.add(new ClsB("t2"));
        int result = countInstances(test,ClsB.class,true,"t1");
        System.out.println(result);
    }

    public static <T> int countInstances(ArrayList<?> list, Class<T> cls, boolean checkForType, String type) {
        int count = 0;
        for(Object o : list) {
            if(cls.isInstance(o)) {
                if(!checkForType) count++;
                else {}// somehow cast o into the given class (?)
            }
        }
        return count;
    }

Answer

Yes, there is cls.cast(o); which will do it, and will give you a T (because the type of cls is Class<T>, and the cast(Object o) method of j.l.Class is defined to return T. It acts just like the cast operator would, in that it does nothing: It just asserts that o is in fact an instance of this class (so, created as new This() or new SomeSubtypeOfThis()). If it is, it does nothing. If it is not, it throws ClassCastException. No conversion occurs in any case.

This isn’t useful; after all, T is still just object. This will not give you the power to call getType() on your o – because T has no bounds, T is not going to have any methods other than what java.lang.Object already has.

In general, what you’re engaging in is structural typing: It doesn’t matter what ClsB is, it just matters that it has a method named getType.

This is very bad.

It means that the one method of public interface Camera { public void shoot(Person p); } and public interface Gun { public void shoot(Person p); } are, to such a system, interchangible, and thus you will blow somebody’s head off by accident.

Types (classes, interfaces, etc) are exempt from this problem because they have a namespace – a package header, which serves to make them effectively unique. A method should therefore never be considered as meaning anything whatsoever, unless that method is in context of the type it in.

Thus, what you COULD do, is something like this:

public class ClsA {
    public String getType() { .... }
}

public class ClsB extends ClsA { .... }

public static int countInstances(ArrayList<?> list, Class<?> cls) {
    int count = 0;
    for (Object o : list) if (cls.isInstance(o)) count++;
    return count;
}

The above would return ‘2’ for a list with one instance of ClsA and one instance of ClsB, and you pass ClsA.class as second param.

After all, an instance of ClsB is also an instance of ClsA.

If you’re looking for an answer of ‘1’, you’re looking for:

for (Object o : list) {
    if (o != null && o.getClass() == cls) count++;
}

Then for your ‘getType’ method, we must link that method to an actual type, because otherwise you’re shooting people in the face and that’s bad. So, I put the getType() method in ClsA, and then we demand that you pass a list of things which are ClsA’s:

public static int countInstancesWithType(List<? extends ClsA> list, String type) {
    int count = 0;
    for (ClsA o : list) {
        if (type.equals(o.getType())) count++;
    }
    return count;
}

Note that this method can be invoked with an ArrayList<ClsA> or an ArrayList<ClsB> – either is fine. <? extends ClsA> makes that possible; the ? extends is important. Without it, new ArrayList<ClsB>() could not be passed as first parameter.

If you want to combine these two ideas, That’s.. a bizarre mix of concerns and sounds like you’re engaging in the concept of structural typing in java, or otherwise some hacky attempt to make dynamic typing happen. Stop doing that; java is not that kind of language, and it will hurt the whole time, and the end result will be non-idiomatic, hard to maintain, and hard to read code. Find a java-esque way to do whatever you are doing. However, and don’t say I didn’t warn you:

public static int countInstancesWithType(List<?> list, String type) {
    int count = 0;
    for (Object o : list) {
        if (!(o instanceof ClsA a)) continue;
        if (type.equals(a.getType())) count++;
    }
    return count;
}
// NB: This uses java15 features. Without java15, you'd have to
// spend an extra line or two to cast ClsA separately.

There is no point trying to generalize ClsA here, because by doing so you remove the ability for your code to be capable of realizing that ClsA instances have a getType() method.



Source: stackoverflow