Java
Java JavaFX NetBeans
このエントリーは、JavaFX Advent Calendar 2017 の 20 日目です。
現時点でもエントリーが無いので急遽先日遭遇した問題に解決策を教えていただいたのでそれを記事にしました。
昨日は @aoetk さんの「Bean ValidationのJavaFX対応」でした。
明日も、この記事を書いている現時点ではまだ空いてます。(^_^; きっと誰かが素敵な記事を投稿してくれると楽しみにしています。
11 月から IBM Cloud (Bluemix)ライト・アカウントが気楽に使えるようになり Watson Personality Insights を利用した人格診断プログラムを組んで楽しんでいました。
その際に Canvas に文字を描くときの位置決めに難儀したので FontMetrics を取得する方法をφ(..)メモメモ
まず、Java で FontMetrics を取得するとしたら java.awt.FontMetrics クラスを使うことができます。
Abstract Window Toolkit を使って下記のようなプログラムを組んでみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
package jp.yucchi.textofcanvas; import java.awt.BorderLayout; import java.awt.Canvas; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.GraphicsEnvironment; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.stream.Stream; import jp.yucchi.textofcanvas.TextOfCanvas.Configuration; /** * * @author Yucchi */ public class TextOfCanvas extends Frame { protected enum Configuration { WIDTH(800), HEIGHT(300), FONT_SIZE(100); int settingValue; private Configuration(int settingValue) { this.settingValue = settingValue; } public int getSettingValue() { return settingValue; } } public static void main(String[] args) { TextOfCanvas textOfCanvas = new TextOfCanvas(); // // 使用可能なフォント // Stream.of(GraphicsEnvironment.getLocalGraphicsEnvironment() // .getAvailableFontFamilyNames()) // .forEach(System.out::println); } private Frame frame; TextOfCanvas() { initGUI(); } private void initGUI() { frame = new Frame("Java AWT Text of Canvas"); frame.setSize(Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue); frame.setLayout(new BorderLayout()); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent we) { System.exit(0); } }); TextCanvas textCanvas = new TextCanvas(); frame.add(textCanvas, BorderLayout.CENTER); frame.setLocationRelativeTo(null); frame.setVisible(true); } } class TextCanvas extends Canvas { @Override public void paint(Graphics g) { g.setColor(Color.LIGHT_GRAY); g.drawLine(0, Configuration.HEIGHT.settingValue / 2, Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue / 2); g.drawLine(Configuration.WIDTH.settingValue / 4, 0, Configuration.WIDTH.settingValue / 4, Configuration.HEIGHT.settingValue); // Font font = new java.awt.Font("あくびん", java.awt.Font.BOLD, Configuration.FONT_SIZE.settingValue); // Leading: 0.0 Font font = new java.awt.Font("SansSerif", java.awt.Font.BOLD, Configuration.FONT_SIZE.settingValue); g.setFont(font); g.setColor(Color.RED); g.drawString("Japan 日本", Configuration.WIDTH.settingValue / 4, Configuration.HEIGHT.settingValue / 2); FontMetrics fontmetrics = g.getFontMetrics(); double height = fontmetrics.getHeight(); // テキスト1行の標準の高さ Height = Ascent + Descent + Leading double width = fontmetrics.stringWidth("Japan 日本"); double ascent = fontmetrics.getAscent(); // ベースラインからの高さ double descent = fontmetrics.getDescent(); // ベースラインから下にはみ出る量 // double maxDecent = fontmetrics.getMaxDecent(); // スペルミスによる @Deprecated (^_^; double leading = fontmetrics.getLeading(); // 前の行の descent のラインと次の行の ascent のラインの間に必要な「行間」の量 System.out.println("Height: " + height); System.out.println("Width: " + width); System.out.println("Ascent: " + ascent); System.out.println("Descent: " + descent); System.out.println("Leading: " + leading); Font currentFont = g.getFont(); Font shrinkFont = currentFont.deriveFont(currentFont.getSize() * 0.3F); g.setFont(shrinkFont); g.setColor(Color.GREEN); g.drawLine(0, (int) (Configuration.HEIGHT.settingValue / 2 - ascent), Configuration.WIDTH.settingValue, (int) (Configuration.HEIGHT.settingValue / 2 - ascent)); g.drawString("Ascent: " + ascent, 0, (int) (Configuration.HEIGHT.settingValue / 2 - height + descent)); g.setColor(Color.BLUE); g.drawLine(0, (int) (Configuration.HEIGHT.settingValue / 2 + descent), Configuration.WIDTH.settingValue, (int) (Configuration.HEIGHT.settingValue / 2 + descent)); g.drawString("Descent: " + descent, 0, (int) (Configuration.HEIGHT.settingValue / 2 + descent - leading)); g.setColor(Color.magenta); g.drawLine(0, (int) (Configuration.HEIGHT.settingValue / 2 + leading + descent), Configuration.WIDTH.settingValue, (int) (Configuration.HEIGHT.settingValue / 2 + leading + descent)); g.drawString("Leading: " + leading, 0, (int) (Configuration.HEIGHT.settingValue / 2 + leading * 2 + descent + g.getFontMetrics().getAscent() - g.getFontMetrics().getDescent())); } } |
懐かしいコードですね。
83 行目で FontMetrics を Graphics コンテキストより取得してフォントの構造データをそれぞれの取得メソッドで取得しています。
原点の X, Y 座標に薄いグレーでラインをひいてます。
フォントの高さ関するデータもそれぞれラインをひいてみました。
Abstract Window Toolkit を使って FontMetrics を得ることは簡単にできることが確認できました。
それでは JavaFX 9 ではどうでしょうか?
同じようなプログラムを組んでしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
package jp.yucchi.textcoordinates; import com.sun.javafx.tk.FontMetrics; import com.sun.javafx.tk.Toolkit; import java.awt.GraphicsEnvironment; import java.util.stream.Stream; import javafx.application.Application; import javafx.geometry.VPos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontSmoothingType; import javafx.scene.text.FontWeight; import javafx.scene.text.Text; import javafx.stage.Stage; /** * * @author Yucchi */ public class TextCoordinates extends Application { private enum Configuration { WIDTH(800), HEIGHT(300), FONT_SIZE(100); private final int settingValue; private Configuration(int settingValue) { this.settingValue = settingValue; } public int getSettingValue() { return settingValue; } } @Override public void start(Stage primaryStage) { // // 使用可能なフォント // Stream.of(GraphicsEnvironment.getLocalGraphicsEnvironment() // .getAvailableFontFamilyNames()) // .forEach(System.out::println); Group root = new Group(); Canvas canvas = new Canvas(Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue); GraphicsContext gc = canvas.getGraphicsContext2D(); drawShapes(gc); root.getChildren().add(canvas); Scene scene = new Scene(root, Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue); primaryStage.setTitle("JavaFX Text of Canvas"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } private void drawShapes(GraphicsContext gc) { Text text = new Text("Japan 日本"); // Font font = Font.font("あくびん", FontWeight.BOLD, Configuration.FONT_SIZE.settingValue); // Leading: 0.0 Font font = Font.font("SansSerif", FontWeight.BOLD, Configuration.FONT_SIZE.settingValue); text.setFont(font); double width = text.getBoundsInLocal().getWidth(); double height = text.getBoundsInLocal().getHeight(); double baselineOffset = text.getBaselineOffset(); double minY = text.getLayoutBounds().getMinY(); double maxY = text.getLayoutBounds().getMaxY(); System.out.println("Width: " + width); System.out.println("Height: " + height); // Ascent + Descent System.out.println("BaselineOffset: " + baselineOffset); // Ascent System.out.println("MinY: " + minY); // -Ascent System.out.println("MaxY: " + maxY); // Descent final FontMetrics fontMetrics = Toolkit.getToolkit().getFontLoader().getFontMetrics(font); // float fontMetricsWidth = fontMetrics.computeStringWidth(text.getText()); // JavaFX9 では無くなった? // このフォントでのテキスト行のための最大行高 float fontMetricsLineHeight = fontMetrics.getLineHeight(); // 平均の小文字の最上部へのベースラインから距離 float fontMetricsXHeight = fontMetrics.getXheight(); // 平均の最大文字高さまでベースラインからの距離。 この値は常に正の値です float fontMetricsAscent = fontMetrics.getAscent(); // ベースラインから最大文字高さまでの距離。 この値は常に正の値です float fontMetricsMaxAscent = fontMetrics.getMaxAscent(); // ベースラインは、デセンダーのない文字(例えば、小文字の「a」)が置かれている仮想線です。 // フォントメトリックに関しては、この点から他のすべてのメトリックが導出されます。 この点は暗黙的にゼロとして定義されています。 int fontMetricsBaseline = fontMetrics.getBaseline(); // ベースラインから最低平均値までの距離。 ディセンダー。 この値は常に正の値です float fontMetricsDescent = fontMetrics.getDescent(); // ベースラインから絶対値の最も低いディセンダーまでの距離。 この値は常に正の値です float fontMetricsMaxDescent = fontMetrics.getMaxDescent(); // このフォントのテキスト行間のスペース量。 // これは、1行のmaxDecentと次のmaxAscentの間のスペース量です。 // この数値は、lineHeightに含まれています。 float fontMetricsLeading = fontMetrics.getLeading(); // System.out.println("FontMetricsWidth: " + fontMetricsWidth); System.out.println("FontMetricsLineHeight: " + fontMetricsLineHeight); // Ascent + Descent + Leading System.out.println("FontMetricsXHeight: " + fontMetricsXHeight); System.out.println("FontMetricsAscent: " + fontMetricsAscent); System.out.println("FontMetricsMaxAscent: " + fontMetricsMaxAscent); System.out.println("FontMetricsBaseline: " + fontMetricsBaseline); System.out.println("FontMetricsDescent: " + fontMetricsDescent); System.out.println("FontMetricsMaxDescent: " + fontMetricsMaxDescent); System.out.println("FontMetricsLeading: " + fontMetricsLeading); gc.setFontSmoothingType(FontSmoothingType.LCD); gc.setStroke(Color.LIGHTGRAY); gc.setLineWidth(1.0); gc.strokeLine(0.0, Configuration.HEIGHT.settingValue / 2.0, Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue / 2.0); gc.strokeLine(Configuration.WIDTH.settingValue / 4.0, 0.0, Configuration.WIDTH.settingValue / 4.0, Configuration.HEIGHT.settingValue); // テキスト gc.setFont(font); gc.setFill(Color.RED); gc.setTextBaseline(VPos.BASELINE); gc.fillText(text.getText(), Configuration.WIDTH.settingValue / 4.0, Configuration.HEIGHT.settingValue / 2.0); // FontMetrics データ表示用フォント gc.setFont(Font.font("SansSerif", FontWeight.BOLD, Configuration.FONT_SIZE.settingValue * 0.3)); // gc.setFont(Font.font("あくびん", FontWeight.BOLD, Configuration.FONT_SIZE.settingValue * 0.3)); final FontMetrics shrinkFontMetrics = Toolkit.getToolkit().getFontLoader().getFontMetrics(gc.getFont()); // Ascent gc.setStroke(Color.GREEN); gc.strokeLine(0.0, Configuration.HEIGHT.settingValue / 2.0 - fontMetricsAscent, Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue / 2.0 - fontMetricsAscent); gc.setFill(Color.GREEN); gc.fillText("Ascent: " + String.format("%.1f", fontMetricsAscent), 0.0, Configuration.HEIGHT.settingValue / 2.0 - fontMetricsAscent - fontMetricsLeading); // XHeight gc.setStroke(Color.PURPLE); gc.strokeLine(0.0, Configuration.HEIGHT.settingValue / 2.0 - fontMetricsXHeight, Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue / 2.0 - fontMetricsXHeight); gc.setFill(Color.PURPLE); gc.fillText("XHeight: " + String.format("%.1f", fontMetricsXHeight), 0.0, Configuration.HEIGHT.settingValue / 2.0 - fontMetricsXHeight - fontMetricsLeading); // Descent gc.setStroke(Color.BLUE); gc.strokeLine(0.0, Configuration.HEIGHT.settingValue / 2.0 + fontMetricsDescent, Configuration.WIDTH.settingValue, (int) (Configuration.HEIGHT.settingValue / 2.0 + fontMetricsDescent)); gc.setFill(Color.BLUE); gc.fillText("Descent: " + String.format("%.1f", fontMetricsDescent), 0.0, Configuration.HEIGHT.settingValue / 2.0 + fontMetricsDescent - fontMetricsLeading); // Leading gc.setStroke(Color.MAGENTA); gc.strokeLine(0.0, (Configuration.HEIGHT.settingValue / 2.0 + fontMetricsLeading + fontMetricsDescent), Configuration.WIDTH.settingValue, Configuration.HEIGHT.settingValue / 2.0 + fontMetricsLeading + fontMetricsDescent); gc.setFill(Color.MAGENTA); gc.fillText("Leading: " + String.format("%.1f", fontMetricsLeading), 0.0, Configuration.HEIGHT.settingValue / 2.0 + fontMetricsDescent + shrinkFontMetrics.getAscent() + shrinkFontMetrics.getLeading()); } } |
残念ながらコンパイルエラーです。
「名前のないモジュールにエクスポートされていません」ってなんのことですか?
JavaFX 9 では java.awt.FontMetrics クラスは使えないようなので com.sun.javafx.tk.FontMetrics, com.sun.javafx.tk.Toolkit を使用しました。
確か、JavaFX 8 では使えていたような記憶があるんだけど・・・
そう言えば、Project Jigsaw の影響で com.sun ではじまるパッケージが使えないものがあるようです。
このプログラムで使っている com.sun.javafx.tk.FontMetrics, com.sun.javafx.tk.Toolkit も JDK 内部(モジュール)にちゃんとあるのにデフォルトで使えなくしてあります。
困りました!
Windows 環境なら JavaFX 9 だったら HiDPI 対応の恩恵を享受することが可能なのに。
ちなみに JavaFX 8 だったら問題なく動きました。
さて、どうしたものか。。。
以前 Twitter で 「hoge は fuga ができないからクソッ!」もしくは女子高生を装ってヘルプをつぶやくと優秀なプログラマが解決策を提案してくれるという法則を学んだ。
私には役者の才能も無いし、小賢しいことをするのは面倒なので素直に Twitter でつぶやいたところ心優しい Java プログラマーが助けてくれました。
ありがとうございます!
早速 NetBeans に javacとjavaのオプションを設定してコンパイル、実行をしたところ無事に動きました。(^_^)
ちなみに、テキストの挿入位置の Y 座標の位置はデフォルトでは VPos.BASELINE となっています。
gc.setTextBaseline(VPos.BASELINE);
これは次のように変更することができます。
gc.setTextBaseline(VPos.TOP);
gc.setTextBaseline(VPos.CENTER);
gc.setTextBaseline(VPos.BOTTOM);
ああっ・・・ しまった。(>_<) 左のテキストの位置の修正忘れた。見なかったことにしてください。(ごめんなさい)
ついでに Leading が無いフォントの表示も見ておきます。
AWT と JavaFX では FontMetrics の扱い方に違いがあるので注意が必要ですね。
まとめ
さて、ここで JavaFX で FontMetrics を扱うために com.sun.javafx.tk.FontMetrics, com.sun.javafx.tk.Toolkit を使用します。
これを JavaFX 9 で使うためには javacとjavaのオプションに –add-exports=javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED の設定が必要です。
参考
JEP 261: Module System
カプセル化を破る
モジュールシステムによって定義されたアクセス制御境界に違反し、コンパイラと仮想マシンによって強制されて、
あるモジュールが別のモジュールの一部の非通知タイプにアクセスできるようにする必要があることがあります。
これは、例えば、内部型のホワイトボックステストを可能にするため、
またはサポートされていない内部APIをそれらに依存するようになったコードに公開するために望ましいことがある。
これを行うには、コンパイル時と実行時の両方で–add-exportsオプションを使用できます。
構文は次のとおりです。
–add-exports <source-module>/<package>=<target-module>(,<target-module>)*
<source-module>と<target-module>はモジュール名で、<package>はパッケージ名です。
–add-exportsオプションは、複数回使用できますが、ソースモジュールとパッケージ名の特定の組み合わせに対して最大で1回使用できます。
各インスタンスの効果は、指定されたパッケージの修飾されたエクスポートをソースモジュールからターゲットモジュールに追加することです。
これは基本的に、モジュール宣言内のエクスポート句のコマンドライン形式、またはModule :: addExportsメソッドの無制限な形式の呼び出しです。
結果として、ターゲットモジュールがソースモジュールの名前付きパッケージ内のパブリックタイプにアクセスできるようになります。
ターゲットモジュールはソースモジュールをモジュール宣言のrequires節、Module :: addReadsメソッド、または–add-readsオプションのインスタンスです。
たとえば、jmx.wbtestモジュールに、java.managementモジュールの非エクスポートcom.sun.jmx.remote.internalパッケージのホワイトボックス・テストが含まれている場合、それが必要とするアクセスはオプションを使用して許可することができます。
–add-exports java.management/com.sun.jmx.remote.internal=jmx.wbtest
特殊なケースとして、<target-module>がALL-UNNAMEDの場合、ソースパッケージは、最初に存在するか、後で作成されるかに関係なく、名前のないすべてのモジュールにエクスポートされます。
したがって、java.managementモジュールのsun.managementパッケージへのアクセスは、オプションを介してクラスパス上のすべてのコードに与えることができます。
–add-exports java.management/sun.management=ALL-UNNAMED
–add-exportsオプションを使用すると、指定されたパッケージのパブリックタイプにアクセスできます。
コアリフレクションAPIのsetAccessibleメソッドを使用して、非公開のすべての要素にさらにアクセスしてアクセスできるようにする必要があることがあります。
これを行うには、実行時に–add-opensオプションを使用することができます。
–add-exportsオプションと同じ構文です:
–add-opens <source-module>/<package>=<target-module>(,<target-module>)*
<source-module>と<target-module>はモジュール名で、<package>はパッケージ名です。
–add-opensオプションは複数回使用できますが、ソースモジュールとパッケージ名の特定の組み合わせに対して最大で1回使用できます。
各インスタンスの効果は、名前付きパッケージの修飾されたオープンをソースモジュールからターゲットモジュールに追加することです。
これは基本的に、モジュール宣言のopens節のコマンドライン形式、またはModule :: addOpensメソッドの無制限な形式の呼び出しです。
結果として、ターゲットモジュール内のコードは、ターゲットモジュールがソースモジュールを読み取る限り、
ソースリフレクションAPIを使用して、ソースモジュールの名前付きパッケージ内のパブリックなどのすべてのタイプにアクセスできます。
オープンパッケージは、コンパイル時にエクスポートされていないパッケージと区別できないため、
–add-opensオプションはそのフェーズでは使用できません。
–add-exportsと–add-opensオプションは、細心の注意を払って使用する必要があります。
それらを使用して、ライブラリモジュールの内部API、またはJDK自体のアクセス権を取得することはできますが、自己責任で行ってください。
内部APIが変更または削除された場合、ライブラリまたはアプリケーションは失敗します。
((((;゚Д゚)))))))
I wish you a Merry Christmas.
TAGS: Java,JavaFX,NetBeans |
2017年12月20日10:24 AM |
Java JavaFX NetBeans
このエントリーは、JavaFX Advent Calendar 2017 の 6 日目です。
昨日は @planet-az さんの「簡単なミュージックプレーヤーを作ってみた」でした。
明日はこの記事を書いている現時点ではまだ空いてます。(^_^; きっと誰かが素敵な記事を投稿してくれると楽しみにしています。
今さらですが JavaFX の非同期処理を復習がてら簡単にみていきたいと思います。
では、次のようなプログラムを作ってみます。
long result = 1;
for (int i = 0; i < repeatProcessingNumber; i++) {
result += result;
初期値 1 で10回ループするプログラムです。
計算終了後は 1024 と結果を表示します。
これだけでは時間のかかる処理とはならないので
TimeUnit.MILLISECONDS.sleep(500);
とスレッドをスリープさせています。
プログラムの状態、計算中の値の表示やキャンセルなどのメッセージ表示、プログレスバーによるプログラムの進捗状態の可視化などを実装します。
思考停止状態でギナギナっと作ったプログラムは次のようなものです。
Warning! This code will make you headache.
You do not have to read it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
package jp.yucchi.badasynchronousprocessing4javafx; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; import javafx.scene.layout.FlowPane; import javafx.stage.Stage; /** * * @author Yucchi */ public class BadAsynchronousProcessing4JavaFX extends Application { private Label statusLabel; private Label messageLabel; private Label interimResult; private Label resultLabel; private ProgressBar progressBar; private Button startButton; private Button cancelButton; private boolean cancelFlag; @Override public void start(Stage primaryStage) { System.out.println(cancelFlag); statusLabel = new Label("STATUS"); statusLabel.setMinWidth(200); messageLabel = new Label("MESSAGE"); messageLabel.setMinWidth(200); interimResult = new Label("INTERIM RESULT"); interimResult.setMinWidth(200); resultLabel = new Label("RESULT"); resultLabel.setMinWidth(200); progressBar = new ProgressBar(0); progressBar.setMinWidth(200); startButton = new Button("START"); startButton.setMinWidth(200); cancelButton = new Button("CANCEL"); cancelButton.setMinWidth(200); cancelButton.setDisable(true); FlowPane root = new FlowPane(); root.setPadding(new Insets(10)); root.setHgap(10); root.setVgap(10); root.getChildren().addAll(statusLabel, messageLabel, interimResult, resultLabel, progressBar, startButton, cancelButton); Scene scene = new Scene(root, 220, 225); primaryStage.setTitle(this.getClass().getSimpleName()); primaryStage.setScene(scene); primaryStage.show(); primaryStage.setOnCloseRequest(we -> { }); startButton.setOnAction(ae -> { resultLabel.setText("RESULT"); executeTask(); }); cancelButton.setOnAction(ae -> { startButton.setDisable(false); cancelButton.setDisable(true); statusLabel.setText("CANCELED"); messageLabel.setText("Cancelled!"); progressBar.setProgress(0.0); interimResult.setText("INTERIM RESULT"); cancelFlag = true; }); } /** * @param args the command line arguments */ public static void main(String[] args) { launch(args); } private void executeTask() { startButton.setDisable(true); cancelButton.setDisable(false); int repeatProcessingNumber = 10; long result = 1; statusLabel.setText("RUNNING"); messageLabel.setText("Start!"); interimResult.setText(String.valueOf(result)); progressBar.setProgress(0); for (int i = 0; i < repeatProcessingNumber; i++) { if (cancelFlag) { System.out.println("This program has been canceled."); break; } try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException ex) { Logger.getLogger(BadAsynchronousProcessing4JavaFX.class.getName()).log(Level.SEVERE, null, ex); if (cancelFlag) { System.out.println("This program has been canceled."); break; } } result += result; messageLabel.setText(String.format("%d/%d", i + 1, repeatProcessingNumber)); interimResult.setText(String.valueOf(result)); progressBar.setProgress(i); } if (!cancelFlag) { statusLabel.setText("SUCCEEDED"); resultLabel.setText(String.valueOf(result)); } else { cancelFlag = false; } startButton.setDisable(false); cancelButton.setDisable(true); } } |
それではこのプログラムを実行してみましょう。
「START」 ボタンを押した瞬間プロブラムがフリーズしたようになりました。
そして暫くすると結果が表示されました。
これは駄目ですね。(>_<。)
プログラムがビジー状態で応答しなくなってしまっては使い物になりません。
進捗状態がわからないのは致命的です。
そして唐突に計算が終了してプログラムが完了する。
そう言えば遠い昔にこのような状態のプログラムが当たり前のように存在していたような気がする。
今の時代こんなのは絶対に許されません。
何がいけないのか?
それは時間のかかる処理を JavaFX アプリケーションスレッド上で行っているからです。
NetBeans のプロファイラで確認してみます。
JavaFX Application Thread タイムラインを見てください。
START ボタンを押した瞬間に時間のかかる処理(スレッドスリープ)が走ってます。(緑色から紫色に変わっているところ)
これでは UI の更新処理などはできませんね。
時間のかかる処理をしている間プログラムの応答は無くなり、ビジー状態のようになってしまいます。
つまり、時間のかかる処理用にスレッドをもう一つ起こせばこの問題は解決するはずです。
この問題を解決するのに最適な方法は javafx.concurrentパッケージを使用することです。
javafx.concurrent パッケージは、Worker インタフェースと、2つの具体的な実装である Task および Service クラスで構成されています。
Worker インタフェースは、バックグラウンドスレッド上の Worker オブジェクトの状態、進捗を監視可能なプロパティで公開しています。
状態は ReadOnlyObjectProperty<Worker.State> stateProperty で確認できます。
次のように Enum Worker.State で定義されています。
- READY Workerがまだ実行されておらず、実行の準備ができているか、またはWorkerが再初期化されたことを示します。
- SCHEDULED Workerの実行がスケジュールされているが、現在は実行中ではないことを示します。
- RUNNING このWorkerが実行中であることを示します。
- SUCCEEDED このWorkerが正常に完了しており、valueプロパティから読み取る準備ができている有効な結果があることを示します。
- CANCELLED このWorkerがWorker.cancel()メソッドによって取り消されたことを示します。
- FAILED 通常は予期しない条件が発生したことによって、このWorkerが失敗したことを示します。
これらは JavaFX アプリケーションスレッドから使用できます。
Worker オブジェクトによって実行される処理の進捗は、totalWork、workDone、progress など、3つの異なるプロパティを通じて取得できます。
Worker オブジェクトの状態が変化するときに発生するイベントは WorkerStateEventクラスによって指定されます。
これは Task クラスと Service クラスの両方に EventTarget インタフェースが実装され状態イベントのリスニングがサポートされているからです。
今回は再利用の必要が無い Worker オブジェクトを生成するので Task クラスを利用します。
上記のように JavaFX の Task クラスは FutureTask クラスの完全に監視可能にした実装となっています。
この Task クラスを拡張しバックグラウンドスレッドで実行するロジックを実装します。
懐かしのSwingWorker クラスを思い出させてくれますね。(もう、ほとんど覚えてないけどね)
SwingWorker では doInBackground() メソッドにてバックグラウンドスレッドロジックを呼び出します。
JavaFX の場合は call() メソッドです。
doInBackground() メソッド、call() メソッドともにどちらも抽象メソッドなのでオーバーライドして使います。
call() メソッドは Task クラスが実行されるときに呼び出され、call() メソッドに実装されたバックグラウンドスレッドロジックを実行します。
つまり、仕組み的には SwingWorker とほとんど変わりはないので馴染みやすいかも知れません。
さて、JavaFX のために用意されたのにこれだけではあまり意味がありません。
この Task クラスの実行にあたり updateProgress() メソッド、updateMessage() メソッド、updateValue() メソッド、updateTitle() メソッドを呼び出すことができます。
これらを使用するとプログラムの状態、進捗状況、実行中の処理の結果の一部を返すなどを可視化できます。
ちょっと updateValue() メソッドの実装をみてみましょう。
|
protected void updateValue(V value) { if (isFxApplicationThread()) { this.value.set(value); } else { // As with the workDone, it might be that the background thread // will update this value quite frequently, and we need // to throttle the updates so as not to completely clobber // the event dispatching system. if (valueUpdate.getAndSet(value) == null) { runLater(() -> Task.this.value.set(valueUpdate.getAndSet(null))); } } } |
呼び出しが JavaFX アプリケーションスレッドかそうでないかで分岐処理をおこなっています。
JavaFX アプリケーションスレッドからだったらそのまま値を更新します。
|
void runLater(Runnable r) { Platform.runLater(r); } |
でなければ runLater()メソッドで javafx.application.Platform クラスの runLater(Runnable runnable) メソッドを呼び出して値を更新します。
何故なら、JavaFX Sceneグラフは、スレッドセーフではなく、JavaFXアプリケーションスレッドのみアクセスおよび変更できます。
これは Swing では javax.swing.SwingUtilities クラスの invokeLater(Runnable doRun) メソッドと同じようなものです。
JDK1.3 からは java.awt.EventQueue.invokeLater() を呼び出すように作られています。
このinvokeLater(Runnable doRun) メソッドはどのスレッドからも呼び出しが可能となっています。
javafx.application.Platform クラスの runLater(Runnable runnable) メソッドも任意のスレッドから呼び出し可能となっています。
updateProgress() メソッド、updateMessage() メソッド、updateValue() メソッド、updateTitle() メソッドの API ドキュメントには「このメソッドは、任意のスレッドから安全に呼び出すことができます。」と記載されています。
ちなみに updateValue() メソッドは JavaFX 8 からなので上記のようにラムダ式を使い綺麗に書かれています。
古くからある updateMessage() メソッドは次のように Java SE 7 の時代のコードのままとなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
protected void updateMessage(String message) { if (isFxApplicationThread()) { this.message.set(message); } else { // As with the workDone, it might be that the background thread // will update this message quite frequently, and we need // to throttle the updates so as not to completely clobber // the event dispatching system. if (messageUpdate.getAndSet(message) == null) { runLater(new Runnable() { @Override public void run() { final String message = messageUpdate.getAndSet(null); Task.this.message.set(message); } }); } } } |
このようなのを目にすると Java は開発者のために進化し続けている言語なんだなぁって感慨深いものがありますね。
SwingWorker のように publish() メソッド、process() メソッドなんてのは JavaFX には必要なくなってます。
このようにありがたい機能を標準で実装されている Task クラスですがさらに便利なプロパティを持っています。
これから先程のプログラムを改善するのにいくつかの Task クラスのプロパティを使用します。
それらプロパティは JavaFX のバインドという機能により快適に使用することが可能です。
JSR 295 Beans Binding と同じようなものです。
実際にどのように利用するかというと Task クラスの実行状態(Worker オブジェクト)のプロパティを利用して「START」ボタン、「CANCEL」ボタンの活性化、非活性化をおこないます。
また、バックグラウンドスレッド(Worker オブジェクト)の状態、計算結果の一部を返す処理、プログレスバーによるプログラムの進捗状態の表示もおこないます。
これらの対応を施したプログラムは次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
package jp.yucchi.asynchronousprocessing4javafx; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Application; import javafx.concurrent.Task; import static javafx.concurrent.Worker.State.RUNNING; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; import javafx.scene.layout.FlowPane; import javafx.stage.Stage; /** * * @author Yucchi */ public class AsynchronousProcessing4JavaFX extends Application { private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private Label statusLabel; private Label messageLabel; private Label interimResult; private Label resultLabel; private ProgressBar progressBar; private Button startButton; private Button cancelButton; @Override public void start(Stage primaryStage) { statusLabel = new Label("STATUS"); statusLabel.setMinWidth(200); messageLabel = new Label("MESSAGE"); messageLabel.setMinWidth(200); interimResult = new Label("INTERIM RESULT"); interimResult.setMinWidth(200); resultLabel = new Label("RESULT"); resultLabel.setMinWidth(200); progressBar = new ProgressBar(0); progressBar.setMinWidth(200); startButton = new Button("START"); startButton.setMinWidth(200); cancelButton = new Button("CANCEL"); cancelButton.setMinWidth(200); cancelButton.setDisable(true); FlowPane root = new FlowPane(); root.setPadding(new Insets(10)); root.setHgap(10); root.setVgap(10); root.getChildren().addAll(statusLabel, messageLabel, interimResult, resultLabel, progressBar, startButton, cancelButton); Scene scene = new Scene(root, 220, 225); primaryStage.setTitle(this.getClass().getSimpleName()); primaryStage.setScene(scene); primaryStage.show(); primaryStage.setOnCloseRequest(we -> { executorService.shutdownNow(); }); startButton.setOnAction(ae -> { resultLabel.setText("RESULT"); executeBackgroundTask(); }); } public static void main(String[] args) { launch(args); } private void executeBackgroundTask() { Task<Long> task = new Task<>() { @Override public Long call() { int repeatProcessingNumber = 10; long result = 1; updateMessage("Start!"); updateValue(result); updateProgress(0, repeatProcessingNumber); for (int i = 0; i < repeatProcessingNumber; i++) { if (isCancelled()) { System.out.println("This program has been canceled."); break; } try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException ex) { Logger.getLogger(AsynchronousProcessing4JavaFX.class.getName()).log(Level.SEVERE, null, ex); if (isCancelled()) { System.out.println("Canceled by InterruptedException."); break; } } result += result; updateMessage(String.format("%d/%d", i + 1, repeatProcessingNumber));; updateValue(result); updateProgress(i + 1, repeatProcessingNumber); } return result; } // Workerオブジェクトの状態が変化したときのコールバックメソッド(5種類) @Override protected void scheduled() { super.succeeded(); updateMessage("scheduled!"); System.out.println("Call scheduled()"); } @Override protected void running() { super.running(); updateMessage("running!"); System.out.println("Call running()"); } @Override protected void succeeded() { super.succeeded(); updateMessage("Succeeded!"); System.out.println("Call succeeded()"); resultLabel.setText("RESULT: " + getValue().toString()); } @Override protected void cancelled() { super.cancelled(); updateMessage("Cancelled!"); System.out.println("Call cancelled()"); progressBar.progressProperty().unbind(); progressBar.setProgress(0.0); interimResult.textProperty().unbind(); interimResult.setText("INTERIM RESULT"); } @Override protected void failed() { super.failed(); updateMessage("Failed!"); System.out.println("Call failed()"); } }; // WorkerStateEven を使ってWorkerオブジェクトの状態を監視 task.setOnScheduled(wse -> System.out.println("setOnScheduled")); task.setOnRunning(wse -> System.out.println("setOnRunning")); task.setOnSucceeded(wse -> System.out.println("setOnSucceeded")); task.setOnCancelled(wse -> System.out.println("setOnCancelled")); task.setOnFailed(wse -> System.out.println("setOnFailed")); // textProperty と Workerオブジェクトの stateProperty をバインド statusLabel.textProperty().bind(task.stateProperty().asString()); // textProperty と Workerオブジェクトの messageProperty をバインド messageLabel.textProperty().bind(task.messageProperty()); // textProperty と Workerオブジェクトの valueProperty をバインド interimResult.textProperty().bind(task.valueProperty().asString()); // progressProperty と Workerオブジェクトの progressProperty をバインド progressBar.progressProperty().bind(task.progressProperty()); // disableProperty と Workerオブジェクトの stateProperty をバインド startButton.disableProperty().bind(task.stateProperty().isEqualTo(RUNNING)); cancelButton.disableProperty().bind(task.stateProperty().isNotEqualTo(RUNNING)); cancelButton.setOnAction(ae -> { task.cancel(); }); // ExecutorService を利用してバックグラウンドスレッドを開始 executorService.submit(task); } } |
このプログラムの実行結果を見てみましょう。
ついでに NetBeans のプロファイラでバックグラウンドスレッドが生成されているのを確認しましょう。
pool-2-thread-1 がバックグラウンドスレッドとして起動されています。
これで JavaFX アプリケーションスレッドはイベント処理が可能となります。
画面が固まることなくバックグラウンドスレッドの状態、計算結果の一部(途中経過)、プログレスバーも表示されています。
また、「START」ボタン、「CANCEL」ボタンの活性化、非活性化もちゃんとできています。
まず、Task クラスをみていきましょう。
今回はcall() メソッドを実行するために java.util.concurrent の ExecutorService インタフェースを利用しました。
184行目です。
executorService.submit(task);
タスクをパラメータとして指定したスレッドの開始としては次の方法もあります。
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
call() メソッドに時間のかかる処理を記述します。バックグラウンドスレッドロジックです。
call() メソッドが呼ばれたら初期値をセットします。
updateMessage() メソッド、updateValue() メソッド、updateProgress() メソッドはプロパティバインドを利用します。
バックグラウンドスレッドが開始されたらキャンセル操作がリクエストされたかも監視しなければいけません。
キャンセルは java.util.concurrent.FutureTask クラスの isCancelled() メソッドを使います。
今回、スレッドをスリープさせているので InterruptedException をスローする場合があります。
また InterruptedException は Task のキャンセルの結果として発生する場合があるため、確実に InterruptedException を処理し、キャンセル状態を調べる必要があります。
それに対応するために 102 行目の catch 節のところにも isCancelled() メソッドを使用しています。
このようにバックグラウンドスレッドロジック内にブロッキング・コールがある場合は注意が必要となります。
バックグラウンドスレッドでの処理の結果の一部を返すために 110 行目に updateValue() メソッドを使います。
同様に 109 行目で messageLabel 、111 行目で progressBar の更新も行っています。
これらの更新は JavaFX のバインドという便利な機能を使います。
Task クラスのプロパティと Label のテキストプロパティ、ProgressBar の プログレスプロパティとバインドします。
168 行目から次のようにバインドしています。
// textProperty と Workerオブジェクトの stateProperty をバインド
statusLabel.textProperty().bind(task.stateProperty().asString());
// textProperty と Workerオブジェクトの messageProperty をバインド
messageLabel.textProperty().bind(task.messageProperty());
// textProperty と Workerオブジェクトの valueProperty をバインド
interimResult.textProperty().bind(task.valueProperty().asString());
// progressProperty と Workerオブジェクトの progressProperty をバインド
progressBar.progressProperty().bind(task.progressProperty());
これで自動的に Task クラス(Workerオブジェクト)のプロパティ updateXXX() メソッドで更新されたら Label 、ProgressBar も更新されます。
もし、キャンセル操作が実行され、プログラムの実行結果の一部が表示されたままになるのが嫌なら
141 行目の Worker オブジェクトの状態が変化したときのコールバックメソッドである cancelled() メソッドの処理でいったん unbind() メソッドによりバインドを解除して初期値を設定し直すといいでしょう。
@Override
protected void cancelled() {
super.cancelled();
updateMessage(“Cancelled!”);
System.out.println(“Call cancelled()”);
progressBar.progressProperty().unbind();
progressBar.setProgress(0.0);
interimResult.textProperty().unbind();
interimResult.setText(“INTERIM RESULT”);
}
このようなコールバックメソッドはこの他にもあり、全部で5種類あります。
必要に応じて便利に使えます。
118 行目から 158 行目を参照ください。
バックグラウンドタスクが無事に終了したら 134 行目の succeeded() メソッドがコールバックされます。
@Override
protected void succeeded() {
super.succeeded();
updateMessage(“Succeeded!”);
System.out.println(“Call succeeded()”);
resultLabel.setText(“RESULT: ” + getValue().toString());
}
ここで Task クラスに戻り値がある場合それを取得します。
戻り値の型はジェネリクスの型パラーメターで指定されたものです。
今回のプログラムでは Long となっています。
必然的に Call() メソッドの戻り値も Long です。
計算結果は 138 行目で javafx.concurrent.Task クラスの getValue() メソッドで取得しています。
resultLabel.setText(“RESULT: ” + getValue().toString());
そして、計算結果を Label のテキストとしてセットしています。
これらのコールバックメソッドは JavaFX アプリケションスレッド上で実行されるので RuntimeException も出ません。
コールバックメソッドを利用する以外にも WorkerStateEvent を使ってイベント処理として扱うこともできます。
160 行目から 165 行目のようにイベントハンドラを登録して使用します。
// WorkerStateEven を使ってWorkerオブジェクトの状態を監視
task.setOnScheduled(wse -> System.out.println(“setOnScheduled”));
task.setOnRunning(wse -> System.out.println(“setOnRunning”));
task.setOnSucceeded(wse -> System.out.println(“setOnSucceeded”));
task.setOnCancelled(wse -> System.out.println(“setOnCancelled”));
task.setOnFailed(wse -> System.out.println(“setOnFailed”));
では、このプログラムを動かしたときの Worker オブジェクトの state プロパティ がどのように変化するか確認します。
プログラムが完する場合次のように出力されます。
setOnScheduled
Call scheduled()
setOnRunning
Call running()
setOnSucceeded
Call succeeded()
この出力結果から
SCHEDULED
RUNNING
SUCCEEDED
と遷移しているのが解ります。
プログラムを途中でキャンセルしてみる場合も確認します。
setOnScheduled
Call scheduled()
setOnRunning
Call running()
setOnCancelled
Call cancelled()
Canceled by InterruptedException.
予想通りの結果ですね。
SCHEDULED
RUNNING
CANCELLED
上記のように遷移しています。これらの動作は想像通りでした。
コールバックメソッドより WorkerStateEvent を使ってイベントハンドラを登録したほうが早い結果となっています。
だからといってメリット、デメリットがあるかどうかは私には解りません。
さて、最後に、「START」ボタン、「CANCEL」ボタンの活性化、非活性化をみてみましょう。
175 行目から 177 行目です。
// disableProperty と Workerオブジェクトの stateProperty をバインド
startButton.disableProperty().bind(task.stateProperty().isEqualTo(RUNNING));
cancelButton.disableProperty().bind(task.stateProperty().isNotEqualTo(RUNNING));
Button の disableProperty と Workerオブジェクトの stateProperty をバインドしているだけです。
startButton は Workerオブジェクトの stateProperty が RUNNING なら非活性化となります。
cancelButton は Workerオブジェクトの stateProperty が RUNNING でなければ非活性化となります。
つまり、Workerオブジェクトの stateProperty が RUNNING の場合だけ活性化します。
これでいちいち操作を行うたびに Button を活性化、非活性化処理を記述しなくてすみます。
このように JavaFX には非同期処理を簡単に扱うことができるようになっています。
SwingWorker を使って非同期処理プログラムを組んでいるのなら JavaFX の javafx.concurrentパッケージを使ってみて時代の流れを感じ取ってみてはいかがでしょうか。
お終い!
I wish you a Merry Christmas.
TAGS: Java,JavaFX,NetBeans |
2017年12月6日2:28 AM |
Java
Minimal Value Types April 2017: Minimal Edition (v. 0.4) をほとんど機械翻訳してみた。
Minimal Value Types
April 2017: Minimal Edition (v. 0.4)
John Rose, Brian Goetz
“What we do in the shadows reveals our true values.”
Background
value typesのfirst public proposal [values] から3年後、Javaコンパイラ、クラス・ファイル・フォーマット、VMで活発なプロトタイピングが行われましたvigorous discussions [valhalla-dev]。
目標は、効率的なジェネリックオブジェクト指向プログラミングをサポートする共通プラットフォームで、プリミティブ、参照、およびvaluesを統一することでした。
議論の多くは、JavaとJVMで完全なパラメトリック多形性を実装する方法として、generic specialization [goetz-jvmls15]に集中しています。
それは、プリミティブが参照と十分に整列できないすべての方法を公開し、バイトコードモデルを拡張するように強制するため、意図的で実り多いものでした。
List <int>を解決すると、List <Complex <int >>を簡単に管理できます。
他の議論では、バリューセマンティクス[valsem-0411]の詳細と、valuesを扱う[simms-vbcs]の新しいバイトコードを実装する具体的な方法に集中しています。
いくつかの実験では、ベクトル化ループ[graves-jvmls16]のような有用なタスクを実行するために、valuesのようなAPIを採用しています。
最近では、JVM Language Summit(2016)、そしてその週のValhalla EGミーティングで、vector、Panama、GPU実験に適したvalue typesのアーリーアクセスバージョンが繰り返されています。
このドキュメントでは、早期採用者に適した、JVMの実験value typeサポートのサブセット(およびより小さい程度、言語とライブラリ)の概要を説明します。
そして、私たちはここ数ヶ月でこれらのアイディアの多くのプロトタイプを作っていました。
振り返ってみると、この複雑な未来をマッピングするために何千人ものエンジニア時間が費やされていると推測することは合理的です。
今、このビジョンを捉えて、最初のバージョン、つまりvalue typesのための「hello world」システムの一種を選ぶ時が来ました。
本文書は、以下の目標をもって、value-type機能の最小化された実行可能なサブセットを提案する:
- HotSpot JVM(参照実装)に実装するのは簡単です。
- Java言語またはVMの今後の展開を制約しない
- 初期の実験やプロトタイプ作成にパワーユーザーが使用可能
- JVMクラスファイル形式の最小変更
- このような変更の使用は、実験的な領域でのみファイアウォールを使って防御されることができます
- ユーザーは、標準的なツールチェーンを使用してvalue-using codeを開発することができます
私たちの非目標は、目標を補完するものです:
- value typesの既知のすべての言語構造をサポートしていない
- Java言語の構文やバイトコードの設計にコミットしない
- Javaコードでvalue typesをコード化するJavaプログラマをサポートしていない
- 最終的なバイトコード形式を提案しない
- 一般的な使用のために配備されません(当初ではなく、おそらく決してそうではありません)
- 開発者ツールチェーンの垂直統合アップグレードを必要としない
言い換えれば、私たちのvaluesを一日の光に向けて公開する前に、肘掛けとスペックの間の日陰の領域で試作します。
このようなプロトタイプは、限られていますが、無用ではありません。
それは、value typesの設計と実装に対する様々なアプローチを試すことができます。
必要に応じてアプローチを破棄することもできます。
パワーユーザー(多くの場合、設計者や実装者と密接に協力する)がさまざまな初期使用事例を実行するため、パフォーマンスと可用性の見積もりを改善することもできます。
Features
value typesの最小限の(しかし実行可能な)サポートの具体的な特徴は、以下のように要約できます。
- VMが関連付けられたderived value typesを作成するための、いくつかのvalue-capable classes(Int128など)。
- クラスファイルの新しいvalue typesを記述するDescriptor の構文( “Q-types”)。
- 定数プールのconstantsが拡張され、これらの記述子と相互運用できます。
- JVMのローカルとスタック間でvalue typesを移動するためのbytecode instructions(vloadなど)の小さなセット。
- value typesのreflectionが制限されています(int.classに似ています)。
- Boxingとアンボクシング、Javaの汎用オブジェクト型の観点からvalues(プリミティブのような)を表現する。
- メソッドは、value operations(メンバーアクセスなど)へのアクセスを提供するfactories を処理します
value-capable classesのあるクラスは、標準のPOJOクラスとして今日のツールチェーンで開発することができます。
この使用モードでは、標準のJavaソースコード(ジェネリッククラスおよびメソッドを含む)は、ボックス化された形式でのみvaluesを参照できます。
ただし、メソッドハンドルと特別に生成されたバイトコードの両方は、ネイティブのアンボックス形式でvaluesを処理できます。
この仕事はJVMに関連するものであり、言語に関連するものではありません。 したがって、非目標は次のとおりです。
- Javaコードから直接value typesを定義または使用するための構文。
- ボックス化されていないvalues(またはプリミティブ)を格納または処理できるJavaコードのスペシャライズドジェネリックス。
- ライブラリのvalue typesまたはjava.util.Optionalのようなvalue-based classesの進化したバージョン。
- 任意のモジュールからのvalue typesへのアクセス。 (通常、value-capable classesはエクスポートされません)。
value typesの全体的なビジョンをキャプチャするスローガン「クラスのようなコード、intのように動作します」が与えられた場合、この最小セットは「ボックスやハンドルでキャッチすることができればintのような働き」のようなものを提供します 。
この作業の範囲を限定することにより、value-typeスタック全体を一度にすべて提供するよりはるかに早く、実動的な実験を本番JVMで有効にできると考えられます。
新しいJVMレベルの機能のサポートにより、新しい言語機能とその機能を直接使用できるツールのプロトタイピングが即座に可能になります。
しかし、この最小限のプロジェクトは、そのような言語機能やツールに依存しません。
このドキュメントの残りの部分は、提案された機能に詳しく説明されています。
Value-capable classes
クラスには@DeriveValueType(または多分属性)という特別なアノテーションが付けられます。
このマーキングを持つクラスは、value-capable class(または略してVCC)と呼ばれ、関連するderived value type(またはDVT)がクラスの型を超えて付与されることを意味します。
このアノテーションの使用は、いくつかの方法で制限おそらくコマンドラインオプションによってロック解除、及びincubator module [JEP-11]のある種に関連付けられます
Example:
@jvm.internal.value.DeriveValueType
public final class DoubleComplex {
public final double re, im;
private DoubleComplex(double re, double im) {
this.re = re; this.im = im;
}
... // toString/equals/hashCode, accessors, math functions, etc.
}
マークされたクラスのセマンティクスは、アノテーションが存在しない場合と同じになります。
しかし、アノテーションによって、マークされたvalue-capable classを関連するderived value typeのソースとして考慮することができるようになります。
value-capable classのスーパークラスはObjectでなければなりません。 (これは、スーパークラスが許可されていない完全な提案に似ています。)
value-capableとしてマークされたクラスは、そのインスタンスが関連するvalue typeのvaluesのボックスとして機能するため、value-basedとして修飾する必要があります。
特に、クラスとそのすべてのフィールドはfinalとマークされ、コンストラクタはprivateでなければなりません。
value-capableとしてマークされたクラスは、valueのボックス化されたバージョンで不確定な結果を生成するため、それ自体の任意のインスタンスのObjectで提供されるメソッドを使用してはなりません。
equalsメソッド、hashCodeメソッド、toStringメソッドは完全に置き換える必要があります。
例外として、getClassメソッドは自由に使用できます。
それはvalue-capableなクラスで定数返されたメソッドによって置き換えられたかのように動作します。
すべてのvalue-basedクラスと同様に、他のオブジェクトメソッド(clone、finalize、wait、notify、およびnotifyAll)は、value-capableクラスで使用しないでください。
(これはユーザーに手動で強制するためのものです。完全な提案では、自動的に強制する方法があります)。
要約すると、JVMは、value capable classで次の構造チェックを行います。
- クラスはfinalとマークする必要があります。
- クラスは適切なクラスです(インターフェイスではありません)。
- スーパークラスはObjectでなければなりません。
- 非静的フィールドはすべてfinalでなければなりません。
- Objectメソッドequals、hashCode、およびtoStringをオーバーライドします。
- Objectメソッドcloneまたはfinalizeをオーバーライドしません。
これらの構造チェックは、JVMがVCCからDVTを取得したときに実行されます。
その導出の位相調整については後述する。
上記の制限とは別に、value-capable classは、コンストラクタ、メソッド、フィールド、ネストされた型の定義、インタフェースの実装、型変数の定義など、通常の値ベースのクラスが行うこともできます。
フィールドの種類には特に制限はありません。
ここからわかるように、derived value type の型にはフィールドのみが含まれます。
これは、それが導出されたvalue-capable classと同じセットのフィールドを含みます。
しかし、JVMは、メソッド、コンストラクタ、ネストされた型、またはスーパー型を提供しません。
(完全な提案では、もちろん、value typesは「クラスのようにコード化」され、それらの機能のすべてをサポートします。
標準のjavacコンパイラを使用してコンパイルされたvalue-capable class は、value typesである “inline sub-value”フィールドを表現できないことに注意してください。
できるだけ多くのことができるのは、関連する参照型( “L-types”)の要求フィールドです。
javacのアップグレードされたバージョンは、本当の “Q-types”のsub-valueフィールドを定義することができます。
このようなバージョンのjavacを使うと、プログラマはvalue-capable classesから別の派生型を派生させるという手間を省き、value typesを直接扱うことができます。
したがって、derived value typeのフィールドがvalue-capable classとして入力されている場合は、意図したインラインsub-valueの意図しないボクシングのようなエラーである可能性があります。
ここでは、「super-long」derived value typeを定義するvalue-capable classのより大きな例を示します。
@DeriveValueType
final class Int128 extends Comparable<Int128> {
private final long x0, x1;
private Int128(long x0, long x1) { ... }
public static Int128 zero() { ... }
public static Int128 from(int x) { ... }
public static Int128 from(long x) { ... }
public static Int128 from(long hi, long lo) { ... }
public static long high(Int128 i) { ... }
public static long low(Int128 i) { ... }
// possibly array input/output methods
public static boolean equals(Int128 a, Int128 b) { ... }
public static int hashCode(Int128 a) { ... }
public static String toString(Int128 a) { ... }
public static Int128 plus(Int128 a, Int128 b) { ... }
public static Int128 minus(Int128 a, Int128 b) { ... }
// more arithmetic ops, bit-shift ops
public int compareTo(Int128 i) { ... }
public boolean equals(Int128 i) { ... }
public int hashCode() { ... }
public boolean equals(Object x) { ... }
public String toString() { ... }
}
Similar types [Long2.java]がループベクトル化プロトタイプで使用されています。
この例は、java.langパッケージのプロトタイプバージョンで定義されています。
しかし、この最小限の提案の一部として定義されたvalue-capable types は、どの標準APIにも現れません。
それらの可視性は、インキュベータモジュールなどのモジュールシステムの機能を使用して制御される可能性が高い。
Initial value-capable classesは、longなどの数値型の拡張である可能性があります。
そのため、標準的で一貫した算術演算とビット演算が必要です。
現時点では、このようなセットは体系化されていません。
そのセットを作成することは、最小セットの範囲を超えています。
最終的には、数値プリミティブと数値の間の共通の操作構造をキャプチャする一連のインタフェースを作成する必要があります。
Splitting the value type from the object type
JVMがvalue-capable classをロードするときには、derived value typeを派生させるか、クラスにフラグを設定し、必要に応じてderived value typeを作成するように調整できます。
(後者が推奨されます)。
(注意:最小限の提案では、この順序は未定義になる可能性があります。
完全なバージョンのvalue typesでは、derived value typeとvalue-capable classが同一であるため、疑問がありません。
value-capable class自体はロード時にまったく変更されません。 それは、value-based classのための普通の “POJO”のままです。
対応するderived value typeは、value-capable classのコピーとして作成されますが、これらの重要な違いがあります。
- derived value typeは、value-typeとしてマークされます。
- derived value typeには、value-capable classから派生した新しい名前が与えられます。
- value-capable classのすべてのスーパータイプが削除されます。
- value-capable classのすべてのメソッドとコンストラクタが削除されます。
- value-capable class の非静的フィールドは、そのまま保持されます。
DVTに与えられた名前は実装によって隠されています。
すべてのケースで、VCCの名前は両方のタイプを参照するために使用され、不明確さを解決するのに十分なコンテキスト情報が常に存在します。
バイトコード記述子では、文字QとLを区別するために使用するので、VCCをQ-typeと呼び、DVTをL-typeと呼びます。
DVTの作成は、VCCをロードした後、DVTのインスタンスを最初に作成する前のある時点で実行する必要があります。
これは、現在のいくつかの命令(getstaticやnewなど)が通常のクラスの初期化をトリガするのとほぼ同じように、DVTの初期化をトリガする特定の命令のセマンティクスで強制されます。
詳細は後述する。
DoubleComplexの例でもう一度やり直してください。
@jvm.internal.value.DeriveValueType
public final class DoubleComplex {
public final double re, im;
...
double realPart() { return re; }
}
JVMがDoubleComplexのderived value typeを合成することを決定すると、2つのdouble
フィールドを除くすべてのクラスメンバを取り除いて新しいコピーが作成されます。
重大なことに、JVMは内部の魔法を使って合成クラスをオブジェクト型ではなくvalue-typeにします。
JVMの内部では、結果のderived value typeは次のようになります。
@jvm.internal.value.DeriveValueType
public final class L-DoubleComplex {
public final double re, im;
...
double realPart() { return $value.re; }
}
public static __ByValue class Q-DoubleComplex {
public final double re, im;
}
仮想的な __ByValue
キーワードは、参照の代わりにvaluesが定義される場所を示します。
多くの作業がスタックの上下に行われるまで、そのようなことはソースコードで直接指定することはできませんが、クラスロード時に実行するのは完全に妥当で有用です。
derived value typeはコンストラクタを持たないことに注意してください。
オブジェクトクラスは少なくとも1つのコンストラクタを持つためにJVMによって必要とされるので、通常はこれが問題になります。
JVMは、derived value typeの場合にこれを許可します。 (そのような制約は、一般的に値型では必要ではありませんが、そのストーリーはここでは知るには時間がかかりすぎます)。
いずれの場合でも、derive value typeは、value-capable classのコンストラクタを「借用」します。 次のセクション。
このデザインは、JVMがボックスタイプのみをロードし、何らかの形で副作用としてvalue-typeを作成するという点で、「ボックスファースト」と呼ばれることがあります。
より自然な “value-first”の設計に終わりますが、現在のボックスファーストデザインは、JVMやjavacを含むクラスファイルを読み書きするツールにはほとんど制約がありません。
だから最初は厄介なことが正しい選択です。
Boxing, unboxing, and borrowing
JVMは、ボリューミング可能クラスとそのvalue-capable classの間で変換するボクシングおよびアンボクシング操作を内部的に整理します。
これらの操作のセマンティクスは、2つのタイプ間の単純なフィールドワイズコピーです。
これは明らかにフィールドリストが同一であるため明確に定義されています
合成unbox操作により、derived value typeは、value-capable classのコンストラクタを間接的に使用することができます。
プログラマは、コンストラクタを使用してボックスを作成し、それをunboxして希望する構築値を取得できます。
JVMはフィールドをボックスの外にコピーし、ボックスを破棄します。
(完全な提案では、value typesは実際のコンストラクタを持ち、ボックスからそれらを借りる必要はありません)。
合成ボックス操作では、derived value typeがvalue-capable classのメソッドを間接的に使用することができます。
プログラマはvalueを一時的にボックスに入れ、メソッドが復帰するとボックスを破棄して、value-capable classのメソッドのいずれかを呼び出すことができます。
ボックスの寿命は短いので、少なくとも単純な方法では、JVMがそれを最適化できる可能性があります。
(完全な提案では、value typesは実際のメソッドを持ち、ボックスから借用する必要はありません。代わりに、valuesからそのメソッドを借りることができます)。
合成ボックス操作では、コンストラクタを実行せずにvalue-capable class の新しいインスタンスが作成されます。
通常、これは問題ですが、この場合、2つのクラスは非常に密接にリンクされているため、正しく構築されたボックスをアンボックスすることによって(最初は)任意のvalueが作成されたと見なすことは安全です。
したがって、コンストラクタは任意の特定のvalueに対して最初の単語を取得します。
アンボクシングとボクシングのパターンは、シリアライゼーションとデシリアライゼーションのパターンに似ています。
両方のパターンで、第2の操作は、通常のオブジェクト構築をバイパスします。
合成ボックス操作はまた、derived value typeがvalue-capable classのインタフェースを間接的に使用することを可能にする。
ここでも、derived value typeをインタフェースが必要などこかに渡す必要がある場合、プログラマは単にそれを囲み、ボックスへの参照を渡すことができます。
(完全な提案では、目に見えるボクシングがなくても、インターフェイス型を介してvaluesを操作する方法を提供するつもりです。
valuesとインターフェイスの直接の関係を定義するためには慎重な作業が必要です。
最後に、静的メソッドと静的フィールドはderived value typeにコピーされないので、プログラマーは元のvalue-capable classであるボックスからのみアクセスできます。
Scoping of these features
実験的なリリースを提供できることの重要な部分は、機能を実験的なものとしてマークし、変更の対象にすることです。
この文書で表現されているアイデアはかなりうまく焼き付けられていますが、実験的なリリースと完全なValhallaリリースの間で変更される可能性は完全に予見されます。
JVMの単一バージョン内では、実験的機能はJVMの初期モジュール層にロードされたクラスまたはコマンドラインオプションによって選択されたモジュールに制限され、その他の点では無視されます。
これらのモジュールは、value-capable modulesと呼ばれます。
さらに、クラスファイル形式の機能は、53.1などの特定のメジャーバージョンおよびマイナーバージョンのクラスファイルでのみ有効にすることができます。
その場合、JVMクラスローダは、そのバージョンのクラスがバリュー可能なモジュールにのみロードされていることを確認し、ここで提案されている実験的な拡張機能の検証およびロード時にはバージョン番号のみを参照します。
一部のマイナーバージョンは実験的な機能のみに使用され、実稼働仕様には現れません。
このプロトタイプの任意の機能の任意の部分の使用は、value-capable moduleのクラスから発生していなければなりません。
JVMは、non-value-capable modulesからの試行を自由に検出して拒否することができます。
@DeriveValueTypeのようなアノテーションは暗黙のうちに無視されることがあります。
しかし、この仕様のプロトタイプの実装では、そのような使用法のチェックが省略され、動作する(または少なくとも適切なエラーをスローしない)ようです。
そのような非拒絶はバグであり、招待ではありません。
Value descriptors
value-capable modulesでは、クラスファイル記述子言語が、ボックス化されていないvalue typesを直接示すQ-typesを含むように拡張されています。
記述子の構文は “QBinaryName;”です。
ここで、BinaryNameはVCC名の内部形式です。 実際には、クラス名は、value-capable classから派生したvalue typeのものでなければなりません。
比較すると、標準の参照型記述子はL-typeと呼ばれます。 value-capable classCについては、Q-typeとL-typeの両方のC言語について言及することがある。
L-typesの使用は、Q-typesの使用と決して関連しないことに留意されたい。
たとえば、それらは、メソッドタイプ、任意の混合物で一緒に表示できます。
Q-type記述子は、value-capable moduleに定義されたクラスフィールドの型として現れます。
しかし、同じ記述子は、その参照が4つのgetfieldファミリの命令のうちの1つによって使用されるときに、そのフィールドのフィールド参照(CONSTANT_Fieldref)に表示されない可能性があります(value-capable moduleであっても)。
(以下に説明するメソッドハンドルファクトリは、valuesとオブジェクトの両方でフィールドロードと更新をサポートしますが、この提案では、フィールド命令自体は変更されていません)。
Q-type記述子は、value-capable moduleのクラスの配列要素型として現れることがあります。
(これはまた、value-capable moduleと、おそらく特定の実験的なクラスファイルバージョンの中にあります。この制限はブランケットステートメントとして既に設定されているので、これを繰り返さないようにしましょう)
これらの配列を読み書きすることはできますが、プロトタイプではこれらの関数でメソッドハンドルを使用できます。
Q-typeのフィールドまたは配列は、nullではなくそのvalue typeのデフォルト値に初期化されます。
このデフォルト値は、フィールドがすべてデフォルト値であるすべての値として(少なくとも現在は)定義されています。
そのようなデフォルトは、 MethodHandles.empty
コンビネータなどの適切なメソッドハンドルから取得できます。
つまり、デフォルト値は、既存の型固有のデフォルト値null、false、¥0,0、および0.0を組み合わせて構築されます。
すべてのJavaヒープ変数は、valuesを含むこれらのゼロデータに初期化されます。 そうでない、または少なくとも将来的には)
Q-type記述子は、クラスファイルで定義されたメソッドのパラメータまたは戻り型として現れることがあります。
後述するように、検証者は、そのようなパラメータまたは戻り値の対応する積み重ねられたvalueをQ-type(対応するL-typeまたは他のタイプではない)に一致させるように強制する。
どのメソッド参照(定数タグ付き CONSTANT_Methodref
または CONSTANT_InterfaceMethodref
)は、その記述子のQ-typesを記述することがあります。
このような定数の解決後、そのようなメソッドの定義はネイティブではない可能性があり、新しいバイトコードを使用してQ-typed valuesを直接使用する必要があります。
同様に、 CONSTANT_Fieldref
定数は、その記述子にQ-typeを記述することができます。
Java言語は、クラスファイルのQ-typesに直接言及する方法を提供していないことに注意してください。
しかしながら、バイトコード生成器はそのような型を言及してそれらと協働するかもしれない。
Valhallaプロジェクトでは、ソースコードがQ-typesで動作するように実験的な言語機能が作成される可能性もあります。
Constant pool and instruction linkage
私たちのvalue typesは参照型のような名前とメンバを持ちますが、すべての参照型とは異なるので、いくつかの定数プール構造を拡張してQ-typesと相互運用する必要があります。
当然のことながら、記述子の構文を拡張した結果、メソッドとフィールドの記述子はQ-typesを記述することができます。 これを行うには、定数プールのフォーマットを変更する必要はありません。
しかし、定数プールの型の中には、通常の記述子エンベロープ文字(Lの前と後ろ)のない「生の」クラス名があるものもあります。
具体的には、CONSTANT_Class
定数は、ロードされたクラスファイルに(決定の後で)直接リンクするので、それに関係するミラーは明らかにそのクラスの主要なミラーです。
セカンダリミラーについて言及する必要がある場合に行うクラスファイルとは何ですか?
ldc
または ldc_w
バイトコードとともに使用される場合、またはブートストラップ・メソッドの静的引数として、エスケープされたディスクリプタで始まる CONSTANT_Class
が解決されたクラスのプリンシパル Class
ミラーに解決されます。
セカンダリミラーは、必要に応じて、一般的な CONSTANT_Dynamic
fフォーム(別の提案JDK-8177279)を使用して作成できます。
CONSTANT_Methodref
または CONSTANT_Fieldref
定数のクラスコンポーネントとして使用する場合、CONSTANT_Class
は常に特定の「ビュー」に向かって「傾斜」することなくクラスファイル自体を表します。
フィールド参照またはメソッド参照を使用するバイトコードは、レシーバーがQ-typeかL-typeかを決定します。
バイトコードのこの「傾き」はモードとして記述することができるので、標準 getfield
命令はL-mode命令であり、vgetfield
命令はQ-mode命令である。
(注:この「不可知論的」フィールドとメソッド参照の設計は、JVMがCONSTANT_Fieldrefがgetfieldとvgetfieldの両方の命令を提供するために定数プールエントリに十分なリソースをキャッシュしたいと考えていることを意味します。
同じクラスのボックス化された L-valuesとバッファリングされたQ-valuesのメモリレイアウト。
メソッド参照にも同様の点があります。
この調整は、そのストレージ規律とID管理を除き、相互に内部的に類似したQ-typeとL-type valuesを作り、JVMの実装に深い影響力を持つことができます。
特にもしU-typesが将来ある時点で構想に加えられているなら、これは良いことであるように思われます。
MethodHandles.Lookup
APIの場合、Q-typeとL-type(したがって呼び出しモード)の区別は、Lookup.findGetter
などのAPI呼び出しの最初の引数として渡される Class
ミラーによって実行されます。
findGetter
がQ-typeのセカンダリーミラーを渡すと、バイナリー・ミラーはby-valueのレシーバーからフィールド・ゲッターを返します。
それがプライマリミラー(L-type)を渡された場合、当然のことながら、参照オブジェクトレシーバーのフィールドゲッターを今日のように返します。
Restrictions on Q-mode method calls
Q-modeメソッド呼び出し(Q-typeレシーバを使用)を実行する場合、java.lang.Object
のメソッドはどれも現れません。
JVMまたはメソッド・ハンドル・ランタイムは、これを実施するために特別なフィルタリング・ロジックを必要とすることがあります。
言い替えれば、Q-typesは Object
から継承しません。
代わりに、value-based classesのルールと同様のObjectメソッドを置き換える独自のメソッドを定義するか、Object
メソッドを完全に回避します。
例外として、Object.getClass
メソッドが許可されている可能性がありますが、VCCに対応するprincpal Class
ミラーを返す必要があります。
ここでの理論は、オブジェクトの型を定義するためにロードされたファイルのクラスミラーをgetClassが報告するということです。
この理論は変わるかもしれない。
これらの制限は、Q-typesから得られたメソッドハンドル、および vinvoke
命令(サポートされている場合)に適用されます。
JVM changes to support Q-types
Q-typesは、他の型記述子型と同様に、多くの場所で言及することができます。 基本的なリストは次のとおりです。
- メソッドとフィールドの定義(
method_info
構造体と field_info
構造体のUTF8参照)
- メソッドとフィールドのシンボリックリファレンス(
CONSTANT_NameAndType
のUTF8コンポーネント)
- 型名(
CONSTANT_Class
定数のUTF8参照)
- 配列のコンポーネント型(任意の記述子の左括弧
[
の後)
- ベリファイアスタックマップのタイプ(Q-typesの新しいアイテムコード経由)
- いくつかのバイトコードのオペランド(
CONSTANT_Class
)(後述)
JVMは、多くの実行パスのプロトタイプを単純化するために、Q-typesの不可視ボクシングを使用することがあります。
これはもちろん、ヒープ内のデータのフラット化というvaluesのキー値の提案に反するものです。
最小限のモデルでは、配列要素とオブジェクト(または値)フィールドのQ-typesの特別な処理、少なくともそのようなフィールドをQ-typesのデフォルト値に初期化するのに十分な特別な処理が必要です(既定値ではない) L-typeの null
。
したがって、クラスローダーがフィールドがQ-typesであるオブジェクトをロードするときは、それらのQ-typesのフィールドを含んでいる新しいクラスを設計するためにそれらのQ-typeのクラスとQ-type定義について十分問合せ情報を確認してロードしなくてはなりません。
この情報は、少なくとも型のサイズを含み、最終的にはQ-typeに含まれる整列された参照および管理された参照のマップを含むことができる。
value typeにプリミティブフィールドのみが含まれている場合は、配列内のvaluesを完全に平坦化して、value typeの配列をサポートする必要があります。
(この提案は、要素がエンドツーエンドに配置されたvalue構造である、いわゆる “フラットアレイ”をサポートします)この提案の最小化された形式では、フラット化されたvalue-bearing配列の一部またはすべてのタイプのサポート を省略することができます。
たとえば、value types のフィールドにプリミティブ、参照、またはsub-valuesの組み合わせが含まれていても、そのような混合値を含む配列は、プリミティブフィールドのみの値を含む配列よりも実装するのが難しい場合があります
このような配列は初期の実装から除かれるかもしれません。
そのような配列を作成するAPIポイントは、一時的に、返す代わりにエラーをスローすることができます。
フラット化された配列は、サポートされている場合、Q-typeのコンポーネントタイプで作成する必要があります。
対応するL-typesの配列と Integer[].class
とちょうど異なっています。
int[].class
と異なります。
同様に、value-bearing配列のスーパータイプは、Objectのみであり、異なる配列タイプではありません(int[]
のように)。
このような配列は、他の配列型を変換することはなく、明示的に取得されたメソッドハンドルによって操作する必要があります。
今日のJVMでは、newを使用してクラスを最初にインスタンス化すると、その初期化がトリガーされます(getstatic
などの別の命令で既に発生している場合を除く)。
初期化は、スーパークラスの初期化を再帰的にトリガする。
これは、クラスの初期ロードがスーパークラスのロードをトリガするのと同様である。
value typeがクラスファイルに埋め込まれると、スーパータイプと同じように(クラスロードと初期化のフェーズに関して)動作します。
特に、オブジェクト内のQ-typeフィールドは、囲みオブジェクトの存在の最初の瞬間からのQ-typeの有効な(デフォルトの)valueであるため、Q-typeフィールドのクラスは、 囲みオブジェクトが割り当てられます。
この要件は、同じ現象のインスタンスとして扱うことができるため、スーパークラスの初期化要件と同じくらい似ています。
クラスが初期化(ロード)されると、その依存関係を最初に初期化(ロード)しなければなりません。
スーパークラスと、サブクラスレイアウトに埋め込まれた任意のvaluesのクラスの両方を含みます。
(別の方法では、スーパークラスがサブクラスの大きな匿名のvalue-typeフィールドを占有しているかのように見えます)
インラインフィールドのクラスのタイムリーな初期化を保証することによって、クラスメソッドがクラスに対して動作できない不変条件を適用できます
クラスの初期化がトリガーされるまで、インスタンス(純粋なvaluesまたはオブジェクト)を返します。
この最小限の提案では、最初のvalueが作成される前にDVTを初期化する必要があります。
つまり、このようなvalue,を含むオブジェクトまたは配列が作成されるか、またはvdefaultまたはvunbox命令が完了します。
DVTはVCCに依存するため、DVTの初期化はVCCの初期化をトリガする必要があります。
入力が実際にはVCCインスタンスであるため、vunbox命令は実際にはこれらのルールを緩和する可能性があるので、DVTを抽出して、コードを含むことができないため、初期化がノーオペレーションであることを確認するだけです。
Value bytecodes
次の新しいバイトコード命令が追加されました。
vload
はローカルからスタックに値(Q-type)をプッシュします。
vstore
は、スタックからローカルに値(Q-type)をポップします。
vreturn
は、スタックから値(Q-type)をポップし、現在のメソッドから戻します。
vbox
と vunbox
は、対応するQ-typesと L-typesの間で変換します
vaload
と vastore
がQ-typesの「フラットな」配列の要素にアクセスします。
vdefault
は、特定のQ-type の固有のデフォルトvalueをスタックにプッシュします
vgetfield
はQ-typeをポップし、Q-typeから選択されたフィールドをプッシュします
vwithfield
は、Q-typeと選択されたフィールド値をポップし、更新されたQ-typeをプッシュします
Valuesは、long
および double
(ローカルペアを使用する)のようにローカルのグループではなく、単一のローカルに格納されます。
これらの命令のフォーマットは未定です。
それらのうちのいくつかは、操作されるvalueのタイプを記述するオペランドフィールドを含まなければならない。
フィールド操作命令では、CONSTANT_Fieldref
が必要です。
確かに、vbox
, vunbox
、および vdefault
には明示的な型のオペランドフィールドが必要です。
JVMはQ-typeのレゾリューションを使用して、Q-typeのサイズと配置要件に関する情報を取得し、インタプリタスタックフレームに適切に「パック」することができます。
または、JVMは単にボックス化またはバッファリングされた表現(対応する値対応L型、または内部ヒープまたはスタック型)を使用し、サイジング情報を無視することができます。
データ移動命令の型オペランドを省略することができるようです。
JVMインタプリタは、スタック上のすべてのvalue typesに対して内部的に統一された「carrier type」を使用する必要があることがわかっている場合、このcarrier typeを自己記述型にする必要があるだけで、正確なvalue typeを再確認する必要はありません データ移動命令で使用します。
invokevirtual
, invokespecial
、および invokeinterface
命令はQ-mode命令であるため、Q-valuesのメソッドを呼び出すことはできません。
メソッドハンドル、invokestatic
と invokedynamic
は、バイトコードが常に Q-typesのメソッドを呼び出すことを許可します。
これは、開始には十分です。
このようなメソッドハンドルは、実際にはQ-typeを内部的に囲み、対応するL-typeメソッドを実行するかもしれませんが、インタプリタへの普及を避けるために、Javaサポートライブラリで改善および最適化できる方法です。
(注:完全な提案にはボクシングなしでQ-typesのメソッドを呼び出すQ-modeの呼び出し命令、vinvokeがあると確信しています。
U-typesの場合、U-mode uinvoke命令は、Q-values またはL-valuesのいずれかであり得る動的タグ付きreceiver valuesに対しても同様に動作する。
この余分なモードは浪費のように見えるかもしれませんが、純粋なvaluesとオブジェクト参照の両方に及ぶ可能性のあるインタフェースまたは型変数を使用する場合には、一般的な変数のアルゴリズムを処理する必要があります。 )
Verifier interactions
メソッドの入力状態を設定するとき、メソッドの引数記述子にQ-typeが現れた場合、検証者はQ-type(L-typeではない!)が対応するローカルのエントリに存在することに注意します。
メソッドから戻るとき、メソッドの戻り値の型がQ-typeの場合、同じQ-typeがスタックの最上部に存在しなければなりません。
呼び出しを(任意のモードで)実行するとき、スタックはメソッド参照の引数記述子の任意のQ-typesに対応する位置に一致するQ-typesを含まなければなりません。
呼び出しの後、戻り値の型記述子がQ-typeの場合、スタックはそのQ-typeを先頭に含むことがわかります。
int
型とfloat
型のプリミティブ型の場合と同様に、Q-typeは他の検証型に変換されません。
あるいは、検証上位型 oneWord
または top
検証型に変換されません。
これは、メソッド呼び出しとコントロールフローマージポイントでのvaluesのマッチングに影響します。
Q-typesはL-typesに変換されません。
ボックスやそのL-typesのスーパータイプ(Object
、インタフェース)は変換されません。
vload
, vstore
, vreturn
、および invoke
ファミリの他に、Q-typeオペランドを生成または消費することが保証される唯一のバイトコードは、pop
, pop2
、swap、および dup
ファミリです。
より多くのバイトコードが時間とともに追加される可能性があります。
検証者は、Q-typesの適切な処理を適用します。
vaload
命令と vastore
命令は、既存の配列命令のように機能します。
統一されたキャリアタイプが与えられているので、彼らが動作するQ-typeを再確認する必要はありません。
その型は常に配列自体から抽出できます。
vgetfield
命令には、既存の getfield
命令と同様のアクセス制御があります。
フィールドがあるvalue typeでpublicである場合、どのクラスもその型の値からそのフィールドを読み取ることができます。
しかし、vwithfield
命令は、フィールドのアクセスに関係なく、厳密なアクセス制御を持っています。
value typeへのプライベートアクセスを持つクラスのみが、フィールド置換を実行できます。
この制限は、フィールドを定義するクラスでのみ、実際にはそのクラスのコンストラクターでのみ許可されている、最終フィールドの putfield
の制限に似ています。
VCCとDVTは同じ論理型の2つの側面であるため、JVMはVCCがDVTで vwithfield
操作を実行できるようにする必要があります。
これは、VCCが何らかの方法でコンパイルされた vwithfield
命令で才能を持たない限り、メソッドハンドルを使って反映されます。
(注:Valueフィールド更新命令は、オブジェクトフィールドがfinalであるオブジェクトフィールド更新命令にほぼ対応する。
ルールの詳細は異なります。
JVMは、コンストラクタ内のfinalフィールドにのみputfieldを制限するルールを強制します。
Java言語はさらに制約を課し、各フィールドをコンストラクターからの通常の戻りまでのすべてのパスに沿って正確に1回設定する必要があります。
JVMは、言語によって定義されたこれらの更なる制約を保証しておらず、また保証することもできないため、コンストラクターのfinalフィールドに0を含む任意の数のputfield命令を発生させることができます。
同様に、言語によって最終的にはvalueフィールドの適切な初期化が保証されますが、vwithfieldの使用をプライベートコードに限定することを除いて、JVMには特別な役割はありません。
vwithfieldは、最終的には “枯れた”メソッドのようなvalueコンストラクタの外で正当な使用を見つけるため、制限はfinalフィールドの制限と同じではありません。)
リフレクティブLookup APIにより、VCCとDVTは、今日の仲間の間で共有されているように、他のプライベートメンバーや機能へのアクセスを共有できます。
これには、vwithfield
命令を使用する権限が含まれます。
DVTにはメソッドがないので、この共有は非対称ですが、どんなレートでも双方向です。
(注:JVMの将来のリビジョンは、相互のプライベートフィールドとメソッドにアクセスできる明示的なVMレベルの “ネストメイト”をサポートします。
そのリビジョンでは、vwithfield命令は、指定されたvalue typeのすべてのネストメイトに対して使用可能になります。
つまり、vwithfieldは、privateメソッドが利用可能な同じ「カプセル」内で利用可能である。)
vdefault
命令は、コンストラクタを使用しないで値を作成できるため、value typeに対して非常に「private」なようです。
しかし、JVMはデフォルトvaluesを非常に特殊な状態にします。
なぜなら、与えられた型の配列は常にその型のデフォルトvaluesであらかじめパックされているからです。
したがって、実際には vdefault
については「private」はありません。
どんなクラスでも、任意のQ-typeのデフォルトvalueをいつでも計算できます。
Q-types and bytecodes
Q-typesと相互作用する他のバイトコードは、少なくともこれらのものである:
- すべての呼び出しバイトコード:引数または戻り値はすべてQ-typeである可能性があります。 レシーバー(
Methodref
のクラスコンポーネント)は、静的メンバーであっても
- l
ldc
と ldc_w
(Q-typeの、またはおそらく動的に生成された定数の)
- Q-type要素の配列に対する
anewarray
, multianewarray
の作業
これらの手順は、minimal versionではサポートされない場合があります。
getfield
, putfield
, getstatic
, putstatic
(Q-type valueの)
JVMは、Q-typeのフィールドを定義するが、フィールドアクセス命令を操作することを許可しないクラスファイルを受け入れる可能性があります。
その場合、メソッドハンドルは、そのようなクラスファイルのQ-typedフィールドを取得して設定するための回避策を提供します。
(このようなクラスファイルは、直接バイトコードスピンやjavacの拡張された非標準バージョンによって生成されることに注意してください。
VCCにはQ-typed記述子は含まれません。)
Value type reflection
value typeの世界に着手すると、反射的なクラス・ミラー型 java.lang.Class
の役割に根本的な変化が起こります。
value typesの前には、クラスファイルと Class
ミラーの間に厳密な1対1の関係があります。
唯一の例外は、クラスファイルから来ていない int.class
のようなプリミティブ型ミラーです。
value typesでは、1つのクラスファイルが複数の Class
ミラーに対応することがあります。
具体的には、value typeをロードすると、Q-type用とL-type用の2つのミラーが使用可能になります。
それらの関係は、 int.class
と Integer.class
の間のようなものですが、2つの異なるビューまたは投影で同じクラスを表現します。
(両方のビューを包含するU-typeもあります。特殊クラスでは、派生する特殊化の数に制限はありません)。
これをすべて考慮すると、ロードされたクラスファイルを最も直接的に表す、クラスの主鏡または適切な鏡について話すことは有益であると思われる。
次に、その “ヘルパー”、すなわち、プリンシパルの二次ビューまたは投影を表す他のクラスミラーがあります。
私たちはこれらを二次ミラーまたは不適切なミラーと言います。
(注:私たちが精細に見れば、Integer.classは不適切なミラーint.classの適切なミラーのように動作することがわかります。
おそらく、それらは将来統合され、共通のクラスについて実際に話すことができます。
minimal value typesの場合、DVTとVCCの Class
ミラーを区別し、VCCを主ミラーとして扱い、DVTを副ミラーとして扱うことで、この勇敢な新しい世界にアプローチします。
パブリッククラス jdk.experimental.value.ValueType
(内部モジュール内)には、この初期プロトタイプのvaluesのランタイムサポートのすべてのメソッドが含まれます。
ValueType
には、Q-typesを反映するための次のパブリックメソッドが含まれます。
public class ValueType<T> {
static boolean classHasValueType(Class<T> x);
static ValueType<T> forClass(Class<T> x);
Class<T> valueClass(); // DVT, secondary mirror
Class<T> boxClass(); // VCC, principal mirror
...
}
述語 classHasValueType
は、引数がQ-typeまたは(L-typeの)value-capable classを表す場合はtrueです。
forClass
ファクトリは、value-capable classから派生した任意の型のQ-typeの記述子を返します。
(他の型が指定されている場合は、IllegalArgumentException
がスローされます。例外を回避するには、まず classHasValueType
を使用してテストします)。
2つのアクセサ valueClass
と boxClass
は、それぞれQ-typeと元の(value-capableの)L-typeの個別の java.lang.Class
オブジェクトを返します。
ValueType.forClass(vt.valueClass())
が vt
と同じで、boxClass
の場合も同様です。
したがって、value typeの任意の Class
側面を使用して、その ValueType
記述子を取得できます。
従来の参照メソッド Class.forName
は、互換性の理由から boxClass
を引き続き返します。
この状態は持続してもしなくてもよい。
(将来、ソース言語の構造 T.class
は、 “intのように動作する”というスローガンの下で、T
に割り当てられたソースコードタイプに自然なものを生成する可能性が高い)。
Q-typeの2次ミラーは、getDeclaredMethods
などの最も意味のある反射照会をサポートしません。
この理由は、VCCから派生したDVTはできるだけ空であるからです。
反射クエリを使用してフィールドのみが表示される可能性があります。
いずれの場合でも、ユーザはVCC(boxClass
)を使用してvalue typeに関連するメンバーを調べる必要があります。
(この状態は、2つのクラスが結合されたときによく変化する可能性があります。)
これは、VCCがロードされたときにPOJO(普通の古いJavaオブジェクト)として扱われ、DVTがVCCから抽出され、そのアスペクトを変更しないため正常です。
(int.class
のような)プリミティブな擬似型が出現することができるところでは、Q-typesの不適切なクラスが反射APIに現れることがあります。
これらのAPIには、コアリフレクション(Class
と java.lang.reflect
の型)と java.lang.invoke
の新しいAPI( MethodType
や MethodHandles.Lookup
など)が含まれます。
これらの型で動作する定数プール定数は、Q-types およびL-typesを参照することができ、その区別は Class
オブジェクトの適切な選択肢(適切か否か)として反映されます。
(不適切なクラスは、時には “crass”と呼ばれることもありますが、 “r”の音は、実行時に必要な区別を明確にするためにのみ存在することを示しています)。
メインクラスはClass.forNameによって返されるもので、 more principled approach to reflection [cimadamore-refman]は、適切に洗練されたインタフェースタイプ階層の “type mirrors”を使用します。
いつものように、メソッドハンドルAPIを使用して、配列の作成と操作、フィールドのロードと格納、メソッドの呼び出し、メソッドハンドルの取得を行うことができます。
Q-typeミラーは、一般に、既存のプリミティブミラーと同じように動作し、JVMに、ボックスへの参照としてではなく、純粋な値としてデータを渡すように指示します。
タイプを変更するメソッドハンドル変換(asType
など)は、プリミティブボクシングとアンボクシングを表現できるように、value-typeのボクシングとアンボクシングをサポートします。
したがって、次のコードは、DoubleComplex
をオブジェクトにボックスするメソッドハンドルを作成します。
Class<DoubleComplex> srcType = DoubleComplex.class;
Class<DoubleComplex> qt = ValueType.forClass(srcType).valueClass();
MethodHandle mh = identity(qt).asType(methodType(Object.class, qt));
もちろん、型変換メソッド MethodHandle.invoke
を使用すると、ユーザーは、現在のJava言語でサポートされているボックス型、またはQ-typesに関して(適切なバイトコードで)より直接的に、Q-typesに対するメソッドハンドルを扱うことができます。
Boxed values
ボクシングは、オブジェクト参照を使用してすべてのタイプにわたって汎用サービスを実行するQ-typesとAPI間の相互運用性を得るために有用です。
多くのツール(デバッガ、ロガー、println
メソッドなど)は、任意のデータを報告するための標準の Object
形式を前提としています。
Q-type(またはより一般的には、どのようなボクシングメカニズムがQ-typesのコンテナとして終わるか)のvalue-capable L-typeは、初期システムにおいてそして最終システムにおいてさえ(おそらくそうであるように)有用な役割を果たす。
前述のように、value-capable class(L-type)のインスタンスは、最初は対応するQ-typeのvaluesのboxesとして機能します。
メソッドハンドルAPIは、box / unbox変換演算子がメソッドハンドルとして浮上したり、引数の変換に暗黙的に適用されたりすることを可能にします。
value-capable L-typeはまた、toString
のようないくつかの種類のQ-type動作を、標準のJavaメソッドとしてL-typeに直接記述することによって、便利な仕様を可能にします。
メソッドハンドル検索ランタイムは、(HotSpot JVMが余分なボクシングステップを除去するのに十分な範囲のスカラー化最適化を有するので)unboxed receiver (this
)とboxed receiverとの間の差異を無視できるコストで補う。
しかし、最も緊密なループは、すべてのホット・パスに沿ってunboxed dataが流れるように構成されることが予想されます。
これは、クラスロード時や println
のようなペリフェラル操作では、この早い段階でボクシングが最も有用であることを意味します
value-capable classはvalue-basedであるため、boxesの同期化、参照等価比較による識別、フィールドの変更、あるいはboxed typeの領域でnull参照をポイントとして取り扱おうと試みることは不適当です。
(これらの制限は、boxesが将来別のフォームを持っていても実行される可能性があります)
将来のJVMは、これらのエラーの一部を検出(または抑制)する際に役立ちます。
(エスケープ解析を完全に必要としない)このようなboxesが存在する場合、追加の最適化を提供する可能性があります。
しかし、この最小限のバージョンの設計では、このような支援や最適化は不要と思われます。
Q-typesは、その性質上、Q-typesが非同期、非可変、非null、およびID非依存であるため、そのようなバグの影響を受けません。
Value operator factories
Q-typesで動作するメソッドハンドルを呼び出すことができると、value typesの他のすべてのセマンティック機能は、メソッドハンドルを介してのみ(一時的に)アクセスできます。
これらには:
- 変換ルーチン(ボックス/ unboxなど)。
- デフォルトのQ-typesを取得する。
- Q-typesの構築。
- Q-typesの比較。
- Q-typesで定義されたメソッドを呼び出す。
- Q-typesで定義されたフィールドを読み込む。
- Q-typesで定義されたフィールドの更新。
- typesが Q-typesであるフィールド(または配列要素)を読み書きする。
- Q-typesの配列の作成、読み取り、書き込み
MethodHandles.Lookup
および MethodHandles
APIは、Q-types(Class
オブジェクトとして表される)、およびほとんどすべてのこれらの機能を実行できるサーフェスメソッドで機能します。
これらの演算子が欠けているバイトコードであると考えるのが役立つことがあります。
実験的なトランスレータが必要とするかもしれないバイトコードをエミュレートするために、invokedynamic命令と共に使用することができます。
結局のところ、そのようなバイトコードのいくつかの形式は、JVMのバイトコードのレパートリーに「選択的に沈降」し、これらの目的のためのinvokedynamicの使用は減少します。
既存のメソッドハンドルAPIポイントは、次のように調整されます:
MethodType
ファクトリメソッドは、今日のプリミティブ型を受け入れるのと同じように、Q-typesを表す Class
オブジェクトを受け入れます。
invoke
, asType
、および explicitCastArguments
は、プリミティブ/ラッパーのペアを扱うように、Q-type/L-typeのペアを扱います。
Lookup.in
は、Q-type/L-typeのペアの間で(特権モードを失うことなく)自由に変換できるようにします。
- Q-typesの非静的ルックアップは、L-typesではなくQ-typesの先行受信器パラメータを取るメソッドハンドルを生成します。
Lookup
の findVirtual
メソッドは、ルックアップクラスがQ-typeであれば、アクセス可能なすべての非静的メソッドをQ-typeで公開します。
Lookup
の findConstructor
メソッドは、元のvalue-capable classのアクセス可能なすべてのコンストラクタを、Q-typeと従来のL-typeの両方で公開します。
findConstructor
によって生成されるメソッドハンドルの戻り値の型は、たとえそれがQ-typeであっても、参照クラスと同じになります。
findVirtual
メソッドと findConstructor
メソッドは、メソッドをQ-typeのみに代入するための規約として、元のL-typeの private static
メソッドに対してアドホックパターンマッチング(TBD)を実行することもできます。
(帰属するメソッドがL-typeで仮想として表現するのが困難または不便な場合に便利です)。
identity
メソッドハンドルファクトリメソッドは、Q-typesを受け入れます。
empty
メソッドハンドルファクトリメソッドはQ-typesを受け入れ、型のデフォルトvalue を返すメソッドハンドルを生成します。
- 配列処理メソッドハンドルファクトリは、Q-typesを受け入れ、Q-type配列の構築、読み込み、および書き込みのためのメソッドを生成します。
(これらには、arrayConstructor
, arrayLength
, arrayElementGetter,arrayElementSetter
、さらにはvar-handleバリアントが含まれます)関連する配列型がサポートされていない場合、配列ファクトリの一部またはすべてが例外をスローすることがあります。
- すべてのメソッドハンドル変換は、今日のプリミティブ型を受け入れるのと同じように、Q-typesで動作するメソッドハンドルを受け入れます。
(はい、value type のメソッドは、仮想クラスがfinal
classに存在しないにもかかわらず、findVirtualで取得されます。
より貧弱な選択肢は、findSpecialを選択するか、新しいAPIポイントfindDirectを素敵な細かい区別にすることです。
Javaは「final virtual」メソッドという概念に既に満足しているので、私たちはこれからも継続していきます。
L-typesのメソッド・ハンドル・ルックアップは、ユーザー・モデルの実験に応じて、L-typesのいくつかのメソッドを抑止することができますが、value typesに比べてボックス型は比較的フィーチャーレスでなければならないと考えられます。
もちろん、L-typeの仮想メソッドは、メソッドハンドルとして実装されると、Q-typeではなくL-typeであるレシーバーパラメータを受け取ります。
一方、前述のように、私たちは、メソッドハンドルを介して、Q-typeのコンストラクタまたは非静的メソッドであるかのように、プライベート静的L-typeメソッドをバインドするための特別な慣習をうまく構築できます。
これは、変換処理方式とメソッドハンドルランタイムとの間の副契約であり、そのうちのJVMは認識されません。
静的メソッドは、メソッドハンドルランタイムにのみ表示され、Q-typeでの使用のみを目的としたロジックのスニペットを含みます。
最終的にもちろん、そのようなことはすべてバイトコードとJVMのハードウェアによる解決ロジックに移行します。
value-basedのクラスとして、Object
からすべての関連するメソッドをオーバーライドするには、value-capable classes が必要です。
派生したQ-typesは、Object
の標準メソッドを継承したり応答したりしません。
一致するシグネチャを実装する Object
(toString
など)のメソッドにのみ応答します。
以下の追加機能は、まだ(まだ) MethodHandle
APIには含まれていないため、ランタイムサポートクラス jdk.experimental.value.ValueType
に配置されています。
ValueType
には次のメソッドが含まれます:
public class ValueType<T> {
...
MethodHandle defaultValueConstant();
MethodHandle substitutabilityTest();
MethodHandle substitutabilityHashCode();
MethodHandle findWither(Lookup lookup, Class<?> refc,
String name, Class<?> type);
}
defaultValueConstant
メソッドは、引数をとらず、そのQ-typeのデフォルト値を返すメソッドハンドルを返します。
このvalue typeの1要素配列を作成し、その結果をロードするのと同等です(しかしおそらくより効率的です)。
このメソッドは、MethodHandles.empty
や類似のコンビネータを実装するのに便利です。
substitutabilityTest
メソッドは、指定されたQ-typeの2つのオペランドを比較可能に比較するメソッドハンドルを返します。
具体的には、フィールドは置換可能性のためにペアごとに比較され、結果はすべての比較の論理積です。
プリミティブと参照は、Java ==演算子の適切なバージョンを使用して等価を比較する場合にのみ代入可能です。
浮動小数点数と倍精度は比較の前に最初に “生のビット”に変換されます。
同様に、substitutabilityHashCode
メソッドは、指定されたQ-typeの単一のオペランドを受け取り、お互いに代入可能である場合、そのタイプの2つの値が等しくなることが保証されたハッシュコードを生成するメソッドハンドルを返します。
そうでなければ異なっている可能性が高い。
(このハッシュコードのサイズを64ビットに拡張するかどうかは未解決であり、おそらくは、従来型のハッシュコード値を使用して、value typeフィールドのハッシュコードの32ビット構成として定義されるでしょう。
サブコードの構成は、その構成手法が非常に最適ではないにもかかわらず、最初はベース31の多項式を使用するでしょう。)
findWither
メソッドは、Lookup.findSetter
と同様に動作します。
ただし、結果のメソッドハンドルは常に新しい値を作成します。
ただし、指定したフィールドが新しい値を含むように変更されている点が異なります。
値には同一性がないため、これはフィールド値を更新する唯一の論理的に可能な方法です。
Witherプリミティブの使用を制限するために、refc
パラメータとlookup-classは ValueType
自体のQ-typeに対してチェックされます。
それらがすべて同じQ-typeでない場合、アクセスは失敗します。
アクセス制限は後で広げることができます。
value-typeはもちろん、プリミティブな枯れたアクションをカプセル化する名前付き枯れメソッドを定義することができます。
最終的にフィールド更新を直接表現するために、withfield
バイトコードが作成されることがあります。
この場合、アクセス制限の同じ問題に対処する必要があります。
名前が枯れているということは、何かを傷つけたり、壊したりする方法を意味するものではありません。
確かに日陰の活動です。
これは、レコード値の機能更新を実行するメソッドの命名規則を指します。
複素数c.withRe(0)を求めると、新しい純粋虚数複素数が返されます。
対照的に、setterメソッドへの呼び出しであるc.setRe(0)は、複素数を変更して、非ゼロの実数成分を削除するようです。
setterメソッドは可変オブジェクトに適していますが、wither メソッドはvaluesに適しています。
実際の getter、setter,、wither のいずれかの標準単語で始まらなくても、メソッドは実際に getter、setter,、wither のいずれかのメソッドになることに注意してください。
value typesの最終的な規則は、withRe(0)のような形式を単純にre(0)に賛成させる可能性があります。
ValueTypeのこれらのメソッドは、最終的に Lookup
自体の仮想メソッド(それが主要な引数である場合)、あるいは MethodHandles
の静的メソッドになる可能性があります。
これらのメソッドは、多くの既存のメソッドが directly express [bcbehavior] 同等のバイトコード操作を処理するのと同様に、バイトコードとしての直接的な表現の候補です。
ここでは、新しいメソッドハンドルと、value typesに対する操作に対する仮のバイトコードの振る舞いをまとめた表を示します。
3番目の列は、仮想バイトコードのスタック効果と実際のメソッドハンドルの型の両方を示します。
この表では、ほぼすべてのタイプがQ-typeになり得ます。
これは、「Q
」接頭辞で強調している事実です。
「QC
」タイプは、特に操作される値タイプを表す。
複合 VT<QC>
は、Q-typeから派生した ValueType
インスタンスを表します。
通常のオブジェクトのQ-typeフィールドのフィールドアクセサーのリストの下に記載されているタイプ “RC
“は、通常のL-typeです。
Q-types( “works like an int!”)は、フィールドと配列要素から全体を読み書きすることができます。
Q-type method handles & behaviors
VT<QC>.defaultValueConstant()
|
“vdefault” QC
|
() → QC
|
VT<QC>.substitutabilityTest()
|
?
|
(QC QC) → boolean
|
VT<QC>.substitutabilityHashCode()
|
?
|
(QC) → int/long
|
L.findGetter(QC, f, QT)
|
“vgetfield” QC.f:QT
|
(QC) → QT
|
VT<QC>.findWither(L, QC, f, QT)
|
“vwithfield” QC.f:QT
|
(QC, QT) → QC
|
L.findVirtual(QC, m, (QA*)QT)
|
“vinvoke” QC.m(QA*)QT
|
(QC QA*) → QT
|
L.findGetter(RC, f, QT)
|
“getfield” RC.f:QT
|
(RC) → QT
|
L.findSetter(RC, f, QT)
|
“putfield” RC.f:QT
|
(RC, QT) → void
|
この表は、単にQ_typed値をコピーしたり、通常のオブジェクトや配列から読み込んだり格納したりする多くのメソッドハンドルには対応していません。
このような操作は、findStaticGetter
, findStaticSetter
, findVirtual
, findStatic
, findStaticSetter
, arrayElementSetter
, identity
, constant
など、さまざまな場所で使用できます。
Reminder: All this will change
上記のバイトコードおよびAPIは、Javaの value types の最終形式ではありません。
この最小限の提案で開発されたコードは、完全な機能が作成されたときには、必ず破棄して書き直す必要があります。
Future work
この最小限の提案は、事実上一時的かつ暫定的です。
最終的な仕様ではなく、今後の作業に必要な基盤を提供します。
今後の作業の一部は本質的に暫定的なものになるでしょうが、時間がたつにつれ、私たちの成功を踏まえ、間違いから学び、最終的には太陽の下で働くことができるうまく設計された仕様を作成します。
value typesをサポートするこの現在の一連の機能は、操作が難しくなります。
これは意図的です。
このドキュメントの残りの部分では、最小限の提案で実用的でない実験を可能にするいくつかの追加機能を示します。
したがって、この最後のセクションは安全にスキップされる可能性があります。
そのような機能は、追求された場合には、独自の裏付文書が与えられます。
しかし、最小値提案で欠落している機能に気付いた人々にとっては、興味深いことかもしれません。
Denoting Q-types in Java source code
最低限、Q-typesを扱うための言語の変更は必要ありません。
興味深いマイクロベンチマークを実行するには、JVMハッキング(value-capable classes)、annotation-driven classfile変換、ダイレクトバイトコード生成の組み合わせで十分です。
メソッドハンドルは、ダイレクトバイトコード生成の有益な代替手段を提供します。
これらのメソッドは、Q-types(以下で説明します)を使用して完全に操作できるようになります。
それにもかかわらず、言語サポートのようなものはありません。
javacを使用した非常に初期の実験では、Q-typesを参照して変数をJavaコードで直接作成する簡単な方法が作成される可能性があります(もちろん文脈上の制約があります)。
特に、オブジェクトのコンストラクタは、value typesのように一見等価なコンストラクタとは非常に異なるバイトコードの形をしています。
(Javaオブジェクトコンストラクタの構文は、すべてのフィールドがfinalである限り、value typeコンストラクタの完全な表記法です。)
javacが、value-capable classの各コンストラクタの両方のバージョンをバイトコンパイルする負担を負うことは合理的です。
同様に、value typeコンストラクタの直接呼び出し、およびvalue type メソッドとフィールドの直接アクセスは、バイトコードのサポートが完了するまで、invokedynamic呼び出しにコンパイルする必要があったとしても、Javaソースコードから使用すると便利です。
Q-replacement within value-capable classes
Javaソースからコンパイルされたvalue-capable classは、value-capable classのファイルがロードまたはコンパイルされたときにバイトコードレベルの変換として、Q-typesの導入を引き起こす選択されたフィールドおよびメソッドで追加の注釈(または属性)を持つことができます。
有用と思われる2つの変換は、Q-replacement および Q-overloading と呼ばれてもよい。
最初はL-typesを削除し、Q-typesで置き換えますが、2番目のメソッドは単純にメソッドをコピーし、記述子のL-types の一部または全部を対応するQ-typesで置き換えます。
このアイデアのセットは、JDK-8164889として追跡されます。
annotation-driven Q-replacmentに代わるものは実験的な言語機能であり、Q-typesをJavaソースで直接記述することができます。
このような実験は、Valhalla Projectの一環として行われる可能性が高く、早急に変換が不要になる可能性があります。
More bytecodes
ライブラリメソッドハンドル defaultValueConstant
は、新しい vdefault
バイトコード、または接頭辞付き aconst_null
バイトコードで置き換えることができます。
ライブラリメソッドハンドル substitutabilityTest
は、新しい vcmp
バイトコード、または接頭辞付き if_acmpeq
バイトコードに置き換えることができます。
ライブラリメソッドハンドル findWither
は、新しい vwithfield
バイトコードで置き換えることができます。
ライブラリメソッドハンドル findGetter
は、適切に拡張された getfield
バイトコードで置き換えることができます。
ライブラリメソッドハンドル arrayConstructor
は、適切に強化された anewarray
または multianewarray
バイトコードで置き換えることができます。
ライブラリメソッドハンドル arrayElementGetter
は、新しい vaload
バイトコードまたは接頭辞付きの aaload
バイトコードで置き換えることができます。
ライブラリメソッドハンドル arrayElementSetter
は、新しい vastore
バイトコードまたは接頭辞付きの aastore
バイトコードで置き換えることができます。
ライブラリメソッドハンドル arrayLength
は、適切に強化された arraylength
バイトコードで置き換えることができます。
このデザインの最終的な “スイートスポット”は、Q-typesだけでなくあらゆるタイプ記述子に言及することによって、リファレンス、values、およびプリミティブを対称的に扱うユニバーサルバイトコード(またはプレフィックス付きマクロバイトコード)の単一のセットに基づいている可能性があります。
このような「ユニバーサルバイトコード」は、uload
, ustore
, uinvoke
, ucmp
, u2u
などの独自の命名規則に服従する必要があります。
バイトコードが値だけで動作する場合、v*
命名規則が使用されます。
記述子型 I
は、int、short、boolean、char、およびbyteを扱うためにJVMで使用され、すべての種類のL-typesを保持するために記述子型 L
(クラス付き)が使用されるのと同様に、記述子型 Q
(クラス付き)は、あらゆる種類のK-typesを運ぶために使用されます。
事実、4つのI-typesとL-typesとQ-typesの無限にもかかわらず、実際にはJVMには3つのキャリアタイプしかありません。
(long型、float型、double型の単形キャリヤ型もあります)L-typeキャリヤはヒープを指す単一の機械語ですが、Q-typeキャリヤはより複雑です。
いくつかのvalueの “ペイロード”だけでなく、そのサイズとレイアウト(少なくともGCの)について説明します。
実際には、value’s classも記述する必要があります。
結局、Q-typeのキャリアは実際にはバッファの型付きロケータの一種であり、これは任意の場所(Javaヒープ、Cヒープ、スレッドスタック)である可能性があります。
この内部データ構造をヒープ内のJavaオブジェクトのレイアウトと整列させると、単純にポインタを戻す vbox や vunbox などの最適化が可能になります。
さらに重要なことは、タグビットの反転だけで、任意のQ-typeまたは任意のU-type も効率的に送信できる、最終的なJVMキャリアタイプであるU-typeキャリアを効率的に作成できることです。
U-typesが必要であると判明した場合、それ以上のU-mode命令が作成される可能性が高い。
しかし、その場合、Q-typeキャリアとU-typeキャリアとが同一であることを保証することによって、Q-modeとU-modeの命令を大きく統合することも可能である。
検証者は、Q-typeが「pure value」であったか、U-typeとの結合によって「polluted(汚染された)」かどうかを追跡し続けるだろう。
論理的には、U-typesはオブジェクト参照と null を持ちますが、Q-typesはそうすることはできません。
しかし共通のキャリアタイプは、すべてのQ-type valuesと区別するために、参照値のために別のタグを付けることによってこれを単純にモデル化できます。
結局、Q-mode命令の大半はおそらくU-mode命令に昇格させることができ、バイトコードをいくつか追加するだけで最大の柔軟性が得られます。
最小限の提案は、U-typesを必要としません。
なぜなら、汎用性のあるvaluesを試すことはできないし、すべてのインタフェース呼び出しが(現在は)L-typeのボックス化されたVCCにプッシュされるからです。
More data structures
最小限の提案では、value typesのすべての特徴の自由な相互適用によって暗示される様々なコーナーケースを省略することができる。
これらのコーナーケースでの塗りつぶしは便利です。
さらに、value types(およびプリミティブ)の制限は、時間の経過とともに削除する必要があります。
機能的増加には、様々な段階で:
- value typesが非プリミティブ型(Q-types、L-types)のフィールドを宣言できるようにする。
- すべてのvalue typesのフラット化された配列を実装する。
- valuesへのアトミックなアクセスをサポートします(たとえば、
volatile
とマークされたフィールド)。
- valuesのスーパーアライメントのアノテーションを提供する(通常のJVM制約付きアラインメント(通常64ビット)を超えて)。
- value typesのJVMレイアウトの詳細の低レベル(“unsafe”)反映を提供する。
- value-typeのコンテナを使用して、外部型(C
unsigned
)またはセーフポインタ(たとえば、アドレス+境界と型+スコープ)を表します。
- numeric typeのタワーを表すには、value-typeのコンテナを使用します。
- 上記のすべてのパフォーマンスとストレージ密度を最適化する。
Integer
やQ-type boxesなどのvalue-basedオブジェクト型の誤使用を検出または防止するために何かを行います。
たとえば、誰かがそれらを同期しようとすると、エラーを投げます。
- value typesを直接サポートするメソッド。
- インターフェイスのデフォルトメソッドの(重要な)実行を含む、value typesで直接インターフェイスの実装をサポートします。
- 以下のための方法をワークアウト
- Q-type valueまたはL-type valueのいずれかを直接表すことができる、安全にタグ付けされた互いに素な結合型(“P-types”)を提供する。
- 印刷、比較、およびハッシュコード用のオブジェクト形式のプロトコルを含む、valuesの標準形状を作成する。
- Q-typesをサポートする特殊ジェネリックとプリミティブを追加する。 (大きなもの!)
value typesがインターフェイスと完全に統合されている場合、Q-typeはインターフェイスのスーパータイプから default
メソッドを継承する必要があります。
これは、valuesと汎用アルゴリズムおよびデータ構造(ソートや TreeMap
など)との相互運用性の重要な形式です。
最小限のバージョンでこの作業を行うには、valueをボクシングする必要があり、不快なパフォーマンスの影響を受ける可能性のあるデフォルトのメソッドをボックスで実行する必要があります。
完全な実装では、デフォルトメソッドの実行は、それぞれの特定のvalue typeに最適化する必要があります。
また、インタフェースメソッド自体が、value-based types(ヌルまたは同期なし、制限付き==など)に適していることを保証するためのフレームワークが必要です。
これには、最小限の提案の範囲を超えたさらなる作業が必要です。
interface-array type I[]
がvalue type QT
のスーパータイプであっても、value-bearing array type QT[]
をinterface-array type I[]
のサブタイプにすることは難しいようです。
これを実現するためには、JVM型構造のさらなる作業が必要になります。
Interface types I
は現在L-typeキャンプにしっかりといます。
インターフェイス配列は参照配列なので、Object []のサブタイプであり、したがって参照配列として見えます。
しかし、value types QT
の配列は、(透過的に)参照の配列として扱うことはできません。
Bridge-o-matic
場合によっては、Qで置き換えられたAPIポイントをクラスに供給することは、適切なブリッジメソッドを提供することの単なる問題です。
バイトコードトランスフォーマーまたはジェネレータは、ブリッジが適切に編成されたブートストラップメソッドを持つ(バイトコードではなく)場合、そのようなブリッジメソッドのボディを指定する必要性を避けることができます。
このアイデアのセットには、標準 equals
, hashCode,toString
メソッドの自動生成を含む多くの追加用途があります。
JDK-8164891として追跡されます。
Natural (JVM-native) value-type classfiles
最小限の提案は、box-firstの読み込み順、POJO typesの読み込み、それらからvaluesとbox typesの導出で始まります。
box types(最初は)は単純にPOJO types であり、value typesはPOJO typeからフィールドのみを抽出して内部的に派生した構造体です。
value typeだけを対象としたメソッドであっても、最初はPOJOメソッドとして渡す必要があります。
これは、変更されたクラスファイルによって直接指定されたvalue typesと、value types のロード中に導出されたボックスを使用して、時間の経過とともにvalue-firstのロードフォーマットに変更する必要があります。
POJO-basedのロード・フォーマットは時代遅れになるので、ボックス自体は引き続きPOJO-shapedのvalue typesであるか、別のフォームを使用する可能性があります。
この変更は、value-first 設計で公開されている非標準機能をサポートするために、value typesのすべてのユーザーがツール(コンパイラ、デバッガ、IDEなど)をアップグレードする必要があるため、比較的高額になります。
上記のように、最小限の提案の厄介な点のいくつかは、ツールチェーンの変更を必要とする機能を避けるために注意が払われています。
これらの機能には:
- Value-firstクラスファイルフォーマット(例えば、ヘッダの
ACC_VALUE
ビット)。
- Q-typesのソース言語による表記。
- Q-typesとQ-typesのフィールドとしてタイプされたフィールドを直接ロードするためのバイトコード。
- Q-typesのメソッドを直接呼び出すためのバイトコード。
- 配列、インタフェースなどとvalue typesの完全な統合
Heisenboxes
上で示唆したように、valuesのL-typesはvalue-basedであり、JVMの一部のバージョンでは、次のようなさまざまな方法でこれを実行しようとする場合があります:
- boxed Q-type valueを同期すると、
IllegalMonitorStateException
のような例外がスローされることがあります。
- 参照比較(Java演算子==または
acmp
命令)は、以前に参照がfalseを返す場合でも、2つの同等のboxed Q-type valuesに “true”を返し、以前に “true”を返したときに “false”を返します。
そのような変化はもちろん、基礎となるQ-typesの代替可能性の論理に従うことになる。
かつては等しい参照として検出された2つのボックスは、相互に完全に置き換え可能であった。
setAccessible
が呼び出された後であっても、boxed Q-type valuesのフィールドにvaluesを反映して格納しようとする試みが失敗することがあります。
setAccessible
が呼び出された後でも、ボックスのコンストラクタを反射的に呼び出そうとする試みが失敗することがあります。
監査から監査までの間、アイデンティティの状態が不確実なボックスは、「heisenbox」と呼ばれます。
この類推を追求するために、2つのheisenboxesの true
の参照平等(==
, acmp
)監査は完全に相互置換可能であることが証明されているため、それらを同じオブジェクトに「崩壊」するので、それらのQ-valuesも同等です。
参照の2つのコピーは、boxed valuesの継続的な相互置換性にもかかわらず、後で非公式であることを報告して解体することができます。
平等の述語はシュレーディンガーの猫が入っているボックスに配線して調べることができます。多くの困惑と悲しい結果があります…
このアイディアのセットは、JDK-8163133として追跡されます。
References
[values]: http://cr.openjdk.java.net/~jrose/values/values.html
[valhalla-dev]: http://mail.openjdk.java.net/pipermail/valhalla-dev/
[goetz-jvmls15]: http://www.oracle.com/technetwork/java/jvmls2015-goetz-2637900.pdf
[valsem-0411]: http://mail.openjdk.java.net/pipermail/valhalla-spec-experts/2016-April/000118.html
[simms-vbcs]: http://mail.openjdk.java.net/pipermail/valhalla-dev/2016-June/001981.html
[graves-jvmls16]: http://youtu.be/Z2XgO1H6xPM?list=PLX8CzqL3ArzUY6rQAQTwI_jKvqJxrRrP_
[value-based]: http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
[JEP-11]: http://openjdk.java.net/jeps/11
[goetz-jvmls16]: http://www.youtube.com/watch?v=Tc9vs_HFHVo
[Long2.java]: http://hg.openjdk.java.net/panama/panama/jdk/file/70b3ceb485cf/src/java.base/share/classes/java/lang/Long2.java
[cimadamore-refman]: http://cr.openjdk.java.net/~mcimadamore/reflection-manifesto.html
[bcbehavior]: http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.Lookup.html#equiv
[JDK-8164891]: http://bugs.openjdk.java.net/browse/JDK-8164891
[JDK-8177279]: http://bugs.openjdk.java.net/browse/JDK-8177279
[JDK-8164889]: http://bugs.openjdk.java.net/browse/JDK-8164889
[JDK-8163133]: http://bugs.openjdk.java.net/browse/JDK-8163133
以上、HTMLファイルからコピペしたので変な感じになってます。(^_^;
それ以前にこのドキュメントは開発に携わる人向けのようで私には何の価値もないものでした。(>_<。)
TAGS: Java |
2017年12月5日7:49 PM |
Java
Java SE 9 の予習 その5です。
今回は地味にアップデートを積み重ねている ProcessAPI をゴニョゴニョしてみました。
JDK 9 でどのようアップデートされたか試してみます。
まずプロセス ID が直感的に取得できるようになってます。
今までは次のような解りにくいコードを書かなければいけませんでした。
|
String pName = ManagementFactory.getRuntimeMXBean().getName(); int pid = Integer.parseInt(pName.split("@")[0]); |
それが JDK 9 では ProcessHandle インターフェイスが用意され便利に使えるファクトリメソッドあるので次のように優しいコードで書くことができるようになりました。
実行中のプロセスIDとコマンドを取得するにはつぎのようなシンプルなコードになります。
|
// 実行中のプロセスIDとコマンドを取得 System.out.println("\n実行中のプロセスIDとコマンドを取得"); ProcessHandle.allProcesses().forEach(processHandle -> System.out.println("PID: " + processHandle.pid() + " Command: " + processHandle.info().command().orElse("none."))); |
static Stream<ProcessHandle> allProcesses() メソッドで現在のプロセスから見えるすべてのプロセスのスナップショットを返します。
ただし、これは非同期で処理されるため注意が必要です。
スナップショットを作成したあとにプロセスの開始、終了などが問題になる場合も考えられます。
このコードの実行結果は次のようになります。(長いので途中省略してます。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
実行中のプロセスIDとコマンドを取得 PID: 1 Command: none. PID: 2 Command: none. PID: 3 Command: none. PID: 5 Command: none. * * * * * PID: 2066 Command: /usr/lib/gvfs/gvfs-mtp-volume-monitor PID: 2071 Command: /usr/lib/gvfs/gvfs-afc-volume-monitor PID: 2080 Command: /usr/lib/evolution/evolution-calendar-factory-subprocess PID: 2083 Command: /usr/lib/gvfs/gvfs-goa-volume-monitor PID: 2088 Command: /usr/lib/gvfs/gvfs-gphoto2-volume-monitor PID: 2100 Command: /usr/lib/evolution/evolution-calendar-factory-subprocess PID: 2112 Command: /usr/lib/gvfs/gvfsd-trash PID: 2126 Command: /usr/lib/evolution/evolution-addressbook-factory PID: 2137 Command: /usr/lib/evolution/evolution-addressbook-factory-subprocess PID: 2173 Command: /usr/lib/gvfs/gvfsd-metadata PID: 2195 Command: /bin/bash PID: 2470 Command: /home/yucchi/Sun/jdk-9/bin/java PID: 2523 Command: /usr/bin/zeitgeist-datahub PID: 2530 Command: /bin/dash PID: 2534 Command: /usr/bin/zeitgeist-daemon PID: 2541 Command: /usr/lib/x86_64-linux-gnu/zeitgeist-fts PID: 2643 Command: /usr/bin/update-notifier PID: 2677 Command: /usr/lib/x86_64-linux-gnu/deja-dup/deja-dup-monitor PID: 2794 Command: none. PID: 2812 Command: none. PID: 2850 Command: none. PID: 3092 Command: none. PID: 3113 Command: none. PID: 3118 Command: /home/yucchi/Sun/jdk-9/bin/java |
それでは JDK 9 から追加された public static interface ProcessHandle.Info インターフェイスについて調べてみましょう。
プロセスに関する情報のスナップショット。
プロセスの属性はオペレーティングシステムによって異なり、すべての実装で利用できるわけではありません。
プロセスに関する情報は、要求を行うプロセスのオペレーティングシステム権限によって制限されます。
もし値が利用可能であるなら、戻り型は明示的なテストとアクションを許す Optional<T> です。
ProcessHandle.Info info() メソッドはプロセスに関する情報のスナップショットを返します。
ProcessHandle.Infoインスタンスには、利用可能なプロセスに関する情報を返すアクセサメソッドがあります。
Optional<String[]> arguments() メソッドはプロセスの引数の文字列の配列を返します。(プロセスの引数の Optional<String[]>)
ただし、いくつかのプラットフォームでは、ネイティブアプリケーションは起動後に引数配列を自由に変更でき、このメソッドは変更された値のみを表示することがあります。
Optional<Instant> startInstant() メソッドはプロセスの開始時刻を返します。(プロセスの開始時刻の Optional<Instant> です。)
Optional<Duration> totalCpuDuration() メソッドはプロセスの累積合計 cputime を返します。(累積合計 cputime の Optional<Duration>)
Optional<String> user() メソッドはプロセスのユーザーを返します。(プロセスのユーザーについての Optional<String>)
Optional<String> command() メソッドはプロセスの実行可能パス名を返しています。(プロセスの実行可能パス名の Optional<String>)
Optional<String> commandLine() メソッドはプロセスのコマンドラインを返します。(プロセスのコマンドラインの Optional<String>)
command() メソッドと arguments() メソッドが Optinal.empty でない Optional<String> を返す場合、これは単に空白で区切られた2つの関数の値を連結する便利なメソッドです。
それ以外の場合は、コマンドラインのベストエフォート、プラットフォーム依存の表現を返します。
ただし、システムの制限により、返された実行可能パス名と引数が一部のプラットフォームで切り捨てられることがあります。
実行可能なパス名には、完全なパス情報がない実行可能ファイルの名前のみが含まれている場合があります。
空白が異なる引数を区切っているのか、単一の引数の一部なのかは決まりません。
以上、ProcessHandle.Info の便利なアクセサメソッドを調べてみました。
今回のプログラムでは全て試してないのですが Windows だと情報取得できるけど Linux (Ubunts) だと取れないとかありました。
でも JDK 9 での ProcessAPI はこれだけでも開発者に優しくなったと思います。
それではいろいろゴニョゴニョしていきましょう。
次に任意のプロセス情報の取得を試してみます。
|
// 任意のプロセス情報の取得 System.out.println("\n任意のプロセス情報の取得"); final int pid = 2677; // PID: 2677 Command: /usr/lib/x86_64-linux-gnu/deja-dup/deja-dup-monitor ProcessHandle processHandle = ProcessHandle.of(pid).get(); System.out.println("PID: " + processHandle.pid()); System.out.println("Command: " + processHandle.info().command().orElse("none.")); System.out.println("CPU Duration: " + processHandle.info().totalCpuDuration().get().getNano() + " nanoSeconds"); |
static Optional<ProcessHandle> of(long pid) メソッドで Optional<ProcessHandle> を返します。
引数は当然プロセス ID です。
プロセスが存在しない場合は、 empty を返します。
このコードの実行結果は次のようになります。
|
任意のプロセス情報の取得 PID: 2677 Command: /usr/lib/x86_64-linux-gnu/deja-dup/deja-dup-monitor CPU Duration: 180000000 nanoSeconds |
次はカレントプロセスの情報を取得してみましょう。
|
// カレントプロセス情報の取得 System.out.println("\nカレントプロセス情報の取得"); ProcessHandle currentProcessHandle = ProcessHandle.current(); System.out.println("PID: " + currentProcessHandle.pid()); System.out.println("Command: " + currentProcessHandle.info().command().orElse("none.")); System.out.println("CPU Duration: " + currentProcessHandle.info().totalCpuDuration().get().getNano() + " nanoSeconds"); |
static ProcessHandle current() メソッドで現在のプロセスの ProcessHandle を返します。
API ドキュメントに次のような注意書きがあります。
ProcessHandle を使用して現在のプロセスを破棄することはできません。
代わりに System.exit を使用してください。
実行結果は次のようになります。
|
カレントプロセス情報の取得 PID: 3118 Command: /home/yucchi/Sun/jdk-9/bin/java CPU Duration: 940000000 nanoSeconds |
それでは子プロセス情報の取得を試してみます。
|
// 子プロセス情報の取得 Process geditProcess1 = new ProcessBuilder("gedit").start(); Process geditProcess2 = new ProcessBuilder("gedit").start(); System.out.println("\n子プロセス情報の取得"); currentProcessHandle.children().forEach(child -> { System.out.println("ChildPID: " + child.pid()); System.out.println("ChildCommand: " + child.info().command().orElse("N/A")); System.out.println("ChildCPU Duration: " + child.info().totalCpuDuration().get().getNano() + " nanoSeconds"); }); |
エディタを二つ起動させてみました。
Stream<ProcessHandle> children() メソッドでカレントプロセスの直接の子のスナップショットを返します。
プロセスの直接のチルドレンであるプロセスの ProcessHandles の連続的なストリームを返すと言うことです。
注意すべきはプロセスが作られて、そして非同期に終わること。 プロセスが生きているという保証がありません。
このコードの実行結果は次のようになります。
|
子プロセス情報の取得 ChildPID: 3152 ChildCommand: /usr/bin/gedit ChildCPU Duration: 0 nanoSeconds ChildPID: 3154 ChildCommand: /usr/bin/gedit ChildCPU Duration: 0 nanoSeconds |
次はプロセスの終了処理を試してみます。
先ほど起動したエディタを終了させてみましょう。
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
// プロセスの終了処理 destroy() System.out.println("\nプロセスの終了処理"); System.out.println("geditProcess1 is running : " + geditProcess1.isAlive()); geditProcess1.destroy(); // いつ終了するかはシステムの都合による // 終了待ち geditProcess1.waitFor(); System.out.println("geditProcess1 is running : " + geditProcess1.isAlive()); // プロセスの強制終了処理 destroyForcibly() System.out.println("geditProcess2 is running : " + geditProcess2.isAlive()); // geditProcess2.destroyForcibly(); // いつ終了するかはシステムの都合による // 終了待ち geditProcess2.destroyForcibly().waitFor(); System.out.println("geditProcess2 is running : " + geditProcess2.isAlive()); |
プロセスの終了処理にpublic abstract void destroy() メソッドと public Process destroyForcibly() メソッドをそれぞれ使ってみました。
public abstract void destroy() メソッドはプロセスを終了させます。
このProcessオブジェクトが表す処理が正常に終了したか否かは実装依存です。
強制的なプロセス破棄がプロセスの即時終了と定義されるのに対して、正常終了はプロセスが正常に終了することを可能にします。
プロセスが生存していない場合、何も実行されません。
プロセスが終了すると、onExit() メソッドの CompletableFuture が完了します。
56行目の geditProcess1.waitFor(); の終了待ちがないと public boolean isAlive() メソッドが true を返す場合があります。
JDK 9 からは public CompletableFuture<Process> onExit() メソッドを使うのが良いのかもしれません。
シンプルに public abstract int waitFor() メソッドを使って対応してしまいました。(^_^;
public Process destroyForcibly() メソッドもプロセスを終了させます。
ただし、このプロセスオブジェクトによって表されたプロセスは強制的に終了させられます。
ProcessBuilder.start()およびRuntime.exec(java.lang.String)によって返されたProcessオブジェクトでこのメソッドを呼び出すと、強制的にプロセスが終了します。
プロセスが終了すると、onExit()のCompletableFutureが完了します。
プロセスはすぐに終了しないことがあります。つまり、isAlive() メソッドは destroyForcibly() メソッドが呼び出された後、短時間の間 true を返すことがあります。
こちらも waitFor() メソッドを使って対応しました。
このコードの実行結果は次のようになります。
|
プロセスの終了処理 geditProcess1 is running : true geditProcess1 is running : false geditProcess2 is running : true geditProcess2 is running : false |
ここまで試してみて JDK 9 での ProcessAPI は OS に依存しないコードで美しく処理が可能となったようです。
次は public static List<Process> startPipeline(List<ProcessBuilder> builders) メソッドを使ったパイプライン処理をためしてみます。
先ず、ProcessBuilder クラスがどんなものか復習しておきましょう。
J2SE 5.0 (Tiger) から存在するクラスです。
このクラスは、オペレーティングシステムのプロセスを作成するために使用されます。
各 ProcessBuilder インスタンスは、プロセス属性のコレクションを管理します。
start() メソッドは、これらの属性を持つ新しい Process インスタンスを作成します。
同じインスタンスからstart() メソッドを繰り返し呼び出すと、同一または関連する属性を持つ新しいサブプロセスを作成できます。
public static List<Process> startPipeline(List<ProcessBuilder> builders) メソッドを呼び出して、各プロセスの出力を次のプロセスに直接送り新しいプロセスのパイプラインを作成することができます。
各プロセスにはそれぞれのProcessBuilderの属性があります。
各プロセスビルダは、次のプロセス属性を管理します。
- コマンド、呼び出される外部プログラムファイルを示す文字列のリストとその引数(存在する場合)。
有効なオペレーティング・システム・コマンドを表す文字列リストは、システムによって異なります。
たとえば、各概念的な引数はこのリストの要素であるのが普通ですが、プログラムがコマンドライン文字列をトークン化することを期待されるオペレーティングシステムがあります。
このようなシステムでは、Java実装では厳密に2つの要素を含む必要があります。
- 環境変数は、変数から値へのシステム依存のマッピングです。
初期値は、現在のプロセスの環境のコピーです。
- 作業ディレクトリ。 デフォルト値は、現在のプロセスの現在の作業ディレクトリです。
通常、システムプロパティ user.dir で指定されたディレクトリです。
- 標準入力のソース。 デフォルトでは、サブプロセスはパイプからの入力を読み取ります。
Java コードは、Process.getOutputStream() によって返される出力ストリームを介してこのパイプにアクセスできます。
ただし、標準入力は redirectInput を使用して別のソースにリダイレクトされます。
この場合、Process.getOutputStream() は null 出力ストリームを返します。
- 書き込みメソッドは常に IOException をスローします。
- close() メソッドは何もしない。
- 標準出力と標準エラーの出力先です。 デフォルトでは、サブプロセスは標準出力と標準エラーをパイプに書き込みます。
Javaコードは、Process.getOutputStream() メソッドおよびProcess.getErrorStream() メソッドによって返された入力ストリームを介してこれらのパイプにアクセスできます。
ただし、標準出力と標準エラーは、redirectOutput と redirectError を使用して他の宛先にリダイレクトされます。
この場合、Process.getInputStream() メソッドまたは Process.getErrorStream() メソッドは null 入力ストリームを返します。
- 読み込みメソッドは常に-1を返します
- 使用可能なメソッドは常に0を返します。
- close() メソッドは何もしない。
- redirectErrorStream プロパティ。最初は、このプロパティは false です。
つまり、サブプロセスの標準出力とエラー出力は、2つの別々のストリームに送信され、Process.getInputStream() メソッドとProcess.getErrorStream() メソッドを使用してアクセスできます。
値が true に設定されている場合は、次のようになります。
- 標準エラーは標準出力とマージされ、常に同じ宛先に送信されます。
(これにより、エラーメッセージを対応する出力と簡単に関連付けることができます)
- 標準エラーと標準出力の共通の宛先は、redirectOutput を使用してリダイレクトできます。
- サブプロセスの作成時に redirectError() メソッドで設定されたリダイレクトは無視されます。
- Process.getErrorStream() メソッドから返されるストリームは常に null 入力ストリームになります
プロセスビルダの属性を変更すると、そのオブジェクトの start() メソッドによってその後に開始されたプロセスに影響しますが、以前に開始したプロセスや Java プロセス自体には影響しません。
ほとんどのエラーチェックはstart() メソッドによって実行されます。 start() メソッドが失敗するようにオブジェクトの状態を変更することは可能です。
たとえば、コマンド属性を空のリストに設定しても、start() メソッドが呼び出されない限り例外はスローされません。
このクラスは同期されていないことに注意してください。
複数のスレッドが同時にProcessBuilderインスタンスにアクセスし、少なくとも1つのスレッドが属性の1つを構造的に変更する場合は、外部と同期する必要があります。
ProcessBuilder クラスの API ドキュメントをザックリ調べてみました。
それでは public static List<Process> startPipeline(List<ProcessBuilder> builders) メソッドについて API ドキュメントもザックリ読んでみます。
各ProcessBuilderのプロセスを開始し、標準出力ストリームと標準入力ストリームによってリンクされたプロセスのパイプラインを作成します。
各ProcessBuilderの属性は、各プロセスの開始時に標準出力が次のプロセスの標準入力に導かれることを除いて、それぞれのプロセスを開始するために使用されます。
最初のプロセスの標準入力と最後のプロセスの標準出力のリダイレクトは、それぞれのProcessBuilderのリダイレクト設定を使用して初期化されます。
他のすべてのProcessBuilderリダイレクトはRedirect.PIPEでなければなりません。
中間プロセス間のすべての入出力ストリームにはアクセスできません。
最初のプロセスを除くすべてのプロセスの標準入力は null 出力ストリームです。
最後のプロセスを除くすべてのプロセスの標準出力は null 入力ストリームです。
各 ProcessBuilder のredirectErrorStream は、それぞれのプロセスに適用されます。 true に設定すると、エラーストリームは標準出力と同じストリームに書き込まれます。
いずれかのプロセス起動で例外がスローされると、すべてのプロセスが強制的に破棄されます。
public static List<Process> startPipeline(List<ProcessBuilder> builders) メソッドは、start() メソッドと同じように各 ProcessBuilder で同じチェックを実行します。
新しいプロセスは、command() メソッドによって与えられたコマンドと引数を、directory() メソッドによって与えられた作業ディレクトリ内で environment() メソッドによって与えられたプロセス環境で呼び出します。
このメソッドは、コマンドが有効なオペレーティングシステムコマンドであることをチェックします。
有効なコマンドはシステムによって異なりますが、コマンドは空でない文字列のリストでなければなりません。
一部のオペレーティングシステムでプロセスを開始するには、最小限のシステム依存環境変数セットが必要な場合があります。
その結果、サブプロセスは ProcessBuilder の environment() メソッド内の環境変数設定を超えて追加の環境変数設定を継承することがあります。
セキュリティマネージャが存在する場合、このオブジェクトのコマンド配列の最初のコンポーネントを引数として呼び出された public void checkExec(String cmd) メソッドが呼び出されます。
これにより、SecurityException がスローされることがあります。
オペレーティングシステムのプロセスを開始することは、システムによって大きく異なります。 間違っていることがある多くのものの中には次のものがあります。
- オペレーティングシステムのプログラムファイルが見つかりませんでした。
- プログラムファイルへのアクセスが拒否されました。
- 作業ディレクトリが存在しません。
- コマンド引数に無効な文字(NULなど)があります。
そのような場合、例外がスローされます。 例外の正確な性質はシステムに依存しますが、常にIOExceptionのサブクラスになります。
オペレーティングシステムがプロセスの作成をサポートしていない場合、UnsupportedOperationExceptionがスローされます。
このプロセスビルダを後で変更しても、返されるプロセスには影響しません。
このメソッドの引数は List<ProcessBuilder> で返り値は List<Process> です。対応する ProcessBuilder から開始されます。
これってコマンドをつなぐパイプを可能にしているだけですよね。
では、cat -n /home/yucchi/valhalla/README | head -n 34 | tail -n 11| cowsay -n -f ghostbusters を実行するコードを組んで確認してみましょう。
ちなみに Ubuntu の端末で実行すると下図のようになります。
プログラムのコードは下記のように組んでみました。
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
|
// public static List<Process> startPipeline(List<ProcessBuilder> builders) throws IOException を使ってみる。 System.out.println("\npublic static List<Process> startPipeline(List<ProcessBuilder> builders) throws IOException を使ってみる。"); ProcessBuilder catBuilder = new ProcessBuilder() .command("cat", "-n", "README") .directory(Paths.get("/home/yucchi/valhalla").toFile()); ProcessBuilder headBuilder = new ProcessBuilder() .command("head", "-n", "34"); ProcessBuilder tailBuilder = new ProcessBuilder() .command("tail", "-n", "11"); ProcessBuilder cowsayBuilder = new ProcessBuilder() .command("cowsay", "-n", "-f", "ghostbusters") .redirectOutput(Redirect.INHERIT) .redirectError(Redirect.INHERIT); List<Process> ghostbustersProcesses = ProcessBuilder .startPipeline(List.of(catBuilder, headBuilder, tailBuilder, cowsayBuilder)); CompletableFuture<?>[] ghostbustersFutures = ghostbustersProcesses .stream() .map(Process::onExit) .map(processFuture -> processFuture.thenAccept( process -> { // 子孫プロセス情報を取得 currentProcessHandle.descendants().forEach(descendant -> { System.out.println(" DescendantPID: " + descendant.pid()); System.out.println(" DescendantCommand: " + descendant.info().command().orElse("none.")); }); // パイプラインのプロセスID取得 System.out.println("GhostBustersPID: " + process.pid()); })) .toArray(CompletableFuture<?>[]::new); CompletableFuture .allOf(ghostbustersFutures) .join(); |
これを NetBeans IDE Dev 版 で実行すると下図のようになります。
見事に再現されています。
えっ? cowsay -n -f ghostbusters コマンドでエラーがでるって?
これは標準ではいってないので cowsay でググってインストールしてくださいね!
ちなみに 89行目からの処理は 91 行目にある public CompletableFuture<Process> onExit() メソッドで返されるプロセスのための新しい CompletableFuture <Process> を利用してます。
public CompletableFuture<Process> onExit() メソッドについてザックリ調べておきましょう。
プロセスの終了のためのCompletableFuture <Process> を返します。
CompletableFuture は、プロセス終了時に同期または非同期に実行される可能性のある依存する関数またはアクションをトリガーする機能を提供します。
プロセスが終了すると、CompletableFuture はプロセスの終了ステータスに関係なく完了します。
onExit() メソッドを呼び出すと、get() メソッドはプロセスが終了するのを待ち、プロセスを返します。
Future は、プロセスが完了したかどうかをチェックしたり、プロセスが終了するのを待つために使用できます。
CompletableFutureをキャンセルしても、プロセスには影響しません。
ProcessBuilder.start() メソッドから返されたプロセスは、プロセスの終了を待つための効率的なメカニズムを提供するために、デフォルトの実装をオーバーライドします。
onExit() メソッドを使用すると、追加の同時実行性とプロセス結果への便利なアクセスの両方を可能にする waitFor() メソッドの代替手段となります。
ラムダ式を使用して、プロセス実行の結果を評価することができます。
値が使用される前に他の処理が行われる場合、onExit() メソッドは現在のスレッドを解放し、値が必要な場合にのみブロックするのに便利なメカニズムです。
たとえば、2つのファイルを比較するプロセスを起動し、それらが同一であればブール値を取得します。
|
Process p = new ProcessBuilder("cmp", "f1", "f2").start(); Future<Boolean> identical = p.onExit().thenApply(p1 -> p1.exitValue() == 0); ... if (identical.get()) { ... } |
このプロセスは、ComputableFuture が完了して依存アクションが呼び出される前に、isAlive() メソッドで終了したことが観察されることがあります。
実装要件:
この実装は、正常に戻るまで別のスレッドでwaitFor()を繰り返し実行します。
waitFor() メソッドの実行が中断された場合、スレッドの割り込みステータスは保持されます。
waitFor() メソッドが正常に終了すると、CompletableFuture はプロセスの終了ステータスに関係なく完了します。
多数のプロセスが並行して待機している場合、この実装はスレッドスタックのための多くのメモリを消費する可能性があります。
外部実装はこのメソッドをオーバーライドし、より効率的な実装を提供する必要があります。
たとえば、基になるプロセスに委任するには、次のようにします。
|
public CompletableFuture<Process> onExit() { return delegate.onExit().thenApply(p -> this); } |
以上 API ドキュメントをザックリ読んでみました。
これはいろいろな処理を順序立てて外部プログラムを利用する場合に有効かもしれませんね。(^_^)
このプログラムではただ単に使ってみただけで有効利用はなにもしていません。(^_^;
いろいろゴニョゴニョやってみたけど JDK 9 での ProcessAPI のアップデートは開発者に優しくなりました。
外部プログラムを利用する開発者にとって JDK 9 のリリースは待ち遠しいことでしょう。
最後にプログラム全体のコードを載せておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
package jp.yucchi.java9processapi; import java.io.IOException; import java.lang.ProcessBuilder.Redirect; import java.nio.file.Paths; import java.util.List; import java.util.concurrent.CompletableFuture; /** * * @author yucchi */ public class Java9ProcessAPI { public static void main(String[] args) throws IOException, InterruptedException { // 実行中のプロセスIDとコマンドを取得 System.out.println("\n実行中のプロセスIDとコマンドを取得"); ProcessHandle.allProcesses().forEach(processHandle -> System.out.println("PID: " + processHandle.pid() + " Command: " + processHandle.info().command().orElse("none."))); // 任意のプロセス情報の取得 System.out.println("\n任意のプロセス情報の取得"); final int pid = 2677; // PID: 2677 Command: /usr/lib/x86_64-linux-gnu/deja-dup/deja-dup-monitor ProcessHandle processHandle = ProcessHandle.of(pid).get(); System.out.println("PID: " + processHandle.pid()); System.out.println("Command: " + processHandle.info().command().orElse("none.")); System.out.println("CPU Duration: " + processHandle.info().totalCpuDuration().get().getNano() + " nanoSeconds"); // カレントプロセス情報の取得 System.out.println("\nカレントプロセス情報の取得"); ProcessHandle currentProcessHandle = ProcessHandle.current(); System.out.println("PID: " + currentProcessHandle.pid()); System.out.println("Command: " + currentProcessHandle.info().command().orElse("none.")); System.out.println("CPU Duration: " + currentProcessHandle.info().totalCpuDuration().get().getNano() + " nanoSeconds"); // 子プロセス情報の取得 Process geditProcess1 = new ProcessBuilder("gedit").start(); Process geditProcess2 = new ProcessBuilder("gedit").start(); System.out.println("\n子プロセス情報の取得"); currentProcessHandle.children().forEach(child -> { System.out.println("ChildPID: " + child.pid()); System.out.println("ChildCommand: " + child.info().command().orElse("N/A")); System.out.println("ChildCPU Duration: " + child.info().totalCpuDuration().get().getNano() + " nanoSeconds"); }); // プロセスの終了処理 destroy() System.out.println("\nプロセスの終了処理"); System.out.println("geditProcess1 is running : " + geditProcess1.isAlive()); geditProcess1.destroy(); // いつ終了するかはシステムの都合による // 終了待ち geditProcess1.waitFor(); System.out.println("geditProcess1 is running : " + geditProcess1.isAlive()); // プロセスの強制終了処理 destroyForcibly() System.out.println("geditProcess2 is running : " + geditProcess2.isAlive()); // geditProcess2.destroyForcibly(); // いつ終了するかはシステムの都合による // 終了待ち geditProcess2.destroyForcibly().waitFor(); System.out.println("geditProcess2 is running : " + geditProcess2.isAlive()); // public static List<Process> startPipeline(List<ProcessBuilder> builders) throws IOException を使ってみる。 System.out.println("\npublic static List<Process> startPipeline(List<ProcessBuilder> builders) throws IOException を使ってみる。"); ProcessBuilder catBuilder = new ProcessBuilder() .command("cat", "-n", "README") .directory(Paths.get("/home/yucchi/valhalla").toFile()); ProcessBuilder headBuilder = new ProcessBuilder() .command("head", "-n", "34"); ProcessBuilder tailBuilder = new ProcessBuilder() .command("tail", "-n", "11"); ProcessBuilder cowsayBuilder = new ProcessBuilder() .command("cowsay", "-n", "-f", "ghostbusters") .redirectOutput(Redirect.INHERIT) .redirectError(Redirect.INHERIT); List<Process> ghostbustersProcesses = ProcessBuilder .startPipeline(List.of(catBuilder, headBuilder, tailBuilder, cowsayBuilder)); CompletableFuture<?>[] ghostbustersFutures = ghostbustersProcesses .stream() .map(Process::onExit) .map(processFuture -> processFuture.thenAccept( process -> { // 子孫プロセス情報を取得 currentProcessHandle.descendants().forEach(descendant -> { System.out.println(" DescendantPID: " + descendant.pid()); System.out.println(" DescendantCommand: " + descendant.info().command().orElse("none.")); }); // パイプラインのプロセスID取得 System.out.println("GhostBustersPID: " + process.pid()); })) .toArray(CompletableFuture<?>[]::new); CompletableFuture .allOf(ghostbustersFutures) .join(); } } |
お終い!
Java SE 9 の予習
Java SE 9 の予習 その2
Java SE 9 の予習 その3
Java SE 9 の予習 その4
TAGS: Java |
2017年6月29日11:47 AM |
Java NetBeans
Java SE 9 の予習 その4です。
前回のエントリーの続きでおそらく今回も必要とされない無駄なお遊びとなります。
今回は JShell API を利用してアプリケーション内でスニペットの評価や実行を試してみます。
なお、今回のエントリーには 「Java SE 9のProject Kulla、JShellの動作とJShell API (3/4)(JShell API)」の記事を参考にさせていただきました。
ほとんどプログラムは丸写し状態になっていますので元記事をまず読んで理解してこのお遊びプログラムを堪能していただければ幸いです。
下記プログラムは普通に19行目のコンストラクタ内で JShell.create() メソッドによりオブジェクトを生成します。
スニペットの評価実行は29行目の eval(String snipetSource) メソッドでおこなわれます。(折り畳んでいるので隠れてます。)
( 最後にいろいろゴニョゴニョ遊んだコードを載せますので行番号はずれてしまいますが参考にしてくださいませ。)
List<SnippetEvent> eval(String snipetSource) メソッドで SnippetEvent オブジェクトを取得します。
JavaDoc では 戻り値:この評価によって直接的または間接的に発生したイベントのリスト となっています。
つまり、ステータスや値です。
種類は、jdk.jshell.Snippet クラスの public Kind kind() メソッドで取得します。
戻り値である jdk.jshell.Snippet.Kind は jdk.jshell.Snippet のサブクラスを示します。
jdk.jshell.Snippet.Kind は jdk.jshell.Snippet クラスの public static enum Kind extends Enum<Kind> となっています。
つまり種類は jdk.jshell.Snippet クラスのイミュータブルプロパティです。
jdk.jshell.Snippetクラスには 種類を補足するための public SubKind subKind() メソッドも用意されています。
これは jdk.jshell.Snippet クラスの public static enum SubKind extends Enum<SubKind> を返します。
これはユーザーへのフィードバックに役立ちます。
これは種類の詳細な情報を取得するのに利用されます。
ステータスの取得には jdk.jshell.SnippetEvent クラスの public Status status() メソッドを使用します。
戻り値は jdk.jshell.Snippet クラスの public static enum Status extends Enum<Status> です。
これは JShell ステート内の動的プロパティです。
したがって JShell クエリで取得されます。
ステータスには次の7つの状態があります。
DROPPED:スニペットは、JShell.drop(Snippet)への明示的な呼び出しのために非アクティブです。
NONEXISTENT:スニペットはまだ存在しないため、非アクティブです。
OVERWRITTEN : 新しいスニペットに置き換えられているため、このスニペットは無効です。
RECOVERABLE_DEFINED:スニペットは、(現在のJShell状態のコンテキストで)回復可能な未解決の参照やその他の問題が本体内にある可能性のある宣言スニペットです。
RECOVERABLE_NOT_DEFINED:スニペットは、(現在のJShell状態のコンテキストで)回復可能な未解決の参照やその他の問題がある可能性のある宣言スニペットです。
REJECTED:スニペットは、初期評価時にコンパイルに失敗し、JShell状態のさらなる変更で有効になることができないため、非アクティブです。
VALID:スニペットは有効なスニペット(現在のJShell状態のコンテキスト内)です。
このプログラムではステータスが VALID でなければ jdk.jshell.JShell クラスの public Stream<Diag> diagnostics(Snippet snippet) メソッドでスニペットの最新評価の診断を取得します。
そして jdk.jshell.Diag クラスの public abstract String getMessage(Locale locale) メソッドで指定されたロケールのローカライズされたメッセージを返します。
実際のメッセージは実装依存です。 ロケールがnullの場合、デフォルトのロケールを使用します。
値の取得には、jdk.jshell.SnippetEvent クラスの public String value() メソッドを使います。
戻り値は成功した実行の結果値。
実行されなかった場合、または例外がスローされた場合、値はnullです。
以上、だいたいこんな感じで JShell API を楽しんでいきたいと思います。
動かすスニペットは例の「よく見かけるネタ IntegerCache 問題」です。
これだけだとさみしいので FunctionalInterface を利用した挨拶プログラムも JShell API で動かしてみましょう。
非常にシンプルなスニペットの実行となります。
Import statement、FunctionalInterface、などのステータス、値、種類などの情報を正確に取得できれば OK です。
では、このプログラムの実行結果を見てみましょう。
FunctionalInterface は、ただの Interface として識別されるようです。
IntegerCache 問題 もちゃんと false となっています。
これだと普通すぎて面白くないですよね。
日本ではもうすぐ Java SE 9 がリリースされようという時代に End Of Life となった Java SE 7 でプロフラムを組まなければいけない
涙無くしては語れない非常に過酷な環境に置かれているプログラマが多く存在するらしい。
そこでそういう可愛そうなプログラマのためにコンパイラオプションを JDK7 にし、
IntegerCache 問題で遊ぶために JVM オプションに -XX:AutoBoxCacheMax=128 を設定して実行してみます。
JShell オブジェクトの生成を JShell.create() メソッドから JShell.builder() メソッドに変更します。
コンパイラオプションの設定は
jdk.jshell.JShell.Builder クラスの public Builder compilerOptions(String… options) メソッドと使い設定します。
可変長引数の使い方が独特で思いっきりドツボにはまったのは内緒です。
JVM オプションの設定は
jdk.jshell.JShell.Builder クラスの public Builder remoteVMOptions(String… options) メソッドを使い設定します。
後は入出力の設定です。
これらは連鎖初期化で使用するため Builderインスタンスを返します。
最後に jdk.jshell.JShell.Builder クラスの public JShell build() メソッドで JShell ステートエンジンを構築します。
これはすべてのJShell機能のエントリーポイントです。
これにより、実行のためのリモートプロセスが作成されます。
それではコンパイラオプションと JVM オプションが正しく機能するかプログラムを走らせてみましょう。
うわぁぁ!!
正しく期待通りの動作となったが何故か目から涙がこぼれそうに・・・
IntegerCache 問題の実行結果も true となり正しく JVM オプションが設定されたようです。
これだけで天の邪鬼な私のお遊びは終了です。
ここから先は思い込みでサプライズさせてもらったお話しです。
先のプログラムにコンパイラオプションと JVM オプションを設定しない元の状態に戻して下記スニペットの実行コードを追加してみました。
JShell では import 文を必要としないパッケージが何故か存在します。
java.util.Arrays もその一つだから無事にプログラムは実行されるだろうと思っていました。
((((;゜Д゜))))))) なんと! ERROR! EXPRESSION [ シンボルを見つけられません・・・ ]
JShell API を利用する場合はおもてなしは無しなのか!?
しかたないから import 文を追記してみた。
今度は期待通りの動作で実行された。
ついでだから enum も試してみた。
ちゃんと enum 認識していた。
実は Linux 環境の NetBeans IDE だからちゃんと日本語対応されているのですが
Windows 環境だと日本語は文字化けしてしまいます。
セレブデベロッパーご愛用の IntelliJ IDEA (2017.2 EAP)でも Windows環境での JShellAPI で日本語は文字化けしてしまう。
これは Java SE 9 リリースまでに修正されるのだろうか?
ちなみに Windows のコマンドプロンプトからの実行は文字化けしません。
以上、JShell API を無駄に使って遊んでみたエントリーでした。
お終い!
おっと!JShell API と戯れた記念コードを載せておきますね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
package jp.yucchi.jshellapiexample; import java.util.List; import java.util.Locale; import jdk.jshell.ErroneousSnippet; import jdk.jshell.ExpressionSnippet; import jdk.jshell.JShell; import jdk.jshell.MethodSnippet; import jdk.jshell.Snippet; import jdk.jshell.Snippet.Status; import jdk.jshell.SnippetEvent; import jdk.jshell.TypeDeclSnippet; import jdk.jshell.VarSnippet; public class JShellAPIExample { private final JShell shell; public JShellAPIExample() { shell = JShell.create(); // shell = JShell.builder() // .compilerOptions("-source", "1.7", "-target", "1.7", // "-bootclasspath", "/home/yucchi/Sun/jdk1.7.0_80/jre/lib/rt.jar", // "-extdirs", "") // .remoteVMOptions("-XX:AutoBoxCacheMax=128") // .in(System.in) // .err(System.err) // .out(System.out) // .build(); } public void close() { if (shell != null) { shell.close(); } } public void eval(String snippetSource) { System.out.println(snippetSource); List<SnippetEvent> se = shell.eval(snippetSource); se.forEach(e -> { if (e.causeSnippet() == null) { Snippet snippet = e.snippet(); switch (snippet.kind()) { case IMPORT: switch (snippet.subKind()) { case SINGLE_TYPE_IMPORT_SUBKIND: System.out.println("<< Single-Type-Import Declaration. >>"); break; case SINGLE_STATIC_IMPORT_SUBKIND: System.out.println("<< Single-Static-Import Declaration. >>"); break; case TYPE_IMPORT_ON_DEMAND_SUBKIND: System.out.println("<< Type-Import-on-Demand Declaration. >>"); break; case STATIC_IMPORT_ON_DEMAND_SUBKIND: System.out.println("<< Static-Import-on-Demand Declaration. >>"); break; default: break; } break; case ERRONEOUS: System.out.println("ERROR! " + ((ErroneousSnippet) snippet).probableKind() + ":"); break; case TYPE_DECL: TypeDeclSnippet type = (TypeDeclSnippet) snippet; System.out.print("<< Created "); switch (snippet.subKind()) { case CLASS_SUBKIND: System.out.print("Class: "); break; case INTERFACE_SUBKIND: System.out.print("Interface: "); break; case ENUM_SUBKIND: System.out.print("Enum: "); break; case ANNOTATION_TYPE_SUBKIND: System.out.print("Annotation: "); break; default: break; } System.out.println(type.name() + " >>"); break; case METHOD: MethodSnippet method = (MethodSnippet) snippet; System.out.println("<< Created Method: " + method.name() + "(" + method.parameterTypes() + ")" + " >>"); break; case VAR: switch (snippet.subKind()) { case VAR_DECLARATION_SUBKIND: System.out.println("<< A variable declaration without initializer. >>"); System.out.println(((VarSnippet) snippet).name() + " ==> " + e.value()); break; case TEMP_VAR_EXPRESSION_SUBKIND: System.out.println("<< An expression whose value has been stored in a temporary variable. >>"); System.out.println(((VarSnippet) snippet).name() + " ==> " + e.value()); break; case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND: System.out.println("<< A variable declaration with an initializer expression. >>"); System.out.println(((VarSnippet) snippet).name() + " ==> " + e.value()); break; default: break; } break; case EXPRESSION: switch (snippet.subKind()) { case VAR_VALUE_SUBKIND: System.out.println("<< A simple variable reference expression. >>"); System.out.println(((ExpressionSnippet) snippet).name() + " ==> " + e.value()); break; case ASSIGNMENT_SUBKIND: System.out.println("<< An assignment expression. >>"); System.out.println(((ExpressionSnippet) snippet).name() + " ==> " + e.value()); break; default: break; } break; case STATEMENT: System.out.println("<< " + "Statement: " + e.snippet() + " >>"); break; default: System.out.println("<< " + e.getClass().getCanonicalName() + " >>"); break; } if (e.status() != Status.VALID) { this.shell.diagnostics(e.snippet()).forEach(i -> { System.out.println("[ " + i.getMessage(Locale.getDefault()) + " ]"); }); } } }); } public static void main(String[] args) throws Exception { JShellAPIExample example = new JShellAPIExample(); example.eval("import java.time.LocalTime;"); example.eval("@FunctionalInterface \n" + "interface Greeting<T, R> {\n" + "R apply(T time, R message);\n" + "\n" + " default void sayGreeting(String greeting) {\n" + " greeting(greeting);\n" + " }\n" + "\n" + " private void greeting(String greeting) {\n" + " System.out.println(greeting + \" from JShellAPI.\");\n" + " };\n" + "}"); example.eval("LocalTime now = LocalTime.now();"); example.eval("int hour = now.getHour();"); example.eval("Greeting<Integer, String> greeting\n" + " = (time, message) -> 4 <= time && time < 11 ? \"Good morning!\"\n" + " : 11 <= time && time < 18 ? \"Good afternoon!\"\n" + " : \"Good evening!\";"); example.eval("String message = null;"); example.eval("greeting.sayGreeting(greeting.apply(hour, message));"); example.eval(""); System.out.println("(○・ω・)ノ----------------------------------------------------------)\n"); example.eval("Integer x = 128;"); example.eval("Integer y = 128;"); example.eval("void equivalenceTest(Integer x, Integer y) { System.out.println(x == y); }"); example.eval("equivalenceTest(x, y);"); System.out.println("(○・ω・)ノ----------------------------------------------------------)\n"); example.eval("import java.util.Arrays;"); // 省略不可 example.eval("String str;"); example.eval("str = \"Java\";"); example.eval("String[] elems = str.split(\"\");"); example.eval("System.out.println(Arrays.toString(elems));"); example.eval("Arrays.stream(elems).forEach(System.out::println);"); System.out.println("(○・ω・)ノ----------------------------------------------------------)\n"); example.eval("public enum JavaPlusYou {\n" + "\n" + " Japanese(\"あなたとJAVA,今すぐダウンロード\"),\n" + " English(\"JAVA+YOU, DOWNLOAD TODAY!\");\n" + "\n" + " private final String downloadNow;\n" + "\n" + " private JavaPlusYou(final String downloadNow) {\n" + " this.downloadNow = downloadNow;\n" + " }\n" + "\n" + " public String getDownloadNow() {\n" + " return downloadNow;\n" + " }\n" + "\n" + "}"); example.eval("for (JavaPlusYou jpy : JavaPlusYou.values()) {\n" + " System.out.println(jpy + \": \" + jpy.getDownloadNow());\n" + " }"); System.out.println("(○・ω・)ノ----------------------------------------------------------)\n"); example.eval("System.out.println(System.getProperty(\"java.vm.version\"));"); example.eval("System.out.println(System.getProperty(\"java.class.version\"));"); System.out.println("(○・ω・)ノ----------------------------------------------------------)\n"); example.close(); } } |
実行結果は次のようになります。
run:
import java.time.LocalTime;
<< Single-Type-Import Declaration. >>
@FunctionalInterface
interface Greeting<T, R> {
R apply(T time, R message);
default void sayGreeting(String greeting) {
greeting(greeting);
}
private void greeting(String greeting) {
System.out.println(greeting + ” from JShellAPI.”);
};
}
<< Created Interface: Greeting >>
LocalTime now = LocalTime.now();
<< A variable declaration with an initializer expression. >>
now ==> 17:28:24.474604
int hour = now.getHour();
<< A variable declaration with an initializer expression. >>
hour ==> 17
Greeting<Integer, String> greeting
= (time, message) -> 4 <= time && time < 11 ? “Good morning!”
: 11 <= time && time < 18 ? “Good afternoon!”
: “Good evening!”;
<< A variable declaration with an initializer expression. >>
greeting ==> $Lambda$17/555826066@a67c67e
String message = null;
<< A variable declaration with an initializer expression. >>
message ==> null
greeting.sayGreeting(greeting.apply(hour, message));
Good afternoon! from JShellAPI.
<< Statement: Snippet:StatementKey#7-greeting.sayGreeting(greeting.apply(hour, message)); >>
(○・ω・)ノ———————————————————-)
Integer x = 128;
<< A variable declaration with an initializer expression. >>
x ==> 128
Integer y = 128;
<< A variable declaration with an initializer expression. >>
y ==> 128
void equivalenceTest(Integer x, Integer y) { System.out.println(x == y); }
<< Created Method: equivalenceTest(Integer,Integer) >>
equivalenceTest(x, y);
false
<< Statement: Snippet:StatementKey#11-equivalenceTest(x, y); >>
(○・ω・)ノ———————————————————-)
import java.util.Arrays;
<< Single-Type-Import Declaration. >>
String str;
<< A variable declaration without initializer. >>
str ==> null
str = “Java”;
<< An assignment expression. >>
str ==> “Java”
String[] elems = str.split(“”);
<< A variable declaration with an initializer expression. >>
elems ==> String[4] { “J”, “a”, “v”, “a” }
System.out.println(Arrays.toString(elems));
[J, a, v, a]
<< Statement: Snippet:StatementKey#16-System.out.println(Arrays.toString(elems)); >>
Arrays.stream(elems).forEach(System.out::println);
J
a
v
a
<< Statement: Snippet:StatementKey#17-Arrays.stream(elems).forEach(System.out::println); >>
(○・ω・)ノ———————————————————-)
public enum JavaPlusYou {
Japanese(“あなたとJAVA,今すぐダウンロード”),
English(“JAVA+YOU, DOWNLOAD TODAY!”);
private final String downloadNow;
private JavaPlusYou(final String downloadNow) {
this.downloadNow = downloadNow;
}
public String getDownloadNow() {
return downloadNow;
}
}
<< Created Enum: JavaPlusYou >>
for (JavaPlusYou jpy : JavaPlusYou.values()) {
System.out.println(jpy + “: ” + jpy.getDownloadNow());
}
Japanese: あなたとJAVA,今すぐダウンロード
English: JAVA+YOU, DOWNLOAD TODAY!
<< Statement: Snippet:StatementKey#19-for (JavaPlusYou jpy : JavaPlusYou.values()) {
System.out.println(jpy + “: ” + jpy.getDownloadNow());
} >>
(○・ω・)ノ———————————————————-)
System.out.println(System.getProperty(“java.vm.version”));
9-ea+169
<< Statement: Snippet:StatementKey#20-System.out.println(System.getProperty(“java.vm.version”)); >>
System.out.println(System.getProperty(“java.class.version”));
53.0
<< Statement: Snippet:StatementKey#21-System.out.println(System.getProperty(“java.class.version”)); >>
(○・ω・)ノ———————————————————-)
ビルド成功(合計時間: 4秒)
TAGS: Java,NetBeans |
2017年5月21日8:52 PM |
« 古い記事
新しい記事 »