왜 반사를 사용해야합니까?


29

저는 Java를 처음 사용합니다. 내 연구를 통해 리플렉션이 클래스와 메서드를 호출하고 어떤 메서드가 구현되었는지 알기 위해 사용된다는 것을 읽었습니다.

리플렉션을 언제 사용해야합니까? 리플렉션을 사용하고 객체를 인스턴스화하는 것과 전통적인 방법을 호출하는 것의 차이점은 무엇입니까?



10
게시하기 전에 귀하의 연구에 참여하십시오. @Jalayn이 지적한 것처럼 StackExchange에는 많은 자료가 있으며 일반적으로 웹에는 반사에 대한 정보가 있습니다. 예를 들어 Java Tutorial on Reflection 을 읽고 더 구체적인 질문이 있으면 다시 돌아올 것을 제안합니다 .
Péter Török

1
백만 개의 속죄가 있어야합니다.
DeadMG

3
소수의 전문 프로그래머 이상이 "가능한 한 거의, 심지어는 결코 그렇지 않다"고 대답 할 것입니다.
로스 패터슨

답변:


38
  • 리플렉션은 이름으로 메서드를 호출하는 것보다 훨씬 느립니다. 미리 컴파일 된 주소와 상수를 사용하는 대신 바이트 코드의 메타 데이터를 검사해야하기 때문입니다.

  • 리플렉션도 더욱 강력합니다. protected또는 final멤버 의 정의를 검색하고 , 보호를 제거하고 마치 변경 가능하다고 선언 된 것처럼 조작 할 수 있습니다! 분명히 이것은 언어가 일반적으로 프로그램에 대해 보장하는 많은 보증을 파괴하며 매우 위험 할 수 있습니다.

그리고 이것은 그것을 언제 사용 해야하는지 거의 설명합니다. 보통은하지 마십시오. 메소드를 호출하려면 호출하십시오. 멤버를 변경하려면 컴파일 백으로 돌아 가지 않고 변경 가능하도록 선언하십시오.

실제 리플렉션 사용 중 하나는 사용자 정의 클래스와 상호 운용해야하는 프레임 워크를 작성할 때 프레임 워크 작성자가 멤버 (또는 클래스)가 무엇인지 알지 못하는 경우입니다. 리플렉션을 통해 학생들은 미리 알지 않고도 모든 수업을 처리 할 수 ​​있습니다. 예를 들어, 복잡한 측면 지향 라이브러리를 반영하지 않고 작성할 수 있다고 생각하지 않습니다.

또 다른 예로, JUnit은 사소한 리플렉션을 사용했습니다. 클래스의 모든 메소드를 열거하고 호출 된 모든 testXXX메소드가 테스트 메소드 라고 가정하고 해당 메소드 만 실행합니다. 그러나 이제는 주석 대신에 더 잘 수행 할 수 있으며 실제로 JUnit 4는 주석 대신 크게 이동했습니다.


6
"더 강력한"관리가 필요합니다. Turing 완성도를 얻기 위해 반사가 필요하지 않으므로 계산이 필요하지 않습니다. 물론 튜링 컴플리트는 I / O 기능 및 리플렉션과 같은 다른 종류의 파워에 대해서는 아무 것도 말하지 않습니다.
Steve314

1
반사가 반드시 "더 느리게"되는 것은 아닙니다. 리플렉션을 한 번 사용하여 직접 호출 래퍼 바이트 코드를 생성 할 수 있습니다.
SK-logic

2
필요할 때 알게 될 것입니다. 나는 왜 언어가 아닌 다른 언어가 왜 필요할지 궁금해했다. 그런 다음 갑자기 ... 나는 다른 개발자의 패널이 내가 유지 관리하는 시스템 중 하나에 들어가도록 할 때 부모 / 자식 체인을 걸어 내려 데이터를 들여다 보거나 찔렀습니다.
Brian Knoblauch

@ SK-logic : 실제로 바이트 코드를 생성하기 위해 리플렉션이 전혀 필요하지 않습니다 (실제 리플렉션에는 바이트 코드 조작을위한 API가 전혀 없습니다!).
Joachim Sauer

1
@JoachimSauer는 물론 생성 된 바이트 코드 를 로드 하려면 리플렉션 API가 필요합니다 .
SK-logic

15

나는 한 번 당신과 같았습니다. 나는 반사에 대해 많이 몰랐습니다. 여전히 모르겠습니다. 그러나 나는 그것을 한 번 사용했습니다.

나는 두 개의 내부 클래스가있는 클래스를 가지고 있었고 각 클래스에는 많은 메소드가있었습니다.

내부 클래스의 모든 메서드를 호출해야했으며 수동으로 호출하면 너무 많은 작업이 수행되었습니다.

리플렉션을 사용하여 메서드 자체 수 대신 2-3 줄의 코드로 이러한 모든 메서드를 호출 할 수 있습니다.


4
왜 공감해야합니까?
Mahmoud Hossam

1
서문에 대한지지
Dmitry

1
@MahmoudHossam 아마도 최선의 방법은 아니지만 대답은 배포 할 수있는 중요한 전술을 보여줍니다.
ankush981

13

리플렉션 사용을 세 그룹으로 그룹화했습니다.

  1. 임의의 클래스를 인스턴스화합니다. 예를 들어, 의존성 주입 프레임 워크에서 인터페이스 ThingDoer가 NetworkThingDoer 클래스에 의해 구현됨을 선언 할 수 있습니다. 그런 다음 프레임 워크는 NetworkThingDoer의 생성자를 찾아 인스턴스화합니다.
  2. 다른 형식으로 마샬링 및 마샬링 해제 예를 들어, Bean 규칙을 따르는 getter 및 설정을 사용하여 오브젝트를 JSON에 맵핑 한 후 다시 맵핑합니다. 코드는 실제로 필드의 이름이나 메서드를 알지 못하며 클래스 만 검사합니다.
  3. 리디렉션 계층에서 클래스를 래핑 (아마도 List가 실제로로드되지는 않지만 데이터베이스에서 가져 오는 방법을 알고있는 것에 대한 포인터) 또는 클래스를 완전히 가짜 (jMock은 인터페이스를 구현하는 합성 클래스를 만듭니다. 테스트 목적으로).

이것은 StackExchange에서 찾은 리플렉션에 대한 가장 좋은 설명입니다. 대부분의 답변은 Java 트레일의 말 ( "이러한 속성에 모두 액세스 할 수 있지만 그 이유는 아님")을 반복하거나,없이 쉽게 수행 할 수있는 리플렉션을 사용하여 작업을 수행하는 예를 제공하거나 Spring의 방법에 대한 모호한 답변을 제공합니다. 사용합니다. 이 답변은 실제로 반성없이 JVM으로 쉽게 해결할 수없는 세 가지 유효한 예를 제공합니다. 고맙습니다!
ndm13

3

리플렉션을 통해 프로그램은 존재하지 않을 수있는 코드로 작업 할 수 있으며 신뢰할 수있는 방식으로 코드를 작성할 수 있습니다.

"정상 코드"에는 스 니펫 URLConnection c = null이있어 클래스 로더가이 클래스를로드하는 동안 클래스 로더가 URLConnection 클래스를로드하여 ClassNotFound 예외를 발생시키고 종료하는 것과 같은 스 니펫 이 있습니다.

리플렉션을 사용하면 문자열 형식으로 이름을 기반으로 클래스를로드하고 클래스에 종속되는 실제 클래스를 시작하기 전에 다양한 속성 (컨트롤 외부의 여러 버전에 유용한)에 대해 테스트 할 수 있습니다. 일반적인 예는 Java 프로그램을 다른 플랫폼에는없는 OS X에서 고유하게 보이도록하는 데 사용되는 OS X 특정 코드입니다.


2

기본적으로 리플렉션은 프로그램 코드를 데이터로 사용하는 것을 의미합니다.

따라서 프로그램 코드가 유용한 데이터 소스 인 경우 리플렉션을 사용하는 것이 좋습니다. (그러나 절충점이 있기 때문에 항상 좋은 생각은 아닙니다.)

예를 들어 간단한 클래스를 생각해보십시오.

public class Foo {
  public int value;
  public string anotherValue;
}

XML을 생성하려고합니다. XML을 생성하는 코드를 작성할 수 있습니다.

public XmlNode generateXml(Foo foo) {
  XmlElement root = new XmlElement("Foo");
  XmlElement valueElement = new XmlElement("value");
  valueElement.add(new XmlText(Integer.toString(foo.value)));
  root.add(valueElement);
  XmlElement anotherValueElement = new XmlElement("anotherValue");
  anotherValueElement.add(new XmlText(foo.anotherValue));
  root.add(anotherValueElement);
  return root;
}

그러나 이것은 많은 상용구 코드이며 클래스를 변경할 때마다 코드를 업데이트해야합니다. 실제로이 코드의 기능을 설명 할 수 있습니다

  • 클래스 이름으로 XML 요소를 만듭니다.
  • 클래스의 각 속성에 대해
    • 속성 이름으로 XML 요소를 만듭니다.
    • 속성 값을 XML 요소에 넣습니다.
    • 루트에 XML 요소를 추가하십시오

이것은 알고리즘이며 알고리즘의 입력은 클래스입니다. 이름과 속성의 이름, 유형 및 값이 필요합니다. 여기에서 리플렉션이 발생합니다.이 정보에 액세스 할 수 있습니다. Java를 사용하면 Class클래스 의 메소드를 사용하여 유형을 검사 할 수 있습니다 .

더 많은 사용 사례 :

  • 클래스의 메소드 이름을 기반으로 웹 서버에서 URL을 정의하고 메소드 인수를 기반으로 URL 매개 변수를 정의하십시오.
  • 클래스의 구조를 GraphQL 타입 정의로 변환
  • 단위 테스트 케이스로 이름이 "test"로 시작하는 클래스의 모든 메소드를 호출하십시오.

그러나 완전한 반영은 기존 코드 ( "자체 검사"라고도 함)를 볼뿐만 아니라 코드를 수정하거나 생성하는 것을 의미합니다. 이를 위해 Java에는 두 가지 주요 사용 사례가 있습니다 : 프록시와 모의.

인터페이스가 있다고 가정 해 봅시다.

public interface Froobnicator {
  void froobnicateFruits(List<Fruit> fruits);
  void froobnicateFuel(Fuel fuel);
  // lots of other things to froobnicate
}

흥미로운 것을 구현하는 구현이 있습니다.

public class PowerFroobnicator implements Froobnicator {
  // awesome implementations
}

실제로 두 번째 구현도 있습니다.

public class EnergySaverFroobnicator implements Froobnicator {
  // efficient implementations
}

이제 로그 출력도 원합니다. 메소드가 호출 될 때마다 단순히 로그 메시지가 필요합니다. 모든 메소드에 로그 출력을 명시 적으로 추가 할 수는 있지만 성가 시므로 두 번 수행해야합니다. 각 구현마다 한 번씩. (더 많은 구현을 추가 할 때 더 많은)

대신 프록시를 작성할 수 있습니다.

public class LoggingFroobnicator implements Froobnicator {
  private Logger logger;
  private Froobnicator inner;

  // constructor that sets those two

  public void froobnicateFruits(List<Fruit> fruits) {
    logger.logDebug("froobnicateFruits called");
    inner.froobnicateFruits(fruits);
  }

  public void froobnicateFuel(Fuel fuel) {
    logger.logDebug("froobnicateFuel( called");
    inner.froobnicateFuel(fuel);
  }
  // lots of other things to froobnicate
}

그러나 알고리즘에 의해 설명 될 수있는 반복적 인 패턴이 있습니다.

  • 로거 프록시는 인터페이스를 구현하는 클래스입니다.
  • 인터페이스와 로거의 다른 구현을 취하는 생성자가 있습니다.
  • 인터페이스의 모든 메소드
    • 구현시 "$ methodname called"메시지가 기록됩니다.
    • 그런 다음 내부 인터페이스에서 동일한 메소드를 호출하여 모든 인수를 전달합니다.

이 알고리즘의 입력은 인터페이스 정의입니다.

리플렉션을 사용하면이 알고리즘을 사용하여 새 클래스를 정의 할 수 있습니다. Java를 사용하면 java.lang.reflect.Proxy클래스 의 메소드를 사용하여이를 수행 할 수 있으며 더 많은 기능을 제공하는 라이브러리가 있습니다.

반사의 단점은 무엇입니까?

  • 코드를 이해하기가 더 어려워집니다. 귀하는 코드의 구체적인 효과에서 한 단계 더 추상화 된 것입니다.
  • 코드를 디버깅하기가 더 어려워집니다. 특히 코드 생성 라이브러리의 경우 실행되는 코드는 작성한 코드가 아니라 생성 한 코드 일 수 있으며 디버거가 해당 코드를 표시하지 않거나 중단 점을 배치 할 수 있습니다.
  • 코드가 느려집니다. 하드 코딩 액세스 대신 런타임 핸들로 유형 정보를 동적으로 읽고 필드에 액세스하면 속도가 느려집니다. 동적 코드 생성은 디버그하기가 더 어려워이 효과를 완화 할 수 있습니다.
  • 코드가 깨지기 쉬워 질 수 있습니다. 동적 리플렉션 액세스는 컴파일러에서 유형을 확인하지 않지만 런타임시 오류를 발생시킵니다.

1

리플렉션은 프로그램의 일부를 자동으로 동기화 된 상태로 유지할 수 있습니다. 이전에는 새 인터페이스를 사용하기 위해 프로그램을 수동으로 업데이트해야했습니다.


5
이 경우 지불하는 가격은 IDE에서 컴파일러의 유형 검사와 리 팩터 안전성을 잃는 것입니다. 내가 기꺼이하지 않는 것이 절충입니다.
Barend
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.