본문 바로가기

Language/Java

[Java8]Functional Interface

public class FuntionalInterfaceExamples {

	public static void main(String[] args) {
		Integer value = toInt("100"); //100
		
	}
	static Integer toInt(String str) {
		return Integer.parseInt(str);
	}

}

String을 Integer로 캐스팅 하는 함수를 만들었다.

1. Function

import java.util.function.Function;

public class FuntionalInterfaceExamples {

	final Function<String,Integer> toInt = new Function<String,Integer>() {
		@Override
		public Integer apply(String str) {
			return Integer.parseInt(str);
		}
	};

	int value2 = toInt.apply("100");

}

Function객체는 하나의 타입을 받아 다른 타입으로 리턴하는 apply를 가진 객체이다.
Function<T,R> 에서

T는 파라미터로 받을 타입 R는 리턴할 타입이다.

 

그리고 apply메소드 이외의 어떤 메소드도 가지고 있지 않다, 그말은 즉슨 람다식으로 풀어 쓸수 있다.

import java.util.function.Function;

public class FuntionalInterfaceExamples {
	final Function<String,Integer> toInt = str -> Integer.parseInt(str);
	int value3 = toInt.apply("100");

}

훨씬 간결해졌다.

2. Consumer

import java.util.function.Consumer;

public class FuntionalInterfaceExamples {
	public static void main(String[] args) {
		final Consumer<String> greetings = value -> System.out.println("Hello "+value);
		greetings.accept("Tom");
	}
	
}

만약 하나의 타입을 받지만 아무것도 리턴하지 않는 경우를 원할 수도 있다.

이럴 때는 Consumer객체를 이용한다.

Consumer의 메소드는 accept

3. Predicate

하나의 타입을 받아 boolean을 리턴할 때는 Predicate의 test를 이용한다.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class FuntionalInterfaceExamples {
	public static void main(String[] args) {
		List<Integer> preList = Arrays.asList(-5,-4,-3,-2,-1,0,1,2,3,4,5);
		List<Integer> afterList = new ArrayList<>();
		for(Integer value : preList) {
			if(validation(value)) {
				afterList.add(value);
			}
		}
		System.out.println(afterList.toString());
		
	}
	
	public static boolean validation(Integer number) {
		if(number>0) {
			return true;
		}
		return false;
	}
}

해당 코드는 양수만 출력하는 코드이다.

(주제와는 벗어났지만, for문의 조건문에 List가 들어 갈 시에 remove() 메소드와 같이 List가 변경되는 메소드를 사용할 수 없다. 뿐만 아니라, preList와 같은 경우는 읽기 전용(정확한 타입이 정해져 있지 않음)이기 때문에 수정할 수 도 없다.)

 

이를 Predicate를 이용하면 다음과 같이 사용한다.

public class FuntionalInterfaceExamples {
	public static void main(String[] args) {
		
		Predicate<Integer> validation = i -> i > 0; 
		
		List<Integer> preList = Arrays.asList(-5,-4,-3,-2,-1,0,1,2,3,4,5);
		List<Integer> afterList = new ArrayList<>();
		for(Integer value : preList) {
			if(validation.test(value)) {
				afterList.add(value);
			}
		}
		System.out.println(afterList.toString());
	}
	
}

다음은 람다식을 이용하지 않고 구체화 한 객체이다.

public class FuntionalInterfaceExamples {
	public static void main(String[] args) {
		List<Integer> preList = Arrays.asList(-5,-4,-3,-2,-1,0,1,2,3,4,5);
		List<Integer> afterList = new ArrayList<>();
		for(Integer value : preList) {
			if(new validation().test(value)) {
				afterList.add(value);
			}
		}
		System.out.println(afterList.toString());
	}
	
}


class validation implements Predicate<Integer>{
	@Override
	public boolean test(Integer i) {
		return i>0;
	}
}

마지막으로 다음과 같이 Predicate 역시 파라미터로 제공하여 리스트를 반환하는 제네릭 메소드를 만들었다.

public class FuntionalInterfaceExamples {
	public static void main(String[] args) {
		List<Integer> preList = Arrays.asList(-5,-4,-3,-2,-1,0,1,2,3,4,5);
		List<Integer> afterList = filter(preList, i->i>0);
		System.out.println(afterList.toString());
	}
	
	public static <T> List<T> filter(List<T> list, Predicate<T> filter){
		List<T> result = new ArrayList<>();
		for(T input : list) {
			if(filter.test(input)) {
				result.add(input);
			}
		}
		return result;
	}
}

4. Supplier

자바스크립트에서 비동기 관련해서 이런 것을 했던거 같은데... 어렵지만 한 번 보자

Supplier는 아무것도 받지 않고 어떤 타입을 리턴할 때 사용한다.

 

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class SupplierExamples {
	
  public static void main(final String[] args) {
	  	//1. 문자열을 리턴하는 예시
	    supplierExamples();
	    
	    //2. Supplier를 사용하지 않을 떄의 예시
	    System.out.println("\nSupplier 사용하지 않고 시간 오래 걸리는 메소드 호출");
	    callingExpensiveMethodWithoutSupplier();

	    System.out.println("\nSupplier 사용해서 시간 오래 걸리는 메소드 호출");
	    callingExpensiveMethodWithSupplier();
  }
  
  // 1. 문자열을 리턴하는 예시
  private static void supplierExamples() {
	    System.out.println("SupplierExamples.supplierExamples()");
	    final Supplier<String> helloSupplier = () -> "Hello "; // Hello를 받아
	    System.out.println(helloSupplier.get() + "world"); // 리턴한다.
  }
  
  //2. Supplier를 사용하지 않을 떄의 예시
  private static void callingExpensiveMethodWithoutSupplier() {
	    System.out.println("SupplierExamples.callingExpensiveMethodWithoutSupplier()");
	    final long start = System.currentTimeMillis();
	    printIfValidIndex(0, getVeryExpensiveValue());
	    printIfValidIndex(-1, getVeryExpensiveValue());
	    printIfValidIndex(-2, getVeryExpensiveValue());
	    System.out.println("It took " + ((System.currentTimeMillis() - start) / 1000) + " seconds."); // 9초
  }
  
  //3. Supplier를 사용 했을 때의 예시
  private static void callingExpensiveMethodWithSupplier() {
    System.out.println("SupplierExamples.callingExpensiveMethodWithSupplier()");
    final long start = System.currentTimeMillis();
    printIfValidIndex(0, () -> getVeryExpensiveValue());
    printIfValidIndex(-1, () -> getVeryExpensiveValue());
    printIfValidIndex(-2, () -> getVeryExpensiveValue());
    System.out.println("It took " + ((System.currentTimeMillis() - start) / 1000) + " seconds."); // 3초
  }
  
  private static String getVeryExpensiveValue() {
    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return "Kevin";
  }

  private static void printIfValidIndex(final int number, final String value) {
    if (number >= 0) {
      System.out.println("The value is " + value + ".");
    } else {
      System.out.println("Invalid");
    }
  }


  private static void printIfValidIndex(final int number, final Supplier<String> valueSupplier) {
    if (number >= 0) {
      System.out.println("The value is " + valueSupplier.get() + ".");
    } else {
      System.out.println("Invalid");
    }
  }

}

2 번을 보자

2번은 printIfValidIndex 실행시에 Validation이 0일 때 getVergetVeryExpensiveValue 이 실행되며 3초간 TimeUnit에 의해 쓰레드가 잠들고, 이는 -1 과 -2일때도 똑같다.

 

하지만 -1과 -2의 경우에는 사실 Invalid한 값이기 때문에 getVergetVeryExpensiveValue 이 실행할 이유가 없다.

 

이때 3번과 같이 Supplier를 이용한다면 

Validation이 조건에 충족할 경우에만 getVergetVeryExpensiveValue 이 실행되기 때문에

원하는 대로 작동하게 할 수 있다.