Java 8 Collectors.toMap
melempar a NullPointerException
jika salah satu nilainya adalah 'nol'. Saya tidak mengerti perilaku ini, peta dapat berisi pointer nol sebagai nilai tanpa masalah. Apakah ada alasan bagus mengapa nilai tidak dapat null Collectors.toMap
?
Juga, apakah ada cara Java 8 yang bagus untuk memperbaikinya, atau haruskah saya kembali ke polos lama untuk loop?
Contoh masalah saya:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Answer {
private int id;
private Boolean answer;
Answer() {
}
Answer(int id, Boolean answer) {
this.id = id;
this.answer = answer;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Boolean getAnswer() {
return answer;
}
public void setAnswer(Boolean answer) {
this.answer = answer;
}
}
public class Main {
public static void main(String[] args) {
List<Answer> answerList = new ArrayList<>();
answerList.add(new Answer(1, true));
answerList.add(new Answer(2, true));
answerList.add(new Answer(3, null));
Map<Integer, Boolean> answerMap =
answerList
.stream()
.collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
}
}
Stacktrace:
Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1216)
at java.util.stream.Collectors.lambda$toMap$168(Collectors.java:1320)
at java.util.stream.Collectors$$Lambda$5/1528902577.accept(Unknown Source)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1359)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at Main.main(Main.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Masalah ini masih ada di Jawa 11.
null
selalu sedikit bermasalah, seperti di TreeMap. Mungkin saat yang tepat untuk mencobaOptional<Boolean>
? Kalau tidak, bagi dan gunakan filter.null
bisa menjadi masalah untuk kunci, tetapi dalam hal ini nilainya.null
,HashMap
misalnya dapat memiliki satunull
kunci dan sejumlahnull
nilai, Anda dapat mencoba membuat customCollector
menggunakanHashMap
alih - alih menggunakan yang default.HashMap
- seperti yang ditunjukkan pada baris pertama stacktrace. Masalahnya bukan bahwa tidakMap
dapat menyimpannull
nilai, tetapi argumenMap#merge
fungsi kedua tidak boleh nol.Jawaban:
Anda dapat mengatasi bug yang dikenal ini di OpenJDK dengan ini:
Ini tidak terlalu cantik, tetapi berhasil. Hasil:
( ini tutorial paling membantu saya.)
sumber
() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
membuat case tidak sensitifString
dikunciTreeMap
.Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);
. Saya punya:incompatible types: cannot infer type-variable(s) R (argument mismatch; invalid method reference no suitable method found for putAll(java.util.Map<java.lang.Integer,java.lang.Boolean>,java.util.Map<java.lang.Integer,java.lang.Boolean>) method java.util.Map.putAll(java.util.Map) is not applicable (actual and formal argument lists differ in length)
HashMap
dan kemudian memanggilputAll()
untuk setiap entri. Secara pribadi, pada keadaan tertentu, saya akan menggunakan solusi non-stream, atauforEach()
jika inputnya paralel.Itu tidak mungkin dengan metode statis
Collectors
. JavadoctoMap
menjelaskan yangtoMap
didasarkan padaMap.merge
:dan javadoc dari
Map.merge
mengatakan:Anda dapat menghindari for for dengan menggunakan
forEach
metode daftar Anda.tetapi itu tidak benar-benar sederhana dari cara lama:
sumber
Map.merge
. IMHO ini adalah kelemahan dalam implementasi yang membatasi kasus penggunaan yang dapat diterima yang telah diabaikan. Metode kelebihantoMap
menyatakan penggunaanMap.merge
tapi bukan yang OP gunakan.Saya menulis
Collector
yang, tidak seperti java standar, tidak crash ketika Anda memilikinull
nilai:Cukup ganti
Collectors.toMap()
panggilan Anda ke panggilan ke fungsi ini dan itu akan memperbaiki masalah.sumber
null
nilai-nilai dan penggunaanputIfAbsent
tidak bisa dimainkan bersama. Itu tidak mendeteksi kunci duplikat ketika mereka memetakan kenull
...Ya, jawaban terlambat dari saya, tapi saya pikir mungkin membantu untuk memahami apa yang terjadi di bawah tenda kalau-kalau ada yang ingin kode beberapa lainnya
Collector
-logic .Saya mencoba memecahkan masalah dengan mengkode pendekatan yang lebih asli dan lurus ke depan. Saya pikir ini selangsung mungkin:
Dan tes menggunakan JUnit dan menegaskan:
Dan bagaimana Anda menggunakannya? Yah, gunakan saja alih-alih
toMap()
seperti tes menunjukkan. Ini membuat kode panggilan terlihat sebersih mungkin.EDIT:
menerapkan ide Holger di bawah ini, menambahkan metode pengujian
sumber
(map1, map2) -> { int total = map1.size() + map2.size(); map1.putAll(map2); if(map1.size() < total.size()) throw new IllegalStateException("Duplicate key(s)"); return map1; }
accumulator()
sebenarnya memeriksa itu. Mungkin saya harus melakukan stream paralel sekali :)Inilah kolektor yang lebih sederhana daripada yang diusulkan oleh @EmmanuelTouzery. Gunakan jika Anda suka:
Kami hanya mengganti
null
dengan beberapa objek khususnone
dan melakukan operasi terbalik di finisher.sumber
Jika nilainya adalah String, maka ini mungkin berfungsi:
map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))
sumber
Menurut
Stacktrace
Kapan disebut
map.merge
Ini akan melakukan
null
pemeriksaan sebagai hal pertamaSaya tidak menggunakan Java 8 begitu sering sehingga saya tidak tahu apakah ada cara yang lebih baik untuk memperbaikinya, tetapi memperbaikinya agak sulit.
Anda bisa melakukannya:
Gunakan filter untuk memfilter semua nilai NULL, dan dalam kode Javascript periksa apakah server tidak mengirim jawaban untuk id ini berarti ia tidak membalasnya.
Sesuatu seperti ini:
Atau gunakan mengintip, yang digunakan untuk mengubah elemen aliran untuk elemen. Dengan mengintip, Anda dapat mengubah jawaban untuk sesuatu yang lebih dapat diterima untuk peta, tetapi itu artinya sedikit mengedit logika Anda.
Kedengarannya seperti jika Anda ingin mempertahankan desain saat ini Anda harus menghindari
Collectors.toMap
sumber
Saya telah sedikit memodifikasi implementasi Emmanuel Touzery .
Versi ini;
Tes unit:
sumber
Maaf membuka kembali pertanyaan lama, tetapi karena sudah diedit baru-baru ini mengatakan bahwa "masalah" masih ada di Jawa 11, saya merasa ingin menunjukkan ini:
memberi Anda pengecualian penunjuk nol karena peta tidak mengizinkan null sebagai nilai. Ini masuk akal karena jika Anda melihat peta untuk kunci
k
dan tidak ada, maka nilai yang dikembalikan sudahnull
(lihat javadoc). Jadi jika Anda bisa memasukkank
nilainull
, peta akan terlihat seperti berperilaku aneh.Seperti yang dikatakan seseorang di komentar, cukup mudah untuk menyelesaikan ini dengan menggunakan pemfilteran:
dengan cara ini tidak ada
null
nilai yang akan dimasukkan dalam peta, dan MASIH akan Anda dapatkannull
sebagai "nilai" saat mencari id yang tidak memiliki jawaban di peta.Saya harap ini masuk akal untuk semua orang.
sumber
answerMap.put(4, null);
tanpa masalah. Anda benar bahwa dengan solusi yang diusulkan Anda, Anda akan mendapatkan hasil yang sama untuk anserMap.get () jika tidak ada seolah-olah nilainya akan dimasukkan sebagai nol. Namun, jika Anda mengulangi semua entri peta jelas ada perbedaan.sumber
Mempertahankan semua id id dengan tweak kecil
sumber
NullPointerException sejauh ini merupakan pengecualian yang paling sering dijumpai (setidaknya dalam kasus saya). Untuk menghindari ini, saya bersikap defensif dan menambahkan banyak cek nol dan akhirnya saya memiliki kode yang membengkak dan jelek. Java 8 memperkenalkan Opsional untuk menangani referensi nol sehingga Anda dapat menentukan nilai nullable dan non-nullable.
Yang mengatakan, saya akan membungkus semua referensi nullable dalam wadah opsional. Kita juga seharusnya tidak merusak kompatibilitas ke belakang juga. Ini kodenya.
sumber
Collectors.toMap()
bukan nilai null