简体   繁体   English

编译Java类时禁用编译时依赖性检查

[英]Disabling compile-time dependency checking when compiling Java classes

Consider the following two Java classes: 考虑以下两个Java类:

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. 此外,假设在类路径中找不到pkg.not.in.classpath.FooBar

The first class will compile fine using the standard javac. 第一个类将使用标准javac进行编译。

However, the second class won't compile and javac will give you the error message "package pkg.not.in.classpath does not exist" . 但是,第二个类不会编译,javac会给你错误消息"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. 虽然很好,也很有帮助,但在编译时检查依赖项是非常严格需要AFAIK来生成上面示例中的Java类文件。

  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? 您是否可以提供在不执行编译时依赖性检查的情况下在技术上无法生成有效Java类文件的示例?

  2. Do you know of any way to instruct javac or any other Java compiler to skip the compile time dependency checking? 您是否知道有任何方法可以指示javac或任何其他Java编译器跳过编译时依赖性检查?

Please make sure your answer addresses both questions. 请确保您的答案解决了这两个问题。

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? 您是否可以提供在不执行编译时依赖性检查的情况下在技术上无法生成有效Java类文件的示例?

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 foo(int n) ,那么将生成以下指令:

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 foo(long n) ,则int将在方法调用之前提升为long

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. 在这种情况下,将无法生成调用指令或如何使用数字16填充类常量池中引用的CONSTANT_Methodref_info结构。有关更多详细信息,请参阅VM规范中的类文件格式

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. 如果找不到Foobar类,则无法编译Test类。

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. 根据Java语言规范 ,方法签名必须采用类型,这些类型在别处定义为在编译时可解析。

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. 这意味着虽然创建一个可以满足您要求的编译器在机械上非常简单,但它会违反JLS,因此在技术上不会是Java编译器。 Besides, circumventing compile-time safety doesn't sound like a great selling-point to me... :-) 此外,规避编译时安全对我来说听起来不是一个很好的卖点... :-)

I can't see how you could allow this without breaking java type checking. 我不知道如何在不破坏java类型检查的情况下允许这样做。 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? 您能否提供有关您要解决的问题的更多信息?

It would be a violation of the JLS to compile a class without looking at the type signatures of the classes it depends on. 编译一个类而不查看它所依赖的类的类型签名是违反JLS的。 No conformant Java compiler would allow you to do this. 没有符合要求的Java编译器允许您这样做。

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: 具体来说,如果我们有一个依赖于A的A类和B类,则可以执行以下操作:

  1. Compile A.java 编译A.java
  2. Compile B.java against A.class. 针对A.class编译B.java。
  3. Edit A.java to change it in an incompatible way. 编辑A.java以不兼容的方式更改它。
  4. Compile A.java, replacing the old A.class. 编译A.java,替换旧的A.class。
  5. Run a Java application using B.class and the new (incompatible) A.class. 使用B.class和新的(不兼容的)A.class运行Java应用程序。

If you do this, the application will fail with a IncompatibleClassChangeError when the class loader notices the signature incompatibility. 如果执行此操作,当类加载器注意到签名不IncompatibleClassChangeError时,应用程序将失败并返回IncompatibleClassChangeError

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 (eg using Class.forName() ) in the application or any of its dependencies, then some of these problems may not show up immediately. 实际上,如果在应用程序或其任何依赖项中存在任何类的动态加载(例如,使用Class.forName() ),则这些问题中的一些可能不会立即显示。

In summary, the cost of ignoring dependencies at compile time would be slower Java development and less reliable Java applications. 总之,在编译时忽略依赖关系的成本将是较慢的Java开发和较不可靠的Java应用程序。

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. Java按设计进行编译时依赖性检查,并使用它不仅可以确定类型,还可以在重载时确定方法调用。 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. 可以做什么(并且为JDBC驱动程序完成)是通过使用反射来延迟依赖性检查。 You can get the class from Class.forName without the compiler knowing the class at compile time. 您可以从Class.forName获取该类,而无需编译器在编译时知道该类。 In general, however, this means that the code is written to an interface and at runtime a class is loaded that implements the interface. 但是,通常,这意味着代码被写入接口,并且在运行时加载实现接口的类。

Extract interface 提取界面

pkg.in.classpath.IFooBar

make the FooBar implements IFooBar and 使FooBar implements IFooBar

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

Your Test class will get compiled. 您的Test类将被编译。 Just plug the right implementation ie FooBar in the runtime using factories and configuration. 只需使用工厂和配置在运行时插入正确的实现,即FooBar Look for some IOC containers . 寻找一些IOC容器

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: Java语法中没有任何内容可以使用pkg.not.in.classpath.FooBar来区分:

 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. 所以只有你的话,在那里使用FooBar是合法的。

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. 内部类在源代码中也称为pkg.not.in.classpath.FooBar ,但在类文件中将被称为pkg$not$in$classpath$FooBar而不是pkg/not/in/classpath/FooBar There is no way that javac can tell which you mean without looking for it in the classpath. 如果没有在类路径中查找它,javac就无法判断你的意思。

I created two classes : Caller and Callee 我创建了两个类: CallerCallee

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 . 我编译了这些类,然后使用javap -c Callee > Callee.bcjavap -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. 编译器为方法调用'callee'生成了方法签名和类型安全的invokevirtual调用 - 它知道在这里调用什么类和方法。 If that class wasn't available, how would the compiler generate the method signature or the `invokevirtual'? 如果该类不可用,编译器将如何生成方法签名或“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. 有一个JSR( JSR 292 )添加一个支持动态调用的'invokedynamic'操作码,但JVM目前不支持这种操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM