Java 상속, 생성자, super, this

상속(Inheritance)

기존 클래스(= 부모 클래스,Super Class, Parent)의 데이터(변수)와 메소드를 물려받아서 자식 클래스( =Sub Class, Child)는 중복적인 코드를 줄이고, 하나의 변수 타입으로 여러 종류의 인스턴스를 의미하는 추상화된 방식의 프로그램이 가능하게 한다. 자식은 부모를 extends로 선택한다.

  • is a 관계다. ~는 ~다 처럼. Ex) 노트북은 컴퓨터다
  • 자식 클래스 = 부모의 모든 메소드 + (자신의 고유 기능)
  • 선언하는 방법 : class 자식 클래스 이름 Extends [부모 클래스 이름]
  • 다중상속(자식 클래스가 하나인데 부모 클래스 여러 개를 상속받는 것)은 Java에서 허용하지 않는다. 단일 상속만 허용한다. 즉 부모클래스는 반드시 1개
    • java에서 단일 상속만 허용하는 이유는 부모 클래스에서 메소드 이름이 똑같을 때 어떤 메소드를 상속할 지 어렵기 때문이다.

상속했던 자식클래스를 부모클래스처럼 다른 클래스로 다시 상속할 수 있다. 마찬가지로 기능을 물려받으며 부모클래스는 자식클래스에 있는 메소드를 쓰지 못하기 때문에 집합관계라고 생각하면 쉽다. 즉, 자식 클래스는 자기 자신의 기능을 포함한 그 위 부모 클래스의 모든 기능을 쓸 수 있다. 그러나 부모 클래스는 자식 클래스의 기능을 쓸 수 없다.

코드의 재사용이 가능해 개발시간이 단축되지만 결합도가 높아져서 결국 의존성이 높아진다. 부모가 변경됐을 때 자식도 변경되야 해서 유지보수가 어려워진다.

클래스 상속의 종류

  • Concrete Class(일반 클래스) 상속
    • 평범한 클래스 상속
    • 메소드들이 다 완성된 것
    • 선택적으로 오버라이딩이 가능하다.
  • Abstract Class 상속 - 추상 클래스에서 다룸
  • 인터페이스 상속 - 인터페이스에서 다룸

instanceof 연산자

  • 상속 관계일 때 사용 가능
  • 자녀객체 instanceof 부모타입 == true
  • 부모객체 instanceof 자녀타입 == false
  • 객체 instanceof 객체타입 == true

생성자

데이터를 중심으로 두고 클래스를 설계하게 되면 가끔은 인스턴스가 반드시 특정 데이터를 가져야 하는 상황이 발생한다. 데이터를 보관하기 위해서 인스턴스를 만들 때 반드시 어떤 필수적인 데이터가 있어야만 하는 인스턴스로 만들고 싶은 경우에 생성자라는 문법을 이용한다. 즉, 인스턴스가 생성되자마자 어떤 기능을 수행하게 할 수 있게 하도록 한다.

  • 생성자는 클래스의 인스턴스화를 통해 인스턴스가 생성될 때 그 인스턴스를 ‘인스턴스화’하는 역할을 맡는다.
  • new 연산자로 인스턴스화를 진행하는데 만약 그 클래스가 부모 클래스가 있다면 부모클래스부터 먼저 메모리에 할당하고 부모클래스의 생성자를 먼저 호출한다.

기본 생성자(파라미터가 없는 생성자)

  • 역컴파일러가 가능한 javap 명령어로 컴파일된 .class파일을 해보면 아무것도 선언하지 않는 클래스라도 자동으로 Object에 상속되고, public 클래스이름(); 이라는 코드가 생긴다. 바로 여기서 public 클래스이름(); 이 기본 생성자다. 클래스에 생성자가 하나도 정의되어있지 않으면 컴파일러는 자동적으로 기본 생성자를 추가하여 컴파일한다.
  • 그래서 사실상 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다
  • 또한 기본 생성자는 Return 타입에 대한 언급이 없다.
//기본 생성자
public class Car{
	public Car(){
	}
}

사용자 정의 생성자(파라미터가 있는 생성자)

  • 프로그래머가 직접 클래스를 만들 때 생성자라는 걸 정의할 수도 있다
  • 메소드처럼 선언하지만 리턴 타입이 없고 메소드의 이름이 클래스의 이름과 같다.
  • 사용자 정의 생성자를 사용하면 컴파일러는 기본 생성자를 자동으로 생성하지 않는다.
    • 그래서 따로 기본 생성자를 만들어 주는 게 좋다.
  • 인스턴스 생성 시 필수적인 데이터가 있도록 제약하는 것(강제성)이 사용자 정의 생성자다. 이를 인스턴스를 생성하는 옵션이라고 보면 편하다. 왜냐면 인스턴스를 생성할 때 해당하는 파라미터를 써야 인스턴스가 생성되니까.
public class Car{
	String name;
	String color;
	int cc;
	
	//생성자 생성.
	public Car(String n, String, c, int ccc){
		name = n;
		color = c;
		cc = ccc;
	}
}
//값대로 호출할 때.
Car myCar = new Car("audi", "black", 300);
  • 사용자 정의 생성자를 여러 개 만들면?
    • 여러 개를 만들 수도 있다
    • 생성자 함수를 여러 개 작성할 때 중복적인 코드를 없애고 싶을 때는 this(name, 1); 으로 다른 생성자를 호출할 수 있다. 파라미터를 두 개 받는 생성자를 찾아서 실행하라는 의미다.
public class Car{
	private String name;
	private String color;
	
	//생성자 생성.
	public Car(String name){
		this.name = name;
		this.color = "yellow";
	}

	public Car(String name){
		//또 다른 생성자를 호출할 때 사용한다. 
		this(name,"yellow"); // 아래에 있는 매개변수가 2개인 메소드를 호출한다.
	}
	

	public Car(String name, String color){
		this.name = name;
		this.color = color;
	}
}
  • 부모 클래스에 파라미터가 있는 생성자가 있고 자식에는 없다면?
    • 그 파라미터가 있는 생성자를 따라서 만들어줘야 한다.
  • 부모 클래스에 파라미터가 있는 생성자가 있고 자식에도 파라미터가 있는 생성자가 있다면?
    • 부모 클래스에 기본 생성자(public 부모클래스(){})가 없으니 따로 기본생성자를 생성해주거나 자식 생성자에서 super();를 이용해서 부모 생성자를 호출하도록 해줘야 한다.
      • super();를 쓸 때 중요한 것이 있는데 자식 클래스의 그 생성자에 쓰는 super();를 가장 먼저 써야 한다. 왜냐면 부모 클래스가 인스턴스화 되기 전에 자식 클래스가 먼저 초기화되면 안되니까.

Super

  • 현재 수행중인 객체의 부모 객체의 reference를 갖고 있다.
  • 부모클래스, 부모 메소드(자식에 똑같은 이름의 메소드)를 호출하거나 이용할 때 사용하는 키워드
  • Override를 하면 자식 클래스는 부모 클래스의 원래 메소드를 잃어버리는 단점을 가지게 됩니다. 재정의하면서 기존 부모 클래스의 메소드를 호출할 수 없게 되지만 super();를 이용하면 부모 클래스의 메소드를 호출하는 것이 가능해진다. Ex) super.makeZaZang();
  • 또는 위에서 말한대로 부모 클래스에서 생성자를 만들면 자식도 생성자를 만들어야 하는가?의 질문에서 super();를 쓸 수 있다
    • 생성자를 똑같이 해준 후, 사용자 정의 생성자 함수에 super();를 넣어주면 컴파일러 오류가 나지 않는다.
  • super();의 의미는 부모클래스의 기본 생성자(public 부모클래스이름(){};)를 실행하라는 말과 같다.
  • super.name = name;과 같이 변수도 바꿔줄 수 있지만 응집도가 떨어져서 잘 쓰지 않는다.

This

  • 현재 코드를 실행하는 현재 객체 자신에 대한 참조값(자신을 가리킴)을 갖는다.
  • 메소드 내에서만 사용된다.
  • static 메소드에서 사용할 수 없다. 왜냐면 new로 인해 인스턴스가 생기기 이전에 메모리에 로드되기 때문이다.
  • 생성자 메소드 내에서 자기 자신의 또 다른 생성자를 호출할 때 this();를 이용해 쉽게 호출할 수 있다. this();안에 있는 매개변수를 통해 생성자를 구분한다.