記事ディレクトリ
概観
コンパイル期間は以下を参照する場合があります。
- フロントエンドコンパイラ:JavaファイルをJavacなどのクラスファイルに変換するプロセス
- ジャストインタイムコンパイラ(JITコンパイラ):ホットスポット仮想マシンのC1およびC2コンパイラなど、実行時にバイトコードをネイティブマシンコードに変換するプロセス
- 静的プリコンパイラー:プログラムをターゲット命令セットに関連するバイナリコードに直接コンパイルするプロセス
Javaでは、ランタイム中のジャストインタイムコンパイラーの最適化プロセスがプログラムの実行効率の継続的な改善をサポートし、コンパイル期間中のフロントエンドコンパイラーの最適化プロセスがプログラマーのコーディング効率と言語ユーザーの幸福をサポートします。改善する
Javacコンパイラ
コンパイルプロセス
- 準備プロセス:プラグインアノテーションプロセッサの初期化
- シンボルテーブルの解析と入力のプロセス
- 字句、文法分析、抽象構文木を構築する
- シンボルテーブルに入力し、シンボルアドレスとシンボル情報を生成します
- プラグインアノテーションプロセッサのアノテーション処理プロセス:プラグインアノテーションプロセッサの実行フェーズ
- セマンティック分析とバイトコード生成プロセス
- 注釈チェック:文法の静的情報をチェックします
- データフローと制御フロー分析。プログラムの動的実行プロセスを確認する
- 構文糖を解読します。単純化されたコードの構文糖度を元の形式に減らす
- バイトコード生成。前のステップで生成された情報をバイトコードに変換します
上記のアクションでは、アノテーションの挿入時に新しいシンボルが生成される可能性があります。新しいシンボルが生成された場合は、以前の分析に戻り、シンボルテーブルへの入力プロセス中にこれらの新しいシンボルを再処理する必要があります。
シンボルテーブルの解析と入力
字句、文法分析
字句解析は、ソースコードの文字ストリームをタグのセットに変換するプロセスです。タグは、コンパイル時の最小要素です。たとえば、intはタグです。
構文解析は、タグシーケンスに基づいて抽象構文ツリーを構築するプロセスです。抽象構文ツリーは、プログラムコードの構文構造を記述するために使用されるツリー表現です
シンボルテーブルに入力
シンボルテーブルは、一連のシンボルアドレスとシンボル情報で構成されるデータ構造であり、キーと値のペアに格納されたハッシュテーブルとして使用できます。
注釈プロセッサ
プラグインアノテーションプロセッサ:アノテーションは通常、実行時に機能します。これはコンパイル期間に進み、コード内の特定のアノテーションを処理するため、フロントエンドコンパイラの動作プロセスに影響します。
プラグインアノテーションプロセッサは、コンパイラのプラグインと見なすことができます。これらのプラグインがアノテーションの処理中に構文ツリーを変更した場合、コンパイラは、シンボルテーブルの解析と入力のプロセスに戻り、すべてのプラグインアノテーションまで再処理します。プロセッサは構文ツリーを変更しなくなり、各サイクルはラウンドと呼ばれます。
プラグイン注釈プロセッサは、注釈、equals()、hashCode()メソッドによるゲッター/セッターメソッドの自動生成など、多くの機能を実現できます。
セマンティック分析とバイトコード生成
抽象構文ツリーは、正しい構造を持つソースプログラムを表すことができますが、ソースプログラムのセマンティクスが論理的であることを保証することはできません。セマンティック分析の主なタスクは、タイプチェックなどの正しい構造を持つソースプログラムの状況依存の性質をチェックすることです。
コンパイルすると、IDEで赤い線でマークされたエラーメッセージが表示されます。これらのほとんどは、セマンティック分析ステージの結果です
ラベルチェック
変数が使用前に宣言されているかどうか、変数と代入の間のデータ型が一致するかどうかなどを確認します。
定数の折りたたみ:int a=1+2
a = 3になります
データおよび制御フロー分析
プログラムのローカル変数が使用前に割り当てられているかどうか、メソッドの各パスに戻り値があるかどうか、チェックされたすべての例外が正しく処理されているかどうかなどの問題を確認してください。
構文糖
言語に特定の文法を追加しても、言語のコンパイル結果や機能には実際の影響はありませんが、プログラマーがその言語を使用すると便利です。コードの量を減らし、プログラムの可読性を高め、プログラムコードエラーの可能性を減らします。
バイトコード生成
この段階で、インスタンスコンストラクター()メソッドとクラスコンストラクター()が構文ツリーに追加されます。
()、()の生成はコード収束のプロセスです。コンパイラーはステートメントブロックを収束し、変数の初期化を行い、親クラスインスタンスコンストラクターとその他の操作を2つのメソッドに呼び出します。
また、ソースコードの出現順序に関係なく、最初に親クラスのインスタンスコンストラクターを実行し、次に変数を初期化し、最後にステートメントブロックを実行する順序で実行する必要があります。
Java構文糖の味
ジェネリック
ジェネリックの本質は、パラメーター化された型またはパラメーター化された多態性のアプリケーションです。つまり、操作のデータ型をメソッドシグネチャの特別なパラメータとして指定できます。
Javaのジェネリックは「型消去ジェネリック」であり、プログラムのソースコードにのみ表示されます。コンパイルされたバイトコードファイルでは、すべてのジェネリックが元のrawタイプ(Raw Type)に置き換えられ、対応する必須の変換コードが挿入されています
したがって、ArrayListとArrayListは実行時に同じ型です
使用効果と操作効率の両方の点で、ジェネリックの実現に遅れをとっています。唯一の利点は、ジェネリックの消去の実現をJavaコンパイラで改善するだけでよいことです...
型消去
Javaは、既存のタイプを一般化することを選択します。たとえば、ArrayList in situ一般化をArrayList
ベアタイプに一般化すると
、このタイプのすべてのジェネリックインスタンスの共通の親タイプと見なされます。ArrayList、ArrayListなどのすべてのジェネリックインスタンスタイプをすべてが自動的にArrayListのサブタイプになり
ます。Javaのベアタイプの実装:コンパイル時にArrayListをArrayListに単純かつ無作法に復元し、要素へのアクセスまたは変更時に必須の型変換とチェック命令を自動的に挿入します。
ただし、いわゆるerasureメソッドの消去は、メソッドのCode属性のバイトコードを消去するだけであり、実際には一般的な情報がメタデータに保持されています。したがって、パラメータ化された型は、エンコード時にリフレクションを介して取得できます
問題
プリミティブデータをサポートしていません
基本タイプのintとObjectの間の必須変換をサポートしていないため、汎用情報が消去されると、必須変換コードはその場所まで挿入できません。Javaはプリミティブ型のジェネリックをサポートせず、ArrayListのみを使用できます。これにより、無数の構築パッケージクラスのパックおよびアンボックスオーバーヘッドが発生します。
長いコード
実行時にジェネリック型情報を取得できません
あいまいさをもたらす
ジェネリックがオーバーロードに遭遇したとき
自動梱包、開梱、ループトラバーサル
ループトラバーサルは、コードをイテレータの実装に復元することです。そのため、トラバースする必要のあるクラスは、イテレータインターフェースを実装します。
可変長パラメーターは、呼び出し時に配列型パラメーターになります
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c==d);//true
System.out.println(e==f);//false
System.out.println(c==(a+b));//true
System.out.println(c.equals(a+b));//true
System.out.println(g==(a+b));//true
System.out.println(g.equals(a+b));//false
パッケージ化クラスの==演算は算術演算に遭遇しないと自動的に開梱されず、equals()メソッドはデータ変換の関係を処理しないので、実際のコーディングではこのような自動ボックス化とボックス化解除を避けてください
条件付きコンパイル
Java言語のコンパイル方法:コンパイラーはJavaファイルを1つずつコンパイルするのではなく、すべてのコンパイル単位の構文ツリーの最上位ノードをリストに入力して処理してからコンパイルするため、各ファイルは相互にシンボリック情報を提供できます
Javaの条件付きコンパイル:一定の条件でifステートメントを使用すると、コンパイル時に実行されます
public static void main(String[] args)
{
if(true)
{
System.out.println("1");
}
else
{
System.out.println("2");
}
}
コードがコンパイルされた後のClassファイルの逆コンパイル結果:
public static void main(String[] args)
{
System.out.println("1");
}
コンパイラーは、ブランチ内の無効なコードを除去します。
この種の構文シュガーはメソッド本体内にのみ記述でき、ステートメントの基本ブロックレベルでのみ条件付きコンパイルを実行できますが、条件に従ってJavaクラス全体の構造を調整する方法はありません。
java
public static void main(String [] args)
{ System.out.println(“ 1”); }
编译器将会把分支中不成立的代码消除掉。
这种语法糖只能写在方法体内部,只能实现语句基本块级别的条件编译,而没有办法实现根据条件调整整个Java类的结构