백엔드 과정/Java

[Java] day19. 컬렉션 프레임워크 ArrayList

mim 2022. 1. 11. 22:15
반응형

컬렉션에 대해서는 완전히 처음들어서 큰일났다. 수업 시작하니 나름 자료구조를 배웠다고 오 자료구조...

했는디 수업 뒤로갈수록 아 코드 너무 길다.... 하나 사용하려면 정말

Iterator<String> dIter = ((LinkedList<String>)stringList).descendingIterator();

이런거 진짜.... 너무 오....

 


 

컬렉션 ? 

자바는 자료구조를 사용해서 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록

인터페이스와 구현클래스를 java.util 패키지에 제공한다. 이들을 총칭해서 컬렉션 프레임 워크라고 부른다.

 

컬렉션은 객체의 저장을 뜻하고, 프레임워크는 사용 방법을 정해놓은 라이브러리를 말하는 것.

컬렉션 프레임워크에는 크게 List, Set. Map이 있다.

 

List

자료들을 순차적으로 나열한 자료구조로 배열과 비슷하게 인덱스로 관리되며

중복되서 인스턴스 저장이 가능하다.

ArrayList , Vector , LinksdList

 

배열과 다른 점은 무엇일까.

바로 저장 용량이 자동으로 증가하며, 객체를 저장할 때 자동으로 인덱스가 부여된다는 것이다.

추가, 삭제, 검색을 위한 다양한 메소드들이 제공된다.

 

ArrayList
가장 많이 사용되는 컬렉션 클래스이다. (JDK 1.2부터 제공)
내부적으로 배열을 이용하여 요소를 관리하며, 인덱스를 이용해 배열 요소에 빠르게 접근할 수 있다.


ArrayList는 배열의 단점을 보완하기 위해 만들어졌다.
배열은 크기를 변경할 수 없고, 요소의 추가, 삭제, 정렬 등이 복잡하다는 단점을 가지고 있다.
ArrayList는 크기 변경(새로운 더 큰 배열을 만들고 데이터 옮기기), 요소의 추가, 삭제, 정렬 기능 등을 
미리 메소드로 구현해서 제공하고 있다. (자동적으로 수행되는 것이지 속도가 빨라지는 것은 아니다.)

 

ArrayList는 인스턴스를 생성하게 되면 내부적으로 10칸짜리 배열을 생성해서 관리한다. 

import java.util.ArrayList; //임포트 구문 추가

ArrayList alist = new ArrayList();

 

다형성을 적용하여 상위 래퍼런스로 ArrayList 객체를 만들 수도 있다.
List 인터페이스 하위의 다양한 구현체들로 타입 변경이 가능하기 때문에
레퍼런스 타입은 List로 해 두는 것이 더 유연한 코드를 작성하는 것이다.

List list = new ArrayList();
		
/* 더 상위 타입인 Collection 타입을 사용할 수 도 있다. */
Collection clist = new ArrayList();

 

ArrayList는 저장 순번이 유지 되며 index(순번)이 적용된다.
ArrayList는 Object 클래스의 하위 타입 인스턴스를 모두 저장할 수 있다.

		alist.add("apple");
		alist.add(123);		//autoBoxing 처리 됨(값 -> 객체)
		alist.add(45.67);
		alist.add(new Date());

 

원하는 인덱스 위치에 값을 추가할 수도 있다. 
값을 중간에 추가하는 경우 인덱스 위치에 덮어쓰는 것이 아니고
새로운 값이 들어가는 인덱스 위치에 값을 넣고 이후 인덱스는 하나씩 뒤로 밀리게 된다.

alist.add(1, "banana");

 

 

size() 메소드는 배열의 크기가 아닌 요소의 갯수를 반환한다.
내부적으로 관리되는 배열의 사이즈는 외부에서 알 필요가 없기 때문에 기능을 제공하지 않는다.

내부 배열에 인덱스가 지정되어 있기 때문에 for문으로도 접근 가능하다.

인덱스에 해당하는 값을 가져올 때get() 메소드를 사용한다. 

System.out.println("alist의 size : " + alist.size());

for(int i = 0; i < alist.size(); i++) {
	System.out.println(i + " : " + alist.get(i));
}

 

지정된 값을 삭제할 때는 remove() 메소드를 사용한다. 
중간 인덱스의 값을 삭제하는 경우 자동으로 인덱스를 하나씩 앞으로 당긴다.

alist.remove(2);

 

지정된 위치의 값을 수정할 때에도 인덱스를 활용할 수 있으며 set() 메소드를 사용한다. 

alist.set(1, true);

 

모든 컬렉션 프레임워크 클래스는 제네릭 클래스로 작성되어 있다. 

List<String> stringList = new ArrayList<>();
stringList.add("apple");

/* 제네릭 타입을 지정하면 지정한 타입 외의 인스턴스는 저장하지 못한다. */
//stringList.add(123);
stringList.add("banana");
stringList.add("orange");
stringList.add("mango");
stringList.add("grape");

 

 

 


ArrayList 정렬하기

 

 

저장 순서를 유지하고 있는 stringList를 오름차순 정렬해보자 
Collection 인터페이스가 아닌 Collections 클래스이다.
Collection에서 사용되는 기능들을 static 메소드로 구현한 클래스이며
인터페이스명 뒤에 s가 붙은 클래스들은 관례상 비슷한 방식으로 작성 된 클래스를 의미하게 된다.

Collections.sort(stringList);

 

조금 복잡하지만 내림차순 정렬을 할 수도 있다.
하지만 기본적으로 ArrayList에는 역순으로 정렬하는 기능은 제공되지 않는다.
역순 정렬은 LinkedList에 정의되어 있는데 현재 사용하는 ArrayList를
LinkedList로 변경할 수 있다.

stringList = new LinkedList<>(stringList);

 

Iterator 반복자 인터페이스를 활용해서 역순으로 정렬한다.

제네릭 적용하는 것이 좋다.
LinkedList 타입으로 형변환 한 후 descendingIterator() 메소드를 사용하면
내림차순으로 정렬 된 Iterator 타입의 목록으로 반환해준다.

Iterator란?
Collection 인터페이스의 iterator() 메소드를 이용해서 인스턴스를 생성할 수 있다.
컬렉션에서 값을 읽어오는 방식을 통일된 방식으로 제공하기 위해 사용한다.
반복자 라고 불리우며 반복문을 이용해서 목록을 하나씩 꺼내는 방식으로 사용하기 위함이다.

 

인덱스로 관리되는 컬렉션이 아닌 경우에는 반복문을 사용해서 요소에 하나씩 접근할 수 없기 때문에
인덱스를 사용하지 않고도 반복문을 사용하기 위한 목록을 만들어주는 역할이라고 보면 된다.

hasNext() : 다음 요소를 가지고 있는 경우 true, 더 이상 요소가 없는 경우 false를 반환
next() : 다음 요소를 반환

Iterator<String> dIter = ((LinkedList<String>)stringList).descendingIterator();

 

역순으로 정렬 된 결과를 저장하기 위해서는 새로운 ArrayList를 만들어서 저장해두면 된다. 

List<String> descList = new ArrayList<>();

while(dIter.hasNext()) {
descList.add(dIter.next());
}

 

 

ArrayList를 생성하려면 저장할 객체 타입을 지정해주어야 하는데,

이때 타입은 기본 자료형이 들어올 수도 있지만,

내가 만들어놓은 클래스를 객체 타입으로 지정할 수 있다. 

 

예를 들어 여러 권의 책 목록을 관리할 ArrayList 인스턴스 생성한다면

BookDTO를 타입으로 지정해주면된다. 

List<BookDTO> bookList = new ArrayList<>();

	/* 도서 정보 추가 */
	bookList.add(new BookDTO(1, "홍길동전", "허균", 50000));
	bookList.add(new BookDTO(2, "목민심서", "정약용", 30000));
	bookList.add(new BookDTO(3, "동의보감", "허준", 40000));
	bookList.add(new BookDTO(4, "삼국사기", "김부식", 46000));
	bookList.add(new BookDTO(5, "삼국유사", "일연", 58000));
		
	/* 정렬 전 책 리스트 출력 */
	for(BookDTO book : bookList) {
		System.out.println(book);
	}

 

똑같이 추가 add, 수정 set, 삭제 remove는 할 수 있지만 만약 정렬을 한다고 하면

/* 제네릭 타입 제한에 의해 Comparable 타입을 가지고 있는 경우에만 sort가 사용 가능하다. */
//		Collections.sort(bookList);

불가능하다. 비교를 어떻게 할 것인지 정의해둔 경우만 가능하기 때문이다.

BookDTO를 어떤 기준으로 정렬해야 하는지 정해져 있지 않다.

 

그렇다면 이 정렬 기준을 세워보자.

 

 

*가격 순으로 오름차순 정렬 - AscendingPrice 추가

implements Comparator<BookDTO>

Comparator 인터페이스를 상속 받으면 오버라이딩 해야 하는 메소드가 강제화 된다. 

import java.util.Comparator;
public class AscendingPrice implements Comparator<BookDTO>{

	@Override
	public int compare(BookDTO o1, BookDTO o2) { 
	/* sort() 에서 내부적으로 사용하는 메소드이다.
	 * 인터페이스를 상속 받아서 메소드 오버라이딩하는 것을 강제화 해 놓았다.
	 * */
		
	/* 비교 대상 두 인스턴스의 가격이 오름차순 정렬이 되기 위해서는
	 * 앞의 가격이 더 작은 가격이어야 한다.
	 * 만약 뒤의 가격이 더 작은 경우 두 인스턴스의 순서를 바꿔야 한다.
	 * 그 때 두 값을 바꾸라는 신호로 양수를 보내주게 되면 정렬 시 순서를 바꾸는 조건으로 사용 된다.
	 * */
		
	/* 양수, 음수 형태로 두 비교 값이 순서를 바꿔야 하는지를 알려주기 위한 용도의 변수 */
	int result = 0;
	
	if(o1.getPrice() > o2.getPrice()) {
		/* 오름차순을 위해 순서를 바꿔야 하는 경우 양수 반환 */
		result = 1;
	} else if(o1.getPrice() < o2.getPrice()) {
		/* 이미 오름차순 정렬로 되어 있는 경우 음수를 반환 */
		result = -1;
	} else {
		/* 두 값이 같은 경우 0을 반환 */
		result = 0;
	}
	
	return result;
}

 

Comparator 인터페이스를 상속 받아 정렬 기준을 정해준 뒤
List의 sort() 메소드의 인자로 정렬 기준이 되는 인스턴스를 넣어주게 되면

내부적으로 우리가 오버라이딩한 메소드가 동작하여 그것을 정렬 기준으로 삼는다.

	bookList.sort(new AscendingPrice()); 
		
	System.out.println("가격 오름차순 정렬 ---------------------------");
	for(BookDTO book : bookList) {
		System.out.println(book);
	}

더 간단히 작성할 수도 있다.

 

인터페이스를 구현할 클래스를 재사용하는 경우 AscendingPrice 클래스처럼 작성하면 되지만
한 번만 사용하기 위해서는 조금 더 간편한 방법을 이용할 수 있다.
익명클래스(Anonymous)를 이용한 방법이다.

익명클래스는 뒤에 {}을 만들어서 마치 Comparator 인터페이스를 상속 받은 클래스인데
이름이 없다고 생각하고 사용하는 것이다. 

booklist.sort(new Compartor<BookDTO>(){
	@Overrride
	public int compare(bookDTO o1, BookDTO 02){
		return o2.getPrice() - o1.getPrice(); //값 비교
	}
});
	System.out.println("가격 내림차순 정렬 ---------------------");
	for(BookDTO book : bookList) {
		System.out.println(book);
	}

 

제목순으로 오름차순 / 내림차순 정렬을 하려면 어떻게 해야할까?

문자열은 대소 비교를 할 수 없는데 ?

 

문자 배열로 변경 후 인덱스 하나하나를 비교해서 어느 것이 더 큰 값인지 확인해야 하는데
String 클래스의 compareTo() 메소드에서 이미 정의해 놓았다. 

	/* 제목 순 오름차순 정렬 */
	bookList.sort(new Comparator<BookDTO>() {

	@Override
	public int compare(BookDTO o1, BookDTO o2) {

		/* 앞의 값이 더 작은 경우(즉, 바꾸지 않아도 되는 경우) 음수 반환,
		 * 같으면 0 반환,
		 * 앞의 값이 더 큰 경우 양수 반환(즉, 바꿔야 하는 경우)
		 * */
			return o1.getTitle().compareTo(o2.getTitle());
		}
			
	});
		
		System.out.println("제목 오름차순 정렬 ---------------------");
		for(BookDTO book : bookList) {
			System.out.println(book);
		}
		
		/* 제목 내림차순 정렬 */
		bookList.sort(new Comparator<BookDTO> () {

			@Override
			public int compare(BookDTO o1, BookDTO o2) {
				return o2.getTitle().compareTo(o1.getTitle());
			}
			
		});
		
		System.out.println("제목 내림차순 정렬 ---------------------");
		for(BookDTO book : bookList) {
			System.out.println(book);
		}
	}

 

 

Comparable 과 Compartor

 

Comparable

  • 패키지 : java.lang
  • 사용 메소드 : compareTo()
  • 정렬 : 기존의 정렬을 구현하는 데 사용
  • 사용법 : 정렬하고자 하는 인스턴스에 Comparable를 상속받아
    compareTo() 메소드를 오버라이딩해 기존의 정렬기준 재정의 -> 한 개의 정렬만 가능

Comparator

  • 패키지 : java.util
  • 사용 메소드 : compare()
  • 정렬 : 그 외 다른 여러 기준으로 정렬하고자 할 때 사용
  • 사용법 : vo 패키지 안에 필요한 정렬 기준에 맞춘 클래스들을 생성하고 Comparator를 상속받아
    compare() 메소드를 오버라이딩해 기존의 정렬 기준 재정의 -> 여러 개의 정렬 가능

 

Collections.sort()

//T인스턴스에 Comparable를 상속받아 compareTo 메소드 재정의를 통해 구현 (단 한가지 기준의 정렬)
Collections.sort(List<T> list) 

//지정한 Comparator 클래스의 의한 정렬 (여러 기준의 정렬)
Collextions.sort(List<T> list.Comparator<T> c)

 


 

 

Vector의 경우 스레드 동기화 처리가 된다는 점만 다르고 ArrayList와 동일하게 동작한다.
JDK 1.0부터 사용하긴 했지만 하위 호환을 위해 남겨놓았고 성능 문제로 현재는 사용하지 않는다.
가급적이면 ArrayList를 사용하면 된다.

반응형