본문으로 건너뛰기

Ch 1.3 자바 기본 구조와 문법

이제 본격적으로 자바 코드를 작성하고 실행하는 방법을 배워보겠습니다. 이 챕터에서는 자바 파일의 기본 구조부터 입출력, 에러 종류, 그리고 간단한 계산기 프로그램까지 다룹니다.


1. 자바 파일 구조 완전 해부

자바 소스 파일(.java)은 정해진 구조를 따릅니다. 아래 예제를 통해 각 부분의 역할을 이해해봅시다.

// 1. 패키지 선언 (선택, 최상단에 위치)
package com.example.myapp;

// 2. import 선언 (사용할 외부 클래스 가져오기)
import java.util.Scanner;
import java.util.ArrayList;

// 3. 클래스 선언 (파일명과 동일해야 함)
public class MyFirstProgram {

// 4. 필드(인스턴스 변수) 선언
private String programName = "나의 첫 프로그램";

// 5. 정적 필드(클래스 변수) 선언
private static int runCount = 0;

// 6. main 메서드 - 프로그램 진입점
public static void main(String[] args) {

// 7. 지역 변수 선언 및 사용
String message = "Hello, Java!";
System.out.println(message);

runCount++;
System.out.println("실행 횟수: " + runCount);
}
}

각 구성 요소 설명

구성 요소위치필수 여부설명
package 선언최상단선택클래스의 네임스페이스 정의
import 선언package 다음선택외부 클래스 사용 선언
클래스 선언파일 본문필수모든 코드는 클래스 안에 있어야 함
main 메서드클래스 안실행 시 필수JVM이 찾는 프로그램 시작점

2. public static void main(String[] args) 완전 분석

이 긴 선언문은 자바에서 가장 중요한 구조입니다. 각 키워드의 의미를 하나씩 파악해봅시다.

public   static   void   main(String[] args)
1 2 3 4 5

1. public (접근 제어자)

  • 어디서든 이 메서드를 호출할 수 있다는 의미
  • JVM이 프로그램 외부에서 main 메서드를 호출하므로 반드시 public이어야 함

2. static (정적 메서드)

  • 객체를 생성하지 않고도 클래스에서 직접 호출할 수 있다는 의미
  • JVM은 프로그램 시작 시 어떤 객체도 만들지 않고 main을 호출하므로 반드시 static이어야 함

3. void (반환 타입)

  • 이 메서드가 아무것도 반환하지 않는다는 의미
  • 프로그램 종료 코드는 System.exit(0) 등으로 별도 처리

4. main (메서드 이름)

  • JVM이 시작점으로 인식하는 정해진 이름
  • 대소문자를 바꾸면(Main, MAIN) JVM이 인식하지 못함

5. String[] args (매개변수)

  • 명령줄에서 프로그램 실행 시 전달되는 인수를 String 배열로 받음
  • args는 관례적인 이름이며 arguments의 줄임말
public class CommandLineArgs {
public static void main(String[] args) {
System.out.println("전달된 인수 개수: " + args.length);

for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}

실행 예:

java CommandLineArgs Hello World 123
# 전달된 인수 개수: 3
# args[0] = Hello
# args[1] = World
# args[2] = 123
노트

Java 21부터는 단일 파일 프로그램에서 클래스 선언 없이 main 메서드만 작성하는 간소화된 main 메서드(Preview 기능) 를 지원하기 시작했습니다. 하지만 표준 형태를 먼저 익히는 것이 중요합니다.


3. 주석 (Comment)

주석은 코드에 설명을 남기는 방법입니다. 컴파일러가 무시하므로 프로그램 실행에 영향을 주지 않습니다.

3.1 한 줄 주석 (//)

public class CommentExample {
public static void main(String[] args) {
// 이것은 한 줄 주석입니다
int age = 25; // 나이 변수 선언 - 줄 끝에도 사용 가능

// System.out.println("이 줄은 실행되지 않습니다");

System.out.println("나이: " + age);
}
}

3.2 여러 줄 주석 (/* */)

public class MultiLineComment {
public static void main(String[] args) {
/*
* 여러 줄에 걸친 주석입니다.
* 코드 블록을 임시로 비활성화할 때 자주 사용합니다.
* 각 줄 앞의 * 는 관례적 표기이며 필수는 아닙니다.
*/
int x = 10;
int y = 20;

/* int z = x + y; -- 임시로 비활성화 */

System.out.println(x + y);
}
}

3.3 JavaDoc 주석 (/** */)

API 문서를 자동 생성하는 데 사용합니다. javadoc 도구가 이 주석을 읽어 HTML 문서를 만듭니다.

/**
* 사각형의 넓이를 계산하는 클래스입니다.
*
* @author 홍길동
* @version 1.0
* @since 2024-01-01
*/
public class Rectangle {

/**
* 너비와 높이를 받아 사각형의 넓이를 계산합니다.
*
* @param width 사각형의 너비 (양수여야 함)
* @param height 사각형의 높이 (양수여야 함)
* @return 계산된 넓이 (너비 × 높이)
* @throws IllegalArgumentException 너비 또는 높이가 0 이하인 경우
*/
public static double calculateArea(double width, double height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("너비와 높이는 양수여야 합니다.");
}
return width * height;
}

public static void main(String[] args) {
double area = calculateArea(5.0, 3.0);
System.out.println("넓이: " + area); // 15.0
}
}

주요 JavaDoc 태그:

태그설명예시
@param매개변수 설명@param name 사용자 이름
@return반환값 설명@return 계산 결과
@throws발생 가능한 예외@throws NullPointerException
@author작성자@author 홍길동
@version버전@version 2.1
@since추가된 버전@since Java 8
@see참고 항목@see String#length()
@deprecated사용 중단 표시@deprecated 다음 버전에서 제거

4. 명명 규칙 (Naming Conventions)

자바는 업계 표준 명명 규칙을 따릅니다. 이 규칙을 지키면 다른 개발자가 코드를 읽기 쉬워집니다.

4.1 camelCase - 변수, 메서드

첫 단어는 소문자, 이후 단어의 첫 글자는 대문자

// 변수 이름 (camelCase)
int studentAge = 20;
String firstName = "길동";
boolean isLoggedIn = false;
double monthlyIncome = 3500000.0;

// 메서드 이름 (camelCase)
public void printStudentInfo() { }
public int calculateTotalScore() { return 0; }
public boolean isValidEmail(String email) { return true; }

4.2 PascalCase - 클래스, 인터페이스, Enum

모든 단어의 첫 글자를 대문자로 (UpperCamelCase라고도 함)

// 클래스 이름 (PascalCase)
public class StudentManager { }
public class HttpRequestHandler { }

// 인터페이스 이름 (PascalCase)
public interface Printable { }
public interface DataProcessor { }

// Enum 이름 (PascalCase)
public enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY }

4.3 UPPER_SNAKE_CASE - 상수

모든 문자 대문자, 단어 구분은 언더스코어(_)

// 상수 (static final 변수)
public static final double PI = 3.141592653589793;
public static final int MAX_RETRY_COUNT = 3;
public static final String DEFAULT_CHARSET = "UTF-8";
public static final int HTTP_OK = 200;

4.4 패키지 이름 - 소문자

package com.mycompany.project.util;   // 모두 소문자
package kr.co.example.service; // 도메인 역순

명명 규칙 종합 예제:

package com.example.school;  // 패키지: 소문자

public class StudentGradeManager { // 클래스: PascalCase

private static final int MAX_SCORE = 100; // 상수: UPPER_SNAKE_CASE
private static final String PASS_GRADE = "합격";

private String studentName; // 변수: camelCase
private int examScore;
private boolean hasPassed;

public StudentGradeManager(String studentName, int examScore) {
this.studentName = studentName;
this.examScore = examScore;
this.hasPassed = examScore >= 60;
}

public void printGradeReport() { // 메서드: camelCase
String result = hasPassed ? PASS_GRADE : "불합격";
System.out.printf("%s: %d점 (%s)%n", studentName, examScore, result);
}

public static void main(String[] args) {
StudentGradeManager student = new StudentGradeManager("홍길동", 85);
student.printGradeReport(); // 홍길동: 85점 (합격)
}
}
경고

자바 명명 규칙을 지키지 않아도 컴파일은 되지만, 팀 프로젝트에서 코드 리뷰 시 지적을 받거나 코드 품질 도구(Checkstyle, SonarQube)에서 경고가 발생합니다. 처음부터 습관을 들이는 것이 좋습니다.


5. 자바 코딩 컨벤션

5.1 들여쓰기

4칸 공백(스페이스)을 사용합니다. 탭 대신 공백을 권장합니다.

public class IndentExample {
public static void main(String[] args) {
if (true) { // 4칸 들여쓰기
System.out.println("if");
if (true) {
System.out.println("중첩 if"); // 8칸 들여쓰기
}
}
}
}

5.2 중괄호 위치

여는 중괄호 {는 같은 줄 끝에, 닫는 중괄호 }는 새 줄에 작성합니다 (K&R 스타일).

// 올바른 방법 (K&R 스타일)
public void goodStyle() {
if (condition) {
doSomething();
} else {
doOther();
}
}

// 지양하는 방법 (Allman 스타일 - 자바에서는 비표준)
public void differentStyle()
{
if (condition)
{
doSomething();
}
}

5.3 공백

// 연산자 양쪽에 공백
int result = a + b; // 좋음
int result = a+b; // 지양

// 콤마 뒤에 공백
method(arg1, arg2, arg3); // 좋음
method(arg1,arg2,arg3); // 지양

// 메서드 이름과 괄호 사이에 공백 없음
void myMethod() { } // 좋음
void myMethod () { } // 지양

6. 출력 메서드 완전 정리

6.1 세 가지 출력 메서드 비교

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

// 1. System.out.println: 출력 후 줄바꿈
System.out.println("첫 번째 줄");
System.out.println("두 번째 줄");
// 출력:
// 첫 번째 줄
// 두 번째 줄

// 2. System.out.print: 출력 후 줄바꿈 없음
System.out.print("A");
System.out.print("B");
System.out.print("C");
System.out.println(); // 수동 줄바꿈
// 출력: ABC

// 3. System.out.printf: C언어 방식 포맷 출력 (줄바꿈 없음)
System.out.printf("이름: %s, 나이: %d%n", "홍길동", 25);
// 출력: 이름: 홍길동, 나이: 25
}
}

6.2 printf 포맷 지정자

printf는 C언어에서 가져온 형식화 출력 방법입니다.

지정자타입설명예시
%d정수10진수 정수printf("%d", 42)42
%f실수부동소수점printf("%f", 3.14)3.140000
%.2f실수소수점 2자리printf("%.2f", 3.14159)3.14
%s문자열문자열printf("%s", "Java")Java
%c문자단일 문자printf("%c", 'A')A
%b불리언true/falseprintf("%b", true)true
%n줄바꿈OS 독립적 개행printf("Hi%n")Hi\n
%10d정수10자리 오른쪽 정렬printf("%10d", 42) 42
%-10d정수10자리 왼쪽 정렬printf("%-10d", 42)42
%05d정수5자리, 빈자리는 0printf("%05d", 42)00042
public class PrintfExample {
public static void main(String[] args) {
String name = "김철수";
int age = 28;
double height = 175.5;
double score = 95.678;

// 기본 포맷
System.out.printf("이름: %s%n", name);
System.out.printf("나이: %d세%n", age);
System.out.printf("키: %.1fcm%n", height);

// 소수점 자릿수 제어
System.out.printf("점수: %.2f%n", score); // 95.68

// 정렬과 패딩
System.out.printf("|%10s|%-10s|%n", "오른쪽", "왼쪽");
System.out.printf("|%10d|%-10d|%n", 42, 42);
System.out.printf("|%010d|%n", 42); // |0000000042|

// 성적표 예제
System.out.println("=== 성적표 ===");
System.out.printf("%-8s %5s %5s%n", "이름", "점수", "등급");
System.out.printf("%-8s %5d %5s%n", "홍길동", 95, "A");
System.out.printf("%-8s %5d %5s%n", "김영희", 82, "B");
System.out.printf("%-8s %5d %5s%n", "이철수", 71, "C");
}
}

출력 결과:

이름: 김철수
나이: 28세
키: 175.5cm
점수: 95.68
| 오른쪽|왼쪽 |
| 42|42 |
|0000000042|
=== 성적표 ===
이름 점수 등급
홍길동 95 A
김영희 82 B
이철수 71 C

7. Scanner를 이용한 사용자 입력

Scanner 클래스를 사용하면 콘솔에서 사용자 입력을 받을 수 있습니다.

import java.util.Scanner;

public class ScannerExample {
public static void main(String[] args) {
// Scanner 객체 생성 (System.in = 표준 입력 스트림)
Scanner scanner = new Scanner(System.in);

System.out.print("이름을 입력하세요: ");
String name = scanner.nextLine(); // 한 줄 전체 읽기

System.out.print("나이를 입력하세요: ");
int age = scanner.nextInt(); // 정수 읽기

System.out.print("키를 입력하세요 (cm): ");
double height = scanner.nextDouble(); // 실수 읽기

System.out.printf("안녕하세요, %s님! 나이 %d세, 키 %.1fcm이시군요.%n",
name, age, height);

// 사용 후 Scanner 닫기 (자원 해제)
scanner.close();
}
}

주요 Scanner 메서드:

메서드읽는 타입설명
next()String공백 전까지 한 토큰 읽기
nextLine()String줄바꿈 전까지 전체 읽기
nextInt()int정수 읽기
nextDouble()double실수 읽기
nextBoolean()booleantrue/false 읽기
nextLong()long큰 정수 읽기
hasNext()boolean다음 토큰 존재 여부 확인
경고

nextInt() 또는 nextDouble() 뒤에 nextLine()을 호출하면 빈 문자열이 읽힙니다. 이는 숫자 입력 후 남아있는 줄바꿈 문자 때문입니다.

int age = scanner.nextInt();
scanner.nextLine(); // 버퍼에 남은 개행 문자 소비
String name = scanner.nextLine(); // 이제 정상적으로 읽힘

8. 변수 선언과 초기화

자바는 강한 타입 언어 로, 모든 변수는 사용 전 타입을 선언해야 합니다.

public class VariableExample {
public static void main(String[] args) {
// 선언과 초기화를 동시에
int count = 0;
String greeting = "안녕하세요";
double pi = 3.14159;
boolean isActive = true;

// 선언 후 나중에 초기화
int result;
result = 100; // 사용 전에 반드시 초기화

// 여러 변수를 한 줄에 선언 (같은 타입만 가능, 권장하지 않음)
int x = 1, y = 2, z = 3;

// Java 10+ var 키워드 (타입 추론)
var message = "Java 10부터 사용 가능"; // String으로 추론
var number = 42; // int로 추론

System.out.println(count + " " + greeting);
System.out.println("pi = " + pi);
System.out.println("result = " + result);
}
}

기본 데이터 타입 (Primitive Types)

타입크기기본값범위예시
byte1 byte0-128 ~ 127byte b = 100;
short2 byte0-32768 ~ 32767short s = 1000;
int4 byte0약 ±21억int i = 100000;
long8 byte0L약 ±922경long l = 100L;
float4 byte0.0f소수점 약 7자리float f = 3.14f;
double8 byte0.0소수점 약 15자리double d = 3.14;
char2 byte'\u0000'0 ~ 65535 (유니코드)char c = 'A';
boolean1 bitfalsetrue / falseboolean b = true;

9. 표현식(Expression)과 문장(Statement)

표현식 (Expression)

값을 계산하거나 반환하는 코드 조각입니다.

// 산술 표현식
3 + 4 // 값: 7
x * y // 값: x와 y의 곱
a > b // 값: true 또는 false

// 메서드 호출 표현식
Math.max(10, 20) // 값: 20
"Hello".length() // 값: 5

// 대입 표현식
x = 5 // 값: 5 (대입 후 x의 값)

문장 (Statement)

실행 가능한 최소 코드 단위로, 세미콜론(;)으로 끝납니다.

public class StatementExample {
public static void main(String[] args) {
// 선언문
int x = 10;

// 표현식 문장 (expression statement)
x = 20; // 대입
x++; // 증가 연산
System.out.println(x); // 메서드 호출

// 제어문 (블록을 포함, 세미콜론 불필요)
if (x > 15) {
System.out.println("15보다 큽니다");
}

// 블록 문장 (여러 문장을 중괄호로 묶음)
{
int temp = 100;
System.out.println("블록 안: " + temp);
}
// temp는 여기서 접근 불가 (블록 스코프)
}
}

10. 에러(오류)의 세 가지 종류

자바에서 발생하는 오류는 발생 시점에 따라 세 가지로 분류됩니다.

10.1 컴파일 에러 (Compile-time Error)

소스코드를 바이트코드로 변환하는 컴파일 단계 에서 발생합니다. IDE나 javac가 즉시 알려줘서 가장 잡기 쉬운 에러입니다.

public class CompileErrorExample {
public static void main(String[] args) {
// 컴파일 에러 예시들:

int x = "Hello"; // 타입 불일치 오류
// → error: incompatible types: String cannot be converted to int

System.out.println(y); // 선언하지 않은 변수 사용
// → error: cannot find symbol

if (true) // 세미콜론 누락 (바로 다음 줄에서 오류)
System.out.println("OK")
// → error: ';' expected
}
}

10.2 런타임 에러 (Runtime Error)

프로그램이 실행되는 도중 발생하는 에러입니다. 컴파일은 성공하지만 실행 시 예외(Exception)가 발생합니다.

public class RuntimeErrorExample {
public static void main(String[] args) {
// 1. NullPointerException: null 참조에 메서드/필드 접근
String s = null;
System.out.println(s.length()); // NullPointerException 발생!

// 2. ArrayIndexOutOfBoundsException: 배열 범위 초과 접근
int[] arr = new int[3]; // 인덱스 0, 1, 2만 유효
arr[5] = 10; // ArrayIndexOutOfBoundsException 발생!

// 3. NumberFormatException: 잘못된 형식으로 변환
int n = Integer.parseInt("abc"); // NumberFormatException 발생!

// 4. ArithmeticException: 0으로 나누기
int result = 10 / 0; // ArithmeticException 발생!
}
}

10.3 논리 에러 (Logic Error)

컴파일과 실행은 모두 성공하지만 결과가 의도와 다른 에러입니다. 가장 발견하기 어렵습니다.

public class LogicErrorExample {
public static void main(String[] args) {
// 섭씨를 화씨로 변환하는 공식: F = C * 9/5 + 32
double celsius = 100.0;

// 논리 에러: 정수 나누기로 인해 9/5가 1이 됨
double fahrenheit = celsius * 9/5 + 32; // 잘못된 계산: 132.0
// 올바른 계산: celsius * 9.0/5 + 32 → 212.0

System.out.println(celsius + "°C = " + fahrenheit + "°F");
// 출력: 100.0°C = 132.0°F (오답! 정답은 212.0°F)
}
}

에러 비교 요약:

종류발생 시점발견 용이성예시
컴파일 에러javac 실행 시매우 쉬움 (IDE가 즉시 표시)문법 오류, 타입 불일치
런타임 에러프로그램 실행 중보통 (스택 트레이스 제공)NullPointerException
논리 에러결과 확인 후어려움 (디버거 필요)잘못된 알고리즘

11. 실전 예제: 사칙연산 계산기

지금까지 배운 내용을 종합한 완전한 계산기 프로그램입니다.

import java.util.Scanner;

/**
* 사칙연산 계산기
* 사용자로부터 두 숫자와 연산자를 입력받아 결과를 출력합니다.
*/
public class Calculator {

/**
* 두 정수의 덧셈 결과를 반환합니다.
*/
public static double add(double a, double b) {
return a + b;
}

/**
* 두 정수의 뺄셈 결과를 반환합니다.
*/
public static double subtract(double a, double b) {
return a - b;
}

/**
* 두 정수의 곱셈 결과를 반환합니다.
*/
public static double multiply(double a, double b) {
return a * b;
}

/**
* 두 정수의 나눗셈 결과를 반환합니다.
*
* @throws ArithmeticException 0으로 나누려 할 때 발생
*/
public static double divide(double a, double b) {
if (b == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다!");
}
return a / b;
}

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

System.out.println("===== 사칙연산 계산기 =====");
System.out.println("연산자: + (덧셈), - (뺄셈), * (곱셈), / (나눗셈)");
System.out.println();

// 첫 번째 숫자 입력
System.out.print("첫 번째 숫자를 입력하세요: ");
double num1 = scanner.nextDouble();

// 연산자 입력
System.out.print("연산자를 입력하세요 (+, -, *, /): ");
String operator = scanner.next();

// 두 번째 숫자 입력
System.out.print("두 번째 숫자를 입력하세요: ");
double num2 = scanner.nextDouble();

// 계산 수행
double result;
boolean isValid = true;

switch (operator) {
case "+":
result = add(num1, num2);
break;
case "-":
result = subtract(num1, num2);
break;
case "*":
result = multiply(num1, num2);
break;
case "/":
if (num2 == 0) {
System.out.println("오류: 0으로 나눌 수 없습니다!");
isValid = false;
result = 0;
} else {
result = divide(num1, num2);
}
break;
default:
System.out.println("오류: 올바르지 않은 연산자입니다 '" + operator + "'");
isValid = false;
result = 0;
}

// 결과 출력
if (isValid) {
System.out.println();
System.out.println("===== 계산 결과 =====");

// 정수로 딱 떨어지면 .0 없이 출력
if (result == (long) result) {
System.out.printf("%.0f %s %.0f = %.0f%n",
num1, operator, num2, result);
} else {
System.out.printf("%.2f %s %.2f = %.4f%n",
num1, operator, num2, result);
}
}

scanner.close();
}
}

실행 예시 1 (덧셈):

===== 사칙연산 계산기 =====
연산자: + (덧셈), - (뺄셈), * (곱셈), / (나눗셈)

첫 번째 숫자를 입력하세요: 15
연산자를 입력하세요 (+, -, *, /): +
두 번째 숫자를 입력하세요: 27

===== 계산 결과 =====
15 + 27 = 42

실행 예시 2 (나눗셈):

첫 번째 숫자를 입력하세요: 10
연산자를 입력하세요 (+, -, *, /): /
두 번째 숫자를 입력하세요: 3

===== 계산 결과 =====
10.00 / 3.00 = 3.3333

실행 예시 3 (0으로 나누기):

첫 번째 숫자를 입력하세요: 5
연산자를 입력하세요 (+, -, *, /): /
두 번째 숫자를 입력하세요: 0
오류: 0으로 나눌 수 없습니다!

IntelliJ 단축키 팁:

  • psvm + Tab: public static void main(String[] args) {} 자동 완성
  • sout + Tab: System.out.println() 자동 완성
  • souf + Tab: System.out.printf() 자동 완성
  • Ctrl + Shift + F10: 현재 파일 실행
  • Ctrl + /: 선택된 줄 주석 처리/해제
  • Alt + Enter: 오류에 대한 빠른 수정 제안 표시

요약

항목핵심 내용
파일 구조package → import → class → method 순서
main 메서드public static void main(String[] args) - JVM 진입점
주석// 한 줄, /* */ 여러 줄, /** */ JavaDoc
명명 규칙변수/메서드: camelCase, 클래스: PascalCase, 상수: UPPER_SNAKE_CASE
출력println(줄바꿈), print(줄바꿈 없음), printf(포맷 지정)
입력Scanner 클래스로 표준 입력 처리
에러 종류컴파일 에러, 런타임 에러, 논리 에러