Menyembunyikan Nama Jawa: Cara yang Sulit

96

Saya punya masalah dengan penyembunyian nama yang sangat sulit dipecahkan. Berikut adalah versi sederhana yang menjelaskan masalahnya:

Ada kelas: org.A

package org;
public class A{
     public class X{...}
     ...
     protected int net;
}

Lalu ada kelas net.foo.X

package net.foo;
public class X{
     public static void doSomething();
}

Dan sekarang, inilah kelas bermasalah yang mewarisi dari Adan ingin dipanggilnet.foo.X.doSomething()

package com.bar;
class B extends A {

    public void doSomething(){
        net.foo.X.doSomething(); // doesn't work; package net is hidden by inherited field
        X.doSomething(); // doesn't work; type net.foo.X is hidden by inherited X
    }
}

Seperti yang Anda lihat, ini tidak mungkin. Saya tidak dapat menggunakan nama sederhana Xkarena disembunyikan oleh tipe yang diwariskan. Saya tidak dapat menggunakan nama yang sepenuhnya memenuhi syarat net.foo.X, karena netdisembunyikan oleh bidang warisan.

Hanya kelas yang Bada di basis kode saya; kelas net.foo.Xdan kelas org.Aperpustakaan, jadi saya tidak bisa mengubahnya!

Solusi saya satu-satunya terlihat seperti ini: Saya dapat memanggil kelas lain yang pada gilirannya memanggil X.doSomething(); tapi kelas ini hanya akan ada karena nama bentrok, yang nampaknya sangat berantakan! Apakah tidak ada solusi di mana saya bisa langsung menghubungi X.doSomething()dari B.doSomething()?

Dalam bahasa yang memungkinkan untuk menentukan namespace global, misalnya, global::di C # atau ::di C ++, saya bisa mengawali netdengan awalan global ini, tetapi Java tidak mengizinkannya.

gexicide
sumber
Beberapa perbaikan cepat bisa jadi ini: public void help(net.foo.X x) { x.doSomething(); }dan panggil denganhelp(null);
Absurd-Mind
@ Absurd-Mind: Yah, memanggil metode statis melalui objek yang tidak ada tampaknya lebih berantakan daripada solusi saya :). Saya bahkan akan menerima peringatan kompiler. Tapi benar, ini akan menjadi solusi hacky lain selain solusi saya.
gexicide
@JamesB: Ini akan menyebabkan X yang salah! net.foo.Xpunya metode, tidak org.A.X!
gexicide
3
Apakah Anda harus mewarisi dari A? Warisan bisa menjadi sangat jahat, seperti yang Anda temukan…
Donal Fellows
2
I could call another class that in turn calls X.doSomething(); but this class would only exist because of the name clash, which seems very messy1 untuk sikap kode bersih. Tapi bagi saya sepertinya ini adalah situasi yang harus Anda lakukan pengorbanan. Cukup lakukan ini, dan berikan komentar panjang yang bagus tentang mengapa Anda harus melakukannya (mungkin dengan tautan ke pertanyaan ini).
sampathsris

Jawaban:

84

Anda bisa mentransmisikan a nullke tipe dan kemudian memanggil metode itu (yang akan bekerja, karena objek target tidak terlibat dalam pemanggilan metode statis).

((net.foo.X) null).doSomething();

Ini memiliki manfaat

  • bebas efek samping (masalah dengan contoh net.foo.X),
  • tidak memerlukan penggantian nama apa pun (sehingga Anda dapat memberikan metode dalam Bnama yang Anda inginkan; itulah mengapa a import statictidak akan berfungsi dalam kasus Anda yang sebenarnya),
  • tidak memerlukan pengenalan kelas delegasi (meskipun itu mungkin ide yang bagus…), dan
  • tidak memerlukan overhead atau kompleksitas bekerja dengan API refleksi.

Sisi negatifnya adalah kode ini benar-benar mengerikan! Bagi saya, ini menghasilkan peringatan, dan itu hal yang baik secara umum. Tetapi karena ini mengatasi masalah yang sebaliknya sama sekali tidak praktis, menambahkan file

@SuppressWarnings("static-access")

pada titik penutup yang sesuai (minimal!) akan menutup kompilator.

Donal Fellows
sumber
1
Kenapa tidak? Anda hanya akan melakukan hal yang benar dan memfaktorkan ulang kodenya.
Gimby
1
Impor statis tidak jauh lebih jelas daripada solusi ini dan tidak berfungsi di semua kasus (Anda kacau jika ada doSomethingmetode di mana pun dalam hierarki Anda), jadi ya solusi terbaik.
Voo
@Voo Saya dengan bebas mengakui bahwa saya benar-benar pergi dan mencoba semua opsi yang telah didaftarkan orang lain sebagai jawaban setelah pertama kali mereproduksi persis apa masalah aslinya, dan saya terkejut betapa sulitnya untuk mengatasinya. Pertanyaan awal dengan rapi menutup semua pilihan lain yang lebih bagus.
Donal Fellows
1
Berikan suara untuk kreativitas, saya tidak akan pernah melakukan ini dalam kode produksi. Saya percaya tipuan adalah jawaban untuk masalah ini (bukankah ini untuk semua masalah?).
ethanfar
Terima kasih Donal untuk solusi ini. Sebenarnya, solusi ini menyoroti tempat di mana "Type" dapat digunakan dan variabel tidak dapat digunakan. Jadi, dalam "cast", compiler akan memilih "Type" meskipun ada variabel dengan nama yang sama. Untuk kasus yang lebih menarik, saya akan merekomendasikan 2 buku "Java Pitfalls": books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM
38

Mungkin cara paling sederhana (belum tentu yang termudah) untuk mengelola ini adalah dengan kelas delegasi:

import net.foo.X;
class C {
    static void doSomething() {
         X.doSomething();
    }
}

lalu ...

class B extends A {
    void doX(){
        C.doSomething();
    }
}

Ini agak bertele-tele, tetapi sangat fleksibel - Anda bisa membuatnya berperilaku sesuka Anda; ditambah itu bekerja dengan cara yang sama baik dengan staticmetode dan objek yang dipakai

Lebih lanjut tentang objek delegasi di sini: http://en.wikipedia.org/wiki/Delegation_pattern

blgt
sumber
Mungkin solusi terbersih yang disediakan. Saya mungkin akan menggunakannya jika saya memiliki masalah itu.
LordOfThePigs
37

Anda dapat menggunakan impor statis:

import static net.foo.X.doSomething;

class B extends A {
    void doX(){
        doSomething();
    }
}

Hati-hati Bdan Ajangan mengandung metode bernamadoSomething

Pikiran Absurd
sumber
Bukankah net.foo.X.doSomething hanya memiliki akses paket? Berarti Anda tidak dapat mengaksesnya dari paket com.bar
JamesB
2
@JamesB Ya, tetapi pertanyaan lengkapnya tidak akan masuk akal, karena metode ini tidak dapat diakses dengan cara apa pun. Saya pikir ini adalah kesalahan saat menyederhanakan contoh
Absurd-Mind
1
Tapi pertanyaan awal tampaknya aktif ingin digunakan doSomethingsebagai nama metode di B...
Donal Fellows
3
@DonalFellows: Anda benar; solusi ini tidak berfungsi jika kode saya harus tetap seperti yang saya posting. Untungnya, saya dapat mengganti nama metode ini. Namun, pikirkan kasus di mana metode saya menggantikan metode lain, maka saya tidak dapat mengganti namanya. Dalam kasus ini, jawaban ini tidak akan menyelesaikan masalah. Tapi ini akan menjadi lebih kebetulan daripada masalah saya yang sudah ada, jadi mungkin tidak akan pernah ada manusia yang akan menghadapi masalah seperti itu dengan bentrokan tiga nama :).
gexicide
4
Untuk memperjelasnya bagi semua orang: Solusi ini tidak berfungsi segera setelah Anda berada doSomethingdi mana pun dalam hierarki pewarisan (karena cara target panggilan metode diselesaikan). Jadi Knuth membantu Anda jika Anda ingin memanggil toStringmetode. Solusi yang diusulkan oleh Donal adalah yang ada di buku Java Puzzlers Bloch (saya pikir, belum mencarinya), jadi kami dapat menganggapnya sebagai jawaban yang benar-benar berwibawa :-)
Voo
16

Cara yang tepat untuk melakukan sesuatu adalah impor statis, tetapi dalam skenario kasus terburuk absolut, Anda BISA membuat instance kelas menggunakan refleksi jika Anda mengetahui nama yang sepenuhnya memenuhi syarat.

Java: newInstance kelas yang tidak memiliki konstruktor default

Dan kemudian aktifkan metode pada instance.

Atau, panggil saja metode itu sendiri dengan refleksi: Memanggil metode statis menggunakan refleksi

Class<?> clazz = Class.forName("net.foo.X");
Method method = clazz.getMethod("doSomething");
Object o = method.invoke(null);

Tentu saja, ini jelas merupakan resor terakhir.

EpicPandaForce
sumber
2
Jawaban ini sejauh ini adalah pembunuhan terbesar sampai sekarang - jempol :)
gexicide
3
Anda harus mendapatkan lencana penyelesai untuk jawaban ini; Anda memberikan jawaban untuk orang malang tunggal yang harus menggunakan Java versi kuno dan menyelesaikan masalah ini dengan tepat.
Gimby
Saya sebenarnya tidak memikirkan fakta bahwa static importfitur hanya ditambahkan di Java 1.5. Saya tidak iri pada orang yang perlu mengembangkan untuk 1.4 atau lebih rendah, saya harus sekali dan itu mengerikan!
EpicPandaForce
1
@ Gimby: Ya, masih ada ((net.foo.X)null).doSomething()solusi yang berfungsi di java lama. Tetapi jika tipe Amengandung tipe dalam net, MAKA ini adalah satu-satunya jawaban yang tetap valid :).
gexicide
6

Tidak benar-benar jawaban THE tetapi Anda dapat membuat instance X dan memanggil metode statis di atasnya. Itu akan menjadi cara (saya akui kotor) untuk memanggil metode Anda.

(new net.foo.X()).doSomething();
Michael Laffargue
sumber
3
Mentransmisikan nullke jenis dan kemudian memanggil metode di atasnya akan lebih baik, karena membuat objek mungkin memiliki efek samping yang tidak saya inginkan.
gexicide
@gexicide Ide casting nulltampaknya menjadi salah satu cara bersih untuk melakukannya. Perlu @SuppressWarnings("static-access")menjadi bebas peringatan ...
Donal Fellows
Terima kasih atas ketepatannya null, tetapi casting nullselalu menjadi penghancur hati bagi saya.
Michael Laffargue
4

Tidak perlu melakukan cast atau menyembunyikan peringatan aneh atau membuat instance yang berlebihan. Hanya trik menggunakan fakta bahwa Anda dapat memanggil metode statis kelas induk melalui sub-kelas. (Mirip dengan solusi hackish saya di sini .)

Buat saja kelas seperti ini

public final class XX extends X {
    private XX(){
    }
}

(Konstruktor privat di kelas terakhir ini memastikan tidak ada yang bisa membuat instance kelas ini secara tidak sengaja.)

Kemudian Anda bebas menelepon X.doSomething()melalui:

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }
billc.cn
sumber
Menarik, pendekatan lain. Namun, kelas dengan metode statis adalah kelas utilitas akhir.
gexicide
Ya, tidak bisa menggunakan trik ini dalam kasus itu. Namun alasan lain mengapa metode statis adalah kegagalan bahasa dan pendekatan objek Scala harus diambil.
billc.cn
Jika Anda tetap akan membuat kelas lain, maka menggunakan kelas delegasi seperti yang dijelaskan dalam jawaban blgt mungkin yang terbaik.
LordOfThePigs
@LordOfThePigs Ini menghindari pemanggilan metode ekstra dan beban pemfaktoran ulang di masa mendatang. Kelas baru pada dasarnya berfungsi sebagai alias.
billc.cn
2

Bagaimana jika Anda mencoba mendapatkan gobalnamespace mengingat semua file berada di folder yang sama. ( http://www.beanshell.org/javadoc/bsh/class-use/NameSpace.html )

    package com.bar;
      class B extends A {

       public void doSomething(){
         com.bar.getGlobal().net.foo.X.doSomething(); // drill down from the top...

         }
     }
Vectoria
sumber
Bagaimana cara kerjanya? AFAIK getGlobal()bukan metode Java standar untuk paket ... (Saya pikir paket tidak dapat memiliki metode apa pun di Java ...)
siegi
1

Inilah salah satu alasan komposisi lebih disukai daripada pewarisan.

package com.bar;
import java.util.concurrent.Callable;
public class C implements Callable<org.A>
{
    private class B extends org.A{
    public void doSomething(){
        C.this.doSomething();
    }
    }

    private void doSomething(){
    net.foo.X.doSomething();
    }

    public org.A call(){
    return new B();
    }
}
emory
sumber
0

Saya akan menggunakan pola Strategi.

public interface SomethingStrategy {

   void doSomething();
}

public class XSomethingStrategy implements SomethingStrategy {

    import net.foo.X;

    @Override
    void doSomething(){
        X.doSomething();
    }
}

class B extends A {

    private final SomethingStrategy strategy;

    public B(final SomethingStrategy strategy){
       this.strategy = strategy;
    }

    public void doSomething(){

        strategy.doSomething();
    }
}

Sekarang Anda juga telah memisahkan ketergantungan Anda, sehingga pengujian unit Anda akan lebih mudah untuk ditulis.

Erik Madsen
sumber
Anda lupa menambahkan implements SomethingStrategypada XSomethingStrategydeklarasi kelas.
Ricardo Souza
@rcdmk Terima kasih. Harus diperbaiki sekarang.
Erik Madsen