質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

3回答

1706閲覧

複数のフィールドで比較する null を許容する Comparator を簡潔に生成するためのメソッドのエラーを解決したい

退会済みユーザー

退会済みユーザー

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2022/05/17 09:27

編集2022/05/17 12:03

前提

Java 17 で null を許容する Comparator を適用した TreeSet を作成するプログラムを簡略化しようとしています。

Stack Overflow のこの質問の回答にあるコードを弄ってみたのですが、エラーが発生して解決ができません。
https://stackoverflow.com/questions/54095271/how-to-simplify-creation-of-null-safe-comparator-on-multiple-fields

どなたか解決策を教えていただけますと幸いです。(解決できない問題であればその旨教えていただけるだけでも助かります)

実現したいこと・発生している問題

複数のフィールドの値を順にそれぞれ自然順序付けで比較する Comparator で、全てのフィールドに対して nullsFirst を適用するメソッドを書きたいです。

メソッドを使わずに書くとstatic import や定数に定義することはできますが、次のコードを繰り返し書く必要があり冗長なので…

.thenComparing(Data::getter, Comparator.nullsFirst(Comparator.naturalOrder())

Stack Overflow で説明されていたものを使いたいのですが、上のコードでの Data の型も変化するのでそちらもジェネリクスにしたところ、エラーが発生するようになってしまい原因も掴めません。

ジェネリクスについて勘違いしていただけでした。ご指摘ありがとうございました。
ジェネリクス(ソースコード中のK)が可変長引数の中で同じ型でないといけないようです。
ただ、上の目的のためにはそれぞれ別の型を指定したい(その型のcompareToを使用したいため)ので、何か複数の型を指定する方法があれば教えていただきたいです。
(2022/5/17 21:00 追記)

エラーメッセージ (複数の型を指定していた場合のみ発生します)

./Main.java:8: error: method comparatorNullsFirst in class Main cannot be applied to given types; Comparator<Data> comparator = comparatorNullsFirst(Data::getString, Data::getInteger); ^ required: Function<C,? extends K>[] found: Data::getString,Data::getInteger reason: inference variable K has incompatible bounds equality constraints: String,Integer lower bounds: Integer,String where C,K are type-variables: C extends Object declared in method <C,K>comparatorNullsFirst(Function<C,? extends K>...) K extends Comparable<K> declared in method <C,K>comparatorNullsFirst(Function<C,? extends K>...)

該当のソースコード

java

1// import文省略 2 3class Main{ 4 public static void main(String[] args) { 5 Comparator<Data> comparator = comparatorNullsFirst(Data::getString, Data::getInteger); 6 /*この部分でそれぞれのFunctionが同じ型を返さないと 7 上記のエラーメッセージでコンパイルエラーになるのですが、 8 複数の型を指定したいです。*/ 9 TreeSet<Data> set = new TreeSet<Data>(comparator); 10 } 11 12 @SafeVarargs 13 @SuppressWarnings("varargs") 14 private static <C, K extends Comparable<K>> Comparator<C> comparatorNullsFirst( 15 Function<C, ? extends K>... keyExtractors) { 16 return Arrays.stream(keyExtractors) 17 .map(f -> Comparator.comparing(f, Comparator.nullsFirst(Comparator.naturalOrder()))) 18 .reduce(Comparator::thenComparing) 19 .orElseThrow(() -> new IllegalArgumentException("The keyExtractors must not be empty.")); 20 } 21}

java

1class Data { 2 private String string; 3 private Integer integer; 4 5 String getString() { 6 return string; 7 } 8 9 Integer getInteger() { 10 /*このメソッドがStringを返すようにすれば問題ないのですが、 11 IntegerとしてでなくStringとして比較されてしまうのでできません*/ 12 return integer; 13 } 14}

試したこと

  • 引数をリストにしてみましたが、呼び出し側で別のコンパイルエラーが発生するようになりました。

  • ジェネリクスの extends を消してみましたが、同様にコンパイルエラーが発生しました

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2022/05/17 11:29

Function<C, ? extends K>... keyExtractors Data String Data Integer CとKの型が異なりますが、全て同じでなければならないのかな? Data Stringに統一したらどうなりますか?
退会済みユーザー

退会済みユーザー

2022/05/17 11:46

Data String で統一したらエラーを吐かなくなりました。ご指摘ありがとうございます。 ジェネリクスについて勘違いしていたようです… もしできるのなら型が異なるもので動かしたいのでまた考えてみます。
xebme

2022/05/17 12:13

.reduce(Comparator::thenComparing) 型が同じでなればthenComparingで次に引き渡せないと思いますが、他に解決方法がないとは断言できません。
退会済みユーザー

退会済みユーザー

2022/05/17 12:20

その前の .map(f -> comparing(f, nullsFirst(naturalOrder()))) で型を Function<C, ? extends K> から Comparator<C> に変換しているので問題ないかと思います。 (メソッドを使わずに書く場合だと型が異なっても動いたことからの推測です。) Cはメソッドを呼び出す際に全て同じものになります。(この例ではData2 String が引数に入ることはありません) わかりづらく申し訳ありません。
xebme

2022/05/17 12:50

はいそのとおりです。「引き渡せない」のは間違いです。
退会済みユーザー

退会済みユーザー

2022/05/17 12:52

わかりました。ありがとうございます。
xebme

2022/05/17 12:56

今の「Comaprator<C>に変換しているので問題ないか」をヒントにすると可変長引数の部分 Function<C, ? extends K>... keyExtractors これを以下のいずれかに変更するという解決策が思い浮かびます Comparator<C>... comparators Supplier<Comparator<C>>... suppliers
退会済みユーザー

退会済みユーザー

2022/05/18 01:59

遅くなってしまいすみません。 回答欄の方でまとめて返信させていただきます。
guest

回答3

0

可変長引数は配列です。
ジェネリクスと配列の相性は良くないといいます。(ジェネリクスと配列)
ですので、早めに妥協するほうが良いと思います。

投稿2022/05/17 18:58

jimbe

総合スコア12646

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/05/18 02:03

ありがとうございます。 ジェネリクスの配列を使える抜け道のような方法を使っていたのですね。自覚していませんでした。 もし無理そうであれば妥協しようと思います。
guest

0

敗北感は出てしまいますが、型パラメータを
<C, K extends Comparable<K>> から
<C, K extends Comparable>
変えてしまうのはどうでしょう。

投稿2022/05/17 17:50

momodx

総合スコア185

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/05/18 02:06

ありがとうございます。 無事コンパイルエラーは吐かなくなりました。(警告は複数出てしまいましたが…) これの使用も検討してみます。
guest

0

ベストアンサー

可変長引数の型を変更する

comparatorNullsFirstの可変長引数をComparator<C>...に変更してみました。

Java

1 public static void main(String[] args) { 2 Comparator<Data> comparator = comparatorNullsFirst(Comparator.comparing(Data::getString), Comparator.comparing(Data::getInteger)); 3 TreeSet<Data> set = new TreeSet<Data>(comparator); 4 } 5 6 @SafeVarargs 7 @SuppressWarnings("varargs") 8 private static <C> Comparator<C> comparatorNullsFirst(Comparator<C>... comparators) { 9 return Arrays.stream(comparators) 10 .map(c -> Comparator.nullsFirst(c)) 11 .reduce((x,y) -> 0, Comparator::thenComparing); 12 }

コード .map(c -> Comparator.nullsFirst(c))は、NullPointerExceptionが発生します。


不具合の修正

次のように修正します。

keyExtractorはキーごとに型が異なる可能性
Comparator <Data>/Supplier<Comparator<Data>>を生成するstaticメソッドを用意。型変数を指定する。

  • keyExtractor -> Comparator<Data>
  • keyExtractor -> Supplier<Comparator<Data>>

Java

1 // nullFirstComparator 2 private static <C, K extends Comparable<K>> Comparator<C> getNullFirstComparator(Function<? super C, ? extends K> f) { 3 return Comparator.comparing(f, Comparator.nullsFirst(Comparator.naturalOrder())); 4 } 5 6 // Supplier<Compparator<Data>> 7 private static <C, K extends Comparable<K>> Supplier<Comparator<C>> getNullFirstSupplier(Function<? super C, ? extends K> f) { 8 return () -> Comparator.comparing(f, Comparator.nullsFirst(Comparator.naturalOrder())); 9 }

Comparator<Data>のリダクション
リダクションに特化した汎用機能は、関数リテラル(ラムダ式)でもstaticメソッドでも、どちらで実装しても良い

Java

1 Comparator<Data> comparator = Stream.of(getNullFirstComparator(Data::getInteger)) 2 .reduce(getNullFirstComparator(Data::getString),Comparator::thenComparing); 3 4 Comparator<Data> comparator = Stream.of(getNullFirstSupplier(Data::getInteger).get()) 5 .reduce(getNullFirstSupplier(Data::getString).get(),Comparator::thenComparing); 6 7 public static <C> Comparator<C> reduceComparators_(Comparator<C> firstC, Stream<Comparator<C>> stream) { 8 return stream.reduce(firstC,Comparator::thenComparing); 9 } 10 11 public static <C> Comparator<C> reduceComparators__(Supplier<Comparator<C>> firstC, Stream<Supplier<Comparator<C>>> stream) { 12 return reduceComparators_(firstC.get(), stream.map(Supplier::get)); 13 }

投稿2022/05/17 14:31

編集2022/05/18 07:22
xebme

総合スコア1081

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2022/05/17 20:25

.map(c -> Comparator.nullsFirst(c)) ここは後で確認します。
xebme

2022/05/17 20:45 編集

Stream<Comparator<Data>>を考えているとこんなふうになっちゃった。 Comparator<Data> comparator = Stream.of(Comparator.comparing(Data::getInteger, Comparator.nullsFirst(Comparator.naturalOrder()))) .reduce( Comparator.comparing(Data::getString, Comparator.nullsFirst(Comparator.naturalOrder())), Comparator::thenComparing ); これをSupplier<Comparator<Data>>を利用するように変更してみる。
xebme

2022/05/18 01:28

>.map(c -> Comparator.nullsFirst(c)) >ここは後で確認します。 NullPointerExceptionが出るので回答を作り直します。関心の分離は次のようになるでしょう。 ・比較するキーごとにnullFirst Comparatorを作る ・Stream <Comparator <Data >>をリダクションする
退会済みユーザー

退会済みユーザー

2022/05/18 03:00

Comparator<C> を使う方法については、メソッドを使わずにベタ書きしている際に試していたのですが、Comparator.comparing(Function<T, U> keyExtractor) の実装が抽出したキーを自然順序付けで比較するようになっているので、nullが許容されないようです。 Supplierについては特に試していませんでしたので考えてみます。ありがとうございます。
退会済みユーザー

退会済みユーザー

2022/05/18 13:33

ご丁寧に教えていただきありがとうございました。とてもわかりやすかったです。 動作確認もできましたのでベストアンサーにさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問