JAVA

자바의 정석(기초편) -ch7. 객체지향프로그래밍 Ⅱ-(2)

광터틀 2022. 7. 28. 19:52

4) 제어자(modifier)

제어자 : 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여함.

 

접근제어자 : public, protected, (default), private ➔ 한 번에 하나만 쓰임.

그 외 : static, final, abstract, native, transient, synchronized ... ➔ 여러 제어자 조합 사용 가능.

(그 외에서는 주로 static, final, abstract만 쓰인다.)

 

static 클래스의, 공통적인 인스턴스 생성하지 않고 사용 가능함. (편리하고 빠름) 맴버변수, 메서드, 초기화블럭
fianl 마지막의, 변경될 수 없는 변수에 사용하면 값을 변경할 수 없는 상수.
매서드에 사용되면 오버라이딩 불가
클래스에 사용되면 자신을 확장하는 자손 클래스 정의 불가
클래스, 메서드, 멤버변수, 지역변수
abstract 추상의, 미완성의 선언부만 작성하고 실제 수행 내용은 구현하지 않는 추상 메서드 선언하는데 사용 클래스, 메서드

 

접근제어자 

사용될 수 있는 곳 : 클래스, 멤버변수, 메서드, 생성자

 

private : 같은 클래스 내에서만 접근이 가능

(default) : 같은 패키지 내에서만 접근이 가능 (default라고 쓰지는 않음. 접근제어자가 따로 지정되지 않으면 default임)

protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능

public : 접근 제한이 전혀 없다

 

접근제한없음    같은 패키지+자손    같은 패키지          같은 클래스

public     >     protected >     (default)     >     private

 

접근제어자를 사용하는 이유

- 외부로부터의 데이터를 보호하기 위해서 (ex. 비밀번호 등)- 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서 (private로 지정하여 외부에 노출시키지 않음.)

 

이런 것을 객체지향개념의 캡슐화라고 한다.

 

멤버변수를 private이나 protected로 제한하고, 멤버변수의 값을 읽고 변경할 수 있는 public 메서드를 만들면 간접적으로 멤버변수의 값을 다룰 수 있다. 이렇게 만든 public 메서드가 getter, setter이다. (상속을 통해 확장될 것이 예상되는 클래스면 멤버에 접근제한을 주되 자손클래스에서 접근하는 것이 가능하도록 private 대신 protected를 이용한다. private이 붙은 멤버는 자손 클래스에서도 접근이 불가능하다.)


 

5) 다형성 

다형성 : 여러가지 형태를 가질 수 있는 능력. 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있다.

 

1
2
3
class Tv{ }
class SmartTv extends Tv{ }
Tv t = new SmartTv(); // 조상 타입의 참조변수로 자손타입 인스턴스 참조
 
 
 
 
 
 
 
 
 
cs

형변환 : 상속관계에 있는 클래스들끼리는 형변환이 가능하다.

 

밑의 코드는 조상 클래스(한 개)와 자손 클래스(두 개)를 예시로 들어 상속관계에만 서로 형변환이 가능하고 자손이 조상클래스로 형변환할 때는 생략이 가능함을 보여준다.

1
2
3
4
5
6
7
8
9
class Car{ }
class FireEngine extends Car{ }
class Ambulance extends Car{ }
 
FireEngine f = new FireEngine();
 
Car c = (Car)f; //참조변수 f는 조상인 Car 타입으로 형변환 가능(생략가능)
FireEngine f2 = (FireEngine)c; //참조변수 c는 자손인 FireEngine 타입으로 형변환 가능(생략불가)
Ambulance a = (Ambulance)f; //에러. 상속관계 아닌 클래스 간의 형변환 불가
cs

 

밑에 코드는 조상 클래스 Car과 자손 클래스 FireEngine가 서로 형변환을 하며 자손 클래스의 water() 메소드를 호출하는 모습을 보인다. 참조변수 fe의 값을 car에 저장하여 car로도 FireEngine 인스턴스를 다룰 수 있으나 water() 메소드는 호출할 수 없고 color, door, drive(), stop() 4개만 사용 가능하다.

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
public class Ex7_7 {
    public static void main(String args[]){
        Car car = null;
        FireEngine fe = new FireEngine();
        FireEngine fe2 = null;
        
        fe.water(); //FireEngine 클래스의 water 메소드가 실행된다.
        car = fe; //car = (Car)fe에서 (Car)이 생략된 것이다. fe를 조상 Car로 형변환 한 것.
//      car.water(); 컴파일 에러. car은 Car 타입 참조변수이므로 조상 클래스이더라도 water() 호출할 수 없다.
        fe2 = (FireEngine)car; //car를 자손 클래스타입으로 형변환
        fe2.water(); //fe2는 FireEngine 클래스 타입이므로 water()를 호출할 수 있다.
    }
}
 
class Car{
    String color;
    int door;
    
    void drive(){
        System.out.println("drive, Brrrr~");
    }
    
    void stop(){
        System.out.println("stop!!!");
    }
}
 
class FireEngine extends Car{
    void water(){
        System.out.println("water!!!");
    }
}
cs

instance of 연산자 : 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위함

다음 코드와 같이 사용하며 결과값으로 true 혹은 false를 반환한다.

true가 나오면 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

1
2
3
4
5
6
void doWork(Car c){
    if (c instanceOf FireEngine){ //형변환이 가능한지 확인
        FireEngine fe = (FireEngine)c; //형변환
        fe.water();
    }
}
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
class Product{
    int price;
    int bonusPoint;
    
    Product(int price){
        this.price = price;
        bonusPoint = (int)(price/10.0);
    }
}
 
class Tv extends Product{
    Tv(){ //조상 클래스의 생성자 Product(int price) 호출
        super(100); //Tv의 가격을 100만원으로 함.
    }
    
    public String toString(){ //Object 클래스의 toString()을 오버라이딩 함
        return "Tv"
    }
}
 
class Computer extends Product{
    Computer(){
        super(200);
    }
    public String toString(){
        return "Computer";
    }
}
 
class Buyer{
    int money = 1000;
    int bonusPoint = 0;
    
    void buy(Product p){ 
//Product p 를 매개변수로 한 위치에 실제로 Product가 들어가지 않는다.
   //대신 Product의 자손 클래스인 Tv와 Computer가 들어간다. 이는 다형성 때문에 가능하다.
   //만약 다형성이 성립되지 않는다면 제품별로 메서드를 계속 만들어줘야 한다.
   //즉, void buy(Tv), void buy(Computer)... 이런 식으로 만들어줘야 하는데
   //지금은 제품이 두개여서 다행이지 제품이 많으면 메서드를 제품 개수만큼 만들어줘야 한다.
        if(money < p.price){
            System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
            return;
        }
        money -= p.price;
        bonusPoint += p.bonusPoint;
        System.out.println(p + "을/를 구입하셨습니다.");
    }
}
 
public class Ex7_8{
    public static void main(String args[]){
        Buyer b = new Buyer();
        
        //다형성을 통해 Buyer 클래스에서 buy(Product p) 메서드를 만들어놓았으므로 
        //다음과 같이 b.buy(new Tv()), b.buy(new Computer()) 이 가능하다.
        b.buy(new Tv()); 
        b.buy(new Computer());
        
        System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
        System.out.println("현재 보너스점수는 " + b.bonusPoint + "점입니다.");
    }
}
cs

여러 종류의 객체를 배열로 다루기 

밑의 코드는 전체적으로 위의 코드와 비슷하나

Product[] cart = new Product[10];

cart[i++] = p;

가 추가되었다. 

Product 조상 클래스의 참조변수로 자손타입의 객체를 참조하는 것이 가능하므로 Product 타입의 배열을 만든 후, 배열의 요소들로 자손타입의 객체들을 넣어주는 것이다. 

밑의 코드를 통해 확인하자.

 

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class Product2{
    int price;
    int bonusPoint;
    
    Product2(int price){
        this.price = price;
        bonusPoint = (int)(price/10.0);
    }
    Product2() {} //기본생성자
}
 
class Tv2 extends Product2{
    Tv2(){ //조상 클래스의 생성자 Product2(int price) 호출
        super(100); 
    }
    
    public String toString(){ 
        return "Tv"
    }
}
 
class Computer2 extends Product2{
    Computer2(){
        super(200);
    }
    public String toString(){
        return "Computer";
    }
}
 
class Audio2 extends Product2{
    Audio2(){
        super(50);
    }
    public String toString(){
        return "Audio";
    }
}
 
class Buyer2{
    int money = 1000;
    int bonusPoint = 0;
    Product2[] cart = new Product2[10];
    int i = 0;
    
    void buy(Product2 p){ 
        if(money < p.price){
            System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
            return;
        }
        money -= p.price;
        bonusPoint += p.bonusPoint;
        //제품을 Product[] cart에 저장한다.
        //제품들이 모두 Product를 조상클래스로 가지므로 Product 타입의 배열에 들어갈 수 있다.
        cart[i++= p; 
        System.out.println(p + "을/를 구입하셨습니다.");
    }
    
    void summary(){
        int sum = 0;
        String itemList = "";
        
        for(int i=0; i<cart.length; i++){
            if(cart[i]==null){
                break;
            }
            //Product 타입의 배열 cart[]에 저장된 여러 제품들의 총금액, 명단을 출력한다.
            sum += cart[i].price;
            itemList += cart[i] + ", ";
        }
        System.out.println("구입하신 물품의 총금액인 " + sum + "만원입니다.");
        System.out.println("구입하신 제품은 " + itemList + "입니다.");
    }
}
 
public class Ex7_8{
    public static void main(String args[]){
        Buyer2 b = new Buyer2();
        
        b.buy(new Tv2()); 
        b.buy(new Computer2());
        b.buy(new Audio2());
        b.summary();
    }
}
cs