JAVA8에 대해 알아보자(3)

java8

이번엔 메소드 레퍼런스에 대해 알아보자.
메소드 레퍼런스를 알아보기전에 알아두어야 할 것이 있다.
first-class citizen(일급 시민) 이 용어는 60년대에 크리스토퍼 스트래치 라는 분이 만들었다.
first-class citizen란 무엇인가.
일단 내가 알고 있는 것으로 설명하겠다.
1. 파라미터로 전달받을 수 있어야 한다.
2. 리턴 값으로 반환 할 수 있어야 한다.
3. 변수 혹은 Data 구조에 담을 수 있어댜 한다.

그럼 필자가 말한 위에 3개가 자바에서 가능 한지를 알아보겠다.
첫 번째로 파라미터롤 전달 받을 수 있는지 알아보자.

일단 function을 담을수 있는 functionalInteface를 만들자

@FunctionalInterface
interface Functional{
    String apply(Integer i);
}

그리고 메소드를 전달하기 위한 메소드를 만들어 보자

private static String methodReferenceParameter(Functional functional){
    return "result : " + functional.apply(1);
}

간단하다 methodReferenceParameter라는 메소드는 Functional을 파라미터로 받고 String으로 돌려주는 메소드이다.
그럼 일반적으로 람다를 쓸경우엔 어떻게 쓰는지 보자.

String lambda = methodReferenceParameter(i -> String.valueOf(i));
System.out.println(lambda);

이렇게 호출하면 result : 1 이런 결과값을 얻을 수 있다.
다음은 메소드 레퍼런스를 사용해보자.
일단 메소드를 넘길려면 메소드를 만들어야 된다. 위에 람다에서 사용했던 String.valueOf(i)를 메소드로 만들어 보자

private static String methodReference(int i){
    return String.valueOf(i);
}

이제 사용해보자!

String methodReference = methodReferenceParameter(MethodReferenceExample::methodReference);
System.out.println(methodReference);

결과는 같을 것이다.

다음으론 리턴 값을 반환 할 수 있는지 알아보자.
일단 비교를 위해 lambda로 먼저 해보겠다.

private static Functional methodReferenceReturnlambda(){
    return i -> String.valueOf(i);
}

사용해보자.

Functional functional = methodReferenceReturnlambda();
System.out.println("string : " + functional.apply(1));

string을 리턴하는 것 이기에 string 이라는 문자를 넣었다.

다음은 메소드 레퍼런스를 사용하여 리턴 값을 만들어 보자.

private static Functional methodReferenceReturn() {
    return MethodReferenceExample::methodReference;
}

호출해보자.

Functional methodReferenceReturn = methodReferenceReturn();
System.out.println("methodReferenceReturn : " + methodReferenceReturn.apply(1));

정상적으로 컴파일 후 실행이 될 것이다.

다음은 모지? 아 변수 혹은 Data구조에 담아 둘 수 있어야 한다.
List<T>에 담기는지 보자.

List<Functional> functionals = Arrays.asList(MethodReferenceExample::methodReference, i -> String.valueOf(i));

컴파일도 잘된다.
실행해보자

System.out.println(functionals.get(0).apply(100));
System.out.println(functionals.get(1).apply(200));

이렇게 사용하면 된다.
부가적으로 Syntax를 보자.
static 메소드는 ClassName::MethodName 으로 하면 된다.
인스턴스 메소드는 object::instanceMethodName으로 하고
생성자ClassName::new으로 정의하면된다.

이상으로 자바8의 변경사항을 알아봤다.

JAVA8에 대해 알아보자(2)

java8

이전에 FunctionalInterface와 람다표현식을 배웠다. 아직 못봤거나 까먹었다면 여기에가서 다시 보자!

Stream

Stream 에는 많은 기능이 있다. 다 설명할 수는 없지만 중요한 몇가지를 공부해보겠다.
자바의 컬렉션들은 Stream을 사용 할 수 있다. 혹은 스트림을 만들 수 있다.
흔히 쓰는 List<T>를 예로 들어 공부해보겠다.
그전에 알아두어야 할 용어가 있다.
Intermediate Operation MethodTerminal Operation Method이다.
Intermediate Operation Method는 중간단계로써 스트림을 리턴하기 때문에 계속 Method Chaining 통해 지시 할 수 있다.
그리고 Terminal Operation Method는 스트림을 끝내는 메소드다. Stream은 Lazy 하기 때문에 이 메소드가 호출되면 그때서야 Intermediate Method를 실행한다.

Optional<T> reduce(BinaryOperator<T> accumulator)
void forEach(Consumer<? super T> action)
Optional<T> max(Comparator<? super T> comparator)
long count()
Optional<T> findFirst()

등이 Terminal Operation Method에 속한다.
한번 코드를 보자!

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println(numbers.stream().reduce((i, j) -> i + j).get());
numbers.stream().forEach(x -> System.out.print(x + " "));
System.out.println();
System.out.println(numbers.stream().max((i, j) -> i.compareTo(j)).get());
System.out.println(numbers.stream().min((i, j) -> i.compareTo(j)).get());
System.out.println(numbers.stream().count());
System.out.println(numbers.stream().findFirst().get());

두 번째의 reduce는 합치는 작업을 한다. i + j의 합이 다시 i 가 되고 j 는 다음번숫자로 넘어가는걸 반복한다.
forEach는 우리가 흔히 썼던 for each 문과 같다.
max는 말그대로 최대값을 찾는다. min도 마찬가지다.
count는 스트림안에 개수를 리턴한다.
findFirst는 첫 번째 있는 값을 가져온다.

Stream<T> filter(Predicate<? super T> predicate)
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
Stream<T> sorted()
Stream<T> limit(long maxSize)

등이 Intermediate Operation Method에 속한다. 이 외에도 많이 있다.
마찬가지로 코드로 보자!

numbers.stream().filter(i -> i > 5).forEach(x -> System.out.print(x + " "));
System.out.println();
numbers.stream().map(i -> i * 2).forEach(x-> System.out.print(x + " "));
System.out.println();
numbers.stream().sorted((i, j) -> j.compareTo(i)).forEach(x -> System.out.println(x));
numbers.stream().limit(4).forEach(x -> System.out.println(x));

filter는 말그대로 걸러주는 역할을 한다. 파라미터가 Predicate이다 Predicate은 boolean 값을 리턴한다.
map은 어떤타입을 다른타입 혹은 같은타입으로 변환해준다. 위 코드는 2배를 해주는 코드다.
flatMap은 map이랑 같지만 Stream타입을 리턴한다.
sorted는 정렬을 해주는 함수다. 위의 코드는 내림차순으로 정렬한 코드다.
limit는 개수는 제한하는 코드다 mysql의 limit와 같다.
그럼 한번 응용을 해보자.
1~9까지중 3보다 큰값을 골라 2배를 해주고 내림차순으로 정렬뒤 3개만출력하는 코드를 구현해보자. 간단하게 구현 할 수 있다.

numbers.stream()
        .filter(i -> i > 3)
        .map(i -> i * 2)
        .sorted((i,j) -> j.compareTo(i))
        .limit(3)
        .forEach(x -> System.out.println(x));

간단하게 Stream에 대해서 알아봤다.
어느정도 감만 잡았다면 성공한거다.
다음 시간엔 method reference 대해서도 알아보겠다.

JAVA8에 대해 알아보자(1)

java8

자바8의 새롭게 바뀐 부분이 많지만 그 중 내가 자주 쓰는걸 정리 해보겠다.

@FunctionalInterface

첫 번째로 @FunctionalInterface 인터페이스다.
람다를 쓰긴 위한 애노테이션이다. 만약 저 애노테이션이 붙은거라면 람다 표현식을 사용 할 수 있다.
하지만 명시적으로 지정 하지 않더라도 abstract 메소드가 한개라면 람바 표현식을 사용 할 수 있다.
만약 저 애노테이션을 사용한다면 abstract 메소드가 2개 이상 있을경우 컴파일 타임에 에러가 난다.
자바 기본 패키지에 있는 Function이라는 인터페이스다.

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    //...
}

한번 커스텀한 interface를 만들어보자.
두개의 같은 타입 파라미터를 넘겨서 두개를 더하는 인터페이스다.

@FunctionalInterface
interface AddFunction<T> {

    T apply(T t, T t1);
}

사용해 보자

AddFunction<Integer> add = (i, j) -> i + j;
System.out.println(add.apply(10, 20));

AddFunction<String> add1 = (i, j) -> i + j;
System.out.println(add1.apply("hello", " world"));

결과는 다음과 같을 것이다.

30
hello world

lambda

두 번째론 람다표현식이다. 자바 개발자 한테는 조금 생소한 표현식 이다. 하지만 다른 언어들은 일찌감치 람다 표현식을 사용하고 있다.
스칼라, 파이썬 하스켈, c#등 여러가지 언어에서 사용 하고 있다.
한번 살펴보자.
우리가 쓰레드를 사용 할때 이용하는 인터페이스중 Runnable가 있다. 이 것도 java8에선 FunctionalInterface로 구현되었다.

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

우리가 java8 이전에는 이렇게 사용하곤 했다. 우리가 흔히 말하는 익명 클래스다. 예를 든 코드다 실질적으로 이렇게 사용하진 않는다.

Runnable runnable = new Runnable() {
     @Override
     public void run() {
         System.out.println("run 호출");
     }
};
runnable.run();

그런데 람바를 쓰면 아주 간단하게 바뀐다. 한번보자.

Runnable runnable = () -> System.out.println("run 호출");
runnable.run();

6줄을 단 한줄로 바꿔버렸다. 대단하다.
그럼 어떻게 바뀌는지 한번 보자.
() 이거는 파라미터다 Runnableabstract 메소드 run은 받는 파라미터가 한개도 없다. 그래서 괄호만 해주는거다.
-> 애로우?라 부른다? 람다? 인가? 흠 그리고 다음 나오는게 메소드 바디이다. 간단하다.
그럼 다음 예제도 살펴보자
이번엔 아까 위에서 봤던 Function 인터페이스다. 이 인터페이스를 아마 자주 사용할 듯 하다.
일단 람다를 쓰지말고 한번 해보자.

Function<Integer,String> function = new Function<Integer, String>() {
    @Override
    public String apply(Integer integer) {
        return String.valueOf(integer);
    }
};
String str = function.apply(1010);
System.out.println(str);

일단 Function 인터페이스는 제네릭타입이다. 첫 번째는 파라미터타입 두 번째는 리턴 타입이다.
한마디로 어떤 타입의 파라미터로 넘기면 다른 타입을 반환하는걸 의미한다. 같은 타입을 지정해도 상관없다. 이런걸 identity라 부른다.
위의 코드는 int를 String으로 변환하는 코드다.
이제 람다로 바꿔보자!

Function<Integer,String> function = x -> String.valueOf(x);
String str = function.apply(1010);
System.out.println(str);

이것도 아주 깔끔하고 짧게 바뀌었다.
다시 한번 살펴보자. apply 메소드는 파라미터가 한개다.그래서 x라는 변수 한개를 선언했다.(파라미터가 한개일땐 ()로 감싸주지 않아도 된다.)
그리고 그 x로 String.valuOf를 이용하여 변환 하였다.
그런데 String.valueOf를 호출하기전에 그 파라미터가 어떤건지 한번 Print로 출력하고 싶다.
그럼 메소드 바디를 감싸주면 된다. 컬리브레이스로 감싸자 해보자.

Function<Integer,String> function = x -> {
    System.out.println(x);
    return String.valueOf(x);
};

이외에도 자주 쓰는 인터페이스는
Consumer<T> 어떤 타입의 파라미터로 받는다 리턴은 없다.
Predicate<T> 어떤 타입의 파라미터로 받는다 리턴은 Boolean 이다.
Supplier<T> 어떤 타입으로 리턴한다. 파라미터는 없다.

이 외에도 java.util.function 패키지에 가보면 function 인터페이스가 있다. 한번씩 보자!
다음 시간에 Stream을 공부해보겠다.