객체란 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중 자신의 속성을 가지고 있으면서 식별 가능한 것을 의미한다.
객체 지향 프로그래밍에서 객체 간의 상호작용은 주로 메서드를 통해 이루어집니다. 메서드는 객체가 수행하는 특정 작업이나 동작을 나타내는 함수입니다. 이 메서드를 통해 다른 객체는 해당 객체의 상태에 접근하거나 특정 동작을 유발할 수 있습니다.
메서드는 객체의 특정 행동을 정의하고, 이를 호출하여 해당 행동을 수행합니다. 객체의 상태는 메서드를 통해서만 변경되며, 외부에서 직접 접근할 수 없도록 캡슐화되어 있습니다. 이로써 객체의 내부 구현이 외부로부터 숨겨지고, 객체 간의 인터페이스를 통한 상호작용이 간소화됩니다.
객체의 상호작용은 메시지 전달을 통해 이루어지는데, 메시지는 다른 객체에 대한 요청 또는 명령을 의미합니다. 객체는 이 메시지를 받아서 해당하는 메서드를 실행하고, 그 결과로 다시 메시지를 반환할 수 있습니다. 이런 방식으로 객체 간의 협력이 이루어지며, 전체 시스템이 유연하게 동작하게 됩니다.
메서드 호출을 통해 객체들은 데이터를 주고 받습니다. 이 과정에서 메서드에는 매개변수(parameter)가 사용됩니다. 매개변수는 메서드가 호출될 때 전달되는 데이터를 받는 변수로서, 메서드가 수행될 때 이 데이터를 활용하여 특정 작업을 수행합니다.
메서드는 종종 입력값을 필요로 하며, 이러한 입력값은 매개변수를 통해 전달됩니다. 매개변수는 메서드 정의에서 선언되며, 메서드가 호출될 때 실제 값이 전달됩니다. 이렇게 전달된 값은 메서드 내에서 해당 매개변수를 통해 활용됩니다.
public class Car {
private int currentSpeed;
// Method definition for accelerating
public void accelerate(int speed) {
// Performing acceleration using the speed parameter
this.currentSpeed += speed;
}
// Method to get current speed
public int getCurrentSpeed() {
return this.currentSpeed;
}
public static void main(String[] args) {
// Creating a car object
Car myCar = new Car();
// Calling the accelerate method
myCar.accelerate(20);
// Printing the current speed
System.out.println("Current speed: " + myCar.getCurrentSpeed() + " km/h");
}
}
객체 지향 프로그래밍에서 객체 간의 관계는 주로 세 가지 유형으로 나뉩니다.
집합 관계 (Composition/Aggregation): 이 관계에서는 하나의 객체가 다른 객체를 포함하거나 그 객체의 일부로 간주됩니다. 완성품과 부품의 관계와 유사하며, 한 객체의 수명이 다른 객체에 의해 영향을 받는 경우가 많습니다.
사용 관계 (Dependency): 이 관계에서는 한 객체가 다른 객체를 사용하거나 그 객체의 기능을 호출합니다. 다른 객체의 필드를 읽고 변경하거나 메서드를 호출하여 작업을 수행하는 관계입니다.
상속 관계 (Inheritance): 이 관계에서는 부모 클래스와 자식 클래스 사이의 관계가 있습니다. 자식 클래스는 부모 클래스의 모든 속성과 메서드를 상속받아 사용할 수 있습니다. 이를 통해 코드 재사용성을 높일 수 있습니다.
이러한 객체 간의 관계를 통해 객체 지향 프로그래밍에서 유연하고 모듈화된 설계를 할 수 있습니다.
객체 지향 프로그래밍(OOP)은 몇 가지 중요한 특징을 가지고 있습니다.
캡슐화 (Encapsulation): 객체의 상태(데이터)와 행동(메서드)을 하나로 묶어 외부에서의 접근을 제어하는 것을 말합니다. 캡슐화는 객체의 내부 구현을 외부로부터 감추어 정보 은닉을 통해 객체의 안정성과 보안을 강화합니다.
상속 (Inheritance): 부모 클래스에서 정의된 속성과 메서드를 자식 클래스에서 재사용할 수 있게 하는 메커니즘입니다. 이를 통해 코드 재사용성이 증가하며, 계층 구조를 통해 일반적인 특성을 상위 클래스에서 정의하고 특수한 특성을 하위 클래스에서 추가할 수 있습니다.
다형성 (Polymorphism): 동일한 인터페이스를 가진 여러 객체가 각각의 특정 구현을 가지고 있는 것을 의미합니다. 다형성은 같은 메서드 호출이 다른 객체에 따라 다르게 구현될 수 있음을 나타내며, 이는 코드의 유연성과 확장성을 높여줍니다.
이러한 특징들은 객체 지향 프로그래밍의 기본 원칙으로서, 모듈화, 재사용성, 유지보수성 등을 증가시키고 복잡한 시스템을 효과적으로 설계하고 구현할 수 있도록 도와줍니다.
클래스 (Class): 클래스는 객체를 만들기 위한 설계도 혹은 틀입니다. 클래스는 객체가 가져야 할 속성(멤버 변수)과 동작(메서드)을 정의합니다. 즉, 클래스는 어떤 종류의 객체를 생성할 것인지에 대한 템플릿 역할을 합니다.
예를 들어, 자동차 클래스는 자동차 객체가 가져야 할 속성(색상, 속도 등)과 동작(가속, 제동 등)을 정의하는데 사용될 수 있습니다.
객체 (Object): 객체는 클래스의 인스턴스로, 클래스를 기반으로 생성된 실제 데이터입니다. 클래스를 사용하여 객체를 생성하면, 해당 객체는 클래스에서 정의한 속성과 동작을 가지게 됩니다.
// Class definition for a Car
public class Car {
// Member variables (attributes)
String color;
int speed;
// Method (behavior)
public void accelerate(int speed) {
this.speed += speed;
}
}
// Main class to demonstrate creating and using objects
public class Main {
public static void main(String[] args) {
// Creating an object based on the Car class
Car myCar = new Car();
// Using the object's attributes and behavior
myCar.color = "Blue";
myCar.accelerate(30);
System.out.println("Color: " + myCar.color);
System.out.println("Speed: " + myCar.speed);
}
}
클래스로부터 생성된 객체를 해당 클래스의 인스턴스(instance)라고 부릅니다. 객체 지향 프로그래밍에서 클래스는 일종의 설계도이고, 이 설계도를 기반으로 실제 데이터를 가지는 객체가 만들어집니다. 이때, 이 객체는 해당 클래스의 인스턴스로 취급됩니다.
예를 들어 앞서 언급한 자동차 클래스의 경우 Car 클래스로부터 생성된 실제 자동차 객체는 Car 클래스의 인스턴스입니다. 인스턴스는 클래스에 정의된 특성과 행동을 가지며, 클래스의 설계에 따라 동작합니다.
public class ClassName {
// Define class members such as variables and methods here
}
public class Car {
// Member variables (attributes)
String color;
int speed;
// Method (behavior)
public void accelerate(int speed) {
this.speed += speed;
}
}
객체를 생성하기 위해서는 new 연산자를 사용합니다. 클래스 변수에 대입할 때는 다음과 같은 구문을 사용합니다:
ClassName variableName = new ClassName();
Car myCar = new Car();
라이브러리 클래스와 실행 클래스는 다음과 같이 정의할 수 있습니다:
라이브러리 클래스 (Library Class): 라이브러리 클래스는 실행 가능한 main()
메서드가 없으며, 주로 다른 클래스에서 재사용되는 클래스입니다. 라이브러리 클래스는 다양한 기능을 제공하고, 다른 프로그램에서 이용할 수 있도록 설계되어 있습니다.
실행 클래스 (Executable Class): 실행 클래스는 main()
메서드를 포함하고 있어 프로그램을 실행할 수 있는 클래스입니다. 이 클래스는 일반적으로 프로그램의 시작점으로 사용되며, 프로그램의 진입점(entry point)이라고도 합니다. 실행 클래스에서는 프로그램의 시작 및 종료, 그리고 다른 클래스 및 라이브러리를 활용하여 프로그램의 흐름을 조절합니다.
// 라이브러리 클래스
public class LibraryClass {
public void doSomething() {
System.out.println("Library class is doing something.");
}
}
// 실행 클래스
public class ExecutableClass {
public static void main(String[] args) {
// 라이브러리 클래스의 인스턴스 생성 및 사용
LibraryClass libraryObject = new LibraryClass();
libraryObject.doSomething();
// 실행 클래스의 로직
System.out.println("Executable class is running.");
}
}
클래스의 선언에는 필드, 생성자, 그리고 메서드를 정의할 수 있습니다.
public class MyClass {
// 필드 선언
int myField;
String anotherField;
}
public class MyClass {
// 필드 선언
int myField;
String anotherField;
// 생성자 선언
public MyClass(int initialValue, String initialString) {
this.myField = initialValue;
this.anotherField = initialString;
}
}
public class MyClass {
// 필드 선언
int myField;
String anotherField;
// 생성자 선언
public MyClass(int initialValue, String initialString) {
this.myField = initialValue;
this.anotherField = initialString;
}
// 메서드 선언
public void myMethod() {
System.out.println("This is a method in MyClass.");
}
}
이러한 선언을 통해 클래스는 필드를 통해 상태를 나타내고, 생성자를 통해 초기화를 수행하며, 메서드를 통해 특정 동작을 정의합니다.
필드와 로컬 변수는 선언된 위치, 생명 주기, 및 접근성 등에서 차이가 있습니다.
public class MyClass {
// 필드 선언
int myField;
}
public class MyClass {
// 메서드 선언
public void myMethod() {
// 로컬 변수 선언
int localVar = 10;
}
}
로컬 변수는 주로 메서드나 생성자 내에서 일시적인 데이터를 저장하는 데 사용되며, 필드는 객체의 상태를 표현하는 데 사용됩니다.
필드가 객체 생성 시에 초기값을 명시적으로 지정되지 않으면 해당 필드는 자바에서 정의한 기본값으로 자동으로 초기화됩니다. 초기화되는 기본값은 필드의 데이터 타입에 따라 정해집니다. 아래는 몇 가지 기본 데이터 타입에 대한 기본값입니다:
byte
, short
, int
, long
): 0float
, double
): 0.0boolean
): falsechar
): '\u0000' (null character)class
, interface
, array
): null예를 들어, 다음과 같은 클래스에서 int
와 boolean
타입의 필드는 객체 생성 시에 각각 0과 false로 초기화됩니다:
public class MyClass {
int myIntegerField; // 초기값: 0
boolean myBooleanField; // 초기값: false
}
따라서 명시적으로 초기값을 제공하지 않았을 때, 필드는 자동으로 해당 타입의 기본값으로 초기화됩니다.
필드는 객체의 데이터를 나타내기 위한 변수이며, 따라서 해당 필드를 사용하려면 객체가 이미 생성되어야 합니다. 필드는 객체의 인스턴스 변수로서, 클래스로부터 생성된 객체에 속하며 객체의 상태를 나타냅니다.
public class MyClass {
// 필드 선언
int myField;
// 메서드 선언
public void setMyField(int value) {
// 객체의 필드에 값 할당
this.myField = value;
}
public void printMyField() {
// 객체의 필드 값 출력
System.out.println("myField: " + this.myField);
}
public static void main(String[] args) {
// MyClass 객체 생성
MyClass myObject = new MyClass();
// 객체의 메서드를 호출하여 필드 조작
myObject.setMyField(42);
myObject.printMyField(); // 출력: myField: 42
}
}
객체 내부에서는 필드에 직접 접근하여 읽고 변경할 수 있지만, 외부에서는 객체에 대한 참조 변수와 도트 연산자(.
)를 사용하여 필드에 접근해야 합니다. 이것은 객체 지향 프로그래밍에서 정보 은닉(Encapsulation) 개념과 관련이 있습니다.
객체 내부에서 필드 읽고 변경:
public class MyClass {
// 필드 선언
int myField;
// 메서드 내에서 필드 읽고 변경
public void updateField() {
myField = 42; // 필드 변경
int value = myField; // 필드 읽기
}
}
외부에서 참조 변수와 도트 연산자를 사용하여 필드 읽고 변경:
public class Main {
public static void main(String[] args) {
// MyClass 객체 생성
MyClass myObject = new MyClass();
// 외부에서 객체의 필드에 접근하여 읽고 변경
myObject.myField = 10; // 필드 변경
int value = myObject.myField; // 필드 읽기
}
}
객체 내부에서는 필드에 직접 접근하는 것이 일반적으로 허용되지만, 외부에서는 객체의 상태를 일관되게 유지하기 위해 필드에 직접 접근하는 대신 메서드를 통한 간접 접근을 선호하는 경우가 많습니다. 이는 정보 은닉을 통해 객체의 내부 구현을 외부로부터 감추고, 객체의 상태를 제어하고 유지보수하기 쉽게 만듭니다.
생성자는 객체가 생성될 때 호출되어 초기화 작업을 수행하는 특별한 메서드입니다. new
연산자를 사용하여 객체를 생성할 때 생성자가 호출되어 객체의 초기화를 담당합니다. 객체 초기화는 필드 초기화, 메서드 호출 등을 포함합니다.
생성자 선언은 클래스 내부에서 이루어지며, 생성자는 클래스의 이름과 동일합니다. 생성자는 리턴 타입을 가지지 않고, 객체 생성 시 한 번 호출됩니다.
public class MyClass {
// 필드 선언
int myField;
// 생성자 선언
public MyClass() {
// 생성자에서 필드 초기화
myField = 42;
System.out.println("Constructor is called. myField is initialized to " + myField);
}
// 메서드 선언
public void someMethod() {
System.out.println("someMethod is called. myField is " + myField);
}
public static void main(String[] args) {
// 생성자 호출을 통한 객체 생성
MyClass myObject = new MyClass();
// 생성된 객체의 메서드 호출
myObject.someMethod();
}
}
위 예제에서 MyClass
의 생성자는 객체가 생성될 때 호출되어 myField
를 초기화하고 메시지를 출력합니다. 생성자는 main
메서드에서 new MyClass()
로 객체를 생성할 때 호출되며, 생성된 객체의 someMethod
메서드가 호출될 때 myField
값이 출력됩니다.
필드를 선언할 때 초기값을 대입하면 해당 필드는 모든 객체에 대해 동일한 초기값을 가지게 됩니다. 이는 모든 객체가 동일한 상태로 시작하도록 하는 편리한 방법입니다.
반면에 생성자를 사용하여 필드를 초기화하면 각 객체마다 다른 값을 가질 수 있습니다. 생성자를 통해 객체를 초기화함으로써 서로 다른 객체가 서로 다른 초기값을 갖도록 할 수 있습니다.
예를 들어, 다음은 초기값을 필드 선언과 생성자에서 각각 대입하는 예제입니다:
public class MyClass {
// 필드 선언과 초기값 대입
int defaultFieldValue = 10;
// 생성자를 통한 초기화
int customFieldValue;
public MyClass() {
// 생성자에서 필드 초기화
customFieldValue = 42;
}
public static void main(String[] args) {
// 객체 생성
MyClass defaultObject = new MyClass();
MyClass customObject = new MyClass();
// 필드 값 출력
System.out.println("defaultObject: " + defaultObject.defaultFieldValue); // 출력: 10
System.out.println("customObject: " + customObject.customFieldValue); // 출력: 42
}
}
이러한 접근 방식을 사용하면 각 객체가 원하는 초기값을 갖도록 할 수 있으며, 코드를 더 유연하게 만들 수 있습니다.
필드명과 매개변수명이 같은 경우, 초기화할 필드에 this
키워드를 사용하여 현재 객체의 필드임을 명시할 수 있습니다. this
는 현재 객체를 가리키는 참조 변수로, 해당 객체의 멤버에 접근할 때 사용됩니다.
public class MyClass {
// 필드 선언
int myField;
// 생성자 선언
public MyClass(int myField) {
// this를 사용하여 필드 초기화
this.myField = myField;
}
// 메서드 선언
public void printMyField() {
// this를 사용하여 필드 값 출력
System.out.println("myField: " + this.myField);
}
public static void main(String[] args) {
// 객체 생성 시 생성자를 통해 필드 초기화
MyClass myObject = new MyClass(42);
// 메서드 호출을 통해 필드 값 출력
myObject.printMyField(); // 출력: myField: 42
}
}
this.myField
를 사용하면 필드명과 매개변수명이 같을 때도 명확하게 현재 객체의 필드를 참조할 수 있습니다. 이는 코드의 가독성을 높이고 혼동을 방지하는 데 도움이 됩니다.
생성자 오버로딩은 같은 클래스 내에서 동일한 이름의 생성자를 여러 개 정의하는 것을 의미합니다. 각 생성자는 서로 다른 매개변수를 가지고 있어야 합니다. 이렇게 함으로써 동일한 생성자 이름을 가지면서 다양한 매개변수 조합을 통해 객체를 초기화할 수 있습니다.
생성자 오버로딩을 사용하면 객체를 다양한 방식으로 초기화할 수 있어 편리합니다. 예를 들어, 기본 생성자와 매개변수를 받는 생성자를 함께 정의할 수 있습니다.
public class MyClass {
// 필드 선언
int myField;
// 기본 생성자
public MyClass() {
this.myField = 0;
}
// 매개변수를 받는 생성자 (생성자 오버로딩)
public MyClass(int myField) {
this.myField = myField;
}
// 메서드 선언
public void printMyField() {
System.out.println("myField: " + this.myField);
}
public static void main(String[] args) {
// 생성자 오버로딩을 통해 객체 생성
MyClass object1 = new MyClass(); // 기본 생성자 호출
MyClass object2 = new MyClass(42); // 매개변수를 받는 생성자 호출
// 메서드 호출을 통해 필드 값 출력
object1.printMyField(); // 출력: myField: 0
object2.printMyField(); // 출력: myField: 42
}
}
생성자 오버로딩이 많아질 경우 중복된 코드를 최소화하고 유지보수성을 높이기 위해 공통 코드를 한 생성자에 집중적으로 작성하는 것이 좋습니다. 이때 this(...)
를 사용하여 다른 생성자를 호출할 수 있습니다.
public class MyClass {
// 필드 선언
int myField;
// 기본 생성자
public MyClass() {
// 공통 코드 호출
this(0);
}
// 매개변수를 받는 생성자 (생성자 오버로딩)
public MyClass(int myField) {
// 공통 코드
this.myField = myField;
}
// 메서드 선언
public void printMyField() {
System.out.println("myField: " + this.myField);
}
public static void main(String[] args) {
// 생성자 오버로딩을 통해 객체 생성
MyClass object1 = new MyClass(); // 기본 생성자 호출
MyClass object2 = new MyClass(42); // 매개변수를 받는 생성자 호출
// 메서드 호출을 통해 필드 값 출력
object1.printMyField(); // 출력: myField: 0
object2.printMyField(); // 출력: myField: 42
}
}
void
로 지정합니다.{}
안에 메서드가 수행할 동작을 정의합니다.public class MyClass {
// 메서드 선언
public int addNumbers(int a, int b) {
// 실행 블록
// 두 정수를 더한 값을 반환
return a + b;
}
public static void main(String[] args) {
// 객체 생성
MyClass myObject = new MyClass();
// 메서드 호출
int result = myObject.addNumbers(3, 5);
// 결과 출력
System.out.println("Result: " + result); // 출력: Result: 8
}
}
메서드의 리턴타입이 void
가 아닌 경우, 메서드의 실행 블록 안에서 반드시 return
키워드를 사용하여 해당 리턴타입에 맞는 값을 반환해야 합니다. return
문을 통해 메서드의 실행이 종료되고, 그 값을 호출한 부분으로 반환됩니다.
public class MyClass {
// 메서드 선언
public int addNumbers(int a, int b) {
// 실행 블록
// 두 정수를 더한 값을 반환
return a + b;
}
public static void main(String[] args) {
// 객체 생성
MyClass myObject = new MyClass();
// 메서드 호출
int result = myObject.addNumbers(3, 5);
// 결과 출력
System.out.println("Result: " + result); // 출력: Result: 8
}
}
자바에서는 메서드명을 작성할 때 주로 캐멀 케이스(camelCase) 규칙을 따릅니다. 캐멀 케이스는 여러 단어로 이루어진 식별자에서 각 단어의 첫 글자를 대문자로 표기하고, 나머지는 소문자로 표기하는 규칙입니다.
public class CamelCaseExample {
// 메서드 선언
public void calculateTotalAmount(int quantity, double price) {
// 실행 블록
// 메서드가 수행할 동작을 정의
double totalAmount = quantity * price;
System.out.println("Total Amount: " + totalAmount);
}
public static void main(String[] args) {
// 객체 생성
CamelCaseExample exampleObject = new CamelCaseExample();
// 메서드 호출
exampleObject.calculateTotalAmount(5, 10.5);
}
}
가변길이 매개변수(variable-length parameter)는 메서드가 동적으로 변하는 매개변수의 개수를 처리할 수 있도록 하는 기능입니다. 이를 통해 메서드 호출 시 필요한 개수의 매개변수를 전달할 수 있습니다.
메서드선언에서 매개변수 타입 뒤에 ...
을 붙여서 사용합니다. 이 가변길이 매개변수는 메서드 내부에서 배열로 처리됩니다.
public class VarargsExample {
// 가변길이 매개변수를 사용한 메서드
public void printNumbers(int... numbers) {
System.out.println("Number of arguments: " + numbers.length);
// 가변길이 매개변수는 배열로 처리되므로 반복문을 통해 접근 가능
for (int number : numbers) {
System.out.print(number + " ");
}
System.out.println();
}
public static void main(String[] args) {
// 객체 생성
VarargsExample exampleObject = new VarargsExample();
// 가변길이 매개변수를 사용한 메서드 호출
exampleObject.printNumbers(1, 2, 3);
exampleObject.printNumbers(10, 20, 30, 40, 50);
}
}
return
문은 메서드의 실행을 강제로 종료하고, 메서드를 호출한 곳으로 돌아가게 합니다. return
문 이후에 나오는 실행문들은 실행되지 않습니다.
return
문은 메서드가 값을 반환할 때 사용되기도 하며, 메서드가 void
타입이라면 단순히 메서드의 실행을 종료시킵니다. 반환값이 있는 메서드에서 return
문을 사용할 때는 해당 반환값의 타입과 일치하는 값을 지정해야 합니다.
public class ReturnExample {
// 반환값이 있는 메서드
public int addNumbers(int a, int b) {
int sum = a + b;
// 반환문
return sum;
// 반환문 이후의 실행문은 실행되지 않음
// 아래의 코드는 도달할 수 없는 코드로 컴파일러 오류 발생
// System.out.println("This line will not be executed.");
}
public static void main(String[] args) {
// 객체 생성
ReturnExample exampleObject = new ReturnExample();
// 메서드 호출과 반환값 사용
int result = exampleObject.addNumbers(3, 5);
// 결과 출력
System.out.println("Result: " + result); // 출력: Result: 8
}
}
이 코드에서 addNumbers
메서드는 두 정수를 더한 값을 반환하고 있습니다. return sum;
문을 통해 메서드가 실행되고 값을 반환한 후, 호출한 곳으로 돌아갑니다. 이후에 있는 코드는 실행되지 않습니다.
return
문은 메서드의 실행 흐름을 제어하고, 값을 반환하여 메서드 호출자에게 결과를 전달하는 중요한 역할을 합니다.
필드(멤버 변수)와 메서드는 선언 방법에 따라 인스턴스 멤버와 정적 멤버로 분류됩니다.
public class MyClass {
// 인스턴스 멤버 - 필드
int instanceField;
// 인스턴스 멤버 - 메서드
public void instanceMethod() {
// 메서드 내용
}
public static void main(String[] args) {
// 객체 생성
MyClass myObject = new MyClass();
// 인스턴스 멤버 사용
myObject.instanceField = 10;
myObject.instanceMethod();
}
}
public class MyClass {
// 정적 멤버 - 정적 필드
static int staticField;
// 정적 멤버 - 정적 메서드
public static void staticMethod() {
// 메서드 내용
}
public static void main(String[] args) {
// 정적 멤버 사용
staticField = 20;
staticMethod();
}
}
정적 메서드(static method)와 정적 초기화 블록(static block)은 클래스의 인스턴스에 속하지 않고 클래스 자체에 속하므로, 객체 생성 없이도 호출되는 특징이 있습니다. 이로 인해 정적 메서드나 정적 초기화 블록 내부에서는 인스턴스 필드나 인스턴스 메서드를 직접적으로 사용할 수 없습니다.
public class MyClass {
// 정적 멤버 - 정적 필드
static int staticField;
// 정적 멤버 - 정적 메서드
public static void staticMethod() {
// 여기에서는 인스턴스 필드나 메서드를 직접 사용할 수 없음
// this.instanceField = 10; // 오류: 정적 메서드에서는 인스턴스 필드 사용 불가
// instanceMethod(); // 오류: 정적 메서드에서는 인스턴스 메서드 호출 불가
// 정적 필드 사용은 가능
staticField = 20;
}
// 정적 초기화 블록
static {
// 여기에서도 인스턴스 필드나 메서드를 직접 사용할 수 없음
// this.instanceField = 10; // 오류: 정적 초기화 블록에서는 인스턴스 필드 사용 불가
// instanceMethod(); // 오류: 정적 초기화 블록에서는 인스턴스 메서드 호출 불가
// 정적 필드 사용은 가능
staticField = 30;
}
// 인스턴스 멤버 - 필드
int instanceField;
// 인스턴스 멤버 - 메서드
public void instanceMethod() {
// 인스턴스 메서드 내에서는 정적 멤버와 인스턴스 멤버를 모두 사용 가능
staticField = 40;
instanceField = 50;
staticMethod();
}
}
final
키워드는 변수, 메서드, 클래스 등에 사용되어 해당 요소에 대한 변경이 불가능하도록 지정합니다.
변수에 사용하는 경우:
final
변수는 상수로 취급되며, 한 번 초기화되면 값을 변경할 수 없습니다.final int MY_CONSTANT = 10;
// MY_CONSTANT = 20; // 오류: final 변수는 값을 변경할 수 없음
메서드에 사용하는 경우:
final
메서드는 하위 클래스에서 오버라이드(재정의)할 수 없습니다.public class Parent {
public final void finalMethod() {
// 메서드 내용
}
}
public class Child extends Parent {
// 오류: final 메서드를 오버라이드할 수 없음
// public void finalMethod() {
// // 메서드 내용
// }
}
클래스에 사용하는 경우:
final
클래스는 다른 클래스에서 상속받을 수 없습니다.final class FinalClass {
// 클래스 내용
}
// 오류: final 클래스를 상속받을 수 없음
// class AnotherClass extends FinalClass {
// // 클래스 내용
// }
일반적으로 자바에서는 패키지를 만들 때 개발 회사의 도메인 이름을 역순으로 사용하는 것이 권장되는 관례입니다. 이렇게 하면 패키지 명이 고유하게 유지되어 충돌을 피하고, 패키지를 생성한 조직의 도메인 구조를 반영할 수 있습니다.
예를 들어, "com.example.myapp"와 같은 패키지 명을 사용하는 것이 일반적입니다. 이 경우, "com"은 상위 도메인을 나타내고, "example"은 도메인의 서브 도메인이며, "myapp"은 실제 패키지의 이름입니다. 이런 식으로 패키지를 구성하면 유사한 명명 규칙을 사용하는 여러 조직 간에 충돌을 방지할 수 있습니다.
이러한 관례는 패키지 명명 규칙을 일관되게 유지하고, 코드를 더 쉽게 이해하고 유지보수할 수 있도록 도와줍니다.
접근 제한자(access modifier)는 클래스의 필드, 메서드, 생성자 등의 멤버에 대한 접근 권한을 지정하는 데 사용됩니다.
public:
public
으로 선언된 멤버는 모든 곳에서 접근 가능합니다. 다른 패키지에 속한 클래스에서도 접근이 가능합니다.public class MyClass {
public int publicField;
public void publicMethod() {
// 메서드 내용
}
}
protected:
protected
로 선언된 멤버는 같은 패키지에 속한 클래스와 해당 클래스를 상속받은 클래스에서 접근 가능합니다.class MyClass {
protected int protectedField;
protected void protectedMethod() {
// 메서드 내용
}
}
private:
private
으로 선언된 멤버는 같은 클래스 내에서만 접근 가능하며, 다른 클래스에서는 접근할 수 없습니다.public class MyClass {
private int privateField;
private void privateMethod() {
// 메서드 내용
}
}
생성자 또한 접근 제한자를 가질 수 있습니다.
public:
public
으로 선언된 생성자는 모든 곳에서 접근 가능합니다. 다른 패키지에서도 접근이 가능합니다.public class MyClass {
// public 생성자
public MyClass() {
// 생성자 내용
}
}
default (package-private):
class MyClass {
// default 생성자 (패키지 내에서만 접근 가능)
MyClass() {
// 생성자 내용
}
}
private:
private
으로 선언된 생성자는 같은 클래스 내에서만 접근 가능합니다. 다른 클래스에서는 접근할 수 없습니다.public class MyClass {
// private 생성자
private MyClass() {
// 생성자 내용
}
}
필드와 메서드도 생성자와 마찬가지로 접근 제한자를 가질 수 있습니다.
public:
public
으로 선언된 필드 또는 메서드는 모든 곳에서 접근 가능합니다. 다른 패키지에서도 접근이 가능합니다.public class MyClass {
// public 필드
public int publicField;
// public 메서드
public void publicMethod() {
// 메서드 내용
}
}
default (package-private):
class MyClass {
// default (package-private) 필드
int defaultField;
// default (package-private) 메서드
void defaultMethod() {
// 메서드 내용
}
}
private:
private
으로 선언된 필드 또는 메서드는 같은 클래스 내에서만 접근 가능합니다. 다른 클래스에서는 직접 접근할 수 없습니다.public class MyClass {
// private 필드
private int privateField;
// private 메서드
private void privateMethod() {
// 메서드 내용
}
}
이러한 접근 제한자를 사용하여 객체의 필드와 메서드에 대한 접근을 제어할 수 있습니다. 정보 은닉과 캡슐화를 통해 객체의 내부를 보호하고, 외부로부터의 불필요한 접근을 방지하는 데 도움이 됩니다.
객체지향 프로그래밍에서는 정보 은닉(Encapsulation)의 원칙에 따라 직접적인 필드 접근을 피하고, 대신 메서드를 통해 필드에 접근하는 것을 선호합니다. 이때 사용되는 메서드가 바로 getter와 setter입니다.
Getter (접근자 메서드):
public class MyClass {
private int myField;
// Getter 메서드
public int getMyField() {
return myField;
}
}
Setter (설정자 메서드):
public class MyClass {
private int myField;
// Setter 메서드
public void setMyField(int value) {
myField = value;
}
}
싱글톤 패턴(Singleton Pattern)은 디자인 패턴 중 하나로, 특정 클래스가 단 하나의 인스턴스를 가지고 이에 대한 전역적인 접근을 제공하는 패턴입니다. 이를 구현하기 위해 생성자를 private으로 선언하고, 유일한 인스턴스를 반환하는 메소드를 제공하여 외부에서는 해당 클래스의 객체를 직접적으로 생성하지 못하도록 합니다.
일반적으로 싱글톤 패턴은 다음과 같은 형태를 가집니다:
public class Singleton {
// 유일한 인스턴스를 저장하는 private 정적 변수
private static Singleton instance;
// private 생성자
private Singleton() {
// 생성자 내용
}
// 유일한 인스턴스를 반환하는 public 정적 메소드
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
위의 예제에서 Singleton
클래스는 생성자를 private으로 선언하여 외부에서 객체를 직접 생성할 수 없도록 하고, getInstance
메소드를 통해 유일한 인스턴스를 반환하도록 합니다. 또한, 이 구현은 초기화된 인스턴스가 없을 경우에만 생성하여 메모리를 절약하는 방식입니다.
이렇게 구현된 싱글톤 패턴은 전역 상태의 유일한 인스턴스를 제공하여, 특정한 상황에서 하나의 객체만을 필요로 할 때 사용됩니다.