Item41. 정의하려는 것이 타입이라면 marker interface를 사용하라

  • marker interface : 아무 method도 없으며, 단지 자신을 구현하는 class가 특정 속성을 가짐을 표시해주는 인터페이스

ex) Serializable interface : 직렬화 가능함을 표시

marker interface 의 장점

marker interface는 2가지 측면에서 marker annotation 에 비해 유리하다.

  1. 타입으로 사용 가능하다
  2. 적용대상을 더 정밀하게 지정이 가능하다.

특정 인터페이스를 구현한 class에만 적용하고 싶은 marker interface 가 있다고 하면 해당 인터페이스를 확장하면 된다.

Read more

Graph

Graph 정의

정점(Vertex)과 간선(Edge)의 집합

그래프 용어 정리

  • 방향 그래프 (Directed Graph) : 간선에 방향이 있는 그래프

  • 무방향 그래프(Undirected Graph) : 간선에 방향이 없는 그래프

  • 차수(Degree) : 임의의 정점에 인접한 정점의 수

  • 경로 : 시작 정점 u로부터 도착 정점 v까지의 정점들

    • 단순 경로 : 경로 내 모든 정점들이 다른 경우
    • Cycle : 시작 정점과 도착 정점이 동일한 경우
  • 연결 성분 (Connected Component) : 그래프에서 정점들이 서로 연결되어 있는 부분

  • 가중치 그래프 (Weighted Graph) : 간선에 가중치가 부여된 그래프

  • 신장트리 (Spanning Tree) : 그래프가 하나의 연결성분으로 구성되어 있을 때, 그래프의 모든 정점들을 cycle없이 연결하는 부분 그래프

    그래프 구현 방법

    그래프를 구현하기 위한 방법으로 2가지가 존재한다.

  1. 인접행렬 (Adajacent Matrix) : 2차원 배열로 표현하는 방법으로 n개의 정점이 존재한다고 하였을 떄 , n x n 배열로 표현하여 그래프의 정점 i 와 정점 j 간에 연결선이 존재하면 1을 표시하고, 연결선이 존재하지 않으면 0을 표시하여 구성한다. dense graph를 표현할떄 유리하다.
  • 장점 : 정점간의 연결선 여부를 O(1) 으로 파악 가능
  • 단점 : O(N^2)의 공간 복잡도를 가짐
  1. 인접 리스트 (Adjacency List) : 정점과 해당 정점에 인접한 정점들을 연결리스트로 표현하는 방식으로, n개의 정점이 존재한다고 하였을떄, n x n 연결리스트로 구성된다. sparse graph를 표현할떄 유리하다.
  • 장점: O(N+E)의 공간 복잡도를 가짐
  • 단점 : 정점간의 연결선 여부를 O(N) 으로 파악가능
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
public class Graph<T> {

// 연결리스트
private Map<Vertex<T>, List<Vertex<T>>> adjVertices = new HashMap<>();

void addVertex(T label){
adjVertices.putIfAbsent(new Vertex(label),new ArrayList<>());
}

void removeVertex(T label){
Vertex vertex = new Vertex(label);
adjVertices.values().stream().forEach((e)->e.remove(vertex));
adjVertices.remove(vertex);
}

void addEdge(T label1,T label2){
Vertex<T> v1 = new Vertex(label1);
Vertex<T> v2 = new Vertex(label2);
adjVertices.get(v1).add(v2);
adjVertices.get(v2).add(v1);
}

void removeEdge(T label1, T label2){
Vertex v1 = new Vertex(label1);
Vertex v2 = new Vertex(label2);
List<Vertex<T>> vertices1 = adjVertices.get(v1);
List<Vertex<T>> vertices2 = adjVertices.get(v2);
if(vertices1 != null){
vertices1.remove(v2);
}
if(vertices2 != null){
vertices2.remove(v1);
}
}
List<Vertex<T>> getAdjvertices(T label ){
return adjVertices.get(new Vertex(label));
}
public class Vertex<T>{
T label;
Vertex(T label){
this.label = label;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Vertex vertex = (Vertex) o;
return Objects.equals(label, vertex.label);
}

@Override
public int hashCode() {
return Objects.hash(label);
}
}
}
Read more

Item40. @Override annotation을 일관되게 사용하라

@Override

Java가 기본으로 제공하는 annotation중 @Override는 상위 타입 method를 재정의하였을때 달릴 수 있다.

@Override annotation을 사용함으로 여러 가지 버그들을 컴파일 시점에 예방해 줄 수 있다.

예를 들어 다음과 같은 영어 알파벳 2개로 구성된 문자열을 표현하는 클래스가 있다고 할때, main method에서 26개의 소문자를 set에 넣어준뒤 출력하면 당연히 26개의 size가 출력되야하지만 실제로는 260 개의 size가 나온다.

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
public class Bigram {
private final char first;
private final char second;

public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram o) {
return first == o.first && second == o.second;
}
public int hashCode() {
return Objects.hash(first, second);
}

public static void main(String[] args) {
Set<Bigram> s = new HashSet<>();
for(int i = 0; i< 10 ; i++){
for (char ch = 'a'; ch <= 'z';ch++){
s.add(new Bigram(ch,ch));
}
}
System.out.println(s.size()); // 260
}
}

그 이유는 바로 equals method를 실수로 parameter 를 Bigram type으로 받아 overrloading하였기 때문이다. @Override 를 붙여주면 바로 compile error를 보여준다.

따라서 상위 class의 method를 재정의하는 모든 method는 @Override annotation을 달 것을 권고한다.

Read more

Git 개념 정리 (2) - Git Branch , Merge

Git Branch

코드를 통쨰로 복사하고 나서 다른 개발자들과 독립적으로 개발을 진행할 수 있도록 branch를 나눌 수 있다.

Git branch는 commit 사이를 이동할 수 있는 포인터와 비슷한 개념으로, 기본적으로 Git은 master branch를 먼저 만든다. 이후 commit을 하게 되면 master branch 는 자동적으로 가장 마지막(최근) commit을 가르킨다.

branch 생성하기

git branch 는 git branch <branch명> 명령어로 만들 수 있다. branch를 새로 만들면 가장 마지막 commit을 가르키게 된다.

1
C:\Users\katd6\OneDrive\바탕 화면\hello-git>git branch testbranch

그럼 두 branch중에 현재 working directory에서 작업중인 branch를 어떻게 나눌까?

Git은 HEAD라고 하는 로컬 Branch를 가르키는 특수한 pointer가 존재한다.

1
2
3
C:\Users\katd6\OneDrive\바탕 화면\hello-git>git branch -a
* master
testbranch

branch 이동하기

git checkout <이동할 branch명>

git branch 는 checkout 명령어로 이동할 수 있다. 아까 정리했던것처럼 Git은 HEAD라고 하는 현재 작업중인 Local branch를 가르키는 pointer인 HEAD가 존재한다고 하였다. git checkout 명령어로 로컬 branch를 바꾼다는 것은 결국에 이 pointer가 가르키는 branch를 변경하겠다는 말이랑 동일하다.

Read more

Git 개념 정리 (1) - Git vs SVN , Git file status

Git 이란?

형상관리(버전관리)를 하기 위한 tool로써 소스코드를 효과적으로 관리하기 위해 개발된 분산형 버전 관리 시스템이다.
형상관리를 하기 위한 또다른 대표적인 tool로 SVN (subversion)이 존재한다. SVN은 Git은 분산형 버전 관리 시스템임에 비해 중앙집중관리식이다. 즉 local PC에서 commit을 하면 바로 중앙저장소에 반영되는 반면에 Git은 각 개발자마다 로컬저장소가 있어, push 전에는 중앙저장소에 반영되지 않는다.

  • Git Repository : Git repository는 변경 이력별로 구분되어 소스코드가 저장되는데, 원격 저장소, 로컬 저장소로 나뉜다.

    • Remote Repository (원격 저장소) : 파일이 원격 저장소 서버에 저장되며 , 협업하는 개발자들과 공유 가능
    • Local Repository (로컬 저장소) : 말 그대로 로컬 PC에 파일이 저장되며 개인 저장소이다.

Git은 로컬저장소가 있음으로 성능상에 SVN에 비해 얻을 수 있는 장점도 있다.
프로젝트의 history를 조회하거나 어떤 파일의 현재 버전과 한달전 버전을 비교하고자 할떄 네트워크 요청없이 수행될 수도 있다.

Git 파일 상태

  • Git은 파일을 Committed , Modified , Staged 이렇게 3가지 상태로 관리한다.
  1. Committed : data 가 local repository에 안전하게 저장되었음 (git commit 이후 상태 )
  2. Modified : data 가 수정되었으나 local repository 에 저장되지 않았음
  3. Staged : 현재 수정한 file을 곧 commit 할것이라고 표시한 상태 (git add 이후 상태)
Read more

Transaction

Transaction

Transaction은 작업의 논리적인 단위로서 , 예를 들면 은행에서 출금 후 입금을 한다고 가정하였을떄 실제로 DB query문은 여러 번 나가겠지만 하나의 논리적인 작업 단위로 볼 수 있다.
Transaction이 필요한 이유는 데이터베이스 연산 중간에 일관되지 않은 상태가 존재하기 떄문이다. 2개의 연산중에 하나만 수행되고 하나는 아직 수행되지 않은 상태가 존재할 수도 있다.
따라서 데이터베이스는 작업의 완전성을 보장해주기 위해서 , 일련의 연산들을 사용자 입장에서 마치 단일 연산인것처럼 보이게 해준다.

추가로 transaction은 다음과 같은 특징을 갖는다.

  • Transaction은 recovery 단위이다. 실제로 DB에 쓰여지는 과정에서 system failure시 write ahead log (WAL) 라고 하는데 log라는 자료구조에 먼저 transaction을 쓰고나서 commit처리를 해준다. 즉 commit 이후에 system failure시에는 이 log를 보고 복구가 가능하다.
  • 아래에 Transaction 특성 중에 Isolation (독립성) 이 있다. 서로 다른 transaction은 독립적으로 수행될 수 있어야 하는데 , 이를 위해서는 concurrency control (동시성 제어) mechanism 이 필요하다.

Transaction의 특성 (ACID)

Transaciton 은 작업의 완전성을 보장하기 위해서 다음과 같은 4개의 특성을 갖는다.

  1. Atomicity (원자성): 원자성을 가지므로, 전부 실행되거나 (commit) 혹은 전부 실행되지 않는다 (rollback)
  2. Consistency(일관성) : Transaction은 데이터베이스의 일관성을 유지해야 한다. 즉 일관된 상태에서 일관된 상태로 변환해야 한다.
  3. Isolation(독립성): Transaction은 서로 독립적으로 수행될 수 있어야 한다.
  4. Durability(지속성) : Transaction이 commit되면 DB에 영구적으로 저장되야 한다.

Transaction 상태

transaction은 다음과 같은 상태를 가진다.

  • Active state : transaction이 아직 실행중인 상태
  • Partially committed state : transaction의 마지막 연산까지 전부 수행되었지만 commit 하기전의 상태
  • Failed : transaction중 오류가 발생해 중단된 상태
  • committed : transaction이 성공적으로 종료되어서 DB에 영구적으로 반영된 상태
  • aborted : transaction이 비정상적으로 종료되어서 rollback 연산을 통해서 작업을 취소하여 transaction 수행 이전의 일관된 DB 상태로 돌아간 상태

transaction의 병행수행 제어 기법 (concurrency control mechanism )

다른말로 동시성 제어인데, 여러가지 transaction 이 공유자원 (DB 데이터)를 동시에 접근할떄 여러가지 문제가 발생할 수 있다

대표적으로 다음과 같은 문제가 생길 수 있다

  • lost update problem(갱신 분실 문제) : transaction A 가 수행한 update가 transaction B가 수행한 update를 덮어씌움.
  • uncommitted dependency problem (비완료 의존성 문제) : transaction A 가 완료되지 않은 상태에서 갱신한 데이터를 transaction B 가 갱신했는데, transaction A가 rollback됨.
Read more

Item39. 명명패턴보다는 annotation을 사용하라

도구나 프레임워크가 특별히 다뤄야 할 프로그램 요소에는 구분되는 명명 패턴을 적용해왔다 예를 들면 JUnit 은 버전 3까지 테스트 method 이름을 test로 시작하게끔 하였다.

명명패턴의 단점

명명 패턴의 단점은 다음과 같다.

  1. 오타시 runtime 예외가 발생
  2. 올바른 프로그램 요소에 사용됨을 보장하지 못함

예를 들면 클래스 이름에 TestSafetyMechanism이라고 명명한다고 하여도 해당 class에 있는 method들은 실행되지 않는다.
3. 매개변수 전달 방법이 없음

특정 예외가 터져야 성공하는 test가 있을때 매개변수로 예외 class type을 전달해줄수 없다.

Annotaion

Junit 4부터는 annotation을 명명패턴 대신에 도입하였다.

annotation에 관한 기본내용은 다음과 같다.

  • Meta-annotation : @Retention, @Target과 같은 annotation 선언에 다는 annotation
  • @Retention
    • SOURCE : 소스코드까지만 annotation이 남아있고, compiler에 의해 .class file에는 제거된다.
    • CLASS : class file 까지 남아있고, run time 시에는 사라진다. (reflection 불가) , default값
    • RUNTIME : run time까지도 남아있는다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      package java.lang.annotation;
      public enum RetentionPolicy {
      /**
      * Annotations are to be discarded by the compiler.
      */
      SOURCE,
      /**
      * Annotations are to be recorded in the class file by the compiler
      * but need not be retained by the VM at run time. This is the default
      * behavior.
      */
      CLASS,

      /**
      * Annotations are to be recorded in the class file by the compiler and
      * retained by the VM at run time, so they may be read reflectively.
      *
      * /
      RUNTIME
      }
Read more

Index

Index 정의

Disk I/O를 최소화시키면서 원하는 데이터를 효율적으로 검색하기 위한 자료구조로 Data file과 분리된 index file 에 보관되며, 데이터 필드에 대한 탐색 키값과 record 물리적 위치를 가르키는 포인터로 구성된다.

  • Index의 크기는 data file에 비해 휠씬 작다.
  • 하나의 data file에 여러 개의 index가 정의될 수 있다. (하나의 희소인덱스와 여러개의 밀집 인덱스를 가질 수 있다. )
  • Index가 정의된 table의 필드를 탐색 키라고 한다.
Read more

Item38. 확장할 수 있는 Enum type이 필요하면 interface를 사용하라

enum type은 상속이 불가능하다.

확장한 타입의 원소는 기반 타입으로 받을 수 있지만, 반대로 기반 타입은 확장한 타입으로 받을 수 없으며, 기반 타입과 확장한 타입들의 원소를 모두 순회할 방법도 마땅치 않다.

enum type의 확장

그럼에도 불구하고 enum type의 확장시 유용한 경우가 종종 있는데, 그 예중에 하나가 연산 코드이다.

enum type은 상속이 불가능한 대신 interface 구현은 가능하다. 따라서 interface를 통해 간접적으로 확장이 가능하다.

client 가 접근할 interface를 두고, 그 interface의 구현체별로 enum type을 정의하는 방법이다.

client가 접근할 operation interface를 두고 operation interface의 구현체로 enum class를 생성하였다.

Read more

Item37. ordinal indexing 대신 EnumMap을 사용하라

다음과 같이 식물 class가 있고 이 class를 LifeCycle enum type을 key로 set에 분류해서 담고 싶다고 가정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Plant {

enum LifeCycle{ANNUAL,PERENNIAL , BIENNIAL}

final String name;
final LifeCycle lifeCycle;

public Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}

@Override
public String toString() {
return name;
}
}

한가지 방법은 Set 배열에 ordinal method()로 가져온 LifeCycle Enum type의 index 값으로 배열에 indexing 해 저장하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for(int i = 0 ; i <plantsByLifeCycle.length; i++){
plantsByLifeCycle[i] = new HashSet<>();
}
for (Plant p : garden) {
plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);
}

for(int i = 0; i< plantsByLifeCycle.length ; i++){
System.out.printf("%s: %s %n", Plant.LifeCycle.values()[i],plantsByLifeCycle[i]);
}

위 코드는 다음과 같은 문제점을 가지고 있다.

  1. Generic 배열 : 타입안전하지 않음
  2. 배열은 각 Index가 무슨 Enum type인지 모름
  3. 상수의 위치가 변경될 경우 바로 고장남 (Item 35. ordinal() method는 사용하지 말라고 권고 )

EnumMap

EnumMap을 쓰면 위와 같이 ordinal method로 indexing 하는 단점을 제거해주고, 추가로 출력 문자열도 자체로 제공해준다.
EnumMap은 runtime에서 generic type 정보 제공을 위해 생성자에서 key 로 사용할 class 객체를 받는다.

1
2
3
4
5
6
7
8
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class);
for(Plant.LifeCycle lc : Plant.LifeCycle.values()){
plantsByLifeCycle.put(lc,new HashSet<>());
}
for (Plant p : garden) {
plantsByLifeCycle.get(p.lifeCycle).add(p);
}
System.out.println("plantsByLifeCycle = " + plantsByLifeCycle);
Read more