다형성(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

 

 

출처 : 패스트캠퍼스

+ Recent posts