반응형
반응형
/** ---- 날짜/시간 파싱 ----**/
int year = dateTime.getYear();      // 2025
Month month = dateTime.getMonth();  // SEPTEMBER
int day = dateTime.getDayOfMonth(); // 7
int hour = dateTime.getHour();      // 13
int minute = dateTime.getMinute();  // 45
int second = dateTime.getSecond();  // 30

/** ---- 날짜/시간 변경 ----**/
LocalDateTime plusDays = dateTime.plusDays(5);       // 5일 뒤
LocalDateTime minusHours = dateTime.minusHours(3);   // 3시간 전
LocalDateTime nextMonth = dateTime.withMonth(10);    // 10월로 변경
LocalDateTime changed = dateTime.withDayOfMonth(1)   // 그 달의 1일
                                .withHour(0)         // 0시
                                .withMinute(0);      // 0분
                                
/** ---- 날짜 비교 ----**/ 
boolean before = dateTime.isBefore(LocalDateTime.now());
boolean after = dateTime.isAfter(LocalDateTime.now());
boolean equal = dateTime.isEqual(LocalDateTime.now());


/** ---- 타입 변환 ----**/ 
LocalDate datePart = dateTime.toLocalDate();
LocalTime timePart = dateTime.toLocalTime();


/** ---- 포맷팅 ----**/ 
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime parsedDateTime = LocalDateTime.parse("2025-09-07 13:45:30", fmt); // 문자열 → LocalDateTime
String formatted = dateTime.format(fmt); // "2025-09-07 13:45:30" // LocalDateTime → 문자열


/** ---- 정해진 시각 ----**/ 
LocalTime min = LocalTime.MIN; // 하루의 가장 이른 시간 (00:00)
LocalTime max = LocalTime.MAX; // 하루의 가장 늦은 시간 (23:59:59.999999999)
LocalTime noon = LocalTime.NOON; // 정오 (12:00)
LocalTime noon = LocalTime.MIDNIGHT; // 자정 (00:00) → 사실상 LocalTime.MIN과 같음
반응형
반응형

📝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이다

반응형
반응형

📝메소드 레퍼런스

"클래스이름::메소드이름, 객체이름::메소드" 형식으로 생략해서 표현이 가능합니다.

메소드 참조로 함수형 인터페이스에 넣어서도 사용이 가능합니다.

메소드 참조는 람다식의 축약형이기 때문에 모든 메소드 참조는 람다식으로 변환이 가능합니다

 

 

Static 메소드 참조

// 예제1) Integer.parseInt("123")
Function<String, Integer> func = Integer::parseInt;
System.out.println(func.apply("123")); // 출력: 123

// 예제2) Stream API
// Function<String, Integer>를 명시하지 않아도 map()이 추론
list.stream()
    .map(Integer::parseInt) // static method 참조
    .forEach(System.out::println);

 

 

특정 객체의 인스턴스 메소드 참조

// 예제1) str.length()
String str = "Hello";
Supplier<Integer> supplier = str::length;
System.out.println(supplier.get()); // 출력:

// 예제2) Stream을 이용한 예제
list.stream()
    .map(prefix::concat) // 특정 객체(prefix)의 concat() 메서드 참조
    .forEach(System.out::println);

 

 

생성자 참조

Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
list.add("Test");
System.out.println(list);


list.stream()
    .map(Integer::new) // String을 받아 Integer 생성자 호출
    .forEach(System.out::println);

new ArrayList<>()를 생성하는 생성자 참조입니다.

 


람다식을 메소드 참조로 변환

람다식 메소드 참조 형태 설명
(a) -> System.out.println(a) System.out::println 인자를 그대로 println에 전달
(str) -> str.toLowerCase() String::toLowerCase 인스턴스 메소드 참조
(s1, s2) -> s1.compareTo(s2) String::compareTo 같은 타입 두 인자 → 인스턴스 메소드
() -> new ArrayList<>() ArrayList::new 생성자 참조

람다식의 경우도 메소드 참조로 간략하게 표현이 가능합니다.

 

 

📝메소드 레퍼런스 실습

  • 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
         */

    }
    
    @Test
    public void flatMapTest(){
        List<List<String>> listOfLists = Arrays.asList(
            Arrays.asList("A", "B"),
            Arrays.asList("C", "D"),
            Arrays.asList("E", "F")
        );

        // flatMap을 사용하지 않으면 Stream<List<String>> -> map 자체는 그 형태로 반환하기 때문이다.
        // flatMap을 쓰면 Stream<String>으로 평탄화
        List<String> result = listOfLists.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());

        System.out.println(result); // [A, B, C, D, E, F]

    }
}

 

 

 

🔗 참고 및 출처

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

반응형
반응형

📝익명 클래스

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

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

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

 

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

 

프로그램 한번만 사용되어야하는 객체의 경우 [ 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
  }
}

 

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

 

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 + "를 엽니다.");
            }

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

 

참고로 익명클래스의 경우 Override가 필요하기 때문에 Interface나 Abstract같은 추상 클래스로부터 구현하는 경우가 많습니다.

 

📝함수형 인터페이스

함수형 인터페이스란 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);
    
   
}


// 여러 줄일 경우
(name, i) -> {
    String msg = "Name: " + name;
    System.out.println(msg);
    System.out.println("Index: " + i);
}
  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
        
        // Integer.parseInt 말고도 Integer는 다양한 함수가 있지만 람다가 가능한 것은 
        // 해당 함수를 왼쪽에 메소드가 하나인 Interface에 넣기 때문에 가능하다
        Function<String, Integer> parser = Integer::parseInt;


    }

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

}

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

 

 

📝 익명함수, 람다식 특징 (AtomicInteger)

public class LambdaStackExample {
    public static Runnable createRunnable() {
        int n = 10;

        // ❌ 만약 n++을 허용한다면?
        return () -> {
            // n++; // 컴파일 에러 발생 (Local variable must be final or effectively final)
            System.out.println("n = " + n);
        };
    }

    public static void main(String[] args) {
        Runnable r = createRunnable();
        r.run();
    }
}

일반적으로 로컬 변수(원시값 or 참조값)은 메서드 스택 프레임안에 생성됩니다.

메서드가 끝나면 변수는 사라지지만 만약 사라진 메서드 안에 로컬 변수를 계속 수정할 수 있게 되면 사라진 메모리에 접근하기 때문에 위험이 있습니다.

 

해당 위험상황은 아래와 같은 경우가 있습니다.

스레드 작업에 많이 쓰이게 되는데 쓰레드에서 계속 밖에 있는 변수를 수정하게 되면 점점 이상해질 가능성이 있습니다.

 

위와 같은 이유로 아예 읽기 전용인 final로만 만듭니다. (변수캡처 = 복사본)

int같은 건 스택에 올라가기 때문에 위험이 있지만 Heap에 저장되는 건 사용할 수 있습니다.

계속적인 참조가 있기 때문에 메소드가 종료되어도 남아있습니다.

AtomicInteger 등 지원하는 클래스를 이용해 수정하거나 값을 읽거나 할 수 있습니다.

 

 

 

🔗 참고 및 출처

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

https://ittrue.tistory.com/161

https://limkydev.tistory.com/226

반응형