여러 개의 데이터를 저장하고, 임의의 위치에 새로운 데이터를 추가하거나, 데이터의 삭제가 가능하고, 임의의 위치의 데이터를 읽을 수 있고, 용량에 제한이 없고.... 모두 리스트의 여러 특징들 중에 하나이다.
이러한 리스트를 구현하는 대표적인 두 가지 방법은 배열(ArrayList), 연결리스트(LinkedList)가 있다.
리스트는 기본적으로 삽입(insert), 삭제(remove), 검색(search) 등 기본적인 연산을 제공하는데, 이러한 연산 과정에서 배열과 연결리스트의 장단점이 드러난다.
먼저, 배열의 가장 큰 특징 중 하나는 크기가 고정되어 있다는 것이다. 이로 인해, 데이터를 추가하려는데 배열의 크기를 넘어서게 된다면, 배열을 재할당(reallocation)해줘야 한다. 재할당하는 3가지 방법에 대해서는 아래 코드에 나타내도록 한다. 또한, 리스트의 중간에 원소를 삽입하거나 삭제할 경우 다수의 데이터를 옮겨야 한다. 가령, 원소를 삽입하려면 삽입하려는 위치(인덱스)부터 그 이후의 원소들을 모두 뒤로 한칸씩 밀어줘야하고, 삭제의 경우에는 앞으로 땡겨줘야 한다.
연결리스트는 배열과는 다르게 다른 데이터의 이동없이 중간에 데이터를 삽입하거나 삭제할 수 있다는 장점이 있고, 크기가 고정되어 있는 것이 아니므로 길이의 제한이 없다.
하지만, 랜덤 엑세스가 불가능하다는 단점이 있다.
랜덤 엑세스에 대해 간단하게 설명하면,
먼저 연결리스트는 아래와 같이 노드들의 연결된 형태이다.(head는 첫번째 노드의 주소를 저장하고 있다.)
이때, 각각의 노드들이 다음 노드들의 주소를 링크 필드에 저장하고 있기 때문에 어떤 노드에 접근하고 싶다면, 첫번째 노드부터 순차적으로 주소를 따라가면서 접근해야 한다. 이는 배열에서 특정 원소에 접근하고 싶다면 해당 인덱스로 바로 접근할 수 있는 것과 차이가 있다. 즉, 배열은 랜덤 엑세스가 가능하지만, 연결리스트는 랜덤 엑세스가 불가능하다.
Theme. ArrayList의 구현
MyArrayList 클래스를 생성하여 ArrayList의 일부 메서드들을 구현하도록 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import java.util.Arrays;
// 하나의 array 리스트를 표현하는 클래스
publicclass MyArrayList<E> {
privatestaticfinalint INIT_CAPACITY =10;
private E[] theData;
privateint size; // 현재 배열의 크기
privateint capacity;
public MyArrayList() {
theData = (E[]) new Object[INIT_CAPACITY];
size =0;
capacity = INIT_CAPACITY;
}
// 해당 인덱스에 데이터를 추가
publicvoidadd(int index, E anEntry) {
if (index <0|| index > size) {
thrownew ArrayIndexOutOfBoundsException(index);
}
if (size >= capacity) {
reallocate();
}
for (int i = size -1; i >= index; i--) { // 해당 인덱스를 포함하여 그 이후의 모든 데이터들을 한칸씩 뒤로 보내줌
이때, 주의할 점이 하나 있다. superclass인 Computer class에서 일반적을 데이터 멤버들은 private로 하고, get/set 메서드를 통해 다른 클래스에서 접근할 수 있도록 하는데, 이 경우에 subclass인 Notebook class에서는 상속받은 그 데이터 멤버들을 클래스 객체를 생성하지 않고도 사용할 수 있을까?
private이면 다른 클래스에서 접근하지 못하도록 하는 것인데... 상속을 받은 것은 다른 클래스이긴 하지만 부모 자식 관계이기도 하고...?
정답은 "일반적인 다른 클래스와 동일하게 접근할 수 없다" 이다. 그래서 오버라이딩을 할 때에 부모클래스에서 private처리된 데이터 멤버들은 자식 클래스에서 자신의 데이터 멤버처럼 똑같이 사용할 수 없음에 주의하자. 이게 규칙이다.
이때 등장하는 것이 protected 이다.
어떤 변수가 private이면 다른 어떤 클래스에서도 그 변수에 원칙적으로 접근할 수 없다. 만약, 상속의 관계라고 하더라도 마찬가지이다.
하지만, 어떤 변수가 protected이면 상속 받은 클래스에서만큼은 그 변수에 접근할 수 있도록 허용해준다.
다른 클래스라고 하기에는 애매한 느낌이 들던 자식 클래스는 부모 클래스에서 protected 처리된 데이터 멤버들에는 자신의 데이터 멤버처럼 접근이 가능한 것이다.
protected를 사용하지 않더라도 오버라이딩을 할 때, superclass의 데이터 멤버가 private인 경우에도 subclass에서 에러없이 사용할 수 있는 방법이 있다.
아래 코드를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package section1;
publicclass Notebook extends Computer {
privatedouble screenSize;
privatedouble weight;
public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
super(man, proc, ram, disk, procSpeed); // 에러 해결!
screenSize = screen;
this.weight = weight;
}
// Overriding
// public String toString() {
// String result = "Manufacturer: " + manufacturer +
클래스는 타입이다. 이전에 집이 아니라 집의 설계도라고 언급한 바 있다. 즉, 실체가 아니라는 것이다.
따라서 클래스 안에서 클래스의 데이터 필드에 데이터를 저장할 수는 없고, 클래스의 멤버 메서드를 실행할 수도 없다.
단지 new 명령으로 해당 클래스 타입의 객체를 만든 후, 그 객체에 데이터를 저장하고, 그 객체의 멤버 메서드를 실행하는 것이다.
여기에는 하나의 예외가 존재하는데 그것이 static 멤버이다.
static 멤버는 클래스 안에 실제로 존재하며(클래스 안에서 실체가 있다), 객체에는 존재하지 않는다.
아래 그림을 통해 자세히 설명하도록 한다.
먼저, 가장 중요한 것은 static 멤버는 class 멤버라는 것과 non-static 멤버는 object 멤버라는 것이다.
non-static 멤버부터 알아보자.
희미하게 적혀있는
public int t = 0;
public void print2() {
System.out.println("t= " + t);
}
부분은 클래스 내에서 실제로 변수 t가 존재하거나 print2() 메서드를 실행할 수 있는 것이 아니다는 것을 나타낸다.
t와 print2()는 new 명령으로 Test 타입의 객체를 생성하였을 때, 그 객체 안에 존재하는 것이다.
즉, Test test1 = new Test(); 를 작성하고,
test1.t = 100;
test1.print2();
와 같이 사용해야 하는 것이다.
만약 Test test2 = new Test();를 통해 새로운 객체를 하나 더 만들었다면,
각각의 객체마다 서로 다른 t, print2()가 존재하는 것이다.
(각각의 t는 다른 변수이므로 t에 서로 다른 값을 저장할 수 있다.)
반면에, static 멤버는 class 멤버로 class안에 존재하는 것이지 생성된 객체에 존재하는 것이 아니다.
그래서 static 멤버는 하나의 클래스 안에 유일하게 존재한다. 생성된 객체마다 어떤 멤버가 존재하는 등의 일은 발생할 수 없다.
몇 가지 질문을 통해 static에 대해 좀 더 알아보자.
Q. 왜 main 메서드는 반드시 static이어야 하는가?
먼저, 자바는 클래스들의 집합임을 기억하자. 클래스 외부에 존재할 수 있는 것은 아무것도 없다.
따라서, main 메서드조차 클래스 안에 존재하는 것이다.
여기서 문제가 발생한다. 클래스 안에서는 어떠한 메서드들도 실행할 수 없는데, 그 메서드들은 어디서 실행할 수 있는가?
메서드를 실행하기 위해서는 그 메서드가 포 함된 클래스 타입의 객체를 생성하고, 그 객체 내의 메서드를 실행해야 한다. 이를 프로그램의 출발점인 main에서 실행시키고자 한다면, main 메서드는 static이어야만 한다.
프로그램의 시작점인 main 메서드가 포함된 클래스의 객체를 만드는 것은 불가능하다. 따라서 main 메서드 만큼은 static으로 두어서 그 안에서 다른 클래스들의 객체를 생성하고, 객체 내 데이터를 사용하거나 메서드를 실행하는 등의 작업을 수행할 수 있는 것이다.
Q. 왜 static 메서드에서 같은 클래스의 non-static 멤버를 엑세스 할 수 없는가?
결론부터 말하자면, static은 class의 멤버이고, non-static 멤버는 object 멤버이기 때문이다.
non-object 멤버는 object 멤버이기 때문에 클래스 내에서는 허상일 뿐이다. 객체를 생성해주고 그 객체 안에서 어떠한 값을 가지거나 메서드를 실행할 수 있다. 따라서, 어떤 클래스 내의 static 메서드에서 non-static 멤버를 엑세스할 수 없는 것이다.
이를 확인할 수 있는 아래 코드를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
package section3;
publicclass Test {
staticint s =0;
int t =0;
// 어떤 클래스 안에 있는 static 메서드 안에서
publicstaticvoid printStatic() {
System.out.println("s= "+ s); // static 멤버에 access하는 것은 문제가 없다.
System.out.println("t= "+ t); // non-static 멤버에 access할 수 없다.
예컨대, 상수 혹은 클래스 당 하나만 유지하고 있으면 되는 값(혹은 객체)의 경우가 있다.
Math.PI 가 3.141592....의 값을 가질 때, 프로그램 전체에서 값을 바꿀 이유가 없기 때문에 static 멤버로 만들어 두고 사용하면 된다.
또한 순수하게 기능만으로 정의되는 메서드. 대표적인 예로 수학함수들의 경우 static을 사용하면 유용하다.
Math.abs(k), Math.sqrt(n), Math.min(a,b)와 같은 메서드들은 어느 클래스에 존재하든 독립적으로 매개변수에 따라서만 메서드의 결과가 달라지므로 static 멤버로 사용할 수 있다.
Theme. public
접근 제어: public, private, default, protected 에 대하여 간략하게 정리하면,
public: 클래스 외부에서 접근이 가능하다.
private: 클래스 내부에서만 접근이 가능하다.(클래서 외부에서 접근 불가능)
default: 동일 패키지에 있는 다른 클래스에서 접근 가능하다.
protected: 동일 패키지의 다른 클래스와 다른 패키지의 하위클래스에서도 접근 가능하다.
이중 public 과 private에 대해 좀 더 자세히 알기 위해,
"데이터 캡슐화"에 대해 먼저 알아보자.
결론부터 말하자면, 어떤 클래스 안에 있는 데이터 멤버들을 모두 private으로 만들어서 다른 클래스에서는 접근하지 못하게 만들어 놓고, 대신 필요한 경우에 접근할 수 있도록 하는 것이다. 접근할 수 있는 방법은 해당 클래스에서 public한 get/set 메서드를 제공하는 것이다.
즉, 다른 클래스에서 접근하고자 할 때, public 한 get/set 메서드를 통해 private한 데이터 멤버들에 접근하는 것이다. 그러한 메서드를 getter, setter 메서드라고도 한다.
클래스 내부에 있는 데이터가 무분별하게 접근되는 것을 방지함으로써 의도하지 않은 프로그램 오류 등을 방지하기 위해 private를 사용하고, 전형적인 객체지향 프로그래밍에서는 모든 데이터 멤버들을 private으로 만들고 필요한 경우에 getter, setter를 제공한다.
어떤 집을 짓는다고 할때, 클래스는 마치 하나의 설계도와 같다고 말할 수 있다. 우리가 설계도 안에서 가구를 놓고 살 수 없듯이 설계도는 집을 만들기 위한 도면일 뿐, 실제로 존재하는 집은 아니다.
마찬가지로 클래스 자체에 어떤 데이터를 저장하거나, 클래스 안에 있는 데이터를 읽거나 등등을 하는 것이 아니라 클래스는 객체나 참조변수 등을 만들어 내기 위해 그 객체나 참조변수의 타입을 정의하는 등의 역할을 하는 하나의 설계도일 뿐이다. 반면에 객체는 설계도가 아니라 이를 통해 지어진 집이라고 표현할 수 있다.
클래스 vs 참조변수, 객체의 구도를 나눈다고 했을 때, 참조변수나 객체가 실체가 되고, 클래스는 허상이라고 생각할 수 있다.
Person a = new Person(); 에서
Person 타입의 객체를 new 연산자를 통해 만들었고, 그 객체의 주소를 저장하는(객체를 가리키는) 참조변수 a가 존재한다.
이때, new 연산자를 통해 생성된 객체 그 자체는 이름이 없으므로 이때 참조변수가 필요한 것이다.
Theme. 객체와 참조변수의 사용시 주의할 부분
어떤 클래스의 객체를 생성하고, 객체의 참조변수를 통해 객체에 접근하는 구조에 대해 이해를 하고 있더라도 코드를 작성하는 과정에서 놓칠 수 있는 부분이 있다. 그 놓칠 수 있는 부분에 대해 아래 예제와 함께 알아보도록 하자.
위 예제를 해결하기 위해, 아래와 같이 클래스를 생성하였다.1. MyPoint1.java - x좌표와 y좌표를 선언한 클래스 public class MyPoint1 { public int x; public int y; }
RDBMS(MySql, Oracle 등) 또는 DBMS에서 작성된 sql문을 실행함으로써 DB에 접근할 수 있는 것은 누구나 알고 있다.
MySql을 예로 들자면, MySql Workbench에서 sql문을 작성하면서 MySql과 연결/인증, 문장실행, 결과패치 등을 할 수 있다.
이는 다시 말하면, sql문을 작성할 줄 알면 DB에 접근할 수 있다는 것이다.
물론, sql문을 작성할 줄 알아야만 DB에 접근할 수 있는 것은 아니다.
예를 들어, 개발자가 만든 UI를 이용하는 client는 sql문을 작성할 줄도 모르고, 작성해야할 이유도 없을 것이다.
client가 회원목록에서 어떤 회원에 대한 정보를 달라고 하면, 해당 UI를 통해 sql문이 작성되도록 만들면 되기 때문이다.
Java 프로그래머로서 DB를 이용하려는 사용자의 요구에 부응하는 sql문을 작성해주고, 이를 실행시켜주면 될 것이다.
client의 요구를 처리해줄 수 있는 sql문이 작성이 되고, 작성된 sql문과 DB를 연결하기 위해 필요한 것은 DB API이다.
이때, DB API에 대해 고려해야할 사항이 있다. MySql, Oracle과 같은 RDBMS들이 모두 같은 메서드, 문법을 사용하지 않는다는 것이다. 이로 인해 sql문을 각각에 연결하기 위 DB API가 모두 달라야 한다.
똑같은 sql문을 실행한다고 해도 어떤 (R)DBMS를 쓰냐에 따라 DB API가 달라지게 되고 이는 굉장히 불편할 것이다. DBMS가 바뀌면 새로운 DBMS에 연결하기 위한 DB API에 대해 알아야 하기 때문이다.
이때 등장한 것이 바로 "JDBC"이다. RDMBS에 연결하기 위한 모든 코드들은 다 단일화된 JDBC안에 모여있다고 이해하면 쉽다. 이를 통해 연결하고자 하는 RDBMS에 따라 DB API에 대해 알아야 할 필요가 없다. 이제 각각의 DB API들은 JDBC의 Driver가 된다.(JDBC Driver)
Theme. JDBC 사용방법
기본적인 코드는 아래의 "순서대로" 사용된다.
1. 드라이버 로드
2. 연결 생성
3. 문장(sql문) 실행
4. 결과집합 사용
각각에 대해서 설명하기 전에,
JDBC를 사용하는 과정을 간단하게 설명하자면, UI를 만들고, UI를 통해 사용자의 요구를 파악하며, 그 요구에 따라 sql문을 작성하고 실행하여 DB를 사용하는 것이다. 아래 그림을 살펴보자.