💡 Java 11 설치하고 통합개발환경도구로는 intelliJ 또는 Eclipse를 설치한다.
Project는 개발에 필요한 라이브러리를 불러와주고 빌드 라이프사이클까지 관리해주는 툴인 Gradle or Maven 둘 중에 하나를 골라서 사용하는데 일반적으로 Gradle을 사용해서 진행합니다.
언어는 JAVA를 선택한 후 진행하고, SpringBoot는 버전을 선택하는데 뒤에 SNAPSHOT(아직 만들고 있는 것) 이나 M2 (정식 릴리즈버전이 아님) 가 붙은 임시 버전을 제외하고 아무 버전이나 사용해도 무방합니다. 3.0 이상 부터는 자바 17이상 지원하기에 자바를 또 설치하셔야 합니다. 2.7.10 으로 선택해서 진행해도 괜찮습니다.
Group - 기업 도메인 명을 작성하나, 연습용으로는 아무거나 괜찮습니다. Artifact - 릴리즈 될때의 결과물인데 프로젝트명과 유사한 의미입니다. Dependencies - 프로젝트 시작 시 어떤 라이브러리를 불러와서 사용할 것인가를 고르는 것입니다. Spring Web과 Thymeleaf를 2개의 라이브러리를 골라서 선택합니다. Generate로 다운로드를 받은 후 압축을 푼 후 intelliJ에서 build.gradle 파일을 오픈합니다.
Java에서 Object는 모든 클래스의 최상위 부모 클래스입니다. 모든 클래스는 암묵적으로 Object 클래스를 상속받습니다. Object 클래스는 Java의 기본적인 기능과 메서드를 제공하며, 모든 객체가 가져야 할 공통 동작을 정의합니다.
java에서 하나의 클래스를 만들면 기본적으로 생략된 코드들이 있다
① default packgae - ex) import.java.lang.*; ② 최상위 클래스 - ex) public class A extends Object ⇒ java.lang.Object ③ default 생성자 - ex) public A() { super(); }
💡 Object 클래스내의 자주 쓰이는 메서드
Object 클래스의 주요 메서드로는 다음과 같은 것들이 있습니다:
equals(): 객체의 동등성을 비교하는 메서드입니다. 기본적으로는 객체의 주소 비교를 수행하지만, 필요에 따라 재정의하여 객체의 내용 비교를 수행할 수 있습니다.
hashCode(): 객체의 해시 코드 값을 반환하는 메서드입니다. 객체의 동등성을 검사하는데 사용될 수 있습니다.
toString(): 객체를 문자열로 표현하는 메서드입니다. 기본적으로는 클래스명과 해시 코드를 반환하며, 필요에 따라 재정의하여 객체의 정보를 적절히 표현할 수 있습니다.
getClass(): 객체의 클래스 정보를 반환하는 메서드입니다. Class 클래스의 인스턴스를 반환합니다.
clone(): 객체를 복제하는 메서드입니다. 객체의 얕은 복사를 수행합니다.
finalize(): 객체가 소멸될 때 호출되는 메서드입니다. Java에서는 가비지 컬렉터가 객체를 수거하기 전에 이 메서드를 호출합니다.
Object 클래스의 존재는 모든 객체가 공통적인 기능과 동작을 가지도록 하여 객체 지향 프로그래밍의 기본 원칙을 준수할 수 있도록 도와줍니다. 또한, 다형성을 지원하고 객체를 일반적인 형식으로 다룰 수 있도록 합니다.
//B클래스
public class B extends Object {
public void printGo() {
System.out.println("나는 B입니다.");
}
}
//A클래스
public class A extends Object{ //생략된 기본 최상위클래스 Object와 상속관계
public A(){ //생략된 A클래스의 default 생성자
super();
}
public void display() {
System.out.println("나는 A 클래스 입니다.");
}
public void printGo() {
System.out.println("나는 A 클래스 입니다.");
}
}
//main 메서드 클래스
import Poly.A;
import Poly.B;
public class ObjectPolyArg {
public static void main(String[] args) {
A a = new A();
display(a); // display 메서드에 A 객체 전달
B b = new B();
display(b); // display 메서드에 B 객체 전달
}
private static void display(A a) {
a.printGo();
}
private static void display(B b) {
b.printGo();
}
}
display() 메서드에 인수로 A a를 전달하는 이유는 메서드 내부에서 A 클래스에 정의된 printGo() 메서드를 호출하기 위해서입니다.
만약 display() 메서드에 인수로 a만 전달하면, 컴파일러는 해당 인수의 타입을 알 수 없기 때문에 어떤 메서드를 호출해야 할지 판단할 수 없습니다. 따라서 인수에 타입을 명시하여 컴파일러에게 전달된 객체의 타입을 알려주어야 합니다.
이렇게 타입을 명시함으로써 컴파일러는 A a가 A 클래스의 인스턴스임을 알 수 있고, 따라서 A 클래스에 정의된 메서드인 printGo()를 호출할 수 있게 됩니다. 타입의 명시는 컴파일러가 올바른 메서드 호출을 검증하고 실행 가능한 코드를 생성하는 데 도움을 줍니다.
Q. 서로 다른 동작을 가지는 클래스를 상속관계로 만들어서 동작을 시켜야한다고 가정해보자 서로 다른 동작의 클래스도 공통 기능을 만들어서 상속구조로 사용 가능?
상위클래스가 일반클래스인 클래스와의 상속관계에서는 하위클래스에서 상위클래스로 객체를 생성해서 사용할 때, 각각의 클래스들의 담겨져 있는 메서드를 사용할때 재정의(Override)를 해야하는 것은 아닙니다 ⇒ 다형성이 보장되진 않습니다.
상위클래스가 추상클래스인 클래스와의 상속관계에서는 추상클래스 내에 생성된 추상메서드에 따라 하위클래스에서 꼭 재정의(Override)를 해야합니다. 재정의를 하게 만들어서 다형성을 보장할 수는 있지만, 상위클래스는 구현클래스를 가질 수 있어서 이로 인해 하위클래스가 오동작할 수 있습니다. 재정의를 하게 만들어서 다형성을 보장할 수는 있지만, 상위클래스는 구현클래스를 가질 수 있어서 이로 인해 하위클래스가 오동작할 수 있습니다. ⇒ 추상메서드와 일반구현메서드가 서로 다른 동작을 가지는 클래스이기 때문
이렇게 서로 다른 동작(=메서드)을 가지는 클래스를 상속관계로 만들어서 동작을 시켜야 할 때에는 클래스가 아닌 인터페이스를 선언하여 사용해야 합니다.
❓ 인터페이스란 ?
객체 지향 프로그래밍에서 클래스와 관련된 동작(메서드)의 집합을 정의하는 추상화된 형식이며, 클래스에서 구현해야 하는 메서드들의 목록을 정의하는 역할을 합니다. 다른 클래스들이 특정 동작을 구현하도록 규약을 제공하며, 클래스 간의 계약(contract)을 정의하는 역할을 합니다.
* interface 문법 interface 인터페이스 이름 { public static final 타입상수이름 = 값; public abstract 메서드이름(매개변수 목록); }
인터페이스도 일종의 추상클래스라고 볼 수 있지만, 추상클래스와 달리 추상 메서드만을 포함하며, 일반 메서드, 멤버 변수, 생성자를 가질 수 없습니다. 클래스에서 인터페이스를 구현할 때는 모든 추상 메서드를 반드시 구현해야 합니다.
인터페이스는 interface 키워드를 사용하여 정의되며, 메서드의 시그니처(이름, 매개변수, 반환값)만을 선언하고, 구현 내용을 가지지 않습니다. 또한, 추상메서드와 final static 상수만 가질 수 있습니다. 클래스는 인터페이스를 implements 키워드를 사용하여 구현할 수 있으며, 인터페이스에 정의된 모든 메서드를 반드시 구현해야 합니다.
인터페이스는 구현메서드가 없기 때문에 객체 생성이 불가능하지만 Upcasting으로 상위클래스의 역할은 할 수 있습니다.인터페이스는 다음과 같은 특징을 갖습니다:
추상 메서드: 인터페이스는 추상 메서드만을 가지므로, 메서드의 구현 내용이 없습니다. 따라서, 인터페이스를 구현하는 클래스에서는 인터페이스에 정의된 메서드를 반드시 구현해야 합니다.
다중 상속: 클래스는 하나의 클래스만 상속할 수 있지만, 인터페이스는 여러 개의 인터페이스를 동시에 구현할 수 있습니다. 이를 통해 다중 상속의 일부 기능을 구현할 수 있습니다.
계약: 인터페이스는 클래스들 사이의 계약(Contract)을 정의하는 역할을 합니다. 클래스가 인터페이스를 구현한다는 것은 인터페이스에 정의된 메서드를 반드시 구현한다는 의미이며, 이를 통해 클래스들 간의 일관성을 유지할 수 있습니다.
다형성: 인터페이스를 사용하면 여러 클래스들을 하나의 인터페이스 타입으로 참조할 수 있으며, 이를 통해 다형성의 특징을 활용할 수 있습니다.
동일한 메시지(메서드 호출)에 대해 서로 다른 객체가 다르게 동작하는 능력(message pholymorphism)을 말합니다. 다형성은 한 가지 타입 또는 인터페이스를 여러 개의 구현체가 가질 수 있도록 함으로써 유연하고 확장 가능한 코드를 작성할 수 있게 합니다.
다형성은 상속과 관련하여 사용되며, 부모 클래스와 그 자식 클래스들 간에 동일한 메서드 이름을 가지고 있지만 각각의 클래스에서 다른 구현을 가지도록 하는 것을 의미합니다. 다형성은 같은 타입으로 선언된 객체가 실제로 실행 시에는 여러 다른 타입의 객체로 동작할 수 있게 합니다.
다형성은 다음과 같은 방식으로 구현됩니다:
업캐스팅(Upcasting): 자식 클래스의 인스턴스를 부모 클래스의 참조 변수로 참조하는 것을 말합니다. 부모 클래스의 참조 변수를 통해 자식 클래스의 객체에 접근할 수 있습니다. 업캐스팅을 통해 여러 개의 자식 클래스를 하나의 부모 클래스 타입으로 관리할 수 있습니다.
메서드 오버라이딩(Method Overriding): 자식 클래스에서 부모 클래스의 메서드를 재정의하여 자식 클래스에서 특화된 구현을 제공합니다. 부모 클래스의 메서드를 자식 클래스에서 동일한 이름으로 재정의하면, 실행 시에는 실제 객체의 타입에 맞는 메서드가 호출됩니다.
다형적 변수(Polymorphic Variables): 부모 클래스의 참조 변수를 사용하여 여러 자식 클래스의 객체를 참조할 수 있습니다. 이렇게 다형적 변수를 사용하면 실행 시에 실제 객체의 타입에 따라 다른 동작을 수행할 수 있습니다.
다형성은 코드의 유연성과 재사용성을 높여주며, 객체 지향 프로그래밍의 핵심 원칙 중 하나인 "프로그램을 작성할 때는 구체적인 타입보다는 추상적인 타입에 의존하라(Depend upon abstractions)"를 실현하는 방법 중 하나입니다.
//Cat 클래스 (자식)
public class Cat extends Animal {
public void eat() {
System.out.println("고양이처럼 밥 먹는다");
}
}
//Dog 클래스 (자식)
public class Dog extends Animal{
public void eat() {
System.out.println("강아지처럼 먹어보자");
}
}
//Animal 클래스 (부모)
public class Animal {
public void eat() {
System.out.println("동물처럼 먹다");
}
}
//Override 클래스 (메인)
public static void main(String[] args) {
/*
Upcastiong(업캐스팅) -> 만약 다른 사람이 Dog클래스를 java 소스코드를 주지 않고,
class 파일만 주었을 때, Dog 클래스로 선언하기 어려움
Animal <---> Dog.class(o) Animal 클래스가 Dog클래스와 상속관계에 있다는 것을
알고있다는 가정하에 Override 통해 eat()을 재 정의 할 수 있게 됩니다.
*/
Animal ani = new Dog();
ani.eat();
// Animal ----실행시점에서 메서드가 결정되는 것(동적바인딩)---> Dog
// 작성 시에는 eat()가 Animal에 속해있는 것이라고 생각되어 에러가 나지 않았지만, 실행과 동시에 Dog의 eat()이 실행되게 됩니다 / 이를 동적바인딩이라고 합니다.
System.out.println("ani = " + ani);
}
}
앞서 업캐스팅과 동적바인딩을 설명할 때 , 사용했던 코드로 다형성을 설명해보면, 부모클래스 (Animal)이 먹다(eat()) 메서드를 자식클래스인 Dog와 Cat에게 메세지를 보내면 자식 클래스가 반응을 하는데, 두 자식 클래스에서 eat() 메서드가 개와 고양이로 다르기 때문에 서로 다르게 반응을 하게 됩니다.
다형성을 적용하기 위해서는 Upcasting으로 객체를 생성 상속 관계로 되어 있어야 하며, 재 정의가 되어있고, 메모리에서 동적 바인딩이 되어있는 관계여야 가능하다. 상위클래스가 동일한 메세지로 하위 클래스를 서로 다르게 동작시키는 객체지향 원리 (=message polymorphism)
컴파일 시점에선 eat()가 Animal내의 eat() , 실행시점에서 호출될 메서드가 결정되는 바인딩 = 동적바인딩 -> 동적바인딩이 일어나야 eat()라는 메서드가 Animal것인지 Dog것인지 알 수 있다.
💡 다형성(message polymorphism)의 전제조건
1. 상속관계 2. Override(재정의) 3. Upcasting(업캐스팅) 4. 동적 바인딩
💡 다형성 활용 - 다형성 인수란 ?
다형성 인수(Polymorphic Parameters)란, 메서드나 함수의 매개변수(parameter)를 다형성(polymorphism)을 활용하여 여러 타입의 객체를 전달할 수 있는 것을 말합니다. 이를 통해 코드의 재사용성과 유연성을 높일 수 있습니다.
public void makeShape(Shape shape) {
// 도형을 처리하는 로직
shape.draw();
}
Circle circle = new Circle();
Triangle triangle = new Triangle();
Rectangle rectangle = new Rectangle();
makeShape(circle); // Circle 객체 전달
makeShape(triangle); // Triangle 객체 전달
makeShape(rectangle); // Rectangle 객체 전달
위의 코드에서 makeShape 메서드는 Shape 타입의 매개변수 shape를 받습니다. 이 매개변수는 다형성 인수로, Shape클래스를 상속한 여러 도형 클래스의 인스턴스를 전달할 수 있습니다. 예를 들면, Circle, Triangle, Rectangle 클래스가 Shape 클래스를 상속한다고 가정해보겠습니다.
위의 코드에서 makeShape 메서드에 각각의 도형 객체를 전달하고 있습니다. 이 때, 전달되는 객체는 각자의 실제 타입인 Circle, Triangle, Rectangle 이지만, 메서드 내부에서는 Shape 타입으로 참조되고 처리됩니다. 이는 다형성의 개념을 활용한 예시로서, 동일한 메서드를 여러 타입의 객체에 대해 재사용할 수 있음을 보여줍니다.
다형성 인수는 코드의 유연성과 확장성을 제공하며, 객체지향 프로그래밍에서 중요한 개념 중 하나입니다.
instanceof 연산자 : instanceof 연산자는 객체의 타입을 확인하기 위해 사용되는 연산자 사용 문법 [객체] instanceof [타입]
instanceof를 사용하여 특정 객체가 특정 클래스의 인스턴스인지를 확인할 수 있습니다.
Shape shape = new Circle();
if (shape instanceof Circle) {
System.out.println("shape은 Circle의 인스턴스입니다.");
} else if (shape instanceof Rectangle) {
System.out.println("shape은 Rectangle의 인스턴스입니다.");
} else {
System.out.println("shape은 Circle 또는 Rectangle의 인스턴스가 아닙니다.");
}
위의 코드에서 makeShape 메서드는 Shape 타입의 매개변수 shape를 받고, shape 객체의 타입을 instanceof 연산자를 사용하여 확인합니다. 이후 해당하는 타입으로 캐스팅하여 각각의 타입에 맞는 처리 로직을 수행합니다. instanceof 연산자는 주어진 객체가 해당 타입의 인스턴스인지를 불리언(Boolean) 값으로 반환합니다. 만약 객체가 해당 타입의 인스턴스이면 true를 반환하고, 그렇지 않으면 false를 반환합니다.
instanceof 연산자는 객체의 상속 관계나 인터페이스 구현 여부를 확인할 때 주로 사용됩니다. 타입 검사를 통해 객체의 동적인 타입을 파악하여 적절한 동작을 수행할 수 있습니다. 다형성과 함께 사용하면 유연하고 확장 가능한 코드를 작성할 수 있습니다.
💡 다형성 활용 - 다형성 배열(상위타입 배열)이란?
다형성배열이란 ? 다형성 개념을 배열에 적용한 것을 의미합니다. 다형성 배열은 여러 타입(서로 다른 타입)의 객체를 하나의 배열에 저장할 수 있는 특징을 갖고 있습니다.
일반적으로 배열은 동일한 타입의 객체들만 저장할 수 있지만, 다형성 배열은 부모 클래스를 상속받은 서브 클래스들을 모두 저장할 수 있습니다. 이를 통해 서로 다른 타입의 객체를 하나의 배열에 함께 저장하고 관리할 수 있습니다. 부모클래스 타입의 배열은 자식클래스 타입을 저장하는 것이 가능합니다.
이전에는 데이터의 상태정보에 초점을 맞춰 DTO, VO 클래스 위주로 데이터를 다루었다면, 행위(동작) 정보를 초점에 맞춰서 클래스와 클래스를 설계(상속)하는 방법을 알아보겠습니다.
수평적 구조로 설계하다보면 Dog와 Cat의 공통적으로 할 수 있는 동작은 eat(먹다) 라는 같은 기능을 하는 동작의 중복이 발생합니다. 동물들에게는 eat라는 중복기능이 나올 확률이 높습니다.
class Animal {
// 부모 클래스 멤버
}
class Dog extends Animal {
// 자식 클래스 멤버
}
Dog dog = new Dog();
Animal animal = dog; // Upcasting
Animal animal = new Dog(); // Upcasting
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // Downcasting
// dog 변수를 통해 animal이 Dog 클래스의 멤버에 접근
}
🎈 Upcasting(업캐스팅 / 자동형변환) 이란 ?
업캐스팅은 부모 클래스 타입으로 자식 클래스의 객체를 참조하는 것을 말합니다. 업캐스팅을 사용하는 이유는 다형성(Polymorphism)을 활용하기 위함입니다. 부모 클래스는 자식 클래스의 공통된 특징과 기능을 포함하고 있으므로, 업캐스팅을 통해 부모 클래스 타입으로 자식 클래스의 객체를 다룰 수 있게 됩니다. 이로써 코드의 유연성과 확장성을 높일 수 있습니다. 업캐스팅은 런타임에 동적으로 객체의 실제 타입을 결정할 수 있기 때문에 다양한 자식 클래스의 객체를 부모 클래스 타입으로 처리할 수 있습니다.
💡 업캐스팅 문법 → 상위 클래스타입 인스턴스 변수 = new 하위클래스();
Animal x = new Dog();
→ 객체로의 생성은 Dog()이지만 Animal의 타입으로 받습니다. 둘은 상속관계이기때문에 가능합니다. (하위가 상위로 들어가는 것은 자동!) 부모가 자식을 가리키는 객체 생성 방법 → 하위 클래스의 동작을 알 수 없을때는, 부모타입으로 객체를 생성할 수 밖에 없기때문에 자주 사용됩니다.
🎈 Downcasting(다운캐스팅 / 강제형변)이란 ?
다운캐스팅은 업캐스팅된 객체를 다시 원래의 자식 클래스 타입으로 변환하는 것을 말합니다. 다운캐스팅은 주로 다형성을 통해 업캐스팅된 객체가 실제로 어떤 자식 클래스의 객체인지 확인하고,자식 클래스에 고유한 메서드나 속성에 접근하기 위해 사용됩니다. 다운캐스팅은 정확한 타입의 메서드나 속성을 사용할 수 있도록 객체를 변환해주는 역할을 합니다. 다운캐스팅은 업캐스팅된 객체를 다시 자식 클래스 타입으로 형변환하여 사용하는 것이기 때문에 정확한 타입 체크가 필요하며, 올바른 타입이 아닌 경우에는 ClassCastException이 발생할 수 있습니다.
💡 다운캐스팅 문법 → 하위클래스의 타입 다운캐스팅된 객체를 저장할 변수 = (하위클래스의 타입)상위클래스의 객체
다운캐스팅은 주의해야 할 점이 있습니다. 만약 다운캐스팅을 시도하는 객체가 실제로 해당 하위 클래스의 인스턴스가 아니라면,ClassCastException이 발생할 수 있습니다. 따라서 다운캐스팅을 수행하기 전에 항상 해당 객체가 정말로 하위 클래스의 인스턴스인지를 확인하는 것이 좋습니다. 이를 위해instanceof 연산자를 사용할 수 있습니다.
instanceof 연산자 : instanceof 연산자는 객체의 타입을 확인하기 위해 사용되는 연산자 사용 문법 : [객체] instanceof [타입]
업캐스팅은 다양한 자식 클래스를 부모 클래스의 타입으로 다룰 수 있도록 하고, 다운캐스팅은 업캐스팅된 객체를 원래의 자식 클래스 타입으로 형변환하여 자식 클래스에 고유한 기능에 접근할 수 있도록 합니다.
클래스를 설계(상속) 각 클래스들이 아무 관계가 없이 만들어지는 것이 아니라 클래스들끼리 연결을 시켜서 프로그램이 개발되는데 그 시작이 상속에서부터 시작됩니다.
❓ Java에서 상속이란 ? 클래스를 계층화 하는 것
상속은 한 클래스가 다른 클래스의 특성과 동작을 상속받아 사용할 수 있도록 해줍니다.
상속 관계에서는 두 개의 클래스가 있습니다: 부모 클래스(상위 클래스 또는 슈퍼 클래스)와 자식 클래스(하위 클래스 또는 서브 클래스)입니다. 부모 클래스는 자식 클래스에게 공통된 속성과 메서드를 상속해줍니다. 자식 클래스는 부모 클래스의 특성을 확장하거나 변경하여 새로운 속성과 메서드를 추가할 수 있습니다.
Java에서 클래스 간의 상속 관계를 구현하기 위해서는 extends 키워드를 사용합니다. 자식 클래스가 부모 클래스를 상속받으려면 클래스 선언 시에 extends 키워드를 사용하고, 부모 클래스의 이름을 지정합니다. 이렇게 하면 자식 클래스는 부모 클래스의 멤버(필드와 메서드)를 상속받습니다.
🎈 상속 관계 설정 문법 -class Child extends Parent
class Animal { // 부모 클래스
protected String name;
public void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal { // 자식 클래스
public void bark() {
System.out.println("Dog is barking.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "Tom";
dog.eat(); // 부모 클래스의 메서드 호출
dog.bark(); // 자식 클래스의 메서드 호출
}
}
//출력 : Animal is eating. Dog is barking.
위 예시에서 Animal 클래스는 부모 클래스로, Dog 클래스는 자식 클래스입니다. Dog 클래스는 extends 키워드를 사용하여 Animal 클래스를 상속받습니다. 따라서 Dog 클래스는 name 필드와 eat() 메서드를 상속받아 사용할 수 있습니다. Dog 클래스는 또한 bark()라는 자신만의 메서드를 추가로 가지고 있습니다.
상속을 통해 부모 클래스의 속성과 메서드를 재사용하고, 자식 클래스에서 새로운 기능을 추가하거나 기존 기능을 재정의할 수 있습니다. 이를 통해 코드의 재사용성과 확장성을 높일 수 있습니다.
🎈 클래스의 수평적 구조 vs 수직적 구조
Dog와 Cat의 클래스를 수평적으로 설계하게 된다면 생성된 클래스들의 연계되었기보다는 독립적으로 생성되어지며, 코드의 중복이 발생하는 단점이 발생합니다. 그로인해 새로운 요구사항이 발생했을 때, 반영이 어려워 유지보수가 어려워집니다. 유지보수가 어렵다보니 그에 따르는 확장성이 떨어지게 됩니다.
💡 클래스의 수직적 구조 = 계층화 = 상속(Inheritance) = 클래스와 클래스의 관계 설계
클래스들이 공통으로 가지게 되는 기능이 담긴 별도의 Animal클래스로 생성하여, 제일 상단에 위치하였습니다. 여기서 Aniaml 클래스가 부모 클래스가 되며 상속(extends : 확장) 을통해 하단의 Dog, Cat클래스는 자손 클래스가 됩니다.
✔ “is a kind of” - 상속관계
"is a kind of"은 객체 지향 프로그래밍에서 클래스 간의 관계를 표현하는 개념입니다. 이 개념은 상속 관계를 나타내는 것을 의미합니다. 상위 클래스에는 공통적인 특징과 동작을 구현하고, 하위 클래스는 상위 클래스의 특성을 상속받으면서 자신만의 추가적인 특성을 정의할 수 있습니다.
객체를 수직적인 구조로 설계하면 코드의 중복을 최소화하고, 새로운 요구사항 발생 시 반영이 쉽다는 장점이 있습니다. 그로 인해 확장성은 좋아질 수 있으나, 코드의 복잡성은 증가하게 됩니다.
java에서는 둘 이상의 클래스로부터 상속을 받을 수 없도록 단일 상속만을 허용한다. 단일상속은 하나의 부모클래스만을 가질 수 있기 때문에 다중상속에 비해 불편하지만, 클래스간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다는 점에서 다중상속보다 유리합니다.
🎈 메모리를 통한 상속(extends)의 이해 (상태정보 재활용)
상속 : UML (Unified Modeling Laguage) 상속을 다이어그램으로 표현할 때, UML - 객체 지향 모델링 언어로 표현할 수 있습니다. 그림으로 표현할 수 있는 언어이며, 그림으로 표현하는 다이어그램으로 설계를 한 후 의사소통을 하는 것이 효율적입니다. 설계는 다이어그램을 보고 코드로 작성이 진행되게 됩니다. ** UML은 프트웨어 시스템을 시각적으로 모델링하고 설계하기 위해 사용되는 표준화된 그래픽 언어입니다. UML은 시스템의 구성 요소, 클래스와 객체, 관계, 동작, 상태 등을 시각적으로 표현할 수 있습니다.
public class Employee {
protected String name;
-> protected : 상속관계에서 하위 클래스가 상위 클래스의 접근을 허용하는 접근 권한
protected String phone;
protected String phone;
protected String empDate;
protected String dept;
public Employee() {
super();
}
public class RempVO() extends Employee {
public RempVO() {
super();
}
}
상속관계에서 상태변수(멤버변수)를 private로 접근제어를 작성한다면, 자식클래스가 부모에 있는 상태변수에 접근할 수 없기 때문에 protected 접근제어자를 사용합니다. (자식 클래스만 작성 가능하도록 해야하기 때문에 public이 아니라 protected로 작성합니다.) protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능합니다.
🎈 super() 메서드 : super()는 상위클래스의 생성자를 호출하는 역할
상속관계의 클래스를 작성한 후 생성자를 만들게 되면 { } 중괄호 안에 아무것도 작성되어 있지 않아도, 자동으로 super(); 키워드가 입력되게됩니다.
❓ Q. 상속관계가 된 후 객체 생성을 진행한다면 ?
RempVo vo = new RempVO(); → RempVO가 상속 관계 있을 때, 객체를 생성하게 될 때 RempVO 객체라는 건 부모 클래스인 Employee() 객체가 존재하기 때문에 존재할 수 있습니다. 상속 관계에서는 자식 클래스의 객체가 존재하기 위해서는 부모의 객체가 먼저 생성이 되어야지만 자식 클래스에서의 객체 생성이 가능합니다. 자식 생성자 안에서 부모 생성자를 호출할 수 있는 방법이 필요한데 그 때 super(); 메서드를 사용하여 호출합니다.
super()는 자식 클래스 내부에서 부모 클래스의 생성자를 호출할 때 사용되며, 자식 클래스의 생성자 첫 줄에서 호출(=first statement)되어야 합니다. super();가 아래에서 초기화가 된다면, 부모클래스가 먼저 만들어지지 않고 객체가 생성되기 때문에 반드시 생성자의 첫번째 문장에 사용해야 합니다. 예를 들어, **super()**를 사용하여 부모 클래스의 기본 생성자를 호출하면 부모 클래스의 필드를 초기화하고 부모 클래스의 생성자에서 수행되는 작업을 수행할 수 있습니다 (객체가 생성되려면 생성자가 호출되어야 생성이 가능)
생성자의 첫 문장에는 super();가 기본적으로 생략된채로 만들어집니다. -> .상속 관계에서 객체 생성은 부모 클래스에서부터 생성되어 자식 클래스들로 전달되는데 이를 상속 체이닝이라고 합니다
참조변수는 객체를 가리키는 변수입니다. 자바에서 객체는 클래스의 인스턴스를 의미하며, 객체를 생성하면 해당 객체를 가리키기 위한 참조변수가 필요합니다. 참조변수는 객체의 주소를 저장하고, 해당 주소를 통해 객체에 접근하여 필드를 읽거나 메서드를 호출할 수 있습니다.
따라서 super()는 부모 클래스의 생성자를 호출하는 특수한 구문이며, 참조변수는 객체를 가리키는 변수를 의미합니다.