반응형
반응형

📝Junit5

JunitJava에서 가장 많이 사용되는 테스트 프레임워크로 단위 테스트를 작성하고 실행하는데 사용됩니다.

 

 

📝테스트 코드 작성팁

  • 일반적으로 테스트 코드를 작성할 때에는 순수 자바코드로만 실행 가능하게끔 만드는 게 좋다. (스프링에 종속되거나 하지 않게끔)
  • given, when, then의 원칙을 지키면서 작성하면 좋다.
  • 테스트가 실패하는 경우도 테스트를 만들어야한다.

 

📝Junit5 예제 코드

package com.spring.core.member;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class MemberServiceTest {

  MemberService memberService = new MemberServiceImpl();

  @Test
  void join() {
    // given
    Member member = new Member (1L, "memberA", Grade.VIP);

    // when
    memberService.join(member);
    Member findMemmber = memberService.findMember(1L);

    // then
    Assertions.assertThat(member).isEqualTo(findMemmber);

  }

}

@Test단위테스트 실행 단위를 설정한다.

 

package com.spring.core.discount;

import static org.assertj.core.api.Assertions.assertThat;

import com.spring.core.member.Grade;
import com.spring.core.member.Member;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class RateDiscountPolicyTest {
  RateDiscountPolicy discountPolicy = new RateDiscountPolicy();
  @Test
  @DisplayName("VIP는 10% 할인이 적용되어야 한다.")
  void vip_o() {
    //given
    Member member = new Member(1L, "memberVIP", Grade.VIP);
    //when
    int discount = discountPolicy.discount(member, 10000);
    //then
    assertThat(discount).isEqualTo(1000);
  }
  @Test
  @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
  void vip_x() {
    //given
    Member member = new Member(2L, "memberBASIC", Grade.BASIC);
    //when
    int discount = discountPolicy.discount(member, 10000);
    //then
    assertThat(discount).isEqualTo(0);
  }
}

@DisplayName으로 Test 명명이 가능하다. (실패케이스와 성공케이스 두개 다 설정)

 

 

class MemberServiceTest {
  MemberService memberService;
  @BeforeEach
  public void beforeEach() {
    AppConfig appConfig = new AppConfig();
    memberService = appConfig.memberService();
  }
  
  @AfterEach
  void afterEach() {
     memberRepository.clearStore();
  }
}

@BeforeEach테스트 코드를 실행하기 전에 실행시켜야할 것들을 작성할 수 있다.

@AfterEach의 경우 테스트 코드를 실행한 후 실행시켜야할 것들을 작성할 수 있다.

 

반응형
반응형

 

 

 

반응형
반응형

Enum은 열거형이라는 뜻을 가지고 있다 Enum이라는 타입을 사용할 시 Enum에서 설정한 데이터만 사용할 수 있다 예를 들면 '요일' "월, 화, 수, 목, 금, 토, 일"이 있다 해당 요일 외에 데이터는 받지 않기 때문에 컴파일 에러로 사전에 버그를 잡아낼 수 있다

 

Enum에 다양한 메소드가 존재한다

 

📝name()

enum Week {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}



Week week = Week.FRIDAY;

String weekName = week.name();
System.out.println(weekName); // FRIDAY

enum의 값을 String으로 반환해준다

 

📝예제

import java.util.Arrays;

public enum AreaEnum {

    SEOUL("서울",1),
    INCHEON("인천",2),
    GYEONGGI("경기",31);

    private final String name;
    private final int code;


    AreaEnum(String name, int code) {
        this.name = name;
        this.code = code;
    }

    public String getName() {
        return this.name;
    }

    public int getCode() {
        return this.code;
    }


    public static String getByCode(int code) {
        Arrays.stream(values())
                .filter(area -> area.getCode() == code)
                .findFirst()
                .map(AreaEnum::getName)
                .orElse(null);
    }

}
반응형
반응형
public class SwitchExample {
    public static void main(String[] args) {
        int dayOfWeek = 3;

        String dayType = switch (dayOfWeek) {
            case 1, 2, 3, 4, 5 -> "Weekday";
            case 6, 7 -> "Weekend";
            default -> throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeek);
        };

        System.out.println("Day type: " + dayType);
    }
}

Java17부터 →을 이용해 각각 break를 안 사용해도 되고 가독성을 높여주는 Syntactic Sugar이다

반응형
반응형

📝메소드 레퍼런스

메소드 레퍼런스란 클래스에 포함된 메소드를 생략해 표현이 가능하다 이 점을 이용해 간략할 수도 있고 무엇보다 일급객체의 특징인 파라미터에 함수를 넣을 수 있다. (물론 적절한 함수형 인터페이스의 시그니처[함수형 인터페이스에 맞는 포맷]이면 메소드 레퍼런스를 안 쓰고도 가능하다)

 

  • System.out::println
Arrays.asList(1, 2, 3, 4, 5)
      .forEach(System.out::println);

forEach안에 System.out::println [클래스명:메소드명]을 넣을 수 있다.

함수형 인터페이스를 이용하면 파라미터 및 반환타입 추론이 가능하기 때문에 생략할 수 있다.

 

  • 나만의 클래스:메소드를 축약해서 사용해보기

public class MethodLambdaTest {

    @Test
    public void testing(){

        System.out.println(
            Arrays.asList(new BigDecimal("10.0"), new BigDecimal("23"), new BigDecimal("5"))
                  .stream()
                   // .sorted()                                 // ✅ 동일
                   // .sorted((bd1, bd2) -> bd1.compareTo(bd2)) // ✅ 동일
                   // .sorted(BigDecimalUtil::compare)          // ✅ 동일
                  .sorted(BigDecimal::compareTo)                // ✅ 동일
                  .collect(toList())
        ); // [5, 10.0, 23]
    }
}

class BigDecimalUtil {
    public static int compare(BigDecimal bd1, BigDecimal bd2){
        return bd1.compareTo(bd2);
    }
}

 

  • sorted 함수 설명
Stream<T> sorted(Comparator<? super T> comparator) 
...

@FunctionalInterface
public interface Comparator<T> {
   int compare(T o1, T o2)
}

 

sorted는 Comparator를 받고 Comparator의 경우 (T, T) -> int이기 때문에 생략해도 알아서 비교값들이 들어간다

 

 

 

  • anyMatch에 equals 함수 적용시키기
@Test
public void testing(){
	final String targetString = "c";
    System.out.println(
        Arrays.asList("a", "b", "c", "d")
                .stream()
                //.anyMatch(x -> x.equals("c")) // ✅ 동일
                //.anyMatch(String::equals)     // ❌ 동작 X
                .anyMatch(targetString::equals) // ✅ 동일
                //.anyMatch("c"::equals)        // ✅ 동일
    );
}

 

  • anyMatch에 String::equals 동작 안 하는 이유
boolean anyMatch(Predicate<? super T> predicate);

...

@FunctionalInterface
public interface Comparator<T> {
   int compare(T o1, T o2)
}

anyMatch의 경우 Predicate 받고 Predicate의 경우 T -> boolean이기 때문에 String::equals의 반환 타입과 맞지만
"c".eqauls("d")와 같이 두개의 비교값 즉, 두개 파라미터가 필요하기 때문에 시그니처가 맞지 않아서 동작하지 않는다.

반응형
반응형

📝Stream

Java8에서 나온 것으로 매개변수들이 함수형 인터페이스 기반으로 만들어져 Stream에서 쓰는 내부 함수들은 Predicate, Function처럼 함수형 인터페이스를 사용한다

// Stream 인터페이스 내부 함수
Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

Stream 장점으로는 Lazy Evaluation으로 이 의미는 즉, 불필요한 연산을 피한다는 의미이다.

배열의 값을 하나씩 읽어서 순차적으로 진행시킨다.  밑에 코드에서 보는 것 처럼 i < 6이라는 조건절이 있는 경우 1 ~ 10까지 i < 6처리한 후에 남은 애들이 2번째  조건 절인 i % 2 == 0을 거치는게 아니라 "1"이 i < 6을 거친 후 i % 2 == 0을 거치는 순차적인 Iterator로 돌아가게 된다.

 

Stream 동작 트래킹

@Test
public void test(){
    final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    System.out.println(
        list.stream()
            .filter(i -> {
                System.out.println(i + " : i < 6");
                return i<6;
            })
            .filter(i -> {
                System.out.println(i + " : i % 2 == 0");
                return i%2==0;
            })
            .collect(Collectors.toList())
    );
    /**
     * 1 : i < 6
     * 1 : i % 2 == 0
     * 2 : i < 6
     * 2 : i % 2 == 0
     * 3 : i < 6
     * 3 : i % 2 == 0
     * 4 : i < 6
     * 4 : i % 2 == 0
     * 5 : i < 6
     * 5 : i % 2 == 0
     * 6 : i < 6
     * 7 : i < 6
     * 8 : i < 6
     * 9 : i < 6
     * 10 : i < 6
     * [2, 4]
     */
}

 

여기서 예제를 보면 1 ~ 10을 1번 필터를 다 거친 후 남은 걸 2번 필터를 거치거나 각 Iterator들을 개별로 1번 → 2번을 거치거나 별 차이는 없어보인다 하지만 밑에 같은 예제의 경우 이야기가 달라진다. findFirst로 1개의 정상 값만 나오면 바로 종료시켜버린다 물론 IF로직같은 걸로 1개 일 때 종료하던가 로직 처리가 가능하지만 가독성 및 효율성이 떨어진다.

 

Stream 동작 트래킹

@Test
public void test(){
    final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    System.out.println(
        list.stream()
            .filter(i -> {
                System.out.println(i + " : i < 6");
                return i<6;
            })
            .filter(i -> {
                System.out.println(i + " : i % 2 == 0");
                return i%2==0;
            })
            .findFirst()
    );
    /**
     * 1 : i < 6
     * 1 : i % 2 == 0
     * 2 : i < 6
     * 2 : i % 2 == 0
     */
}

 

동작 방식을 이제 알아봤으니 이걸 이용해 익숙해져보자

 

Stream 사용해보기

package hello.exception;

import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.stream.Collectors.*;

public class StreamTest {

    @Test
    public void intStreamTest(){

        /** ---- 연속적인 값 할당 ---- **/
        IntStream.range(0, 10).forEach(i -> System.out.println("range : " + i)); // range : 0 range : 1 .... range : 9
        // IntStream.iterate(1, i -> i + 1).forEach(i -> System.out.println("iterate : " + i)); // iterate : 1 ... iterate : 21
        IntStream.rangeClosed(0, 10).forEach(i -> System.out.println("rangeClose : " + i)); // rangeClose : 0 rangeClose : 1 ... rangeClose : 10

        /** ---- Sum ---- **/
        long sum = IntStream.of(1, 2, 3, 4, 5)
                .sum();

        System.out.println("sum : " + sum); // sum : 15

    }

    @Test
    public void streamTest(){

        /** ---- of, forEach ---- **/
        Stream.of(1, 2, 3, 4, 5)
                .forEach(i -> System.out.println("stream of : " + 1)); // stream of : 1 ... stream of : 5

    }

    @Test
    public void streamChaningTest(){

        /** ---- stream chaning ---- **/
        final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Optional<Integer> result =
                numbers.stream() // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
                        .filter(number -> number > 3) // 4, 5, 6, 7, 8, 9, 10
                        .filter(number -> number < 9) // 4, 5, 6, 7, 8
                        .map(number -> number * 2)    // 8, 10, 12, 14, 16
                        .filter(number -> number < 15) // 8, 10, 12, 14
                        .findAny(); // 8

        System.out.println("stream result : " + result.get());


        // Intermediate Operation Method [Stream 리턴 -> chaning 가능]
        // Terminated Operation Method   [Stream 외 리턴]
    }
    @Test
    public void streamChaningTest2(){

        final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        /** ---- collection(toList()) ---- **/
        List<String> result = numbers.stream()
                .filter(number -> number > 8) // 9, 10
                .map(number -> "#" + number)
                .collect(toList());

        System.out.println("result : " + result); // result : [#9, #10]

        /** ---- collection(joining) ---- **/
        String result2 = numbers.stream() // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
                .filter(number -> number > 8) // 4, 5, 6, 7, 8, 9, 10
                .map(number -> "#" + number)
                .collect(joining(",","[","]"));

        System.out.println("result2 : " + result2); // result2 : [#9,#10]

        /** ---- collection(toSet()) ---- **/
        Set<String> result3 = numbers.stream() // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
                .filter(number -> number > 8) // 4, 5, 6, 7, 8, 9, 10
                .map(number -> "#" + number)
                .collect(toSet());

        System.out.println("result3 : " + result3); // result3 : [#10, #9]

        /** ---- distinct ---- **/
        List<String> result4 =
                Stream.of(1, 3, 3, 5, 5)
                        .filter(number -> number > 2)
                        .map(number -> number * 2)
                        .map (number -> "#" + number) // [#6, #6, #10, #10]
                        .distinct()                   // [#6, #10]
                        .collect(toList());

        System.out.println("result4 : " + result4); // result4 : [#6, #10]

        /** —— count —— **/
        long result5 =
                Stream.of(1, 3, 3, 5, 5)
                        .filter(number -> number > 2)
                        .map(number -> number * 2)
                        .map (number -> "#" + number) // [#6, #6, #10, #10]
                        .distinct()                   // [#6, #10]
                        .count();

        System.out.println("result5 : " + result5); // result5 : 2

    }
    
	@Test
    public void reduceTest(){
        /** —— reduce —— **/
        List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer result =
                numbers.stream()
                        .reduce(100, (x, y) -> {
                            System.out.println("x : " + x + ", y :" + y);
                            return x+y;
                        });

        // reduce : 반복된 패턴을 추상화 할 수 있다.
        System.out.println("result : " + result);

        /**
         * x : 100, y :1
         * x : 101, y :2
         * x : 103, y :3
         * x : 106, y :4
         * x : 110, y :5
         * x : 115, y :6
         * x : 121, y :7
         * x : 128, y :8
         * x : 136, y :9
         * x : 145, y :10
         * result : 155
         */

    }

}

 

 

 

🔗 참고 및 출처

https://dororongju.tistory.com/137

반응형
반응형

📝Optional

개발할 때 가장 많이 발생하는 예외중 하나는 NPE로서 NPE를 피하려면 null 여부 검사를 해야하는데 코드가 복잡하고 번거롭다 Java8에서 나온 Optional<T> 클래스를 이용해 NPE를 방지할 수 있도록 도와준다.

 

  • 참고로 자바스크립트에서는 Optional사용시 null이나 undefinedundefined를 반환시킨다 [Null에 대한 에러 발생 핸들링]

 

 

Optional 사용해보기

package hello.exception;

import java.util.Optional;

public class Main {
    public static void main(String[] args) throws Exception{

        /** ---- optional 값 체크 ---- **/
        Optional<String> optVal0 = Optional.of("abc");
        Optional<String> optVal2 = Optional.ofNullable(null); // Null 체크 NPE 발생 X
        // Optional<String> optVal1 = Optional.of(null);      // NullPointerException 발생

        System.out.println(optVal2);
        // hello 값이 있는 경우 : Optional[hello]
        // null인 경우         : Optional.empty

        /** ---- optional 초기화 ---- **/
        Optional<String> optVal3 = null;             // 사용 금지!!
        Optional<String> optVal4 = Optional.empty(); // 정상 초기화 작업

        /** ---- optional 값 체크해서 Null 핸들링 하기 ---- **/
        Optional<String> optVal = Optional.ofNullable("test");
        String str1 = optVal.get();                                  // optVal에 저장된 값 반환, Null이면 예외 발생
        String str2 = optVal.orElse("");                             // optVal에 저장된 값 반환, Null이면 orElse안에 값 반환    [많이 사용] | 매개변수 : T
        String str3 = optVal.orElseGet(()->"");                      // optVal에 저장된 값 반환, Null이면 orElseGet안에 값 반환 [많이 사용] | 매개변수 : Supplier
        String str4 = optVal.orElseThrow(NullPointerException::new); // null이면 false, 아니면 true를 반환

        // ifPresent -> null이 아닐 때만 수행한다
        Optional.ofNullable("notNull").ifPresent(System.out::println);

    }

}

Optional이라는게 NPE를 체크하기 위해서 사용하는데 Optional.of의 경우 이미 null이 아니라는 걸 알고 있어야 쓸 수 있기 때문에 Optional.ofNullable을 사용합니다.

 

  • Optional.of
    • null이 아닌 값을 감싸는 Optional 객체를 생성합니다. 인자가 null이면 NullPointerException을 던집니다.
  • Optional.ofNullable
    • 값이 null일 수 있는 경우 사용합니다. 값이 null이면 빈 Optional 객체를 반환합니다.
  • Optional.empty()
    • 빈 Optional을 반환합니다. null을 넣는게 아니라 빈 Optional을 넣어서 초기화 시킵니다.
  • orElseThrow
    • null이 아닌 경우는 해당 값을 반환하고 null인 경우 Supplier가 제공하는 예외를 던집니다.
  • ifPresent
    • 값이 존재하면 true, 그렇지 않으면 false를 반환합니다. 만약 ifPresent의 매개변수 값에 함수가 들어간 경우 Null이 아닐 때 실행되게 됩니다.

 

orElse vs orElseGet 

package hello.exception;


import org.junit.jupiter.api.Test;

import java.util.Optional;

public class orElseTest {

    @Test
    public void testOptionalElse(){

        String notNull ="not Null";
        String nulll = null;

        String resultNullOrElse = Optional.of(notNull).orElse(welcome());           // welcome 함수 실행
        String resultNotNullOrElse = Optional.ofNullable(nulll).orElse(welcome());  // welcome 함수 실행

        System.out.println("notNullOrElse : " + resultNullOrElse); // notNullOrElse : not Null
        System.out.println("NullOrElse : " + resultNotNullOrElse); // NullOrElse : WelComeTo Blog

        System.out.println("===============================");

        String resultNullOrElseGet = Optional.of(notNull).orElseGet(this::welcome);           // doesn't call welcome
        String resultNotNullOrElseGet = Optional.ofNullable(nulll).orElseGet(this::welcome);  // welcome 함수 실행

        System.out.println("notNullOrElseGet : " + resultNullOrElseGet); // notNullOrElseGet : not Null
        System.out.println("NullOrElseGet : " + resultNotNullOrElseGet); // NullOrElseGet : WelComeTo Blog

        /**
         * ---- 출력결과
         * welcome!!
         * welcome!!
         * notNullOrElse : not Null
         * NullOrElse : WelComeTo Blog
         * ===============================
         * welcome!!
         * notNullOrElseGet : not Null
         * NullOrElseGet : WelComeTo Blog
         *
         */

    }

    public String welcome(){
        System.out.println("welcome!!");
        return "WelComeTo Blog";
    }
}

null도 아닌데 실행시키는 경우 말고는 orElseGet을 대체적으로 사용합니다.

 

  • orElse
    • (T other)로 값을 받는데 얘는 Eager Evaluation으로 동작하기 때문에 Null이든 말든 orElse의 값이 있으면 실행하게 됩니다.
  • orElseGet
    • Supplier의 get을 이용하는데 Supplier의 get이 Lazy Evaluation으로 동작하기 때문에 결과가 Null 이여야지만 동작하게 됩니다. 그래서 Null인 경우 아예 동작을 하지 않는 것이죠

 

 

🔗 참고 및 출처

https://erjuer.tistory.com/102

반응형
반응형

 

📝BigDecimal

Java 언어에서 숫자를 정밀하게 저장하고 표현할 수 있는 유일한 방법이다.
소수점을 저장할 수 있는 가장 크기가 큰 타입인 double은 소수점의 정밀도에 있어 한계가 있어 값이 유실될 수 있다.
Java 언어에서 돈과 소수점을 다룬다면 BigDecimal은 선택이 아니라 필수이다

 

BigDecimal 단점

  • 느린 연산 처리 속도
  • 기보타입에 비해 많은 양의 리소스 차지
  • 기본 타입보다 조금 불편한 사용법

 

double vs BigDecimal

  • double 타입
    • 내부적으로 수를 저장할 때 이진수의 근사치를 저장한다 (64비트 부동 소수점 자료형)
  • BigDecimal 타입
    • 내부적으로 수를 십진수로 저장하여 아주 작은 수과 큰 수의 연산에 대해 거의 무한한 정밀도를 보장한다

 

package hello.exception;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

public class study {

    public static void main(String[] args) {

        BigDecimal bigDecimalA = new BigDecimal("10.0000000000");
        BigDecimal bigDecimalB = new BigDecimal("3.000000000");

        DecimalFormat decimalFormat = new DecimalFormat("0.0000000000000000000000");

        double a = 10.0000000000;
        double b = 3.000000000;

        /** a / b -> 기대값: 3.333333333333 **/

        System.out.println(decimalFormat.format(a / b)); // 출력값 : 3.3333333333333335000000


        // 20자리까지 표현하고 그 이후에는 반올림을 한다
        BigDecimal result = bigDecimalA.divide(bigDecimalB, 20, RoundingMode.HALF_UP);
        System.out.println(result); // 출력값 : 3.33333333333333333333


    }
}

 

🔗 참고 및 출처

https://jsonobject.tistory.com/466#:~:text=BigDecimal%EC%9D%80%20Java%20%EC%96%B8%EC%96%B4%EC%97%90%EC%84%9C,%EC%9D%B4%20%EC%9C%A0%EC%8B%A4%EB%90%A0%20%EC%88%98%20%EC%9E%88%EB%8B%A4.

반응형
반응형

📝익명 클래스

프로그램에서 일시적으로 한번만 사용되고 버려지는 객체입니다.

일시적으로 사용된다는 것은 재사용이 되지 않거나 그럴 필요가 없다는 것인데 이건 확장성이 좋지 않다는 걸 의미합니다.

OOP 측면에서는 확장성 있게 만드는 걸 지향하지만 오히려 설계를 해치는 경우가 있습니다.

 

이러한 경우에 익명 클래스를 많이 씁니다.

 

    1. 프로그램 한번만 사용되어야하는 객체의 경우 [ UI 이벤트 처리, 스레드 객체 등... ]

 

밑에는 UI 이벤트 처리에 대한 예제이다 사실상 어거지로 만든 거고 클래스로도 처리가 가능할 거 같은데 설계 부분에 솔직히 이해는 가지만 크게 와닿진 않는다

 

Button를 생성해서 인터페이스를 즉각 구현해서 익명 객체(매개변수)를 구현한 예제이다.

 

RingListener

package org.example;

public interface RingListener {

    public void ring ();
}

 

Button

package org.example;

public class Button {
    public void ring(RingListener listener){
        listener.ring();
    }
}

 

Main

package org.example;

public class Main {

    public static void main(String[] args) {

    Button button = new Button();
    button.ring(new RingListener() {
        @Override
        public void ring() {
            System.out.println("1회용 ring ring");
        }
    }); // 1회용 ring ring
  }
}

 


    2. 비즈니스 로직이 제각각이며 재사용성이 없어서 매번 클래스를 생성해야하는 경우 [ 아래와 예제와 같이 버튼이 여러개인데 각각 다른 역할의 응답을 하는 경우 버튼이 많아질수록 기하급수적으로 늘어난다]

 

Button

package org.example;

public class Button {

    void turnOn () {
        System.out.println("버튼을 킵니다.");
    }
}

 

Anonymous

package org.example;

public class Anonymous {


    /** 객체 생성해 재정의하기 **/
    Button button = new Button(){
        String name = "방 스위치";

        // 익명 클래스 안에서만 살아있기 때문에 사용 불가
        void turnOff(){
            System.out.println("Down 버튼을 눌러 " + name + "를 끕니다");
        }

        // 재정의를 했기 때문에 사용 가능
        @Override
        void turnOn(){
            System.out.println("Up 버튼을 눌러 " + name + "를 킵니다");
        }
    };


    /** 클래스안 메소드에서 객체 생성해 재정의하기 [1번과 동일하다] **/
    void method1(){

        Button button1 = new Button(){

            String name = "정답 스위치";

            // 익명 클래스 안에서만 살아있기 때문에 사용 불가
            void turnOff(){
                System.out.println("X 버튼을 눌러 " + name + "를 끕니다");
            }

            // 재정의를 했기 때문에 사용 가능
            @Override
            void turnOn(){
                System.out.println("O 버튼을 눌러 " + name + "를 킵니다");
            }

        };

        button1.turnOn();
    }

    /** 매개변수에 익명함수 사용해보기 **/
    void method2(Button button){
        button.turnOn();
    }
    
}

 

Main Thread

public class Main {
    public static void main(String[] args) {
    
        Anonymous button = new Anonymous();

        // 1번 방법으로 호출
        button.button.turnOn(); // 위 버튼을 눌러 방 스위치를 킵니다

        // 2번 방법으로 호출
        button.method1();       // O 버튼을 눌러 정답 스위치를 킵니다

        // 3번 방법으로 호출
        // 2번은 Anonymous에서 상세 내용을 작성한 거고 3번 방법은 그걸 밖에서 내가 정의해서 사용하는 차이가 있다.
        // 즉, 어디서 상세 내용을 재정의하냐에 따라 다름
        button.method2(new Button(){

            String name = "엘레베이터";

            // 익명 클래스 안에서만 살아있기 때문에 사용 불가
            void turnOff(){
                System.out.println("엑스 버튼을 눌러 " + name + "를 닫습니다.");
            }

            // 재정의를 했기 때문에 사용 가능
            @Override
            void turnOn(){
                System.out.println("동그라미 버튼을 눌러 " + name + "를 엽니다.");
            }

        }); // 동그라미 버튼을 눌러 엘레베이터를 엽니다.
    }
}

 

 

📝함수형 인터페이스

함수형 인터페이스란 1 개의 추상 메소드를 갖는 인터페이스로 여러 개의 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스라고 한다 (default method 또는 static method 는 여러 개 존재해도 상관 없다)

매번 함수형 인터페이스를 직접 만들어서 사용하는 건 번거로운 일로서 그래서 Java 에서는 기본적으로 많이 사용되는 함수형 인터페이스를 제공한다 기본적으로 제공되는 것만 사용해도 충분하기 때문에 직접 만드는 경우는 거의 없다.

 

예제 코드

@FunctionalInterface // 함수형 인터페이스 조건에 맞는지 검사
interface CustomInterface<T> {
    
    T myCall(); // abstract method 오직 하나!!

    // default method 는 존재해도 상관없음
    default void printDefault() {
     	System.out.println("Hello Default");
    }

    // static method 는 존재해도 상관없음
    static void printStatic() {
     	System.out.println("Hello Static");
    }
}

 

함수형 인터페이스 장점

  1. 같은 Input에 같은 Ouput을 제공하고 변경 개념이 아니라 복사되고 복사 된 것을 함수를 거쳐 결과를 내기 때문에 멀티스레드 공유자원에 안전하다
  2. 코드를 간결하고 가독성 있게 작성할 수 있다.

 

함수형 인터페이스 단점

  1. 익명 함수의 재사용이 불가능
  2. 재귀함수의 구현이 어렵다.
  3. 재사용이 불가능하므로 비슷한 함수 구현이 잦을 수 있다.
  4. 디버깅이 어렵다.

 

Java에서 제공하는 많이 쓰는 함수형 인터페이스

  1. Runnable           → 파라미터 없고 리턴값도 없는 경우                / 추상메소드 = run()
  2. Consumer<T>  → 파라미터 있고 리턴값 없는 경우                   / 추상메소드 = accept(T t)
  3. Supplier<T>      → 파라미터 없고 리턴값 있는 경우                   / 추상메소드 = get()
  4. Function<T,R>  → 파라미터 있고 리턴값 있는 경우                   / 추상메소드 = apply(T t) // T파라미터, R 반환값
  5. Operator<T>     → 파라미터 있고 리턴값 있는 경우                   / 연산용도, 임의의 타입 전달하고 임의의 타입 반환
  6. Predicate<T>    → 파라미터 있고 리턴값(boolean) 있는 경우  / 추상 메소드 = test(T t)

 

  • Runnable
public class Main {
    public static void main(String[] args) {
    	Runnable runnable = () -> System.out.println("Runnable run");
        runnable.run(); // Runnable run
    }
}

 

  • Consumer
public class Main {
    public static void main(String[] args) {
        Consumer<String> con = text -> System.out.println("parameter : " + text);
        con.accept("Consumer Testing!!");

        /** Consumer Chaining **/
        Consumer<String> cutFruit = fruit -> System.out.println(fruit + "를 깍습니다." ); // Apple를 깍습니다.
        Consumer<String> eatFruit = fruit -> System.out.println(fruit + "를 먹습니다.");  // Apple를 먹습니다.
        cutFruit.andThen(eatFruit).accept("Apple");
    }
}

 

Consumer 파생형

BiConsumer =>  파라미터를 2개 받아서 처리
DoubleConsumer ==> double타입으로 파라미터를 받아서 처리
IntConsumer ==> int타입으로 파라미터를 받아서 처리
LongConsumer ==> long타입으로 파라미터를 받아서 처리
ObjDoubleConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 double
ObjIntConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 int
ObjLongConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 long

 

public class Main {
    public static void main(String[] args) {

        BiConsumer<String, Integer> bi = (text, num) -> System.out.println("파리미터 2개 -> " + text + " + " + num);
        bi.accept("프로그래밍!!", 1234); // 파리미터 2개 -> 프로그래밍!! + 1234
    }
}

 

  • Supplier
public class Main {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "supplier has return but no params";
        System.out.println(supplier.get()); // supplier has return but no params
    }
}

 

Supplier 파생형

BooleanSupplier :  boolean 타입으로 반환
DoubleSupplier  :  double 타입으로 반환
IntSupplier     :  int 타입으로 반환
LongSupplier    :  long 타입으로 반환

 

  • Operator
public class Main {
    public static void main(String[] args) {
        BinaryOperator<Integer> binaryOperator = (num1, num2) -> num1 + num2;
        int result = binaryOperator.apply(5, 10);
        System.out.println(result); // 15
    }
}

Operator라는 인터페이스는 없다

 

BinaryOperator<T> : BiFunction<T,U,R>  상속 →  두개의 파라미터 전달하고 반환 ( 모두 데이터타입 동일 )

UnaryOperator<T> : Function<T,R> 상속          →  하나의 파라미터 전달하고 반환 ( 모두 데이터타입 동일 )

 

 

Operator 파생형

DoubleBinaryOperator : 두개의 파라미터 double 전달하고  double 반환 ( 모두 데이터타입 동일 )
DoubleUnaryOperator  : 하나의 파라미터 double 전달하고 double반환
IntBinaryOperator    : 두개의 파라미터 int 전달하고  int 반환 ( 모두 데이터타입 동일 )
IntUnaryOperator     : 하나의 파라미터 int 전달하고 int반환
LongBinaryOperator   : 두개의 파라미터 long 전달하고  long 반환 ( 모두 데이터타입 동일 )
LongUnaryOperator    : 하나의 파라미터 long 전달하고 long반환

 

 

  • Predicate
package org.example;

import java.util.function.*;

public class Main {
    public static void main(String[] args) {

        Predicate<String> isNameString = str -> str.equals("name");
        System.out.println("is String Name? test() -> " + isNameString.test("age"));
        // is String Name? test() -> false
        System.out.println("is String Name? test() -> " + isNameString.test("name"));
        // is String Name? test() -> true
    }
}

 

📝 람다식 [익명 객체]

람다는 익명 객체이다 (익명 함수는 함수가 독자적으로 존재해야하지만 Java는 클래스에 종속적이기 때문에 익명 객체라는 말이 맞다)

 

  • 람다식 축약 예제 따라해보기
 class Calculator {

    int sum(int x, int y){
        return x + y;
    } // 람다식 : (x,y) -> x+y;

    void print(String name, int i){
        System.out.println(name + " = " + i);
    } // 람다식 : (name,i) -> System.out.println(name + " = " + i);

    int square(int x){
        return x * x;
    } // 람다식 : x -> x * x

    int roll() {
        return (int) (Math.random() * 6);
    } // 람다식 : () -> (int) (Math.random() * 6);
}
  1. 함수반환 타입을 생략할 수 있다.
  2. 함수명을 생략할 수 있다.
  3. 인자 타입을 생략할 수 있다.
  4. 함수내용인 중괄호 내용이 한줄이면 중괄호는 생략이 가능하다

 

  • 함수형 인터페이스 + 람다식 이용해보기
package org.example;

public class Main {
    public static void main(String[] args) {

        /** 익명 객체 잘못된 사용 방법 **/
        Object object = new Object() {
            int max(int a, int b){
                return a > b ? a : b;
            }
        }; // Object에 max라는게 없기 때문에 object.max(3,5); 사용 불가

        /** 함수형 인터페이스 **/
//        MyFunction myFunction = new MyFunction() {
//            @Override
//            public int max(int a, int b) {
//                return a > b ? a: b;
//            }
//        };

        /** 함수형 인터페이스 + 람다식 **/
        MyFunction myFunction = (a, b) -> a > b ? a : b;
        // 함수형 인터페이스 자체가 추상 메서드가 하나밖에 없기 때문에 
        // 매개변수 및 리턴값에 뭐가 나올지 알고 있어서 생략이 가능하다

        System.out.println(myFunction.max(3,5)); // 5


    }

    interface MyFunction {
        public abstract int max(int a, int b);
    }

}

 

람다식하고 함수형 인터페이스는 짝궁이라고 생각하면 된다.

 

 

 

 

 

 

🔗 참고 및 출처

https://youngest-programming.tistory.com/476

https://ittrue.tistory.com/161

https://limkydev.tistory.com/226

반응형