<목차>
• 리스트란?
리스트는 배열과 비슷하지만 좀 더 편의 기능이 많다고 볼 수 있다. 그 차이점은 좀 있다가 설명하고, 리스트에 대해서 서술하자면 리스트는 동일한 데이터 타입의 요소들을 순서대로 저장하는 자료 구조이다.
크기를 동적으로 조절할 수 있으며, 자바에서는 'java.util' 패키지에서 제공하는 'List' 인터페이스와 그를 구현한 클래스를 통해 사용할 수 있다.
- 리스트의 주요 특징
1. 크기 조절
리스트는 동적으로 크기를 조절할 수 있어 요소의 추가 및 삭제가 가능하다.
2. 중복 요소 허용
하나의 리스트 내에 같은 요소가 여러 번 포함될 수 있다.
3. 순서 유지
요소들은 추가된 순서대로 유지된다.
4. 인덱스
각 요소는 0부터 시작하는 인덱스로 접근이 가능하다.
5. 다양한 구현체
자바에서는 'ArrayList', 'LinkedList' 등 다양한 리스트 구현체를 지원한다.
- List 인터페이스의 주요 메서드
add() : 요소를 리스트에 추가한다.
get() : 인데스를 통해 요소를 가져온다.
remove() : 인덱스나 요소를 기준으로 해당 요소를 삭제한다.
size() : 리스트의 크기를 반환한다.
isEmpty() : 리스트가 비어있는지 여부를 확인
contains() : 지정한 요소가 리스트에 포함되어 있는지 여부를 반환한다.
• ArrayList와 LinkedList
자바의 리스트 구현의 대표적인 리스트 구현체중 두 개다.
간단히 정리하면 ArrayList는 배열을 기반으로 구현되어 빠른 접근이 가능하며, LinkedList는 링크드 리스트를 기반으로 구현되어 요소 추가 및 삭제에 특히 효율적이다.
<ArrayList 예시>
import java.util.ArrayList;
import java.util.List;
public class sample {
public static void main (String[] args) {
// ArrayList 생성
List<String> names = new ArrayList<>();
// 요소 추가
names.add("한석봉");
names.add("홍길동");
names.add("권율");
// 요소 접근
System.out.println(names.get(1)); // 인덱스 위치1의 값을 가져온다.
// 요소 삭제
names.remove(0); // 인덱스 위치0의 값을 삭제
// List의 크기
System.out.println(names.size());
System.out.println(names); // 각 요소 전체 출력
// 해당 요소가 존재하는지 여부 확인
System.out.println(names.contains("권율")); // true
}
}
<결과>
홍길동 2 [홍길동, 권율] true |
<LinkedList 예시>
링크드 리스트는 데이터 요소들을 노드(Node)라는 개체로 구성하여 연결된 구조를 가지는 자료구조이다.
각 노드는 데이터와 다음 노드를 가리키는 참조(포인터)로 이루어져 있다.
링크드 리스트는 배열과 달리 데이터의 삽입, 삭제가 상대적으로 효율적으로 이루어지는 특징을 가지고 있습니다.
여기서는 다음 노드를 가리키는 참조 역할은 current가 한다.
// 정수형 데이터를 다루는 링크드 리스트의 예시
// 노드(데이터 요소) 정의
class Node {
int data;
Node next; // 다음 노드에 대한 정보가 담길 노드
// 초기 노드의 값과 다음 노드의 주소를 받아옴
public Node(int data){
this.data = data;
this.next = null;
}
}
// 기본 동작 구현
class LinkedList {
Node head; // 시작 노드에 대한 정보를 담을 head
public LinkedList() {
this.head = null; // LinkedList가 생성되면 자동으로 헤드에 null값 지정
}
// 요소 끝에 노드를 추가하는 메서드
public void appendLst(int data) {
Node newNode = new Node(data);
// 처음 값이 없을 경우 appendFirst를 실행해도 된다.
if (head == null){
head = newNode;
return;
}
// 기존에 값이 있는 경우
Node current = head; // 마지막 노드의 정보를 담기 위함
// 다음에 담길 노드(의 정보)가 null이 아닌 경우
while (current.next != null){ // 다음 노드를 받아오기
current = current.next;
}
current.next = newNode;
}
// 요소 처음에 추가하는 메서드
public void appendFirst(int data) {
Node newNode = new Node(data);
newNode.next = head;
head = newNode;
}
// 리스트의 개수(노드의 수)
public int length() {
int count = 0;
Node current = head;
while (current != null) {
count++;
current = current.next;
}
return count;
}
// 중간에 추가하는 메서드
public void appendMiddle(int data, int i) { // i는 index를 뜻한다.
// 인덱스 위치가 0보다 작거나 입력된 데이터의 수를 초과하는 경우
if (i < 0 || i > length()) {
System.out.println("Invalid index.");
return;
}
// 인덱스의 위치가 0인 경우
if (i == 0) {
appendFirst(data);
} else {
Node newNode = new Node(data);
Node prevNode = head;
for (int j = 0; j < i - 1; j++) {
prevNode = prevNode.next;
}
newNode.next = prevNode.next;
prevNode.next = newNode;
}
}
// 특정(값) 데이터를 삭제
public void deleteNode(int key) { // 여기서 key는 data값이다.
Node current = head;
Node prevNode = null;
// 현재 정보를 담은 노드가 null이 아니고 해당 데이터가 key와 일치한다면
if (current != null && current.data == key){
head = current.next; // 일치하는 경우 현재 노드의 정보를 다음 노드의 정보로 할당
return;
}
// 현재 정보를 담은 노드가 null이 아니고 데이터가 키와 일치하지 않으면
while (current != null && current.data != key) {
prevNode = current;
current = current.next;
}
// 현재 정보를 담은 노드가 null인 경우
if (current == null) {
System.out.println("Node with key " + key + " not found.");
return;
}
// 이전 노드의 다음 주소를 현재 노드의 다음 주소로 할당(지우고자 하는 노드의 정보가 사라짐)
// 말이 복잡해서 다시 정리하면 삭제 기준 앞 노드의 다음 노드로 삭제 기준 뒤 노드를 지정한다.
prevNode.next = current.next;
prevNode = null; // 지우고자 하는 노드의 연결을 끊는 것만으로 끝나면 안된다. 메모리 누수가 발생하기 때문에 null값을 부여.
}
// 리스트의 내용을 출력하는 메서드
// 방법1 - 일반적인 출력
public void printList() {
Node current = head;
while (current != null) {
System.out.print(current.data + " ");
current = current.next;
}
System.out.println();
}
// 방법2 - 배열이 출력되는 형식으로 출력
public void str_printList() {
if (head == null) {
System.out.println("[]");
}
Node current = head;
String str = "[";
while (current.next != null) {
str += current.data + ", ";
current = current.next;
}
str += current.data;
str += "]";
System.out.println(str);
}
}
// 메인
public class sample {
public static void main (String[] args) {
LinkedList list = new LinkedList();
list.appendLst(100);
list.appendLst(200);
list.appendLst(300);
list.appendFirst(50);
list.appendMiddle(25, 2);
System.out.println("Linked List : ");
list.printList();
list.deleteNode(25);
list.str_printList();
list.deleteNode(100);
list.str_printList();
}
}
<결과>
Linked List : 50 100 25 200 300 [50, 100, 200, 300] [50, 200, 300] |
• 제너릭스(Generics)
자바 제너릭스는 자바 프로그래밍 언어의 기능 중 하나로, 클래스나 메서드를 작성할 때 데이터 타입을 일반화(Generalize)하는 방법을 말한다.
제너릭스를 사용하면 클래스나 메서드를 특정 데이터 타입에 종속되지 않도록 만들 수 있다.
이는 코드의 재사용성과 유연성을 향상하며, 타입 안정성(type safety)을 보장하면서 컴파일 타임에 오류를 검출할 수 있도록 도와준다.
좀 더 쉽게 풀어서 설명하면 코드에서 사용할 데이터 타입을 명시하지 않고, 인스턴스화 또는 메서드 호출 시점에 해당 타입을 결정할 수 있으며 이를 통해 같은 로직을 다양한 데이터 타입에 대해 사용할 수 있고, 타입 간의 일관성과 안전성을 유지할 수 있다. 대표적인 예로 자료형을 강제로 바꿀 때 발생하는 캐스팅 오류를 줄일 수 있다.
- 2가지 사용 방법
ArrayList<String> 리스트명 = new ArrayList<String>();
ArrayList<String> 리스트명 = new ArrayList<>(); <- 이 방법을 더 선호한다. 첫 번째 예시에서도 해당 방법을 사용했다.
<제네릭스 예시>
public class sample<T> {
private T content;
public void input(T item) {
this.content = item;
}
public T output() {
return content;
}
public static void main (String[] args) {
sample<Integer> integer_sample = new sample<>();
integer_sample.input(14);
Integer intValue = integer_sample.output();
System.out.println(intValue);
sample<String> string_sample = new sample<>();
string_sample.input("와우, 놀라워");
String stringValue = string_sample.output();
System.out.println(stringValue);
// System.out.println(string_sample.output()); 바로 출력해도 된다.
}
}
• 제네릭스의 특징
위 내용에서 제네릭스란 무엇인지 그 예시를 살펴보았다면 이번엔 제네릭스의 특징에 대해 정리해보려고 한다.
1. 타입 매개변수(Type Parameter)
제네릭 클래스나 메서드를 정의할 때 사용되는 변수로, 실제 타입으로 대체되는 변수다.
대부분의 경우 알파벳 대문자로 표기하며, 예를 들어 위 목차 예시에서 사용된 '<T>'와 같이 표기한다.
2. 타입 인자(Type Argument)
제네릭 클래스나 메서드를 실제로 사용할 때, 타입 매개변수에 대응하여 실제 타입을 지정하는 값이다.
예를 들어 위 예시에서 사용한 'sample<Integer>'에서 'Integer'는 타입 인자에 해당한다.
3. 타입 경계(Type Bounds)
제네릭 타입에 대한 특정 조건을 설정하는 것으로, 타입 매개변수가 특정 클래스나 인터페이스를 상속하거나 구현하도록 제한할 수 있다.
4. 와일드카드(Wildcard)
'?' 기호를 사용하여 제네릭 타입의 일반화된 표현을 나타낼 수 있다. 와이드카드는 특정 타입을 대신할 수 있고, 제한적인 조건으로 사용이 가능하다.
하지만 다음과 같은 상황에서 유용하게 사용될 수 있다.
- 타입을 알 수 없는 경우에 범용적으로 사용하는 경우
- 여러 다른 타입을 다룰 수 있는 유연한 메서드나 클래스를 구현하는 경우
- 타입 매개변수를 정확히 지정할 필요가 없는 경우
<제네릭스 특징과 관련된 예시>
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class sample {
public static void printList(List<?> list) { // 와일드카드를 이용한 리스트 생성
for (Object item : list) { // 오브젝트 타입은 자바의 모든 클래스의 최상위 클래스이며, 모든 객체 참조 가능
// 그러나 타입 정보를 잃어버리기 때문에 런타임 캐스팅 오류가 발생할 수 있다.
// 여기서는 어떤 객체든 참조가 가능하기 때문에 와일드카드에서 사용됨
System.out.print(item + " ");
}
System.out.println();
}
public static void main (String[] args) {
String[] sub = {"지구과학", "화학", "생물학", "물리학"};
Integer[] sub_code = {1112, 1332, 2556, 4447};
List<Integer> intList = new ArrayList<>(Arrays.asList(sub_code));
List<String> strList = new ArrayList<>(Arrays.asList(sub));
printList(intList);
printList(strList);
strList.sort(Comparator.naturalOrder());
System.out.println(strList); // 정렬 기능도 추가
}
}
<결과>
1112 1332 2556 4447 지구과학 화학 생물학 물리학 [물리학, 생물학, 지구과학, 화학] |
• 리스트와 배열의 차이점
자바에서 리스트와 배열은 둘 다 데이터를 순차적으로 저장하는 자료 구조를 갖고 있어 비슷해 보이지만 몇 가지 차이점이 존재한다.
1. 크기 조절
- 배열 : 배열은 생성 시 크기가 고정된다. 크기를 변경하려면 새로운 배열을 생성하고 기존 배열의 데이터를 복사해야 한다.
- 리스트 : 리스트는 동적으로 크기를 조절할 수 있다. 요소의 추가 및 삭제가 자유롭다.
2. 타입
- 배열 : 정적인 타입을 가지며, 한 번 생성한 배열의 타입은 변경할 수 없다. 모든 요소는 동일한 데이터 타입을 가져야 한다.
- 리스트 : 리스트는 동적인 타입을 가지며, 여러 종류의 데이터 타입을 하나의 리스트에 저장할 수 있다.
import java.util.ArrayList;
import java.util.List;
public class MixedTypeList {
public static void main(String[] args) {
List<Object> mixedList = new ArrayList<>();
mixedList.add(42); // 정수
mixedList.add("Hello"); // 문자열
mixedList.add(3.14); // 실수
mixedList.add(true); // 불린
mixedList.add(new ArrayList<>()); // 또 다른 리스트도 추가 가능
for (Object item : mixedList) {
System.out.println(item);
}
}
}
3. 기능
- 배열 : 자체적으로 제공하는 메서드가 제한적이다. 길이를 제외한 많은 작업은 직접 구현해야 한다.
- 리스트 : 'List' 인터페이스와 그를 구현한 클래스들이 풍부한 메서드가 있다. 요소의 추가, 삭제, 검색, 정렬 등의 작업을 간단하게 수행할 수 있다.
4. 메모리 할당
- 배열 : 배열은 메모리에 연속적으로 할당되기 때문에 큰 크기의 배열을 생성할 때 메모리 관리에 주의해야 한다.
- 리스트 : 내부 구조에 따라 메모리가 연속적으로 할당되지 않을 수 있으며, 대규모 데이터 저장이나 빈번한 크기 변경이 필요한 경우 메모리 효율성을 고려해야 한다.
5. 다차원
- 배열 : 다차원 배열은 행렬(Matrix) 등 다차원 데이터를 표현해야 할 때 유용하다.
- 리스트 : 리스트는 일차원 데이터를 순차적으로 저장하는 데 주로 사용한다. 다차원 데이터를 표현하려면 중첩된 리스트를 사용해야만 한다.
• 추가내용(기존 내용 포함)
1. 요소 추가 및 삭제
- add(element) : 리스트에 요소를 추가
- addAll(collection) : 다른 컬렉션의 모든 요소를 리스트에 추가
- remove(element) : 지정한 요소를 리스트에서 삭제
- remove(index) : 지정한 인덱스에 위치한 요소를 삭제
2. 요소 접근
- get(index) : 지정한 인덱스에 위치한 요소를 반환
- set(index, element) : 지정한 인덱스에 위치한 요소를 다른 값으로 변경
3. 크기 및 존재 여부
- size() : 리스트에 포함된 요소의 개수를 반환
- isEmpty() : 리스트가 비어있는지 여부를 반환
4. 요소 포함 여부 검사
- contains(element) : 지정한 요소가 리스트에 포함되어 있는지 여부를 반환
- containsAll(collection) : 다른 컬렉션의 모든 요소가 리스트에 포함되어 있는지 여부를 반환
5. 인덱스 및 순회
- indexOf(element) : 지정한 요소의 첫 번째 등장하는 인덱스를 반환
- lastIndexOf(element) : 지정한 요소의 마지막으로 등장하는 인덱스를 반환
- iterator() : 리스트를 순회하기 위한 반복자(iterator)를 반환
- listIterator() : 리스트를 순회하기 위한 양방향 반복자(list iterator)를 반환
6. 하위 리스트 생성
- subList(fromIndex, toIndex) : 지정한 범위의 요소들로 이루어진 하위 리스트를 생성
7. 배열 변환
- toArray() : 리스트의 요소들을 배열로 변환
- toArray(array) : 리스트의 요소들을 지정한 배열에 복사
8. 리스트 비교
- equals(otherList) : 두 리스트가 같은지 여부를 반환
9. 정렬 및 검색
- sort(comparator) : 요소들을 지정한 컴패레이터(comparator)를 사용하여 정렬
- sort(null) : 요소들을 기본 정렬 순서로 정렬
- binarySearch(element) : 정렬된 리스트에서 이진 검색을 통해 요소의 인덱스를 찾는다.
10. 클리어 및 복사
- clear() : 리스트의 모든 요소를 삭제
- clone() : 리스트의 얕은 복사본을 생성
그 밖에도 많은 추가 기능이 존재한다.
'자바(JAVA)' 카테고리의 다른 글
자바 - 형 변환(Type Conversion) (0) | 2023.08.30 |
---|---|
자바 - Map, 집합, 상수 집합 (0) | 2023.08.29 |
자바 - 배열(Array) (0) | 2023.08.14 |
자바 - StringBuffer 클래스 (0) | 2023.08.11 |
자바 - 문자 (0) | 2023.08.10 |