1. Supplier
@FunctionalInterface
public interface Supplier<T> {
T get();
}
사용 예시
public static void main(String[] args) {
Supplier<String> myStringSupplier = () -> {return "hello world!";};
myStringSupplier.get();
Supplier<Double> myRandomDoubleSupplier = () -> Math.random();
System.out.println("myRandomDoubleSupplier.get() = " + myRandomDoubleSupplier.get());
printRandomDoubles(myRandomDoubleSupplier, 5);
}
public static void printRandomDoubles(Supplier<Double> randomSupplier, int count){
for (int i=0; i<count; i++){
System.out.println("randomSupplier = " + randomSupplier.get());
}
}
2. Consumer
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
안에서 프로세스를 하고 리턴하는 것은 없다.
스트링을 받아서 아무것도 하지 않고 print만 하고 끝낸다.
public static void main(String[] args) {
Consumer<String> myStringConsumer = (String string) ->{
System.out.println("string = " + string);
};
myStringConsumer.accept("hello");
}
여러 가지 consumer를 패스해서 다양한 Integer를 프로세스 해보자.
public static void main(String[] args) {
Consumer<String> myStringConsumer = (String string) -> System.out.println("string = " + string);
myStringConsumer.accept("hello");
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Consumer<Integer> myIntegerProcessor = (Integer x) -> System.out.println("process integer: " + x);
Consumer<Integer> myIntegerProcessor2 = (Integer x) -> System.out.println("process in different way: " + x);
}
public static void process(List<Integer> inputs, Consumer<Integer> processor){
for (Integer input : inputs) {
processor.accept(input);
}
}
public static <T> void process2(List<T> inputs, Consumer<T> processor){
for (T input : inputs) {
processor.accept(input);
}
}
3. BiConsumer
@FunctionalInterface
public interface BiConsumer<T, U> {
/**
* Performs this operation on the given arguments.
*
* @param t the first input argument
* @param u the second input argument
*/
void accept(T t, U u);
/**
* Returns a composed {@code BiConsumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code BiConsumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
2개의 타입을 받아서 consume한다.
사용 예시
public static void main(String[] args) {
BiConsumer<String, Integer> printValues = (name, age) -> {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
};
printValues.accept("kim", 25);
BiConsumer<Integer, Integer> performOperation = (a, b) -> {
int sum = a + b;
int difference = a - b;
int product = a * b;
System.out.println("Sum: " + sum);
System.out.println("Difference: " + difference);
System.out.println("Product: " + product);
};
performOperation.accept(10, 5);
}
4. Predicate
@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();
}
}
value를 테스트 하고 boolean을 리턴하는 메서드를 구현한다.
사용 예시
public class PredicateExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -2, 0, -5);
Predicate<Integer> isEven = number -> number % 2 == 0;
// numbers 리스트에서 짝수만 필터링하여 출력
System.out.println("Even numbers:");
numbers.stream()
.filter(isEven)
.forEach(System.out::println);
// 양수를 판별하는 Predicate
Predicate<Integer> isPositive = number -> number > 0;
// numbers 리스트에서 양수만 필터링하여 출력
System.out.println("Positive numbers: " + filter(numbers, isPositive));
System.out.println("Non-Positive numbers: " + filter(numbers, isPositive.negate()));
}
public static <T> List<T> filter(List<T> inputs, Predicate<T> predicate){
List<T> output = new ArrayList<>();
for (T input : inputs) {
if (predicate.test(input)){
output.add(input);
}
}
return output;
}
}
5. Comparator
java.util에 Comparator Interface를 알아보자.
FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
.
.
.
.
.
.
}
Comparator를 사용하여 비교하는 예시 코드를 작성해보자.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 30));
people.add(new Person("Charlie", 20));
// 이름을 기준으로 오름차순으로 정렬하는 Comparator
Comparator<Person> nameComparator = (person1, person2) -> person1.getName().compareTo(person2.getName());
Comparator<Person> nameComparator2 = Comparator.comparing(Person::getName);
// 나이를 기준으로 내림차순으로 정렬하는 Comparator
Comparator<Person> ageComparator = (person1, person2) -> person2.getAge() - person1.getAge();
Comparator<Person> ageComparator2 = Comparator.comparingInt(Person::getAge).reversed();
// 이름을 기준으로 오름차순으로 정렬
people.sort(nameComparator);
System.out.println("Sorted by name (ascending):");
for (Person person : people) {
System.out.println(person.getName() + " - " + person.getAge());
}
// 나이를 기준으로 내림차순으로 정렬
people.sort(ageComparator);
System.out.println("Sorted by age (descending):");
for (Person person : people) {
System.out.println(person.getName() + " - " + person.getAge());
}
}
Person 객체는 name과 age라는 속성을 갖고 있다. Comparator<Person>를 사용하여 name과 age를 기준으로 정렬하는 nameComparator와 ageComparator를 정의할 수 있다.
people 리스트에 여러 Person 객체를 추가한 후, nameComparator를 사용하여 이름을 기준으로 오름차순으로 정렬하고 출력한다. 그 후 ageComparator를 사용하여 나이를 기준으로 내림차순으로 정렬하고 출력한다.
이처럼 Comparator를 사용하면 정렬 기준을 유연하게 변경할 수 있으며, 다양한 정렬 방식을 구현할 수 있다.
반응형
'Programming > Java, Spring' 카테고리의 다른 글
Java의 Arrays.sort()를 사용할 때 원시형 타입이 아닌 참조형 타입으로 전달하면 성능 향상이 가능하다 (Dual-Pivot Quicksort, Tim Sort) (0) | 2023.06.08 |
---|---|
자바의 method reference (0) | 2023.06.03 |
자바의 Functional Interface와 andThen 메서드 이해하기 (0) | 2023.06.02 |
자바와 함수형 프로그래밍 (0) | 2023.06.02 |
[Spring Cloud] Kafka 를 사용한 마이크로서비스 간 데이터 전송, 단일 데이터베이스를 사용해 동기화 문제를 해결하기 (0) | 2023.06.02 |