상속이란? 부모가 가진것을 자식에게 물려주는것을 의미한다.
새로운 클래스를 정의 할 때 이미 구현된 클래스를 상속(inheritance) 받아서 속성이나 기능을 확장하여 클래스를 구현한다.
하위 클래스를 생성하면 상위 클래스가 먼저 생성된다.
클래스가 상속받은 경우 하위 클래스의 생성자에서는 반드시 상위 클래스의 생성자를 호출한다.
이미 구현된 클래스보다 더 구체적인 기능을 가진 클래스를 구현해야 할때 기존 클래스를 상속한다.
상속하는 클래스 : 상위 클래스, parent class, base class, super class
상속받는 클래스 : 하위 클래스, child class, derived class, subclass
상위 클래스는 하위 클래스 보다 더 일반적인 개념과 기능을 가짐
하위 클래스는 상위 클래스 보다 더 구체적인 개념과 기능을 가짐
하위 클래스가 상위 클래스의 속성과 기능을 확장 (extends)한다는 의미
Car 를 상속받은 Bus 를 class로 표현하는 방법
public class Car{
}
public class Bus extends Car{
}
extends 키워드 뒤에는 단 하나의 클래스만 올 수 있음
자바는 단일 상속(single inheritance)만을 지원함
부모클래스에 메소드 추가하기
public class Car{
public void run(){
System.out.println("달리다.");
}
}
public class BusExam{
public static void main(String args[]){
Bus bus = new Bus();
bus.run();
//Bus class 는 아무런 코드를 가지지 않는다. 그럼에도 run 이라는 메소드를 사용하는데 문제가 발생되지 않는다.
}
}
public class Bus extends Car{
public void ppangppang(){
System.out.println("빵빵");
}
}
접근 제한자란 클래스 내에서 멤버의 접근을 제한하는 역할을 한다.
public
어떤 클래스든 접근할 수 있다는 것을 의미
protected
자기 자신, 같은 패키지, 그리고 서로 다른 패키지라 하더라도 상속받은 자식 클래스에서는 접근할 수 있다는 것을 의미
private
자기 자신만 접근할 수 있다는 것을 의미
접근제한자를 적지 않으면 default 접근 지정자
자기 자신과 같은 패키지에서만 접근할 수 있다는 것을 의미
public class AccessObj{
private int i = 1;
int k = 2; // default접근 제한자
public int p = 3;
protected int p2 = 4;
}
같은 패키지 내의 AccessObj를 사용하는 AccessObjExam
public class AccessObjExam{
public static void main(String args[]){
AccessObj po = new AccessObj();
System.out.println(po.i); // 컴파일 오류가 발생합니다.
System.out.println(po.k);
System.out.println(po.p);
System.out.println(po.p2);
}
}
AccessObj와 다른 패키지에서 사용해보기
패키지가 달라졌기때문에 default접근제한자로 지정된 필드 k와 protected 접근제한자로 지정된 필드 p2도 접근할 수 없다.
public class AccessObjExamOtherPackage{
public static void main(String args[]){
AccessObj po = new AccessObj();
System.out.println(po.i); // 컴파일 오류가 발생합니다.
System.lout.println(po.k);// 컴파일 오류가 발생합니다.
System.lout.println(po.p);
System.lout.println(po.p2);// 컴파일 오류가 발생합니다.
}
}
AccessObjExamOtherPackage를 AccesObj로 부터 상속받도록 수정한 후 사용해 보기
패키지는 다르지만 상속관계에 있으므로 protected 접근제한자로 지정된 필드 p2에 접근할 수 있다.
public class AccessObjExamOtherPackage extends AccessObj{
public static void main(String[] args) {
AccessObj obj = new AccessObj();
System.out.println(obj.i);// 컴파일 오류가 발생합니다.
System.out.println(obj.k);// 컴파일 오류가 발생합니다.
System.out.println(obj.p);
System.out.println(obj.p2);
}
}
private 으로 선언된 멤버 변수 (필드)에 대해 접근, 수정할 수 있는 메서드를 public으로 제공
get() 메서드만 제공 되는 경우 read-only 필드
이클립스에서 자동으로 생성됨
private으로 제어한 멤버 변수도 public 메서드가 제공되면 접근 가능하지만 변수가 public으로 공개되었을 때보다
private일때 각 변수에 대한 제한을 public 메서드에서 제어 할 수 있다.
public void setMonth(int month) {
if ( month < 1 || month > 12) {
isValid = false;
}
else {
this.month = month;
}
}
위와 같이 코드를 짜면 month에 직접 접근하는 것이 아닌 setMonth 메소드를 통해 접근하기 때문에, valid하지 않은 값을 잡아낼 수 있다.
객체 지향 프로그램에서 정보 은닉은 필요한 외부에서 접근 가능한 최소한의 정보를 오픈함으로써 객체의 오류를 방지하고 클라이언트 객체가 더 효율적으로 객체를 활용할 수 있도록 해준다.
정보 은닉을 활용한 캡슐화
꼭 필요한 정보와 기능만 외부에 오픈함
대부분의 멤버 변수와 메서드를 감추고 외부에 통합된 인터페이스만은 제공하여 일관된 기능을 구현하게 함
각각의 메서드나 멤버 변수를 접근함으로써 발생하는 오류를 최소화 한다.
추상 클래스란 구체적이지 않은 클래스를 의미한다. 독수리, 타조는 구체적인 새를 지칭하는데 새, 포유류 같은 것은 구체적이지 않다.
이런 것을 구현한 클래스를 추상 클래스라고 한다.
public abstract class Bird{
public abstract void sing();
public void fly(){
System.out.println("날다.");
}
}
public class Duck extends Bird{
@Override
public void sing() {
System.out.println("꽥꽥!!");
}
}
public class DuckExam {
public static void main(String[] args) {
Duck duck = new Duck();
duck.sing();
duck.fly();
//Bird b = new Bird();
}
}
class가 인스턴스화 될 때 생성자가 실행되면서 객체의 초기화를 한다. 그 때 자신의 생성자만 실행이 되는것이 아니고, 부모의 생성자부터 실행된다.
부모 생성자
public class Car{
public Car(){
System.out.println("Car의 기본생성자입니다.");
}
}
public class Bus extends Car{
public Bus(){
System.out.println("Bus의 기본생성자입니다.");
}
}
생성자 테스트
public class BusExam{
public static void main(String args[]){
Bus b = new Bus();
}
}
결과
Car의 기본생성자입니다. Bus의 기본생성자입니다.
자신을 가리키는 키워드가 this 라면, 부모들 가리키는 키워드는 super
super() 는 부모의 생성자를 의미한다.
super는 생성된 상위 클래스 인스턴스의 참조 값을 가지므로 super를 이용하여 상위 클래스의 메서드나 멤버 변수에 접근할 수 있다.
부모의 생성자를 임의로 호출하지 않으면, 부모 class의 기본 생성자가 자동으로 호출된다.
아래 예제처럼 호출해보면 위에서 super()를 호출하지 않을때와 결과가 같다.
public Bus(){
super();
System.out.println("Bus의 기본생성자입니다.");
}
부모의 기본생성자가 아닌 다른 생성자를 호출하는 방법
클래스는 기본 생성자가 없는 경우도 존재한다.
public class Car{
public Car(String name){
System.out.println(name + " 을 받아들이는 생성자입니다.");
}
}
Car class가 위 처럼 수정되면, Bus생성자에서 컴파일 오류가 발생한다.
부모가 기본생성자가 없기 때문에 컴파일 오류가 발생하게 되는 것이다.
이런 문제를 해결하려면 자식 클래스의 생성자에서 직접 부모의 생성자를 호출해야 한다.
public Bus(){
super("소방차"); // 문자열을 매개변수로 받는 부모 생성자를 호출하였다.
System.out.println("Bus의 기본생성자입니다.");
}
super 키워드는 자식에서 부모의 메소드나 필드를 사용할 때도 사용한다.
오버라이딩이란 부모가 가지고 있는 메소드와 똑같은 모양의 메소드를 자식이 가지고 있는 것이다. 즉 오버라이딩이란 메소드를 재정의 하는 것이다.
Car 클래스를 상속받은 Bus 클래스는 부모 클래스가 가지고 있는 run() 메소드를 잘 사용한다.
//run 메소드를 가지고 있는 Car클래스
public class Car{
public void run(){
System.out.println("Car의 run메소드");
}
}
//Car 를 상속받는 Bus 클래스
public class Bus extends Car{
}
public class BusExam{
public static void main(String args[]){
Bus bus = new Bus();
bus.run(); //Car의 run메소드가 실행된다.
}
}
Bus클래스에 부모가 가지고 있는 메소드와 모양이 같은 메소드를 선언
public class Bus extends Car{
public void run(){
System.out.println("Bus의 run메소드");
}
}
public class BusExam{
public static void main(String args[]){
Bus bus = new Bus();
bus.run(); //Bus run메소드가 실행된다.
}
}
BusExam을 실행해보면 "Bus의 run메소드"가 출력된다.
메소드를 오버라이드 하면, 항상 자식클래스에서 정의된 메소드가 호출된다.
오버라이딩 한다고 해서 부모의 메소드가 사라지는 것은 아니다.
super 키워드를 이용하면, 부모의 메소드를 호출 할 수 있다.
public class Bus extends Car{
public void run(){
**super.run();** // 부모의 run()메소드를 호출
System.out.println("Bus의 run메소드");
}
}
부모 타입으로 자식 객체를 참조하게 되면 부모가 가지고 있는 메소드만 사용할 수 있다. 자식객체가 가지고 있는 메소드나 속성을 사용하고 싶다면 형변환 해야 한다.
public class Car{
public void run(){
System.out.println("Car의 run메소드");
}
}
public class Bus extends Car{
public void ppangppang(){
System.out.println("빵빵.");
}
}
상속관계란 is a 관계이다. "Bus는 Car다." 라는 관계가 성립되는 것이다.
현실에서도 우리는 버스를 가리키면서 차다. 라고 말하곤 한다.
부모타입으로 자식객체를 참조할 수 있다. 자식이 부모를 내포하고 있기 때문이다. (항상 상위 클래스의 인스턴스가 먼저 생성되고, 하위 클래스의 인스턴스가 생성되므로)
부모타입으로 자식객체를 참조하게 되면 부모가 가지고 있는 메소드만 사용할 수 있다.
public class BusExam{
public static void main(String args[]){
Car car = new Bus();
car.run();
car.ppangppang(); // 컴파일 오류 발생
}
}
ppangppang()메소드를 호출하고 싶다면 Bus타입의 참조변수로 참조해야 한다.
public class BusExam{
public static void main(String args[]){
Car car = new Bus();
car.run();
//car.ppangppang(); // 컴파일 오류 발생
Bus bus = (Bus)car; //부모타입을 자식타입으로 형변환
bus.run();
bus.ppangppang();
}
}
객체들 끼리도 형변환이 가능하다. 단 상속관계에 있었을 때만 가능하다.
부모타입으로 자식타입의 객체를 참조할 때는 묵시적으로 형변환이 일어난다.
부모타입의 객체를 자식타입으로 참조하게 할때는 명시적으로 형변환 해주어 한다. 단 이렇게 형변환 할때에는 부모가 참조하는 인스턴스가 형변환 하려는 자식타입일 때만 가능하다.
Customer vc = new VIPCustomer(); 에서 vc가 가리키는 것은?
VIPCustomer() 생성자에 의해 VIPCustomer 클래스의 모든 멤버 변수에 대한 메모리는 생성되었지만, 변수의 타입이 Customer 이므로 실제 접근 가능한 변수나 메서드는 Customer의 변수와 메서드이다.
Human은 내부적으로 Promate와 mammal의 타입을 모두 내포하고 있다
Primate pHuman = new Human();
Mammal mHuman = new Human();
메서드(함수)의 이름은 주소값을 나타냄
메서드는 명령어의 set이고 프로그램이 로드되면 메서드 영역(코드 영역)에 명령어 set이 위치
해당 메서드가 호출되면 명령어 set이 있는 주소를 찾아 명령어가 실행됨
이때 메서드에서 사용하는 변수들은 스택 메모리에 위치하게 됨
따라서 다른 인스턴스라도 같은 메서드의 코드는 같으므로 같은 메서드가 호출됨
인스턴스가 생성되면 변수는 힙 메모리에 따로 생성되지만, 메서드 명령어 set은 처음 한번만 로드 됨
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();
}
}
가상 메서드 테이블(vitual method table)에서 해당 메서드에 대한 address를 가지고 있음
재정의 된 경우는 재정의 된 메서드의 주소를 가리킴
하나의 코드가 여러 자료형으로 구현되어 실행되는 것
같은 코드에서 여러 다른 실행 결과가 나옴
정보은닉, 상속과 더불어 객체지향 프로그래밍의 가장 큰 특징 중 하나임
다형성을 잘 활용하면 유연하고 확장성있고, 유지보수가 편리한 프로그램을 만들수 있음
업캐스팅 된 클래스를 다시 원래의 타입으로 형변환
하위 클래스로의 형변환은 명시적으로 해야 함
Customer vc = new VIPCustomer(); //묵시적
VIPCustomer vCustomer = (VIPCustomer)vc; //명시적
원래 인스턴스의 형이 맞는지 여부를 체크하는 키워드
맞으면 true 아니면 false를 반환 함
public void testDownCasting(ArrayList<Animal> list) {
for(int i =0; i<list.size(); i++) {
Animal animal = list.get(i);
if ( animal instanceof Human) {
Human human = (Human)animal;
human.readBooks();
}
else if( animal instanceof Tiger) {
Tiger tiger = (Tiger)animal;
tiger.hunting();
}
else if( animal instanceof Eagle) {
Eagle eagle = (Eagle)animal;
eagle.flying();
}
else {
System.out.println("error");
}
}
}
추상 메서드나 구현 된 메서드를 활용하여 코드의 흐름(시나리오)를 정의하는 메서드
final로 선언하여 하위 클래스에서 재정의 할 수 없게 함
프레임워크에서 많이 사용되는 설계 패턴
추상 클래스로 선언된 상위 클래스에서 템플릿 메서드를 활용하여 전체적인 흐름을 정의하고 하위 클래스에서 다르게 구현되어야 하는 부분은 추상 메서드로 선언하여 하위 클래스에서 구현 하도록 함
public abstract class Car {
public abstract void drive();
public abstract void stop();
public void startCar() {
System.out.println("시동을 켭니다.");
}
public void turnOff() {
System.out.println("시동을 끕니다.");
}
final public void run() {
startCar();
drive();
stop();
turnOff();
}
}
final 변수 : 값이 변경될 수 없는 상수
final 메서드 : 하위 클래스에서 재정의 할 수 없는 메서드
final 클래스 : 상속할 수 없는 클래스