0

I have some trouble with casting attributes "automatically".

public abstract class MyType<T> {
    public abstract T getValue(String content);
}

public class MyString extends MyType<String> {
    @Override
    public String getValue(String content) {
        return String.valueOf(content);
    }
}

public class MyInteger extends MyType<Integer> {
    @Override
    public Integer getValue(String content) {
        return Integer.valueOf(content);
    }
}

public class User {
    private String _name;
    private int _id;

    public void setName(String name) {
        _name = name;
    }

    public String getName() {
        return _name;
    }

    public void setId(int id) {
        _id = id;
    }

    public int getId() {
        return _id;
    }
}

public class MainTest {
    public static void main(String[] args) {
        ArrayList<MyType> myTypes = new ArrayList<MyType>();
        myTypes.add(new MyString());
        myTypes.add(new MyInteger());

        User user = new User();

        for (MyType myType : myTypes) {
            try {
                user.setName((String) myType.getValue("foobar")); // getValue always returns an Object that I have to parse
                user.setId((Integer) myType.getValue("42")); // getValue always returns an Object that I have to parse
            } catch (Exception e) {
            }
        }
    }
}

Please keep in mind that this is only an abstract example of my problem.

How can I replace the castings? I need something like this:

user.setName((user.getName().getClass()) myType.getValue("foobar"));

Unfortunately eclipse tells me that this is wrong The method getValue(String) is undefined for the type Class<capture#3-of ? extends String>

I will not cast explicity. I will cast more implicity/automatically.

Keppil
  • 45,603
  • 8
  • 97
  • 119
erwingun2010
  • 123
  • 1
  • 1
  • 6

2 Answers2

9

The problem is that the myTypes list is declared as a List<MyType>, which uses the raw type MyType instead of a particular parameterization of MyType in the list. This is generally discouraged, warned against, bad practice, etc.

Ordinarily you'd declare the list like List<MyType<String>> or List<MyType<Integer>>. That would enable you to get elements out without having to cast. However, this is problematic because you want to be able to mix MyType<String> and MyType<Integer> in the same list. Again, this is generally not recommended, and is a code smell.

So you could declare a List<MyType<?>> instead, which is better than List<MyType>, but not by much. The only correct conclusion is that the design is inherently flawed: don't try to mix heterogeneous types in collections.*


The class MyType was just an abstract example. Actually my question was how I can cast: user.setName((user.getName().getClass()) object); if object is Object object = "foobar";

Regardless, the problem is still fundamentally that you're using raw types in the list. It seems like you might be better off using something that maintains the type parameterization, such as a Map<Class<T>, MyType<T>> instead of a List<MyType>.

Consider applying the strategy pattern here.


*Expert mode: unless there is a sensible common supertype which you can use as an upper bound.

Community
  • 1
  • 1
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • The class MyType was just an abstract example. Actually my question was how I can cast: user.setName((user.getName().getClass()) object); if object is Object object = "foobar"; – erwingun2010 Feb 23 '13 at 16:08
3

I agree 100% with Matt Ball's answer. A simple, quick solution, though maybe not the ideal one, is to remove the abstractness from MyType and add the ability to get the value as a specific type. You'll find this kind of pattern in ResultSet in the standard library, among other places:

public class MyType {
    private String content;
    MyType(String content) { this.content = content }
    public String getStringValue() { return content; }
    public Integer getIntValue() { return Integer.parseInt(content); }
}
Community
  • 1
  • 1
Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • Like I mentioned above, it was just an abstract example. The real problem/question was the casting => How I can cast: user.setName((user.getName().getClass()) object); if object is Object object = "foobar"; – erwingun2010 Feb 23 '13 at 16:10
  • 1
    You can't for all the same reasons that Matt listed in his answer. You don't have the necessary type information. The only thing you could do here is [use reflection](http://docs.oracle.com/javase/tutorial/reflect/index.html), which is almost always the wrong solution outside of infrastructure-ish code. – Ryan Stewart Feb 23 '13 at 16:36