Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

이지은님의 블로그

250107 - Java 람다와 스트림, null 처리, Enum, StringBuilder 활용 및 데이터 변환 메소드 비교(valueOf(), parseInt()) 본문

TIL

250107 - Java 람다와 스트림, null 처리, Enum, StringBuilder 활용 및 데이터 변환 메소드 비교(valueOf(), parseInt())

queenriwon3 2025. 1. 7. 21:08

 코드 문제풀이

[JAVA] 코드카타 - (11)~(15)

 

[JAVA] 코드카타 - (11)~(15)

문제 (11) : 짝수와 홀수정수 num이 짝수일 경우 "Even"을 반환하고 홀수인 경우 "Odd"를 반환하는 함수, solution을 완성해주세요.https://school.programmers.co.kr/learn/courses/30/lessons/12937 프로그래머스SW개발

queenriwon3.tistory.com

[JAVA] 코드카타 - (16)~(20)

 

[JAVA] 코드카타 - (16)~(20)

문제 (16) : x만큼 간격이 있는 n개의 숫자함수 solution은 정수 x와 자연수 n을 입력 받아, x부터 시작해 x씩 증가하는 숫자를 n개 지니는 리스트를 리턴해야 합니다. 다음 제한 조건을 보고, 조건을

queenriwon3.tistory.com

 

 

 

 

▷ 오늘 배운 것

람다와 스트림관련으로 배운 5주차 내용을 정리하고 계산기문제로 작성했던 enum, 코드문제를 풀면서 알게되었던 StringBuilder, valueOf(), parseInt()에 대해 정리해보고자 한다.

 

 

1. 람다와 스트림

    1) 람다와 스트림

    2) 람다와 스트림 사용하기

    3) 람다와 스트림 사용 코드 분석

 

2. null처리하기 (Optimal<T>)

     1) null처리하는 방법

     2) Optimal<T>

 

3. Enum 타입

    1) Enum 타입

    2) Enum의 구조

    3) Enum의 사용

    4) Enum에서 사용할 수 있는 메서드

    5) Enum에 필드와 메서드 추가(클래스처럼 사용가능)

 

4. StringBuilder 클래스

5. valueOf()와 parseInt()의 차이점

 

 

 

 


1. 람다와 스트림

람다와 스트림의 등장배경(Java 8)
  • 시장에서 프로그래머가 해결해야 하는 문제가 계속 변화 함으로써, 프로그래밍 언어에 요구되는 기능들도 변화한다. 이는 새로운 언어를 만들고 기존의 언어를 업데이트 시키는 계기가 된다.
  • 자바는 객체지향 프로그래밍, 그중에서도 캡슐화와 같은 장점을 가지고 있어서 프로그래머들의 생산성과 프로그램의 안정성을 가지고 있었다.
  • 특히 초기의 c언어가 이식성이 높아서 사랑받았던 것처럼(다양한 하드웨어에서 최소한의 수정으로 c로 작성된 프로그램을 실행시킬 수 있음), 자바 역시 jvm이 설치되어 있는 환경이라면 프로그램을 실행시킬 수 있다는 큰 장점을 가지고 있었다.
  • 하지만 java에게도 다양한 요구가 있었으며, 그중 빅데이터를 처리할 필요성, 병렬처리 있었다.

 

 

자바의 요구사항
  1. 병렬처리
    • 빅데이터의 처리로 인해 멀티 코어 컴퓨터와 같은 병렬프로세싱이 필요하게 되었다. -> 람다와 스트림의 사용
  2. 함수형 프로그래밍
객체 지향 프로그래밍 함수형 프로그래밍
프로그램을 객체들의 협력과 상호작용으로
바라보고 구현한다
프로그램을 순수한 함수(결과값이 오직 입력값에만 영향)
모음으로 바라보고 구현한다
1. 코드의 재사용성이 높아진다.
2. 코드를 유지 보수, 확장하기 쉬워진다.
3. 코드를 신뢰성 있게 사용하기 쉬워진다.
1. 검증이 쉽다 (검증이 필요한 부분만 검증할 있음)
2. 성능 최적화가 쉽다 (특정 input 대한 output 재사용 있음 - 캐싱)
3. 동시성 문제를 해결하기 쉽다 (함수는 다른 값의 변경을 야기하지 않음)

 

 

 

 

1) 람다와 스트림

이후 Java 8에서 추가된 내용
  1. 함수형 프로그래밍
    1. 함수를 일급객체로 다룸
      • 일급 객체: 사실상 프로그래밍에서 지원하는 모든 연산을 지원하고 있고, 이러한 연산을 모두 지원하는 “값”
      • 메서드는 특정한 연산을 지원하지 않는 값이기 떄문에 이급에 해당
        —> 함수를 값으로 취급할 수 있다면?(함수형 프로그래밍)
        —> 메서드 참조기능(함수를 일급객체로 바라봄)
    2. 람다 (익명함수)
      • 이름이 없는 함수, 람다를 이용해 일급객체로 취급가능하다.
      • 함수를 값으로 사용할 수 있으며, 파라미터에 전달하기, 변수에 대입하기 등이 가능하다.

  2. 스트림
  • 데이터 연산을 위해 연속으로 사용할 수 있는 기능.
  • 컬렉션이 데이터를 저장접근하는데 초첨을 맞춘 인터페이스라면, 스트림은 데이터를 처리하는데 초점을 맞춘 인터페이스
  • 병렬처리를 도와준다.

 

 

2) 람다와 스트림 사용하기

 

  1. 함수를 값으로 전달하기
public exampleMethod(int parameter1, ??? parameterFunction) { ... }

 

함수의 타입은 뭐라고 정의할 수 있을까? >> 함수형 인터페이스 <<

인터페이스는 타입 역할을 해준다. 이를 이용해 함수의 타입을 정의하기 위해 함수형 인터페이스를 사용해야 한다.

 

 

 

구현방법 1) 클래스 내부에 메서드를 static 메소드를 구현한다.

// Car 클래스 내부에 두 메서드 구현
public static boolean hasTicket(Car car) {
        return car.hasParkingTicket;
}

public static boolean noTicketButMoney(Car car) {
        return !car.hasParkingTicket && car.getParkingMoney() > 1000;
}

 

 

구현방법 2) 함수형 인터페이스 정의

interface Predicate<T> {
    boolean test(T t);
}

 

 

구현방법 3)  Predictate<Car>를 인터페이스로 함수전달

public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
    List<Car> cars = new ArrayList<>();

    for (Car car : carsWantToPark) {
        if (function.test(car)) {
          cars.add(car);
        }
    }
    return cars;
}

 

 

구현방법 4) 함수의 사용

parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket));
parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));

함수 인자 전달시 Car클래스에서 사용할 메서드를 전달(Car::hasTicket)

 

 

 

     2. 람다를 사용하여 익명함수만들기

// 주말의 주차장 추가
ArrayList<Car> weekendParkingLot = new ArrayList<>();

weekendParkingLot
.addAll(parkCars(carsWantToPark, (Car car) -> car.hasParkingTicket() && car.getParkingMoney() > 1000));

(파라미터 값, …) -> { 함수 } 의 형식으로 일회용으로 사용할 수 있음

 

 

 

     3. 스트림

  • 원본의 데이터를 변경하지 않고, 일회용으로 사용할 수 있다.
  • 파이썬이나 R에서 테이블을 다루는 것처럼 데이터를 병렬로 처리할 수 있다.
List<Car> benzParkingLot =
                carsWantToPark.stream()
                        .filter((Car car) -> car.getCompany().equals("Benz"))
                        .toList();

스트림을 받아오고(.stream()) 이후 가공(.filter()) 후 결과를 리스트형태로 만들 수 있다.(.toList())

 

대표적인 스트림 api는 map(), forEach(), filter()

 

- forEach(): 각 원소에 대한 함수실행

carNames.stream() .forEach(System.out::println);

 

- map(): 각원소의 값을 변환

carNames.stream() .map(name -> name.toUpperCase()).toList();

 

 

 

 

3) 람다와 스트림 사용 코드 분석

  • [조건 1] 카테고리가 여행인 제목 조회
bookList.stream().filter(book -> book.getCategory().equals("여행"))
        .forEach(book -> System.out.println(book.getBookName() + " (카테고리: " + book.getCategory()+")"));

bookList에서 filter()를 이용해서 카테고리가 여행인 요소를 필터링한다.

forEach()를 이용하여 필터링된 요소의 책제목과 카테고리를 추출 후 출력한다.

 

 

  • [조건 2] 가격이 16200 이하인 제목 조회
bookList.stream().filter(book -> book.getPrice() <= 16200)
        .forEach(book -> System.out.println(book.getBookName() + " (" + (int) book.getPrice()+"원)"));

bookList에서 filter()를 이용해서 책가격이 16200이하인 요소를 필터링한다.

forEach()를 이용하여 필터링된 요소의 책제목과 가격을 추출 후 출력한다.

 

 

  • [조건 3] 제목에 "경제"라는 용어가 들어간 제목 조회
bookList.stream().filter(book -> book.getBookName().contains("경제"))
        .forEach(book -> System.out.println(book.getBookName()));

bookList에서 filter()를 이용해서 책제목에 경제가 포함된 요소를 필터링한다.

forEach()를 이용하여 필터링된 요소의 책제목을 추출 후 출력한다.

 

 

  • [조건 4] 가격이 가장 비싼 가격 조회
//bookList.stream().mapToDouble(Book::getPrice);  //java.util.stream.ReferencePipeline$6@1540e19d
//bookList.stream().mapToDouble(Book::getPrice).max();  //OptionalDouble[40500.0]
//bookList.stream().mapToDouble(Book::getPrice).max().getAsDouble()  //40500.0

int maxPrice = (int) bookList.stream().mapToDouble(Book::getPrice).max().getAsDouble();
System.out.println(maxPrice);

bookList에서 mapToDouble()로 각 책들의 가격을 추출하고 .max()로 가장 큰 요소를 구한다. 이후 형변환을 해주고 출력한다.

 

 

  • [조건 5] 카테고리가 IT 책들의 가격 조회
int sumITPrice = (int) bookList.stream().filter(book -> book.getCategory().equals("IT"))
        .mapToDouble(Book::getPrice).sum();
System.out.println(sumITPrice);

bookList에서 filter()를 이용해서 카테고리가 IT인 요소를 필터링한다.

mapToDouble()로 각 책들의 가격을 추출하고 .sum()으로 그 합을 구한다.

 

 

  • [조건 6] IT 할인 이벤트
bookList.stream().filter(book -> book.getCategory().equals("IT"))
    .map(book -> {
        book.setPrice(book.getPrice() * 0.6);
        return book;
    })
    .forEach(book -> System.out.println(book.getBookName() + " (할인가: " + (int) book.getPrice() + "원)"));

bookList에서 filter()를 이용해서 카테고리가 IT인 요소를 필터링한다.

필터링한 요소에 가격을 추출하여 가격을 재설정해주고 이를 반환해준다.

반환된 값으로 forEach()를 이용해 책제목과 가격을 출력한다.

 

 

 

 

 

2. null처리하기 (Optimal<T>)

1) null처리하는 방법

null값을 처리하는 데 많은 방법이 있다. 그 방법은 다음과 같다.

 

  • null 반환될 여지가 있을 경우 null반환 검사하기
String userId = myDB.findUserIdByUsernameOrThrowNull("HelloWorldMan"); 
// 개선 1: null이 반환 될 수 있음을 인지한 메서드 사용자는, null을 대비합니다.
 
if (userId != null) { 
    System.out.println("HelloWorldMan's user Id is : " + userId); 
}

 

  • 객체로 null체크하기
// 개선 2: 결과값을 감싼 객체를 만듭니다.
class SomeObjectForNullableReturn {
    private final String returnValue;
    private final Boolean isSuccess;  //null검사

    SomeObjectForNullableReturn(String returnValue, Boolean isSuccess) {
        this.returnValue = returnValue;
        this.isSuccess = isSuccess;
    }

    public String getReturnValue() {
        return returnValue;
    }

    public Boolean isSuccess() {
        return isSuccess;
    }
}

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

        SomeDBClient myDB = new SomeDBClient();

        // 개선 2 : 이제 해당 메서드를 사용하는 유저는, 객체를 리턴받기 때문에 더 자연스럽게 성공여부를 체크하게 됩니다.
        SomeObjectForNullableReturn getData = myDB.findUserIdByUsername("HelloWorldMan");

        if (getData.isSuccess()) {
            System.out.println("HelloWorldMan's user Id is : " + getData.getReturnValue());
        }
    }
}

class SomeDBClient {
    // 개선 2 : 결과값을 감싼 객체를 리턴합니다.
    public SomeObjectForNullableReturn findUserIdByUsername(String username) {
        // ... db에서 찾아오는 로직
        String data = "DB Connection Result";
        
        if (data != null) {
            return new SomeObjectForNullableReturn(data, true);
        } else {
            return new SomeObjectForNullableReturn(null, false);
        }
    }

}

null 함께 저장하는 객체의 형식을 제네릭으로 수정하면 모든 메서드에 사용할 있다.(java.util.Optional 객체)

 

 

2) Optimal<T>

Optional<T> 클래스를 사용해 Null Pointer Exception 방지

(Optional 비어있더라도, 참조해도 Null Pointer Exception 발생하지 않음)

  • 값이 null인 Optional 생성하기
Optional<Car> emptyOptional = Optional.empty();

 

  • 값이 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.of(new Car());

 

  • 값이 있을 수도 없을 수도 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());

 

  • Optional 객체 사용하기 ( 받아오기)
Optional<String> carName = getCarNameFromDB();
// orElse() 를 통해 값을 받아옵니다, 파라미터로는 null인 경우 반환할 값을 적습니다.
String realCarName = carName.orElse("NoCar");

// 위는 예시코드고 실제는 보통 아래와 같이 사용하겠죠?
String carName = getCarNameFromDB().orElse("NoCar");

// orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있습니다.
// 파라미터로는 없는 경우 실행될 함수를 전달합니다.
Car car = getCarNameFromDB().orElseGet(Car::new);

// 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있습니다.
Car car = getCarNameFromDB()
    .orElseThrow(() -> new CarNotFoundException("NO CAR!)

 

 

 

 

 

3. Enum 타입

1) Enum타입

Enum 타입은 Java에서 열거형 데이터 타입으로, 고정된 상수 집합을 정의할 때 사용한다. Enum은 클래스처럼 동작하면서 특정 이름과 값의 집합을 표현할 수 있다.

 

Enum타입은 값이 제한되어 있을 때 상수의 타입안정성을 보장할 수 있다.

메서드, 필드를 정의할 있으며 생성자도 포함가능하여 클래스처럼 동작할 있다.

 

 

2) Enum 구조

public enum Direction {
    NORTH, SOUTH, EAST, WEST
}
  • Enum 타입에서는 정의된 상수 외의 값은 가질 수 없다.
  • 일반적으로 Enum상수는 모두 대문자로 작성한다.

 

3) Enum 사용

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class Main {
    public static void main(String[] args) {
        Day today = Day.FRIDAY; // FRIDAY

        System.out.println("Today is " + today); // Today is FRIDAY

        if (today == Day.FRIDAY) {
            ...
        }
    }
}

 

 

4) Enum에서 사용할 있는 메서드

.values()  // 상수의 배열을 반환(for-each에서 사용)
.valueOf(String name)  // 지정된 이름과 일치하는 enum상수 반환
.name()  // 상수 이름 반환
.ordinal()  // 상수의 순서 반환

 

 

 

5) Enum 필드와 메서드 추가(클래스처럼 사용가능)

public enum Season {
    SPRING("Warm"), 
    SUMMER("Hot"), 
    FALL("Cool"), 
    WINTER("Cold");

    // 필드
    private final String description;

    // 생성자
    Season(String description) {
        this.description = description;
    }

    // 메서드
    public String getDescription() {
        return description; //괄호안을 반환
    }
}

 

 

 

 

4. StringBuilder 클래스

https://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html?is-external=true

https://myeongju00.tistory.com/61

 

String은 불변(immutable) 객체라고 한다. 즉 String객체는 한번 생성되면 변경할 수 없다. String객체를 수정하려고 하면 새로운 문자열 객체가 생성되고 이전에 있던 문자열은 JVM의 GC가 처리하여 삭제된다. 따라서 String을 변경하고자 하는일은 메모리 할당 및 해제를 발생시키며 이와같은 연산이 많아지면 성능적으로 좋지않다.

 

그에비해 StringBuilder은 값이 변할 수 있는 객체이다. 문자열을 더할 때 새로운 객체를 생성하는 것이 아니라 기존의 데이터에 더하는 방식을 사용하기 때문에 속도도 빠르며 상대적으로 부하가 적다.(다수의 문자열 조작 작업에서는 StringBuilder가 훨씬 빠름)

 

비슷한 StringBuffer 클래스가 있는데, 차이점은 thread-safe에서 갈린다. StringBuffer thread-safe 하므로 여러 쓰레드에서 동시에 해당 문자열에 접근한다면 StringBuffer 사용을 고려하고, 그렇지 않다면 StringBuilder 사용하는 것이 성능에 유리하다. 

 

StringBuilder는 문자열을 조작하는 동안 동적 배열을 사용하여 데이터를 저장한다. 이때, 새로운 문자열을 추가하거나 삭제하면, StringBuilder의 용량(capacity)은 자동으로 증가한다. 하지만 문자열을 삭제하거나 줄였을 때, 여전히 이전의 용량(capacity)을 유지하고 있는 경우가 발생한다.

trimToSize() 현재 문자열 길이만큼 용량을 줄여 사용되지 않는 메모리를 해제한다.

 

StringBuilder 사용방법
StringBuilder sb = new StringBuilder();  // 기본 생성자 호출
StringBuilder sb = new StringBuilder(20); // 버퍼 사이즈를 이용해 호출
StringBuilder sb = new StringBuilder("aaa"); //String문자열을 이용한 호출

 

StringBuilder 주요 메서드
sb.append(str);  // str 뒤에 추가

sb.insert(index, str);  //index위치에 str 추가

sb.replace(idx1, idx2, str);  //idx1~idx2 위치에 str로 대체
sb.setCharAt(idx, str);  // idx위치의 문자를 str로 변경

sb.substring(idx);  // idx~끝 문자열 추출
sb.substring(idx1, idx2);  // idx1~(idx2 - 1) 문자열 추출

sb.deleteCharAt(idx);  // idx에 위치한 문자 삭제
sb.delete(idx1, idx2);  // idx1~idx2에 위차한 문자열 삭제

sb.toString();  // String으로 변환

sb.reverse();  // 문자 전체를 뒤집기

sb.setLength(len);  // len만큼 문자열 길이를 조정

sb.capacity();  // 메모리 용량 반환
sb.trimToSize();  // 사용하지 않는 메모리 해제

 

 

 

5. valueOf() parseInt()의 차이점

형변환을 할때 valueOf()와 parseInt()는 문자열을 정수로 변환할 때 사용된다.

특징 valueOf() parseInt()
소속 클래스 Integer 클래스 Integer 클래스
용도 문자열을 정수 객체(Integer) 변환 문자열을 기본형 int 변환
반환값 Integer 객체 기본형 int
오토박싱 필요 없음 (이미 객체로 반환) 반환값이 기본형이므로 필요할 있음
사용 예시 Integer.valueOf("123") Integer.parseInt("123")


참고


valueOf() 내부적으로 객체 (Caching) 사용하여 동일한 값을 반복적으로 생성하지 않고 캐싱된 Integer 객체를 반환.
: Integer.valueOf("100") 여러 호출되더라도, 메모리 사용을 최적화하기 위해 같은 객체를 반환한다.
객체를 반환하므로 박싱/언박싱이 필요 없는 경우 유리.
반환값이 기본형이므로 메모리 오버헤드가 적음.
객체 연산이 필요 없는 순수 숫자 계산에 적합.
기본형을 반환하므로 가볍고 빠름.