繁体   English   中英

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

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

考虑以下两个Java类:

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

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

此外,假设在类路径中找不到pkg.not.in.classpath.FooBar

第一个类将使用标准javac进行编译。

但是,第二个类不会编译,javac会给你错误消息"package pkg.not.in.classpath does not exist"

在一般情况下,错误消息很好,因为检查依赖项允许编译器告诉您是否有一些方法参数错误等。

虽然很好,也很有帮助,但在编译时检查依赖项是非常严格需要AFAIK来生成上面示例中的Java类文件。

  1. 您是否可以提供在不执行编译时依赖性检查的情况下在技术上无法生成有效Java类文件的示例?

  2. 您是否知道有任何方法可以指示javac或任何其他Java编译器跳过编译时依赖性检查?

请确保您的答案解决了这两个问题。

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

考虑以下代码:

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

如果目标方法具有签名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

如果目标方法具有签名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

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

我不认为有这样的方法 - 编译器需要知道参数的类,以便创建适当的字节码。 如果找不到Foobar类,则无法编译Test类。

请注意,虽然您的两个类在功能上是等效的,因为您并未真正使用该参数,但它们并不相同,并且在编译时会产生不同的字节码。

所以你的前提 - 编译器不需要在这种情况下找到要编译的类 - 是不正确的。

编辑 -您的评论似乎在问:“不能编译只是忽略了一个事实,并生成字节码,这是适当的呢?”

答案是,不 - 它不能。 根据Java语言规范 ,方法签名必须采用类型,这些类型在别处定义为在编译时可解析。

这意味着虽然创建一个可以满足您要求的编译器在机械上非常简单,但它会违反JLS,因此在技术上不会是Java编译器。 此外,规避编译时安全对我来说听起来不是一个很好的卖点... :-)

我不知道如何在不破坏java类型检查的情况下允许这样做。 您将如何在方法中使用引用的对象? 为了扩展你的例子,

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

如果你在某些情况下你必须编译(并调用一个方法)在一个适用于库的东西上你无法访问最接近你可以通过反射得到方法的东西,例如(从内存调用方法,可能不准确)

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

但是,我无法真正看到这样做的重点。 您能否提供有关您要解决的问题的更多信息?

编译一个类而不查看它所依赖的类的类型签名是违反JLS的。 没有符合要求的Java编译器允许您这样做。

但是......有可能做一些相似的事情。 具体来说,如果我们有一个依赖于A的A类和B类,则可以执行以下操作:

  1. 编译A.java
  2. 针对A.class编译B.java。
  3. 编辑A.java以不兼容的方式更改它。
  4. 编译A.java,替换旧的A.class。
  5. 使用B.class和新的(不兼容的)A.class运行Java应用程序。

如果执行此操作,当类加载器注意到签名不IncompatibleClassChangeError时,应用程序将失败并返回IncompatibleClassChangeError

实际上,这说明了为什么编译忽略依赖关系会是一个坏主意。 如果运行具有不一致字节码文件的应用程序(仅),将报告检测到的第一个不一致。 因此,如果你有很多不一致的地方,你需要多次运行你的应用程序来“检测”它们。 实际上,如果在应用程序或其任何依赖项中存在任何类的动态加载(例如,使用Class.forName() ),则这些问题中的一些可能不会立即显示。

总之,在编译时忽略依赖关系的成本将是较慢的Java开发和较不可靠的Java应用程序。

Java按设计进行编译时依赖性检查,并使用它不仅可以确定类型,还可以在重载时确定方法调用。 我知道没办法解决这个问题。

可以做什么(并且为JDBC驱动程序完成)是通过使用反射来延迟依赖性检查。 您可以从Class.forName获取该类,而无需编译器在编译时知道该类。 但是,通常,这意味着代码被写入接口,并且在运行时加载实现接口的类。

提取界面

pkg.in.classpath.IFooBar

使FooBar implements IFooBar

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

您的Test类将被编译。 只需使用工厂和配置在运行时插入正确的实现,即FooBar 寻找一些IOC容器

关于你唯一能做的就是使用一些字节码操作将它转换为更具体的类型。

Java语法中没有任何内容可以使用pkg.not.in.classpath.FooBar来区分:

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

由此:

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

所以只有你的话,在那里使用FooBar是合法的。

包源范围类和源中的内部类之间也存在歧义:

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

内部类在源代码中也称为pkg.not.in.classpath.FooBar ,但在类文件中将被称为pkg$not$in$classpath$FooBar而不是pkg/not/in/classpath/FooBar 如果没有在类路径中查找它,javac就无法判断你的意思。

我创建了两个类: 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() {
    }
}

我编译了这些类,然后使用javap -c Callee > Callee.bcjavap -c Caller > Caller.bc它们进行了反汇编。 这产生了以下结果:

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

}

编译器为方法调用'callee'生成了方法签名和类型安全的invokevirtual调用 - 它知道在这里调用什么类和方法。 如果该类不可用,编译器将如何生成方法签名或“invokevirtual”?

有一个JSR( JSR 292 )添加一个支持动态调用的'invokedynamic'操作码,但JVM目前不支持这种操作。

暂无
暂无

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

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