Java 접근제한자, final, static, 블럭, 캡슐화(Encapsulation)

자바는 객체지향 프로그래밍 언어라서 캡슐화의 개념을 사용한다. 캡슐화는 외부 객체가 객체의 내부 구조를 알지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용하는 것을 의미한다. 이 캡슐화를 위해서 접근제한자를 사용한다.

접근제한자

프로그래밍 도구의 기본적인 목표는 생각하는 것을 자유롭게 표현할 수 있도록 하는 것이다. 하지만 자유 같은 변화를 수용하기 위해서는 다양한 규제가 필요해지게 된다.

접근 제한자는 데이터에 직접 접근하는 것에 대한 방지책이다.

접근제한자의 종류

  • Public - 외부에서 직접 접근하거나 호출할 수 있다.
  • Protected - 현재 클래스와 동일 패키지이거나 다른 패키지이더라도 상속 시에는 접근하거나 호출할 수 있다.
  • Default - 현재 클래스와 동일한 패키지 내에서만 접근하거나 호출할 수 있다.
  • Private - 현재 클래스의 {} 바깥쪽에서는 절대로 보이지 않는다. 자기 클래스가 아닌 바깥에서 호출할 수 없다.

접근제한자를 어디에 쓸까?

  • 클래스 : public(외부에 클래스를 노출), default(현재 패키지 내에서만 보이겠다)
  • 인스턴스 : public, protected, default, private(protected, default는 메소드를 이용해서 접근한다.)
  • 메소드 : public, protected, default, private(추상 메소드의 형태로 메소드를 만들 때는 private으로 선언할 수 없다)

final

  • 메소드 선언 시에 넣으면 현재의 메소드를 수정할 수 없게 한다. 오버라이드해서 스스로 재정의하는 작업을 막는다.
  • 클래스 선언 시에 넣으면 상속하지 못하게 된다.
  • 변수 선언 시에 넣으면 변수의 값을 변경할 수 없게 한다.
  • 생성자에는 붙일 수 없다.

static

Static을 사용하면 ‘모든 객체가 공유’한다는 의미다.

인스턴스가 자신의 인스턴스 변수를 한 인스턴스에서 바꿔봤자 새로운 인스턴스를 만들면 새 인스턴스 변수로 나오니까 공유가 되지않는다. 그 값 자체를 공유하고 싶을 때 쓴다. 즉, 인스턴스 간에 공유된다.

static을 사용할 때 인스턴스는 필요없다. 그냥 클래스이름.메소드, 클래스이름.변수로 쓸 수 있다.

  • static 변수(클래스 변수)
    • Static은 인스턴스와 묶이는 데이터가 아니라 클래스와 묶이는 데이터라서 static이 붙은 변수를 클래스 변수라고 한다.
    • 인스턴스가 아닌 클래스의 변수이기 때문에 별도의 인스턴스를 생성할 필요 없이 그냥 사용할 수 있다.
    • static이 붙은 변수는 당연히 일반 메소드에 쓰일 수 있다. 변수가 먼저 메모리에 올라가니까
  • static 메소드(클래스 메소드)
    • 마찬가지로 클래스에서 나온 인스턴스의 데이터에 영향을 받지 않고 완벽히 동일하게 동작하는 메소드.
    • 모든 인스턴스가 클래스에 정의된 방법대로 사용하게 된다.
    • Ex) Integer.parseInt()의 경우, 문자열을 int 값으로 변경시키는데 어느 상황이든 인스턴스의 데이터에 영향을 받지 않고 같은 기능만 제공한다.
    • Static이 붙은 메소드에서는 클래스의 인스턴스 변수나 인스턴스의 메소드를 사용할 수 없다. 왜냐하면 클래스의 인스턴스 변수를 사용할 때 인스턴스화 되어서 인스턴스로 생성되어야 하는데 그 전에 static 메소드가 먼저 메모리에 할당 되어 쓰일 수 없기 때문.
    • 그래서 static은 this를 못 쓴다. this 자체가 객체에서 찾는 건데 static은 객체 없이 만들어질 수 있으므로.
    • 물론 static이 붙은 메소드에 static이 붙은 인스턴스 변수라면 상관없다.

그러면 언제 static을 쓸까?

데이터를 보관할 필요가 없을 때 쓴다. 데이터를 활용해야 되는 경우인지, 아닌지.

  • 예를 들어, 랜덤 seed를 가지게 하는 방법 중에 Math.random()과 Random클래스의 nextDouble()이 있다. Random클래스를 이용하면 Random r1 = new Random(11)으로 각 인스턴스가 자신만의 Seed 값을 갖게 할 수 있다. 그러나 Math.random()은 static으로 매번 동일한 방법으로 동작한다.
  • 즉, 어떤 메소드가 완전히 인스턴스의 데이터와 독립적인 결과를 만들어내야 하는 상황에서 static을 쓴다.
  • static을 쓸 때 되는 지 안 되는 지 헷갈린다면 이것만 잘 기억하면 될 것 같다. static과 클래스는 메모리에 먼저 할당되기 때문에 인스턴스를 이용해서 static과 클래스에 접근하는 건 상관이 없다. 왜냐면 인스턴스를 이용해서(다 생성되고 난 후에) 이미 할당 된 곳(클래스와 static)에 접근하는 거니까. 하지만 static과 클래스를 이용해서 인스턴스에 접근하는 건 아직 인스턴스가 생성되지 않았음에도 불구하고 static과 클래스가 이용하려는 것과 같다.
  • Static은 속도는 빠르지만 GC의 대상이 아니라서 메모리에 계속 상주해있기 때문에 주의해야 한다.

블럭

public class Person {

// 그냥 블럭에 static을 붙이면 클래스가 메모리에 로드될 때 딱 한 번 찍힌다. 딱 한 번만 할 일에 쓴다.
	static {
		System.out.println("static initializer.......");
		totalCount = 100;
	}

	// 그냥 블럭이라면 객체가 생성될 때마다 실행되는 블럭이다. 근데 어차피 생성자에서 해도 되니까 굳이 만들 필요가 없다. 모든 생성자들 앞에 뭔가 실행해주고 싶을 때 쓴다.
	{
		System.out.println("instance initalizer......");
	}

캡슐화(Encapsulation)

인스턴스 변수를 private으로 선언하는 이유는 캡슐화, 즉 정보은닉을 위해서이다.

그러나 데이터를 숨겨버리니 사용하기가 어려워졌다.

이에 따라서 조회만 할 수 있게 하는 메소드 getter와 데이터를 세팅할 수 있는 메소드 setter를 만들어 두고 사용하자는 게 나왔다.

private int val;
...
getVal(){ // 권한 체크에 쓰인다
	return val;
	}
setVal(int val){ // 입력 값 체크에 쓰인다
	this.val = val; 
}
AccessTest at = new AccessTest();
at.setVal(100);
int value = at.getVal();
  • Getter 메소드(get??라고 이름을 지은 메소드) : 보안을 위해서 인스턴스의 데이터를 private으로 처리하고 외부에서 필요하면 getter 메소드를 통해 알 수 있도록 한다. 메소드를 통해 인스턴스가 가진 데이터를 가져온다. 리턴한 값을 다른 변수에 저장하면 복사되기 때문에 원본을 알 수 없다.
  • Setter 메소드 : 데이터를 바꿀 때(저장할 때) Setter 메소드를 이용한다. 이렇게 했을 때 데이터가 손상되지 않는 이유는 at.getVal();로 데이터를 가져오지만 return값으로 하고 =으로 할당되어 복사되기 때문.