클래스에서 객체 자기 자신을 가리키는 키워드 : this
부모 클래스를 호출하는 키워드 : super
부모가 자식에게 물려주는 것
객체 지향 프로그램에서도 부모 클래스의 멤버를 자식 클래스에 물려줄 수 있음
클래스 구조를 그려서 확인하는 툴
코드를 그대로 치면 알아서 표를 그려줌.
public class 클래스명 extends 부모클래스명
상속을 받을 수 있는 것들
부모의 요소를 전달 받을 수 있어 자식 요소에서 그대로 끌어다 사용할 수 있음. → 클래스의 재 사용
상속을 받은 기능을 제 각각 개발 가능하여 기능 확장에 용이
여기서 만약 부모 클래스에서 private 접근 제한을 갖는 필드와 메서드는 상속대상에서 제외됨
package chap07;
public class InheitA {
int field1;
private int fieldOne;
protected fieldTwo;
void method1() {
System.out.println("InheritA.method1 field1:" + field1);
System.out.println("InheritA.method1 fieldOne:" + fieldOne);
System.out.println("InheritA.method1 fieldTwo:" + fieldTwo);
}
}
package chap07;
public class InheitB extends InheitA {
int field2;
void method2() {
System.out.println("InheritB.method field2:" + field2);
}
}
package chap07;
// InheritA,lnheritB,Example 클래스 생성 후 '상속'된 구성요소 확인
public class InheritAlnheritBExample {
public static void main(String[] args){
InheitB inheitB = new InheitB();
inheitB.field1 = 10;
// inheitB.fieldOne = 10; // java: fieldOne has private access in chap07.InheitA
// inheitB.fieldTwo = 10; // InheritA.method1 fieldTwo: 10
inheitB.method1(); // 부모 클래스의 구성요소
inheitB.field2 = 30;
inheitB.method2();
}
}
// 결과
InheritA.method1 field1:10
InheritB.method field2:30
현실에서 상속은 부모가 자식을 선택해서 물려주지만 자바에서는 자식이 부모를 선택
이때 부모 클래스는 다중 선택 상속이 불가능하다.
class 자식클래스 extends 부모클래스 { // 부모 클래스 하나만 상속
// 필드
// 생성자
// 메소드
}
package chap07;
public class Animal {
String name;
void setName(String name){
this.name = name;
}
void sleep() {
System.out.println(this.name + "Zzz.....");
}
}
package chap07;
public class Puppy extends Animal{
@Override
void sleep() {
//부모 클래스의 sleep() 메소드 오버라이딩
}
void sleep(int times) {
System.out.println(name + " Zzz... " + times + " hours");
}
}
package chap07;
public class PuppyExample {
public static void main(String[] args){
Puppy puppy = new Puppy();
puppy.setName("Dino");
// puppy.sleep();
puppy.sleep(9);
}
}
자바에서는 자식 객체를 생성하면 부모 객체가 먼저 생성되고 자기 객체가 그 다음에 생성
자식의 클래스의 생성자 안에는 암묵적으로 부모 클래스의 생성자를 호출하는 코드가 포함되어 있기 때문에 부모 클래스의 생성자가 먼저 실행 된 후 자식 클래스의 생성자가 실행.
부모 클래스의 기본 생성자를 호출하는 메서드 : super()
자식클래스(매개변수 선언, ...)[
super(매개값, ...);
...
}
직접 자식 생성자를 선언하고 명시적으로 부모 생성자를 호출 하고 싶을 떄.
이때 super(매개값)은 매개값의 타입과 일치하는 부모 생성자를 호출
만약 super() 라인을 주석처리하면 There is no default constructor available in 'Person’
해당 컴파일 오류를 마주침.
자습시간에 다시 생각해서 풀어보기
@startuml
class Employee {
String name
double calculateSalary()
}
class FullTimeEmployee extends Employee {
double salary
double calculateSalary()
}
class PartTimeEmployee extends Employee {
double hourlyRate
int hoursWorked
double calculateSalary()
}
@enduml
package chap07.employee;
/**
* 일반 클래스 Employee
*/
public class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
double calculateSalary() {
return 0;
}
}
package chap07.employee;
public class FullTimeEmployee extends Employee {
double salary;
FullTimeEmployee(String name, double salary) {
super(name);
this.salary = salary;
}
// 메소드 재정의 (Override)
double calculateSalary() {
return salary;
}
}
package chap07.employee;
public class PartTimeEmployee extends Employee {
double hourlyRate;
int hoursWorked;
PartTimeEmployee(String name, double hourlyRate, int hoursWorked) {
super(name); // Employee(name) 호출
this.hourlyRate = hourlyRate;
this.hoursWorked = hoursWorked;
}
// 메소드 재정의 (Override)
@Override
double calculateSalary() {
return hourlyRate * hoursWorked;
}
}
package chap07.employee;
public class EmployeeExample {
public static void main(String[] args) {
// 객체 생성
FullTimeEmployee alice = new FullTimeEmployee("Alice", 4000);
PartTimeEmployee bob = new PartTimeEmployee("Bob", 1000, 4);
// 출력(객체 내부 요소들 호출)
System.out.println(alice.getName() + "'s Salary: " + alice.calculateSalary());
System.out.println(bob.getName() + "'s Salary: " + bob.calculateSalary());
}
}
mothod의 시그니처가 같음.
상속된 부모의 메소드 내용이 자식 클래스에 맞지 않을 경우 자식 클래스에서 동일한 메서드로 재정의 할 수 있다.
메소드가 오버라이딩 되었다면 부모 객체의 메소드는 숨겨지기 때문에, 자식 객체에서 메소드를 호출하면 오버라이딩된 자식 메소드가 호출됨.
double claculateSalary()
public class Parent {
void method1() {
System.out.println("Parent의 method1 호출");
}
void method2() {
System.out.println("Parent의 method2 호출");
}
}
public class Child extends Parent {
void method2() {
System.out.println("Child의 method2 호출");
}
void method3() {
System.out.println("Child의 method3 호출");
}
}
public class ChildExample {
public static void main(String[] args) {
Child child = new Child();
child.method1();
child.method2(); // 재정의된 메소드 호출
child.method3();
}
}
자식 클래스에서 @Override 어노테이션은 생략해도 좋지만, 정확히 해당 메소드가 오버라이딩된 것인지 컴파일러가 체크할 수 있기 때문에 개발자의 실수를 줄여줄 수 있음.
매개변수를 여러 개로 늘린 것.
메소드 오버라이딩과 비슷하게 메소드를 재정의할 수 있는 방법.
메소드의 이름은 동일하고 입력 항목이 다른 경우를 메서드 오버로딩(method overloading)이라고 함.
void sleep(){}
void sleep(int times){}
void sleep(int times, String name){}
package chap07;
public class Animal {
String name;
Animal(){
System.out.println("Animal 객체 생성");
}
void setName(String name){
this.name = name;
}
void sleep() {
System.out.println(this.name + "Zzz.....");
}
}
package chap07;
public class Puppy extends Animal{
@Override
void sleep() {
//부모 클래스의 sleep() 메소드 오버라이딩
}
Puppy(String name){
// super() 가 생략되어 있음. 부모의 생성자를 호출시켜주는 메서드.
System.out.println("Puppy 객체 생성 name " + name);
}
void sleep(int times) { // 메소드 오버로딩
System.out.println(name + " Zzz... " + times + " hours");
}
}
package chap07;
// 자식 클래스에서 sleep() 메소드 오버로딩
public class PuppyExample {
public static void main(String[] args){
Puppy puppy = new Puppy("Ping");
puppy.setName("Dino");
// puppy.sleep();
puppy.sleep(9);
}
}
자식 클래스에서 반드시 오버라이드 해야만 사용할 수 있는 메서드.
추상 클래스를 선언하는 이유 → 자식 클래스에서 반드시 추상클래스를 구현하도록 하기 위함.
추상클래스만으로는 객체를 생성할 수 없음.
구체화된 클래스가 하나 존재해야 객체를 생성할 수 있음.
추상클래스란 추상 메서드를 하나 이상 포함하는 클래스.
abstract 리턴타입 메소드명 ();
추상은 실체 간에 공통된 특성을 추출한 것을 말함.
즉 실체들의 공통되는 특성을 사지고 있는 것을 클래스로 따로 빼놓은 것을 추상클래스
추상클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에 일반 클래스와는 다르게 객체를 직접 생성해서 사용할 수 없다.
→ 추상클래스는 new 연산자를 사용하여 인스턴스를 생성하지 못함.
Animal animal = new Animal(); (X)
추상클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용
실체 클래스들의 공통적인 특성(필드, 메서드)을 뽑아내 추상 클래스로 만드는 이유
추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙여야 함.
abstract를 붙이게 되면 new 연산자를 이용해 객체를 만들지 못하고 상속으로만 자식 클래스만 만들수 있다.
public abstract class 클래스명 {
// 필드
// 생성자
// 메소드
}
추상클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메서드를 선언할 수 있다.
public abstract class Phone {
private String owner;
public Phone(String owner) {
this.owner = owner;
}
public void turnOn() {
System.out.println("폰 전원을 켭니다.");
}
public void turnOff() {
System.out.println("폰 전원을 끕니다.");
}
}
public class SmartPhone extends Phone {
// 생성자
public SmartPhone(String owner) {
super(owner);
}
// 메소드
public void internetSearch() {
System.out.println("인터넷 검색을 합니다.");
}
}
public class PhoneExample {
public static void main(String[] args) {
// Phone phone = new Phone();
SmartPhone smartPhone = new SmartPhone("홍길동");
smartPhone.turnOn(); // Phone의 메소드
smartPhone.internetSearch();
smartPhone.turnOff(); // Phone의 메소드
}
}
추상 클래스는 실체 클래스가 공통적으로 가져야 할 필드와 메서드들을 정의해 놓은 추상적인 클래스이므로 실체 클래스의 멤버(필드, 메서드)를 통일하는데 목적이 있다.
예제 코드
public abstract class Animal { // 추상 클래스
private String kind;
public void breathe() {
System.out.println("숨을 쉽니다.");
}
public abstract void sound(); // 추상 메소드
}
public class Dog extends Animal {
public Dog() {
this.kind = "포유류";
}
@Override
public void sound() { // 추상 메소드 재정의
System.out.println("멍멍");
}
}
public class Cat extends Animal {
public Cat() {
this.kind = "포유류";
}
@Override
public void sound() { // 추상 메소드 재정의
System.out.println("야옹");
}
}
abstract 메소드로 정의했음에도 실체(자식)클래스에서 해당 메소드를 구현하지 않는다면 컴파일 오류가 발생한다.
실제 오류..
어노테이션은 메타데이터로서 컴파일 및 실행 과정에서 코드가 어떻게 처리될지를 컴파일러나 런타임에 알려주는 역할을 한다. 주로 @AnnotationName형식으로 작성된다.
어노테이션을 직접 정의할 때는 다음과 같은 형식을 사용한다:
@AnnotationName
public @interface Annotation {
타입 elementName() [default 값];
}
어노테이션이 적용될 대상을 지정할 때 @Target 어노테이션을 사용한다. @Target의 value요소는 ElementType 배열로, 여러 대상에 적용 가능하다.
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface AnnotationName {
}
ElementType 열거 상수 | 적용 대상 |
---|---|
TYPE | 클래스, 인터페이스, 열거타입 |
ANNOTATION_TYPE | 어노테이션 |
FIELD | 필드 |
CONSTRUCTOR | 생성자 |
METHOD | 메소드 |
LOCAL_VARIABLE | 로컬 변수 |
PACKAGE | 패키지 |
실제 프로젝트에서는 스프링이나 롬복과 같은 라이브러리에서 제공하는 어노테이션을 주로 활용하며, 커스텀 어노테이션을 작성하는 경우는 상대적으로 적다.
어노테이션은 메타데이터를 통해 코드의 컴파일 및 실행 과정을 제어하며, 정의된 타입과 적용 대상을 바탕으로 다양한 곳에 적용 가능하다.
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
// 어노테이션 형태로 사용할 것이라 @를 붙혀준다.
public @interface AnnotationName {
String elementNameOne(); // 구현부가 없는 메서드로 정의
int elementNameTwo() default 5; // 아무런 값도 설정하지 않았을 때는 5라는 값이 들어가도록 default 값 설정
}
@AnnotationName(elementNameOne = "값", elementNameTwo = 40)
public class ClassName {
@AnnotationName(elementNameOne = "값")
int field;
int getField(){
return field;
}
void setField(int field){
this.field = field;
}
@AnnotationName(elementNameOne = "값") // AnnotationName.java의 target에서 메서드 부분을 지우게 된다면 에러 발생
void method(){
}
}