[TIL] Java Package & Import

revo·2026년 4월 8일

자바

목록 보기
18/30
post-thumbnail

1. Package란?

클래스를 논리적으로 묶는 단위이자 파일 시스템의 디렉토리 구조와 1:1로 대응된다.

src/
├── com/
│   └── myapp/
│       ├── model/
│       │   └── User.java        → package com.myapp.model;
│       └── service/
│           └── UserService.java → package com.myapp.service;

선언은 파일의 첫 번째 줄에 위치해야 한다.

package com.myapp.model;  // 이 파일이 속한 패키지 선언

2. Import란?

다른 패키지에 있는 클래스를 현재 파일에서 쓸 수 있도록 참조하는 선언이다.
import는 클래스를 "복사"하거나 "포함"하는 게 아니라, 컴파일러에게 풀 경로(FQCN)를 알려주는 것이다.

FQCN (Fully Qualified Class Name): com.myapp.model.User 처럼 패키지 + 클래스명 전체


예제 1 - 기본 package / import 구조

파일 구조

src/
├── com/myapp/model/User.java
└── com/myapp/Main.java

User.java

package com.myapp.model;

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void printInfo() {
        System.out.println("이름: " + name + ", 나이: " + age);
    }
}

Main.java

package com.myapp;

import com.myapp.model.User;  // User 클래스 import

public class Main {
    public static void main(String[] args) {
        User user = new User("워그레이몬", 25);
        user.printInfo();
    }
}
이름: 워그레이몬, 나이: 25

예제 2 - import 없이 FQCN으로 직접 사용

import를 쓰지 않으면 클래스를 쓸 때마다 풀 경로를 명시해야 한다.

package com.myapp;

public class Main {
    public static void main(String[] args) {

        // import 없이 FQCN으로 직접 참조
        com.myapp.model.User user = new com.myapp.model.User("브이몬", 30);
        user.printInfo();
    }
}
이름: 브이몬, 나이: 30

import는 이 FQCN을 반복해서 쓰지 않기 위한 단축 선언이다. 동작 결과는 동일하다.


예제 3 - 와일드카드 import (*)

같은 패키지의 클래스를 여러 개 쓸 때 *로 한 번에 선언할 수 있다.

와일드카드의 실제 동작 원리

import com.myapp.model.*을 선언해도 컴파일러는 패키지 전체를 무조건 읽어들이는 게 아니다.
소스 코드에서 실제로 사용된 클래스 이름을 먼저 확인하고, 그 클래스가 * 패키지 안에 있는지 탐색한다.

컴파일 결과인 .class 파일에는 실제 사용된 클래스의 FQCN만 기록된다.
런타임에서도 JVM 클래스 로더는 참조가 발생하는 시점에만 해당 클래스를 로딩한다.

예제 코드

파일 구조

src/
└── com/myapp/model/
    ├── User.java
    ├── Product.java
    └── Order.java

User.java

package com.myapp.model;

public class User {
    static {
        System.out.println("[클래스 로딩] User 로딩됨");
    }

    public void hello() {
        System.out.println("User 사용");
    }
}

Product.java

package com.myapp.model;

public class Product {
    static {
        System.out.println("[클래스 로딩] Product 로딩됨");
    }

    public void hello() {
        System.out.println("Product 사용");
    }
}

Order.java

package com.myapp.model;

public class Order {
    static {
        System.out.println("[클래스 로딩] Order 로딩됨");
    }

    public void hello() {
        System.out.println("Order 사용");
    }
}

static { } 블록은 해당 클래스가 JVM에 로딩되는 순간 딱 한 번 실행된다.
로딩 시점을 눈으로 확인하기 위해 사용했다.

Main.java

package com.myapp;

import com.myapp.model.*;  // model 패키지 전체 선언

public class Main {
    public static void main(String[] args) {
        System.out.println("=== main 시작 ===");

        User user = new User();   // User만 실제 사용
        user.hello();

        // Product, Order는 import 선언됐지만 사용 안 함
    }
}
=== main 시작 ===
[클래스 로딩] User 로딩됨
User 사용
//

//Source code recreated from a .class file by IntelliJ IDEA
//(powered by FernFlower decompiler)

//


package myapp;


import myapp.model.User;


public class Main {
	public static void main(String[] args) {
		System.out.println("=== main 시작 ===");	
		User user = new User();
		user.hello();
	}
}

ProductOrderimport com.myapp.model.*에 포함되어 있었지만
실제로 사용되지 않았으므로 로딩 자체가 발생하지 않는다.

와일드카드 import 주의점

import com.myapp.*        → com.myapp 패키지의 클래스만 포함
                            com.myapp.model.User 는 포함되지 않음

*는 해당 패키지의 클래스만 가져온다. 하위 패키지는 포함되지 않는다.


예제 4 - 같은 이름의 클래스가 두 패키지에 존재할 때

java.util.Datejava.sql.Date는 이름이 같다.
이런 경우 둘 다 import할 수 없고, 하나는 반드시 FQCN으로 써야 한다.

package com.myapp;

import java.util.Date;  // 이것만 import

public class Main {
    public static void main(String[] args) {

        Date utilDate = new Date();  // java.util.Date → import된 것 사용
        java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());  // FQCN 사용

        System.out.println("util.Date : " + utilDate);
        System.out.println("sql.Date  : " + sqlDate);
    }
}
util.Date : Wed Apr 08 21:00:00 KST 2026
sql.Date  : 2026-04-08

예제 5 - static import

클래스의 static 멤버(필드, 메서드)를 클래스명 없이 바로 사용할 수 있다.

package com.myapp;

import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;

public class Main {
    public static void main(String[] args) {

        double radius = 5.0;

        // Math. 없이 바로 사용 가능
        double area = PI * pow(radius, 2);
        double diagonal = sqrt(pow(3, 2) + pow(4, 2));

        System.out.println("원의 넓이: " + area);
        System.out.println("빗변의 길이: " + diagonal);
    }
}
원의 넓이: 78.53981633974483
빗변의 길이: 5.0

예제 6 - default package (패키지 선언 없음)

패키지를 선언하지 않으면 default package에 속한다.

// package 선언 없음 → default package

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("default package입니다.");
    }
}
default package입니다.

default package에 있는 클래스는 다른 패키지에서 import할 수 없다.


예제 7 - 같은 패키지 내 클래스는 import 불필요

src/
└── com/myapp/
    ├── Calculator.java
    └── Main.java

Calculator.java

package com.myapp;

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

Main.java

package com.myapp;

// Calculator는 같은 패키지이므로 import 불필요

public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println("합계: " + calc.add(10, 20));
    }
}
합계: 30

핵심 정리

항목내용
package 위치파일의 첫 번째 줄 (주석 제외)
import 위치package 선언 다음, class 선언 이전
import A.*A 패키지의 클래스만 포함, 하위 패키지 미포함
와일드카드 성능컴파일/런타임 모두 사용된 클래스만 로딩, 성능 차이 없음
와일드카드 vs 단일 import성능은 동일, 차이는 가독성과 명시성
import 생략 가능한 경우java.lang 패키지, 같은 패키지 내 클래스
이름 충돌 시하나만 import하고 나머지는 FQCN으로 직접 사용
static importstatic 멤버를 클래스명 없이 사용할 때

0개의 댓글