Collections.emptyList () mengembalikan Daftar <Object>?

269

Saya mengalami beberapa masalah dalam menavigasi aturan Java untuk menyimpulkan parameter tipe generik. Pertimbangkan kelas berikut, yang memiliki parameter daftar opsional:

import java.util.Collections;
import java.util.List;

public class Person {
  private String name;
  private List<String> nicknames;

  public Person(String name) {
    this(name,Collections.emptyList());
  }

  public Person(String name,List<String> nicknames) {
    this.name = name;
    this.nicknames = nicknames;
  }
}

Kompiler Java saya memberikan kesalahan berikut:

Person.java:9: The constructor Person(String, List<Object>) is undefined

Tapi Collections.emptyList()tipe pengembalian <T> List<T>, bukan List<Object>. Menambahkan gips tidak membantu

public Person(String name) {
  this(name,(List<String>)Collections.emptyList());
}

hasil panen

Person.java:9: inconvertible types

Menggunakan EMPTY_LISTbukanemptyList()

public Person(String name) {
  this(name,Collections.EMPTY_LIST);
}

hasil panen

Person.java:9: warning: [unchecked] unchecked conversion

Sedangkan perubahan berikut membuat kesalahan hilang:

public Person(String name) {
  this.name = name;
  this.nicknames = Collections.emptyList();
}

Adakah yang bisa menjelaskan aturan pengecekan jenis apa yang saya hadapi di sini, dan cara terbaik untuk mengatasinya? Dalam contoh ini, contoh kode akhir memuaskan, tetapi dengan kelas yang lebih besar, saya ingin dapat menulis metode mengikuti pola "parameter opsional" ini tanpa menggandakan kode.

Untuk kredit tambahan: Kapan tepat untuk menggunakan EMPTY_LISTsebagai lawan emptyList()?

Chris Conway
sumber
1
Untuk semua pertanyaan terkait Java Generics, saya sangat merekomendasikan " Java Generics and Collections " oleh Maurice Naftalin, Philip Wadler.
Julien Chastang

Jawaban:

447

Masalah yang Anda temui adalah meskipun metode emptyList()kembali List<T>, Anda belum memberikannya dengan tipe, jadi default untuk kembali List<Object>. Anda dapat memberikan parameter type, dan kode Anda berperilaku seperti yang diharapkan, seperti ini:

public Person(String name) {
  this(name,Collections.<String>emptyList());
}

Sekarang ketika Anda melakukan penugasan langsung, kompiler dapat mengetahui parameter tipe generik untuk Anda. Ini disebut inferensi tipe. Misalnya, jika Anda melakukan ini:

public Person(String name) {
  List<String> emptyList = Collections.emptyList();
  this(name, emptyList);
}

maka emptyList()panggilan akan kembali dengan benar a List<String>.

InverseFalcon
sumber
12
Mengerti. Berasal dari dunia ML, aneh bagi saya bahwa Java tidak dapat menyimpulkan tipe yang benar: tipe parameter formal dan tipe kembalinya blankList jelas-jelas dapat disatukan. Tapi saya kira tipe penyimpulan hanya bisa mengambil "langkah kecil."
Chris Conway
5
Dalam beberapa kasus sederhana, kompiler mungkin dapat menyimpulkan parameter tipe yang hilang dalam kasus ini - tetapi ini bisa berbahaya. Jika ada beberapa versi metode ini dengan parameter yang berbeda, Anda mungkin akan memanggil yang salah. Dan yang kedua bahkan mungkin belum ada ...
Bill Michell
13
Notasi "Koleksi. <String> blankList ()" ini benar-benar aneh, tetapi masuk akal. Lebih mudah daripada Enum <E extends Enum <E>>. :)
Thiago Chaves
12
Memasok parameter tipe tidak diperlukan di Java 8 lagi (kecuali ada ambiguitas dalam tipe generik yang mungkin).
Vitalii Fedorenko
9
Cuplikan kedua memang menunjukkan inferensi jenis dengan baik tetapi tentu saja tidak akan dikompilasi. Panggilan untuk thisharus menjadi pernyataan pertama dalam konstruktor.
Arjan
99

Anda ingin menggunakan:

Collections.<String>emptyList();

Jika Anda melihat sumber untuk blankList apa Anda melihat bahwa itu sebenarnya hanya a

return (List<T>)EMPTY_LIST;
Carson
sumber
26

metode blankList memiliki tanda tangan ini:

public static final <T> List<T> emptyList()

Bahwa <T>sebelum daftar kata berarti bahwa itu menyimpulkan nilai parameter generik T dari jenis variabel hasil ditugaskan. Jadi dalam hal ini:

List<String> stringList = Collections.emptyList();

Nilai kembali kemudian direferensikan secara eksplisit oleh variabel tipe List<String>, sehingga kompiler dapat mengetahuinya. Pada kasus ini:

setList(Collections.emptyList());

Tidak ada variabel pengembalian eksplisit untuk kompiler yang digunakan untuk mengetahui tipe generik, jadi defaultnya adalah Object.

Dan Vinton
sumber