예비 개발자(학원)

자바[8] 상속

Im늘품 2024. 4. 1. 10:18

1. 상속

상속: 코드의 재사용성을 위해 사용한다.

  • 자바는 다중상속을 허용하지 않는다.

상위(부모) 타입 객체가 먼저 생성되어야 하위(자식) 타입의 객체를 생성할 수 있음.
생성자 호출 순서: 상위클래스 생성자 -> 하위클래스 생성자 호출.
하위클래스의 생성자에서 상위클래스의 생성자를 명시적으로 호출하지 않으면, 
상위클래스의 "기본 생성자"가 자동으로 호출됨.
super(); 호출은 생략 가능.
하위클래스의 생성자에서 상위클래스의 생성자를 명시적으로 호출할 때는 super 키워드를 사용
상위클래스의 아규먼트를 갖는 생성자는 자동으로 호출되지 않음.
상위클래스의 아규먼트를 갖는 생성자를 호출하려면 반드시 super(...);를 호출해야만 함.
생성자 코드 안에서 super(...) 호출은 가장 먼저 작성되어야 함.
  
this의 의미:
(1) 생성된 객체(인스턴스)의 참조값: this.field, this.method(...)
(2) 오버로딩(overloading)된 생성자: this(), this(...)
  
super의 의미:
(1) 생성된 상위 타입 객체의 참조값: super.field, super.methid(...)
(2) 상위클래스의 생성자: super(), super(...)

 

<BasicTv.java>

// extends Object는 생략 가능.
public class BasicTv {
	// 상수 정의:
	public static final int MIN_CHANNEL = 0;
	public static final int MAX_CHANNEL = 2;
	public static final int MIN_VOLUME = 0;
	public static final int MAX_VOLUME = 2;

	// 필드
	private boolean powerOn; // 전원 상태(true->ON, false->OFF)
	private int channel; // 현재 채널 번호
	private int volume; // 음량
	
	// 생성자
	public BasicTv() {
	}

	public BasicTv(boolean powerOn, int channel, int voulme) {
		this.powerOn = powerOn;
		this.channel = channel;
		this.volume = voulme;
	}

	// getters and setters
	public boolean isPowerOn() {
		return powerOn;
	}

	public void setPowerOn(boolean powerOn) {
		this.powerOn = powerOn;
	}

	public int getChannel() {
		return channel;
	}

	public void setChannel(int channel) {
		this.channel = channel;
	}

	public int getVolume() {
		return volume;
	}

	public void setVolume(int volume) {
		this.volume = volume;
	}

	// 메서드
	/**
	 * TV가 켜져 있으면 끄고, TV가 켜져 있으면 켬. powerOn의 값이 true이면 false 변경, powerOn의 값이 false이면
	 * true로 변경.
	 * 
	 * @return powerOn의 값.
	 */
	public boolean powerOnOff() {
		if (powerOn) {
			powerOn = false;
			System.out.println("TV OFF");
		} else {
			powerOn = true;
			System.out.println("TV ON");
		}
		return powerOn;
	}

	/**
	 * ChannelUp. 현재 채널의 값을 1 증가. 만약 현재 채널 값이 채널의 최댓값인 경우(MAX_CHANNEL), 채널의
	 * 최솟값(MIN_CHANNEL)으로 변경.
	 * 
	 * @return 바뀐 현재 채널 값.
	 */
	public int channelUp() {
		if (powerOn) { // TV가 켜져 있을 때만
			if (channel < MAX_CHANNEL) {
				channel++;
			} else {
				channel = MIN_CHANNEL;
			}
			System.out.println("CH: " + channel);
		}
		return channel;
	}

	/**
	 * ChannelUp. 현재 채널의 값을 1 감소. 만약 현재 채널 값이 채널의 최솟값인 경우(Min_CHANNEL), 채널의
	 * 최댓값(MAX_CHANNEL)으로 변경.
	 * 
	 * @return 바뀐 현재 채널 값.
	 */
	public int channelDown() {
		if (powerOn) {
			if (channel > MIN_CHANNEL) {
				channel--;
			} else {
				channel = MAX_CHANNEL;
			}
			System.out.println("CH: " + channel);
		}
		return channel;
	}

	/**
	 * voulmeUp. 현재 음량의 값을 1 증가. 만약 현재 음량 값이 채널의 최댓값인 경우(MAX_VOLUME), 음량의
	 * 최솟값(MIN_VOLUME)으로 변경.
	 * 
	 * @return 바뀐 현재 음량 값.
	 */
	public int volumeUp() {
		if (powerOn) {
			if (volume < MAX_CHANNEL) {
				volume++;
			} 
			System.out.println("VOL: " + volume);
		}
		return volume;
	}

	/**
	 * voulmeDown. 현재 음량의 값을 1 감소. 만약 현재 음량 값이 음량의 최솟값인 경우(MIN_VOLUME), 음량의
	 * 최댓값(MAX_VOLUME)으로 변경.
	 * 
	 * @return 바뀐 현재 음량 값.
	 */
	public int volumeDown() {
		if (powerOn) {
			if (volume > MIN_VOLUME) {
				volume--;
			} 
			System.out.println("VOL: " + volume);
		}
		return volume;
	}
}

 

<SmartTv.java>

// 스마트TV은 기본TV를 확장(상속)한다.
// 상위(super), 부모(parent), 기본(base) 클래스 - BasicTv
// 하위(sub), 자식(child), 유도(derived) 클래스 - SmartTv
// 모든 클래스의 최상위 클래스는 java.lang.Object 클래스.
// 상위클래스가 java.lang.Object인 경우 extends Object는 생략 가능.
public class SmartTv extends BasicTv{
	
	private String ip;
	
	public void webBrowsung() {
		System.out.println("인터넷 연결");
	}

	public void checkChannel() {
		System.out.println(getChannel());
	} // 상위 클래스 private 객체는 하위 클래스라도 접근하지 못한다. 다만 getter를 사용하면 가능.
}

 

<InheritanceMain01.java>

public class InheritanceMain01 {

	public static void main(String[] args) {
		// BasicTv 타입 객체 생성
		BasicTv tv1 = new BasicTv();
		tv1.powerOnOff();
		

		tv1.channelUp();
		tv1.channelUp();
		tv1.channelDown();
		tv1.channelDown();
		tv1.channelDown();
		tv1.volumeUp();
		tv1.volumeDown();
		tv1.volumeDown();
		
		tv1.powerOnOff();
		
		// SmartTv 타입 객체 생성
		SmartTv tv2 = new SmartTv();
		tv2.powerOnOff(); // 상위클래스에서 상속받은 메서드를 호출.
		tv2.webBrowsung();
	}
}

TV ON

CH: 1

CH: 2

CH: 1

CH: 0

CH: 2

VOL: 1

VOL: 0

VOL: 0

TV OFF

TV ON

인터넷 연결


 

<Person.java>

public class Person {
	// 필드
	private String name;
	
	// 생성자
	public Person() {
		System.out.println("Person 생성자");
	}
	
	public Person(String name) {
		this.name = name;
		System.out.println("Person(name) 생성자");
	}
	
	// 메서드
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
}

 

<BusinessPerson.java>

// 회사원은 사람이다. (BusinessPerson IS A Person.)
public class BusinessPerson extends Person {
	// 필드
	private String company;
	
	// 생성자
	
	public BusinessPerson() {
//		super(); // 생략 가능
		System.out.println("BusinessPerson 생성자");
	}
	
	public BusinessPerson(String company) {
//		super(); 
		this.company = company;
		System.out.println("BusinessPerson(company) 생성자");
	}
	
	public BusinessPerson(String name, String company) {
//		super(name); // 상위클래스의 아규먼트가 1개인 생성자를 "명시적"으로 호출
		this.company = company;
		System.out.println("BusinessPerson(name, company) 생성자");
	}
	
	// 메서드
	public String getCopmany() {
		return this.company;
	}
	
	public void getCompany(String company) {
		this.company = company;
	}
}

- super는 부모객체, super();는 부모가 가지고 있는 생성자를 호출하겠다라는 의미

 

<InheritanceMain02.java>

public class InheritanceMain02 {

	public static void main(String[] args) {
		// BusinessPerson 타입 객체 생성:
		BusinessPerson person1 = new BusinessPerson();
		System.out.println(person1.getName() + " : " + person1.getCopmany());
		
		BusinessPerson person2 = new BusinessPerson("아이티윌");
		System.out.println(person2.getName() + " : " + person2.getCopmany());
		
		BusinessPerson person3 = new BusinessPerson("오쌤", "아이티윌");
		System.out.println(person3.getName() + " : " + person3.getCopmany());
	}

}

BusinessPerson 생성자

null : null

Person 생성자

BusinessPerson(company) 생성자

null : 아이티윌

Person 생성자

BusinessPerson(name, company) 생성자

null : 아이티윌

 

 

2. 다형성

하나의 인터페이스나 부모 클래스에서 파생된 여러 자식 클래스가 같은 메서드 호출에 대해 서로 다른 방식으로 동작할 수 있게 하는것.

다형성을 구현 하려면 메소드 재정의와 타입 변환이 필요합니다.

 

다형성의 장점: 코드의 재사용성(re-usability)을 높여줌.
(예) 배열 선언, 파라미터 선언 등에서 상위 타입으로 선언한 변수에 하위 타입 객체들을 할당할 수 있기 때문에 코드 재사용성이 높아짐.

 

메서드/생성자 오버로딩(overloading): 파라미터가 다른 같은 이름의 메서드/생성자를 여러개 선언(정의).
메서드 오버라이딩(overriding): 상위 타입에서 선언된 메서드를 하위 타입에서 재정의 하는 것. 리턴 타이, 메서드 이름, 파라미터
선언이 모드 같아야 함. 접근 수식어는 상위 타입의 가시성 범위와 같거나 더 넓어질 수 있음.

 

<car.java>

// 하이브리드 자동차는 자동차이다.
// 자동차 - 상위 타입, 하이브리드 자동차 - 하위 타입
// class HybridCar extends Car
public class Car {
	//필드
	private int fuel; // 연료
	private int speed; // 속력
	
	// 생성자
	public Car(int fuel, int speed) {
		this.fuel = fuel;
		this.speed = speed;
	}

	// getter & setter
	
	public int getFuel() {
		return fuel;
	}

	public void setFuel(int fuel) {
		this.fuel = fuel;
	}

	public int getSpeed() {
		return speed;
	}

	public void setSpeed(int speed) {
		this.speed = speed;
	}

	// 메서드
	public void drive() {
		System.out.println("자동차 운전중(speed: " + speed +", 연료: " + fuel + ")");
	}
}

 

<HybridCar.java>

public class HybridCar extends Car {
	// 필드
	private int battery;

	// 생성자
	public HybridCar(int fuel, int speed, int battery) {
		// 상위 타입(Car)에 기본 생성자가 없기 때문에
		// 반드시 "명시적으로(explicitly)" 아규먼트를 갖는 상위클래스 생성자를 호출해야 함.
		super(fuel, speed);
		this.battery = battery;
	}

	// 메서드
	public void charge(int battery) {
		this.battery = battery;
		System.out.println("충전중: battery= " + battery);
	}

	@Override // 애너테이션(annotation) - 자바 컴파일러가 사용.(범위는 메서드 1개)
	public void drive() {
		// super.drive(); // -> 상위 객체의 재정의 하기 전의 메서드 호출
		System.out.println("하이브리드자동차 운전중( 속력: " + getSpeed() 
		+ ", 연료: " + getFuel() 
		+ ", 배터리: " + battery 
		+ ")");
	}
}

 

<InheritanceMain03.java>

package com.itwill.inheritance03;

public class InheritanceMain03 {

	public static void main(String[] args) {
		// Car 타입의 객체를 생성
		Car car1 = new Car(50, 30);
		car1.drive();
		
		// HybridCar 타입의 객체를 생성:
		HybridCar car2 = new HybridCar(25, 30, 0);
		car2.drive();
		car2.charge(100);

		// 다형성(polymorphism)을 사용한 변수 선언과 초기화:
		// SuperType var = new SubType();
		Car car3 = new HybridCar(50, 40, 100);
		car3.drive(); //-> 다형성으로 선언되어 있더라도, override되어 있는 메서드가 호출됨!
		// car3.charge (100); //-> casting을 하지 않으면 SubType의(실제 객체의) 모든 메서드를 사용할 수 없다.
		((HybridCar)car3).charge(100); //-> casting을 하면, SubType의 모든 메서드를 사용할 수 있다.
		
		// SubType var = new SuperType(); -> 컴파일 오류
		// HybridCar car = new Car(100, 100);
		
		Car[] cars = new Car[3];
		cars[0] = car1; // new Car(...)
		cars[1] = car2; // new HybridCar(...)
		cars[2] = car3; // new HybridCar(...)
				
		for( Car c : cars) {
			test(c);
		}
	}
	
	public static void test(Car car) {
		// instanceof 연산자: 객체가 어떤 타입인 지를 반환하는 연산자
		// 변수 instanceof Type(Class): 변수가 클래스 타입이면 true, 그렇지 않으면 false.
		if (car instanceof HybridCar) {
			System.out.println("하이브리드 자동차 검사...");
		} else if(car instanceof Car) {
			System.out.println("자동차 검사...");
		}
	}
}

 

 

3. Final 클래스


final: 변경할 수 없다.
final 지역변수: 값을 초기화한 이후에 그 값을 변경할 수 없는(재할당할 수 없는) 지역 변수.
final 필드: 반드시 한 번은 명시적으로 초기화해야 하고, 이후에는 값을 변경할 수 없는 필드.
(1) final 필드를 선언과 동시에 초기화
(2) final 필드를 초기화할 수 있는 아규먼트를 갖는 생성자를 작성.

final 메서드: 변경할 수 없는 메서드. 재정의(override)할 수 없는 메서드.
final 클래스: 변경할 수 없는 클래스. 상속받을 수 없는 클래스.
(예) java.lang.System, java.lang.String, ...

 

<InheritanceMain04.java>

//class MyString extends String {} //-> final 클래스를 상속하는 새로운 클래스는 선언할 수 없음.

class C {}
class E extends C {}

final class D {}
//class F extends D {};

class A {
	public void test1() {
		System.out.println("test1");
	}
	
	public final void test2() {
		System.out.println("test2");
	}
}

class B extends A {
	@Override // 상위 클래스의 final이 아닌 메서드는 override 할 수 있음.
	public void test1() {
		System.out.println("B overrides test1");
	}
	
	// 상위클래스에서 final로 선언된 메서드는 하위클래스에서 override 할 수 없음!
//	public  void test2() {
//		System.out.println("...");
//	}
	
}

public class InheritanceMain04 {

	public static void main(final String[] args) {
	}
}

- final:

파라미터 앞에 final 이라고 쓰는 이유는 누군가가 값(객체)을 넘겨줬을때 아규먼트로 전달한 객체를 변경하지 못하게 하기 위해서이다.


 

Object

  • java.lang.Object 클래스: 자바의 최상위 클래스.
  • 자바의 모든 클래스는 Object 클래스를 확장(상속).
  • Object 클래스에서 public으로 공개된 메서드들은 모든 하위 타입에서 사용할 수 있음.
  • Object 클래스의 모든 메서드는 하위 클래스에서 재정의(override) 할 수 있음.
  • (예) toString, equals(), hashCode(), getClass(), ...
  • - toString(): 객체의 문자열 표현식을 리턴.  "패키지.클래스@주소" 형식의 문자열을 만들어서 리턴.
  • - equals(): 객체 동등 비교 메서드.
    힙(heap)에 생성된 객체가 같으면(객체의 주소가 같으면) true, 다르면 false를 리턴.
  • - hashCode(): 객체의 해시코드(정수) 값을 리턴.
    o. 객체의 주소값으로 해시코드를 만듦.
    o. 해시코드가 만족해야 하는 조건
      1. 같은 객체에서 hashCode() 여러번 호출하더라도 항상 같은 정수가 리턴되어야 함.(난수는 안됨)
      2. equal() 메서드의 결과가 true가 되는 두 객체의 해시코드 값은 같아야 함.
      3. 해시코드 값이 같은 두 객체에서 equals() 메서드의 결과가 항상 true가 될 필요는 없음.

<InheritanceMain05.java>

import java.util.Random;
import java.util.Scanner;

class T {
	@Override
	public String toString() {
		return "T 타입 인스턴스";
	}
}

public class InheritanceMain05 {

	public static void main(String[] args) {
		T t = new T();
		System.out.println(t);
		// System.out.println(Object x) 메서드는 객체의 문자열 표현식을 콘솔에 출력
		// 아규먼트가 null이 아닌 경우에는 x.toString()이 리턴하는 문자열을 콘솔에 출력.
		// 아규먼트가 null인 경우에는 콘솔에 "null"이라고 출력.
		System.out.println(t.toString());
		System.out.println(t.getClass());
		System.out.println(t.hashCode());
		
		T t1 = new T();
		T t2 = new T();
		T t3 = t1;
		System.out.println(t1.equals(t2));
		System.out.println(t1.equals(t3));
		// Object 클래스에서 상속받은 equals() 메서드:
		// 두 객체가 같은 지(true), 다른 지(false)를 반환하는 메서드.
		// Object에서는 실제로 생생된 객체가 같으면 true를 리턴, 그렇지 않으면 false 리턴.

		Object o = new Object();
		System.out.println(o);
		
		Random r = new Random();
		System.out.println(r); //-> Random 클래스는 toString() 메서드를 override 하지 않음.
		
		Scanner sc = new Scanner(System.in);
		System.out.println(sc); //-> Scanner 클래스는 toString() 메서드를 override 함.		
	}
}

 

<User.java>

import java.util.Objects;

public class User {
	// field
	private String id;
	private String password;
	
	// constructors
	public User() {}
	
	public User(String id, String password) {
		this.id = id;
		this.password = password;
	}

	// toString 재정의 - id와 password를 출력.
	@Override
	public String toString() {
		return "User(id= " + id + ", password= " + password + ")";
	}
	
	// hashCode 재정의 - id가 같은 User 객체는 같은 정수값이 리턴되도록.
	@Override
	public int hashCode() {
		return Objects.hash(id);
	}
	
	// equals 재정의 - id만 같으면 같은 객체, 그렇지 않으면 다른 객체.
	@Override
	public boolean equals(Object obj) {
		 boolean result = false;
		 
		 if (obj instanceof User && id != null) {
			 User other = (User) obj;
			 result = id.equals(other.id);
		 }
		 
		 return result;
		
//		이클립스가 만든 코드
//		if (this == obj)
//			return true;
//		if (obj == null)
//			return false;
//		if (getClass() != obj.getClass())
//			return false;
//		User other = (User) obj;
//		return Objects.equals(id, other.id);
	} 
}

 

<Point.java>

// 2차원 평면의 점의 좌표를 표현
public class Point {
	// field
	private int x;
	private int y;

	// constructor
	public Point() {
	}

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

	@Override // Object의 클래스에서 상속받은 toString() 재정의
	public String toString() {
		return "Point(x=" + x + ", y=" + y + ")";
	}

	@Override // Object에서 상속받은 equals를 재정의
	public boolean equals(Object obj) {
		boolean result = false;
		
		if (obj instanceof Point) {
			Point pt = (Point) obj;
			result = (this.x == pt.x) && (this.y == pt.y); 
		}
		
		return result;
	}
	
	@Override // Object 클래스의 hashCode() 메서드를 재정의
	public int hashCode() {
		return x + y;
	}
	
}

 

<Inheritance06.java>

public class InheritanceMain06 {

	public static void main(String[] args) {
		// 기본 생성자를 사용해서 Point 타입 객체 생성
		Point p1 = new Point();
		System.out.println(p1); // p1.toString()

		// 아규먼트를 갖는 생성자를 사용해서 Point 타입 객체 생성
		Point p2 = new Point(0, 0);
		System.out.println(p2); // p2.toString()

		System.out.println("비교 연산자 == 결과: " + (p1 == p2));
		System.out.println("equals() 메서드 결과: " + p1.equals(p2));
		// 객체의 동등비교에서는 비교 연산자(==, !=)를 사용하면 안됨!

		System.out.println("p1 hashCode = " + p1.hashCode());
		System.out.println("p2 hashCode = " + p2.hashCode());

		// String 객체의 동등(equals) 비교
		String s1 = new String("hello");
		String s2 = new String("hello");
		System.out.println(s1);
		System.out.println(s2);
		System.out.println("s1 == s2 결과: " + (s1 == s2));
		System.out.println("s1.equals(s2) 결과: " + s1.equals(s2));

		User user1 = new User("admin", "1234");
		User user2 = new User("admin", "abcd");
		User user3 = new User();

		System.out.println(user1);
		System.out.println(user2);
		System.out.println(user3);

		System.out.println("user1 == user2: " + (user1 == user2));
		System.out.println("user1.equals(user2): " + user1.equals(user2));
		System.out.println("user1.equals(user3): " + user1.equals(user3));
		System.out.println("user3.equals(user1): " + user3.equals(user1));

		System.out.println("user1 hash: " + user1.hashCode());
		System.out.println("user2 hash: " + user2.hashCode());
		System.out.println("user3 hash: " + user3.hashCode());
	}
}

Point(x=0, y=0)

Point(x=0, y=0)

비교 연산자 == 결과: false

equals() 메서드 결과: true

p1 hashCode = 0

p2 hashCode = 0

hello

hello

s1 == s2 결과: false

s1.equals(s2) 결과: true

User(id= admin, password= 1234)

User(id= admin, password= abcd)

User(id= null, password= null)

user1 == user2: false

user1.equals(user2): true

user1.equals(user3): false

user3.equals(user1): false

user1 hash: 92668782

user2 hash: 92668782

user3 hash: 31

4. 추상 클래스

추상 메서드(abstract method):
- 메서드의 signature(수식어, 리턴 타입, 메서드 이름, 파라미터 선언)
- 메서드의 body(몸통)가 구현되어 있지 않은 메서드.
- 추상 메서드는 반드시 "abstract" 키워드로 수식해야 함.
- 추상 메서드 선언은 반드시 세미콜론(;)으로 끝내야 함.

추상 클래스(abstract class):
- "abstract" 수식어가 사용된 클래스.
- 대부분의 경우 추상 클래스는 추상 메서드를 가지고 있는 경우가 많음.
- 클래스가 추상 메서드를 가지고 있는 경우에는 반드시 abstract로 선언해야 함.
- 추상 클래스는 객체를 생성할 수 없음.
- 추상 클래스를 상속하는 클래스를 선언하고 모든 추상 메서드를 override한 이후에 객체를 생성할 수 있음.

 

<InheritanceMain07.java>

package com.itwill.inheritance07;

// 추상 클래스
abstract class Animal {
	public abstract void move(); // 추상 메서드
}

class Dog extends Animal {
	@Override
	public void move() {
		System.out.println("강아지 총총총...");
	}
}

class Fish extends Animal {
	@Override
	public void move() {
	System.out.println("물고기는 스윔스윔...");
	}
}

class Bird extends Animal {
	@Override
	public void move() {
		System.out.println("새는 훨훨~~~");
	}
}

public class InheritanceMain07 {

	public static void main(String[] args) {
		Animal[] animals = {
				new Dog(),
				new Fish(),
				new Bird(),
//				new Animal() 
		};
		
		for (Animal a : animals) {
			a.move();
		}
	}
}

강아지 총총총...

물고기는 스윔스윔...

새는 훨훨~~~