ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • List 정렬에 대하여
    책/Head First Java 2023. 12. 21. 03:18

     

     

    List를 정렬하는 여러가지 방법들을 정리해본다.
    Collections.sort 에서부터 stream.sorted까지 살펴본다.
    글의 흐름은 Head First Java를 기초로 하고 있다.

     

     


    정렬하는 방법들

     

    1.  Collections.sort(List)

    • List인 경우만 정렬이 가능하다.
    /* 배열은 정렬 불가 */
    String[] sArr = {"z", "b", "k"};
    Collections.sort(sArr);              // error
    
    
    /* List로 변경해서 정렬 */
    String[] sArr = {"z", "b", "k"};
    List<String> sList = Arrays.asList(sArr);
    
    System.out.println("b4" + sList);        // z,b,k
    Collections.sort(sList);
    System.out.println("a4" + sList);        // b,k,z - 원본 수정
    
    원본 배열을 정렬하기 때문에, 원본 배열에 변경이 발생!!

     

    • List < T extends Comparable > 인 경우만 가능하다.
    /* Comparable 구현 전 */
    
    class NotComparable {
    	private String name;
    	private int age;
    	
    	NotComparable(String nm, int age) {
    		this.name = nm;
    		this.age = age;
    	}
    	
    	String getName() {
    		return name;
    	}
    	
    	Integer getAge() {
    		return age;
    	}
    
    }
    
    NotComparable nc3 = new NotComparable("3", 10);
    NotComparable nc2 = new NotComparable("2", 17);
    NotComparable nc1 = new NotComparable("1", 12);
    		
    List<NotComparable> ncList = List.of(nc3, nc2, nc1);
    System.out.println(ncList);
    Collections.sort(ncList);    // error - comparable 미구현

     

    Comparable< T > 인터페이스
    Collections.sort를 사용하기 위해 구현되어야하는 인터페이스
    String class는 이미 Comparable을 구현하고 있기 때문에 sort를 사용가능
    SAM interface a.k.a Functional interfaces
    SAM은 single abstract method를 의미한다.
    한개의 추상메소드 구현을 요구하는 interface는 람다로 치환될 수 있다.

     

    /* Comparable 구현 후 */
    
    class NotComparable implements Comparable<NotComparable> {
    	private String name;
    	private int age;
    	
    	NotComparable(String nm, int age) {
    		this.name = nm;
    		this.age = age;
    	}
    	
    	String getName() {
    		return name;
    	}
    
    	Integer getAge() {
    		return age;
    	}
    
    	@Override
    	public int compareTo(NotComparable o) {
    		System.out.println("is executed?");
    		return name.compareTo(o.name);
    	}
    }
    
    NotComparable nc3 = new NotComparable("3", 10);
    NotComparable nc2 = new NotComparable("2", 17);
    NotComparable nc1 = new NotComparable("1", 12);
    		
    List<NotComparable> ncList = List.of(nc3, nc2, nc1);
    System.out.println(ncList);
    Collections.sort(ncList);    // error - 불변객체를 변경하려함
                                 // Collections.sort는 원본을 변경하는데,
                                 // List.of로 생성된 List는 불변List
    
    List<NotComparable> ncList2 = new ArrayList<>();
    ncList2.add(nc3);
    ncList2.add(nc2);
    ncList2.add(nc1);
    
    System.out.println(ncList2);   // nc3, nc2, nc1
    Collections.sort(ncList2);
    System.out.println(ncList2);   // nc1, nc2, nc3

     

    • Collections.sort(List)의 한계

    Comparable을 구현한 형태는 compareTo라는 함수를 Override하게 되는데, 이는 정렬하려는 기준이 여러개가 되었을 때 대응할 수 없다.

     

    /* 이름 순 정렬기능이 구현되어있는데, 나이로 정렬하고 싶다면? */
    
    class Student implement Comparable<Student> {
      private String name;
      private int age;
      
      @Override
      public int compareTo(Student o) {
      	// Override를 사용하기 때문에 1가지 기능만 구현 가능
        // 혹은 flag를 사용하는 좋지 않은 코드가 나올수도..
        return name.compareTo(o.name)
      }
      
    }

     

     

    2.  Collections.sort(List, Comparator)

    • List의 요소가 Comparable을 구현하는 것에서 오는 한계에 대응할 수 있는 방법이다.
    • 원하는 만큼 Comparator 클래스를 작성해서 사용할 수 있다.
    • Comparator을 사용하면 Comparable을 구현할 필요가 없다.
    class NotComparable implements Comparable<NotComparable> {
    	private String name;
    	private int age;
    	
    	NotComparable(String nm, int age) {
    		this.name = nm;
    		this.age = age;
    	}
    	
    	String getName() {
    		return name;
    	}
    
    	Integer getAge() {
    		return age;
    	}
    
    	@Override
    	public int compareTo(NotComparable o) {
    		System.out.println("is executed?");
    		return name.compareTo(o.name);
    	}
    }
    
    class AgeComparator implements Comparator<NotComparable> {
    	@Override
    	public int compare(NotComparable o1, NotComparable o2) {
    		return o1.getAge().compareTo(o2.getAge());
    	}
    }
    
    	
    /* 실행 */    
    NotComparable nc3 = new NotComparable("3", 10);
    NotComparable nc2 = new NotComparable("2", 17);
    NotComparable nc1 = new NotComparable("1", 12);
    		
    List<NotComparable> ncList2 = new ArrayList<>();
    ncList2.add(nc3);
    ncList2.add(nc2);
    ncList2.add(nc1);
    		
    AgeComparator ac = new AgeComparator(); // 원하는 로직을 생성
    System.out.println(ncList2);
    Collections.sort(ncList2, ac); // 외부에서 주입
    							   // 기존에 실행되던 compareTo는 무시됨
    System.out.println(ncList2);

     

    3.  List.sort(null) ㅡ 사용x 참고용

    • Comparator를 넣지 않으면 어떻게 Collections.sort(List)와 동일하게 작동
    • 요소의 Comparable을 사용하여 정렬을 진행
    • 사용을 위해선 Comparable 구현이 필수, 없다면 ClassCastException 발생
    class NotComparable {
    	private String name;
    	private int age;
    	
    	NotComparable(String nm, int age) {
    		this.name = nm;
    		this.age = age;
    	}
    	
    	String getName() {
    		return name;
    	}
    	
    	Integer getAge() {
    		return age;
    	}
    
    }
    
    NotComparable nc3 = new NotComparable("3", 10);
    NotComparable nc2 = new NotComparable("2", 17);
    NotComparable nc1 = new NotComparable("1", 12);
    		
    List<NotComparable> ncList2 = new ArrayList<>();
    ncList2.add(nc3);
    ncList2.add(nc2);
    ncList2.add(nc1);
    			
    System.out.println(ncList2);  // nc3, nc2, nc1
    ncList2.sort();               // compile error 발생
    ncList2.sort(null);           // ClassCastException 발생
    System.out.println(ncList2);

     

     

    4.  List.sort(Compartor) ㅡ Java8

    • 자바8 버전에서 새롭게 도입된, 사용성이 개선된 정렬방법이라고 보면 되겠다.
    • List 인터페이스가 자체적으로 메소드를 가지고 있다.
    • 요소의 Comparable가 사용되지 않으므로, 굳이 구현할 필요없음
    NotComparable nc3 = new NotComparable("3", 10);
    NotComparable nc2 = new NotComparable("2", 17);
    NotComparable nc1 = new NotComparable("1", 12);
    		
    List<NotComparable> ncList2 = new ArrayList<>();
    ncList2.add(nc3);
    ncList2.add(nc2);
    ncList2.add(nc1);
    		
    AgeComparator ac = new AgeComparator();
    		
    System.out.println(ncList2);  // nc3, nc2, nc1
    ncList2.sort(ac);
    System.out.println(ncList2);  // nc3, nc1, nc2
                                  // 여전히 원본은 변경된다

     

    5. stream.sorted() ㅡ Java8

    • stream 역시 자바8에 새롭게 도입된 기능.
    • stream.sorted를 사용하면 기존 List를 변경하지 않고 정렬된 새로운 List를 반환할 수 있다.
    • List의 요소가 Comparable을 구현하지 않는다면 런타임에 Exception
    • stream은 lazy evaluation을 하기 때문에 runtime error
    /* Runtime Exception case */
    
    class NotComparable {
    	private String name;
    	private int age;
    	
    	NotComparable(String nm, int age) {
    		this.name = nm;
    		this.age = age;
    	}
    	
    	String getName() {
    		return name;
    	}
    	
    	Integer getAge() {
    		return age;
    	}
    
    }
    
    NotComparable nc3 = new NotComparable("3", 10);
    NotComparable nc2 = new NotComparable("2", 17);
    NotComparable nc1 = new NotComparable("1", 12);
    		
    List<NotComparable> ncList2 = new ArrayList<>();
    ncList2.add(nc3);
    ncList2.add(nc2);
    ncList2.add(nc1);
    		
    System.out.println(ncList2);
    Stream<NotComparable> stream = ncList2.stream().sorted();
    System.out.println(ncList2);
    		
    List<NotComparable> l = stream.collect(Collectors.toList()); // exception 발생
    System.out.println("기존" + ncList2);
    System.out.println("뉴" + list);

     

    6. stream.sorted(Comparator) ㅡ Java8

    Collections.sort와 비슷하게, Comparator를 사용하지 않는 경우는 필수로 Comparable을 구현해야하고, Comparator를 사용한다면 구현여부와 상관없이 Comparable 관련 메소드는 실행되지 않는다.

    NotComparable nc3 = new NotComparable("3", 10);
    NotComparable nc2 = new NotComparable("2", 17);
    NotComparable nc1 = new NotComparable("1", 12);
    		
    List<NotComparable> ncList2 = new ArrayList<>();
    ncList2.add(nc3);
    ncList2.add(nc2);
    ncList2.add(nc1);
    		
    AgeComparator ac = new AgeComparator();
    		
    System.out.println(ncList2);
    Stream<NotComparable> stream = ncList2.stream().sorted(ac);
    System.out.println(ncList2);
    		
    List<NotComparable> l = stream.collect(Collectors.toList());
    System.out.println("기존" + ncList2);  // 유지
    System.out.println("뉴" + list);       // 정렬됨

     

     


    요약

    1. Comparator || Comparable

    • Collections, List, Stream 모두 Comparator가 있다면 Comparator를 기준으로 하고 Comparable을 무시한다.
    • Comparator가 없다면 Comparable을 꼭 구현해야한다.

     

    2. 불변성

    • Collections, List는 원본 List를 변경하는 방식의 sort
    • Stream은 List를 반환하는 방식의 sort

     

     


    [이미지 출처]

    https://www.amazon.com/Head-First-Java-Brain-Friendly-Guide/dp/1491910771 (글 표지 이미지)

    ' > Head First Java' 카테고리의 다른 글

    HashSet! 무엇을 기준으로 같다고 할 것인가?  (0) 2023.12.20
    원시타입과 래퍼클래스  (0) 2023.12.20
Designed by Tistory.