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


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.X와 org.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();
    }
}

조심 그 B와 A이름이 방법을 포함하지 않는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.