enumの使い方のおさらいと高機能enumとしてのSealedクラス

Javaでは複数の定数をまとめて扱う型としてenum(列挙型)が用意されています。
これはこれで便利なのですが物足りないところもあって、それがSealedクラスなどを使うことで解決できるようになるので、解説します。

enum

enumは状態やデータ区分を表すのによく使われます。
構文は次のようになります。

enum 型名 {
  列挙1, 列挙2, ...
}

例えば次のような状態を表すとします。

この状態を表すenumは次のようになります。それぞれの値は大文字で書くようにします。

enum State {
  READY, RUNNING, SUSPENDED, TERMINATED
}

enumではそれぞれの値ごとに処理を行うということがよくあります。そこでswitchと相性がいいです。

State s = State.READY;

switch (s) {
  case READY -> System.out.println("準備完了");
  case RUNNING -> System.out.println("実行中");
  case SUSPENDED-> System.out.println("停止中");
  case TERMINATED-> System.out.println("終了");
}

switch文の場合はenum値が足りなくても大丈夫ですが、switch式では全てのenum値を処理しないとエラーになります。

このことを考えると、enum値すべての処理をするswitchは式にしておいたほうがいいですね。(Java 14以降)

var message = switch (current) {
    case READY -> "準備完了";
    case RUNNING -> "実行中";
    case SUSPENDED -> "停止中";
    case TERMINATED -> "終了";
};

enumに値を持たせる

enum値それぞれに数値などを割り当てたい場合があります。そういう場合はフィールドとして定義することができます。 メソッドも定義できるのでgetterを用意するか、toStringメソッドをオーバーライドすることになりますね。 フィールドはprivate finalにしておきましょう。

enum State {
    READY("準備完了"),
    RUNNING("実行中"),
    SUSPENDED("停止中"),
    TERMINATED("終了");

    private final String name;
    State(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public String toString() {
        return name;
    }
}

ただまあ、フィールドとコンストラクタとgetterを定義していくのめんどいので、Lombokを使いたくなります。

@Value
enum State {
    READY(1, "準備完了"),
    RUNNING(2, "実行中"),
    SUSPENDED(3, "停止中"),
    TERMINATED(4, "終了");

    int code;
    String name;

    @Override
    public String toString() {
        return name;
    }
}

Sealedクラスを拡張enumとして使う

JavaenumはCのenumよりは便利なのですが、パラメータを追加したいという使い方ができません。
例えばMouseEventというenumがあって、CLICKEDとMOVEDがあるんだけど座標も持たせたい、というような場合です。あとCLICKEDにはどのボタンかも持たせたい。

という場合にSealedクラスが使えます。実際に使うのはinterfaceになるけど。

enum Button {
    LEFT, MIDDLE, RIGHT
}
sealed interface Event {
    record Clicked(Button button, int x, int y) implements Event {}
    record Moved(int x, int y) implements Event {}
}

sealedをつけたクラスやインタフェースでは、そのnestedクラスだけで継承が行えるようになります。nestedクラス以外で継承を行う場合にはpermitsで指定します。

sealed interface Event permits Clicked, Moved {}
record Clicked(Button button, int x, int y) implements Event {}
record Moved(int x, int y) implements Event {}

そして、Java 19でプレビューとして導入されたpattern matching for switchとrecord patternを利用すれば、enumのように扱えます。

Event e = new Event.Clicked(Button.RIGHT, 7, 3);
var message = switch (e) {
    case Event.Clicked(var b, int x, int y) -> 
            "%sボタンが%d,%dでクリックされた".formatted(b, x, y);
    case Event.Moved(int x, int y) -> 
            "%d,%dに動いた".formatted(x, y);
};

ここでEvent型はSealedなので、継承するクラスはClickedかMovedに限られるため、可能なすべての型をチェックしたことになります。

ということで、recordとsealedクラスとパターンマッチとswitch式を組み合わせると高機能enumのように使えるという話でした。

インスタンスとオブジェクトの違い

インスタンスとオブジェクトは混同しがちで区別がようわからんになりがちです。
とりあえず某所で説明したものを再構成します。

クラス・インスタンス・オブジェクト

クラスをインスタンス化(実体化)したものがオブジェクト(物)です。

実際に在るものはクラスとオブジェクトで、インスタンスはそれらの関係です。colorsやsportsが並んでるツリーが「オブジェクト」で、右のパレットに並んでるTreeが「クラス」、Treeからみたときのツリーが「インスタンス」ということになります。

ここでツリーはオブジェクトでもインスタンスでもあります。
このように、同じものをオブジェクトともインスタンスともいうことができるので混同してしまうわけですが、インスタンスというときには視点がTreeクラスである必要があります。

オブジェクトは必ずクラス(or なにかの型)と紐づいています。しかし、インスタンスそれ自身はクラスと結びついていません。なので、「クラスのインスタンス」という言い方ができます。
一方で、「クラスのオブジェクト」というと違和感があります。ここでオブジェクトを「クラスのインスタンス」で置き換えると「クラスのクラスのインスタンス」となってクラスがかぶってしまうからです。そうすると、Rubyのようにクラスがオブジェクトとして扱える言語で、クラスをあらわすオブジェクトのことを「クラスのオブジェクト」といっている感じになります。

ただし、入門者にJavaの説明をするときには、インスタンスという言葉を使うだけで難しさがあがってしまうので、本来は「Treeクラスのインスタンス」というべきところを「Treeクラスのオブジェクト」と言うことがあります。

クラスはオブジェクトか?

Rubyのようにクラスがオブジェクトとして扱える言語」と書きましたが、Javaではクラスはオブジェクトではありません。

Javaではオブジェクトはクラスのインスタンスか配列かどちらかです。クラスは含まれません。

An object is a class instance or an array.

https://docs.oracle.com/javase/specs/jls/se18/html/jls-4.html#jls-4.3.1

ところで配列はオブジェクトではあるけどクラスのインスタンスではないわけですね。 なので「オブジェクトとインスタンスは同じ」という説明があったとき「いやいや、配列はオブジェクトだがインスタンスではないぞ」というツッコミができるわけです。
※追記 「instances of the same array type」のような記述が言語仕様にあったりするので、配列もインスタンスです。

Classクラスは?

java.lang.Classクラスあるじゃん、クラスもオブジェクトじゃん」と言いたくなるかもしれません。
Classクラスは、Javaのようにクラスがオブジェクトではない言語で実行時にクラスを扱うためのクラスということで、メタクラスという位置づけになってます。
「めたxx」は「xxのxx」と置き換えることができるので、メタクラスは「クラスのクラス」です。
(「メタモンはモンのモン」は既出です。モンスターのモンスターですね)

他の言語では?

上で書いたようにRubyではクラスはオブジェクトです。Smalltalkの影響が強い言語ではクラスがオブジェクトとして扱えるみたいなことがWikipediaに書いてありますね。
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9

で、そうするとクラスのオブジェクトとそっからインスタンス化されたオブジェクトとようわからんになるので、クラス・オブジェクトとインスタンス・オブジェクトのような使い分けをするらしいです。

ちなみにJavaScriptでは

というように、クラスはオブジェクトか?いやオブジェクトじゃない、とかクラスもオブジェクトでインスタンスもオブジェクトで、とかいう話に
「ようわからんわ!クラスとかいらんのじゃ!ぜんぶオブジェクトじゃ!」
ってブチ切れて、オブジェクトだけで全部やろうとした人がいました(ブチ切れたかどうか知りませんがそういうややこしさの解消がキッカケだったようです)

つまりクラスの代わりになるような、元ネタになるオブジェクトを用意して、それをコピーして別のオブジェクトを生成という方針です。このとき元ネタになるオブジェクトをプロトタイプといいます。
このようにオブジェクトを生成する言語を「プロトタイプベースのオブジェクト指向」と呼びます。
代表的なのがJavaScriptですね。
まあ結局プロトタイプベースでは考え方は楽かもしれないけど使い方が超ややこしいということになって、classが導入されたわけです。
※追記 いずれにしろJavaScript(TypeScript)でクラスのようなものの出番はあまりないという指摘があった。

結論

どうやってもややこしい

ノンアル ヒューガルデン来た!

ベルギーで飲んだときにこれはうまいと思って日本でも飲みたいと思ってたノンアル ヒューガルデン、2年前に日本で買える話が流れて残念に思っていたのですが、ようやく買えるようになりました。

ヒューガルデンの味がする!コリアンダーの華やかさとオレンジピールのさわやかさをちゃんと感じます。少し香りの華やかさが控えめになって甘めになった感じ。
もちろんアルコールがない分の物足りなさはありますが、逆にいえばほんとにヒューガルデンからアルコールを抜いた感じです。

酵母が入っていて、最後に振って注いで酵母を入れると味がかわるのもヒューガルデンと同じです。甘さが気になる場合は、振らずに注いで飲んでみるといいかもしれません。もったいないので最後だけ酵母いれましょう。

ベルギーではビールを飲んだ帰りに1本ノンアルヒューガルデンを買って帰ってホテルで飲んでたのですが、そうすると普通のヒューガルデンでした。一度まちがえてアルコール入りヒューガルデンを買って飲んだとき、今日のはちょっと濃いなって思っただけだったくらい。

韓国製造のようで、ベルギーで飲んだものとはちょっと違う可能性はありますが、アルコール入りのヒューガルデンも韓国製造なので。

ほんもののヒューガルデンと同じく、日本のどの料理にも合うという感じではないので飲む場面は選ぶと思うけど、ヒューガルデンを常備してる人なら定期購入してよさそう。

他のノンアルビールはこちらでまとめてます。

オブジェクト指向は継承で多態するプログラミング

オブジェクト指向って継承による多態があるからこそなんだけど、継承が非推奨になって以降に雰囲気でオブジェクト指向を知った人には、継承はオプションでカプセル化だけでオブジェクト指向って言ってしまいがちに思います。 実際はカプセル化オブジェクト指向固有じゃなくて、クラスでカプセル化を実現してるだけです。

さまざまな人のオブジェクト指向の定義

本来ならどのように継承こそがオブジェクト指向なのかという説明をするんですが、かなり長くなりそうなので、とりあえずはいろいろな人たちのオブジェクト指向の定義を抜き出してみます。

ストラウストラップ

C++を産んだストラウストラップは「C++の設計と進化」で次のように言ってます。ここでデータ抽象化というのはカプセル化だと考えて問題ないと思います。

オブジェクト指向プログラミングは継承を使うプログラミングだ。データ抽象化はユーザ定義タイプを使うプログラミングだ。ほとんど例外なく、オブジェクト指向プログラミングはデータ抽象化のスーパーセットだ

ランボー

オブジェクト指向方法論OMTを開発したランボーは次のように書いてます。ここでアイデンティティまで含めているのが面白いところです。値型との区別ですね。

オブジェクト指向的アプローチに要求される特徴が正確に何か、ということに関してはいくつかの議論があるが、それらの議論は普通以下の4つの面を含んでいる。すなわち、アイデンティティ、分類、多相性、そして継承である

メイヤー

オブジェクト指向入門」を書いたメイヤーは、次のようなオブジェクト指向の原則を書いています。一言で継承が重要とは書いていませんが、著書全体を通して継承の使い方を解説しています。

方法論と言語には、中心的な概念としてクラスの概念が必須である
すべての型はクラスに基づいていなければならない
ほかのクラスを継承するクラスを定義できなければならない

ヴァン・ロイ、ハリディ

ガウディ本として知られる「コンピュータプログラミングの概念・技法・モデル」には次のような記述があります。
ガウディ本はオブジェクト指向だけではなくプログラミングのさまざまなモデルや概念をまとめて紹介した本で、他の手法との対比をよりはっきりと行っています。

オブジェクト指向プログラミングとは、オブジェクト、データ抽象、明示的状態、多態性、および継承を使うプログラミングである」
「 他の計算モデルと比較すると、多態性と継承を使うことが主たる特徴である」

また、ガウディ本には次のような記述があります。高階プログラミング技法というのは関数型と考えるといいです。

オブジェクト指向言語ではできるだけ明示的状態と継承を使用することが推奨される。」「このことは一見、単純で直観的であるが、実際はプログラミングを複雑にする」「好ましいプログラミング技法を記述するための共通用語を定義するデザインパターンは普通、継承を使って説明される。多くの場合、より簡単な高階プログラミング技法で十分であろう」

次のように、特別な場合以外はオブジェクト指向のアプローチを避けたほうがいいということも書いています。

オブジェクト指向に頼るのは、プログラム構造が著しく簡単になる場合に限ることを奨める。プログラムに、密接に関連するデータ抽象の集合があって、明らかに継承の必要があるような場合がその一例である。そうでなければ、もっと簡単なプログラミング技法を使用することを奨める。」

2004年時点では まだオブジェクト指向の熱気が残っていたので、このくらいの控えめな記述になったのかなという感じがします。

リファクタリングはエンジニアの福利厚生であり管理指標への影響はほとんどないんでは

おそらくリファクタリング工数を確保する説得力のある材料がほしくて、リファクタリングの効果をどう示すか悩んでる人がいたのですが、リファクタリングって非開発者に示せるような数字だすのは難しいよねという結論になったので、そのまとめ。
工数としてはコード管理費みたいな感じで乗せるのがよさそう。

まず、リファクタリングはそれ自体では価値を示せません。人工衛星に搭載するプログラムで、動きだしたらメンテナンスできないようなコードを最後にリファクタリングしたとして、どのような価値を示せるかと考えると想像できるのではないかと思います。

なのでリファクタリングの価値というのは、その後で新しいコードを追加したり既存のコードを変更したりといった作業がどれだけ作業時間短く品質高くなったかという間接的な指標で測ることになります。

ここでまず、最初のコードを書いた人とリファクタリングする人が同じなら、そこまで保守性かわるか?という疑問があります。
リファクタリングで保守性高いコードを導き出せる人は最初からそれなりの保守性のコード書いてるのでは?保守性オワットルコードを書く人はリファクタリングしても保守性を盛り込めないんでは?と。
保守性オワットルコードを、保守性高いマンがリファクタリングするとき、それリファクタリングの範囲ですむのか、アーキテクチャ変更になるんでは、とか。

また、コードの追加変更って、まずどこをいじくるか探して、新しいコードを既存コードに接続すると、あとはずっと同じコードをいじくり続けがちになると思います。機能開発の間に開いてるファイルは決まっているというか。
そうすると、リファクタリングの効果が出るのはどこをいじくるか探すところで、機能開発が進むと効果は薄まっていくのではという気がします。

あと、「住めば都」原理により、人間はそれなりに散らかったコードでも割と適応して、結構コードを把握できてしまって、リファクタリングの効果が構造の変化ほどは出ないんではないかというのもあり。

そして、リファクタリング効果が高い条件設定をすると、それはそのままリファクタリングができない条件にもなりがちということもあります。たとえば、コードは保守性オワットルマンが書き、それを保守性高いマンがリファクタリングに集中して構成変更していく、と。それは効果ありそうなんですが、現実には保守性高いマンは機能開発にまわされて、リファクタリングというお金を産みにくい作業にまわしてもらえないように思います。

もちろん、リファクタリングがちゃんとされているコードと書き散らしのコードでは、作業の気持ちよさが違います。ということはリファクタリングはエンジニアの精神衛生を保つ福利厚生の面が高いんではないでしょうか。
福利厚生、つまり、エンジニアに与えられる金銭以外の報酬ってことです。

ということでリファクタリング工数とってまとめてやるんじゃなく、スキマ時間で気がついたときにやったほうがいいのではないかと思います。そしてそういったリファクタリング作業を「バグが入る可能性があるからやるな」などと言われないよう、テストをちゃんと書いてエンバグを防いで、リファクタリングできる環境を整えておきましょう、というのが現実的かなという結論でした。

合わせて読みたい
TDDで「テストばかり書いて間に合うのか?」と質問されたときの正解 - きしだのHatena

なぜオブジェクト指向方法論に代わる方法論が出ないのか

1990年代にオブジェクト指向分析・設計の方法論がめちゃ流行ったことがあります。 ただ、そのブームが終わって、後続となるような方法論が流行ることはありませんでした。

で、なぜなのか考えていたのですけど、オブジェクト指向方法論のウリは分析段階で出てきたオブジェクト(といいつつクラス)がコードにそのまま引き継がれるというものでした。ようするにオブジェクト指向方法論というのはコードのスケッチを書いて詳細化していくというものだったのです。
しかしながらこれは、スケッチとして書いた分析・設計が間違っていればコードも間違うわけで、強くウォーターフォールの性質をもつものでした。
結局のところスケッチの妥当性というのはコードを書かないと検証ができません。分析・設計段階で見出されたクラスが妥当かというのは、コード書かなければわからなかったのです。逆に、コードを書けば妥当かどうかわかります。であれば、最初からコードを書く方が楽です。

また、この考え方は、ソフトウェアを適用する環境は変わらないという暗黙の前提をもっていました。実際にはソフトウェアを開発して導入すると、導入した先の環境もかわります。そこでソフトウェアに求められる要求は変化します。
そうであれば、最初にソフトウェア導入前の環境を観察して分析設計を確定させてもあまり意味がなく、ソフトウェアを導入して変化した環境にあわせてソフトウェアも変化させる必要が出ます。
そこで1999年にエクストリームプログラミングの考え方がでて、アジャイルへとつながっていきます。

オブジェクト指向方法論のひとつであるヤコブソンのOOSEでは、ユースケース分析としてソフトウェアを適用する環境を観察するという過程をもっていました。
アジャイルの流れもあり、分析・設計する対象は作成するソフトウェアではなく、そのソフトウェアを適用する環境であるという考えが広まっていきます。そしてドメイン駆動設計として成熟することになります。

そういうこともあって、オブジェクト指向というときオブジェクト指向分析・設計のことが話題になることはなくなり、オブジェクト指向プログラミングの話だけが残っています。

かくして、分析・設計する対象はソフトウェアではなくソフトウェアの環境であり、工夫する対象はソフトウェアをどう作ってどう配備してどう運用するかというプロセスであり、コードに関してはあらかじめスケッチするのではなく方針や原則を決めプラクティスを実践する、ということに落ち着いたのではないかと思います。
つまり、DDD、アジャイル継続的インテグレーション、SREという感じになったわけですね。これらは実装パラダイムには依存しないので、プログラマは自分たちに適した技術を採用できます。

Javaのプログラムはコンパイルなしで動かせます

Java 11から単一ソースファイルのJavaプログラムをコンパイルなしで直接javaコマンドで実行できるようになっているので、紹介動画を作りました。

例えば次のようなJavaプログラムをHello.javaという名前で保存したとします。

public class Main {
  public static void main(String[] args) {
    System.out.println("Hello Java!");
  }
}

このプログラムはファイル1つだけで完結しているので、javaコマンドで次のように実行できます。

>java Hello.java
Hello Java!

ここで、クラス名はMainですが、ファイル名がHello.javaになっていることに注意してください。

javacでコンパイルしようとするとエラーが出ます。

>javac Hello.java
Hello.java:1: エラー: クラス Mainはpublicであり、ファイルMain.javaで宣言する必要があります
public class Main {
       ^
エラー1個

拡張子が.javaではないファイルでも実行できます。
その場合は--sourceソースコードのバージョンを指定する必要があります。

C:\Users\naoki\Desktop>java --source 17 Hello.txt
Hello Java!

LinuxMacなどUNIX系のシステムではShebangという仕組みでJavaのソースファイルをコマンドのように実行できます。
次のようなファイルをhelloという名前で作ります。

#! /usr/bin/java --source 11
public class Main{
  public static void main(String[] args) {
     System.out.println("Hello Java!!!");
  }
}

最初の行で#!から始めてjavaコマンドのフルパスと--sourceでのバージョン指定を書きます。

chmodで実行権限をつけると、コマンドのように実行できます。

# chmod +x hello
# ./hello
Hello Java!!!

「プロになるJava」では「複数のソースファイルがからむときはコンパイルが必要」と、javacがいる場合を特別な場合として説明しています。 わざわざコマンドラインjavacするのはソースファイル1つの場合だし。