자바에서 메소드 참조 시작하기

람다와 함께 Java SE 8은 Java 언어에 대한 메서드 참조를 가져 왔습니다. 이 자습서에서는 Java의 메서드 참조에 대한 간략한 개요를 제공 한 다음 Java 코드 예제와 함께 사용을 시작합니다. 튜토리얼이 끝나면 메서드 참조를 사용하여 클래스의 정적 메서드, 바인딩 및 언 바운드 비 정적 메서드 및 생성자를 참조하는 방법과이를 사용하여 수퍼 클래스 및 현재 클래스의 인스턴스 메서드를 참조하는 방법을 알게됩니다. 유형. 또한 많은 Java 개발자가 람다 식과 메서드 참조를 익명 클래스에 대한보다 깔끔하고 간단한 대안으로 채택한 이유를 이해할 수 있습니다.

이 자습서의 코드 예제는 JDK 12와 호환됩니다.

다운로드 코드 받기이 자습서에서 예제 응용 프로그램의 소스 코드를 다운로드합니다. JavaWorld를 위해 Jeff Friesen이 만들었습니다.

방법 참조 : 입문서

저의 이전 Java 101 자습서에서는 기능 인터페이스의 인스턴스로 처리 할 수있는 익명 메서드를 정의하는 데 사용되는 람다 식을 소개했습니다. 때때로 람다 식은 기존 메서드를 호출하는 것 이상을 수행하지 않습니다. 예를 들어, 다음 코드 조각은 람다를 사용 하여 람다의 단일 인수에 대한 System.outvoid println(s)메서드 를 호출합니다. s의 유형은 아직 알려지지 않았습니다.

(s) -> System.out.println(s)

람다는 (s)형식 매개 변수 목록과 System.out.println(s)표현식이 s의 값을 표준 출력 스트림에 인쇄 하는 코드 본문으로 표시 됩니다. 명시적인 인터페이스 유형이 없습니다. 대신 컴파일러는 인스턴스화 할 기능 인터페이스를 주변 컨텍스트에서 추론합니다. 예를 들어, 다음 코드 조각을 고려하십시오.

Consumer consumer = (s) -> System.out.println(s);

컴파일러는 이전 선언을 분석하고 java.util.function.Consumer미리 정의 된 기능 인터페이스의 void accept(T t)메서드가 람다의 형식 매개 변수 목록 ( (s)) 과 일치 하는지 확인합니다 . 또한 그 결정 accept()void반환 형식 일치 println()void반환 형식을. 람다 따라서되는 바인딩Consumer.

보다 구체적으로, 람다는 Consumer. 를 호출하도록 컴파일러 코드 생성 Consumervoid accept(String s)로 전달 스트링 인수 메소드 결과 s에 전달되는 System.outS ' void println(String s)방법. 이 호출은 다음과 같습니다.

consumer.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

키 입력을 저장하려면 람다를 기존 메서드에 대한 압축 참조 인 메서드 참조로 바꿀 수 있습니다 . 예를 들어 다음 코드 조각은 (String s) -> System.out.println(s)로 대체 됩니다 System.out::println. 여기서 의 메서드가 참조되고 ::있음을 나타냅니다 .System.outvoid println(String s)

Consumer consumer2 = System.out::println; // The method reference is shorter. consumer2.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

컴파일러는 Consumer이 매개 변수가있는 형식의 java.lang.String실제 형식 인수가에서 대체 T되고 void accept(T t)람다 본문의 System.out.println()메서드 호출 에있는 단일 매개 변수의 형식이기도 하므로 컴파일러가이 목록을 유추 할 수 있으므로 이전 메서드 참조에 대한 형식 매개 변수 목록을 지정할 필요가 없습니다 .

심도있는 메서드 참조

있어서 참조는 기존 방법에서 람다를 생성하는 신택 틱 단축된다. 구현 본문을 제공하는 대신 메서드 참조는 기존 클래스 또는 개체의 메서드를 참조합니다. 람다와 마찬가지로 메서드 참조에는 대상 유형이 필요합니다.

메서드 참조를 사용하여 클래스의 정적 메서드, 바인딩 및 바인딩 해제 된 비 정적 메서드, 생성자를 참조 할 수 있습니다. 메서드 참조를 사용하여 수퍼 클래스 및 현재 클래스 유형의 인스턴스 메서드를 참조 할 수도 있습니다. 이러한 각 메서드 참조 범주를 소개하고 작은 데모에서 어떻게 사용되는지 보여 드리겠습니다.

메서드 참조에 대해 자세히 알아보기

이 섹션을 읽은 후 바운드 및 언 바운드 비 정적 메서드 컨텍스트의 메서드 참조에 대한 자세한 내용은 Java 8의 메서드 참조 (Toby Weston, 2014 년 2 월)를 확인하십시오.

정적 메서드에 대한 참조

정적 메소드 참조는 특정 클래스의 정적 메소드를 말한다. 구문은 이며 여기서 클래스를 식별하고 정적 메서드를 식별합니다. 예는 입니다. 목록 1은 정적 메소드 참조를 보여줍니다.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

목록 1. MRDemo.java (버전 1)

import java.util.Arrays; import java.util.function.Consumer; public class MRDemo { public static void main(String[] args) { int[] array = { 10, 2, 19, 5, 17 }; Consumer consumer = Arrays::sort; consumer.accept(array); for (int i = 0; i < array.length; i++) System.out.println(array[i]); System.out.println(); int[] array2 = { 19, 5, 14, 3, 21, 4 }; Consumer consumer2 = (a) -> Arrays.sort(a); consumer2.accept(array2); for (int i = 0; i < array2.length; i++) System.out.println(array2[i]); } }

목록 1의 main()메서드는 java.util.Arrays클래스의 static void sort(int[] a)메서드 를 통해 한 쌍의 정수 배열을 정렬하며 , 이는 정적 메서드 참조 및 동등한 람다 식 컨텍스트에 나타납니다. 배열을 정렬 한 후 for루프는 정렬 된 배열의 내용을 표준 출력 스트림에 인쇄합니다.

메서드 참조 또는 람다를 사용하려면 먼저 기능 인터페이스에 바인딩해야합니다. Consumer메서드 참조 / 람다 요구 사항을 충족 하는 미리 정의 된 기능 인터페이스를 사용하고 있습니다. 정렬 작업은 정렬 할 배열을 Consumeraccept()메서드 에 전달하여 시작됩니다 .

목록 1 ( javac MRDemo.java)을 컴파일 하고 애플리케이션 ( java MRDemo)을 실행합니다 . 다음 출력을 볼 수 있습니다.

2 5 10 17 19 3 4 5 14 19 21

바인딩 된 비 정적 메서드에 대한 참조

결합 비 정적 메소드 참조 (A)에 바인딩 된 비 정적 방식을 말한다 수신기 객체. 구문은입니다 . 여기서 수신자를 식별하고 인스턴스 메소드를 식별합니다. 예는 입니다. 목록 2는 바인딩 된 비 정적 메서드 참조를 보여줍니다.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

목록 2. MRDemo.java (버전 2)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { String s = "The quick brown fox jumped over the lazy dog"; print(s::length); print(() -> s.length()); print(new Supplier() { @Override public Integer get() { return s.length(); // closes over s } }); } public static void print(Supplier supplier) { System.out.println(supplier.get()); } }

Listing 2의 main()메소드는 문자열을 String변수에 할당 한 s다음 print()이 문자열의 길이를이 메소드의 인수로 가져 오는 기능이 있는 클래스 메소드 를 호출합니다 . print()(방법 참조 호출 s::length-가 length()결합된다 s), λ 당량과 동등한 익명 클래스 컨텍스트.

나는 정의한 print()사용에 java.util.function.Supplier누구의 미리 정의 된 기능 인터페이스 get()방법 결과의 공급 업체를 반환합니다. 이 경우에 Supplier전달 된 인스턴스는 반환 print()get()메서드를 구현합니다 s.length(). print()이 길이를 출력합니다.

s::length종료되는 클로저를 도입합니다 s. 람다 예제에서 더 명확하게 볼 수 있습니다. 람다에는 인수가 없으므로의 값은 s바깥 쪽 범위에서만 사용할 수 있습니다. 따라서 람다 본문은 s. 익명의 클래스 예제는 이것을 더 명확하게합니다.

목록 2를 컴파일하고 애플리케이션을 실행하십시오. 다음 출력을 볼 수 있습니다.

44 44 44

바인딩되지 않은 비 정적 메서드에 대한 참조

바운드 비 정적 메소드 레퍼런스 수신기 객체에 바인딩있어 비 정적 방식을 말한다. 구문은이며 , 여기서 인스턴스 메서드를 선언하고 인스턴스 메서드를 식별하는 클래스를 식별합니다. 예는 입니다.className::instanceMethodNameclassNameinstanceMethodNameString::toLowerCase

String::toLowerCase클래스 의 비 정적 String toLowerCase()메서드 를 식별하는 바인딩되지 않은 비 정적 메서드 참조입니다 String. 그러나 비 정적 메소드에는 여전히 수신자 객체 (이 예 에서는 메소드 참조를 통해 String호출하는 데 사용되는 toLowerCase()객체)가 필요하기 때문에 수신자 객체는 가상 머신에 의해 생성됩니다. toLowerCase()이 개체에서 호출됩니다. 수신자 개체 인 String::toLowerCase단일 String인수를 사용하고 String결과를 반환 하는 메서드를 지정합니다 . String::toLowerCase()lambda와 동일합니다 (String s) -> { return s.toLowerCase(); }.

목록 3은이 언 바운드 비 정적 메서드 참조를 보여줍니다.

목록 3. MRDemo.java (버전 3)

import java.util.function.Function; public class MRDemo { public static void main(String[] args) { print(String::toLowerCase, "STRING TO LOWERCASE"); print(s -> s.toLowerCase(), "STRING TO LOWERCASE"); print(new Function() { @Override public String apply(String s) // receives argument in parameter s; { // doesn't need to close over s return s.toLowerCase(); } }, "STRING TO LOWERCASE"); } public static void print(Function function, String s) { System.out.println(function.apply(s)); } }

Listing 3's main() method invokes the print() class method with functionality to convert a string to lowercase and the string to be converted as the method's arguments. print() is invoked in method reference (String::toLowerCase, where toLowerCase() isn't bound to a user-specified object) and equivalent lambda and anonymous class contexts.

I've defined print() to use the java.util.function.Function predefined functional interface, which represents a function that accepts one argument and produces a result. In this case, the Function instance passed to print() implements its R apply(T t) method to return s.toLowerCase(); print() outputs this string.

Although the String part of String::toLowerCase makes it look like a class is being referenced, only an instance of this class is referenced. The anonymous class example makes this more obvious. Note that in the anonymous class example the lambda receives an argument; it doesn't close over parameter s (i.e., it's not a closure).

Compile Listing 3 and run the application. You'll observe the following output:

string to lowercase string to lowercase string to lowercase

References to constructors

You can use a method reference to refer to a constructor without instantiating the named class. This kind of method reference is known as a constructor reference. Its syntax is className::new. className must support object creation; it cannot name an abstract class or interface. Keyword new names the referenced constructor. Here are some examples:

  • Character::new: equivalent to lambda (Character ch) -> new Character(ch)
  • Long::new: equivalent to lambda (long value) -> new Long(value) or (String s) -> new Long(s)
  • ArrayList::new: equivalent to lambda () -> new ArrayList()
  • float[]::new: equivalent to lambda (int size) -> new float[size]

The last constructor reference example specifies an array type instead of a class type, but the principle is the same. The example demonstrates an array constructor reference to the "constructor" of an array type.

To create a constructor reference, specify new without a constructor. When a class such as java.lang.Long declares multiple constructors, the compiler compares the functional interface's type against all of the constructors and chooses the best match. Listing 4 demonstrates a constructor reference.

Listing 4. MRDemo.java (version 4)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { Supplier supplier = MRDemo::new; System.out.println(supplier.get()); } }

Listing 4's MRDemo::new constructor reference is equivalent to lambda () -> new MRDemo(). Expression supplier.get() executes this lambda, which invokes MRDemo's default no-argument constructor and returns the MRDemo object, which is passed to System.out.println(). This method converts the object to a string, which it prints.

Now suppose you have a class with a no-argument constructor and a constructor that takes an argument, and you want to call the constructor that takes an argument. You can accomplish this task by choosing a different functional interface, such as the predefined Function interface shown in Listing 5.

Listing 5. MRDemo.java (version 5)

import java.util.function.Function; public class MRDemo { private String name; MRDemo() { name = ""; } MRDemo(String name) { this.name = name; System.out.printf("MRDemo(String name) called with %s%n", name); } public static void main(String[] args) { Function function = MRDemo::new; System.out.println(function.apply("some name")); } }

Function function = MRDemo::new;의 메서드에는 단일 (이 컨텍스트에서) 인수가 필요 String하기 때문에 컴파일러가 인수 를받는 생성자를 찾도록합니다 . 실행 하면에 전달됩니다 .Functionapply()Stringfunction.apply("some name")"some name"MRDemo(String name)