들어가며

객체지향을 추구하던 Java는 8버전 부터 큰 변화를 시도하였습니다.
Java8 버전에 함수형 패러다임이 추가되었는데, 그 부분에서 가장 큰 역할을 하는 것이 람다와 함수형 인터페이스가 아닐까라고 생각합니다.
이번 포스팅에선 함수형 인터페이스의 선언과 자주 사용하는 인터페이스 몇가지를 소개드릴까 합니다.

다만, 이번 포스팅을 읽기전에 람다와 익명클래스에 관해 간단히 읽어 보시면 좋을거 같습니다!!
JAVACOFFEE :: Java Java8 - 람다 표현식 사용하기 | Lambda | 익명클래스


함수형 인터페이스 선언

함수형 인터페이스란 한개의 추상 메서드로 이루어진 인터페이스를 말합니다.
간단한 예제를 통해 알아보겠습니다.

@FunctionalInterface
public interface FunctionalInterfaceExample {
    void printMsg(String msg);
    //void printName(String name);

    default void defaultMethod() {
        System.out.println("defalut Method");
    }

    static void staticMethod() {
        System.out.println("static Method");
    }
}
  • @ FunctionalInterface를 선언하여 줍니다
  • 추상메서드는 하나만 선언할 수 있습니다
  • 두 개 이상시 컴파일 에러가 발생합니다
  • default, static 메서드는 사용 가능합니다

함수형 인터페이스 사용

생성한 함수형 인터페이스를 사용해보도록 하겠습니다.
먼저 Yunho.class

public class Yunho implements FunctionalInterfaceExample {
    @Override
    public void printMsg(String msg) {
        System.out.println(msg);
    }
}
public class FunctionMain {
    public static void main(String[] args) {
        FunctionalInterfaceExample yunhoInterface = new Yunho();
        yunhoInterface.printMsg("안녕하세요");
        yunhoInterface.defaultMethod();
        FunctionalInterfaceExample.staticMethod();
    }
}

위의 main()을 실행하게 되면
추상메서드, default 메서드, static 메서드가 아래와 같이 실행됩니다.

안녕하세요
defalut Method
static Method

자주 사용하는 함수형 인터페이스

자바에서 기본적으로 제공하는 함수형 인터페이스는 다음과 같은 것들이 있습니다.

Function<T,R> : <T> -> <R>
Consumer<T> : <T> -> Void
Predicate<T> : <T> -> Boolean
Supplier<T> : lazy evaluation

이외에도 다양한 인터페이스를 제공합니다. java.util.function (Java Platform SE 8 )

Predicate

T타입 인자를 받고 결과로 Boolean을 리턴합니다.

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
    @SuppressWarnings("unchecked")
    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

주요 메서드로 test가 있고, and, or 등의 메서드를 제공합니다

public class PredicateExample {

    public static void main(String[] args) {
        Predicate<Integer> integerPredicate = num ->  num > 10;
        Predicate<Integer> integerPredicate1 = num -> num < 20;

        System.out.println(integerPredicate.test(5));
        System.out.println(integerPredicate.and(integerPredicate1).test(15));
    }
}

and는 Predicate를 파라미터로 받기 때문에 위와 같은 연산을 수행할 수 있습니다.

Consumer

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); };
    }
}

Accept, andThen 메서드를 제공합니다

public class ConsumerExample {

    public static void main(String[] args) {
        Consumer<List<Integer>> numberConsumer = list -> {
            for (int i = 0; i < list.size(); i++) {
                list.set(i, list.get(i) * list.get(i));
            }
        };
        Consumer<List<Integer>> printConsumer = list -> {
            for (Integer num : list) {
                System.out.println(num);
            }
        };
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
        numberConsumer.andThen(printConsumer).accept(numbers);
    }
}

andThen은 Consumer를 Parameter로 받기 때문에 복합 연산을 수행할 수 있습니다.

Supplier

인자를 받지 않고 T 타입을 리턴합니다.

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

get 메서드를 제공합니다

public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}

public class SupplierExmaple {

    public static void main(String[] args) {
        Supplier<String> supplier= () -> "hello world";
        System.out.println(supplier.get());

        Supplier<Student> studentSupplier = () -> new Student("황윤호", 20);
        System.out.println(studentSupplier.get());
    }
}

Function

T타입의 인자를 받고, R타입의 객체를 리턴합니다.

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

apply 이외에 compose, andThen 등의 메서드를 제공합니다

public class FunctionExample {

    public static void main(String[] args) {
        Function<Integer, Integer> multiplyFunction = number -> number * number;
        System.out.println(multiplyFunction.apply(10));

        Function<Integer, Integer> minusFunction = number -> number - 10;
        Integer result = multiplyFunction.andThen(minusFunction).apply(10);
        System.out.println(result);
    }
}

마찬가지로 andThen을 이용하면 복합 연산을 이용할 수 있습니다


마치며

이번 포스팅에서는 함수형 인터페이스에 대하여 알아보았는데요, 개인적으로는 Java8에서 꽃은 람다, 스트림, 함수형인터페이스 라고 생각합니다. 그만큼 중요하고, 많이 쓰이고 이해가 어려운 부분이라, 이번 포스팅에서 간단한 이해를 하시고 실제로 많은 연습을 통해 익히시는 걸 권해드립니다 @.@


참고

모던 자바 인 액션 - YES24
Java8 - 함수형 인터페이스(Functional Interface) 이해하기
java-self-study/src/main/java/fi at master · yhmane/java-self-study · GitHub

블로그 이미지

사용자 yhmane

댓글을 달아 주세요

Generic

들어가며

제네릭(Generic)은 Java5부터 새로 추가된 내용으로,
제네릭 타입을 이용하여 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있게 되었습니다.
컬렉션, 람다식, 스트림, NIO에서 널리 사용되고 많은 API 도큐먼트에서 제네릭 표현이 사용되므로 정확한 학습이 필요합니다.

개인적으로, Java를 학습하며 Generic은 가장 이해가 어려웠던 파트중 하나로 기억합니다. 이번 포스팅에서는 신용권님의 ‘이것이 자바다’ 13장 Generic을 기반으로 Generic 클래스, 메서드, 형변환 등에 대해서 정리하도록 하겠습니다.


제네릭의 장점

컴파일시 강한 타입 체크를 할 수 있다

자바 컴파일러는 잘못 사용된 타입을 미리 체크하여 주는데, 제니릭 코드에 대해 강한 타입 체크를 한다.
따라서 실행 이전에 컴파일 단계에서 에러를 체크해 준다

타입 변환(casting)을 제거한다

generic을 사용하지 않을 경우

List strList = new ArrayList();
strList.add("hi");
Object strObject = strList.get(0); 
String str = (String) strList.get(0);

generic을 사용할 경우

List<String> strList = new ArrayList<String>();
strList.add("hi");
String str = strList.get(0);

<> 안에 Type을 지정하여 줌을로써, 요소를 가져올때 형변환 과정이 필요 없어진다


제네릭 타입(class, interface)

제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말합니다.

public class 클래스명<T> {...}
public interface 인터페으스명<T> {...}

Generic 표기 방법


일반적으로 Java 진영에서는 위와 같은 컨벤션을 지키려고 한다. 다만, 경우에 따라서 적절한 Name을 지정해 주는 것도 좋다.

다음은 예제를 통해서 간단히 Generic class와 interface를 만들어 보자

Generic class

public class GenericClass<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

사용

GenericClass<String> strObject = new GenericClass<String>();
strObject.setValue("Hello, Yhmane!");
System.out.println(strObject.getValue());

GenericClass<Integer> intObject = new GenericClass<Integer>();
intObject.setValue(31);
System.out.println(intObject.getValue());

Generic Interface

public interface QueryResult<ID> {
    ID id();
}

Store.java

public class Store implements QueryResult<Long> {
    private Long storeId;
    private String name;
    private String address;

    public Store(Long storeId, String name, String address) {
        this.storeId = storeId;
        this.name = name;
        this.address = address;
    }

    @Override
    public Long id() {
        return storeId;
    }

    @Override
    public String toString() {
        return "Store{" +
            "storeId=" + storeId +
            ", name='" + name + '\'' +
            ", address='" + address + '\'' +
            '}';
    }
}

User.java

public class User implements QueryResult<Long> {
    private Long userId;
    private String name;
    private String email;

    public User(Long userId, String name, String email) {
        this.userId = userId;
        this.name = name;
        this.email = email;
    }

    @Override
    public Long id() {
        return userId;
    }

    @Override
    public String toString() {
        return "User{" +
            "userId=" + userId +
            ", name='" + name + '\'' +
            ", email='" + email + '\'' +
            '}';
    }
}

사용

User user = new User(1L, "윤호", "test1234@gmail.com");
Store store = new Store(1L, "윤호가게", "강남구 봉은사로");
System.out.println(user);
System.out.println(store);

generic 메서드

제네릭 메서드는 매개 타입과 리턴 탕비으로 타입 파라미터를 갖는 메서드를 말한다.

public <타입파라미터...> 리턴타입 메서드명(매개변수, ...) {...}

Util.java

public class Util {
    public static <T> Box<T> boxing(T t) {
        Box<T> box = new Box<T>();
        box.set(t);
        return box;
    }
}

Box.java

public class Box<T> {
    private T t;

    public T get() {
        return t;
    }

    public void set(T t) {
        this.t = t;
    }
}

사용
은 명시적으로 사용해도 되고 사용하지 않아도 된다

Box<Integer> box1 = Util.<Integer>boxing(100);
Box<String> box2 = Util.boxing("홍길동");

제네릭 타입의 제한과 와일드카드

제네릭도 마찬가지로 extends와 super를 이용할 수 있다

<T extends 상위타입>
<T super 하위타윕>

<?> // 와일드카드
<? extends 상위타입>
<? super 하위타입>

이해를 돕기위해 책에 있는 간단한 예제를 가져와 봤습니다.

Course<?> // Person, Worker, Student, HightStudent가 올수 있습니다
Course<? extends Student> // Student, HighStudent만 올 수 있습니다
Course<? super Worker> // Worker와 Person만 올 수 있습니다

참조

블로그 이미지

사용자 yhmane

댓글을 달아 주세요