Why can't I make two overloaded methods whose parameters are both array lists, but with different data types?
public class test {
public static void main(String[] args){
ArrayList<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.add(4);
ints.add(5);
showFirst(ints);
ArrayList<Double> dubs = new ArrayList<Double>();
dubs.add(1.1);
dubs.add(2.2);
dubs.add(3.3);
dubs.add(4.4);
dubs.add(5.5);
showFirst(dubs);
}
public static void showFirst(ArrayList<Integer> a)
{
System.out.println(a.remove(0));
}
public static void showFirst(ArrayList<Double> a)
{
System.out.println(a.remove(0));
}
}
I am in eclipse, and it underlines the problem causing code in red and gives this message: Method showFirst(ArrayList<Integer>) has the same erasure showFirst(ArrayList<E>) as another method in type test
The only way I could get it to work is my adding other parameters, such as , int b after showFirst(ArrayList<Integer> a and , int b after showFirst(ArrayList<Double> a.
Is there any way to make this code work the way I intended? If not, I'd like to know why this is happening.
Running the program generates the following error message:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method showFirst(ArrayList<Integer>) in the type test is not applicable for the arguments (ArrayList<Double>)
at test.test.main(test.java:25)
Edit:
Using or , what if I wanted to do things where I need the data type such as:
public static int[] reverseInArray(ArrayList<Integer> a)
{
int n = a.size();
int[] b = new int[n];
while(n > 0)
{
b[n] = a.remove(0);
n--;
}
return b;
}
public static double[] reverseInArray(ArrayList<Double> a)
{
double n = a.size();
double[] b = new int[n];
while(I > 0)
{
b[n] = a.remove(0);
n--;
}
return b;
}
At run time, every ArrayList<Whatever> will be converted to ArrayList (raw) due to type erasure. So, just have one method that receives List<? extends Number>.
//renamed to show what the method really does
public static void removeFirst(List<? extends Number> a) {
System.out.println(a.remove(0));
}
Note that the method above will work only for Lists (ArrayList, LinkedList and other implementations of List) which declares to hold a class that extends from Number. If you want/need a method to remove the first element from List that holds any type, use List<?> instead:
public static void removeFirst(List<?> a) {
System.out.println(a.remove(0));
}
Remember to always program to interfaces instead of specific class implementation.
Generics are only enforced at compile time. At runtime an ArrayList is an ArrayList.
You can combine the two methods in this particular case, though:
public static void showFirst(ArrayList<? extends Number> a)
{
System.out.println(a.remove(0));
}
Because generics are erased at runtime. In other words, your code doesn't know that the two methods are different. In fact, you have another compiler error that you're not telling us about:
Method showFirst(ArrayList<Integer>) has the same erasure showFirst(ArrayList<E>) as another method in type Main
... because of type erasure, your generic parameters are unknown at runtime, hence your overridden methods share an ambiguous signature.
Have this method only, because, generics are available only in compile, so, both of your methods are compiled to same signature. So it's ambiguous which method to be called
public static void showFirst(ArrayList<? extends Number> a)
{
System.out.println(a.remove(0));
}
Related
Consider the following code:
import java.math.BigDecimal;
class Scratch {
public static class Test<N extends Number> {
public void foo(Class<N> numberClass) {
System.out.println(numberClass);
}
public void bar() {
foo(BigDecimal.class);
}
}
public static void main(String[] args) {
Test<Number> t = new Test<>();
t.bar();
}
}
This fails to compile on line 12 (the call to foo()) with incompatible types: java.lang.Class<java.math.BigDecimal> cannot be converted to java.lang.Class<N>. I don't get it, because the generic N extends Number, and so I should be able to pass BigDecimal.class to a method which takes a Class<N> parameter. TIA for any thoughts!
Suppose a caller did:
var t = new Test<Integer>();
t.bar();
then, bar() would pass a BigDecimal to foo(), which expects an Integer.
The short answer really is that BigDecimal is not necessarily N. It is a little confusing because BigDecimal is within the <N extends Number> bounds, but the current instance's generic type argument can be any type that extends Number, but the bar method assumes BigDecimal.
Check this slightly modified version of the method:
public void bar() {
new Test<BigDecimal>().foo(BigDecimal.class);
}
That method compiles. Why? Becuase Test<BigDecimal>() has BigDecimal as type argument.
When you call foo(BigDecimal.class), you're assuming that this was instantiated with BigDecimal as type argument, which is not always true, and the the compiler is preventing a bug.
The error in your code would be similar to a hypothetical bar() method in ArrayList<T> that does add("string"), which would be wrong for the same reason (the actual array list instance may be created to hold integers, not strings, so the inside code shouldn't make assumptions)
This question already has answers here:
Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?
(19 answers)
Closed 5 years ago.
I just have learned that there is a problem with generic types in Java.
The problem occurs if I want to write a method that uses a generic class.
With the explicit specification of a type I will lose the flexibility that generic classes offer to me. I also cant use "Object" for the generic data type in the Method. But why not? "Object" is the basic class of every class in Java and this language has polymorphism so why this doesnt work?
Own Example to demonstrate:
Triplet.java
public class Triplet <T> {
public T a, b, c;
Triplet(T a, T b, T c) {
this.a = a;
this.b = b;
this.c = c;
}
}
Main.java
public class Main {
public static void main(String[] args) {
Triplet<String> triplet = new Triplet<>("Hello", "Guten Tag", "Bonjour");
printPartsOfTriplet(triplet);
}
static void printPartsOfTriplet(Triplet<Object> triplet) {
System.out.println(triplet.a);
System.out.println(triplet.b);
System.out.println(triplet.c);
}
}
Error Message
Main.java:4: error: incompatible types: Triplet<String> cannot be converted to Triplet<Object>
printPartsOfTriplet(triplet);
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
Add a generic argument in the method static too, replace Object for a type variable:
public class Triplet <T> {
public T a, b, c;
Triplet(T a, T b, T c) {
this.a = a;
this.b = b;
this.c = c;
}
public static void main(String[] args) {
Triplet<String> triplet = new Triplet<String>("Hello", "Guten Tag", "Bonjour");
printPartsOfTriplet(triplet);
}
static void printPartsOfTriplet(Triplet<?> triplet) {
System.out.println(triplet.a);
System.out.println(triplet.b);
System.out.println(triplet.c);
}
}
}
or a valid variant:
static <A> void printPartsOfTriplet(Triplet<A> triplet) {
System.out.println(triplet.a);
System.out.println(triplet.b);
System.out.println(triplet.c);
}
here static <A> void printPartsOfTriplet(Triplet<A> triplet) you are saying "give me any Triplet<A>, it means a Triple with type A (not given yet), and I will print its values.
here static void printPartsOfTriplet(Triplet<Object> triplet) you are saying give me a Triplet<Object> ( a Triplet of type Object (a fixed type), and I will print its values (each one is an Object)).
But Object type is not == String type, so, that gives you an error
You need to use a wildcard.
static void printPartsOfTriplet(Triplet<?> triplet) {
System.out.println(triplet.a);
System.out.println(triplet.b);
System.out.println(triplet.c);
}
There's a very simple rule hereāthe type of the variable
declaration must match the type you pass to the actual object type.
http://www.thejavageek.com/2013/08/27/generics-polymorphism-with-generics/
Just to contribute to Damian's answer.
Your method called printPartsOfTriplet should probably be replaced to stay inside the Triplet class and then you should use the same generic type T to print it.
the owner of the information should handle the processing in major cases.
I'm trying to see if the template expression pattern can be imitated in Java, to do optimizations like loop fusion.
As an example, I port the c++ classes found in this expression template example to java classes: https://en.wikipedia.org/wiki/Expression_templates#Motivation_and_example
First, a template class VecExpression<E> representing a vector expression. It uses a template parameter E and takes the class type of E as a constructor parameter. It then creates a private variable thisAsE set to this cast to the class type of E
public abstract class VecExpression <E> {
private VecExpression thisAsE;
public VecExpression(Class<E> type) throws Exception {
if(type.isInstance(this)) {
thisAsE = (VecExpression)type.cast(this);
}
else {
throw new Exception("Class type must extend VecExpression");
}
}
public double get(int i) {
return thisAsE.get(i);
}
public int size() {
return thisAsE.size();
}
}
Second, a class Vec extending VecExpression<Vec> which passes Vec.class into the super constructor and implements the get() and size() methods called in the VecExpression<E> class.
public class Vec extends VecExpression<Vec> {
private double[] elems;
public <E> Vec(VecExpression<E> expression) throws Exception {
super(Vec.class);
for(int i = 0; i < expression.size(); ++i) {
elems[i] = expression.get(i);
}
}
public Vec(double[] elems) throws Exception {
super(Vec.class);
this.elems = elems;
}
public double get(int i) {
return elems[i];
}
}
And third, a template class VecSum<E1, E2> which extends VecExpression<VecSum<E1, E2>, and uses its get() method to return the sum of two VecExpression<E>s. The type is passed as an explicit parameter Class<VecSum<E1, E2>> type.
public class VecSum <E1, E2> extends VecExpression<VecSum<E1, E2>> {
private VecExpression u;
private VecExpression v;
public VecSum(Class<VecSum<E1, E2>> type, VecExpression<E1> u, VecExpression<E2> v) throws Exception {
super(type);
if(u.size() != v.size()) {
throw new Exception("Vectors must be of the same size");
}
this.u = u;
this.v = v;
}
public double get(int i) {
return u.get(i) + v.get(i);
}
public int size() {
return v.size();
}
}
Finally, we use the expression template to generate a class that can add three vectors with a single pass through memory.
public class Main {
public static void main(String[] args) throws Exception {
Vec a = new Vec(new double[] {1, 2, 3});
Vec b = new Vec(new double[] {1, 2, 3});
Vec c = new Vec(new double[] {1, 2, 3});
VecSum<Vec, Vec> ab = new VecSum<Vec, Vec>(VecSum<Vec, Vec>.class, a, b);
VecSum<VecSum<Vec, Vec>, Vec> abc = new VecSum<>(VecSum<VecSum<Vec, Vec>, Vec>.class, ab, c);
}
}
EDITED as per Louis Wasserman's comment
However, the class types passed into the VecSum constructor don't work because the expression is trying to get a class from a parameterized type. Louis pointed out that implementations of a generic class don't compile to different classes like they do in c++. How would you pass their type, or is there another approach to the expression template pattern?
What you're trying to do won't work in Java, at least insofar as you're trying to use to get a compile-time optimization through the use of a Java generic. The reason is that, unlike a C++ template, the Java generic does not get resolved at compile-time. Since the compiler is not resolving the type at compile-time it cannot use anything about it to make a compile-time optimization. The byte code created by the Java compiler, in some sense, goes the other way "erasing" the generic information completely. If your Java class is class C<A> then everywhere the type A appears in your code, it is replaced by the class Object. If your Java class is class D<E extends F> then everywhere that E appears in your code is replaced by F.
In that case, you might ask why the generics at all. The answer is that before the complier throws away the parameter, it does do type-safe checking on inputs and it implicitly inserts a cast on method returns. That's a convenience that was added to Java a few versions back, but the Java container classes like ArrayList existed. It's just that you didn't have type-safety in the same way that you do now since the inputs were explicitly Object (letting you put in any object even if you knew it was supposed to only contain, say, String objects and forcing you to cast the result of get to, say, a String explicitly).
This is in contrast to a C++ template where the compiler creates a class definition from the template and compiles that class. That class can then be compiled as any other class, including potentially using optimizations that are specific to the value of the template parameter. Moreover, template specialization in C++ allows for template metaprogramming more generally since it allows you to create a base case for recursion in the template parameters.
(You cannot have "generic specialization" in any analogous sense in Java for the reason noted above - The Java compiler is throwing out the generic parameter already, so your "specialized" class - if you tried to define such a thing - would be the same as the "generic" class.)
Finally, as regards your examples, keep in mind that Class with a capital 'C' in Java is a class like any other, including that it derives from Object. This isn't going to get you around the compile-time vs. runtime differences between the C++ templates and the Java generics.
I try to dive deeply into the Java Generics and I've come across a problem described by the following sample code.
public static void test(Object o) {
System.out.println("Hello Object!");
}
public static void test(Integer i) {
System.out.println("Hello Integer!");
}
public static <T> void test(Collection<T> col) {
for (T item : col) {
System.out.println(item.getClass().getSimpleName());
test(item);
}
}
public static void main (String[] args) throws java.lang.Exception
{
Collection<Integer> ints = new ArrayList<>();
ints.add(1);
test(ints);
}
The output of the sample is
Integer
Hello Object!
All the types are obviously known at compile time. As far as I understand, Java holds only a single compiled copy of each method (unlike C++) and because no other constraints are given about the parameter T, it's forced to call the generic Object implementation.
My question is - is there any way to call the "Hello Integer" method for integers without overloading the test method for Collection<Integer> and without using runtime type checking?
It cannot be done. Due to type erasure type Collection<T> will be resolved to type Collection<Object>. Erasure of Generic Methods
You can observe the same behavior without generics.
Object o = new Integer(5);
test(o); // will output "Hello Object!"
Overloaded methods are resolved using compile-time rather than run-time type information. See JLS: https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.9
No, you can't do what you ask for, for the reasons you gave. Type erasure means that the overload is resolved once for all type variables, and it has to resolve to Object.
I'm having trouble understanding why I'm getting a compilation error here. Let me share some simple code. The following block of code works fine:
public class Test {
public static void main(String[] args) {
String[] arr = new String[0];
MethodA(arr);
}
public static <E> void MethodA(E[] array) {
Integer[] intArray = new Integer[0];
MethodB(array, intArray);
}
public static <E> void MethodB(E[] array, E[] secondArray) {
//Stuff
}
}
The problem arises when I add a new generic List parameter to MethodB, calling it from MethodA:
public class Test {
public static void main(String[] args) {
String[] arr = new String[0];
MethodA(arr);
}
public static <E> void MethodA(E[] array) {
Integer[] intArray = new Integer[0];
List<E> someList = new ArrayList<E>();
MethodB(array, intArray, someList);
}
public static <E> void MethodB(E[] array, E[] secondArray, List<E> list) {
//Stuff
}
}
Which gives me the following error:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method MethodB(E[], E[], List) in the type Test is not applicable for the arguments (E[], Integer[], List)
It seems to be telling me to change the parameter from E[] to Integer[], which is weird because it did not complain about such a thing until after I introduced the List parameter. Again, I feel like I must be making a silly mistake somewhere, but I can't figure it out. Any help would be appreciated! Thanks!
In the first example, you're calling MethodB with a String[] and an Integer[].
Since arrays are "covariant" - meaning, for example, you can cast a String[] to an Object[], it calls the version of MethodB with Object for E.
In the second example, it's similar, but you also have a List<E>. Generic classes do not work the same way of arrays - you cannot cast a List<String> to a List<Object>. So it would be invalid for E to be Object (or anything other than whatever E is in MethodA) since then the third parameter couldn't be converted, and it would also be invalid for E to be String since then the first parameter couldn't be converted. So there is no type that works for E.
Note: If you changed String to Integer in main, it still wouldn't compile, even though E could be Integer. That's because the compiler doesn't know that MethodA is never called with anything other than Integer.
In method B declaration You use the same generic type(E) for all three parameters.
It meens that you may use a parameter of any kind (E) but it must be the same for all 3 parameters.
Try adding another generic type(T) like this:
public class Test {
public static void main(String[] args) {
String[] arr = new String[0];
MethodA(arr);
}
public static <E> void MethodA(E[] array) {
Integer[] intArray = new Integer[0];
List<E> someList = new ArrayList<E>();
MethodB(array, intArray, someList);
}
public static <E, T> void MethodB(E[] array, T[] secondArray, List<E> list) {
//Stuff
}
}
Or if there is a need you can add third so that List does not require the E to be the same type as in E[] array.
MethodB requires all three parameters be of same type. But you are calling it with E and Integer. Try E[] intArray = null; and compiler will not compalin