클래스 정의 클래스는 OOP의 핵심이다. 자바 프로그래밍은 결국 수많은 클래스를 정의하는 과정이다. 모든 프로그램은 그 자체로 하나의 클래스이며, 모든 소프트웨어는 클래스들의 집합으로 구성된다. 사용자가 정의하는 모든 타입은 곧 클래스이다
클래스는 곧 타입이다
- 클래스는 특별한 종류의 데이터 타입이다. 변수를 선언할 때 타입으로 사용할 수 있다
- 객체는 인스턴스와 같은 역할이다. ㅋ을래스라는 설계도로 만든 실제 결과물이다
- 클래스의 역할: 객체가 어떤 데이터를 담을 수 있는지, 그리고 어떤 동작을 할 수 있는지를 결정한다
클래스 정의의 구성 요소
클래스는 크게 두 가지 요소로 구성되며, 이를 통칭하여 멤버(Members)라고 부른다
데이터 항목(Data Items): 객체가 가지는 상태값이다
- 필드또는 인스턴스 변수라고 부른다
메서드: 객체가 수행할 수 있는 동작이다
클래스 정의 내에서 인스턴스 변수와 메서드 정의의 순서는 상관없지만, 보통 변수를 먼저 선언하고 메서드를 뒤에 배치하는 것이 관례이다
예제
//클래스 정의
public class DateFirstTry {
// 인스턴스 변수 (데이터)
public String month;
public int day;
public int year;
// 메서드 (동작)
public void writeOutput() {
System.out.println(month + " " + day + ", " + year);
}
}
//클래스 사용
public class DateFirstTryDemo {
public static void main(String[] args) {
// 1. 객체 생성 (인스턴스화)
DateFirstTry date1 = new DateFirstTry();
DateFirstTry date2 = new DateFirstTry();
// 2. 데이터 할당 (필드 접근)
date1.month = "December";
date1.day = 31;
date1.year = 2007;
// 3. 동작 수행 (메서드 호출)
System.out.println("date1:");
date1.writeOutput();
}
}new 연산자
자바에서 클래스는 설계도일 뿐이며, 이를 실제로 사용하려면 메모리에 ‘객체’를 만들어야 한다. 이때 사용하는 것이 바로 new 연산자이다
객체 변수의 선언 클래스 타입의 변수를 선언하여 객체에 이름을 붙여주는 단계이다. 이 단계에서는 아직 실제 객체가 생성된 것은 아니다
- 형식:
클래스이름 변수이름; - 예시:
ClassName classVar
객체 생성 및 연결
new 연산자를 사용하여 메모리에 실제 객체를 생성하고, 앞서 선언한 변수와 연결(참조)한다
- 형식:
변수이름 = new 클래스이름(); - 예시:
classVar = new ClassName();
한 문장으로 결합 선언과 생성을 동시에 처리하는 가장 흔한 방식이다
- 형식:
클래스이름 변수이름 = new 클래스이름(); - 예시:
ClassName classVar = new ClassName();
인스턴스 변수(Instance Variables)
객체가 가지는 ‘상태’나 ‘데이터’를 저장하는 변수이다
정의 방법: 클래스 내부에서 선언하며, 현재는 접근 제어자로 public을 사용한다
public String instanceVar1;
public int instanceVar2;- 변수 참조: 특정 객체의 인스턴스 변수를 가리키려면 객체 이름 뒤에 ”.”를 붙여 사용한다
- 예:
objectName.instanceVar1
- 예:
메서드의 구조(Method Definitions)
메서드는 객체의 ‘동작’을 정의하며, 크게 두 부분으로 나뉜다
헤딩(Heading): 메서드의 이름, 반환 타입, 매개변수 등을 선언하는 윗부분이다
- 예:
public void myMethod()
몸체(Body): 중괄호 {}안에 작성하며, 실제 동작을 수행하거나 값을 계산하는 코드가 들어간다
호출(Invocation): 메서드를 실행하려면 객체이름.메서드이름(); 형식을 사용한다. 이는 곧 메서드 바디의 코드를 실행하라는 의미와 같다
메서드의 두 가지 종류
메서드는 목적에 따라 크게 두 가지로 분류할 수 있다
| 종류 | 특징 | 선언 방식 |
|---|---|---|
| 값을 반환하는 메서드 | 계산을 수행하고 결과를 돌려줌 | 헤딩에 반환될 데이터 타입을 명시 |
| void 메서드 | 특정 동작만 수행하고 값을 돌려주지 않음 | 헤딩에 void 키워드를 사용 |
- 값 반환 메서드 예시:
public typeReturned methodName(paramList) - void 메서드 예시:
public void methodName(paramList)
main 메서드의 정체
우리가 항상 써왔던 main 메서드 역시 사실은 하나의 void 메서드이다
- 자바 프로그램:
main메서드를 가진 클래스 그 자체이다 - 실행 원리: 자바 실행 시스템(Runtime System)이 프로그램을 시작할 때 가장 먼저
main메서드를 호출한다 - 구조 분석:
public static void main(String[] args)- 여기서
void는main메서드가 실행 후 아무런 값도 반환하지 않음을 나타낸다
- 여기서
지역 변수
메서드가 실행되는 동안만 잠깐 살아있는 변수들이다
지역 변수
메서드 정의 내부에서 선언된 모든 변수를 지역 변수라고 부른다
특징:
main메서드 내부: 우리가 작성하는main메서드 안에 선언된 모든 변수 역시 지역 변수이다- 매개변수(Parameters): 메서드 이름 옆 괄호 안에 들어가는 매개변수들도 해당 메서드의 지역 변수로 취급한다
독립성: 서로 다른 두 메서드에 이름이 똑같은 지역 변수가 있더라도, 그들은 서로 완전히 다른 변수이다
전역 변수(Global Variables)
프로그램 어디에서나 접근할 수 있는 변수를 의미한다. 자바에는 전역 변수라는 것이 존재하지 않는다. 자바는 모든 것이 클래스 내부에 있어야 하는 객체지향 언어이기 때문에, 모든 변수는 클래스의 멤버(필드)이거나 메서드의 지역 변수여야 한다
블록(Block)
자바에서 중괄호로 묶인 부분을 블록이라고 한다. 블록 내부에서 선언된 변수는 해당 블록의 지역 변수가 된다
제한 사항: 블록 안에서 선언된 변수는 그 블록 외부에서는 사용할 수 없다
예시
{
int i = 5; // 블록 안에서 선언
System.out.println(i); // 가능
}
System.out.println(i); // 에러 발생! (변수 i를 찾을 수 없음)this 파라마터의 이해
this는 자바에서 현재 이 메서드를 호출한 바로 그 객체를 가리키는 특별한 예약어이다
기본 개념
모든 인스턴스 변수 앞에는 보이지 않는 호출한 객체명이 생략되어 있다고 생각하면 된다. 명시적으로 호출 객체를 지칭해야 할 때 this 키워드를 사용한다. 즉, myInstanceVariable은 언제나 this.myInstanceVariable과 동일한 의미이며 서로 바꿔 쓸 수 있다
코드 예시
//생략된 경우이다
public void writeOutput() {
// 사실은 this.month, this.day, this.year를 의미함
System.out.println(month + " " + day + ", " + year);
}반드시 this를 사용해야 하는 경우: 이름 충돌 해결
메서드의 매개변수(parameter)나 내부의 지역 변수 이름이 인스턴스 변수의 이름과 같을 때, this는 필수이다
이름 가리기(Shadowing): 매개변수와 인스턴스 변수의 이름이 같으면, 자바는 해당 이름을 무조건 지역 변수로 해석한다
- 해결책: 인스턴스 변수를 정확히 가리키기 위해 앞에
this.을 붙여준다
코드 예시:
public void setDate(int month, int day, int year) {
this.month = month; // 왼쪽은 클래스의 변수, 오른쪽은 전달받은 매개변수
this.day = day;
this.year = year;
}equals와 toString 메서드
자바에서는 거의 모든 클래스가 이 두 가지 메서드를 포함하도록 권장한다
equals 메서드
목적: 두 객체가 논리적으로 “같음”의 정의를 만족하는지 비교한다
반환 타입: boolean
선언 예시: public boolean equals(ClassName objectName)
주의
객체를 비교할 때
==연산자를 사용하면 안된다.==는 메모리 주소를 비교하지만,equals는 내용물을 비교하도록 설계된다
toString 메서드
목적: 객체 내부에 저장된 데이터를 대표하는 문자열 값을 반환한다
활용: 객체를 출력할 때 자동으로 호출되어 객체의 상태를 한 눈에 보여준다
선언 예시: public String toString()
정보 은닉(Information Hiding)과 캡슐화(Encapsulation)
이 갸념들은 복잡한 프로그램을 관리하기 쉽게 만드는 객체지향의 핵심 철학이다
정보 은닉(Information Hiding) 클래스의 사용 방법과 상세 구현 내용을 분리하는 연습이다
- 추상화(Abstraction): 정보 과부하를 피하기 위해 세부 사항을 생략하는 개념을 일컫는 또 다른 용어이다. “어떻게 돌아가는지” 몰라도 “어떻게 쓰는지”만 알면 된다
캡슐화(Encapsulation)
데이터와 메서드를 하나의 단위(클래스 객체)로 묶는 것을 의미한다. 주로 멤버 변수나 메서드 앞에 private 키워드를 붙여 외부 접근을 차단함으로써 달성한다
- 구현 세부 사항 숨기기: 잘 설계된 인터페이스를 통해서만 객체와 상호작용하므로, 내부 코드가 어떻게 짜여 있는지 알 필요가 없다
public과 private 접근 제어자
접근 제어자는 클래스 외부에서 특정 변수나 메서드에 접근할 수 었는 범위를 결정한다
public (공개): 인스턴스 변수나 메서드 사용에 제한이 없습니다. 어디서든 접근 가능하다
private (비공개): 클래스 외부에서 이름을 통해 접근할 수 없다. 클래스 내부에서만 사용할 수 있다
프로그래밍 관례:
- 모든 **인스턴스 변수는
private**으로 만드는 것이 좋은 설계 습관이다 - 대부분의 **메서드는
public**으로 설정하여 객체에 대한 제어된 접근을 제공한다 private메서드는 대개 클래스 내부의 다른 메서드를 돕기 위한 “보조 메서드”로 사용된다
접근자(Accessor)와 수정자(Mutator) 메서드
데이터를 private으로 숨겼기 때문에, 이를 안전하게 다루기 위한 통로가 필요하다
접근자(Accessor 메서드)
- 객체의 인스턴스 변수 값을 가져오는 역할을 한다
- 데이터를 읽을 수는 있지만 변경할 수는 없다
- 보통 get으로 시작하며, 흔히 Getter라고 부른다
수정자(Mutator 메서드)
- 객체의 인스턴스 변수 값을 변경하는 역할을 한다
- 값을 변경하기 전에 입력 데이터가 유효한지 검사하거나 필터링할 수 있어 안전하다
- 보통 set으로 시작하며, 흔히 Setter라고 부른다
캡슐화(Encapsulation)
캡슐화는 객체의 내부 구현을 숨기고 외부에는 필요한 인터페이스만 노출하는 원칙이다
숨겨진 부분:
private인스턴스 변수 및 상수private보조 메서드- 메서드의 실제 구현 코드 (Body)
공개된 부분 (인터페이스):
- 주석(설명문)
public메서드의 헤더(이름, 매개변수 등)
클래스를 사용하는 프로그래머는 내부가 어떻게 돌아가는지 알 필요 없이, 공개된 메서드(인터페이스)만 알면 객체를 사용할 수 있다
같은 클래스 내 객체 간의 비공개 멤버 접근
자바에서 아주 중요한 규칙 중 하나이다
규칙: 클래스 정의 내부에서는 해당 클래스로 생성된 모든 객체의 private 멤버에 접근할 수 있다. 즉, this의 데이터뿐만 아니라, 매개변수로 들어온 다른 동일 클래스 객체의 private 변수도 마음대로 읽고 쓸 수 있다
코드 분석(equals 메서드):
public boolean equals(DateSecondTry otherDate) {
// otherDate.month는 private이지만, 같은 클래스 내부이므로 접근 가능!
return (month.equalsIgnoreCase(otherDate.month) &&
day == otherDate.day &&
year == otherDate.year);
}오버로딩(Overloading)
동일한 클래스 내에서 이름이 같은 메서드를 두 개 이상 정의하는 것을 말한다
오버로딩 예시:
사용자가 다양한 방식으로 날짜를 설정할 수 있도록 setData라는 이름을 공유하는 여러 메서드를 만들 수 있다
public void setDate(int month, int day, int year)public void setDate(String month, int day, int year)public void setDate(int year)
메서드 시그니처(Signature) 오버로딩이 성립하려면 각 메서드의 시그니처가 서로 달라야 한다. 자바는 시그니처를 보고 어떤 메서드를 호출할지 결정한다
- 구성 요소: 메서드 이름 + 매개변수 목록(타입, 개수, 순서)
- 조건: 시그니처가 다르다는 것은 매개변수의 개수가 다르거나, 매개변수의 타입이 달라여 함을 의미한다
오버로딩과 자동 타입 변환
자바는 메서드를 호출할 때 시그니처가 완벽하게 일치하는 것을 먼저 찾지만, 없을 경우 자동 타입 변환을 시도한다
작동 방식: 예를 들어 int를 받는 메서드만 있는데 사용자가 바이트 값을 넣으면, 자바는 자동 변환하여 해당 메서드를 실행한다
모호한 호출(Ambiguous Invocation): 만약 자동 변환을 통해 호출할 수 있는 메서드가 여러 개라면, 자바는 어떤 것을 선택할지 결정하지 못해 컴파일 에러를 발생시킨다
주의
오버로딩과 자동 타입 변환이 복합적으로 작용하면 예상치 못한 메서드가 호출될 수 있다
주의점: 반환 타입으로는 오버로딩 불가
메서드의 반환 타입은 시그니처에 포함되자 않는다
불가능한 사례:
public int getVal() { ... }public double getVal() { ... }- 위와 같이 이름과 매개변수는 같은데 리턴 타입만 다른 경우는 자바에서 허용되지 않으며 컴파일 에러가 발생합니다.
자바 컴파일러는 메서드를 호출할 때 리턴 값을 어떻게 활용할지 미리 알 수 없기 때문에, 매개변수만으로 어떤 메서드를 실행할지 명확히 구분할 수 있어야 한다
생성자(Constructor)
생성자
생성자는 객체가 생성될 때 인스턴스 변수들을 초기화하기 위해 설계된 특별한 메서드이다
구문: public 클래스명(매개변수) { 초기화 코드 }
핵심 규칙:
- 이름: 반드시 클래스 이름과 동일해야 한다
- 반환 타입: 반환 타입 자체가 아예 없습니다. 심지어
void조차 쓰지 않는다 - 오버로딩: 한 클래스 내에 매개변수가 다른 여러 개의 생성자를 둘 수 있다(일반적으로 권장됨)
호출 방법
생성자는 일반적인 메서드처럼 아무 때나 호출할 수 있는 것이 아니다
호출 시점: new 연산자를 사용하여 클래스의 객체를 만들 때만 호출된다
형식: ClassName 객체명 = new ClassName(인자들);
특이사항: new 연산자 뒤에 오는 ClassName(인자들) 부분이 바로 생성자를 호출하는 코드이다. 생성자는 오직 이 방법으로만 실행할 수 있으며, 일반 메서드처럼 나중에 따로 호출하는 것은 불가능하다
예제 분석
import java.util.Scanner;
public class Date {
// 1. 비공개 인스턴스 변수 (정보 은닉)
private String month;
private int day;
private int year; // 4자리 숫자
// 2. 매개변수가 없는 생성자 (No-argument constructor)
public Date() {
month = "January";
day = 1;
year = 1000;
}
// 3. 숫자로 된 월, 일, 년을 받는 생성자
public Date(int monthInt, int day, int year) {
setDate(monthInt, day, year); // 생성자 내부에서 다른 메서드 호출 가능
}
// 4. 문자열로 된 월과 숫자 일, 년을 받는 생성자 (오버로딩)
public Date(String monthString, int day, int year) {
setDate(monthString, day, year);
}
// 5. 연도만 받는 생성자
public Date(int year) {
setDate(1, 1, year); // 매개변수가 없더라도 모든 변수를 초기화하는 것이 관례
}
// 6. 복사 생성자 (Copy Constructor)
public Date(Date aDate) {
if (aDate == null) { // null 체크: 실제 객체가 없는 경우 에러 처리
System.out.println("Fatal Error.");
System.exit(0);
}
// 다른 객체의 데이터를 내 변수에 복사
month = aDate.month;
day = aDate.day;
year = aDate.year;
}
}Date(): 매개변수가 없는 생성자(기본 생성자 역할). “January 1, 1000”으로 기본값을 설정Date(int m, int d, int y): 월, 일, 년을 숫자로 받아setDate메서드를 호출해 초기화한다Date(String m, int d, int y): 월을 문자열(예: “December”)로 받아 초기화한다Date(int year): 연도만 입력받고, 월과 일은 1월 1일로 고정하여 초기화한다Date(Date aDate): 다른Date객체를 복사하여 새로운 객체를 만든다
생성자 내에서의 메서드 호출
생성자가 실행될 때 객체 내부에서는 벌어는지 과정
첫 번째 작업: 생성자가 가장 먼저 하는 일은 인스턴스 변수들을 가진 객체를 메모리에 만드는 것이다
메서드 호출 가능: 객체가 이미 만들어진 상태에서 생성자 코드가 실행되므로, 생성자 정의 내부에서 다른 메서드를 호출하는 것이 가능하다
- 예를 들어, 값을 설정하는
set메서드를 생성자 안에서 사용하여 변수 값을 초기화할 수 있다
생성자 간 호출: 한 생성자가 동일한 클래스의 다른 생성자를 호출하는 것도 가능하다
기본 생성자(No-Argument Constructor) 포함하기
자바가 자동으로 만들어주는 생성자에 의존할 때 주의해야 할 점이다
자동 생성: 클래스에 생성자를 단 하나도 정의하지 않으면, 자바가 아무 인자도 받지 않는 기본 생성자(Default Constructor)를 자동으로 만들어 준다
자동 생성 중단: 하지만 여러분이 생성자를 하나라도 직접 정의하는 순간, 자바는 더 이상 기본 생성자를 제공하지 않는다
권장 사항: 다른 생성자를 만들더라도, 아무 인자 없이 객체를 생성할 수 있도록 직접 기본 생성자를 정의해 두는 것이 좋다
변수의 기본 초기화 규칙
자바는 우리가 값을 넣지 않아도 인스턴스 변수들을 특정 값으로 미리 채워준다
| 변수 타입 | 기본 초기화 값 |
|---|---|
| boolean | false |
| 기본 타입(int, double 등) | 각 타입의 0에 해당하는 값 |
| 클래스 타입(참조형) | null |
주의
- 자바가 자동으로 초기화해 주긴 하지만, 생성자에서 명시적으로 값을 초기화하는 것이 더 좋은 습관이다
- 지역 변수(Local variables)는 자동으로 초기화되지 않는다. 사용하기 전에 반드시 직접 값을 넣어줘야 에러가 나지 않는다
this 프로그램 예제 분석
제공된 코드 예제는 this 키워드가 어떻게 나 자신과 다른 객체를 구분하는지 보여준다
public class ThisTestClass {
public int i;
// 생성자
public ThisTestClass(int i) {
this.i = i; // this.i는 인스턴스 변수, i는 매개변수
}
public void writeOutput(ThisTestClass that) {
// this.i: 메서드를 호출한 나의 i
// that.i: 매개변수로 전달받은 다른 객체의 i
System.out.println("i is " + i + ", this.i is " + this.i + ", and that.i is " + that.i);
}
}a.writeOutput(b)를 호출하면:this는a가 되고,that은b가 돤다- 출력된 결과값들을 보면
this가 현재 코드를 실행 중인 주인공 객체를 정확히 가리키고 있음을 알 수 있다다