객체(Objcet)들이 모여서 상호 협력하면서 데이터를 처리하는 방식의 프로그래밍 설계 방법을 일컫는다
키워드 : 재사용성 유지보수성 중복 코드 제거
클래스(class) : 객체를 정의해 놓은 것 (설계도)
객체(object) : 실제로 존재하는 것 (설계도로 만든 사물)
객체는 속성과 기능으로 이루어져 있다
인스턴스
설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체
즉, 객체를 소프트웨어에 실체화 하면 그것을 ‘인스턴스’라고 부른다
실체화된 인스턴스는 메모리에 할당된다
클래스명 변수명;new 클래스명();new 연산자가 인스턴스를 생성하고 생성자인 클래스명()은 변수를 초기화한다Fruit f = new Fruit(); // 참조변수 f의 타입은 Fruit, new 키워드로 생성된 인스턴스의 타입도 Fruit메서드(method) : 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것
클래스 내에 선언된 함수
메서드의 장점
메서드의 구조
int add(int x, int y) { // 선언부. 순서대로 반환타입, 메서드 이름, 매개변수
int result = x + y; // 구현부
return result;
}
매개변수
매개변수(parameter) : 함수에 전달되는 변수
| 구분 | 기본형 매개변수 (Primitive Type) | 참조형 매개변수 (Reference Type) |
|---|---|---|
| 데이터 타입 | int, double, char, boolean 등 | 배열, String, 객체(Class) 등 |
| 전달 방식 | 값(Call by Value)만 전달 | 참조(Call by Reference) 전달 |
| 원본 데이터 영향 | 없음 (복사본만 변경됨) → READ ONLY | 있음 (같은 객체를 참조) → READ & WRITE |
| 메모리 저장 위치 | 스택(Stack) 메모리 | 힙(Heap) 메모리 |
| 메모리 할당 방식 | 값 자체가 저장됨 | 객체의 주소(참조값)가 저장됨 |
| 예제 코드 | void modify(int x) { x = 10; } | void modify(int[] arr) { arr[0] = 10; } |
클래스 메서드와 인스턴스 메서드
| 구분 | 클래스(static) 메서드 | 인스턴스 메서드 |
|---|---|---|
| 선언 방식 | static 키워드 사용 | static 키워드 없음 |
| 소속 | 클래스 자체에 소속 | 객체(인스턴스)에 소속 |
| 호출 방법 | 클래스명.메서드명() | 객체명.메서드명() |
| 인스턴스 필요 여부 | 필요 없음 (객체 생성 없이 호출 가능) | 필요함 (객체 생성 후 호출 가능) |
| 멤버 변수 접근 | 클래스 변수(static 변수)만 접근 가능 | 모든 인스턴스 변수와 메서드에 접근 가능 |
| 사용 예시 | Math.pow(2,3); | String str = "hello"; str.length(); |
인스턴스 메서드에서는 클래스 메서드나 변수를 사용할 수 있지만,
클래스 메서드에서는 인스턴스 메서드나 변수를 사용할 수 없다
→ 인스턴스가 존재하지 않을 수도 있기 때문이다
인스턴스 멤버 간의 호출은 문제 없다
→ 하나의 인스턴스 멤버가 존재한다는 것이 인스턴스가 이미 존재한다는 것을 의미하기 때문이다
오버로딩(overloading) : 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것
void println()
void println(boolean x)
void println(char x)
void println(int x)가변인자(variable arguments) : 매개변수의 수를 가변적으로 조절할 수 있게 해주는 매개변수
선언 : 타입... 변수명
가변 인자는 매개변수 중 가장 마지막에 선언해야 한다 → 가변인자인지 아닌지를 구별할 방법이 없기 때문이다
내부적으로 배열을 사용하기 때문에 메서드를 호출할 때마다 배열이 생성된다 → 비효율적이므로 꼭 필요한 경우에만 사용해야 한다
public PrintStream printf(String format, Object... args) {...}String test(String s, String... args) {...}
String test(String... args) {...}
test("1", "2", "3"); // reference to test is ambiguous 에러 발생타입이 같아 첫 번째 인자와 가변인자가 구분되지 않으므로 어떤 메서드를 사용할 지도 모호한 상태가 된다생성자(constructor) : 인스턴스 초기화 메서드 (인스턴스 변수들을 초기화하는 것)
this : 객체 자기 자신을 의미하는 참조 변수
→ 인스턴스 변수와 지역 변수를 구분할 수 있다
예시
class Test {
String iv1;
int iv2;
Test() {...}
Test(String iv1, int iv2) {
this(); // 자신의 다른 생성자 호출
this.iv1 = iv1; // this.iv1은 자신의 인스턴스 변수, iv1은 입력받은 매개변수(지역 변수)
this.iv2 = iv2;
}
/** 원래 생성자 → 생략하면 변경에 유리하다
Test(생략) Test(Test this(생략), String iv1, int iv2) {
this.iv1 = iv1;
this.iv2 = iv2;
return this;(생략)
}
**/
}
생성자를 사용해서 인스턴스를 복사할 수 있다
Test t = new Test();
Test t2 = new Test(t);명시적 초기화 : int a = 10;
생성자 : 생성자 내에서 초기화
초기화 블럭
int a, b;
static int c, d;
//인스턴스 초기화 블럭
{
a++;
b = 20;
}
// 클래스 초기화 블럭
static {
c++;
d = 40;
}
상속(inheritance) : 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
extends 키워드를 사용하여 구현할 수 있다
class Child extends Parent {...}
출처 : https://www.tcpschool.com/java/java_inheritance_concept#google_vignette
특징
super : 상속한 조상 클래스를 의미하는 키워드
Object 클래스 : 모든 클래스의 조상
Object 클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자.this() 또는 super()를 호출해야 한다 (안했을 경우, 컴파일러가 자동으로 삽입한다)
조상 클래스의 멤버변수는 조상의 생성자에 의해 초기화되도록 해야한다
왜? 자손 클래스에서 조상 클래스의 멤버 변수를 사용하기 위해서
Q1. super.XXX 의 형태로 직접 초기화 하면 안되나?
super.x = 10 의 형태로 자손 클래스에서 초기화하면 캡슐화의 원칙에 위배 된다 → 유지보수에 불리하다Q2. setter를 사용해서 초기화 하면 안되나?
위 두 질문 자체가 모순된다
한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것
기존의 클래스를 재사용하는 또 다른 방법
class Circle {
Point p = new Point();
int r;
}오버라이딩(overriding) : 조상 클래스로부터 상속받은 메서드의 내용(구현부)을 변경하는 것
필요성
제한 사항
class Parent {
void parentMethod() throws IOException, SQLException {...}
}
class Child extends Parent {
void parentMethod() throws Exception {...}
}→ Exception은 모든 예외의 조상이므로 자손이 조상 클래스보다 더 많은 예외를 선언한 것이므로 잘못되었다package : 클래스의 묶음
import : 클래스의 패키지를 미리 명시하는 키워드
import 패키지명.클래스명;
import static 패키지명.클래스명;
클래스, 변수, 메서드에 부가적인 의미를 부여한다
접근 제어자
| 접근 제어자 | 적용 대상 | 같은 클래스 | 같은 패키지 | 자식 클래스 | 전체 접근 |
|---|---|---|---|---|---|
public | 클래스, 필드, 메서드 | ✅ 가능 | ✅ 가능 | ✅ 가능 | ✅ 가능 |
protected | 필드, 메서드 | ✅ 가능 | ✅ 가능 | ✅ 가능 | ❌ 불가능 |
default (명시 안함) | 클래스, 필드, 메서드 | ✅ 가능 | ✅ 가능 | ❌ 불가능 | ❌ 불가능 |
private | 필드, 메서드 | ✅ 가능 | ❌ 불가능 | ❌ 불가능 | ❌ 불가능 |
* 생성자에 private을 사용할 수 있다
→ 클래스 내부에서 인스턴스를 생성하여 외부로 반환할 수 있다
class Singleton {
private static Singleton s = new Singleton();
private Singleton() {...}
public static Singleton getInstance() { //static을 사용해서 인스턴스 없이 호출할 수 있게 해준다
return s;
}
}
→ 사용할 수 있는 인스턴스의 개수 제한
→ 다른 클래스에서 이 클래스의 생성자를 사용하지 못하므로 다른 클래스의 조상이 될 수 없다
그 외
| 제어자 | 적용 대상 | 설명 |
|---|---|---|
static | 필드, 메서드, 클래스(내부 클래스) | 클래스 변수 및 메서드를 정의하며, 인스턴스 없이 사용 가능 |
final | 클래스, 메서드, 필드, 변수 | 클래스 상속 금지, 메서드 오버라이딩 금지, 변수 값 변경 불가 |
abstract | 클래스, 메서드 | 추상 클래스 및 추상 메서드 정의 (구현 강제) |
synchronized | 메서드, 블록 | 멀티스레드 환경에서 동기화 수행 |
volatile | 필드 | 변수 값이 항상 메인 메모리에서 최신 상태 유지 (멀티스레드 환경) |
transient | 필드 | 직렬화(Serialization)에서 제외되는 변수 지정 |
strictfp | 클래스, 메서드 | 부동소수점 연산을 플랫폼에 관계없이 동일하게 수행 |
native | 메서드 | 네이티브 코드(C/C++ 등)에서 구현된 메서드 호출 |
제어자의 조합
다형성(polymorphism) : 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하는 것
Parent c = new Child();
Child p = (Child) new Parent(); // 컴파일은 되지만, 실행 시 에러
* 조상 클래스 타입이면 조상 클래스의 멤버만 사용 가능
* 조상 타입을 자손 타입으로 변경(Down-casting) 시 명시적으로 형변환해야 한다 → 실행 시 에러가 발생하기 때문에 허용되지 않는다
형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
단지 참조변수의 형변환을 통해서 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것뿐이다.
출처 : Java의 정석 3판 (저자 : 남궁성)
다형성에서 멤버의 사용
Parent p = new Child();
Child c = new Child();
p.x; // Parent 타입이므로 Parent의 x인 50
p.method(); // 인스턴스가 Child 이므로 Child의 메서드 사용
c.x; // Child 타입이므로 Child의 x인 10
c.method(); // 인스턴스가 Child 이므로 Child의 메서드 사용
class Parent {
int x = 50;
void method() {
System.out.println("Parent");
}
}
class Child extends Parent {
int x = 10;
void method() {
System.out.println("Child");
}
}
매개변수에 다형성을 적용시킴으로써 중복되는 코드를 제거할 수 있다
void add(Parent p) {
p.printSomthing();
return p.x + p.y;
} // Parent를 상속받는 클래스가 여러 개일 경우, add 메서드를 각 클래스별로 작성하지 않아도 된다
배열에서도 여러 자손 클래스를 사용할 수 있다
Parent[] pArr = new Parent[10]; // Parent를 상속받는 클래스들을 pArr의 요소로 사용할 수 있다
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용
참조변수 instanceof 타입(클래스명)
참조변수의 타입이 우측 타입과 일치하거나 우측 타입을 상속 받는 클래스(우측 타입의 자손 클래스)이면 true, 아니면 false를 반환한다
Child casting(Parent p) {
if (p instanceof Child) { // p는 Child의 자손이 아니므로 false 반환
Child c = (Child)p;
return c;
}
}정적 바인딩(Static Binding) : 컴파일 타임에 참조 변수의 타입으로 어떤 메서드를 호출할 지 결정하는 것
동적 바인딩(Dynamic Binding) : 런타임에 실제 객체의 타입으로 어떤 메서드를 호출할 지 결정하는 것
| 구분 | 정적 바인딩 (Static Binding) | 동적 바인딩 (Dynamic Binding) |
|---|---|---|
| 변수 | ✅ 항상 정적 바인딩 | ❌ 해당 없음 |
| 인스턴스 메서드 | ❌ 오버라이딩된 경우 제외 | ✅ 오버라이딩된 경우 적용 |
| static 메서드 | ✅ 정적 바인딩 | ❌ 동적 바인딩 불가 |
| final/private 메서드 | ✅ 오버라이딩 불가 → 정적 바인딩 | ❌ 동적 바인딩 불가 |
| 오버로딩 메서드 | ✅ 컴파일 시점에 결정 | ❌ 해당 없음 |
쉐도잉(Shadowing) : 자식 클래스에서 부모 클래스와 같은 이름의 변수를 선언해서 부모 클래스의 변수가 가려지는 현상
| 항목 | 쉐도잉 (Shadowing) | 오버라이딩 (Overriding) |
|---|---|---|
| 대상 | 변수 | 메서드 |
| 동작 방식 | 부모 클래스의 변수와 같은 이름으로 자식 클래스가 다시 정의 | 부모 클래스의 메서드를 자식 클래스가 재정의 |
| 바인딩 방식 | ✅ 정적 바인딩 (컴파일 시 타입 기준) | ✅ 동적 바인딩 (실행 시 객체 타입 기준) |
| 접근 기준 | 참조 변수의 타입 기준 | 실제 객체의 클래스 기준 |
| 실행 결과 예측 | 변수는 오버라이딩이 안되므로 항상 타입 기준으로 정해짐 | 메서드는 오버라이딩 가능, 실행 시 객체 기준으로 호출됨 |
| 대표 예 | int x = 10 in Parent, int x = 20 in Child | void speak() in Parent, overridden in Child |
| 주의점 | 헷갈리기 쉬움 → 변수는 오버라이딩이 아니라 숨김(hiding) | 메서드는 재정의되며 동작이 다형성(polymorphism)에 영향 |
class Parent {
int x = 10;
void show() { System.out.println("Parent"); }
}
class Child extends Parent {
int x = 20; // 🟡 쉐도잉
@Override
void show() { System.out.println("Child"); } // 🔵 오버라이딩
}
public class Main {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.x); // 🔸 10 → 변수는 정적 바인딩
p.show(); // 🔹 "Child" → 메서드는 동적 바인딩
}
}
추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
추상클래스(abstract class) : 하나 이상의 추상 메서드를 포함한 클래스. 미완성 설계도
추상메서드(abstract method) : 선언부만 작성한 메서드
인터페이스(interface) : 모든 메서드가 추상 메서드인 클래스. 기본 설계도
특징
리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다
출처 : Java의 정석 3판 (저자 : 남궁성)
→ 매개변수도 마찬가지로 구현한 클래스의 인스턴스를 입력해야 한다
장점
출처 : https://blog.naver.com/29java/70189390355디폴트 메서드
인터페이스에 새로운 메서드를 추가하는 것은 구현한 모든 클래스들이 구현을 해야하므로 디폴트 메서드로 기본적인 구현을 제공한다
static 메서드 : 인터페이스의 원칙에는 위배되지만 에러가 발생하지는 않는다
장점
단점
내부 클래스(inner class) : 클래스 내에 선언된 클래스. 두 클래스가 밀접한 관계에 있을 경우 사용
e.g. 외부 클래스의 데이터를 처리하거나 특정 연산을 수행하는 helper 클래스를 구현할 때
장점
종류와 특징
| 내부 클래스 종류 | 선언 위치 | 접근 가능 요소 | 주요 특징 |
|---|---|---|---|
| 인스턴스 클래스 | 클래스 내부 (static 아님) | 외부 클래스의 모든 멤버(필드, 메서드) | - 외부 클래스의 인스턴스를 생성해야 사용 가능 - 외부 클래스의 private 멤버에도 접근 가능 |
| 정적(static) 클래스 | 클래스 내부 (static 선언) | 외부 클래스의 static 멤버만 | - 외부 클래스의 인스턴스 없이 사용 가능 - static 멤버만 접근 가능 - 일반적인 클래스처럼 사용 가능 |
| 지역 클래스 | 메서드 내부 | 외부 클래스의 멤버 + 메서드의 final 지역 변수 | - 특정 메서드 내에서만 사용 가능 - 컴파일러가 자동으로 final 처리 (Java 8 이후) |
| 익명 클래스 | 메서드 내부 (new 연산자로 즉시 생성) | 외부 클래스의 멤버 + 메서드의 final 지역 변수 | - 이름이 없는 일회성 클래스 - 생성과 동시에 인스턴스 생성 - 주로 인터페이스나 추상 클래스 구현에 사용 |
메서드와 지역 클래스 인스턴스의 생명주기가 다르기 때문이다
Q1. 변경이 되든 안되든 그냥 사용하면 안되나?
서로 다른 문맥에서 사용되기 때문에 어느 한 쪽에서 변경하면 오류나 혼란(예기치 못한 동작)이 가능성이 있다
Q2. List 같은 객체는 내부 요소 변경이 가능한데 위와 같은 혼란이 생길 수 있지 않은가?
생길 수도 있다
→ 불변 컬렉션을 사용하거나 책임 분리를 명확하게 하여 혼란을 방지해야 한다
책임 분리 예시 : add 메서드는 add만. add 메서드에서 다른 로직을 수행하거나 다른 메서드에서도 add를 수행하지 않도록 한다