📌 Java → '현실세계' 와 닮아있다.
필요한 부품(객체)들을 만들고 하나씩 조립해서 하나의 완성된 프로그램을 만드는 기법
객체는 세상에 존재하는 물체를 뜻하며 식별이 가능한 것
객체는 속성과 행위로 구성이 되어있다.
예) 자동차
필드)메서드)UML(Unified Modeling Language) 객체 다이어그램

→ 이처럼 현실 세계에 있는 객체를 소프트웨어의 객체로 설계하는 것을 '객체 모델링'이라고 부른다.
객체 간의 협력
사람이라는 객체와 자동차라는 객체는 서로 행위를 통해 상호작용을 하며 협력할 수 있습니다.
- 사람이 자동차의 가속 페달을 밟으면 자동차는 속도를 올리며 앞으로 이동한다.(gasPedal(50);)
- 사람이 자동차의 브레이크 페달을 밟으면 자동차는 속도를 줄이며 정지한다.(brakePedal();)

💡 사람은 자동차의 가속, 감속의 정확한 작동 원리까지 알 필요가 없다!
또한 소프트웨어의 객체들은 메서드를 통해 데이터를 주고 받을 수도 있다.
사람 객체는 메서드를 호출할 때 () 안에 데이터를 넣어 호출할 수 있는데 이때 이 괄호 안에 넣는 데이터를 '파라미터' 혹은 '매개값'이라고 표현한다.
또한 자동차 객체는 속도를 바꾸는 작업을 수행한 후 사람 객체에게 실행 결과인 속도의 값을 반환할 수 있다. 이때 반환되는 값을 '리턴값'이라고 표현한다.
(⇒ 자동차는 '리턴값'을 반환하지만 그것을 활용할지는 받는 쪽에서 선택할 수 있다.)
객체 간의 관계
포함 관계상속 관계✨ 객체지향 프로그래밍의 특징
캡슐화, 상속, 다형성, 추상화
속성(필드)와 행위(메서드)를 하나로 묶어 객체로 만든 후 실제 내부 구현 내용은 외부에서 알 수 없게 감추는 것을 의미한다.
부모 객체와 자식 객체가 존재
1. 각각의 객체를 상속 관계로 묶음으로써 객체 간의 구조 파악이 쉬움
2. 필드와 메서드를 변경하는 경우 부모 객체에 있는 것만 수정하면 자식 객체 전부 반영되기 때문에 일관성 유지에 용이
3. 자식 객체가 부모 객체의 필드와 메서드를 물려받아 사용할 수 있기 때문에 코드의 중복이 줄어들며 코드의 재사용성이 증가됨
객체가 연산을 수행할 때 하나의 행위에 대해 각 객체가 가지고 있는 고유한 특성에 따라 다른 여러가지 형태로 재구성 되는 것을 의미

동물은 모두 소리를 낸다. 하지만, 각 객체의 고유한 특성에 따라 강아지는 멍멍, 고양이는 야옹, 소는 음메하고 소리를 낸다.
객체에서 공통된 부분들을 모아 상위 개념으로 새롭게 선언하는 것을 추상화라고 한다.

클래스를 기반으로 생성(인스턴스화)된 것을 객체(인스턴스)라고 부른다.
클래스의 구성은 필드, 생성자, 메서드가 있다.

1. 만들려고 하는 설계도를 선언한다. (클래스 선언)
2. 객체가 가지고 있어야 할 속성(필드)를 정의한다.
3. 객체를 생성하는 방식을 정의한다(생성자)
4. 객체가 가지고 있어야 할 행위(메서드)를 정의한다.
기본 생성자: 파라미터가 아무것도 없는(필수값이 없는) 생성자package week03;
public class Car {
// 필드
String company; // 회사
String model; // 모델
String color; // 색상
double price; // 가격
double speed; // 속도
char gear; // 기어 상태
boolean lights; // 조명 상태
// 생성자(Constructor): 클래스명과 동일
public Car() {
}
// 메서드
// gasPedal: kmh 을 입력받아 속도를 kmh 만큼 변속한다.
double gasPedal(double kmh) {
speed = kmh;
return speed;
}
// brakePedal: 속도를 0으로 감속한다.
double brakePedal() {
speed = 0;
return speed;
}
// changeGear: type을 입력받아 기어 상태를 type으로 변경한다.
char changeGear(char type) {
gear = type;
return gear;
}
// onOffLights: 조명 상태를 켜거나 끈다.
boolean onOffLights() {
lights = !lights;
return lights;
}
// horn: 경적을 울린다.
void horn() {
System.out.println("빵빵");
}
}
이제 Car 클래스로 객체를 생성해보자.
new Car(); // Car 클래스 객체 생성
Car car1 = new Car(); // car1 인스턴스
Car car2 = new Car(); // car2 인스턴스
System.out.println(car1);
System.out.println(car2);

car1, car2 인스턴스를 콘솔에 출력하면 참조형 변수와 같이 주소가 반환되어 해당 클래스의 참조형 변수를 사용하여 받아줄 수 있다.
😮 클래스는 참조형 데이터 타입으로 생각
주소값을 대입한 참조형 변수에 .(dot)을 사용하면 공개된(public) 클래스 내에 선언한 필드와 메서드에 접근하여 사용할 수 있다.
객체는 참조형 변수와 동일하게 취급되기 때문에 배열 또는 컬렉션에도 저장하여 관리할 수 있다.
package week03;
public class Main {
public static void main(String[] args) {
Car[] carArray = new Car[3];
Car car1 = new Car();
car1.changeGear('P');
carArray[0] = car1;
Car car2 = new Car();
car2.changeGear('N');
carArray[1] = car2;
Car car3 = new Car();
car3.changeGear('D');
carArray[2] = car3;
for (Car car : carArray) {
System.out.println("car.gear = " + car.gear);
}
}
}

❓클래스가 참조형 데이터 타입이면, 클래스를 객체의 필드로 가질 수 있을까? 가질 수 있다!
자동차의 속성으로 바퀴, 문, 핸들을 가질 수 있다.
바퀴, 문, 핸들은 int도, String도 아니다 → Tire, Door, Handle 이라는 타입을 만들자.
public class Car {
// 필드
// 1) 고유 데이터 영역
String company; // 회사
String model; // 모델
String color; // 색상
// 2) 상태 데이터 영역
double price; // 가격
double speed; // 속도
char gear; // 기어 상태
boolean lights; // 조명 상태
// 3) 객체 데이터 영역
Tire tire;
Door door;
Handle handle;
// ...
}
필드는 객체의 데이터를 저장하는 역할
필드의 초기값과 초기화
필드를 선언만 하고 초기값을 할당하지 않을 경우 객체가 생성될 때 자동으로 기본값(default)으로 초기화된다.
| 데이터 타입 | 기본값 |
|---|---|
| byte | 0 |
| char | \u0000 (공백) |
| short | 0 |
| int | 0 |
| long | 0L |
| float | 0.0F |
| double | 0.0 |
| boolean | false |
| 배열 | null |
| 클래스 | null |
| 인터페이스 | null |
'필드를 사용한다'라는 의미는 필드의 값을 변경하거나 읽는 것을 의미한다.
외부 접근
객체를 생성(인스턴스화)했다면 참조변수를 이용하여 외부에서 객체 내부에 필드에 접근하여 사용
→ 필드 접근 방법: .(dot)연산자
⚠️ 하지만, 이렇게 외부에서 클래스 내부의 필드를 직접적으로 접근하여 값을 호출하거나 변경하는 것은 캡슐화가 되어있지 않다는 것을 의미하므로 보통 외부에서 필드에 접근하지 않도록 한다.
내부 접근
클래스 내의 메서드에서 동일 클래스 내부 필드에 접근할 수 있다.
double speed; // 속도
double brakePedal() {
speed = 0;
return speed;
}
메서드 brakePedal()은 필드인 speed를 호출하여 사용
package week03;
public class Main2 {
public static void main(String[] args) {
Car car = new Car(); // 객체 생성
// 초기값과 기본값 확인: 초기값을 준 것은 그 값이 들어가고, 아닌 값은 default value가 set
System.out.println("car.model = " + car.model);
System.out.println("car.color = " + car.color);
System.out.println();
System.out.println("car.speed = " + car.speed);
System.out.println("car.gear = " + car.gear);
System.out.println("car.lights = " + car.lights);
System.out.println();
System.out.println("car.tire = " + car.tire);
System.out.println("car.door = " + car.door);
System.out.println();
// 필드 사용
car.color = "blue";
car.speed = 100;
car.lights = false;
System.out.println("car.color = " + car.color);
System.out.println("car.speed = " + car.speed);
System.out.println("car.lights = " + car.lights);
}
}

메서드는 객체의 행위를 뜻하며 객체 간의 협력을 위해 사용됩니다.
리턴타입 메서드명(매개변수, ...) {
// 실행할 코드 작성
// return 리턴 타입의 반환값;
}
리턴 타입 (output)
메서드가 실행된 후 호출을 한 곳으로 값을 반환할 때 해당 값의 타입
- 반환할 값이 없을 때는 리턴 타입에 void를 작성
매개변수 (input)
메서드를 호출할 때 메서드로 전달하려는 값을 받기 위해 사용되는 변수
gasPedal(double kmh, char type) → gasPedal(100, 'D');가변 길이의 매개변수도 선언할 수 있다.
매개값을 , 로 구분하여 개수 상관 없이 전달 가능하다. (이 때, 가변 매개변수는 배열[array]이다.)
▶ Car.java
// 가변길이 매개변수 선언 -> 개수 상관 X
void carSpeeds(double ... speeds) {
for (double v : speeds) {
System.out.println("v = " + v);
}
}
▶ Main3.java
package week03;
public class Main3 {
public static void main(String[] args) {
Car car = new Car();
car.carSpeeds(100, 80);
System.out.println("-----");
car.carSpeeds(10, 30, 50, 80);
System.out.println("-----");
car.carSpeeds(110, 120, 150);
}
}

호출: '메서드명(매개 변수)'
'메서드를 호출한다' : 메서드의 블록 내부에 작성된 코드를 실행한다.
객체를 생성(인스턴스화)했다면 메서드를 사용할 수 있다.
필드와 마찬가지로 외부 접근과 내부 접근이 가능하다.
외부 접근
참조변수를 이용하여 외부에서 객체 내부 메서드에 접근하여 사용
→ 메서드 접근 방법: .(dot)연산자
이 때, 반드시 매개변수의 순서와 타입에 맞게 매개값을 넣어줘야 한다. (다른 메서드로 인식)
double gasPedal(double kmh, char type) {
changeGear(type);
speed = kmh;
return speed;
}
System.out.println("페달 밟기 전 car.gear = " + car.gear);
double speed = car.gasPedal(100, 'D');
System.out.println("speed = " + speed);
boolean lights = car.onOffLights();
System.out.println("lights = " + lights);
System.out.println("페달 밟기 후 car.gear = " + car.gear);

오버로딩(Overloading = 과적)은 하나의 메서드 이름으로 여러 기능을 구현하도록 하는 Java의 기능이다.
한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도, 매개변수의 개수 또는 타입, 순서가 다르면 동일한 이름을 사용해서 메서드를 정의할 수 있다.
1. 메서드 이름 하나로 상황에 따른 동작을 개별로 정의할 수 있다.
- println()의 매개변수로 int, double, String 등이 가능
2. 메서드 이름을 절약할 수 있다.
- 같은 기능을 수행하는 println() → printInt(), printDouble() 등으로 이름 짓지 않고 하나의 메서드명으로 정의 가능
기본형 매개변수
매개변수의 타입이 기본형일 때는 값 자체가 복사되어 넘어가기 때문에 매개값으로 지정된 변수의 원본 값이 변경되지 않는다.
참조형 매개변수
⭐️ 자바는 항상 변수의 값을 복사해서 대입한다.

📌 멤버 = 필드 + 메서드
인스턴스 멤버는 객체 생성 후에 사용할 수 있다.
클래스 멤버는 객체 생성 없이도 사용할 수 있다.
▶︎ 클래스 멤버 선언
필드와 메서드를 클래스 멤버로 만들기 위해서는 static 키워드를 사용하면 된다.
static String company = "GENESIS"; // 자동차 회사 : GENESIS
String getCompany() {
return "(주)" + company;
}
클래스 멤버는 객체 생성 없이 바로 사용 가능하기 때문에 객체가 생성되어야 존재할 수 있는 인스턴스 멤버를 사용할 수 없다.
참조형 변수를 사용하여 클래스 멤버에 접근은 가능하지만 추천하지 않는다.→ 마치 인스턴스 변수에 접근하는 것으로 오해할 수 있다.
✏️ IntelliJ IDE 에서 instance reference를 통해 정적 변수에 접근하는 것을 경고함
package week03.staticFolder;
public class Car {
static String company = "GENESIS"; // 자동차 회사 : GENESIS
// 클래스 메서드 안에서 클래스 필드인 company의 이름을 변경
static String setCompany(String companyName) {
// System.out.println("자동차 모델 확인: " + model); // 인스턴스 필드 사용 불가
company = companyName;
return company;
}
}
package week03.staticFolder;
public class Main {
public static void main(String[] args) {
// 클래스 필드 company 확인: Car.java 에서 선언
System.out.println(Car.company + "\n");
// 클래스 필드 변경 및 확인: 클래스명.필드명
Car.company = "Audi";
System.out.println(Car.company + "\n");
// 클래스 메서드 호출: 클래스명.메서드명
String companyName = Car.setCompany("Benz"); // Audi -> Benz
System.out.println("companyName = " + companyName);
System.out.println();
// 참조형 변수 사용
Car car = new Car(); // 객체 생성
car.company = "Ferrari"; // 인스턴스 필드
System.out.println(car.company + "\n");
// 클래스 메서드의 리턴값으로 클래스 필드 company 를 반환함
String companyName2 = car.setCompany("Lamborghini");
System.out.println("companyName2 = " + companyName2);
}
}
package week03.sample;
public class Main {
public static void main(String[] args) {
Main main = new Main();
System.out.println(main.getNumber());
System.out.println(main.getNumber());
}
// 메서드
public int getNumber() {
// [지역변수]
// 해당 메서드가 실행될 때 마다 독립적인 값을 저장하고 관리한다.
// 이 지역변수는 메서드 내부에서 정의될 때 생성된다.
// 이 메서드가 종료될 때 소멸된다.
int number = 1;
number += 1; // number = number + 1;
return number;
}
}
number = number + 1의 변경상태는 getNumber() 메서드 호출이 끝나면 사라진다.

final 이라는 키워드가 붙으면 더는 값을 변경할 수 없다.
단 하나만 존재하는 변하지 않는 고정된 값. 즉, 인스턴스 마다 상수를 저장할 필요없이 static 영역에 하나를 선언하여 그 값을 모든 인스턴스가 공유하며 변경이 불가능하도록 선언한다.
생성자는 객체가 생성될 때 호출되며 객체를 초기화하는 역할을 수행
Car(); 즉, 생성자가 호출된다.💡 Intellij Mac 자동 생성 단축키 :
⌘+n→ Constructor
기본 생성자는 선언할 때 괄호( ) 안에 아무것도 넣지 않는 생성자를 의미한다.
public class Car {
public Car(String model) {} // 생성자 선언
// 생성자가 한개 이상 선언되었기 때문에 기본 생성자를 추가하지 않음.
}
생성자는 객체를 초기화하는 역할을 수행한다.
객체를 만들 때 인스턴스마다 다른 값을 가져야 한다면 생성자를 통해서 필드를 초기화할 수 있다.
생성자를 초기화할 때 오버로딩을 적용할 수 있다.
public Car() {}
public Car(String color, double price) {}
public Car(String model, String color, double Price) {}
⚠️ 주의
오버로딩을 할 때 개수, 타입, 순서가 동일할 때 매개변수 명만 다르게 하는 경우 오버로딩 규칙에 위배되어 오류 발생
→ Car 생성자 (String, String, double)이 이미 정의되어 있습니다.
this는 객체 즉, 인스턴스 자신을 표현하는 키워드이다.
객체 내부 생성자 및 메서드에서 객체 내부 멤버에 접근하기 위해 사용한다.
Car returnInstance() {
return this;
}
this()는 인스턴스 자신의 생성자를 호출하는 키워드이다.
public Car(String model) {
this(model, "Blue", 50000000);
}
public Car(String model, String color) {
this(model, color, 100000000);
}
public Car(String model, String color, double price) {
this.model = model;
this.color = color;
this.price = price;
}
접근 제어자 : public, protected, default, private
멤버 또는 클래스에 사용, 외부에서 접근하지 못하도록 제한한다.
public : 접근 제한이 전혀 없습니다.protected : 같은 패키지 내에서, 다른 패키지의 자손 클래스에서 접근이 가능합니다default : 같은 패키지 내에서만 접근이 가능합니다.private : 같은 클래스 내에서만 접근이 가능합니다.▶︎ 사용 가능한 접근 제어자
클래스 : public, default
메서드, 멤버변수: public, protected, default, private
지역변수 : 없음
객체의 무결성 즉, 변경이 없는 상태를 유지하기 위해 접근 제어자를 사용합니다.
💡 Intellij Mac 자동 생성 단축키 :
⌘+n→ Getter / Setter
▶︎ Getter
외부에서 객체의 private 한 필드를 읽을 필요가 있을 때 Getter 메서드를 사용합니다.
▶︎ Setter
외부에서 객체의 private 한 필드를 저장/수정할 필요가 있을 때 Setter 메서드를 사용합니다.
📌 패키지란 클래스의 일부분이면서 클래스를 식별해 주는 용도입니다.
package 상위 패키지.하위 패키지; 이렇게 선언할 수 있습니다.사용하는 클래스와 패키지가 다를 때 마다 전체 경로를 포함한 클래스를 적는 것은 불편 → import를 사용하면 된다.
부모 클래스의 필드와 메서드를 자식 클래스에게 물려주는 것
public class 자식클래스 extends 부모클래스 {
}
public class SportsCar extends Car{
String engine;
public void booster() {
System.out.println("엔진 " + engine + " 부앙~\n");
}
}
Car를 상속받은 SportsCar는 Car의 필드와 메서드를 사용할 수 있다.
상속관계 : ~은 ~ 이다.
포함관계 : ~은 ~ 을 가지고 있다.
Java는 다중 상속을 허용하지 않습니다.
다중 상속을 허용하면 클래스 간의 관계가 복잡해지는 문제가 생기기 때문이다.
▶︎ final 클래스
final 클래스는 최종적인 클래스가 됨으로 더 이상 상속할 수 없는 클래스가 된다.

→ Cannot inherit from final : final 클래스는 상속받을 수 없다.
▶︎ final 메서드
final 메서드는 자식 클래스를 생성 후 호출은 할 수 있지만 자식 클래스에서 오버라이딩은 불가능하다. (final로 선언되어 변경할 수 없다.)

부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것을 오버라이딩이라고 합니다.
1. 선언부가 부모 클래스의 메서드와 일치해야 한다.
2. 접근 제어자를 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
3. 예외는 부모 클래스의 메서드보다 더 많이 선언할 수 없다.
“@Overriding” 애노테이션은 상위 클래스의 메서드를 오버라이드하는 것임을 나타낸다.
이름 그대로 오버라이딩한 메서드 위에 이 애노테이션을 붙여야 한다.
컴파일러는 이 애노테이션을 보고 메서드가 정확히 오버라이드 되었는지 확인한다. 오버라이딩 조건(메서드 이름이 일치하지 않는 경우 등)을 만족하지 않았으면 컴파일 에러를 발생시킨다.
실수로 오버라이딩을 못하는 경우를 방지해준다.

📌 super는 부모 클래스의 멤버를 참조할 수 있는 키워드입니다.
▶︎ Car :부모 클래스
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
▶︎ SuperCar : 자식 클래스
String model = "Ferrari"; // 자동차 모델
String color = "Red"; // 자동차 색상
double price = 300000000; // 자동차 가격
public void setCarInfo(String model, String color, double price) {
super.model = model;
super.color = color;
this.price = price;
}
▶︎ Main : 실행
// setCarInfo 메서드 호출해서 부모 및 자식 필드 값 저장
sportsCar.setCarInfo("GV80", "Black", 50000000);
// 결과 확인을 위해 자식 클래스 필드 model, color 확인 & 부모 클래스 메서드인 getModel(), getColor() 호출
// 자식 클래스 필드 값은 변화 없음.
System.out.println("sportsCar.model = " + sportsCar.model); // Ferrari
System.out.println("sportsCar.color = " + sportsCar.color); // Red
// this.price = price; 결과 확인
System.out.println("sportsCar.price = " + sportsCar.price); // 5.0E7

super(…)는 부모 클래스의 생성자를 호출할 수 있는 키워드입니다.
⚠️ 부모 클래스의 생성자를 자식 클래스의 생성자에서 호출하지 않으면 에러 발생
▶︎ Car 생성자
// 부모 클래스 Car 생성자
public Car(String model, String color, double price) {
this.model = model;
this.color = color;
this.price = price;
}
▶︎ SportsCar 생성자
// 자식 클래스 SportsCar 생성자
public SportsCar(String model, String color, double price, String engine) {
// this.engine = engine; // 오류 발생
super(model, color, price);
this.engine = engine;
}
다형성이란 ‘여러 가지 형태를 가질 수 있는 능력’을 의미합니다.
참조 변수 타입 변환을 활용해서 다형성을 구현할 수 있습니다.
자동 타입 변환
부모 타입 변수 = 자식 타입 객체; 는 자동으로 부모 타입으로 변환이 일어납니다.
강제 타입 변환
📌 자식 타입 변수 = (자식 타입) 부모 타입 객체;
다운캐스팅: 이럴 때는 (자식 타입) 즉, 타입 변환 연산자를 사용하여 강제로 자식 타입으로 변환할 수 있습니다.⚠️ 다만 무조건 강제 타입 변환을 할 수 있는 것은 아닙니다.
→ 자식 타입 객체가 부모 타입으로 자동 타입 변환된 후 다시 자식 타입으로 변환될 때만 강제 타입 변환이 가능합니다.
: 즉, 생성 시점에 자식 타입이 인스턴스에 같이 생성되어 있어야 형 변환이 가능함.
: 컴파일 오류로 잡히지 않기 때문에 개발자의 주의를 요구한다.
Mammal newMammal = new Mammal();
Whale newWhale = (Whale) newMammal; // ClassCastException 발생

다형성 기능으로 인해 해당 클래스 객체의 원래 클래스명을 체크하는 것이 필요한데 이때 사용할 수 있는 연산자가 instanceof 입니다.
추상 클래스는 미완성된 설계도입니다.
abstract 키워드를 사용하여 추상 클래스를 선언할 수 있습니다.
public abstract class 추상클래스명 {
}
public abstract class 추상클래스명 {
abstract 리턴타입 메서드이름(매개변수, ...);
}
상속받은 클래스에서 추상 클래스의 추상 메서드는 반드시 오버라이딩 되어야 합니다.
상속 관계가 없는 다른 클래스들이 서로 동일한 행위 즉, 메서드를 구현해야 할 때 인터페이스는 구현 클래스들의 동일한 사용 방법과 행위를 보장한다.
public interface 인터페이스명 {
public static final char A = 'A';
static char B = 'B';
final char C = 'C';
char D = 'D';
void turnOn(); // public abstract void turnOn();
}
public class 클래스명 implements 인터페이스명 {
// 추상 메서드 오버라이딩
@Override
public 리턴타입 메서드이름(매개변수, ...) {
// 실행문
}
}
인터페이스 간의 상속이 가능하다.
package week03.interfaceExample;
public class Main extends D implements C {
@Override
public void a() {
System.out.println("A");
}
@Override
public void b() {
System.out.println("B");
}
@Override
void d() {
super.d();
}
public static void main(String[] args) {
Main main = new Main();
main.a();
main.b();
main.d();
}
}
interface A {
void a();
}
interface B {
void b();
}
interface C extends A, B { }
class D {
void d() {
System.out.println("D");
}
}

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드입니다.
🤨 하지만, 인터페이스의 목적이 인터페이스를 구현(implements) 하는 곳에서 반드시 오버라이딩하여 기능을 완성시키도록 제약을 주는 것으로 default 메서드는 예외적으로 특별한 경우에만 사용해야 한다.
package week03.methodExample;
public class Main implements A {
@Override
public void a() {
System.out.println("A");
}
public static void main(String[] args) {
Main main = new Main();
main.a();
// 디폴트 메서드 재정의 없이 바로 사용가능합니다.
main.aa();
}
}
interface A {
void a();
default void aa() {
System.out.println("AA");
}
}
📌 인터페이스에서 static 메서드 선언이 가능합니다.
말하자면, 인터페이스에서 모든 인스턴스가 공통으로 쓸 static 메서드를 선언한 것과 동일하다.
public class Main implements A {
@Override
public void a() {
System.out.println("A");
}
public static void main(String[] args) {
Main main = new Main();
main.a();
main.aa();
System.out.println();
// static 메서드 aaa() 호출
A.aaa();
}
}
interface A {
void a();
default void aa() {
System.out.println("AA");
}
static void aaa() {
System.out.println("static method");
}
}
public class Main {
public static void main(String[] args) {
// A 인터페이스에 구현체 B 대입
A a1 = new B();
// A 인터페이스에 구편체 B를 상속받은 C 대입
A a2 = new C();
}
}
interface A { }
class B implements A {}
class C extends B {}
public class Main {
public static void main(String[] args) {
// A 인터페이스에 구현체 B 대입
A a1 = new B();
a1.a();
// a1.b(); // 불가능
System.out.println("\nB 강제 타입변환");
B b = (B) a1;
b.a();
b.b(); // 강제 타입변환으로 사용 가능
System.out.println();
// A 인터페이스에 구편체 B를 상속받은 C 대입
A a2 = new C();
a2.a();
//a2.b(); // 불가능
//a2.c(); // 불가능
System.out.println("\nC 강제 타입변환");
C c = (C) a2;
c.a();
c.b(); // 강제 타입변환으로 사용 가능
c.c(); // 강제 타입변환으로 사용 가능
}
}
interface A {
void a();
}
class B implements A {
@Override
public void a() {
System.out.println("B.a()");
}
public void b() {
System.out.println("B.b()");
}
}
class C extends B {
public void c() {
System.out.println("C.c()");
}
}

