진도표 3일차와 연결됩니다
우리는
JdbcTemplate
을 사용하는 과정에서익명 클래스
와람다식
이라는 자바 문법을 사용했습니다.
익명 클래스
는 자바의 초창기부터 있던 기능이고,람다식
은 자바 8에서 등장한 기능입니다.
다음 키워드를 사용해 몇 가지 블로그 글을 찾아보세요! 아래 질문을 생각하며 공부해보면 좋습니다! 😊
[키워드]
익명 클래스 / 람다 / 함수형 프로그래밍 /@FunctionalInterface
/ 스트림 API / 메소드 레퍼런스
[질문]
- 자바의 람다식은 왜 등장했을까?
- 람다식과 익명 클래스는 어떤 관계가 있을까? - 람다식의 문법은 어떻게 될까?
Functional Programming is programming without assignment satements
함수형 프로그래밍은 대입문이 없는 프로그래밍이다.
- Rober C.Martin -
함수형 프로그래밍이란 함수를 일급 객체로 취급하고 이러한 함수들 간의 조합으로 프로그램을 구성하는 프로그래밍 방법론을 의미한다.
아래서 다룰 람다식은 이러한 함수형 프로그래밍을 지원하기 위한 문법 중 하나로, 함수를 간결하고 효율적으로 표현할 수 있는 문법적 표현을 말한다. 자바에서는 메서드를 간결하고 효율적으로 표시하기 위한 문법이 된다.
일급 객체는 프로그래밍 언어론의 개념으로서,
우리가 많이 사용하는 언어 중Javascript
,Python
이 일급 객체 언어이고,c
,c++
이 아니라고 볼 수 있다.
(Java는 람다로 지원)
일급 객체란 무슨 특별한 혜택을 받는다는 뜻이 아니라, 사용할 때 다른 요소들과 아무런 차별이 없다는 것을 뜻한다.
보통 일급 객체는 아래 3가지 조건을 충족한 객체를 일컫는다.
모든 일급 객체는 변수나 데이터에 담을 수 있어야 한다.
public class Main {
public static void hello(){
System.out.println("print Hello!");
}
public static void main(String[] args) {
Object sayHello = hello();//에러발생
}
}
const printHello = function hello(){
console.log("Hello runners!")
}
console.log(printHello)
모든 일급 객체는 함수의 파라미터로 전달 할 수 있어야 한다.
public class Main2 {
public static void hello(){
System.out.println("print Hello!");
}
public static void print(Object My_function){
My_function();
}
public static void main(String[] args) {
print((Object) hello()); //print 메서드에 hello 메서드를 전달할 수 없다.
}
}
const hello = function (){
console.log("print Hello!")
}
function print (My_function){
My_function();
}
print(hello);
//print Hello!
모든 일급 객체는 함수의 리턴값으로 사용 할 수 있어야 한다.
자바의 메서드는 리턴값으로 메서드를 반환할 수 없다.
반면 자바스크립트는 가능하다.
const hello = function(){
console.log("warmingUp Club")
return function(){
console.log("Hello runners!")
}
}
const printHello = hello();
printHello();
이러한 관점에서 자바의
람다식
혹은익명 클래스
는 변수나 매개변수에 사용할 수 있고, 리턴값으로도 사용할 수 있기에 일급 객체의 요건을 충족한다.
익명 클래스
를 이용하여 익명 구현 객체를 사용할 수 있었다.
모든 일급 객체는 변수나 데이터에 담을 수 있어야 한다.
public class Lamda {
public static void main(String[] args) {
Consumer<String> printMessage = (AnyString) -> System.out.println(AnyString);
printMessage.accept("Hello WarmingUp Club!");
}
}
⚠️ Consumer는 뭘까?
자바가 제공하는 대표적인 FunctionalInterface중에 하나로, 자세한 내용은 아래에서 다룰 예정이다.
모든 일급 객체는 함수의 파라미터로 전달 할 수 있어야 한다.
public class Lamda2 {
public static void print(Consumer<String> anyMessage, String str){
anyMessage.accept(str);
}
public static void main(String[] args) {
print((inflearn) -> System.out.println(inflearn), "Hello, inflearn");
}
}
모든 일급 객체는 함수의 리턴값으로 사용 할 수 있어야 한다.
public class Lamda3 {
public static Consumer<String> printAnyMessage(){
return System.out::println; //메서드 레퍼런스
}
public static void main(String[] args) {
Consumer<String> MyMessage = printAnyMessage();
MyMessage.accept("Hello! runners!");
}
}
메서드 레퍼런스란?
메서드 레퍼런스는 실행하려는 람다식의 메서드를 참조해서 파라미터의 정보, 반환 타입을 추론한 뒤
람다식에서 선언이 불필요한 부분을 제거하고 람다식을 더 간결하게 표현해 주는 문법이다.public class Lamda3 { public static Consumer<String> printAnyMessage(){ return message -> System.out.println(message); } public static void main(String[] args) { Consumer<String> MyMessage = printAnyMessage(); MyMessage.accept("Hello! runners!"); } }
기존의
return message -> System.out.println(message)
부분을 보면
반환값 없이 전달받은 message 인자를 그대로 받아서 System클래스의 출력 메서드를 호출하고 있다.
이를 메서드 레퍼런스를 이용하여 정리하면 이렇게된다.public class Lamda3 { public static Consumer<String> printAnyMessage(){ return System.out::println; } public static void main(String[] args) { Consumer<String> MyMessage = printAnyMessage(); MyMessage.accept("Hello! runners!"); } }
class WarmingUp{
public String study(){
return "공부를 열심히 한다.";
}
}
public class AnonymousClass1 {
public static void main(String[] args) {
WarmingUp runners = new WarmingUp(){
@Override
public String study(){
return "익명 클래스에 대해 공부한다.";
} // 이미 정의되어 있는 클래스를 일회성으로 재정의하여 사용
};
System.out.println(runners.study()); //익명 클래스 사용
}
}
즉, 익명 클래스는 재사용할 필요가 없는 일회성 클래스를 굳이 정의하고 생성하는 것이 비효율적이기 때문에, 익명 클래스를 통해 코드를 줄이는 일종의 기법이라고 말 할 수 있다.
interface Runners {
public String study();
public String homework();
}
public class AnonymousClass2 {
public static void main(String[] args) {
Runners student = new Runners() {
@Override
public String study() {
return "인터페이스 익명 구현 객체를 공부해요";
}
@Override
public String homework() {
return "3일차 과제를 해요";
}
};
System.out.println(student.study()); //인터페이스 익명 구현 객체를 공부해요
System.out.println(student.homework()); // 3일차 과제를 해요
}
}
익명 클래스 기법은 길고 복잡한 자바 문법을 간결하게 하는 것에 초점을 둔다.
그래서 람다식과 매우 잘 어울리며 람다식을 이용하면 익명 구현 객체의 코드를 획기적으로 줄일 수 있다.
interface Calculator{
int calculator(int a, int b);
}
public class AnonymousLamda {
public static void main(String[] args) {
Calculator anonymous = new Calculator() { // 익명클래스만 사용
@Override
public int calculator(int a, int b) {
return a + b;
}
};
//-> 람다를 사용하여 줄이기
Calculator lamda = (a,b) -> {
return a + b;
};
//-> 더 줄이기
Calculator shortlamda = (a,b) -> a + b;
//-> + 메서드 레퍼런스
Calculator methodReference = Integer::sum;
}
}
@FunctionalInterface
- 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션
- 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할
1개의 추상 메서드를 선언하고, 위에 @FunctionalInterface 어노테이션을 붙여주면된다.
@FunctionalInterface
interface MyFunctionalInterface{
int sum(int a, int b);
}
public class functionalInterface1 {
public static void main(String[] args) {
MyFunctionalInterface Myfunction = (a, b) -> a + b;
System.out.println(Myfunction.sum(15,17));
//
}
}
⚠️ 주의
- 람다식으로 생성된 순수 함수는 함수형 인터페이스로만 선언이 가능하다.
@FunctionalInterface
가 해당 인터페이스가 1개의 추상 메서드만을 갖도록 제한하기 때문에
(default 메서드와 static 메서드 제외)
여러 개의 추상 메서드를 선언하면 컴파일 에러가 발생한다.
java.util.function
에는 각Supplier
,Consumer
,Predicate
,Function
기본 함수형 인터페이스에 대해 파생적인 인터페이스가 40여개가 있다.
인터페이스명 | 추상 메소드 | 설명 |
---|---|---|
Runnable | void run() | 기본적인 인터페이스, 매개변수와 반환값 없음 |
Supplier<T> | T get() | 매개변수 없음, 제네릭타입 반환값 가짐 |
Consumer<T> | void accept() | 제네릭 매개변수 하나, 반환값 없음(void) |
Predicate<T> | boolean test() | 제네릭 매개변수 하나, Boolean 반환값 하나 |
Function<T,R> | R apply(T t) | 제네릭 매개변수 하나와 다른 제네릭 반환값하나 |
Comparator<T> | int compare(T o1, T o2) | 같은 제네릭 타입 매개변수 두개를 받고,Integer 반환값 하나 가짐, 객체간 비교를 위핸 compare를 위한 인터페이스 |
BiConsumer<T,U> | void accept(T t, U u) | 서로다른 제네릭 매개변수 두개를 받고 반환값 없음 |
BiFunction<T,U,R> | R apply(T t, U u) | 서로 다른 제네릭 매개변수 두개를 받고 다른 제네릭 타입의 반환값 하나 |
BiPridicate<T,U> | boolean test(T t, U u) | 서로 다른 제네릭 타입의 매개변수 2개를 받고Boolean 타입의 반환값 하나 가짐 |
자바의 람다식은 왜 등장했을까?
자바의 람다식은 기존 익명 클래스로 함수형 인터페이스를 구현하려면 코드가 길어지고 가동성이 떨어지는 문제가 있었기에 익명 클래스로 구현하던 함수형 프로그래밍을 지원하기 위해 등장하였다.
람다식과 익명 클래스는 어떤 관계가 있을까?
익명 클래스를 사용하여 함수형 인터페이스를 구현할 때, 람다식을 통해 훨씬 간단하고 직관적으로 작성할 수 있다.