본문 바로가기

Java

형 변환 / valueOf() / stream

Java 형 변환. 뭐야 왜 안돼. 할 때가 많지 않나요? 이참에 그냥 이해 빡 하고 다 짚고 넘어가 봅시다. 

 

뭐 명시적 형변환, 묵시적 형변환, 자동 형변환, 강제 형변환, 업 캐스팅, 다운 캐스팅은.... 많으니까..ㅎㅅㅎ

 

설명하는 건 자주 쓰이는 쉬운 문법일 뿐이고, 다양한 방법은 더 많습니다.

 

1. int -> String

int num = 123;

String str = String.valueOf(num); // String.valueOf()
String str = Integer.toString(num); // Integer.toString()

 

* 참고로 toString은 null이 입력되면 예외가 발생하지만 valueOf()는 "null"이 반환됩니다.

 

2. String -> int

String str = "123";

int num = Integer.valueOf(str); // 참고로 Integer 객체로 변환되고 int로 자동 언박싱 됩니다.
int num = Integer.parseInt(str);

 

3. int [] -> List <Integer>

int[] arr = {1,2,3};

List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList()); // stream 

// 반복문 사용
List<Integer> list = new ArrayList<>();
for(int i : intArray){
   list.add(i);
}

 

4. List <Integer> -> int []

List<Integer> list = Arrays.asList(1,2,3); 

int[] arr = list.stream().mapToInt(Integer::intValue).toArray(); // stream
int[] arr = list.stream().mapToInt(i -> i).toArray(); 

// 반복문 사용
int[] arr = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
    intArray[i] = list.get(i);
}

 

* 주의할 점. List <Integer> list = Arrays.asList(1,2,3);은 고정 크기 리스트로 요소의 추가/제거가 불가능하다. 

 

? List는 동적으로 크기가 조절된다면서요;; Arrays.asList 메서드로 생성된 리스트는 내부적으로 배열을 기반으로 하기 때문에 크기 조절이 불가능합니다.. ㅜㅜ 단순히 배열을 리스트로 사용하기 편하게 하는 방법이에요. 수정은 가능합니다!

 

그리고 헷갈리시면 안 되는데 List <Integer> list = new ArrayList <>(Arrays.asList(1,2,3); 이 녀석은 가변 리스트예요.. 

 

수정, 삭제하고 싶으시면 가변 리스트로 만들어야 합니다. -

 

뭔가 감이 오지 않나요? valueOf 이 녀석.. 유용하잖아?

 

5. valueOf

 

valueOf 메서드는 다양한 입력을 객체 형태로 반환하는 데 사용합니다. 중요해요. 객체 형태예요. 그래서 아까 Integer로 반환됐죠? String은 원래 객체여서 상관없는 거예요! Double, Boolean, Character 등등의 객체로 반환됩니다.

 

다른가요..? 할 수도 있는데 일단 저장되는 메모리가 다릅니다. 기본 형태의 int, double, boolean, char 등은 메모리의 스택 영역에 저장되고, 객체형은 힙 영역에 저장됩니다. 

 

어? 그럼 객체형이랑 기본형이랑 호환이 안 되나요..? 됩니다. 

 

Java 5부터는 오토박싱과 언박싱이 지원되기 때문에, 기본형과 객체형 간의 변환이 자동으로 이루어집니다.

* Auto-boxing - 기본형 -> 객체형

* Unboxing - 객체형 -> 기본형

 

뭐 객체의 생성과 소멸로 인한 성능 문제 측면에서는.. 따로 생각해봐야 합니다 ㅎ...

 

그리고 ' 그럼 모든 타입을 다 변환할 수 있는 거 아닌가요? ' 아닙니다. valueOf()는 주로 문자열을 형 변환하는데 쓰이는데 알맞은 형식이어야 변환되고 아니면 예외가 발생합니다. str = "true"인데 Integer.valueOf(str); 하면 예외 발생해요 ㅠ

 

* 참고로 char 문자는 Integer.valueOf()로 변환하게 되면 아스키코드 값으로 나오게 됩니다. 

Integer.valueOf('A') // 65

 

6. stream 

 

예제 보면서.. 좀 어려운 녀석이 껴있지 않았나요? 리스트와 배열 변환할 때..? stream이라는 녀석에 대해 알아봅시다.

 

저도 stream은 까먹은 건지.. 기억이 없어가지고 공부해야겠어요.

 

스트림은 자바 8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다.

 

아고 람다식도 나왔네. 반복자 하면 Iterator를 많이 아실 텐데요. 이것도.. 컬렉션 정리하면서 해야겠네요. 지금은 컬렉션을 순회하는 방법을 제공하는 인터페이스이다. 정도만 알고 갑시다.

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

// Iterator 사용
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
   String element = iterator.next();
   System.out.println(element);
}

// stream 사용
list.stream().forEach(num -> System.out.println("값 : " + num));

 

list의 모든 요소를 출력한다고 했을 때 stream을 사용하면 훨씬 간단해지는 걸 알 수 있죠.

 

알아볼게 많으니 부지런하게 알아봅시다.

 

6-1. stream 생성

List<Integer> list = Arrays.asList(1,2,3);
Stream<Integer> stream = list.stream();

int[] arr = {1,2,3};
IntStream intStream = Arrays.stream(arr);

 

약간 다르죠? 컬렉션 List, Set, Map 등은 그냥 stream() 메서드로 변환할 수 있는데 배열은 약간 다릅니다.

 

만약 double []이라면 DoubleStream doubleStream = Arrays.stream(doubleStream); 이렇게 되겠네요. 

 

근데 스트림을 생성하는 방법은 더 있습니다. 

// range()
IntStream intStream = IntStream.range(1,4); // 1, 2, 3

// builder()
Stream<String> stream = Stream.<String>builder()
                              .add("a")
                              .add("b")
                              .add("c")
                              .build(); // a, b, c

// Stream.of()
String<String> stream = Stream.of("a","b","c"); // a, b, c                              

// iterate 초기값과 람다식 표현식으로 무한 스트림
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 1).limit(3); // 0, 1, 2

// generate 람다 표현식으로 무한 스트림
Stream<String> stringStream = Stream.generate(() -> "a").limit(3); // a a a

// empty() 빈 스트림
String<String> stream = Stream.empty();

 

* 참고로 limit 안 써주면 무한 루프에 빠지게 됩니다.. 

 

* build() / builder.build()를 호출하지 않으면 stream이 완성되지 않습니다. 완성되지 않은 채 최종 연산을 호출하면 에러 발생합니다. 

 

* IntStream, LongStream, DoubleString 등 은 기본형 int, long, double을 처리하는 스트림입니다. Stream<Integer>, Stream<String>, Stream<Integer> 객체형 스트림이에요. 생각해 보면 List에도 객체형이 들어간다 했잖아요?

 

그래서 객체형 스트림은 컬렉션, 그러니까 리스트로 바꿀 때 그냥 리스트로 바꿔주면 collect(Collectors.toList()); 되는데 기본형 스트림은 객체형으로 바꿔줘야 해요. 그게 .boxed()입니다. 이따 나올 거예요.

 

어 그러면? 배열 만들기는 쉽겠네요? intStream.toArray() 하면 끝이잖아~

 

6-2. 중간 연산 - filter()

List<Integer> list = Arrays.asList(1,2,3);
  
list.stream().filter(num -> num.equals(2))
             .forEach(num -> System.out.print(num)); // 2
               
List<Integer> filteredList = list.stream()
                                 .filter(num -> num.equals(2))  
                                 .collect(Collectors.toList()); // [2]

 

stream은 filter() 메서드를 활용해서 내가 원하는 값만 뽑아낼 수 있어요. 2와 일치하는 요소만 필터링해서 새로운 결과를 반환하였고, 출력했습니다.

 

filter() 같은 메서드를 중간 연산이라고 하고, forEach()와 collect() 같은 메서드를 최종 연산이라고 해요. 

 

최종 연산은 중간 연산을 통해 변환된 stream의 각 요소를 소모하여 결과 stream을 생성하기 때문에 최종 연산 후 stream은 닫히게 되어 다시 사용할 수 없습니다. 최종 연산은 한 개만 가능하다는 얘기예요. 

 

6-3. 중간 연산 - map() / flatMap() / mapTo

 

Map은 스트림 내 요소들을 특정 값으로 변환해 줍니다. 즉, 필터링된 값들을 새로운 값으로 바꿔 줍니다.

List<Integer> list = Arrays.asList(1,2,3);
list.stream().filter(num -> num.equals(2))
    .map(num -> num + 10)
    .forEach(num -> System.out.print(num)); // 12

List<Integer> filteredList = list.stream()
                .filter(num -> num.equals(2))
                .map(num -> num*2)
                .collect(Collectors.toList()); // [4]

 

flatMap()은 여러 개의 요소들로 구성된 새로운 스트림을 반환합니다. 무슨 소리냐면 map()은 요소마다 1:1 매핑이잖아요? flatMap()은 요소를 1로 매핑합니다. 그래도 모르겠으니까 예제를 보면

List<String> list = Arrays.asList("a a", "b b", "c c");

List<String> words = list.stream()
                         .flatMap(str -> Arrays.stream(str.split(" ")))
                         .collect(Collectors.toList()); // [a,a,b,b,c,c];

 

flatMap()이 어떻게 작동하냐면 각 요소를 스트림으로 변환해야 합니다. 그래서 Arrays.stream()을 사용한 거예요. 왜냐면 str.split(" ")을 하면 반환이 String [] 배열로 반환되기 때문이죠. 그렇게 각 요소를 각각의 스트림으로 반환하여 마지막에 평탄화한다고 하여 하나의 스트림으로 합치는 작업을 하게 됩니다!

 

그리고 mapToInt / mapToDouble / mapToLong 등 객체 스트림을 정수 스트림으로 변환해주는 연산도 있습니다. 

List<String> list = Arrays.asList("1","2","3");
IntStream stream = list.stream().mapToInt(String::length);

 

이런 식으로 말이죵. 문자열의 길이를 int로 변환하고 IntStream을 생성합니다.

 

6-4. 중간 연산 - sorted()

List<Integer> list = Arrays.asList(3,1,2);
Stream<Integer> stream = list.stream().sorted(); // [1,2,3]
Stream<Integer> Stream = list.stream().sorted(Comparator.reverseOrder()); // [3,2,1]

List<String> list2 = Arrays.asList("aaaa","b","cc");
Stream<String> stream2 = list2.stream().sorted(Comparator.comparingInt(str -> str.length())); // ["b","cc","aaaa"]

 

sorted() 기본 정렬은 오름차순이고, 다른 기준은 Comparator을 이용합니다. 

 

근데 길이가 같을 수도 있잖아요? 

 Stream<String> stream2 = list2.stream()
                               .sorted(Comparator.comparingInt(str -> str.length())
                               .thenComparing(str -> str)); // 에러

 

이렇게 하면 길이가 같은 문자열은 사전식으로 비교하게 됩니다! 하지만 이렇게 하면 에러 나요. 아니 틀린 게 없는데 에러가 왜 나나 했더니 여러 타입의 메서드가 오버로드된 경우나 제네릭 타입이 복잡한 경우 타입 추론 문제가 일어나서 에러가 난다더라고요.. 쓰려면

// Comparator 미리 생성
Comparator<String> comparator = Comparator.comparingInt((String str) -> str.length())
                                          .thenComparing(str -> str); // 먼저 비교를 해주고

Stream<String> stream2 = list2.stream().sorted(comparator); // 스트림 정렬

// 메서드 참조
Stream<String> stream2 = list2.stream()
                              .sorted(Comparator.comparingInt(String::length)
                              .thenComparing(Comparator.naturalOrder()));

 

이렇게 쓰면 됩니다. 메서드 참조는 람다 표현식의 축약형인데 람다는 따로 정리할게요.

 

6-5 중간 연산 - peak()

 

peak()는 쉽게 말해서 한 번 쓰면 스트림을 다시 못 쓰는 최종 연산을 대신하는 중간 연산 확인 메서드..? 

 

스트림의 각 요소를 처리하면서 스트림을 변경하지 않고 그대로 전달합니다.

 

* 최종 연산은 스트림의 요소를 소모하여 이후에는 사용 불가

List<Integer> list = Arrays.asList(1,2,3);

list.stream()                               
    .peek(n -> System.out.println(n)) //123
    .filter(n -> n%2==0) 			  
    .collect(Collectors.toList());

 

6-6. 중간 연산 - limit

List<Integer> list = Arrays.asList(1,2,3);
List<Integer> limitList = list.stream()
	                      .limit(2)
                              .collect(Collectors.toList()); // [1,2]

 

limit은 무한 스트림 얘기하면서 나왔었는데 말 그대로 제한을 걸어줍니다. 최대 크기 제한으로 이해하시면 될 거 같아요.

 

6-7. 중간 연산 - skip / distinct

List<Integer> list = Arrays.asList(1,2,3,3,4);
list = list.stream()
           .skip(2) // n개 스킵
           .distinct() // 중복 제거
           .collect(Collectors.toList());

 

6-8. 최종 연산 - forEach() / collect() / toArray()

List<Integer> list = Arrays.asList(1,2,3);

// foreach()
list.stream().forEach(n -> System.out.print(n)); // 123

// collect()
list.stream().collect(Collectors.toList()); // [1,2,3]

 

* 혹시 stream을 배열로 변환하고 싶다면 2가지 방법이 있습니다.

list.stream()
    .toArray(); // 1

list.stream()
    .toArray(size -> new Integer[size]); // 2

list.stream()
    .toArray(Integer[]::new); // 3

 

stream 인터페이스의 toArray 메서드는 2가지가 있다고 합니다. 

  • Object[] toArray()
    • 1번같은 경우인데 배열 생성자를 지정해주지 않았기에 Object[] 배열로 반환됩니다.
  • <A> A[] toArray(IntFunction<A[]> generator)
    • 배열 생성자를 받아 특정 타입의 배열을 반환합니다. 2번과 3번에 해당하겠죠? 

근데 3번은 람다식으로 쓰였는데 배열 사이즈가 어디있나 싶지 않나요..? 스트림 API가 내부적으로 스트림 크기를 계산해서 계산된 크기로 배열 생성자를 호출한다고 합니다.

 

 

6-9. 최종 연산 

 

초기값과 결합 함수를 사용하여 스트림의 요소를 하나의 값으로 축소합니다. 주로 곱셈이나 합계 연산에 사용됩니다.

List<Integer> list = Arrays.asList(1,2,3);

// reduce()
list.stream().reduce(0, Integer::sum);
    
// count()
list.stream().count(); // 3

// findFirst()
Optional<Integer> optionalList = list.stream().findFirst(); // 1

// findAny()
Optional<Integer> optionalList = list.stream().findAny();

// allMatch()
list.stream().allMatch(n -> n % 2 == 0); // false

// anyMatch()
list.stream().anyMatch(n -> n % 2 == 0); // true

// noneMatch()
list.stream().anyMatch(n -> n > 10); // true

 

* reduce는 람다식을 사용하지 않으면 좀 복잡해지더라고요.. 0은 초기값 입니다. 스트림이 비었을 떄 초기값을 반환 합니다.

 

* find 메서드들에 대해서 잠깐 얘기하자면 Optional은 값이 있을 수도 있고 없을 수도 있는 경우를 처리하는데 사용합니다. 저대로 출력하면 Optional[1] 이렇게 나오는데요. 요소만 출력하는 방법은 아래와 같습니다.

List<Integer> list = Arrays.asList(1,2,3,5,6);
Optional<Integer> optionalList = list.stream().findFirst();

// ifPresent() 값이 있을때만 출력
optionalList.ifPresent(value -> System.out.println(value));

// orElse() 기본값 제공
Integer defalutList = list.stream().findFirst().orElse(1);

// get() 값이 반드시 존재한다고 확신할 때만
optinalList.get();

 

* get() 메서드는 비어있으면 예외가 발생합니다. 조건문을 사용하거나 있다고 확신할 때만 써야 합니다.

 

*findAny()는 순차 스트림 에서는 findFirst()와 똑같이 작용하지만 병렬 스트림에서는 제일 먼저 찾은 값을 반환합니다.

 

드디어 스트림 기초를 대충 정리했네요.. 많아서 뭐 빼먹은거 같은데 차차 추가하겠습니다.

 

많은걸 정리하다 보니까 헷갈리네요.. 역시 많이 써봐야 기억하나봐요.

 


 

참고 출처

[JAVA] Stream API에 대해 알아보기 _ Stream 최종 연산(집계) (4/5) — ZINU (tistory.com)

 

[JAVA] Stream API에 대해 알아보기 _ Stream 최종 연산(집계) (4/5)

Stream의 객체를 구성하고자 할 때 "Stream 생성 → 중간 연산→ 최종 연산"의 세 단계의 과정을 통해서 Stream의 구성이 이루어집니다.  이번 포스팅에서는 Stream 생성 후 생성된 스트림을 필터링하

pixx.tistory.com

[Java] 스트림(Stream) 정리 (velog.io)

 

[Java] 스트림(Stream) 정리

자바로 배열 또는 컬렉션 객체를 다룰 때 IDE의 추천 메소드에는 stream()이 항상 있었다. 하지만 for문이나 향상된 for문으로도 충분히 원하는 결과를 이끌어낼 수 있어서 매번 지나쳤던 기억이 있

velog.io

 

'Java' 카테고리의 다른 글

Set / Map / Iterator  (0) 2024.06.14
익명 클래스 / 람다식 / 메소드 참조  (1) 2024.06.08
String / StringBuilder / StringBuffer  (1) 2024.06.07
static과 final  (1) 2024.06.05
Class, Object, Instance  (0) 2024.06.05