
!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を呼び出すことでローカルクラスを利用
}
}
どうしても使いたい場合は、上のような構成にすることが重要!
ここから先は
¥ 100
この記事が参加している募集
少しずつですが、投稿をしております。 noteで誰かのためになる記事を書いています。 よろしくおねがいします。