Anotasi Java SafeVarargs, apakah ada standar atau praktik terbaik?

175

Saya baru saja menemukan @SafeVarargspenjelasan java . Googling untuk apa yang membuat fungsi variadic di Java tidak aman membuat saya agak bingung (heap poisoning? Terhapus jenis?), Jadi saya ingin tahu beberapa hal:

  1. Apa yang membuat fungsi Java variadic tidak aman dalam @SafeVarargsarti (lebih disukai dijelaskan dalam bentuk contoh mendalam)?

  2. Mengapa anotasi ini diserahkan pada kebijaksanaan programmer? Bukankah ini sesuatu yang harus bisa diperiksa oleh kompiler?

  3. Apakah ada standar yang harus dipatuhi untuk memastikan fungsinya benar-benar aman? Jika tidak, apa praktik terbaik untuk memastikannya?

Oren
sumber
1
Pernahkah Anda melihat contoh (dan penjelasan) di JavaDoc ?
jlordo
2
Untuk pertanyaan ketiga Anda, salah satu latihan adalah untuk selalu memiliki elemen pertama dan lain-lain: retType myMethod(Arg first, Arg... others). Jika Anda tidak menentukan first, array kosong diperbolehkan, dan Anda mungkin memiliki metode dengan nama yang sama dengan tipe pengembalian yang sama tanpa argumen, yang berarti JVM akan kesulitan menentukan metode mana yang harus dipanggil.
fge
4
@ jlordo saya lakukan, tapi saya tidak mengerti mengapa ini diberikan dalam konteks varargs karena orang dapat dengan mudah mengganti situasi ini di luar fungsi varargs (diverifikasi ini, mengkompilasi dengan peringatan keselamatan jenis yang diharapkan dan kesalahan saat runtime) ..
Oren

Jawaban:

244

1) Ada banyak contoh di Internet dan StackOverflow tentang masalah khusus dengan obat generik dan vararg. Pada dasarnya, ini terjadi ketika Anda memiliki jumlah variabel argumen tipe-parameter:

<T> void foo(T... args);

Di Jawa, vararg adalah gula sintaksis yang mengalami "penulisan ulang" sederhana pada waktu kompilasi: parameter tipe vararg X...diubah menjadi parameter tipe X[]; dan setiap kali panggilan dilakukan ke metode varargs ini, kompiler mengumpulkan semua "argumen variabel" yang masuk dalam parameter varargs, dan membuat array seperti new X[] { ...(arguments go here)... }.

Ini bekerja dengan baik ketika jenis varargs seperti beton String.... Ketika itu adalah variabel tipe seperti T..., itu juga berfungsi ketika Tdikenal sebagai tipe konkret untuk panggilan itu. misalnya jika metode di atas adalah bagian dari sebuah kelas Foo<T>, dan Anda memiliki Foo<String>referensi, kemudian memanggil foodi atasnya akan baik-baik saja karena kita tahu Tadalah Stringpada saat itu dalam kode.

Namun, itu tidak berfungsi ketika "nilai" dari Tparameter tipe lain. Di Jawa, tidak mungkin untuk membuat larik tipe komponen-tipe ( new T[] { ... }). Jadi Java malah menggunakan new Object[] { ... }(di sini Objectadalah batas atas T; jika ada batas atas adalah sesuatu yang berbeda, itu akan menjadi bukan Object), dan kemudian memberi Anda peringatan kompiler.

Jadi apa yang salah dengan menciptakan new Object[]bukan new T[]atau apa pun? Nah, array di Java tahu tipe komponen mereka saat runtime. Dengan demikian, objek array yang dikirimkan akan memiliki tipe komponen yang salah saat runtime.

Untuk kemungkinan penggunaan paling umum dari varargs, cukup untuk beralih ke elemen-elemen, ini bukan masalah (Anda tidak peduli dengan tipe runtime dari array), jadi ini aman:

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

Namun, untuk apa pun yang bergantung pada tipe komponen runtime dari array yang diteruskan, itu tidak akan aman. Berikut adalah contoh sederhana dari sesuatu yang tidak aman dan macet:

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

Masalahnya di sini adalah bahwa kita bergantung pada jenis yang argsakan digunakan T[]untuk mengembalikannya T[]. Tetapi sebenarnya jenis argumen saat runtime bukan turunan dari T[].

3) Jika metode Anda memiliki argumen tipe T...(di mana T adalah parameter tipe apa pun), maka:

  • Aman: Jika metode Anda hanya bergantung pada fakta bahwa elemen array adalah instance dari T
  • Tidak Aman: Jika itu tergantung pada fakta bahwa array adalah turunan dari T[]

Hal-hal yang bergantung pada tipe runtime dari array meliputi: mengembalikannya sebagai tipe T[], meneruskannya sebagai argumen ke parameter tipe T[], mendapatkan tipe array menggunakan .getClass(), meneruskannya ke metode yang bergantung pada tipe runtime dari array, seperti List.toArray()dan Arrays.copyOf(), dll.

2) Perbedaan yang saya sebutkan di atas terlalu rumit untuk dibedakan secara otomatis.

berita baru
sumber
7
nah sekarang semuanya masuk akal. Terima kasih. jadi hanya untuk melihat bahwa saya mengerti Anda sepenuhnya, katakanlah kita memiliki kelas FOO<T extends Something>apakah aman untuk mengembalikan T [] metode varargs sebagai array Somthings?
Oren
30
Mungkin perlu dicatat bahwa satu kasus di mana itu (mungkin) selalu benar untuk digunakan @SafeVarargsadalah ketika satu-satunya hal yang Anda lakukan dengan array adalah meneruskannya ke metode lain yang sudah begitu dijelaskan (misalnya: Saya sering menemukan diri saya menulis metode vararg yang ada untuk mengubah argumen mereka ke daftar menggunakan Arrays.asList(...)dan meneruskannya ke metode lain; kasus seperti itu selalu dapat dijelaskan dengan @SafeVarargskarena Arrays.asListmemiliki anotasi).
Jules
3
@newacct Saya tidak tahu apakah ini yang terjadi di Java 7, tetapi Java 8 tidak mengizinkan @SafeVarargskecuali metode ini staticatau final, karena kalau tidak bisa ditimpa dalam kelas turunan dengan sesuatu yang tidak aman.
Andy
3
dan di Java 9 juga akan diizinkan untuk privatemetode yang juga tidak dapat diganti.
Dave L.
4

Untuk praktik terbaik, pertimbangkan ini.

Jika Anda memiliki ini:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

Ubah ke ini:

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

Saya menemukan bahwa saya biasanya hanya menambahkan varargs agar lebih nyaman bagi penelepon saya. Hampir selalu akan lebih mudah bagi implementasi internal saya untuk menggunakan a List<>. Jadi untuk mendukung Arrays.asList()dan memastikan tidak ada cara saya bisa memperkenalkan Heap Pollution, inilah yang saya lakukan.

Saya tahu ini hanya menjawab # 3 Anda. newacct telah memberikan jawaban yang bagus untuk # 1 dan # 2 di atas, dan saya tidak memiliki reputasi yang cukup untuk meninggalkan ini sebagai komentar. : P

Kulkas
sumber