Let's say I have two constructors in my class:
public User (List<Source1> source){
...
}
public User (List<Source2> source) {
...
}
Let's say that both of these constructors provide the same information about a User and are equally valid ways to construct a user for different use cases.
In Java, you can't do this because of type erasure -- Java won't accept two constructors that have as parameters List< ? >.
So, what is the way to get around this? What is a solution that is not overkill but still respects basic OO? It seems wrong to have to construct a factory method or other interface around this just because Java doesn't have strong generics support.
Here are the possibilities I can think of:
1) Accept a List<?>
as a parameter for the constructor and parse in the constructor which kind of logic you need, or throw an exception if it's not any of the accepted types.
2) Create a class that accepts either List, constructs the appropriate User object, and returns it.
3) Create wrappers around List<Source1>
and List<Source2>
that can be passed to the User constructor instead.
4) Subclass this guy with two classes, where all of the functionality is inherited except for the constructor. The constructor of one accepts Source1, the other accepts Source2.
5) Wrap this guy with a builder where are two different builder methods for the two different sources of data for instantiation.
My questions are these:
1) Is the need to do this a flaw with Java, or an intentional design decision? What is the intuition?
2) Which solution is strongest in terms of maintaining good code without introducing unneeded complexity? Why?
This question is similar: Designing constructors around type erasure in Java but does not go into specifics, it just suggests various work-arounds.
- Erasure is a type of alteration in document. It can be classified as chemical erasure and physical erasure.
Java For Testers Type erasure is a process in which compiler replaces a generic parameter with actual class or bridge method. In type erasure, compiler ensures that no extra classes are created and there is no runtime overhead.
What is the following method converted to after type erasure? public static <T extends Comparable<T>> int findFirstGreaterThan(T[] at, T elem) { // ... } Answer: public static int findFirstGreaterThan(Comparable[] at, Comparable elem) { // ... }
The only way to "stop" a constructor is to throw an exception. Bearing in mind of course that the caller is supposed to "know" about this exception and be able to handle the case where the constructor fails.
To support generic programming, as well as perform a stricter type check, Java implements type erasure. All type parameters in generic types are replaced with the bound (if unbounded) or object type. This way, the bytecode will only contain classes, methods, and interfaces. Type casts to preserve the type.
Type erasure can be explained as the process of enforcing type constraints only at compile time and discarding the element type information at runtime. The compiler replaces the unbound type E with an actual type of Object: Therefore the compiler ensures type safety of our code and prevents runtime errors. 3. Types of Type Erasure
Type Erasure in Java 1 All type parameters in generic types are replaced with the bound (if unbounded) or object type. This way, the bytecode will only contain classes, methods, and interfaces. 2 Type casts to preserve the type. 3 Bridge methods are generated so as to preserve the polymorphism concept in extended generic types.
In our example above, the Java compiler preserves polymorphism of generic types after erasure by ensuring no method signature mismatch between IntegerStack ‘s push (Integer) method and Stack ‘s push (Object) method. Consequently, Stack class's push method after type erasure, delegates to the original push method of IntegerStack class.
The usual approach is to use factory methods:
public static User createFromSource1(List<Source1> source) {
User user = new User();
// build your User object knowing you have Source1 data
return user;
}
public static User createFromSource2(List<Source2> source) {
User user = new User();
// build your User object knowing you have Source2 data
return user;
}
If you only want construction using Source1
or Source2
(ie you don't have a default constructor), you simply hide your constructor, forcing clients to use your factory methods:
private User () {
// Hide the constructor
}
This problem arises because you can't name constructors differently, which would be how you'd overcome this if these were normal methods. Because constructor names are fixed as the class name, this code pattern is only way to distinguish then give the same type erasure.
1: Maintaining backward compatibility with erasure.
2: Can your class use generics? Something like this:
public class User<T> {
private List<T> whatever;
public User(List<T> source){
....
}
}
I am not sure if this is what you meant by (2)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With