Java Compiler transistive type checking behaviour - java

Consider the following scenario:
Let say I have a class A in "src" folder of my project.
class A {
void foo() {
B b = new B();
}
}
Class B is defined in another jar which is included as a dependency in build.gradle
class B extends C {
}
Now, Class C is defined in another jar which will be provided on runtime and not on compile time. Gradle is able to compile Class A without error.
But, when I import Class c in Class A then it gives "class not found".
import other.C; // this line gives error
class A {
void foo() {
B b = new B();
}
}
Is this the desired behavior of Java compiler to ignore the Class C if it not imported directly?
Also, what happened if use a function in class A using object of B which is in Class C but not overridden in class B.

The exact answer to your question depends on the Java compiler version and whether or not it requires access to C for doing its job.
All in all, I would say that such a setup is fragile and you should not do it. If your library that defines A requires B which effectively makes use of C in its public API as is the case for extends then C should be made visible to your library.

First of all we need to understand how java compilers work.
Whatever you reference by name or as a 'token' in your code, should be accessible to compiler during compilation phase.
The classes that you load using Class.forload or getClass method are not required to be available in the classpath.
What you are essentially referring by compiletime and runtime is about packaging.
What you say that a particular dependency say Class C will be provided at runtime, its an instruction to bundle tasks to ignore that dependency while building the jar. So if you compile and deploy your application as jar, class C will not be present in it. At the same time, the jar containing class B will be included in your deployment package.
If you provide exact gradle file, I might be able to answer more precise.

Related

How to make an interface a compile-only dependency when loading its implementation dynamically

Consider the following interface
// src/MyInterface.java
interface MyInterface {
public void quack();
}
which is used in the following application dynamically; i.e. its implementation is loaded dynamically—for demonstration purposes we'll just use the implementing class' name to determine which implementation to load.
// src/Main.java
class Main {
public static void main(String[] args) {
try {
MyInterface obj = (MyInterface) Class.forName("Implementation")
.getDeclaredConstructor()
.newInstance();
obj.quack();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
The following implementation of the interface is available:
// src/Implementation.java
class Implementation implements MyInterface {
public void quack() {
System.out.println("This is a sample implementation!");
}
}
As I would intuitively think, MyInterface provides information that is only relevant at compile-time, such as which methods can be invoked on objects that implement it, but it shouldn't be needed at runtime, since it doesn't provide any "executable code". But this is not the case: if I try to run the compiled Main.class without MyInterface.class, it complains:
$ javac -d bin/ src/*
$ rm bin/MyInterface.class
$ java -cp bin/ Main
Exception in thread "main" java.lang.NoClassDefFoundError: MyInterface
[...]
I guess it makes sense because it needs access to the MyInterface's Class object to perform the cast to MyInterface, so it needs to load MyInterface. But I feel there should be a way to make it a compile-time only dependency. How?
Some context
This question arose when I learned that there can be compile-time only dependencies, an example of which is the servlet api. I read that when compiling servlet code, you need to have the servlet-api (in Tomcat's case) jar, but at runtime it is not needed because the server provides an implementation. Since I didn't understand exactly how that could work, I tried setting up the little experiment above. Did I misunderstand what that means?
Edit: this Gradle page mentions that a compile-time only dependency could be
Dependencies whose API is required at compile time but whose implementation is to be provided by a consuming library, application or runtime environment.
What would be an example for that? I find that sentence a bit confusing, because it seems to imply that the API is not needed at runtime, and only the implementation is. From the answers, I gather that's not possible, right? (Unless somehow implementing a custom classloader?)
Yes, looks like you misunderstood example with servlet-api.jar. You need it in your project as a compile time dependency because Tomcat comes itself with that jar and that jar will be added to runtime classpath by Tomcat.
if you use classes/interfaces in your code they should be somehow added to classpath since your code depends on them.
And starting Java 8 interfaces can have default implementations for methods ("executable code") and interfaces also can have constants.
Maybe it is possible to run application without interface declaration but in that case you need to develop your custom Classloader which will check for interface implementation and load it instead of interface itself.
Did I misunderstand what that means?
Yes.
You're talking about "provided" dependencies (at least, that's what Maven calls them). Such a dependency still must be present on the classpath/modulepath at both compile-time and runtime. However, you don't have to include the provided dependency with your application when deploying your application, because the target container/framework already includes the dependency.

How to avoid exposing interfaces from other packages

Let us say an Interface MyInterface is present in jar A. jar B implements the interface . Now if I have to use this interface I need to take a dependency on jar A and B . What is the best way to avoid this double dependency ? The problem is if you are using gradle you need to specify a version for jar A and jar B. Updating at 2 places is tricky and half of the times I see one of them is updated but not the other. One way I am thinking is implement MyInterface2 on jarB and ask to modify the downstream consumers to use MyInterface2 they can declare dependency only on Jar B. But gradle will do the transitive dependency walk so that they can declare only one version. Any thoughts/comments ?
//======> Jar A <======
public interface MyInterface {
public void doSoemthing();
}
// ======> Jar B <=======
public class Impl1 implements MyInterface {
public void doSomething() { }
}
The gradle file for Jar B will look something like
compile('package:jarA:<Version>')
======> Project 1 <============
MyInterface foo = new Impl1();
The gradle file will look something like
compile('package:jarA:<Version>')
compile('package:jarB:<Version>')
The problem I have is I want the gradle to pull in jarA from transitive dependency, but if it is not there I get an compilation error saying that it could not find MyInterface. But if I have the explicit version I need to update it at 2 places and need to track the version dependencies.
Is there an easier way ?

Loading co-dependent groovy classes in Java

I'm trying to figure out how to load two co-dependent Groovy scripts in java at runtime. If I have two groovy scripts like:
A.groovy
import B
class A {
A() {
B b = new B()
}
}
B.groovy
import A
class B {
B() {
A a = new A()
}
}
I would like to load them as java classes, but when I run:
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
loader.parseClass(new File("A.groovy"));
I get the error:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
A.groovy: 1: unable to resolve class B
# line 1, column 1.
import B
I certainly understand the reason for the error, but is there any way to load these classes at runtime despite their co-dependency? Thanks!
GroovyClassLoader must be enabled to find B.groovy on the classpath. Normally that means you change the classpath of you application to include the root of the scripts. Since there is no package here for the scripts and since you use new File("A.groovy"), I would assume that it is here the current directory.
If you don't want to change the classpath of the application, you can also call addURL to add the path containing the scripts.
One more thing to mention.... parseClass will always create a newly parsed class. You might want to try a standard loadClass call instead to avoid compiling the file multiple times. But of course that works only after you fixed the lookup for GroovyClassLoader, because using loadClass, GroovyClassLoader will also have to look for A.groovy in the same manner it does have to look for B.groovy

"constructor has private access" error message

I'm working in Java and have come across an incredibly odd error. I have a very basic class as follows:
public class ClassA{
private static Logger log = Logger.getLogger(ClassA.class.getName());
private boolean trace;
public ClassA(){
trace = log.isTraceEnabled();
}
public void doSomething(){
//does stuff
}
}
I can use this class just fine within my current project. However, when I build, package, and install to my local repo (using Maven, no remote artifact repo set up), other projects cannot properly use this class because they cannot instantiate it. When I try anything like:
ClassA classA = new ClassA();
I get the following compilation error:
ClassA() has private access in [package].ClassA
I've decompiled the .jar in my local repo to ensure the constructor is present and is public - it is. I've also used the -U flag to force updates and the compilation continues to fail. What could be causing this error?
Maybe you have some other ClassA.class file somewhere in the classpath. Check all the jars used by the project that cannot call the constructor: one of them should contain an old version of your class.
My only thought is that you have a problem with your package. Make sure to define the package at the top of the source file for classA using the package keyword. When you call it ensure that the file is in include list with the include keyword. You could be running into the error because ClassA exists in some default package and that is what you are actually calling instead of calling your locally made ClassA class. The code you posted looks fine and you have already double checked to ensure the changes have taken effect in your repository.
//for those with Kotlin-Java mixed projects:
If the said file (With constructor) is in Kotlin and is being used in Java:
Instead of A a = new A(); //which causes the said error
Use A.INSTANCE. …
I have this error, where write "private", instead "public" for class constructor;

Object instantiation when dependency is missing (Java)

Guys, can anyone explain the following scenario:
1) Web application has module1.jar in its lib directory. There is a class A in that module:
package module1;
import module2.B;
public interface IA {
void methodOk() {}
void methodWithB(B param) {}
}
package module1;
import module2.B;
public class A implements IA {
public A() {}
//...
void methodWithB(B param) {
//do job on B
}
}
2) module2.jar is absent - it is not in the classpath.
3) Application is able to create objects of class A though it's missing the dependency. In application a method A.methodOk() is called.
Would be cool if you could give a reference to any spec on this.
Thanks a lot.
Since the code is already compiled, it will not throw an error until you directly use class B. From the looks of your code, you don't actually use an instance of B for anything.
If B is not used by A anywhere, then the resulting bytecode will have no reference to module2.B, therefore it gets compiled away. No dependency exists, except at compilation in this case.
If the question is unclear and B is used in A somewhere, then I'd be interested in seeing more code to try to determine what's going on.
Look at it from the perspective of the classloader. If you never have to load the class, you don't care if the bytecode for that class is missing.
Your question is really, "What triggers classloading?"
Two reasons I can think of off the top of my head are:
- Construction
- Static access

Categories