Study/JAVA

[JAVA] 9장 - 자바 제어자 1

오구민 2022. 10. 2. 22:57

이 챕터에서는 접근 지정자와 static에 대해 공부한다.

개념이 조금 어렵거나 이해되지 않으면, 유튜브 강의를 활용할 생각이다.

 

 

1. 접근 지정자

1.1 개념

자바 제어자는 클래스, 필드, 메서드, 생성자 등에게 어떠한 특징을 부여하는 문법 요소다.

접근 지정자는 자바 제어자의 한 종류로, 클래스, 멤버, 생성자 앞에 위치할 수 있고, 사용 범위를 정의하는 역할을 한다.

 

1.2 멤버 및 생성자의 접근 지정자

  1.2.1 멤버 및 생성자의 종류

  • public
  • protected
  • default (또는 package)
  • private

 

  1.2.2 사용 범위

  • public > protected > default > private

 

  1.2.3 각각의 특징

  • private : 자신의 클래스 내부에서만 사용할 수 있는 접근 지정자. 같은 멤버끼리만 사용 가능하며 외부 클래스에서는 전혀 사용할 수 없다.
  • default : 아무것도 작성하지 않으면 해당 접근 지정자로 자동 설정된다. 같은 패키지 안의 모든 클래스에서 사용할 수 있다. 'package 접근 지정자'라고도 불린다.
  • protected : default보다는 넓은 범위로, 같은 패키지의 모든 클래스 뿐만 아니라, 다른 패키지의 자식 클래스 안에서도 사용할 수 있다.
  • public : 동일 패키지의 모든 클래스에서는 물론이고, 다른 패키지의 모든 클래스에도 접근할 수 있다. (어디서든 사용 가능)

 

  1.2.4 예제

  • 패키지 3개, 클래스 5개로 이뤄진 예제다.
  • 각각의 접근성을 보는 예제다.
// # 2번 패키지, A 코드
package java_study_002;

public class A {
	public int a = 1;
	protected int b = 2;
	int c = 3;
	private int d = 4;
	
	public void print() {
		System.out.print(a + " ");
		System.out.print(b + " ");
		System.out.print(c + " ");
		System.out.print(d);
		System.out.println();
	}
}
// # 2번 패키지 B코드
package java_study_002;

public class B {
	public void print() {
		//객체 생성
		A a = new A();
		
		//멤버 활용
		System.out.print(a.a + " ");
		System.out.print(a.b + " ");
		System.out.print(a.c + " ");
		//System.out.print(a.d + " ");
		//private 접근 지정자로 지정되어 접근 불가
		System.out.println();
	}
}
// # 3번 패키지 C코드
package java_study_003;

import java_study_002.A;

public class C {
	public void print() {
		//객체 생성
		A a = new A();
		
		//멤버 활용
		System.out.print(a.a + " ");
		//System.out.print(a.b + " ");
		//System.out.print(a.c + " ");
		//System.out.print(a.d + " ");
		//public 접근 지정자만 접근 가능
		System.out.println();
	}
}
// # 3번 패키지 D코드
package java_study_003;

import java_study_002.A;

public class D extends A {
	// D 클래스는 A 클래스의 자식 클래스라는 뜻
	public void print() {
		//멤버 활용
		System.out.print(a + " ");
		System.out.print(b + " ");
		//System.out.print(c + " ");
		//System.out.print(d + " ");
		//자식 클래스는 다른 패키지에 있어도 객체의 생성 없이 protected 접근 지정자 필드까지 접근 가능
		System.out.println();
	}
}
// # 1번 패키지 main 코드
package java_study_001;

import java_study_002.A;
import java_study_002.B;
import java_study_003.C;
import java_study_003.D;

public class Java_001 {
	public static void main(String[] args) {
		A a = new A();
		B b = new B();
		C c = new C();
		D d = new D();
		
		a.print();
		b.print();
		c.print();
		d.print();
	}
}
// 결과
1 2 3 4
1 2 3
1
1 2

 

 

1.3 클래스의 접근 지정자

클래스에서는 public과 defualt 접근 지정자만 사용할 수 있다.

쉽게 말해 class 키워드 앞에 public이 붙어 있거나 붙어있지 않거나라는 것이다.

클래스를 default로 정의하게 되면, 다른 패키지에서는 임포트가 불가능해진다.

반대로 말하자면 public은 다른 패키지에서 임포트가 된다.

 

 

1.4 클래스 접근 지정자와 생성자 접근 지정자의 연관성

클래스에 생성자가 없을 때 컴파일러는 기본 생성자를 자동으로 추가한다고 했다.

이때 자동으로 추가되는 생성자의 접근 지정자는 클래스의 접근 지정자에 따라 결정된다.

 

  1.4.1 차이 예시

  • 클래스의 public : 다른 패키지에서 임포트 할 수 있음
  • 생성자의 public : 생성자를 호출해 객체를 생성할 수 있음

 

  1.4.2 여러 예시

  • public 클래스 : 다른 패키지로 임포트 가능
  • default 클래스 : 다른 패키지로 임포트 불가능
  • public 클래스의 default 생성자 : 다른 패키지로 임포트 가능 / 생성자는 호출 불가능
  • default 클래스의 public 생성자 : 다른 패키지로 임포트 불가 / 임포트되지 않아 생성자도 호출 불가능

 

  1.4.3 예제1 - 같은 패키지에서 클래스 접근 지정자와 생성자 접근 지정자

// # 같은 패키지 내 A 코드
package java_study_002;

//public 클래스
public class A {
	// public 기본 생성자를 컴파일러가 추가
}
// # 같은 패키지 내 B 코드
package java_study_002;

//default 클래스
class B {
	// default 기본 생성자를 컴파일러가 추가
}
// # 같은 패키지 내 C 코드
package java_study_002;

//public 클래스
public class C {
	C() {
		//default 생성자 직접 생성
	}
}
// # 같은 패키지 내 main 역할 코드
package java_study_002;

public class MainClassExample {
	public static void main(String[] args) {
		//객체 생성
		A a = new A();
		B b = new B();
		C c = new C();
	}
}

 

  1.4.4 예제2 - 다른 패키지에서 클래스 접근 지정자와 생성자 접근 지정자

  • 외부 패키지에 main 내용을 옮겼을 때의 동작이다.
// # 다른 패키지의 Main 역할 코드
// # A, B, C는 기존 코드와 동일하다.
package java_study_001;

import java_study_002.A;
//import java_stuyd_002.B; //default라 불가능
import java_study_002.C;

public class Java_001 {
	public static void main(String[] args) {
		//객체 생성
		A a = new A();
		//B b = new B();
		//C c = new C();
		//B는 default 클래스라 임포트 불가. 객체 선언과 생성자 호출 모두 불가능
		//C는 객체는 선언할 수 있지만 default 생성자이므로 생성자 호출이 불가능
	}
}

 

 

 

2. Static 제어자

2.1 개념

  • Static : 클래스의 멤버(필드, 메서드, 이너 클래스)에 사용하는 제어자
  • 인스턴스 멤버 : 객체 안에 있을 때 사용할 수 있는 상태가 되는 멤버 (앞에 static이 붙어 있지 않은 것)
  • 정적 멤버 : 앞에 static이 붙은 멤버 (객체의 생성 없이 '클래스명.멤버명'만으로 바로 사용 가능)

 

2.2 인스턴스 필드와 정적 필드

  2.2.1 인스턴스 필드

  • 인스턴스 필드를 사용하기 위해선 객체를 생성한 후, [ 참조 변수명.인스턴스 필드명 ]으로 사용이 가능하다.
  • 저장 공간은 객체 내부에 생성되므로, 객체를 생성해야만 사용이 가능하다.
  • 저장 공간이 힙 메모리에 위치하기 때문에 저장 공간에 값을 읽거나 쓰기 위해서는 참조 변수명을 사용해야 한다.

 

  2.2.2 정적 필드

  • [ 클래스명.정적 필드명 ]처럼 사용한다.
  • 클래스 내부에 저장 공간을 지니고 있기 때문에 객체 생성 없이 바로 사용이 가능하다.
  • 객체를 생성한 후 사용하는 법은 권장되지 않는다.
  • 객체 간 공유 변수의 성질이 있다.

 

  2.2.3 예제 - 1 : 인스턴스 필드와 정적 필드의 활용 방법

package java_study_001;

class A {
	int m = 3;
	static int n = 5;
}
public class Java_001 {
	public static void main(String[] args) {
		//인스턴스 필드 활용 방법(객체를 생성한 후 사용 가능);
		A a1 = new A();
		System.out.println(a1.m);
		
		//정적 필드 활용 방법
		//1. 객체 생성 없이 클래스명으로 바로 활용
		System.out.println(A.n);
		//2. 객체를 생성한 후 활용(권장하지 않음)
		A a2 = new A();
		System.out.println(a2.n);
	}
}

 

  2.2.4 예제 2 : 정적 필드의 공유

  • 인스턴스 필드 m은 객체 안에 실제 데이터값을 저장한다.
  • 정적 필드 n은 정적(클래스) 영역의 클래스 A 안에 실제 데이터값, 객체 안에는 실제 데이터값의 위칫값을 저장한다.
  • 즉, 아래 코드의 두 정적 필드 n이 가리키는 곳이 같기 때문에 값이 각각 8과 9로 나오는 것이다.
package java_study_001;

class A {
	int m = 3; //인스턴스 필드
	static int n = 5; // 정적 필드
}
public class Java_001 {
	public static void main(String[] args) {
		A a1 = new A();
		A a2 = new A();
		
		//인스턴스 필드
		a1.m = 5;
		a2.m = 6;
		System.out.println(a1.m);
		System.out.println(a2.m);
		
		//정적 필드
		a1.n = 7;
		a2.n = 8;
		System.out.println(a1.n);
		System.out.println(a2.n);
		
		A.n = 9;
		System.out.println(a1.n);
		System.out.println(a2.n);
	}
}
// 실행 결과
5
6
8
8
9
9

 

 

2.3 인스턴스 메서드와 정적 메서드

메서드는 필드와는 달리 모두 메모리의 첫 번째 영역에 위치한다.

메모리의 첫 번째 영역에는 클래스, 메서드, 정적 필드와 정적 메서드, 상숫값이 저장된다.

 

  2.3.1 인스턴스 메서드

  • 반드시 객체를 생성한 후에 사용이 가능하다.
  • 인스턴스 메서드 영역에서만 존재한다.

 

  2.3.2 정적 메서드

  • 클래스명으로도 바로 접근할 수 있고, 인스턴스 메서드처럼 객체로도 호출할 수 있다.
  • 클래스 내부에 존재한다.

 

  2.3.3 예제 1 : 인스턴스 메서드와 정적 메서드

package java_study_001;

class A {
	void abc() {  //인스턴스 메서드
		System.out.println("instance 메서드");
	}
	static void bcd() {  //정적 메서드
		System.out.println("static 메서드");
	}
}
public class Java_001 {
	public static void main(String[] args) {
		//인스턴스 메서드 활용 (객체를 생성한 후 사용 가능)
		A a1 = new A();
		a1.abc();
		
		//정적 메서드 활용
		//1. 클래스명으로 바로 접근해 사용
		A.bcd();
		//2. 객체를 생성한 후 사용(권장하지 않음)
		A a2 = new A();
		a2.bcd();
	}
}

 

 

2.4 정적 메서드 안에서 사용할 수 있는 필드와 메서드

정적 메서드 내에서는 정적 필드 또는 정적 메서드만 사용할 수 있다.

즉, 인스턴스 필드나 인스턴스 메서드는 사용할 수 없다는 뜻이다.

 

  2.4.1 왜?

  • 정적 멤버(정적 필드, 정적 메서드)는 객체의 생성 없이 실행될 수 있어야하기 때문이다.
  • 인스턴스 멤버는 반드시 객체를 생성한 후 사용할 수 있기 때문에, 객체 생성 이전에 실행하려면 내부에는 객체 생성 이전에 사용할 수 있는 요소들로만 구성돼야 할 것이다.
  • 까닭에 정적 메서드 내부에는 정적 멤버만 올 수 있다.

 

  2.4.2 그렇다면?

  • 이는 즉, 정적 메서드 내부에서는 this 키워드를 사용할 수 없다는 것을 의미한다.
  • this.가 자동으로 붙어야 하는 것은 인스턴스 멤버기 때문이다.

 

 

2.5 정적 초기화 블록

정적 필드를 초기화하기 위한 문법이 별도 제공되는 데, 이게 정적 초기화 블록(static {})이다.

정적 초기화 블록은 클래스가 메모리에 로딩될 때 가장 먼저 실행된다.

 

  2.5.1 인스턴스 필드의 초기화

일반적으로 인스턴스 필드의 초기화는 객체가 만들어지는 시점에서 이뤄진다.

즉, 객체가 생성자에서 만들어지므로 생성자 내에서 인스턴스 필드를 초기화하는 게 일반적이다.

 

  2.5.2 정적 필드의 초기화

하지만 정적 필드는 객체의 생성 이전에도 사용할 수 있어야 하므로 생성자가 호출되지 않은 상태에서도 초기화할 수 있어야 한다.

즉! 생성자에서는 정적 필드를 초기화할 수 없다. 그래서 정적 초기화 블록을 사용한다.

 

  2.5.3 예제 : 정적 초기화 블록

package java_study_001;

class A {
	int a;
	static int b;
	static {
		b = 5; // 정적 필드의 초기화는 static {} 내에서 수행
		System.out.println("클래스 A가 로딩 되었습니다!");
	}
	A() {
		a = 3; //인스턴스 필드 초기화는 일반적으로 생성자에서 수행한다.
	}
}
public class Java_001 {
	public static void main(String[] args) {
		System.out.println(A.b);
		//A.b가 실행되는 시점에 클래스 A가 메모리 로딩되며, 이때 static {} 초기화 블록 실행
	}
}

 

 

2.6 static main() 메서드

지금까지 작성했던 public static void main(String[] ar)도 정적 메서드다.

 

  2.6.1 왜 main() 메서드는 정적 메서드로 구성됐을까?

  • 실제로 프로그램을 실행할 때 main() 메서드가 가장 먼저 실행되는 이유는 자바 가상 머신이 main() 메서드를 실행시켜 주기 때문이다.

 

  2.6.2 자바 가상 머신은 어떤 방식으로 main() 메서드를 실행할까?

  • 자바 가상 머신은 프로그램을 실행할 때 [ 실행 클래스명.main() ]을 실행하므로 가장 먼저 실행한다.
  • main() 메서드가 인스턴스 메서드라면 먼저 객체를 생성하고, 객체의 참조 변수를 통해 main() 메서드를 호출해야 했을 것
  • 그러므로 가장 먼저 실행되는 메서드인 main() 메서드를 정적 메서드로 구성한 것이다.

 

 

 

 

 

 

앞으로의 공부 분량을 조금 조절하기로 했다.

난이도가 올라갈수록 이해와 블로그 업로드에 걸리는 시간이 길어지고 있다.

그 결과, 몇 시간씩 앉아있다가 비명을 지르는 나를 발견했다...

50페이지씩 매일 하려니 마치 자체 휴강을 밥먹듯하다가 시험기간에 벼락치기하는 대학생이 된 기분이다.

난 자체 휴강 해본 적도 없는데...😂 성실했는데...

 

개인 프로젝트도 해야 하는데... 이러다 개발자가 될 것 같다... 나는 QA가 좋다...

무리했다간 지쳐서 공부고 뭐고 나가 떨어질 테니, 할 수 있는 만큼만 해야겠다.