[Java-07] 객체지향프로그래밍Ⅱ - 패키지, import문, 제어자
1. 패키지(package)
1) 패키지(package)
- 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음(패키지)으로써 클래스를 효율적으로 관리할 수 있음
- 클래스 또는 인터페이스를 포함시킬 수 있음
- 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용함
- 모든 클래스는 반드시 하나의 패키지에 속해야 함
- 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있음
- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리임
2) 패키지의 선언
package 패키지명;
- 대소문자를 모두 허용하지만 클래스명과 구분하기 위해 소문자로 하는 것을 원칙으로 함
- 이름없는 패키지(unnamed package) : 소스파일에 자신이 속할 패키지를 지정하지 않은 클래스들이 자동적으로 속하는 패키지
- 큰 프로젝트나 Java API와 같은 클래스 라이브러리를 작성하는 경우, 미리 패키지를 구성하여 적용해야 함
package com.codechobo.book;
class PackageTest {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
C:\jdk1.8\work>javac -d . PackageTest.java
-d : 소스파일에 지정된 경로를 통해 패키지의 위치를 찾아서 클래스파일을 생성
(만약 지정된 패키지와 일치하는 디렉토리가 존재하지 않으면 자동적으로 생성)
2. import문
1) import문
- 클래스의 코드를 작성하기 전 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시하여 소스코드에 사용되는 클래스이름에서 패키지명을 생략할 수 있음
2) import문의 선언
일반적인 소스파일(*.java)의 구성
1. package문
2. import문
3. 클래스 선언
import 패키지명.클래스명;
또는
import 패키지명.*;
단, *을 사용하는 것은 하위 패키지의 클래스까지 포함하는 것은 아님
import java.text.SimpleDateFormat;
import java.util.Date;
class ImportTest {
public static void main(String[] args) {
Date today = new Date();
SimpleDateFormat date = new SimpleDateFormat("yyyy/MM/dd");
System.out.println(date.format(today)); // 결과 : 2021/02/24
}
}
- 모든 소스파일에는 묵시적으로 java.lang 패키지에 대한 import문이 선언되어 있음
import java.lang.*;
- 따라서 java.lang 패키지의 System, String 등을 패키지명 없이 사용할 수 있음
3) static import문
- static 멤버를 호출할 때 클래스 이름을 생략할 수 있음
- 특정 클래스의 static 멤버를 자주 사용할 때 편리하며 코드도 간결해짐
import static java.lang.System.out;
import static java.lang.Math.*;
class StaticImportEx1 {
public static void main(String[] args) {
// System.out.println(Math.random());
out.println(random());
// System.out.println(Math.PI);
out.println(PI);
}
}
3. 제어자(modifier)
1) 제어자란?
- 클래스, 변수, 메서드의 선언부와 함께 사용되어 부가적인 의미를 부여함
- 하나의 대상에 대해서 여러 제어자를 조합하여 사용할 수 있음
- 단, 접근 제어자는 네 가지 중 하나만 선택해서 사용할 수 있음 (ex) public private 동시 사용 불가
접근 제어자 public, protected, default, private
그 외 static, final, abstract, native, transient, synchronized, volatile strictfp
2) static - 클래스의, 공통적인
- 클래스변수(static 멤버변수)는 인스턴스에 관계없이 같은 값을 가짐
- 하나의 변수를 모든 인스턴스가 공유하기 때문
- static이 사용될 수 있는 곳 : 멤버변수, 메서드, 초기화 블럭
제어자 | 대상 | 의미 |
static | 멤버변수 | - 모든 인스턴스에 공통적으로 사용되는 클래스변수가 됨 - 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능 - 클래스가 메모리에 로드될 때 사용 |
메서드 | - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 됨 - static 메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없음 |
class StaticTest {
static int width = 200; // static변수
static int height = 100; // static변수
static {
// static변수의 복잡한 초기화 수행
}
static int max(int a, int b) { // static메서드
return a > b ? a : b;
}
}
3) final - 마지막의, 변경될 수 없는
- final이 사용될 수 있는 곳 : 클래스, 메서드, 멤버변수, 지역변수
제어자 | 대상 | 의미 |
final | 클래스 | - 변경될 수 없는 클래스, 확장될 수 없는 클래스가 됨 - final로 지정된 클래스는 다른 클래스의 조상이 될 수 없음 |
메서드 | - 변경될 수 없는 메서드 - final로 지정된 메서드는 오버라이딩을 통해 재정의될 수 없음 |
|
멤버변수 | - 변수 앞에 final이 붙으면 값을 변경할 수 없는 상수가 됨 | |
지역변수 |
final class FinalTest { // 조상이 될 수 없는 클래스
final int MAX_SIZE = 10; // 값을 변경할 수 없는 멤버변수(상수)
final void getMaxSize() { // 오버라이딩할 수 없는 메서드(변경불가)
final int LV = MAX_SIZE; // 값을 변경할 수 없는 지역변수(상수)
return MAX_SIZE;
}
}
※ 생성자를 이용한 final멤버 변수의 초기화
- final이 붙은 변수는 상수이므로 일반적으로 선언과 동시에 초기화
- 하지만 인스턴스변수의 경우 생성자에서 초기화할 수 있음
class Card {
final int NUMBER;
final String KIND;
static int w = 100;
static int h = 250;
Card(String kind, int num) {
KIND = kind;
NUMBER = num;
}
Card() {
this("HEART", 1);
}
}
class FinalCardTest {
public static void main(String[] args) {
Card c = new Card("HEART", 10);
// c.NUMBER = 5; // Error! final이 붙은 변수는 변경할 수 없음
System.out.println(c.Kind); // HEART
System.out.println(c.NUMBER); // 10
}
}
4) abstract - 추상의, 미완성의
- 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용
- abstract가 사용될 수 있는 곳 : 클래스, 메서드
제어자 | 대상 | 의미 |
abstract | 클래스 | - 클래스 내에 추상 메서드가 선언되어 있음을 의미함 |
메서드 | - 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알림 |
abstract class AbstractTest { // 추상 클래스 (추상 메서드를 포함한 클래스)
abstract void move(); // 추상 메서드 (구현부가 없는 메서드)
}
5) 접근 제어자(access modifier)
- 해당되는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한함
- 접근 제어자가 지정되어 있지 않다면 접근 제어자는 default임
- 접근 제어자가 사용될 수 있는 곳 : 클래스, 멤버변수, 메서드, 생성자
private 같은 클래스 내에서만 접근 가능
default 같은 패키지 내에서만 접근 가능
protected 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근 가능
public 접근 제한이 전혀 없음


대상 | 사용 가능한 접근 제어자 |
클래스 | public, (default) |
메서드 | public, protected, (default), private |
멤버변수 | |
지역변수 | 없 음 |
※ 접근 제어자를 사용하는 이유
- 외부로부터 데이터를 보호하기 위해
- 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해
- 객체지향개념의 캡슐화(encapsulation)에 해당하는 데이터 감추기(data hiding)
public class Time {
private int hour; // 접근제어자가 private로 지정된 변수
Time(int hour) {
this.hour = hour;
}
public int getHour() { return hour; }
public void setHour(int hour) {
if (hour < 0 || hour > 23) return;
this.hour = hour;
}
}
public class TimeTest {
public static void main(String[] args) {
Time t = new Time(10);
// t.hour = 13; // Error! 변수 hour의 접근 제어자가 private이므로 접근불가
t.setHour(12);
}
}
-> private를 사용하여 멤버변수로의 직접적인 접근을 허가하지 않고 메서드를 통한 접근만 허용하여 변수의 잘못된 변경을 예방함
※ 생성자의 접근 제어자
- 생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있음
- 생성자의 접근 제어자는 클래스의 접근 제어자와 다르게 지정할 수도 있음
ex) 생성자의 접근 제어자가 private
-> 클래스 외부에선 인스턴스 생성 불가능, 클래스 내부에선 인스턴스 생성 가능
-> 대신 인스턴스를 생성해서 반환해주는 public메서드를 제공해야 함 (단, static이어야 함)
class Singleton {
...
private static Singleton s = new Singleton();
private Singleton() {
...
}
// 인스턴스를 생성하지 않고도 호출할 수 있어야 하므로 static이어야 함
public static Singleton getInstance() {
return s;
}
}
6) 제어자(modifier)의 조합
- 대상에 따라 사용할 수 있는 제어자
대상 | 사용 가능한 제어자 |
클래스 | public, (default), final, abstract |
메서드 | 모든 접근 제어자, final, abstract, static |
멤버변수 | 모든 접근 제어자, final, static |
지역변수 | final |
제어자를 조합해서 사용할 때 주의할 점
1. 메서드에는 static과 abstract를 함께 사용할 수 없음
static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문
2. 클래스에 abstract와 final을 동시에 사용할 수 없음
확장할 수 없는 final과 상속을 통해 완성되는 abstract의 의미가 서로 모순되기 때문
3. abstract 메서드의 접근 제어자가 private일 수 없음
abstract는 자손클래스에서 구현해야하는데 private 메서드는 자손클래스에서 접근할 수 없기 때문
4. 메서드에 private와 final을 같이 사용할 필요는 없음
접근 제어자인 private인 메서드는 어차피 오버라이딩될 수 없기 때문
참고 - Java의 정석 3rd Edition (저자 : 남궁성, 출판 : 도우출판)