Java 마스터하기: 객체, 클래스, 핵심 OOP 개념 이해
Java는 객체 지향 언어입니다. 객체 지향 기능을 가진 언어로서 Java는 다음 기본 개념들을 지원합니다:
- 다형성
- 상속
- 캡슐화
- 추상화
- 클래스
- 객체
- 인스턴스
- 메서드
- 메시지 전달
이 장에서는 클래스와 객체 개념을 살펴보겠습니다.
객체 - 객체는 상태와 행동을 가집니다. 예: 개는 색상, 이름, 품종 같은 상태와 꼬리 흔들기, 짖기, 먹기 같은 행동을 가집니다. 객체는 클래스의 인스턴스입니다.
클래스 - 클래스는 그 타입의 객체가 지원하는 행동/상태를 설명하는 템플릿/청사진으로 정의할 수 있습니다.
Java의 객체
객체가 무엇인지 깊이 살펴보겠습니다. 현실 세계를 고려하면 자동차, 개, 인간 등 많은 객체를 찾을 수 있습니다. 이 모든 객체는 상태와 행동을 가집니다.
개를 고려하면, 상태는 이름, 품종, 색상이고 행동은 짖기, 꼬리 흔들기, 달리기입니다.
소프트웨어 객체를 현실 세계 객체와 비교하면 매우 유사한 특성을 가집니다.
소프트웨어 객체도 상태와 행동을 가집니다. 소프트웨어 객체의 상태는 필드에 저장되고 행동은 메서드를 통해 나타납니다.
따라서 소프트웨어 개발에서 메서드는 객체의 내부 상태를 조작하고 객체 간 통신은 메서드를 통해 이루어집니다.
Java의 클래스
클래스는 개별 객체가 생성되는 청사진입니다.
다음은 클래스의 예입니다.
예제
public class Dog {
String breed;
int age;
String color;
void barking() { }
void hungry() { }
void sleeping() { }
}
클래스는 다음 변수 타입들을 포함할 수 있습니다.
지역 변수 - 메서드, 생성자, 블록 내부에서 정의된 변수들입니다. 변수는 메서드 내에서 선언되고 초기화되며 메서드 완료 시 소멸됩니다.
인스턴스 변수 - 클래스 내부에 있지만 메서드 밖에 있는 변수들입니다. 이 변수들은 클래스가 인스턴스화될 때 초기화됩니다. 인스턴스 변수는 해당 클래스의 모든 메서드, 생성자, 블록에서 접근 가능합니다.
클래스 변수 - 클래스 내부, 메서드 밖에 static 키워드로 선언된 변수들입니다.
클래스는 다양한 종류의 메서드 값을 접근하기 위한 여러 메서드를 가질 수 있습니다. 위 예제에서 barking(), hungry(), sleeping()은 메서드들입니다.
Java 언어의 클래스를 볼 때 논의해야 할 중요한 주제들:
생성자
클래스에 대해 논의할 때 가장 중요한 하위 주제 중 하나는 생성자입니다. 모든 클래스는 생성자를 가집니다. 클래스에 명시적으로 생성자를 작성하지 않으면 Java 컴파일러가 그 클래스의 기본 생성자를 만듭니다.
새 객체가 생성될 때마다 최소 하나의 생성자가 호출됩니다. 생성자의 주요 규칙은 클래스와 같은 이름을 가져야 한다는 것입니다. 클래스는 하나 이상의 생성자를 가질 수 있습니다.
다음은 생성자의 예입니다:
예제
public class Puppy {
public Puppy() { }
public Puppy(String name) {
// 이 생성자는 하나의 매개변수 name을 가집니다.
}
}
Java는 한 클래스의 인스턴스를 하나만 생성할 수 있는 싱글톤 클래스도 지원합니다.
참고 - 생성자는 두 가지 유형이 있습니다. 이후 장에서 생성자를 자세히 다룰 예정입니다.
객체 생성
앞서 언급했듯 클래스는 객체의 청사진을 제공합니다. 기본적으로 객체는 클래스에서 생성됩니다. Java에서는 new 키워드를 사용하여 새 객체를 생성합니다.
클래스에서 객체를 생성할 때 세 단계가 있습니다:
선언 - 객체 타입의 변수 이름과 함께 변수 선언.
인스턴스화 - 'new' 키워드로 객체 생성.
초기화 - 'new' 키워드 뒤에 생성자 호출이 옵니다. 이 호출이 새 객체를 초기화합니다.
다음은 객체 생성 예제입니다:
예제
Live Demo
public class Puppy {
public Puppy(String name) {
// 이 생성자는 하나의 매개변수 name을 가집니다.
System.out.println("Passed Name is :" + name );
}
public static void main(String []args) {
// 다음 문장은 myPuppy 객체를 생성합니다
Puppy myPuppy = new Puppy( "tommy" );
}
}
위 프로그램을 컴파일하고 실행하면 다음 결과가 나옵니다:
출력
Passed Name is :tommy
인스턴스 변수와 메서드 접근
인스턴스 변수와 메서드는 생성된 객체를 통해 접근합니다. 인스턴스 변수에 접근하는 완전한 경로는 다음과 같습니다:
/* 먼저 객체 생성 */
ObjectReference = new Constructor();
/* 이제 변수 호출 */
ObjectReference.variableName;
/* 이제 클래스 메서드 호출 */
ObjectReference.MethodName();
예제
이 예제는 클래스의 인스턴스 변수와 메서드에 접근하는 방법을 설명합니다.
Live Demo
public class Puppy {
int puppyAge;
public Puppy(String name) {
System.out.println("Name chosen is :" + name );
}
public void setAge( int age ) {
puppyAge = age;
}
public int getAge( ) {
System.out.println("Puppy's age is :" + puppyAge );
return puppyAge;
}
public static void main(String []args) {
/* 객체 생성 */
Puppy myPuppy = new Puppy( "tommy" );
/* 클래스 메서드 호출하여 강아지 나이 설정 */
myPuppy.setAge( 2 );
/* 다른 클래스 메서드 호출하여 강아지 나이 가져오기 */
myPuppy.getAge( );
/* 인스턴스 변수에 직접 접근도 가능 */
System.out.println("Variable Value :" + myPuppy.puppyAge );
}
}
위 프로그램을 컴파일하고 실행하면 다음 결과가 나옵니다:
출력
Name chosen is :tommy
Puppy's age is :2
Variable Value :2
소스 파일 선언 규칙
이 섹션의 마지막 부분으로 소스 파일 선언 규칙을 살펴보겠습니다. 이 규칙들은 소스 파일에서 클래스, import 문, package 문을 선언할 때 필수적입니다.
소스 파일당 public 클래스는 하나만 있을 수 있습니다.
소스 파일에는 여러 non-public 클래스가 있을 수 있습니다.
public 클래스 이름은 소스 파일 이름과 같아야 하며 끝에 .java가 붙어 있어야 합니다. 예: 클래스 이름이 public class Employee{}이면 소스 파일은 Employee.java여야 합니다.
클래스가 패키지 안에 정의되어 있으면 package 문이 소스 파일의 첫 번째 문장이어야 합니다.
import 문이 있으면 package 문과 클래스 선언 사이에 작성해야 합니다. package 문이 없으면 import 문이 소스 파일의 첫 줄이어야 합니다.
import와 package 문은 소스 파일에 있는 모든 클래스에 적용됩니다. 소스 파일의 서로 다른 클래스에 서로 다른 import/package 문을 선언할 수 없습니다.
클래스에는 여러 접근 수준이 있고 추상 클래스, final 클래스 등 다양한 타입의 클래스가 있습니다. 이것들은 접근 수정자 장에서 설명하겠습니다.
위에서 언급한 클래스 타입 외에도 Java에는 내부 클래스와 익명 클래스라는 특수한 클래스들이 있습니다.
Java 패키지
간단히 말해 클래스와 인터페이스를 분류하는 방법입니다. Java로 애플리케이션을 개발할 때 수백 개의 클래스와 인터페이스가 작성되므로 이들을 분류하는 것은 필수이며 삶을 훨씬 쉽게 만듭니다.
Import 문
Java에서 패키지와 클래스 이름을 포함한 완전히 자격 있는 이름이 주어지면 컴파일러가 소스 코드나 클래스를 쉽게 찾을 수 있습니다. Import 문은 컴파일러가 특정 클래스를 찾을 적절한 위치를 알려주는 방법입니다.
예를 들어 다음 줄은 컴파일러에게 java_installation/java/io 디렉토리에 있는 모든 클래스를 로드하라고 요청합니다:
import java.io.*;
간단한 사례 연구
사례 연구로 두 클래스를 만들겠습니다. Employee와 EmployeeTest입니다.
먼저 메모장을 열고 다음 코드를 추가하세요. 이것은 Employee 클래스이고 public 클래스입니다. 이제 이 소스 파일을 Employee.java라는 이름으로 저장하세요.
Employee 클래스에는 name, age, designation, salary 네 개의 인스턴스 변수가 있습니다. 클래스에는 매개변수를 받는 명시적 생성자가 하나 있습니다.
예제
import java.io.*;
public class Employee {
String name;
int age;
String designation;
double salary;
// Employee 클래스의 생성자
public Employee(String name) {
this.name = name;
}
// Employee의 나이를 age 변수에 할당
public void empAge(int empAge) {
age = empAge;
}
/* designation을 designation 변수에 할당 */
public void empDesignation(String empDesig) {
designation = empDesig;
}
/* salary를 salary 변수에 할당 */
public void empSalary(double empSalary) {
salary = empSalary;
}
/* Employee 세부사항 출력 */
public void printEmployee() {
System.out.println("Name:"+ name );
System.out.println("Age:" + age );
System.out.println("Designation:" + designation );
System.out.println("Salary:" + salary);
}
}
이 튜토리얼에서 앞서 언급했듯 처리는 main 메서드에서 시작합니다. 따라서 Employee 클래스를 실행하려면 main 메서드가 있어야 하고 객체들이 생성되어야 합니다. 이를 위해 별도 클래스를 만들겠습니다.
다음은 EmployeeTest 클래스로, Employee 클래스의 두 인스턴스를 생성하고 각 객체에 대해 메서드를 호출하여 각 변수에 값을 할당합니다.
다음 코드를 EmployeeTest.java 파일에 저장하세요.
import java.io.*;
public class EmployeeTest {
public static void main(String args[]) {
/* 생성자를 사용하여 두 객체 생성 */
Employee empOne = new Employee("James Smith");
Employee empTwo = new Employee("Mary Anne");
// 생성된 각 객체에 대해 메서드 호출
empOne.empAge(26);
empOne.empDesignation("Senior Software Engineer");
empOne.empSalary(1000);
empOne.printEmployee();
empTwo.empAge(21);
empTwo.empDesignation("Software Engineer");
empTwo.empSalary(500);
empTwo.printEmployee();
}
}
이제 두 클래스를 모두 컴파일하고 EmployeeTest를 실행하면 다음과 같은 결과를 볼 수 있습니다:
출력
C:\> javac Employee.java
C:\> javac EmployeeTest.java
C:\> java EmployeeTest
Name:James Smith
Age:26
Designation:Senior Software Engineer
Salary:1000.0
Name:Mary Anne
Age:21
Designation:Software Engineer
Salary:500.0
다음 단계
다음 세션에서는 Java의 기본 데이터 타입과 Java 애플리케이션 개발 시 활용 방법에 대해 논의하겠습니다.