"Saya akan memberi tahu Anda tentang « pengubah akses ». Saya pernah membicarakannya sebelumnya, tetapi pengulangan adalah pilar pembelajaran."

Anda dapat mengontrol akses (visibilitas) yang dimiliki kelas lain ke metode dan variabel kelas Anda. Pengubah akses menjawab pertanyaan «Siapa yang dapat mengakses metode/variabel ini?». Anda hanya dapat menentukan satu pengubah untuk setiap metode atau variabel.

1) pengubah « publik ».

Variabel, metode, atau kelas yang ditandai dengan pengubah publik dapat diakses dari mana saja di dalam program. Ini adalah tingkat keterbukaan tertinggi: tidak ada batasan.

2) pengubah « pribadi ».

Variabel, metode, atau kelas yang ditandai dengan pengubah privat hanya dapat diakses di kelas tempat ia dideklarasikan. Metode atau variabel yang ditandai disembunyikan dari semua kelas lain. Ini adalah tingkat privasi tertinggi: hanya dapat diakses oleh kelas Anda. Metode seperti itu tidak diwariskan dan tidak dapat diganti. Selain itu, mereka tidak dapat diakses di kelas keturunan.

3)  « Pengubah default ».

Jika variabel atau metode tidak ditandai dengan pengubah apa pun, maka dianggap ditandai dengan pengubah "default". Variabel dan metode dengan pengubah ini dapat dilihat oleh semua kelas dalam paket tempat mereka dideklarasikan, dan hanya untuk kelas tersebut. Pengubah ini juga disebut akses " paket " atau " paket pribadi ", mengisyaratkan fakta bahwa akses ke variabel dan metode terbuka untuk seluruh paket yang berisi kelas.

4) pengubah « terlindungi ».

Tingkat akses ini sedikit lebih luas daripada paket . Variabel, metode, atau kelas yang ditandai dengan pengubah yang dilindungi dapat diakses dari paketnya (seperti "paket"), dan dari semua kelas yang diwariskan.

Tabel ini menjelaskan semuanya:

Jenis visibilitas Kata kunci Mengakses
Kelas Anda Paket Anda Keturunan Semua kelas
Pribadi pribadi Ya TIDAK TIDAK TIDAK
Kemasan (tidak ada pengubah) Ya Ya TIDAK TIDAK
Terlindung terlindung Ya Ya Ya TIDAK
Publik publik Ya Ya Ya Ya

Ada cara untuk mengingat tabel ini dengan mudah. Bayangkan Anda sedang menulis surat wasiat. Anda membagi semua barang Anda menjadi empat kategori. Siapa yang dapat menggunakan barang-barang Anda?

Siapa yang memiliki akses Pengubah Contoh
Hanya  saya pribadi Jurnal pribadi
Keluarga (tidak ada pengubah) Foto keluarga
Keluarga dan ahli waris terlindung Harta keluarga
Semua orang publik Memoar

"Ini seperti membayangkan bahwa kelas dalam paket yang sama adalah bagian dari satu keluarga."

"Saya juga ingin memberi tahu Anda beberapa nuansa menarik tentang metode penggantian."

1) Implementasi implisit dari metode abstrak.

Katakanlah Anda memiliki kode berikut:

Kode
class Cat
{
 public String getName()
 {
  return "Oscar";
 }
}

Dan Anda memutuskan untuk membuat kelas Tiger yang mewarisi kelas ini, dan menambahkan antarmuka ke kelas baru

Kode
class Cat
{
 public String getName()
 {
   return "Oscar";
 }
}
interface HasName
{
 String getName();
 int getWeight();
}
class Tiger extends Cat implements HasName
{
 public int getWeight()
 {
  return 115;
 }

}

Jika Anda hanya menerapkan semua metode yang hilang yang diminta oleh IntelliJ IDEA untuk diterapkan, Anda mungkin akan menghabiskan waktu lama untuk mencari bug.

Ternyata kelas Tiger memiliki metode getName yang diwarisi dari Cat, yang akan diambil sebagai implementasi metode getName untuk antarmuka HasName.

"Aku tidak melihat sesuatu yang buruk tentang itu."

"Itu tidak terlalu buruk, itu kemungkinan tempat terjadinya kesalahan."

Tapi itu bisa lebih buruk:

Kode
interface HasWeight
{
 int getValue();
}
interface HasSize
{
 int getValue();
}
class Tiger extends Cat implements HasWeight, HasSize
{
 public int getValue()
 {
  return 115;
 }
}

Ternyata Anda tidak selalu dapat mewarisi dari banyak antarmuka. Lebih tepatnya, Anda dapat mewarisinya, tetapi Anda tidak dapat menerapkannya dengan benar. Lihatlah contohnya. Kedua antarmuka mengharuskan Anda mengimplementasikan metode getValue() , tetapi tidak jelas apa yang harus dikembalikan: bobot atau ukurannya? Ini cukup tidak menyenangkan untuk dihadapi.

"Saya setuju. Anda ingin mengimplementasikan suatu metode, tetapi tidak bisa. Anda telah mewarisi metode dengan nama yang sama dari kelas dasar. Itu rusak."

"Tapi ada kabar baik."

2) Memperluas visibilitas. Saat Anda mewarisi suatu tipe, Anda dapat memperluas visibilitas suatu metode. Begini tampilannya:

kode jawa Keterangan
class Cat
{
 protected String getName()
 {
  return "Oscar";
 }
}
class Tiger extends Cat
{
 public String getName()
 {
  return "Oscar Tiggerman";
 }
}
Kami telah memperluas visibilitas metode dari protectedke public.
Kode Mengapa ini «legal»
public static void main(String[] args)
{
 Cat cat = new Cat();
 cat.getName();
}
Semuanya bagus. Di sini kita bahkan tidak tahu bahwa visibilitas telah diperluas di kelas turunan.
public static void main(String[] args)
{
 Tiger tiger = new Tiger();
 tiger.getName();
}
Di sini kami memanggil metode yang visibilitasnya telah diperluas.

Jika ini tidak memungkinkan, kita selalu dapat mendeklarasikan metode di Tiger:
public String getPublicName()
{
super.getName(); // panggil metode yang dilindungi
}

Dengan kata lain, kita tidak berbicara tentang pelanggaran keamanan apa pun.

public static void main(String[] args)
{
 Cat catTiger = new Tiger();
 catTiger.getName();
}
Jika semua kondisi yang diperlukan untuk memanggil metode di kelas dasar ( Cat ) terpenuhi, maka mereka pasti puas untuk memanggil metode pada tipe keturunan ( Tiger ) . Karena pembatasan pemanggilan metode lemah, tidak kuat.

"Saya tidak yakin saya benar-benar mengerti, tetapi saya akan ingat bahwa ini mungkin."

3) Mempersempit jenis pengembalian.

Dalam metode yang diganti, kita dapat mengubah tipe pengembalian menjadi tipe referensi yang dipersempit.

kode jawa Keterangan
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Tiger getMyParent()
 {
  return (Tiger) this.parent;
 }
}
Kami mengesampingkan metode getMyParent, dan sekarang mengembalikan Tigerobjek.
Kode Mengapa ini «legal»
public static void main(String[] args)
{
 Cat parent = new Cat();

 Cat me = new Cat();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Semuanya bagus. Di sini kita bahkan tidak tahu bahwa tipe pengembalian metode getMyParent telah diperluas di kelas keturunan.

Bagaimana «kode lama» bekerja dan bekerja.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Tiger me = new Tiger();
 me.setMyParent(parent);
 Tiger myParent = me.getMyParent();
}
Di sini kita memanggil metode yang jenis pengembaliannya telah dipersempit.

Jika ini tidak memungkinkan, kita selalu dapat mendeklarasikan metode di Tiger:
public Tiger getMyTigerParent()
{
return (Tiger) this.parent;
}

Dengan kata lain, tidak ada pelanggaran keamanan dan/atau pelanggaran pengecoran tipe.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Dan semuanya bekerja dengan baik di sini, meskipun kami memperluas tipe variabel ke kelas dasar (Cat).

Karena pengesampingan, metode setMyParent yang benar dipanggil.

Dan tidak ada yang perlu dikhawatirkan saat memanggil metode getMyParent , karena nilai yang dikembalikan, meskipun dari kelas Tiger, masih dapat ditetapkan ke variabel myParent dari kelas dasar (Cat) tanpa masalah.

Objek harimau dapat disimpan dengan aman baik dalam variabel Harimau maupun variabel Kucing.

"Ya. Mengerti. Saat mengganti metode, Anda harus mengetahui cara kerja semua ini jika kami meneruskan objek kami ke kode yang hanya dapat menangani kelas dasar dan tidak tahu apa-apa tentang kelas kami. "

"Tepat sekali! Lalu pertanyaan besarnya adalah mengapa kita tidak bisa mempersempit tipe nilai pengembalian saat mengganti metode?"

"Jelas bahwa dalam hal ini kode di kelas dasar akan berhenti berfungsi:"

kode jawa Penjelasan masalahnya
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Object getMyParent()
 {
  if (this.parent != null)
   return this.parent;
  else
   return "I'm an orphan";
 }
}
Kami membebani metode getMyParent dan mempersempit jenis nilai pengembaliannya.

Semua baik-baik saja disini.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 Cat myParent = me.getMyParent();
}
Maka kode ini akan berhenti bekerja.

Metode getMyParent dapat mengembalikan instance Object apa pun, karena sebenarnya dipanggil pada objek Tiger.

Dan kami tidak memiliki cek sebelum penugasan. Dengan demikian, sangat mungkin variabel myParent tipe Cat akan menyimpan referensi String.

"Contoh yang bagus, Amigo!"

Di Java, sebelum suatu metode dipanggil, tidak ada pemeriksaan apakah objek tersebut memiliki metode tersebut. Semua pemeriksaan terjadi saat runtime. Dan panggilan [hipotetis] ke metode yang hilang kemungkinan besar akan menyebabkan program mencoba mengeksekusi bytecode yang tidak ada. Ini pada akhirnya akan menyebabkan kesalahan fatal, dan sistem operasi akan menutup program secara paksa.

"Wah. Sekarang aku tahu."