개체 간의 일반화(Generalization) 또는 특수화(Specialization) 관계를 나타냄
예시 : 강아지는 동물이다, 학생은 인간이다 ...
// 부모 클래스 (Super Class)
class Animal {
void eat() {
System.out.println("동물이 음식을 먹습니다.");
}
}
// 자식 클래스 (Sub Class)
class Dog extends Animal { // 상속 (is-a 관계)
void bark() {
System.out.println("멍멍!");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.eat(); // 부모 클래스의 메서드 사용
myDog.bark(); // 자식 클래스의 메서드 사용
}
}
has-a 관계
포함 관계또는구성관계를 표현할 때 사용
'A는 B를 가진다' 로 해석할 수 있는 관계
is-a 관계보다 덜 밀접하게 연관된 관계
일반적으로 is-a 관계보다 has-a 관계를 고려하는 것이 유연성과 유지 보수 측면에서 유리함
두 클래스가 독립적으로 존재할 때, 한 클래스 내에서 다른 클래스를 속성(변수)로 포함할 때 사용
개체 간의 객체 포함(Composition/Aggregation)을 나타냄
예시 : 자동차는 엔진을 가진다, 스마트폰은 배터리를 가진다 ...
// Engine 클래스 (독립적인 클래스)
class Engine {
void start() {
System.out.println("엔진이 시작됩니다.");
}
}
// Car 클래스는 Engine을 '가지고 있음' (Composition)
class Car {
private Engine engine; // Engine 객체를 포함 (has-a 관계)
// 생성자를 통해 Engine 객체를 주입
Car(Engine engine) {
this.engine = engine;
}
void startCar() {
System.out.println("자동차 시동을 겁니다.");
engine.start(); // 포함된 Engine 객체의 기능 사용
}
}
public class Main {
public static void main(String[] args) {
Engine myEngine = new Engine(); // Engine 객체 생성
Car myCar = new Car(myEngine); // Car 객체에 Engine 포함
myCar.startCar(); // Car의 기능 실행
}
}
extends 키워드를 사용 (예시 : class 자식클래스명 extends 부모클래스명)
instanceof 연산자로 상속 관계 확인 가능 (예시 : if(자식클래스명 instanceof 부모클래스명))
상속하는 부모 클래스 = Super Class (Base Class) : 기존 속성과 메서드를 정의한 클래스
상속받는 자식 클래스 = Sub Class (Derived Class) : 기존 속성과 메서드를 확장하여 새로운 기능을 추가하는 클래스
상속의 특징
is-a 관계임
Sub Class에서 Super Class에 없는 메서드나 인스턴스 변수를 추가할 수 있음
Sub Class에서 Super Class의 속성과 메서드를 사용, 변경, 확장하여 사용할 수 있음
Sub Class에서 Super Class의 Private, Static, Final 메서드 또는 변수에 대해서는 접근 불가할 수 있음
특히, Final class는 상속이 불가능하고 Final method는 오버라이딩이 불가능함
Super Class의 참조가 Sub Class가 되는 경우는 안전하지 않을 수 있음 (ILLEGAL)
Java에서 클래스의 다중 상속은 불가능하지만, 인터페이스(Interfaces)의 다중 상속은 가능함
Java의 많은 메서드들은 Object class에서 상속 받은 것임
// Super Class
class Animal {
String name;
void eat() {
System.out.println(name + " is eating food.");
}
}
// Sub Class (Animal 클래스를 상속)
class Dog extends Animal { // extends로 상속
void bark() {
System.out.println(name + " is barking.");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.name = "Poppi"; // Super Class의 필드 사용
myDog.eat(); // Super Class의 메서드 사용
myDog.bark(); // Sub Class의 메서드 사용
}
}
class Animal {
void makeSound() {
System.out.println("Animal makes sounds.");
}
}
class Dog extends Animal {
@Override // 어노테이션으로 명시
void makeSound() { // 부모 클래스의 메서드를 재정의
System.out.println("Bow-wow!");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal();
myAnimal.makeSound(); // "Animals make sounds."
Dog myDog = new Dog();
myDog.makeSound(); // "Bow-wow!" (오버라이딩된 메서드 실행)
}
}
업캐스팅 & 다운캐스팅 (Upcasting & Downcasting)
상속 관계에서 객체 간 타입 변환이 가능함
업캐스팅 (Upcasting)
자식 클래스→ 부모 클래스
컴파일러에서 자동으로 변환
부모 클래스 타입의 참조 변수로 자식 클래스의 객체를 참조
자식 클래스의 메서드를 부모 클래스 타입으로 호출 가능
하지만, 자식 클래스에만 있는 멤버(필드, 메서드)에는 접근 불가능
오버라이딩된 메서드는 자식 클래스의 메서드를 따름
다운캐스팅 (Downcasting)
부모 클래스→ 자식 클래스
부모 타입을 다시 자식 타입으로 변환
명시적 변환 필요 (자식 클래스명 괄호)
부모 클래스에서 선언되지 않은 자식의 멤버(필드, 메서드)에 접근 가능
잘못된 다운캐스팅은 ClassCastException 발생 가능
instanceof 연산자를 사용하여 변환이 안전한지 확인
class Animal {
void makeSound() {
System.out.println("Animal makes sounds.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bow-wow!");
}
void wagTail() {
System.out.println("Wagging the tail.");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog(); // 업캐스팅
myAnimal.makeSound(); // 출력 : Bow-wow!, 오버라이딩된 메서드 호출
if (myAnimal instanceof Dog) { // instanceof로 변환 안전 확인
Dog myDog = (Dog) myAnimal; // 다운캐스팅 (명시적)
myDog.wagTail(); // 출력 : Wagging the tail.
}
}
}
super, super()
super : 부모 클래스의 필드나 메서드를 호출할 때 사용
부모의 메서드를 자식 클래스에서 오버라이딩 후 호출 가능 (예시 : super.메서드())
super() : 자식 클래스에서 부모의 생성자를 호출할 때 사용
생성자는 기본적으로 상속되지 않으나, Sub Class의 생성자 안에 super(); 를 사용하여 가져올 수 있음
Sub Class에서는 반드시 super(); 를 이용한 생성자를 만들어야 하며, 생성하지 않으면 default 생성자가 생성됨
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void makeSound() {
System.out.println(name + " makes sounds.");
}
}
class Dog extends Animal {
Dog(String name) {
super(name); // super()로 부모 클래스의 생성자 호출
}
@Override
void makeSound() {
super.makeSound(); // super로 부모 클래스의 메서드 호출
System.out.println("Bow-wow!");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog("Poppi");
myDog.makeSound();
}
}
class Display {
Display() { // 생성자 생성
System.out.println("This is test text");
}
}
public class Main {
public static void main(String[] args) {
Display view = new Display(); // 출력
}
}
2. 매개 변수가 있는 생성자 / Parameterized Constructor
매개 변수를 사용하여 객체 생성 시 초기 값을 설정할 수 있도록 하는 생성자
class Display {
String text;
Display(String txt) { // 생성자 생성
text = txt;
System.out.println(text);
}
}
public class Main {
public static void main(String[] args) {
Display view = new Display("test message"); // 출력
}
}
class Display {
String text1, text2;
int number;
Display() {
System.out.println("This is test text");
}
Display(String txt) {
text1 = txt;
System.out.println(text1);
}
Display(String txt, int num) {
text2 = txt;
number = num;
System.out.println(text2 + number);
}
}
public class Main {
public static void main(String[] args) {
Display view1 = new Display(); // This is test text
Display view2 = new Display("message 1"); // message 1
Display view3 = new Display("message ", 2); // message 2
}
}
4. this()
하나의 생성자에서 다른 생성자를 호출할 때 this()를 사용하여 코드 중복을 줄일 수 있음
※ this와 this()는 다른 개념
this : 인스턴스 매서드의 매개 변수로 선언된 변수명이 인스턴스 변수명과 같을 때, 인스턴스 변수와 지역 변수를 구분하기 위해서 사용
this() : 같은 class의 다른 생성자를 호출
class Display {
String text;
int number;
Display() {
this("message ", 0); // this()로 매개 변수가 있는 다른 생성자 호출
}
Display(String text, int number) { // 인스턴스 변수명과 매개 변수명이 같음
this.text = text; // this로 구분
this.number = number;
}
void show() {
System.out.println(text + number);
}
}
public class Main {
public static void main(String[] args) {
Display view1 = new Display();
Display view2 = new Display("message ", 1);
view1.show(); // 출력 : message 0
view2.show(); // 출력 : message 1
}
}
모든 인스턴스(객체)에서 공유되어 새로운 객체 생성 없이 기존 클래스명으로 직접 접근 가능
공통 데이터 관리에 유리
class Test {
static int staticValue = 50; // 클래스 변수 선언
public void print() {
System.out.println(staticValue);
}
}
public class Main {
public static void main(String[] args) {
// Test test1 = new Test();
// 새로운 객체 생성 불필요
System.out.println(Test.staticValue); // 출력: 50
}
}
2. 인스턴스 변수 / Instance Variable
클래스 내부에 선언되지만, static이 없는 변수
객체가 생성될 때마다 Heap 영역에 개별적으로 할당됨
각 객체마다 독립적인 값을 가짐 (test1.instanceValue와 test2.instanceValue는 다른 값)
static으로 선언된 클래스 메서드에서 접근 시, 새로운 객체 생성 필요
class Test {
static int staticValue = 50; // 클래스 변수 선언
int instanceValue = 30; // 인스턴스 변수 선언
public void print() {
System.out.println(staticValue);
System.out.println(instanceValue); // 클래스 메서드(static) 이외에서는 직접 사용 가능
}
}
public class Main {
public static void main(String[] args) {
Test test1 = new Test();
Test test2 = new Test();
test1.instanceValue = test1.instanceValue + 10;
test2.instanceValue = test2.instanceValue - 10;
System.out.println(Test.staticValue); // 출력: 50
System.out.println(test1.instanceValue); // 출력: 40
System.out.println(test2.instanceValue); // 출력: 20
}
}
3. 로컬 변수 / Local Variable
메서드나 블록 내부에서 선언된 변수 메서드
해당 메서드가 실행될 때 Stack 영역에 생성되고,종료되면 사라짐
생성된 메서드에서만 접근 가능하며, 다른 메서드에서 접근할 수 없음
반드시 초기화 후 사용해야 함
class Test {
public void print() {
int number = 100; // 로컬 변수 생성
System.out.println(number + 50);
}
}
public class Main {
public static void main(String[] args) {
Test test1 = new Test();
test1.print(); // 출력: 150
// System.out.println(test1.number);
// 접근 불가
}
}
4. 매개 변수 / Parameter
메서드(함수)에서 입력 값을 전달받기 위해 사용하는 변수
메서드 호출 시 인자(Argument)를 받아서 내부에서 처리할 수 있음
Parameter는 선언한 변수, Argument는 전달되는 실제 값
메서드의 입력값을 동적으로 변경 가능
여러 개의 매개변수를 가질 수 있음
class Calculator {
public void Sum(int a, int b) { // 매개 변수 선언
System.out.println(a + b);
}
}
public class Main {
public static void main(String[] args) {
Calculator cal = new Calculator();
cal.Sum(4, 7); // 출력 : 11
}
}