
=this.file.tags
[[4. 클래스 기본#객체 모델링|객체 모델링]]을 통해 추출된 클래스들 간에 생성되는 관계
예시를 들어보자. 어떤 기능을 하는 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]]
다수 클래스들 간의 공통점을 발견하는 방법
특정 클래스에서 각 하위 클래스로 분화
// 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;
}
}
//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;
}
}
// 실행 클래스
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()을 호출한다.
앞선 예제들에서 인스턴스를 구분하는게 어렵지 않아서 사용성이 두드러지지는 않는데, 만약 크고 복잡한 클래스를 만들게 된다면 가독성 측면에서 이점이 있다.
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를 매개변수로 집어넣었는데 굳이 아래에 써야하는가?
인스턴스 자기자신을 가르키는 변수
자기 자신의 다른 생성자를 호출하거나, 자신의 멤버를 호출할 때 사용한다. 예를들면 지역변수와 인스턴스 변수가 동일한 경우 다음과 같이 사용한다.
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);
}
상속 관계시 부모 클래스의 메소드를 자식 클래스에서 형식만 빌려와서 재정의 해서 사용하는 방법
[[4. 클래스 기본#✏️오버로딩| 메소드 오버로딩]]과 메서드 명을 재사용한다는 공통점이 있지만, 오버로딩은 메소드 인자의 순서, 개수, 타입이 달라야 하고, 상속관계가 아니어도 상관없지만 오버라이딩은 그렇지 않다.
외부에서 클래스의 맴버에 접근하는 범위를 일정하게 제한할 경우에 사용하는 지정자.
접근지정자를 사용하여 클래스의 멤버나 메서드에 접근하는 방법을 일정하게 함으로서 프로그램이 일관성 있게 동작 및 관리되도록 한다.
캡슐화를 통해 사용자가 임의로 조작하는 것을 배제함으로서 소스가 복잡해지는것을 막고, 기능의 사용 방법도 일정하게 정하여 기능 사용의 통일성을 유지한다.
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를 이미 설정한 적 있다. [[4. 클래스 기본#지정자|여기서]] 볼 수 있다.
추가 설명은 생략한다.
상속 구조에서 상위 클래스 타입의 변수가 하위 클래스의 인스턴스를 가리킬 수 있는 기능
다형성은 다시말해, 상속 관계에 있는 다른 타입의 변수가 같은 타입이 아닌 다른 타입도 가리킬 수 있는 능력을 뜻한다. 다형성은 반드시 상속이 전제되어야 한다.
다형성은 [[2. 자바의 기본 문법#데이터 형 변환|데이터형 변환]]과 비슷하게 역할을 수행한다.
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#✏️메소드 오버라이딩|재정의]] 된 메소드가 있는지 확인한 다음 만약 존재한다면 하위 클래스의 메소드를 실행하기 때문이다.
public class StudentTest2{
public static void main(String[] args){
Student s = new University("박지성", 3, 20);
University u = (University)s; //다운캐스팅
}
}
앞서 업캐스팅의 경우 하위클래스에서 오버라이딩 된 메소드를 호출할 수 있다고 했다. 하지만 하위 클래스에서만 존재하는 매서드를 사용해야할때, 업캐스팅 되어있는 경우 오류가 발생한다. 따라서 하위 클래스의 메소드를 호출하기 위해서는 다운 캐스팅을 해야한다.
#java/question : 일단 왜 쓰는지는 알았다. 근데 어느 상황에서 쓰는지는 모르겠다.
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();
}
}
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
);
}
}
package com.chapter6.practice;
public class PracticeSedan extends PracticeCar{
public PracticeSedan(int velocity, int wheel, String s){
super(velocity, wheel, s);
}
public PracticeSedan(){
super();
}
}
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 + "명");
}
}
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원