람다 함수란?
람다 함수는 익명 함수(Anonymous functions)를 지칭하는 용어로 함수를 보다 단순하게 표현한다.
함수를 하나의 식(expression)으로 표현한 것으로 함수를 람다식으로 표현하면 메소드의 이름이 없기 때문에 익명 함수(Anonymous Function)의 한 종류라고 볼 수 있다.
! 람다 식의 도입으로 익명 클래스를 생략할 수 있게 하여 boilerplate code를 크게 줄이고 가독성을 향상시켰다.
** 익명 함수(Anonymous functions) : 이름이 없는 함수로, 익명 함수들은 모두 일급 객체이다.
** 일급 객체 : 일급 객체인 함수는 변수처럼 사용이 가능하며 매개변수로 전달이 가능하다.
람다식으로 선언된 함수는 일급 객체이기 때문에 Stream API의 매개변수로 전달이 가능하다. (아래 예제 확인)
람다의 특징
익명 함수(Anonymous functions) : 람다 대수는 이름을 가질 필요가 없다. 익명 함수들은 공통적으로 1급 시민(First Class Citizen)이라는 특징을 가진다.
커링 (Curring) : 여러 개의 매개변수를 가진 함수를 한개의 매개변수를 가진 여러 함수의 연결로 나타내는 방법이다.
1급 개체(First class citizen)?
3가지 조건을 충족한다. 1) 변수나 데이터에 할당할 수 있어야 한다.
2) 객체의 인자로 넘길 수 있어야 한다.
3) 객체의 리턴값으로 리턴할 수 있어야 한다.
람다의 장단점
장점
- 코드 간결성 : 람다를 사용하면 불필요한 반복문의 삭제가 가능하여 복잡한 식을 단순하게 표현할 수 있다.
- 지연연산 수행 : 람다는 지연연산을 수행함으로써 불필요한 연산을 최소화할 수 있다.
- 병렬처리 : 멀티스레드를 사용하여 병렬처리를 할 수 있다.
단점
- 람다식 사용이 까다롭다.
- 람다 stream 에서 단순 for/while문 사용 시 성능이 떨어진다.
- 불필요하게 많이 사용하게 되면 오히려 가독성을 떨어뜨릴 수 있다.
예제
기존 자바 문법
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world!");
}
}).start();
람다 문법
new Thread(()->{
System.out.println("Hello world!");
}).start();
함수형 인터페이스
람다식으로 순수 함수를 선언할 수 있게 되었지만 Java는 기본적으로 객체지향 언어익기 때문에 순수 함수와 일반 함수를 다르게 취급하고 있다. Java에서는 이를 구분하기 위해 함수형 인터페이스가 등장했다.
함수형 인터페이스란 함수를 1급 객체처럼 다룰 수 있는 어노테이션으로 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할을 한다.
함수형 인터페이스를 사용하는 이유는 Java의 람다식이 함수형 인터페이스를 반환하기 때문이다.
@FunctionalInterface
Functional Interface는 '구현해야 하는 추상 메소드가 하나만 정의된 인터페이스'를 의미한다.
자바 컴파일러는 @FunctionalInterface annotation이 붙은 함수형 인터페이스에 두 개 이상의 메소드가 선언되면 오류를 발생시킨다.
함수형 인터페이스를 사용하면 함수를 변수처럼 선언할 수 있다.
예제)
함수형 Interface 선언
@FunctionalInterface
public interface Math {
public int Calc(int first, int second);
}
추상 메소드 구현 및 함수형 인터페이스 사용
public static void main(String[] args){
Math plusLambda = (first, second) -> first + second;
System.out.println(plusLambda.Calc(4, 2));
Math minusLambda = (first, second) -> first - second;
System.out.println(minusLambda.Calc(4, 2));
}
실행 결과
6
2
Java에서 제공하는 함수형 인터페이스
- Supplier<T>
- Consumer<T>
- Function<T,R>
- Predicate<T>
- BiFunction<T,U,R>
간단하게 설명하면,
1) Supplier<T>
인자를 받지 않고 Type T 객체를 리턴하는 함수형 인터페이스이다.
// 정의
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 사용 예시
Supplier<LocalDateTime> s = () -> LocalDateTime.now();
LocalDateTime time = s.get();
System.out.println(time);
2) Consumer<T>
객체 T를 매개변수로 받아서 사용하며, 반환값은 없는 함수형 인터페이스이다.
// 정의
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
// 예시
import java.util.function.Consumer;
public class Java8Consumer1 {
public static void main(String[] args) {
Consumer<String> print = x -> System.out.println(x);
print.accept("java"); // java
}
}
3) Function<T,R>
인수(타입 T의 객체)를 사용하고 객체(타입 R의 객체)를 반환한다. 인수와 출력은 다른 유형일 수 있다.
- T – Type of the input to the function.
- R – Type of the result of the function.
// 정의
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
// 예시
import java.util.function.Function;
public class JavaMoney {
public static void main(String[] args) {
Function<String, Integer> func = x -> x.length();
Integer apply = func.apply("hello"); // 5
System.out.println(apply);
}
}
4) Predicate<T>
객체 T를 매개변수로 받아 처리한 후 Boolean을 반환한다.
보통 객체들의 모음을 위한 필터에 적용된다. filter()는 매개변수로 predicate를 받는다.
// 정의
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
// 예시
// Predicate in filter()
public class Java8Predicate {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> collect = list.stream().filter(x -> x > 5).collect(Collectors.toList());
System.out.println(collect); // [6, 7, 8, 9, 10]
}
}
// 결과
[6, 7, 8, 9, 10]
5) BiFunction<T,U,R>
2개의 매개변수를 받고 객체를 반환한다.
- T – Type of the first argument to the function.
- U – Type of the second argument to the function.
- R – Type of the result of the function.
// 정의
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
// 사용
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
public class Java8BiFunction1 {
public static void main(String[] args) {
// takes two Integers and return an Integer
BiFunction<Integer, Integer, Integer> func = (x1, x2) -> x1 + x2;
Integer result = func.apply(2, 3);
System.out.println(result); // 5
// take two Integers and return an Double
BiFunction<Integer, Integer, Double> func2 = (x1, x2) -> Math.pow(x1, x2);
Double result2 = func2.apply(2, 4);
System.out.println(result2); // 16.0
// take two Integers and return a List<Integer>
BiFunction<Integer, Integer, List<Integer>> func3 = (x1, x2) -> Arrays.asList(x1 + x2);
List<Integer> result3 = func3.apply(2, 3);
System.out.println(result3);
}
}
// 결과
5
16.0
[5]
자바 스트림(Stream)
자바 스트림(stream)은 Java 8부터 지원하기 시작한 기능으로 컬렉션에 저장되어 있는 element들을 하나씩 순회하면서 처리할 수 있는 코드패턴이다.
람다식과 함께 사용되어 컬렉션에 들어있는 데이터에 대한 처리를 간결하게 작성할 수 있다.
또한 내부 반복자를 사용하기 때문에 병렬처리가 쉽다.
이전에는 컬렉션의 엘리멘트들을 순회하기 위해 Iterator 객체나 foreach문을 사용했다.
Java 8부터는 스트림을 사용하여 조금 단순하게 코드를 작성할 수 있다.
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
list.stream()
.filter("b"::equals)
.forEach(System.out::print);
스트림 생성
//// 컬렉션
List<String> list = Arrays.asList("a","b","c");
Stream<String> steam = list.steam();
//// 배열
String[] array = new Sting[]{"a","b","c"};
Stream<String> stream1 = Arrays.stream(array);
// 인덱스 1 포함, 3 제외 -> "b","c"
Stream<String> stream2 = Arrays.stream(array,1,3);
//// builder
Stream.Builder<String> builder = Stream.builder();
// Adding elements in the stream of Strings
Stream<String> stream = builder.add("Geeks")
.add("for")
.add("Geeks")
.add("GeeksQuiz")
.build();
// Displaying the elements in the stream
stream.forEach(System.out::println);
//// Generate
// using Stream.generate() method
// to generate 5 random Integer values
Stream.generate(new Random()::nextInt)
.limit(5).forEach(System.out::println);
이 외에도 Iterator, Empty, 기본 타입(Primitive type)으로 생성할 수 있다.
스트림 데이터 가공
//// Filter
List<Customer> customersWithMoreThan100Points = customers
.stream()
.filter(c -> c.getPoints() > 100)
.collect(Collectors.toList());
// we added the 'hasOverHundrredPoints' method to our Customer class
List<Customer> customersWithMoreThan100Points = customers
.stream()
.filter(Customer::hasOverHundredPoints)
.collect(Collectors.toList());
map()은 스트림에서 나오는 데이터를 변환한다.
map() 메소드는 값을 변환해주는 람다식을 인자로 받아서 새로운 데이터를 생성한다.
//// Map
List<Staff> staff = Arrays.asList(
new Staff("mkyong", 30, new BigDecimal(10000)),
new Staff("jack", 27, new BigDecimal(20000)),
new Staff("lawrence", 33, new BigDecimal(30000))
);
//Java 8
List<String> collect = staff.stream().map(x -> x.getName()).collect(Collectors.toList());
System.out.println(collect); //[mkyong, jack, lawrence]
이 외에도 flatMap, Sorted, Peek 등이 있다.
Java 8 method references, double colon (::) operator (메서드 참조)
double colon (::) operator는 method references 라 불린다.
Method references(메서드 참조)는 람다 식의 특수한 유형으로 간단한 람다 식을 만드는데 자주 사용된다.
Anonymous class to print a list
List<String> list = Arrays.asList("node", "java", "python", "ruby");
list.forEach(new Consumer<String>() { // anonymous class
@Override
public void accept(String str) {
System.out.println(str);
}
});
Anonymous class -> Lambda expressions
List<String> list = Arrays.asList("node", "java", "python", "ruby");
list.forEach(str -> System.out.println(str)); // lambda
Lambda expressions -> Method references
List<String> list = Arrays.asList("node", "java", "python", "ruby");
list.forEach(System.out::println); // method references
람다 식 또는 메서드 참조는 모두 기존 메서드를 호출하는 다른 방법일 뿐이다. 메소드 참조를 사용하면 더 나은 가독성을 얻을 수 있다.
method references 4가지
- Reference to a static method
ClassName::staticMethodName - Reference to an instance method of a particular object
object::instanceMethodName - Reference to an instance method of an arbitrary object of a particular type --> 예제로 확인!
ContainingType::methodName - Reference to a constructor
ClassName::new
1. Reference to a static method
// Lambda expression
(args) -> ClassName.staticMethodName(args)
// Method Reference
ClassName::staticMethodName
예제)
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Java8MethodReference1a {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C");
// anonymous class
list.forEach(new Consumer<String>() {
@Override
public void accept(String x) {
SimplePrinter.print(x);
}
});
// lambda expression
list.forEach(x -> SimplePrinter.print(x));
// method reference
list.forEach(SimplePrinter::print);
}
}
class SimplePrinter {
public static void print(String str) {
System.out.println(str);
}
}
2. Reference to an instance method of a particular object
instance의 메소드 호출
// Lambda expression
(args) -> object.instanceMethodName(args)
// Method Reference
object::instanceMethodName
예제)
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
public class Java8MethodReference2 {
public static void main(String[] args) {
List<Employee> list = Arrays.asList(
new Employee("mkyong", 38, BigDecimal.valueOf(3800)),
new Employee("sunrise", 5, BigDecimal.valueOf(100)),
new Employee("min", 25, BigDecimal.valueOf(2500)),
new Employee("unknown", 99, BigDecimal.valueOf(9999)));
// anonymous class
/*list.sort(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return provider.compareBySalary(o1, o2);
}
});*/
ComparatorProvider provider = new ComparatorProvider();
// lambda
// list.sort((o1, o2) -> provider.compareBySalary(o1, o2));
// method reference
list.sort(provider::compareBySalary);
list.forEach(x -> System.out.println(x));
}
}
class ComparatorProvider {
public int compareByAge(Employee o1, Employee o2) {
return o1.getAge().compareTo(o2.getAge());
}
public int compareByName(Employee o1, Employee o2) {
return o1.getName().compareTo(o2.getName());
}
public int compareBySalary(Employee o1, Employee o2) {
return o1.getAge().compareTo(o2.getAge());
}
}
3. Reference to an instance method of an arbitrary object of a particular type
위 문장으로 정리하기엔 모호한 느낌이 있다. 아래 예제로 알아보자.
**java arbitary number of arguments
int sum(int ... values){...};
//// Lambda expression
// example, assume a and b are String
(a, b) -> a.compareToIgnoreCase(b)
//// Method Reference
// example, a is type of String
String::compareToIgnoreCase
(String a, String b)에서 a와 b는 arbitrary name(임의 이름)이며 String은 arbitrary type(임의 유형)이다.
String[] stringArray = { "Barbara", "James", "Mary", "John",
"Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
// Arrays.sort method
public static <T> void sort(T[] a, Comparator<? super T> c) {
}
매개변수 타입의 instance method를 참조한다고 생각한다..
(String a, String b) -> a.compareToIgnoreCase(b) // return int
// a is type of String
// method reference
String::compareToIgnoreCase
첫 번째 매개변수(f)의 타입이 InvoiceCalculator이다.
따라서 우리는 arbitrary object(f)의 인스턴스 메소드(normal of promotion)을 참조할 수 있다.
* InvoiceCalculator class는 normal, promotion method를 가진다.
(f, o) -> f.normal(o))
(f, o) -> f.promotion(o))
InvoiceCalculator::normal
InvoiceCalculator::promotion
참고 : double colon (::) operator - mkyong
4. Reference to a constructor
//// Lambda expression
(args) -> new ClassName(args)
//// Method Reference
ClassName::new
예제) 생성자를 참조할 때 ClassName::new 를 사용한다.
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class Java8MethodReference4a {
public static void main(String[] args) {
// lambda
Supplier<Map> obj1 = () -> new HashMap(); // default HashMap() constructor
Map map1 = obj1.get();
// method reference
Supplier<Map> obj2 = HashMap::new;
Map map2 = obj2.get();
// lambda
Supplier<Invoice> obj3 = () -> new Invoice(); // default Invoice() constructor
Invoice invoice1 = obj3.get();
// method reference
Supplier<Invoice> obj4 = Invoice::new;
Invoice invoice2 = obj4.get();
}
}
class Invoice {
String no;
BigDecimal unitPrice;
Integer qty;
public Invoice() {
}
//... generated by IDE
}
References
https://mkyong.com/java8/java-8-streams-map-examples/
https://mkyong.com/java8/java-8-method-references-double-colon-operator/
https://mkyong.com/java8/java-8-consumer-examples/ ( 외 다수의 예제 페이지. 추천하는 사이트 )
'프로그래밍 언어' 카테고리의 다른 글
Python Numpy - Array 1D, 2D, 3D (0) | 2023.01.22 |
---|---|
Python NumPy (1) Array, Indexing, Sorting (0) | 2023.01.22 |
ArrayList와 LinkedList의 차이 / Stringbuilder (0) | 2022.08.14 |
자바 HashMap의 구조 / HashTable / LinkedHashMap (0) | 2022.08.14 |
자바 동기화 | Synchronized(Monitor), Atomic Type (1) | 2022.08.14 |
댓글