Функції байт-коду недоступні на мові Java


146

Чи є в даний час (Java 6) речі, які ви можете робити в байт-коді Java, який ви не можете зробити з мови Java?

Я знаю, що Тьюрінг завершений, тому читайте "можна зробити", як "можна зробити значно швидше / краще, або просто по-іншому".

Я думаю про додаткові байт-коди типу invokedynamic, які неможливо генерувати за допомогою Java, за винятком конкретного для майбутньої версії.


3
Визначте "речі". Зрештою, мова Java та байт-код Java - це Тюрінг завершений ...
Майкл Боргвардт

2
Чи справжнє питання; Чи є якесь програмування переваг у байтовому коді, наприклад, використовуючи Jasmin, а не Java?
Пітер Лоурі

2
Як rolу асемблері, який ви не можете написати на C ++.
Martijn Courteaux

1
Це дуже поганий оптимізуючий компілятор, який не може компілювати (x<<n)|(x>>(32-n))в rolінструкцію.
Випадково832

Відповіді:


62

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

Однак є деякі функції, які не виробляються сучасними компіляторами Java:

  • ACC_SUPERпрапор :

    Це прапор, який можна встановити для класу та визначає, як конкретний кутовий випадок invokespecialбайт-коду обробляється для цього класу. Його встановлюють усі сучасні компілятори Java (де "modern" є> = Java 1.1, якщо я правильно пам’ятаю) і лише стародавні компілятори Java виробляли файли класів, де це не було встановлено. Цей прапор існує лише з міркувань зворотної сумісності. Зауважте, що починаючи з Java 7u51, ACC_SUPER повністю ігнорується через причини безпеки.

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

    Ці байт-коди використовувались для реалізації підпрограм (здебільшого для finallyблоків реалізації ). Вони більше не випускаються з Java 6 . Причиною їх знецінення є те, що вони дуже ускладнюють статичну перевірку без великого виграшу (тобто код, який використовує, майже завжди може бути повторно реалізований за допомогою звичайних стрибків з дуже невеликими накладними витратами).

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

    Специфікація мови Java не допускає двох методів у тому ж класі, коли вони відрізняються лише своїм типом повернення (тобто тим же ім'ям, тим самим списком аргументів, ...). Однак специфікація JVM не має такого обмеження, тому файл класу може містити два такі методи, просто немає можливості створювати такий файл класу за допомогою звичайного компілятора Java. У цій відповіді є приємний приклад / пояснення .


5
Я можу додати ще одну відповідь, але ми можемо також зробити вашу канонічну відповідь. Ви можете згадати, що підпис методу в байт-коді включає тип повернення . Тобто ви можете мати два способи з абсолютно однаковими типами параметрів, але різними типами повернення. Дивіться цю дискусію: stackoverflow.com/questions/3110014/is-this-valid-java/…
Адам Пейнтер

8
Ви можете мати назви класів, методів та полів з майже будь-яким символом. Я працював над одним проектом, де «поля» мали пробіли та дефіси у своїх назвах. : P
Пітер Лорі

3
@ Peeter: Говорячи про символи файлової системи, я наткнувся на обфускатора, який перейменував клас у aінший і Aвсередині файлу JAR. Разархівувавшись на машині Windows, мені знадобилося півгодини, перш ніж я зрозумів, де відсутні заняття. :)
Адам Пейнтер

3
@JoachimSauer: перефразувати JVM специфікації, сторінка 75: імена класів, методів, полів і локальних змінних можуть містити будь-які символи , крім '.', ';', '['або '/'. Назви методів однакові, але вони також не можуть містити '<'або '>'. (З примітними винятками <init>і <clinit>для екземпляра і статичних конструкторів.) Я хотів би відзначити, що якщо ви прямуєте специфікаціям строго, імена класів, на насправді набагато більш обмежені, але обмеження не застосовуються.
leviathanbadger

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, методом accessor імені 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;
  }
}

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

Створіть невідворотні петлі

Компілятор Java перетворює петлі в goto заяви в байт-коді Java. Такі заяви можна використовувати для створення невідворотних циклів, що компілятор Java ніколи не робить.

Визначте рекурсивний блок лову

У байт-коді Java ви можете визначити блок:

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

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

Викличте будь-який метод за замовчуванням

Для компілятора Java потрібно виконати кілька умов, щоб дозволити виклик методу за замовчуванням:

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

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

Викликати супер метод для екземпляра, який це не так this

Компілятор Java дозволяє викликати супер (або інтерфейс за замовчуванням) лише у випадках this. У байтовому коді, однак, також можна викликати супер метод на екземплярі одного типу, подібного до наступного:

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 завершиться після запуску інструкції. Навряд чи це особливість , хоча, але це технічно то , що ні представляється можливим з JAVAC скомпільований Java. У 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конструктора 's безпосередньо у файл класу, де ці анотації згодом читаються API відображення.

Використовуйте інструкції з невикористаного / застарілого байтового коду

Оскільки інші назвали його, я також включу його. Java раніше використовувала підпрограми за допомогою JSRі RETзаяви. JBC навіть знав власний тип зворотної адреси для цієї мети. Однак використання підпрограм зробило надто складний аналіз статичного коду, тому ці інструкції більше не використовуються. Натомість компілятор Java буде дублювати код, який він компілює. Однак, це в основному створює ідентичну логіку, тому я не дуже вважаю, щоб досягти чогось іншого. Так само ви можете, наприклад, додатиNOOPінструкція по байтовому коду, яка також не використовується компілятором Java, але це також не дозволить вам досягти чогось нового. Як було зазначено в контексті, ці згадані "інструкції щодо функцій" тепер вилучені з набору юридичних опкодів, що робить їх ще менше функцією.


3
Щодо імен методів, у вас може бути більше одного <clinit>методу, визначаючи методи з ім'ям, <clinit>але приймаючи параметри або маючи voidнеповернений тип. Але ці методи не дуже корисні, JVM їх ігнорує, і байт-код не може викликати їх. Єдине використання було б бентежити читачів.
Холгер

2
Я щойно виявив, що JVM Oracle виявляє невідомий монітор при виході з методу і видає, IllegalMonitorStateExceptionякщо ви пропустили monitorexitінструкцію. І у випадку виняткового виходу з методу, який не вдалося зробити monitorexit, він мовчки скидає монітор.
Холгер

1
@Holger - не знав цього, я знаю, що це було можливо принаймні в попередніх JVM, JRockit навіть має власного обробника для подібної реалізації. Я оновлю запис.
Рафаель Вінтерхалтер

1
Ну, специфікація JVM не вимагає такої поведінки. Я щойно виявив це, тому що намагався створити звисаючий внутрішній замок, використовуючи такий нестандартний байт-код.
Холгер

3
Гаразд, я знайшов відповідну специфікацію : " Структуроване блокування - це ситуація, коли під час виклику методу кожен вихід на заданий монітор відповідає попередньому запису на цьому моніторі. Оскільки немає впевненості, що весь код, що подається на віртуальну машину Java, буде здійснювати структуроване блокування, реалізація віртуальної машини Java дозволена, але не вимагає виконання обох наступних правил, що гарантують структуроване блокування. … »
Холгер

14

Ось деякі функції, які можна виконати в байт-коді Java, але не у вихідному коді Java:

  • Викидання перевіреного винятку з методу, не оголошуючи, що метод кидає його. Перевірені та неперевірені винятки - це те, що перевіряється тільки компілятором Java, а не JVM. Наприклад, Скала може викидати перевірені винятки з методів, не оголошуючи їх. Хоча з Java generics існує вирішення, яке називається підлий кидок .

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


4
Зверніть увагу, що є перший спосіб зробити на Java. Іноді його називають підлий кидком .
Йоахім Зауер

Тепер це підступно! : D Дякую за обмін.
Еско Луонтола

Я думаю, ви також можете використовувати Thread.stop(Throwable)для підступного кидка. Я припускаю, що одна зв'язана швидше.
Барт ван Хекелом

2
Ви не можете створити екземпляр без виклику конструктора в байт-коді Java. Перевіряльник буде відхиляти будь-який код, який намагається використовувати неініціалізований екземпляр. Реалізація десеріалізації об'єкта використовує вбудовані помічники коду для створення примірників без виклику конструктора.
Холгер

Для класу Foo, що розширює Object, ви не можете створити Foo шляхом виклику конструктора, який оголошується в Object. Верифікатор відмовив би в цьому. Ви можете створити такий конструктор за допомогою Java ReflectionFactory, але це навряд чи є функцією коду байтів, але реалізованої Jni. Ваша відповідь неправильна, і Хольгер правильний.
Рафаель Вінтерхалтер

8
  • GOTOможна використовувати з мітками для створення власних структур управління (крім for whileтощо)
  • Ви можете замінити thisлокальну змінну всередині методу
  • Комбінуючи обидва ці моменти, ви можете створити байтовий код, оптимізований для виклику хвоста (я це роблю в JCompilo )

Як пов’язаний момент, ви можете отримати ім'я параметра для методів, якщо їх компілювати з налагодженням ( Paranamer робить це, читаючи байт-код


Як вам overrideця локальна змінна?
Майкл

2
Переоцінка @Michael - занадто сильне слово. На рівні байт-коду всі локальні змінні мають доступ до числового індексу, і немає різниці між записом до існуючої змінної або ініціалізацією нової змінної (з диз'юнктною областю), в будь-якому випадку це просто записування в локальну змінну. thisМінлива має нульовий індекс, але крім того , що попередньо инициализируется thisпосиланням при введенні методу примірника, це просто локальна змінна. Таким чином, ви можете записати на нього інше значення, яке може діяти як закінчення this"області" чи зміна thisзмінної в залежності від того, як ви її використовуєте.
Холгер

Я бачу! Так справді це thisможна перепризначити? Я думаю, що це було просто слово переохолодження, яке змусило мене задатися питанням, що воно означає саме.
Майкл

5

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


Цікаво читати, але це не схоже на те, що хотілося б (аб) використовувати будь-яку з цих речей.
Барт ван Хекелом

4

На мові Java першим висловом у конструкторі має бути виклик конструктору суперкласу. Байт-код не має цього обмеження, натомість правило полягає в тому, що конструктор суперкласу або інший конструктор того ж класу повинні викликати об'єкт перед тим, як отримати доступ до членів. Це має забезпечити більше свободи, такі як:

  • Створіть примірник іншого об’єкта, збережіть його у локальній змінній (або стеці) та передайте його як параметр конструктору суперкласу, зберігаючи при цьому посилання в цій змінній для іншого використання.
  • Викликайте інших конструкторів на основі умови. Це повинно бути можливим: як умовно викликати інший конструктор на Java?

Я їх не перевіряв, тому, будь ласка, виправте мене, якщо я помиляюся.


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

3

Щось ви можете зробити з байт-кодом, а не звичайним кодом Java, це генерувати код, який можна завантажувати та працювати без компілятора. У багатьох системах є JRE, а не JDK, і якщо ви хочете динамічно генерувати код, може бути краще, якщо не простіше, генерувати байт-код замість Java-коду, перш ніж його використовувати.


6
Але тоді ви просто пропускаєте компілятор, не створюючи те, що не вдалося б створити за допомогою компілятора (якщо він був би доступний).
Барт ван Хекелом

2

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


2

У Java, якщо ви намагаєтесь замінити загальнодоступний метод із захищеним методом (або будь-яке інше зменшення доступу), ви отримаєте помилку: "спроба призначити слабкіші права доступу". Якщо ви робите це з байт-кодом JVM, перевіряючий файл із цим добре, і ви можете викликати ці методи через батьківський клас так, ніби вони були загальнодоступними.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.