[JAVA-5] 클래스 고급 1

ParkJunHa·2023년 10월 8일

=this.file.tags

🔏상속

✏️클래스들의 관계 (Relationship)

[[4. 클래스 기본#객체 모델링|객체 모델링]]을 통해 추출된 클래스들 간에 생성되는 관계

is-a 관계

  • 한 클래스가 다른 클래스의 구체적인 경우. (상속 관계)
  • ex) 대학생 is a 학생 , 버스 is a 자동차

has-a 관계

  • 한 클래스가 다른 클래스의 멤버 변수가 되는 경우 (소유 관계)
  • ex) 비행기 has a 날개 , 컴퓨터 has a 모니터

use-a 관계

  • 한 클래스가 다른 클래스를 이용하는 경우 (실행 클래스에서 다른 클래스의 인스턴스를 생성하는 경우 - 앞선 예제들이 모두 다 이경우에 해당함.)
  • ex) 운전사 use a 자동차

상속의 정의

  • is-a 관계가 성립되는 클래스 관계가 변환된 것.
  • 특정 클래스가 가지는 속성과 기능을 다른 클래스가 사용할 수 있도록 하는 클래스들 간의 관계
    즉, 상속의 목적은 기존 클래스의 기능과 속성을 다른 클래스에서 재사용하는데 있다. 객체 지향 언어는 사람의 사고나 행위를 프로그래밍에 그대로 적용한 것이다.

상속의 용도

  • 클래스 속성이나 기능들을 다른 클래스에서 재사용할 수 있다.
  • 중복 코드를 제거하면 가독성을 높이고 개발시간을 단축할 수 있다.

예시를 들어보자. 어떤 기능을 하는 A 클래스가 쓰이고있을때, 새로운 기능을 하는 B클래스를 개발한다고 가정했을 때, 새로운 기능 중 일부분이 A 클래스에서도 이미 쓰이고 있는 기능이라면 B클래스에 동일한 기능을 구현하는대신 A 클래스의 기능을 그대로 부모에게 상속받듯이 사용할 수 있다.

대부분 객체지향은 이러한 재사용성을 높이기 위해 도입된 개념들이다. 상속 또한 이러한 재사용성을 높이기 위한 것이다.

✏️형식 및 특징

형식

public class Child(undo, sub) extends Parents(redo, super){
...
}

상속받는 자식의 클래스 뒤에 extends 키워드를 이용하여 부모 클래스 동작과 속성을 자식 클래스에서 자유롭게 접근하여 사용할 수 있다.

![[Pasted image 20231007134510.png | 500]]

특징

  • 단일 상속만 가능
  • 상위 클래스의 속성과 기능을 자식 클래스에서 자유롭게 사용 가능

![[Pasted image 20231007135932.png | 300]]

![[Pasted image 20231007135952.png | 300]]

✏️상속 관계로 만들기

각 클래스 일반화 하기(Generalization)

다수 클래스들 간의 공통점을 발견하는 방법

  • 각 클래스들의 공통점을 가진 클래스를 부모 클래스로 만든다
  • 각 클래스들은 부모 클래스를 상속받는다

특정 클래스에서 다른 클래스로 분화하기(Specialization)

특정 클래스에서 각 하위 클래스로 분화

  • 특정 클래스에서 각 하위 클래스로 분화된다.

✏️상속 예제

부모 클래스(Student)

// Student 클래스 (부모)

public class ExStudent {  
    public String name;  
    public int grade;  
  
    public ExStudent(String _name){  
        name = _name;  
    }  
    public ExStudent(){}  
    public String getName(){  
        return name;  
    }  
    public int getGrade(){  
        return grade;  
    }  
    public void setName(String _name){  
        name = _name;  
    }  
    public void setGrade(int _grade){  
        grade = _grade;  
    }  
  
    public String getStudentInfo(){  
        return "name : " + name + "grade : " + grade;  
    }  
}

자식 클래스 (Element, University)

//Elementary 클래스 (자식 1)
import com.chapter5.ExStudent;  
public class InheritStudentElement extends ExStudent{  
    public InheritStudentElement (String _name, int _grade){  
        name = _name;  
        grade = _grade;  
    }  
}
// University 클래스 (자식 2)

import com.chapter5.ExStudent;  
public class InheritStudentUniversity extends ExStudent{  
    private int courses;  
    public InheritStudentUniversity(String _name, int _grade, int _courses){  
        name = _name;  
        grade = _grade;  
        courses = _courses;  
    }  
    public int getCourses(){  
        return courses;  
    }  
}

실행 클래스 (Main)

// 실행 클래스
import com.chapter6.InheritStudentUniversity;  
import com.chapter6.InheritStudentElement;  
  
public class Main {  
    public static void main(String[] args) {  
        String studentInfo = null;  
        InheritStudentElement e = new InheritStudentElement("Lee",2);  
        InheritStudentUniversity u = new InheritStudentUniversity("Hong", 3, 20);  
  
        studentInfo = e.getStudentInfo();  
        System.out.println("INFO : " + studentInfo);  
  
        studentInfo = u.getStudentInfo();  
        System.out.println("INFO : " + studentInfo + ", course : " + u.getCourses());  
    }  
}

#java/question : 서로다른 패키지에있는 클래스의 상속시, 인스턴스에 할당할 때는 public을 사용해야만하는지? (애초에 그런식으로 사용하면 설계오류인가?)
그렇다면 그래도 괜찮은건지? (잘 모르지만 밖에서 접근하지 못하도록 private 를 사용하는걸로 아는데, 문제가 되지는 않는지?)

연습

앞선 Student 상속 예제에 상위 부모 클래스 person을 추가한다.

상속 관계 : Person <= Student <= (Univ, Element)

// Person
package com.chapter6;  
  
public class ParentPerson {  
    public String gender;  
    public int age;  
    
    public String getGender(){  
        return gender;  
    }  
    public void getAge(int _age){  
        age = _age;  
    }  
  
}
//ExStudent 수정사항 
// + ParentPerson 상속
public class ExStudent extends ParentPerson
// InheritStudentElement 에서 생성자 추가

public InheritStudentElement(String _name, int _grade, String _gender, int _age){  
    name = _name;  
    grade = _grade;  
    gender = _gender;  
    age = _age;  
}
// 실행 클래스 e2 인스턴스 생성 후 출력

InheritStudentElement e2 = new InheritStudentElement("Park",3,"Male",12);
studentInfo = e2.getStudentInfo();  
System.out.println("INFO : " + studentInfo + ", age : "+e2.age + ", gender : "+e2.gender);
// 출력결과

INFO : name : Leegrade : 2
INFO : name : Honggrade : 3, course : 20
INFO : name : Parkgrade : 3, age : 12, gender : Male

super

메모리에있는 자식 인스턴스에서 부모 인스턴스를 가르킬 때 사용한다.

단순히 구분하는 용도로서 만약에 명시적으로 호출하지 않는 경우 컴파일 시 자동으로 super()을 호출한다.
앞선 예제들에서 인스턴스를 구분하는게 어렵지 않아서 사용성이 두드러지지는 않는데, 만약 크고 복잡한 클래스를 만들게 된다면 가독성 측면에서 이점이 있다.

public University(String name, int grade, int _courses){
	super();
	super.name = name;
	super.grade = grade;
	courses = _courses
}

또는 클래스 생성자에서 인자를 추가하는 방법도 있다.

public University(String name, int grade, int _courses){
	super(name, grade);
	super.name = name;
	super.grade = grade;
	courses = _courses
}

#java/question : 부모 클래스에 name과 grade를 매개변수로 집어넣었는데 굳이 아래에 써야하는가?

this

인스턴스 자기자신을 가르키는 변수

자기 자신의 다른 생성자를 호출하거나, 자신의 멤버를 호출할 때 사용한다. 예를들면 지역변수와 인스턴스 변수가 동일한 경우 다음과 같이 사용한다.

public void setName(String name){
	this.name = name;
}

또한 디폴트 생성자를 호출하여 인스턴스를 생성한 경우 인스턴스 변수를 초기화 하지 못한다는 단점이 있었는데, this를 이용하여 다른 생성자를 호출하면 디폴트 생성자를 호출해도 인스턴스 변수들을 초기화할 수 있다.

public University(String name, int grade, int courses){
	super(name, grade);
	super.name = name;
	super.grade = grade;
	this.courses = courses
}

public University(){
	this("이순신", 2, 20);
}

✏️메소드 오버라이딩

상속 관계시 부모 클래스의 메소드를 자식 클래스에서 형식만 빌려와서 재정의 해서 사용하는 방법

규칙

  • 메소드 이름이 동일해야 한다.
  • 리턴 타입이 동일해야 한다.
  • 매개변수 리스트가 동일해야 한다 (순서, 개수, 타입)
  • static, final, private가 지정된 메소드는 오버라이딩 불가.

용도

  • 자식 클래스에서 상속받는 메소드를 그냥 사용해서는 자신이 원하는 결과를 완전하게 얻을 수 없을때.
  • 하위 클래스에서 상위 클래스의 메소드 형식만 빌려와서 본체의 기능을 재정의 해서 사용해서 자신이 원하는 결과를 얻는다.

[[4. 클래스 기본#✏️오버로딩| 메소드 오버로딩]]과 메서드 명을 재사용한다는 공통점이 있지만, 오버로딩은 메소드 인자의 순서, 개수, 타입이 달라야 하고, 상속관계가 아니어도 상관없지만 오버라이딩은 그렇지 않다.

✏️접근지정자

외부에서 클래스의 맴버에 접근하는 범위를 일정하게 제한할 경우에 사용하는 지정자.

종류

  • public : 모든 클래스에서 접근 가능
  • protected : 같은 패키지 안에 있는 클래스와 상속 관계의 클래스들만 접근 가능
  • default : 같은 패키지에 있는 클래스들만 접근 가능
  • private : 같은 클래스 내에서만 접근 가능

지정자별 적용 대상

  • 클래스 : public, default
  • 멤버 변수 : 모든 접근 지정자
  • 멤버 메서드 : 모든 접근 지정자
  • 지역 변수 : 적용 불가

캡슐화

접근지정자를 사용하여 클래스의 멤버나 메서드에 접근하는 방법을 일정하게 함으로서 프로그램이 일관성 있게 동작 및 관리되도록 한다.

캡슐화를 통해 사용자가 임의로 조작하는 것을 배제함으로서 소스가 복잡해지는것을 막고, 기능의 사용 방법도 일정하게 정하여 기능 사용의 통일성을 유지한다.

예제

  • 캡슐화 이전
public class Data{
	public int x, y;
	public int value;
}
public class DataTest1{
	public static void main(String[] args){
		Data d = new Data();
		d.value = d.x + d.y;
	}
}

value 값은 x와 y의 합을 저장하는 기능을한다. 하지만 그렇게 되면 의도하지 않은 동작을 할 수 도 있고, 소스 관리 측면에서 좋지 않다. 예를들어 더하는 기능을 모두 곱하는 기능으로 바꿔야 한다면 소스의 모든 부분에서 이 부분을 찾아 수정해야한다.

  • 캡슐화 이후
public class Data1{
	private int x, y;
	private int value;

	public int getValue(){
		value = x+y;
		return value;
	}
}
public class DataTest1{
	public static void main(String[] args){
		Data1 d = new Data1();
		int value = d.getValue();
	}
}

만약 여기서 클래스변수 x, y에 직접 접근한다면 오류가 발생한다.
만약 변수에 접근하고 싶다면 getter/setter를 이용하여 간접접근해야 한다.

getter/setter

앞서 우리는 간접접근을 위한 getter/setter를 이미 설정한 적 있다. [[4. 클래스 기본#지정자|여기서]] 볼 수 있다.
추가 설명은 생략한다.

✏️다형성(Polymorphism)

상속 구조에서 상위 클래스 타입의 변수가 하위 클래스의 인스턴스를 가리킬 수 있는 기능

  • 반드시 상속 관계인 클래스들 사이에서 성립한다.
  • 재사용성이 높아진다
  • [[2. 자바의 기본 문법#instanceof 연산자|instanceof]] 연산자와 함께 사용한다.

다형성은 다시말해, 상속 관계에 있는 다른 타입의 변수가 같은 타입이 아닌 다른 타입도 가리킬 수 있는 능력을 뜻한다. 다형성은 반드시 상속이 전제되어야 한다.

다형성은 [[2. 자바의 기본 문법#데이터 형 변환|데이터형 변환]]과 비슷하게 역할을 수행한다.

업캐스팅(upcasting)

  • 상속구조에서 상위타입 변수가 하위 타입의 인스턴스를 가리킬 수 있는 능력을 말한다. 그냥 예시를 보자.
public class StudentTest2{
	public static void main(String[] args){
		Student s = new University("박지성", 3, 20);
		System.out.println(s.getStudentInfo());
	}
}

인스턴스 s를 선언하는 부분은 상위 Student가 하위 University 인스턴스를 받고 있다.
[[4. 클래스 기본#인스턴스 생성법|앞에서]] 인스턴스를 생성하면 같은 클래스 타입의 변수가 받아야 한다라고 했다. 이게 가능한 이유는 University와 Student중 Student가 상위 클래스이고, 상위 클래스 타입으로 선언한 변수가 더 큰 타입이므로 하위의 University 인스턴스를 받을 수 있는것이다. 이를 업캐스팅이라고 한다.

하지만 getStudentInfo()를 출력하면 상식적으로 상위클래스인 Student의 매소드가 실행될 것이라고 생각이 되는데 실제로는 오버라이딩 된 University의 메소드가 실행된다.

그 이유는 업캐스팅 된 변수 s의 메소드를 확인하고, 하위 클래스에서 [[5.클래스 고급 - 1#✏️메소드 오버라이딩|재정의]] 된 메소드가 있는지 확인한 다음 만약 존재한다면 하위 클래스의 메소드를 실행하기 때문이다.

다운캐스팅(downcasting)

  • 상위클래스 타입을 하위 클래스 타입으로 캐스팅 한다. 역시 예제를 먼저 보자
public class StudentTest2{
	public static void main(String[] args){
		Student s = new University("박지성", 3, 20);
		University u = (University)s; //다운캐스팅
	}
}

앞서 업캐스팅의 경우 하위클래스에서 오버라이딩 된 메소드를 호출할 수 있다고 했다. 하지만 하위 클래스에서만 존재하는 매서드를 사용해야할때, 업캐스팅 되어있는 경우 오류가 발생한다. 따라서 하위 클래스의 메소드를 호출하기 위해서는 다운 캐스팅을 해야한다.

#java/question : 일단 왜 쓰는지는 알았다. 근데 어느 상황에서 쓰는지는 모르겠다.

다형성 실습

main.java

import com.chapter6.practice.PracticeCar;  
import com.chapter6.practice.PracticeSedan;  
import com.chapter6.practice.PracticeTruck;  
import com.chapter6.practice.PracticeBus;  
  
public class Main {  
    public static void main(String[] args) {  
        PracticeCar b = new PracticeBus();  
        PracticeCar t = new PracticeTruck();  
        PracticeCar s = new PracticeSedan();  
          
        System.out.println("==========");  
        s.getInfo();  
        System.out.println("==========");  
        t.getInfo();  
        System.out.println("==========");  
        b.getInfo();  
          
    }  
}

Car.java

package com.chapter6.practice;  
public class PracticeCar {  
    private int velocity;  
    private int wheelNum;  
    private String carName;  
  
    public PracticeCar(int velocity, int wheelNum, String s){  
        this.velocity = velocity;  
        this.wheelNum = wheelNum;  
        this.carName = s;  
    }  
    public PracticeCar(){  
        this(0, 4, "default");  
    }  
  
    public int getVelocity() {  
        return velocity;  
    }  
    public int getWheelNum(){  
        return wheelNum;  
    }  
    public String getCarName(){  
        return carName;  
    }  
  
    public void speedUp(PracticeCar c, int speed){  
        if (c instanceof PracticeTruck)  
            System.out.println("Truck의 속도를 "+ speed +" 놉힙니다.");  
  
        if (c instanceof PracticeSedan)  
            System.out.println("Sedan의 속도를 "+ speed +" 놉힙니다.");  
  
        if (c instanceof PracticeBus)  
            System.out.println("Bus의 속도를 "+ speed +" 놉힙니다.");  
  
        velocity += speed;  
    }  
  
    public void speedDown(PracticeCar c, int speed){  
        if (c instanceof PracticeTruck)  
            System.out.println("Truck의 속도를 "+ speed +" 낮춥니다.");  
  
        if (c instanceof PracticeSedan)  
            System.out.println("Sedan의 속도를 "+ speed +" 낮춥니다.");  
  
        if (c instanceof PracticeBus)  
            System.out.println("Bus의 속도를 "+ speed +" 낮춥니다.");  
  
        velocity -= speed;  
    }  
    public void stop(){  
        System.out.println("차량을 멈춥니다.");  
        velocity = 0;  
    }  
    public void getInfo(){  
        System.out.println("이 차량의 정보는 다음과 같습니다.");  
        System.out.println(  
                "1. 차종 : "+carName+"\n"+  
                "2. 속도 : "+velocity+"\n"+  
                "3. 바퀴 수 : "+wheelNum  
        );  
    }  
}

Sedan.java

package com.chapter6.practice;  
public class PracticeSedan extends PracticeCar{  
    public PracticeSedan(int velocity, int wheel, String s){  
        super(velocity, wheel, s);  
    }  
    public PracticeSedan(){  
        super();  
    }  
}

Truck.java

package com.chapter6.practice;  
  
public class PracticeTruck extends PracticeCar{  
    private int load;  
    public PracticeTruck(int velocity, int wheel, String s, int load){  
        super(velocity, wheel, s);  
        this.load = load;  
    }  
    public PracticeTruck(){  
        super();  
        this.load = 0;  
    }  
    public int getLoad() {  
        return load;  
    }  
    public void carryLoad(int load){  
        this.load += load;  
    }  
    public void getInfo(){  
        super.getInfo();  
        System.out.println("4. 적재물 중량 : "+ this.load + "명");  
    }  
}

Bus.java

package com.chapter6.practice;  
public class PracticeBus extends PracticeCar{  
    private int passenger;  
    private int fee;  
    private int income;  
  
    public PracticeBus(int velocity, int wheel, String s, int passenger, int fee){  
        super(velocity, wheel, s);  
        this.passenger = passenger;  
        this.fee = fee;  
    }  
  
    public PracticeBus(){  
        super();  
    }  
  
    public int getPassenger() {  
        return passenger;  
    }  
  
    public void setFee(int fee) {  
        this.fee = fee;  
    }  
  
    public int getFee() {  
        return fee;  
    }  
  
    public void ridePassenger(){  
        this.passenger += 1;  
        System.out.println("승객이 탑승합니다. 현재 승객은" + this.passenger + "명 입니다.");  
        this.payFee();  
    }  
  
    public void payFee(){  
        System.out.println("승객이 요금을"+this.fee+"원 지불합니다.");  
        this.income += this.fee;  
        System.out.println("현재까지 버스의 수익은 " + this.income +"원 입니다.\n");  
    }  
  
    public void getInfo(){  
        super.getInfo();  
        System.out.println("4. 현재 승객 : "+ this.passenger + "명\n" +  
                "5. 버스 요금 : "+ this.fee + "원\n" +  
                "6. 버스 수입 : "+ this.income + "원\n");  
    }  
}

결과

==========
이 차량의 정보는 다음과 같습니다.
1. 차종 : default
2. 속도 : 0
3. 바퀴 수 : 4
==========
이 차량의 정보는 다음과 같습니다.
1. 차종 : default
2. 속도 : 0
3. 바퀴 수 : 4
4. 적재물 중량 : 0명
==========
이 차량의 정보는 다음과 같습니다.
1. 차종 : default
2. 속도 : 0
3. 바퀴 수 : 4
4. 현재 승객 : 0명
5. 버스 요금 : 0원
6. 버스 수입 : 0원

Reference

다형성 과정

profile
PS린이

0개의 댓글