7

I have this piece of code from "Java - A beginner's Guide - Schildt', Chapter 13:

package com.chapter.thirteen;

public class GenericMethodDemo {
static <T extends Comparable<T>, V extends T> boolean arraysEqual(T[] x, V[] y){
    if(x.length != y.length) return false;

    for(int i = 0; i < x.length; i++)
        if(!x[i].equals(y[i])) return false;

    return true;
}

public static void main(String args[]){

    Integer [] nums  = { 1, 3, 3, 4, 6 };
    Integer [] nums2 = { 1, 3, 3, 4, 6 };
    Integer [] nums3 = { 1, 3, 3, 4, 6 };
    Integer [] nums4 = { 1, 3, 3, 4, 6, 7};
    Double [] dVals = {1.1, 2.2, 3.3, 4.4};

    if(arraysEqual(nums, nums))
        System.out.println("nums equal nums");

    if(arraysEqual(nums, nums2))
        System.out.println("nums equal nums2");

    if(arraysEqual(nums, nums2))
        System.out.println("nums equal nums2");

    if(arraysEqual(nums, nums3))
        System.out.println("nums equal nums3");

    if(arraysEqual(nums, nums4))
        System.out.println("nums equal nums4");

    //Edit:Removed the comments from the below two lines.

    if(arraysEqual(nums, dVals))
        System.out.println("Nums equal dVals");

    }
}

The compilation fails with the message - "Error:(39, 12) java: method arraysEqual in class com.chapter.thirteen.GenericMethodDemo cannot be applied to given types; required: T[],V[] found: java.lang.Integer[],java.lang.Double[] reason: inference variable T has incompatible bounds equality constraints: java.lang.Integer lower bounds: V,java.lang.Double,java.lang.Integer", which is expected.

However, when I missed adding the parameter to Comparable (as shown in the code below), the code compiles and produces the correct result.

package com.chapter.thirteen;


public class GenericMethodDemo {
    static <T extends Comparable, V extends T> boolean arraysEqual(T[] x, V[] y){
    if(x.length != y.length) return false;

    for(int i = 0; i < x.length; i++)
        if(!x[i].equals(y[i])) return false;

    return true;
}

public static void main(String args[]){

    Integer [] nums  = { 1, 3, 3, 4, 6 };
    Integer [] nums2 = { 1, 3, 3, 4, 6 };
    Integer [] nums3 = { 1, 3, 3, 4, 6 };
    Integer [] nums4 = { 1, 3, 3, 4, 6, 7};
    Double [] dVals = {1.1, 2.2, 3.3, 4.4};

    if(arraysEqual(nums, nums))
        System.out.println("nums equal nums");

    if(arraysEqual(nums, nums2))
        System.out.println("nums equal nums2");

    if(arraysEqual(nums, nums2))
        System.out.println("nums equal nums2");

    if(arraysEqual(nums, nums3))
        System.out.println("nums equal nums3");

    if(arraysEqual(nums, nums4))
        System.out.println("nums equal nums4");

    if(arraysEqual(nums, dVals))
        System.out.println("Nums equal dVals");
   }
}

Can someone please explain why the compilation does not fail in the second instance? I had expected the compiler to complain about T extends Comparable, V extends T in the second instance?

What's going on?

xuxu
  • 418
  • 5
  • 15
  • Your first code compiles also. Which java version are you using? – Ruben Oct 27 '15 at 08:36
  • When you remove the type parameter from `Comparable`, you are starting to use raw types and the story becomes completely different. Raw types exist only for compatibility with the pre-Java-5 code and should not be used in new code which does not need such compatibility. You should not try to understand when raw types work and when not, you should instead forget about them. – Tagir Valeev Oct 27 '15 at 08:37
  • Hi, I'm using java version 1.8.0_65 on IntelliJ IDEA 14.1.5 Community Edition. Thanks – xuxu Oct 27 '15 at 08:39

3 Answers3

2

The reason is because of the rules of PECS.

When you do,

static <T extends Comparable, V extends T> boolean arraysEqual(T[] x, V[] y)

You are basically stating, both T and V are subtype of Comparable. Which means calling arraysEqual(Integer[], Double[]) should work because both Integer and Double implement Comparable.

But when you add generic type to Comparable, the contract is lost,

static <T extends Comparable<T>, V extends T> boolean arraysEqual(T[] x, V[] y)

In this, Double is not implementing Comparable<Integer>, which is why the compiler error.

EDIT: If your question is why the rawtype Comparable is not giving compiler error, the answer is that's how generics work...

You can try with Number too,

static <T extends Number, V extends T> boolean arraysEqual(T[] x, V[] y)

No rawtypes are involved in this, and you can call arrayEquals(Integer[], Double[]) to this and it will work fine because both are Number.

Community
  • 1
  • 1
Codebender
  • 14,221
  • 7
  • 48
  • 85
  • Thanks for the explanation. I haven't read up on "Raw Types" yet. The section on "Raw Types and Legacy Code" comes up next in the chapter. I had just mis-typed the function invocation and expected the compilation to fail. It baffled me when it didn't! Thanks again for your answer. – xuxu Oct 27 '15 at 08:49
0

The method arraysEqual is declared to recive type V that has T as supertype. In other words V must implement or extend T. This is not case with java.lang.Double(V) and java.lang.Integer (T), and therefore constitutes a compile error.

Edited to add If you remove parameterization from Comparable (declare it as Comparable not Comparable<T>) compiler sees the T as Comparable, not as Comparable<Integer>, and then V is seen as V extends Comparable which java.lang.Double is.

Zoran Regvart
  • 4,630
  • 22
  • 35
0

The fact that it succeeds with a raw Comparable is actually due to the improved type inference in Java 8.

If you try to compile it with Java 7 javac, you get:

            if(arraysEqual(nums, dVals))
               ^
  required: T[],V[]
  found: Integer[],Double[]
  reason: inferred type does not conform to declared bound(s)
    inferred: Double
    bound(s): Integer
  where T,V are type-variables:
    T extends Comparable declared in method arraysEqual(T[],V[])
    V extends T declared in method arraysEqual(T[],V[])

However, Java 8 is trying to find a proper set of types that successfully fulfills the constraints. Instead of insisting that the arrays are types Integer[] and Double[], it infers them as Comparable[] and Double[]. Double is declared to implement Comparable<Double> which means that it implements the raw Comparable.

This cannot work when the generic type is properly used, as Integer implement Comparable<Integer>, not raw Comparable, and Double does not implement that.

This example in the book seems to be a bit contrived, as you are not actually using compareTo in the method. You would have run into the same issue if you had removed the constraint on T completely:

static <T, V extends T> boolean arraysEqual(T[] x, V[] y){...}

This would not have compiled in Java 7 for Integer[] and Double[], as Double does not extend Integer. But in Java 8 it compiles, because it infers the type Number & Comparable<?> for T and Double extends that. Number & Comparable<?> is the result of Java 8 trying to find the most strict common type, so that you'll be able to assign T to a variable of type Number or Comparable<?> without explicit cast.

RealSkeptic
  • 33,993
  • 7
  • 53
  • 79