Binary compatibility issue - an example? - java

As far as I understand the source compatibility and how you can easily show an example that would break source compatibility (change name of the method, remove method etc.), I am having a bit of a problem seeing how binary compatibility can be broken in practice. Does anyone have a simple example of preservation of source compatibility that would cause binary compatibility issues i.e. no code changes are required but recompilation is necesssary?

One example (and this is by no means the only one) would be if the signature of a method in a library changes, in a compatible way. For example, consider:
// Library.java v1
public class Library {
public static void print(String foo) {
System.out.println(foo);
}
}
// Client.java v1
public class Client {
public static void main(String[] args) {
Library.print("hello");
}
}
Compile and run:
$ javac Client.java Library.java
$ java Client
hello
Now change Library.java - note the type of the foo parameter:
// Library.java v2
public class Library {
public static void print(Object foo) {
System.out.println(foo);
}
}
Just recompile Library.java and try to rerun Client:
$ javac Library.java
$ java Client
Exception in thread "main" java.lang.NoSuchMethodError: Library.print(Ljava/lang/String;)V
at Client.main(Client.java:3)

First need to understand both compatibility.
Source compatibility - Program is source compatible with new version if Program can be compiled with that new version of code(library or api)
Binary compatibility - Program is binary compatible with new version of code if Program can be linked with that code without recompilation
Following link has more example for "Source compatible but Binary incompatible "
Specialising Return Types
Generalising Parameter Types
Primitive vs Wrapper Types
Read http://praitheesh.blogspot.com.au/2014/09/compatibility-and-api-evolution-in-java.html for more details.

If you import an interface with string constants.
(An anti-pattern in Java.)
Then the importing class copies the constants in the constant table, and uses those constants immediately. The import dependency to the interface then is missing.
When the string value of the constant in the interface is changed, the compiler does not see that it needs to recompile the class that will remain using the old value - as there is no longer an import to the interface.
The running is not broken, but the behaviour is - wrong value.

An example I met :
public class Class1 {
public void do() {
System.out.println("do!");
}
}
Client part :
public class Class2 {
public void callDo() {
Class1 c = new Class1();
c.do();
}
}
Now you change the return of do method :
public class Class1 {
public String do() {
System.out.println("do!");
return "done!";
}
}
If you run the client code without a recompilation you will get a NoSuchMethodError exception because the method signature has changed.

Related

Why prefer the indirect generic's import to the actual class?

Using eclipse if I write this interface in the package mypack:
package mypack;
public interface MyInterface<A>{
public interface Test{
void sayHi();
}
}
And if I write this class in no package.
public class Test implements mypack.MyInterface<mypack.MyInterface.Test> {
private Test test = new Test();
}
Eclipse trigger me an error at compile-time, that I must implement the method sayHi().
I see no way out!
If I Ctrl+LMB to the type of the field test it takes me to the Class.
Bug reported
A small bug is reported here: https://bugs.eclipse.org/bugs/show_bug.cgi?id=488077
What is happening here is
Test test = new Test();
the Test is being taken as a nested-type of the MyInterface you inherit from.
I will look into the JLS to see if there is a reason it chooses the inherited class over it's own name.
Note: MyInterface doesn't have to be generic. A simpler form of this problem is
interface MyInterface {
interface Test {
}
}
class Test extends MyInterface {
Test test = new Test(); // thinks this is the MyInterface.Test
}
BTW: As this is very confusion combination of class structure and names, I suggest you never do this in reality.
A note from JLS 7.4.2
Unnamed packages are provided by the Java SE platform principally for convenience when developing small or temporary applications or when just beginning development.

Java 8 - default methods - concerns for legacy code

Question from a book:
In the past (pre-Java 8), you were told that it’s bad form to add methods to an interface because it would break existing code. Now you are told that it’s okay to add new methods, provided you also supply a default implementation.
How safe is that? Describe a scenario where the new stream method of the Collection interface causes legacy code to fail compilation.
What about binary compatibility? Will legacy code from a JAR file still run?"
My answers are as follows but I am not quite sure about them.
It's safe only if legacy code does not provide a method with the same name stream and with the same signature (e.g. in a legacy class that implements Collection). Otherwise, this old legacy code will fail compilation.
I think binary compatibility is preserved, legacy code from old JAR file will still run. But I have no clear arguments at all about this.
Could anyone confirm or reject these answers, or just add some more arguments, references, or clarity to these answers?
The new stream() default method in Collection returns a Stream<E>, also a new type in Java 8. Legacy code will fail compilation if it contains a stream() method with the same signature, but returning something else, resulting in a clash of return types.
Legacy code will continue to run as long as it's not recompiled.
First, in 1.7, set up the following:
public interface MyCollection {
public void foo();
}
public class Legacy implements MyCollection {
#Override
public void foo() {
System.out.println("foo");
}
public void stream() {
System.out.println("Legacy");
}
}
public class Main {
public static void main(String args[]) {
Legacy l = new Legacy();
l.foo();
l.stream();
}
}
With -source 1.7 -target 1.7, this compiles and runs:
$ javac -target 1.7 -source 1.7 Legacy.java MyCollection.java Main.java
$ java Main
foo
Legacy
Now in 1.8, we add the stream method to MyCollection.
public interface MyCollection
{
public void foo();
public default Stream<String> stream() {
return null;
}
}
We compile only MyCollection in 1.8.
$ javac MyCollection.java
$ java Main
foo
Legacy
Of course we can't recompile Legacy.java any more.
$ javac Legacy.java
Legacy.java:11: error: stream() in Legacy cannot implement stream() in MyCollection
public void stream()
^
return type void is not compatible with Stream<String>
1 error

Cannot reproduce result of Type Erasure example

I am reading 'Java Generics and Collections' section 8.4. The author defines the following code while trying to explain Binary Compatibility:
interface Name extends Comparable {
public int compareTo(Object o);
}
class SimpleName implements Name {
private String base;
public SimpleName(String base) {
this.base = base;
}
public int compareTo(Object o) {
return base.compareTo(((SimpleName)o).base);
}
}
class ExtendedName extends SimpleName {
private String ext;
public ExtendedName(String base, String ext) {
super(base); this.ext = ext;
}
public int compareTo(Object o) {
int c = super.compareTo(o);
if (c == 0 && o instanceof ExtendedName)
return ext.compareTo(((ExtendedName)o).ext);
else
return c;
}
}
class Client {
public static void main(String[] args) {
Name m = new ExtendedName("a","b");
Name n = new ExtendedName("a","c");
assert m.compareTo(n) < 0;
}
}
and then talks about making the Name interface and SimpleName class generic and leaving the ExtendedName as is. As a result the new code is:
interface Name extends Comparable<Name> {
public int compareTo(Name o);
}
class SimpleName implements Name {
private String base;
public SimpleName(String base) {
this.base = base;
}
public int compareTo(Name o) {
return base.compareTo(((SimpleName)o).base);
}
}
// use legacy class file for ExtendedName
class Test {
public static void main(String[] args) {
Name m = new ExtendedName("a","b");
Name n = new ExtendedName("a","c");
assert m.compareTo(n) == 0; // answer is now different!
}
}
The author describes the result of such an action as following:
Say that we generify Name and SimpleName so that they define
compareTo(Name), but that we do not have the source for ExtendedName. Since it defines
only compareTo(Object), client code that calls compareTo(Name) rather than compareTo(Object) will invoke the method on SimpleName (where it is defined) rather than
ExtendedName (where it is not defined), so the base names will be compared but the
extensions ignored.
However when I make only Name and SimpleName generic I get a compile time error and not what the author describes above. The error is:
name clash: compareTo(Object) in NameHalfMovedToGenerics.ExtendedName and compareTo(T) in Comparable have the same erasure, yet neither overrides the other
And this is not the first time I am facing such an issue - earlier while trying to read Sun documentation on erasure I faced a similar issue where my code doesn't show the same result as described by the author.
Have I made a mistake in understanding what the author is trying to say?
Any help will be much appreciated.
Thanks in advance.
This is an example of a problem that can occur under separate compilation.
The main subtlety with separate compilation is that, when a caller class is compiled, certain information is copied from the callee into the caller's class file. If the caller is later run against a different version of the callee, the information copied from the old version of the callee might not match exactly the new version of the callee, and the results might be different. This is very hard to see by just looking at source code. This example shows how the behavior of a program can change in a surprising way when such a modification is made.
In the example, Name and SimpleName were modified and recompiled, but the old, compiled binary of ExtendedName is still used. That's really what it means by "the source code for ExtendedName is not available." When a program is compiled against the modified class hierarchy, it records different information than it would have if it were compiled against the old hierarchy.
Let me run through the steps I performed to reproduce this example.
In an empty directory, I created two subdirectories v1 and v2. In v1 I put the classes from the first example code block into separate files Name.java, SimpleName.java, and ExtendedName.java.
Note that I'm not using the v1 and v2 directories as packages. All these files are in the unnamed package. Also, I'm using separate files, since if they're all nested classes it's hard to recompile some of them separately, which is necessary for the example to work.
In addition I renamed the main program to Test1.java and modified it as follows:
class Test1 {
public static void main(String[] args) {
Name m = new ExtendedName("a","b");
Name n = new ExtendedName("a","c");
System.out.println(m.compareTo(n));
}
}
In v1 I compiled everything and ran Test1:
$ ls
ExtendedName.java Name.java SimpleName.java Test1.java
$ java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
$ javac *.java
$ java Test1
-1
Now, in v2 I placed the Name.java and SimpleName.java files, modified using generics as shown in the second example code block. I also copied in v1/Test1.java to v2/Test2.java and renamed the class accordingly, but otherwise the code is the same.
$ ls
Name.java SimpleName.java Test2.java
$ javac -cp ../v1 *.java
$ java -cp .:../v1 Test2
0
This shows that the result of m.compareTo(n) is different after Name and SimpleName were modified, while using the old ExtendedName binary. What happened?
We can see the difference by looking at the disassembled output from the Test1 class (compiled against the old classes) and the Test2 class (compiled against the new classes) to see what bytecode is generated for the m.compareTo(n) call. Still in v2:
$ javap -c -cp ../v1 Test1
...
29: invokeinterface #8, 2 // InterfaceMethod Name.compareTo:(Ljava/lang/Object;)I
...
$ javap -c Test2
...
29: invokeinterface #8, 2 // InterfaceMethod Name.compareTo:(LName;)I
...
When compiling Test1, the information copied into the Test1.class file is a call to compareTo(Object) because that's the method the Name interface has at this point. With the modified classes, compiling Test2 results in bytecode that calls compareTo(Name) since that's what the modified Name interface now has. When Test2 runs, it looks for the compareTo(Name) method and thus bypasses the compareTo(Object) method in the ExtendedName class, calling SimpleName.compareTo(Name) instead. That's why the behavior differs.
Note that the behavior of the old Test1 binary does not change:
$ java -cp .:../v1 Test1
-1
But if Test1.java were recompiled against the new class hierarchy, its behavior would change. That's essentially what Test2.java is, but with a different name so that we can easily see the difference between running an old binary and a recompiled version.

Instantiation error

I am following a beginner's java book and one of the projects is to pick an error from commonly thrown errors and try to do what you can to induce the error.
I picked InstantiationError and am up to the level of knowing you can't instantiate abstract classes or interfaces. The docs says the error is usually caught by the compiler but can be thrown at runtime if the definition of a class has incompatibly changed.
I don't know how a class definition could be changed while the program is running and am looking for some tips on where I should read further.
Thanks!
I don't know how a class definition could be changed while the program is running
It can't change while it's running but it can change after you've compiled.
For example, try this:
// In Test.java
public class Test {
public static void main(String[] args){
System.out.println(new Foo());
}
}
// In Foo.java
public class Foo {
}
Compile:
javac Foo.java Test.java
Run:
java Test
// Some output like this...
Foo#1d6535bf
Now change Foo.java like this:
// No parameterless constructor any more!
public class Foo {
public Foo(int x) {
}
}
Recompile just Foo.java:
javac Foo.java
And rerun Test:
Exception in thread "main" java.lang.NoSuchMethodError: Foo: method <init>()V
not found
at Test.main(Test.java:3)
This is not what I'd call a "commonly thrown error" though.
Note that that's not InstantiationError - but you can change Foo.java again, to:
public interface Foo {
}
Again, just recompile Foo.java and this time you'll get:
Exception in thread "main" java.lang.InstantiationError: Foo
at Test.main(Test.java:3)
Maybe when you instantiate an abstract class at runtime using reflection.
Code sample:
public abstract class MyAbstractClass{
}
public class MyMainClass() {
public void test(String[] args) {
this.getClass().getClassLoader().loadClass("MyAbstractClass").getConstructors()[0].newInstance();
}
}
Have a look at the Reflection API, which offers a way to instantiate a class by name.
public void throwError() {
AbstractType type = this.getClassLoader().newInstance("my.abstract.Type");
}
a simple thing that can cause this type of error is if you try to create an object with the wrong number of parameters. say the constructor takes two parameters, but you have your interface designed in such a way that the user could input 3 or more parameters. normally, the number of parameters would be caught by the compiler, but if it was created "on the fly" then it would get caught at runtime

Java: Multiple class declarations in one file

In Java, you can define multiple top level classes in a single file, providing that at most one of these is public (see JLS §7.6). See below for example.
Is there a tidy name for this technique (analogous to inner, nested, anonymous)?
The JLS says the system may enforce the restriction that these secondary classes can't be referred to by code in other compilation units of the package, e.g., they can't be treated as package-private. Is that really something that changes between Java implementations?
e.g., PublicClass.java:
package com.example.multiple;
public class PublicClass {
PrivateImpl impl = new PrivateImpl();
}
class PrivateImpl {
int implementationData;
}
Javac doesn't actively prohibit this, but it does have a limitation that pretty much means that you'd never want to refer to a top-level class from another file unless it has the same name as the file it's in.
Suppose you have two files, Foo.java and Bar.java.
Foo.java contains:
public class Foo
Bar.java contains:
public class Bar
class Baz
Let's also say that all of the classes are in the same package (and the files are in the same directory).
What happens if Foo refers to Baz but not Bar and we try to compile Foo.java? The compilation fails with an error like this:
Foo.java:2: cannot find symbol
symbol : class Baz
location: class Foo
private Baz baz;
^
1 error
This makes sense if you think about it. If Foo refers to Baz, but there is no Baz.java (or Baz.class), how can javac know what source file to look in?
If you instead tell javac to compile Foo.java and Bar.java at the same time, or if you had previously compiled Bar.java (leaving the Baz.class where javac can find it), or even if Foo happens to refer to Bar in addition to Baz, then this error goes away. This makes your build process feel very unreliable and flaky, however.
Because the actual limitation, which is more like "don't refer to a top-level class from another file unless it either has the same name as the file it's in or you're also referring to another class that's named the same thing as that file that's also in that file" is kind of hard to follow, people usually go with the much more straightforward (though stricter) convention of just putting one top-level class in each file. This is also better if you ever change your mind about whether a class should be public or not.
Newer versions of javac can also produce a warning in this situation with -Xlint:all:
auxiliary class Baz in ./Bar.java should not be accessed from outside its own source file
Sometimes there really is a good reason why everybody does something in a particular way.
My suggested name for this technique (including multiple top-level classes in a single source file) would be "mess". Seriously, I don't think it's a good idea - I'd use a nested type in this situation instead. Then it's still easy to predict which source file it's in. I don't believe there's an official term for this approach though.
As for whether this actually changes between implementations - I highly doubt it, but if you avoid doing it in the first place, you'll never need to care :)
I believe you simply call PrivateImpl what it is: a non-public top-level class. You can also declare non-public top-level interfaces as well.
e.g., elsewhere on SO: Non-public top-level class vs static nested class
As for changes in behavior between versions, there was this discussion about something that "worked perfectly" in 1.2.2. but stopped working in 1.4 in sun's forum: Java Compiler - unable to declare a non public top level classes in a file.
You can have as many classes as you wish like this
public class Fun {
Fun() {
System.out.println("Fun constructor");
}
void fun() {
System.out.println("Fun mathod");
}
public static void main(String[] args) {
Fun fu = new Fun();
fu.fun();
Fen fe = new Fen();
fe.fen();
Fin fi = new Fin();
fi.fin();
Fon fo = new Fon();
fo.fon();
Fan fa = new Fan();
fa.fan();
fa.run();
}
}
class Fen {
Fen() {
System.out.println("fen construuctor");
}
void fen() {
System.out.println("Fen method");
}
}
class Fin {
void fin() {
System.out.println("Fin method");
}
}
class Fon {
void fon() {
System.out.println("Fon method");
}
}
class Fan {
void fan() {
System.out.println("Fan method");
}
public void run() {
System.out.println("run");
}
}
Just FYI, if you are using Java 11+, there is an exception to this rule: if you run your java file directly (without compilation). In this mode, there is no restriction on a single public class per file. However, the class with the main method must be the first one in the file.
1.Is there a tidy name for this technique (analogous to inner, nested, anonymous)?
Multi-class single-file demo.
2.The JLS says the system may enforce the restriction that these secondary classes can't be referred to by code in other compilation units of the package, e.g., they can't be treated as package-private. Is that really something that changes between Java implementations?
I'm not aware of any which don't have that restriction - all the file based compilers won't allow you to refer to source code classes in files which are not named the same as the class name. ( if you compile a multi-class file, and put the classes on the class path, then any compiler will find them )
Yes you can, with public static members on an outer public class, like so:
public class Foo {
public static class FooChild extends Z {
String foo;
}
public static class ZeeChild extends Z {
}
}
and another file that references the above:
public class Bar {
public static void main(String[] args){
Foo.FooChild f = new Foo.FooChild();
System.out.println(f);
}
}
put them in the same folder. Compile with:
javac folder/*.java
and run with:
java -cp folder Bar
According to Effective Java 2nd edition (Item 13):
"If a package-private top-level class (or interface) is used by only
one class, consider making the top-level class a private nested class
of the sole class that uses it (Item 22). This reduces its
accessibility from all the classes in its package to the one class
that uses it. But it is far more important to reduce the accessibility
of a gratuitously public class than a package-private top-level class:
... "
The nested class may be static or non-static based on whether the member class needs access to the enclosing instance (Item 22).
No. You can't. But it is very possible in Scala:
class Foo {val bar = "a"}
class Bar {val foo = "b"}

Categories

Resources