[2023.08.20] Java 객체지향 2

이재하·2023년 8월 20일
2

항해99

목록 보기
19/48

객체지향개념 2


상속

기존의 클래스로 새로운 클래스를 작성하는 것 (코드의 재사용)
두 클래스를 부모와 자식으로 관계를 맺어주는 것

class 자식클래스 extends 부모클래스 {
	// ...
}
  • 자손은 조상의 모든 멤버를 상속받는다. (생성자, 초기화블럭 제외)
  • 자손의 멤버 개수는 조상보다 적을 수 없다. (같거나 많다.)
  • 자손의 변경은 조상에 영향을 미치지 않는다.

포함

포함(composite)
클래스의 멤버로 참조변수를 선언하는 것

  • 작은 단위의 클래스를 만들고, 이들을 조합해서 클래스를 만든다.

클래스간의 관계 결정하기

상속관계 : ~은 ~이다. (is - a)
포함관계 : ~은 ~을 가지고 있다. (has - a)

대부분의 경우 포함관계를 사용한다. 90% 정도.
상속은 여러가지 제약이 있기 때문에 꼭 필요할 때만 사용함.


단일 상속

  • Java는 단일상속만을 허용한다. (조상은 하나만 허용)
  • 비중이 높은 클래스 하나만 상속관계로, 나머지는 포함관계로 한다.

    다중 상속의 효과를 낼 수 있다.

Object 클래스 - 모든 클래스의 조상

  • 부모가 없는 클래스는 자동적으로 Object 클래스를 상속받게 된다.
    (컴파일러에 의해)
  • 모든 클래스는 Object 클래스에 정의된 11개의 메소드를 상속받는다.
    toString(), equals(Object obg), hashCode(), ...

오버라이딩

-상속받은 조상의 메소드를 자신에 맞게 변경하는 것

class Point {
	int x;
    int y;
    
    String getLocation() {
    	  return "x :" + x + ", y :" + y;
    }
}

class Point3D entends Point {
	int z;
    
    String getLocation() {		// 오버라이딩
    	  return "x :" + x + ", y :" + y + ", z :" +z;
    }
}

조건

  • 선언부가 조상 클래스의 메소드와 일치해야 한다.
  • 접근 제어자를 조상 클래스의 메소드보다 좁은 범위로 변경할 수 없다.
  • 예외는 조상 클래스의 메소드보다 많이 선언할 수 없다.

오버로딩 vs 오버라이딩

오버로딩 :

  • 기존에 없는 새로운 메소드를 정의하는 것 (이름만 같은 메소드)
  • 상속과 관계 없음

오버라이딩 :

  • 상속받은 메소드의 내용을 변경하는 것

참조변수 super

  • 객체 자신을 가리키는 참조변수. 인스턴스 메소드(생성자) 내에만 전재
    (static 메소드 내에 사용 불가)
  • 조상의 멤버를 자신의 멤버와 구별할 때 사용
    (this 는 lv 와 iv 구별에 사용)
class Ex7_2 {
	public static void main(String args[]) {
		Child c = new Child();
		c.method();
	}
}

class Parent { int x=10; }

class Child extends Parent {
	int x=20;

	void method() {
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x="+ super.x);
	}
}

// x=20
// this.x=20
// super.x=10
class Ex7_3 {
	public static void main(String args[]) {
		Child2 c = new Child2();
		c.method();
	}
}

class Parent2 { int x=10; } // super.x 와 this.x 둘 다 가능

class Child2 extends Parent2 {
	void method() {
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x="+ super.x);
	}
}

// x=10
// this.x=10
// super.x=10

super() - 조상의 생성자

  • 조상의 생성자를 호출할 때 사용 // 생성자와 초기화 블럭은 상속되지 않기 때문
  • 조상의 멤버는 조상의 생성자를 호출해서 초기화

자손의 생성자는 자신이 생성한 것만 초기화 해야한다.

class Point {
	int x, y;

	Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

class Point3D extends Point {
	int z;

	Point3D(int x, int y, int z) {
		super(x, y);	// 조상클래스의 생성자 Point(int x, int y)를 호출
		this.z = z;		// 자신의 멤버를 초기화
	}
}
  • 생성자의 첫 줄에 반드시 생성자를 호출해야 한다.
    그렇지 않으면 컴파일러가 생성자의 첫 줄에 super(); 를 삽입

제어자

  • 클래스와 클래스의 멤버(멤버 변수, 메소드)에 부가적인 의미 부여

접근 제어자 : public ,protected,(default),private
제어자 : static,final,abstact . . .

  • 하나의 대상에 여러 제어자를 같이 사용가능 (접근 제어자는 하나만)

접근 제어자

캡슐화

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

  • 외부로부터 데이터를 보호하기 위해

    hour의 값이 if문을 만족 시키면 return 을 통해 setHour를 빠져나가기 때문에 바뀌지 않음.

다형성

  • 조상 타입 참조 변수로 자손 타입 객체를 다루는 것

  • 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 없다.

조상 타입 참조변수로 자손 타입 인스턴스 참조

부모의 참조변수로 자식 인스턴스를 가르키면 여러 기능 중 자식의 기능만 안쓰면 됨.
그러나 자식의 참조변수로 부모의 인스턴스를 가르키면 
실제 가지고 있는 멤버의 개수보다 기능이 많아서 동작 안하는 경우 생김 -> 에러
// 부모 타입 참조변수가 자식 타입 인스턴스 가르키는 것 ! ! ! !

참조변수의 형변환

사용할 수 있는 멤버의 갯수를 조절하는 것
-> 객체에 속한 멤버들에 대한 사용범위가 달라진다는 것

  • 조상 자손 관계(상속 관계)의 참조변수는 서로 형변환 가능

  • 자손 타입에서 조상 타입으로 형변환 하는 경우, 형변환 생략가능
    자손타입 -> 조상타입 (Up-casting) : 형변환 생략가능
    조상타입 -> 자손타입 (Down-casting) : 형변환 생략불가

    생략 가능, 불가 / 업캐스팅, 다운캐스팅의 개념이 있지만 신경쓰지 말고	
    조상 자손 관계에 형변환 가능한 것에 집중하자

// Person 클래스 정의 (부모 클래스)
public class Person {
	
	String name;
	int age;
		
	public void speak() {
		System.out.println(name + ": 안녕하세요");
	}
}

// Dancer 클래스 정의 (자식 클래스)
public class Dancer extends Person {
		
	public void dance() {
		System.out.println(name + ": 춤을 춥니다");
	}
}

// 객체 생성 및 실행
public class HelloWorld {
	public static void main(String[] args) {

		//==================================
		System.out.println("==== 예시1 ====");
		//==================================
		Person p1 = new Dancer();
		
		p1.name = "홍길동";
		p1.speak();
		// p1.dance();  // 사용불가 
		
		Dancer d1 = (Dancer) p1;	// 다운캐스팅
		d1.name = "이순신";
		d1.speak();
		d1.dance();
		
		//==================================
		System.out.println("==== 예시2 ====");
		//==================================
		Dancer d2 = new Dancer();
		
		d2.name = "제갈공명";
		d2.speak();
		d2.dance();
		
		Person p2 = (Person) d2;	// 업캐스팅 - (Person) 생략가능
		p2.name = "신사임당";
		p2.speak();
		// p2.dance();  // 사용불가
		
	}
}


// 실행결과
==== 예시1 ====
홍길동: 안녕하세요
이순신: 안녕하세요
이순신: 춤을 춥니다
==== 예시2 ====
제갈공명: 안녕하세요
제갈공명: 춤을 춥니다
신사임당: 안녕하세요

장점

  • 여러 자식클래스 객체를 하나의 배열로 다룰수 있다.
  • 메소드의 매개변수를 부모클래스 타입 하나로 전달받아 사용할 수 있다.

instanceof 연산자

부모클래스 타입으로 형변환하여 사용하게 되면, 실제 생성된 인스턴스는 여러 자식클래스 중에 어느 것인지 알아야 하는 경우가 생긴다.

instanceof 연산자는 해당 참조 변수가 형변환이 가능한지 여부를 확인할 때 사용된다.

  • 사용법 : 참조변수 instanceof 클래스명
  • 반환값 : 형변환 가능하면 true, 아니면 false 반환

형변환 전에 반드시 instanceof 로 확인!!!

// 클래스 정의
public class Person { 	} // 부모

class Dancer extends Person {    } // 자손

class Actor extends Person {    } // 자손


// 객체 생성 및 실행
public class HelloWorld {
	public static void main(String[] args) {
		
		Dancer d1 = new Dancer();
		Person p1 = (Person) d1;
		
		System.out.println("Person? " + (p1 instanceof Person));
		System.out.println("Dancer? " + (p1 instanceof Dancer));
		System.out.println("Actor?  " + (p1 instanceof Actor));
	}
}

// 실행결과
Person? true
Dancer? true
Actor?  false

수정 예정

이해가 가지 않음

매개변수의 다형성

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


추상 클래스와 인터페이스는 따로 정리

0개의 댓글