入門 Java言語仕様を読もう! with Java Puzzlers
2025年6月7日(土) JJUG CCC 2025 Spring お疲れさまでした。登録者は1000人を超えたようで、会場もなかなかの人手だったかと思います。
タイムテーブルを見るとよく分かりますが、8トラックも並行しているのでどのセッションを見ようか悩ましかったのではないでしょうか。
今回の私のセッションは「入門 Java言語仕様を読もう! with Java Puzzlers」という題目で、Java言語仕様を読むための基礎の話と、Java PuzzlersというJava言語仕様にまつわるクイズの出題、関連するJava言語仕様の解説、という内容でした。
JJUGのセッションはどうも雰囲気が硬くなりがちなので、聞き手にゆとりさんを迎えて対話形式で進めました。資料部分は事前に共有していましたが、問題部分は見せていないので、新鮮なリアクションをいただけて良かったと思います。
Java言語仕様を1次資料として参照してくれる人が増えると嬉しいな。
書籍 Java Puzzlers (2005年 ジョシュア・ブロック, ニール・ガフター)は絶版ですが、運が良ければ中古で安く入手できるかも? 今回はこの書籍からの改題したものを中心に出題しました。
枕の話の部分だけまとめた資料を別途公開します。問題とその解説はスライドでは断片的なのでこのblogでやります。
Q1 拡張forでEnumerationのasIterator()を回そうとしたらどうなるか?
Q1 のポイントはfor文です。 e::asIterator とメソッド参照でfor文を回そうとしています。
import java.util.*; public class Q01 { public static void main(String[] args) { Enumeration<String> e = Collections.enumeration( List.of("Hello", "JJUG", "2025")); for (String s : e::asIterator) { System.out.print(s); } } }
選択肢は
- HelloJJUG2025 が表示される
- コンパイルエラー
- その他
Q1の解説
正解は 2.コンパイルエラー。メソッド参照の登場できる場所には制約があります。
It is a compile-time error if a method reference expression occurs in a program in someplace other than an assignment context (§5.2), an invocation context (§5.3), or a casting context (§5.5).
代入、メソッドやコンストラクタの呼び出し、キャスト以外に現れたメソッド参照はコンパイルエラーとなります
§15.13. Method Reference Expressions
これは過去のblog Enumeration を for-each ループする方法 が元ネタ。
言語仕様のバッカス・ナウア記法(BNF : Backus–Naur form)の解説をやるために、拡張for文がらみの問題をチョイスしました。
拡張for文のBNFは §14.14.2. The enhanced for statement に書かれていて、右辺はExpressionとなっています。ここから追いかけて行って「メソッド参照がない」ことを確信するのはちょっと大変かもしれない。
blog側に解説していますが、キャストを書けばコンパイルが通ります。
Q2 突然の URL
import java.util.*; import java.net.http.*; public class Q02 { public static void main(String[] args) throws Exception { HttpClient httpClient = HttpClient.newHttpClient(); URI https = URI.create("https://www.google.com"); HttpRequest request = HttpRequest.newBuilder(https).build(); HttpResponse<String> response = httpClient.send( request, HttpResponse.BodyHandlers.ofString()); https://www.google.com System.out.println(response.body()); } }
- 正常終了
- コンパイルエラー
- その他
コード中に現れる https:// の文字……。果たして。また、httpsという名前の変数も宣言しているのですが、影響するのか否か?
Q2 解説
これは一部界隈では有名なネタで……(なんの界隈かと言われるとアレなんですが)Javaの構文的に https: 部分がラベルで、//より後ろが1行コメントとなって合法になります。正常終了……と言いたいところですが、セッション中はネットワーク不調で実行時エラーが出るというはハプニングが。URLにちなんでネットワーク使うコードになんてしなければ良かった……。
これはJava Puzzlers より パズル22 を改題。書籍Java Puzzlersは2005年の本なので http://www.google.com としてましたが、時代に合わせて https としました。
今回はちょっとひねっていて、変数 https を用意していて、名前が被っている時はどうなの?をネタを知っている人には問うているわけですね。
There is no restriction against using the same identifier as a label and as the name of a package, class, interface, method, field, parameter, or local variable.
同じ識別子をラベル名と、パッケージ、クラス、インターフェース、メソッド、フィールド、パラメータ、またはローカル変数名として使用することに制限はありません
§14.7. Labeled Statements
とわざわざ記載があって、ラベル名は変数名と被っていても問題ありません。
Q3 i != i をtrueにせよ
public class Q03 { public static void main(String[] args) { int i = 0; if ( i != i ) { System.out.println("Hello, JJUG 2025!"); } } }
このコードの int i = 0; 部分を書き換えてif文内を動くようにしてください
Q3 解説
これも書籍Java Puzzlersよりパズル29 を改題。想定解としては Float.NaN ないし Double.NaN を使います。
public class Q03 { public static void main(String[] args) { float i = Float.NaN; if ( i != i ) { System.out.println("Hello, JJUG 2025!"); } } }
intで 0/0 をやるとjava.lang.ArithmeticExceptionとなりますが、浮動小数点数ではNaN (Not a Number、非数)となります。要するに「解なし」みたいな感じですね。
0.0/0.0 の他、Math.sqrt(-1) や 10.0 % 0などでNaNが発生しますが、これらは同値ではないですよね? そのためNaN同士の==比較はfalseになりますし、!=比較はtrueになるように設計されています。言語仕様上は以下の通り。
The equality operator == returns false if either operand is NaN.
The inequality operator != returns true if either operand is NaN.等価演算子==は、 いずれかのオペランドが NaN の場合にfalseを返します
§4.2.3. Floating-Point Types and Values
不等号演算子 != は、どちらかのオペランドが NaN の場合にtrueを返します
あるいは §15.21.1. Numerical Equality Operators == and != を参照してみてください。
Q4 long と int の足し算
public class Q04 { public static void main(String[] args) { System.out.println( Long.toHexString(0x1_0000_0000L + 0xcafebabe)); } }
選択肢は
- 1cafebabe が出力される
- コンパイルエラー
- その他
Q4 解説
ポイントは0x1_0000_0000L がlongで 0xcafebabe がintというところ。intがワイドニングされてlongになるのだけども、ここで0xcafebabeは最上位ビットが立っているのでマイナス値(2の補数表現)で0xffff_ffff_cafe_babe L とされてからlong同士の足し算がされるので結果がcafebabeとなってしまいます。つまり正解は3のその他です。
言語仕様には以下のような記載があり
A widening conversion of a signed integer value to an integral type T simply sign-extends the two's-complement representation of the integer value to fill the wider format.
符号付き整数値を整数型Tに拡大変換すると 、整数値の 2の補数表現が単純に符号拡張され、より広い形式が満たされます
§5.1.2. Widening Primitive Conversion
2の補数表現で符号拡張される、つまりintのマイナス値はlongのマイナス値になるわけです。
Q5 int最小値
public class Q05 { public static void main(String[] args) { System.out.println(-2147483648); } } // ※ 2147483648 = 2^31
選択肢は
- -2147483648 が出力される
- コンパイルエラー
- その他
Q5 解説
Q5は後続の問いへの枕になっていて、これはそのまま素直に動くというひっかけ問題。正解は1の-2147483648 が出力されるです。
壇上からは会場で聞いている人々の表情が良く見えるのだけど、だんだん疑心暗鬼になってきたところで普通の問題を出したので割とみんな間違えましたね。自分の脳内コンパイラ脳内JVMが信じれなくなってくるのがJava Puzzlersの醍醐味ですね。
言語仕様解説はまとめて後ろで。
Q5-2
public class Q05_2 { public static void main(String[] args) { System.out.println(-(2147483648)); } } // ※ 2147483648 = 2^31
選択肢は
- -2147483648 が出力される
- コンパイルエラー
- その他
先ほどのQ5に対して()が付け加えられました。さてどうなるでしょうか?
Q5-2 解説
Java Puzzlers より パズル86 を改題。Q5は普通に-2147483648 が出力されました、では -(2147483648) はどうですか?というのが本来の問い。そんな、カッコつけたからって……と思いきや、これはコンパイルエラー。
もう一問見てから解説をしましょう。
Q5-3
public class Q05_3 { public static void main(String[] args) { System.out.println(-(0x8000_0000)); } } // ※ 2147483648 = 2^31 = 0x8000_0000
今度は16進数表記でカッコ付きです。さてどうなるでしょうか。
- -2147483648 が出力される
- コンパイルエラー
- その他
Q5-3 解説
これは正常動作して-2147483648 が出力されます。さてどういうことなのでしょう?
It is a compile-time error if the decimal literal 2147483648 appears anywhere other than as the operand of the unary minus operator; or if a decimal literal of type int is larger than 2147483648 (2^31).
10進リテラル2147483648が単項マイナス演算子のオペランド以外の場所に出現した場合、またはint型の10進リテラルが2147483648(2^31)より大きい場合は、コンパイル時エラーになります。
§3.10.1. Integer Literals
とピンポイントに2147483648(2^31)についての記載があるのが面白ろポイントで、これは 2の補数表現の関係上、int最大値は 2147483647 (2^31-1)で、マイナスは-2147483648(-2^31)と絶対値が1大きい。
そして、-2147483648は構文上はマイナス値のリテラルではなくて、"-"の単項演算子と数値リテラル2147483648になっており、単項演算子"-"の後ろだけ特別扱いで2147483648が許されるという特例になってるんですね。
そしてこれは10進リテラルの場合限定なので16進数表記の0x8000_0000はセーフ。Q5の -2147483648 は正常に動いて、Q5-2の -(2147483648) はコンパイルエラーになるというわけです。
Q6 finallyでreturn
public class Q06 { public static void main(String[] argos) { System.out.println(decision()); } static boolean decision() { try { return true; } finally { return false; } } }
try で return しつつ、 finally でも returnしています。さてどうなるでしょうか。
- true が出力される
- false が出力される
- コンパイルエラー
- その他
Q6 解説
Java Puzzlers より パズル36 を微修正。この問題は比較的成果率が高かった気がします。finally節でreturnすると値が上書きされる感じですね。正解は 2のfalseです。
言語仕様的にはちょっと独特な表現になっていて
If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S.
§14.20.2. Execution of try-finally and try-catch-finally
finallyブロックが理由S により突然完了した 場合、try文も理由 Sにより突然完了します
突然完了というのは、要するに順次処理して抜けるんじゃなくて、例外が出る、ないしreturnする、の両方を共通的に仕様として記述しているのかな、と。
finally節がreturn false で完了すると、try節もreturn false で完了、というわけですね。
Q7 発生しない例外をcatch
public class Q07 { public static void main(String[] args) throws Exception { try { System.out.println("Hello, JJUG 2025!"); } catch (java.io.IOException e) { System.out.println("起きないIO例外"); } } }
発生しない IOExceptionをcatchしています。どうなるでしょうか。
- 正常終了
- コンパイルエラー
- その他
Q7 解説
tryで発生しない例外をcatchする場合、catch節が到達不能になるんですね。これは 2のコンパイルエラーになります。Javaは到達不能なコードは検出できる限りはコンパイル時にエラーにする方向性なので、そこに寄せた仕様なのかなと思います。詳しい解説は後続の問題の後で。
Q7-2 発生しない java.lang.Exceptionをcatch
public class Q07_2 { public static void main(String[] args) throws Exception { try { System.out.println("Hello, JJUG 2025!"); } catch (Exception e) { System.out.println("起きない例外"); } } }
今度は例外が java.lang.Exceptionになりました。
- 正常終了
- コンパイルエラー
- その他
Q7-2 解説
IOExceptionがtryでthrowされないのにcatch(IOException ioe) とするとこれはコンパイルエラー。でも、Q7-2でcatch(Exception e)とするとこれは通ります。正解は1です。
java.lang.Exceptionは特例になっていて、これは java.lang.RuntimeException が extends java.lang.Exception であるという継承階層の不都合がこのあたりの仕様をややこしくしているのでしょう。
該当言語仕様は次のQ8の後に合わせて解説します。
Q8
import java.io.*; public class Q08 { public static void main(String[] args) throws Exception { try { throwIOException(); } catch (FileNotFoundException e) { System.out.println("ファイルがないケース"); } } static void throwIOException() throws IOException { throw new IOException(); } }
選択肢は
- 正常にIOExceptionがthrowされて終了
- コンパイルエラー
- その他
Q8 解説
Q7, Q7-2, Q8では例外のcatchの型の制約にまつわるパズルでした。Q8の正解は1でコンパイルエラーにはなりません。
It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception.
catch節が検査例外E1をキャッチする場合に、対応するtry節がE1のサブクラスまたはスーパークラスの検査例外をthrowできない場合は、コンパイルエラーとなる。
§11.2.3. Exception Checking
ただし、E1がjava.lang.Exceptionの場合、もしくはjava.lang.Exceptionのスーパークラスの場合を除く。
ここで、catch(E1 e1)の場合を仮定しているわけですが、try節でE1がthrowされる、あるいはE1のサブクラスがthrowされるケースは分かると思います。E1のスーパークラスの場合も大丈夫なのはどういうことでしょう?
Q8ではtryで throws IOExceptionなメソッドを呼び出していて、catchはサブクラスのFileNotFoundExceptionなんですね。FileNotFoundException以外のIOExceptionないしそのサブクラスはcatchされずにthrows Exception な mainメソッドを例外で抜けていく。
catchしきらないけど、漏れたものはthrowされるのでセーフ。そしてthrows IOExceptionなメソッドであれば、具象型がFileNotFoundExceptionな例外を投げるかもしれないので、到達可能性的にもセーフ、そういう扱いになります。
これが《対応するtry節がE1のサブクラスまたはスーパークラスの検査例外》の意味合いです。
続く!
さて、問題は全部で15問用意してたのですが、解説が長くなってきたのでいったん区切ります。続きは後編で!
推敲のロジック
技術記事とかを書くとき、推敲をする。僕がどういうロジックで推敲をしているかの話。
前提知識
想定読者をまず決める。この記事を読むには前提知識としてこのぐらいは知っていてね、のライン。例えばJavaの記事を書くなら基礎的な構文は分かってる前提とする、とか、マニアックな記事なら、もっと深い知識を持っている前提とする、とか。一般向けの記事だと高校までで習うような前提知識があれば良いとするとかとか。
これは、前提知識の部分なら解説なしに「知ってるよね」の前提で話をする。知ってるかどうか危うい時はちょっと注釈をつける。知らない想定の部分は丁寧に解説をする。そのラインを決めるのが想定読者の前提知識。
知識は前提知識の上に積みあがる。積み重ねていく積み木のようなもので、この記事は既にここまで積んである人に対して、追加で何個の積み木を積ませよう、みたいな目標を定める。
記事内での時系列と積み木
記事と言うのは上から下に読まれる。方向があり、読者は上から下に読む。下から読んでも構わないがそういう読者は対象外なのでそういう読者への配慮はしない。
上から順に読んだ場合、記事の中でも知識が積みあがっていく。一段落目で説明したことは二段落目で使っても良い。一段落目で積み木を積んだから、二段落ではその上に何かを積んでも良い。
でも、逆はダメ。
プログラミングで言えば、宣言せずに初期化せずにいきなり変数を使うようなもので、これはコンパイルエラーにならなくてはいけない。自然言語はコンパイルできないのでしょうがないから自分で推敲してエラーとして処理しなければならない。
推敲のロジック
技術記事であることを説明しようとする場合、この前提知識の積み上げを意識する。
枕の話として、Aを解説し、Bを解説し、AとBが揃えばCを解説でき、Cを前提知識としたDについて語れる、といった具合に。
文章の上から下に向かって順に積んでいくようになっているか?唐突に未解説の概念を投入していないか?
こうしたことをチェックするためには、自分が知っていることを「知らない体」で読み返す必要がある。自分が書いた文章なら当然自分は結末まで知っている。知っているのだけど知らないことにして、ここでこのAの概念が出てきた、ここで読者はAを知る、ここでCの概念が出てきた。おや?Cの前提のBの解説がされていないな?などとやる。
この宣言順の前後チェックをしっかりやる。そのうえで、それらの前提知識で自然な論理展開にできているか?などを考える。前提知識の積み木が正しく積みあがっているか?を検証するだけでも文章はずっと良くなる。
雑誌記事などはかなり気合を入れて推敲をする。ブログ記事は割と適当。Twitterなんかは思い付きで散文を書いている。
条件演算子の二項目と三項目が違う型でも動くことがあるやつ
昨日の発表資料です(資料?https://t.co/GKPhtBKprx #kanjava
— Toy (@mdstoy) 2024年7月31日
関ジャバ'24 7月度 - connpass のショートセッションで出てきたネタ、「条件演算子の二項目と三項目が違う型でも動くことがあるやつ」の言語仕様解説。
この挙動はなぜなのか?
§15.25. Conditional Operator ? :
いわゆる三項演算子 ? : の型の仕様についてはJava言語仕様の §15.25. Conditional Operator ? : を読み解く必要がある。
第二項と第三項の型の組み合わせでいろいろあるのだけども、組み合わせによりどういう型になるのかという表が掲載されている。例の場合は"Fizz"がString型であるが、これはまるっとObject型という扱いになって Table 15.25-A. Conditional expression type (Primitive 3rd operand, Part I) より、第二項がObject型で第三項がint型の場合、lub(Object,Integer) 型になると分かる。
この lub(Object,Integer) とは何かということなのだけど、
§4.10.4. Least Upper Bound に記載があって参照型の集合の最小上限(least upper bound = lub)のことである。ふたつの参照型の一番具体的な共通のスーパークラスと思ってもらえば良い。
lub(Object,Integer) とあるけども、上記例ではこのObjectの部分はString型だった。ここではString型とInteger型の共通のスーパークラスと考えてもらえば良くて、方便で言えばこれはObject型ということになる。
これで System.out.println(Object) が大丈夫で、String str = (i % 3 == 0 ? "Fizz" : i); はダメな理由が分かるだろう。String型の変数にObject型を代入しようとしたらダメなわけである。
なお、System.out はPrintStream型なのでどんなメソッドがあるかは
PrintStream型のjavadoc を参照すると良い。
正確な型
先ほど方便で言えばObject型であると言った。正確なところはどうなのか?
これは Object & Comparable<?> & Constable & ConstantDesc & Serializable型 となる。
この & がつく型は §4.9. Intersection Types で解説されており、日本語だと交差型などと呼ばれる。Javaの型システムはclassの単一継承、interfaceの複数実装が可能となっており、ジェネリクスの境界などの指定では&を用いてこのclassを継承しつつ、このinterfaceを実装している型変数 T みたいな指定をすることができる。
残念ながら通常の変数宣言では交差型での変数宣言はできないが、メソッドスコープの型変数を型推論させたりvarを用いたりすると明示的ではないが交差型を用いることができる。
ここで String型を確認してみよう。javadocを見ると
すべての実装されたインタフェース:
と書かれている。
Integerの方も確認してみよう。
すべての実装されたインタフェース:
この共通部分を括りだすと Object & Comparable<?> & Constable & ConstantDesc & Serializable型 になるというわけである。
細かいことはさておき、System.out.println(Object) に食わせることができる。
まとめ
正式名称がわからないJavaの仕様も 好き 好き 大好き
参考
lub の推論が気になる方はこの記事がおすすめ
リファクタリングの事象の地平線
これは経験則なのですが、ある程度を超えたスパゲッティコードはリファクタリングを試みても状況が改善しなくなります。
— なぎせ ゆうき (@nagise) 2023年6月27日
ブラックホールから光が脱出できない事象の地平線になぞらえ、これをソフトウェア品質における事象の地平線と言い(ません
リファクタリングの事象の地平線という比喩について解説をしておきます。
前提知識として リファクタリングの価値の考察 - プログラマーの脳みそ での議論を参照していただけるとありがたいです。
事象の地平線とは
事象の地平線というのは天文学用語でシュバルツシルト面などとも呼ばれます。超大雑把にいえば、ブラックホールのまわりのここから中に入ると光でさえも出ることはできないぞ、というラインです。
つまり、比喩としてはこの中に落ち込むと二度と脱出できないぞ、ということです。リファクタリングの力ではどうやってもソフトウェアの保守性を上げられない状態に陥って脱出できないという主張です。
リファクタリングのおさらい
リファクタリングの原義について再確認しておきましょう。バイブルとされるマーチン・ファウラー著「リファクタリング」では次のように書かれています。
リファクタリングとは、ソフトウェアの外部的振る舞いを保ったままで、内部の構造を改善していく作業を指します。
原義としてリファクタリングは外部的な振る舞い、つまるところ挙動を変えてはいけません。ソフトウェアというものは、プログラムというものは、同じような挙動をするものであっても様々な書き方ができるものです。その多数の選択肢の中から、いまのコードとは別の選択肢に切り替えるという捉え方もできるでしょう。より現在の状況に即したコードに切り替える。
単にコードを書き直すことは「リライト」です。外的振舞いを保って初めて「リファクタリング」と言える。そうした「リライト」と「リファクタリング」を本稿では区別します。
リファクタリングをする動機は、単にコードが稚拙だったからとは限りません。ソフトウェアの機能拡張をしたいとなったとき、今まではなんら問題なかったソースコードが、突如、都合の悪いものになってしまうことがあります。そもそも機能拡張というのは今まではなかった話ですから、その存在しなかった拡張に備えた都合の良い設計になっていないことが往々にしてある。もし未来が予め見えていたなら、より都合の良い選択肢を取ることができたのに。そうした後悔を取り返すことがリファクタリングの動機のひとつとしてあります。
リファクタリングと自動テスト
書籍リファクタリングでは第4章にて自動テストについて解説がされています。JUnitについて取り上げられていますが、しかし、書籍リファクタリングではJUnitのテストコードを前提にリファクタリングの作業が書かれているわけではありません。
これは多分に時代背景によるもので、JUnitによる自動テストがまだ十分な市民権を得てはいなかったことが挙げられるでしょう。しかし、現代にリファクタリングをやるのであれば、JUnitなど自動テストツールによる補助を使わない手はない。およそ「必須」のツールと認識されているのではないでしょうか。
大きなステップ
なぜリファクタリングによって保守性を上げられなくなるのか。その主張の中心にあるのは「大きなステップ」です。
そもそもリファクタリングは「小さなステップ」で行います。書籍リファクタリングはIDEによる機械的なリファクタリングのサポートのない時代の想定で書かれており(原書は1999年。第14章にわずかばかりリファクタリングツールについて記載があります)手動でリファクタリングを行うためのかなり細かい手順が記載されています。
本来、リファクタリングというのは小さなステップで動作確認を行いながら慎重に進めるものなのです。そうでないと「外部的振る舞いを保」てないからです。現在の状況から、外部的振る舞いを保った最寄りの飛び石に飛び乗るように進めていきます。
データ構造や、クラス構造といったものや、根底のアーキテクチャのようなところに手を加えようとすると、外部的振る舞いを保った最寄りの飛び石が、とてもジャンプして届くような位置に存在しなくなることがあります。「ここに手を加えるには、参照箇所1000箇所を一斉に変更するしかないな」ということが生じます。こうした状況を「小さなステップ」に対して「大きなステップ」「ビッグステップ」などと表現しました。
この「大きなステップ」が人間の能力の限界を超えたとき、それはもはやリファクタリング不能な、改善不能なソースコードであると言えましょう。これが脱出できない事象の地平線とたとえられる代物です。
※この「大きなステップ」「ビッグステップ」という表現は、どこかで見たような気がしていて、私のオリジナルではないつもりなのですが、この語義での利用は出典を探してもうまく見つからないのです。情報があれば教えていただけると幸いです。
リファクタリングのコスト論
リファクタリングのコストとその採算性については リファクタリングの価値の考察 - プログラマーの脳みそ にて詳しく議論しています。端的には、リファクタリングは品質特性の「保守性(maintainability)」に作用し、保守性の価値は、そのビジネス規模に依存するというものです。
一般的に、リファクタリングするべきか、しないべきか、という議論はこうしたコスト論に基づいた判断の話をしているかと思います。本稿の主張はそうではない。
「大きなステップ」がある限界値を超えると、本質的にリファクタリングそのものが不可能になるのだ、という主張です。
判別困難
リファクタリング不可能なソースコードが存在するというのが本稿の論旨なのですが、しかしおそらくソースコードを表面的に見たときに、このソースコードはリファクタリング不可能な状態にあるかどうかは判別困難でしょう。
なので、事前の予測としてリファクタリングにこのぐらいのコストがかかりそうだ(≒労力がかかりそうだ)と見積もって、先に述べたような採算を考慮してGoサインが出ることがあります。しかし忘れないでください。リファクタリング不可能なソースコードというものが存在しうるということを。
もし、事前の予想に反して、リファクタリングが困難であるならば、進捗が思わしくないならば、このことを思い出してください。そして、目の前の「大きなステップ」があまりにも大きいのであれば、諦めて撤退することを考えてください。損切りすることを選択肢として考えてください。もう少し粘ればいける、などと考えると被害は広がるばかりです。顧客からは噓つき呼ばわりされることすらあります。
大きなステップを乗り越えるもの
大きなステップは、その大きさによっては人間には克服不可能なものであると主張しました。もしこれを乗り越える可能性があるとすれば、それは機械によるパワーでしょう。
例えば、大量に参照されている、そして同名の別の変数も存在するような変数のリネームなんてリファクタリングは、およそ昔は不可能でした。しかし、現代のIDEなどの機械補助は、こうした人間には困難なリネームのリファクタリングをいともたやすく行ってくれます。
膨大な量を、漏れなく、正確に処理をするという、およそ人間には不得意な作業ですが、機械がそれを可能にしてくれるかもしれません。現代ではデータ構造や、クラス構造といったものや、根底のアーキテクチャのようなところに手を加えようとするならば、それぞれの箇所で個別に人間の判断を要するため、機械的な一括処理は困難ですが、そうした判断をも未来のAIはうまく処理してくれるかもしれません。
Project Valhalla 2023
2023/03/30 にやったJava仕様勉強会の動画が公開されました。当日はJavaのマスコットDuke風の服で臨みました(どうでもいい裏情報)
セッション資料もアップロードしたので参考にしてください。
いずれも 2023年3月時点での情報です。JEPもドラフト版だったりするので、将来的に変更が入る可能性が高いことをお断りしておきます。本稿では勉強会のセッション内容に加えて、セッション時点で追従できていなかった変更点や、勉強会での指摘を踏まえてフォローアップした内容を含みます。
もしもValhalla世界でJava入門したら
ここでは、Valhalla導入後のJava世界だと入門者視点でどのように変わるのかというアプローチをしています。まず、Javaのデータ型は大きくふたつに分類できて、Identity Objects と Value Objects です。
| nullの使用 | 不変性 | 同値判定 | ||
| Identity Objects | Record | 可 | 不変 | equals |
| Class | 可 | 可変 | equals | |
| Interface | 可 | 可変 | equals | |
| Array | 可 | 可変 | Arrays.deepEquals | |
| Enum* | 可 | 不変 | == | |
| Value Objects | Record | 可 | 不変 | == |
| Class | 可 | 不変 | == | |
| Primitive | 不可 | 不変 | == |
どう違うの?というと割と難しくて、パッと見は大きな違いを感じないかもしれません。
言語的な定義を言ってしまえば、 class宣言で identity class と宣言されていればそのインスタンスは Identity Objects ですし、value class と宣言されていればそのインスタンスは Value Objects です。でも、そんな説明では納得されないことでしょう。
Value Objects はメモリ上に直接展開可能なデータで、メモリレイアウトを効率化することができます。

しかしこれは、あくまでJavaVMの中の話で、プログラミング言語Javaを扱うプログラマー視点で見た場合、オブジェクトの可変性 / 不変性という特性や、同値判定をする際に equalsメソッドを使うか、==演算子を使うかという形でしか実感しにくいものであろうと思います。
Valhallaのスローガンとして出てくる
Codes like a class, works like an int.
クラスのように書き、intのように動く、という標語は、反面、その違いを分かりにくいものとしています。
まあ、ライトユースでは雰囲気でコードを書いてもだいたいいい感じになるんじゃないかな。
プリミティブ型の位置づけ
プリミティブ型は Value Objectsの一部という位置づけになります。
プリミティブ型については JEP 402: Enhanced Primitive Boxing (Preview) にて検討されています。このJEP 402で目を引くのは以下の部分でしょうか。
int i = 12; int iSize = i.SIZE; double iAsDouble = i.doubleValue(); Supplier<String> iSupp = i::toString;
int 型変数に対して、あたかも普通の参照型のようにメソッド呼び出しをしているかのように記述することができます。これは、裏の仕組みとしてはラッパー型のIntegerの該当メソッドを呼ぶような仕組みを想定しているようですが、これにより言語の表面上はプリミティブ型も参照型も同じように扱えるように見えるのではないでしょうか。
まあ演算子で操作できるのはプリミティブ型(と一部のクラス)の特権という感じでしょうけども。
ジェネリクスでも List<int>という記述が可能なように拡張しようとしています。こちらの裏の仕組みは後述します。
JavaVM の大改造
Valhalla後のJavaを表層的に見れば、言語がリファクタリングされて綺麗になったね、めでたしめでたしといったところなのですが、その裏方は地獄です。
Project Valhalla は 2014年に発表されました。Java8がリリースされた後のJavaOne 2014 San Franciscoが舞台です。当時の様子は櫻庭さんの記事を参照してください。
2023年4月現在参照可能な JavaVM の Valhalla での変更点の解説セッションは JVM Language Summit 2019 のものが分かりやすいでしょうか。
2014年に発表されてすでに2023年ですから随分と時間がかかっていますね。たくさんのプロトタイピングとたくさんの教訓から掘り下げているといったことが語られています。
Early Access Release
Valhalla の Java VM のプロトタイプ版がリリースされています。2023年4月現在のバージョンはLW-4 となっています。ダウンロードして動かすこともできますが、主にJava VM側の実証プロトタイプで、プログラミング言語側のUniversal Genericsといった面白い部分は未実装です。jshellも制限があるようなので、機能を確認したい場合はjavacでコンパイルしてjavaで実行した方が良さそうです。
JavaVM のプロトタイピング
Valhalla は既存の JavaVM に対して大きなインパクトの修正を加える必要が生じます。そのため、まずプロトタイプを開発して検証を行いました。最初のプロトタイプが "Q-World" と呼ばれるものです。この"Q"とはValue型だけを表す型のことで、既存のプリミティブ型や参照型とは別に独立のValue型を作るという方針のものでした。これは言わば「ユーザー定義のプリミティブ型を作る」といったアプローチです。
このプロトタイプは機能することはしたのですが、プロトタイプを実装することで問題もまた見えてきました。Q-Worldの問題点として
- プリミティブ型と参照型の分断をさらに大きくする
- 加えて更なるボクシングも必要になる
- 苦痛を伴うマイグレーション
- 苦痛を伴う特殊化。配列は特に痛い
といったものが挙げられています。
そこで方針を転換して作られたプロトタイプが "L-World" というものです。この"L"とは既存の参照型のことで、しばしばスタックトレースなどで見かける LJava/lang/Object といった型表現の先頭の"L"です。つまり、Value型をjava.lang.Objectを継承した型として扱うアプローチです。
- バイトコードは既存の a* を用いる (これはaload, astore といったバイトコードを指しています)
- java.lang.Objectを継承した型として扱う
- Value型の配列は Object[] の継承型として扱う
- プログラム言語のモデルはよりよくなるが、VMへのインパクトはより大きくなる
といったことが先の動画で解説されています。
互換性
Project Valhalla では既存のJavaとの互換性を維持する方針が示されています。この背景については Background: How We Got the Generics We Have という文章があります。これは2004年にリリースされたJava5のときに、いかにして互換性を保ちながらジェネリクスを導入したのか?ということについて書かれています。この文章はとてもエモいと私は思っていて、機会を改めて解説したいところですが、要点を超訳すると
Goal: Gradual migration compatibility
ゴール: 段階的な移行の互換性
ということになります。つまり、バイナリ互換性のない分断された言語仕様追加では、その断絶の前後で、ソースコードになんの変更がなくとも、すべてのクラスを一斉に再コンパイルする「フラグの日」が必要になります。
java.util.ArrayListのようなコアAPIのクラスを変更するならば、世界中全てのJavaコードを一斉に再コンパイルする必要があります。あるいは、Java 1.4 まで用のクラスとして永遠にとどめおく必要があります。
Javaは動的リンク、つまり実行時にJavaVMによって外部のjarファイルが読み込まれたりする動きをしますから、この「フラグの日」というのはより強い嫌悪感をもたらしました。(補足するなら当時はまだJava Appletが活きていましたし、Java VM 間のRMIによる呼び出しのようなものも活きていてネットワークを介して複数のJavaVMが協調動作するような想定も強く存在していました)
そうした20年前の事例を踏まえて、Valhallaでも互換性を保ちながら、徐々に移行する道が選ばれたのです。
Valhalla のJEPs
Project Valhallaの中核となるJEPは4つあります。
- JEP draft: Value Objects (Preview)
- JEP 401: Implicit Value Object Creation (Preview) 旧 User-defined Primitives
- JEP 402: Enhanced Primitive Boxing (Preview)
- JEP draft: Universal Generics (Preview) 旧 Specialized Generics
セッションをやった当日(2023年3月30日)の朝に参照していたJEPのタイトルが変更になるハプニングがありました。本稿執筆時点で再度確認しているのですが、また名前が変わっている……。現時点で活発に更新がされていることの証左でもありましょう。過去の資料を照会する時には名称変更にお気を付けください。
JEP401, JEP402 は既にJEPの番号がついていますが、 Value Objects や Universal Generics はまだドラフト版で正式なJEPが発番されていません。Java VM 部分が固まってきたため、ようやく上モノの言語仕様が進むようになってきたと見るべきでしょうか。
また、関連JEPとして以下のものがValhalla公式ページには挙げられています。
- JEP 181: Nest-Based Access Control (delivered in 11)
- JEP 309: Dynamic Class-File Constants (delivered in 11)
- JEP 334: JVM Constants API (delivered in 12)
- JEP 371: Hidden Classes (delivered in 15)
- JEP 390: Warnings for Value-Based Classes (delivered in 16)
- Better-defined JVM class file validation (draft)
これらはリリース済みのものが多くあります。こうした部分でも徐々に進んでいることが伺えるでしょう。
型の再整理
Valhalla では既存のプリミティブ型と参照型という枠組みを、再整理してJava言語をリファクタリングしようという試みをしています。


この図は The Language Model というBackground Documentsで出てくるのですが、上の図の既存のJavaのデータ型をValhallaでは下の図のようにしますよ、という話です。
先に挙げた表を再掲しますが
| nullの使用 | 不変性 | 同値判定 | ||
| Identity Objects | Record | 可 | 不変 | equals |
| Class | 可 | 可変 | equals | |
| Interface | 可 | 可変 | equals | |
| Array | 可 | 可変 | Arrays.deepEquals | |
| Enum* | 可 | 不変 | == | |
| Value Objects | Record | 可 | 不変 | == |
| Class | 可 | 不変 | == | |
| Primitive | 不可 | 不変 | == |
既存の型を大きく Identity Objects と Value Objects に再分類しようとしています。なお、Enumについては理論的にはValue型にできるものですが、java.lang.Enumを継承する構成上、Identity Objects になっているという注釈がありました。
Record については Identity にも Value にもなれますが、いずれも不変です。フィールドにIdentity を含んでいると Identity にせざるを得ないといった面倒くさい考慮事項があります。
配列は "L-World" では既存の配列の性質を引き継いでいます。配列の仕様にまつわる型システム的な不都合がいろいろとあるのですが、そのあたりは互換を維持する方向で梃入れはしない方針のようです。
プリミティブ型は Value Objects のひとつという位置に据えられ、プリミティブ型と参照型の分断を軽減しようと試みられています。
Value Objects
クラス宣言に value 識別子を加えて宣言するとそのクラスのインスタンスは Value Objects になります。
クラス宣言に identity 識別子を加えて宣言するとそのクラスのインスタンスは Identity Objects になります。(既存の参照型を使いたい場合はこちら)
Value Objectの利点としては == で値比較ができるようになる点が挙げられます。
また、value 宣言した場合の主な制約は
- クラスは暗黙にfinalとなり継承できなくなります
- すべてのフィールドは暗黙にfinalとなり、コンストラクター(ないし初期化子)で1度だけ値を設定しなければなりません
- identityクラスを拡張またはidentity インターフェースを実装して Valueクラスを作ることはできません
- コンストラクターでthisやsuperを使うことはできません。インスタンスの作成は、スーパークラスの初期化コードを実行せずに行われます
- synchronized メソッドを宣言できません
- finalize() メソッドを宣言できません(おそらく)
- コンストラクタではthisを参照できません(例外事項があるがここでは省略)
といったものとなっています。これらは今後、言語仕様を詰めていく段階で変更になる可能性があります。
既存の value ないし identity 識別子のついていないコードは、およそ identity クラスになると思ってよいようです。(例外事例があるようなのですが複雑でよくわかりません……)
このあたりは言語仕様の差分に詳細な記載があります。the specification changes がリンク切れなので少し古い版へのリンクを貼っておきます。 8.1.1.5 identity and value Classes
JEP 401: Implicit Value Object Creation
- JEP 401: Implicit Value Object Creation (Preview) 旧 User-defined Primitives
"Q-World"のような古いValhallaの提案では、ユーザー定義のプリミティブ型を作成できるような提案となっていました。現在の "L-World" になってこの提案は大きく修正されたように思います。
JEP 401 はざっくりいえば、Value型のデフォルト値の動きを規定するようなJEPで、
public implicit Range();
といったように implicit キーワードをつけたデフォルトコンストラクタを記述することで、各フィールドをデフォルト値としたValue型のインスタンスを生成します。(構文はあくまで仮のものです)
また、 Null-Restricted and Nullable Types (Preview) に依存するのですが、nullを許容しない型についての検討があるようです。しかし、このJEPはドラフト 8303099 なのですがリンク切れになっており、おそらく再検討されているのではないかと思います。
JEPは消えていますが、何が書いてあったかというと ValhallaのML に記載があったので紹介しておきます。
'Foo!' refers to a non-null Foo, and 'Foo?' refers to a Foo or null
'Foo!' は nullではないFoo を参照し、'Foo?' は Foo または null を参照します。
Type variable types have nullness, too. Besides 'T!' and 'T?', there's also a "parametric" 'T*' that represents "whatever nullness is provided by the type argument".
型変数の型にも nullness があります。 「T!」のほかに および「T?」、「型引数によって提供される nullness が何であれ」を表す「パラメトリックな」「T*」もあります。
Kotlinの構文に雰囲気は似ているでしょうか。このあたりはおそらく大きく変更が入ることでしょう。あまり期待しすぎずに様子を見ましょう。
JEP 401 でもうひとつ触れられていることは non-atomic についてで、背景を説明するのが難しいのですが、端的に言えばマルチスレッド下で同時にValue型の値を作成するようなケースで、“Atomicity”(原子性/不可分性)を犠牲にしてパフォーマンスを出すオプションを用意しようというテーマです。本稿では深入りしません。
JEP 402: Enhanced Primitive Boxing
プリミティブ型を参照型のようにあつかう言語拡張です。プリミティブ型変数に対して、"."でメソッド呼び出しを行ったり、メソッド参照を用いたりすることができます。
int i = 12; int iSize = i.SIZE; double iAsDouble = i.doubleValue(); Supplier<String> iSupp = i::toString;
これにより、プリミティブ型と参照型の分断を軽減しようという試みのようです。仕組みとしては、ラッパー型のメソッドを呼び出すようです。
また、ジェネリクスの型変数に対してもプリミティブ型を用いれるようにします。この仕組みとしては、int を Integer! 型 にボクシングします。この ! は先に照会した Null-Restricted and Nullable Types (Preview) での null を許容しない型のことです。
この辺は個人的には好きなジャンルなのですが、深入りすると複雑になるので割愛します。
Universal Generics
- JEP draft: Universal Generics (Preview) 旧 Specialized Generics
端的に言えば、Value型と従来の参照型にまたがるユニバーサルなジェネリクスという内容です。
このへんのジャンルは私の好物なのですが、しかし具体的なところに踏み込むととにかく複雑かつマニアックになるので本稿では割愛します。
このJEPドラフトでは Collection<Point.ref> といったnull許容型の型変数を表す記法が暫定的に用いられています。ここでは Null-Restricted and Nullable Types (Preview) が参照されておらず、このあたりの統一的な方向性をどうするかといった部分はまだまだこれからなのでしょう。
null 許容型、非許容型が扱えるようになると型システムマニアの人達は狂喜するところなのですが。
マウンティングの語源調査の仕方
前回、思い出話として
を書いたのだが、その中でネットスラングの「マウンティング」について、初出が
であるということを書いた。この記事は 2008年9月20日で、以下の用例が見られる。
そんなテクニックはいかに相手のニホンザルを押さえ込んでマウンティングを成功させるかというテクニックでしかない。そんなことをやったところで疑問はなんら解決しないし、誤った意見をこうした手法で押し通しでもした日には、後で大きな惨劇が起こりかねない。勝った負けたなどと言っていても議論は進展しないのである。
これは明確なエビデンスで、この時点でこのような用例があった、というのはひとつの道標となる。
ここで、「筆者が発案したというのは本当か?」「より古い用例はないか?」という疑問が生じることだろう。では、そうした疑問が生じたときにどのように対処すればよいか。
書籍での用例
書籍では 瀧波ユカリ・犬山紙子(2014)『女は笑顔で殴りあう : マウンティング女子の実態』筑摩書房 が最古とされており、インターネット上のネットスラングが先行して用いられていることは既知の通りである。この書籍を筆者が読んで着想を得て2008年に上述の記述をするにはタイムマシンが必要になる。
簡易検索結果|「マウンティング」に一致する資料: 4件中1から3件目|国立国会図書館サーチ
国会図書館の検索では出版年別のカテゴリがあるので、古い時代の検索結果を確認していただきたい。上述の2008年のネットスラングより前に先行の用例があれば、それを見て剽窃したのだろうというシナリオもあるいは考えられることになる。
注意しなくてはならないのは、2008年より前に生物学的な「マウンティング」という用語が既にあったことは周知の事実であり前提である。なので、それを人と人との間の関係性として用いた用例を見出さなければならないという点である。
※注 追記参照。より古い用例の情報を頂いた。まさに「マウンティング」というテーマそのものについて記述した本というわけではないが、用例として利用がある本が見つかったというのは特記するべきことだろう。
プログラマーの脳みそでの用例
ブログ「プログラマーの脳みそ」では2008年9月20日の初出からその時期に集中的に利用が見られる。ここでは日付とタイトルのみを挙げる。
- 2008年9月20日
- 2008年9月22日
- 2008年9月23日
- 2008年9月25日 不良がいいことをすると凄くいい奴に見えるメソッド - プログラマーの脳みそ
- 2008年9月26日 人間考の難しさ - プログラマーの脳みそ
思いつきで単一使用したわけでなく、ここで考察がされ人間関係におけるマウンティングという概念を見出したことが伺える。
検索エンジンで古い用例を探す
例えばGoogleだと、「ツール」から検索対象となる年月日を指定することができる。

インターネット上の用例を探す場合はこの機能が便利だが、時折、新しい記事が古い時期の記事と誤認されることがある点に注意。該当サイトは実際に2006年に書かれたものではない。

技術的には、ページを表示しようとHTTPリクエストを送った際の応答にこのコンテンツは何月何日のものです、という日時が入っているのだが、Googleのロボットがサーバーに情報収集に赴いた時に変な値がかえるようになっていたのだと思われる。
ときどきこうした事例もあるので、検索でみつけた各ページを実際に確認して用例としても合致しているかを確認していく。
2010年以前の用例を探すと分かるが、犬のマウンティングや、猿のマウンティングについての用例ばかりで、現代のネットスラングの人が人に対して優位をアピールする行為を指して「マウンティング」を用いている例は見られない。先の2008年の用例ぐらいである。候補を見つけたらぜひブログを書くなりして発表して欲しい。(注:情報をいただいた。追記参照)
2008年以後、初期のネットスラングとしての「マウンティング」の用例としては、2011年7月5日の内田樹氏による 暴言と知性について - 内田樹の研究室 などが挙げられる。
今回彼が辞職することになったのは、政府と自治体の相互的な信頼関係を構築するための場で、彼が「マウンティング」にその有限な資源を優先的に割いたという政治判断の誤りによる。
こうした初期の用例は、ネットスラングとしての「マウンティング」の黎明期を捉える上では貴重な資料となるだろう。
格闘技用語について
現代ではネットスラングの「マウンティング」の類語として「マウントをとる」という言葉がある。こちらの用例はどうか。
格闘技用語としての「マウントをとる」についてはネット上での検索では用例を遡ることが案外難しく、国会図書館の検索ではなかなか用例を見出せない。
2002年の書籍で「マウントポジション」の用例があることはみつけることができた
中井祐樹柔術バイブル (クエスト): 2002|書誌詳細|国立国会図書館サーチ
筆者の調べでは1995年のヒクソン・グレイシーの試合についての記述でマウントポジション、マウントという表現がある。この記述が当時近くに書かれたものなのか、後に書かれたものなのかは少し判別しにくい。
静寂なる戦い【中井祐樹vsヒクソン・グレイシー】 両者は何を想う!?
ちなみに日本のインターネットの普及は1995年末のWindows95発売からで、それ以前の時代のインターネットコンテンツはとても少ない。当時のコンテンツそのものではなくとも、ブログサービス移転などに伴い転記され残されているものである可能性もある。あるいは雑誌など紙媒体などに書かれたものがWeb公開されているようなケースもある。一概に言えないが、1995年当時に格闘技用語として「マウント」という概念があったことは筆者は疑っていない。エビデンスとしての傍証を出すのが難しいという話である。
うろ覚えではあるが、マウントポジションについては川原正敏(1987-1996)『修羅の門』で見た覚えがある。そのぐらいの時期には格闘技用語としての「マウントポジション」「マウントをとる」は認知されていたと言えるのではないか。
格闘技用語からの転用の可能性は?
さて、ここで重要なポイントとして格闘技用語では「マウントポジション」ないし「マウントをとる」といった表現である点。決して「マウンティング」という言い方ではないのだ。マウントポジションをとることを指して「マウンティング」と呼んでいる用例は少なくとも筆者が調査している間には見かけなかった。
ネットスラングの「マウンティング」の語源を格闘技用語の「マウント」とする説は、なぜ「マウント」ではなく「マウンティング」という表現で用いられるのか?という疑問に答えなくてはならない。
筆者の考えでは「マウンティング」という概念がスラングとして広まった際に、格闘技用語由来の「マウントポジション」や「マウントをとる」という表現と混用されるようになったのだとみている。初出の用例は明らかにニホンザルのマウンティングと表現されており、生物学的な用語としての「マウンティング」が由来であることは明らかである。
なんせ、「マウンティング」も格闘技用語の「マウント」も、語源としては当然に英語のmountが由来なのであるから、語形が似ているのも当然だし、混同されやすいのも当然であろう。どちらも上に乗るニュアンスの言葉である。
説を否定するには
以上が筆者の考察結果なのだが、異論がある人もいよう。
古い時代に用例が「ない」ことを証明することは難しく「まだ見つかっていないだけ」の可能性は否定できない。「マウンティング」の発案者がなんらかの既存の表現を剽窃したとするのであれば、その元になったであろう候補を提示しなければ始まらない。
それがみつからないことには、その初出を書いた人が発案者であろうとするのが自然な推定であろう。また、より古い用例が見つかったとして、剽窃があったのか、それぞれ独立に生じた表現なのかは検討が必要である。いずれも、より古い用例を挙げなければいけない。
例えば、2008年以前の「マウンティング」の用例を見つけ出すとか、あるいは2008年以前に格闘技用語の「マウントをとる」という表現を、対人関係で優位をアピールする意味で用いている用例を見つけ出すとか。(注:追記参照。古い用例が見つかった)
ここまで「マウンティング」の発案者は~と、さも他人事のように書いてきたが、当事者主観の証言をするならば、まさにニホンザルのマウンティングに着想してそれを人間関係に転用したものであり、既存の用例を剽窃したものではない。
まあ、私見を言えば独立に誰かがそのような表現を産みだしていても不思議はないと思っている。その時は先行事例こそが初出ということになり、当用例はあくまでも2008年時点でのいち用例という扱いになることだろう。(注:追記参照。古い用例があったようだ)
追記
元記事側ではてブで情報を頂いた
素晴らしい。古い用例を見事に見つけ出して頂いている。
いずれも未読の書である。独立に生じてなんら不思議のない用法であるから、そういう先行事例は筆者が見つけれられていないだけでありそうだなーと思っていた。謎がひとつとけて嬉しい。
筆者の用例はせいぜい「はてなダイアリーでの初出」とかそんなもんだろう。自分が考え付く程度のことは他の誰かも考え付くだろうということの証左になった。
話を少しでも盛るとこんなもんであるし、こうした新情報が得られたときに大事なのは、訂正することである。
諸君 - Google ブックス
1996年
「政界サル山の権力争奪戦で、小沢にマウンティングされた過去があるからだ。」
2005年
「小山の大将やボス猿になりたがるやつっているよね。
(中略)
サルと仕事をするかしないかは
マウンティングの例を書き込んでくれれば
あとは読んだ側が判断すればいいと思う。」
このあたりの用例はまさに猿のマウンティングの比喩であるとみて間違いないだろう。
参考資料
ゲームの歴史とハックルさんとの思い出
思い出話。今回はポエムと思って気楽に読んで欲しい。
インターネットの世界には情報が溢れかえっていて、やれ、なんやらが炎上しただのそんなニュースもありふれていて特に興味もわかない。ふーん、ゲームの歴史について書いた本が噓八百で炎上してんのか、まあ適当なことを書きならべる人がトンデモ本を書きましたなんてのは昔から枚挙に暇がない話だ。
その程度に思っていたのだけども、先日ようやくそのゲームの歴史なる本を書いた人が岩崎夏海氏であったことを認識した。

僕の古い古い記憶が、この人物を知っているぞ、と呼び掛けてきた。そう、あれは2008年のことだった。今から15年も前のことになるのか。
岩崎氏ははてなダイアリー(はてなブログの前身となるブログサービス)ではちょっとした有名人だった。彼はそのblogのタイトルから、ハックルさんと呼ばれていた。よく炎上していた人だったと記憶している。というのも、ハックル氏のblogははてなから消えてしまっていて、過去の経緯をエビデンスをもって示すことが困難になってしまっているからである。その後、ニコニコ公式ブロマガに移って、その際に過去のたくさんの名文を削除してしまった。
いやいや、ハックル氏にとってはエビデンスベースドでどうであったかなんてことを言おうなどというのは失礼か。そんなのはAIがすればよろしい。
歴史書としては偽書の謗りも免れない話だが「ゲームの歴史」という本では、実在人物が言ってもないことを言ったことにされたりしていたようだ。これこそエビデンスベースドの話が面白いエンタメになるわけではないという放送作家の感性なのだろう。そう、岩崎夏海氏は放送作家らしいのである。2008年当時の僕はそのようには認識していなかったけれども。
2008年当時、彼はミリオンセラー作家というわけでもなく、僕にしてみればよく炎上するブロガーという認識だった。炎上でもなんでも話題になれば露出が増えるもので、はてなダイアリーやはてなブックマークをよく使っていた僕は度々彼を、彼の書いたblogを目にすることがあった。僕と彼はその程度の関係でしかなかった。
15年前のその日、僕はblogを書いていた。
《「意見が違うということ」=「喧嘩」という捕らえ方をされた》という話から、議論のあり方とは……といった内容に切り込んでいく。そこで僕はなんのけなしに悪い例として彼のblogを引用したのだった
どちらが勝ったか負けたかということを意識しているのだとしたら、それは議論ではない。だが、世間的には「言い合い」はみんな議論だと思っているふしがある。最近見かけたblogから例を挙げよう。
例えば議論に勝つための初歩的なテクニックというのがある。それは相手のプライドを刺激することだ。人間誰しも矜持というものがある。例えばぼくなら「面白い」ということについては一家言ある。あるいは「言葉」についても並々ならぬこだわりがあったりする。そういうぼくに向かって、「おまえの言ってることは正しいかも知れないけど面白くないよね」とか、「言わんとしていることは分からなくもないけど言葉の使い方を間違ってるから分かりにくいんだ」とか言うともうとたんにカーッとくる。それでペースを乱されて以降の物言いがはちゃめちゃになって結局議論に負けるというのもある。
http://d.hatena.ne.jp/aureliano/20080919/1221753236そんなテクニックはいかに相手のニホンザルを押さえ込んでマウンティングを成功させるかというテクニックでしかない。そんなことをやったところで疑問はなんら解決しないし、誤った意見をこうした手法で押し通しでもした日には、後で大きな惨劇が起こりかねない。勝った負けたなどと言っていても議論は進展しないのである。
議論に勝つテクニックだって?そもそも議論を勝ち負けだと思ってるのかこの人は。そして挙げているテクニックがまた酷い。相手がカーッとなって物言いがはちゃめちゃになって自滅するだって?まさにダメな議論の好例みたいな文章だ!と僕は嬉々として引用した。はてなダイアリーというのはトラックバックという機能があって、平たく言えば引用をすると相手に通知が行く機能と思えば分かりやすいだろうか。
僕のblogに引用されたことはトラックバックによって氏の知るところになったのだろう。氏の反応は素早かった。僕がblogを書いた9月20日その日のうちにアンサーエントリが書かれたのだった。(当時のはてなダイアリーが残っていないのでweb archiveを載せておく)
氏によれば、僕は、《噛ませ犬のように誰かを引き立たせるために、あるいは闘牛のように誰かに屠られるためだけに生まれてきた者》なのだそうだ。
自重した方が良い。本当に自重した方が。ここは誰が見ているかも分からないインターネットなのだ。id:Nagiseがこれまで浸かってきた生ぬるいサークル内での議論ごっこではないのである。
(中略)
ぼくには見える。id:Nagiseが会社の中で、あるいはそれ以外のコミュニティでも、良いようにこき使われているのを。そうして、もう使い物にならなくなった時には、ボロ雑巾のようにポイとお払い箱にされてしまうだろうことを。
しかしそれでも良いのかも知れない。身も蓋もないが、人間社会には、そうした生け贄が必要なのだ。
このエントリは名文だと僕は思う。この文章はなかなかAIでは書けない、魂の叫びのような文章だ。
この人は議論というものを無闇に信用し過ぎている。疑うことを知らないのだ。
なぜ疑うことを知らないか?
それは、自分で考えることを放棄しているからだ。楽をしようとしているのである。「議論」というものの存在価値を無闇に信奉し、そこに寄っかかることで楽をしようとしているのだ。「自分で考える」「責任を持つ」ということから逃げているのである。
議論を信用するからには一般論として当然自ら議論をすることを前提としていることだろう。自ら議論に身を投じることを《疑うことを知らない》《自分で考えることを放棄》《「自分で考える」「責任を持つ」ということから逃げている》と断じるこの論理展開!凄い!
何が彼にこのような支離滅裂な理屈を書かせるに至ったのだろう?当時の僕は気付かなかったけども、ハックル氏によって未来を予言するように書かれていた。
例えば議論に勝つための初歩的なテクニックというのがある。それは相手のプライドを刺激することだ
(中略)
もうとたんにカーッとくる。それでペースを乱されて以降の物言いがはちゃめちゃになって結局議論に負ける
なるほど、プライドが刺激されてカーッときてはちゃめちゃな物言いになったということか。ともあれ、氏のこのアンサーエントリでは結局そもそもの議題であるところの「議論の意義」はなんら進展しなかった。まさに僕が述べたように
そんなことをやったところで疑問はなんら解決しないし、誤った意見をこうした手法で押し通しでもした日には、後で大きな惨劇が起こりかねない。勝った負けたなどと言っていても議論は進展しないのである。
嘆かわしいばかりである。
僕とハックル氏の邂逅は衝撃的なものだった。この邂逅の後、ハックル氏は2009年に「もしドラ」(もし高校野球の女子マネージャーがドラッカーの『マネジメント』を読んだら)をヒットさせ有名作家となる。一方の僕は、この邂逅が大ヒットネットスラングを産むことになるとは想像だにしていなかった。
僕はハックル氏のテクニックとやらを
そんなテクニックはいかに相手のニホンザルを押さえ込んでマウンティングを成功させるかというテクニックでしかない。
と評した。改めて言うが2008年のことである。僕は「マウンティング」というネットスラングの提唱をしたと自負しているが、今の今までそのスラングの産まれた瞬間のことをすっかり忘れていた。
僕が勝った負けたに終始して議論のできない人をニホンザルのマウンティングに例えて評したのは、その対象の第一号は、ハックル氏だったのか!ハックル氏こそ、日本で初めてその行動をマウンティングと評された人物だったのである!!
翌9月21日、僕はblogを書いた。このハックル氏とのやり取りが僕にインスピレーションを与えてくれた。ホモサピエンスもニホンザルのようにマウンティングをする本能を生来、持っているんじゃないだろうか?(これを厳密に科学の俎上で論じるのは難しい。与太話程度に思っていただければ)
議論をしようとしたときに勝ち負けにこだわってしまうこの行為に名前を与えた僕は、それ以来、「マウンティングしてはならない」を議論する際の戒めとした。議論は議題を進展させたいからするのであって、マウンティングのためにやるんじゃないんだよ、そういう思想に行き着いたのだった。
このマウンティングというネットスラングはその後、2014年発行された瀧波ユカリ、犬山紙子著『女は笑顔で殴りあう:マウンティング女子の実態』
https://www.amazon.co.jp/dp/4480815198 によって広く周知された。今では辞書に載るほどである。
2023年3月18日追記
「マウンティング」の初出という話が気になる方が多いようだ。どのような調査をして検証すれば良いのかを記事にしたので、気になる方は検証に参加してみてもらいたい。