Parameter int terakhir digunakan dalam Runnable anonim

Saya bertanya-tanya apakah saya menggunakan metode yang benar untuk memanggil sesuatu di utas yang berbeda. Saya melakukannya di Android, tetapi menurut saya itu adalah pertanyaan umum Java.

Saya punya metode dengan beberapa parameter. Katakanlah mereka int.

class Main1 {
  public static void mainOnlyWork(int x, int y) {
     // not important here.
  }
}

class Test {
  Handler mHandler;

  // called from main thread, stored as reference.
  public Test() {
    mHandler = new Handler();
  }

  public static void callInMainThread(final int x, final int y) {
    mHandler.post(new Runnable() {
      public void run() {
        Main1.mainOnlyWork(x, y);
      }
    });
  }

Sekarang pertanyaan saya adalah, apakah aman menggunakan int final untuk membuat runnable anonim tanpa anggota kelas? Jika saya menghilangkan kata kunci terakhir untuk parameter x dan y, Eclipse akan mengeluh. Menurut saya, hal itu dimaksudkan untuk hanya menggunakan bilangan konstan dalam kasus seperti itu. Jika saya tidak meneruskan konstanta, apakah tidak masalah? Apakah Java "membuatnya" konstan dengan meneruskan ke fungsi ini?

Tapi saya ingin memanggil Test.callInMainThread dari asli menggunakan JNI. Menurut pendapat saya, tidak ada cara Java mengetahui apakah angka-angka itu konstan atau tidak. Bisakah saya mempercayai Java untuk menghasilkan keajaiban? Apakah akan selalu seperti ini?

Saya pikir mungkin saya harus membuat kelas proxy, seperti:

private abstract RunnableXY implements Runnable {
  public RunnableXY(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public int x;
  public int y;

  public abstract void run();
}

Dan metode panggilan akan menggunakan:

  public static void callInMainThread(final int x, final int y) {
    mHandler.post(new RunnableXY(x,y) {
      public void run() {
        Main1.mainOnlyWork(this.x, this.y);
      }
    });
  }

Dengan cara ini, saya melindungi nilai-nilai dari pengumpulan sampah sampai runnable digunakan dan dibuang. Apakah saya harus membuat pembungkus, atau apakah menandai x terakhir dalam parameter metode aman? Saat saya mencobanya, kata kunci final berfungsi dengan baik. Namun di thread, menurut saya jika ini berfungsi sekarang maka itu akan selalu berfungsi dengan baik. Apakah ini selalu berhasil dengan final? Bagaimana cara pembuatannya jika memang demikian? Apakah akan ada perbedaan jika parameternya bukan tipe primitif, melainkan Objek?

Pembaruan: Saya sudah mengerti apa arti final di Jawa. Itu bukanlah hal yang perlu dipertanyakan. Pertanyaannya adalah di mana ruang lingkup variabel yang digunakan dalam pembuatan Runnable. Mereka adalah penduduk lokal, artinya setelah fungsi berakhir, nilainya tidak dapat direferensikan. Di mana nilai-nilai itu disimpan, ketika objek Runnable diteruskan ke thread lain dan menunggu untuk dieksekusi?


person Pihhan    schedule 23.07.2013    source sumber
comment
Saya pikir saya telah menemukan jawabannya sendiri. Parameter tersebut harus bersifat final, karena parameter tersebut kemudian digunakan saat membuat metode run() dari Runnable anonim. Jika saya menjalankan callInMainThread(x=3, y=5), public void run() akan menghasilkan isi sebagai Main1.mainOnlyWork(3, 5), tidak mereferensikan variabel apa pun, lokal atau global. Inilah yang saya cari. Itu disimpan dalam kode Runnable.run() yang dibuat pada saat itu. Dan kode tidak akan berubah saat melewati antar thread.   -  person Pihhan    schedule 24.07.2013


Jawaban (3)


Dimulai dari hal mendasar: Java membuat salinan saat Anda meneruskan parameter. X dan y yang diterima metode Anda bukanlah variabel asli yang diteruskan, melainkan salinan dari nilai asli. Saat Anda mendeklarasikan metode seperti ini, nilai yang Anda berikan tidak harus berupa konstanta, namun salinan yang diterima metode tersebut adalah1:

callInMainThread(final int x, final int y) { ..... }

Kedua: Tidak, Anda tidak perlu membuat pembungkus. Saat Anda mengakses variabel lokal pada lingkup luar, kompiler Java secara otomatis membuat kolom untuk menyimpannya, sama seperti pembungkus yang Anda buat secara manual. Ini transparan bagi Anda.

Salah satu alasan mengapa Anda tidak dapat menghilangkan final adalah karena Java tidak menerapkan mekanisme apa pun untuk mentransfer perubahan nilai variabel antara variabel lokal metode dan bidang yang dihasilkan di kelas anonim. Selain itu, kelas anonim mungkin hidup lebih lama daripada pemanggilan metode. Apa yang terjadi jika kelas anonim membaca atau menulis variabel setelah metode kembali? Jika variabelnya bukan final, Anda tidak dapat membaca nilainya lagi atau menulis ke dalamnya.


1 Sebenarnya final variabel bukanlah konstanta. Anda dapat menetapkan nilai yang berbeda kepada mereka tetapi hanya sekali. Nilai parameter metode akhir ditetapkan saat metode dipanggil, sehingga nilai parameter tersebut cukup konstan selama durasi metode.

person Joni    schedule 23.07.2013
comment
Terima kasih! Saya tidak mengerti apa sebenarnya yang dimaksud dengan paragraf ketiga, sampai saya memikirkannya sendiri dengan kata-kata yang berbeda. - person Pihhan; 24.07.2013

Secara internal, salinan parameter dan variabel lokal (berada di tumpukan panggilan metode) diambil di thread. Hal ini karena setelah pemanggilan metode selesai, thread masih aktif.

Dan variabel-variabel tersebut harus bersifat final, untuk melarang penimpaan variabel asli dalam metode, yang akan menyebabkan versi berbeda di thread. Itu sungguh menyesatkan. Jadi ini soal membiarkan kedua versi dengan nama yang sama memiliki arti yang sama.

Desain bahasa yang cermat.

(Agak disederhanakan, tidak menyebutkan kasus yang terbalik secara simetris, yang berlaku.)

person Joop Eggen    schedule 23.07.2013

Tipe primitif selalu diteruskan berdasarkan nilai. Bahkan jika Anda memodifikasinya di dalam suatu metode, nilai aslinya (di luar metode) tidak pernah berubah. Jadi ya, ini aman karena nilai-nilai ini bersifat lokal pada metode tersebut (baik itu final atau tidak).

Tentang final kata kunci dalam parameter, sebenarnya hanya menghalangi Anda untuk menetapkan ulang nilai, tidak memiliki tujuan lain. Ini hanya untuk keamanan kode. Parameter selalu diteruskan berdasarkan nilai di Java (referensi objek juga diteruskan berdasarkan nilai), jadi apa pun yang terjadi, penetapan ulang apa pun akan bersifat lokal pada metode tersebut. Setelah metode ini selesai, semua penetapan ulang yang telah Anda lakukan dalam metode ini akan hilang. Misalnya, tidak ada perbedaan antara keduanya

public void test(String a) {
    a = "Hello";
}

Dan

public void test(final String a) {
    a = "Hello";
}

kecuali kompiler itu akan memunculkan kesalahan dalam kasus kedua. Dalam kasus pertama ketika metode test() selesai a akan dikembalikan ke nilai asli (bukan "Halo"). Jadi secara efektif final dalam parameter membuat parameter "konstan" (perhatikan bahwa untuk objek: Anda masih dapat mengubah status objek, tetapi bukan referensi objek).


Kata kunci final diperlukan dalam kelas anonim karena Anda mereferensikan variabel dalam lingkup kelas lain (dalam kasus Anda, instance Runnable anonim Anda mereferensikan variabel instance Main1). Jadi jika ini dijalankan di thread lain dan belum final, Anda bisa menimpa referensi asli selama durasi metode ini. Ini akan membuat setiap thread mereferensikan objek berbeda dengan nama variabel yang sama, sehingga membingungkan, sehingga diblokir dalam desain bahasa.


Anda tidak perlu membuat pembungkus atau referensi tambahan apa pun. Parameter sudah direferensikan selama durasi metode dan tidak akan dikumpulkan dari sampah.

person m0skit0    schedule 23.07.2013
comment
Fakta sebenarnya bahwa mereka adalah orang lokal yang berfungsi membuat saya khawatir. Kalau saya panggil tiga kali dengan parameter berbeda, cepat berturut-turut. Sebelum thread utama mulai menjalankan fungsi pertama, parameter metode diubah sebanyak 3 kali. Di mana nilai-nilai lokal itu disimpan? Kalau di tumpukan thread lokal, kemungkinan besar akan ditimpa. Utas utama tidak boleh mengakses tumpukan utas pekerja, bukan? Apakah int tersebut ada di memori heap yang dibagikan antar thread? - person Pihhan; 24.07.2013
comment
Variabel lokal disimpan dalam tumpukan karena merupakan penyimpanan sementara. Setiap utas memiliki tumpukannya sendiri, tidak dibagikan. Ini adalah konteks lokal thread. Mereka tidak dapat ditimpa di tumpukan thread lokal kecuali dengan konteks thread saat ini, tidak peduli seberapa cepat mereka dipanggil atau dari mana - sebenarnya saya tidak mengerti apa sebenarnya yang Anda maksud dengan ini. Selain itu, sekali lagi, nilai ini disalin, jadi tidak peduli apakah nilai tersebut berubah dalam cakupan lokal, Anda secara eksklusif mengubah salinan lokal tersebut dan bukan salinan lain dan bukan nilai aslinya. - person m0skit0; 24.07.2013
comment
Lalu pertanyaan saya adalah bagaimana cara menyalinnya ke Runnable() {} baru. Apakah mereka dibuat sebagai bidang objek anonim itu? Saya ingin tahu bagaimana variabel-variabel tersebut dikirim ke thread lain. Saya mengerti apa arti variabel lokal. Namun variabel tersebut tidak digunakan saat metode tersebut dipanggil. Mereka diteruskan ke metode, yang akan dipanggil di masa depan. Di mana mereka berada di antara waktu tersebut? - person Pihhan; 24.07.2013
comment
Runnable juga dibuat saat Anda memanggil metode tersebut. Jadi nilai yang digunakan Runnable akan menjadi nilai yang diteruskan ke metode karena kelas anonim berada di dalam cakupan tersebut. Cara mereka disalin ke antarmuka anonim sebenarnya bergantung pada implementasi kelas/antarmuka anonim Java. IMHO ini tidak relevan: disalin sebagai bidang, atau apa pun. Anda bisa mengecek source code JRE jika memang ingin mengetahui detailnya. - person m0skit0; 24.07.2013
comment
Itu yang saya minta. Apakah Java/Dalvik memastikan variabel selalu utuh dan bagaimana cara melakukannya? Mungkin tidak relevan jika saya yakin VM memastikannya tidak akan pernah ditimpa atau tidak valid. Saya tidak cukup terampil untuk memahami sumber JRE dan memahami apakah aman atau tidak. Saya harap orang yang lebih bijak akan mengetahuinya. - person Pihhan; 24.07.2013
comment
Saya masih tidak mengerti apa yang Anda maksud dengan memastikan variabel selalu utuh. Variabel final tidak dapat diubah, ini diperiksa pada waktu kompilasi. Sekali lagi: parameter adalah salinan, konteks lokal dapat memodifikasinya dengan cara apa pun yang diinginkan, ini tidak akan memengaruhi variabel yang sama di thread lain. bagaimana cara melakukannya Baca kode sumbernya. Jika Anda tidak memiliki pengetahuan untuk memahami sumber JRE, Anda tidak akan memahami cara kerjanya. Lagi pula, bagaimana sebenarnya tidak relevan. Selain itu ini mungkin diimplementasikan dengan cara yang berbeda tergantung pada pengembang JRE. Anda dapat berasumsi JRE melakukannya dengan benar. - person m0skit0; 24.07.2013