メソッドの応用

このレッスンでは、再帰、静的メソッド、メソッド参照などの高度なメソッド機能を詳しく学びます。

再帰

再帰はメソッドが自分自身を呼び出すプログラミング技法です。

再帰の3要素

要素 説明
基本ケース 再帰を停止する条件
再帰呼び出し メソッドが自分自身を呼び出す
問題の縮小 各再帰呼び出しで問題のサイズが縮小する

例:階乗の計算

JAVA
public class Factorial {
    public static long factorial(int n) {
        // 基本ケース
        if (n <= 1) {
            return 1;
        }
        // 再帰呼び出し
        return n * factorial(n - 1);
    }
    
    public static void main(String[] args) {
        System.out.println("5! = " + factorial(5));  // 120
        System.out.println("10! = " + factorial(10)); // 3628800
    }
}
▶ 試してみよう

再帰プロセス

TEXT
factorial(5)
  = 5 * factorial(4)
  = 5 * 4 * factorial(3)
  = 5 * 4 * 3 * factorial(2)
  = 5 * 4 * 3 * 2 * factorial(1)
  = 5 * 4 * 3 * 2 * 1
  = 120

例:フィボナッチ数列

JAVA
public class Fibonacci {
    public static int fibonacci(int n) {
        if (n <= 1) {
            return n;
        }
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.print(fibonacci(i) + " ");
        }
        // 出力: 0 1 1 2 3 5 8 13 21 34
    }
}
▶ 試してみよう
⚠️ 注意: 単純な再帰は非効率的です。メモ化を使用して最適化してください。フィボナッチでは、反復の方がより効率的です。

例:再帰的なディレクトリ走査

JAVA
import java.io.File;

public class ListFiles {
    public static void listFiles(File dir, String indent) {
        File[] files = dir.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            System.out.println(indent + file.getName());
            if (file.isDirectory()) {
                listFiles(file, indent + "  ");
            }
        }
    }
    
    public static void main(String[] args) {
        File dir = new File(".");
        listFiles(dir, "");
    }
}
▶ 試してみよう

静的メソッド vs インスタンスメソッド

静的メソッド

staticで修飾されたメソッドはクラスに属し、オブジェクトを作成せずに呼び出すことができます。

JAVA
public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

// 呼び出し
int sum = MathUtils.add(3, 5);

インスタンスメソッド

staticのないメソッドはオブジェクトに属し、オブジェクトを作成して呼び出す必要があります。

JAVA
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

// 呼び出し
Calculator calc = new Calculator();
int sum = calc.add(3, 5);

比較

特徴 静的メソッド インスタンスメソッド
キーワード static なし
呼び出しスタイル クラス名.メソッド() オブジェクト.メソッド()
メンバーアクセス 静的メンバーのみアクセス可能 すべてのメンバーにアクセス可能
thisキーワード 使用不可 使用可能

例:静的 vs インスタンスメソッド

JAVA
public class MethodTypeDemo {
    private int count = 0;
    
    // 静的メソッド
    public static int add(int a, int b) {
        return a + b;
    }
    
    // インスタンスメソッド
    public void increment() {
        count++;
    }
    
    public int getCount() {
        return count;
    }
    
    public static void main(String[] args) {
        // 静的メソッドは直接呼び出し
        System.out.println(add(3, 5));  // 8
        
        // インスタンスメソッドはオブジェクトが必要
        MethodTypeDemo demo = new MethodTypeDemo();
        demo.increment();
        demo.increment();
        System.out.println(demo.getCount());  // 2
    }
}
▶ 試してみよう

ユーティリティクラス設計

ユーティリティクラスには通常、静的メソッドのみが含まれ、オブジェクトの作成は不要です。

例:文字列ユーティリティクラス

JAVA
public class StringUtils {
    // 空かどうかを確認
    public static boolean isEmpty(String str) {
        return str == null || str.isEmpty();
    }
    
    // 空白かどうかを確認
    public static boolean isBlank(String str) {
        return str == null || str.trim().isEmpty();
    }
    
    // 文字列を反転
    public static String reverse(String str) {
        if (str == null) return null;
        return new StringBuilder(str).reverse().toString();
    }
    
    // 最初の文字を大文字に
    public static String capitalize(String str) {
        if (isEmpty(str)) return str;
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
    
    public static void main(String[] args) {
        System.out.println(isEmpty(""));      // true
        System.out.println(isEmpty("hello")); // false
        System.out.println(reverse("hello")); // olleh
        System.out.println(capitalize("hello")); // Hello
    }
}
▶ 試してみよう

メソッド参照

Java 8では、Lambda式の短縮形としてメソッド参照が導入されました。

4種類のメソッド参照

タイプ 構文
静的メソッド クラス名::静的メソッド Math::abs
インスタンスメソッド オブジェクト::インスタンスメソッド System.out::println
クラスのインスタンスメソッド クラス名::インスタンスメソッド String::length
コンストラクタ クラス名::new ArrayList::new

例:メソッド参照

JAVA
import java.util.Arrays;
import java.util.List;

public class MethodRefDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        
        // Lambda式
        names.forEach(name -> System.out.println(name));
        
        // メソッド参照(より簡潔)
        names.forEach(System.out::println);
        
        // 静的メソッド参照
        List<Integer> numbers = Arrays.asList(-3, -1, 0, 2, 5);
        numbers.stream()
               .map(Math::abs)
               .forEach(System.out::println);
        // 出力: 3 1 0 2 5
    }
}
▶ 試してみよう

例:コンストラクタ参照

JAVA
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

public class ConstructorRefDemo {
    public static void main(String[] args) {
        // Lambda式
        Supplier<List<String>> listFactory = () -> new ArrayList<>();
        
        // コンストラクタ参照
        Supplier<List<String>> listFactory2 = ArrayList::new;
        
        List<String> list = listFactory2.get();
        list.add("Hello");
        System.out.println(list);  // [Hello]
    }
}
▶ 試してみよう

再帰の最適化

末尾再帰

末尾再帰は、再帰呼び出しが関数の最後の操作である場合です。

JAVA
// 通常の再帰(末尾再帰ではない)
public static long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);  // 再帰後にnを掛ける
}

// 末尾再帰バージョン
public static long factorialTail(int n, long acc) {
    if (n <= 1) return acc;
    return factorialTail(n - 1, n * acc);  // 再帰が最後のステップ
}

// 呼び出し
long result = factorialTail(5, 1);
💡 注意: Javaコンパイラは末尾再帰を自動的に最適化しませんが、この概念を理解することは他の言語を学ぶ際に役立ちます。

メモ化

計算済みの結果をキャッシュに格納し、重複計算を回避します。

JAVA
import java.util.HashMap;
import java.util.Map;

public class MemoFibonacci {
    private static Map<Integer, Long> cache = new HashMap<>();
    
    public static long fibonacci(int n) {
        if (n <= 1) return n;
        
        // キャッシュを確認
        if (cache.containsKey(n)) {
            return cache.get(n);
        }
        
        // 計算してキャッシュに保存
        long result = fibonacci(n - 1) + fibonacci(n - 2);
        cache.put(n, result);
        return result;
    }
    
    public static void main(String[] args) {
        System.out.println(fibonacci(50));  // 12586269025
    }
}

❓ よくある質問

Q いつ再帰とループのどちらを使うべきですか?
A 簡単な問題にはループを使用してください—より効率的です。再帰はツリー構造や分割統治アルゴリズムなど、自然に再帰的な問題に適しています。
Q いつ静的メソッドを使用すべきですか?
A オブジェクトの状態にアクセスする必要がないユーティリティメソッドには静的メソッドを使用します。オブジェクトの状態にアクセスする必要がある場合はインスタンスメソッドを使用します。
Q メソッド参照とLambdaの違いは何ですか?
A メソッド参照はLambdaの短縮形で、コードをより簡潔にします。ただし、すべてのLambdaをメソッド参照に置き換えることはできません。

📖 まとめ

📝 演習

  1. 再帰練習: 再帰を使用して1からnまでの合計を計算
  2. 再帰練習: 再帰を使用して文字列を反転
  3. ユーティリティクラス: 最大値、最小値、平均値などのメソッドを持つ数学ユーティリティクラスを作成

次のレッスン

次のレッスンでは、Stringクラスを学びます — 一般的な文字列操作。

100%