Подтвердить что ты не робот

Отключение проверки зависимостей во время компиляции при компиляции классов 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 не найден в пути к классам.

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

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

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

Это означает, что, хотя было бы довольно просто создать компилятор, который будет делать то, о чем вы просите, это нарушит JLS и, следовательно, технически не будет компилятором Java. Кроме того, обход безопасности во время компиляции не звучит как отличная точка продажи для меня...: -)

Ответ 3

Я не вижу, как вы могли это позволить, не нарушая проверку типа 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);
       }
       ...
     }

Я не могу понять, как это сделать. Можете ли вы предоставить дополнительную информацию о проблеме, которую вы пытаетесь решить?

Ответ 4

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

Однако... можно сделать нечто похожее. В частности, если у нас есть класс A и класс B, который зависит от A, то можно сделать следующее:

  • Скомпилировать A.java
  • Скомпилируйте B.java против A.class.
  • Отредактируйте A.java, чтобы изменить его несовместимым образом.
  • Скомпилируйте A.java, заменив старый класс A.class.
  • Запустите приложение Java с использованием B.class и нового (несовместимого) A.class.

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

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

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

Ответ 5

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

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

Ответ 6

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

pkg.in.classpath.IFooBar

сделайте FooBar implements IFooBar и

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

Тестовый класс будет скомпилирован. Просто подключите правильную реализацию, т.е. FooBar во время выполнения, используя заводы и конфигурацию. Найдите некоторые контейнеры МОК.

Ответ 7

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

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

Ответ 8

Я создал два класса: 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. Это привело к следующему:

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

}

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

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