다형성(polymorphism)이란?

- 하나의 코드가 여러 자료형으로 수현되어 실행되는 것

- 같은 코드에서 여러 다른 실행결과가 나옴

- 정보은닉, 상속과 더불어 객체지향 프로그래밍의 가장 큰 특징 중 하나

- 다형성을 잘 활용하면 유연하고 확장성있고 유지보수가 편리한 프로그램을 만들 수 있음

ex)

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
class Animal {
    public void move() {
        System.out.println("동물이 움직입니다.");
    }
}
 
class Human extends Animal {
    @Override
    public void move() {
        System.out.println("사람이 두 발로 걷습니다.");
    }
    
    public void readBook() {
        System.out.println("사람이 책을 읽습니다.");
    }
}
 
class Tiger extends Animal {
    @Override
    public void move() {
        System.out.println("호랑이가 네 발로 뜁니다.");
    }
    
    public void hunting() {
        System.out.println("호랑이가 사냥을 합니다.");
    }
}
 
class Eagle extends Animal {
    @Override
    public void move() {
        System.out.println("독수리가 하늘을 날아다닙니다.");
    }
    
    public void flying() {
        System.out.println("독수리가 양 날개를 쭉 펴고 날아다닙니다.");
    }
}
 
public class AnimalTest {
    public static void main(String[] args) {
        Animal hAnimal = new Human();
        Animal tAnimal = new Tiger();
        Animal eAnimal = new Eagle();
 
        AnimalTest test = new AnimalTest();
        test.moveAnimal(hAnimal);
        test.moveAnimal(tAnimal);
        test.moveAnimal(eAnimal);
 
        ArrayList<Animal> animalList = new ArrayList<Animal>();
        animalList.add(hAnimal);
        animalList.add(tAnimal);
        animalList.add(eAnimal);
        
        for(Animal animal : animalList) {
            animal.move();
        }
 
    }
 
    public void moveAnimal(Animal animal) {
        animal.move();
    }
}
cs

 

결과 :

사람이 두 발로 걷습니다.

호랑이가 네 발로 뜁니다.

독수리가 하늘을 날아다닙니다.

사람이 두 발로 걷습니다.

호랑이가 네 발로 뜁니다.

독수리가 하늘을 날아다닙니다.

 

 

다형성을 사용하는 이유

- 상속과 메서드 재정의를 활용하여 확장성있는 프로그램을 만들 수 있음

- 코드의 유지보수가 편리해짐

- 상위 클래스에서는 공통적인 부분을 제공하고 하위 클래스에서는 각 클래스에 맞는 기능 구현

- 여러 클래스를 하나의 타입(상위 클래스)으로 핸들링할 수 있음

하위 클래스에서 매서드 재정의 하기

- 오버라이딩(overriding) : 상위 클래스에 정의된 매서드의 구현 내용이 하위 클래스에서 구현할 내용과 맞지 않는 경우 하위 클래스에서 동일한 이름의 매서드를 재정의할 수 있음

 

ex) VIPCustomer 클래스의 calcPrice()에 할인율을 적용하여 재정의

 

Customer.java

1
2
3
4
public int calcPrice(int price) {
    bonusPoint += price * bonusRatio;
    return price;
}
cs

VIPCustomer.java

1
2
3
4
5
6
@Override
public int calcPrice(int price) {
    bonusPoint += price * bonusRatio;
    price -= (int)(price * salesRatio);
    return price;
}
cs

 

 

@Override 어노테이션

- 어노테이션은 원래 주석이라는 의미

- 컴파일러에게 특별한 정보를 제공해주는 역할

- @Override는 재정의된 매서드라는 의미로 선언부가 기존의 매서드와 다른 경우 에러남

 

형 변환과 오버라이딩 매서드 호출

Customer vc = new VIPCustomer();

vc변수의 타입은 Customer지만 인스턴스의 타입은 VIPCustomer이다.

Java에서는 항상 ❗인스턴스의 매서드가 호출된다. (가상 매서드의 원리)

자바의 모든 매서드는 가상 매서드(virtual method) 이다.

 

 

메서드는 어떻게 호출되고 실행되는가?

- 메서드(함수)의 이름은 주소값을 나타냄

- 메서드는 명령어의 set이고 프로그램이 로드되면 메서드 영역(코드 영역)에 명령어 set이 위치

- 해당 메서드가 호출되면 명령어 set이 있는 주소를 찾아 명령어가 실행됨

- 이 때 메서드에서 사용하는 변수들은 스택 메모리에 위치하게 됨

- 따라서 다른 인스턴스라도 같은 메서드의 코드는 같으므로 같은 메서드가 호출됨

- 인스턴스가 생성되면 변수는 힙 메모리에 따로 생성되지만, 메서드 명령어 set은 처음 한 번만 로드됨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestMethod {
    int num;
    void aaa() {
        System.out.println("aaa() 호출");
    }
 
    public static void main(String[] args) {
        TestMethod a1 = new TestMethod();
        a1.aaa();
 
        TestMethod a2 = new TestMethod();
        a2.aaa();
    }
}
cs

 

가상 메서드의 원리

- 가상 메서드 테이블(virtual method table)에서 해당 메서드에 대한 address를 가지고 있음

- 재정의된 경우는 재정의된 메서드의 주소를 가리킴

 

출처 : 패스트캠퍼스

 

<전제조건>

1
2
3
public class VIPCustomer extends Customer() {
 
}
cs

 

하위 클래스가 생성되는 과정

- 하위 클래스를 생성하면 상위 클래스가 먼저 생성됨

- new VIPCustomer()를 호출하면 Customer()가 먼저 호출됨

 

 

상위 클래스를 호출하는 코드가 없는데, 어떻게 하위 클래스의 생성자는 상위 클래스를 호출할까?

=> 컴파일러가 자동으로 하위 클래스의 생성자에 super(); 를 넣어준다!!

=> super(); 는 상위 클래스의 디폴트 생성자를 호출한다.

=> super.상위클래스에구현된함수 : 이렇게 사용할 수 있다.

===> 즉, super는 상위클래스에 접근할 수 있게 한다. (물론 super 없이 그냥 써도 가능)

 

** 상위 클래스에 디폴트 생성자가 없는 경우(매개변수가 있는 다른 생성자를 선언한 경우)에는 하위 클래스의 생성자도 형태를 맞춰주고 super(매개변수)를 명시적으로 써주어야 한다.

 

 

❗❗super 키워드 정리

- 하위 클래스에서 가지는 상위 클래스에 대한 참조값

- super()는 상위 클래스의 기본 생성자를 호출함

- 하위 클래스에서 명시적으로 상위 클래스의 생성자를 호출하지 않으면 super()가 호출됨

  (이때 반드시 상위 클래스의 기본 생성자가 존재해야 함)

- 상위 클래스의 기본 생성자가 없는경우(다른 생성자가 있는 경우) 하위 클래스 생성자에서는 super를 이용하여 명시적으로 상위 클래스의 생성자를 호출해야 함

- super는 생성된 상위 클래스 인스턴스의 참조값을 가지므로 super를 이용하여 상위 클래스의 매서드나 멤버변수에 접근할 수 있음

 

 

상속에서 인스턴스 메모리의 상태

- private 변수도 메모리에 생성되며, 접근을 못하는것 뿐이다. -> 지난번 예제에서 접근할 수 있게 protected로 변경함.

힙 메모리

 

형 변환(업캐스팅)

- 상위 클래스로 변수를 선언하고 하위 클래스의 생성자로 인스턴스를 생성

- 하위 클래스는 상위 클래스를 내포하고 있기 때문에 가능(위 메모리 상태 참고)

1
Customer customerLee = new VIPCustomer();
cs

- 상위 클래스 타입의 변수에 하위 클래스 변수가 대입

1
2
3
4
5
VIPCustomer vCustomer = new VIPCustomer();
addCustomer(vCustomer);
int addCustomer(Customer customer) {
 
}
cs

- 하위 클래스는 상위 클래스의 타입을 내포하고 있으므로 상위 클래스로의 묵시적 형 변환이 가능함

- 상속관계에서 모든 하위 클래스는 상위 클래스로 형 변환(업캐스팅)이 됨 (그 역은 성립하지 않음❌)

 

형 변환과 메모리

- Customer vc = new VIPCustomer(); 에서 vc가 가리키는 것은?

- VIPCustomer()생성자에 의해 VIPCustomer 클래스의 모든 멤버변수에 대한 메모리는 생성됐지만, 변수의 타입이 Customer 이므로 실제 접근 가능한 변수나 매서드는 Customer의 변수와 매서드이다.

 

클래스의 계층구조가 여러 단계일 수 있다!

 

출처 : 패스트캠퍼스

클래스 상속

- 이미 구현된 기능을 확장하여 사용한다는 개념 ( 클래스를 재사용하는 개념이 아님 )

- 새로운 클래스를 정의할 때 이미 구현된 클래스를 상속받아서 속성이나 기능을 확장하여 클래스를 구현함

- 이미 구현된 클래스보다 더 구제적인 기능을 가진 클래스를 구현해야할 때 기존 클래스를 상속함

 

상속하는 클래스(A) : 상위 클래스

상속받는 클래스(B) : 하위 클래스

일 때 아래처럼 구현함.

1
2
3
class B extends A {
 
}
cs

 

- extends 키워드 뒤에는 단 하나의 클래스만 올 수 있음

- java는 단일 상속(single inheritance)만을 지원함(c++은 여러개 상속 가능)

- 여러개를 상속하게되면 모호성이 발생할 수 있음 -> 추후 업데이트 예정!

  : 상속받을 수 있는 클래스가 많을수록 더 많은 기능을 확장할 수 있지만, 그로인해서 발생하는 문제점을 없애버리기 위해 java는 단일상속으로 고안됨

 

 

상속을 구현하는 경우

- 상위 클래스는 하위 클래스보다 더 일반적인 개념과 기능을 가짐

- 하위 클래스는 상위 클래스보다 더 구체적인 개념과 기능을 가짐

- 하위 클래스가 상위 클래스의 속성과 기능을 확장(extends)한다는 의미

- 원래있던 클래스보다 기능이 많아야하고 구체적이어야 하는 경우 기존의 클래스를 상속받음(결은 같음!)

- 기존의 괜찮은 클래스가 있고 그 기능을 가져다 쓰고싶다고 상속을 하는것은 아님!!!

 

 

실습을 해보자아

- 클래스에 if else가 많으면 상속을 한 번 생각해볼 필요가 있음

- protected 접근제어자 : 하위클래스에서는 접근이 가능하지만 외부 클래스에서는 접근할 수 없음

 

Customer.java

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
public class Customer {
    protected int customerID;
    protected String customerName;
    protected String customerGrade;
    
    int bonusPoint;
    double bonusRatio;
    
    public int getCustomerID() {
        return customerID;
    }
 
    public void setCustomerID(int customerID) {
        this.customerID = customerID;
    }
 
    public String getCustomerName() {
        return customerName;
    }
 
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
 
    public String getCustomerGrade() {
        return customerGrade;
    }
 
    public void setCustomerGrade(String customerGrade) {
        this.customerGrade = customerGrade;
    }
    
    public Customer() {
        customerGrade = "SILVER";
        bonusRatio = 0.01;
    }
 
    public int calcPrice(int price) {
        bonusPoint += price * bonusRatio;
        return price;
    }
    
    public String showCustomerInfo() {
        return customerName + "님의 등급은 " + customerGrade + "이며, 보너스 포인트는 " + bonusPoint + "입니다.";
    }
 
}
cs

 

VIPCustomer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class VIPCustomer extends Customer {
    
    double salesRatio;
    private String agentID;
    
    
    public String getAgentID() {
        return agentID;
    }
 
 
    public void setAgentID(String agentID) {
        this.agentID = agentID;
    }
 
 
    public VIPCustomer() {
        bonusRatio = 0.05;
        salesRatio = 0.1;
        customerGrade = "VIP";
    }
    
    
}
cs

 

CustomerTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CustomerTest {
 
    public static void main(String[] args) {
        Customer customerLee = new Customer();
        customerLee.setCustomerName("이순신");
        customerLee.setCustomerID(10010);
        customerLee.bonusPoint = 1000;
        System.out.println(customerLee.showCustomerInfo());
        
        VIPCustomer customerKim = new VIPCustomer();
        customerKim.setCustomerName("김유신");
        customerKim.setCustomerID(10020);
        customerKim.bonusPoint = 10000;
        System.out.println(customerKim.showCustomerInfo());
 
    }
 
}
cs

 

 

 

출처 : 패스트캠퍼스

java.util.ArrayList

- 기존의 배열 선언과 사용 방식은 배열의 길이를 정하고 요소의 개수가 배열의 길이보다 커지면 배열을 재할당하고 복사해야 했음

- 기존배열의 요소를 추가하거나 삭제하면 다른 요소들의 이동에 대한 구현을 해야함

- ArrayList는 객체배열을 좀 더 효율적으로 관리하기 위해 자바에서 제공해주는 클래스

- 이미 많은 메서드들이 최적의 알고리즘으로 구현되어 있어 각 메서드의 사용 방법만 익히면 유용하게 사용할 수 있음

 

주요 매서드

- boolean add(E e)

- int size()

- E get(int index)

- E remove(int index)

- boolean isEmpty()

 

출처 : 패스트캠퍼스

- 동일한 자료형의 순차적 자료구조

- 인덱스 연산자[]를 이용하여 빠른 참조 가능

: 물리적 위치와 논리적 위치가 동일하기때문에 자료형의 크기만큼 계산하여 배열에 저장된 값을 빠르게 가져올 수 있다.

: 몇번째 요소를 가져오는 것이 배열은 빠름(LinkedList는 이런게 빠르지 않음(n번 걸림. 물리적 위치와 논리적 위치가 다르기 때문)

: 중간에 값을 추가하거나 빼는 연산은 느림. 값을 추가하거나 빼는 위치 이후의 값들을 한 칸씩 당기거나 밀어야하기 때문

- java에서는 객체 배열을 구현한 ArrayList를 많이 활용함

 

ArrayList

- new로 선언하면 32비트 컴퓨터는 하나의 인덱스당 4바이트짜리 방이 생기고 64비트는 8바이트짜리 방이 생김

  : Array와 다른점! 각 방은 null값으로 되어있음. 따라서 객체를 만들어서 각 인덱스에 넣어주어야 함!

 

객체 배열 복사하기

- System.arrayCopy(대상배열,시작포지션,목적지배열,목적지시작포시션,길이)

- 주의!! 이렇게 복사하면 주소값도 동일하게 복사되기 때문에(같은 객체를 가리키기 때문에) 둘 중 하나만 수정되어도 같이 수정된다.

- 아예 다른 주소값으로 복사하려면 객체배열을 새로 생성 후(new) 각 인덱스에 값을 직접 넣어주어야 한다.

  : 전혀 다른 객체를 가지기 때문

 

 

출처 : 패스트캠퍼스

싱글톤 패턴이란?

- 프로그램에서 인스턴스가 단 한 개만 생성되어야 하는 경우 사용하는 디자인 패턴

- static변수, 메서드를 활용하여 구현할 수 있음

 

** 디자인 패턴?

객체지향 프로그램에서 어떻게 하면 좀 더 효율적으로 객체지향 프로그램을 구현해서 추후 유지보수 하는 데에서 수정이 쉽고(확장성이 좋고) 좀 더 객체지향에 적합하게 코딩을 할 것인가에 대해 여러가지 패턴으로 정리해 놓은 것. 23가지 정도 있음. 그 중 하나가 싱글톤 패턴!

 

ex) 하나의 회사가 있고, 직원이 여러명인 상황

 

- 싱글톤 패턴 코드

1
2
3
4
5
6
7
8
9
10
11
public class Company {
    private static Company instance = new Company();
 
    private Company() {}
 
    // 외부에서 사용할 수 있도록 public으로 getter 생성
    public static Company getInstance() {
        if(instance == null) instance = new Company();
        return instance;
    }
}
cs

 

- 사용하는 코드 : new를 사용하여 인스턴스를 새로 생성하는것이 아니고, static으로 이미 만들어진 인스턴스를 가져가다 쓰는 것. 따라서 클래스를 활용하여 호출하고, 호출된 Company의 주소값은 동일함!(같은것을 가져다가 쓰기 때문)

1
2
3
4
5
public static void main(String[] args) {
    Company company1 = Company.getInstance;
    Company company2 = Company.getInstance;
    // company1 과 company2의 주소값은 동일함!
}
cs

 

 

=> Company는 외부에서 생성할 수 없고, private으로 내부에서 생성되어 있으며, getInstance() 메서드로만 제공되고 있다. 따라서 getInstance()로 내부에서 생성되어있는 Company만 사용할 수 있다. 이것이 싱글톤 패턴이다!

 

유일한 객체를 제공할 때 싱글톤 패턴을 쓴다.

 

대표적인 예로 java.util.Calendar 가 있다. 아래 코드처럼 불러와서 사용한다.

1
Calendar calendar = Calendar.getInstance();
cs

출처 : 패스트캠퍼스

여러 인스턴스에서 공통으로 사용하는 변수를 선언하는 것.

 

공통으로 사용하는 변수가 필요한 경우

- 여러 인스턴스가 공유하는 기준 값이 필요한 경우

- 값을 변경할 수 있음

ex. 학생마다 새로운 학번 생성 - 학번의 기준

ex. 카드회사에서 카드를 새로 발급할때마다 새로운 카드 번호를 부여 - 카드 번호 기준

ex. 회사에 사원이 입사할때마다 새로운 사번이 부여 - 사번 기준

 

staic 변수

- 인스턴스가 생성될 때 만들어지는 변수가 아닌, 처음 프로그램이 메모리에 로딩될 때 메모리를 할당

  : 따라서 heap 메모리에 올라오지 않음. 프로그램이 메모리에 할당되어 프로세스가 되는 순간 Data영역(static, 상수 라고도 함)에 저장되고, 프로그램이 전부 끝난 후 메모리에서 unload되는 순간 없어짐

- 클래스 변수, 정적변수 라고도 함.

- 인스턴스 생성과 상관없이 사용 가능하므로 클래스 이름으로 직접 참조

 

 

** 짚고 넘어가는 메모리 할당

- java 뿐만 아니라 다른 프로그램 언어도 마찬가지로 프로그램은 보통 아래 세 가지 영역의 메모리를 사용하게 됨

- 힙 메모리 : 동적 메모리, 다 쓰고나면 free, release 시키는 메모리, java는 GC(Garbage Collector)가 알아서 처리해줌

- 스택 메모리 : function이 호출될 때마다 함수(메서드) 안에서 사용하는 지역변수 메모리

- 데이터 영역(constant 영역) 메모리 : 처음 프로그램이 메모리에 load되는 순간부터 할당되어 공유되다가 프로그램이 unload될 때 사라지는 영역

 

 

출처 : 패스트캠퍼스

 

 

this가 하는 일

 

- 인스턴스 자신의 메모리를 가리킴

- 생성자에서 또 다른 생성자를 호출할 때 사용

- 자신의 주소(참조값)을 반환함

 

 

생성된 인스턴스 메모리의 주소를 가짐

- 클래스 내에서 참조변수가 가지는 주소값과 동일한 주소값을 가지는 키워드

1
2
3
4
public static void main(String[] args) {
    BirthDay day = new BirthDay();
    day.setYear(2000);
}
 
1
2
3
public void setYear(int year) {
    this.year = year;
}
cs

 

stack메모리
메서드, 함수가 호출되면 메서드, 함수의 지역변수 메모리 공간은 스택 메모리에 잡히게 된다.

 

heap메모리

객체(인스턴스)가 생성되면 힙 메모리에 잡히게 된다.

나중에 GC(Garbage Collector)에 의해 메모리가 수거됨

 

this가 생성된 인스턴스 힙 메모리의 주소를 가리킴

 

 

생성자에서 다른 생성자를 호출하는 this

자신의 주소를 반환하는 this

 

 

출처 : 패스트캠퍼스

java.util.Arrays 유틸리티 클래스를 사용하면 배열(Array)을 정렬, 복제하거나 List로 변환하는 등의 작업을 쉽게 처리할 수 있다. 해당 클래스의 sort() 매서드를 사용하면 쉽게 오름차순 정렬리 가능하다. sort()매서드는 클래스 매서드로써 Arrays 클래스의 인스턴스 생성없이 바로 사용하면 된다.

 

기본 정렬조건이 오름차순인 이유는 Class 내에 기본적으로 구현되어있는 Comparable 인터페이스의 compareTo 메서드를 기준으로 하기 때문이다. Java에서 인스턴스를 서로 비교하는 클래스들은 모두 Comparable 인터페이스가 구현되어 있다.

ex) A.compareTO(B)
   A==B // 같으면 0 반환
   A>B // A가 더 크면 1 반환
   A<B // B가 더 크면 -1 반환

 

Arrays.sort() 사용

int[] intarr = new int[] {1, 5, 4, 2, 3};

Arrays.sort(intarr); // 1 2 3 4 5

Arrays.sort(intarr,2,5); // intarr[2]~intarr[4]의값 4,2,3 만 정렬 : 1 5 2 3 4

 

내림차순이나 원하는대로 조건을 달리하고 싶을 때에는 Class내에 구현되어있는

1. Comparable 인터페이스의 compareTo() 메서드를 원하는 조건으로 오버라이드하거나,

2. 익명인터페이스 java.util.Comparator를 구현한 Class내 compare() 메서드를 원하는 정렬조건으로 오버라이드하여

sort 메서드 호출 시 구현한 Comparator 클래스를 명시해주면 된다.

 

주의할 점은 byte, char, double, short, long, int, float같은 PrimitiveType의 배열에는 적용이 불가능하니 Integer같은 Wrapper "Class"를 이용하셔야 한다는 점!!

 

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
public class Custom implements Comparator<Player> {
    @Override
    public int compare(Player o1, Player o2) {
        // TODO Auto-generated method stub
        if(o1.score < o2.score) {
            return 1;
        }
        else if(o1.score == o2.score) {
            if((o1.name).compareTo(o2.name) < 0) {
                return -1;
            }
            else if((o1.name).compareTo(o2.name) > 0) {
                return 1;
            }
            else return 0;
        }
        return -1;
    }
}
class Player{
    String name;
    int score;
    
    Player(String name, int score){
        this.name = name;
        this.score = score;
    }
}
cs

위 예제는 score 내림차순으로 정렬하되, score값이 같으면 name 오름차순으로 정렬하도록 compare 메서드를 커스터마이징한 것이다.

 

1
2
3
4
5
6
class Custom implements Comparator<String> {
    @Override
    public int compare(String o1, String o2) {
        return o2.compareTo(o1);
    }
}
cs

위 예제는 내림차순으로 정렬하도록 구현한 것이다.

사용할때에는

1
2
String arr = new String[] {"A","B","C","D","E"};
Arrays.sort(arr, new Custom);
cs

이런식으로 사용하면 E D C B A 순으로 정렬된다.

 

***출처***

https://ifuwanna.tistory.com/232

+ Recent posts