상속(Inheritance)

- 기존의 클래스로 새로운 클래스를 작성하는 것(코드의 재사용)

- 두 클래스를 부모와 자식으로 관계를 맺어주는 것

 

class Parent { }
class Child extends Parent {
	// ...
}

 

- 자손은 조상의 모든 멤버를 상속받는다.(생성자, 초기화블럭은 상속이 되지 않는다)

- 자손의 멤버 개수는 조상보다 적을 수 없다.(같거나 많다)

- 자손의 변경은 조상에 영향을 미치지 않는다.

class Tv {
	boolean power;	// 전원상태(on/off)
	int channel;	// 채널
	
	void power()	{
		power = !power;
	}
	void channelUp()	{
		++channel;
	}
	void channelDown()	{
		--channel;
	}
}

class SmartTv extends Tv {	// SmartTv는 Tv에 캡션(자막)을 보여주는 기능을 추가
	boolean caption;		// 캡션상태 (on/off)
	void displayCaption(String text) {
		if (caption) {		// 캡션 상태가 on(true)일 때만 text를 보여준다.
			System.out.println(text);
		}
	}
}

public class Ex7_1 {

	public static void main(String[] args) {
		SmartTv stv = new SmartTv();
		stv.channel = 10;			// 조상 클래스로부터 상속받은 멤버
		stv.channelUp();			// 조상 클래스로부터 상속받은 멤버
		System.out.println(stv.channel);
		stv.displayCaption("Hello, World");
		stv.caption	= true;
		stv.displayCaption("Hello, World2");

	}

}

포함 관계

포함(composite)이란?

- 클래스의 멤버로 참조변수를 선언하는 것

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

class Car {
	Engine e =new Engine();	// 엔진
	Door[] d = new Door[4];		// 문, 문의 개수를 넷으로 가정하고 배열로 처리했다. 
	// ...
}

ex)

class MyPoint {
	int x,y;
}

//class Circle extends MyPoint {	// 상속
//	int r;
//}

class Circle {	// 포함
	MyPoint P = new MyPoint();	// 참조변수의 초기화
	int r;
}

public class InheritanceTest {

	public static void main(String[] args) {
		Circle c = new Circle();
		c.P.x = 1;
		c.P.y = 2;
		c.r = 3;
		System.out.println("c.P.x = "+c.P.x);
		System.out.println("c.P.y = "+c.P.y);
		System.out.println("c.r = "+c.r);
		
	}

}

클래스 간의 관계 결정하기

상속관계 ‘~~이다.(is-a)’

포함관계 ‘~~을 가지고 있다.(has-a)’

 

ex) (Circle)은 점(Point)이다. - Circle is a Point.

(Circle)은 점(Point)를 가지고 있다. - Circle has a Point.

 

두 번째 문장이 더 자연스러운 관계이므로 포함관계가 어울린다.

실제로 대부분의 경우(90%?)가 포함관계이다.

 

 

단일 상속(Single Inheritance)

- Java는 단일상속만을 허용한다(C++은 다중상속 허용)

- 비중이 높은 클래스 하나만 상속관계로, 나머지는 포함관계로 한다.

 

 

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

- 부모가 없는 클래스는 자동적으로 Object클래스를 상속받게 된다.

- 모든 클래스는 Object클래스에 정의된 11개의 메소드를 상속받는다.

  toString(), equals(Object obj), hashCode() 등등...

 

ex)

class MyPoint {
	int x,y;
}

//class Circle extends MyPoint {	// 상속
//	int r;
//}

class Circle {	// 포함
	MyPoint P = new MyPoint();	// 참조변수의 초기화
	int r;
}

public class InheritanceTest {

	public static void main(String[] args) {
		Circle c = new Circle();
		Circle c2 = new Circle();
		
		System.out.println(c.toString());
		System.out.println(c);
		
		
	}

}

메소드 오버라이딩(overriding)

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

내용만 변경 가능(구현부, { } ), 선언부 변경 불가

 

ex)

class MyPoint3 {
	int x,y;
	
	MyPoint3 (int x, int y) {
		this.x=x;
		this.y=y;
	}
	public String toString() {		// Object 클래스의 toString()을 오버라이딩
		return "x:" + x + ", y:" + y;
	}
}

public class OverrideTest {

	public static void main(String[] args) {
		MyPoint3 p = new MyPoint3(1,2);
		System.out.println(p);
		
//		MyPoint3 p = new MyPoint3();
//		p.x = 1;
//		p.y = 2;
//		System.out.println("p.x=" + p.x);
//		System.out.println("p.y=" + p.y);
	
	}

}

오버라이딩의 조건 (꼭 숙지)

1. 선언부(반환타입, 메소드이름, 매개변수 목록)가 조상 클래스의 메소드와 일치해야 한다.

2. 접근 제어자를 조상 클래스의 메소드보다 좁은 범위로 변경할 수 없다.

3. 예외는 조상 클래스의 메소드보다 많이 선언할 수 없다.

 

 

오버로딩 vs. 오버라이딩

오버로딩(overloading) 기존에 없는 새로운 메소드를 정의하는 것(new)

오버라이딩(overriding) 상속받은 메소드의 내용을 변경하는 것(change, modify)

class Parent {
	void parentMethod() {}
}
class Child extends Parent {
	void parentMethod() {}		//  오버라이딩
	void parentMethod(int i) {}	//  오버로딩

	void childMethod() {}		// 메소드 정의
	void childMethod(int i) {}	// 오버로딩
	void childMethod() {}		// 중복정의, 에러
}

참조변수 super

- 객체 자신을 가리키는 참조변수. 인스턴스 메소드(생성자) 내에서만 존재

- 조상의 멤버를 자신의 멤버와 구별할 때 사용

 
class Parent {
	int x = 10; // super.x
}

class Child extends Parent {
	int x = 20; // this.x
	
	void method() {
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x=" + super.x);
	}
}

public class Ex7_2 {

	public static void main(String[] args) {
		Child c = new Child();
		c.method();

	}

}

결과

x=20

this.x=20

super.x=10

 

ex) this와 super 둘다 해당되는 경우

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);
	}
}
public class Ex7_3 {

	public static void main(String[] args) {
		Child2 c = new Child2();
		c.method();

	}

}

결과

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();를 삽입한다.

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

class Point3D extends Point {
	int z;
	
	Point3D(int x, int y, int z) {
		super(x, y);
		this.z=z;
	}
	
	String getLocation() {
		return "x : " + x + ", y : " + y + ", z : " + z;
	}
}
public class PointTest {

	public static void main(String[] args) {
		Point3D p3 = new Point3D(1,2,3);
		System.out.println(p3.getLocation());

	}

}

 

패키지(package)

- 서로 관련된 클래스의 묶음

- 클래스는 클래스 파일(*.class), 패키지는 폴더. 하위 패키지는 하위 폴더

- 클래스의 실제 이름(full name)은 패키지를 포함한다.(java.lang.String)

 

패키지의 선언

- 패키지는 소스파일의 첫 번째 문장으로 단 한번 선언

- 같은 소스 파일의 클래스들은 모두 같은 패키지에 속하게 된다.

- 패키지 선언이 없으면 이름없는(unnamed) 패키지에 속하게 된다.

package com.codechobo.book;

public class PackageTest {
	public static void main(String[] args) {
		System.out.println(“Hello, world!”);
	}
}

class PackageTest2 {}

클래스 패스(classpath)

- 클래스 파일(*.class)의 위치를 알려주는 경로(path)

- 환경변수 classpath로 관리하며, 경로간의 구분자는 ‘;’를 사용

classpath(환경변수)에 패키지의 루트를 등록해줘야 함.

 

import

(이클립스 단축기 ctrl + shift + o 로 자동으로 import문 생성 가능)

- 클래스를 사용할 때 패키지 이름을 생략할 수 있다.

- 컴파일러에게 클래스가 속한 패키지를 알려준다.

- java.lang패키지의 클래스는 import 하지 않고도 사용할 수 있다.

ex) String, Object, System, Thread ...

 

import문의 선언

- import문을 선언하는 방법은 다음과 같다.

import 패키지명.클래스명; 또는 import 패키지명.*;

 

- import문은 패키지문과 클래스선언의 사이에 선언한다.

- import문은 컴파일 시에 처리되므로 프로그램의 성능에 영향없음.

- 이름이 같은 클래스가 속한 두 패키지를 import 할 때는 클래스 앞에 패키지명을 붙여줘야 한다.

import java.sql.*;
import java.util.*;

public class ImportTest {
	public static void main(string[] args) {
		java.util.Date today = new java.util.Date();
	}
}

static import

- static멤버를 사용할 때 클래스 이름을 생략할 수 있게 해준다.

 

import static java.lang.Integer.*;	// Integer클래스의 모든 static메소드
import static java.lang.Math.random;	// Math.random()만. 괄호 안 붙임.
import static java.lang.System.out;	// System.out을 out만으로 참조가능

ex)

import static java.lang.System.out;
import static java.lang.Math.*;		

public class Ex7_6 {

	public static void main(String[] args) {
//		System.out.println(Math.random());
		out.println(random());

//		System.out.println("Math.PI :" + Math.PI);
		out.println("Math.PI :" + PI);
	}

}
 

제어자(modifier)

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

접근 제어자 public, protected, (default), private

그 외 static, final, abstract, native, transient, synchronized, volatile, strictfp

 

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

public class ModifierTest {
	public static final int WIDTH = 200;

	public static void main(String args) {
		System.out.println(“WIDTH=”+WIDTH);
	}
}

static 클래스의, 공통적인

final 마지막의, 변경될 수 없는

final class FinalTest {				// 조상이 될 수 없는 클래스
	final int MAX_SIZE = 10;		// 값을 변경할 수 없는 멤버변수(상수)
	
	final void getMaxSize() {			// 오버라이딩 할 수 없는 메소드(변경불가)
		final int LV = MAX_SIZE;	// 값을 변경할 수 없는 지역변수(상수)
		return MAX_SIZE;

abstract 추상의, 미완성의

abstract class AbstractTest {	// 추상 클래스(추상 메소드를 포함한 클래스)
	abstract void move();	// 추상 메소드(구현부가 없는 메소드)
}

미완성 클래스(미완성 설계도)이므로 인스턴스 생성 불가(제품 생성 불가)

추상 클래스를 상속 받아서 완전한 클래스를 만든 후에 객체 생성 가능

 

 

접근 제어자(access modifier)

private 같은 클래스 내에서만 접근이 가능하다.

(default) 같은 패키지 내에서만 접근이 가능하다.

protected 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.

public 접근 제한이 전혀 없다.

 

4개중 단 하나만 사용 가능.

class 앞에 붙을 수 있는건 public(default) 뿐이고 멤버에는 4가지가 다 붙을 수 있다.

 

ex)

package pkg1;

public class MyParent {
	private int prv;	// 같은 클래스
	int dft;			// 같은 패키지
	protected int prt;	// 같은 패키지+자손
	public int pub;		// 접근 제한 없음.

	public void printMembers() {
		System.out.println(prv);	// OK
		System.out.println(dft);	// OK
		System.out.println(prt);	// OK
		System.out.println(pub);	// OK
	}

	public static void main(String[] args) {
		MyParent p = new MyParent();
//		System.out.println(p.prv);	// 에러, private는 같은 클래스 내에서만 사용 가능
		System.out.println(p.dft);	// OK
		System.out.println(p.prt);	// OK
		System.out.println(p.pub);	// OK

	}
}
package pkg2;

import pkg1.MyParent;

// ctrl + shift + o 눌러서 import를 해줘야 한다.


class MyChild extends MyParent {
	
	public void printMembers() {
//		System.out.println(prv);	// 에러
//		System.out.println(dft);	// 에러
		System.out.println(prt);	// OK, 다른패키지의 자손 클래스에서 접근 가능
		System.out.println(pub);	// OK
	}

}

public class MyParentTest2 {
	
	public static void main(String[] args) {
		MyParent p = new MyParent();
//		System.out.println(p.prv);	// 에러
//		System.out.println(p.dft);	// 에러		다른 패키지의 다른 클래스라서 3개 에러
//		System.out.println(p.prt);	// 에러 		
		System.out.println(p.pub);	// OK
		
	}

}

 

캡슐화와 접근 제어자

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

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

- 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서

public class Time {
	private int hour;
	private int minute;	// 접근제어자를 private로 하여 외부에서 직접 접근하지 못하도록 한다. 메소드를 통하여 간접접근 허용
	private int second;

	// 메소드는 public으로 하여 private에 간접접근 할 수 있게 한다.
	public int getHour() {
		return hour;
		}
	public void setHour(int hour) {
		if (hour < 0 || hour > 23) return;	// 값을 보호
		this.hour = hour;
	}
 

ex)

class Time {
	private int hour;	// 0~23 사이의 값을 가져야 함.
	private int minute;
	private int second;
	
	public void setHour(int hour) {
		if(isNotValidHour(hour)) return;
		
		this.hour = hour;
	}
	
	// 매개변수로 넘겨진 hour가 유효한지 확인해서 알려주는 메소드, alt + shift + m 으로 추출했다.
	private boolean isNotValidHour(int hour) {	// 클래스 내부에서만 쓰는 메소드는 private로 접근범위를 줄이는게 좋다.
		return hour < 0 || hour > 23;
	}
	
	public int getHour() {
		return hour;
	}
}

public class TimeTest {

	public static void main(String[] args) {
		Time t = new Time();
		t.setHour(21);	// hour의 값을 21로 변경
		System.out.println(t.getHour());
		t.setHour(100);
		System.out.println(t.getHour());
		
	}

}

 

다형성 (polymorphism)

- 여러 가지 형태를 가질 수 있는 능력

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

Tv t = new SmartTv(); // 조상 타입 참조변수 t로 자손 타입 객체 SmartTv를 다룬다.

 

- 객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이?

SmartTv s = new SmartTv();	// 모든 멤버 사용 가능
Tv t = new SmartTv();		// Tv가 가지고 있는 멤버만 사용 가능

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

Tv t = new SmartTv(); // Ok. 허용
SmartTv s = new Tv(); // 에러, 허용 안 됨.

좌측은 가능하지만, 우측은 불가능하다

참조변수의 형변환

- 사용할 수 있는 멤버의 개수를 조절하는 것

- 조상-자손 관계의 참조변수는 서로 형변환 가능

class Car {
	String color;
	int door;
	
	void drive() {	// 운전하는 기능
		System.out.println("drive, Brrrr~");
	}
	
	void stop() {	// 멈추는 기능
		System.out.println("stop!!!");
	}
}

class FireEngine extends Car {	// 소방차
	void water() {	// 물을 뿌리는 기능
		System.out.println("water!!!");
	}
}
class Ambulance extends Car {
	
}

public class Pr {

	public static void main(String[] args) {
		FireEngine f = new FireEngine();
		
		Car c = (Car)f;					// OK. 조상인 Car타입으로 형변환
		FireEngine f2 = (FireEngine)c;	// OK. 자손인 FireEngine타입으로 형변환
//		Ambulance a = (Ambulance)f;		// 에러, 상속관계가 아닌 클래스 간의 형변환 불가

	}

}

cCar에 속한 멤버 4개만 사용 가능

f2는 다시 멤버 5개 사용 가능

 

 

class Car {
	String color;
	int door;
	
	void drive() {	// 운전하는 기능
		System.out.println("drive, Brrrr~");
	}
	
	void stop() {	// 멈추는 기능
		System.out.println("stop!!!");
	}
}

class FireEngine extends Car {	// 소방차
	void water() {	// 물을 뿌리는 기능
		System.out.println("water!!!");
	}
}
public class Ex7_7 {

	public static void main(String[] args) {
		Car car = null;
		FireEngine fe = new FireEngine();
		FireEngine fe2 = null;
		
		fe.water();
		car = (Car)fe;
//		car.water();	// 에러, Car타입의 참조변수로는 water()를 호출할 수 없다.
		fe2 = (FireEngine)car;
		fe2.water();
		
	}

}

 


출처 : 남궁성의 정석코딩 Youtube

+ Recent posts