Impossible Java
barahilia.github.io
barahilia.github.io
The trick is that generic types in Java are subject to type erasure at runtime. Due to an oversight, it was possible to declare a class with methods like:
String foo(List<X> l);
double foo(List<Y> l);
which would be erased to: String foo(List l);
double foo(List l);
At runtime, even though the type information for your List was no longer available, the compiler would be able to locate the correct method using the method signature stored in the caller's bytecode, giving the appearance of return-type dispatch. Technically this violates the Java language spec, and javac 7 was updated to be stricter and prevent this sort of code: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6182950I guess it's ultimately not that mysterious, but I first encountered it "in the wild", and ended up scratching my head for a while before I figured out why our code suddenly stopped compiling when we upgraded the JDK.
I think the compiler actually has to work around that when you narrow the return type of an overriden function: A method Object Base::get() overriden with Integer Child::get() will result in an additional compiler generated Object Child::get() in the bytecode.
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.htm...
It don't know that it was an appearance, my understanding is it is return-type dispatch which is supported by the JVM (but not java).
That's not correct. Rust has no issue statically dispatching based on the return type.
Java-the-language does not allow it so it does not have to deal with calls which don't use the return value e.g.
int getSomething()
String getSomething()
If the return value is not used, you have to explicitly disambiguate this call somehow, and Java provides no way to do so. Integer getSomething()
String getSomething()
when the calling site is: Object something = obj.getSomething(); void putSomething(Integer i)
void putSomething(String i)
and obj.putSomething(null)
which I think throws a compiler error in java.Of course, Java provides no way to do that.
Note that in Rust we don't have inheritance (not exactly the same kind, at least), so it's rare where a statically typed value can be referred to with a different type. So there are still annoyances in Java with this approach that Rust wouldn't have.
X and Y are certainly both possible, even if the Java languages chooses not to have Y.
Please read charitably and give the poster the benefit of the doubt.
Except the second clause does not generally follow from the first yet is presented thus. You can't Y in Java because Java very specifically decided not to support Y.
1. Java needs X.
2. Unreleatedly, Java chose not to Y.
And thus "Java needs to be able to decide which overloaded method implementation to use at compile time" is not an issue relevant to return-type overloading.
There was no issue with Java implementing return-type overloading, the creators of Java decided not to do it for non-technical reasons.
Anyway, I think we understand each other now, and I shall endeavor to be clearer in the future.
ambiguousNull(null);
Should args be null? Or should args be new String[]{null}? In the absence of any more information the compiler chooses the first, but may still issue a warning. But you can clearly disambiguate by casting: ambiguousNull((String)null); // args is null
ambiguousNull((String[])null); // args is a 1-element array int something() { }
String something() { }
A hypothetical calling syntax could be (int)something();
Or the compiler could even choose one for you with a warning. Seems like a good suggestion for Java 10.The JVM does, `bar(foo: List<String>): String` can be compiled to `bar(Ljava/util/List;)Ljava/lang/String;` and `bar(foo: List<Int>): Int` to `bar(Ljava/util/list;)Ljava/lang/Integer;` or somesuch, there is no ambiguity at the bytecode level.
In fact, the documentation for Class#getMethod specifically outlines this issue[0]:
> Note that there may be more than one matching method in a class because while the Java language forbids a class to declare multiple methods with the same signature but different return types, the Java virtual machine does not.
[0] http://docs.oracle.com/javase/8/docs/api/java/lang/Class.htm...
But you are actually right. It looks that JVM bytecode includes full function signature in function invocation: https://www.ibm.com/developerworks/library/it-haggar_bytecod... (if I'm reading examples correctly).
My comment describes the actual JVM, hence having linked to official Java/JVM documentation.
One use case is API backwards compatibility. If your API wants to change the return type of a function, say from int to double, but also wants to maintain binary backwards compatibility, you can do that. See OverMapped [ https://github.com/Wolvereness/OverMapped ].
Obfuscation is another area and ProGuard employs this to make decompliling more difficult iirc.
String s = "123";
foo(s);
Java will call foo(string) if this is re-compiled, which is wrong. The decompiler would need to show foo((Object)s).On the CLR you can go even further and erase a lot of the type info and just pass objects around. That is, you can just change all your method signatures to foo(object, object, object) no matter which classes (not structs) you pass. Or have even more fun and randomize the classes. So now foo(string) becomes foo(DBConnectionInfo).
Since the JVM identifies methods by their name + argument types this would most likely break overloading unless done very carefully. I've decompiled quite a few Java applications and haven't seen anything like this either, but perhaps I've just had the luck to not yet run into it.
GHC would beg to differ.
A complaint against Ada is that it is hard for the compiler to figure out the right overloading in a complex statement with many nested function calls. My compilers prof took the time to show how to do it, and why it doesn't take much time.
Too bad C's automatic conversions prevent this from being used in C++.
Edit: Based on the responses below, I guess the point is that the disassembler can't generate Java code that will "naively" (wrong word, but I can't think of a better one) generate the same output. Notable (I assume) in that name munging would be problematic outside the current compilation unit.
It compiles.
Eventually that disassembled will come along, and then more tricks will be used by the obfuscators...
There is an arms race in whether the resulting output is at all human-comprehensible. The only thing preventing that from terminating with victory for the obfuscaters is that the obfuscators have a finite technical budget with which to obfuscate. Changing identifiers is nearly free, but as you go beyond that and start rewriting the source code itself they start incurring performance penalties and increasing odds that the rewrite will fail as they get more aggressive.
if (!new Exception().getStackTrace().getSha1sum().startsWith("0000")) alert("hello decompiler")
Your comment about java and turing completeness doesn't make sense unless you want the decompiler to basically output a java implementation of a JVM?
The interesting part to me is that it seems that Java would be perfectly capable of differentiating between methods by return type if the compiler was tweaked slightly. Is there a reason why this isn't a formal language feature?
Edit: here's the edge case though. You can call a function and use the return value directly as a parameter: foo(bar()). It's possible to have two foo that take both possible bar return types, at which point the compiler is stuck.
It could require a cast in this instance, however. The more I think about this the more I wonder why this isn't possible.
That's no more an edge case than the cases where you throw away the return value or you're binding to an ambiguous type e.g. `A getFoo()`, `B getFoo()`, `Object foo = getFoo()`.
> The more I think about this the more I wonder why this isn't possible.
The Java spec does not say, for C++ Stroustrup states it's
> to keep resolution for an individual operator or function call context-independent.
the Java reason is likely also some sort of Principle of Least Surprise claim.
One of the more fun things I've done is use the ASM and BCEL libraries to make (and unmake) these kinds of manipulations (manipulations that javac won't let you do.)
public static void main(String[] args) {
class Fun implements Supplier<String> {
public String get() { return null; }
}
Arrays.stream(Fun.class.getMethods())
.filter(m -> m.getDeclaringClass() == Fun.class)
.forEach(System.out::println);
}This is part of the bootstraping process of a programming language.
Usually such special types are built manually in the compiler data structures, or make use of special primitives, like native methods on Java's case.
Author admits this is not valid Java. It is not even compilable.
If I read correctly it is just artifacts from a partially sucessful decompile.
Interesting and this discussion is interesting but this is not and have never been valid Java.