Java: Visibilitas subpackage?

150

Saya memiliki dua paket dalam proyek saya: odp.projdan odp.proj.test. Ada beberapa metode yang saya ingin terlihat hanya oleh kelas-kelas dalam dua paket ini. Bagaimana saya bisa melakukan ini?

EDIT: Jika tidak ada konsep subpackage di Jawa, apakah ada cara lain? Saya memiliki metode tertentu yang saya inginkan hanya tersedia untuk penguji dan anggota lain dari paket itu. Haruskah saya membuang semuanya ke dalam paket yang sama? Gunakan refleksi yang luas?

Nick Heiner
sumber
2
Sebagai tambahan, tes seharusnya hanya menguji perilaku objek Anda yang dapat diamati dari luar paket. Mengakses metode / kelas paket-lingkup dari tes Anda memberi tahu saya tes mungkin menguji implementasi bukan perilaku. Dengan menggunakan alat build seperti maven atau gradle, tes ini akan memudahkan tes Anda untuk berjalan di jalan kelas yang sama tetapi tidak dimasukkan dalam tabung final (hal yang baik), sehingga tidak perlu bagi mereka untuk memiliki nama paket yang berbeda. Namun menempatkan mereka dalam paket terpisah adalah untuk menegakkan bahwa Anda tidak mengakses ruang lingkup privat / default dan dengan demikian hanya menguji api publik.
derekv
3
Ini mungkin benar jika Anda bekerja dengan perilaku yang didorong oleh perilaku murni dan ingin pengujian Anda hanya melakukan pengujian kotak hitam. Tetapi mungkin ada kasus-kasus di mana penerapan perilaku yang diinginkan membutuhkan kompleksitas siklomatik yang tinggi. Dalam hal ini, akan bagus untuk memecah implementasi menjadi potongan-potongan yang lebih kecil dan lebih sederhana (masih pribadi untuk implementasi) dan menulis beberapa tes unit untuk melakukan pengujian kotak putih pada jalur yang berbeda melalui potongan-potongan ini.
James Woods

Jawaban:

165

Kamu tidak bisa Di Jawa tidak ada konsep subpackage, jadi odp.projdan odp.proj.testbenar-benar paket yang terpisah.

starblue
sumber
10
Meskipun saya suka seperti ini, itu membingungkan bahwa sebagian besar IDE menyatukan paket dengan nama yang sama. Terima kasih atas klarifikasi.
JacksOnF1re
Ini tidak sepenuhnya akurat: JLS memang mendefinisikan sub-paket, meskipun satu - satunya arti bahasa yang mereka miliki adalah melarang "terhadap suatu paket yang memiliki sub-paket dengan nama sederhana yang sama dengan tipe tingkat atas". Saya baru saja menambahkan jawaban untuk pertanyaan ini menjelaskan ini secara rinci.
M. Justin
59

Nama-nama paket Anda mengisyaratkan bahwa aplikasi di sini adalah untuk pengujian unit. Pola khas yang digunakan adalah untuk meletakkan kelas yang ingin Anda uji dan kode uji unit dalam paket yang sama (dalam kasus Anda odp.proj) tetapi dalam pohon sumber yang berbeda. Jadi, Anda akan memasukkan kelas src/odp/projAnda dan kode tes Anda test/odp/proj.

Java memang memiliki pengubah akses "paket" yang merupakan pengubah akses default ketika tidak ada yang ditentukan (mis. Anda tidak menentukan publik, pribadi atau dilindungi). Dengan pengubah akses "paket", hanya kelas di yang odp.projakan memiliki akses ke metode. Tetapi perlu diingat bahwa di Jawa, pengubah akses tidak dapat diandalkan untuk menegakkan aturan akses karena dengan refleksi, akses apa pun dimungkinkan. Pengubah akses hanya bersifat sugestif (kecuali jika ada manajer keamanan yang membatasi).

Asaf
sumber
11

Ini bukan hubungan khusus antara odp.projdan odp.proj.test- mereka kebetulan dinamai terkait.

Jika paket odp.proj.test hanya menyediakan tes maka Anda dapat menggunakan nama paket yang sama ( odp.proj). IDE seperti Eclipse dan Netbeans akan membuat folder terpisah ( src/main/java/odp/projdan src/test/java/odp/proj) dengan nama paket yang sama tetapi dengan semantik JUnit.

Perhatikan bahwa IDE ini akan menghasilkan pengujian untuk metode di odp.projdan membuat folder yang sesuai untuk metode pengujian yang tidak ada.

peter.murray.rust
sumber
5

Ketika saya melakukan ini di IntelliJ, pohon sumber saya terlihat seperti ini:

src         // source root
- odp
   - proj   // .java source here
- test      // test root
  - odp
     - proj // JUnit or TestNG source here
Duffymo
sumber
4

EDIT: Jika tidak ada konsep subpackage di Jawa, apakah ada cara lain? Saya memiliki metode tertentu yang saya inginkan hanya tersedia untuk penguji dan anggota lain dari paket itu.

Mungkin sedikit tergantung pada motif Anda untuk tidak menampilkannya tetapi jika satu-satunya alasan adalah Anda tidak ingin mencemari antarmuka publik dengan hal-hal yang hanya dimaksudkan untuk pengujian (atau beberapa hal internal lainnya) saya akan meletakkan metode dalam pisahkan antarmuka publik dan minta konsumen metode "tersembunyi" menggunakan antarmuka itu. Itu tidak akan menghentikan orang lain menggunakan antarmuka tetapi saya tidak melihat alasan mengapa Anda harus.

Untuk pengujian unit, dan jika memungkinkan tanpa menulis ulang lot, ikuti saran untuk menggunakan paket yang sama.

Fredrik
sumber
3

Seperti yang telah dijelaskan orang lain, tidak ada yang namanya "subpackage" di Jawa: semua paket terisolasi dan tidak mewarisi apa pun dari orang tua mereka.

Cara mudah untuk mengakses anggota kelas yang dilindungi dari paket lain adalah dengan memperluas kelas dan menimpa anggota.

Misalnya, untuk mengakses ClassInAdalam paket a.b:

package a;

public class ClassInA{
    private final String data;

    public ClassInA(String data){ this.data = data; }

    public String getData(){ return data; }

    protected byte[] getDataAsBytes(){ return data.getBytes(); }

    protected char[] getDataAsChars(){ return data.toCharArray(); }
}

buat kelas dalam paket itu yang menimpa metode yang Anda butuhkan di ClassInA:

package a.b;

import a.ClassInA;

public class ClassInAInB extends ClassInA{
    ClassInAInB(String data){ super(data); }

    @Override
    protected byte[] getDataAsBytes(){ return super.getDataAsBytes(); }
}

Itu memungkinkan Anda menggunakan kelas utama menggantikan kelas di paket lain:

package a.b;

import java.util.Arrays;

import a.ClassInA;

public class Driver{
    public static void main(String[] args){
        ClassInA classInA = new ClassInA("string");
        System.out.println(classInA.getData());
        // Will fail: getDataAsBytes() has protected access in a.ClassInA
        System.out.println(Arrays.toString(classInA.getDataAsBytes()));

        ClassInAInB classInAInB = new ClassInAInB("string");
        System.out.println(classInAInB.getData());
        // Works: getDataAsBytes() is now accessible
        System.out.println(Arrays.toString(classInAInB.getDataAsBytes()));
    }
}

Perhatikan bahwa ini hanya berfungsi untuk anggota yang dilindungi, yang terlihat oleh kelas yang diperluas (warisan), dan bukan anggota paket-pribadi yang hanya terlihat oleh sub / kelas yang diperluas dalam paket yang sama. Semoga ini bisa membantu seseorang!

ndm13
sumber
3

Sebagian besar jawaban di sini menyatakan bahwa tidak ada subpackage di Jawa, tetapi itu tidak sepenuhnya akurat. Istilah ini telah dalam Spesifikasi Bahasa Jawa sejauh Jawa 6, dan mungkin lebih jauh ke belakang (sepertinya tidak ada versi JLS yang dapat diakses secara bebas untuk versi Java yang lebih lama). Bahasa di sekitar subpackages tidak banyak berubah di JLS sejak Java 6.

Java 13 JLS :

Anggota sebuah paket adalah subpackage-nya dan semua tipe kelas tingkat atas dan tipe antarmuka tingkat atas yang dinyatakan dalam semua unit kompilasi paket.

Misalnya, di Java SE Platform API:

  • Paket ini javamemiliki subpackages awt, applet, io, lang, net, dan utilunit, tetapi tidak ada kompilasi.
  • Paket ini java.awtmemiliki sub paket bernama image, serta sejumlah unit kompilasi yang berisi deklarasi tipe kelas dan antarmuka.

Konsep subpackage relevan, seperti memberlakukan batasan penamaan antara paket dan kelas / interface:

Paket mungkin tidak mengandung dua anggota dengan nama yang sama, atau hasil kesalahan waktu kompilasi.

Berikut ini beberapa contohnya:

  • Karena paket tersebut java.awtmemiliki subpackage image, paket tidak dapat (dan tidak) berisi deklarasi kelas atau tipe antarmuka yang dinamai image.
  • Jika ada paket bernama mousedan tipe anggota Buttondalam paket itu (yang kemudian dapat disebut sebagai mouse.Button), maka tidak ada paket dengan nama mouse.Buttonatau kualifikasi penuh mouse.Button.Click.
  • Jika com.nighthacks.java.jagnama jenis yang sepenuhnya memenuhi syarat, maka tidak mungkin ada paket yang nama lengkapnya memenuhi syarat adalah com.nighthacks.java.jagatau com.nighthacks.java.jag.scrabble.

Namun, batasan penamaan ini adalah satu - satunya signifikansi yang diberikan kepada sub paket oleh bahasa:

Struktur penamaan hirarkis untuk paket dimaksudkan agar nyaman untuk mengatur paket terkait secara konvensional, tetapi tidak memiliki arti penting selain larangan terhadap paket yang memiliki subpackage dengan nama sederhana yang sama dengan tipe tingkat atas yang dinyatakan dalam paket tersebut. .

Misalnya, tidak ada hubungan akses khusus antara paket yang diberi nama oliverdan paket lain yang diberi nama oliver.twist, atau antara paket yang diberi nama evelyn.wooddan evelyn.waugh. Artinya, kode dalam paket bernama oliver.twisttidak memiliki akses yang lebih baik ke jenis yang dinyatakan dalam paket oliverdaripada kode dalam paket lain.


Dengan konteks ini, kita dapat menjawab pertanyaan itu sendiri. Karena secara eksplisit tidak ada hubungan akses khusus antara paket dan subpackage-nya, atau antara dua subpackages yang berbeda dari paket induk, tidak ada cara dalam bahasa untuk membuat metode terlihat oleh dua paket berbeda dengan cara yang diminta. Ini adalah keputusan desain yang terdokumentasi dan disengaja.

Entah metode tersebut dapat dibuat publik dan semua paket (termasuk odp.projdan odp.proj.test) akan dapat mengakses metode yang diberikan, atau metode tersebut dapat dibuat paket pribadi (visibilitas default), dan semua kode yang perlu mengaksesnya secara langsung harus dimasukkan ke dalam paket (sub) yang sama dengan metode.

Yang mengatakan, praktik yang sangat standar di Jawa adalah memasukkan kode tes dalam paket yang sama dengan kode sumber, tetapi di lokasi yang berbeda pada sistem file. Misalnya, dalam alat pembuatan Maven , konvensi adalah untuk menempatkan file sumber dan uji masing-masing dalam src/main/java/odp/projdan src/test/java/odp/proj. Ketika alat build mengkompilasi ini, kedua set file berakhir di odp.projpaket, tetapi hanya srcfile yang termasuk dalam artefak produksi; file uji hanya digunakan pada waktu build untuk memverifikasi file produksi. Dengan pengaturan ini, kode pengujian dapat dengan bebas mengakses setiap paket kode pribadi atau dilindungi dari kode yang diuji, karena mereka akan berada dalam paket yang sama.

Dalam kasus di mana Anda ingin berbagi kode lintas sub paket atau paket saudara bukan kasus uji / produksi, salah satu solusi yang saya lihat beberapa perpustakaan gunakan adalah dengan meletakkan kode bersama sebagai publik, tetapi mendokumentasikan bahwa itu dimaksudkan untuk perpustakaan internal gunakan saja.

M. Justin
sumber
0

Tanpa meletakkan pengubah akses di depan metode yang Anda katakan itu adalah paket pribadi.
Lihatlah contoh berikut.

package odp.proj;
public class A
{
    void launchA() { }
}

package odp.proj.test;
public class B
{
    void launchB() { }
}

public class Test
{
    public void test()
    {
        A a = new A();
        a.launchA()    // cannot call launchA because it is not visible
    }
}
Alberto Zaccagni
sumber
0

Dengan kelas PackageVisibleHelper, dan merahasiakannya sebelum PackageVisibleHelperFactory beku, kita dapat memanggil metode launchA (by PackageVisibleHelper) di mana saja :)

package odp.proj;
public class A
 {
    void launchA() { }
}

public class PackageVisibleHelper {

    private final PackageVisibleHelperFactory factory;

    public PackageVisibleHelper(PackageVisibleHelperFactory factory) {
        super();
        this.factory = factory;
    }

    public void launchA(A a) {
        if (factory == PackageVisibleHelperFactory.INSTNACNE && !factory.isSampleHelper(this)) {
            throw new IllegalAccessError("wrong PackageVisibleHelper ");
        }
        a.launchA();
    }
}


public class PackageVisibleHelperFactory {

    public static final PackageVisibleHelperFactory INSTNACNE = new PackageVisibleHelperFactory();

    private static final PackageVisibleHelper HELPER = new PackageVisibleHelper(INSTNACNE);

    private PackageVisibleHelperFactory() {
        super();
    }

    private boolean frozened;

    public PackageVisibleHelper getHelperBeforeFrozen() {
        if (frozened) {
            throw new IllegalAccessError("please invoke before frozen!");
        }
        return HELPER;
    }

    public void frozen() {
        frozened = true;
    }

    public boolean isSampleHelper(PackageVisibleHelper helper) {
        return HELPER.equals(helper);
    }
}
package odp.proj.test;

import odp.proj.A;
import odp.proj.PackageVisibleHelper;
import odp.proj.PackageVisibleHelperFactory;

public class Test {

    public static void main(String[] args) {

        final PackageVisibleHelper helper = PackageVisibleHelperFactory.INSTNACNE.getHelperBeforeFrozen();
        PackageVisibleHelperFactory.INSTNACNE.frozen();


        A a = new A();
        helper.launchA(a);

        // illegal access       
        new PackageVisibleHelper(PackageVisibleHelperFactory.INSTNACNE).launchA(a); 
    }
}
qxo
sumber