클래스와 객체
클래스는 값과 그 값을 다루는 동작을 함께 표현합니다. 객체를 생성하고 메서드를 호출한 뒤 상태를 읽는 순서로 살펴봅니다. 각 코드에서 필드·생성자·호출 대상을 찾고, 빌드·실행 버튼으로 현재 서버의 실제 결과를 확인할 수 있습니다. 실행 지원 한계는 현재 실행 확인 범위에 정리되어 있습니다.
필드와 생성자
섹션 제목: “필드와 생성자”Counter의 value는 객체가 보관하는 필드입니다. 생성자는 클래스와 이름이 같고 반환 타입을 쓰지 않습니다. Counter(10)으로 만들 때 전달한 초기값을 this.value에 저장합니다. 객체 생성에 new를 붙이지 않습니다.
class Counter {
int value;
public Counter(int initial) {
this.value = initial;
}
public void add(int step) {
this.value = this.value + step;
}
public int current() {
return this.value;
}
}
class CounterExample {
static void main() {
Counter first = Counter(10);
Counter second = Counter(0);
first.add(2);
print(first.current());
print(second.current());
}
}class Counter {
private int value;
public Counter(int initial) { value = initial; }
public void add(int amount) { value += amount; }
public int current() { return value; }
}
public class ClassCounter {
public static void main(String[] args) throws Exception {
Counter first = new Counter(10);
Counter second = new Counter(0);
first.add(2);
System.out.println(first.current());
System.out.println(second.current());
}
}class Counter:
def __init__(self, initial: int):
self._value = initial
def add(self, amount: int) -> None:
self._value += amount
def current(self) -> int:
return self._value
first = Counter(10)
second = Counter(0)
first.add(2)
print(first.current())
print(second.current())using System;
class Counter {
private int value;
public Counter(int initial) { value = initial; }
public void add(int amount) { value += amount; }
public int current() { return value; }
}
class ClassCounter {
static void Main() {
Counter first = new Counter(10);
Counter second = new Counter(0);
first.add(2);
Console.WriteLine(first.current());
Console.WriteLine(second.current());
}
}class Counter {
constructor(private value: number) {}
add(amount: number): void { this.value += amount; }
current(): number { return this.value; }
}
const first = new Counter(10);
const second = new Counter(0);
first.add(2);
console.log(first.current());
console.log(second.current());package main
import (
"fmt"
)
type Counter struct{ value int }
func (c *Counter) add(amount int) { c.value += amount }
func (c *Counter) current() int { return c.value }
func main() {
first := &Counter{value: 10}
second := &Counter{value: 0}
first.add(2)
fmt.Println(first.current())
fmt.Println(second.current())
}다른 언어의 탭은 직접 작성한 대응 코드이며 컴파일러의 자동 생성 결과가 아닙니다. 언어마다 API와 실행 환경이 다릅니다. 이 페이지의 빌드·실행은 Unibase 탭에서 사용할 수 있습니다.
Unibase 실행 결과
빌드·실행 메시지
출력 비교 기준
12
0
검증 상태: 문법 검사 통과 · 실행 출력 일치 (2026-10-06)
읽기 기준 결과와 실제 실행 출력 비교
코드를 읽어 정한 비교 기준입니다. 실행 성공 결과로 대신 표시하지 않습니다.
12
0실제 네이티브 실행: 빌드 passed, 실행 passed, 종료 코드 0
12
0현재 실행 확인 범위first.add(2)는 첫 객체의 값을 바꾸는 호출입니다. 코드 읽기 기준으로 첫 객체는 12, 두 번째 객체는 0을 보관합니다. 객체마다 필드가 따로 보관되는지 두 결과를 함께 확인해야 합니다.
this는 현재 객체입니다. 다른 클래스에서 사용하는 생성자와 메서드는 public으로 선언합니다. static 메서드는 객체를 만들지 않고 클래스 수준에서 호출할 때 사용합니다.
생성과 초기화의 책임
섹션 제목: “생성과 초기화의 책임”생성자는 객체를 처음 사용할 수 있는 상태로 만드는 곳입니다. Counter(10)은 초기값을 받는 계약이므로 인수를 생략하거나 문자열을 전달하는 호출과 다릅니다. 클래스 이름과 같은 생성자 이름, 매개변수의 개수·타입, 접근 범위를 함께 확인합니다.
필드를 직접 공개할 것인가
섹션 제목: “필드를 직접 공개할 것인가”Counter.value를 감추고 add()와 current()를 제공하면 값의 변경 경로가 분명해집니다. 모든 필드를 public으로 공개하면 다른 코드가 상태를 직접 바꿀 수 있습니다. 값을 조회하는 메서드와 변경하는 메서드를 분리하면 어느 호출이 상태를 바꾸는지 읽기 쉽습니다.
인스턴스와 static
섹션 제목: “인스턴스와 static”| 호출 형태 | 사용하는 상태 |
|---|---|
first.add(2) |
first라는 특정 객체의 상태 |
second.current() |
second의 별도 상태 |
static int square(int value) |
객체 없이 입력값을 사용하는 클래스 수준 계산 |
static 문맥에서 객체 없이 인스턴스 필드를 참조하는 코드는 현재 의미 검사에서 거절됩니다. 객체 상태가 필요하면 그 객체를 전달하거나 객체의 메서드로 호출하세요.
extends: 기존 클래스 확장
섹션 제목: “extends: 기존 클래스 확장”Worker extends Named는 이름 필드와 조회 메서드를 부모 클래스에서 가져오고, 자식 클래스에 index를 추가하는 구조입니다.
class Named {
public string name;
public string getName() {
return this.name;
}
}
class Worker extends Named {
public int index;
public Worker(string name, int index) {
this.name = name;
this.index = index;
}
}
class InheritanceExample {
static void main() {
Worker worker = Worker("worker", 3);
print(worker.getName());
print(worker.index);
}
}class Named {
public String name;
public String getName() { return name; }
}
class Worker extends Named {
public int index;
public Worker(String name, int index) { this.name = name; this.index = index; }
}
public class Inheritance {
public static void main(String[] args) throws Exception {
Worker worker = new Worker("worker", 3);
System.out.println(worker.getName());
System.out.println(worker.index);
}
}class Named:
def get_name(self) -> str:
return self.name
class Worker(Named):
def __init__(self, name: str, index: int):
self.name = name
self.index = index
worker = Worker("worker", 3)
print(worker.get_name())
print(worker.index)using System;
public class Named {
public string name;
public string getName() { return name; }
}
class Worker : Named {
public int index;
public Worker(string name, int index) { this.name = name; this.index = index; }
}
class Inheritance {
static void Main() {
Worker worker = new Worker("worker", 3);
Console.WriteLine(worker.getName());
Console.WriteLine(worker.index);
}
}class Named {
constructor(public name: string) {}
getName(): string { return this.name; }
}
class Worker extends Named {
constructor(name: string, public index: number) { super(name); }
}
const worker = new Worker("worker", 3);
console.log(worker.getName());
console.log(worker.index);package main
import (
"fmt"
)
type Named struct{ name string }
func (n Named) getName() string { return n.name }
type Worker struct {
Named
index int
}
func main() {
worker := Worker{Named: Named{name: "worker"}, index: 3}
fmt.Println(worker.getName())
fmt.Println(worker.index)
}Go에는 클래스 상속이 없습니다. Named를 내장한 Worker에서 메서드 승격으로 같은 호출을 구성합니다.
다른 언어의 탭은 직접 작성한 대응 코드이며 컴파일러의 자동 생성 결과가 아닙니다. 언어마다 API와 실행 환경이 다릅니다. 이 페이지의 빌드·실행은 Unibase 탭에서 사용할 수 있습니다.
Unibase 실행 결과
빌드·실행 메시지
출력 비교 기준
worker
3
검증 상태: 문법 검사 통과 · 실행 출력 일치 (2026-10-06)
읽기 기준 결과와 실제 실행 출력 비교
코드를 읽어 정한 비교 기준입니다. 실행 성공 결과로 대신 표시하지 않습니다.
worker
3실제 네이티브 실행: 빌드 passed, 실행 passed, 종료 코드 0
worker
3현재 실행 확인 범위worker.getName()은 부모에 선언한 메서드를, worker.index는 자식에 추가한 필드를 사용합니다. 상속은 두 클래스가 같은 기본 속성과 동작을 공유할 때 고려할 수 있습니다. 재정의와 동적 호출의 실행 지원을 이 예제로 확정하지 않습니다.
interface와 implements
섹션 제목: “interface와 implements”인터페이스는 구현할 메서드의 이름·매개변수·반환 타입을 선언합니다. Transform은 정수를 받아 정수를 반환하는 apply()를 요구하고, DoubleValue가 본문을 작성합니다.
interface Transform {
int apply(int value);
}
class DoubleValue implements Transform {
public int apply(int value) {
return value * 2;
}
}
class InterfaceExample {
static void main() {
DoubleValue transform = DoubleValue();
int result = transform.apply(7);
print(result);
}
}interface Transform { int apply(int value); }
class DoubleValue implements Transform {
public int apply(int value) { return value * 2; }
}
public class InterfaceTransform {
public static void main(String[] args) throws Exception {
Transform transform = new DoubleValue();
System.out.println(transform.apply(7));
}
}from abc import ABC, abstractmethod
class Transform(ABC):
@abstractmethod
def apply(self, value: int) -> int:
pass
class DoubleValue(Transform):
def apply(self, value: int) -> int:
return value * 2
transform: Transform = DoubleValue()
print(transform.apply(7))using System;
interface Transform { int apply(int value); }
class DoubleValue : Transform {
public int apply(int value) { return value * 2; }
}
class InterfaceTransform {
static void Main() {
Transform transform = new DoubleValue();
Console.WriteLine(transform.apply(7));
}
}interface Transform { apply(value: number): number; }
class DoubleValue implements Transform {
apply(value: number): number { return value * 2; }
}
const transform: Transform = new DoubleValue();
console.log(transform.apply(7));package main
import (
"fmt"
)
type Transform interface{ Apply(value int) int }
type DoubleValue struct{}
func (DoubleValue) Apply(value int) int { return value * 2 }
func main() {
var transform Transform = DoubleValue{}
fmt.Println(transform.Apply(7))
}다른 언어의 탭은 직접 작성한 대응 코드이며 컴파일러의 자동 생성 결과가 아닙니다. 언어마다 API와 실행 환경이 다릅니다. 이 페이지의 빌드·실행은 Unibase 탭에서 사용할 수 있습니다.
Unibase 실행 결과
빌드·실행 메시지
출력 비교 기준
14
검증 상태: 문법 검사 통과 · 실행 출력 일치 (2026-10-06)
읽기 기준 결과와 실제 실행 출력 비교
코드를 읽어 정한 비교 기준입니다. 실행 성공 결과로 대신 표시하지 않습니다.
14실제 네이티브 실행: 빌드 passed, 실행 passed, 종료 코드 0
14현재 실행 확인 범위이 코드는 구현 클래스에서 apply(7)을 호출하는 구조입니다. 현재 확인한 컴파일러는 구현 객체를 인터페이스 타입 변수에 대입하는 소스를 거절하므로, 인터페이스 참조를 통한 다형성 예제로 확대하지 않습니다.
Thread와 비동기 작업에서도 Runnable.run()과 Callable.call()의 구현을 사용합니다.
인터페이스를 구현할 때는 이름만 같은 메서드를 만드는 것이 아니라 매개변수·반환 타입과 공개 접근을 계약에 맞춰 작성합니다. void apply(int)는 int apply(int)의 구현이 아닙니다. 여러 구현을 교체해 사용하는 동적 호출은 구현 클래스의 직접 호출과 따로 검증해야 합니다.
조합: 다른 객체에 동작 맡기기
섹션 제목: “조합: 다른 객체에 동작 맡기기”객체를 필드로 보관하면 필요한 동작을 그 객체에 맡길 수 있습니다. Calculation은 Multiplier를 전달받고, run()에서 곱셈을 위임합니다.
class Multiplier {
int factor;
public Multiplier(int factor) {
this.factor = factor;
}
public int apply(int value) {
return value * this.factor;
}
}
class Calculation {
Multiplier multiplier;
public Calculation(Multiplier multiplier) {
this.multiplier = multiplier;
}
public int run(int value) {
return this.multiplier.apply(value);
}
}
class CompositionExample {
static void main() {
Calculation calculation = Calculation(Multiplier(2));
print(calculation.run(4));
}
}class Multiplier {
private int factor;
public Multiplier(int factor) { this.factor = factor; }
public int apply(int value) { return value * factor; }
}
class Calculation {
private Multiplier multiplier;
public Calculation(Multiplier multiplier) { this.multiplier = multiplier; }
public int run(int value) { return multiplier.apply(value); }
}
public class ObjectComposition {
public static void main(String[] args) throws Exception {
Calculation calculation = new Calculation(new Multiplier(2));
System.out.println(calculation.run(4));
}
}class Multiplier:
def __init__(self, factor: int):
self.factor = factor
def apply(self, value: int) -> int:
return value * self.factor
class Calculation:
def __init__(self, multiplier: Multiplier):
self.multiplier = multiplier
def run(self, value: int) -> int:
return self.multiplier.apply(value)
calculation = Calculation(Multiplier(2))
print(calculation.run(4))using System;
class Multiplier {
private int factor;
public Multiplier(int factor) { this.factor = factor; }
public int apply(int value) { return value * factor; }
}
class Calculation {
private Multiplier multiplier;
public Calculation(Multiplier multiplier) { this.multiplier = multiplier; }
public int run(int value) { return multiplier.apply(value); }
}
class ObjectComposition {
static void Main() {
Calculation calculation = new Calculation(new Multiplier(2));
Console.WriteLine(calculation.run(4));
}
}class Multiplier {
constructor(private factor: number) {}
apply(value: number): number { return value * this.factor; }
}
class Calculation {
constructor(private multiplier: Multiplier) {}
run(value: number): number { return this.multiplier.apply(value); }
}
const calculation = new Calculation(new Multiplier(2));
console.log(calculation.run(4));package main
import (
"fmt"
)
type Multiplier struct{ factor int }
func (m Multiplier) apply(value int) int { return value * m.factor }
type Calculation struct{ multiplier Multiplier }
func (c Calculation) run(value int) int { return c.multiplier.apply(value) }
func main() {
calculation := Calculation{multiplier: Multiplier{factor: 2}}
fmt.Println(calculation.run(4))
}다른 언어의 탭은 직접 작성한 대응 코드이며 컴파일러의 자동 생성 결과가 아닙니다. 언어마다 API와 실행 환경이 다릅니다. 이 페이지의 빌드·실행은 Unibase 탭에서 사용할 수 있습니다.
Unibase 실행 결과
빌드·실행 메시지
출력 비교 기준
8
검증 상태: 문법 검사 통과 · 실행 출력 일치 (2026-10-06)
읽기 기준 결과와 실제 실행 출력 비교
코드를 읽어 정한 비교 기준입니다. 실행 성공 결과로 대신 표시하지 않습니다.
8실제 네이티브 실행: 빌드 passed, 실행 passed, 종료 코드 0
8현재 실행 확인 범위호출 흐름은 calculation.run(4) → multiplier.apply(4) → 4 * 2입니다. Multiplier(3)을 전달하면 같은 호출 구조에서 배수를 바꿀 수 있습니다. 상속 대신 객체 관계로 동작을 구성하는 방식입니다.
객체 관계 선택하기
섹션 제목: “객체 관계 선택하기”| 관계 | 이 장의 코드 | 사용을 고려할 상황 |
|---|---|---|
| 상속 | Worker extends Named |
같은 기본 속성·동작을 확장 |
| 인터페이스 구현 | DoubleValue implements Transform |
구현할 메서드 계약을 표현 |
| 객체 조합 | Calculation이 Multiplier 보관 |
필요한 동작을 다른 객체에 위임 |
이름이 비슷하다는 이유만으로 상속 관계를 만들기보다, 호출자가 어떤 계약을 필요로 하는지 먼저 확인하세요. 변경할 동작을 생성자로 전달하는 조합은 그 관계를 소스에 드러냅니다.