Java言語で利用できないバイトコード機能


146

現在、Java言語で実行できないJavaバイトコードで実行できる(Java 6)ことはありますか?

私は両方ともチューリング完全であることを知っているので、「できる」を「大幅に速く/より良くできる、または別の方法でできる」と読みます。

のような追加のバイトコードを考えinvokedynamicています。Javaを使用して生成することはできませんが、特定のバイトコードは将来のバージョン用です。


3
「もの」を定義します。結局、Java言語とJavaバイトコードはどちらも完全なチューリングです...
Michael Borgwardt

2
本当の質問です。Javaの代わりにJasminを使用するなど、バイトコードでプログラミングする利点はありますか?
Peter Lawrey

2
同様にrolアセンブラで、あなたがC ++で書くことができません。
Martijn Courteaux

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以降、これらは生成されなくなりました。それらが非推奨となった理由は、静的検証を大幅に複雑化して大きな利益をもたらさないためです(つまり、を使用するコードは、ほとんどの場合、オーバーヘッドがほとんどない通常のジャンプで再実装できます)。

  • 戻り値の型のみが異なるクラスに2つのメソッドがある。

    Java言語仕様では、戻り値の型のみが異なる場合(つまり、同じ名前、同じ引数リストなど)、同じクラス内の2つのメソッドを許可していません。ただし、JVM仕様にはそのような制限はないため、クラスファイルにそのような2つのメソッドを含めることができ、通常のJavaコンパイラを使用してそのようなクラスファイルを生成する方法はありません。この回答には、良い例/説明があります


5
別の答えを追加することもできますが、あなたの答えを正解にすることもできます。バイトコードでのメソッドのシグネチャには戻り値の型が含まれていることに言及した方がよいでしょう。つまり、パラメーターの型はまったく同じで、戻り値の型が異なる2つのメソッドを持つことができます。この議論を参照してください。stackoverflow.com/questions/3110014/is-this-valid-java/...
アダム・ペインター

8
クラス、メソッド、フィールドの名前には、ほぼすべての文字を使用できます。「フィールド」の名前にスペースとハイフンが含まれるプロジェクトを1つ作成しました。:P
ピーター・ローリー

3
@ピーター:ファイルシステムの文字といえば、JARファイル内でクラス名を変更しa、別のクラス名に変更した難読化ツールに遭遇しましたA。不足しているクラスの場所を知る前に、Windowsマシンで約30分解凍しました。:)
Adam Paynter

3
@JoachimSauer:JVM仕様、75ページ言い換え:クラス名、メソッド、フィールド、およびローカル変数を含めることができます任意の以外の文字を'.'';''['、または'/'。メソッド名は同じですが、'<'またはを含めることもできません'>'。(の顕著な例外を除いて<init>および<clinit>インスタンスおよび静的コンストラクタのため。)私はあなたが厳密に仕様を以下している場合、クラス名はずっと実際により拘束されているが、制約が適用されていないことを指摘しなければなりません。
leviathanbadger 2013年

3
@JoachimSauer:また、文書化されていない独自の追加:Java言語には"throws ex1, ex2, ..., exn"、メソッドシグネチャの一部としてが含まれます。オーバーライドされたメソッドに例外スロー句を追加することはできません。しかし、JVMはそれほど気にすることができませんでした。したがってfinalRuntimeExceptionsとErrorsを除いて、JVMによって例外のないことが本当に保証されているのはメソッドだけです。そんなにチェックする例外処理のために:D
leviathanbadger

401

かなり長い間Javaバイトコードを操作して、この問題についていくつかの追加調査を行った後、これが私の発見の要約です。

スーパーコンストラクターまたは補助コンストラクターを呼び出す前にコンストラクターでコードを実行する

Javaプログラミング言語(JPL)では、コンストラクターの最初のステートメントは、スーパーコンストラクターまたは同じクラスの別のコンストラクターの呼び出しでなければなりません。これは、Javaバイトコード(JBC)には当てはまりません。バイトコード内では、コンストラクターの前にコードを実行することは絶対に正当です。

  • このコードブロックの後で、互換性のある別のコンストラクターが呼び出されます。
  • この呼び出しは条件付きステートメント内ではありません。
  • このコンストラクター呼び出しの前に、構築されたインスタンスのフィールドは読み取られず、そのメソッドは呼び出されません。これは次の項目を意味します。

スーパーコンストラクターまたは補助コンストラクターを呼び出す前にインスタンスフィールドを設定する

前述のように、別のコンストラクターを呼び出す前にインスタンスのフィールド値を設定することは完全に合法です。6より前のJavaバージョンでこの「機能」を利用できるようにするレガシーハックも存在します。

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コンパイラーは常に、任意のクラスに対して少なくとも1つのコンストラクターを実装します。Javaバイトコードでは、これは必要ありません。これにより、リフレクションを使用しても構築できないクラスを作成できます。ただし、を使用sun.misc.Unsafeすると、そのようなインスタンスを作成できます。

シグネチャは同じであるが戻り値の型が異なるメソッドを定義する

JPLでは、メソッドはその名前と生のパラメータタイプによって一意として識別されます。JBCでは、生の戻り値の型がさらに考慮されます。

名前ではなくタイプのみが異なるフィールドを定義する

クラスファイルには、異なるフィールドタイプを宣言する限り、同じ名前の複数のフィールドを含めることができます。JVMは常にフィールドを名前とタイプのタプルとして参照します。

未宣言のチェック済み例外をキャッチせずにスローする

JavaランタイムとJavaバイトコードは、チェックされた例外の概念を認識していません。チェックされた例外がスローされた場合、常にキャッチまたは宣言されていることを確認するのはJavaコンパイラのみです。

ラムダ式の外で動的メソッド呼び出しを使用する

いわゆる動的メソッド呼び出しは、Javaのラムダ式だけでなく、あらゆる目的に使用できます。この機能を使用すると、たとえば、実行時に実行ロジックを切り替えることができます。JBCまで煮詰めた多くの動的プログラミング言語は、この命令を使用することでパフォーマンスを向上させました。Javaバイトコードでは、Java 7でラムダ式をエミュレートすることもできます。JVMはすでに命令を理解しているのに、コンパイラはまだ動的メソッド呼び出しの使用を許可していません。

通常は正当とは見なされない識別子を使用する

メソッド名にスペースと改行を使用することを考えたことはありますか?独自のJBCを作成し、コードレビューのために頑張ってください。識別子の唯一の不正な文字は.;[/。さらに、名前が付いていない、<init>またはandを<clinit>含むことができないメソッド。<>

finalパラメータまたはthis参照を再割り当てします

finalパラメータはJBCに存在しないため、再割り当てすることができます。this参照を含むすべてのパラメーターは、単一のメソッドフレーム内のthisインデックス0で参照を再割り当てできるようにするJVM内の単純な配列にのみ格納されます。

finalフィールドを再割り当て

finalフィールドがコンストラクター内で割り当てられている限り、この値を再割り当てすることも、値をまったく割り当てないこともできます。したがって、次の2つのコンストラクターは有効です。

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

以下のためにstatic finalフィールド、でも、クラス初期化子の外のフィールドを再割り当てすることが許可されています。

コンストラクタとクラス初期化子を、それらがメソッドであるかのように扱います。

これはより概念的な機能ですが、JBC内では、コンストラクターは通常のメソッドと同じように扱われます。コンストラクタが別の正当なコンストラクタを呼び出すことを保証するのは、JVMのベリファイアのみです。それ以外は、コンストラクタを呼び出さなければならず<init>、クラス初期化子が呼び出されるのは単なるJava命名規則です<clinit>。この違いを除けば、メソッドとコンストラクタの表現は同じです。Holgerがコメントで指摘したように、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#bazBar#baz。直接のスーパークラスのスーパーメソッド実装とは別のスーパーメソッド実装を呼び出すための明示的な呼び出しを定義することは引き続き可能ですが、これは1.1以降のJavaバージョンではもはや効果がありません。Java 1.1では、この動作はACC_SUPER、直接のスーパークラスの実装のみを呼び出すのと同じ動作を有効にするフラグを設定することによって制御されていました。

同じクラスで宣言されているメソッドの非仮想呼び出しを定義する

Javaでは、クラスを定義することはできません

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

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

上記のコードでは、常にのインスタンスでRuntimeExceptionwhen fooが呼び出されますBar。メソッドを定義して、で定義されている独自のメソッドをFoo::foo呼び出すことはできません。非プライベートインスタンスメソッドで、呼び出しは常に仮想です。バイトコードを使用すると、1はしかし、使用する呼び出しを定義することができます直接リンクオペコードでメソッド呼び出しをするのバージョンを。このオペコードは通常、スーパーメソッド呼び出しを実装するために使用されますが、オペコードを再利用して、説明されている動作を実装できます。 barFoobarINVOKESPECIALbarFoo::fooFoo

細粒度の型注釈

Javaでは、アノテーションは、アノテーションが@Target宣言する内容に従って適用されます。バイトコード操作を使用すると、このコントロールとは無関係に注釈を定義できます。また、たとえば、@Target注釈が両方の要素に適用される場合でも、パラメータに注釈を付けずにパラメータタイプに注釈を付けることが可能です。

タイプまたはそのメンバーの属性を定義する

Java言語内では、フィールド、メソッド、またはクラスのアノテーションのみを定義できます。JBCでは、基本的にあらゆる情報をJavaクラスに埋め込むことができます。ただし、この情報を利用するために、Javaクラスのロードメカニズムに依存することはできなくなりましたが、メタ情報を自分で抽出する必要があります。

オーバーフローと暗黙的に割り当てbyteshortcharおよびboolean

後者のプリミティブ型は、JBCでは通常知られていませんが、配列型またはフィールドおよびメソッド記述子に対してのみ定義されています。バイトコード命令内では、すべての名前付き型は32ビットのスペースを使用し、として表すことができますint。公式には、唯一のintfloatlongおよびdoubleタイプは、バイトコードJVMの検証のルールによって、すべての必要明示的な変換の中に存在します。

モニターを解放しない

synchronizedブロックは、実際には二つの文、取得してモニタを解放するために1:1で構成されています。JBCでは、リリースせずに取得できます。

:HotSpotの最近の実装では、これは代わりにIllegalMonitorStateExceptionメソッドの最後の、またはメソッド自体が例外自体によって終了した場合の暗黙のリリースにつながります。

return型初期化子に複数のステートメントを追加する

Javaでは、次のような自明な型初期化子でさえ

class Foo {
  static {
    return;
  }
}

違法です。バイトコードでは、型初期化子は他のメソッドと同じように扱われます。つまり、returnステートメントはどこにでも定義できます。

既約ループを作成する

Javaコンパイラーは、ループをJavaバイトコードのgotoステートメントに変換します。このようなステートメントを使用して、Javaコンパイラーが行うことのできない既約ループを作成できます。

再帰的なcatchブロックを定義する

Javaバイトコードでは、ブロックを定義できます。

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

synchronizedJavaでブロックを使用すると、同様のステートメントが暗黙的に作成され、モニターの解放中に例外が発生すると、このモニターを解放する命令に戻ります。通常、そのような命令では例外は発生しませんが、発生した場合(非推奨などThreadDeath)、モニターは解放されます。

デフォルトのメソッドを呼び出す

Javaコンパイラーは、デフォルトのメソッドの呼び出しを許可するために、いくつかの条件を満たす必要があります。

  1. メソッドは最も具体的なものでなければなりません(スーパータイプを含む任意のタイプによって実装されるサブインターフェースによってオーバーライドされてはいけません)。
  2. デフォルトメソッドのインターフェースタイプは、デフォルトメソッドを呼び出すクラスによって直接実装される必要があります。ただし、BインターフェースAがインターフェースを拡張するものの、のメソッドをオーバーライドしない場合Aでも、メソッドを呼び出すことができます。

Javaバイトコードの場合、2番目の条件のみがカウントされます。ただし、最初のものは無関係です。

でないインスタンスでスーパーメソッドを呼び出す 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コンパイラーによって適用され、2番目の検証はクラスのロード時に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は以前、JSRand RETステートメントによってサブルーチンを利用していました。JBCは、この目的のための独自のタイプの返信アドレスさえ知っていました。ただし、サブルーチンを使用すると、静的コード分析が複雑になりすぎたため、これらの命令は使用されなくなりました。代わりに、Javaコンパイラはコンパイルしたコードを複製します。ただし、これは基本的に同じロジックを作成するので、別のことを実現することはあまり考慮していません。同様に、たとえば、NOOPJavaコンパイラーでも使用されないバイトコード命令ですが、これにより実際に何か新しいことを実現することはできません。コンテキストで指摘されているように、これらの言及された「機能命令」は、機能をさらに少なくする一連の正当なオペコードから削除されています。


3
メソッド名に関しては、名前で<clinit>メソッドを定義する<clinit>が、パラメーターを受け入れるか、void戻り値の型を持たないようにすることで、複数のメソッドを持つことができます。しかし、これらのメソッドはあまり役に立ちません。JVMはそれらを無視し、バイトコードはそれらを呼び出すことができません。唯一の使用法は読者を混乱させることです。
Holger 14

2
私が発見したのは、OracleのJVMがメソッドの終了時にリリースされていないモニターを検出しIllegalMonitorStateExceptionmonitorexit指示を省略した場合にがスローされることです。また、を実行できなかった例外的なメソッド出口の場合は、monitorexit警告なしでモニターをリセットします。
Holger 2014

1
@Holger-知らなかった、これは少なくとも以前のJVMで可能であったことを知っていた。JRockitにはこの種の実装用の独自のハンドラさえある。エントリーを更新します。
Rafael Winterhalter 2014

1
まあ、JVM仕様はそのような振る舞いを義務付けていません。このような非標準のバイトコードを使用してぶら下がっている組み込みロックを作成しようとしたので、それを発見しました。
Holger 2014

3
[OK]を、私は、関連する仕様を見つけました:「構造化ロックはメソッド呼び出しの間に、与えられたモニター上のすべての出口がそのモニタ上の前のエントリと一致した場合、状況です。Java仮想マシンに送信されたすべてのコードが構造化ロックを実行する保証はないため、Java仮想マシンの実装は許可されますが、構造化ロックを保証する次の2つのルールの両方を強制する必要はありません。…」
Holger

14

Javaバイトコードでは実行できるがJavaソースコードでは実行できないいくつかの機能を次に示します。

  • メソッドが例外をスローすることを宣言せずに、メソッドからチェック例外をスローする。チェックされた例外とチェックされていない例外は、JVMではなくJavaコンパイラによってのみチェックされるものです。このため、たとえばScalaは、宣言されていないメソッドからチェック例外をスローできます。Javaジェネリックには、卑劣なスローと呼ばれる回避策があります。

  • のみ、戻り値の型が異なることをクラスの2つのメソッドを持つすでに述べたようにヨアヒムの答えが異なる場合、Java言語仕様では、同じクラスの2つのメソッドを許可していません:だけ彼らの戻り値の型(すなわち、同じ名前、同じ引数リストで、 ...)。ただし、JVM仕様にはそのような制限はないため、クラスファイルにそのような2つのメソッドを含めることができ、通常のJavaコンパイラを使用してそのようなクラスファイルを生成する方法はありません。この回答には、良い例/説明があります


4
Javaで最初に行う方法あることに注意してください。それは時々卑劣な投げと呼ばれます。
ヨアヒムザウアー

今こそ卑劣です!:D共有いただきありがとうございます。
Esko Luontola、2011

Thread.stop(Throwable)卑劣なスローにも使えると思います。すでにリンクされている方が速いと思います。
Bart van Heukelom、2011

2
Javaバイトコードでコンストラクターを呼び出さずにインスタンスを作成することはできません。ベリファイアは、初期化されていないインスタンスを使用しようとするコードを拒否します。オブジェクトの逆シリアル化の実装では、ネイティブコードヘルパーを使用して、コンストラクターを呼び出さずにインスタンスを作成します。
Holger 2013

Objectを拡張するクラスFooの場合、Objectで宣言されているコンストラクターを呼び出してFooをインスタンス化することはできませんでした。検証者はそれを拒否します。JavaのReflectionFactoryを使用してそのようなコンストラクターを作成することもできますが、これはほとんどバイトコード機能ではありませんが、Jniによって実現されます。あなたの答えは間違っていて、ホルガーは正しいです。
Rafael Winterhalter 2014年

8
  • GOTOラベルと一緒に使用して、独自の制御構造を作成できます(以外for while
  • thisメソッド内のローカル変数をオーバーライドできます
  • これらの両方を組み合わせて、末尾呼び出しの最適化されたバイトコードを作成できます(私はJCompiloでこれを行います

関連する点として、デバッグでコンパイルした場合、メソッドのパラメーター名を取得できます(Paranamerは、バイトコードを読み取ることでこれを行います


どのように行うoverride、このローカル変数を?
マイケル

2
@Michaelのオーバーライドは強すぎます。バイトコードレベルでは、すべてのローカル変数は数値インデックスによってアクセスされ、既存の変数への書き込みと新しい変数の初期化(分離したスコープで)の間に違いはありません。どちらの場合でも、それはローカル変数への単なる書き込みです。this変数は、インデックスゼロを持ち、しかしで予め初期化される以外にthisインスタンスメソッドを入力する際の基準、それだけで、ローカル変数です。そのため、使用方法に応じて、thisスコープの終了やthis変数の変更のように機能する別の値を書き込むことができます。
Holger

そうですか!それで、本当にそれをthis再割り当てできるのですか?それが正確に何を意味するのか疑問に思ったのは、単にオーバーライドという言葉だけだったと思います。
Michael


4

Java言語では、コンストラクターの最初のステートメントはスーパークラスコンストラクターの呼び出しでなければなりません。バイトコードにはこの制限はありません。代わりに、メンバーにアクセスする前に、オブジェクトに対してスーパークラスコンストラクターまたは同じクラス内の別のコンストラクターを呼び出す必要があるという規則があります。これにより、次のようなより多くの自由が可能になります。

  • 別のオブジェクトのインスタンスを作成し、それをローカル変数(またはスタック)に格納し、パラメーターとしてスーパークラスコンストラクターに渡しながら、参照をその変数に保持して、他の用途に使用します。
  • 条件に基づいて、他のさまざまなコンストラクターを呼び出します。これは可能なはずです:Javaで条件付きで別のコンストラクターを呼び出す方法は?

私はこれらをテストしていませんので、間違っている場合は修正してください。


スーパークラスコンストラクターを呼び出す前に、インスタンスのメンバーを設定することもできます。ただし、それ以前はフィールドの読み取りやメソッドの呼び出しはできません。
Rafael Winterhalter 2014年

3

プレーンなJavaコードではなく、バイトコードでできることは、コンパイラーなしでロードして実行できるコードを生成することです。多くのシステムにはJDKではなくJREがあり、コードを動的に生成したい場合は、Javaコードの代わりにバイトコードを生成する方が簡単ではありませんが、使用する前にコンパイルする必要があります。


6
ただし、コンパイラーをスキップするだけで、コンパイラーを使用して作成できないもの(使用可能な場合)は作成しません。
Bart van Heukelom、2011

2

私はI-Playのときにバイトコードオプティマイザを作成しました(これはJ2MEアプリケーションのコードサイズを小さくするように設計されています)。私が追加した機能の1つは、インラインバイトコードを使用する機能でした(C ++のインラインアセンブリ言語と同様)。値を2回必要とするため、DUP命令を使用して、ライブラリメソッドの一部である関数のサイズをなんとか削減できました。私はゼロバイト命令も持っていました(charを受け取るメソッドを呼び出していて、intを渡したい場合、キャストする必要がないことを知っているので、char(var)を置き換えるためにint2char(var)を追加しました。コードのサイズを減らすためのi2c命令。floata = 2.3; float b = 3.4; float c = a + b;を実行するように設定しました。これは固定小数点に変換されます(より高速で、一部のJ2MEでは浮動小数点をサポートします)。


2

Javaでは、保護されたメソッド(またはその他のアクセスの削減)でパブリックメソッドをオーバーライドしようとすると、「より弱いアクセス権限を割り当てようとしています」というエラーが発生します。JVMバイトコードでそれを行う場合、ベリファイアはそれで問題なく、それらがパブリックであるかのように、親クラスを介してこれらのメソッドを呼び出すことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.