Отключение проверки зависимостей во время компиляции при компиляции классов Java

рассмотрим следующие два класса Java:

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

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

кроме того, предположим, что pkg.not.in.classpath.FooBar не находится в classpath.

первый класс будет компилироваться отлично, используя стандартный javac.

однако, второй класс не будет компилироваться и javac даст вам сообщение об ошибке "package pkg.not.in.classpath does not exist".

сообщение об ошибке приятно в общем случае, так как проверка ваших зависимостей позволяет компилятору сообщить вам, если вы получили какой-то аргумент метода неправильно, так далее.

В то время как приятно и полезно эта проверка зависимостей во время компиляции AFAIK не строго необходимо создать файл класса Java в приведенном выше примере.

  1. можете ли вы привести пример, для которого было бы технически невозможно создать допустимый файл класса Java без выполнения проверки зависимостей времени компиляции?

  2. знаете ли вы какой-либо способ инструктировать javac или любой другой компилятор Java, чтобы пропустить проверку зависимостей времени компиляции?

пожалуйста, убедитесь, что ваш ответ касается обоих вопросов.

8 ответов


можете ли вы привести пример, для которого было бы технически невозможно создать допустимый файл класса 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

в этом случае было бы невозможно создать инструкции вызова или как заполнить CONSTANT_Methodref_info структура, упомянутая в пуле констант класса числом 16. Вижу формат файла класса в спецификации ВМ для больше деталей.


Я не думаю, что есть такой способ - компилятор должен знать о классе аргумента, чтобы создать соответствующий байт-код. Если он не может найти класс Foobar, он не может скомпилировать Test класса.

обратите внимание, что в то время как ваши два класса функциональные equivalent поскольку вы на самом деле не используете аргумент, они не идентичны и будут давать Различный байт-код при компиляции.

Итак, ваша предпосылка-что компилятору не нужно найти класс для компиляции в этом случае-неверно.

редактировать - ваш комментарий, похоже, спрашивает: "не может ли компилятор просто игнорировать факт и генерировать байт-код, который б в любом случае уместен?"

ответ в том, что нет-не может. согласно спецификации языка 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 и класс B, который зависит от, ТО можно сделать следующее:

  1. Компилировать A.java
  2. компиляции B.java против A.class.
  3. изменить A.java изменить это несовместимо.
  4. компиляции A.java замена старого A.class.
  5. запустите приложение Java с помощью B.class и новое (несовместимое) A.class - ...

Если вы это сделаете, приложение не будет работать с IncompatibleClassChangeError когда загрузчик классов замечает несовместимость сигнатур.

На самом деле это иллюстрирует, почему компиляция игнорирующих зависимостей была бы плохой идеей. Если вы запускаете приложение с несогласованными файлами байт-кода, (только) первый будет сообщено об обнаруженной несогласованности. Поэтому, если у вас много несоответствий, вам нужно будет запускать приложение много раз, чтобы "обнаружить" их все. Действительно, если есть динамическая загрузка классов (например, использование Class.forName()) в приложении или любой из его зависимостей, то некоторые из этих проблем могут не отображаться сразу.

в целом, стоимость игнорирования зависимостей во время компиляции будет медленнее разработки Java и менее надежных приложений Java.


Java по дизайну выполняет проверку depenency во время компиляции и использует ее не только для определения типов, но и для определения вызовов методов при их перегрузке. Я не знаю, как это обойти.

что можно сделать (и делается, скажем, для драйверов JDBC), это отложить проверку зависимостей с помощью отражения. Вы можете получить класс Class.forName без компилятора, знающего класс во время компиляции. В общем, однако, это означает, что код записывается в интерфейс и на среда выполнения загружается класс, реализующий интерфейс.


извлечение интерфейса

pkg.in.classpath.IFooBar

сделать FooBar implements IFooBar и

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

ваш тестовый класс будет скомпилирован. Просто подключите правильную реализацию, т. е. FooBar во время выполнения с использованием фабрик и конфигурации. Искать какие-то IoC-контейнеры.


единственное, что вы можете сделать, это использовать некоторые манипуляции байт-кода, чтобы преобразовать его в более конкретный тип.

в грамматике Java нет ничего для использования pkg.not.in.classpath.FooBar чтобы отличить этот:

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

из этого:

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

таким образом, есть только ваше слово, что законно использовать FooBar там.

существует также неоднозначность между классами с областью действия пакета и внутренними классами в source:

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 мог сказать, что вы имеете в виду, не ища его в пути к классам.


Я создал два класса : Caller и 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() {
    }
}

я скомпилировал эти классы, а затем разобрал их с помощью javap -c Callee > Callee.bc и javap -c Caller > Caller.bc. Это привело к следующему:--8-->

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

}

компилятор сгенерировал сигнатуру метода и typesafe invokevirtual вызов метода вызывает 'callee' - он знает, какой класс и какой метод вызывается здесь. Если этот класс недоступен, как компилятор будет генерировать сигнатуру метода или "invokevirtual"?

существует JSR (JSR 292), чтобы добавить код операции "invokedynamic", который будет поддерживать динамический вызов, однако в настоящее время это не поддерживается JVM.