簡體   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