开发者

How to dynamic cast/ensure typesafety from collection with varying generics

开发者 https://www.devze.com 2023-04-08 20:28 出处:网络
I\'ve decided to use generics in my design, which works fine. Only when I get to abstraction, I got stuck. I\'m collecting different instances implementing the same interface with a different generics

I've decided to use generics in my design, which works fine. Only when I get to abstraction, I got stuck. I'm collecting different instances implementing the same interface with a different generics. To save those in a list, I need to use wildcards, as far as I know. So my current code is like this:

import java.util.List;
import java.util.ArrayList;


public class Main {
    private List<IView<?>> views = new ArrayList<IView<?>>();


    public void addView(IView<?> view)
    {
        this.views.add(view);
    }

    public void handle()
    {
        for (IView<?> view : this.views)
        {       
            // ## ERROR ##
            view.render(
                    view.cast(new Object())
                );
        }
    }
}

public interface IView<T> { 
    public T cast(Object entity);
    public void render(T entity);
}

public class FirstView implements IView<One> {  
    // .. Added to Main-collection views
}

public class SecondView implements IView<Two> { 
    // .. Added to Main-collection views
}

I also tried an alternative approach returning a Class from the IView which I then use to call the class.cast. Same problem.

This is NOT accepted by the compiler - The method Render(Capture#2-of 开发者_开发技巧?) in the type IView(Capture#2-of ?) is not applicable for the arguments (Capture#3-of ?).

I partially think to understand the problem, but I see no way of solving it. It would be very much appriciated if someone could help me getting going again.


I don't believe you can assign a value to an ? "typed" variable. An unbounded "?" means "the original type of the variable is unknown", which means there is no known type it's safe to assign to it. It doesn't mean that the actual value of the type parameter is retrieved at runtime.

The entire idea of using a generic method to do a cast doesn't make sense in Java generics. The only way to do a cast of an unknown type object in a type-safe way is with an actual cast.

The only solution is to get rid of the cast() method, and use raw types:

public void handle()
{
    for (IView view : this.views)
    {
        view.render(new Object());
    }
}

Assuming the cast() method is only doing casting and not type conversion. If the implementing method that ends up being called is FirstView.render(One object), I believe Java will actually cast the parameter to One. (This can also cause amusing bugs where a ClassCastException is thrown on a line of code with no casts.)

If cast() can do a type conversion (in which case, "cast" is a bad name for the operation), that means that every IView must be able to accept Object, in which case it doesn't make much sense have the parameter of render() be the generic type to begin with. The design I would use is something like the following:

interface IView<T> {
    void renderObject(Object o);
    void render(T);
    T coerce(Object o);
}

abstract class ViewBase<T> implements IView<T> {
    void renderObject(Object o) {
        render(coerce(o));
    }
}

class FirstView extends ViewBase<One> {
    // …
}

encapsulating the "cast" operation.

There is no statically typesafe way to work with a collection of objects of different types - in Java, IView<One> and IView<Two> are types as different as String and Integer and there are no safe conversions between the two.


Since I was wondering about the two captures in code view.render(view.cast(new Object());, I read the appropriate section in Angelika Langer's Generics FAQ. It states that:

We cannot call methods through an unbounded wildcard parameterized type that take arguments of the "unknown" type.

PERIOD.

So using a wildcard declaration prevents the use of parameterized arguments, even if type inference were simple, as in your case :( Hence view.cast(new Object(); is allowed, but view.render(view.cast(new Object()); isn't.


Just for clarification via an example, Angelika gives the following:

Box<?> box = new Box<String>("abc");

box.contains("abc");    // error 

where contains takes as argument the parameterized type of Box. She states:

The invocation is illegal if performed through a reference variable of type Box<?> . [Defining the contains method as taking an argument of type Object , instead of T , would avoid this effect. In this case the contains method would not take an object of "unknown", but an object of "any" type, and it would be permitted to invoke it through a reference variable of type Box<?> .]

So she suggests retrieting back to Object (similarly to the suggestion in my other answer). Btw, she discourages using raw types, as does Josh Bloch in Effective Java's item 23, "Don’t use raw types in new code".


I do not understand the compiler error, i.e. why view.render(view.cast(new Object()); produces two captures. (I've tried this code with view being final, and the same error message shows).

But since IView<One> and IView<Two> only have Object as common type, the way of solving it is using List<IView<Object>>.


I sort of replaced the casting by returning a typed object (and I slightly changed the example by replacing the new Object with IEntity);

public class Main {
    public static List<IView<? extends IEntity<?>>> views = new ArrayList<IView<? extends IEntity<?>>>();

    public static <T extends IEntity<T>> void register(IView<T> view)
    {
        Main.views.add(view);
    }


    public static void handle()
    {
        for (IView<? extends IEntity<?>> view : Main.views)
        {
        // ## Error ##
        /* Bound mismatch: The generic method view(IView<T>) of the type Main 
         * is not applicable for the arguments (IView<capture#1-of ? extends IEntity<?>>). 
         * The inferred type capture#1-of ? extends IEntity<?> is not a valid 
         * substitute for the bounded parameter <T extends IEntity<T>> 
         */             
            Main.view(view);
        }
    }

    public static <T extends IEntity<T>> void view(IView<T> view)
    {
        IKey<T> key = view.getKey(/* Some arg */);
        T entity = key.getEntity();

        view.render(entity);
    }

}

public interface IView<T> {
    public IKey<T> getKey(/* Some args */);
    public void render(T entity);
}

public interface IKey<T> {
    public T getEntity();
}

The wildcard is someway bounded, but then it creates another problem (which may actually be the same): Is there a way to check that two wildcards match, i.e.:

for (IView<? extends IEntity<?>> view : Main.views)

Where ? can only be one (=the same) (generic)type?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号