Функции байт-кода недоступны на языке Java

146

Существуют ли в настоящее время (Java 6) вещи, которые вы можете сделать в байт-коде Java, которые вы не можете сделать из языка Java?

Я знаю, что оба Тьюринга завершены, поэтому читать «можно сделать», как «можно сделать значительно быстрее / лучше, или просто по-другому».

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

Барт ван Хейкелом
источник
3
Определите «вещи». В конце концов, язык Java и байт-код Java завершены по Тьюрингу ...
Майкл Боргвардт,
2
Это реальный вопрос; Есть ли какое-либо преимущество программирования в байт-коде, например, с использованием Jasmin вместо Java?
Питер Лори
2
Как rolв ассемблере, который вы не можете написать на C ++.
Мартин Курто
1
Это очень плохой оптимизирующий компилятор , который не может скомпилировать (x<<n)|(x>>(32-n))с rolинструкцией.
Random832

Ответы:

62

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

Однако есть некоторые функции, которые не создаются современными компиляторами Java:

  • ACC_SUPERФлаг :

    Это флаг, который может быть установлен для класса и определяет, как invokespecialобрабатывается конкретный угловой случай байт-кода для этого класса. Он устанавливается всеми современными компиляторами Java (где «современный» -> = Java 1.1, если я правильно помню), и только древние компиляторы Java создавали файлы классов, где это не было установлено. Этот флаг существует только по причинам обратной совместимости. Обратите внимание, что начиная с Java 7u51, ACC_SUPER полностью игнорируется по соображениям безопасности.

  • В jsr/ retбайткодах.

    Эти байт-коды использовались для реализации подпрограмм (в основном для реализации finallyблоков). Они больше не производятся начиная с Java 6 . Причина их устаревания заключается в том, что они значительно усложняют статическую проверку без большой выгоды (т. Е. Используемый код почти всегда может быть повторно реализован с обычными переходами с очень небольшими накладными расходами).

  • Наличие двух методов в классе, которые отличаются только типом возвращаемого значения.

    Спецификация языка Java не допускает двух методов в одном классе, если они отличаются только по типу возвращаемого значения (т. Е. Одно и то же имя, один и тот же список аргументов, ...). Спецификация JVM, однако, не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто нет способа создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение .

Joachim Sauer
источник
5
Я мог бы добавить другой ответ, но мы могли бы также сделать ваш канонический ответ. Вы можете упомянуть, что подпись метода в байт-коде включает тип возвращаемого значения . То есть у вас может быть два метода с одинаковыми типами параметров, но с разными типами возвращаемых значений. Смотрите это обсуждение: stackoverflow.com/questions/3110014/is-this-valid-java/…
Адам Пейнтер,
8
Вы можете иметь имена классов, методов и полей практически с любым символом. Я работал над одним проектом, где в «полях» были пробелы и дефисы. : P
Питер Лори
3
@Peter: Говоря о символах файловой системы, я столкнулся с обфускатором, который переименовал класс в JAR-файл, aа другой - Aв него. Мне потребовалось около получаса, чтобы разархивировать на компьютере с Windows, прежде чем я понял, где пропали классы. :)
Адам Пейнтер
3
@JoachimSauer: перефразировать JVM спецификации, страница 75: имена классов, методов, полей и локальных переменных могут содержать любые символы , кроме '.', ';', '['или '/'. Имена методов одинаковы, но они также не могут содержать '<'или '>'. (С заметными исключениями <init>и, <clinit>например, статических конструкторов.) Я должен отметить, что если вы строго следуете спецификации, имена классов на самом деле гораздо более ограничены, но ограничения не применяются.
Левиафанбаджер
3
@JoachimSauer: также, мое недокументированное дополнение: язык java включает в себя "throws ex1, ex2, ..., exn"сигнатуры методов; Вы не можете добавлять предложения исключения в переопределенные методы. НО, JVM не волнует. Таким образом, finalJVM действительно гарантирует, что только методы свободны от исключений - кроме RuntimeExceptions и Errors, конечно. Вот вам и проверенная обработка исключений: D
leviathanbadger
401

После долгой работы с байт-кодом Java и некоторых дополнительных исследований по этому вопросу, вот краткое изложение моих выводов:

Выполнить код в конструкторе перед вызовом супер-конструктора или вспомогательного конструктора

В языке программирования Java (JPL) первым оператором конструктора должен быть вызов суперконструктора или другого конструктора того же класса. Это не относится к байт-коду Java (JBC). В байт-коде абсолютно законно выполнять любой код перед конструктором, если:

  • Другой совместимый конструктор вызывается через некоторое время после этого блока кода.
  • Этот вызов не входит в условный оператор.
  • Перед этим вызовом конструктора ни одно поле созданного экземпляра не читается, и ни один из его методов не вызывается. Это подразумевает следующий пункт.

Установите поля экземпляра перед вызовом супер-конструктора или вспомогательного конструктора

Как упоминалось ранее, совершенно правильно установить значение поля экземпляра перед вызовом другого конструктора. Существует даже устаревший хак, который позволяет использовать эту «функцию» в версиях Java до 6:

class Foo {
  public String s;
  public Foo() {
    System.out.println(s);
  }
}

class Bar extends Foo {
  public Bar() {
    this(s = "Hello World!");
  }
  private Bar(String helper) {
    super();
  }
}

Таким образом, поле может быть установлено до вызова супер-конструктора, что, однако, более невозможно. В JBC такое поведение все еще можно реализовать.

Разветвите вызов супер-конструктора

В Java невозможно определить вызов конструктора как

class Foo {
  Foo() { }
  Foo(Void v) { }
}

class Bar() {
  if(System.currentTimeMillis() % 2 == 0) {
    super();
  } else {
    super(null);
  }
}

До Java 7u23, однако, верификатор HotSpot VM пропустил эту проверку, поэтому это было возможно. Это использовалось несколькими инструментами генерации кода как своего рода хак, но реализация такого класса уже недопустима.

Последний был просто ошибкой в ​​этой версии компилятора. В новых версиях компилятора это снова возможно.

Определить класс без конструктора

Компилятор Java всегда реализует по крайней мере один конструктор для любого класса. В байт-коде Java это не требуется. Это позволяет создавать классы, которые не могут быть построены даже при использовании отражения. Тем не менее, использование по- sun.misc.Unsafeпрежнему позволяет создавать такие экземпляры.

Определите методы с одинаковой подписью, но с другим типом возврата

В JPL метод идентифицируется как уникальный по имени и типам необработанных параметров. В JBC необработанный тип возврата дополнительно рассматривается.

Определите поля, которые не отличаются по имени, но только по типу

Файл класса может содержать несколько полей с одинаковыми именами, если они объявляют другой тип поля. JVM всегда ссылается на поле как кортеж имени и типа.

Бросьте необъявленные проверенные исключения, не ловя их

Среда выполнения Java и байт-код Java не знают о концепции проверяемых исключений. Только компилятор Java проверяет, что проверенные исключения всегда либо перехватываются, либо объявляются, если они выброшены.

Использовать динамический вызов метода вне лямбда-выражений

Так называемый динамический вызов метода может использоваться для чего угодно, не только для лямбда-выражений Java. Использование этой функции позволяет, например, отключить логику выполнения во время выполнения. Многие динамические языки программирования, которые сводятся к JBC, улучшили свою производительность с помощью этой инструкции. В байт-коде Java вы также можете эмулировать лямбда-выражения в Java 7, где компилятор еще не допускал никакого использования динамического вызова метода, в то время как JVM уже поняла инструкцию.

Используйте идентификаторы, которые обычно не считаются законными

Вы когда-нибудь мечтали использовать пробелы и разрыв строки в имени вашего метода? Создайте свой собственный JBC и удачи в проверке кода. Единственный недопустимые символы для идентификаторов ., ;, [и /. Кроме того, методы, которые не названы <init>или <clinit>не могут содержать <и >.

Переназначить finalпараметры или thisссылку

finalпараметры не существуют в JBC и, следовательно, могут быть переназначены. Любой параметр, включая thisссылку, хранится только в простом массиве в JVM, что позволяет переназначить thisссылку на индекс в 0пределах одного фрейма метода.

Переназначить finalполя

Пока конечное поле назначается в конструкторе, допустимо переназначить это значение или даже вообще не присваивать значение. Следовательно, следующие два конструктора являются допустимыми:

class Foo {
  final int bar;
  Foo() { } // bar == 0
  Foo(Void v) { // bar == 2
    bar = 1;
    bar = 2;
  }
}

Для static finalполей даже разрешено переназначать поля вне инициализатора класса.

Обрабатывайте конструкторы и инициализатор класса так, как если бы они были методами

Это скорее концептуальная особенность, но конструкторы в JBC не трактуются иначе, чем обычные методы. Только верификатор JVM гарантирует, что конструкторы вызывают другой допустимый конструктор. Кроме этого, это просто соглашение об именах Java, что конструкторы должны вызываться и вызываться <init>инициализатор класса <clinit>. Помимо этой разницы, представление методов и конструкторов идентично. Как отметил Хольгер в комментарии, вы можете даже определить конструкторы с типами возвращаемых данных, отличными от voidинициализатора класса с аргументами, даже если невозможно вызвать эти методы.

Создать асимметричные записи * .

При создании записи

record Foo(Object bar) { }

javac сгенерирует файл класса с одним именем bar, методом доступа bar()и одним конструктором Object. Кроме того, добавлен атрибут записи для bar. Создавая запись вручную, можно создать конструктор другой формы, пропустить поле и реализовать средство доступа по-разному. В то же время, все еще возможно заставить API отражения полагать, что класс представляет фактическую запись.

Вызовите любой супер метод (до Java 1.1)

Однако это возможно только для версий Java 1 и 1.1. В JBC методы всегда отправляются с явным целевым типом. Это означает, что для

class Foo {
  void baz() { System.out.println("Foo"); }
}

class Bar extends Foo {
  @Override
  void baz() { System.out.println("Bar"); }
}

class Qux extends Bar {
  @Override
  void baz() { System.out.println("Qux"); }
}

можно было реализовать Qux#bazдля вызова Foo#bazпри перепрыгивании Bar#baz. Хотя все еще возможно определить явный вызов для вызова другой реализации супер-метода, чем у прямого суперкласса, это больше не имеет никакого эффекта в версиях Java после 1.1. В Java 1.1 это поведение контролировалось путем установки ACC_SUPERфлага, который включал бы то же поведение, которое только вызывает реализацию прямого суперкласса.

Определить не виртуальный вызов метода, который объявлен в том же классе

В Java невозможно определить класс

class Foo {
  void foo() {
    bar();
  }
  void bar() { }
}

class Bar extends Foo {
  @Override void bar() {
    throw new RuntimeException();
  }
}

Приведенный выше код всегда приводит к тому, что RuntimeExceptionкогда fooвызывается экземпляр Bar. Невозможно определить Foo::fooметод для вызова его собственного bar метода, который определен в Foo. Поскольку barэто не частный метод экземпляра, вызов всегда виртуальный. С байт - код, можно , однако определить вызов использовать INVOKESPECIALопкод , который непосредственно связывает barвызов метода в Foo::fooк Fooверсии «S. Этот код операции обычно используется для реализации вызовов супер-методов, но вы можете повторно использовать код операции для реализации описанного поведения.

Мелкозернистые аннотации

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

Определите любой атрибут для типа или его членов

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

Перепускные и неявно Присвоить byte, short, charи booleanзначения

Последние типы примитивов обычно не известны в JBC, но определяются только для типов массивов или для дескрипторов полей и методов. В инструкциях байт-кода все именованные типы занимают 32-битное пространство, что позволяет представлять их как int. Официально, только int, float, longи doubleтипы существуют в байт - код , который всем необходимо явное преобразование по правилу испытателя в JVM в.

Не выпускать монитор

synchronizedБлок на самом деле состоит из двух утверждений, одно приобретение и один , чтобы выпустить монитор. В JBC вы можете приобрести его, не выпуская его.

Примечание . В недавних реализациях HotSpot это приводит к IllegalMonitorStateExceptionзавершению метода или к неявному освобождению, если метод завершается самим исключением.

Добавить более одного returnоператора в инициализатор типа

В Java даже тривиальный инициализатор типа, такой как

class Foo {
  static {
    return;
  }
}

незаконно В байтовом коде инициализатор типа обрабатывается так же, как и любой другой метод, т. Е. Операторы return могут быть определены где угодно.

Создать неприводимые циклы

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

Определить рекурсивный блок catch

В байт-коде Java вы можете определить блок:

try {
  throw new Exception();
} catch (Exception e) {
  <goto on exception>
  throw Exception();
}

Подобное утверждение создается неявно при использовании synchronizedблока в Java, где любое исключение при освобождении монитора возвращает к инструкции по освобождению этого монитора. Как правило, в такой инструкции не должно возникать никаких исключений, но если она будет (например, устарела ThreadDeath), монитор все равно будет освобожден.

Вызовите любой метод по умолчанию

Компилятор Java требует выполнения нескольких условий, чтобы разрешить вызов метода по умолчанию:

  1. Метод должен быть самым конкретным (не должен быть переопределен подчиненным интерфейсом, который реализован любым типом, включая супертипы).
  2. Тип интерфейса метода по умолчанию должен быть реализован непосредственно классом, вызывающим метод по умолчанию. Однако, если интерфейс Bрасширяет интерфейс, Aно не переопределяет метод A, метод все равно может быть вызван.

Для байт-кода Java учитывается только второе условие. Первый, однако, не имеет значения.

Вызовите метод super для экземпляра, который не this

Компилятор Java позволяет вызывать метод super (или интерфейс по умолчанию) только в случаях this. В байтовом коде также возможно вызвать метод super для экземпляра того же типа, подобного следующему:

class Foo {
  void m(Foo f) {
    f.super.toString(); // calls Object::toString
  }
  public String toString() {
    return "foo";
  }
}

Доступ к синтетическим членам

В байт-коде Java можно получить прямой доступ к синтетическим членам. Например, рассмотрим, как в следующем примере Barосуществляется доступ к внешнему экземпляру другого экземпляра:

class Foo {
  class Bar { 
    void bar(Bar bar) {
      Foo foo = bar.Foo.this;
    }
  }
}

Как правило, это верно для любой синтетической области, класса или метода.

Определить несинхронизированную информацию общего типа

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

Верификатор не проверяет согласованность этих Stringзначений, закодированных в метаданных. Следовательно, можно определить информацию об универсальных типах, которая не соответствует стиранию. Как следствие, следующие утверждения могут быть верными:

Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());

Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);

Кроме того, подпись может быть определена как недопустимая, так что возникает исключение времени выполнения. Это исключение выдается, когда к информации обращаются впервые, поскольку она оценивается лениво. (Аналогично значениям аннотации с ошибкой.)

Добавлять метаинформацию только для определенных методов

Компилятор Java позволяет встраивать имя параметра и информацию модификатора при компиляции класса с parameterвключенным флагом. В формате файла класса Java эта информация сохраняется для каждого метода, что позволяет встраивать такую ​​информацию о методе только для определенных методов.

Запутать вещи и крушение вашей JVM

Например, в байт-коде Java вы можете определить, что вызывать любой метод любого типа. Обычно верификатор будет жаловаться, если тип не знает такого метода. Однако, если вы вызываете неизвестный метод в массиве, я обнаружил ошибку в какой-то версии JVM, когда верификатор пропустит это, и ваша JVM завершит работу после вызова инструкции. Хотя это вряд ли особенность, но технически это то, что невозможно с помощью Java скомпилированного javac . У Java есть своего рода двойная проверка. Первая проверка применяется компилятором Java, вторая - JVM при загрузке класса. Пропустив компилятор, вы можете найти слабое место в проверке верификатора. Хотя это скорее общее утверждение, чем особенность.

Аннотируйте тип получателя конструктора, когда нет внешнего класса

Начиная с Java 8, нестатические методы и конструкторы внутренних классов могут объявлять тип получателя и аннотировать эти типы. Конструкторы классов верхнего уровня не могут аннотировать свой тип получателя, так как большинство из них не объявляют его.

class Foo {
  class Bar {
    Bar(@TypeAnnotation Foo Foo.this) { }
  }
  Foo() { } // Must not declare a receiver type
}

Однако, поскольку Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()он возвращает AnnotatedTypeпредставление Foo, можно включить аннотации типов для Fooконструктора России непосредственно в файл класса, где эти аннотации позднее читаются API-интерфейсом отражения.

Использовать неиспользуемые / устаревшие инструкции байт-кода

Поскольку другие назвали это, я включу это также. Java была ранее использование подпрограмм по JSRи RETотчетности. JBC даже знал свой собственный тип обратного адреса для этой цели. Однако использование подпрограмм усложнило статический анализ кода, поэтому эти инструкции больше не используются. Вместо этого компилятор Java будет дублировать код, который он компилирует. Тем не менее, это в основном создает идентичную логику, поэтому я на самом деле не считаю это достижением чего-то другого. Точно так же вы можете, например, добавитьNOOPинструкция байтового кода, которая также не используется компилятором Java, но это также не позволит вам достичь чего-то нового. Как указано в контексте, эти упомянутые «инструкции по функциям» теперь удалены из набора допустимых кодов операций, что делает их еще менее функциональными.

Рафаэль Винтерхальтер
источник
3
Что касается имен методов, вы можете иметь более одного <clinit>метода, определяя методы с именем, <clinit>но принимая параметры или имея voidневозвратный тип. Но эти методы не очень полезны, JVM будет их игнорировать, а байт-код не сможет их вызвать. Единственное использование было бы, чтобы запутать читателей.
Хольгер
2
Я только что обнаружил, что Oracle JVM обнаруживает невыпущенный монитор при выходе из метода и выдает, IllegalMonitorStateExceptionесли вы пропустили monitorexitинструкцию. И в случае исключительного метода выхода, который не смог сделать это monitorexit, он сбрасывает монитор без вывода сообщений.
Хольгер
1
@Holger - я этого не знал, я знаю, что это было возможно, по крайней мере, в более ранних версиях JVM, у JRockit даже есть собственный обработчик для реализации такого рода. Я обновлю запись.
Рафаэль Винтерхальтер
1
Ну, спецификация JVM не требует такого поведения. Я обнаружил это, потому что пытался создать висячую внутреннюю блокировку, используя такой нестандартный байт-код.
Хольгер
3
Хорошо, я нашел соответствующую спецификацию : « Структурная блокировка - это ситуация, когда во время вызова метода каждый выход на данном мониторе соответствует предыдущей записи на этом мониторе. Поскольку нет уверенности в том, что весь код, представленный виртуальной машине Java, будет выполнять структурированную блокировку, реализации виртуальной машины Java разрешены, но не обязательны для применения обоих из следующих двух правил, гарантирующих структурную блокировку. … »
Хольгер,
14

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

  • Выдача проверенного исключения из метода без объявления того, что метод его выбрасывает. Проверенные и непроверенные исключения - это то, что проверяется только компилятором Java, а не JVM. Из-за этого, например, Scala может генерировать проверенные исключения из методов, не объявляя их. Тем не менее, с помощью дженериков Java есть обходной путь, называемый хитрым броском .

  • Наличие в классе двух методов, которые отличаются только типом возвращаемого значения, как уже упоминалось в ответе Йоахима : спецификация языка Java не допускает двух методов в одном классе, если они различаются только по типу возвращаемого значения (т. Е. Одно и то же имя, один и тот же список аргументов, ...). Спецификация JVM, однако, не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто нет способа создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение .

Эско Луонтола
источник
4
Обратите внимание, что в Java есть способ сделать первое. Это иногда называют подлый бросок .
Иоахим Зауэр
Теперь это подлый! : D Спасибо, что поделились.
Эско Луонтола
Я думаю, что вы также можете использовать Thread.stop(Throwable)для подлого броска. Я предполагаю, что тот, который уже связан, быстрее, хотя.
Барт ван Хейкелом
2
Вы не можете создать экземпляр без вызова конструктора в байт-коде Java. Верификатор отклонит любой код, который пытается использовать неинициализированный экземпляр. Реализация десериализации объектов использует встроенные помощники кода для создания экземпляров без вызова конструктора.
Хольгер
Для класса Foo, расширяющего Object, вы не можете создать экземпляр Foo, вызвав конструктор, объявленный в Object. Верификатор откажется от этого. Вы можете создать такой конструктор, используя Java ReflectionFactory, но это вряд ли функция байт-кода, но реализованная Jni. Ваш ответ неверный, а Хольгер верный.
Рафаэль Винтерхальтер
8
  • GOTOможет использоваться с метками для создания собственных управляющих структур (кроме for whileetc)
  • Вы можете переопределить thisлокальную переменную внутри метода
  • Комбинируя оба из них, вы можете создать оптимизированный байт-код создания хвостового вызова (я делаю это в JCompilo )

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

Дэниел Уортингтон-Бодарт
источник
Как вам overrideэта локальная переменная?
Майкл
2
@ Майкл переопределение - слишком сильное слово. На уровне байт-кода все локальные переменные доступны по числовому индексу, и нет разницы между записью в существующую переменную или инициализацией новой переменной (с разобщенной областью действия), в любом случае это просто запись в локальную переменную. thisПеременная имеет нулевой индекс, но помимо того , что предварительно инициализируется thisссылкой при вводе метода экземпляра, это просто локальная переменная. Таким образом, вы можете записать в него другое значение, которое может действовать как завершение thisили изменение thisпеременной, в зависимости от того, как вы ее используете.
Хольгер
Я вижу! Так действительно ли это thisможет быть переназначено? Я думаю, что это было просто слово переопределить, что заставило меня задуматься, что именно это означает.
Майкл
5

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

eljenso
источник
Интересное чтение, но не похоже, что кто-то захочет использовать любую из этих вещей.
Барт ван Хейкелом
4

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

  • Создайте экземпляр другого объекта, сохраните его в локальной переменной (или в стеке) и передайте его в качестве параметра конструктору суперкласса, сохраняя при этом ссылку на эту переменную для другого использования.
  • Вызовите другие конструкторы, основанные на условии. Это должно быть возможно: как условно вызвать другой конструктор в Java?

Я не проверял это, поэтому, пожалуйста, поправьте меня, если я ошибаюсь.

msell
источник
Вы даже можете установить члены экземпляра перед вызовом его конструктора суперкласса. Однако чтение полей или вызов методов невозможно до этого.
Рафаэль Винтерхальтер
3

То, что вы можете делать с байтовым кодом, а не с простым Java-кодом, - это генерировать код, который можно загружать и запускать без компилятора. Многие системы имеют JRE, а не JDK, и если вы хотите генерировать код динамически, может быть лучше, если не проще, генерировать байт-код вместо кода Java, прежде чем его можно будет использовать.

Питер Лори
источник
6
Но тогда вы просто пропускаете компилятор, а не производите что-то, что не может быть создано с помощью компилятора (если он был доступен).
Барт ван Хейкелом
2

Я написал оптимизатор байт-кода, когда был I-Play (он был разработан для уменьшения размера кода для приложений J2ME). Одной из функций, которую я добавил, была возможность использовать встроенный байт-код (аналогично встроенному языку ассемблера в C ++). Мне удалось уменьшить размер функции, которая была частью библиотечного метода, с помощью инструкции DUP, поскольку мне нужно значение в два раза. У меня также были нулевые байтовые инструкции (если вы вызываете метод, который принимает символ, и вы хотите передать int, который, как вы знаете, не нужно приводить, я добавил int2char (var) для замены char (var), и он удалил бы Инструкция i2c для уменьшения размера кода. Я также сделал это с плавающей точкой a = 2.3; с плавающей точкой b = 3.4; с плавающей точкой c = a + b; и это будет преобразовано в фиксированную точку (быстрее, а некоторые J2ME этого не сделали) поддержка плавающей запятой).

nharding
источник
2

В Java, если вы пытаетесь переопределить открытый метод защищенным методом (или любым другим ограничением доступа), вы получаете ошибку: «попытка назначить более слабые права доступа». Если вы делаете это с помощью байт-кода JVM, то с верификатором все в порядке, и вы можете вызывать эти методы через родительский класс, как если бы они были публичными.

Джозеф Сибл-Восстановить Монику
источник