자바 이름 숨기기 : 어려운 길


96

해결하기 매우 어려운 이름 숨김 문제가 있습니다. 다음은 문제를 설명하는 단순화 된 버전입니다.

수업이 있습니다. org.A

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

그런 다음 수업이 있습니다 net.foo.X

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

이제 여기에서 상속 A하고 호출하려는 문제 클래스가 있습니다.net.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
    }
}

보시다시피 이것은 불가능합니다. X상속 된 유형에 의해 숨겨져 있기 때문에 간단한 이름을 사용할 수 없습니다 . 나는 완전한 이름을 사용할 수 net.foo.X있기 때문에, net상속 된 필드에 의해 숨겨져 있습니다.

B내 코드베이스 에는 클래스 만 있습니다. 클래스 net.foo.Xorg.A라이브러리 클래스이므로 변경할 수 없습니다!

내 유일한 솔루션은 다음과 같습니다. 차례로 호출하는 다른 클래스를 호출 할 수 있습니다 X.doSomething(). 하지만이 클래스는 이름 충돌로 인해 존재할 수 있습니다. 내가 직접 호출 할 수있는 해결책이없는 X.doSomething()에서는 B.doSomething()?

예를 들어 global::C # 또는 ::C ++ 에서 전역 네임 스페이스를 지정할 수있는 언어에서는 net이 전역 접두사를 단순히 접두사 로 지정할 수 있지만 Java는이를 허용하지 않습니다.


일부 quickfix이 될 수 있습니다 : public void help(net.foo.X x) { x.doSomething(); }및 콜help(null);
부조리 - 마음

@ Absurd-Mind : 글쎄, 존재하지 않는 개체를 통해 정적 메서드를 호출하는 것은 내 솔루션보다 더 지저분 해 보입니다. :). 컴파일러 경고도 받게됩니다. 그러나 사실, 이것은 내 것 외에도 또 다른 해키 솔루션이 될 것입니다.
gexicide 2014-07-04

@JamesB : 이것은 잘못된 X를 불러 일으킬 것입니다! net.foo.X방법이 아니라 org.A.X!
gexicide 2014

3
당신이 할 상속에서 A? 당신이 ... 찾은으로 상속, 그래서 불쾌한 수 있습니다
DONAL 휄로우

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 messy깨끗한 코드 태도에 +1. 하지만 저에게는 이것이 트레이드 오프를해야하는 상황 인 것 같습니다. 이 작업을 수행하고 왜 그렇게해야했는지에 대한 멋진 긴 코멘트를 던지십시오 (아마이 질문에 대한 링크 포함).
sampathsris

답변:


84

null형식으로 캐스트 한 다음 해당 메서드를 호출 할 수 있습니다 (대상 개체가 정적 메서드 호출에 관여하지 않기 때문에 작동 함).

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

이것은의 이점이 있습니다

  • 부작용이 없음 (인스턴스화 문제 net.foo.X)
  • 아무 것도 이름을 바꿀 필요가 없습니다 ( B원하는 이름으로 메서드를 지정할 수 있으므로 import static정확한 경우에는 작동하지 않습니다).
  • 델리게이트 클래스의 도입이 필요하지 않습니다 (좋은 생각이긴하지만…).
  • 리플렉션 API 작업의 오버 헤드 나 복잡성이 필요하지 않습니다.

단점은이 코드가 정말 끔찍하다는 것입니다! 저에게는 경고가 발생하며 일반적으로 좋은 일입니다. 그러나 완전히 비현실적인 문제를 해결하기 때문에

@SuppressWarnings("static-access")

적절한 (최소한!) 둘러싸는 지점에서 컴파일러가 종료됩니다.


1
왜 안돼? 옳은 일을하고 코드를 리팩토링하면됩니다.
Gimby

1
고정 수입은 훨씬이 솔루션보다 명확 것을하지 않습니다 모든 경우에 그렇지 않은 작업 (A가 있다면 당신은 끝장 doSomething, 계층 구조 방법 어디가) 너무 옙 최고의 솔루션입니다.
Voo

@Voo 나는 내가 실제로 가서 다른 사람들이 답변으로 나열한 모든 옵션을 시도하고 원래 문제가 정확히 무엇인지를 처음으로 재현 한 것을 자유롭게 인정하고 해결하는 것이 얼마나 힘들 었는지 놀랐습니다. 원래 질문은 다른 모든 더 좋은 옵션을 깔끔하게 닫습니다.
Donal Fellows

1
창의성에 투표하세요.하지만 프로덕션 코드에서는 절대 이렇게하지 않겠습니다. 나는 간접이이 문제에 대한 답이라고 믿는다 (모든 문제가 아닌가?).
ethanfar 2014-07-06

이 솔루션에 대해 Donal에게 감사드립니다. 실제로이 솔루션은 "유형"을 사용할 수 있고 변수를 사용할 수없는 위치를 강조합니다. 따라서 "cast"에서 컴파일러는 같은 이름의 변수가 있더라도 "Type"을 선택합니다. 더 흥미로운 사례를 위해 2 개의 "Java Pitfalls"책을 추천합니다 : books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM

38

아마도 이것을 관리하는 가장 간단한 (반드시 가장 쉬운 것은 아님) 방법은 델리게이트 클래스를 사용하는 것입니다 :

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

그리고 ...

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

이것은 다소 장황하지만 매우 유연합니다. 원하는 방식으로 작동하도록 할 수 있습니다. 또한 static메서드와 인스턴스화 된 객체 모두에서 거의 동일한 방식으로 작동 합니다.

델리게이트 객체에 대한 자세한 내용은 http://en.wikipedia.org/wiki/Delegation_pattern을 참조 하십시오.


아마도 가장 깨끗한 솔루션이 제공되었을 것입니다. 그 문제가 있으면 아마 그것을 사용할 것입니다.
LordOfThePigs

37

정적 가져 오기를 사용할 수 있습니다.

import static net.foo.X.doSomething;

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

조심 그 BA이름이 방법을 포함하지 않는doSomething


net.foo.X.doSomething은 패키지 액세스 권한 만 가지고 있지 않습니까? 당신을 의미하는 것은 패키지 com.bar에서 액세스 할 수 없습니다
JamesB

2
@JamesB 예, 그러나 방법이 어떤 식 으로든 액세스 할 수 없기 때문에 완전한 질문은 의미가 없습니다. 나는 예를 단순화하는 동안이 오류라고 생각
부조리 - 마음

1
그러나 원래의 질문에 적극적으로 사용하려는 것 같다 doSomething의 메소드의 이름으로 B...
DONAL 휄로우

3
@DonalFellows : 당신 말이 맞아요. 이 솔루션은 게시 한대로 코드를 유지해야하는 경우 작동하지 않습니다. 다행히도 메서드 이름을 바꿀 수 있습니다. 그러나 내 방법이 다른 방법을 재정의 한 경우 이름을 바꿀 수없는 경우를 생각해보십시오. 이 경우이 답변은 실제로 문제를 해결하지 못합니다. 그러나 이것은 이미 내 문제보다 우연히 더 많을 것이므로 트리플 이름 충돌로 그런 문제를 겪을 사람은 결코 없을 것입니다 :).
gexicide 2014-07-04

4
다른 모든 사용자에게 명확하게하기 위해 :이 솔루션은 doSomething상속 계층 구조에있는 즉시 작동하지 않습니다 (메서드 호출 대상이 확인되는 방식으로 인해). 따라서 Knuth는 toString메서드 를 호출하려는 경우 도움을줍니다 . Donal이 제안한 솔루션은 Bloch의 Java Puzzlers 책에있는 솔루션입니다 (내 생각에는 아직 찾아 보지 않은 것 같습니다). 그래서 우리는 그것을 신뢰할 수있는 대답이라고 생각할 수 있습니다. :-)
Voo

16

작업을 수행하는 적절한 방법은 정적 가져 오기이지만 절대 최악의 시나리오에서 정규화 된 이름을 알고 있으면 리플렉션을 사용하여 클래스의 인스턴스를 구성 할 수 있습니다.

Java : 기본 생성자가없는 클래스의 newInstance

그런 다음 인스턴스에서 메서드를 호출합니다.

또는 리플렉션을 사용하여 메서드 자체를 호출합니다. 리플렉션을 사용하여 정적 메서드 호출

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

물론 이것들은 분명히 최후의 수단입니다.


2
이 대답은 지금까지 가장 큰 과잉입니다-엄지 손가락 :)
gexicide 2014-07-04

3
이 답변에 대한 완성 자 배지를 받아야합니다. 당신은 자바의 고대 버전을 사용하고이 정확한 문제를 해결해야하는 단수 가난한 사람에 대한 답을 제공했습니다.
Gimby

static import기능이에만 추가 되었다는 사실은 실제로 생각하지 않았습니다 Java 1.5. 1.4 이하로 개발해야하는 사람들이 부럽지 않습니다. 한 번만해야했는데 끔찍했습니다!
EpicPandaForce

1
@Gimby : 글쎄, 여전히 ((net.foo.X)null).doSomething()오래된 자바에서 작동 하는 솔루션이 있습니다. 유형하지만 A심지어 내부 유형을 포함 net, THEN 이 유효 :)을 남아있는 유일한 대답이다.
gexicide 2014

6

정답은 아니지만 X의 인스턴스를 만들고 이에 대한 정적 메서드를 호출 할 수 있습니다. 그것은 당신의 방법을 호출하는 방법 (내가 인정하는 더러운) 일 것입니다.

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

3
null유형으로 캐스팅 한 다음 메서드를 호출하는 것이 더 좋을 것입니다. 객체를 만드는 데 원하지 않는 부작용이있을 수 있기 때문입니다.
gexicide 2014

@gexicide 캐스팅에 대한 아이디어 null는 그것을 수행하는 더 깨끗한 방법 중 하나 인 것 같습니다. A가 필요 @SuppressWarnings("static-access")... 경고 무료로
DONAL 휄로우

에 대한 정확성에 감사드립니다 null.하지만 캐스팅 null은 항상 저에게 가슴 아픈 일이었습니다.
Michael Laffargue

4

캐스트를 수행하거나 이상한 경고를 억제하거나 중복 인스턴스를 만들 필요가 없습니다. 하위 클래스를 통해 부모 클래스 정적 메서드를 호출 할 수 있다는 사실을 사용하는 트릭입니다. ( 여기 내 hackish 솔루션과 유사합니다 .)

이런 클래스를 만드세요

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

(이 최종 클래스의 개인 생성자는 실수로이 클래스의 인스턴스를 만들 수 없도록합니다.)

그런 다음 자유롭게 전화 X.doSomething()를 걸 수 있습니다.

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }

흥미롭지 만 또 다른 접근 방식입니다. 그러나 정적 메서드가있는 클래스는 최종 유틸리티 클래스입니다.
gexicide 2014-07-05

예,이 경우에는이 트릭을 사용할 수 없습니다. 정적 메서드가 언어 실패 인 또 다른 이유는 Scala의 객체 접근 방식을 취해야합니다.
billc.cn 2014-07-05

어쨌든 다른 클래스를 만들려면 blgt 대답에 설명 된 것처럼 위임 클래스를 사용하는 것이 가장 좋습니다.
LordOfThePigs

@LordOfThePigs 그래도 추가 메서드 호출과 향후 리팩토링 부담을 피할 수 있습니다. 새 클래스는 기본적으로 별칭 역할을합니다.
billc.cn 2014

2

모든 파일이 동일한 폴더에있는 경우 gobalnamespace를 얻으려고하면 어떨까요? ( 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...

         }
     }

어떻게 작동합니까? AFAIK getGlobal()는 패키지에 대한 표준 Java 메소드가 아닙니다 ... (나는 패키지가 Java에서 메소드를 가질 수 없다고 생각합니다 ...)
siegi

1

이것은 구성이 상속보다 선호되는 이유 중 하나입니다.

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();
    }
}

0

전략 패턴을 사용합니다.

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();
    }
}

이제 종속성도 분리 했으므로 단위 테스트를 더 쉽게 작성할 수 있습니다.


클래스 선언 implements SomethingStrategy에 를 추가하는 것을 잊었습니다 XSomethingStrategy.
Ricardo Souza

@rcdmk 감사합니다. 지금 수정해야합니다.
Erik Madsen 2014 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.