Predicate

공병주(Chris)·2022년 3월 6일
0

Java를 탄탄히

목록 보기
5/9

Oracle에 공식 홈페이지에 나와 있는 Predicate에 대한 명세이다.

  • jdk 1.8부터 추가되었다.
  • Predicate<T> 와 같이 타입 매개변수를 가진다.
  • 함수형 인터페이스로써 추상메소드는 test(T t) 하나이고 나머지 메소드들은 static, default 메소드다.

Predicate의 메소드

1. test


boolean 타입 메소드로매개 변수 하나를 받아 매개 변수가 논리식에 맞는지 반환한다.

public class User {
    private final String name;
        
    public User(final String name, final int age) {
        this.name = name;
    }
        
    public boolean hasName(final String name) {
        return this.name.equals(name);
    }
}
@Test
void test_Test() {
    final Predicate<User> namePredicate = (user) -> user.hasName("Chris");
    assertThat(namePredicate.test(new User("Chris"))).isTrue(); //pass
    assertThat(namePredicate.test(new User("John"))).isFalse(); //pass
}

위와 같이 lambda로 처리할 수 있고 매개변수 값 하나를 받아 값이 식을 만족하는지 반환한다.

2. and

default 메소드로 and 메소드로 다수의 Predicate들을 Chaning식으로 연결할 수 있다.

다른 Predicate를 받아 자신의 test 결과 값과 받아온 Predicate의 test의 결과 값을 && 연산자로 계산해 반환한다.

public class User {
    private final String name;

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

    public boolean hasNameStartingWith(final String word) {
        return name.startsWith(word);
    }

    public boolean hasNameLongerThan(final int nameLength) {
        return name.length() > nameLength;
    }
}
class UserTest {
    @Test
    void and_Test() {
        final Predicate<User> firstWordPredicate = (user) -> user.hasNameStartingWith("C");
        final Predicate<User> nameLengthPredicate = (user) -> user.hasNameLongerThan(6);
        final User first = new User("Chris");
        final User second = new User("Christian");

        final boolean firstActual = firstWordPredicate.and(nameLengthPredicate).test(first);
        final boolean secondActual = firstWordPredicate.and(nameLengthPredicate).test(second);

        assertThat(secondActual).isTrue(); //pass
        assertThat(firstActual).isFalse(); //pass
    }
}

위와 같이 체이닝으로 연결된 모든 Predicate에서 true가 반환되어야 한다.

3. negate

default 메소드로, 쉽게 말해서 ! 연산이라고 생각하면 된다. true면 false를 false면 true를 반환한다.

public class User {
    private final String name;

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

    public boolean hasNameStartingWith(final String word) {
        return name.startsWith(word);
    }

    public boolean hasNameLongerThan(final int nameLength) {
        return name.length() > nameLength;
    }
}
class UserTest {
    @Test
    void negate_Test() {
        final Predicate<User> namePredicate = (user) -> user.hasNameStartingWith("C");
        final User chris = new User("Chris");

        final boolean negatedActual = namePredicate.negate().test(chris);
        final boolean notNegatedActual = namePredicate.test(chris);

        assertThat(notNegatedActual).isTrue(); //pass
        assertThat(negatedActual).isFalse();   //pass
    }
}

4. or

default 메소드로, and 메소드와 같이 체이닝 방식으로 쓰이고 연결된 모든 Predicate의 반환 값을 or 연산자로 계산해 반환한다.

public class User {
    private final String name;

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

    public boolean hasNameStartingWith(final String word) {
        return name.startsWith(word);
    }

    public boolean hasNameLongerThan(final int nameLength) {
        return name.length() > nameLength;
    }
}
class UserTest {
    @Test
    void or_Test() {
        final Predicate<User> firstWordPredicate = (user) -> user.hasNameStartingWith("C");
        final Predicate<User> nameLengthPredicate = (user) -> user.hasNameLongerThan(6);
        final User first = new User("Chris");

        final boolean actual = firstWordPredicate.or(nameLengthPredicate).test(first);

        assertThat(actual).isTrue(); //pass
    }
}

Chris는 C로 시작하지만(true), 이름의 길이가 6보다 길지 않다(false)
이 두 결과를 or 연산자로 계산하면 true가 반환된다.

5. isEqual

isEqual은 static 연산자로 equals라고 생각하면 된다.

public class User {
    private final String name;

    public User(final String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
class UserTest {
    @Test
    void and_Test() {
        final User first = new User("Chris");
        final User second = new User("Chris");

        final Predicate<User> chrisPredicate = Predicate.isEqual(first);

        final boolean actual = chrisPredicate.test(second);

        assertThat(actual).isTrue(); //pass
    }
}

Predicate.isEqual(first); → first.equals() 라고 생각하면 된다.

equals가 override 되있지 않으면 의도대로 작동하지 않는다.

6. not

default 메소드로 java 11에 추가되었다. negate와 같이 논리식 결과의 반대를 반환하지만 사용방식이 다르다.

class UserTest {
    @Test
    void and_Test() {
        final User first = new User("Chris");
        final User second = new User("Chris");

        final Predicate<User> chrisPredicate = Predicate.isEqual(first);
        final Predicate<User> notPredicate = Predicate.not(chrisPredicate);

        final boolean actual = notPredicate.test(second);

        assertThat(actual).isFalse();
    }
}

IntPredicate, DoublePredicate, LongPredicate


Predicate<Integer> Predicate<Long> Predicate<Double>과 동일하게 동작한다.

그런데 IntPredicate, LongPredicate, DoublePredicate이 존재하는 이유는 아래와 같다.

final List<Integer> numbers = Arrays.asList(1, 2, 3, 4);

우리는 Integer(Wrapper) 타입의 컬렉션에 int(primitive)를 넣어서 사용할 수 있다. Auto-Boxing 덕분이다.

여기서 주목할 점은 결국 Auto-Boxing을 해줘야한다는 것이다.

Predicate<Integer>는 가능한데 Predicate<int>는 불가능 하다.
따라서 정수에 대한 Predicate Auto-Boxing이 일어난다. 이런 불필요한 과정을 없애기 위해서 존재하는 것이다.

❗️ 또한 Predicate와는 달리 and(), negate(), or(), test() 이 네가지 메소드만 제공한다.
not과 isEqaul은 제공하지 않는다. 생각해보면 primitive 타입이 isEqual은 당연히 필요없을 것이다.
not은 java 11에 Predicate와 같이 추가해주지 않았을까?

BiPredicate

BiPredicate<T, U> 는 2개의 인자를 받는 Predicate라고 생각하면 된다.

and(), negate(), or(), test() 이 네가지 메소드만 제공한다.
isEqual은 역시 T, U가 다른 타입이기 때문에 필요없다. (같은 타입이 들어올 수도 있지만 말이야)

public class User {
    private final Name name;

    public User(final String name) {
        this.name = new Name(name);
    }

    public boolean hasName(final Name name) {
        return this.name.equals(name); //Name은 equals가 override 되어있다.
    }
}
class UserTest {
    @Test
    void and_Test() {
        final User chris = new User("Chris");
        final Name chrisName = new Name("Chris");

        final BiPredicate<User, Name> biPredicate = (user, name) -> user.hasName(name);

        final boolean actual = biPredicate.test(chris, chrisName);

        assertThat(actual).isTrue(); //pass
    }
}

위와 같이 test 메소드를 사용하면 되고 나머지 메소드들은 동일하다.


참고자료
https://docs.oracle.com/en/java/javase/11/docs/api/index.html![](https://velog.velcdn.com/images%2Fbyeongju%2Fpost%2F83383930-cd31-4528-bb1e-bdb22ad508ee%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.55.05.png)

https://codechacha.com/ko/java8-predicate-example/

https://stackoverflow.com/questions/37813271/why-different-predicate-interfaces-n-java-8

0개의 댓글