💜 상속(extends)
: 기존 클래스 재사용 → 새로운 클래스 작성
보다 적은 양의 코드로 새로운 클래스 작성 ✅
코드 공통적으로 관리 → 코드 추가 및 변경 용이 ✅
코드 재사용성 높임
+ 코드 중복 제거
➡️ 프로그램 생산성 • 유지보수에 크게 기여 ⭐️
📍 새로 작성하는 클래스 뒤 → extends + 상속받는 클래스
class Parent {}
class Child extends Parent {}
서로 상속관계에 있음
조상클래스
: 상속해주는 클래스
자손클래스
: 상속받는 클래스
자손클래스는 조상클래스의 모든 멤버를 상속받음 (포함) ⭐️
(생성자, 초기화블럭 상속 ⛔️)
자손클래스 멤버개수 >= 조상클래스 멤버개수
상속
: 조상클래스를 확장(extends)하는 의미 ✅
📍 조상클래스
class Tv {
// 멤버(인스턴스)변수
boolean power;
int channel;
// 멤버메서드
void power() {power = !power;}
void channelUp() {++channel;}
void channelDown() {--channel;}
}
📍 자손클래스
class SmartTv extends Tv {
// 멤버(인스턴스)변수
boolean caption;
// 멤버메서드
void displayCaption(String text) {
if (caption) {
System.out.println(text);
}
}
}
class Object1 {
public static void main(String[] args) {
// 객체생성
SmartTv smartTv = new SmartTv();
// 객체멤버초기화
smartTv.channel = 10; // 조상클래스로부터 상속받은 멤버
smartTv.channelUp(); // 조상클래스로부터 상속받은 메서드 실행
System.out.println(smartTv.channel);
smartTv.caption = true;
smartTv.displayCaption("Hello, mallang!");
}
}
💜 클래스 간 포함관계
- 한 클래스의 멤버변수로 다른클래스의 객체를 선언해 포함하는 것
- 상속과는 다른개념
📍 기본클래스
class Circle {
int x; // x좌표
int y; // y좌표
int r; // 반지름
}
class Point {
int x; // x좌표
int y; // y좌표
}
📍 Point 클래스 재사용 → Circle 클래스 작성
class Circle {
Point point = new Point();
int r;
}
클래스에 다른클래스의 객체를 생성해 멤버로 활용하는 것 → 포함관계
Point 클래스
의 객체
를 Circle 클래스
에 선언 → int x
+ int y
활용가능 ✅
클래스를 작성 시,
① 상속관계(조상-자손)를 맺을 것인지
② 포함관계(다른클래스 객체 → 멤버변수 선언)를 맺을 것인지 결정
클래스 관계 | 구분방법 |
---|---|
상속관계 | ~은 ~이다 (is-a) |
포함관계 | ~은 ~을 가지고있다 (has-a) |
SportsCar는 Car이다 → 상속관계
Circle은 Point를 가지고있다 → 포함관계
💜 클래스 단일상속
- Java에서는 둘 이상의 클래스로부터 상속받을 수 없음 ⛔️
- 단일상속만 허용 ✅ (하나의 조상클래스로부터만 상속가능)
클래스 간 관계가 매우 복잡해짐 🚨
상속받은 멤버 간 이름 같은 경우 → 구별할 수 있는 방법 없음
클래스 간 관계가 보다 명확해짐 ⭐️
코드 신뢰도 향상
💜 Object 클래스
: 모든 클래스상속계층도의 최상위에 있는 조상클래스
다른 클래스로부터 상속받지않는 모든 클래스
➡️ 자동적으로 컴파일러가 Object 클래스 상속받게 함 ✅ (단일상속 준수)
다른 클래스로부터 상속받는 클래스
➡️ 조상클래스를 찾아 올라가다보면 결국 마지막 최상위는 Object 클래스
Java 모든 클래스 → Object 클래스를 상속받음
Object 클래스에 정의된 멤버 → 모든 클래스에서 사용 가능 ⭐️
toString()
equals(Object o)
💜 오버라이딩(overriding)
: 조상클래스로부터 상속받은 메서드내용 → 자손클래스 자신에 맞게 변경하는 것
상속받은 메서드 그대로 사용 가능하지만, 자손클래스 자신에 맞게 변경해야하는 경우가 더 많음
새로운 메서드 생성 ⛔️, 오버라이딩 ✅ ➡️ 객체지향언어 제대로 활용
📍 조상클래스
class Point {
// 멤버(인스턴스)변수
int x, y;
// 멤버메서드
String getLocation() {
return "x : " + x + ", y : " + y; // 2차원
}
}
📍 자손클래스
class Point3D extends Point {
// 자손 멤버(인스턴스)변수
int z;
// 메서드 오버라이딩
String getLocation() {
return "x : " + x + ", y : " + y + ", z : " + z; // 3차원 (자손에 맞게 변경)
}
}
class Object2 {
public static void main(String[] args) {
// 객체생성
Point3D point3D = new Point3D();
// 객체멤버초기화
point3D.x = 10;
point3D.y = 20;
point3D.z = 30;
// 오버라이딩메서드 실행
System.out.println(point3D.getLocation());
}
}
① 메서드선언부 (메서드이름, 매개변수, 반환타입) → 조상메서드와 일치 ✅
② 접근제어자 → 조상클래스 메서드보다 좁은범위로 변경불가 ⛔️
public
> protected
> default
> private
③ 예외 → 조상클래스 메서드 예외보다 많이 선언불가 ⛔️
📍 오버로딩 (overloading)
- 한 클래스 내 같은 이름의 메서드 여러개 정의하는 것
- 생성자에 많이 활용됨
- 매개변수 개수, type 달라야함
- 반환 type은 관계없음
📍 오버라이딩 (overriding)
- 상속받은 메서드의 내용을 변경하는 것
- 메서드선언부는 조상메서드와 동일해야함
class Parent {
void parentMethod() {}
}
class Child extends Parent {
void parentMethod() {} // 오버라이딩
void parentMethod(int i) {} // 오버로딩 (매개변수 type)
void childMethod() {}
void childMethod(int i) {} // 오버로딩 (매개변수 type)
}
💜 참조변수 super
: (자손 멤버이름 = 조상 멤버이름) → 자손클래스에서 조상클래스로부터 상속받은 멤버를 참조하는데 사용
참조변수 | 기능 |
---|---|
this | class 내에서 멤버(인스턴스)변수 자기자신을 가리킴 |
super | 자손class에서 조상class 멤버(인스턴스)변수를 가리킴 |
class Parent2 {
// 조상클래스 -> 멤버(인스턴스)변수
int x = 10;
}
class Child2 extends Parent2 {
// 자손클래스 -> 멤버(인스턴스)변수
int x = 20;
void method() {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x); // class내 자기자신
System.out.println("super.x = " + super.x); // 조상class 멤버변수
}
}
class Object3 {
public static void main(String[] args) {
// 객체생성
Child2 child2 = new Child2();
// 메서드실행
child2.method();
}
}
💜 참조변수 super
: 생성자는 상속이 되지 않으므로, 조상의 생성자를 호출하는데 사용함
생성자 | 기능 |
---|---|
this() | 같은 class 내에서 생성자 오버로딩 → 다른 생성자를 호출함 |
super() | 조상의 생성자를 호출함 |
생성자는 상속이 되지 않음
class 자신에 선언된 변수 → 자신의 생성자가 초기화를 책임져야 함
📍 this() 예시
class Car2 {
// 멤버(인스턴스)변수
String color;
String gearType;
int door;
// 생성자
Car2(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
// 생성자호출
Car2() { // 위의 생성자 호출 -> 초기화
this("white", "auto", 4);
}
Car2(String color) { // 위의 생성자 호출 -> 초기화 (color만 따로 초기화)
this(color, "auto", 4);
}
}
📍 super() 예시
class Point2 {
// 조상클래스 -> 멤버(인스턴스)변수
int x, y;
// 생성자
Point2 (int x, int y) {
this.x = x;
this.y = y;
}
}
class Point3D2 extends Point2 {
// 자손클래스 -> 멤버(인스턴스)변수
int z;
// 생성자
Point3D2 (int x, int y, int z) {
super(x, y); // 조상생성자 호출 Point2(int x, int y) {}
this.z = z;
}
}
class Object4 {
public static void main(String[] args) {
// 객체생성
Point3D2 point3D2 = new Point3D2(1, 2, 3);
System.out.println("x = " + point3D2.x + ", y = " + point3D2.y + ", z = " + point3D2.z);
}
}
💜 패키지(package)
: 클래스의 묶음 → 클래스 or 인터페이스를 포함함
서로 관련된 class → 그룹단위로 묶음 ➡️ 효율적으로 관리함
같은 이름의 class라도 서로 다른 패키지에 존재가능 ✅
class full name : 패키지명 + 클래스명
하나의 디렉토리와 같은 개념
📍 package 패키지명;
패키지 선언문 → 소스파일(.java) 주석 및 공백 제외, 첫번째 문장에 작성 ⭐️
하나의 소스파일(.java) → 패키지 단 한번만 선언가능
클래스명과 구분을 위해 소문자사용
패키지 선언 ❌ → 이름없는 패키지에 자동으로 속함
💜 import문
: 다른 패키지 class 사용 시 → 컴파일러에게 다른 소스파일에 사용된 class의 패키지에 대한 정보를 제공함
소스코드 작성 시, 다른 패키지 class 사용 → 패키지명이 포함된 class 이름을 사용해야함
import문으로 사용할 class 패키지 미리 명시 → 패키지명 생략가능 ✅
📍 import문 사용 ❌
java.util.Date today = new java.util.Date();
📍 import문 사용 ✅
import java.util.Date;
Date today = new Date(); // 패키지 생략가능
📍 import 패키지명.class명;
// 사용하는 class마다 모두 선언해야함
📍 import 패키지명.*;
// 패키지 하위 모든 class 사용가능
// 실행 시 성능차이 없음
package문
→ import문
→ class선언문
package문과 달리, import문은 여러번 선언가능
static
멤버 호출 시, 클래스이름(패키지명 + 클래스명) 생략 가능
static 멤버 변수 or 메서드
*
사용 → 하위 모든 static 멤버 사용가능
메서드 괄호 () → 생략함
import static java.lang.System.out;
import static java.lang.Math.*;
class Object5 {
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);
}
}