- Final
- 클래스 : 더 이상 상속이 안된다
- 메소드 : 더 이상 오버라이딩이 안된다
- 필드 : 더 이상 바꿀 수 없다
- 기본 자료형에 final : 할당한 값을 바꿀 수 없다
- 참조 자료형에 final : 할당한 값을 바꿀 수 없거나 새로 생성자를 사용해서 초기화 할 수
- Enum
- Enum 상수 저장 영역
- Enum을 쓰는 이유
- Java에서 Enum 장점
- LinkedList
- Array의 한계점 : 배열의 크기는 고정된다는 것, 새로운 요소를 삽입하거나 삭제하기 위해 빈 공간 관리를 해주어야 한다
- 배열과 비교한 Linked List의 장점 : 삽입 삭제가 수월함 (맨 앞에 아이템 삽입 : O(1))
- LinkedList의 삽입 / 삭제 방식
- 클래스
- 메소드
- 필드
public final class FinalClass {
}
- 더 이상 확장해서는 안되는 클래스, 상속 받아서 내용을 변경해서는 안되는 클래스를 선언할 때
final
public abstract class FinalMethodClass {
public final void printLog(String data) {
System.out.println("Data=" + data);
}
}
- 더 이상 오버라이딩할 수 없게 할 때
final
public class FinalVariable {
final int instanceVariable = 1;
}
- 더 이상 값을 바꿀 수 없는 변수를 선언할 때
final
- 기본 자료형에 final : 값을 바꿀 수 없다
- 참조 자료형에 final : 두 번 이상 값을 할당하거나 새로 생성자를 사용해서 초기화할 수 없다
- 변수 생성과 동시에 초기화를 해야만 컴파일 시 에러가 발생하지 않는다
- 변수 선언만 하고 생성자나 메소드에서 초기화하면 되는 것 아닌지?
- 중복되어 변수값이 선언될 수도 있으니까 final의 기본 의도를 벗어난다
public class FinalVariable {
final int instanceVariable = 1;
public void method(final int parameter) {
final int localVariable;
localVariable = 2;
// localVariable = 3; // 재할당 불가능하다 (컴파일에러)
// parameter = 4; // 재할당 불가능하다 (컴파일에러)
}
}
- 매개변수나 지역변수를 final로 쓸 때는, 선언과 동시에 반드시 초기화 할 필요는 없음 (매개변수는 어차피 초기화되어 넘어오는 값이고, 지역 변수는 메소드를 선언하는 중괄호 내에서만 참조되니까)
- 재할당만 안하면 된다
public class FinalReferenceType {
final MemberDTO dto = new MemberDTO(); // 선언과 동시에 초기화
public static void main(String[] args) {
FinalReferenceType referenceType = new FinalReferenceType();
referenceType.checkDTO();
}
public void checkDTO() {
System.out.println(dto);
// dto = new MemberDTO(); // 두 번 이상 값을 할당할 수 없다
dto.name = "Sangmin";
System.out.println(dto);
}
}
-
final로 선언함과 동시에 초기화 필요하고
-
값을 다시 할당할 수 없다
-
dto 객체는 두 번 이상 생성할 수 없지만, 그 객체 안의 객체들은 final로 선언된 것이 아니므로 이런 제약이 없다!!!
-
해당 클래스가 final 이라고 해서 그 안에 있는 인스턴스 변수나 클래스 변수가 final은 아니다
- 열거 타입 (Enumeration Type)
- 한정된 값을 갖는 데이터 타입
public enum 열거타입이름 { ... }
public enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
...
}
// 열거타입 변수;
Week today;
- 열거 타입 변수를 선언하면, Enum 상수를 지정해서 Enum 클래스 객체 생성을 완료할 수 있다
// 열거타입 변수 = 열거타입.열거상수;
Week today = Week.SUNDAY;
- Enum 타입은 참조 타입이기 때문에
null
도 저장할 수 있다
Week birthday = null;
- Enum 상수도 Enum 객체로 생성된다
- 그래서, 열거 상수는 총 7개의 Enum 객체로 Heap 영역에 생성된다
- 그리고 메소드 영역에 생성된 열거 상수가 Enum 객체를 각각 참조하게 된다
Week today = Week.SUNDAY;
- 열거 타입 변수 (
today
) 는 스택 영역에 생성된다 today
에 저장되는 값은Week.SUNDAY
(열거 상수)가 참조하는 객체의 번지를 가진다
- Enum 클래스는 무조건
java.lang.Enum
이라는 클래스의 상속을 받는다 - extends 하지 않아도 컴파일러가 알아서 이 문장을 추가해서 컴파일한다
- 누가 만들어 놓은 enum을 extends 할 수도 없다
protected Enum(String name, int ordinal) { ... }
- name : enum 상수의 이름
- ordinal : 상수가 선언된 순서대로 0부터
- 문자열과 비교해서, IDE의 지원을 받을 수 있다
- 허용 가능한 값들을 제한할 수 있다
- 리팩토링 시 변경 범위가 최소화된다 (내용의 추가가 필요해도, Enum만 수정하면 된다) (참고 : https://techblog.woowahan.com/2527/)
- 데이터들 간의 연관 관계 표현
- 상태와 행위를 한 곳에서 관리할 수 있다
- 데이터 그룹을 관리할 수 있다
- 관리 주체를 DB에서 객체로 전환할 수 있다
- 배열의 크기는 고정,
- 새로운 요소를 삽입하거나 삭제 시, 배열의 경우 삽입 시에는 새로운 요소가 들어갈 빈 공간을 만들어 주어야 하고, 삭제 시에는 요소가 삭제되어 빈 공간이 생기면, 이를 없애주어야한다
- Dynamic Array
- 삽입 / 삭제가 수월하다
- Linked List의 맨 앞에 새로운 아이템을 삽입하는 경우의 시간 복잡도는
O(1)
이다 - 배열의 맨 앞에 새로운 아이템을 삽입하는 경우의 시간 복잡도는
O(n)
이다
- Random access 가 허용되지 않는다
- 배열의 경우, 인덱스만 알면 random access 를 할 수 있는데, Linked List의 경우 맨 앞의 head node부터 순차적으로 접근을 해야하므로 linked list에서는 이진 탐색을 할 수 없다
- list 내 각 노드에 값, 포인터가 들어가서 포인터를 저장할 추가적인 메모리 공간이 필요하다
- 배열의 경우 연속적인 공간을 할당 받으므로, 캐싱하기 쉬운데, linked list의 경우 연속적인 공간을 할당받지 않으므로 캐싱하기 쉬운 것은 아니다
- 포인터를 바꾸려면 시간이 오래 걸린다
- 단방향 연결 리스트 (singly linked list) 에서는 역방향으로 순회하는 것이 불가능하다
- 탐색은 O(N) 의 시간 복잡도를 가진다
- linked list의 마지막에 아이템을 삽입하는 것은
O(N)
의 시간 복잡도를 가진다
- Simple Linked List
- 각 노드는 단방향으로만 연결되어 있고, 각 노드의 포인터는 다음 노드만 가리킨다
- 마지막 노드의 포인터는
NULL
이다
- Doubly Linked List
- 각 노드의 순방향, 역방향으로 탐색이 가능하다
- Circular Linked List
- Doubly Cicular Linked List
- linked list의 첫번째 노드는
head
라고 불린다 - 각 노드는 두 부분으로 구성되어 있다
- Data : 값을 저장하는 부분
- Pointer : 다음 노드로의 참조 또는 다른 노드의 주소
class LinkedList {
Node head; // linked list의 head
class Node {
int data;
Node next;
// 새로운 노드 생성자
// 처음에는 next가 null로 초기화
Node(int d) {
data = d;
next = null;
}
}
}
- 삭제
- 삽입
- 검색
class LinkedList {
Node head;
static class Node {
int data;
Node next;
Node(int d) {
data = d;
next = null;
}
}
public void printList() {
Node n = head;
while (n != null) {
System.out.println(n.data + " ");
n = n.next;
}
}
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.head = new Node(1);
Node second = new Node(2);
Node third = new Node(3);
list.head.next = second;
second.head.next = third;
list.printList();
}
}
시간복잡도 | 최악의 경우 | 평균 |
---|---|---|
검색 | O(N) | O(N) |
삽입 | O(1) | O(1) |
삭제 | O(1) | O(1) |
- 배열은 Random Access에 대해
O(1)
의 시간 복잡도를 가진다 - 배열과는 다르게, Linked list 에서는 O(1) 만에 요소에 접근할 수는 없다
- 왜냐하면, 연결 리스트에서 i번째 항목에 접근하려면, 첫번째 헤드 노드부터 하나씩 순회해야 한다. 따라서, N개의 요소가 있는 연결 리스트에서는 인덱스를 통해 요소에 접근하려면, 평균적으로
O(N)
이 걸린다.
- 새로운 노드
cur
를 주어진 값으로 초기화 cur
노드의 다음 노드로next
노드를 연결해주기 위해cur
의next
필드를next
노드로 설정한다prev
노드의next
필드를cur
노드로 설정한다
- 삭제될 노드의 앞 노드와 이후 노드를 찾는다
- 이전 노드인
prev
노드의next
필드를next
노드로 연결한다.
cur
노드의 이후 필드인 next
노드를 찾는 것은 어렵지 않은데, 이전 노드인 prev
노드를 찾는 것은 첫번째 head 노드부터 prev
노드를 찾아야 하므로, O(N)
의 시간복잡도가 소요된다. 따라서, 연결 리스트의 삭제 연산은 O(N) 를 가진다고 할 수있다.