Arrays.asList() 사용 시 주의할 점
개요List 선언 시, 선언과 동시에 초기화하여 사용하기 편리하여 Arrays.asList()를 종종 사용했습니다. 편리하게 사용도중 필요에 의해 선언된 리스트 첫 원소에 새로운 원소를 더해서 사용해야하는 상황이 있어 add 메서드를 사용하였습니다. 컴파일 에러도 없기에 자신있게 소스를 실행했지만 실제 실행 단계에서 다음과 같은 예외가 발생하였습니다. "arrays.aslist unsupportedoperationexception" 이 오류의 원인을 찾던 중 Arrays.asList() 사용하는데 조심해야 할 부분을 찾게 되어 공유하고자 글을 남깁니다. Arrays.asList()의 결과물은 Arrays 안의 inner 클래스다.오류가 나는 부분을 디버깅 찍어보면 평소에 사용하는 java.util.Arr..
2025.04.03
List에서 특정 조건 만족하는 요소 삭제하기 (List.removeIf 사용하기)
개요List 사용 중에 목록에서 특정 조건을 만족하는 요소를 삭제하는 로직을 다룰 일이 종종 있습니다.이 때, 그 동안은 매번 반복문을 작성하여 구현하였는데 List 자체 메서드에 좋은 기능이 있다는 것을 알게되어 글을 정리하게 되었습니다. 기존 사용하던 방법for문을 돌려가며 찾아서 삭제 했었습니다.import java.util.*;public class test { public static void main(String[] args) { List list = new ArrayList(); list.add("A"); list.add("A"); list.add("B"); list.add("C"); list.add("D"); ..
2025.01.17
상속관계의 클래스 일 때, @Builder 사용하기
개요코드 구현 중, 상속 관계의 클래스에 @Builder를 적용해야 하는 일이 있었습니다. 평소처럼 @Builder 사용하였지만 상속받은 필드는 빌더의 요소로 나타나지 않는 이슈가 있어 관련 내용을 찾게 되었습니다. @SuperBuilder를 사용하면 된다.@SuperBuilder는 @Builder 사용 때, 상속받은 필드를 빌더에서 사용하지 못하는 등의 제한을 해결하고자 만들어졌습니다. @SuperBuilder를 사용하면 상속받은 필드도 빌더에서 사용 할 수 있게 됩니다.주의 할 점은 부모와 자식 클래스 양쪽 모두 @SuperBuilder 어노테이션을 추가해주어야 한다는 것 입니다. @SuperBuilder 사용 예시- 부모 클래스 Parent@SuperBuilderpublic class Parent ..
2024.11.16
no image
ObjectMapper, Pattern 등의 클래스를 싱글톤으로 사용해도 될지 고민했던 내역
개요코드 짤 때, 급하게 짠다고 다른 사람 코드를 복붙하거나 습관적으로 ObjectMapper, Formatter 등을 로컬 메서드에서 매번 새롭게 선언해가며 사용하였었습니다. 이 때, ObjectMapper, Formatter를 싱글톤으로 빈 주입하여 사용하려고 하는데 가능한건가 싶어 확인하다가 찾은 내용을 정리하고자 합니다. 문서에 다 적혀 있음ObjectMapper, Pattern, DateTimeFormatter를 싱글톤으로 사용해도 괜찮은 이유가 설명 문서에 다 있었습니다.[ObjectMapper][Pattern][DateTimeFormatter]
2024.11.15
no image
인텔리제이에서 자바 버전 변경하기
개요 저는 기본적으로 자바 11 버전을 사용하고 있는데요. 김영한님의 강의를 듣던 중 자바 17 버전을 사용하게 되어야 해서 인텔리제이의 자바 버전을 변경하는 방법을 알아보았습니다. Project Structure 설정 File → Project Structure (Ctrl + Alt + Shift + S)를 들어가서 Project 탭에서 SDK를 원하는 버전으로 바꿔줍니다. 1. [File] 탭을 선택한 후 [Project Structure] 탭을 클릭합니다. 2. Project Structure에 들어간 후 [Project] 탭을 클릭한 후 우측의 Edit 버튼을 눌러 원하는 자바 버전을 선택합니다. 3. 만약 원하는 자바 버전이 인텔리제이에 없다면 중앙 상단에 있는 + 버튼을 눌러 추가해줍니다. 4..
2023.10.17
no image
Java에서의 HashMap 동작 원리 파악하기
개요 코딩테스트를 준비하던 중 효율성 문제 때문에 HashMap을 사용해야했던 문제가 있었습니다. 풀던 중 갑자기 왜 HashMap을 사용하면 시간이 빨라질까 궁금하여 공부하게 되었습니다. 결론 결론부터 말씀드리자면 HashMap의 값을 넣는 put 메서드와 값을 가져오는 get 메서드의 시간복잡도가 O(1)이기 때문입니다. 그러면 왜 시간복잡도가 O(1)인지 파악해보도록 하겠습니다. 해시(Hash)란? 해시란 해시 함수(Hash Function)에 의해 얻어지는 값을 뜻합니다. (해시 값, 해시 코드, 해시 체크섬으로도 불립니다.) 해시 값을 전달해주는 해시 함수에 대해서도 알아보도록 하겠습니다. 해시 함수 또는 해시 알고리즘으로 불리며 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수를 뜻..
2023.07.27

목차

    개요

    List 선언 시, 선언과 동시에 초기화하여 사용하기 편리하여 Arrays.asList()를 종종 사용했습니다. 편리하게 사용도중 필요에 의해 선언된 리스트 첫 원소에 새로운 원소를 더해서 사용해야하는 상황이 있어 add 메서드를 사용하였습니다. 컴파일 에러도 없기에 자신있게 소스를 실행했지만 실제 실행 단계에서 다음과 같은 예외가 발생하였습니다. "arrays.aslist unsupportedoperationexception" 이 오류의 원인을 찾던 중 Arrays.asList() 사용하는데 조심해야 할 부분을 찾게 되어 공유하고자 글을 남깁니다.

     

    Arrays.asList()의 결과물은 Arrays 안의 inner 클래스다.

    오류가 나는 부분을 디버깅 찍어보면 평소에 사용하는 java.util.ArrayList가 아니라 Arrays$arrayList로 찍히는 것을 볼 수 있습니다. 즉, Arrays안의 inner 클래스 임을 알 수 있습니다.

    결론부터 말씀드리자면 아래의 과정으로 인해 unsupportedoperationexception 오류가 발생하게 됩니다.

     

    - Arrays Class(Arrays$arrayList Class)를 살펴보면, AbstractList를 상속받아 생성된 것을 확인할 수 있다.

    - abstractList class에 있는 add나 addAll메서드를 override 하고 있지 않다.

    - 그렇기 때문에 add()메서드 사용 시, AbstractList Class에 있는 add() 메서드를 수행하게 된다.

    - AbstractList Class를 살펴보면 add() 메서드 사용 시 UnsupportedOperationException을 던지게 되어 있다.

     

    아래는 각 코드 입니다.

    **평소 사용하는 ArrayListClass

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        private static final long serialVersionUID = 8683452581122892189L;
        
        ...
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
        ...
        
        public void add(E e) {
                checkForComodification();
    
                try {
                    int i = cursor;
                    ArrayList.this.add(i, e);
                    cursor = i + 1;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
        ...
    }

     

    ** Arrays.asList() 사용 시 사용하게 되는 Class

    private static class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, java.io.Serializable
        {
            private static final long serialVersionUID = -2764017481108945198L;
            private final E[] a;
    
            ArrayList(E[] array) {
                a = Objects.requireNonNull(array);
            }
    
            @Override
            public int size() {
                return a.length;
            }
    
            @Override
            public Object[] toArray() {
                return a.clone();
            }
    
            @Override
            @SuppressWarnings("unchecked")
            public <T> T[] toArray(T[] a) {
                int size = size();
                if (a.length < size)
                    return Arrays.copyOf(this.a, size,
                                         (Class<? extends T[]>) a.getClass());
                System.arraycopy(this.a, 0, a, 0, size);
                if (a.length > size)
                    a[size] = null;
                return a;
            }
    
            @Override
            public E get(int index) {
                return a[index];
            }
    
            @Override
            public E set(int index, E element) {
                E oldValue = a[index];
                a[index] = element;
                return oldValue;
            }
    
            @Override
            public int indexOf(Object o) {
                E[] a = this.a;
                if (o == null) {
                    for (int i = 0; i < a.length; i++)
                        if (a[i] == null)
                            return i;
                } else {
                    for (int i = 0; i < a.length; i++)
                        if (o.equals(a[i]))
                            return i;
                }
                return -1;
            }
    
            @Override
            public boolean contains(Object o) {
                return indexOf(o) != -1;
            }
    
            @Override
            public Spliterator<E> spliterator() {
                return Spliterators.spliterator(a, Spliterator.ORDERED);
            }
    
            @Override
            public void forEach(Consumer<? super E> action) {
                Objects.requireNonNull(action);
                for (E e : a) {
                    action.accept(e);
                }
            }
    
            @Override
            public void replaceAll(UnaryOperator<E> operator) {
                Objects.requireNonNull(operator);
                E[] a = this.a;
                for (int i = 0; i < a.length; i++) {
                    a[i] = operator.apply(a[i]);
                }
            }
    
            @Override
            public void sort(Comparator<? super E> c) {
                Arrays.sort(a, c);
            }
        }

     

    **AbstractList Class

    public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    
        protected AbstractList() {
        }
    
        ...
        
        //	해당 메소드가 실행되는 것이다.
        public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
        
        ...
    }

     

    해결법

    해결법은 간단합니다.

    ArraysList나 LinkedList 등의 컬렉션 클래스로 한번 Wrapping해서 사용하면 됩니다.

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

     

    결론

    Arrays.asList()를 사용 시 주의할 점에 대해 알아보았습니다. 해결법이라고 작성한 부분에서 Wrapping해 사용하면 된다고 하였지만 제일 좋은 방법은 자신이 사용하려는 리스트가 고정된 리스트인지 원소에 변경이 있을 리스트인지 구분해서 적절한 초기화 방법을 처음부터 사용하는 것이 좋을 것 같다고 생각합니다.

     

     

    https://yonguri.tistory.com/137

    https://dev-jwblog.tistory.com/72

    https://blog.gangnamunni.com/post/Arrays-arrayList-ArrayList/

     

    목차

      개요

      List 사용 중에 목록에서 특정 조건을 만족하는 요소를 삭제하는 로직을 다룰 일이 종종 있습니다.

      이 때, 그 동안은 매번 반복문을 작성하여 구현하였는데 List 자체 메서드에 좋은 기능이 있다는 것을 알게되어 글을 정리하게 되었습니다.

       

      기존 사용하던 방법

      for문을 돌려가며 찾아서 삭제 했었습니다.

      import java.util.*;
      
      public class test {
          public static void main(String[] args) {
              List<String> list = new ArrayList<>();
      
              list.add("A");
              list.add("A");
              list.add("B");
              list.add("C");
              list.add("D");
              list.add("E");
      
              for(int i=0;i<list.size();i++){
                  String str = list.get(i);
                  if(str.equals("A")) list.remove(i--);
              }
              System.out.println(String.join(", ", list));
          }
      }

       

      위와 같이 리스트 중 A인 원소가 있으면 삭제하라는 방식의 코드였습니다.

      해당 코드로 실행하고자하면 A가 삭제 될 때, 리스트의 전체 길이가 주는 것을 고려하여 i를 하나 빼주어야 합니다.

      이 때문에 i-1 값을 생각하고 코드를 작성해야해서 가독성이 떨어집니다.

       

      iterator로 하는 방법도 있습니다. 

      아래의 블로그 글을 참고해주시면 됩니다.

      https://hi-dot.tistory.com/4

       

      List.removeIf 사용하기

      List 자체 함수로 위에서 작성하였던 코드를 바꾸면 아래와 같이 됩니다.

      list.removeIf(str -> str.equals("A"));
      System.out.println(String.join(", ", list));

       

      매우 간결하며 직관적으로 변경되었음을 볼 수 있습니다.

       

       

       

      참고자료

      https://velog.io/@peanut_/Java-List.removeIf

      https://hi-dot.tistory.com/4

      목차

        개요

        코드 구현 중, 상속 관계의 클래스에 @Builder를 적용해야 하는 일이 있었습니다. 평소처럼 @Builder 사용하였지만 상속받은 필드는 빌더의 요소로 나타나지 않는 이슈가 있어 관련 내용을 찾게 되었습니다.

         

        @SuperBuilder를 사용하면 된다.

        @SuperBuilder는 @Builder 사용 때, 상속받은 필드를 빌더에서 사용하지 못하는 등의 제한을 해결하고자 만들어졌습니다. @SuperBuilder를 사용하면 상속받은 필드도 빌더에서 사용 할 수 있게 됩니다.

        주의 할 점은 부모와 자식 클래스 양쪽 모두 @SuperBuilder 어노테이션을 추가해주어야 한다는 것 입니다.

         

        @SuperBuilder 사용 예시

        - 부모 클래스 Parent

        @SuperBuilder
        public class Parent {
            private String parentField;
        }

         

        - 자식 클래스 Child

        @SuperBuilder
        public class Child extends Parent {
            private String childField;
        }

         

        - 최종 예제 코드

        Child child = Child.builder()
                        .parentField("parent")
                        .childField("child")
                        .build();

        목차

          개요

          코드 짤 때, 급하게 짠다고 다른 사람 코드를 복붙하거나 습관적으로 ObjectMapper, Formatter 등을 로컬 메서드에서 매번 새롭게 선언해가며 사용하였었습니다. 이 때, ObjectMapper, Formatter를 싱글톤으로 빈 주입하여 사용하려고 하는데 가능한건가 싶어 확인하다가 찾은 내용을 정리하고자 합니다.

           

          문서에 다 적혀 있음

          ObjectMapper, Pattern, DateTimeFormatter를 싱글톤으로 사용해도 괜찮은 이유가 설명 문서에 다 있었습니다.

          [ObjectMapper]

          [Pattern]

          [DateTimeFormatter]

          목차

            개요

            저는 기본적으로 자바 11 버전을 사용하고 있는데요. 김영한님의 강의를 듣던 중 자바 17 버전을 사용하게 되어야 해서 인텔리제이의 자바 버전을 변경하는 방법을 알아보았습니다.

             

            Project Structure 설정

            File → Project Structure (Ctrl + Alt + Shift + S)를 들어가서 Project 탭에서 SDK를 원하는 버전으로 바꿔줍니다.

            1. [File] 탭을 선택한 후 [Project Structure] 탭을 클릭합니다.

            2. Project Structure에 들어간 후 [Project] 탭을 클릭한 후 우측의 Edit 버튼을 눌러 원하는 자바 버전을 선택합니다.

            3. 만약 원하는 자바 버전이 인텔리제이에 없다면 중앙 상단에 있는 + 버튼을 눌러 추가해줍니다.

            4. [Add JDK...] 탭을 클릭하여 원하는 JDK 버전의 폴더를 추가해줍니다. 만약 원하는 JDK 버전의 폴더가 없다면 바로 위의 [Download JDK...] 탭을 클릭하여 편리하게 설치할 수 있습니다.

            Add JDK...선택
            azul-17.0.8.1 폴더 선택
            선택한 자바 버전의 이름을 설정하고 Apply 클릭
            Project 탭으로 돌아간 뒤 설정했던 SDK 이름을 선택하고 Apply 클릭

            만약 IntelliJ 자체에서 JDK를 설치해야한다면

            앞서 보였던 JDK 추가하는 곳에서 Download JDK... 클릭
            버전과 판매사를 설정하면 끝!!

            설치 및 등록을 완료하였으면 다음은 [Modules] 탭을 클릭한 후 [Sources] 탭을 눌러 Language Level을 버전에 맞게 바꾸어 줍니다. (디폴트면 자동으로 맞춰져있습니다.)

            Sources 설정을 마친 후 우측의 [Dependencies] 탭을 클릭합니다. 이동 후 Module SDK의 버전을 맞게 바꾸어 줍니다.

             

            Settings 설정

            File → Setting (Ctrl + Alt + S)을 들어가서 Build, Execution, Deployment → Compiler → Java Compiler로 이동 후 Project bytecode version을 설정합니다.

             

            Gradle 설정

            마지막으로 본인이 사용하고 있는 빌드 툴의 파일에서 설정한 자바 버전이 올바른지 확인합니다.

            저 같은 경우는 Gradle을 사용 중이기에 Gradle 설정을 진행했습니다.

             

             

            reference

            https://dev-emmababy.tistory.com/139

            https://inpa.tistory.com/entry/IntelliJ-%F0%9F%92%BD-%EC%9E%90%EB%B0%94-JDK-%EB%B2%84%EC%A0%84-%EB%B3%80%EA%B2%BD-%EB%B0%A9%EB%B2%95

            목차

              개요

              코딩테스트를 준비하던 중 효율성 문제 때문에 HashMap을 사용해야했던 문제가 있었습니다.

              풀던 중 갑자기 왜 HashMap을 사용하면 시간이 빨라질까 궁금하여 공부하게 되었습니다.

               

              결론

              결론부터 말씀드리자면 HashMap의 값을 넣는 put 메서드와 값을 가져오는 get 메서드의 시간복잡도가 O(1)이기 때문입니다. 그러면 왜 시간복잡도가 O(1)인지 파악해보도록 하겠습니다.

               

              해시(Hash)란?

              해시란 해시 함수(Hash Function)에 의해 얻어지는 값을 뜻합니다. (해시 값, 해시 코드, 해시 체크섬으로도 불립니다.)

              해시 값을 전달해주는 해시 함수에 대해서도 알아보도록 하겠습니다.

              해시 함수 또는 해시 알고리즘으로 불리며 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수를 뜻합니다.

              이러한 해시 값을 Java의 HashMap에서 어떻게 사용하는지 파악해보고자 합니다.

               

              Java의 HashMap 기본 원리

              Java의 HashMap은 Hash Table의 자료구조 형태를 기본하여 작동합니다.

              Hash Table이란 key 값을 해시 함수에 통과시킨 후, 해당 값을 인덱스로 사용한 배열에 값을 저장하는 자료구조입니다.

              출처: wikipedia

               

              이러한 Hash Table을 바탕으로 동작하기에 값을 저장하기 위해 <Key, Value> 쌍을 전달하면 Key 값이 hash function을 통과하여 인덱스화 되고 배열의 해당 인덱스 위치에 Value가 저장되는 것입니다.

              또한 값을 찾기 위해 Key값을 전달하면 hash function을 거쳐 인덱스를 얻은 후 값이 저장된 배열의 해당 인덱스 위치에 값을 얻어오면 되기 때문에 빠른 것입니다.

              Java라는 Key 값에 Spring이라는 Value를 가지도록 HashMap에 저장하는 예시를 아래에 들어보겠습니다.

              HashMap 삽입 시
              HashMap 조회 시

               

              지금까지 <Key, Value> 쌍으로 이루어진 값을 Key를 인덱스화 시켜서 값을 넣고 조회하는게 빠르다는 것을 확인했습니다.

              하지만 만약 새로운 <Key, Value> 쌍을 저장하려는데 새로운 Key 값의 해시 값에 이전에 저장했던 값이 있다면 어떻게 해야할까요.

               

              해시 충돌(Hash Collision)

              앞서 소개드렸던 것과 같은 이슈를 해시 충돌이라고 합니다.

              해시 함수가 입력 값의 길이가 어떻든 고정된 길이의 값을 출력하기 때문에 입력값이 다르더라도 같은 결과값이 나오는 경우가 있습니다. 그렇기 때문에 해시 충돌이 발생하는 것입니다.

              (그래서 해시 충돌이 적을수록 좋은 해시 함수라고 불립니다.)

               

              해시 충돌 회피 방법

              해시 충돌이 일어나도 <Key, Value> 쌍을 안정적으로 저장하고 조회할 수 있도록 하는 방법이 두 가지가 있습니다.

               

              1. Open Addressing

              Open Addressing 방식은 해시 충돌이 발생하면 비어있는 다른 공간에 해당 자료를 삽입하는 방식입니다.

              (위 그림을 예시로 들면 새로운 Key값의 해시 값이 2라서 충돌이 발생하면 3번 자리가 비어있는지 확인하고 비어있으니까 3번 자리에 값을 넣는 방식입니다.)

               

              2. Seperate Chaining

              Seperate Chaining 방식은 해시 충돌이 발생하면 해당 버킷값을 첫 부분으로 하는 LinkedList를 만들어 해결하는 방식입니다.

               

              출처: https://d2.naver.com/helloworld/831311

               

              이 중 Java는 Seperate Chaining 방식을 사용하는데 조금 더 향상된 방법을 사용합니다.

               

              Java의 해시 충돌 회피 방법

              Java에서는 해시 충돌 회피 방법으로 앞서 말씀드렸듯이 Seperate Chaining 방식을 사용하는데요. 조금 더 향상된 방법을 사용한다고 말씀드렸습니다.

              조금 더 향상된 방법이란 바로 Linked List가 아니라 Tree도 사용한다는 점입니다.

              Tree를 사용하게 되면 해당 인덱스에 많은 값들이 들어가게 되었을 때, Linked List보다 조회에 있어 더 높은 효율을 보이게 됩니다.

               

              Java의 향상된 방식을 소개드릴 때 Tree도 사용한다고 말씀드렸는데요.

              그 이유는 Linked List와 Tree를 함께 사용하기 때문입니다.

              HashMap.java 중 일부

               

              위 사진과 같이 TREEIFY라는 용어를 토대로 충돌이 일어난 인덱스에 값이 8개가 모이면 Linked List를 Tree로 변경하고 6개로 바뀌면 다시 Tree를 Linked List로 변경시킵니다.

              이 때, 7개에 대한 기준이 없는 것을 보실 수 있는데요. 이는 잦은 Linked List와 Tree로의 변경으로 인한 성능저하를 막기 위함이라고 합니다.

               

              정리

              이렇듯 Java의 HashMap에 대한 동작 원리를 간단하게 살펴보았습니다.

              사실 제가 다룬 내용은 HashMap 동작 원리의 개략적인 부분입니다.

              혹시 더 많은 부분을 공부해보고 싶으신 분들께서는 아래 참조 블로그 중 네이버 d2의 글을 참고해주시기를 바랍니다.

               

               

              참고 블로그

              https://creampuffy.tistory.com/124

              https://dkswnkk.tistory.com/679

              https://d2.naver.com/helloworld/831311