Java: Beberapa deklarasi kelas dalam satu file

237

Di Jawa, Anda dapat mendefinisikan beberapa kelas tingkat atas dalam satu file, dengan ketentuan bahwa paling banyak salah satunya adalah publik (lihat JLS §7.6 ). Lihat di bawah sebagai contoh.

  1. Apakah ada nama rapi untuk teknik ini (analog dengan inner, nested, anonymous)?

  2. JLS mengatakan sistem dapat memberlakukan pembatasan bahwa kelas-kelas menengah ini tidak dapat referred to by code in other compilation units of the package, misalnya, mereka tidak dapat diperlakukan sebagai paket-pribadi. Apakah itu benar-benar sesuatu yang berubah di antara implementasi Java?

misalnya, PublicClass.java:

package com.example.multiple;

public class PublicClass {
    PrivateImpl impl = new PrivateImpl();
}

class PrivateImpl {
    int implementationData;
}
Michael Brewer-Davis
sumber
11
+1 Pertanyaan bagus. Saya tidak pernah benar-benar memikirkan masalah ini, karena hampir tidak pernah perlu melakukan ini.
Michael Myers
12
perhatikan bahwa ini adalah fitur peninggalan; tidak akan pernah mungkin jika java memiliki kelas bersarang dari awal.
Kevin Bourrillion

Jawaban:

120

Nama saya yang disarankan untuk teknik ini (termasuk beberapa kelas tingkat atas dalam satu file sumber) akan menjadi "berantakan". Serius, saya tidak berpikir itu ide yang bagus - saya akan menggunakan tipe bersarang dalam situasi ini sebagai gantinya. Maka masih mudah untuk memprediksi file sumber mana yang masuk. Saya tidak percaya ada istilah resmi untuk pendekatan ini.

Adapun apakah ini benar-benar berubah di antara implementasi - saya sangat meragukannya, tetapi jika Anda menghindari melakukannya sejak awal, Anda tidak perlu peduli :)

Jon Skeet
sumber
71
Saya bukan downvoter, tetapi fakta bahwa jawaban ini adalah sesuatu yang bisa disebut "normatif" (mis. "Anda harus" bukannya "pada kenyataannya ... namun ...") adalah alasan yang paling mungkin untuk itu dapatkan downvote saya pikir. Sebenarnya tidak menjawab pertanyaan apa pun. Seperti mengajukan pengecualian yang tidak relevan alih-alih mengembalikan apa pun / mengajukan pengecualian yang memiliki informasi tentang fakta aktual, bukan pendapat.
n611x007
6
Saya menemukan apa yang saya anggap pengecualian kecil terhadap saran @JonSkeet untuk menggunakan tipe bersarang (yang jika tidak saya setujui): jika kelas utama generik dan parameter tipe adalah kelas kedua, kelas kedua tidak bisa bersarang. Dan jika kedua kelas sangat erat (seperti PublicClass dan PrivateImpl dalam pertanyaan), saya pikir itu ide yang baik untuk menempatkan PrivateImpl sebagai kelas tingkat atas dalam file yang sama.
jfritz42
6
@ BoomerRogers: Tidak, ini jelas bukan "dasar inti pemrograman berbasis komponen". Jika Anda memprogram komponen, mengapa Anda peduli bagaimana kode sumber diatur? (Secara pribadi saya lebih suka injeksi ketergantungan daripada pola pelokasi layanan, tapi itu masalah yang berbeda.) Pisahkan API dan organisasi kode sumber dalam pikiran Anda - semuanya sangat berbeda.
Jon Skeet
1
@JonSkeet Biarkan saya ulangi: "Jawaban" Anda adalah pendapat pribadi yang tidak relevan. (Yaitu jawaban seperti "berantakan" dan "saya ragu" memiliki sedikit nilai.) Jadi, posting Anda tidak menjawab salah satu dari 2 pertanyaan yang diajukan. Periksa jawaban polygenelubricants, dan Anda akan melihat bahwa ia berhasil menjawab keduanya.
bvdb
1
@ Bvdb: (Dan ada banyak hal yang merupakan praktik buruk tetapi diizinkan oleh spec. Saya akan mendorong orang untuk tidak menulis public int[] foo(int x)[] { return new int[5][5]; }juga, meskipun itu valid.)
Jon Skeet
130

javac tidak secara aktif melarang ini, tetapi ia memiliki batasan yang cukup banyak berarti bahwa Anda tidak akan pernah ingin merujuk ke kelas tingkat atas dari file lain kecuali ia memiliki nama yang sama dengan file itu.

Misalkan Anda memiliki dua file, Foo.java dan Bar.java.

Foo.java berisi:

  • kelas publik Foo

Bar.java berisi:

  • Bar kelas publik
  • kelas Baz

Katakan juga bahwa semua kelas berada dalam paket yang sama (dan file-file tersebut di direktori yang sama).

Apa yang terjadi jika Foo.java merujuk ke Baz tetapi bukan Bar dan kami mencoba untuk mengkompilasi Foo.java? Kompilasi gagal dengan kesalahan seperti ini:

Foo.java:2: cannot find symbol
symbol  : class Baz
location: class Foo
  private Baz baz;
          ^
1 error

Ini masuk akal jika Anda memikirkannya. Jika Foo.java merujuk ke Baz, tetapi tidak ada Baz.java (atau Baz.class), bagaimana bisa javac tahu file sumber apa yang harus dicari?

Jika Anda sebaliknya memberitahu javac untuk mengkompilasi Foo.java dan Bar.java pada saat yang sama, atau bahkan jika Anda sebelumnya telah mengkompilasi Bar.java (meninggalkan Baz.class di mana javac dapat menemukannya) maka kesalahan ini hilang. Ini membuat proses pembuatan Anda terasa sangat tidak bisa diandalkan dan serpihan.

Karena batasan sebenarnya, yang lebih mirip "jangan merujuk ke kelas tingkat atas dari file lain kecuali jika itu memiliki nama yang sama dengan file itu atau Anda juga merujuk ke kelas yang ada di file yang sama yang bernama hal yang sama seperti file "agak sulit untuk diikuti, orang biasanya pergi dengan konvensi yang jauh lebih mudah (walaupun lebih keras) hanya menempatkan satu kelas tingkat atas di setiap file. Ini juga lebih baik jika Anda pernah berubah pikiran tentang apakah suatu kelas harus umum atau tidak.

Terkadang memang ada alasan bagus mengapa semua orang melakukan sesuatu dengan cara tertentu.

Laurence Gonsalves
sumber
Apakah Maven melakukan sesuatu untuk membuat kompilasi dapat diandalkan?
Aleksandr Dubinsky
23

Saya yakin Anda cukup menyebutnya PrivateImplapa adanya: a non-public top-level class. Anda juga dapat mendeklarasikan non-public top-level interfaces.

misalnya, di tempat lain di SO: Kelas atas tingkat non-publik vs kelas bersarang statis

Adapun perubahan perilaku antara versi, ada diskusi tentang sesuatu yang "bekerja dengan sempurna" di 1.2.2. tetapi berhenti bekerja di 1.4 di forum sun: Java Compiler - tidak dapat mendeklarasikan kelas tingkat atas non publik dalam file .

polygenelubricants
sumber
1
Satu-satunya masalah saya dengan ini adalah bahwa Anda dapat non-public top level classmenjadi satu-satunya kelas dalam file, sehingga tidak mengatasi multiplisitas.
Michael Brewer-Davis
Saya memahami keprihatinan ini, tetapi seperti yang Anda lihat ini adalah istilah yang digunakan orang lain secara historis. Jika saya harus membuat istilah saya sendiri, saya mungkin akan menyebutnya secondary top level types.
polygenelubricants
7

Anda dapat memiliki kelas sebanyak yang Anda inginkan seperti ini

public class Fun {
    Fun() {
        System.out.println("Fun constructor");
    }
    void fun() {
        System.out.println("Fun mathod");
    }
    public static void main(String[] args) {
        Fun fu = new Fun();
        fu.fun();
        Fen fe = new Fen();
        fe.fen();
        Fin fi = new Fin();
        fi.fin();
        Fon fo = new Fon();
        fo.fon();
        Fan fa = new Fan();
        fa.fan();
        fa.run();
    }
}

class Fen {
    Fen() {
        System.out.println("fen construuctor");

    }
    void fen() {
        System.out.println("Fen method");
    }
}

class Fin {
    void fin() {
        System.out.println("Fin method");
    }
}

class Fon {
    void fon() {
        System.out.println("Fon method");
    } 
}

class Fan {
    void fan() {
        System.out.println("Fan method");
    }
    public void run() {
        System.out.println("run");
    }
}
Denis
sumber
1
@Nenotlep Ketika Anda melakukan "perbaiki pemformatan", maka harap berhati-hati agar tidak mengacaukan kode itu sendiri, seperti menghapus garis miring terbalik.
Tom
3
Itu tidak menjawab pertanyaan.
ᴠɪɴᴄᴇɴᴛ
4

1. Apakah ada nama yang rapi untuk teknik ini (analog dengan batin, bersarang, anonim)?

Demo file tunggal multi-kelas.

2. JLS mengatakan sistem dapat memberlakukan pembatasan bahwa kelas-kelas sekunder ini tidak dapat disebut dengan kode di unit kompilasi lain dari paket, misalnya, mereka tidak dapat diperlakukan sebagai paket-pribadi. Apakah itu benar-benar sesuatu yang berubah di antara implementasi Java?

Saya tidak mengetahui ada yang tidak memiliki batasan itu - semua kompiler berbasis file tidak akan memungkinkan Anda untuk merujuk ke kelas kode sumber dalam file yang tidak bernama sama dengan nama kelas. (jika Anda mengkompilasi file multi-kelas, dan meletakkan kelas-kelas di jalur kelas, maka setiap kompiler akan menemukannya)

Pete Kirkham
sumber
1

Menurut Java edisi 2 yang Efektif (Butir 13):

"Jika kelas tingkat atas (atau antarmuka) paket-pribadi hanya digunakan oleh satu kelas, pertimbangkan untuk menjadikan kelas tingkat atas sebagai kelas bersarang pribadi dari kelas tunggal yang menggunakannya (Item 22). Ini mengurangi aksesibilitasnya dari semua kelas-kelas dalam paketnya ke satu kelas yang menggunakannya. Tetapi jauh lebih penting untuk mengurangi aksesibilitas kelas publik yang serampangan daripada kelas tingkat atas privat-paket: ... "

Kelas bersarang mungkin statis atau non-statis berdasarkan apakah kelas anggota perlu akses ke instance terlampir (Butir 22).

rezzy
sumber
OP tidak bertanya tentang kelas bersarang.
charmoniumQ
0

Ya Anda bisa, dengan anggota statis publik di kelas publik luar, seperti:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

dan file lain yang merujuk pada di atas:

public class Bar {

    public static void main(String[] args){

        Foo.FooChild f = new Foo.FooChild();
        System.out.println(f);

    }
}

letakkan di folder yang sama. Kompilasi dengan:

javac folder/*.java

dan jalankan dengan:

 java -cp folder Bar
Alexander Mills
sumber
Contoh itu tidak menjawab pertanyaan. Anda memberikan contoh kelas statis bersarang, yang tidak sama dengan memiliki dua kelas tingkat atas yang didefinisikan dalam file yang sama.
Pedro García Medina
0

Hanya FYI, jika Anda menggunakan Java 11+, ada pengecualian untuk aturan ini: jika Anda menjalankan file java Anda secara langsung ( tanpa kompilasi ). Dalam mode ini, tidak ada batasan pada satu kelas publik per file. Namun, kelas dengan mainmetode harus yang pertama dalam file.

ZhekaKozlov
sumber
-5

Kamu tidak bisa. Tetapi sangat mungkin di Scala:

class Foo {val bar = "a"}
class Bar {val foo = "b"}
Michal Štein
sumber