コンテンツにスキップ

Java/初級編

出典: フリー教科書『ウィキブックス(Wikibooks)』

この単元で学ぶことは、Javaプログラミングの基礎と初歩的な構文です。 変数、演算、条件分岐、反復処理、型、リテラル、文字列、配列、クラス、例外処理など、プログラミングの基本要素を学びます。 これにより、Javaでの基本的なプログラミング概念やスキルを身に付けることができます。

クイックツアー

[編集]

Javaの基本的な特徴

[編集]

Javaは、オブジェクト指向プログラミング言語の中でも特に広く使われています。その特徴を見ていきましょう。

  1. プラットフォーム非依存性
    Javaは、"Write once, run anywhere"(一度書けば、どこでも実行できる)の特性を持ちます。これは、Javaプログラムがプラットフォームに依存しないことを意味します。Windows、Mac、Linux、Androidなど、どのようなオペレーティングシステムでも動作します。
  2. オブジェクト指向プログラミング
    Javaは、クラスとオブジェクトに基づくオブジェクト指向プログラミング言語です。クラスはデータとその操作をカプセル化し、再利用可能なコードのベースとなります。
  3. 静的型付け言語
    Javaは静的型付け言語であり、コンパイル時に型の整合性をチェックします。これにより、実行時の型エラーを事前に検出できます。
  4. ガベージコレクション
    Javaは自動ガベージコレクションを備えています。これは、プログラマーが明示的にメモリ管理を行う必要がなく、不要なオブジェクトが自動的に回収されることを意味します。
  5. 多言語サポート
    Java仮想マシン(JVM)を介して、Javaはさまざまなプログラミング言語をサポートします。これにより、Javaと他の言語との間で相互運用性を確保することができます。
  6. 高度なセキュリティ
    Javaはセキュリティに重点を置いており、Java仮想マシンが実行時にアプリケーションを監視し、悪意のあるコードの実行を防止します。
  7. マルチスレッド
    Javaはマルチスレッドをサポートしており、複数のスレッドを同時に実行できます。これにより、プログラムの効率が向上します。
  8. 例外処理
    Javaは例外処理をサポートしており、予期しないエラーに対処するための機構を提供します。
  9. 高性能
    JavaはJIT(Just-In-Time)コンパイラを使用してコンパイルされたコードを実行し、高速な実行を実現します。また、メモリ管理における最適化も行われています。
  10. 広範なライブラリ
    Javaには豊富な標準ライブラリが用意されており、開発者はこれらのライブラリを活用してアプリケーションを効率的に開発できます。

Javaの基本的な概念と構文

[編集]
  1. Javaの基本構造
    Javaプログラムはクラスという単位で構成されます。Javaのクラスは、フィールド(変数)とメソッド(関数)の集まりです。以下は、基本的なJavaクラスの例です。
    HelloWorld.java
    /**
     * HelloWorldクラスは、Javaプログラムの基本的な構造を示し、
     * コンソールに "Hello, World!" というメッセージを出力します。
     */
    public class HelloWorld {
    
        /**
         * mainメソッドは、Javaプログラムのエントリーポイントです。
         * コンソールに "Hello, World!" というメッセージを出力します。
         *
         * @param args コマンドライン引数(このプログラムでは使用しません)。
         */
        public static void main(String[] args) {
            // "Hello, World!" をコンソールに出力する
            System.out.println("Hello, World!");
        }
    }
    
  2. データ型と変数
    Javaにはさまざまなデータ型があります。代表的なものには、int(整数)、double(浮動小数点数)、boolean(真偽値)、String(文字列)などがあります。変数を宣言するには、データ型に続けて変数名を指定します。
    int age = 30;
    double price = 10.5;
    boolean isStudent = true;
    String message = "Hello, Java!";
    
  3. 制御構造
    Javaでは、条件分岐や繰り返し処理を行うための制御構造があります。代表的なものには、if文、whileループ、forループ、拡張forループなどがあります。
    // if文
    int x = 10;
    if (x >= 5) {
        System.out.println("xは5以上です");
    } else {
        System.out.println("xは5未満です");
    }
    
    // whileループ
    int count = 0;
    while (count < 5) {
        System.out.println("カウント: " + count);
        count++;
    }
    
    // forループ
    for (int i = 0; i < 5; i++) {
        System.out.println("iの値は: " + i);
    }
    
    //拡張forループ
    int[] numbers = { 2, 3, 5, 7 }; // 配列の各要素に対して繰り返し処理を行う
    for (int number : numbers) {
        System.out.println(number); 
    }
    
  4. メソッド
    Javaでは、複数の文をまとめて特定の処理を行うメソッドを定義することができます。
    // メソッドの定義
    public static int add(int a, int b) {
        return a + b;
    }
    
    // メソッドの呼び出し
    int result = add(5, 3);
    System.out.println("結果: " + result); // 結果: 8
    
  5. クラスとオブジェクト
    Javaはオブジェクト指向言語です。クラスを定義し、そのクラスから複数のオブジェクトを作成することができます。
    /**
     * Personクラスは、人物の情報を表すクラスです。
     */
    public class Person {
        /**
         * 名前を表す文字列です。
         */
        String name;
        
        /**
         * 年齢を表す整数です。
         */
        int age;
        
        /**
         * Personクラスのコンストラクタです。
         * @param name 名前
         * @param age 年齢
         */
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        /**
         * 人物の情報を表示するメソッドです。
         */
        public void introduce() {
            System.out.println("名前: " + name + ", 年齢: " + age);
        }
    }
    
    // オブジェクトの作成とメソッドの呼び出し
    Person person1 = new Person("太郎", 25);
    person1.introduce(); // 名前: 太郎, 年齢: 25
    

これらはJavaの基本的な概念と構文の一部です。 Javaを学ぶ際には、これらの概念を実際のコードで試してみることが重要です。

ユースケース

[編集]

Javaは非常に多様なユースケースで使用されています。以下はその一部です。

  1. Webアプリケーション開発: Javaは、ServletやJSP、Spring Frameworkなどの技術を使用してWebアプリケーションを開発するために広く使用されています。人気のあるフレームワークにはSpring BootやJava EE(Jakarta EE)があります。
  2. モバイルアプリケーション開発: JavaはAndroidアプリケーションの開発に広く使用されています。Android StudioやKotlinと組み合わせて使用されることが一般的です。
  3. デスクトップアプリケーション開発: JavaはSwingやJavaFXなどのGUIライブラリを使用してデスクトップアプリケーションを開発するために使用されます。これにより、クロスプラットフォームのアプリケーションを作成することができます。
  4. サーバーサイド開発: Javaは、大規模なエンタープライズアプリケーションの開発に広く使用されています。Java EE(現在のJakarta EE)やSpring Frameworkなどのフレームワークを使用して、高度なサーバーサイドアプリケーションを構築することができます。
  5. 大規模なデータ処理: Javaは、Apache HadoopやApache Sparkなどのビッグデータ処理フレームワークで使用されています。これらのフレームワークは、大規模なデータセットを処理するためにJavaで実装されています。
  6. 組み込みシステム: Javaは組み込みシステムで使用されることもあります。例えば、AndroidのOS自体はJavaで書かれています。また、組み込みデバイスやIoT(Internet of Things)デバイスでJavaが使用されることもあります。
  7. 金融業界: 金融機関では、トランザクション処理やデータベースアクセスなどの要件を満たすために、Javaが広く使用されています。Javaの安全性、パフォーマンス、信頼性は、金融取引の処理に適しています。

Javaはこれらのユースケースにおいて幅広く活用され、その堅牢性、安全性、クロスプラットフォーム性、豊富なライブラリなどの特徴により、多くの開発者に選択されています。

ベストプラクティス

[編集]

Javaのベストプラクティスには、以下のようなものがあります。

  1. 命名規則に従う: クラス名、メソッド名、変数名などは意味のある名前を使い、CamelCaseの命名規則に従うことが推奨されます。
  2. コードの再利用: コードの再利用性を高めるために、継承やコンポジションなどのオブジェクト指向の原則を活用します。また、共通の機能をメソッドやクラスにまとめることも重要です。
  3. コメントを記述する: コードに十分なコメントを付けることで、他の開発者がコードを理解しやすくなります。ただし、コメントはコードを補完するためにあり、適切に保守される必要があります。
  4. 例外処理を行う: 適切な例外処理を行うことで、プログラムの安定性を確保します。適切な例外クラスを選択し、適切なハンドリングを行います。
  5. メモリ管理: Javaは自動ガベージコレクションを備えていますが、メモリリークを避けるためにはメモリの使用を最小限に抑えるように心がけます。
  6. 効率的なデータ構造の選択: 適切なデータ構造を選択することで、アプリケーションのパフォーマンスを向上させることができます。例えば、リスト、セット、マップなどを適切に使用します。
  7. テストを行う: 単体テスト、統合テスト、およびシステムテストを適切に行うことで、プログラムの品質を確保します。JUnitなどのテスティングフレームワークを使用して自動化されたテストを実施します。
  8. バージョン管理: ソースコードのバージョン管理を行うことで、複数の開発者が同時に作業でき、コードの変更履歴を追跡できます。Gitなどのバージョン管理システムを使用します。

これらのベストプラクティスを遵守することで、より堅牢でメンテナブルなJavaプログラムを開発することができます。

イディオム

[編集]

Javaのイディオム(慣用句)には、次のようなものがあります。

  1. JavaBeansパターン: JavaBeansは、プレーンなJavaクラスであり、特定のルールに従って命名されたプロパティを持つことが特徴です。これにより、JavaBeansはデータの保持と操作に使用されます。
  2. Builderパターン: Builderパターンは、オブジェクトの生成過程をカプセル化し、柔軟性を提供するデザインパターンです。このパターンを使用すると、複雑なオブジェクトを構築する際にコンストラクタのパラメータ数を減らすことができます。
  3. ファクトリーメソッド: ファクトリーメソッドは、オブジェクトの生成をサブクラスに委任するパターンです。これにより、生成するオブジェクトの型をサブクラスによって決定することができます。
  4. シングルトンパターン: シングルトンパターンは、特定のクラスのインスタンスがアプリケーション全体で一意であることを保証するデザインパターンです。これにより、アプリケーション内の特定のリソースへの一貫したアクセスが可能になります。
  5. ストラテジーパターン: ストラテジーパターンは、アルゴリズムをカプセル化し、実行時に交換可能にするデザインパターンです。これにより、アルゴリズムの実装を柔軟に変更できます。
  6. イテレーターパターン: イテレーターパターンは、コレクション内の要素に順次アクセスするためのデザインパターンです。これにより、コレクション内の要素に対する反復処理を抽象化し、クライアントコードを簡素化します。

これらのイディオムは、Javaのプログラミングスタイルやデザインパターンにおいてよく使用されます。 それぞれが特定の問題を解決するために設計されており、Java開発者が効果的にコードを記述するための重要なツールとなっています。

Hello World!

[編集]

「Hello World!」は、プログラミングの世界で伝統的に使われる初心者向けの最初のプログラムです。 Javaでも同様に、「Hello World!」プログラムは、基本的な構文や開発環境の設定を理解するために使われます。

ソースコードの書き方

[編集]

まず、テキストエディタや統合開発環境(IDE)を使用して、Javaのソースコードを書きます。

以下が「Hello World」のソースコード例です。

HelloWorld.java
public class HelloWorld {
    public static void main(String[] args){
        System.out.println("Hello, world.");
    }
}

このコードでは、HelloWorldという名前のクラスを定義し、その中にmainメソッドを記述しています。 mainメソッドはJavaプログラムのエントリポイントであり、プログラムの実行が開始される場所です。System.out.printlnは、コンソールに文字列を出力するためのメソッドです。

コンパイル

[編集]

Javaのソースコードをコンパイルするには、Java Development Kit(JDK)がインストールされている必要があります。 コンパイルは次の手順で行います。

  1. 以下のコマンドを入力して、ソースコードをコンパイルします。
    javac HelloWorld.java
    

これにより、HelloWorld.javaファイルがHelloWorld.classという名前のバイトコードファイルにコンパイルされます。

実行

[編集]

コンパイルされたJavaプログラムを実行するには、以下の手順を実行します。

  1. 以下のコマンドを入力して、Javaプログラムを実行します。
    java HelloWorld
    

これにより、コンソールに Hello, World! という文字列が表示されます。

以上が、Javaでの「Hello World」プログラムの作成、コンパイル、実行の手順です。


Javaでの「Hello World」プログラムは、単純な文字列の表示に過ぎませんが、その背後にはいくつかの興味深い蘊蓄があります。

  1. クラスとメソッドの構造: Javaでは、全てのコードがクラスの中に存在します。そして、実行可能なJavaプログラムは、必ずmainメソッドを持つクラスから始まります。これは、Java仕様で定義されています。
  2. プラットフォームの移植性: Javaは、プラットフォームに依存しないという特徴があります。つまり、Javaで書かれたプログラムは、Java仮想マシン(JVM)上で実行されるため、どのオペレーティングシステムでも動作します。これは、Javaが初めて登場したときからの重要な利点であり、広範囲なデバイスやシステムでの使用を可能にしました。
  3. コンパイルと実行の分離: Javaプログラムは通常、ソースコードをコンパイルしてバイトコードに変換し、その後JVMで実行します。この2段階のプロセスにより、Javaは柔軟性とセキュリティを提供します。コンパイルされたバイトコードは、様々な環境で安全に実行されます。
  4. オブジェクト指向プログラミングの導入: Javaはオブジェクト指向プログラミング言語として設計されています。そのため、「Hello World」プログラムでも、クラスとメソッドの構造が強調されています。オブジェクト指向の原則に基づいて設計されたJavaプログラムは、柔軟性がありメンテナンス性が高くなります。
  5. 豊富な標準ライブラリ: Javaには、開発者が利用できる豊富な標準ライブラリが用意されています。これにより、多くの一般的なタスクを簡単に実装できます。例えば、文字列操作、ファイル入出力、ネットワーク通信などが含まれます。

Javaの「Hello World」プログラムは、単なる最初のステップに過ぎませんが、Java言語の重要な特徴や原則を理解するのに役立ちます。

プログラミングのための準備

[編集]

JDK

[編集]
Wikipedia
Wikipedia
ウィキペディアJava Development Kitの記事があります。

JDK(Java Development Kit)は、Javaプログラムを開発するためのソフトウェア開発キットです。JDKには、Javaプログラムを作成、コンパイル、実行するために必要なツールやファイルが含まれています。具体的には以下のような要素が含まれます。

  1. Javaコンパイラ(javac): Javaソースコードをバイトコードに変換するためのコンパイラです。Javaコンパイラは、Javaプログラムをコンパイルして実行可能な形式に変換します。
  2. Javaランタイム環境(JRE): Javaプログラムを実行するための実行時環境です。JREには、Java仮想マシン(JVM)やJavaクラスライブラリが含まれています。
  3. デバッグツール: デバッグやトラブルシューティングのためのツールが含まれています。例えば、デバッガやプロファイラなどがあります。
  4. APIドキュメント: JavaプログラミングのためのAPIドキュメントが含まれています。これにより、Javaの標準ライブラリやクラスの使用方法を確認することができます。
  5. 開発ツール: jshell(Java REPL)、jlink(モジュールリンカー)、jdeps(依存関係分析)などの開発支援ツールが含まれています。

javac

[編集]
Wikipedia
Wikipedia
ウィキペディアjavacの記事があります。

javacは、Javaコンパイラのコマンドラインツールです。このツールは、Javaソースコードファイル(.java拡張子)をJava仮想マシン(JVM)が理解できるバイトコードファイル(.class拡張子)に変換します。

具体的には、以下のような役割があります:

  1. Javaソースコードのコンパイル: javacは、Javaプログラムをコンパイルしてバイトコードに変換します。このバイトコードはJVM上で実行されるため、プラットフォームやオペレーティングシステムに依存しません。
  2. エラーチェックとデバッグ: javacは、ソースコードに文法エラーや論理エラーがないかをチェックし、必要に応じてエラーメッセージを生成します。これにより、開発者はコードの品質を向上させることができます。
  3. バイトコードの生成: javacは、コンパイルされたJavaソースコードからバイトコードファイルを生成します。このバイトコードは、JVM上で実行されるJavaアプリケーションやアプレットの基盤となります。
  4. モジュール対応: Java 9以降、javacはモジュールシステム(Project Jigsaw)に対応し、モジュール化されたアプリケーションのコンパイルをサポートしています。

JRE

[編集]
Wikipedia
Wikipedia
ウィキペディアJava Runtime Environmentの記事があります。

JRE(Java Runtime Environment)は、Javaプログラムを実行するための環境を提供するソフトウェアパッケージです。主な役割は以下の通りです:

  1. Javaアプリケーションの実行: JREにはJava仮想マシン(JVM)が含まれており、Javaプログラムを実行可能な形式に変換します。これにより、Javaアプリケーションは異なるプラットフォームやオペレーティングシステム上で動作します。
  2. Javaクラスライブラリの提供: JREには、Java標準ライブラリが含まれています。これにより、Javaアプリケーションは基本的なデータ構造や機能を利用できます。例えば、文字列処理、ネットワーク通信、ファイル操作などが含まれます。
  3. セキュリティ機能の提供: JREは、Javaアプリケーションの実行中にセキュリティを確保し、悪意ある操作やリソースの不正使用を防ぎます。これには、セキュリティマネージャーなどが含まれます。
  4. パフォーマンス最適化: JITコンパイラによるコードの最適化やガベージコレクションの実行など、実行時のパフォーマンスを向上させる機能を提供します。

Java仮想マシン

[編集]
Wikipedia
Wikipedia
ウィキペディアJava仮想マシンの記事があります。

Java仮想マシン(Java Virtual Machine、JVM)は、Javaプログラムを実行するための仮想的なコンピュータ環境です。JVMは、Javaバイトコード(.classファイル)をホストプラットフォームのネイティブマシンコードに変換し、実行します。

以下はJVMの主な役割です:

  1. プログラムの実行: JVMはJavaバイトコードを解釈し、ネイティブマシンコードに変換して実行します。これにより、Javaプログラムはホストプラットフォームやオペレーティングシステムに依存せずに実行できます。
  2. メモリ管理: JVMはメモリの割り当てや解放を管理し、ガベージコレクションと呼ばれるメカニズムを使用して不要なオブジェクトを自動的に解放します。これにより、メモリリークや無効なメモリアクセスなどの問題を回避できます。
  3. セキュリティ: JVMは、セキュリティ管理を強化するための機能を提供します。例えば、セキュリティマネージャーを使用して、Javaアプリケーションのアクセス権を制御することができます。
  4. 例外処理: JVMは、Javaプログラム内で発生する例外をキャッチし、適切な例外処理を行います。これにより、プログラムの安定性が向上し、予期せぬエラーに対処できます。
  5. JITコンパイル: Just-In-Time(JIT)コンパイラを使用して、頻繁に実行されるコードをネイティブコードに変換し、実行性能を向上させます。
  6. プロファイリング: アプリケーションの実行状況を監視し、パフォーマンスデータを収集する機能を提供します。

Oracle JDKとOpen JDK

[編集]

Oracle JDK

[編集]

Oracle JDKは、Javaプログラミング言語の開発および実行環境を提供するOracle Corporationによる製品です。Oracle JDK 17以降はNFTC(No-Fee Terms and Conditions)ライセンスの下で提供され、開発、テスト、プロトタイピング、デモンストレーション目的での使用に加えて、商用環境での使用も無料で許可されています。

主な特徴:

  1. 商用サポート: 有償のサポートサービスが利用可能です。
  2. パフォーマンス最適化: 商用環境向けの最適化が施されています。
  3. セキュリティアップデート: 定期的なセキュリティアップデートが提供されます。
  4. 追加ツール: Java Flight Recorderなどの商用機能が含まれています。

Open JDK

[編集]

OpenJDKは、Javaプログラミング言語のオープンソースのリファレンス実装です。GNU General Public License (GPL) version 2 with Classpath Exceptionの下で提供され、商用利用も含めて無料で利用できます。

主な特徴:

  1. オープンソース: ソースコードが公開され、コミュニティによる開発が行われています。
  2. プラットフォーム互換性: 様々なプラットフォームやアーキテクチャに対応しています。
  3. コミュニティサポート: 活発なコミュニティによるサポートが提供されます。
  4. 最新機能: 新機能の実装が早期に行われます。

その他の JDK

[編集]

その他の主要なJDK実装には以下のようなものがあります:

Amazon Corretto
Amazonが提供する無償のOpenJDK配布版です。AWS環境での最適化が施されており、長期サポート(LTS)が提供されます。特徴:
  • AWS環境での最適化
  • 自動パッチ適用
  • 無償の長期サポート
  • マルチプラットフォーム対応
Eclipse Temurin (旧AdoptOpenJDK)
Eclipse Foundationが管理するOpenJDKの配布版です。特徴:
  • 厳格な品質管理
  • 豊富なプラットフォームサポート
  • コミュニティドリブンな開発
  • 定期的なアップデート提供
Azul Zulu
Azul Systemsが提供するOpenJDKの実装です。特徴:
  • エンタープライズグレードのサポート
  • プラットフォーム最適化
  • セキュリティ強化
  • ARM版を含む幅広いアーキテクチャ対応

Java SE LTS

[編集]

Java SE LTS(Java Standard Edition Long-Term Support)は、Java SEプラットフォームの長期サポートを提供するプログラムです。2024年2月現在、最新のJava SE LTSはJava 21です。

以下は、最新のJava SE LTSバージョンの一覧です:

Java SE LTSのバージョン一覧
Java バージョン リリース日 LTS サポート終了日 主な特徴
Java SE 8 LTS 2014年3月18日 2030年12月
  • Lambdaサポート
  • Stream API
  • JavaFX 8
Java SE 11 LTS 2018年9月25日 2026年9月
  • HTTPクライアントAPI
  • 新しいガベージコレクタ
  • モジュールシステム
Java SE 17 LTS 2021年9月14日 2029年9月
  • シールドクラス
  • パターンマッチング
  • 新しいランダム生成器
Java SE 21 LTS 2023年9月19日 2031年9月
  • 仮想スレッド
  • レコードパターン
  • シーケンシャルコレクション

プログラミングに必要なもの

[編集]

統合開発環境(IDE)

[編集]

現代のJava開発では、以下のような統合開発環境(IDE)が広く使用されています:

IntelliJ IDEA
JetBrainsが開発する高機能IDE。Community Edition(無料)とUltimate Edition(有料)があります。
主な特徴:
  • インテリジェントなコード補完
  • 高度なリファクタリング機能
  • フレームワークサポート
  • デバッグ機能の充実
  • Git統合
Eclipse
オープンソースの統合開発環境。無料で利用可能です。
主な特徴:
  • プラグインによる拡張性
  • マルチ言語サポート
  • 豊富なプロジェクトテンプレート
  • デバッグツールの充実
  • チーム開発支援機能
Visual Studio Code
Microsoftが開発する軽量なコードエディタ。Java拡張機能により、IDEとしても利用可能です。
主な特徴:
  • 軽量で高速
  • 豊富な拡張機能
  • Git統合
  • デバッグ機能
  • Live Share機能

ビルドツール

[編集]

現代のJavaプロジェクトでは、以下のビルドツールが主流です:

Apache Maven
XMLベースの構成管理とビルド自動化ツール。
主な特徴:
  • 宣言的な依存関係管理
  • 標準化されたプロジェクト構造
  • 豊富なプラグイン
  • センターリポジトリ
  • ライフサイクル管理
Gradle
GroovyまたはKotlinベースのビルド自動化ツール。
主な特徴:
  • 柔軟なビルドスクリプト
  • インクリメンタルビルド
  • ビルドキャッシュ
  • 依存関係の最適化
  • Android公式ビルドツール

開発環境のセットアップ

[編集]

JDKのインストール

[編集]
  1. JDKのダウンロード:
    Oracle公式サイト(Java SE Downloads)からJDKをダウンロードします。
    以下の点に注意してダウンロードを行います:
    • 使用するOSに合ったバージョンを選択
    • LTSバージョンの選択を推奨
    • プロセッサアーキテクチャ(x64/ARM)の確認

SDKMANを使う方法

[編集]

SDKMANはJavaの開発環境を簡単に管理できるコマンドラインツールです。複数のJDKバージョンを切り替えて使用できる便利な機能を提供します。

  1. SDKMANのインストール:
    Unix系OS(Linux/macOS)で以下のコマンドを実行:
    curl -s "https://get.sdkman.io" | bash
    インストール後、次のコマンドでSDKMANを有効化:
    source "$HOME/.sdkman/bin/sdkman-init.sh"
  2. SDKMANでのJDK管理:
    • 利用可能なJDKの一覧表示:
    sdk list java
    • 特定バージョンのJDKインストール:
    sdk install java 21.0.2-tem
    • デフォルトJDKの設定:
    sdk default java 21.0.2-tem
    • インストール済みJDKの切り替え:
    sdk use java 21.0.2-tem

ビルドツールの設定

[編集]

Javaプロジェクトの依存関係管理とビルド自動化のため、以下のツールの導入を推奨します:

  1. Maven:
    • 最も広く使われているビルドツール
    • インストール方法:
    • SDKMANを使用:sdk install maven
    • 手動インストール:Apache Maven公式サイトからダウンロード
    • 基本的な設定はsettings.xmlで管理
  2. Gradle:
    • 柔軟性の高いビルドツール
    • インストール方法:
    • SDKMANを使用:sdk install gradle
    • 手動インストール:Gradle公式サイトからダウンロード
    • build.gradleファイルでビルド設定を管理

これらの環境設定が完了したら、実際の開発作業を開始できます。

変数と代入

[編集]

変数

[編集]

プログラミングにおける変数は、値やデータを格納するための記号的な名前です。変数は、メモリ内の特定の場所を参照し、その場所に値を保持します。これにより、プログラム内でデータを操作したり処理したりすることができます。

変数は、プログラム内で使用されるデータの値を表現し、名前を介してそれらの値にアクセスする手段を提供します。また、変数は値を保持するだけでなく、値を変更することもできます。これにより、プログラムが動的に振る舞うことが可能になります。

例えば、プログラムがユーザーの名前や年齢などの情報を取得し、それを後で使用する必要がある場合、変数を使用してその情報を格納し、必要に応じて変更や処理を行うことができます。

Javaにおける変数

[編集]

Javaにおける変数とは、データを格納する箱のようなものです。変数は、プログラム内で使用されるデータを保持し、それに名前を付けることができます。これにより、プログラム内で値を簡単に参照したり変更したりできます。

Javaでは、変数を使用する前に、その変数の型(データの種類)を宣言する必要があります。例えば、整数を格納する変数の場合はint型、小数を格納する変数の場合はdouble型、文字列を格納する変数の場合はString型などがあります。

宣言

[編集]

変数の宣言は次のように行います:

// 整数を格納する変数の宣言
int age;

// 小数を格納する変数の宣言
double height;

// 文字列を格納する変数の宣言
String name;

これにより、ageheightnameという名前の変数が宣言されます。ただし、この時点ではまだ値は割り当てられていません。

代入

[編集]

変数に値を代入するには、以下のようにします:

age = 25;
height = 1.75;
name = "Alice";

これにより、それぞれの変数に値が割り当てられます。

初期化

[編集]

また、変数の宣言と初期化を同時に行うこともできます:

int age = 25;
double height = 1.75;
String name = "Alice";

これにより、変数の宣言と初期化を一度に行うことができます。

再代入

[編集]

Javaにおいて、再代入とは変数に新しい値を割り当てることを指します。Javaでは、再代入が可能な変数は、その型がプリミティブ型であるか、または参照型であるかによって挙動が異なります。

  1. プリミティブ型の変数の再代入: プリミティブ型の変数は、その型に対応する値を直接保持します。再代入すると、変数が新しい値に更新されます。
    int x = 5; // int型の変数xに初期値5を代入
    x = 10;    // xに新しい値10を再代入
    
  2. 参照型の変数の再代入: 参照型の変数は、オブジェクトへの参照を保持します。再代入すると、変数が新しいオブジェクトへの参照に更新されますが、元のオブジェクトは影響を受けません。
    String str1 = "Hello"; // String型の変数str1に文字列"Hello"を代入
    str1 = "World";        // str1に新しい文字列"World"を再代入
    

変数に再代入された値の型は、変数が宣言された際に指定された型と一致する必要があります。たとえば、int型の変数には整数値を、double型の変数には浮動小数点数を再代入する必要があります。再代入された値の型が異なる場合、コンパイルエラーが発生します。

文字列

[編集]

Javaにおける文字列は、文字のシーケンスを表現するためのデータ型です。Javaでは、文字列はjava.lang.Stringクラスを使用して表現されます。文字列は、ダブルクォーテーション(”)で囲まれた文字のシーケンスとして表現されます。

文字列はイミュータブル(変更不能)であり、一度作成されると変更することができません。つまり、文字列を変更する操作は、新しい文字列を作成することになります。

以下は、Javaで文字列を宣言する例です:

String greeting = "Hello, world!";

この例では、greetingという名前の変数に文字列"Hello, world!"が代入されています。

final 修飾された変数

[編集]

Javaでは、final修飾子を使って宣言された変数は、イミュータブル(不変)です。final修飾子を使用すると、その変数に対する再代入ができなくなります。つまり、一度初期化された後はその値を変更することができません。

以下は、final修飾子を使って宣言されたイミュータブルな変数の例です:

final int AGE = 30;
final String NAME = "John";

これらの変数AGENAMEは、一度初期化された後に再代入することができません。このような変数は、プログラム内で定数として使用されることがあります。

final 修飾された参照型変数

[編集]

Javaにおいて、final修飾された参照型変数は、変数の参照先(オブジェクトへの参照)が不変であることを示します。つまり、一度初期化された後は、その変数が参照するオブジェクトを変更することはできませんが、オブジェクト自体の内容は変更可能です。

以下は、final修飾子を使って宣言された参照型変数の例です:

final StringBuilder builder = new StringBuilder("Hello");

builder.append(", world!"); // オブジェクトの内容を変更する操作

System.out.println(builder.toString()); // 出力: Hello, world!

この例では、final修飾子が付けられたbuilder変数は、一度StringBuilderオブジェクトに初期化された後、その参照先を変更することができません。ただし、StringBuilderオブジェクト自体の内容を変更することは可能です。

キャスト

[編集]

Javaにおけるキャスト(cast)は、データ型を変換する操作を指します。Javaでは、異なるデータ型の間で変換を行う際にキャストを使用します。キャストは、変換元のデータ型を指定し、変換後のデータ型に変換するための演算子です。

キャストは基本的に2つの形式があります:

  1. 明示的なキャスト(Explicit cast): 明示的なキャストは、変換元のデータ型を指定して行われます。キャストの前に、変換先のデータ型を括弧で囲んで指定します。
    double d = 10.5;
    int i = (int) d; // 明示的なキャスト
    
    この例では、double型の変数dint型の変数iに明示的にキャストしています。このキャストにより、dの小数部分が切り捨てられてiには整数部分のみが格納されます。
  2. 暗黙的なキャスト(Implicit cast): 暗黙的なキャストは、Javaの型変換ルールに従って、自動的に行われます。暗黙的なキャストは、より小さいデータ型からより大きいデータ型への変換に使用されます。
    int i = 10;
    double d = i; // 暗黙的なキャスト
    
    この例では、int型の変数idouble型の変数dに代入される際に、暗黙的なキャストが行われます。Javaはint型からdouble型への変換を自動的に行い、iの値をdに代入します。

キャストを行う際には、変換元のデータ型と変換後のデータ型が互換性があるかどうかを確認する必要があります。 たとえば、数値型同士のキャストや、参照型の継承関係に基づくキャストが可能ですが、互換性のない型同士のキャストはコンパイルエラーを引き起こします。

型推論 (var)

[編集]

Java 10以降、Javaにはローカル変数の型推論機能が追加されました。これは、varキーワードを使用して変数を宣言する際に、コンパイラが変数の型を自動的に推論する機能です。この機能により、変数の型を明示的に指定する必要がなくなり、コードの冗長性を減らすことができます。

変数の宣言時に初期化子が提供される場合、その初期化子の型に基づいて変数の型が推論されます。推論された型は静的な型であり、実行時に変更されることはありません。

例えば、以下のようにvarを使用して変数を宣言することができます:

var i = 1;

varを使用することで、変数の型が明確になる場合には冗長な型の記述を省略できます。ただし、可読性を損なわない範囲での使用が推奨されます。また、varを使用する場合でも、適切な変数名やコメントを追加することで、コードの理解を助けることが重要です。

ループ変数

[編集]

Java 10以降、Javaにはループ変数の型推論機能が追加されました。 これにより、forループや拡張forループ(拡張for文、拡張forループ)で、ループ変数の型を自動的に推論することができます。 これにより、コードの冗長性を減らし、可読性を向上させることができます。

具体的には、forループの初期化部でループ変数を宣言し、その型をvarキーワードで指定することができます。 この際、初期化式によって型が明示的に指定される場合、その型を推論してループ変数の型を決定します。

拡張forループでは、コレクションや配列を反復処理する際に、ループ変数の型を指定せずに、varキーワードを使用してループ変数を宣言することができます。この場合、コレクションや配列の要素の型がループ変数の型として推論されます。

以下は、forループと拡張forループでのループ変数の型推論の例です:

// forループでの型推論
for (var i = 0; i < 5; i++) {
    System.out.println(i); // iの型はintと推論される
}

// 拡張forループでの型推論
List<String> strings = List.of("foo", "bar", "baz");
for (var str : strings) {
    System.out.println(str); // strの型はStringと推論される
}

varを使用することで、ループ変数の型を省略し、コードをよりシンプルにすることができます。ただし、適切な変数名やコメントを追加して、コードの可読性を確保することが重要です。

Javaにおける型推論の変遷
Javaにおける型推論は、言語仕様の進化とともに次第に強化されてきました。以下にその変遷をまとめます。
  1. Java SE 5(2004年):ジェネリクスの導入
    • 背景: Java SE 5でジェネリクスが導入され、型安全なコレクション操作が可能になりました。
    • 型推論の始まり:
      ダイヤモンド演算子なし
      List<String> list = new ArrayList<String>();
      
      • 宣言部分とインスタンス化部分で型を明示的に指定する必要がありました。
  2. Java SE 7(2011年):ダイヤモンド演算子の導入
    ダイヤモンド演算子(<>によって型推論が可能に
    List<String> list = new ArrayList<>();
    
    • コンパイラがコンテキストから型を推論できるようになり、コードの簡潔性が向上しました。
  3. Java SE 8(2014年):ラムダ式とターゲット型
    • ラムダ式:
      ラムダ式では、コンパイラがコンテキスト(ターゲット型)から型を推論します。
      List<String> list = Arrays.asList("a", "b", "c");
      list.forEach(item -> System.out.println(item)); // 型推論
      
      上記では、itemの型がStringであることを推論します。
      明示的に型を書くことも可能
      list.forEach((String item) -> System.out.println(item));
      
    • メソッド参照:
      型推論がメソッド参照にも適用されました。
  4. Java SE 9(2017年):<>の改善
    • 匿名クラスのダイヤモンド演算子対応:
      Java SE 8までは、匿名クラスのインスタンス化ではダイヤモンド演算子が使えませんでしたが、Java SE 9で対応しました。
      List<String> list = new ArrayList<>() {
          // 匿名クラスの内容
      };
      
  5. Java SE 10(2018年):varの導入
    • ローカル変数型推論:
      varキーワードによってローカル変数の型をコンパイラに推論させることが可能になりました。
      var list = new ArrayList<String>();
      list.add("Hello");
      
    • 制約:
      • ローカル変数でのみ使用可能(クラスフィールドやメソッド引数には使用不可)。
      • 型が明確に推論できない場合にはエラーになる。
  6. Java SE 11(2018年):varのラムダ式引数への拡張
    • ラムダ式の引数でvarを使用可能に:
      list.forEach((var item) -> System.out.println(item));
      
      これにより、ラムダ引数に注釈を付与しつつ型推論を利用できるようになりました。
      list.forEach((@NonNull var item) -> System.out.println(item));
      
  7. 型推論の限界と注意点
    • 読みやすさの問題:
      過度に型推論に頼ると、コードの可読性が低下する場合があります。
      var map = new HashMap<>();
      
      • 上記の例では、型が明示されていないため、読み手が型を推測する必要があります。
    • 複雑な型:
      • 型推論が有効でないケースもあります(特に複雑なジェネリクスや無名クラス)。
Javaの型推論は、ダイヤモンド演算子やvarの導入を通じて徐々に拡張され、コードの簡潔性を向上させました。ただし、型推論が有効である反面、コードの可読性や明確性を意識して使用することが重要です。

ラムダ式

[編集]

ラムダ式の型推論とは、Javaでラムダ式を使用する際に、コンパイラがラムダ式のパラメータの型や戻り値の型を自動的に推論する機能を指します。つまり、ラムダ式のパラメータや戻り値の型を明示的に指定せずに、コンパイラがコードの文脈から型を推測することができます。

var x = (int i) -> i * i;

例えば、次のコードでは、ラムダ式 (int i) -> i * i のパラメータ i の型が int として指定されています。しかし、Javaのコンパイラは、このコードの文脈から i の型が int 型であることを推論することができます。

var キーワードを使用することで、変数 x の型を明示的に指定せずに、コンパイラがラムダ式の型を推論することができます。そのため、コードをよりシンプルに記述することができます。

varキーワードを使わずに、ラムダ式の型を明示的に指定する場合は、次のようになります:

IntUnaryOperator x = (int i) -> i * i;

このコードでは、IntUnaryOperatorインターフェースを使用してラムダ式を宣言しています。IntUnaryOperatorは、int型の引数を受け取り、int型の値を返す関数型インターフェースです。ラムダ式の引数がint型であるため、このインターフェースを使ってラムダ式を宣言しています。

IntUnaryOperatorインターフェース
IntUnaryOperator インターフェースは、Javaの標準ライブラリである java.util.function パッケージに含まれています。このパッケージは、関数型プログラミングをサポートするためのインターフェースやクラスが定義されています。
IntUnaryOperator インターフェースは、引数として整数型を受け取り、整数型の値を返す関数型インターフェースです。具体的には、int applyAsInt(int operand) メソッドを持ちます。これにより、整数型の引数を受け取り、整数型の結果を返す関数を表現することができます。
Javaの関数型インターフェースは、java.util.function パッケージに含まれているため、import java.util.function.IntUnaryOperator; を使用してインポートすることで利用することができます。

varによるラムダ式の型推論をつかうと、この調べごとをする必要がなくなります。

複雑な型推論

[編集]

複雑な型を返すメソッドを受け取る場合にも、型推論は有用です。以下の例では、flatMap() メソッドを使用して、文字列のリストを1つの文字列に変換しています。

List<String> list = Arrays.asList("hello", "world");
var result = list.stream()
        .flatMap(s -> Arrays.stream(s.split("")))
        .collect(Collectors.joining());

最後に、ループを用いた具体例を示します。以下の例では、リストを String[] に変換しています。

List<String> list = Arrays.asList("hello", "world");
var array = new String[list.size()];
var index = 0;
for (var element : list) {
    array[index++] = element;
}
Javaにおける型推論の変遷
Javaのラムダ式は、Java SE 8で導入され、関数型プログラミングの要素をJavaに取り入れる重要な機能として進化しました。その後のJavaバージョンでの関連機能の改善や拡張を含め、以下に変遷をまとめます。
  1. Java SE 8(2014年):ラムダ式の導入
    背景
    • 従来の匿名クラスを簡潔に記述する手段としてラムダ式が導入されました。
    • 関数型インターフェース(1つの抽象メソッドを持つインターフェース)を基にした記述が可能に。
    基本構文
      (引数) -> { 処理 }
    
    従来の匿名クラス
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello, World!");
        }
    };
    
    ラムダ式
    Runnable r = () -> System.out.println("Hello, World!");
    
    標準ライブラリへの影響
    • java.util.function パッケージに豊富な関数型インターフェース(Predicate, Consumer, Supplier, Functionなど)が追加されました。
      Stream API でラムダ式を活用したデータ操作が可能に
      List<String> list = Arrays.asList("a", "b", "c");
      list.stream()
          .filter(s -> s.startsWith("a"))
          .forEach(System.out::println);
      
  2. Java SE 9(2017年):小規模な改良
    改良点
    • 型推論やラムダ式の適用範囲に直接影響はないものの、Java SE 9ではStream APIに新機能が追加され、ラムダ式の適用例が増加しました。
    StreamtakeWhiledropWhileメソッドの導入。
    List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
    numbers.stream()
           .takeWhile(n -> n < 4)
           .forEach(System.out::println); // 1, 2, 3
    
    匿名クラスの型推論改善
    • 匿名クラスの中でジェネリクス型を省略可能に(ダイヤモンド演算子の改善)。
  3. Java SE 10(2018年):varキーワードの導入
    ラムダ式内のローカル変数型推論
    • varを利用してローカル変数の型推論が可能に。
    var list = List.of(1, 2, 3);
    list.forEach(n -> System.out.println(n));
    
    ラムダ式の影響は限定的
    • ラムダ式自体に新機能は追加されませんでしたが、varによる型推論の利用によりコードが簡潔に記述可能になりました。
  4. Java SE 11(2018年):varのラムダ引数への適用
    拡張内容
    • ラムダ式の引数でvarを使用可能に。
    List<String> list = List.of("a", "b", "c");
    list.forEach((var item) -> System.out.println(item));
    
    メリット
    • 型推論と注釈(例えば、@NonNull)の組み合わせが可能になりました。
    list.forEach((@NonNull var item) -> System.out.println(item));
    
    注意点
    • ラムダ式の引数全体で一貫してvarを使用する必要があります。
    (var a, b) -> { ... } // コンパイルエラー
    
  5. Java SE 17(2021年):ラムダ式とレコード型
    背景
    • Java SE 14で導入されたレコード型(record)は、関数型プログラミングとの相性が良いデータ構造。
    • ラムダ式を使用したコードでレコード型が使いやすくなりました。
    record Point(int x, int y) {}
    
    List<Point> points = List.of(new Point(1, 2), new Point(3, 4));
    points.stream()
          .filter(p -> p.x > 2)
          .forEach(System.out::println);
    
  6. Java SE 21(2023年):関数型プログラミングの強化
    シーリングクラスとパターンマッチングの改良
    • パターンマッチングや型チェックでラムダ式の利用がさらに直感的になりました。
    例(パターンマッチングとラムダ式の併用)
    Stream.of(1, "text", 3.0)
          .map(o -> switch (o) {
              case Integer i -> i * 2;
              case String s -> s.length();
              case Double d -> d.intValue();
              default -> 0;
          })
          .forEach(System.out::println);
    
ラムダ式の利点と限界
利点
  • 匿名クラスより簡潔で読みやすい。
  • スレッドやイベント処理などの非同期プログラミングに適している。
  • Stream APIとの組み合わせで高い生産性を発揮。
限界
  • 過度な使用はコードの可読性を低下させる可能性がある。
  • 直感的でない場合や複雑な処理では依然として従来の構文が適している。
Javaのラムダ式は、Java SE 8での導入以降、さまざまな改良や拡張を経てきました。特に、型推論の向上や新しいAPIの追加により、簡潔で直感的なコードが書きやすくなっています。これにより、Javaがよりモダンなプログラミングスタイルを取り入れ、他の関数型言語とのギャップを縮めています。

附録

[編集]

チートシート

[編集]
public class Main {
    public static void main(String[] args) {
        // 変数の宣言と初期化
        int num1 = 10;
        int num2 = 20;
        String name = "John";
        boolean flag = true;
        
        // 変数の使用と演算
        System.out.println("num1 + num2 = " + (num1 + num2)); // 30
        System.out.println("My name is " + name); // My name is John
        System.out.println("flag is " + flag); // flag is true
        
        // プリミティブ型と参照型
        int[] array = {1, 2, 3}; // 参照型の例
        char ch = 'a'; // プリミティブ型の例
        
        // プリミティブ型と参照型の違い
        int num3 = num1; // num1の値をコピーしてnum3に代入(値渡し)
        int[] array2 = array; // arrayの参照をコピーしてarray2に代入(参照渡し)
        array[0] = 100; // arrayの値を変更
        
        System.out.println("num3 = " + num3); // num3 = 10
        System.out.println("array[0] = " + array[0]); // array[0] = 100
        System.out.println("array2[0] = " + array2[0]); // array2[0] = 100
        
        // プリミティブ型と参照型の比較
        boolean isEqual = num1 == num3; // プリミティブ型同士の比較
        boolean isSameArray = array == array2; // 参照型同士の比較
        
        System.out.println("num1 == num3 : " + isEqual); // num1 == num3 : true
        System.out.println("array == array2 : " + isSameArray); // array == array2 : true
    }
}

用語集

[編集]
  1. 変数 (Variable): プログラミングにおける変数は、値やデータを格納するための記号的な名前です。プログラム内でデータを操作したり処理したりする際に使用されます。
  2. 宣言 (Declaration): 変数の宣言とは、変数をプログラム内で使用する準備をすることです。変数の型と名前を指定して、コンピュータにその変数を識別するための領域を割り当てます。
  3. 代入 (Assignment): 代入とは、変数に値を格納する操作です。変数に値を代入することで、その変数がその値を保持することができます。
  4. 初期化 (Initialization): 初期化とは、変数に初期値を設定する操作です。変数を宣言と同時に初期値を設定することで、その変数が宣言された時点で値を持つことができます。
  5. 再代入 (Reassignment): 再代入とは、変数に新しい値を割り当てる操作です。変数には、同じ型の新しい値を代入することができます。
  6. イミュータブル (Immutable): イミュータブルとは、変更不可能なことを指します。Javaでは、final修飾子を使用して宣言された変数や、文字列などの一部のオブジェクトはイミュータブルです。
  7. キャスト (Cast): キャストとは、データ型を変換する操作です。Javaでは、異なるデータ型の間で変換を行う際にキャストが使用されます。
  8. 型推論 (Type Inference): 型推論とは、コンパイラがコードの文脈から変数の型を推測する機能です。Java 10以降では、varキーワードを使用して型推論を行うことができます。
  9. ラムダ式 (Lambda Expression): ラムダ式とは、無名関数を表現するための記法です。Javaでは、関数型インターフェースの実装として使用されます。
  10. ループ変数 (Loop Variable): ループ変数とは、forループや拡張forループで使用される反復変数のことを指します。Java 10以降では、ループ変数の型推論機能が追加されました。
  11. 複雑な型推論 (Complex Type Inference): 複雑な型を返すメソッドを受け取る場合にも、型推論が有用です。Javaでは、flatMap()メソッドなどでの複雑な型推論が行われます。

コメント

[編集]

プログラミングにおけるコメントは、ソースコード内に記述される説明文です。プログラムの動作には影響を与えず、主にコードの理解や保守を助けるために使用されます。コメントには以下のような用途があります:

  1. コードの説明: 処理の目的や実装の詳細を説明します。他の開発者がコードを理解しやすくなります。
  2. 設計意図の記録: なぜその実装方法を選んだのか、代替案や制約事項などを残します。
  3. 一時的な無効化: コードの一部を実行から除外する際に使用します(コメントアウト)。

適切なコメントは、コードの品質向上や開発効率の改善に貢献する重要な要素です。

Javaのコメント記法

[編集]

Javaでは3種類のコメント記法が提供されています:

単一行コメント

[編集]

行末までをコメントとして扱います。短い説明や、特定の行の補足に適しています。

// これは単一行コメントです
int count = 0; // カウンター変数の初期化

複数行コメント

[編集]

複数行に渡る説明を記述できます。コードブロックの説明や、一時的に複数行のコードを無効化する際に使用します。

/* 
 * これは複数行コメントです。
 * 長い説明を記述できます。
 */
int result = calculate(); /* 計算結果を
                            格納します */

ドキュメンテーションコメント(JavaDoc)

[編集]

API仕様書を生成するためのコメントです。クラスやメソッドの仕様を記述します。

/**
 * ユーザー情報を管理するクラス。
 * システム全体でユーザーの登録や認証を担当します。
 */
public class UserManager {
    /**
     * 指定されたIDのユーザーを取得します。
     * @param id ユーザーID
     * @return ユーザーオブジェクト。該当するユーザーが存在しない場合はnull
     * @throws IllegalArgumentException IDが無効な場合
     */
    public User getUser(String id) {
        // メソッドの実装
    }
}

JavaDoc

[編集]

JavaDocは、JavaプログラムのAPIドキュメントを生成するための公式ツールです。ソースコード内の特別な形式のコメントから、HTML形式の仕様書を生成します。

基本的なタグ

[編集]

JavaDocでは、以下のような標準タグを使用できます:

  • @param - メソッドの引数の説明
  • @return - 戻り値の説明
  • @throws - 発生する可能性がある例外の説明
  • @see - 関連する他の要素への参照
  • @since - 導入されたバージョン
  • @deprecated - 非推奨である理由と代替手段
/**
 * 商品の在庫を管理するクラス。
 * 
 * @author 開発部
 * @version 1.0
 * @since 2024-01-01
 */
public class InventoryManager {
    /**
     * 指定された商品の在庫数を更新します。
     * 
     * @param productId 商品ID
     * @param quantity 在庫数
     * @return 更新後の在庫数
     * @throws IllegalArgumentException 商品IDが無効な場合
     * @throws StockException 在庫数が負数の場合
     * @see ProductManager
     */
    public int updateStock(String productId, int quantity) {
        // メソッドの実装
    }
}

JEP 467: Markdown Documentation Comments

[編集]

Java 21から導入された新機能で、JavaDocコメントでMarkdown記法が使用できるようになりました。これにより、ドキュメントの記述がより直感的になります。

主な特徴

[編集]
  1. Markdown記法のサポート: 複雑なHTMLタグの代わりに、シンプルなマークダウンが使用できます。
  2. 既存JavaDocとの互換性: 従来のHTMLベースの記法も引き続き使用できます。
  3. コードスニペット機能: @snippetタグによる、より柔軟なコード例の記述が可能です。

記述例

[編集]
Point.java
// Point.java
package com.example;

/// A point in a 2-dimensional space.
/// 
/// # Examples
/// 
/// ```java
/// Point p = new Point(10, 20);
/// p.move(5, -3);
/// System.out.println(p.getX()); // prints 15
/// ```
/// 
/// # Implementation Notes
/// 
/// This class is immutable and thread-safe.
public class Point {
    private final int x;
    private final int y;
    
    /// Creates a new point at the specified coordinates.
    /// 
    /// # Parameters
    /// 
    /// * x   the x coordinate
    /// * y   the y coordinate
    /// 
    /// # Throws
    /// 
    /// * IllegalArgumentException  if coordinates are out of bounds (-100 to 100)
    public Point(int x, int y) {
        if (x < -100 || x > 100 || y < -100 || y > 100) {
            throw new IllegalArgumentException("Coordinates must be between -100 and 100");
        }
        this.x = x;
        this.y = y;
    }

    /// Returns a new point that is offset from this point.
    /// The original point remains unchanged.
    /// 
    /// Example usage:
    /// ```java
    /// Point p1 = new Point(0, 0);
    /// Point p2 = p1.move(5, 10);
    /// ```
    /// 
    /// @param  dx  the distance to move along the x axis
    /// @param  dy  the distance to move along the y axis
    /// @return     a new point with the specified offset
    /// @see        [#getX()]
    /// @see        [#getY()]
    public Point move(int dx, int dy) {
        return new Point(x + dx, y + dy);
    }

    /// Returns the x coordinate of this point.
    /// 
    /// # Returns
    /// 
    /// the x coordinate
    public int getX() {
        return x;
    }

    /// Returns the y coordinate of this point.
    /// 
    /// # Returns
    /// 
    /// the y coordinate
    public int getY() {
        return y;
    }
}
module-info.java
// module-info.java
module com.example {
    exports com.example;
}

コメントの書き方のベストプラクティス

[編集]

効果的なコメントを書くためのガイドラインです:

  1. 必要な場所に書く: コードだけでは意図が分かりにくい箇所に記述します。
  2. 簡潔に書く: 冗長な説明は避け、要点を絞って記述します。
  3. 最新の状態を保つ: コードの変更に合わせて、コメントも更新します。
  4. なぜそうしたのかを説明する: 実装の理由や背景を記録します。

コメントを書く際の注意点

[編集]

コメントは有用なツールですが、以下の点に注意が必要です:

  1. 自明な内容は書かない: コードを読めば明らかな内容をコメントで繰り返さない。
  2. コードの代わりにしない: 分かりにくいコードを補完する目的でコメントを使わない。
  3. 古いコメントを放置しない: 誤解を招く可能性があるため、定期的に見直す。
  4. コメントに頼りすぎない: まずはコード自体を分かりやすくすることを心がける。

警告: 既定のソートキー「こめんと」が、その前に書かれている既定のソートキー「ふろくらみんくのためのしゆんひ」を上書きしています。

式と演算子

[編集]

プログラミングにおける式(Expression)は、値や演算子、関数呼び出し、変数などから構成される計算を表す文法構造です。式はプログラム内で評価され、結果の値を生成します。

演算子(Operator)は、式内で値や変数を操作するための記号またはキーワードです。演算子には算術演算子(加算、減算など)、比較演算子(等しい、大なり、小なりなど)、論理演算子(AND、ORなど)、代入演算子(変数に値を代入する演算子)などがあります。演算子は一般的に、式内で値を組み合わせて新しい値を生成するために使用されます。

例えば、以下のような式があります:

a + b * c

この式は、変数 abc の値を使用して、乗算(*)と加算(+)の演算を行います。これにより、新しい値が生成されます。

Javaにおける式と演算子

[編集]

Javaにおける式(Expression)は、値、変数、演算子、メソッド呼び出し、またはこれらの組み合わせから構成されるプログラム内の計算を表す文法要素です。式はプログラム内で評価され、結果の値を生成します。

Javaの演算子(Operator)は、式内で値や変数を操作するための特殊な記号またはキーワードです。

Javaの演算子は多岐にわたり、主な種類には以下のようなものがあります:

  1. 算術演算子(Arithmetic Operators): 加算(+)、減算(-)、乗算(*)、除算(/)、剰余(%)など、数値データ型の間で算術演算を実行するための演算子です。
  2. 比較演算子(Comparison Operators): 等しい(==)、等しくない(!=)、大なり(>)、小なり(<)、大なりイコール(>=)、小なりイコール(<=)など、値や式の比較を行うための演算子です。結果は真偽値(trueまたはfalse)となります。
  3. 論理演算子(Logical Operators): 論理積(AND:&&)、論理和(OR:||)、否定(NOT:!)など、真偽値を操作するための演算子です。
  4. ビット演算子(Bitwise operators): 論理積(AND:&)、論理和(OR:|)、排他的論理和(XOR:^)、否定(NOT:~)などの操作を提供し、ビット単位の操作により効率的なデータ処理やビットマスクの作成が可能です。
  5. 代入演算子(Assignment Operators): 代入(=)、加算代入(+=)、減算代入(-=)、乗算代入(*=)など、変数に値を代入するための演算子です。
  6. その他の演算子(Other Operators): インクリメント(++)、デクリメント(--)、条件演算子(条件式 ? 結果1 : 結果2)、ビット演算子(ビット単位の論理演算を行う演算子)など、その他の種類の演算子があります。

これらの演算子を使用して、Javaプログラム内で様々な計算や操作を行うことができます。

算術演算子

[編集]

Javaにおける算術演算子は、基本的な数値計算を行うための演算子を指します。 Javaでは、整数型と浮動小数点型のデータを扱うための算術演算子が提供されています。

主な算術演算子には以下が含まれます:

  1. 加算(Addition)
    int sum1 = 5 + 3; // 整数の加算、sum1には8が代入される
    double sum2 = 5.5 + 3.2; // 浮動小数点数の加算、sum2には8.7が代入される
    
  2. 減算(Subtraction)
    int difference1 = 10 - 4; // 整数の減算、difference1には6が代入される
    double difference2 = 10.5 - 4.2; // 浮動小数点数の減算、difference2には6.3が代入される
    
  3. 乗算(Multiplication)
    int product1 = 6 * 2; // 整数の乗算、product1には12が代入される
    double product2 = 3.5 * 2.0; // 浮動小数点数の乗算、product2には7.0が代入される
    
  4. 除算(Division)
    double quotient1 = 10.0 / 3.0; // 浮動小数点数の除算、quotient1には3.3333...が代入される
    double quotient2 = 10 / 3.0; // 整数と浮動小数点数の除算、quotient2には3.3333...が代入される
    
  5. 剰余(Modulus)
    int remainder1 = 10 % 3; // 整数の剰余、remainder1には1が代入される
    double remainder2 = 10.5 % 3.0; // 浮動小数点数の剰余、remainder2には1.5が代入される
    

Javaの算術演算は、整数型と浮動小数点型の両方で動作し、適切な結果を返します。 プログラマは、演算に使用される数値の型を適切に選択する必要があります。

多様な算術演算子

[編集]
/**
 * 算術演算子の例
 */
public class Main {

    /**
     * メインメソッド。プログラムのエントリーポイント。
     * 
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        int a = 10;
        int b = 3;
        
        System.out.println(a + " + " + b + " = " + (a + b));
        System.out.println(a + " - " + b + " = " + (a - b));
        System.out.println(a + " * " + b + " = " + (a * b));
        System.out.println(a + " / " + b + " = " + (a / b));
        System.out.println(a + " % " + b + " = " + (a % b));

        a = Integer.MAX_VALUE;
        b = 1;
        System.out.println(a + " + " + b + " = " + (a + b) + "\t// オーバーフロー!");

        // ゼロ除算:
        a = 42;
        b = 0;
        try {
            System.out.print(a + " / " + b + " = ");
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            System.out.println("例外捕捉:" + e);
        }
        
        // ゼロ除算:
        try {
            System.out.print(a + " % " + b + " = ");
            System.out.println(a % b);
        } catch (ArithmeticException e) {
            System.out.println("例外捕捉:" + e);
        }
        
        // double型の算術演算
        double x = 10.5;
        double y = 3.2;
        System.out.println(x + " + " + y + " = " + (x + y));
        System.out.println(x + " - " + y + " = " + (x - y));
        System.out.println(x + " * " + y + " = " + (x * y));
        System.out.println(x + " / " + y + " = " + (x / y));
        System.out.println(x + " % " + y + " = " + (x % y));

        y = 0.0;
        System.out.println(x + " / " + y + " = " + (x / y));

        x = 0.0;
        System.out.println(x + " / " + y + " = " + (x / y));
    }
}
実行結果
10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3
10 % 3 = 1
2147483647 + 1 = -2147483648	// オーバーフロー!
42 / 0 = 例外捕捉:java.lang.ArithmeticException: / by zero
42 % 0 = 例外捕捉:java.lang.ArithmeticException: / by zero
10.5 + 3.2 = 13.7
10.5 - 3.2 = 7.3
10.5 * 3.2 = 33.6
10.5 / 3.2 = 3.28125
10.5 % 3.2 = 0.8999999999999995
10.5 / 0.0 = Infinity 
0.0 / 0.0 = NaN

InfinityとNaN

[編集]

JavaのInfinityとNaNは、浮動小数点数の特殊な値であり、IEEE 754規格に基づいて定義されています。以下に、それぞれの意味と特性について説明します。

JavaのInfinityとNaNは、浮動小数点数の計算において特殊な値として使用されます。これらの値は、次のような場面で発生します。

  1. Infinity(無限大):
    • 無限大は、有限の数値を0.0で割った場合や、オーバーフローが発生した場合に発生します。
    • 例えば、double result = 5.0 / 0.0;のような式を評価すると、resultの値は無限大になります。
    • 無限大は、有限の数値よりも大きいことが特性として挙げられます。
    • Double.POSITIVE_INFINITYの定数は(正の)無限大を表します。
    • IEEE 754規格では、無限大は指数部が符号が+、最大値でかつ仮数部が0の状態で表されます。
  2. -Infinity(負の無限大):
    • 負の無限大、有限の負の数値を0.0で割った場合や、オーバーフローが発生した場合に発生します。
    • 例えば、double result = 5.0 / -0.0;のような式を評価すると、resultの値は負の無限大になります。
    • 負の無限大は、有限の数値よりも小さいことが特性として挙げられます。
    • Double.NEGATIVE_INFINITYの定数は、負の無限大を表します。
    • IEEE 754規格では、負の無限大は指数部が符号が+、最大値でかつ仮数部が0の状態で表されます。
  3. NaN(非数):
    • NaNは、0を0で割った場合や、0.0を0.0で割った場合、あるいは数学的に不正な演算が行われた場合に発生します。
    • 例えば、double result = Math.sqrt(-1);のような式を評価すると、resultの値はNaNになります。
    • NaNは、数値としての意味を持たないことを示します。
    • Double.NaNという定数があり、これはNaNを表します。
    • NaNは、どの数値とも等しくないため、NaNとの比較は常にfalseになります。
    • IEEE 754規格では、NaNは指数部が最大値でかつ仮数部が0でない状態で表されます。

isNaN()とisFinite()メソッド:

  • Javaでは、DoubleクラスやFloatクラスには、NaNやInfinityを判定するためのメソッドが用意されています。
  • isNaN()メソッドは、引数がNaNかどうかを判定します。NaNの場合はtrueを返し、それ以外の場合はfalseを返します。
  • isFinite()メソッドは、引数が有限の数かどうかを判定します。InfinityやNaNの場合はfalseを返し、それ以外の場合はtrueを返します。

IEEE 754規格に基づいて定義されたこれらの特殊な値は、浮動小数点数の算術演算においてエラー処理や特殊な状況を処理するために使用されます。これらの特性を理解することは、正確な数値計算を行う上で重要です。

-0.0

[編集]

-0.0(マイナスゼロ)は、浮動小数点数の一部で、通常のゼロとは異なる概念です。IEEE 754規格では、ゼロを表現する方法として、符号付きゼロ(+0.0および-0.0)が導入されています。

  • +0.0(プラスゼロ): すべてのビットが0で、符号ビットが0。
  • -0.0(マイナスゼロ): すべてのビットが0で、符号ビットが1。

この区別は、通常の算術演算では影響を与えませんが、一部の特殊な状況で重要になります。例えば、次のような場面で符号つきゼロが役立ちます。

  1. ゼロで割った場合:
    • 1.0 / +0.0 は正の無限大になります。
    • 1.0 / -0.0 は負の無限大になります。
  2. 符号を保持する場合:
    • 符号つきゼロは、正確な結果を保持するために使用されます。例えば、正の数から負の数を引くときなどに、結果の符号を正確に反映するために使われます。

JavaのDoubleとFloatのデータ型では、+0.0と-0.0は異なる値として区別されます。これは、JavaがIEEE 754規格に従っているためです。例えば、以下のようなコードを実行すると、+0.0と-0.0が等しいかどうかを確認できます。

System.out.println(0.0 == -0.0); // true
System.out.println(Double.compare(0.0, -0.0)); // 1
System.out.println(Double.compare(0.0, 0.0)); // 0
System.out.println(Double.compare(-0.0, 0.0)); // -1
このコードでは、==演算子によって0.0と-0.0が等しいかどうかを比較しています。
Double.compareメソッドによって比較すると、ゼロの符号の違いを考慮します。

Double.compare() は、Java プログラミング言語において、2 つの double 値を比較するための静的メソッドです。このメソッドは、次のような形式で使用されます:

public static int compare(double d1, double d2)

Double.compare() メソッドは、以下のルールに従って比較を行います。

  • もし d1d2 より小さい場合、負の値が返されます。
  • もし d1d2 より大きい場合、正の値が返されます。
  • もし d1d2 と等しい場合、0 が返されます。

算術演算の注意事項

[編集]

桁あふれ

[編集]

整数演算の桁あふれ

[編集]

整数演算の桁あふれ(オーバーフロー)は、整数の演算結果がそのデータ型で表現可能な範囲を超える場合に発生します。Javaの整数型には、それぞれの範囲が定義されています。以下に、主な整数型とその範囲を示します。

  1. byte: -128 から 127
  2. short: -32,768 から 32,767
  3. int: -2,147,483,648 から 2,147,483,647
  4. long: -9,223,372,036,854,775,808 から 9,223,372,036,854,775,807

たとえば、int型でのオーバーフローが発生する状況を考えてみましょう。以下のコードでは、整数の最大値に1を加えようとしています。

int maxValue = Integer.MAX_VALUE;
int overflow = maxValue + 1;
System.out.println("Overflow: " + overflow);

このコードを実行すると、overflow 変数は -2147483648 になります。これは、Integer.MAX_VALUE で表現される最大値に1を加えることで、オーバーフローが発生し、最小値(Integer.MIN_VALUE)に戻るためです。

同様に、他の整数型でも同様の挙動が発生します。オーバーフローを防ぐためには、適切な範囲内での演算を行うか、オーバーフローが発生する可能性がある場合には適切に処理する必要があります。

Math.addExactと、そのファミリー
[編集]

Javaでは、整数演算でオーバーフローを生じても例外は上がりません。 例外をあげオーバーフローを捕捉するために、Math.addExactと、そのファミリーが用意されています。

Math.addExact() メソッドは、Java 8 で追加された整数演算時のオーバーフローを検出するためのメソッドの一部です。このメソッドは、整数型の加算を行い、結果がそのデータ型で表現可能な範囲を超えた場合に ArithmeticException をスローします。

Math クラスには、他にもオーバーフローを検出するためのメソッドが用意されています。主なものには、次のようなものがあります:

  1. Math.addExact(int x, int y):整数値 xy を加算し、オーバーフローが発生した場合は ArithmeticException をスローします。
  2. Math.subtractExact(int x, int y):整数値 x から y を減算し、オーバーフローが発生した場合は ArithmeticException をスローします。
  3. Math.multiplyExact(int x, int y):整数値 xy を乗算し、オーバーフローが発生した場合は ArithmeticException をスローします。
  4. Math.incrementExact(int a):整数値 a をインクリメントし、オーバーフローが発生した場合は ArithmeticException をスローします。
  5. Math.decrementExact(int a):整数値 a をデクリメントし、オーバーフローが発生した場合は ArithmeticException をスローします。

これらのメソッドを使用することで、整数演算時にオーバーフローが発生した場合に、適切に例外を処理できます。

以下は、Math.addExact() メソッドを使用して整数値の加算を行い、オーバーフローが発生した場合に例外を処理する例です。

public class Main {
    public static void main(String[] args) {
        try {
            int result = Math.addExact(Integer.MAX_VALUE, 1);
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Overflow occurred: " + e.getMessage());
        }
    }
}

このコードでは、Integer.MAX_VALUE に 1 を加算しようとしています。Math.addExact() メソッドは、この加算がオーバーフローを引き起こす可能性があるため、例外をスローします。try-catch ブロックを使用して、ArithmeticException をキャッチし、オーバーフローが発生したことを示すメッセージを出力しています。

浮動小数点演算の桁あふれ

[編集]

浮動小数点演算の桁あふれは、浮動小数点数を操作する際に、その値がデータ型で表現可能な範囲を超える場合に発生します。浮動小数点数は、指数部と仮数部から構成され、一定の精度を保ちつつ非常に大きな値や小さな値を表現するために使用されます。

Javaにおいて、浮動小数点数は主に以下の2つのデータ型で表現されます。

  1. float: 単精度浮動小数点数 (32ビット)
  2. double: 倍精度浮動小数点数 (64ビット)

これらのデータ型は、それぞれ一定の範囲と精度を持っていますが、非常に大きな値や小さな値に対しても表現可能です。

桁あふれは、浮動小数点演算の結果が、そのデータ型で表現可能な範囲を超える場合に発生します。これにより、計算結果が無限大や無限小に発散する場合や、精度が失われる場合があります。また、浮動小数点数の演算において、有効桁数を超えた部分が切り捨てられることも桁あふれの一形態です。

例えば、次のコードでは、倍精度浮動小数点数の最大値に1を加える操作を行っています。

double maxValue = Double.MAX_VALUE;
double overflow = maxValue + 1;
System.out.println("Overflow: " + overflow);

この場合、overflow 変数の値は Infinity になります。これは、最大値に1を加えた結果が倍精度浮動小数点数の表現可能な範囲を超えたため、桁あふれが発生したことを示しています。

浮動小数点数と誤差

[編集]

Javaの浮動小数点数における誤差は、主に2つの要因によって生じます。

  1. 有効桁数の制限: Javaの浮動小数点数は、IEEE 754標準に基づいて実装されています。float型は32ビットで、有効桁数は約7桁です。double型は64ビットで、有効桁数は約15桁です。そのため、非常に大きな数や非常に小さな数を表現する場合、精度の制限により誤差が生じます。たとえば、10進数で0.1をfloat型やdouble型で表現すると、厳密には0.1ではなく、近似的な値になります。
  2. 丸め誤差と演算誤差: 浮動小数点数の演算においては、丸め誤差や演算誤差が生じることがあります。これは、浮動小数点数を近似的に表現するための有限なビット数で演算を行うために生じるものです。特に、加算や減算、乗算、除算などの演算において、丸め誤差や演算誤差が生じることがあります。これにより、計算結果が厳密な値と異なる場合があります。

Javaの浮動小数点数における誤差を最小限に抑えるためには、次のような注意点があります。

計算の順序を適切に管理し、丸め誤差を最小限に抑える。

  • 必要に応じて、BigDecimalクラスを使用して高精度な計算を行う。
  • 数値計算ライブラリや専門家によって提供される特殊な数値計算手法を使用する。

これらの対策を講じることで、Javaの浮動小数点数による誤差を最小限に抑えることができます。

また、浮動小数点数の演算における丸め誤差や演算誤差を最小限に抑えるためには、以下のような注意点があります。

  1. 適切なデータ型を選択する: 浮動小数点数は精度を失う可能性があるため、必要な精度に合わせてfloat型またはdouble型を選択する必要があります。より高い精度が必要な場合はdouble型を使用しますが、その分メモリ使用量も大きくなります。
  2. 演算の順序を考慮する: 浮動小数点数の演算においては、演算の順序が結果に影響を与える場合があります。たとえば、加算や減算といった基本的な演算から始め、乗算や除算といった複雑な演算を後に行うと、丸め誤差を最小限に抑えることができます。
  3. 比較演算における注意: 浮動小数点数の比較演算は、厳密な等値判定が難しい場合があります。丸め誤差や演算誤差の影響を考慮して、適切な精度で比較演算を行う必要があります。通常は、等値判定ではなく、ある値が別の値よりも大きいか、または小さいかを判定することが推奨されます。
  4. 結果の解釈: 浮動小数点数の演算結果を解釈する際には、その精度や誤差の範囲を理解する必要があります。特に、科学計算や金融取引などの精密な計算においては、計算結果の信頼性を確保するために十分な検証と解釈が必要です。

これらの注意点を考慮することで、Javaの浮動小数点数による計算における誤差を最小限に抑えることができます。

さらに、浮動小数点数の計算における誤差を最小限に抑えるために、次のようなアプローチも考慮されます。

  1. 丸めモードの設定: Javaでは、浮動小数点数の計算における丸めモードを設定することができます。デフォルトでは、丸めモードは「デフォルト」であり、周囲に最も近い数に丸められます。しかし、必要に応じて丸めモードを変更して、計算結果に影響を与えることができます。例えば、丸め誤差を最小限に抑えるために、上向き丸め(ceiling)、下向き丸め(floor)、ゼロから遠い方向の丸め(向上丸め)、ゼロに近い方向の丸め(向下丸め)などの丸めモードを使用することができます。
  2. 有効桁数の管理: 浮動小数点数の計算においては、有効桁数の制限により誤差が生じることがあります。特に、非常に大きな数や非常に小さな数を表現する場合、有効桁数の不足により計算結果に誤差が生じることがあります。そのため、計算に使用する浮動小数点数の有効桁数を適切に管理することが重要です。必要に応じて、BigDecimalなどの精度の高いデータ型を使用して計算を行うことも考慮されます。
  3. ライブラリの活用: 数値計算に関連するライブラリを活用することで、浮動小数点数の計算における誤差を最小限に抑えることができます。これらのライブラリには、高精度な計算を行うための機能や、誤差を最小限に抑えるためのアルゴリズムが含まれています。特に、金融取引や科学計算などの精密な計算を行う場合には、これらのライブラリを活用することが推奨されます。

以上のようなアプローチを組み合わせることで、Javaの浮動小数点数を使用した計算における誤差を最小限に抑えることができます。ただし、特定の問題や要件に応じて、最適なアプローチを選択する必要があります。

0.1を10回足しても1.0にならない!

[編集]

浮動小数点数の内部表現により、一部の10進数を正確に表現することができません。例えば、0.1を浮動小数点数として表現すると、その厳密な値ではなく近似値になります。そのため、0.1を10回足しても厳密に1.0にはならない場合があります。

Javaでは、float型やdouble型を使用して浮動小数点数を表現しますが、これらの型は有限のビット数で浮動小数点数を表現するため、一部の10進数を正確に表現することができません。その結果、浮動小数点数の計算においては、丸め誤差や演算誤差が生じる可能性があります。

例えば、次のコードを見てみましょう。

double sum = 0.0;
for (int i = 0; i < 10; i++) {
    sum += 0.1;
}
System.out.println(sum); // 出力: 0.9999999999999999

このコードでは、0.1を10回足した結果が正確に1.0にならず、0.9999999999999999 という近似値になります。

このように誤差が生じるのは、内部的に浮動小数点数が2進数で扱われているためです(0.1は、2進数では循環小数になるため正確に表現できません)。

カハンの総和アルゴリズム
[編集]

誤差を補正する方法はいくつかありますが、カハンの総和アルゴリズム( Kahan summation algorithm )が代表的です。

カハンの総和アルゴリズム
public class Main {
    public static void main(String[] args) {
        double d = 0.0;
        for (int i = 0; i < 10; i++) d += 0.1;
        System.out.println(d);

        d = 0.0;
        double c = 0.0;
        for (int i = 0; i < 10; i++) {
            double y = 0.1 - c;
            double t = d + y;
            c = ( t - d ) - y;
            d = t;
        }
        System.out.println(d);

        float f = 0.0f;
        for (int i = 0; i < 10; i++) f += 0.1f;
        System.out.println(f);
        
        f = 0.0f;
        float fc = 0.0f;
        for (int i = 0; i < 10; i++) {
            float y = 0.1f - fc;
            float t = f + y;
            fc = ( t - f ) - y;
            f = t;
        }
        System.out.println(f);
    }
}
$ javac Main.java
$ java Main
0.9999999999999999
1.0
1.0000001 
1.0
Java Stream API
[編集]

また、Java Stream API も補正アルゴリズムを実装しています。

Stream APIを使った総和
import java.util.stream.DoubleStream;

class Main {
    public static void main(String[] args) {
        // DoubleStream.builder() を使用して、DoubleStream.Builderオブジェクトを作成します。
        // これは、DoubleStreamを構築するためのビルダーオブジェクトです。
        final var builder = DoubleStream.builder();
        
        // ループを10回実行し、builder.add(0.1) を使用して、ビルダーオブジェクトに0.1を追加します。
        // これにより、10回のループを通じて0.1が10回追加されます。
        for (int i = 0; i < 10; i++)
            builder.add(0.1);
        
        // builder.build() を使用して、ビルダーオブジェクトからDoubleStreamオブジェクトを構築します。
        // これにより、10回のループで追加された0.1の値が含まれたDoubleStreamが得られます。
        System.out.println(builder.build().sum());
        
        // sum() メソッドを使用して、DoubleStreamオブジェクトの合計を計算します。
        // これにより、10回のループで追加された0.1の値の合計が計算されます。
        
        // 最後に、計算された合計値が出力されます。
    }
}
1.0
DoubleStreamのメソッドsum()は、保証を行うアルゴリズムを採用しています。

優先順位と結合性

[編集]

Javaの算術演算子には、優先順位と結合性があります。以下に、一般的な算術演算子の優先順位と結合性を示します。

乗算 (*) および除算 (/、%): 乗算と除算は、加算と減算よりも高い優先順位を持ちます。また、乗算と除算は左から右に結合します。
加算 (+) および減算 (-): 加算と減算は、乗算と除算と同じ優先順位を持ちます。また、加算と減算も左から右に結合します。

優先順位

[編集]

この優先順位と結合性に基づいて、式が評価されます。例えば、次の式を考えてみましょう。

int result = 5 + 3 * 2;

この式では、乗算 (*) が加算 (+) よりも高い優先順位を持つため、まず 3 * 2 が計算されます。結果は 6 です。その後、5 と 6 の加算が行われ、最終的な結果は 11 になります。

結合性

[編集]

結合性(associativity)は、演算子が式内で連続して出現する場合に、その演算子がどのような順序で評価されるかを示す性質です。結合性は通常、左から右への結合(左結合性)または右から左への結合(右結合性)のいずれかとして定義されます。

算術演算子の結合性により、この式は左から右に評価されます。例えば、次の式を考えてみましょう。

int result = 10 - 5 - 3;

この式では、減算 (-) は左から右に結合するため、まず左側の 10 - 5 が計算されます。結果は 5 です。その後、5 から 3 を減算することで、最終的な結果は 2 になります。

Javaの算術演算子の優先順位と結合性を理解することで、式を正しく評価することができます。

比較演算子

[編集]

Javaの比較演算子は、異なる値や式の間で比較を行い、結果を真偽値(trueまたはfalse)で返します。以下は、Javaで使用される主な比較演算子です。

  1. ==:2つの値が等しいかどうかを比較します。プリミティブ型の場合、値の比較を行います。参照型の場合、オブジェクトの参照が同じかどうかを比較します。
  2. !=:2つの値が等しくないかどうかを比較します。
  3. >:左辺の値が右辺の値より大きいかどうかを比較します。
  4. <:左辺の値が右辺の値より小さいかどうかを比較します。
  5. >=:左辺の値が右辺の値以上かどうかを比較します。
  6. <=:左辺の値が右辺の値以下かどうかを比較します。

これらの比較演算子は、条件文やループ、その他の制御構造でよく使用されます。

論理演算子

[編集]

論理演算子は、論理値(真偽値)を操作するために使用されます。Javaでは、3つの主要な論理演算子があります。

  1. &&(AND):論理積演算子は、両側の式がともにtrueの場合にtrueを返します。片方でもfalseであれば、falseを返します。
  2. ||(OR):論理和演算子は、両側の式の少なくとも一方がtrueの場合にtrueを返します。両方がfalseの場合にfalseを返します。
  3. !(NOT):論理否定演算子は、式の真偽を反転させます。trueはfalseに、falseはtrueになります。

これらの演算子は、条件文やループなどの制御構造で論理式を組み立てるために使用されます。

ビット演算子

[編集]

ビット演算子は、整数のビットレベルでの演算を実行します。Javaのビット演算子は、以下のようになります。

  1. &:ビット単位のAND演算を行います。
  2. |:ビット単位のOR演算を行います。
  3. ^:ビット単位のXOR(排他的論理和)演算を行います。
  4. ~:ビット単位の補数(ビット反転)演算を行います。
  5. <<:左シフト演算子は、ビットを左に指定された数だけシフトします。
  6. >>:符号付き右シフト演算子は、ビットを右に指定された数だけシフトします。符号ビットは左端のビット(MSB)にコピーされます。
  7. >>>:符号なし右シフト演算子は、ビットを右に指定された数だけシフトします。左端のビットは0になります。

これらの演算子は、ビット単位のデータ処理や、効率的なビットマスクの作成に使用されます。

代入演算子

[編集]

代入演算子は、変数に値を割り当てるために使用されます。Javaの代入演算子には、単純な代入演算子と複合代入演算子があります。

  1. =:単純な代入演算子は、右辺の値を左辺の変数に代入します。
  2. +=:加算代入演算子は、左辺の変数に右辺の値を加算し、結果を左辺の変数に代入します。
  3. -=:減算代入演算子は、左辺の変数から右辺の値を減算し、結果を左辺の変数に代入します。
  4. *=:乗算代入演算子は、左辺の変数に右辺の値を乗算し、結果を左辺の変数に代入します。
  5. /=:除算代入演算子は、左辺の変数を右辺の値で除算し、結果を左辺の変数に代入します。
  6. %=:剰余代入演算子は、左辺の変数を右辺の値で剰余計算し、結果を左辺の変数に代入します。

これらの演算子は、変数の値を更新する際に使用され、簡潔なコードを記述するのに役立ちます。


式と演算子
public class Main {
    public static void main(String[] args) {
        int x = 5;
        int y = 3;
        
        // 算術演算子
        int sum = x + y; // 加算
        int difference = x - y; // 減算
        int product = x * y; // 乗算
        int quotient = x / y; // 除算
        int remainder = x % y; // 剰余
        
        // 比較演算子
        boolean isEqual = x == y; // xとyが等しいかどうか
        boolean isNotEqual = x != y; // xとyが等しくないかどうか
        boolean isGreaterThan = x > y; // xがyより大きいかどうか
        boolean isLessThan = x < y; // xがyより小さいかどうか
        boolean isGreaterOrEqual = x >= y; // xがy以上かどうか
        boolean isLessOrEqual = x <= y; // xがy以下かどうか
        
        // 論理演算子
        boolean andResult = (x > 0) && (y < 0); // 論理積(AND)
        boolean orResult = (x > 0) || (y < 0); // 論理和(OR)
        boolean notResult = !(x > 0); // 論理否定(NOT)
        
        // ビット演算子
        int bitAnd = x & y; // ビット単位のAND
        int bitOr = x | y; // ビット単位のOR
        int bitXor = x ^ y; // ビット単位のXOR
        int bitComplementX = ~x; // ビット単位の補数
        int leftShift = x << 1; // 左シフト
        int rightShift = x >> 1; // 右シフト
        
        // 代入演算子
        int a = 10;
        a += 5; // a = a + 5
        int b = 20;
        b -= 3; // b = b - 3
    }
}
このJavaプログラムでは、算術演算子、比較演算子、論理演算子、ビット演算子、代入演算子が使用されています。コメント中には各演算子の説明が含まれており、それぞれの演算子が何を行うかが明確に示されています。

用語集

[編集]
  1. 式と演算子 (Expression and Operators):
    • 式 (Expression): プログラミングにおいて、値、演算子、変数、関数呼び出しなどから構成される計算を表す文法構造。プログラム内で評価され、結果の値を生成する。
    • 演算子 (Operator): 式内で値や変数を操作するための記号やキーワード。算術演算子、比較演算子、論理演算子、ビット演算子、代入演算子などがある。
  2. Javaにおける式と演算子 (Expression and Operators in Java):
    • 算術演算子 (Arithmetic Operators): 加算、減算、乗算、除算、剰余など、数値データ型の間で算術演算を実行するための演算子。
    • 比較演算子 (Comparison Operators): 等しい、等しくない、大なり、小なりなど、値や式の比較を行う演算子。
    • 論理演算子 (Logical Operators): 論理積 (AND)、論理和 (OR)、否定 (NOT) など、真偽値を操作するための演算子。
    • ビット演算子 (Bitwise Operators): 論理積、論理和、排他的論理和、否定など、ビット単位の操作に使用される演算子。
    • 代入演算子 (Assignment Operators): 代入、加算代入、減算代入、乗算代入など、変数に値を代入する演算子。
  3. 算術演算子 (Arithmetic Operators in Java):
    • 加算 (Addition): +。整数や浮動小数点数の加算。
    • 減算 (Subtraction): -。整数や浮動小数点数の減算。
    • 乗算 (Multiplication): *。整数や浮動小数点数の乗算。
    • 除算 (Division): /。浮動小数点数の除算。整数と浮動小数点数の場合もあり。
    • 剰余 (Modulus): %。整数や浮動小数点数の剰余。
  4. 多様な算術演算子の例 (Various Arithmetic Operators Example in Java):
    • Javaで異なるデータ型での算術演算子の使用例。
  5. InfinityとNaN (Infinity and NaN in Java):
    • Infinity (無限大): 有限の数値を0.0で割った場合やオーバーフローが発生した場合に発生。Double.POSITIVE_INFINITYで表現される。
    • -Infinity (負の無限大): 有限の負の数値を0.0で割った場合やオーバーフローが発生した場合に発生。Double.NEGATIVE_INFINITYで表現される。
    • NaN (非数): 0を0で割った場合や不正な演算が行われた場合に発生。Double.NaNで表現され、数値としての意味を持たないことを示す。

条件分岐

[編集]

概要

[編集]

条件分岐は、プログラムの実行フローを制御する基本的な構造の1つです。この章では、Javaにおける以下の条件分岐の仕組みを解説します:

  • if-else文による基本的な条件分岐
  • switch文による複数条件の分岐
  • switch式による値を返す条件分岐
  • 論理演算子を使用した条件の組み合わせ
  • 三項演算子による簡潔な条件分岐
  • パターンマッチングによる型に基づく分岐

基本的な条件分岐:if-else文

[編集]

構文

[編集]

if-else文の基本構文は以下の通りです:

if (条件式) {
    // 条件が真の場合の処理
} else {
    // 条件が偽の場合の処理
}

複数条件の判定

[編集]

複数の条件を判定する場合は、else if を使用します:

if (条件式1) {
    // 条件1が真の場合の処理
} else if (条件式2) {
    // 条件2が真の場合の処理
} else {
    // いずれの条件も偽の場合の処理
}

実践例

[編集]
public class IfElseExample {
    public static void main(String[] args) {
        var n = 0.0 / 0.0;

        if (n < 0.0) {
            System.out.println("負の数です");
        } else if (n > 0.0) {
            System.out.println("正の数です。");
        } else if (n == 0.0) {
            System.out.println("零です。");
        } else {
            System.out.println(n + "です。");
        }
    }
}

複数条件の分岐:switch文

[編集]

基本構文

[編集]

switch文は、1つの値に基づいて複数の分岐を行う場合に使用します:

switch () {
    case 値1:
        // 値1の場合の処理
        break;
    case 値2:
        // 値2の場合の処理
        break;
    default:
        // どの値にも一致しない場合の処理
        break;
}

文字列による分岐例

[編集]
String fruit = "りんご";
switch (fruit) {
    case "りんご":
    case "バナナ":
    case "いちご":
        System.out.println(fruit + "は果物です");
        break;
    default:
        System.out.println(fruit + "は果物ではありません");
        break;
}

enum型による分岐例

[編集]
enum Animal { DOG, CAT, BIRD }

Animal pet = Animal.DOG;
switch (pet) {
    case DOG:
        System.out.println("ワンワン");
        break;
    case CAT:
        System.out.println("ニャー");
        break;
    case BIRD:
        System.out.println("ピヨピヨ");
        break;
}

値を返す条件分岐:switch式

[編集]

基本構文

[編集]

Java 12以降で導入されたswitch式は、条件分岐の結果を値として返すことができます:

var result = switch () {
    case 値1 -> 返り値1;
    case 値2 -> 返り値2;
    default -> デフォルト値;
};

実践例

[編集]
String fruit = "りんご";
int score = switch (fruit) {
    case "りんご", "バナナ", "いちご" -> 1;
    case "メロン", "スイカ" -> 2;
    default -> 0;
};

条件の組み合わせ:論理演算子

[編集]

主な論理演算子

[編集]
演算子 意味
&& 論理積(AND) A && B
|| 論理和(OR) A || B
! 論理否定(NOT) !A
== 等価 A == B
!= 不等価 A != B

真理値表

[編集]
論理演算子の真理値表
A B !A A && B A || B
false false true false false
false true true false true
true false false false true
true true false true true

この表では、AとBがそれぞれ真(true)または偽(false)の場合に対する、NOT、AND、ORの結果が示されています。

簡潔な条件分岐:三項演算子

[編集]

構文

[編集]
結果 = 条件式 ? 真の場合の値 : 偽の場合の値;

実践例

[編集]
int a = 5, b = 10;
int max = a > b ? a : b;  // より大きい値を選択

パターンマッチングによる型判定

[編集]

instanceofによるパターンマッチング

[編集]

Java 16以降で導入された新しい構文:

if (obj instanceof String str) {
    System.out.println(str.toUpperCase());
}

switchでのパターンマッチング

[編集]

Java 21以降で導入された新機能:

String formatted = switch (obj) {
    case Integer i -> "整数: " + i;
    case String s -> "文字列: " + s;
    case null -> "null値";
    default -> "その他: " + obj;
};

パターンマッチングのキャプチャー

[編集]

キャプチャーとは

[編集]

パターンマッチングにおけるキャプチャーとは、マッチしたパターンの値を変数に代入して再利用できるようにする機能です。これにより、型チェックとキャストを1つの操作で安全に行うことができます。

キャプチャー変数のスコープ

[編集]

キャプチャー変数は、パターンマッチングが成功した場合にのみ使用可能です:

if (obj instanceof String s && s.length() > 0) {
    // このスコープでsを使用可能
    System.out.println(s.toUpperCase());
}
// このスコープではsは使用不可

ガード条件でのキャプチャー

[編集]

キャプチャー変数は、パターンマッチングのガード条件内でも使用できます:

public record Point(int x, int y) {}

void processPoint(Object obj) {
    if (obj instanceof Point p && p.x() > 0 && p.y() > 0) {
        // 正の座標の場合の処理
        System.out.println("正の座標: " + p);
    }
}

switch式でのキャプチャー

[編集]

switch式でもキャプチャーを使用できます:

String describe(Object obj) {
    return switch (obj) {
        case String s when s.length() > 0 -> 
            "非空の文字列: " + s;
        case String s -> 
            "空の文字列";
        case Integer i when i > 0 -> 
            "正の整数: " + i;
        case Integer i -> 
            "0または負の整数: " + i;
        case null -> 
            "null値";
        default -> 
            obj.toString();
    };
}

ネストされたパターンでのキャプチャー

[編集]

複雑なデータ構造でもパターンマッチングとキャプチャーを組み合わせることができます:

record Person(String name, Address address) {}
record Address(String street, String city) {}

void processPerson(Object obj) {
    if (obj instanceof Person p 
        && p.address() instanceof Address a 
        && a.city().equals("東京")) {
        // 東京在住の人の処理
        System.out.println(p.name() + "さんは東京在住です");
    }
}

キャプチャーの利点

[編集]
利点 説明
型安全性 コンパイル時の型チェックにより、型関連のエラーを防ぐ
コードの簡潔さ 型チェックとキャストを1行で行える
可読性 意図が明確で理解しやすいコードになる
スコープの制限 変数の使用範囲が適切に制限される

キャプチーにおける注意点

[編集]
  • キャプチャー変数は、パターンマッチングが成功した場合にのみ初期化される
  • 同じスコープ内で既存の変数名と競合してはいけない
  • switch式では、各caseブランチで異なるキャプチャー変数を使用できる
  • ガード条件(when節)内でもキャプチャー変数を使用可能

パターンマッチングとキャプチャーの組み合わせ例

[編集]
sealed interface Shape permits Circle, Rectangle {
    double area();
}
record Circle(double radius) implements Shape {
    public double area() { return Math.PI * radius * radius; }
}
record Rectangle(double width, double height) implements Shape {
    public double area() { return width * height; }
}

String describeShape(Shape shape) {
    return switch (shape) {
        case Circle c when c.radius() > 10 -> 
            "大きな円(半径: " + c.radius() + ")";
        case Circle c -> 
            "円(半径: " + c.radius() + ")";
        case Rectangle r when r.width() == r.height() -> 
            "正方形(辺長: " + r.width() + ")";
        case Rectangle r -> 
            "長方形(幅: " + r.width() + ", 高さ: " + r.height() + ")";
    };
}

補足

[編集]

パターンマッチングの種類

[編集]
パターン 説明
型パターン 型の一致を確認 case String s
nullパターン null値の確認 case null
リテラルパターン 具体的な値との一致 case 42
列挙型パターン 列挙型の値との一致 case Color.RED
デコンストラクタパターン オブジェクトの構造分解 case Point(int x, int y)

警告: 既定のソートキー「しようけんふんき」が、その前に書かれている既定のソートキー「こめんと」を上書きしています。

反復処理 (Iteration)

[編集]

反復処理とは、同じ一連の操作を繰り返すことによって、コードを簡潔にし、再利用性を向上させるプログラミング技術です。Javaでは、次のような方法で反復処理を実現できます。

以下では、それぞれの手法について詳しく説明し、適用場面や使用例を示します。

for文

[編集]

for文は、反復処理を行うための制御構造で、次の形式で使用されます。

for (初期化式; 継続条件式; 更新式) {
    // 繰り返し実行されるコード
}
各構成要素
初期化式
反復処理が始まる前に一度だけ実行されます。通常、反復変数を初期化します。
継続条件式
反復を続ける条件を定義します。trueの場合にループを継続し、falseになると終了します。
更新式
各反復の後に評価され、反復変数の更新などに使用されます。
以下は、0から9までの整数を出力するコード例です。
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

while文

[編集]

while文は、指定された条件がtrueである間、コードを繰り返し実行します。

while (条件式) {
    // 繰り返し実行されるコード
}
次のコードは、0から9までの整数を出力します。
int i = 0;
while (i < 9) {
    System.out.println(i);
    i++;
}

do-while文

[編集]

do-while文は、少なくとも一度はコードを実行し、その後条件を評価して反復を続けるかどうかを決定します。

do {
    // 最低1回実行されるコード
} while (条件式);
以下は、do-while文を使って0から9までの整数を出力する例です。
int i = 0;
do {
    System.out.println(i);
    i++;
} while (i < 10);

フロー制御構文

[編集]

フロー制御構文 (Flow Control Structures)は、プログラムの実行順序を制御するための重要な構造です。Javaでは、反復処理を途中で制御するためのフロー制御構文として、breakcontinueを使用できます。これらを利用すると、ループ内で処理の流れを柔軟に変更することができます。

break文

[編集]

break文は、現在のループを即座に終了させるために使用します。主にfor文、while文、do-while文それに拡張for文で使われます。

以下は、for文内でbreakを使用して、10までの数字を出力し、途中で5を見つけた時点でループを終了する例です。

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break; // 5が出力される前にループを終了
    }
    System.out.println(i);
}
このコードでは、iが5のときにbreakが実行され、ループは終了します。結果として、0から4までの数値が出力されます。

continue文

[編集]

continue文は、現在の反復処理をスキップして、次の反復に進むために使用します。ループ内で特定の条件を満たす場合に処理を飛ばすことができます。

以下のコードでは、for文内でcontinueを使用して、偶数だけを出力する例です。

for (int i = 0; i < 10; i++) {
    if (i % 2 != 0) {
        continue; // 偶数以外は次の反復に進む
    }
    System.out.println(i);
}
このコードでは、iが偶数の場合のみ出力され、奇数のときはcontinueにより次の反復に進みます。結果として、0, 2, 4, 6, 8が出力されます。
breakとcontinueの使用場面
  • break: ループを途中で終了したい場合に使用します。例えば、探索アルゴリズムで目標の要素を見つけた時に検索を終了したい場合に便利です。
  • continue: 条件に基づいて反復処理をスキップしたい場合に使用します。例えば、特定の条件に一致しない要素を無視する場合に役立ちます。

これらのフロー制御構文を使うことで、プログラムの制御がより柔軟になり、効率的なコードの記述が可能となります。

Iterator

[編集]

Iteratorは、Javaのコレクションフレームワークで提供されるインターフェースで、コレクション内の要素に順番にアクセスするための仕組みを提供します。これを使用することで、コレクションの内部構造を意識せずに要素を反復処理することができます。

Iteratorの基本的な使い方

[編集]

Iteratorを使用するには、まずコレクションからiterator()メソッドを呼び出してIteratorオブジェクトを取得します。その後、hasNext()メソッドとnext()メソッドを組み合わせて反復処理を行います。

以下は、ArrayListを例にしたIteratorの基本的な使用方法です。

import java.util.ArrayList;
import java.util.Iterator;

class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
このコードでは、ArrayListに格納された文字列をIteratorを使用して順番に出力しています。
  • hasNext()メソッドは、次の要素が存在する場合にtrueを返します。
  • next()メソッドは、次の要素を返し、Iteratorを次に進めます。

Iteratorでの要素削除

[編集]

Iteratorを使用すると、コレクション内の要素を安全に削除することができます。削除にはremove()メソッドを使用します。

以下は、特定の要素をIteratorで削除する例です。

import java.util.ArrayList;
import java.util.Iterator;

class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            if ("Banana".equals(iterator.next())) {
                iterator.remove();
            }
        }

        System.out.println(list); // 出力: [Apple, Cherry]
    }
}
このコードでは、コレクションから"Banana"を削除しています。
注意点として、Iteratorを使用しないでコレクションを直接操作すると、ConcurrentModificationExceptionが発生する場合があります。

Iteratorを使用する場面

[編集]
Iteratorは次のような場面で使用されます。
  • コレクションの内部構造に依存せずに要素を操作したい場合。
  • 要素を反復処理しながら安全に削除を行いたい場合。
  • コレクションフレームワークが提供する統一的なAPIを利用したい場合。

Iteratorはシンプルで強力なツールですが、最近ではfor-each構文やStreamAPIがよく使用されるため、使用頻度はやや減少しています。ただし、特殊な操作が必要な場合には今でも有用です。

forEachメソッド

[編集]

Java 8以降では、コレクションや配列を簡潔に反復処理するforEachメソッドが利用可能です。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Arrays.asList("A", "B", "C").forEach(item -> {
            System.out.println(item);
        });
    }
}

拡張for文(for-each構文)

[編集]

拡張for文(for-each構文)は、Iteratorを内部的に使用して、コレクションや配列の要素を簡潔に反復処理できる構文です。

以下は、拡張for文を使用した例です。

import java.util.ArrayList;

class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        for (var item : list) {
            System.out.println(item);
        }
    }
}
このコードでは、ArrayListの各要素を簡潔に出力しています。

利点と制限

[編集]
拡張for文の利点:
  • コードが簡潔で読みやすい。
  • Iteratorの操作を手動で行う必要がない。
制限:
  • コレクション内の要素を削除する操作はできない。
  • Iteratorを直接操作したい場合には適さない。

拡張for文は、コレクションの要素を単純に反復処理する場合に最適です。一方、要素を削除する必要がある場合は、Iteratorを使用する必要があります。

比較演算子

[編集]

反復処理の条件を指定する際に、比較演算子を使用します。以下は主な比較演算子の一覧です。

比較演算子一覧
演算子 意味
== 等しい場合にtrueを返す
!= 等しくない場合にtrueを返す
< 小さい場合にtrueを返す
> 大きい場合にtrueを返す
<= 小さいか等しい場合にtrueを返す
>= 大きいか等しい場合にtrueを返す
instanceof オブジェクトが指定された型である場合にtrueを返す

注意点

[編集]
  • == はプリミティブ型の値やオブジェクト参照を比較します。オブジェクトの内容を比較する場合は、equals() メソッドを使用してください。
  • forEachメソッドは簡潔ですが、例外処理が必要な場合や特定の順序を保証する必要がある場合は適していないことがあります。

この章では、Javaで使用される反復処理の主な構文とその使い方を解説しました。次のステップとして、各手法の適用場面を実際にコードを書きながら試してみましょう。

警告: 既定のソートキー「はんふくしより」が、その前に書かれている既定のソートキー「しようけんふんき」を上書きしています。

型 (データ型)

[編集]

プログラミングにおける「型」または「データ型」は、データの種類や性質を示す概念です。各プログラミング言語では、変数、関数の引数や戻り値、オブジェクトなどに特定の型が割り当てられ、その型がデータの扱い方、演算方法、メモリ管理を決定します。

型の役割

[編集]

型は以下のような重要な役割を持っています:

  1. データの性質を表現する
    型はデータの性質を定義します。たとえば、整数型は整数の値を、文字列型は文字列のシーケンスを表します。
  2. メモリの割り当てと管理
    型は、データがメモリ上でどのように格納されるかを定義し、適切なメモリの管理を可能にします。
  3. 演算と操作の定義
    型によって、そのデータに対して可能な演算や操作が決まります。例えば、整数型では算術演算が可能で、文字列型では結合や比較が可能です。
  4. プログラムの正確性と安全性の確保
    型システムはプログラムの正確性と安全性を保証し、不適切な型の操作を防止します。

Javaにおける型

[編集]

Javaでは型は主に以下の3種類に分類されます:

プリミティブ型 (Primitive Types)
プリミティブ型は、データそのものを格納する基本的な型です。これには、整数型(byteshortintlong)、浮動小数点型(floatdouble)、論理型(boolean)、文字型(char)が含まれます。
クラス型 (Class Types)
クラス型は、オブジェクトへの参照を格納する型です。Javaでは、クラスやインタフェースを使用してユーザーが独自のデータ型を定義できます。
配列型 (Array Types)
配列型は、同じ型の複数の要素を格納するための型です。Javaの配列はオブジェクトとして管理され、配列型変数は配列への参照を保持します。

値型と参照型

[編集]

Javaの型はさらに「値型」と「参照型」に分けることができます。

値型 (Value Types)
値型は、変数が実際のデータの値を直接保持する型です。プリミティブ型(intbooleanなど)は値型に分類されます。
参照型 (Reference Types)
参照型は、変数がデータへの参照を保持する型です。クラス型や配列型は参照型に分類されます。参照型はヒープ領域にデータを格納し、変数はそのデータへの参照を保持します。

プリミティブ型

[編集]

プリミティブ型は、Javaの基本的なデータ型で、以下の8つの型があります。

プリミティブ型
データ型 サイズ (ビット数) 値の範囲 ラッパークラス
boolean 1 true または false Boolean
byte 8 -128 から 127 Byte
short 16 -32768 から 32767 Short
int 32 -2147483648 から 2147483647 Integer
long 64 -9223372036854775808 から 9223372036854775807 Long
float 32 IEEE 754 単精度浮動小数点数 Float
double 64 IEEE 754 倍精度浮動小数点数 Double
char 16 UTF-16の16ビット分(サロゲートペアを使って16ビット以上のUnicodeも表現可能) Character
型の名前、説明、リテラル表現
型の名前 説明 リテラル表現
boolean 真偽値を表す型。値は true または false のいずれか。 true, false
byte 8ビットの符号付き整数型。範囲は -128 から 127。 0, -128, 127
short 16ビットの符号付き整数型。範囲は -32768 から 32767。 0, -32768, 32767
int 32ビットの符号付き整数型。範囲は -2147483648 から 2147483647。 0, -2147483648, 2147483647
long 64ビットの符号付き整数型。範囲は -9223372036854775808 から 9223372036854775807。 0L, -9223372036854775808L, 9223372036854775807L
float 32ビットの単精度浮動小数点型。IEEE 754に基づく。 0.0f, 3.14f, -2.71f
double 64ビットの倍精度浮動小数点型。IEEE 754に基づく。 0.0, 3.14, -2.71
char 16ビットのUnicode文字型。1つの文字を表す。 'A', '1', 'あ'
String 文字列型。複数の文字を順序付けて格納する参照型。 "Hello", "Java"
Object すべてのクラスの基底型。すべてのオブジェクト型は Object 型から派生する。 new Object(), new Person()
配列型 (Array) 同じ型の複数の要素を格納する型。要素の型は任意。 {1, 2, 3}, {"Hello", "World"}, {new Person(), new Person()}


プリミティブ型は、データそのものを格納し、計算や処理に利用されます。int型とdouble型で計算を行うと、Javaは自動的に型変換を行い、int型の値をdouble型に変換します。これにより、精度の低下や情報の欠落が発生する場合があるため、注意が必要です。

参照型

[編集]

参照型は、データへの参照を格納する型です。主にクラス型や配列型が該当します。

クラス型 (Class Types)
クラス型は、ユーザー定義のデータ型です。Javaでは、クラスを使って独自のオブジェクトを作成し、それに対して操作を行います。
配列型 (Array Types)
配列型は、同じ型の複数の値を格納するためのオブジェクト型です。配列は固定長ですが、コレクションフレームワークを使用すると可変長リストとして使用することもできます。
参照渡しと値渡し
プリミティブ型は「値渡し」され、参照型は「参照渡し」されます。値渡しでは変数のコピーが渡されるため、元のデータは変更されません。参照渡しでは、変数がオブジェクトの参照を渡すため、元のオブジェクトが変更される可能性があります。

ラッパークラス

[編集]

Javaにおける ラッパークラス (Wrapper Class) とは、プリミティブ型(基本データ型)をオブジェクトとして扱うためのクラスです。プリミティブ型はオブジェクトではないため、クラスやメソッドで扱う際に不便なことがあります。ラッパークラスは、これらプリミティブ型の値をオブジェクトとして扱うための手段を提供します。

ラッパークラスの特徴:
  • オブジェクト化: プリミティブ型の値をオブジェクトとして扱うことができるため、コレクション(例:ArrayList)などのオブジェクトを格納するデータ構造にプリミティブ型の値を保存できます。
  • メソッドの利用: ラッパークラスには、プリミティブ型に対する操作を行うメソッド(例えば、数値を文字列に変換するメソッドや、文字列を数値に変換するメソッド)があります。
プリミティブ型とそのラッパークラスの対応:
プリミティブ型 ラッパークラス 主なメソッド例
boolean Boolean parseBoolean, toString
byte Byte parseByte, toString
short Short parseShort, toString
int Integer parseInt, toString, valueOf
long Long parseLong, toString
float Float parseFloat, toString, isNaN
double Double parseDouble, toString, isNaN
char Character toString, isDigit
ラッパークラスの主なメソッド:
  • parseXxx(String s):文字列を指定したプリミティブ型の値に変換します。例えば、Integer.parseInt("10") は文字列 "10" を整数 10 に変換します。
  • toString():ラッパークラスのインスタンスの値を文字列に変換します。
  • valueOf():文字列や他のオブジェクトを使ってラッパークラスのインスタンスを作成します。
自動ボクシングとアンボクシング:
  • 自動ボクシング (Autoboxing): プリミティブ型の値をラッパークラスのインスタンスに自動的に変換する機能。例えば、int 型の値を Integer 型に自動的に変換できます。
  Integer i = 10;  // int -> Integer (自動ボクシング)
  • 自動アンボクシング (Unboxing): ラッパークラスのインスタンスを対応するプリミティブ型に自動的に変換する機能。例えば、Integer 型の値を int 型に自動的に変換できます。
  int j = i;  // Integer -> int (自動アンボクシング)
使用例:
public class WrapperExample {
    public static void main(String[] args) {
        // ボクシング: プリミティブ型 -> ラッパークラス
        Integer num = Integer.valueOf(10);
        
        // アンボクシング: ラッパークラス -> プリミティブ型
        int primitiveNum = num.intValue();
        
        // 自動ボクシング
        Integer autoBoxed = 25;
        
        // 自動アンボクシング
        int autoUnboxed = autoBoxed;
        
        // ラッパークラスのメソッドを利用
        String str = Integer.toString(100);  // 数値を文字列に変換
        System.out.println(str);  // 出力: "100"
        
        // 文字列から数値に変換
        int parsedNum = Integer.parseInt("200");
        System.out.println(parsedNum);  // 出力: 200
    }
}
ラッパークラスを使用する場面:
  1. コレクションに格納: Javaのコレクション(例えば ArrayList)はオブジェクトのみを格納できるため、プリミティブ型をラッパークラスに変換して格納します。
       List<Integer> list = new ArrayList<>();
       list.add(1);  // 自動ボクシングにより、int 1 が Integer に変換される
    
    メソッドの引数や戻り値として: 一部のメソッドではオブジェクトを受け取る必要があるため、プリミティブ型をラッパークラスに変換して使用します。

ラッパークラスを使うことで、プリミティブ型の値をオブジェクトとして扱うことができ、柔軟なプログラム設計が可能になります。

警告: 既定のソートキー「かた」が、その前に書かれている既定のソートキー「はんふくしより」を上書きしています。

リテラル

[編集]
Wikipedia
Wikipedia
ウィキペディアリテラルの記事があります。

リテラル(Literal)とは、プログラミング言語において、コード内で直接値を表現する方法のことです。リテラルは、そのままの値を表し、プログラムの実行時にその値がそのまま使用されます。

プログラミング言語にはさまざまなタイプのリテラルがあります。以下にいくつかの例を挙げます。

  1. 整数リテラル: 整数の値を直接表現します。例えば、123, 0, -42などが整数リテラルです。
  2. 浮動小数点数リテラル: 浮動小数点数の値を直接表現します。例えば、3.14, 0.0, -1.5などが浮動小数点数リテラルです。
  3. 文字列リテラル: 文字列の値を直接表現します。文字列リテラルは通常、引用符('または")で囲まれます。例えば、"Hello, World!", 'a', "123"などが文字列リテラルです。
  4. 真偽値リテラル: 真偽値(真または偽)を直接表現します。例えば、truefalseが真偽値リテラルです。
  5. 特殊なリテラル: 一部のプログラミング言語には、特殊なリテラルが存在します。例えば、nullリテラルは、何も値がないことを表現します。

リテラルは、プログラムのコード内で値を明示的に表現するために使用されます。例えば、変数に値を代入する際や関数の引数として値を渡す際などに、リテラルを使用することが一般的です。

Javaにおけるリテラル

[編集]

Javaにおけるリテラル(literal)とは、ソースコード上で直接的に表現される値のことを指します。つまり、コード内に直接書かれた値そのものを指します。

public class NumberLiteralExample {

    public static void main(String[] args) {
        // 10進数の整数リテラル
        int decimalInteger = 42;

        // 2進数の整数リテラル
        int binaryInteger = 0b101010; // 10進数の42

        // 8進数の整数リテラル
        int octalInteger = 052; // 10進数の42

        // 16進数の整数リテラル
        int hexInteger = 0x2A; // 10進数の42

        // 浮動小数点数リテラル
        double realNumber = 3.14;

        // 指数表記の浮動小数点数リテラル
        double scientificNotation = 6.022e23; // 6.022 × 10^23

        // 16指数表記の浮動小数点数リテラル
        double hexadecimalFP = 0x1.426fe718a86d7p-10; // 0x1.426fe718a86d7 × 2^-10

        // 文字リテラル: 'A'を文字型の変数に代入
        char charValue = 'A';

        // 文字列リテラル: "Hello, World!"を文字列型の変数に代入
        String stringValue = "Hello, World!";

        // 真偽値リテラル: falseを真偽値型の変数に代入
        boolean booleanValue0 = false;

        // 真偽値リテラル: trueを真偽値型の変数に代入
        boolean booleanValue1 = true;

        // nullリテラル: nullをオブジェクト型の変数に代入
        Object nullValue = null;
    }
}

数値リテラルの中のアンダースコア

[編集]

プログラミングにおける数値リテラルの中のアンダースコアは、数値を区切るために使用される特殊な記号です。この機能は、主に可読性を向上させるために導入されています。

大きな数値や複雑な数値を表す際に、アンダースコアを使用して数値をグループ化することで、コードがより見やすくなります。これにより、数値の桁を簡単に識別しやすくなります。

具体的には、以下のような場面でアンダースコアが使用されます。

  1. 整数リテラル: 例えば、1_000_000というように、大きな整数を区切る場合に使用されます。
  2. 浮動小数点数リテラル: 例えば、3.14_15というように、浮動小数点数を区切る場合に使用されます。
  3. バイナリリテラル: 例えば、0b1101_1010_0110のように、バイナリリテラルを区切る場合に使用されます。

アンダースコアは、数値リテラルの中で連続して使用することができますが、数値の先頭や末尾、小数点の前後、およびリテラルの接頭辞または接尾辞の直前に配置することはできません。また、プログラミング言語によってはアンダースコアの使用が制限される場合がありますので、言語の仕様に従って使用する必要があります。

Javaにおける数値リテラルの中のアンダースコア

[編集]

Javaにおける数値リテラルの中のアンダースコアは、数値を区切るために使用される特殊な記号です。Javaでは、整数や浮動小数点数のリテラル内でアンダースコアを使用して、数値をグループ化することができます。これにより、数値の桁を視覚的に分けることができ、可読性が向上します。

以下に、Javaにおける数値リテラルの中のアンダースコアの使用例を示します。

  1. 整数リテラル:
    int million = 1_000_000;
    long creditCardNumber = 1234_5678_9012_3456L;
    int binaryNumber = 0b1101_1010_0110;
    
  2. 浮動小数点数リテラル:
    float pi = 3.14_15F;
    double bigNumber = 1_000_000.123_456;
    

アンダースコア(_)は、数値の先頭や末尾、小数点の前後、およびリテラルの接頭辞または接尾辞の直前に配置することができます。ただし、連続したアンダースコアや数値の先頭や末尾にアンダースコアを配置することはできません。また、アンダースコアを使用することで数値の値は変わりませんが、コードの可読性が向上します。

JEP 213

[編集]

Java Enhancement Proposal(JEP)は、Javaプラットフォームおよび言語の改善提案をドキュメント化するためのフォーマットです。数値リテラル内のアンダースコアに関連するJEPは、「JEP 213: Milling Project Coin」です。

JEP 213は、Java 7のリリースで導入されました。このJEPの目的は、Java言語のシンプルな機能を向上させることで、コードの可読性と生産性を向上させることでした。その一環として、整数リテラルおよび浮動小数点数リテラル内でのアンダースコアの使用が導入されました。

アンダースコアを使用することで、大きな数値や複雑な数値をより読みやすくすることができるようになりました。この機能は、プログラムのメンテナンスやデバッグの際に特に有用です。

JEP 213の提案として、アンダースコアを使用した数値リテラルの改善が行われ、Javaのコードの可読性が向上しました。

null

[編集]

プログラミングにおける「null」とは、値が存在しないことを表す特別な値です。nullはプログラムで使用されるさまざまなデータ型(オブジェクト、配列、ポインタなど)の初期値として設定されることがあります。

主な用途としては以下のようなものがあります:

  1. 値の不在を表現する: 変数が値を持たないことを示すために使用されます。例えば、ある関数が特定の条件下で値を返さない場合、通常はnullを返します。
  2. 初期化: 変数を初期化するために使用されることがあります。特に、オブジェクトや参照型の変数はnullで初期化されることがあります。
  3. オブジェクトの欠落を示す: オブジェクトが存在しないことを示すために使用されることがあります。例えば、あるオブジェクトのメンバー変数がnullであれば、そのオブジェクトが特定の状態にないことを意味することができます。

nullは便利な概念ですが、誤った使用やnullポインター例外などのランタイムエラーを引き起こす可能性があります。そのため、nullの使用は慎重に行う必要があります。近年のプログラミング言語では、null安全性を向上させるためにオプショナル型やnull許容型などの機能が導入されています。

Javaにおけるnull

[編集]

Javaにおける「null」とは、参照型(Reference Type)の変数が参照するオブジェクトが存在しないことを示す特別な値です。Javaのすべてのオブジェクトはヒープ領域に保存され、変数はそのオブジェクトへの参照(アドレス)を保持します。nullは、そのような参照がオブジェクトを指していないことを示します。

以下はnullの主な特徴です:

  1. オブジェクトの不在を示す: 変数がnullを持っている場合、その変数がどのオブジェクトも指していないことを意味します。つまり、その変数には有効な値が含まれていないということです。
  2. 参照型の初期値: 参照型の変数はデフォルトでnullに初期化されます。つまり、明示的に値を設定しない限り、参照型の変数はnullを持ちます。
    String str; // strはnullを持つ
    
  3. NullPointerException(NPE): nullへの参照を解決しようとすると、NullPointerExceptionが発生する可能性があります。つまり、nullが予期せずプログラムの実行中に使用されるとエラーが発生します。

Javaでは、nullを適切に扱うことが重要です。nullをチェックし、nullポインターエクセプションを防ぐための適切な手段があります。 例えば、条件付きのnullチェックやOptionalクラスの使用などが挙げられます。以下のように、Optionalを使うことで、nullを扱う際のリスクを軽減できます:

Optional<String> optionalStr = Optional.ofNullable(str);
optionalStr.ifPresent(s -> System.out.println(s)); // strがnullでない場合のみ処理が実行される

Javaにはnull安全性を向上させるためのさまざまな手段があり、プログラマーはこれらの手段を活用してnull関連の問題を回避できます。

最新のnull安全性に関する動向

[編集]

近年、多くのプログラミング言語はnull安全性を向上させるための新しい機能を提供しています。Javaでは、Optional クラスの導入により、nullの問題をより安全に扱えるようになりました。また、C#やKotlinなどの他の言語でも、null安全性を高める機能(例えば、null許容型や非null型の明示的な区別)が導入されています。これにより、開発者はnull関連のエラーを減らすことができ、コードの可読性と信頼性が向上しています。

警告: 既定のソートキー「NULL」が、その前に書かれている既定のソートキー「かた」を上書きしています。

NULL許容型

[編集]

NULL許容型(nullable type)は、プログラミング言語において、変数が値を持たないことを明示的に許容する型のことです。通常、NULL許容型は、値が存在しないことを示すために特別な値(通常はNULL、nil、またはundefinedなど)を持ちます。

NULL許容型の主な目的は、nullポインタ例外などのランタイムエラーを回避し、プログラムの安全性を向上させることです。これにより、プログラマーは値が存在しない場合に備えて適切にコードを記述することができます。

多くのプログラミング言語でNULL許容型がサポートされています。例えば、JavaのOptional型、SwiftのOptional型、C#のNullable型などがあります。これらの型は、値が存在しない場合を明示的に示すことで、コードの安全性と可読性を向上させます。

以下は、各プログラミング言語におけるNULL許容型の例です:

各プログラミング言語におけるNULL許容型の例
言語 説明
Optional<T> Java JavaのOptional型は、値が存在しない場合にnullではなくOptional.empty()を返すため、nullポインタ例外を回避し、コードの安全性を向上させます。
Type? Kotlin KotlinのNullable型は、変数やプロパティの型の末尾に "?" を付けることで、その変数やプロパティがnullを許容するNullable型として扱われます。
Option<T> Scala ScalaのOption型は、Some値を持つ場合は値が存在し、Noneを持つ場合は値が存在しないことを示します。これにより、nullチェックやnullポインタ例外を回避することができます。
Nullable<T> C# C#のNullable型は、値型にnullを割り当てるための型です。通常の値型ではnullを許可しないため、Nullable型を使用することでnull許容型を表現します。
Optional<T> Swift SwiftのOptional型は、値が存在する場合はSome値を持ち、値が存在しない場合はnilを持ちます。これにより、nilチェックやnilによるクラッシュを回避するために使用されます。

Optional

[編集]

JavaのOptionalは、Java 8で導入されたクラスで、nullセキュリティという問題に対処するための手段の1つです。Optionalは、値が存在するかどうかを示すラッパークラスであり、nullを明示的に処理することなく、値の有無を安全に操作するための手段を提供します。

Optionalは以下のように使用できます:

  1. Optionalの作成: Optional.ofNullable() メソッドを使用して、可能性のあるnull値を含むオブジェクトからOptionalを作成できます。
    String str = null;
    Optional<String> optionalStr = Optional.ofNullable(str);
    
  2. 値の取得: 値が存在する場合、Optional.get() メソッドを使用してその値を取得できます。ただし、値が存在しない場合は NoSuchElementException がスローされる可能性がありますので、Optional.isPresent() メソッドを使用して事前に値の存在を確認することが重要です。
    if (optionalStr.isPresent()) {
        String value = optionalStr.get();
        System.out.println("Value: " + value);
    } else {
        System.out.println("Value is not present.");
    }
    
  3. 値の存在確認とデフォルト値の提供: 値が存在しない場合にデフォルト値を提供する場合は、Optional.orElse() メソッドを使用できます。
    String valueOrDefault = optionalStr.orElse("Default Value");
    
  4. 値の存在確認と値の処理: 値が存在する場合のみ、値に対して処理を行いたい場合は、Optional.ifPresent() メソッドを使用できます。
    optionalStr.ifPresent(value -> System.out.println("Value: " + value));
    

Optionalは、null値の扱いに関するバグを減らし、より安全で明確なコードを作成するのに役立ちます。 ただし、Optionalを過度に使用することは、コードを複雑にする可能性があるため、適切なバランスが必要です。

以下は、JavaのOptionalクラスの一部の主要なメソッドとその説明を表形式で示したものです。

Optionalクラスの主要なメソッド一覧
メソッド 説明
empty() 空のOptionalインスタンスを作成します。
of(T value) 指定された非nullの値でOptionalインスタンスを作成します。
ofNullable(T value) 指定された値がnullであれば空のOptional、そうでなければその値でOptionalインスタンスを作成します。
isPresent() Optionalに値が含まれている場合はtrueを返します。
ifPresent(Consumer<? super T> consumer) Optionalが値を含んでいる場合、指定されたコンシューマー関数を値に適用します。
get() 値を取得します。値が存在しない場合は例外NoSuchElementExceptionをスローします。
orElse(T other) 値が存在する場合はその値を返し、そうでなければ指定された値を返します。
orElseGet(Supplier<? extends T> supplier) 値が存在する場合はその値を返し、そうでなければサプライヤー関数の結果を返します。
orElseThrow(Supplier<? extends X> exceptionSupplier) 値が存在する場合はその値を返し、そうでなければ指定された例外をスローします。
filter(Predicate<? super T> predicate) 値が述語によってテストされ、述語がtrueを返した場合はこのOptionalを返します。それ以外の場合は空のOptionalを返します。
map(Function<? super T, ? extends U> mapper) 値を関数に適用し、結果が非nullであればそれを含むOptionalを返します。そうでなければ空のOptionalを返します。
flatMap(Function<? super T, Optional<U>> mapper) 値を関数に適用し、結果が空のOptionalでない場合はそのOptionalを返します。そうでなければ空のOptionalを返します。
equals(Object obj) 指定されたオブジェクトがこのOptionalと等しいかどうかを比較します。
hashCode() Optionalのハッシュコード値を返します。
toString() Optionalの文字列表現を返します。

これらのメソッドは、Java 8からOptionalクラスで導入され、nullを避けるために便利な方法を提供します。

Optionalが出来ても、参照型はnullを取り得る

[編集]

Optionalはnullを扱う際の安全性を向上させるための手段ですが、参照型には依然としてnullを取り得るという点に留意する必要があります。

Optionalは、主にnullを返す可能性のあるメソッドの戻り値や、メソッドの引数として使用されます。しかし、既存のコードや外部ライブラリとの連携、あるいは一時的なnull値の許容など、様々な理由でnullが依然として存在することがあります。

そのため、Optionalを使用することでnullの扱いを明確にすることができますが、すべてのnullを完全に排除することは難しい場合があります。したがって、プログラマーは依然としてnullの可能性に備えてコードを記述する必要があります。

警告: 既定のソートキー「OPTIONAL」が、その前に書かれている既定のソートキー「NULL」を上書きしています。

文字列

[編集]

プログラミングにおける文字列(String)は、文字の連続した列を表すデータ型です。文字列は、テキストや文字データを扱うための基本的なデータ構造であり、多くのプログラミング言語でサポートされています。

概要

[編集]

文字列は、文字の配列として内部的に表現されます。各文字は、主にUnicodeやASCIIなどの文字コードで符号化され、文字列全体はそれらの文字の連続したシーケンスとして表現されます。

主な用途

[編集]

文字列は、以下のような多様な用途で使用されます:

  1. テキスト処理
    • ユーザー入力の処理
    • ファイルの読み書き
    • テキストの検索、置換、分割
    • 正規表現によるパターンマッチング
  2. データ表現
    • JSONやXMLなどの構造化データ
    • URLやファイルパス
    • 設定ファイルの内容
    • データベースクエリ
  3. 出力とフォーマット
    • ログメッセージ
    • 多言語対応(i18n)テキスト
    • レポート生成
    • テンプレート処理

Javaの文字列

[編集]

Javaの文字列はjava.lang.Stringクラスで実装される不変(イミュータブル)なオブジェクトです。

文字とエンコーディング

[編集]

Javaの文字列では、文字はUnicodeで表現されます:

  1. 文字の内部表現
    • 基本的な文字は16ビットのchar型で表現
    • サロゲートペアを必要とする文字(絵文字や特殊な漢字など)は2つのchar値で表現
    • 文字列長(length())はchar単位で計算されるため、サロゲートペアは2とカウント
public class StringEncodingExample {
    public static void main(String[] args) {
        // サロゲートペアを含む文字列
        String text = "𠮷野家";  // "𠮷"は1文字だがサロゲートペア
        
        System.out.println("文字列の長さ: " + text.length());  // 4を表示("𠮷"が2文字分)
        
        // コードポイント単位での処理
        System.out.println("実際の文字数: " + text.codePointCount(0, text.length()));  // 3を表示
        
        // 文字列内の各文字(コードポイント)を処理
        text.codePoints().forEach(codePoint -> {
            System.out.printf("U+%04X ", codePoint);  // Unicode コードポイントを16進数で表示
        });
    }
}

文字列操作の基本

[編集]

以下は最新のJava(Java 17以降)での文字列操作の基本例です:

public class ModernStringExample {
    public static void main(String[] args) {
        // テキストブロック(Java 15以降)
        String multiLine = """
                複数行の
                テキストを
                自然に書けます。
                """;
        
        // 文字列結合(Java 11以降のrepeat())
        String repeated = "Hello ".repeat(3);
        
        // 空白処理(Java 11以降)
        String text = "  Hello, World!  ";
        System.out.println(text.strip());      // 前後の空白を除去(Unicode対応)
        System.out.println(text.trim());       // 従来の空白除去
        System.out.println("".isBlank());      // true(空または空白文字のみ)
        
        // 文字列変換(Java 17以降)
        String transformed = text
            .transform(String::strip)
            .transform(String::toUpperCase);
        
        // 行単位の処理(Java 11以降)
        multiLine.lines()
                .map(String::trim)
                .filter(line -> !line.isBlank())
                .forEach(System.out::println);
    }
}

メソッド一覧

[編集]
主要な文字列メソッド
メソッド 説明 導入バージョン
codePointAt(int index) 指定位置のUnicodeコードポイントを取得 Java 5
codePointCount(int begin, int end) 指定範囲のコードポイント数を取得 Java 5
strip() Unicode対応の空白除去 Java 11
isBlank() 空白文字のみかを判定 Java 11
transform(Function<String,R> f) 文字列に関数を適用 Java 17
indent(int n) 行のインデントを調整 Java 17

パフォーマンスと最適化

[編集]

文字列処理を効率的に行うためのベストプラクティス:

  1. StringBuilder の使用
    • 大量の文字列結合にはStringBuilderを使用
    • String + Stringの代わりにStringBuilder.append()を使用
  2. 適切な文字処理
    • サロゲートペアを含む可能性がある場合はcodePoints()を使用
    • 文字数のカウントにはcodePointCount()を使用
  3. メモリ効率
    • コンパクト文字列(Java 9以降): ASCII文字のみの場合、内部的に1バイト/文字で格納
    • 文字列プール: intern()メソッドによる文字列の共有
public class StringPerformanceExample {
    public static void main(String[] args) {
        // 効率的な文字列結合
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 1000; i++) {
            sb.append("Item ").append(i).append(", ");
        }
        String result = sb.toString();
        
        // サロゲートペアを含む文字列の適切な処理
        String text = "🌟Hello🌍";  // 絵文字を含む
        int charCount = text.length();                    // 文字単位(不適切)
        int codePoints = text.codePointCount(0, text.length());  // 実際の文字数(適切)
        
        System.out.println("char count: " + charCount);         // 7 (各絵文字が2文字としてカウント)
        System.out.println("actual count: " + codePoints);      // 5 (実際の文字数)
    }
}

警告: 既定のソートキー「もしれつ」が、その前に書かれている既定のソートキー「OPTIONAL」を上書きしています。

配列

[編集]

プログラミングにおける配列(Array)は、同じデータ型の要素が連続して格納されたデータ構造です。配列は、複数の値を一つの変数に格納するために使用され、要素はインデックス(通常は0から始まる整数)によって識別されます。効率的なデータアクセスと操作が可能で、データの集合を扱う際に重要な役割を果たします。

配列の特性

[編集]

配列の主な特性は以下の通りです:

  1. 同じデータ型の要素: 配列内のすべての要素は同じデータ型である必要があります。
  2. 連続したメモリ領域: 配列の要素は連続したメモリ領域に格納されます。
  3. 要素のインデックス: 通常、0から始まる整数インデックスでアクセスされます。
  4. 固定サイズ: 配列のサイズは作成時に決まり、変更できません。

Javaの配列

[編集]

Javaの配列は、以下の特徴を持ちます:

  • オブジェクトとして扱われる。
  • 固定サイズで、要素はインデックスを用いてアクセス可能。

基本操作

[編集]

以下に配列の基本操作を示します。

import java.util.Arrays;

public class ArrayExample {
    public static void main(String[] args) {
        // 配列の宣言と初期化
        int[] numbers = {5, 2, 7, 1, 9};

        // 配列の要素へのアクセス
        System.out.println("First element: " + numbers[0]);

        // 配列の長さを取得
        System.out.println("Array length: " + numbers.length);

        // 拡張forループで要素を反復
        for (int num : numbers) {
            System.out.println(num);
        }

        // 配列のコピー
        int[] copy = Arrays.copyOf(numbers, numbers.length);
        System.out.println("Copied array: " + Arrays.toString(copy));

        // 配列のソート
        Arrays.sort(numbers);
        System.out.println("Sorted array: " + Arrays.toString(numbers));

        // 配列の比較
        int[] numbers2 = {1, 2, 5, 7, 9};
        System.out.println("Arrays are equal: " + Arrays.equals(numbers, numbers2));
    }
}

このコードは、配列の基本的な操作を示しており、Javaのjava.util.Arraysクラスが提供する便利なメソッドを活用しています。

配列の主なメソッド

[編集]

以下は、java.util.Arraysクラスで提供される主なメソッド一覧です。

配列のメソッド一覧
メソッド 説明
copyOf() 配列を指定した長さでコピーします。
sort() 配列を昇順にソートします。
equals() 配列同士を比較します。
binarySearch() ソートされた配列で二分探索を行います。
fill() 配列の全要素を指定値で初期化します。
toString() 配列を文字列として返します。

多次元配列

[編集]

多次元配列は、配列の要素が配列となる構造です。以下は2次元および3次元配列の例です。

// 2次元配列の宣言
int[][] twoDimArray = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 3次元配列の宣言
int[][][] threeDimArray = {
    {
        {1, 2}, {3, 4}
    },
    {
        {5, 6}, {7, 8}
    }
};

多次元配列はネストされたforループで反復処理することが一般的です。

// 2次元配列の反復処理
for (int[] row : twoDimArray) {
    for (int element : row) {
        System.out.print(element + " ");
    }
    System.out.println();
}

参考文献

[編集]

警告: 既定のソートキー「はいれつ」が、その前に書かれている既定のソートキー「もしれつ」を上書きしています。

コレクション

[編集]

プログラミングにおけるコレクション(Collection)は、複数の要素をまとめて管理するためのデータ構造です。コレクションは、配列やリスト、セット、マップなど、さまざまな形式で提供されます。これらのデータ構造は、異なる目的や要件に応じて使われ、データの格納、検索、操作、管理を行うための便利な手段として利用されます。

コレクションの主な特徴は以下の通りです:

  1. 動的サイズ: コレクションは通常、要素の追加や削除などの操作によって動的にサイズが変化します。配列のように固定サイズではなく、必要に応じて要素を増減させることができます。
  2. ジェネリック: 多くの場合、コレクションはジェネリック型をサポートしており、異なるデータ型の要素を格納することができます。これにより、汎用的なコレクションを作成し、様々な種類のデータを扱うことが可能になります。
  3. 高度な操作: コレクションは、要素の追加、削除、検索、ソート、フィルタリングなど、さまざまな操作をサポートします。これにより、効率的なデータの管理や処理が可能になります。
  4. 相互変換: コレクション間でデータを相互変換することができる場合があります。たとえば、配列をリストに変換したり、リストをセットに変換したりすることができます。

プログラミングにおけるコレクションは、さまざまな用途に活用されます。例えば、データの一時的な保存や処理、データの集計や集合演算、データの操作や変換など、さまざまな場面で利用されます。多くのプログラミング言語やフレームワークは、標準ライブラリやサードパーティライブラリを通じてさまざまなコレクションを提供しています。

Javaのコレクション

[編集]

Javaのコレクションは、複数の要素を格納し、効率的なデータ操作を可能にするためのフレームワークです。Javaのコレクションフレームワークは、java.utilパッケージに含まれており、さまざまなインターフェースとそれらを実装するクラスが提供されています。主なコレクションインターフェースには、リスト、セット、マップなどがあります。

以下は、Javaのコレクションフレームワークを使った例を1つのソースコードにまとめたものです。

import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ArrayDeque;

public class CollectionQuickTour {
    public static void main(String[] args) {
        // リストの例
        var myList = new ArrayList<String>();
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Cherry");
        System.out.print("List: " + myList);
        System.out.println("; The type of myList is: " + myList.getClass().getName());

        // セットの例
        var mySet = new HashSet<Integer>();
        mySet.add(1);
        mySet.add(2);
        mySet.add(3);
        mySet.add(1); // 重複した要素は無視される
        System.out.print("Set: " + mySet);
        System.out.println("; The type of mySet is: " + mySet.getClass().getName());

        // マップの例
        var myMap = new HashMap<String, Integer>();
        myMap.put("One", 1);
        myMap.put("Two", 2);
        myMap.put("Three", 3);
        System.out.print("Map: " + myMap);
        System.out.println("; The type of mySet is: " + myMap.getClass().getName());

        // キューの例
        var myQueue = new LinkedList<String>();
        myQueue.add("First");
        myQueue.add("Second");
        myQueue.add("Third");
        System.out.print("Queue: " + myQueue);
        System.out.println("; The type of myQueue is: " + myQueue.getClass().getName());

        // デッキの例
        var myDeque = new ArrayDeque<Integer>();
        myDeque.addFirst(1);
        myDeque.addLast(2);
        myDeque.addLast(3);
        System.out.print("Deque: " + myDeque);
        System.out.println("; The type of myDeque is: " + myDeque.getClass().getName());
    }
}

このコードでは、リスト、セット、マップ、キュー、デッキのそれぞれのコレクションを作成し、要素を追加しています。それぞれのコレクションの要素が出力されます。これにより、Javaのコレクションフレームワークの基本的な使い方を示しています。

以下は、Javaのコレクションフレームワークに含まれる主要なクラスとインターフェースの一覧を、名前、クラス・インターフェースの別、説明の順に表組みで示したものです。

コレクションフレームワークに含まれる主要なクラスとインターフェース一覧
名前 クラス・インターフェースの別 説明
Collection インターフェース オブジェクトのコレクションを表します。リスト、セット、キューなどの基本的な操作を提供します。
List インターフェース 順序付きの要素のコレクションを表します。要素の重複を許します。
Set インターフェース 重複を許さない要素のコレクションを表します。
Map インターフェース キーと値のペアのコレクションを表します。キーは一意であり、各キーには1つの値が関連付けられます。
Queue インターフェース FIFO (First-In-First-Out) データ構造を表します。要素の挿入はキューの末尾に行われ、削除はキューの先頭から行われます。
Deque インターフェース 双方向キュー (Double Ended Queue) データ構造を表します。要素の挿入と削除がキューの両端で行われます。
ArrayList クラス 動的配列を実装したリストです。
LinkedList クラス リンクリストを実装したリストです。
HashSet クラス ハッシュテーブルを使用して実装されたセットです。
TreeSet クラス 赤黒木を使用して実装されたセットです。要素は自然順序またはコンパレータによってソートされます。
LinkedHashSet クラス ハッシュテーブルとリンクリストを組み合わせて実装されたセットです。要素の挿入順序を保持します。
HashMap クラス ハッシュテーブルを使用して実装されたマップです。
TreeMap クラス 赤黒木を使用して実装されたマップです。キーは自然順序またはコンパレータによってソートされます。
LinkedHashMap クラス ハッシュテーブルと双方向リンクリストを組み合わせて実装されたマップです。要素の挿入順序を保持します。

これらのクラスとインターフェースは、Javaのコレクションフレームワークを構成し、さまざまなデータ構造を効率的に操作するための手段を提供します。

コレクションフレームワーク

[編集]

Javaのコレクションフレームワークは、データを効率的に管理し、操作するための標準的なAPIセットです。これにより、プログラマーはリスト、セット、マップなどのさまざまなデータ構造を使用してデータを格納し、操作することができます。Javaのコレクションフレームワークは、java.utilパッケージに含まれており、多くのインターフェースとクラスから構成されています。

コレクションフレームワークの主な特徴は次のとおりです:

  1. 柔軟性: コレクションフレームワークは、異なる種類のデータ構造を提供し、プログラマーがプログラムの要件に応じて適切なデータ構造を選択できるようにします。
  2. 再利用性: 既存のコレクションクラスやインターフェースを使用することで、プログラマーは再利用可能なコードを作成しやすくなります。
  3. 拡張性: コレクションフレームワークは、カスタムコレクションの作成や既存のコレクションの拡張を容易にします。プログラマーは独自のデータ構造を定義し、それをコレクションとして使用することができます。
  4. 効率性: コレクションフレームワークは、データの効率的な格納、検索、操作を実現するために最適化されています。さまざまなデータ構造は、特定の操作に対して最適な性能を提供します。
  5. 型安全性: ジェネリクスを使用することで、コンパイル時の型安全性が向上します。これにより、コンパイル時に型エラーを検出しやすくなります。

コレクションフレームワークは、Javaプログラミングにおいて非常に重要であり、さまざまなアプリケーションやライブラリで広く使用されています。プログラマーは、これらのAPIを十分に理解し、適切に活用することで、効率的で堅牢なコードを作成することができます。

クラス

[編集]

クラスは、オブジェクト指向プログラミングの基本概念であり、データ(フィールド)と操作(メソッド)をまとめた設計図です。このセクションでは、Javaにおけるクラスの構造と使用方法を説明します。

クラスの基本

[編集]

Javaにおけるクラスは、オブジェクトの属性と振る舞いを定義するための青写真です。クラスを基にしてオブジェクトを作成し、そのオブジェクトがプログラムで使用されます。

クラスの役割

[編集]
  • データの格納 - クラスは、オブジェクトの状態を保持するためのフィールドを提供します。
  • 操作の実装 - クラスは、オブジェクトが実行できるメソッドを定義します。
  • コードの再利用 - クラスを使用すると、共通の機能を再利用可能な単位として構築できます。

Javaにおけるクラスの定義

[編集]

Javaでクラスを定義する際の構造と基本的な宣言方法を以下に示します。

クラスの宣言

[編集]

以下は、基本的なクラスの宣言例です。

public class MyClass {
    // フィールド
    int myField;

    // メソッド
    void myMethod() {
        System.out.println("Hello, World!");
    }
}
  • public: このクラスが他のクラスからアクセス可能であることを示すアクセス修飾子。
  • class: クラスを宣言するキーワード。
  • MyClass: クラスの名前。

インスタンス化とオブジェクトの作成

[編集]

クラスからオブジェクトを生成するには、new キーワードを使用します。

MyClass myObject = new MyClass();
  • new: メモリにオブジェクトを確保し、コンストラクタを呼び出します。

フィールドとメソッドへのアクセス

[編集]

オブジェクトを通じてフィールドやメソッドにアクセスします。

myObject.myField = 10;  // フィールドに値を代入
myObject.myMethod();    // メソッドを呼び出す

クラスの活用例

[編集]

以下は、クラスを利用した簡単なプログラムの例です。

public class Main {
    public static void main(String[] args) {
        MyClass myObject = new MyClass();
        myObject.myField = 20;
        myObject.myMethod(); // "Hello, World!" を出力
    }
}

プログラムの解説

[編集]
  • Main クラスはプログラムのエントリーポイントです。
  • MyClass オブジェクトが生成され、myField に値を設定し、myMethod を呼び出しています。

まとめ

[編集]

クラスは、Javaにおけるオブジェクト指向プログラミングの基盤であり、コードの再利用性や管理の効率化に寄与します。この基本を理解することで、より複雑なオブジェクト指向プログラミングを習得するための基礎が築かれます。

警告: 既定のソートキー「くらす」が、その前に書かれている既定のソートキー「はいれつ」を上書きしています。

例外処理

[編集]

プログラミングにおける例外処理は、プログラムが実行中に発生する予期しないエラーや異常な状況に対処するための仕組みや手法を指します。プログラムが実行中にエラーが発生する可能性がある場合、例外処理はプログラムの安定性を維持し、クラッシュや異常終了を防ぎます。

以下は、プログラミングにおける例外処理の基本的な概念です:

  1. 例外の発生: プログラムが実行中にエラーが発生すると、通常のプログラムのフローが中断されます。このような状況を例外と呼びます。例外は、ゼロ除算、配列の範囲外アクセス、ファイルが見つからないなどのさまざまな条件で発生します。
  2. 例外のスロー: プログラム内の特定の箇所で、例外が発生したことを明示的に示すことができます。この動作を例外のスローと呼びます。通常、特定の条件が満たされた場合やエラーが発生した場合に、例外をスローします。
  3. 例外のキャッチ: プログラム内で例外がスローされた場合、適切な処理を行うために例外をキャッチすることができます。この動作を例外のキャッチと呼びます。例外をキャッチすることで、プログラムは正常に処理を継続するか、エラーを適切に通知することができます。
  4. 例外ハンドリング: 例外をキャッチして処理する手法を例外ハンドリングと呼びます。例外ハンドリングでは、例外をキャッチし、エラーをログに記録したり、ユーザーにエラーメッセージを表示したり、プログラムの状態を回復させたりすることができます。

例外処理は、プログラミングにおいて非常に重要です。適切に実装された例外処理は、プログラムの安定性を高め、ユーザーエクスペリエンスを向上させるのに役立ちます。また、例外処理はデバッグや問題解決の際にも役立ちます。

Javaの例外処理

[編集]

Javaの例外処理は、プログラム実行中に予期しない状況やエラーが発生した場合に、その状況を適切に処理するための仕組みです。Javaの例外処理は、プログラムの安全性や信頼性を高めるために非常に重要です。

例外は、実行時に発生するエラーの種類や条件を表します。例えば、ゼロ除算、配列の範囲外へのアクセス、ファイルが見つからないなどのエラーは、Javaの例外処理を使用して適切に処理することができます。

Javaの例外処理は以下のような特徴を持ちます:

  1. 例外クラスの階層構造: Javaでは、Throwable クラスを基底クラスとして、例外を表すさまざまなクラスが階層的に定義されています。Throwable クラスのサブクラスには、Exception(検査例外)や RuntimeException(非検査例外)などがあります。
  2. try-catch-finally ブロック: Javaでは、try ブロック内で例外が発生する可能性のあるコードを囲み、それに対する処理を catch ブロックで定義します。また、finally ブロックを使用して、例外の発生にかかわらず必ず実行される処理を記述することができます。
  3. 例外のスロー: メソッド内で発生した例外を明示的に処理せずに、呼び出し元に例外をスローすることができます。これにより、例外を適切な場所で処理することができます。
  4. 検査例外と非検査例外: Javaでは、検査例外(checked exception)と非検査例外(unchecked exception)の2種類の例外があります。検査例外はコンパイル時にチェックされるため、明示的に処理するか、メソッドの throws 宣言で伝播させる必要があります。一方、非検査例外は実行時に発生し、明示的な処理が必要ありません。

Javaの例外処理は、プログラムのロバストさを向上させ、予期しない状況に対処するための重要な手段です。例外処理を適切に使用することで、プログラムの安全性や信頼性を向上させることができます。

以下は、Javaの例外処理を1つのソースコードで解説したものです。

public class ExceptionHandlingQuickTour {
    public static void main(String[] args) {
        try {
            // 例外が発生する可能性のあるコード
            int result = divide(10, 0);
            System.out.println("結果: " + result); // この行は実行されません
        } catch (ArithmeticException e) {
            // ArithmeticException が発生した場合の処理
            System.out.println("0 で割ることはできません。");
        } finally {
            // 必ず実行されるブロック
            System.out.println("プログラムの実行が完了しました。");
        }
    }

    // 例外をスローする可能性のあるメソッド
    public static int divide(int num1, int num2) {
        if (num2 == 0) {
            throw new ArithmeticException("0 で割ることはできません。");
        }
        return num1 / num2;
    }
}

この例では、次のような内容を含んでいます:

  1. main メソッド内で try-catch-finally ブロックが使用されています。try ブロック内では例外が発生する可能性のあるコードが配置され、catch ブロックでは特定の例外が発生した場合の処理が定義されています。finally ブロックは例外の発生にかかわらず必ず実行されるブロックです。
  2. divide メソッドは、引数 num2 が 0 の場合に ArithmeticException をスローする可能性があります。
  3. main メソッドでは、divide メソッドを呼び出し、0 で割るエラーが発生した場合に ArithmeticException をキャッチし、適切なメッセージを出力します。
  4. finally ブロックは、プログラムが正常に終了したかどうかに関係なく、必ず実行されることが保証されています。

throws

[編集]

Javaには throws キーワードがあります。throws キーワードは、メソッドが特定の例外をスローする可能性があることを宣言するために使用されます。

メソッドが特定の例外をスローする可能性がある場合、そのメソッドのシグネチャに throws キーワードを使用して、その例外を指定します。これにより、メソッドを呼び出す際に、呼び出し元がその例外を適切に処理するか、またはさらに上位の呼び出し元に例外を伝播させるかを決定できます。

例えば:

public void readFile() throws IOException {
    // ファイルを読み込む処理
}


この例では、readFile メソッドが IOException をスローする可能性があることが宣言されています。メソッド内で IOException が発生する可能性がある場合、その例外をキャッチして処理するか、または readFile メソッドの呼び出し元で try-catch ブロックを使用して例外を処理する必要があります。

throws キーワードを使用することで、メソッドの呼び出し元がそのメソッドがスローする可能性がある例外に対処できるようになります。

例外クラス

[編集]

Javaの例外には、いくつかの主要なクラスがあります。

以下に、Javaの例外クラスのいくつかを表組みで示します。

主要な例外クラス
例外クラス 説明
ArithmeticException 数学的な操作中に発生する例外(ゼロ除算など)
ArrayIndexOutOfBoundsException 配列への無効なインデックスアクセスが発生した場合の例外
NullPointerException ヌル参照が使用された場合の例外
IOException 入出力操作中に発生する例外
FileNotFoundException ファイルが見つからない場合の例外
NumberFormatException 文字列が数値に変換できない場合の例外
IllegalArgumentException メソッドに渡された引数が無効な場合の例外
RuntimeException 実行時に発生する一般的な例外の基底クラス

これらはJavaの例外の一部であり、それぞれ特定の状況や条件で発生します。Javaの例外処理では、これらの例外クラスを適切にキャッチして処理することが重要です。また、必要に応じて独自の例外クラスを定義することもできます。

モダンな例外処理

[編集]

Java 7以降で導入された機能と、最新バージョンでの改善点を説明します:

try-with-resources

[編集]

リソースの自動クローズを行う構文です:

public class ModernExceptionHandling {
    public static void readFile(String path) {
        // try-with-resources: リソースは自動的にクローズされます
        try (var reader = new BufferedReader(new FileReader(path))) {
            String line = reader.readLine();
            System.out.println(line);
        } catch (IOException e) {
            System.err.println("ファイル読み込みエラー: " + e.getMessage());
        }
        // finally不要: readerは自動的にクローズされます
    }
    
    // 複数のリソースを扱う例
    public static void copyFile(String src, String dst) {
        try (var reader = new BufferedReader(new FileReader(src));
             var writer = new BufferedWriter(new FileWriter(dst))) {
            reader.lines().forEach(line -> {
                try {
                    writer.write(line);
                    writer.newLine();
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        } catch (IOException e) {
            System.err.println("ファイル操作エラー: " + e.getMessage());
        }
    }
}

マルチキャッチと型推論

[編集]

複数の例外を効率的に処理する方法:

public class MultiCatchExample {
    public void processData() {
        try {
            // 複数の例外が発生する可能性がある処理
            readFromFile();
            processNumbers();
        } catch (IOException | NumberFormatException e) {
            // 複数の例外を1つのcatchブロックで処理
            logger.error("データ処理エラー", e);
        }
    }
    
    // Java 17以降の改善された型推論
    public void handleException(Exception e) {
        if (e instanceof IOException || e instanceof SQLException ex) {
            // exの型が自動的に推論される
            logError(ex);
        }
    }
}

改善されたNullPointerException

[編集]

Java 14以降で導入された詳細なNullPointerExceptionメッセージ:

public class ImprovedNPE {
    record Person(String name, Address address) {}
    record Address(String street) {}
    
    public void demonstrateNPE() {
        Person person = new Person("John", null);
        // 以下のコードは詳細なエラーメッセージを生成:
        // "Cannot invoke "ImprovedNPE.Address.street()" because the return value 
        // of "ImprovedNPE.Person.address()" is null"
        String street = person.address().street();
    }
}

カスタム例外の設計

[編集]

モダンなJavaでのカスタム例外の実装例:

public class CustomExceptionExample {
    // カスタム例外クラス
    public sealed abstract class BusinessException 
        extends Exception 
        permits ValidationException, NotFoundException {
        
        private final String errorCode;
        
        protected BusinessException(String message, String errorCode) {
            super(message);
            this.errorCode = errorCode;
        }
        
        public String getErrorCode() {
            return errorCode;
        }
    }
    
    // 具体的な例外クラス
    public final class ValidationException extends BusinessException {
        public ValidationException(String message) {
            super(message, "VAL_001");
        }
    }
    
    public final class NotFoundException extends BusinessException {
        public NotFoundException(String message) {
            super(message, "NOT_001");
        }
    }
}

警告: 既定のソートキー「れいかいしより」が、その前に書かれている既定のソートキー「くらす」を上書きしています。