Java의 Lambda 표현식 이해
가상 확장 방법은 Java에 다중 계승의 특성을 가져왔다. 비록 이 팀은 다중 계승과 달리 가상 확장 방법은 행위 계승에 제한된다고 주장하지만.아마도 이 특성을 통해 다중 계승의 그림자를 볼 수 있을 것이다.그래도 너는 실례 상태의 계승을 모의할 수 있다.나는 다음 글에서 자바 8에서mixin 혼입을 통해 상태의 계승을 실현하는 것을 상세하게 설명할 것이다.
믹스가 뭐예요?
혼입은 조합의 추상적인 클래스로 주로 다중 계승 상하문에서 하나의 클래스에 여러 개의 서비스를 추가하는 데 사용되며, 다중 계승은 여러 개의mixin을 당신의 클래스로 조합합니다.예를 들어, 만약 당신이 "말"을 나타내는 클래스가 있다면, 이 클래스를 실례화하여 "말"의 실례를 만든 다음, "차고"와 "화원"을 계승하여 그것을 확장할 수 있습니다. Scala를 사용하는 방법은 다음과 같습니다.
val myHouse = new House with Garage with Garden
mixin에서 계승하는 것은 특정한 규범이 아니다. 이것은 단지 각종 기능을 기존 유형에 추가하는 방법일 뿐이다.OOP에서mixin이 있으면 이를 통해 클래스의 가독성을 향상시킬 수 있습니다.
예를 들어 Python의 socketserver 모듈에서mixin을 사용하는 방법이 있습니다. 여기서 mixin은 다중 프로세스를 지원하는 UDP와 TCP 서비스, 다중 스레드를 지원하는 UDP와 TCP 서비스 등 4개의 서로 다른 Socket 기반 서비스를 도와줍니다.
class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
가상 확장 방법은 무엇입니까?Java8은 가상 확장 방법의 개념을 도입하고public defender method라고도 부른다.우선 이 개념을 VEM으로 간소화합시다.
VEM은 Java 인터페이스에 기본적인 방법 정의를 제공하기 위해서입니다. 기존 인터페이스에 새로운 방법 정의를 추가할 수 있습니다. 예를 들어 Java의 집합 API입니다.이렇게 하면 Hibernate와 같은 제3자 라이브러리는 이러한 집합 API를 실현하는 모든 방법을 중복할 필요가 없다. 왜냐하면 이미 기본적인 방법을 제공했기 때문이다.
다음은 인터페이스에서 방법을 정의하는 방법의 예입니다.
public interface Collection<T> extends Iterable<T> {
<R> Collection<R> filter(Predicate<T> p)
default { return Collections.<T>filter(this, p); }
}
Java 8 혼입 시뮬레이션이제 VEM을 통해 혼입 효과를 실현하겠습니다. 미리 경고한 것은 업무 중에 사용하지 마세요!
아래의 실현은 라인이 안전하지 않을 뿐만 아니라 메모리 유출 문제도 존재할 수 있습니다. 이것은 당신이 클래스에서 정의한hashCode와 equals 방법에 달려 있습니다. 이것도 또 다른 단점입니다. 저는 뒤에서 이 문제를 토론하겠습니다.
먼저 인터페이스 (아날로그 상태 Bean) 를 정의하고 방법의 기본 정의를 제공합니다.
public interface SwitchableMixin {
boolean isActivated() default { return Switchables.isActivated(this); }
void setActivated(boolean activated) default { Switchables.setActivated(this, activated); }
}
그리고 우리는 맵 실례를 포함하여 실례와 상태의 관련을 저장하는 도구 클래스를 정의합니다. 상태는 도구 클래스의 개인 플러그인 클래스를 통해 대표됩니다.
public final class Switchables {
private static final Map<SwitchableMixin, SwitchableDeviceState> SWITCH_STATES = new HashMap<>();
public static boolean isActivated(SwitchableMixin device) {
SwitchableDeviceState state = SWITCH_STATES.get(device);
return state != null && state.activated;
}
public static void setActivated(SwitchableMixin device, boolean activated) {
SwitchableDeviceState state = SWITCH_STATES.get(device);
if (state == null) {
state = new SwitchableDeviceState();
SWITCH_STATES.put(device, state);
}
state.activated = activated;
}
private static class SwitchableDeviceState {
private boolean activated;
}
}
이것은 사용 용례로 상태의 계승을 돋보이게 한다.
private static class Device {}
private static class DeviceA extends Device implements SwitchableMixin {}
private static class DeviceB extends Device implements SwitchableMixin {}
"전혀 다른 것".위의 구현은 정상적으로 작동하는 것 같지만, Oracle의 Java 언어 프로그래머인 Brian Goetz는 나에게 현재 구현은 작동할 수 없다고 의문을 제기했다(라인 보안과 메모리 유출 문제가 해결되었다고 가정한다).
interface FakeBrokenMixin {
static Map<FakeBrokenMixin, String> backingMap
= Collections.synchronizedMap(new WeakHashMap<FakeBrokenMixin, String>());
String getName() default { return backingMap.get(this); }
void setName(String name) default { backingMap.put(this, name); }
}
interface X extends Runnable, FakeBrokenMixin {}
X makeX() { return () -> { System.out.println("X"); }; }
X x1 = makeX();
X x2 = makeX();
x1.setName("x1");
x2.setName("x2");
System.out.println(x1.getName());
System.out.println(x2.getName());
너는 이 코드가 실행된 후에 어떤 결과가 나타날지 알아맞혀 보아라.의문의 해결
첫눈에 이 실현된 코드는 문제가 없다.X는 하나의 방법만 포함하는 인터페이스입니다. getName과 setName은 이미 기본적인 정의가 있지만, Runable 인터페이스의 run 방법은 정의가 없기 때문에 우리는 lambda 표현식을 통해 X의 실례를 생성하고 run 방법의 실현을 제공할 수 있습니다. 예를 들어makeX와 같습니다.따라서 이 프로그램이 실행된 후에 나타나는 결과는 다음과 같습니다.
x1
x2
getName 메서드의 호출을 삭제하면 실행 결과는 다음과 같습니다.
MyTest$1@30ae8764
MyTest$1@123acf34
이 두 줄은makeX 방법의 실행이 두 가지 다른 실례에서 나온 것을 보여 줍니다. 이때 현재 OpenJDK 8이 생성되었습니다. (여기에서 저는 OpenJDK 8 24.0-b07을 사용합니다.)어쨌든 현재의 OpenJDK 8은 최종 자바 8의 행동을 반영할 수 없습니다. 이 문제를 해결하기 위해서는 특수한 매개 변수인 XDlambdaToMethod를 사용하여javac 명령을 실행해야 합니다. 이 매개 변수를 사용한 후에 실행 결과는 다음과 같습니다.
x2
x2
getName 메서드가 호출되지 않으면 다음과 같이 표시됩니다.
MyTest$$Lambda$1@5506d4ea
MyTest$$Lambda$1@5506d4ea
모든 makeX 호출 방법은 같은 익명 내부 클래스에서 온 하나의 사례인 것 같습니다. 컴파일된 자바 클래스 파일이 포함된 디렉터리를 살펴보면 MyTestClass$Lambda$1이라는 이름이 없습니다.class 파일입니다.컴파일할 때 lambda 표현식은 완전한 번역을 거치지 않았기 때문에 사실상 이 번역 과정은 컴파일과 운행할 때 완성된 것이다.javac컴파일러는 lambda 표현식을 JVM에 추가된 지령인 invokedynamic(JSR292)로 바꾸었다.이 명령은 실행할 때 lambda 표현식을 실행하는 데 필요한 모든 메타데이터를 포함합니다.호출할 방법 이름, 입력 출력 형식, 그리고 bootstrap이라는 방법을 포함합니다.bootstrap 방법은 이 방법의 호출을 수신하는 실례를 정의하는 데 사용됩니다. JVM이 invokedynamic 명령을 실행하면 JVM은 특정한 bootstrap에서 lambda 메타팩토리 메타데이터(lambda metafactory method)를 호출합니다.
다시 아까 그 의문으로 돌아가서 lambda 표현식은 개인적인 정적 방법으로 바뀌었습니다. () -> {System.out.println ("X");}y Test로 이동:
private static void lambda$0() {
System.out.println("X");
}
자바 컴파일러를 사용하고 -private 파라미터를 사용하면 이 방법을 볼 수 있고, -c 파라미터를 사용하여 더욱 완전한 변환을 볼 수 있습니다.프로그램을 실행할 때, JVM은 invokedynamic 명령을 설명하기 위해 lambda metafactory method를 호출합니다.우리의 예에서makeX를 처음 호출할 때, lambda metafactory method는 X의 실례를 생성하고 동적 링크run 방법을lambda $0 방법으로 연결합니다.X의 실례는 다음에 메모리에 저장됩니다. 두 번째makeX를 호출할 때 메모리에서 이 실례를 읽습니다. 따라서 두 번째 호출의 실례는 첫 번째와 같습니다.
복구됐어요?해결책이 있습니까?
아직 이 문제의 직접적인 복구나 해결 방법은 없다.비록 Oracle의 Java 8 계획은 기본적으로 -XDlambdaToMethod 파라미터를 활성화할 계획이다. 이 파라미터는 JVM 규범의 일부분이 아니기 때문에 서로 다른 공급업체와 JVM의 실현은 다르다.lambda 표현식에 대해 말하자면, 당신이 유일하게 기대할 수 있는 것은 클래스에서 당신의 인터페이스를 실현하는 방법이다.
다른 방법
지금까지 Mixin에 대한 모방은 Java8을 호환할 수 없지만 다중 계승과 위임을 통해 기존 클래스에 여러 서비스를 추가할 수 있습니다.이 방법은 바로virtualfield pattern (가상 필드 모드) 입니다.
그래서 우리 스위치를 보러 왔어요.
interface Switchable { boolean isActive();
void setActive(boolean active);
}
우리는 Switchable 기반의 인터페이스를 필요로 하고 추가적인 추상적인 방법을 제공하여 Switchable의 실현을 되돌려 줄 수 있다.통합 방법은 Getter를 사용하여 Switchable로 변환하는 기본 정의를 포함합니다.
public interface SwitchableView extends Switchable {
Switchable getSwitchable();
boolean isActive() default { return getSwitchable().isActive(); }
void setActive(boolean active) default { getSwitchable().setActive(active); }
}
다음은 전체 Switchable 구현을 만듭니다.
public class SwitchableImpl implements Switchable {
private boolean active;
@Override
public boolean isActive() {
return active;
}
@Override
public void setActive(boolean active) {
this.active = active;
}
}
다음은 가상 필드 모드를 사용하는 예입니다.
public class Device {}
public class DeviceA extends Device implements SwitchableView {
private Switchable switchable = new SwitchableImpl();
@Override
public Switchable getSwitchable() {
return switchable;
}
}
public class DeviceB extends Device implements SwitchableView {
private Switchable switchable = new SwitchableImpl();
@Override
public Switchable getSwitchable() {
return switchable;
}
}
결론이 글에서 우리는 자바 8의 가상 확장 방법을 통해 여러 개의 서비스를 클래스로 추가하는 두 가지 방법을 사용했다.첫 번째 방법은 하나의 맵을 사용하여 실례 상태를 저장하는데 이 방법은 매우 위험하다. 스레드가 안전하지 않고 메모리 유출 문제가 존재하기 때문에 이것은 서로 다른 JVM이 자바 언어에 대한 실현에 전적으로 의존한다.또 다른 방법은 가상 필드 모드를 사용하여 추상적인 getter를 통해 최종 실현 실례를 되돌려주는 것이다.두 번째 방법은 더욱 독립적이고 안전하다.
가상 확장 방법은 자바의 새로운 특성이다. 본고는 주로 다중 계승의 실현을 소개한다. 상세한 연구와 다른 측면에 응용할 수 있으니 여러분과 공유하는 것을 잊지 마세요.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
JPA + QueryDSL 계층형 댓글, 대댓글 구현(2)이번엔 전편에 이어서 계층형 댓글, 대댓글을 다시 리팩토링해볼 예정이다. 이전 게시글에서는 계층형 댓글, 대댓글을 구현은 되었지만 N+1 문제가 있었다. 이번에는 그 N+1 문제를 해결해 볼 것이다. 위의 로직은 이...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.