1. 객체 지향 프로그래밍이란?

 

객체 지향 프로그래밍(Object-Oriented Programming), 줄여서 OOP.

프로그램을 어떻게 설계해야 하는지에 대한 일종의 개념이자 방법론.

 

프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 

프로그램을 수많은 '객체'라는 기본 단위로 나누고 이 객체들의 상호작용으로 서술하는 방식이다. 

 

객체를 데이터의 묶음으로만 착각하기 쉬운데, 그보다는 하나의 '역할'을 수행하는 메소드와 데이터의 묶음으로 봐야 한다.

 

1.1 절차적 프로그래밍 방식

 

입력을 받아 명시된 순서대로 처리한 다음, 그 결과를 내는 것

어떻게 어떤 논리를 어떤 순서대로 써나가는 것인가로 간주되었다. 

즉, 프로그램 자체가 가지는 기능에 대해서만 신경을 썼지, 이 프로그램이 대체 어떤 데이터를 취급하는 것인가에는 

그다지 관심이 없었던 것이다.

 

1.2 구조적 프로그래밍 방식

 

절차적 프로그래밍 방식을 개선하기 위해 나온 방식으로

프로그램을 함수(procedure) 단위로 나누고 

프로시져끼리 호출을 하는 것이 구조적 프로그래밍 방식이다. 

프로그램이라는 큰 문제를 해결하기 위해 그것을 몇개의 작은 문제들로 나누어 해결하기 때문에 

하향식(Top-down) 방식이라고도 한다.

 

1.3 객체 지향 프로그래밍 방식

 

구조적 프로그래밍 박식을 개선하기 위해 나온 방식으로

객체 지향 프로그래밍이다. 

큰 문제를 작게 쪼개는 것이 아니라, 먼저 작은 문제들을 해결할 수 있는 객체들을 만든 뒤, 

이 객체들을 조합해서 큰 문제를 해결하는 상향식(Bottom-up) 해결법을 도입한 것이다. 

이 객체란 것을 일단 한번 독립성/신뢰성이 높게 만들어 놓기만 하면 그 이후엔 

그 객체를 수정 없이 재사용할 수 있으므로 개발 기간과 비용이 대폭 줄어들게 된다.

 

OOP를 사용하면 코드의 중복을 어느 정도 줄일 수 있고 입력 코드, 

계산 코드와 결과 출력 코드 등 코드의 역할 분담을 좀 더 확실하게 할 수 있어서 가독성이 높아질 수 있다.

 

 

2. 

객체 지향 프로그래밍 요소

 

2.1 캡슐화

 

캡슐화는 프로그램의 세부 구현을 외부로 드러나지 않도록 특정 모듈 내부로 감추는 것이다. 

캡슐화는 객체지향 언어를 구성하는 주요 요소이지만 객체지향 언어에서만 사용되는 개념은 아니다. 

내부의 구현은 감추고(=정보 은닉) 모듈 내에서의 응집도를 높이며, 외부로의 노출을 최소화하여 

모듈 간의 결합도를 떨어뜨리는 개념은 거의 대부분의 언어에 녹아있다.

 

많은 객체지향 언어에서 사용되는 클래스를 기준으로 보면, 

클래스 외부에서는 바깥으로 노출된 특정 메소드에만 접근이 가능하며 클래스 내부에서 

어떤 식으로 처리가 이루어지는지는 알지 못하도록 설계된다.

 

일반적으로 세 종류의 접근 제한이 사용된다.

 

public : 클래스의 외부에서 사용 가능하도록 노출시키는 것이다.

 

protected : 다른 클래스에게는 노출되지 않지만, 상속받은 자식 클래스에게는 노출되는 것이다.

 

private : 클래스의 내부에서만 사용되며 외부로 노출되지 않는다.

 

 

2.2 상속

 

상속은 자식 클래스가 부모 클래스의 특성과 기능을 그대로 물려받는 것을 말한다. 

기능의 일부분을 변경해야 할 경우 자식 클래스에서 상속받은 그 기능만을 수정해서 다시 정의하게 되는데, 

이러한 작업을 '오버라이딩(Overriding: 재정의)'이라고 한다. 

상속은 캡슐화를 유지하면서도 클래스의 재사용이 용이하도록 해 준다.

 

 

2.3 다형성

 

하나의 함수명 등이 상황에 따라 다른 의미로 해석될 수 있는 것을 말한다. 

이를 '오버로딩(Overloading: 중복 정의/다중 정의)'이라고 한다.

 

함수 오버로딩

C++과 C#, Java에서는 함수 오버로딩을 통해 동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작하도록 

할 수 있다.  함수 오버로딩을 너무 자주 사용하면 해당 함수가 어디서 오버로딩되었는지 찾기 어려워질 수 있으므로, 

지나친 남발은 자제하는 것이 좋다.

 

 

2.4 장점

 

이 OOP 특성 덕분에 개발시간 단축(잘 만들어진 클래스는 재사용성을 보장한다) 

정확한 코딩(구현 목적을 위해 클래스를 나눌 수 있으니 구현 단위와 목표가 뚜렷해진다.)이 가능하다.

 

 

3. 객체지향의 원칙[ 5원칙(SOLID) ]

 

객체지향에서 꼭 지켜야 할 5개의 원칙을 말한다. 

일단 한번 보면 개념은 알아 듣긴 하지만 막상 실현하려면 생각보다 어려움이 따른다. 

이 5개의 원칙의 앞글자를 따서 SOLID라고도 부른다.

 

 

3.1 SRP(Single Responsibility Principle) : 단일 책임 원칙 

 

객체는 오직 하나의 책임을 가져야 한다. (객체는 오직 하나의 변경의 이유만을 가져야 한다.)

사칙연산 함수를 가지고 있는 계산 클래스가 있다고 치자. 이 상태의 계산 클래스는 오직 사칙연산 기능만을 책임진다. 

만일 프로그램이 대대적으로 공사를 들어가게 되더라도 계산 클래스가 수정될만한 사유는 누가 봐도 사칙연산 함수와 

관련 된 문제 뿐이다.  이처럼 단일 책임 원칙은 클래스의 목적을 명확히 함으로써 구조가 난잡해지거나 수정 사항이 

불필요하게 넓게 퍼지는 것을 예방하고 기능을 명확히 분리할 수 있게 한다.

위의 원칙이 제대로 지켜지지 않으면 어떻게 될까? 

어떤 프로그래머가 위의 계산 클래스를 통해 GUI를 가지는 계산기 프로그램을 개발하고 있다. 

그런데 중간에 귀찮다고 GUI 관련 코드를 계산 클래스에 넣어버렸다. 

이렇게 되면 계산 클래스는 계산과 GUI라는 두 가지 책임을 지게 되는데 만일 GUI 관련 수정 사항이 발생하게 되면 

별 상관도 없어보이는 계산 클래스를 고치게 된다. 

이처럼 하나의 클래스가 두 가지 이상의 책임을 지니게 되면 클래스의 목적이 모호해지고 기능을 수정할 때 

영향을 받는 범위도 커져서 유지보수가 힘들어지며[1] 결국 작성한 본인조차도 이게 정확히 뭐하는 클래스인지 

명확히 설명할 수가 없는 스파게티 코드가 되어버린다.

 

 

3.2 OCP(Open-Closed Principle) : 개방-폐쇄 원칙

 

객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 한다는 원칙이다. 

즉, 객체 기능의 확장을 허용하고 스스로의 변경은 피해야 한다.

 

예를 들면 도롱뇽과 개구리가 있다. 당신은 이런저런 공통사항을 생각하며 메소드와 필드를 정의한다. 

둘다 이동속도는 다르지만 기본 이동은 도룡뇽은 기어다니고 개구리는 기어다니며 점프하고를 반복한다.

하지만 개구리 같은 동물의 움직임을 구현할 때 애로사항이 있을 것 같다.

 

이동 메소드에서 이동 패턴을 나타내는 코드를 별도의 메소드로 분리하고, 구현을 하위 클래스에 맡긴다. 

그러면 개구리 클래스에서는 이동 패턴 메소드만 재정의하면 동물 클래스의 변경 없이 

기어다니며 점프하는 움직임을 보여줄 수 있다! '동물' 클래스의 '이동' 메소드는 수정할 필요조차 없다(수정에 대해선 폐쇄). 

그냥 개구리 클래스의 이동 패턴 메소드만 재정의하면 그만인 것이다(확장에 대해선 개방).

 

 

3.3 LSP(Liskov Substitution Principle) : 리스코프 치환 원칙

 

자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다는 원칙이다. 

즉 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다는 것. 

상속의 본질인데, 이를 지키지 않으면 부모 클래스 본래의 의미가 변해서 다형성을 지킬 수 없게 된다.

 

또다시 예를 들면, 컴퓨터용 '마우스' 클래스가 있다고 치자. 

컴퓨터에 있는 ps/2 포트나 usb 포트를 통해 연결할 수 있고, 마우스를 바닥에 대고 움직이면 

컴퓨터가 신호를 받아들인다는 것을 안다.  사용 면에서는 왼쪽과 오른쪽 버튼, 그리고 휠이 있어 사용자가 

누르거나 굴릴수 있을 것이다.  마우스가 볼마우스든 광마우스든, 아니면 GPS를 이용하건 간에 아무튼 

사용자는 바닥에 착 붙여 움직일 것이고, 모든 마우스는 예상대로 신호를 보내 줄 것이다. 

 

또한 만약 추가적인 특별한 버튼이 있는 마우스(상속)라도 그 버튼의 사용을 제외한 다른 부분은 

보통의 마우스와 다를 바 없으므로 사용자는 그 마우스의 그 버튼이 뭔 역할을 하던간에 문제 없이 잘 사용한다. 

여기까지 나온 마우스들은 LSP를 잘 지킨다고 볼 수 있다.

 

하지만 오른쪽/왼쪽 버튼 대신 옆쪽 버튼을 사용하는 펜마우스를 처음으로 접하게 되면 사용자는 

평소 보던 버튼을 누를 수 없다며 이상을 호소할 것이다. 이런 경우 LSP를 전혀 지키지 못 하는 것이다.

 

 

3.4 ISP(Interface Segregation Principle) : 인터페이스 분리 원칙

 

클라이언트에서 사용하지 않는 메서드는 사용해선 안된다. 그러므로 인터페이스를 다시 작게 나누어 만든다. 

OCP와 비슷한 느낌도 들지만 엄연히 다른 원칙이다. 하지만 ISP를 잘 지키면 OCP도 잘 지키게 될 확률이 

비약적으로 증가한다.

 

게임을 만드는데 충돌 처리와 이팩트 처리를 하는 서버를 각각 두고 

이 처리 결과를 (당연히) 모두 클라이언트에게 보내야 한다고 가정하자. 

그러면 아마 Client라는 인터페이스를 정의하고 그 안에 충돌전달()과 이펙트전달(이펙트)를 넣어놓을 것이다. 

그리고 충돌 서버와 이펙트 서버에서 이 인터페이스를 구현하는 객체들을 모아두고 있으며, 때에 따라 적절히 신호를 보낸다. 

하지만 이렇게 해두면 충돌 서버에겐 쓸모없는 이펙트전달 인터페이스가 제공되며, 

이펙트 서버에겐 쓸모없는 충돌전달 인터페이스가 제공된다.  이를 막기 위해선 Client 인터페이스를 쪼개 

이펙트전달가능 인터페이스와 충돌전달가능 인터페이스로 나눈 뒤, 충돌에는 충돌만, 

이펙트에는 이펙트만 전달하면 될 것이다. 

또한 Client 인터페이스는 남겨두되 이펙트전달가능과 충돌전달가능 이 둘을 상속하면 된다.

 

 

3.5 DIP(Dependency Inversion Principle) : 의존성 역전 원칙

 

추상성이 높고 안정적인 고수준의 클래스는 구체적이고 불안정한 저수준의 클래스에 의존해서는 안된다는 원칙으로서, 

일반적으로 객체지향의 인터페이스를 통해서 이 원칙을 준수할 수 있게 된다. 

(상대적으로 고수준인) 클라이언트는 저수준의 클래스에서 추상화한 인터페이스만을 바라보기 때문에, 

이 인터페이스를 구현한 클래스는 클라이언트에 어떤 변경도 없이 얼마든지 나중에 교체될 수 있다..

 
 

 


1. 추상화란 무엇인가


추상의 뜻 : 여러 가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용.

抽: 뺄 추

象: 코끼리 상


코끼리 형태의 상의 구조를 빼낸 다는 것이다.


일반화라고 생각하면 된다.


추상화를 사용하면 코드의 재사용성, 가독성을 높이고,


생산성의 증가, 에러의 감소, 유지 보수에 있어 많은 시간을 줄일수 있다.



2. 추상화의 종류


2.1 변수와 상수의 추상화


1
2
3
4
5
6
7
8
public class MyValue{
 
int value = 1;
 
static final double PI = 3.14;
static final int Week = 7;
 
}
cs



변수와 상수를 추상화하지 않는다면 매번 같은 value를 반복해서 넣어야 할 것이다.


2.2 반복문의 추상화



1
2
3
4
5
6
7
System.out.println("a");
System.out.println("a");
System.out.println("a");
 
for(int i=0; i<3; i++){
 System.out.println("a");
}
cs


반복문 또한 추상화이다.

반복문을 사용하지 않는다면 a의 갯수에 따라 a를 출력하기 위해 3줄 이상 적어야하지만

반복문을 사용한다면 문자 a의 갯수에 따라 a를 찍는 것을 한줄로 끝낼수 있다.


2.3 함수의 모듈화 및 재사용


파람값 리턴값 모두 함수단위로 쪼개서 넣고 받는다.


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
package test1;
 
public class Animal {
    
    public int legsCount = 4;
    public int tailCount = 1;
    public int fightingPower = 300;
    public int weight = 400;
    
    // 동물은 기본적으로 다리4개, 꼬리1개, 전투력300, 무게 400을 가지고 있다.
    public int getLegs() {
       return legsCount;
    }
 
    public int getTail() {
       return tailCount;
    }
    
    public int getFightingPower() {
       return fightingPower;
    }
 
    public int getWeight() {
       return weight;
    }
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package test1;
 
public class Bird extends Animal{
    
    public int wings = 2;
    // 동물은 기본적으로 다리4개, 꼬리1개, 전투력300, 무게 400을 가지고 있다.
    // 새는 동물이지만 다리2개, 꼬리 0개 이다.
    public int getLegs() {
        return super.getLegs()-2;
     }
 
     public int getTail() {
        return super.getTail()-1;
     }
     
     public int getWings(){
         return wings;
     }   
 }
cs




Animal에서 상속받은 getLegs(), getTail() 메서드를 Bird에서 부모클래스의 메서드값을 불러와 계산하여 리턴값으로 재정의하고 있다. 

함수안에서 부모메서드를 호출하는 행동, 선언 및 초기화 해 놓은 변수를 사용하는 행동 모두 모듈화 및 재사용의 포함된다.


그럼 이 개념을 적용해서 모델을 만들어 보자.





구조를 설명하면


Animal(동물)을 상속받은 Bird(조류), Mammal(포유류) 클래스

Animal(동물)을 상속받은 Bird(조류)를 상속받은 IslandChicken(무인도닭)

IslandChicken(무인도닭)은 클래스이므로 자바에서는 단일상속만 가능해 Mammal(포유류)를 상속받을 수 없다.

그래서 Mammal(포유류)를 상속받기 위해서 interface MammalInterface를 만들었고 IslandChicken에서 implements 하고 있다.

나머지 LandAnimalInterface(육지동물인터페이스), AnimalInterface(동물인터페이스)를 implements 하고 있다.


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
package test1;
 
public class Animal {
    
    public int legsCount = 4;
    public int tailCount = 1;
    public int fightingPower = 300;
    public int weight = 400;
    
    // 동물은 기본적으로 다리4개, 꼬리1개, 전투력300, 무게 400을 가지고 있다.
    public int getLegs() {
       return legsCount;
    }
 
    public int getTail() {
       return tailCount;
    }
    
    public int getFightingPower() {
       return fightingPower;
    }
 
    public int getWeight() {
       return weight;
    }
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package test1;
 
public class Bird extends Animal{
    
    public int wings = 2;
    // 동물은 기본적으로 다리4개, 꼬리1개, 전투력300, 무게 400을 가지고 있다.
    // 새는 동물이지만 다리2개, 꼬리 0개 이다.
    public int getLegs() {
        return super.getLegs()-2;
     }
 
     public int getTail() {
        return super.getTail()-1;
     }
     
     public int getWings(){
         return wings;
     }   
 }
cs


1
2
3
4
5
6
7
8
9
10
package test1;
 
public class Mammal extends Animal{
 
    // 동물은 기본적으로 다리4개, 꼬리1개, 전투력300, 무게 400을 가지고 있다.
    // 포유류는 동물의 속성을 다 상속받지만 털을 가지고 있다.
    public String getFur(){
        return "털을 가지고 있다.";
    }
}
cs


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
package test1;
 
public class IslandChicken extends Bird implements AnimalInterface, LandAnimalInterface, MamalInterface{
 
    public int averageSpeed = 50;
    public int averageLength = 60;
    // if you define Override Annotation, eclipse inform you
    // must override or implement a supertype method
    // 조류의 다리는 2개이지만 무인도의 사는 특별한 닭의 다리는 1개라고 한다.
    @Override
    public int getLegs() {
        return 1;
   }
    
    // AnimalInterface 인터페이스에 선언된 callSound 내용 구현
    @Override
    public void callSound() {
        System.out.println("꼬꼬댁");
    }
 
    // AnimalInterface 인터페이스에 선언된 runSpeed 내용 구현
    @Override
    public int runSpeed(int number) {
        return number;
    }        
    
    // LandAnimalInterface 인터페이스에 선언된 specialty 내용 구현
    @Override
    public void specialty() {
        System.out.println("발로 땅을 팔수 있음");
    }
 
    // LandAnimalInterface 인터페이스에 선언된 bodyLength 내용 구현
    @Override
    public int bodyLength(int number) {
        return number;
    }    
 
    // LandAnimalInterface 인터페이스에 선언된 getFur 내용 구현
    @Override
    public String getFur() {
        return "털을 가지고 있다.";
    }
    
    public static void main(String[] args) {
        
        //  IslandChicken 생성자 만듬
        IslandChicken islandChicken = new IslandChicken();
        
        // 동물은 다리 4개, 꼬리1개, 조류(extends 동물)는 다리 2개 꼬리 0개, 무인도닭(extends 조류) 다리 1개
        // 클래스는 다중상속이 안되니깐 상속의 상속으로 부르고 있는 구조
        
        islandChicken.getTail(); //returns 0 동물의 꼬리는 1개인데 조류에서 꼬리 0개로 메소드 재정의해서 0
        islandChicken.getLegs(); //returns 1 조류에서 다리 2개로 상속받았는데도 불구하고 무인도닭에서 메소드 재정의시 1
        islandChicken.getWings(); // returns 2 조류에서 상속받은 날개 2개
        // Bird 생성자 만듬
        Bird bird = new Bird();
        bird.getTail(); //returns 1
        bird.getLegs(); //returns 2
        
        // Animal 클래스의 메소드 호출
        bird.getFightingPower(); //returns 300
        bird.getWeight(); //returns 400
        
        // AnimalInterface, LandAnimalInterface 메소드 호출
        islandChicken.callSound();    // 꼬꼬댁
        islandChicken.runSpeed(islandChicken.averageSpeed);    // 50
        islandChicken.specialty();    // 발로 땅을 팔수 있음
        islandChicken.bodyLength(islandChicken.averageLength); // 60
    }
}
cs






여기서 보면 getLegs()메서드는 Animal부터 Bird , IslandChicken 클래스까지 재정의하고 있다.


메소드 이름을 같게 하여 서로 상속받게 하여 어떤 결과가 나오는지 확인해보았는데

IslandChicken에서 getLegs() 오버라이드 어노테이션을 붙이니 must override or implement a supertype method 경고가 떴다.

재정의시 값은 가장 위쪽에서 새로 재정의한 값으로 나왔다.


3. include, extends, implements difference


이 공부를 하면서 자바는  왜 extends와 implements를 구별해서 쓰는가라는 의문이 들었다.

복잡하게 그냥 다 extends 하면 안되나?, implements 다 하면 안되나?

자식클래스의 extends 부모클래스 implements 인터페이스1, 인터페이스2 ...

부모클래스 들어가면 extends 부모의부모클래스 등... 상속의 상속을 거듭하고 

구조가 복잡해지는데 적절하게 언제 extends를 하고 언제 implements를 하고 언제 include를 하는지 알아보았다.

그래서 include, extends , implements에 대해 정리해보았다.



3.1 include와 extends 차이


include는 프로젝트의 필수(must) 기능을 나타내지만 

extends는 선택적(should / could / want) 동작을 의미한다.


use case -> 나 도시에 갈 예정이야

include -> 차 타고 간다.

extends -> 가솔린을 채워라


가솔린을 채우는 건 항상 필요한 일이 아니다.

가솔린이 있어야 차가 동작하는데 가솔린이 있다면 안채워도 되는 것이다.

가솔린이 떨어졌을때 선택적(optionally)으로 필요한 것이다.


하지만 차타고 가는 건 꼭 필요한(prerequisite) 것이다.



3.2 extends와 implements 차이


언제 extends를 써야하고 implements를 써야하나?

일단 extends의 용도와 의미부터 생각해보면



3.2.1 주용도와 형태가 다름


extends클래스를 확장하기 위한 것이고( 클래스는 선언과 내용이 들어가 있는 것 )

implements인터페이스를 구현하기 위해 생긴 것이다. ( 터페이스는 선언만 되어 있는 것 )


3.2.2 단일상속 문제해결을 위해


자바에서 클래스 단일상속만 가능함(java doesn't support multiple inheritance for classes)

하지만 인터페이스 다중상속을 통해 해결가능함.(but this is solved by using multiple interfaces.)


이러한 것들로 인해 include, extends, implements를 적절히 구분하여사용해야 할 것이다.



마지막으로 소스와 작업한 파일을 올린다.




람다식은 메서드를 하나의 식(expression)으로 표현한 것.


-  객체 지향 언어보다는 함수 지향 언어에 가까움.

-  함수를 간략하면서도 명확한 식으로 표현가능하다.

-  메서드를 람다식으로 표현하면 메서드의 이름 및 반환 값이 없어지므로 익명 함수 라고도 한다.

-  람다식의 형태는 매개 변수를 가진 코드 블록이지만 런타임 시에는 익명 구현 객체를 생성한다.



1. 람다의 표현방식


1
2
3
4
5
6
        /*
         * ( parameters ) -> expression body
            ( parameters ) -> { expression body }
            () -> { expression body }
            () -> expression body
         */
cs




2. lambda 사용한 List foreach


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
        Map<String, Object> myMap1 = new HashMap<>();
        Map<String, Object> myMap2 = new HashMap<>();
 
        Map<Integer, Object> finalMap = new HashMap<>();
 
        myMap1.put("name""이상현");
        myMap1.put("age""29");
        myMap1.put("country""KOREA");
 
        myMap2.put("name""김철수");
        myMap2.put("age""25");
        myMap2.put("country""USA");
 
        List<Map<String,Object>> myList1 = new ArrayList<>();
        myList1.add(myMap1);
        myList1.add(myMap2);
 
        // List foreach 문
        for (Map<String, Object> map : myList1) {
            System.out.println(map);
        }
 
        // lambda사용한 List foreach 문
        myList1.forEach(x -> {
            System.out.println(x);
        });
 
        //    output :
        //    {country=KOREA, name=이상현, age=29}
        //    {country=USA, name=김철수, age=25}
        //    {country=KOREA, name=이상현, age=29}
        //    {country=USA, name=김철수, age=25}
cs




3. Map 안에 List와 Map 모두 넣기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        Map<String, Object> myMap3 = new HashMap<>();
        Map<String, Object> myMap4 = new HashMap<>();
 
        myMap3.put("name""김수르");
        myMap3.put("age""26");
        myMap3.put("country""CHINA");
 
        myMap4.put("name""김영희");
        myMap4.put("age""23");
        myMap4.put("country""UK");
 
        // finalMap 안에 List와 Map을 담음
        finalMap.put(0, myList1);
        finalMap.put(1, myMap3);
        finalMap.put(2, myMap4);
 
        //    finalMap :
        //    {
        //        0=[{country=KOREA, name=이상현, age=29}, {country=USA, name=김철수, age=25}]
        //      , 1={country=CHINA, name=김수르, age=26}
        //      , 2={country=UK, name=김영희, age=23}
        //    }
cs




4. lambda 사용한 Map foreach


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
        // for문
        for(int i=0; i< finalMap.size(); i++) {
            System.out.println(finalMap.get(0));
        }
 
        // foreach문 - keySet사용
        for ( Integer key : finalMap.keySet() ) {
            System.out.println( key );
            System.out.println(finalMap.get(key));
        }
 
        // foreach문 - entrySet사용
        for ( Map.Entry<Integer, Object> entry : finalMap.entrySet()) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
 
        // lambda 사용한 foreach문
        finalMap.forEach((k,v) ->{
            System.out.println(k);
            System.out.println(v);
        });
 
        // lambda 사용한 foreach문
        finalMap.forEach((k,v) -> System.out.println(k +""+ v));
 
        // output :
        //    0[{country=KOREA, name=이상현, age=29}, {country=USA, name=김철수, age=25}]
        //    1{country=CHINA, name=김수르, age=26}
        //    2{country=UK, name=김영희, age=23
cs




5. lambda 로 interface와 추상메소드 호출 


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
package myTest;
 
public class myTest2 {
 
    public static void main(String[] args) {
 
        // 람다 사용한 인터페이스 메소드 호출 1
        func1((i, j)->{
            return  i * j;
        });
        // output : 20000
 
        // 람다 사용한 인터페이스 메소드 호출 2
        myInterface2 addExp1 = (int a, int b) -> a + b;
        myInterface2 addExp2 = (int a, int b) -> { return a + b; };
        myInterface2 sub = (int a, int b) -> a - b;
 
        int result = addExp1.calc(12+ addExp2.calc(12+ sub.calc(-55); // 6 - 10 = -4
        System.out.println(result);
        // output : -4
 
    }
 
    // 람다식을 위한 인터페이스에서 추상 메소드는 단 하나여야 한다.
    // 어노테이션을 사용함으로써 추상메소드를 1개만 선언할수 있게 제한함.
    @FunctionalInterface
    public interface myInterface1{
        public int compareMethod(int value1, int value2);
    }
 
    @FunctionalInterface
    public interface myInterface2 {
        public int calc(int a, int b);
    }
 
    public static void func1(myInterface1 myinterface1){
        int value1 = 100;
        int value2 = 200;
 
        int finalValue = myinterface1.compareMethod(value1, value2);
        System.out.println(finalValue);
    }
}
 
cs




6. lambda 사용하여 함수 정의 및 호출 


1
2
3
4
5
6
7
8
9
10
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("As-Is Thread Runnable Define");
            }
        }).start();
 
        new Thread(()->{
            System.out.println("To-be Thread Lambda Express");
        }).start();
cs




7. lambda 사용하여 List Stream Method


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
        List<Integer> myList1 = new ArrayList<>();
        for(int i=0; i<=4; i++) {
            myList1.add(i);
        }
 
        // 스트림// 스트림
        myList1.stream();                
        
        // stream 요소 반복
        myList1.stream().forEach(System.out::println);                             
        // 0 1 2 3 4
        
        // stream 요소를 연산가능하게 함
        myList1.stream().map(i -> i*i).forEach(System.out::println);      
        // 0 1 4 9 16
        
        // stream 요소의 인덱스까지 제한함
        myList1.stream().limit(1).forEach(System.out::println);                
        // 0
        
        // stream 요소의 인덱스를 생략
        myList1.stream().skip(1).forEach(System.out::println);                
        // 1 2 3 4
        
        // stream 요소를 조건문과 비교하여 필터
        myList1.stream().filter(i-> i<=1).forEach(System.out::println);     
        // 0 1
        
        // stream 단일요소 반환 0+1+2+3+4
        myList1.stream().reduce((a,b)-> a+b).get();                                
        // 10
cs




1. Java Collection의 이해


Java Collection FrameWork Hierarchy Diagram  자바 컬렉션 프레임 워크 계층 다이어그램






Java에서 데이터를 저장하는 기본적인 자료구조들을 한 곳에 모아 관리할 수 있다.

JCF의 상속 구조이며 List, Set, Map 3가지로 요약할 수 있다.



2. Java Collection Interface의 종류 및 특징


 인터페이스

 구현 클래스 

 특징 

List

 LinkedList

 Stack

 Vector

 ArrayList

  1. 순서가 있는 데이터의 집합 Ok

  2. 데이터의 중복을 허용 Ok

Set

 HashSet

 TreeSet

 Java Collection Framework (JCF)

  1. 순서를 유지하지 않는 데이터의 집합 No

  2. 데이터의 중복을 허용하지 않는다

Map

 HashMap

 TreeMap

 HashTable

 Properties

  1. 키(key)와 값(value)의 쌍으로 이루어진 데이터의 집합

  2. 순서는 유지되지 않음 No

  3. 키 중복을 허용하지 않음No

  4. 값 중복을 허용 Ok



3. Java Collection Interface

 


3. Collection Interface


모든 콜렉션의 상위 인터페이스로써 콜렉션들이 갖고 있는 핵심 메소드를 선언 

(add, contain, isEmpty, remove, size, iterator ...)



3.1 List Interface 


Collection 인터페이스를 확장한 자료형으로 요소들의 순서를 저장하여 색인(Index)를 사용하여 

특정 위치에 요소를 삽입하거나 접근할 수 있으며 중복 요소 허용


3.1.1 ArrayList


상당히 빠르고 크기를 마음대로 조절할 수 있는 배열, 단방향 포인터 구조로 자료에 대한 순차적인 접근에 강점이 있음


3.1.2 LinkedList


양방향 포인터 구조로 데이터의 삽입, 삭제가 빈번할 경우 빠른 성능을 보장함

스택, 큐, 양방향 큐 등을 만들기 위한 용도로 쓰임


3.1.3 Vector

ArrayList의 구형버전이며, 모든 메소드가 동기화 되어있음, 잘 쓰이진 않음



3.2 Set Interface 


집합을 정의하며 요소의 중복을 허용하지 않음, 상위 메소드만 사용함


3.2.1 HashSet

가장 빠른 임의 접근 속도, 순서를 전혀 예측할 수 없음


3.2.2 LinkedHashSet

추가된 순서, 또는 가장 최근에 접근한 순서대로 접근 가능


3.2.3 TreeSet

정렬된 순서대로 보관하며 정렬 방법을 지정할 수 있음



3.3 Map Interface


Key와 Value의 쌍으로 연관지어 저장하는 객체


3.3.1 HashMap

Map 인터페이스를 구현하기 위해 해시테이블을 사용한 클래스

중복을 허용하지 않고 순서를 보장하지 않음

키와 값으로 null이 허용


3.3.1.1 LinkedHashMap

기본적으로 HashMap을 상속받아 HashMap과 매우 흡사

Map에 있는 엔트리들의 연결 리스트를 유지되므로 입력한 순서대로 반복 가능


3.3.2 Hashtable

HashMap 보다는 느리지만 동기화가 지원

키와 값으로 null이 허용되지 않음


3.3.3 TreeMap

이진검색트리의 형태로 키와 값의 쌍으로 이루어진 데이터를 저장

정렬된 순서로 키/값 쌍을 저장하므로 빠른 검색이 가능

저장시 정렬(오름차순)을 하기 때문에 저장시간이 다소 오래 걸림





java api url : https://docs.oracle.com/javase/7/docs/api/






1. ArrayList와 LinkedList의 구조







2. ArrayList와 LinkedList의 삽입과 삭제 과정



 

  삽입할 자료만큼 

공간을 늘리는 작업을 한다.


 ② 삽입할 자료의 위치를 기준으로 기존의 데이터들을 뒤로 or 앞으로 이동하는 연산을 수행한다.


 ③ 삽입할 위치에 자료가 입력되면 삽입연산을 마친다.

  ArrayList 삽입

 


  삭제할 자료가 위치한 인덱스의 자료를 삭제한다.


 ② 삭제한 자료의 인덱스를 기준으로 이후의 자료들을 이동하는 연산을 한다.


 ③ List의 맨 마지막은 비어있는 상태로 완료한다.

 ArrayList 삭제

 


  추가될 자료의 node를 생성한다.


 ② 추가될 자료의 인덱스 이전 node와 추가될 자료 인덱스 이후 node를 설정한다.

 LinkedList 삽입



    삭제할 노드의 이전 노드와 이후 노드를 연결한다.

 LinkedList 삭제



3. ArrayList와 LinkedList의 특징 및 비교


ArrayList 특징



1. n개의 자료를 저장할때 ArrayList는 자료들을 하나의 연속적인 묶음으로 묶어 자료를 저장

2. 무작위접근(random access) 가능


3. 사이즈 고정되어 있음 

4. 삽입 시 사이즈를 늘려주는 연산 추가되야 함

5. 삭제 시에는 순차적인 인덱스 구조로 인해 삭제된 빈 인덱스를 채워야 하기 하기 때문에 연산이 추가되어야 함

6. 지속적으로 삭제 되는 과정에서 공간만큼 낭비되는 메모리가 많음

7. 삽입 삭제가 빈번하게 발행하는 프로세스의 경우 좋지 않음





Linked List 특징


1. 연결형태로 연결 가능

2. ArrayList처럼 뒤로 밀거나 채우는 작업 없이 주소만 서로 연결시켜 주면 되기 때문에 추가 삭제가 ArrayList보다 빠르고 용이함.

3. 삽입삭제가 빈번하게 발생되면 Linked List을 사용해 시스템 구현이 바람직함.


4. 순차접근(sequential access)만 가능

5. 단순 LinkedList 는 단방향성을 갖고 있어 인덱스를 이용해 자료를 검색하는 애플리케이션에 적합하지 않음

6. 순차접근도 참조의지역성(한번 참조한 데이터는 다시 참조될 가능성이 높음) 때문에 LinkedList보다 ArrayList가 훨씬 빠름.

7. n개의 자료를 저장할때 LinkedList는 자료들을 저장공간에 불연속적인 단위로 저장

8. LinkedList는 메모리 이곳저곳에 산재해 저장되어 있는 노드들을 접근하는데 ArrayList보다는 긴 지연 시간이 소모됨

9. LinkedList는 참조자를 위해 추가적인 메모리를 할당해야함(자료들의 크기가 작은 리스트의 경우 참조자를 위한 추가 적인 메모리할당은 비실용적임)



작업파일 첨부 : 


arraylist, linkedlist.pptx




1. String to LocalDateTime, String to LocalDate




1
2
3
4
5
6
7
8
9
10
11
12
13
14
// String to LocalDateTime
String issueDate = "20180604"
String tempDate = issueDate.substring(04)+"-"+issueDate.substring(46)+"-"+issueDate.substring(68)+" 00:00";
 
//  tempDate = "2018-06-04 00:00"
LocalDate tempDate2 = LocalDate.parse( issueDate , DateTimeFormatter.BASIC_ISO_DATE); // 2018-10-04
 
//  tempDate2 = "2018-06-04"
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime localDateIssueDate = LocalDateTime.parse(tempDate, formatter); // localDateIssueDate "2018-06-04T00:00"
 
// String to LocalDate
DateTimeFormatter dateformatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate a = LocalDate.parse("2018-06-04",dateformatter); // LocalDate 2018-06-04
cs



2. LocalDateTime to LocalDate, LocalDate to LocalDateTime



1
2
3
4
5
6
7
8
9
10
11
12
// LocalDateTime to LocalDate 
LocalDateTime localDateTime = LocalDateTime.parse("2018-06-04T00:00");
 
LocalDate localDate = localDateTime.toLocalDate(); // localDate : 2018-06-04     
 
// LocalDate to LocalDateTime
 
LocalDate date = LocalDate.parse("2018-06-04");        // 2018-06-04
 
LocalDateTime localDateTime1 = date.atStartOfDay();     // 2018-06-04T00:00
LocalDateTime localDateTime2 = date.atTime(13,15);      // 2018-06-04T13:15
LocalDateTime localDateTime3 = date.atTime(16,0540);    // 2018-06-04T16:05:40
cs



3. LocalDate, LocalDateTime Compare 비교하기



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
// 1. LocalDate compareTo
LocalDate startDt = LocalDate.of(201864);
LocalDate endDt = LocalDate.of(201866);
LocalDate issueDt = LocalDate.of(201865);
 
System.out.println(startDt.compareTo(endDt));  // startDt(2018, 6, 4) > endDt(2018, 6, 6) -> return -1
System.out.println(startDt.compareTo(startDt));    // startDt(2018, 6, 4) = startDt(2018, 6, 4) -> return 0
System.out.println(endDt.compareTo(startDt));  // endDt(2018, 6, 6) > startDt(2018, 6, 4) -> return 1
 
// startDt(시작일자)와 endDt(종료일자) 사이에 issueDt(발행일자)가 들어가는지 비교하기 위해 조건문을 작성하면
 
if(issueDt.compareTo(cmpnStrtDttm) == -1 // 발행일이 시작일보다 작거나
        || issueDt.compareTo(cmpnEndDttm) == 1 ) { // 발행일이 종료일보다 크면
    throw new exception("발행일자가 시작일자와 종료일자 기간사이에 속해있지 않습니다.");
}
 
// 2. LocalDateTime isBefore, isAfter
 
// LocalDateTime형식의 strtDt : 2018-06-04T00:00 , endDt: 2018-06-05T00:00, isuueDt: 2018-06-04T00:00
LocalDateTime strtDt = LocalDateTime.parse("2018-06-04T00:00");
LocalDateTime endDt = LocalDateTime.parse("2018-06-05T00:00");
LocalDateTime isuueDt = LocalDateTime.parse("2018-06-04T00:00");
 
if( (isuueDt.isAfter(strtDt) // 발행일이 시작일보다 커야(이후여야)하고
    && isuueDt.isBefore(endDt) ) // 발행일이 종료일보다 작아야(이전이어야)하고
        || (isuueDt.isEqual(strtDt) // 발행일이 시작일과 같을경우
        || isuueDt.isEqual(endDt) )){ // 발행일이 종료일과 같을경우
        System.out.println("발행일이 시작일과 종료일 사이에 속해있음."); 
}else{
    throw new exception("발행일이 시작일과 종료일 사이에 속해있지 않습니다.");
}
 
if( (!isuueDt.isAfter(strtDt) // 발행일(06-04)이 시작일(06-05)보다 크지(이후이지) 않고
    && !isuueDt.isBefore(endDt) )) // 발행일(06-04)이 종료일(06-05)보다 작지(이전이지)않고
{
    throw new exception("발행일이 시작일과 종료일 사이에 속해있지 않습니다.");
}else{
        System.out.println("발행일이 시작일과 종료일 사이에 속해있음."); 
}
cs


LocalDate, LocalDateTime의 형식과 Compare 방법에 대해 알 수 있었다.



자바 동적변수 생성 테스트, 해결방법은?


1. 첫번째 시도



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 엄청 긴 숫자와 문자가 섞인 문자열이 있는데 
// 5자리씩 잘라서 이 자릿수가 맞는지 검증해야 했었다.
// 어떻게하면 효율적으로 문자열을 잘라내 저장하여 활용할수 있는지 테스트 해보았다.
 
String myString = "ABCDEFGHIJKLMNOPQRSTUVWXY";
 
// 1번째 방법 시도 : 필요한 변수를 모두 선언한 뒤 값 넣기
String firstNo ="";
String secondNo ="";
String thirdNo ="";
String fourthNo ="";
 
firstNo      = myString.substring(05);       // ABCDE
secondNo     = myString.substring(510);       // FGHIJ
thirdNo      = myString.substring(1015);    // KLMNO
fourthNo      = myString.substring(1520);    // PQRST
 
// 각각의 값을 다르게 관리하기 위해 매번 필요한 변수명을 만들어줘야하나 라는 생각이 들어.
cs


결과


firstNo  : ABCDE

secondNo : FGHIJ

thirdNo  : KLMNO

fourthNo : PQRST



2. 두번째 시도



1
2
3
4
5
6
7
8
9
10
// 2번째 방법 시도 : 자바로 동적변수를 만들수 있나?
for(int i=0,  j=0; i<=3; i++, j=j+5) {
    // ABCDE , FGHIJ, KLMNO, PQRST, UVWXY
    // String a0 = "ABCDE", String a1 = "FGHIJ", a2, a3 ... 
    // 동적변수 선언 및 5자리씩 값을 초기화 해보려고 시도
    
    String k = String.valueOf(i); // i를 String으로 치환후 k로 재선언
    String a+= myString.substring(j, j+5);
    // 동적변수 만들기 불가 .. 같은 String이어도 +기호를 사용해 만들순 없었다.
}
cs


결과


FAIL



3. 세번째 시도



1
2
3
4
5
6
7
8
//  3번째 방법 시도 : Map을 이용해봄.
HashMap<Integer, String> myHashMap = new HashMap<Integer, String>();
for(int i=0,  j=0; i<=3; i++, j=j+5) {
    myHashMap.put(i, myString.substring(j, j+5));
}
myHashMap.forEach((k,v)->{
    System.out.println("key: "+k+"value: "+v); // 해결가능
});
cs


결과


key: 0 value: ABCDE

key: 1 value: FGHIJ

key: 2 value: KLMNO

key: 3 value: PQRST

key: 4 value: UVWXY




1. AnyEdit 플러그인 사용 이유



Model을 만들때 테이블 컬럼명을 기준으로 기본을 잡는경우가 많다.


테이블 컬럼명은 대부분 Underscores(_표시) 예) dc_type_cd 로 되어있는 경우가 많은데


테이블 _(언더바)를 지우고 dcTypeCd인 카멜로 변환시켜주는 에디트 플러그인이 AnyEdit 플러그인이다.



2. AnyEdit 플러그인 기능 설명



2.1 소문자로 바꾸기 :  Ctrl+Alt+L, 


dcTypeCd -> dctypecd



2.2 대문자로 바꾸기 :  Ctrl+Alt+U


dcTypeCd -> DCTYPECD



2.3 Camel(낙타) <-> Underscores(_표시) : Ctrl+Alt+K


private String dc_type_cd -> private String dcTypeCd



3. AnyEdit 설치방법



eclipse Help > Eclipse MarketPlace  > AnyEdit 검색 > install



이 플러그인을 사용함으로써 Model 작성시 시간과 오타 모두를 줄일수 있다고 생각한다.





+ Recent posts