[Java Basic] 클래스

buckshot·2025년 6월 4일

JAVA

목록 보기
14/18

객체 지향 프로그래밍

객체 지향 프로그래밍에 있어서 객체의 의미를 짚고 가야 한다. 여기서 객체는 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성과 동작(기능)을 갖고 있는 것을 말한다. 자바에서는 속성과 동작을 각각 필드와 메서드라고 부른다.

객체의 상호작용

소프트웨어에서 객체들은 각각 독립적으로 존재하고, 다른 객체와 서로 상호작용을 하면서 동작을 한다. 객체들 사이의 상호작용 수단은 메서드가 된다. 이떄 객체가 다른 객체의 메서드를 이용하는 것이 메서드 호출이다.

대부분의 메서드 호출의 형태는 아래와 같다.

리턴값 = 객체.메서드(매개값1, 매개값2, ...)
  • 객체 바로 뒤에 토드(.)연산자를 사용하여 호출할 메서드의 이름을 기술하면 된다.
  • 매개값은 메서드 동작시 필요한 데이터이다.

객체 간의 관계

객체는 개별적으로 사용될 수 있지만, 대부분 다른 객체와 관계를 맺고 있다. 관계는 3가지 관계가 존재를 한다.

  • 집합 관계

    하나의 객체를 완성하기 위해 필요한 부품같은 객체 사이의 관계
  • 사용 관계

    서로 다른 객체 간의 상호작용을 하는 관계
  • 상속 관계

    상위 객체를 기반으로 만들어진 하위 객체 관계

객체와 클래스

자바에서 클래스(class)는 설계도와 같은 존재입니다. 클래스에는 객체를 만들기 위한 필드(멤버 변수)메서드(기능)가 정의되어 있습니다. 이 클래스라는 설계도를 바탕으로 실제 메모리에 만들어지는 구체적인 실체를 객체(object)라고 하며, 객체를 다른 말로 인스턴스(instance)라고 부릅니다.

클래스를 기반으로 객체를 만드는 과정을 인스턴스화(instantiation)라고 합니다. 이때 new 키워드를 사용하여 클래스의 인스턴스를 생성하게 됩니다. 예를 들어 Dog이라는 클래스를 정의해 두었다면, new Dog()를 통해 메모리에 실제 강아지 객체가 만들어지는 것이죠.

Dog dog1 = new Dog(); // 첫 번째 인스턴스
Dog dog2 = new Dog(); // 두 번째 인스턴스

위 코드처럼 하나의 클래스는 여러 개의 인스턴스를 생성할 수 있습니다. 각 인스턴스는 같은 구조(필드와 메서드)를 공유하지만, 서로 독립적인 데이터를 가질 수 있습니다. 즉, dog1과 dog2는 모두 Dog 클래스에서 만들어졌지만, 서로 다른 상태(예: 이름, 나이 등)를 가질 수 있습니다.

클래스 선언

이제 구현하고자 하는 객체를 구상을 했다면 그 객체를 구현을 하자.
우리가 만들려고 하는 객체가 사람이거나, 사물 등일 경우에 특정 이름을 이용하여 클래스 이름을 지정할 수 있다. 클래스 이름은 다른 클래스와 식별할 목적으로 사용되기 때문에 자바의 식별자 작명 규칙을 준수해야 한다.

  • 하나 이상의 문자로 구성되어야 한다. -> car (x)
  • 첫 글자에는 숫자가 올 수 없다. -> 4Car (x)
  • ’$’, ‘_’외의 특수 문자는 사용할 수 없다. -> @Car, &Car (x)
  • 자바 키워드는 사용할 수 없다. -> int, for (x)

클래스명을 작성할 때 통상적으로 단일 단어일 경우에는 첫 글자만을 대문자로 작성하고 나머지는 소문자로 작성을 한다. 그리고 여러 단어가 연속으로 올 경우에는 처음 글자만을 대문자로 작성하고 나머지를 소문자로 작성할 경우에 가독성이 떨어진다. 이러한 문제를 해결하기 위해서 각 단어의 첫 글자만을 대문자로 작성한다.

Calculater, Car, Member, ChatClient, ChatServer, ...

클래스명을 정했다면 이제 소스파일을 생성할 차례이다. 소스파일 이름 역시 대소문자를 구분하므로 반드시 클래스 이름과 대소문자가 동일하게 해야 한다.

public class 클래스 {

}

여기서 public static키워드는 클래스를 선언할 때 사용하며 반드시 소문자로 작성해야 한다. 클래스 이름 뒤 반드시 중괄호를 붙여줘야 한다. 이는 클래스 선언의 시작선언의 끝을 명시해주는 역활을 수행한다.

객체 생성과 클래스 변수

클래스 선언 후 컴파일을 했다면 객체 생성할 준비는 모두 끝났다. 그러면 이제 new연산자를 사용하면 된다.

new 클래스();

new는 클래스로 부터 객체를 생성시키는 연산자이다. 연산자 뒤에는 생성자가 위치하게 된다. 이는 클래스()형태를 갖고 있다. new연산자를 통하여 생성된 갹체는 메모리의 힙 영역에 생성이 된다.

클래스 변수;
변수 = new 클래스();

클래스 변수 = new 클래스();

클래스의 구성 멤버

클래스에는 객체가 가져야 할 구성 멤버가 있다. 기본적인 구성 멤버에는 필드, 생성자, 메서드가 있다.
이 구성 멤버들은 생략되거나 복수의 개수로 작성될 수 있다.

public class 클래스 {
// 필드
int 필드;
// 생성자
클래스() {...}
// 메서드
void 메서드() {...}
}
  • 필드
    필드는 객체의 고유 데이터?, 속성? 등의 정보를 저장하는 곳이라고 보면 된다. 선언 형태는 변수와 비슷하지만, 필드를 변수라고 부르지 않는다.
    변수는 생성자와 메서드 내에서만 사용되고 생성자와 메서드가 실행이 끝나면 자동으로 소멸된다. 하지만 필드는 생성자와 메서드 전체에사 사용되며 객체가 소멸되지 않는 한 객체와 함꼐 존재하게 된다.

  • 생성자
    생성자는 new()연산자로 호출되는 특별한 중괄호 블럭이다. 생성자의 역활은 객체 생성 시 초기화를 담당하게 된다. 필드를 초기화하거나 메서드를 호출에서 객체를 사용할 준비를 한다.
    생성자는 메서드와 비슷한 형태를 갖고 있지만, 클래스 이름으로 되어 있어 린턴 타입이 없다.

  • 메서드
    메서드는 객체의 동작에 해당하는 중괄호 블럭을 의미한다. 중괄호 블럭은 이름을 갖고 있는데, 이것을 메서드라고 부른다.
    메서드를 호출하면 중괄호 부분에 있는 모든 코드들이 일괄적으로 실행이 된다. 이때 메서드는 필드를 읽고 수정하는 역활도 하지만, 다른 객체를 생성하여 다양한 기능을 수행하기도 한다.


필드

필드 선언

필드 선언은 클래스 중괄호 내부 어디서든 존재할 수 있다. 생성자 선언과 메서드 선언의 앞과 뒤 어디든지 선언이 가능하다. 하지만 생성자와 매서드 중괄호 블럭 내부에는 선언될 수 없다. 생성자와 메서드 중괄호 블럭에서 선언되면 이는 필드가 아닌 로컬 변수가 된다는 것을 명심해야 한다.

타입 필드 [ = 초기값 ];

필드는 위 처럼 선언할 수 있다. 그리고 선언과 동시에 초기값을 주거나 나중에 생성자를 통해서 값을 지정할 수 있다.

만약 초기값을 지정되지 않은 필드의 경우에는 각 타입에 맞는 기본 초기값으로 설정이 된다.

분류타입초기값
기본 타입 (정수)byte [ ]0
char [ ]“\u0000”
short [ ]0
int [ ]0
long [ ]0L
기본 타입 (실수)float [ ]0.0F
double [ ]0.0
가보 타입 (논리)boolean [ ]false
참조 타입클래스 [ ]null
인터페이스 [ ]null

필드 사용

필드를 사용한다는 것은 필드값을 읽고 변경하는 작업을 의미하게 된다. 클래스 내부 생성자나 메서드에서 사용할 경우 단순히 필드 이름으로 읽고 변경하면 되지만, 클래스 외부에서 사용할 경우 우선적으로 클래스로부터 객체를 생성한 뒤 필드를 사용해야 한다.

// Car 클래스

int speed;

Car() {
	speed = 0; // 필드 값 변경
}	

void method() {
	speed = 10;	// 필드 값 변경
}

필드 사용은 변수 사용이랑 큰 차이점은 없어 보인다. 그렇지만 차이점은 존재하고 있다. 변수는 자신이 선언된 생성자 또는 메서드 블럭 내부에서만 존재하고 사용할 수 있지만, 필드는 생성자와 메서드 블럭 내부를 벗어나 사용이 가능하다는 점이다.

그리고 위 예제 코드에서는 클래스 내에서만 값을 사용 했지만, 외부에서 사용할 경우 아래의 코드 처럼 해당 객체를 우선적으로 생성해야 필드를 사용할 수 있다.

// Person 클래스
void method() {
	Car myCar = new Car();

	myCar.speed = 30; // 외부에서 필드 값 변경
}

생성자

기본 생성자

모든 클래스는 생성자가 반드시 존재하며, 생성자를 하나 이상 갖고 있어야 한다. 우리가 클래스 내부에 생성자 선언을 생략했다면 컴파일러는 아래와 같은 기본적인 생성자를 만들어준다.

[public] 클래스() { } // 여기서 중괄호에는 아무런 코드가 없다.
// 소스 파일
public class Car() {
}

// 컴파일 
// 바이트 코드 파일 
public class Car() {
	public Car() { } // 자동 추가
}

위 상황 처럼 컴파일러는 자동으로 기본 생성자를 생성을 해준다. 그런데 컴파일러는 그냥 막 생성해주는 것이 아니다. 생성자 코드가 하나라도 있을 경우 기본 생성자를 생성해주지 않게 된다.

생성자 선언

기본 생성자 대신 우리가 생성자를 명시적으로 선언하는 방법을 알아 보자.

클래스(매개변수 선언,...) {
	// 객체 초기화 코드
}

생성자는 메서드와 비슷한 구조를 갖고 있지만 메서드와 달리 리턴 타입이 존재하지 않는다. 그리고 클래스 이름과 동일한 이름을 갖고 있다. 마지막으로 생성자 블럭 내부에는 객체 초기화 코드가 작성 된다.
일반적으로 필드에 초기값을 저장하거나 메서드 를 호출하여 객체 사용 전 필요한 준비를 하게 된다.
(좀 더 자세한 내용은 뒤에서 다루겠다.)

매개 변수 선언은 생략 하거나 여러개를 선언할 수 있다. 여기서 선언된 매개 변수는 new 연산자를 통해서 생성자를 호출할 때 외부의 값을 생성자 블럭 내부로 전덜하는 역활을 하게 된다.

예를 들어 Car객체를 생성할 때 생성자에서 모델 명, 색상 정보를 매개 변수로 선언 했다고 가정을 하자.

Car car = new Car("소나타", "검정");

이 처럼 생성자를 호출할 때 참조타입의 문자열 매개값을 2개를 제공하고 있다.

그러면 생성자는 두 문자열을 받기 위해 매개 변수를 선언해야 한다.

public class Car {
	Car(String model, String color) {...}
}

필드 초기화

앞에서 간단하게 필드에 대해서 알아볼 때 초기값을 지정하지 않을 경우에 해당 타입의 기본 초기값으로 설정 된다고 했다. 만약 기본 초기값이 아닌 다른 값으로 지정해주고 싶을 떄 어떻게 해야 하는가?

기본 초기값이 아닌 다른 값을 주는 방법으로 두 가지 방법이 존재 한다. 하나는 필드를 선언할 때 초기값을 주는 방법과 다른 방법으로 생성자에서 초기값을 주는 방법 이렇게 각 방법이 존재한다.

// 필드 선언할 때 초기값 
public class A {
	int 필드1 = 10;
	String 필드2 = "A";
	
	public A() { }
}

// 생성자를 통하여 초기값
public class A {
	int 필드1;
	String 필드2;
	
	public A(int n, String str) {
		필드1 = n;
		필드2 = str;
	}
}

메서드

메서드 선언

메서드 선언은 선언부(리턴 타입, 메서드 이름, 매개 변수 선언)와 실행 블럭으로 구성 된다.

  • 리턴 타입
    리턴 타입은 리턴값의 타입을 의미한다. 리턴값이란 메서드를 실행 후 전달해주는 값을 의미한다.
    메서드는 리턴값이 있을 수 있고 없을 수 있다. 그리고 이를 메서드 선언부에 명시를 해줘야 한다.

    메서드의 리턴값이 없을 경우에는 void를 명시하고, 있을 경우에는 리턴되는 값의 타입을 명시를 해줘야 한다.

    void powerOn() {...} // 리턴값이 없음
    double divide(int x, int y) {...} // 리턴값이 double 타입

    추가적으로 메서드를 호출하여 리턴값을 변수에 바로 저장할 수 있다.

    double result = divide(10, 5);

    하지만 위 처럼 메서드의 리턴값을 변수에 바로 저장할 떄 주의해야 할 부분이 존재한다. 만약 리턴값을 저장할 변수의 타입과 메서드의 리턴값의 타입이 일치하지 않을 경우에는 컴파일 에러가 발생된다.

  • 메서드명
    메서드 이름은 자바 식별자 규정에 맞도록 작명을 해야 한다.

    • 숫자로 시작하면 안 되며, ‘$’ 와 ‘_’ 외 다른 특수문자가 사용되지 않아야 한다.
    • 관례적으로 메서드 이름은 소문자로 작성되어야 한다.
    • 여러 단어가 혼합된 이름일 경우, 각 단어의 이작을 대문자로 작성해야 한다.
  • 매개 뱐수 선언
    매개변수는 메서드가 실행할 때 필요한 데이터를 외부로부터 받기 위해서 사용이 된다. 메서드에서 메개 변수가 필요한 경우가 있고 필요 없는 경우가 있다.

    위 메서드 powerOndivide를 보면 리턴 타입이 없는 powerOn메서드를 보면 매개 변수가 선언되어 있지 않지만, divide메서드는 매개 변수가 선언되어 있다.

    • 정확한 매개 변수의 수를 모를 경우
      메서드의 매개 변수는 개수가 이미 정해져 있는 것이 일반적이지만, 어떤 경우에서는 메서드를 선언할 때 매개 변수의 개수를 알 수 없는 경우또한 조재한다. 예를 들어거 여러 개의 수를 모두 합산하여 반환하는 경우를 생각하자.

      이 경우를 해결할 수 있는 경우는 매개 변수를 배열로 선언하는 방법이 있다. 하지만 이는 매개 변수를 배열 타입으로 선언하게 되면 메서드를 호출하는 상황에서 배열을 생성해야 한다는 다소 불편한 부분이 존재하게 된다.

      그렇기 때문에 해당 방법 보다는 간편한 방법으로 값의 목록을 전달해주는 방법이 존재한다.
      메서드 매개 변서 선언 부분에서 을 사용 하는 방법이다.

      int sum(int... nums) { }

      이 처럼 선언을 하게 되면 조금 더 간편하게 사용할 수 있다.

      int result1 = sum(1, 2, 5);
      int result2 = sum(1, 2, 5, 7, 10);

리턴문

  • 리턴값이 있는 메서드
    메서드 선언에 리턴 타입이 존재하는 메서드는 반드시 리턴문을 사용하여 리턴값을 지정해줘 한다.
    만약 리턴문이 없다면 컴파일 에러가 발생하게 된다. 또한 리턴문이 싱행되면 메서드는 즉시 종료가 된다.

  • 리턴값이 없는 메서드 : void
    선언문에서 리턴 타입 위치에 void 키워드가 작성된 메서드를 보았을 것이다. 이는 해당 메서드는 리턴 타입이 없다는 것을 의미하게 된다.

    그런데 앞에서 확인한 리턴값이 있는 메서드 경우에는 메서드 결과값을 주기 위해서 return을 사용한다. 그러면 리턴값이 없는 메서드에서는 사용이 가능할까?

    리턴값이 없는 메서드에서도 return을 사용할 수 있다. 하지만 기능이 다소 다르다. void메서드에서 return은 메서드를 종료 시키는 역활을 수행하게 된다.

메서드 호출

메서드는 클래스 내부, 외부 호출에 의해 실행이 된다. 클래스 내부의 다른 메서드에서 호출할 경우에는 단순하게 메서드의 이름만 호출 하면 되지만, 클래스 외부에서 호출할 경우에는 우선 클래스로부터 객체를 생성한 뒤 참조변수를 이용하여 호출을 해야한다.

// 객체 내부
// 메서드 선언
void run() { }
void stop() { }
void sound() { }

void 내부메서드() {
	run();
	stop();
	sound();
}

// 객체 외부 
void 외부메서드() {
	Car car = new Car();
	car.run();
	car.stop();
	car.sound();
}

인스턴스 멤버와 정적 멤버

인스턴스 멤버와 this

인스턴스 멤버한 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메서드를 의미한다. 이들은 각각 인스턴스 필드, 인스턴스 메서드라고 한다.

  • 인스턴스 멤버 선언
    인스턴스 필드와 메서드를 선언하는 방법은 지금가지 알아본 내용에서 크게 다르지 않다.

    public class Car {
    	int gas; // 필드
    	int speed;
    
    	void setSpeed(int speed) { // 메서드
    	}
    }
  • this
    객체 외부에서 인스턴스 멤버에 접근하기 위해서 참조 변수를 사용하는 것과 마찬가지로 객체 내부에서도 인스턴스 멤버 접근을 막기 위해서 this를 사용할 수 있다. 우리가 자신을 ‘나’라고 가리키듯이 객체는 자신을 this를 이용하여 명시를 한다. 따라서 this.필드명은 객체가 갖고 있는 필드라고 명시하는 것이다.

    그러면 setSpeed메서드를 보면 매개 변수 이름이 필드의 이름과 동일하다는 것을 확인 가능하다. 그런데 메서드명 처럼 기능을 하기 위해서 매개값을 speed필드에 저장을 해야한다.

      	void setSpeed(int speed) { // 메서드
    		speed = speed;
    	}

    이 처럼 작성한다면 필드 speed를 매개 변수로 저장하는 것인지, 매개 변수를 필드 값으로 저장하는 것인지 정확하게 어떤 기능을 하는지 모르게 된다는 문제가 있다.
    this.speed = speed로 작성하게 된다면 매개값을 필드에 저장하는 코드로 쉽게 이해할 수 있다.

정적 멤버와 static

정적은 ‘고정된’이란 의미를 갖고 있다. 그렇기 때문에 정적 멤버클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메서드를 의미한다. 이들을 각각 정적 필드정적 메서드라고 한다.

정적 멤버 선언

정적 필드와 메서드를 선언하려면 선언부에 ststic키워드를 추가적으로 작성을 해야 한다.

static 타입 필드[= 초기값];
static 리턴 타입 메서드([매개변수 선언],...) { }

정적 멤버는 클래스에 고정된 멤버이므로 클래스 로더가 클래스(바이트 코드)를 로딩해서 메서드 메모리 영역에 적재할 때 클래스별로 관리가 된다. 따라서 클래스의 로딩이 끝나면 바로 사용할 수 있게 되는 것이다.

	클래스 로더   		➡️ 			메서드 영역(정적 필드, 정적 메서드)
				바이트 코드 로딩
	  ⬆️ 바이트 코드 읽기

	 클래스

필드를 선언할 떄 인스턴스 필드로 선언을 할 것인가 아니면 정적 필드로 선언할 것인가의 판단 기준이 명확해야 한다. 각 객체마다 가지고 있어야 할 정보라면 인스턴스 필드로 선언을 해야 하고, 객체마다 갖고 있을 필요가 없는 공용 정보라면 정적 필드로 선언을 하는것이 좋다.

정적 멤버 사용

클래스가 메모리로 로딩되면 정적 멤버는 바로 사용할 수 있는데, 클래스 이름과 함께 도트(.) 연산자를 통해서 접근을 해야 한다.

클래스.필드;
클래스.메서드(매개값, ...);

아래와 같이 Calculator클래스에 정적 멤버가 작성되어 있다.

public class Calculator {
	static double pi = 3.14159;
	static int plus(int x, int y) {...}
	static int minus(int x, int y) {...}
}

정적 필드 piplus(), minus()메서드가 존재할 때 다음과 같이 사용할 수 있다.

double result1 = 10 * 10 * Calculator.pi;
int result2 = Calculator.plus(2, 50);
int result3 = Calculator.minus(4, 1);

위 처럼 정적 멤버 사용은 원틱적으로 클래수 이름과 도트 연산자를 통해 접근을 해야 한다. 하지만 다음과 같이 객체 참조 변수로 접근도 가능하다.

Calculator c = new Calculator();
double result1 = 10 * 10 * c.pi;
int result2 = c.plus(2, 50);
int result3 = c.minus(4, 1);

정적 메서드 선언 시 주의점

객체가 없어도 실행 된다는 특징 때문에 정적 메서드를 선언할 때는 이들 내부에 인스턴스 필드나 인스턴스 메서드를 사용할 수 없다 또한 객체 자신의 참조인 this키워드도 사용이 불가능 하다는 점이다.

왜 정적 메서드를 선언할 때 내부에 인스턴스 필드나 인스턴스 메서드를 사용할 수 없으며, this키워드 또한 사용이 불가능 할까?

일단 정적(static) 메서드를 선언할 때 내부에서 인스턴스 필드나 메서드, 그리고 this키워드를 사용할 수 없는 이유는 정적 메서드가 클래스에 속해 있기 때문에 객체와 독립적으로 동작을 하기 때문이다.

정적 메서드클래스가 메모리에 로딩이 될 때 함께 메모리로 올라간다. 객체 생성하지 않고도 사용할 수 있어야 한다는 점을 지키기 위해서다. 반면, 인스턴스 멤버의 경우 각각의 객체가 생상될 때 비로소 메모리에 할당이 된다. 그렇기 때문에 인스턴스 멤버는 객체가 있어야 존재 가능하다.

따라서 정적 메서드가 호출이 될 떄 객체가 존재하지 않을 수도 있기 때문에, 인스턴스 멤버에 접근할 방법이 없다. 어떤 객체의 멤버를 참조 해야 하는지 알 수 없기 때문에, 정적 메서드 내부에서 인스턴스 필드나 메서드를 직접 사용할 수 없게 되는 것이다.

this키워드 또한 같은 내용이다. this는 현제 메서드를 실행 중인 객체 자신을 가리키는 참조인데, 정적 메서드는 객체 없이 호출되어 동작할 수 있어야 하기 때문에 객체가 없이 호출될 때 자신을 가리킬 tihs가 존재하지 않게 된다. 따라서 this또한 정적 메서드 내에서 사용할 수 없게 되는 것이다.

싱글톤

가끔 전체 프로그램에서 단 하나의 객체를 만들도록 보장해야 하는 경우가 있다. 단 하나만 생성된다고 해서 해당 객체를 싱글톤이라고 한다.

싱글톤을 만들려면 클래스 외부에서 new연산자로 생성자를 호출할 수 없도록 막아야 한다. 그 이유로 생성자를 호출한 만큼 객체가 생성되기 때문에 생성자 호출을 막아야 한다.

외부에서 생성자 호출을 막기 위해서 뒤에서 나올 접근 제어자를 사용해야 한다. 사용되는 접근 제어자로 private접근 제어자를 붙여서 막는다. 그리고 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화를 한다. (참고로 클래스 내부에서는 new연산자로 생성자 호출이 가능하다.

정적 필드로 private접근 제어자를 붙여 외부에서 필드값을 변경하지 못하도록 막는다. 대신 외부에서 호출할 수 있는 정적 메서드인 getInstance을 선언하고 정적 필드를 참조하고 자신의 객체를 리턴하게 한다.

public class 클래스 {
	// 정적 필드
	private static 클래스 singleton = new 클래스();

	// 생성자 
	private 클래스() { }

	// 정적 메서드
	static 클래스 getInstance() {
		return singleton;
	}
}

외부에서 객체를 얻는 유일한 방법은 정적 메서드를 호출하는 방법이 된다. 그렇기 때문에 단 하나의 객체만 리턴하기 때문에 아래의 코드에서 변수 1과 2는 동일한 객체를 참조하게 된다.

클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();

final 필드와 상수

final 필드

final은 ‘최종적인’ 의미란 뜻을 갖고 있다. 그렇다면 final필드는 최종적인 필드라는 의미가 된다. 이는 초기값이 저장이 되면 이것이 최종적인 값이 되어 프로그램 실행 중 수정할 수 없다는 것이다.

final 타입 필드 [= 초기값];

해당 필드는 초기값을 줄 수 있는 방법은 딱 두 가지 방법 뿐이다. 첫 번쨰 방법으로 필드 선언 시 값을 주는 방법생성자를 통하여 값을 지정하는 방법으로 초기값을 줄 수 있다.

단순 값일 경우 선언 시 바로 값을 주는 방법이 제일 간단하지만 복잡한 초기화 코드가 필요하거나 객체 생성 시 외부 데이터로 초기화해야 한다면 생성자에서 초기값을 지정해야 한다. 하지만 생성자를 통해 최종 초기값을 주지 않을 경우에 컴파일 에러가 발생하게 된다.

상수

일반적으로 불변의 값을 상수(static final)라고 부른다. 불변의 값은 수학에서 사용되는 원주율이나 지구의 무게 및 둘레 등이 해당한다. 이러한 불변의 값을 저장하는 필드를 자바에서 상수(constant)라고 한다.

앞에서 확인한 final필드는 한 번 초기화되면 수정이 불가능한 필드라고 했는데 그러면 final필드를 상수라고 불러도 되지 않을까?
하지만 final필드를 상수라고 부르지 않는다. 이는 불변의 값은 객체마다 저장할 필요가 없는 공용성을 띠고 있으며, 여러 값으로 초기화될 수 없기 때문이다.
final필드는 객체마다 저장이 되고, 생성자의 매개값을 통해 여러 가지 값을 가질 수 있기 떄문에 상수가 될 수 없는 것이다.

상수 선언 시 지켜야 할 부분이 있다. 상수명은 모두 대문자로 선언을 해야 한다는 관례가 존재를 한다. 만약 여러 단어가 연속적으로 등장할 경우 언더바(_)를 사용하여 구분을 한다.

static final double PI = 3.14159;
static final double EARTH_RADIUS = 6400;
static final double EARTH_AREA = 4 * Math.PI * EARTH_RADIUS * EARTH_RADIUS;

패키지와 접근 제어자

패키지의 물리적인 형태는 파일 시스템 폴더이다. 패키지는 단순 파일 시스템의 폴더 기능만을 수행하는 것이 아닌 클래스의 일부분으로, 클래스를 유일하게 만들어주는 식별자 역활을 하게 된다.

클래스의 이름이 동일하더라도 패키지가 다르면 다른 클래스로 인식을 하게 된다. 클래스의 전체 이름은 ‘패키지 이름 + 클래스 이름’인데 패키지가 상위 하위로 구분되어 있다면 도트를 사용해 표현하게 된다.

package com.step4.problem05

패키지 선언

클래스를 작성할 떄 해당 클래스가 어떤 패키지에 속할 것인지를 선헌하는 것을 패키지 선언이라고 한다.

package com.step4.problem05

이 처럼 구성되어 있다면 해당 파일은 아래와 같은 패키지 구조를 갖게 된다.

com/
 └── step4/
      └── problem05/
             └── [여기에 클래스 파일들 (.java)가 위치]

패키지 선언은 크게 작성해야 하거나 이런 부분은 없다. 대충 어떤 의미를 갖고 있는지에 대해서 알고 있으면 된다.

import문

사용자가 사용하고자 하는 클래스 또는 인터페이스가 다른 패키지에 소속되어 있다면, import문으로 해당 패키지 클래스 또는 인터페이스를 가져와 사용할 것임을 컴파일러에게 명시해줘야 한다.

import java.util.HashMap;
import java.util.Map;

위 처럼 import문을 사용하면 해당 패키지나 인터페이스를 접근할 수 있게 된다. 여기서 import문은 개수에 제한이 없고 얼마든지 추가하여 사용할 수 있다.

주의할 점은 상위 패키지를 import핶다고 하위 패키지까지 import가 되는 것이 아니기 때문에 주의해야 한다.

접근 제한자

접근 제한자(Access Modifiter)는 의미 그대로 접근을 제한하는 기능을 수행한다. 야기서 접근이라는 것은 클래스나 인터페이스 그리고 이들이 갖고 있는 멤버의 접근을 의미한다.

어떤 경우에는 클래스와 인터페이스를 다른 패키지에서 사용하지 못 하도록 막을 필요가 존재한다. 그리고 객체 생성을 막기 위해서 생성자를 호출하지 못하게 하거나 필드나 메서드를 사용하지 못하도록 막아야 한다. 이 때 접근 제한자를 사용할 수 있다.

  • public :
    단어 뜻 그대로 외부 클래스가 자유롭게 사용할 수 있도록 한다.
  • protected :
    같은 패키지 또는 자식 클래스에서 사용할 수 있도록 한다.
  • private :
    외부에서 사용할 수 없도록 막고, 내부에서만 사용이 가능하다.
  • default :
    같은 패키지에 소속된 클래스에서만 사용할 수 있다.
profile
let's go insane

0개의 댓글