들어가며
오늘은 자바에서의 참조에 대해서 알아보겠습니다.
위 내용을 이해하기 위해서는 Java 람다식에 대한 선수지식이 조금 있으면 좋다고 생각합니다.
람다식은 JDK8 에 처음으로 나왔고, 람다 문법을 사용하는 개발자분들도 현재 많이 있을거라고 생각합니다.
여러분들 중에서 람다 문법을 쓰다보면 가끔 알맞게 람다 문법을 잘 사용했는데
자꾸 문법에 줄이 그어지는 상황을 보신적이 있을 것 입니다.
그리고 그 메시지를 확인해보면 이런 문구가 있습니다.
왜그럴까? 하고 Replace lambda 를 누르면
문법이 좀 신기한 모양으로 바뀌고, 간결해 지는 것을 볼 수 있습니다. 그리고 오류도 당연히 생기지 않죠
실제로 실행일 시켜봐도 같은 동작을 하는 것을 볼 수 있습니다.
한번 간단한 예제로 보겠습니다.
두개의 값을 받아 큰 수를 리턴하는 예제 입니다.
(left, right) -> Math.max(left,right);
->
Math :: max;
위 첫번째 식이 어떻게 아래 처럼 간결하게 될 수 있었을까요?
바로 메소드 참조 때문 입니다.
일단 제 경험을 살려보면, 보통 스트림 문법을 람다랑 함께 사용하는 상황에서 코드 간결을 위해서 IDE 추천을 받아 참조를
사용했습니다. 다른 상황에서는 아직 사용해본적은 없습니다.
항상 IDE가 위 문법을 추천하길래 궁금해서 직접 공부를 해보게되었습니다.
이제 본론으로 들어가보겠습니다.
참조에는 종류가 대표적으로 2가지 있습니다
1) 메소드 참조
1-1) 매개변수의 메소드 참조
2) 생성자 참조
위 참조안에서도 또 여러가지 참조가 있습니다.
이 글에서는 대표적인 참조인 위 2가지 참조에 대한 내용을 담겠습니다.
메소드 참조
첫번째로 메소드 참조에서 정적 메소드와, 인스턴스 메소드 참조 에대한 내용을 다뤄보겠습니다
정적 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름을 기술합니다.
클래스 :: 메소드
인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 기술합니다.
참조변수 :: 메소드
다음 예제를 통하여 전체적인 흐름을 보고 이해를 도와보겠습니다.
@FunctionalInterface
public interface Calcuable {
double calc(double x, double y);
}
public class Person {
void action(Calcuable calcuable) {
double result = calcuable.calc(10,4);
System.out.println("result: " + result);
}
}
public class Computer {
public static double staticMethod(double x, double y) {
return x + y;
}
public double instanceMethod(double x, double y) {
return x*y;
}
}
public class MethodReferEx {
public static void main(String[] args) {
Person person = new Person();
//정적 메소드 -> static
//람다식
person.action((x,y) -> Computer.staticMethod(x,y));
//정적 메소드 -> static
//정적 메소드 참조
person.action(Computer ::staticMethod);
// 인스턴스 생성
Computer com = new Computer();
//인스턴스 메소드
//람다식
person.action((x,y) -> x * y);
//인스턴스 메소드
//인스턴스 메소드 참조
person.action(com ::instanceMethod);
}
}
----- 결과 -----
result: 14.0
result: 14.0
result: 40.0
result: 40.0
마지막 메인 메소드를 보면은 람다식 과 메소드 참조 에 대한 차이를 크게 느낄 수 있습니다.
결과값은 보시다 시피 같게 나옵니다.
두가지의 차이점은 확실히 간결하다는 점에서 명확한 차이가 있습니다.
하지만 단점은 처음 보는 사람은 어? 이게 뭐지? 라고 생각을 합니다.
못보던 것이니, 어려워 할 수도 있구요
저또한 그랬습니다.
그러면 이 예제를 보면 저 메소드 안에 있는 내용만 딱보고 어떻게 이해를 하지 라고 생각을 할 수 있습니다.
간단하게 생각하시면 다들 알다시피 배열이나, List를 출력할 때 향상된 for문을 사용하시는 거 알고 있을거라고 생각합니다.
그 문법이랑 비슷한 느낌이라고 생각하며 공부했습니다. (같다고 생각하시면 절대 절대 안됩니다........)
즉 ( 참조를 주는 객체 :: 객체안의 메소드) 이런 느낌으로 공부했습니다.
왼쪽은 어디서 가져다 쓸것인지, 오른쪽은 무엇을 가져다 쓸것인지 라고 생각하시면 이해에 도움이 될 것같습니다.
왜 ( :: ) 을 쓰냐고 물어보시면은, 해답은 없습니다ㅠㅠ
JDK 업그레이드가 되면서 기존 Java 개발 방식이 업그레이드가 되가고 개발자들한테 편하게 맞춰지고 있다고 생각이 듭니다.
그러므로 우리는 그냥 업그레이드된거에 적응하는게 좋다고 생각합니다.
어차피 기존에 방식이 Deprecated 되는일은 없을 것이므로, 그냥 본인 편한것 쓰는게 좋긴 할 것 입니다.
매개변수의 메소드 참조
예시를 들어보겠습니다.
다음과 같이 람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용하는 경우가 있습니다
(a,b) -> {
a.instanceMethod(b);
}
위 람다식을 간편하게 메소드 참조로 표현하면은
클래스 :: instanceMethod
한편 예제로 보겠습니다
@FunctionalInterface
public interface Comparable {
int compare(String a, String b);
}
public class Person {
public void order(Comparable comparable) {
String a = "진";
String b = "현규";
int result = comparable.compare(a, b);
if(result<0) {
System.out.println(a + "은" + b + " 보다 앞에 온다");
} else {
System.out.println(a + "은" + b + " 보다 뒤에 온다");
}
}
}
public class MethodRef {
public static void main(String[] args) {
Person person = new Person();
person.order( (a,b) -> a.compareToIgnoreCase(b));
// 메소드 참조
person.order(String::compareToIgnoreCase);
}
}
--결과--
진은현규 보다 앞에 온다
진은현규 보다 앞에 온다
위의 결과를 나타 냅니다.
확실히 간결해 진거는 아시겠지만, 적절한 상황에 잘 사용할 수 있어야 합니다ㅠㅠ아직은 저도 저게 어렵지만, 노력을 해봐야겠죠..?
생성자 참조
생성자를 참조한다는 것은 객체를 생성하는 것을 의미합니다.
아마도 스프링환경에서 CRUD를 할 때 Stream 문법을 사용해서 객체 변환을 할 때 사용할 것 입니다.
(a,b) -> {
return new 클래스(a,b);
}
위 코드는 람다식을 통하여 객체를 생성하고 리턴하는 것 입니다.
위 코드를 참조로 개선을 해보면
클래스 :: new
위 처럼 변환한다. 뭔가 이상하지 않나요? 이렇게 짧아진게.. 그냥 아직 적응이 덜 된것입니다
실무에서 생성자 오버로딩을 사용하여 여러개의 생성자가 있을 경우, 컴파일러는 함수형 인터페이스의 추상 메소드와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 내부에서 찾아 실행합니다.
만약 해당 생성자가 존재하지 않는다면, 컴파일 오류가 발생하게 됩니다.
다음 예제 에서는 두가지 방법으로 객체를 생성한 후 진행해 보겠습니다
@FunctionalInterface
public interface Create1 {
public Member create(String id);
}
@FunctionalInterface
public interface Create2 {
public Member create(String id, String name);
}
public class Member {
private String id;
private String name;
public Member(String id) {
this.id = id;
}
public Member(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Member [id=" + id + ", name=" + name + "]";
}
}
public class Person {
public Member getMember1(Create1 create1) {
String id = "abc";
Member member = create1.create(id);
return member;
}
public Member getMember2(Create2 create2) {
String id = "abc";
String name = "def";
Member member = create2.create(id, name);
return member;
}
}
public class ConstructorRef {
public static void main(String[] args) {
Person person = new Person();
Member m1 = person.getMember1(Member :: new);
System.out.println(m1);
System.out.println();
Member m2 = person.getMember2(Member :: new);
System.out.println(m2);
System.out.println();
}
}
--결과--
Member [id=abc, name=null]
Member [id=abc, name=def]
생성자 참조는 두 가지 방법 모두 동일하지만, 함수형 인터페이스의 매개변수 개수에 따라 실행되는 Member 생성자가 다르다는 것을 확인할 수 있습니다
이상 자바에서 메소드,생성자 참조에 대해서 알아봤습니다. 감사합니다
참조
1) 이것이 자바다(16.5~16.6)