Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this valid Java?

Is this valid Java?

import java.util.Arrays; import java.util.List;  class TestWillThatCompile {      public static String f(List<String> list) {         System.out.println("strings");         return null;     }      public static Integer f(List<Integer> list) {         System.out.println("numbers");         return null;     }      public static void main(String[] args) {         f(Arrays.asList("asdf"));         f(Arrays.asList(123));     }  } 
  • Eclipse 3.5 says yes
  • Eclipse 3.6 says no
  • Intellij 9 says yes
  • Sun javac 1.6.0_20 says yes
  • GCJ 4.4.3 says yes
  • GWT compiler says yes
  • Crowd at my previous Stackoverflow question says no

My java theory understanding says no!

It would be interesting to know what the JLS is saying about it.

like image 561
lacroix1547 Avatar asked Jun 24 '10 12:06

lacroix1547


People also ask

What is valid Java?

Here is the syntax for valid Java identifiers: Each identifier must have at least one character. The first character must be picked from: alpha, underscore, or dollar sign. The first character can not be a digit. The rest of the characters (besides the first) can be from: alpha, digit, underscore, or dollar sign.

Is valid string in Java?

To validate a string for alphabets you can either compare each character in the String with the characters in the English alphabet (both cases) or, use regular expressions.

What is @validated in Java?

@Validated annotation is a class-level annotation that we can use to tell Spring to validate parameters that are passed into a method of the annotated class. and. @Valid annotation on method parameters and fields to tell Spring that we want a method parameter or field to be validated.


2 Answers

It depends upon how you wish to call these methods. If you wish to call these methods from other Java source code, then it is considered invalid for reasons illustrated in Edwin's answer. This is a limitation of the Java Language.

However, not all classes need to be generated from Java source code (consider all the languages that use the JVM as their runtime: JRuby, Jython, etc...). At the bytecode level, the JVM can disambiguate the two methods because the bytecode instructions specify the return type they are expecting. For example, here is a class written in Jasmin that can call either of these methods:

.class public CallAmbiguousMethod .super java/lang/Object  .method public static main([Ljava/lang/String;)V   .limit stack 3   .limit locals 1    ; Call the method that returns String   aconst_null   invokestatic   TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/String;    ; Call the method that returns Integer   aconst_null   invokestatic   TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/Integer;    return  .end method 

I compile it to a class file using the following command:

 java -jar jasmin.jar CallAmbiguousMethod.j 

And call it using:

 java CallAmbiguousMethod 

Behold, the output is:

 > java CallAmbiguousMethod strings numbers 

Update

Simon posted an example program that calls these methods:

import java.util.Arrays; import java.util.List;  class RealyCompilesAndRunsFine {      public static String f(List<String> list) {         return list.get(0);     }      public static Integer f(List<Integer> list) {         return list.get(0);     }      public static void main(String[] args) {         final String string = f(Arrays.asList("asdf"));         final Integer integer = f(Arrays.asList(123));         System.out.println(string);         System.out.println(integer);     }  } 

Here is the Java bytecode generated:

 >javap -c RealyCompilesAndRunsFine Compiled from "RealyCompilesAndRunsFine.java" class RealyCompilesAndRunsFine extends java.lang.Object{ RealyCompilesAndRunsFine();   Code:    0:   aload_0    1:   invokespecial   #1; //Method java/lang/Object."":()V    4:   return  public static java.lang.String f(java.util.List);   Code:    0:   aload_0    1:   iconst_0    2:   invokeinterface #2,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;    7:   checkcast       #3; //class java/lang/String    10:  areturn  public static java.lang.Integer f(java.util.List);   Code:    0:   aload_0    1:   iconst_0    2:   invokeinterface #2,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;    7:   checkcast       #4; //class java/lang/Integer    10:  areturn  public static void main(java.lang.String[]);   Code:    0:   iconst_1    1:   anewarray       #3; //class java/lang/String    4:   dup    5:   iconst_0    6:   ldc     #5; //String asdf    8:   aastore    9:   invokestatic    #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;    12:  invokestatic    #7; //Method f:(Ljava/util/List;)Ljava/lang/String;    15:  astore_1    16:  iconst_1    17:  anewarray       #4; //class java/lang/Integer    20:  dup    21:  iconst_0    22:  bipush  123    24:  invokestatic    #8; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;    27:  aastore    28:  invokestatic    #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;    31:  invokestatic    #9; //Method f:(Ljava/util/List;)Ljava/lang/Integer;    34:  astore_2    35:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;    38:  aload_1    39:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V    42:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;    45:  aload_2    46:  invokevirtual   #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V    49:  return 

It turns out the Sun compiler is generating the bytecode necessary to disambiguate the methods (see instructions 12 and 31 in the last method).

Update #2

The Java Language Specification suggests that this may, in fact, be valid Java source code. On page 449 (§15.12 Method Invocation Expressions) we see this:

It is possible that no method is the most specific, because there are two or more methods that are maximally specific. In this case:

  • If all the maximally specific methods have override-equivalent (§8.4.2) signatures, then:
    • If exactly one of the maximally specific methods is not declared abstract, it is the most specific method.
    • Otherwise, if all the maximally specific methods are declared abstract, and the signatures of all of the maximally specific methods have the same erasure (§4.6), then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that have the most specific return type. However, the most specific method is considered to throw a checked exception if and only if that exception or its erasure is declared in the throws clauses of each of the maximally specific methods.
  • Otherwise, we say that the method invocation is ambiguous, and a compiletime error occurs.

Unless I am mistaken, this behavior should only apply to methods declared as abstract, though...

Update #3

Thanks to ILMTitan's comment:

@Adam Paynter: Your bolded text does not matter, because it is only a case when two methods are override-equivalent, which Dan showed was not the case. Thus, the determining factor must be if the JLS takes generic types into account when determining most specific method. – ILMTitan

like image 70
Adam Paynter Avatar answered Sep 22 '22 17:09

Adam Paynter


--- Edited in response to comments below ---

Ok, so it is valid Java, but it shouldn't be. The key is that it's not really relying on the return type, but on the erased Generics parameter.

This wouldn't work on a non-static method, and is explicitly forbidden on a non-static method. Attempting to do so in a class would fail due to the extra issues, the first being that a typical class isn't final as the class Class is.

It's an inconsistency in an otherwise rather consistent language. TI'll go out on a limb and say that it should be illegal, even if technically allowed. It doesn't really add anything to the readability of the language, and it adds scant little to the ability to solve meaningful problems. The only meaningful problem it seems to solve is whether you are familiar enough with the language to know when it's core tenets seem to be violated by the language's internal inconsistencies in resolving type erasure, generics, and the resulting method signatures.

Definitely code to be avoided, as it is trivial to solve the same problem in any number of more meaningful ways, and the only benefit is to see if the reviewer/extender knows a dusty dirty corner of the language spec.

--- Original Post follows ---

While the compilers might have allowed it, the answer is still no.

Erasure will turn both the List<String> and List<Integer> into an unadorned List. That means that both of your "f" methods will have the same signature but different return types. Return type can't be used to differentiate methods, because doing so will fail when you return into a common super-type; like:

Object o = f(Arrays.asList("asdf")); 

Have you tried capturing the returned values into variables? Perhaps the compiler has optimized out things in such a way that it's not stepping on the right error code.

like image 36
Edwin Buck Avatar answered Sep 20 '22 17:09

Edwin Buck