@FunctinonaInterface public interface ITrade{ public boolean check( Trade t ); }새로운 Trade들에 대한 상태를 체크하기 위해서 람다 표현식으로 작성해보자. 작성한 코드는 아래와 같다.
ITrade newTradeChecker = (Trade t) -> t.getStatus().equals("NEW"); // Or we could omit the input type setting: ITrade new TradeChecker = (t) -> t.getStatus().equals("NEW");->를 token으로 해서 좌측에는 입력받을 객체를 지정하고, 우측에는 실제로 작동해야 할 메소드 몸체를 기술한다. 이 문장은 Trade객체의 상태를 체크하고 boolean 값을 리턴한다. 위의 예시는 이해를 돕기 위한 간단한 표현이지만 람다 사용시에 얻을 수 있는 진짜 파워는 실세계를 표현할 수 있는 다수의 행위적인 함수들을 만들 때다. 예를 들면, 부가적으로 우리가 이미 봤듯이 여기에 큰 trade(1 million 이상) 또는 새롭게 생성된 큰 google trade를 찾기 위한 람다 표현식들이 있다.
// Lambda for big trade ITrade bigTradeLambda = (Trade t) -> t.getQuantity() > 10000000; // Lambda that checks if the trade is a new large google trade ITrade issuerBigNewTradeLambda = (t) -> { return t.getIssuer().equals("GOOG") && t.getQuantity() > 1000000 && t.getStatus().equals("NEW"); };이 함수들은 메소드로 전달될 수 있다. ( 대부분 서버 사이드) ITrade를 하나의 파라미터로서 갖는다. Trade 타입의 Collection이 있을 때 특정 조건에 의해서 필터링되도록 해보자. 람다 표현식을 사용하면 이 문제를 쉽게 해결할 수 있다. 람다 표현식으로 작성했던 ITrade와 Trade 타입을 저장할 수 있는 List 타입의 collection 2개의 파라미터를 입력받는 코드를 작성해보자.
private ListfilterTrades 메소드는 newTrades라는 ArrayList 객체를 만들고 Trade들은 컬렉션 안에서 1 대 1로 순회하면서 만일 입력값이 람다에 의해 작성된 판별식을 만족하는 경우 해당 trade객체는 newTrades(ArrayList)에 누적된다. 만족하지 않는 경우는 버려진다. 좋은 점은 lamda표현식을 파라미터로 전달할 수 있기 때문에 아래와 같은 방식으로 하나의 filterTrades 메소드를 이용해서 다른 대상에 대한 filter를 적용할 수 있다.filterTrades(ITrade tradeLambda, List trades){ List newTrades = new ArrayList<>(); for(Trade trade : trades){ if(tradeLamda.check(trade)){ newTrades.add(trade); } } return newTrades; }
// Big trades function is passed List함수 라이브러리bigTrades = client.filterTrades(bigTradeLambda, tradesCollection); // "BIG+NEW+ISSUER" function is passed List bigNewIssuerTrades = client.filterTrades(issuerBigNewTradeLambda, tradeCollection); // cancelled trades function is passed List bigNewIssuerTrades = Client.filterTrades(cancelledTradesLamda, tradesCollection);
@FunctionalInterface public Interface Predicate우리가 알고 있는 람다에 대한 지식과는 조금 거리가 있어보인다. 람다를 이용해서 predicate를 표현한 코드는 아래와 같다.{ boolean test(T t); }
// A large or cancelled trade (this time using library function)! Predicate이 함수들의 호출방식은 우리가 해왔던 Itrade와 유사하다. Predicate들은 Itrade처럼 값을 체크하고 boolean 타입으로 참 거짓을 리턴한다.largeTrade = (Trade t) -> t.isBigTrade(); // Parenthesis and type are optional Predicate cancelledTrade = t -> t.isCancelledTrade(); // Lambda to check an empty string Predicate emptyStringChecker = s -> s.isEmpty(); // Lambda to find if the employee is an executive Predicate isExec = emp -> emp.isExec();
// Check to see if the trade has been cancelled boolean cancelledTrade = cancelledTrade.test(t); // Check to find if the employee is executive boolean executive = isExec.test(emp);[java.util.Function]
@FunctionalInterface public Interface Function우리는 Function을 참 거짓 판별 뿐만 아니라 타입 변환을 위해서 쓸 수도 있다. 온도를 섭씨에서 화씨로 전환하듯이 문자열에서 정수형으로 변환하듯이 기타 등등{ R apply (T t); }
// convert centigrade to fahrenheit Functionfunction generic에 두 개의 파라미터 타입 String과 Integer를 확인했다면 String 타입의 값을 입력받아서 Integer 타입으로 리턴한다는 걸 짐작할 수 있을 것이다. 좀 더 복잡한 요구사항을 고려해보면, trade의 리스트를 얻기 위해 trade의 수량을 측정할 때 function은 아래와 같이 표현할 수 있다.centigradeToFahrenheitInt = x -> new Double((x*9/5)+32); // String to an integer Function stringToInt = x -> Integer.valueOf(x); // tests System.out.println("Centigrade to Fahrenheit: "+centigradeToFahrenheitInt.apply(centigrade)) System.out.println(" String to Int: " + stringToInt.apply("4"));
// Function to calculate the aggregated quantity of all the trades - taking in a collection and returning an integer! Function수집은 Stream API를 사용하면 좀 더 유려하게 표현할 수 있다.,Integer> aggegatedQuantity = t -> { int aggregatedQuantity = 0; for (Trade t: t){ aggregatedQuantity+=t.getQuantity(); } return aggregatedQuantity; };
// Using Stream and map and reduce functionality aggregatedQuantity = trades.stream() .map((t) -> t.getQuantity()) .reduce(0, Integer::sum); // Or, even better aggregatedQuantity = trades.stream() .map((t) -> t.getQuantity()) .sum();[다른 함수들]
@FunctionalInterface public interface Consumer이것은 지속적인 직원들, 집을 돌보는 일련의 작업들의 호출, 정기적인 email newletter와 같은 경우에 주로 사용된다. Supplier interface는 이름에서 알 수 있듯이 consumer와 다르게 결과를 리턴한다. 해당 메소드 시그니처는 아래와 같다.{ void accept(T t); }
@FunctionalInterface public interface Supplier예를 들면, 데이터베이스로부터 결과를 획득할 때, 참조된 데이터를 로딩할 때, 기본 식별자로 학생리스트를 만들때 등 어떤 결과를 표현하고자 하는 모든 경우에 Supplier 인터페이스를 사용할 수 있다.{ T get(); }
// Here, the expected input and return type are exactly same. // Hence we didn"t use Function, but we are using a specialized sub-class UnaryOperatorFunction이 하나의 제너럴 타입과 함께 선언된 걸 확인해보자. 이 경우에 양쪽의 타입이 같기 때문에 Function 대신 unarayOPerator를 사용했다. 조금 더 살펴보면 java.util.function 패키지에 있는 인터페이스들은 원시형 데이터의 표현 또한 지원한다. LongUnaryOperator, IntUnaryOperator 등 long은 long으로 double은 double로 Function 패키지는 IntPredicate 또는 LongConsumer,. BooleanSupplier와 같은 많은 특수한 case들을 수용할 수 있는 함수들 또한 가지고 있다. 예를 들면 IntPredicate는 integer 값을 표현하는 함수이고 LongConsumer는 longvalue를 입력받고 결과를 리턴하지 않는다. 그리고 BooleanSupplier는 boolean value를 지원한다. Function 패키지에는 이처럼 과다하게 특수화된 케이스가 매우 많아서 api를 깊게 이해하고 사용하길 권장한다.toLowerUsingUnary = (s) -> s.toLowerCase();
@FunctionalInterface public interface BiFunction완성도를 위해서 아래의 BitFunction 사용 코드를 보자. 2개의 Trade를 입력받아서 trade 수량의 합을 만든다. input 타입은 trade이고 리턴타입은 Integer이다.{ R apply(T t, U u); }
BiFunction2개의 인자를 다루는 다른 함수를 보자. BitPredicate를 이용해서 2개의 인자를 입력받고 하나의 boolean 값을 리턴하도록 할 수 있다.(놀라지 마시라!)sumQuantities = (t1, t2) -> { return t1.getQuantity()+t2.getQuantity(); };
// Predicate expecting two trades to compare and returning the condition"s output BiPredicate이미 지금 추측했듯이 2개의 입력 함수는 특화되어 있다. 예를 들면 같은 타입의 연산 같은 기능들은 BinaryOperator function에 있다. BinaryOperator 은 BitFunction을 상속했다. 다음 예제에서 BinaryOperator를 보자. 이 예제에서는 2개의 Trade를 입력 받아서 병합한다(기억하자. BinaryOperator는 BitFunction의 특수케이스이다).isBig = (t1, t2) -> t1.getQuantity() > t2.getQuantity();
BiFunction우리는 타입을 선언할 때 세 개의 타입을 넘기지 않았다. (모든 input , output 타입이 같을 것이라고 예상했기 때문에) 또한 실제로직 전달은 BitFunction이 BinaryOperator보다 더 나은 것 같다. 왜냐면 BinaryOperator가 BitFunction을 상속했기 때문이다.tradeMerger2 = (t1, t2) -> { // calling another method for merger return merge(t1,t2); }; // This method is called from the lambda. // You can prepare a sophisticated algorithm to merge a trade in here private Trade merge(Trade t1, Trade t2){ t1.setQuantity(t1.getQuantity()+t2.getQuantity()); return t1; }
@FunctionalInterface public interface IComponent { // Functional method - note we must have one of these functional methods only public void init(); // default method - note the keyword default default String getComponentName(){ return "DEFAULT NAME"; } // default method - note the keyword default default Date getCreationDate(){ return new Date(); } }}예제코드를 통해서 알 수 있듯이 default 예약어를 이용해서 추상 메소드가 아닌 구체적인 메소드를 interface 안에 정의할 수 있다.
public class Student extends AbstractClass1 implements Faculity{ public static void main(String ar[]){ test(); } public static void test(){ String name = new Student().getName(); System.out.println(name); } } Output: abstract Name
// Person interface with a concrete implementation of name interface Person{ default String getName(){ return "Person"; } } // Faculty interface extending Person but with its own name implementation interface Faculty extends Person{ default public String getName(){ return "Faculty"; } }person, faculty 양쪽의 인터페이스 모두 default 메소드를 이용해서 name에 대한 구현을 제공한다. 그렇지만 faculty 인터페이스에서는 person 인터페이스를 통해 상속받은 getName 메소드를 오버라이딩해서 Faculty를 리턴하도록 했다. 그래서 student라는 서브클래스가 faculty를 상속받으면 Student의 getName 메소드는 Faculty name을 출력한다.
// The Student inherits Faculty"s name rather than Person class Student implements Faculty, Person{ .. } // the getName() prints Faculty private void test() { String name = new Student().getName(); System.out.println("Name is "+name); } output: Name is Faculty여기에 진짜 중요한 포인트가 있다. 만약 Faculty 클래스가 person을 상속하지 않으면 어떻게 될까? 이 경우에는 student는 name 속성에 대한 상속을 양쪽으로부터 구현해야 하기 때문에 컴파일러가 괴롭게 된다.
Description Resource Path Location Type Duplicate default methods named getName with the parameters () and () are inherited from the types Faculity and Person
interface Person{ .. } // Notice that the faculty is NOT implementing Person interface Faculty { .. } // As there"s a conflict, out Student class must explicitly declare whose name it"s going to inherit! class Student implements Faculty, Person{ @Override public String getName() { return Person.super.getName(); } }메소드 레퍼런스(Method references)
public class AddableTest { // Add given two integers private int addThemUp(int i1, int i2){ return i1+i2; }addThemUp을 MethodReference를 이용해서 IAddable을 구현해보자. this::addThemUp에서 this는 AddableTest 클래스의 인스턴스를 나타내고 ::뒤에는 이미 정의된 메소드명을 () 를 사용하지 않고 기술한다. :: 를 사용하면 이제 보다 간결하게 다른 클래스의 static 메소드를 사용할 수 있다.
// Class that provides the functionality via it"s static method public class AddableUtil { public static int addThemUp(int i1, int i2){ return i1+i2; } } // Test class public class AddableTest { // Lambda expression using static method on a separate class IAddable addableViaMethodReference = AddableUtil::addThemUp; }우리는 생성자 또한 쉽게 호출할 수 있다. 예를 들면 Employee::new 또는 Trade::new 처럼 ::를 사용해서 보다 간결하게 표현할 수 있다.
Interface명 | Arguments | Return Value |
---|---|---|
Predicate |
T | boolean |
Consumer |
T | Void |
Function |
T | R |
Supplier |
None | T |
unaryOperator |
T | T |
BinaryOperator |
(T , T) | T |
이전 글 : 인터넷에 연결된 LED 넥타이
다음 글 : 스마트폰 로켓 발사대
최신 콘텐츠