Search

반응형

'자바'에 해당되는 글 52건

  1. 2020.04.09 [java]비교연산 "==" vs "equals()" 쉽게 이해하기.
  2. 2020.03.17 [디자인패턴]스트래티지 패턴(Strategy Pattern) 쉽게 이해하기 편. with 자바
반응형

안녕하세요~ 신기한연구소 티보이입니다.

자바(java) 개발 중 값을 비교해야 하는 경우가 종종 있습니다.

보통 숫자는 비교 연산자인 "=="로 문자열은 문자열 비교 메서드인 equals()를 주로 사용하는데 왜 문자열은 비교 연산자인 "=="를 사용하면 다르게 나오는지 같이 확인해 보겠습니다.

1. 비교연산자 (==)

비교 연산자(==)는 변수의 데이터가 저장된 메모리 위치(해시코드)를 비교합니다.

다음 예를 살펴볼게요.

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
        // 샘플 1
        long long1 = 32L;
        long long2 = 32L;
        
        System.out.println(long1);
        System.out.println(long2);
        System.out.println(System.identityHashCode(long1));
        System.out.println(System.identityHashCode(long2));
        
        
        System.out.println((long1 == long2?"same value" : "diff value")); //결과1
        
        //샘플 2
        String s1 = "te";
        String s2 = "st";
        String s3 = s1 + s2;
        String s4 = "test";
        String s5 = "test";
        
        System.out.println(s3);
        System.out.println(s4);
        System.out.println(s5);
        System.out.println(System.identityHashCode(s3));
        System.out.println(System.identityHashCode(s4));
        System.out.println(System.identityHashCode(s5));
        
        System.out.println((s3 == s4?"same value" : "diff value")); //결과2
        System.out.println((s4 == s5?"same value" : "diff value"));  //결과3
cs

결과 :

32
32
366712642
366712642
same value
test
test
test
1829164700
2018699554
2018699554
diff value
same value

우선 샘플 1을 보면 기본형 long 변수인 long1, long2를 32L 값으로 할당했습니다.

두 값을 비교 연산자인 '=='를 사용한 결과1을 보시면 'same value'가 출력되었습니다.

값은 둘 다 32를 출력했고 해시코드(hashcode)도 동일한 '366712642'값을 출력했습니다.

메모리 위치를 나타내는 해시코드가 같음을 알 수 있습니다.

비교 연산자는 이 해시코드 값을 비교합니다.

샘플 2를 보시면 문자열 비교를 합니다.

S4와 S5 둘 다 'test' 값이 할당되어 있습니다.

결과3에서 비교 연산자를 사용해서 비교해 보면 'same value'로 나옵니다.

두 변수 S4와 S5의 해시코드 값도 '2018699554'로 동일합니다.

그런데 S3와 S4를 보면 값은 둘 다 'test'인데 결과2를 보면 'diff value'로 표출됩니다.

비교 연산자는 이 두 개의 값을 다르게 인식합니다.

두 값의 해시코드를 보면 S3 (1829164700)와 S4 (2018699554)로 서로 다름을 알 수 있습니다.

왜 다르게 나오냐면 S3의 경우 S1과 S2의 결합으로 비록 값은 같지만 새로운 메모리에 객체로 생성되기 때문입니다.

결국 비교 연산자는 눈에 보이는 값으로 비교하는 게 아닌 변수의 값이 저장된 해시코드값으로 비교한다는 것을 확인할 수 있습니다.

효율적인 메모리 사용을 위해 변수에 값을 할당할 때 기존에 같은 값이 있으면 같은 해시코드로 지정됩니다.

2. equals() 메서드로 비교하기

우선 이 메서드는 기본형(primitive type)에서는 사용할 수 없습니다.

이유는 equals()는 객체에서 호출되는 메서드인데 기본형 타입은 객체가 아니라 변수이기 때문입니다.

쩜 찍어보면 아무것도 안 나오거든요. ㅎㅎ

equals() 메서드의 예를 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
//샘플 3
        String s1 = "te";
        String s2 = "st";
        String s3 = s1 + s2;
        String s4 = "test";
        String s5 = "test";
        
        System.out.println((s3.equals(s4)?"same value" : "diff value")); //결과4
        System.out.println((s4.equals(s5)?"same value" : "diff value")); //결과5
cs

위 예제의 결과4와 결과5는 둘 다 'same value'가 나옵니다. 비교 연산자와 다른 결과가 나왔습니다.

그 이유는 equals() 메서드는 두 변수의 메모리(해시코드)에는 관심이 없고 단지 눈에 보이는 글자 그 자체를 비교하기 때문입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        Long rLong1 = new Long(32L);
        Long rLong2 = rLong1;
        Long rLong3 = new Long(rLong1);
        Long rLong4 = new Long(32L);
        
        
        System.out.println(System.identityHashCode(rLong1));
        System.out.println(System.identityHashCode(rLong2));
        System.out.println(System.identityHashCode(rLong3));
        System.out.println(System.identityHashCode(rLong4));
        
        System.out.println((rLong1.equals(rLong2)?"same value" : "diff value")); //결과6
        System.out.println((rLong1.equals(rLong3)?"same value" : "diff value")); //결과7
        System.out.println((rLong1.equals(rLong4)?"same value" : "diff value")); //결과8
        System.out.println((rLong3.equals(rLong4)?"same value" : "diff value")); //결과9
        
        
        System.out.println((rLong1 == rLong2?"same value" : "diff value")); //결과10
        System.out.println((rLong1 == rLong3?"same value" : "diff value"));  //결과11        
        System.out.println((rLong1 == rLong4?"same value" : "diff value"));  //결과12    
        System.out.println((rLong3 == rLong4?"same value" : "diff value"));  //결과13
cs

위 예제에서는 Long 객체를 생성해서 '=='와 'equals()'로 비교를 해봤습니다.

이제 어떤 결과가 나올지 예측이 되시나요?

결과6은 equals()를 사용했기에 값 자체만 비교하므로 'same value'가 나옵니다.

결과7, 결과8, 결과9도 'same value'가 나옵니다.

 

결과10은 rLong1객체를 rLong2에 대입시켰기 때문에 같은 해시코드를 갖게 됩니다. 그래서 'same value'가 나오게 됩니다.

결과 11부터 13까지는 새로운 객체를 생성했기에 눈에 보이는 값은 32로 같지만 다른 해시코드를 갖고 있기에 'diff value'가 나옵니다.

3. 결론

같은 메모리 위치를 참조하는지 확인하기 위해서는 비교 연산자 == 를 사용하면 됩니다.

아래 예와 같이 long1과 long2는 32L 을 할당받게 됩니다. long2의 값이 long1과 같기에 굳이 새로운 메모리에 똑같은 값을 넣고 메모리를 차지할 이유가 없기 때문입니다. 효율적인 자바네요. ㅎㅎ

이런 경우는 비교 연산자 ==로 비교 시 같다고 나옵니다.

하지만 아래와 같이 새로운 객체(참조형 변수)를 만들게 되면 값은 중요하지 않습니다. 새로운 놈이 나타난 것이기 때문에 값이 같더라도 새로운 위치를 하나 내어줍니다.

그래서 long1과 long2의 값이 32L로 같더라도 new로 새로운 객체로 만들어졌기 때문에 해시코드값도 다르게 됩니다.

해시코드값(메모리 위치)이 같은지 비교할 때는 비교 연산자 (==)를 사용하고

해시코드값이 달라도 글자 그 자체를 비교할 때는 equals()메소드를 사용하면 됩니다.

즐 코딩하세요~

반응형
반응형

안녕하세요. 신기한 연구소 티보이입니다.

프로그래밍에 전략과 전술이 필요하다면 스트래티지 패턴(Strategy Pattern)을 사용하라~

"공감"과 "구독"은 블로거에게 큰 힘이 됩니다. 도움이 되길 바라며 감사합니다.

우리가 만들 객체는 같은 기능, 슷한 기능 또는 전혀 다른 기능이 각각 포함될 수 있는데 이 기능들을 한 곳에 모아두면 엄청 심각한 문제가 발생하게 됩니다.

프로그래머들은 다들 아실겁니다. 수정사항이 발생했을 때 관련 소스를 다 찾아내고 수정하고 테스트하고 짜증도 나지만 오류까지 발생하면 진짜 돌아버리는 상황도 발생합니다.

그렇다고 다 걷어내고 새로 구성하기도 힘든 상황이 대부분이죠.

그래서 개발보다 유지보수에 시간이 더 필요하다는 말이 나오는 거겠죠.

이런 경우에 사용할 수 있는 패턴이 있습니다. 바로 스트래티지 패턴(Strategy Pattern) 인데요. 똑같은 기능을 구현하는 경우는 상속해서 사용하고 기능이 조금씩 다르거나 사용하지 않는 경우는 별도로 분리해서 관리하는 개념입니다. 이렇게 하면 유연성이 뛰어나게 되서 기능을 수정하게 되면 그 알고리즘만 수정하면 전체가 적용되게 됩니다.

하나의 예를 들어서 설명 시작합니다. 게임 프로그램을 하는데 전투에 필요한 객체를 만들어야 합니다. 첫 번째 기능은 공격입니다. 하지만 전투 객체 모두가 같은 공격을 하진 않아요. 물어뜯는 공격, 발톱으로 하는 공격, 몸에서 독을 뿜어내는 공격 등 공격이라는 기능이 있지만 상세하게는 다릅니다. 그리고 어떤 객체는 수송만 담당해서 공격을 하지 않아요. 어떤 객체는 하늘을 나는 기능이 있고 어떤 객체는 땅속으로 이동하는 기능이 있어요. 이동은 하지만 상세 기능이 다른겁니다.

그런데 종족마다 공통된 특징이 있어요 전부 눈이 4개이던지 아니면 꼬리가 있다던지 공통부분이 존재하는겁니다.

그리고 게임 객체들이 자원을 이용해 업그레이드도 할 수있습니다.

이제 이 기능들을 크게 분류해 봅니다.

공격 기능,

공격 기능에는 물어뜯기 공격, 발톱 공격, 독 공격으로 나눌 수 있고 이동 기능은 걷기, 날기, 땅속으로 파고 이동하기로 나눌 수 있습니다.

종족 클래스 하나에 이 모든 기능을 넣는다면 코드 가독성, 관리 등에 어려움이 발생합니다.

그래서 보통 종족별로 같은 기능을 추상 클래스로 만들어 상속 및 오버로딩, 오버라이딩을 할 수 있도록 설계합니다.

슈퍼 클래스로 종족을 대표하는 추상 클래스를 만듭니다. (예를 들어 화성인으로 Mars로 클래스명을 만듭니다.)

1. 추상 클래스에는 기본 생성자를 만듭니다.

2. 공통으로 구현할 부분을 추상메소드로 만들어 상속받을 서브클래스(게임 객체들)에서 구현하게 만듭니다.. 눈이 4개던지 꼬리가 있다던지 눈이 4개라도 위치가 다르거나 꼬리가 있어도 모양이 다를 수 있지만 꼭 있어야 하는 부분이라 추상메소드로 정의하고 하위 클래스에서 반드시 구현하게 만들어 줍니다.

3. 수정이 필요 없는 똑같은 기능은 직접 구현합니다. 종족의 모든 객체는 걸을 수 있다면 그 부분을 구현해서 상속받게 합니다. 굳이 서브클래스에서 중복해서 구현할 필요가 없어졌습니다.

4. 가장 중요한 부분인데 공격과 이동에 대한 각 객체의 구현이 다르기 때문에 유연하게 대응할 set 메서드와 실행 메서드가 필요합니다. 즉 추상 클래스나 서브클레스에서 구현하지 않고 어떤 기능을 어떻게 작동하게 할 건지 관련된 기능 클래스의 객체 설정부 및 호출부를 만듭니다.

이렇게 만든 추상 클래스를 이제 상속받아서 각 객체를 만들 클래스를 만듭니다. 수퍼클래스에 대한 서브클래스입니다.

이제 서브클래스에 대해 알아봅니다.

슈퍼클래스(추상 클래스)의

그게 서브클래스가 됩니다.

3개를 만든다면 지상 공격수(Grounder), 비행공격수비행 공격수(Flyer), 수송객체(Transer)수송 객체(Transer)로 만듭니다. 당연히 Mars 종족을 상속받아야겠지요..

이 클래스는 생성자로 공격과 이동에 대한 관련 클래스를 찾아 객체를 구현해야 합니다.

그리고 외형애 대한 메서드를 오버라이드 해서 눈 4개,4개, 꼬리를 상세하게 표현합니다.

이렇게 추상 클래스와 게임 객체들의 클래스를 구성합니다.

이제 남은 부분은 상세 기능들을 그룹화해서 구현하고 사용할 수 있게 만드는 겁니다.

크게 공격과 이동입니다. 이 부분을 공통 인터페이스로 작동이라는 메서드를 추상화해서 각각 만듭니다.

그리고 이빨 공격, 발톱 공격, 독 공격을 클래스로 만든 뒤 공격 인터페이스를 구현합니다.

또한 하늘날기, 땅굴파기, 수영 등 이동에 대한 클래스를 만들고 이동 인터페이스를 구현합니다.

크게 공격과 이동에 대한 그룹(캡슐화)을 만들었고 이 클래스들을 서브클래스에서 객체로 구현한 뒤 실행하면 됩니다.

, 이제 공격 기능이 추가되었다고 가정해봅니다. 자폭기능을 추가한다면 추상 클래스나 각 게임 캐릭터들을 만든 서브클래스에서 수정할 부분이 없습니다.

공격 인테페이스에묶인 공격 클래스 중 자폭이라는 클래스를 하나 추가하고 실행부에서 공격 기능(자폭클래스 객체 생성)을 바꿔서 실행만 하면 됩니다.

결국 추가적인 이동 방법이나 공격 방법이 필요하면 공격 알고리즘이나 이동 알고리즘에 해당 클래스만 추가하고 실행부에서 그 객체를 생성해서 종족에 던져주면 실행이 되는 겁니다.

게다가 다른 종족도 자폭기능이 추가된다면 종족 관련 클래스는 수정할 필요 없이 공격방법 알고리즘에 추가만 하면 되는거지요.

이렇게 전략적으로 캡슐화 한 인터페이스(알고리즘) 구성부를 잘 활용하면 기능 추가 및 변경으로 인한 메인 객체들이 수정될 일이 없고 분리되서 유연하게 사용할 수 있게 됩니다.

스트래티지 패턴(Strategy Pattern)에 대해 설명해 봤는데 이해가 되셨나요?

실제 코딩은 다음 포스팅 때 같이 해볼 계획입니다.

잘못된 부분이나 수정이 필요한 부분이 있으면 댓글 주세요.

우리 모두 즐코딩해요~

 

반응형