views:

448

answers:

8

Consider the following two Java classes:

a.) class Test { void foo(Object foobar) { } }

b.) class Test { void foo(pkg.not.in.classpath.FooBar foobar) { } }

Furthermore, assume that pkg.not.in.classpath.FooBar is not found in the classpath.

The first class will compile fine using the standard javac.

However, the second class won't compile and javac will give you the error message "package pkg.not.in.classpath does not exist".

The error message is nice in the general case since checking your dependencies allows the compiler to tell you if you got some method argument wrong, etc.

While nice and helpful this checking of dependencies at compile-time is AFAIK not strictly needed to generate the Java class file in the example above.

  1. Can you give any example for which it would be technically impossible to generate a valid Java class file without performing compile time dependency checking?

  2. Do you know of any way to instruct javac or any other Java compiler to skip the compile time dependency checking?

Please make sure your answer addresses both questions.

+2  A: 

I can't see how you could allow this without breaking java type checking. How would you use your referenced object in your method? To extend on your example,

class test {
   void foo (pkg.not.in.classpath.FooBar foobar) { 
       foobar.foobarMethod(); //what does the compiler do here?
  } 
}

If you're in some situation where you've got to compile (and call a method ) on something that works on a library you don't have access to the closest you can come is to get the method via reflection, something like (method calls from memory, may be inaccurate)

 void foo(Object suspectedFoobar)
     {
       try{
        Method m = suspectedFoobar.getClass().getMethod("foobarMethod");
        m.invoke(suspectedFoobar);
       }
       ...
     }

I can't really see the point of doing this, though. Can you provide more information on the problem you're trying to solve?

Steve B.
The compiler would just happily generate the class file. If foobarMethod() exists or not is a runtime concern.
knorv
"Can you provide more information on the problem you're trying to solve?" I'm not trying to solve any problem. I want to know the answers to my two questions in order to better understand the JVM.
knorv
@knorv - "If foobarMethod() exists or not is a runtime concern." - No. It's not. At least not in Java.
Nate
Nate: Are you implying that skipping the dependency checking at compile time is impossible? Please be more specific.
knorv
@knorv - added a post to explain this more...
Nate
+4  A: 

I don't think there is such a way - the compiler needs to know about the class of the argument, in order to create appropriate bytecode. If it cannot locate the Foobar class, it cannot compile the Test class.

Note that while your two classes are functionally equivalent since you're not really using the argument, they aren't identical and will yield different bytecode when compiled.

So your premise - that the compiler doesn't need to find the class to compile in this case - is incorrect.

Edit - your comment seems to be asking "can't the compiler just overlook the fact and generate the bytecode that would be appropriate anyway?"

The answer is that no - it can't. According to the Java Language Specification, method signatures must take types, which are elsewhere defined to be resolvable at compile-time.

Which means that while it would be mechanically quite simple to create a compiler that would do what you're asking for, it would violate the JLS and thus wouldn't technically be a Java compiler. Besides, circumventing compile-time safety doesn't sound like a great selling-point to me... :-)

Andrzej Doyle
How is the generated bytecode affected more than "java/lang/Object" being replaced with "pkg/not/in/classpath/FooBar"? Please be more specific.
knorv
I think the sample code is a special case because the object is not actually used (no methods are called, it's not used as an argument anywhere), so in this case a compiler *might* be able to compile it without knowing about the class. But I can't think of a good use for this special-case.
Joachim Sauer
A: 

Extract interface

pkg.in.classpath.IFooBar

make the FooBar implements IFooBar and

class Test { void foo(pkg.in.classpath.IFooBar foobar) {} }

Your Test class will get compiled. Just plug the right implementation i.e. FooBar in the runtime using factories and configuration. Look for some IOC containers.

Boris Pavlović
How could you have Foobar implement IFooBar without reference to the original FooBar? The compiler won't accept this even if the method signature is identical.
Steve B.
Boris: Thanks, but I'm not looking for a work-around. What do you think about the two questions asked?
knorv
+6  A: 

Can you give any example for which it would be technically impossible to generate a valid Java class file without performing compile time dependency checking?

Consider this code:

public class GotDeps {
  public static void main(String[] args) {
    int i = 1;
    Dep.foo(i);
  }
}

If the target method has the signature public static void foo(int n), then these instructions will be generated:

public static void main(java.lang.String[]);
  Code:
   0:   iconst_1
   1:   istore_1
   2:   iload_1
   3:   invokestatic    #16; //Method Dep.foo:(I)V
   6:   return

If the target method has the signature public static void foo(long n), then the int will be promoted to a long prior to the method invocation:

public static void main(java.lang.String[]);
  Code:
   0:   iconst_1
   1:   istore_1
   2:   iload_1
   3:   i2l
   4:   invokestatic    #16; //Method Dep.foo:(J)V
   7:   return

In this case, it would not be possible to generate the invocation instructions or how to populate the CONSTANT_Methodref_info structure referred to in the class constant pool by the number 16. See the class file format in the VM spec for more details.

McDowell
Great answer! To the point and with a clear proof (as opposed to the hand-waving usually seen on SO :-)).
knorv
@knorv: can you prove that there's hand-waving on SO or are you just hand-waving? ;-)
Joachim Sauer
Joachim: No, that's very subjective and hence impossible to prove .-)
knorv
@knorv: that looks like an admission of hand-waving ... Mister Pot :-)
Stephen C
A: 

About the only thing you can do is use some bytecode manipulation to transform it to the more specific type.

There is nothing in the Java grammar for a use of pkg.not.in.classpath.FooBar to distinguish this:

 package pkg.not.in.classpath;
 public class FooBar { }

from this:

 package pkg.not.in.classpath;
 class FooBar { }

So there's only your word that it's legal to use FooBar there.

There also an ambiguity between package scoped classes and inner classes in source:

class pkg {
    static class not {
        static class in {
            static class classpath {
                static class FooBar {}
            }
        }
    }
}

The inner class is also called pkg.not.in.classpath.FooBar in the source but will be referred to as pkg$not$in$classpath$FooBar rather than pkg/not/in/classpath/FooBar in the class file. There is no way that javac can tell which you mean without looking for it in the classpath.

Pete Kirkham
You're absolutely correct wrt inner classes. But regarding "public" vs "protected" - that doesn't alter the bytecode and could hence theoretically be deferred to being checked at runtime. Or am I missing something?
knorv
Yes, it could be deferred until later - IIRC it would fail when the class was verified on loading it.
Pete Kirkham
+1  A: 

Java by design does compile-time depenency checking and uses it not only to determine types but to determine method calls when they are overloaded. I know of no way around that.

What can be done (and is done for, say, JDBC drivers) is to delay dependency checking through the use of reflection. You can get the class from Class.forName without the compiler knowing the class at compile time. In general, however, this means that the code is written to an interface and at runtime a class is loaded that implements the interface.

Kathy Van Stone
+2  A: 

It would be a violation of the JLS to compile a class without looking at the type signatures of the classes it depends on. No conformant Java compiler would allow you to do this.

However ... it is possible to do something rather similar. Specifically, if we have a class A and a class B that depends on A, it is possible to do the following:

  1. Compile A.java
  2. Compile B.java against A.class.
  3. Edit A.java to change it in an incompatible way.
  4. Compile A.java, replacing the old A.class.
  5. Run a Java application using B.class and the new (incompatible) A.class.

If you do this, the application will fail with a IncompatibleClassChangeError when the class loader notices the signature incompatibility.

Actually, this illustrates why compiling ignoring dependencies would be a bad idea. If you run an application with inconsistent bytecode files, (only) the first inconsistency detected will be reported. So if you have lots of inconsistencies, you will need to run your application lots of times to "detect" them all. Indeed, if there is any dynamic loading of classes (e.g. using Class.forName()) in the application or any of its dependencies, then some of these problems may not show up immediately.

In summary, the cost of ignoring dependencies at compile time would be slower Java development and less reliable Java applications.

Stephen C
A: 

I created two classes : Caller and Callee

public class Caller {
    public void doSomething( Callee callee) {
        callee.doSomething();
    }

    public void doSame(Callee callee) {
        callee.doSomething();
    }

    public void doSomethingElse(Callee callee) {
        callee.doSomethingElse();
    }
}

public class Callee {
    public void doSomething() {
    }
    public void doSomethingElse() {
    }
}

I compiled these classes and then disassembled them with javap -c Callee > Callee.bc and javap -c Caller > Caller.bc. This produced the following:

Compiled from "Caller.java"
public class Caller extends java.lang.Object{
public Caller();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

public void doSomething(Callee);
Code:
0: aload_1
1: invokevirtual #2; //Method Callee.doSomething:()V
4: return

public void doSame(Callee);
Code:
0: aload_1
1: invokevirtual #2; //Method Callee.doSomething:()V
4: return

public void doSomethingElse(Callee);
Code:
0: aload_1
1: invokevirtual #3; //Method Callee.doSomethingElse:()V
4: return

}

Compiled from "Callee.java"
public class Callee extends java.lang.Object{
public Callee();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

public void doSomething();
Code:
0: return

public void doSomethingElse();
Code:
0: return

}

The compiler generated a method signature and a typesafe invokevirtual call for the method calls to 'callee' - it knows what class and what method is being invoked here. If that class wasn't available, how would the compiler generate the method signature or the `invokevirtual'?

There is a JSR (JSR 292) to add an 'invokedynamic' opcode that would support dynamic invocation, however this isn't currently supported by the JVM.

Nate