見出し画像

!T52 JavaGold(Enum)

以下は全て無料です。もし余裕のある方がいらっしゃいましたらカンパください!☺️

mycompilerで確認。

復習(3問)

問題 1: メンバークラスのアクセス

以下のコードを実行するとどうなりますか?エラーが発生する場合、その理由を説明してください。

class Outer {
    private int outerValue = 10;

    class Inner {
        void display() {
            System.out.println("outerValue: " + outerValue);
        }
    }

    public static void main(String[] args) {
        Inner inner = new Inner();
        inner.display();
    }
}

問題 2: スタティックネストクラスの利用

次のコードの出力を予想しなさい。

class Outer {
    static class StaticNested {
        void show() {
            System.out.println("Static Nested Class");
        }
    }

    public static void main(String[] args) {
        StaticNested nested = new StaticNested();
        nested.show();
    }
}

問題 3: ローカルクラスのスコープ

次のコードを実行するとどうなりますか?エラーが発生する場合、その理由を説明してください。

class Outer {
    void outerMethod() {
        class LocalInner {
            void show() {
                System.out.println("Local Inner Class");
            }
        }
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        LocalInner inner = new LocalInner();  // エラーになるか?
        inner.show();
    }
}

答えは最後にのせています!


関数型インターフェース

java.util.function.Supplierインターフェースで宣言されている抽象メソッドは、T型のget();と書くことができる。

Supplierは、”提供者”なので、引数を取らず値をget()で戻す。
同様に、似たような関数型インターフェースも多いようです。

Consumer<T>は、"消費者"で、戻り値void, accept()メソッドなどがあります。
以下は一覧です。

  • Function<T, R>

    • 戻り値型: R

    • メソッド: R apply(T t)

    • 説明: T 型の入力を R 型に変換

  • BiFunction<T, U, R>

    • 戻り値型: R

    • メソッド: R apply(T t, U u)

    • 説明: 2つの入力を受けて R を返す

  • Consumer<T>

    • 戻り値型: void

    • メソッド: void accept(T t)

    • 説明: T 型の入力を消費(出力なし)

  • BiConsumer<T, U>

    • 戻り値型: void

    • メソッド: void accept(T t, U u)

    • 説明: 2つの入力を受けて処理を行う

  • Supplier<T>

    • 戻り値型: T

    • メソッド: T get()

    • 説明: 引数なしで T 型の値を返す

  • Predicate<T>

    • 戻り値型: boolean

    • メソッド: boolean test(T t)

    • 説明: T 型の条件判定を行う

  • BiPredicate<T, U>

    • 戻り値型: boolean

    • メソッド: boolean test(T t, U u)

    • 説明: 2つの入力を判定

  • UnaryOperator<T>

    • 戻り値型: T

    • メソッド: T apply(T t)

    • 説明: Function<T, T> の特殊形(入力と出力が同じ型)

  • BinaryOperator<T>

    • 戻り値型: T

    • メソッド: T apply(T t1, T t2)

    • 説明: BiFunction<T, T, T> の特殊形


とはいえ、どうしても覚えられない・・・。

フクスプ で覚える


フ(Function)Function<T, R> BiFunction<T, U, R>変換する(T → R)

ク(Consumer)Consumer<T> BiConsumer<T, U>消費する(出力なし)

ス(Supplier)Supplier<T>供給する(値を返す)

プ(Predicate)Predicate<T> BiPredicate<T, U>判定する(true / false)

O(Operator)UnaryOperator<T> BinaryOperator<T>同じ型で変換する(T → T)

「プ」の丸まで入れる!

ただ、意味が欲しい自分などはこれで覚えます。

Function<T, R>apply()変換は apply(適用する)

Consumer<T>accept()消費は accept(受け取る)

Supplier<T>get()供給は get(取る)

Predicate<T>test()判定は test(テストする)

Consumerは、消費するので、受け取る(accept)のみ。
戻り値はない。つまり、引数を受け取って消費しちゃう。

問題1: Consumer の基本

次のコードを実行すると、コンソールには何が表示されますか?

import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> consumer = s -> System.out.println("Hello, " + s + "!");
        consumer.accept("Java");
    }
}

sという引数を渡す必要がある。(消費)そしてそれは返さない。
Hello, Java!

問題2: Consumer の連結

以下のコードの実行結果は何になるでしょうか?

import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> first = s -> System.out.print(s.toUpperCase() + " ");
        Consumer<String> second = s -> System.out.print(s.length());

        Consumer<String> combined = first.andThen(second);
        combined.accept("java");
    }
}

JAVA 4

問題3: Consumer の応用
以下のコードの出力を予想してください。

import java.util.List;
import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        List<String> list = List.of("Apple", "Banana", "Cherry");
        Consumer<String> printConsumer = System.out::println;

        list.forEach(printConsumer);
    }
}
Apple
Banana
Cherry
  • A: "Hello, Java!" が表示される。

  • A: "JAVA 4"(first で大文字にし、second で長さを出力)

  • A: forEach() でリストの要素が1行ずつ出力される。

  • Consumer<T> は 引数を受け取って処理を行うが、戻り値はない 関数型インターフェース。

  • andThen() を使うと Consumer を連結 できる。

  • forEach() と組み合わせるとリストの各要素に対して処理を適用できる。

ここで疑問なのは、

Consumer<String> consumer = s -> System.out.println("Hello, " + s + "!");の部分で、System.out.println("Hello, " + s + "!")は戻り値ではないの?
ということ。

しかし、println()の定義をみてみると、

public void println(String x) { ... }

voidになっているので、やはり戻り値はない。

結論として、
s -> System.out.println("Hello, " + s + "!") は、

  • s を受け取る

  • コンソールに表示する

  • 戻り値は何も返さない

Consumer<string> c = x -> "Hello, " + x;
System.out.println(c.accpt("test");

はどうしてだめなのか?
この理由は、 Consumer<T> は戻り値を持たない (void を返す) のに、戻り値を期待している から。

Predicateインターフェースに定義されている抽象メソッドは、boolean型で戻り値があるtest()がある。

java.util.funtion.Functionインターフェースは元の型を別の型に適応させるため、apply()となる。

Function()は合成することもできる。
andThenが順次処理
composeが逆順処理

java.util.funtion.UnaryOperatorインターフェースは、
1つの型を処理して同じ型の結果をもどす。
Functionインターフェースのサブクラスとなっている。

UnaryOperatorは、リストなどで一律で処理する際に使用できる。

例題:以下のリスト numbers の各要素を 2倍にする ように UnaryOperator<Integer> を使用して実装してください。
ただし、replaceAll() メソッドを用いること。

import java.util.List;
import java.util.function.UnaryOperator;

public class UnaryOperatorExample {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);
        
        // ここに UnaryOperator を使用してリストの要素を2倍にする処理を追加
        
        System.out.println(numbers);
    }
}

mycompilerでも確認できるとOK

import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;

public class UnaryOperatorExample {
    public static void main(String[] args) {
        // 可変リストにする(List.of() のままだと replaceAll() が使えない)
        List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // UnaryOperator を定義(各要素を2倍にする)
        UnaryOperator<Integer> doubleValue = x -> x * 2;

        // replaceAll() を使用してリストの要素を一律で2倍にする
        numbers.replaceAll(doubleValue);

        // 結果を出力
        System.out.println(numbers); // 出力: [2, 4, 6, 8, 10]
    }
}

ということで、
演習問題!

interface A {
    void methodA();
}

interface B {
    int methodB(int x, int y);
}

@FunctionalInterface
interface C {
    boolean isEven(int x);
}

すぐに

@FunctionalInterface
interface C {
    boolean isEven(int x);
}

ぽいとわかる。

問題2
正しいものはどれか

import java.util.function.Function;

public class LambdaExample {
    public static void main(String[] args) {
        Function<Integer, Integer> multiplyByTwo = x -> x * 2;
        System.out.println(multiplyByTwo.apply(5));  // 出力: ?
        
        Function<Integer, Integer> addThree = (x, y) -> x + y;  // ❌ コンパイルエラー
        System.out.println(addThree.apply(3, 4));  // 出力: ?
    }
}
  • multiplyByTwo は正しくラムダ式が適用されている

  • addThree は関数型インターフェースが引数2つを取ることができる

  • multiplyByTwo と addThree はどちらも正しいラムダ式の使い方である

  • multiplyByTwo は正しく、addThree は間違ったラムダ式の使い方である

Functionクラスは、引数が二つ。よって、addThree は関数型インターフェースが引数2つを取ることができるが正解かな??

→Functionは引数は1つ!

問題 3: 関数型インターフェースの使い方

次のコードの実行結果は何か。

import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> printMessage = message -> System.out.println(message);
        printMessage.accept("Hello, World!");
    }
}
  • 出力されるのは「message」

  • 出力されるのは「Hello, World!」

  • コンパイルエラーが発生する

  • 出力されるのは「Hello, message!」

Consumerは、受け取って、それを消費してしまう。戻り値はない。(void)
よって、出力されるのは「Hello, World!」

回答1
関数型インターフェースとは、1つの抽象メソッド を持つインターフェースです。
@FunctionalInterface アノテーションが付いているインターフェースは関数型インターフェースです。

  • A は抽象メソッド void methodA(); を持っており、関数型インターフェースではないため ×

  • B は methodB(int x, int y); として引数2つを取るメソッドを持っているため、関数型インターフェースにはなりません ×

  • C は boolean isEven(int x); という1つの抽象メソッドを持っているので 関数型インターフェースです。

問題 2: ラムダ式の利用

ラムダ式の使用について確認します。

  • multiplyByTwo = x -> x * 2; は 正しいラムダ式 です。

  • addThree = (x, y) -> x + y; は2つの引数を取る関数型インターフェース BiFunction で使われるべきですが、Function は引数1つしか受け取らないため エラー です。

答え: 4 (multiplyByTwo は正しく、addThree は間違ったラムダ式の使い方である)

問題 3: 関数型インターフェースの使い方

Consumer<String> は引数を1つ取り、戻り値を返さない関数型インターフェースです。accept("Hello, World!"); は "Hello, World!" を出力します。

答え: 2 (出力されるのは「Hello, World!」)


さて、ここで疑問がでてきた。というのは、以下の3問目のコード。

import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> printMessage = message -> System.out.println(message);
        printMessage.accept("Hello, World!");
    }
}

まず、messageの使い方がよくわからん。messageを引数として左辺で受け取っているのはまあわかる。しかし、それを

System.out.println(message);

として出力して、

printMessage.accept("Hello, World!");

としている。ここで、

printMessage.accept(message);

じゃないのがなんか気持ち悪い。だってそれを使えばいいじゃん!とおもった。

さらに、
これ、そもそもHello, World!と出力されるのなら、

いわゆるお決まりのsysoutでそのまま出力すればよくないかなぁ・・・と思う。

しかし、これもそれぞれ理由があった。

まず、

printMessage.accept("Hello, World!");

とする理由は、その前段階のmessageが理由だった。

というのも、前行のmessageは、ラムダ式の中でしか使えない変数だから。

よって、クラスを呼んでメソッドを使うように、そのラムダ式で使った変数をFunctionインターフェースで使うメソッドを指定してあげて使うことでmessageという変数の中にHello, World!が入ることになるのだ。

そう、よく考えればわかる話だった。なぜなら、

System.out.println(message);

が入っているとするならば、引数の中身は何を入れればいいのよ?

という結果に陥ってしまう。
OKOK。

しかし、次の疑問は拭えないはずだ。というのは、

いわゆるお決まりのsysoutでそのまま出力すればいいのでは・・・。
という疑問。

結果的にHello, World!を出力するのであれば(もちろん、簡易的な文言なのは前提として、わざわざ出力する文字を引数で書いてあげて(引数として書いている時点で、もうそのままSysOutPrintlnで出力すればいいじゃん)、そして関数型インターフェースで書く(ラムダ式でかかないといけないし、まあコード量はへるけど可読性微妙笑))

きいてみたが

確かにその通りだそう。

Consumer を使う目的は、1つの引数を受け取って何らかの操作を行う処理を定義すること です。▲しかし、このコードのように単純に System.out.println() を使うだけであれば、わざわざ Consumer を使う必要は全くありません。

例えば、次のような単純な実装で十分です:

System.out.println("Hello, World!");

では、Consumerの使いどころはなにか。

副作用が大事

Consumer は、引数を受け取って副作用を引き起こす操作(この場合は出力)を行うために使用しますが、複雑な処理を引数として渡す場合にこそ、その真価を発揮します。例えば、リストの各要素に対して何か処理をしたい場合などです。

List<String> messages = List.of("Hello", "World", "Java");

Consumer<String> printMessage = message -> System.out.println(message);
messages.forEach(printMessage);

以上!


復習問題1の回答
インナークラスを使用する場合は、外→内に扉を開けていくイメージでインスタンスをしていく。innerクラスを使用するにはまずそのクラスを囲っているエンクロージングクラスであるOuterをインスタンス化しないといけない。
よって、コンパイルエラーがおきる。

  • インナークラス (Inner) を使用するには、まず Outer クラスのインスタンスを作成し、そのインスタンスを通じて Inner クラスをインスタンス化する必要がある。

  • Inner inner = new Inner(); という記述は Outer のインスタンスなしに Inner を直接生成しようとしているため、コンパイルエラーになる。

public static void main(String[] args) {
    Outer outer = new Outer(); // まずOuterをインスタンス化
    Outer.Inner inner = outer.new Inner(); // それを使ってInnerをインスタンス化
    inner.display();
}


問題2の回答
staticNatedクラスに対しては、staticでインスタンス化をせずに呼び出せる。
よって、Static Nested Classが出力される。

  • static なネストクラス (StaticNested) は Outer クラスのインスタンスを作成せずに直接インスタンス化できる。

  • したがって、StaticNested nested = new StaticNested(); は問題なく動作し、 "Static Nested Class" が出力される。

問題3の回答
ローカルクラスを呼び出しているが、ローカルクラスは、別クラスのメソッド内にある。このローカルクラスはそのクラス内でしか使うことができないため、エラーになる。

  • LocalInner クラスは outerMethod() 内で定義されたローカルクラスであり、メソッドのスコープ内でのみ利用可能。

  • main() メソッドの中では LocalInner はスコープ外のため、LocalInner inner = new LocalInner(); はコンパイルエラーになる。

class Outer {
    void outerMethod() {
        class LocalInner {
            void show() {
                System.out.println("Local Inner Class");
            }
        }
        LocalInner inner = new LocalInner(); // このメソッド内では有効
        inner.show();
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.outerMethod(); // outerMethodを呼び出すことでローカルクラスを利用
    }
}

どうしても使いたい場合は、上のような構成にすることが重要!

ここから先は

0字

¥ 100

この記事が参加している募集

少しずつですが、投稿をしております。 noteで誰かのためになる記事を書いています。 よろしくおねがいします。