はじめに
- 対象とする仕様書は、Java8の仕様書です
- 個人的メモのため基本的な内容については記載していない。あくまで私が書き留めておきたいと思った点のメモです
- 以下、仕様書を読んでいく中で気づいた点や、初めて知った点についてまとめる
きっかけ
- 3年ほどJavaメインのバックエンドエンジニアとして働いているが、Javaの仕様書を改めて読んだことがなかった
- 一次資料を読むことで正確な理解につながるのではと思ったから
- Java開発を始めた初期は参考書やJava Silver資格の取得を通してJavaの基礎について学んだが、それから数年経った今、基礎の再確認や、改めて基礎を学ぶことで新たな発見があるのではと思ったから
全て読んでみての感想
- 当たり前だが、基本的な内容も多く含まれるため、既知の内容も多く、新しい発見はそこまで多くはない
- 英語をそのまま読めないし、ブラウザの翻訳も見やすいとは言えず読むのに難儀した
- 途中から章ごとにAIを用いて内容をまとめさせていたが、正確でない内容を含んでいたり、網羅性に課題があったりと、本来の趣旨である正確な理解とはちょっと外れたものになってしまった感が否めない
- 結論、日本語訳の参考書を読めばよく、無理して原文にこだわる必要はないのではという気がした
- ただし、JVM仕様書など参考書が少なそうなものや、最新バージョンのJavaの最新情報をキャッチアップするためなら原文を読む必要はありそう
- 型推論については奥深そうなので別途書籍等で勉強したほうが良さそうと感じた
以下、仕様書内容のメモ
3. Lexical Structure
- JavaプログラムはUnicodeを用いて記述
- UTF-16エンコーディングを用いる(UTF:Unicode Transformation Format)
- 内部的にはUTF16だが、Java9以降はソースコードはUTF-8がデフォルトらしい
-
\\\u005a:\zとして解釈されるが、\\u005aはUnicodeエスケープとして解釈されない- UnicodeはASCIIとの後方互換性がある(ASCIIのA=10進数で65=16進数の41=Unicodeのu0041)
- トークンはプログラムの最小単位であり、左右の概念 = ソースコード上の視覚的位置ではなく、トークンストリーム内での順序である
- この概念は、例えば「右オペランド」のような、用語を理解する際の基本になる
- javaの空白文字(WhiteSpace)は、ASCIIの以下と対応する:
- the ASCII SP character, also known as "space"
- the ASCII HT character, also known as "horizontal tab"
- the ASCII FF character, also known as "form feed"
- コメント:
//or/** **/ - 識別子は、 Java 文字とJava 数字の長さ無制限のシーケンスであり、その最初の文字は Java 文字でなければなりません
- 「Java 文字」とは、
Character.isJavaIdentifierStart(int)がtrueを返す文字 - 「Java の文字または数字」は、
Character.isJavaIdentifierPart(int)がtrueを返す文字
- 「Java 文字」とは、
- リテラル
- 整数リテラル
- javaの整数リテラルとして、2,8,10,16進数が利用可能
- intとして定義可能な整数リテラルの例
0 2 0372 0xDada_Cafe 1996 0x00_FF__00_FF
- 浮動小数展リテラル(float(7桁),double(15-17桁))
- 浮動小数点リテラルは10進数または16進数で表現可能
- 型はfloatまたはdouble(デフォルトはdouble)
- 2.5e-3fは2.5*10(-3)のfloat型を示す。eは自然対数ではない
- IEEE 754標準に準拠した32ビット(float)・64ビット(double)形式
- アンダースコアを桁区切りとして使用可能
- Boolean
- Charactor
-
'で囲まれる - UTF-16で利用可能な範囲のみを使用可能(
\u0000to\uffff) - charリテラルの例:
'a','%','\t','\\','\'','\u03a9','\uFFFF','\177','™'- トークン化(リテラルの解釈)より、Unicodeエスケープ変換の方が早いタイミングで行われるため、LFとかは
'\u000a'のようにUnicodeで表現することはできない。なぜなら改行変換により,'しか行内に表示されずコンパイルエラーになるためである。そのため\nのようなエスケープシーケンスを利用すること
- トークン化(リテラルの解釈)より、Unicodeエスケープ変換の方が早いタイミングで行われるため、LFとかは
-
- String
-
""で囲まれる - charと同様LF等のUnicodeエスケープは使用不可
- String Intern Pool:同じ内容の文字列リテラルは同じオブジェクトを参照
- Stringの同一性の例
-
-
hello == "Hello"→ true
理由: 同じクラス内の文字列リテラル
動作: 両方ともString Poolの同じオブジェクトを参照 -
Other.hello == hello→ true
理由: 同じパッケージ内の異なるクラス
動作: パッケージが違ってもString Poolは共有 -
other.Other.hello == hello→ true
理由: 異なるパッケージでも文字列リテラルは共有
動作: JVM全体でString Poolは共通 -
hello == ("Hel"+"lo")→ true
理由: コンパイル時定数式
動作: コンパイル時に"Hello"として計算され、リテラル扱い -
hello == ("Hel"+lo)→ false
理由: 実行時文字列結合
動作: 新しいStringオブジェクトが作成される -
hello == ("Hel"+lo).intern()→ true
理由: intern()でString Poolから取得
動作: 既存のリテラルと同じオブジェクトを返す
-
-
-
- Nullリテラル
- 整数リテラル
4. Types, Values, and Variables
- プリミティブ型
- boolean,byte,short,int,long,char,float,double
- 演算時に数値拡張によりよりビット数が多い型に自動変換される
- 浮動小数点型は、「IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-1985」標準に従う
- 参照型(Reference Type)
- クラス型、インターフェース型、配列型
- Objectクラスのメソッドを利用可能
- 文字列リテラルはStringオブジェクトとして表現される
- オブジェクトとは、クラスインスタンスまたは配列
- オブジェクトの生成
- クラスインスタンス作成式
- 配列作成式・配列初期化式
-
+文字列連携演算子("Java " + VERSION(定数)→コンパイル時に定数プールにオブジェクト作成される。"Java " + name(変数)の場合、実行時に新しいオブジェクトが作られる) - ボクシング変換
- オブジェクトの生成
- 型変数
- クラスの型変数と、メソッドの型変数を利用可能
-
<T extends Number>で上限境界を設定(TはNumberまたはそのサブクラス) -
<? super Integer>で下限境界を設定(Tは Integerまたはそのスーパークラス)
- 型消去
- コンパイル時に残っている型情報が実行時に失われる
- 型消去により、ジェネリクスのメソッドは実行時にObject型になるが、継承関係を保つためにコンパイラが自動的にブリッジメソッドを生成
// コンパイル時 List<String> stringList = new ArrayList<String>(); // 実行時(型消去後) List stringList = new ArrayList(); // 生の型 - パラメータ化型とジェネリック型の違い
- ?はジェネリクスの型変数と違い、その場限りの利用しかできない
// ジェネリック型(型変数を含む宣言)
public class List<E> { ... }
public interface Map<K, V> { ... }
// パラメータ化型(具体的な型引数を指定)
List<String> // E = String
Map<String, Integer> // K = String, V = Integer
List<?> unknownList = new ArrayList<String>(); //これもパラメータ化型
- PECS原則とは
Producer Extends, Consumer Superの略で、ワイルドカードの使い分けの指針。これにより型安全性を維持しつつ、ジェネリクスを適切に利用できる
Producer(生産者):データを読み取る場合 →? extends
Consumer(消費者):データを書き込む場合 →? super - Reifiable Types(具現化可能型)
- 実行時に完全な型情報が利用可能な型のこと。型消去の影響を受けない型を指します
- 具現化可能型の例
- プリミティブ型
- 非ジェネリック参照型
- 無制限ワイルドカード型(?)
- Raw Types
- 要素型が具現化可能な配列型
- 具現化可能型の例
- 実行時に完全な型情報が利用可能な型のこと。型消去の影響を受けない型を指します
- Raw Types
- ジェネリック型から型引数を省略した型
-
List<String>に対して、Listはrow typesである - raw typesの場合は、型変数の情報は失われる
-
- ジェネリック型から型引数を省略した型
- Intersection Types(交差型)
- 複数の型の共通部分を表現する型。主に型変数の境界で使用される
- 使用例
public class IntersectionExample<T extends Serializable & Comparable<T>>- T は Serializable かつ Comparable を実装することが保障される
- Subtyping(サブタイピング)
- 型の階層関係を定義する概念。型Sが型Tのサブタイプである場合、S型の値をT型として使用できる
- ジェネリクスのサブタイピング
List<String> strings = new ArrayList<>(); List<Object> objects; // これはエラー:List<String> は List<Object> のサブタイプではない // objects = strings; // コンパイルエラー // ワイルドカードで解決 List<? extends Object> extendsList = strings; // OK List<? super String> superList = new ArrayList<Object>(); // OK- 配列型のサブタイピング
- 配列は共変のため、そのままサブタイプの配列を代入可能(多次元配列の場合も同様)
- Least Upper Bound(最小上界)
- 複数の型の共通のスーパータイプの中で最も具体的な型です。型推論で重要な役割を果たします
5. Conversions and Contexts
- 型変換の種類
- 1 同一変換(Identity Conversion)
- 型から同じ型への変換
- 2 拡大プリミティブ変換
- byte → short → int → long → float → double
- char → int → long → float → double
- 情報を失わない安全な変換だが、int/long → float/doubleの変換の場合は精度が失われる場合がある
- 3 縮小プリミティブ変換(Narrowing Primitive Conversion)
- 情報が失われる可能性のある変換。明示的なキャストが必要
- 4 拡大・縮小プリミティブ変換
- byte/short → char
- 5 拡大参照変換(Widening Reference Conversion)
- サブタイプからスーパータイプへの安全な変換
- 6 縮小参照変換(Narrowing Reference Conversion)
- スーパータイプからサブタイプへの変換
- 7 ボクシング変換(Boxing Conversion)
- プリミティブ型からラッパー型への変換
- 8 アンボクシング変換(Unboxing Conversion)
- ラッパー型からプリミティブ型への変換
- 9 未チェック変換(Unchecked Conversion)
- 生の型からパラメータ化型への変換
- コンパイル警告しか起こらず実行時エラーとなる場合があるため注意
- 10 キャプチャ変換(Capture Conversion)
- ワイルドカード型の内部処理で使用される変換
- 11.文字列変換(String Conversion)
- 任意の型からStringへの変換
- 1 同一変換(Identity Conversion)
- 変換コンテキストの種類
- 代入コンテキスト
- 呼び出しコンテキスト
- 文字列コンテキスト
- キャストコンテキスト
- 数値コンテキスト
6. Names
- protected型は、サブクラスでprotectedフィールドが特定の型として保証される場合のみアクセス可能
-
*を用いたimport(オンデマンドインポート)によるクラス名の競合について- 優先順位:
- 現パッケージ内で宣言された型(同じ
.javaファイル内) - 単一型インポート
- 現パッケージ内で宣言された型
- オンデマンドインポート
- 現パッケージ内で宣言された型(同じ
- オンデマンドインポートが複数ある場合など、曖昧な場合はコンパイルエラーになる
- 優先順位:
- 内部クラスは外部クラスを暗黙的に継承する
- 静的コンテキストでは型パラメータの使用不可
7. package
- コンパイル単位は、事前定義されたパッケージ
java.langで宣言されたすべての public 型名を暗黙的にインポートします -
package-info.javaは、パッケージレベルの情報を格納するための特別なJavaファイルです。通常のクラスファイルとは異なる- 従来の
package.htmlの代替 - Javadoc生成時のパッケージ説明として使用
- 記載例
// パッケージレベルアノテーションの定義 /** * このパッケージはユーティリティクラスを提供します。 * @author 開発者名 * @version 1.0 * @since 2023 */ @Deprecated @SuppressWarnings("all") package com.example.utils; // import分を定義 import org.springframework.stereotype.Repository; - 従来の
8 classes
- strictfpm修飾子を付与されたクラス
- 修飾子の効果は、クラス宣言内のstrictfpすべてのfloatまたはdouble 式(変数初期化子、インスタンス初期化子、静的初期化子、およびコンストラクタ内を含む)を明示的に FP 厳密(§15.4)にすることです
- FP厳密とは:浮動小数点演算の結果を完全に統一する仕組み。IEEE 754標準に厳密に準拠しプラットフォーム間での完全な互換性を保証。科学計算や金融システムで特に重要
- クラスメンバーの定義
- フィールド、メソッド、メンバークラス、メンバーインターフェース(継承したものも含む)
- コンストラクタ(§8.8)、静的初期化子(§8.7)、およびインスタンス初期化子(§8.6)はメンバーではない
- static宣言されているメンバークラス=静的メンバークラス
- メンバーインターフェースは暗黙的にstatic
- 継承される対象
- クラス本体でprivateとして宣言されていないすべてのメンバー
- フィールド
- static宣言されたフィールドはクラス変数と呼び、クラスのインスタンスに紐づけられていない
- blank final: 宣言時に初期化されていないfinalフィールドのこと
- コンストラクタの完了前に初期化が必要
- transient: オブジェクトの永続化状態の一部ではないことを示す。シリアライズ時に保存されない(シリアライズの対象外となる)
- volatile: 異なるスレッドによってアクセスされた際に、常に最新の値となることが保証される
-
final volatileは宣言できない
-
- 初期化子(変数宣言の後の=(値))はインスタンス作成時に実行される(宣言順序が後続の変数へのアクセスはコンパイルエラーになる)詳細は8.3.3参照
- メソッド
- メソッドの仮パラメータ最後(引数の最後の要素)には、可変長パラメータ(...)を指定できる
-
public static void printNumbers(String message, int... numbers)- 下記のように可変長引数パラメータが非具現化要素型(ジェネリクス型)を持つ場合、警告が発生する
public static void problematicMethod(List<String>... lists) {` // 内部的には `List<String>[]` として扱われる // しかし実行時には `List[]` になってしまう(型情報消去)- レシーバパラメータ:メソッドが所属するオブジェクト自身を示すパラメータ
public void myMethod(@MyAnnotation MyClass this)- 使い所の例(実際はほぼ使われていないらしい。使うとしても静的解析ライブラリとか)
// レシーバパラメータにアノテーションを付けたメソッド // このメソッドは初期化済みのオブジェクトでのみ呼び出されるべき public void initializationRequiredMethod(@Initialized ReceiverParameterExample this) { System.out.println("初期化済みオブジェクトのメソッド: " + this.data); // 外部の型チェッカーがあれば、初期化されていないオブジェクトでの // 呼び出しを検出できる }- オーバーロード等価シグネチャはコンパイルエラーになる
- オーバーロード等価シグネチャとは、戻り値と引数が等しい場合(片方がabstractであってもエラーになる)
- nativeメソッド
- C等の別のプログラミング言語で書かれたプラットフォーム依存のコードで実装される
- 宣言例
public native void open(String name, boolean writeable) throws IOException;
- C等の別のプログラミング言語で書かれたプラットフォーム依存のコードで実装される
- strictfp メソッド
- メソッド内すべての浮動小数点演算をFP厳密にする
- synchronized メソッド
- 実行前にモニターを取得する
- モニターとは、javaオブジェクト単位での排他制御のメカニズムであり、同時に1オブジェクトのみが取得できる鍵のようなもの。これによりスレッド間同期が実現されている
- 同期処理の実現のためには、すべての関連メソッドをsyncronize宣言する必要がある
- 実行前にモニターを取得する
- オーバーライドに関する制約について(詳細は割愛)
- もし、子クラスに独自のメソッドがない、かつ親クラスから具象メソッドを継承している場合、インターフェースの抽象/デフォルトメソッドは無視して、親クラスの具象メソッドが用いられる
- クラスの本体は、メンバー(フィールド、メソッド、ネストされたクラスおよびインターフェース)、インスタンスおよび静的イニシャライザ、およびコンストラクタを含むことができる
- メソッド名はオーバーライド可能
- クラス定義にジェネリクスを含む時、これらのパラメータ化された型はすべて、実行時に同じクラスを共有します。→コンパイル後に型変数が消されるから?
- ジェネリッククラスがThrowable(§11.1.1)の直接的または間接的なサブクラスである場合、コンパイル時エラーとなる
- インナークラス
- インナークラスは、明示的または暗黙的にstaticと宣言されていないネストされたクラス
- 非staticメンバークラス、ローカルクラス、または匿名クラスのいずれか
- ローカルクラス
- メソッド、コンストラクタ、イニシャライザ内で定義されたクラス
- 字句的なスコープ
- インナークラスは、それを囲むクラスの字句的なスコープ(lexical scope)内に存在します。したがって、囲むクラスのインスタンス変数やメソッドに、特別な修飾子なしでアクセスできます
- インナークラスは、定数以外のstaticフィールドを宣言できない
- インナークラス(特に ローカルクラス や匿名クラス)は、外側メソッドのローカル変数を使えます。ただし、その変数は final または実質的に final(変更されない) である必要があります。
- インスタンス変数は変更可(ヒープに保持されているため)
- インナークラスであれアウタークラスであれ値変更しようとするとコンパイルエラーになる。(インナークラスで使われた時点でキャプチャされ実質finalになるため)
- インナークラスは、明示的または暗黙的にstaticと宣言されていないネストされたクラス
- インターフェースのデフォルトメソッドはサブクラスでオーバーライド可能
- ダイヤモンド継承関係にあるとき、メソッドが重複かつシグネチャが完全一致しているとき、両方を実装していると見做される。そうでない場合コンパイルエラーになる
- enum
- シングルトンを保証
-
java.lang.Enumクラスをスーパークラスに持つ - インナークラスは暗黙的にstatic
- クラス本体を持つenum定数((enum constant)enumの各定数の後に{}をつけ、クラス定義を書いたもの)以外のenumクラスは暗黙的にfinal
- enum定数→引数リストやクラス本体を持てる
9 インターフェース
- インターフェースが他のインターフェースを拡張→extendsを用いる
- ジェネリクスは明示的に型を指定する必要がある
- インターフェースが持てるメンバー
- 定数、抽象メソッド、デフォルトメソッド,静的メソッド
- フィールドは暗黙的暗黙的にpublic、static、finalの修飾子をもつ
- クラスや,インターフェースを内包できる
-
@Interfaceを使ってアノテーション型を宣言できる
アノテーション型
- defaultをもてる
- アノテーション
- normal: 複数引数をもつ
- marker: 引数を持たない
- single-element: 単一引数を持つ(引数名は省略可能)
- どこでアノテーションが利用できるのか、
@Targetによる指定が必須 -
@Repeatableアノテーションが付与されている場合を除き、同じアノテーションを複数回使用できない - 引数にはデフォルト値を指定できる
@interface Inner { String name(); int count() default 1; // 定数式 Inner info() default @Inner() // アノテーションも可 }- 引数として使用できないもの: パラメータ化クラスリテラル(
?),多次元配列,null - 実際にどこまで保持されるかは
@Retention(SOURCE/CLASS/RUNTIME)に依存
functional interface
- ラムダ式/メソッド参照のターゲット型になれる「抽象メソッドをちょうど1つ持つ(SAM)」インターフェース
- 例:Runnableは
FunnctionalIF:Runnable r = () -> System.out.println("run"); - 例:
Function<String, Integer> p = Integer::parseInt;
- 例:Runnableは
- アノテーション
@FunctionalInterfaceは必須ではないが、付与するとコンパイラが「関数型」の要件を満たすか検査してくれる
@FunctionalInterface
interface Logger {
void log(String msg); // 唯一の抽象メソッド
default void info(String m) { log("[INFO] " + m); }
static Logger nop() { return s -> {}; }
boolean equals(Object o); // Object相当 → カウントしない
}
- 交差型(例: Serializable & Runnable)も「実質1つの抽象メソッド」に収束すれば関数型として扱える
Serializable & Runnable r = (Serializable & Runnable) ( () -> System.out.println("x") );
Function Types
- 関数型インターフェースを設計する際は、複数の抽象メソッドが「事実上一致して1つに収束する」かを常に意識(交差/継承で増えがち)
- ジェネリクスと raw/ワイルドカードが混じると関数型の「最も具体的な型」選択が発生する。利用側はできるだけ具体的に型を与え、曖昧なら明示キャスト/型引数指定で解決
// 例1
interface A { void m(java.util.List<?> x); }
interface B { void m(java.util.List<String> x); }
// 多くの場合、これを継承統合すると name clash(同名・同erasure・非subsignature)でエラー
interface AB extends A, B {} // コンパイルエラーになりがち
// 従ってラムダのターゲットにもできない
// 例2
interface A { java.util.List<String> foo(java.util.List<String> arg) throws java.io.IOException; }
interface C { java.util.List foo(java.util.List arg) throws Exception; }
// A と C を同時に継承すると、関数型は (List)->List に落ちる(raw化)方向になり得る
interface E extends A, C {} // 関数型: (List)->List throws EOFException, SQLTransientException(JLS例参照)
-
signature(erasure(m.A())) == signature(m.B())なら、関数型IFの統一が可能(どちらか一方が成立でも可)、そうでない場合はコンパイルエラーに
11. 例外
- 例外の種類と原因
- throw句の実行
- JVMのエラー(ゼロ除算等)
- 非同期例外(超レア)
-
Thread.stop(およびThreadGroup.stop)を他スレッドから呼ばれたとき(ThreadDeathが実行) - JVMの内部エラーやリソース限界で Java のセマンティクスを維持できないとき(VirtualMachineError(例: OutOfMemoryError, StackOverflowError)等)
- 非同期でない場合でも起きうる。復旧不能
- 握りつぶして実行継続は期待しない例外
-
- 実装上は、JVMは「即時」ではなく、例外を安全に投げられるポイント(制御移譲や分岐などのタイミング=セーフポイント)で検知・スローする。このため「少し遅れて」投げられるが、遅延には上限があり、最終的には必ず検知される。その間、JITは制御転送の間で命令順序を最適化できる(性能のため)
- コンパイル時例外チェック
- チェック対象は「チェック例外(checked)」のみ。実行時例外(RuntimeException系)やError系はチェックしない
- メソッド/コンストラクタ本体で発生し得る全てのチェック例外は、その場でcatchするかその宣言にthrowsで列挙すること
- 実行時の例外処理
- try/catch が例外で中断しても finally は必ず実行される
- finally 自身が別の例外で中断した場合、元の例外は破棄され、新しい例外が伝播する(マスク)
- 例外でスレッドがブロックを抜ける際、該当のモニタは必ず解放される(synchronized文/メソッド)
12. 実行
JVMの起動
- JVMは指定クラスの
public static void main(String[] args)を呼び出すところから開始 - ホスト環境(例: コマンドライン)では完全修飾名でクラスを指定し、その後の引数がargsに渡る
- ステップ1: クラスのロード(Load)
- 最初にmainを持つクラス(例: Test)のバイナリ表現をクラスローダで読み込む
- 見つからなければエラー(ロードエラー)
- 定義ローダ(defining loader): 実際にその型を defineClass 等で「定義」したクラスローダ
- 同一型の同一性は「バイナリ名 + 定義ローダ」で決まる(定義ローダが違えば別型)
- 定義ローダは、通常JVM単位でブートストラップローダ(
java.base等基盤モジュール)・プラットフォームローダ・アプリケーションローダが作成される。FWにより追加のクラスローダが作成されることもある
- 定義ローダは、通常JVM単位でブートストラップローダ(
- すべての配列クラスは Object を直接のスーパークラスとし、Cloneable と Serializable を実装
- 解決タイミングは実装自由だが、エラーは当該参照を使う時点で必ず報告される
- クラスローダ境界が型同一性・パッケージ可視性に影響するため、ローダ設計が重要
- ステップ2: リンク(Link = 検証・準備・(任意)解決)
- 検証(Verify): クラスファイルが正当で、言語/JVMの要件に合致するか検証。問題があればエラー
- 準備(Prepare): 静的領域やVM内部構造(メソッドテーブル等)を割り当て
- 解決(Resolve・任意時期): 参照している他クラス/インターフェースを実体化して参照を確定
- 実装依存で「早期に一括解決」も「使用時に遅延解決」も可。エラーは当該参照を使う時点で投げられる
- ステップ3: 初期化(Initialize)
- クラスの静的フィールド初期化子やstatic初期化子をテキスト順に実行
- その前にスーパークラス連鎖を上位から順に初期化(Objectまで遡る)
- 初期化中に、必要な他クラスのロード/リンク/初期化が連鎖的に発生し得る
- クラスの初期化は、必ずその「直接のスーパークラス」を先に初期化してから行う(再帰的に Object まで)
- インターフェースを初期化しても、その「スーパーインターフェース」は自動では初期化されない(必要になった時点まで遅延)
- ステップ4: main実行
- 上記を完了後、mainを呼び出してプログラム実行が開始
- finalization
- 終了時処理を記述。別スレッドで実行される
- 近年は非推奨の設計とされ、代替(try-with-resources/Cleaner/AutoCloseable)推奨
13. バイナリ互換性
- 開発ツールは「ソースがあるなら必要に応じて自動再コンパイルしてね」という前提がある。でも、広く配布したライブラリ/クラスは利用者側の再コンパイルが現実的でない。だから「再コンパイルなしで、既存バイナリ(.class)が動き続ける変更=バイナリ互換」を仕様で定義する、という話
- 既存クライアントのバイトコードをそのままに、ライブラリ側だけを差し替えてもリンクエラーにならない変更の範囲を明確化
- 具体的な対策の例
- 旧APIを残す(推奨)
- インターフェース拡張は default メソッドで
- 戻り値・型パラメータは「消去(erasure)」を壊さない
- serialVersionUID固定によるシリアライズ互換
- クラス、メソッド種別ごとの互換性に関する注意事項が列挙されている
14.ブロックと文
- あまり意識してなかった構文
- ラベル付き文
- ラベル付き文は、文に識別子を付けることでbreakやcontinue文から特定の文を参照できるようにする機能
- 商用では基本使わない方が無難
searchLoop: for (String[] row : matrix) { for (String cell : row) { if (cell.equals(target)) { break searchLoop; // 検索ループ全体から脱出 } } } - ラベル付き文は、文に識別子を付けることでbreakやcontinue文から特定の文を参照できるようにする機能
- ローカルクラス
- assert文
- アプリケーションでassertionが有効になっている場合しか評価されない
-
-enableassertionsまたは-eaオプションで制御(デフォルトは無効) - 使い所の例
- https://qiita.com/SP-ya/items/a97e58aaf44badf91114
-
- アプリケーションでassertionが有効になっている場合しか評価されない
- ラベル付き文
-
try-with-resource分は、指定したリソースに対し、(resource).closeを暗黙的に呼び出す
15.式(Expressions)
-
式の評価結果は以下の3つのいずれか:
- 変数(variable - C言語でいう左辺値(lvalue)
- 値(value) - プリミティブ型またはオブジェクト参照
- 何もない(void)-
voidメソッドの呼び出しの場合
-
式のカテゴリ
- 式名(Expression names) - §6.5.6
- 一次式(Primary expressions) - §15.8-§15.13
- 単項演算子式(Unary operator expressions) - §15.14-§15.16
- 二項演算子式(Binary operator expressions) - §15.17-§15.24, §15.26
- 三項演算子式(Ternary operator expressions) - §15.25
- ラムダ式(Lambda expressions) - §15.27
-
Poly Expression(多相式)とは:型がコンテキストから推論される式
-
括弧付き式・クラスインスタンス生成式・メソッド呼び出し式・メソッド参照式・条件式・ラムダ式は多層式になる可能性がある
17. 同期実行
-
すべてのJavaオブジェクトにモニターが関連付けられている
-
スレッドはモニターをロック/アンロックできる
-
一度に1つのスレッドのみがモニターをロック可能
-
同じスレッドが複数回ロックできる(再入可能)
-
Thread.sleep(): 指定時間スリープ(モニターは保持したまま) -
Thread.yield(): 他のスレッドに実行機会を譲る(ヒントのみ) -
共有変数:ヒープ上のインスタンスフィールド、static フィールド、配列要素
-
ローカル変数はスレッドごとに独立
-
アクション:read, write, lock, unlock, volatile read/write など
-
happens-before関係
ルール名 内容 プログラム順序 同一スレッド内では、プログラム順序に従う モニタールール あるモニターのアンロックは、その後の同じモニターのロックより前 volatileルール volatile変数への書き込みは、その後の読み取りより前 スレッド開始 Thread.start() は、開始されたスレッドの全アクションより前 スレッド終了 スレッドの全アクションは、そのスレッドのjoin()より前 推移性 A happens-before B かつ B happens-before C なら、A happens-before C -
コンストラクタ内でfinalフィールドが設定された後、他のスレッドがそのオブジェクトを見る場合、正しい値が見える
-
finalの凍結(freeze): コンストラクタの終了時に発生
-
注意点
-
waitはループ内で使う
-
volatileはアトミック性を保証しない:書き込みのアトミック性を保証するには
AtomicIntegerを用いる -
notify vs notifyAll の選択:notify()は、putを待っているスレッドかtakeを待っているスレッドのどちらかを起こす。間違った種類のスレッドを起こすと、デッドロックの可能性。notifyAllを用いるのが安全
-
volitaileとsyncronizedの違い
- volatileは可視性だけ、synchronizedは可視性とアトミック性の両方を保証
特性 volatile synchronized 可視性 ✅ 保証する ✅ 保証する アトミック性 ❌ 保証しない ✅ 保証する 順序保証 ✅ 部分的に保証 ✅ 完全に保証 ロック 不要 必要 パフォーマンス 高速 低速
-
18 型推論
- 型推論は以下の3プロセスに分解される
- Reduction: 削減
- Incorporation: 統合
- Resolution: 解決
- Java8からPoly Expression(=型がコンテキストから決まる)をサポート。型推論の遅延評価により方推論の幅が広がった
- Bidirectional Type Checking Conflict(双方向型チェックの競合)(最近の型システム研究での用語。)
- Synthesis(合成): 式から型を推論(ボトムアップ)
- Checking(検査): ターゲット型に対して検証(トップダウン)
etc..