Java Basic

Acorn Academy 구라쌤·2024년 11월 15일
post-thumbnail

1. Java 개요

Java 언어 소개

Java는 전 세계적으로 널리 사용되는 객체지향 프로그래밍 언어로, 강력한 플랫폼 독립성과 안정성을 갖추고 있습니다. 다양한 애플리케이션 개발에 활용될 수 있으며, 특히 대규모 웹 애플리케이션, 엔터프라이즈 시스템, 모바일 애플리케이션 등에 강점을 보입니다. 아래는 Java 언어의 주요 특징과 장점에 대한 설명입니다.

1. 주요 특징

1. 객체지향 프로그래밍 (Object-Oriented Programming, OOP)

  • Java는 클래스 기반의 객체지향 언어로, 모든 것이 객체로 구성됩니다.
  • 코드의 재사용성, 확장성, 유지보수성을 높여주며, 대규모 프로젝트에 적합합니다.

2. 플랫폼 독립성 (Write Once, Run Anywhere)

  • Java는 바이트코드로 컴파일되며, JVM(Java Virtual Machine) 위에서 실행됩니다.
  • 덕분에 Windows, Mac, Linux 등 운영체제에 구애받지 않고 동일한 코드를 실행할 수 있습니다.

3. 안정성 및 보안성

  • 메모리 관리를 자동화하는 Garbage Collection 기능을 제공하여 메모리 누수를 방지합니다.
  • Java는 여러 가지 보안 기능을 내장하고 있어, 네트워크 기반 애플리케이션 개발에 적합합니다.

4. 풍부한 표준 라이브러리

  • Java는 데이터 구조, 파일 입출력, 네트워킹, 다중 스레딩 등 다양한 기능을 제공하는 표준 라이브러리를 포함하고 있습니다.
  • 개발자는 필요한 기능을 직접 구현하지 않고 라이브러리를 활용하여 효율적인 코드를 작성할 수 있습니다.

5. 멀티스레딩 지원

  • Java는 멀티스레딩을 기본적으로 지원하며, 병렬 처리를 통해 애플리케이션 성능을 향상시킬 수 있습니다.
  • 특히 서버와 같은 고성능 애플리케이션 개발에 유리합니다.

2. Java의 역사

Java는 1995년 Sun Microsystems에서 처음 출시되었으며, 현재는 Oracle에서 관리하고 있습니다. Java는 최초 개발 당시 가전제품 및 소형 디바이스를 위한 언어로 설계되었으나, 시간이 흐르면서 다양한 환경에서 널리 사용되는 범용 언어로 발전했습니다.

3. 주요 활용 분야

  • 웹 애플리케이션: Spring, JSP 등을 이용한 대규모 웹 애플리케이션 개발
  • 모바일 애플리케이션: Android 애플리케이션 개발에 주로 사용
  • 엔터프라이즈 애플리케이션: 은행, 보험 등 대규모 비즈니스 환경에서 활용
  • 빅데이터 및 데이터 분석: Hadoop, Spark 등 빅데이터 도구에서 Java 기반의 API 제공
  • 임베디드 시스템: IoT 기기와 같은 다양한 하드웨어에서도 사용

Java는 강력한 객체지향 프로그래밍 언어로, 안정성과 플랫폼 독립성을 제공하며 다양한 분야에서 널리 사용되고 있습니다. Java를 통해 프로그래밍의 기본 원칙을 배우고, 복잡한 시스템을 개발할 수 있는 능력을 갖출 수 있습니다.

Java 학습을 통해 탄탄한 프로그래밍 기초와 더불어, 다양한 환경에서의 개발 경험을 쌓을 수 있습니다.

2. 기본 문법

Hello World App 만들기

Java에서 가장 기본적인 프로그램인 "Hello World"를 작성해 보겠습니다. 이 예제를 통해 Java의 기본 구조와 실행 방법을 이해할 수 있습니다.

1. 예제코드

아래는 Java로 작성한 "Hello World" 프로그램 코드입니다. HelloWorld.java 파일로 저장합니다.

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

2. 코드 설명

  • public class HelloWorld: 모든 Java 프로그램은 클래스부터 시작합니다. 여기서 클래스 이름은 HelloWorld이며, 파일 이름도 HelloWorld.java로 저장해야 합니다.
  • public static void main(String[] args): Java 애플리케이션이 실행되는 메인 메서드입니다. 프로그램의 시작점으로, main 메서드가 없으면 Java 프로그램은 실행되지 않습니다.
  • System.out.println("Hello, World!");: System.out.println 메서드를 사용하여 콘솔에 "Hello, World!" 문자열을 출력합니다.

3. 프로그램 실행 방법

  1. 컴파일:

    • 명령 프롬프트(또는 터미널)에서 파일이 저장된 디렉터리로 이동합니다.
    • 다음 명령어를 입력하여 Java 파일을 컴파일합니다.
      javac HelloWorld.java
    • 컴파일이 성공하면 HelloWorld.class 파일이 생성됩니다.
  2. 실행:

    • 컴파일된 파일을 실행하려면 다음 명령어를 입력합니다.
      java HelloWorld
    • 콘솔에 Hello, World!가 출력되면 성공적으로 프로그램이 실행된 것입니다.

4. 출력 결과

Hello, World!

이 "Hello World" 예제는 Java의 기본적인 프로그램 구조와 콘솔 출력 방법을 이해하는 데 도움이 됩니다. Java 개발의 첫걸음으로, 이 프로그램을 직접 작성하고 실행해 보세요.

기본 데이터 타입

Java에는 총 8개의 기본 데이터 타입(Primitive Types)이 있습니다. 이 기본 타입들은 메모리에 직접 값을 저장하는 단순한 데이터 값입니다. 각 데이터 타입은 고정된 메모리 크기를 가지며, 특정 범위 내의 값만 저장할 수 있습니다.

1. 정수 타입 (Integral Types)

byte

  • 크기: 8비트
  • 범위: -128 ~ 127
  • 기본 값: 0
  • 특징: 메모리를 절약할 수 있는 작은 크기의 정수 타입으로, 256 범위 내의 값을 다룰 때 유용합니다.
byte b = 100;

short

  • 크기: 16비트
  • 범위: -32,768 ~ 32,767
  • 기본 값: 0
  • 특징: byte보다 넓은 범위가 필요하지만 int까지는 필요 없는 경우에 사용됩니다.
short s = 10000;

int

  • 크기: 32비트
  • 범위: -2,147,483,648 ~ 2,147,483,647
  • 기본 값: 0
  • 특징: 일반적인 정수형 데이터 타입으로 가장 널리 사용됩니다.
int i = 100000;

long

  • 크기: 64비트
  • 범위: -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
  • 기본 값: 0L
  • 특징: int 범위를 초과하는 큰 정수 값을 저장할 때 사용합니다. 값 뒤에 L을 붙여 구분합니다.
long l = 10000000000L;

2. 실수 타입 (Floating-Point Types)

float

  • 크기: 32비트
  • 범위: 약 ±3.4 * 10^38 (7자리 정확도)
  • 기본 값: 0.0f
  • 특징: 소수점을 다룰 수 있으며, 값 뒤에 f를 붙여 표시합니다.
float f = 3.14f;

double

  • 크기: 64비트
  • 범위: 약 ±1.7 * 10^308 (15자리 정확도)
  • 기본 값: 0.0d
  • 특징: 높은 정확도가 필요한 소수점 데이터를 저장할 때 사용하며, d를 붙이거나 생략할 수 있습니다.
double d = 3.141592653589793;

3. 문자 타입 (Character Type)

char

  • 크기: 16비트
  • 범위: \u0000 (0) ~ \uffff (65,535)
  • 기본 값: \u0000
  • 특징: 한 글자를 저장할 수 있는 유니코드 타입으로, 작은 따옴표를 사용하여 문자 하나를 저장합니다.
char c = 'A';

4. 논리 타입 (Boolean Type)

boolean

  • 크기: 1비트
  • 범위: true 또는 false
  • 기본 값: false
  • 특징: 참(true) 또는 거짓(false) 값을 가지며, 조건문과 논리 연산에서 주로 사용됩니다.
boolean isJavaFun = true;

데이터 타입 요약표

타입크기기본 값범위 또는 정확도
byte8비트0-128 ~ 127
short16비트0-32,768 ~ 32,767
int32비트0-2,147,483,648 ~ 2,147,483,647
long64비트0L-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
float32비트0.0f약 ±3.4 * 10^38 (7자리)
double64비트0.0d약 ±1.7 * 10^308 (15자리)
char16비트\u0000\u0000 (0) ~ \uffff (65,535)
boolean1비트falsetrue 또는 false

Java의 기본 데이터 타입들은 프로그램이 필요한 자료를 효율적으로 저장하고 처리할 수 있도록 설계되어 있습니다.

변수와 상수

Java에서 변수와 상수는 데이터를 저장하고 관리하는 중요한 요소입니다. 변수는 프로그램 실행 중에 값을 변경할 수 있는 반면, 상수는 선언 후 값이 변경되지 않는 특성을 가집니다. 아래는 Java에서 변수와 상수를 정의하고 사용하는 방법입니다.

1. 변수 (Variable)

변수는 데이터를 저장하기 위한 메모리 공간을 의미하며, 변수의 값은 프로그램 실행 중에 변경될 수 있습니다. 변수는 타입, 변수 이름, 으로 구성됩니다.

변수 선언 및 초기화

변수는 먼저 선언하고, 필요할 때 초기화하여 사용할 수 있습니다.

// 변수를 선언만 하고 값을 넣지 않는다
int number;

// 변수에 값 대입
number = 10;

// 선언과 동시에 초기화
int age = 25;

변수 규칙

  • 변수 이름은 문자, 숫자, _(언더스코어) 또는 $ 기호로만 구성할 수 있습니다.
  • 변수 이름은 숫자로 시작할 수 없습니다.
  • 대소문자를 구분하며, 일반적으로 camelCase를 사용합니다.
  • Java의 예약어는 변수 이름으로 사용할 수 없습니다.

변수의 종류

  1. 지역 변수: 메서드 내에서 선언되며, 해당 메서드가 실행되는 동안만 사용할 수 있습니다.
  2. 인스턴스 변수: 클래스에 속하며, 각 객체마다 다른 값을 가질 수 있습니다.
  3. 클래스 변수: static 키워드를 사용하여 선언되며, 클래스에 속하고 모든 객체가 공유합니다.

2. 상수 (Constant)

상수는 한번 초기화된 후에는 값을 변경할 수 없는 변수입니다. Java에서 상수는 final 키워드를 사용하여 선언하며, 대문자와 언더스코어를 사용하는 UPPER_CASE 형식으로 이름을 짓는 것이 관례입니다.

상수 선언 및 초기화

final int MAX_VALUE = 100;
  • final 키워드를 통해 값을 고정시킵니다.
  • 상수는 선언과 동시에 초기화해야 하며, 이후 값을 변경할 수 없습니다.
  • 클래스 수준에서 상수를 선언할 때 static 키워드를 함께 사용하여 모든 객체에서 공유할 수 있도록 설정할 수 있습니다.
public class MyClass {
    public static final double PI = 3.14159;
}

변수와 상수의 차이점

항목변수상수
변경 가능 여부변경 가능변경 불가능 (final로 선언)
키워드없음final
사용 예int age = 25;final double PI = 3.14159;
네이밍 규칙camelCaseUPPER_CASE
public class Example {
    public static final int MAX_SCORE = 100; // 상수 선언

    public static void main(String[] args) {
        int score = 90; // 변수 선언 및 초기화
        System.out.println("Score: " + score);
        System.out.println("Max Score: " + MAX_SCORE);
    }
}

위 코드에서 score는 변수로, 프로그램 실행 중 값이 변경될 수 있습니다. 반면 MAX_SCORE는 상수로 final 키워드를 사용하여 선언되었기 때문에 값이 변경되지 않습니다.

Java에서 변수와 상수를 적절히 사용하면 코드의 가독성과 유지보수성이 향상됩니다.

연산자 (Operators)

Java에서는 다양한 연산자를 제공하여 변수와 상수에 대해 여러 연산을 수행할 수 있습니다. 주로 사용하는 연산자에는 산술 연산자, 비교 연산자, 논리 연산자, 대입 연산자가 있습니다.

1. 산술 연산자

산술 연산자는 수학적 계산을 수행할 때 사용합니다.

연산자설명예제 코드결과
+덧셈a + ba와 b의 합
-뺄셈a - ba에서 b를 뺌
*곱셈a * ba와 b의 곱
/나눗셈a / ba를 b로 나눔
%나머지a % ba를 b로 나눈 나머지
int a = 10;
int b = 3;
System.out.println(a + b); // 13
System.out.println(a - b); // 7
System.out.println(a * b); // 30
System.out.println(a / b); // 3
System.out.println(a % b); // 1

2. 비교 연산자

비교 연산자는 두 값을 비교하여 참(true) 또는 거짓(false)을 반환합니다.

연산자설명예제 코드결과
==같다a == ba와 b가 같으면 true
!=같지 않다a != ba와 b가 다르면 true
>크다a > ba가 b보다 크면 true
<작다a < ba가 b보다 작으면 true
>=크거나 같다a >= ba가 b보다 크거나 같으면 true
<=작거나 같다a <= ba가 b보다 작거나 같으면 true
int a = 5;
int b = 10;
System.out.println(a == b); // false
System.out.println(a != b); // true
System.out.println(a > b);  // false
System.out.println(a < b);  // true
System.out.println(a >= b); // false
System.out.println(a <= b); // true

3. 논리 연산자

논리 연산자는 주로 조건문에서 사용되며, 여러 조건을 결합할 때 유용합니다.

연산자 설명 예제 코드 결과
&& 논리 AND (a > b) && (a > c) 두 조건 모두 참이면 true
|| 논리 OR (a > b) || (a > c) 둘 중 하나라도 참이면 true
! 논리 NOT (부정) !(a > b) 조건이 참이면 false, 거짓이면 true
boolean a = true;
boolean b = false;
System.out.println(a && b); // false
System.out.println(a || b); // true
System.out.println(!a);     // false

4. 대입 연산자

대입 연산자는 변수에 값을 할당할 때 사용합니다.

연산자설명예제 코드결과
=대입a = 5a에 5를 대입
+=더한 후 대입a += 3a에 3을 더하고 대입
-=뺀 후 대입a -= 2a에서 2를 빼고 대입
*=곱한 후 대입a *= 4a에 4를 곱하고 대입
/=나눈 후 대입a /= 2a를 2로 나누고 대입
%=나머지 후 대입a %= 3a를 3으로 나눈 나머지를 대입
int a = 10;
a += 5;  // a = a + 5; 결과: 15
a -= 3;  // a = a - 3; 결과: 12
a *= 2;  // a = a * 2; 결과: 24
a /= 4;  // a = a / 4; 결과: 6
a %= 5;  // a = a % 5; 결과: 1

5. 증감 연산자

증감 연산자는 변수의 값을 1씩 증가시키거나 감소시킬 때 사용합니다.

연산자설명예제 코드결과
++1 증가a++a를 1 증가
--1 감소a--a를 1 감소
int a = 10;
System.out.println(a++); // 10 (출력 후 1 증가, a는 11)
System.out.println(++a); // 12 (1 증가 후 출력)
System.out.println(a--); // 12 (출력 후 1 감소, a는 11)
System.out.println(--a); // 10 (1 감소 후 출력)

6. 3항 연산자

3항 연산자(ternary operator)는 조건에 따라 다른 값을 선택하는 간단한 방법을 제공합니다. if-else 구문의 축약형으로, 코드가 더 간결해집니다. Java에서 3항 연산자는 다음과 같은 형식을 가집니다.

3항 연산자 형식

조건식 ? 참일 때의 값 : 거짓일 때의 값;
  • 조건식: true 또는 false로 평가되는 조건식입니다.
  • 참일 때의 값: 조건식이 true일 때 반환되는 값입니다.
  • 거짓일 때의 값: 조건식이 false일 때 반환되는 값입니다.
int a = 10;
int b = 20;

// a가 b보다 크면 "a가 더 큽니다"를, 그렇지 않으면 "b가 더 큽니다"를 출력
String result = (a > b) ? "a가 더 큽니다" : "b가 더 큽니다";
System.out.println(result); // 출력: b가 더 큽니다

3항 연산자를 사용하여 짝수와 홀수를 판별할 수 있습니다.

int number = 7;
String type = (number % 2 == 0) ? "짝수" : "홀수";
System.out.println(type); // 출력: 홀수

3항 연산자 vs if-else

3항 연산자는 간단한 조건 분기에서 유용하지만, 조건이 복잡하거나 여러 줄의 코드가 필요할 경우 if-else 구문이 더 적합합니다.

  • 장점: 코드가 간결해짐
  • 단점: 복잡한 조건문에서는 가독성 저하

3항 연산자는 코드의 간결함과 효율성을 높여주지만, 가독성을 유지하면서 사용할 수 있는 경우에만 사용하는 것이 좋습니다.

Java의 다양한 연산자를 활용하여 여러 가지 계산 및 조건 처리를 할 수 있습니다. 이러한 연산자들을 잘 이해하고 사용하면 더욱 효과적인 Java 프로그래밍을 할 수 있습니다.

3. 제어문

조건문

Java에서 조건문은 프로그램의 흐름을 제어하기 위해 사용됩니다. 특정 조건을 평가하고 그 결과에 따라 다른 코드 블록을 실행할 수 있습니다.

1. if 문

if 문은 조건이 true일 때만 특정 코드 블록을 실행합니다.

if (조건식) {
    // 조건이 true일 때 실행될 코드
}

예제

int number = 10;

if (number > 5) {
    System.out.println("number는 5보다 큽니다.");
}

2. if-else 문

if-else 문은 조건이 true일 때와 false일 때 각각 다른 코드를 실행합니다.

if (조건식) {
    // 조건이 true일 때 실행될 코드
} else {
    // 조건이 false일 때 실행될 코드
}

예제

int number = 3;

if (number > 5) {
    System.out.println("number는 5보다 큽니다.");
} else {
    System.out.println("number는 5보다 작거나 같습니다.");
}

3. if-else if-else 문

여러 조건을 순차적으로 평가하고, 해당 조건이 true인 블록을 실행합니다.

if (조건식1) {
    // 조건식1이 true일 때 실행될 코드
} else if (조건식2) {
    // 조건식2가 true일 때 실행될 코드
} else {
    // 위 조건이 모두 false일 때 실행될 코드
}

예제

int score = 85;

if (score >= 90) {
    System.out.println("A 학점");
} else if (score >= 80) {
    System.out.println("B 학점");
} else if (score >= 70) {
    System.out.println("C 학점");
} else {
    System.out.println("F 학점");
}

4. switch 문

switch 문은 하나의 변수 값을 기준으로 여러 경우(case)를 처리합니다. 각 case 블록은 해당 값과 일치할 때 실행됩니다.

switch (표현식) {
    case1:
        // 값1일 때 실행될 코드
        break;
    case2:
        // 값2일 때 실행될 코드
        break;
    default:
        // 모든 case에 해당하지 않을 때 실행될 코드
}

예제

int day = 3;

switch (day) {
    case 1:
        System.out.println("월요일");
        break;
    case 2:
        System.out.println("화요일");
        break;
    case 3:
        System.out.println("수요일");
        break;
    default:
        System.out.println("주말");
}

주의사항

  • break 키워드는 실행을 종료하고 switch 문 밖으로 빠져나오게 합니다. 생략하면 다음 case까지 실행됩니다.
  • default는 모든 조건에 해당하지 않을 때 실행됩니다.

5. 삼항 연산자

삼항 연산자는 간단한 조건문을 한 줄로 표현할 수 있는 방법입니다.

변수 = (조건식) ?1 :2;

예제

int number = 10;
String result = (number > 5) ? "크다" : "작다";

System.out.println(result); // 출력: 크다

6. 중첩 조건문

조건문 안에 또 다른 조건문을 사용하는 방식입니다.

if (조건식1) {
    if (조건식2) {
        // 조건식1과 조건식2가 모두 true일 때 실행될 코드
    }
}

예제

int number = 15;

if (number > 10) {
    if (number % 2 == 0) {
        System.out.println("10보다 큰 짝수입니다.");
    } else {
        System.out.println("10보다 큰 홀수입니다.");
    }
}

7. 조건문 작성 팁

  • 코드 가독성: 조건이 복잡할 경우, 변수에 의미 있는 이름을 부여하여 가독성을 높입니다.
  • 중복 제거: 동일한 코드가 여러 조건에 반복될 경우, 코드를 리팩토링합니다.
  • 논리 연산자: 조건식에서 && (AND), || (OR) 연산자를 적절히 활용합니다.
if (age >= 18 && age <= 65) {
    System.out.println("성인입니다.");
}

이와 같이 Java 조건문은 다양한 방식으로 활용 가능하며, 코드의 흐름을 유연하게 제어할 수 있습니다.

반복문 (Loop)

Java에서 반복문은 특정 조건이 충족될 때까지 코드 블록을 반복 실행하는 구조를 제공합니다. 반복문을 사용하여 동일한 작업을 여러 번 수행할 수 있습니다.

1. for 문

for 문은 반복 횟수가 명확할 때 사용됩니다. 초기화, 조건식, 증감식을 통해 반복을 제어합니다.

for (초기화; 조건식; 증감식) {
    // 반복할 코드
}

예제

for (int i = 0; i < 5; i++) {
    System.out.println("i의 값: " + i);
}

2. 확장 for 문

확장 for 문은 배열이나 컬렉션 같은 데이터 구조를 순차적으로 탐색할 때 사용합니다.

for (자료형 변수 : 배열이나 컬렉션) {
    // 배열 요소에 대해 반복할 코드
}

예제

int[] numbers = {1, 2, 3, 4, 5};

for (int number : numbers) {
    System.out.println(number);
}

3. while 문

while 문은 조건이 true인 동안 반복을 수행합니다. 조건을 먼저 평가하고, 조건이 참이면 블록을 실행합니다.

while (조건식) {
    // 조건이 true일 때 반복할 코드
}

예제

int i = 0;

while (i < 5) {
    System.out.println("i의 값: " + i);
    i++;
}

4. do-while 문

do-while 문은 while과 유사하지만, 먼저 블록을 실행한 후 조건을 평가합니다. 따라서 조건이 false라도 최소한 한 번은 실행됩니다.

do {
    // 최소 한 번 실행할 코드
} while (조건식);

예제

int i = 0;

do {
    System.out.println("i의 값: " + i);
    i++;
} while (i < 5);

5. 중첩 반복문 (Nested Loop)

중첩 반복문이란, 하나의 반복문 안에 또 다른 반복문이 포함된 구조를 말합니다. 이는 주로 2차원 데이터(예: 2D 배열) 처리나 특정 규칙에 따라 반복 작업을 수행할 때 사용됩니다.


중첩 반복문의 기본 구조

중첩 반복문은 외부 루프(Outer Loop)내부 루프(Inner Loop)로 구성됩니다. 외부 루프가 한 번 실행될 때마다 내부 루프가 처음부터 끝까지 실행됩니다.

public class NestedLoopExample {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) { // 외부 루프
            System.out.println("외부 루프 i: " + i);
            
            for (int j = 1; j <= 2; j++) { // 내부 루프
                System.out.println("  내부 루프 j: " + j);
            }
        }
    }
}

실행 결과

외부 루프 i: 1
  내부 루프 j: 1
  내부 루프 j: 2
외부 루프 i: 2
  내부 루프 j: 1
  내부 루프 j: 2
외부 루프 i: 3
  내부 루프 j: 1
  내부 루프 j: 2

설명

  1. 외부 루프 횟수 × 내부 루프 횟수 = 전체 반복 횟수
    • 예를 들어, 외부 루프가 3번, 내부 루프가 2번 반복되면 총 3 × 2 = 6번 실행됩니다.
  2. 내부 루프는 외부 루프 변수에 접근 가능
    • 내부 루프에서 외부 루프의 변수를 활용할 수 있습니다.

6. 반복문 제어문

Java에서는 반복문의 흐름을 제어하기 위한 몇 가지 키워드를 제공합니다.

break

break 문은 반복문을 즉시 종료합니다.

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break; // i가 5일 때 반복 종료
    }
    System.out.println(i);
}

continue

continue 문은 반복문의 현재 반복을 건너뛰고 다음 반복으로 진행합니다.

for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue; // 짝수일 때는 건너뜀
    }
    System.out.println(i);
}

return

return 문은 반복문을 포함한 메서드를 종료하고, 해당 메서드의 결과를 반환합니다. 일반적으로 메서드 내에서 사용되며, 반복문을 포함한 코드의 실행을 중단하고 메서드 자체를 종료합니다.

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        return; // 메서드를 종료하고 반환
    }
    System.out.println(i);
}

7. 무한 루프

Java에서 무한 루프란, 특정 조건 없이 반복문이 끝없이 실행되는 구조를 말합니다. 이는 의도적으로 작성하거나, 종료 조건을 잘못 설정했을 때 발생할 수 있습니다.

while 문을 사용한 무한 루프

public class InfiniteLoop {
    public static void main(String[] args) {
        while (true) { // 항상 true
            System.out.println("무한 루프 실행 중...");
        }
    }
}

for 문을 사용한 무한 루프

public class InfiniteLoop {
    public static void main(String[] args) {
        for (;;) { // 조건이 없음
            System.out.println("무한 루프 실행 중...");
        }
    }
}
  • for 문에서 조건식을 생략하면 기본적으로 항상 참으로 간주됩니다.

무한 루프의 제어 (탈출)

무한 루프는 break 문을 사용하여 제어할 수 있습니다. 특정 조건에서 반복문을 종료하도록 설정할 수 있습니다.

public class InfiniteLoopWithBreak {
    public static void main(String[] args) {
        int i = 0;

        while (true) { // 무한 루프
            System.out.println("i 값: " + i);
            i++;

            if (i >= 5) { // 특정 조건에서 탈출
                System.out.println("반복문 종료");
                break;
            }
        }
    }
}

실행 결과

i 값: 0
i 값: 1
i 값: 2
i 값: 3
i 값: 4
반복문 종료

8. 반복문 작성 팁

  • 조건문 최적화: 불필요한 반복을 피하기 위해 조건을 최적화합니다.
  • break와 continue 활용: 불필요한 연산을 줄이고, 특정 조건에서 반복을 효율적으로 제어합니다.
  • 가독성 유지: 중첩된 반복문이나 복잡한 조건은 함수로 분리하여 가독성을 높입니다.

이와 같이 Java의 반복문은 다양한 방식으로 사용 가능하며, 반복 작업을 효율적으로 수행할 수 있도록 합니다.

4. 배열 (Arrays)

Java에서 배열은 동일한 데이터 타입의 여러 값을 저장할 수 있는 자료 구조입니다. 배열은 고정된 크기를 가지며, 인덱스를 통해 각 요소에 접근할 수 있습니다.

1. 배열의 특징

  • 정적 크기: 배열의 크기는 선언 시 결정되며, 이후 변경할 수 없습니다.
  • 동일한 데이터 타입: 배열은 동일한 데이터 타입의 요소만 포함할 수 있습니다.
  • 인덱스 기반 접근: 배열의 첫 번째 요소는 인덱스 0을 가지며, 마지막 요소는 배열의 길이 - 1 인덱스를 가집니다.

2. 배열 선언 및 생성

Java에서는 배열을 선언하고 생성하여 사용할 수 있습니다.

// 1. 배열 선언
데이터타입[] 식별자;
데이터타입 식별자[]; // 이 형식도 가능

// 2. 배열 생성
new 데이터타입[크기];

// 선언과 생성을 동시에
데이터타입[] 식별자 = new 데이터타입[크기];

예제

// size 가 5인 정수형 배열 선언 및 생성
int[] numbers = new int[5];
int numbers2[] = new int[5];

// size 가 5인 문자열 배열 선언 및 생성
String[] names = new String[5];
String names2[] = new String[5];

3. 배열 초기화

배열을 생성할 때 각 요소의 값을 초기화할 수 있습니다.

// 배열 생성과 동시에 초기화
int[] numbers = {1, 2, 3, 4, 5};

배열을 생성하고 나서 요소에 값을 할당할 수도 있습니다.

// 0 으로 초기화된 size 가 3인 정수형 배열객체 생성
int[] numbers = new int[3];
// 0 번, 1 번, 2 번 요소에 값을 각각 할당하기
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

4. 배열 요소 접근

배열 요소는 인덱스를 사용하여 접근할 수 있습니다.

int[] numbers = {10, 20, 30, 40, 50};

// 첫 번째 요소에 접근
System.out.println(numbers[0]); // 출력: 10

// 세 번째 요소 변경
numbers[2] = 25;
System.out.println(numbers[2]); // 출력: 25

5. 배열의 길이

배열의 길이는 .length 속성을 사용하여 확인할 수 있습니다.

int[] numbers = {10, 20, 30, 40, 50};
System.out.println("배열의 길이: " + numbers.length); // 출력: 5

6. 배열과 반복문

배열은 반복문을 사용하여 쉽게 순회할 수 있습니다.

for 문을 이용한 배열 순회

int[] numbers = {10, 20, 30, 40, 50};

for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
}
/*
	-실행결과
	10
    20
    30
    40
    50
*/

확장 for 문을 이용한 배열 순회

int[] numbers = {10, 20, 30, 40, 50};

for (int number : numbers) {
    System.out.println(number);
}
/*
	-실행결과
	10
    20
    30
    40
    50
*/

7. 다차원 배열

Java에서는 2차원 이상의 다차원 배열을 생성할 수 있습니다. 2차원 배열은 행과 열로 이루어진 배열이며, 주로 테이블 형식의 데이터를 표현할 때 사용됩니다.

// 2차원 배열 선언 및 생성
int[][] matrix = new int[3][3];

// 2차원 배열 초기화
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 2차원 배열 요소 접근
System.out.println(matrix[0][1]); // 출력: 2

8. 다차원 배열 반복문

중첩된 반복문을 사용하여 다차원 배열을 순회할 수 있습니다.

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

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

9. Arrays 클래스

Java에서는 java.util.Arrays 클래스를 사용하여 배열 관련 다양한 기능을 제공받을 수 있습니다.

배열 정렬

import java.util.Arrays;

int[] numbers = {5, 3, 1, 4, 2};
Arrays.sort(numbers);

System.out.println(Arrays.toString(numbers)); // 출력: [1, 2, 3, 4, 5]

배열을 문자열로 변환

int[] numbers = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(numbers)); // 출력: [1, 2, 3, 4, 5]

배열의 특정 값 검색

int[] numbers = {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(numbers, 3);

System.out.println("3의 인덱스: " + index); // 정렬된 배열에서 3의 인덱스 출력

10. 배열 사용 시 주의 사항

  • 경계 검사: 배열의 인덱스는 0부터 시작하므로 배열의 길이 - 1까지 접근 가능합니다. 범위를 초과하는 인덱스에 접근하면 ArrayIndexOutOfBoundsException이 발생합니다.
  • 초기화되지 않은 배열 요소: 배열 생성 시 초기값이 자동으로 할당됩니다. 정수형 배열은 0, 논리형 배열은 false, 객체형 배열은 null로 초기화됩니다.

Java 배열은 고정 크기와 단일 데이터 타입으로 제한되지만, 효율적인 데이터 관리와 처리에 유용하게 사용됩니다. 배열을 활용하여 다양한 데이터 구조와 알고리즘을 구현할 수 있습니다.

5. 메소드

1. 메소드 (Method) 란?

Java에서 메소드는 특정 작업을 수행하는 코드 블록입니다. 메소드를 사용하면 코드의 재사용성을 높이고, 프로그램의 구조를 더욱 체계적으로 구성할 수 있습니다. 메소드는 입력값(매개변수)을 받아 처리하고, 결과를 반환할 수도 있습니다.

2. 메소드의 형식

Java에서 메소드는 다음과 같은 구조로 정의됩니다.

접근지정자 반환타입 메소드이름(매개변수 목록) {
    // 메소드의 실행 코드
}

구성 요소

  • 접근지정자: 메소드에 접근할 수 있는 범위를 지정합니다. (예: public, private, protected)
  • 반환타입: 메소드가 반환하는 값의 데이터 타입입니다. 반환값이 없으면 void로 지정합니다.
  • 메소드 이름: 메소드를 호출할 때 사용할 이름입니다. 보통 소문자로 시작합니다.
  • 매개변수 목록: 메소드가 처리할 데이터를 받기 위한 변수 목록입니다. (예: (int x, int y))
  • 메소드 바디: 중괄호 {}로 둘러싸인 부분으로, 메소드가 실행할 실제 코드입니다.
public int add(int a, int b) {
    int result = a + b;
    return result;
}

위 예제는 두 개의 int 매개변수를 받아 그 합을 반환하는 add 메소드를 정의합니다.


3. 메소드 호출

정의된 메소드는 메소드 이름을 통해 호출할 수 있습니다. 호출 시에는 메소드에 정의된 매개변수의 타입과 순서에 맞게 값을 전달해야 합니다.

메소드이름(인수);
public class Calculator {
    
    // 메소드 정의
    public int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        Calculator calc = new Calculator();
        
        // 메소드 호출
        int result = calc.add(5, 3);
        System.out.println("결과: " + result); // 출력: 결과: 8
    }
}

4. 메소드의 반환(return)값

메소드는 return 키워드를 사용하여 결과를 호출한 위치로 반환할 수 있습니다. 반환값의 타입은 메소드의 반환타입과 일치해야 합니다.

반환값이 있는 메소드

public int multiply(int a, int b) {
    return a * b;
}

반환값이 없는 메소드 (void)

public void printMessage() {
    System.out.println("Hello, World!");
}

5. 매개변수

메소드에 전달되는 값을 매개변수(parameter)라고 합니다. 메소드를 호출할 때 전달하는 실제 값을 인수(argument)라고 합니다.

public void greet(String name) {
    System.out.println("Hello, " + name + "!");
}

public static void main(String[] args) {
    greet("Alice"); // 출력: Hello, Alice!
}

위 예제에서 greet 메소드는 name이라는 문자열 매개변수를 받아, 인사 메시지를 출력합니다.


6. 메소드 오버로딩

Java에서는 같은 이름의 메소드를 여러 개 정의할 수 있습니다. 이를 메소드 오버로딩이라 하며, 매개변수의 타입이나 개수가 다를 때 유효합니다.

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

public double add(double a, double b) {
    return a + b;
}

위 예제에서는 add 메소드를 두 개 정의하였고, 하나는 int 타입, 다른 하나는 double 타입 매개변수를 받습니다. 호출 시 전달되는 인수의 타입에 따라 알맞은 메소드가 실행됩니다.


7. 메소드의 접근지정자

Java에서 메소드는 접근제어자를 통해 외부 접근을 제한할 수 있습니다.

  • public: 모든 클래스에서 접근 가능.
  • protected: 동일 패키지 또는 하위 클래스에서 접근 가능.
  • default (아무것도 작성하지 않은 경우): 동일 패키지에서만 접근 가능.
  • private: 동일 클래스 내에서만 접근 가능.
public class MyClass {

    public void publicMethod() {
        System.out.println("Public Method");
    }

    private void privateMethod() {
        System.out.println("Private Method");
    }

    protected void protectedMethod() {
        System.out.println("Protected Method");
    }

    void defaultMethod() {
        System.out.println("Default Method");
    }
}

8. static 메소드와 일반 메소드

Java에서는 메소드를 static으로 선언할 수 있습니다. static 메소드와 일반 메소드는 몇 가지 차이점이 있으며, 사용되는 목적과 방식이 다릅니다.

Static 메소드

static 메소드는 클래스 레벨에서 호출할 수 있는 메소드입니다. 객체를 생성하지 않고도 클래스 이름으로 직접 호출할 수 있습니다. static 메소드는 일반적으로 유틸리티 메소드나 특정 작업을 수행하기 위한 메소드로 사용됩니다.

특징

  • 객체 생성 없이 사용 가능: 클래스 이름으로 직접 호출할 수 있습니다.
  • 인스턴스 변수 접근 불가: static 메소드에서는 클래스의 인스턴스 변수에 접근할 수 없습니다. static 변수나 static 메소드에만 접근할 수 있습니다.
  • this 키워드 사용 불가: static 메소드 내에서는 this 키워드를 사용할 수 없습니다. this는 인스턴스를 가리키는데, static 메소드는 클래스 레벨에서 존재하기 때문에 인스턴스가 없습니다.
public class MathUtils {
    
    // static 메소드
    public static int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        // 객체 생성 없이 클래스 이름으로 호출
        int result = MathUtils.add(5, 3);
        System.out.println("결과: " + result); // 출력: 결과: 8
    }
}

일반 메소드 (Instance Method)

일반 메소드는 객체 인스턴스에서 호출되는 메소드입니다. 일반 메소드는 static 키워드가 없으며, 클래스의 인스턴스를 생성한 후 인스턴스 변수와 인스턴스 메소드에 접근할 수 있습니다.

특징

  • 객체 생성 후 호출 가능: 일반 메소드는 인스턴스를 생성한 후 인스턴스 이름으로 호출합니다.
  • 인스턴스 변수 접근 가능: 일반 메소드에서는 클래스의 인스턴스 변수와 다른 인스턴스 메소드에 접근할 수 있습니다.
  • this 키워드 사용 가능: 일반 메소드에서는 this 키워드를 사용하여 현재 객체를 참조할 수 있습니다.
public class Calculator {
    
    // 인스턴스 변수
    private int value;

    // 생성자
    public Calculator(int value) {
        this.value = value;
    }

    // 일반 메소드
    public int multiply(int factor) {
        return this.value * factor;
    }
}

public class Main {
    public static void main(String[] args) {
        // 객체 생성 후 메소드 호출
        Calculator calc = new Calculator(10);
        int result = calc.multiply(5);
        System.out.println("결과: " + result); // 출력: 결과: 50
    }
}

Static 메소드와 일반 메소드의 차이점

비교 항목Static 메소드일반 메소드
호출 방식클래스 이름으로 호출 (ClassName.method())객체 인스턴스에서 호출 (instance.method())
인스턴스 변수 접근불가능 (static 변수만 접근 가능)가능
this 키워드 사용불가능 (this는 인스턴스에 대한 참조이므로)가능 (현재 인스턴스에 대한 참조)
주요 용도유틸리티 메소드, 객체와 무관한 작업 수행인스턴스 변수나 인스턴스 메소드와 관련된 작업 수행
객체 생성 여부필요 없음객체 생성 필요

Static 메소드에서는 인스턴스 변수나 일반 메소드에 접근할 수 없기 때문에, 인스턴스와 독립적인 작업을 수행해야 합니다.
일반 메소드는 인스턴스가 필요하므로, 인스턴스 변수와 인스턴스 메소드에 자유롭게 접근할 수 있습니다.
과도한 static 메소드 사용은 객체 지향 설계 원칙을 해칠 수 있으므로, 객체의 상태나 행동과 관련된 작업은 일반 메소드로 정의하는 것이 좋습니다.

9. 메소드 작성 시 주의 사항

  • 메소드 이름: 의미 있는 이름을 사용하여 메소드가 수행하는 작업을 명확히 나타내야 합니다.
  • 매개변수: 필요한 만큼의 매개변수를 정의하고, 불필요하게 사용하지 않도록 주의합니다.
  • 반환값: 반환 타입이 void가 아닌 경우 반드시 return 문을 사용해야 합니다.
  • 코드 중복 방지: 동일한 코드가 여러 메소드에서 반복되지 않도록 메소드를 적절히 분리합니다.

Java에서 메소드는 프로그램의 가독성을 높이고 유지보수를 쉽게 해주는 중요한 요소입니다. 메소드의 정의와 호출을 이해함으로써 보다 효율적이고 체계적인 코드를 작성할 수 있습니다.

6. 객체지향 프로그래밍 기초

1. Java 클래스와 객체

Java에서 클래스와 객체는 객체 지향 프로그래밍(Object-Oriented Programming, OOP)의 핵심 요소입니다. 클래스를 통해 객체를 생성하고, 객체는 프로그램 내에서 데이터를 저장하고 행동을 정의하는 역할을 합니다.

2. 클래스 (Class)

클래스는 객체를 생성하기 위한 청사진(설계도)입니다. 클래스는 속성(필드)과 동작(메소드)을 포함할 수 있습니다. 하나의 클래스는 여러 객체를 생성하는 데 사용될 수 있으며, 각 객체는 고유한 상태를 가질 수 있습니다.

클래스 정의

Java에서 클래스는 class 키워드를 사용하여 정의합니다.

public class 클래스이름 {
    // 필드 (속성)
    데이터타입 변수이름;

    // 생성자
    public 클래스이름() {
        // 생성자 코드
    }

    // 메소드 (동작)
    반환타입 메소드이름() {
        // 메소드 코드
    }
}

가상의 Car 클래스 정의해 보기

public class Car {
    // 필드 (속성)
    String color;
    int speed;

    // 생성자
    public Car(String color, int speed) {
        this.color = color;
        this.speed = speed;
    }

    // 메소드 (동작)
    public void accelerate() {
        speed += 10;
        System.out.println("현재 속도: " + speed);
    }
}

위 예제에서 Car 클래스는 colorspeed라는 두 개의 속성을 가지고 있으며, accelerate() 메소드를 통해 속도를 증가시킵니다.


3. 객체 (Object)

객체는 클래스로부터 생성된 인스턴스(instance)입니다. 클래스가 설계도라면, 객체는 그 설계도로 만들어진 실제 사물에 해당합니다. 객체는 클래스의 속성과 동작을 가지며, 실제 데이터를 저장하고 메소드를 통해 행동을 수행합니다.

객체 생성

Java에서 객체는 new 키워드를 사용하여 아래와 같은 형식으로 생성합니다.

 new 클래스명(생성자에 전달할 값);

public class Main {
    public static void main(String[] args) {
        // Car 클래스의 객체 생성
        Car myCar = new Car("빨간색", 0);

        // 객체의 속성과 메소드 사용
        System.out.println("색상: " + myCar.color);
        myCar.accelerate();
    }
}

위 예제에서는 Car 클래스의 객체 myCar를 생성하여, color 속성을 출력하고 accelerate() 메소드를 호출하여 속도를 증가시킵니다.


4. 클래스와 객체의 구성 요소

1. 필드 (Field)

필드는 클래스의 속성을 나타내는 변수입니다. 필드는 클래스 내에서 선언되며, 객체의 상태를 저장하는 역할을 합니다.

public class Car {
    String color; // 필드
    int speed;    // 필드
}

2. 생성자 (Constructor)

생성자는 클래스의 인스턴스를 생성할 때 호출되는 특별한 메소드입니다. 생성자는 클래스 이름과 동일해야 하며, 반환 타입이 없습니다. 생성자를 통해 객체의 초기 상태를 설정할 수 있습니다.

public class Car {
    String color;
    int speed;

    // 생성자
    public Car(String color, int speed) {
        this.color = color;
        this.speed = speed;
    }
}

3. 메소드 (Method)

메소드는 객체의 동작을 정의합니다. 메소드는 객체의 상태를 변경하거나 특정 작업을 수행할 수 있습니다.

public class Car {
    int speed;

    // 메소드
    public void accelerate() {
        speed += 10;
    }
}

5. 클래스와 객체

// Dog 클래스를 정의하고 
public class Dog {
    // 필드
    String name;
    int age;

    // 생성자
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 메소드
    public void bark() {
        System.out.println(name + "가 멍멍 짖습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 정의된 Dog 클래스로 객체 생성
        Dog myDog = new Dog("바둑이", 3);

        // 객체의 속성 사용
        System.out.println("이름: " + myDog.name);
        System.out.println("나이: " + myDog.age);

        // 객체의 메소드 호출
        myDog.bark(); // 출력: 바둑이가 멍멍 짖습니다.
    }
}

위 예제에서는 Dog 클래스를 정의하고, nameage라는 필드를 가집니다. bark() 메소드를 통해 개가 짖는 동작을 정의하고 있습니다.


6. 생성자와 this 키워드

Java에서 생성자와 this 키워드는 객체 지향 프로그래밍의 중요한 개념입니다. 생성자는 객체가 생성될 때 호출되며, this 키워드는 객체 자기 자신을 참조하는 데 사용됩니다. 이 두 가지를 이해하면 클래스의 객체를 더 효율적으로 생성하고 관리할 수 있습니다.

생성자 (Constructor) 란?

생성자는 객체가 생성될 때 호출되는 특별한 메소드입니다. 생성자는 객체의 초기 상태를 설정하는 데 사용되며, 클래스 이름과 동일한 이름을 가지고 반환 타입이 없습니다.

생성자의 특징

  • 클래스 이름과 동일한 이름을 가집니다.
  • 반환 타입이 없음: 반환 타입을 명시하지 않습니다.
  • 객체 생성 시 자동 호출: new 키워드를 통해 객체가 생성될 때 자동으로 호출됩니다.
  • 생성자를 정의하지 않으면 기본 생성자가 자동으로 제공됩니다.

생성자 만드는 방법

public class 클래스이름 {
    // 생성자
    public 클래스이름() {
        // 초기화 코드
    }
}

예제

public class Car {
    String color;
    int speed;

    // 생성자
    public Car(String color, int speed) {
        this.color = color;
        this.speed = speed;
    }
}

위 예제에서는 Car 클래스의 생성자가 colorspeed를 초기화하는 역할을 합니다.


Default 생성자

Default 생성자는 매개변수가 없는 생성자입니다. 클래스를 정의할 때 생성자를 따로 정의하지 않으면 컴파일러가 자동으로 기본 생성자를 추가합니다.

public class Car {
    String color;
    int speed;

    // 기본 생성자
    public Car() {
        color = "검정색";
        speed = 0;
    }
}

위 예제에서는 Car 클래스에 매개변수가 없는 기본 생성자를 정의하여, colorspeed 필드의 초기값을 설정합니다.


this 키워드

this 키워드는 현재 객체 자신을 참조하는 키워드입니다. 객체의 필드와 메소드에 접근할 때 주로 사용되며, 특히 생성자나 메소드에서 매개변수와 필드 이름이 동일할 때 유용합니다.

this 키워드의 주요 용도

  1. 인스턴스 변수와 매개변수 구분: 생성자나 메소드에서 매개변수와 인스턴스 변수 이름이 동일할 때, this 키워드를 사용하여 인스턴스 변수를 참조할 수 있습니다.
  2. 다른 생성자 호출: 하나의 생성자에서 다른 생성자를 호출할 때 this() 구문을 사용합니다. 이를 생성자 오버로딩이라고 합니다.
  3. 현재 객체 참조 반환: 메소드에서 현재 객체 자체를 반환할 때 사용합니다.

this 키워드를 사용하여 인스턴스 변수와 매개변수 구분하기

public class Car {
    String color;
    int speed;

    // 생성자
    public Car(String color, int speed) {
        this.color = color; // this.color는 인스턴스 변수, color는 매개변수
        this.speed = speed; // this.speed는 인스턴스 변수, speed는 매개변수
    }
}

위 예제에서 this.colorthis.speed는 인스턴스 변수를 참조하며, colorspeed는 생성자의 매개변수를 나타냅니다.


this()를 사용한 생성자 오버로딩

  • Java에서는 하나의 클래스에 여러 개의 생성자를 정의할 수 있습니다.
  • 이를 생성자 오버로딩이라고 하며, this()를 사용하여 하나의 생성자에서 다른 생성자를 호출할 수 있습니다.
public class Car {
    String color;
    int speed;

    // 기본 생성자
    public Car() {
        this("검정색", 0); // 다른 생성자를 호출
    }

    // 매개변수가 있는 생성자
    public Car(String color, int speed) {
        this.color = color;
        this.speed = speed;
    }
}

위 예제에서 Car() 생성자는 this("검정색", 0); 구문을 통해 Car(String color, int speed) 생성자를 호출합니다. 이렇게 하면 코드 중복을 줄일 수 있습니다.


this 키워드를 사용하여 현재 객체 반환

  • 메소드에서 this를 반환하여 메소드 체이닝(method chaining)을 구현할 수 있습니다.
public class Car {
    String color;
    int speed;

    public Car setColor(String color) {
        this.color = color;
        return this; // 현재 객체 반환
    }

    public Car setSpeed(int speed) {
        this.speed = speed;
        return this; // 현재 객체 반환
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        
        // 메소드 체이닝
        myCar.setColor("빨간색").setSpeed(100);
    }
}

위 예제에서 setColor()setSpeed() 메소드는 this를 반환하여 메소드 체이닝을 가능하게 합니다.


Java의 생성자와 this 키워드는 객체 초기화와 클래스 내에서의 코드 재사용성을 높이는 데 중요한 역할을 합니다. 이 개념을 잘 활용하면 보다 효율적이고 유지보수하기 쉬운 코드를 작성할 수 있습니다.

7. 접근 지정자

Java에서 접근 지정자는 클래스, 메소드, 변수 등에 대한 접근 범위를 설정하는 키워드입니다. 접근 지정자를 사용하면 클래스 외부에서 접근할 수 있는 범위를 제어할 수 있으며, 캡슐화와 데이터 보호에 중요한 역할을 합니다.

Java에는 네 가지 주요 접근 지정자가 있습니다: public, protected, default (아무것도 지정하지 않을 때), private. 각각의 접근 지정자는 접근 가능한 범위가 다릅니다.

접근 지정자같은 클래스같은 패키지하위 클래스모든 클래스
public가능가능가능가능
protected가능가능가능불가능
default가능가능불가능불가능
private가능불가능불가능불가능

public 접근 지정자

public 접근 지정자는 모든 클래스에서 접근할 수 있도록 허용합니다. 클래스, 메소드, 필드에 public 접근 지정자를 사용할 수 있으며, 이를 사용하면 어느 패키지에서나 접근이 가능합니다.

예제

public class Car {
    public String color;

    public void drive() {
        System.out.println("Driving...");
    }
}

위 예제에서 color 필드와 drive() 메소드는 public 접근 지정자가 있어 모든 클래스에서 접근 가능합니다.


protected 접근 지정자

protected 접근 지정자는 같은 패키지 내의 클래스와 다른 패키지의 하위 클래스에서 접근할 수 있도록 허용합니다. 주로 상속 관계에서 사용됩니다.

예제

public class Animal {
    protected String name;

    protected void makeSound() {
        System.out.println("Some sound...");
    }
}
public class Dog extends Animal {
    public void bark() {
        System.out.println(name + "가 멍멍 짖습니다.");
    }
}

위 예제에서 name 필드와 makeSound() 메소드는 protected 접근 지정자로 선언되어, Animal 클래스와 같은 패키지 내의 클래스 또는 Animal의 하위 클래스인 Dog에서 접근할 수 있습니다.


default 접근 지정자

default 접근 지정자는 접근 지정자를 명시하지 않은 경우를 의미합니다. default 접근 지정자는 같은 패키지 내에서만 접근이 가능하며, 다른 패키지에서는 접근할 수 없습니다.

class Car {
    String color;

    void drive() {
        System.out.println("Driving...");
    }
}

위 예제에서 color 필드와 drive() 메소드는 default 접근 지정자로 선언되어, 같은 패키지 내에서만 접근 가능합니다. 다른 패키지에서는 접근할 수 없습니다.


private 접근 지정자

private 접근 지정자는 같은 클래스 내에서만 접근이 가능하도록 제한합니다. 다른 클래스에서는 접근할 수 없으며, 클래스 내부에서만 사용할 수 있습니다. 주로 데이터 캡슐화와 보안을 위해 사용됩니다.

예제

public class Car {
    private String color;

    private void startEngine() {
        System.out.println("Engine started");
    }

    public void drive() {
        startEngine(); // 같은 클래스 내에서 private 메소드 호출
        System.out.println("Driving...");
    }
}

위 예제에서 color 필드와 startEngine() 메소드는 private 접근 지정자로 선언되어, Car 클래스 외부에서는 접근할 수 없습니다. drive() 메소드는 Car 클래스 외부에서 접근할 수 있으며, 클래스 내부에서 startEngine() 메소드를 호출합니다.


다양한 접근 지정자 예제

public class Person {
    public String name;       // 모든 클래스에서 접근 가능
    protected int age;        // 같은 패키지와 하위 클래스에서 접근 가능
    String address;           // 같은 패키지 내에서만 접근 가능 (default)
    private String password;  // 같은 클래스 내에서만 접근 가능

    public Person(String name, int age, String address, String password) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.password = password;
    }

    // password에 대한 getter 메소드
    public String getPassword() {
        return this.password;
    }
}

위 예제에서 namepublic 접근 지정자로 설정되어 모든 클래스에서 접근 가능합니다. ageprotected로 설정되어, 같은 패키지나 하위 클래스에서 접근할 수 있습니다. addressdefault 접근 지정자로 설정되어 같은 패키지 내에서만 접근할 수 있습니다. passwordprivate 접근 지정자로 설정되어, 같은 클래스 내에서만 접근 가능합니다.


접근 지정자 사용 시 주의 사항

  1. 캡슐화 (Encapsulation): 데이터를 보호하고 외부에서 접근을 제한해야 할 경우 private을 사용하여 캡슐화를 강화합니다.
  2. 패키지 구조 고려: 클래스가 패키지 외부에서도 사용될 경우 public을 사용하지만, 그렇지 않을 경우 protecteddefault 접근 지정자를 고려합니다.
  3. 필요한 경우에만 public 사용: 모든 필드나 메소드를 public으로 설정하면 보안 문제가 발생할 수 있습니다. 가능한 필요한 경우에만 public을 사용하고, 최대한 접근을 제한합니다.

Java 접근 지정자는 클래스, 메소드, 필드에 대한 접근 범위를 제어하여 보안을 강화하고 코드를 더 구조화하고 관리하기 쉽게 만듭니다. 적절한 접근 지정자를 사용하여 프로그램의 안전성과 캡슐화를 높이는 것이 중요합니다.

7. 상속과 다형성

1. 상속 (Inheritance)

개념

  • 상속은 기존 클래스(부모 클래스 또는 슈퍼 클래스)의 속성과 메소드를 새로운 클래스(자식 클래스 또는 서브 클래스)에서 재사용하고 확장하는 개념입니다.
  • 코드의 재사용성을 높이고 객체 간 계층 구조를 형성할 때 사용됩니다.

특징

  • extends 키워드를 사용하여 상속을 선언합니다.
  • 자식 클래스는 부모 클래스의 모든 공개(public)보호(protected) 멤버를 상속받습니다.
  • 부모 클래스의 private 멤버는 상속되지 않으며, getter/setter 메소드를 통해 접근해야 합니다.
class Parent {
    String name;

    public void display() {
        System.out.println("This is the parent class.");
    }
}

class Child extends Parent {
    public void showName() {
        System.out.println("Name: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        Child child = new Child();
        child.name = "John"; // 부모 클래스의 멤버 사용
        child.display(); // 부모 클래스의 메소드 호출
        child.showName();
    }
}

2. 오버라이딩 (Method Overriding)

개념

  • 오버라이딩은 자식 클래스에서 부모 클래스의 메소드를 재정의하여 새로운 동작을 제공하는 기능입니다.
  • 메소드 이름, 매개변수, 반환 타입이 부모 클래스의 메소드와 동일해야 합니다.

특징

  • 부모 클래스의 메소드는 @Override 어노테이션을 사용하여 재정의합니다.
  • 접근 제한자는 부모 클래스의 메소드보다 더 좁게 설정할 수 없습니다.
  • 런타임 시 다형성을 구현하는 데 사용됩니다.
class Parent {
    public void display() {
        System.out.println("This is the parent class.");
    }
}

class Child extends Parent {
    @Override
    public void display() {
        System.out.println("This is the child class.");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent parent = new Child();
        parent.display(); // "This is the child class." 출력
    }
}

3. 다형성 (Polymorphism)

개념

  • 다형성은 같은 타입의 참조 변수로 여러 다른 객체를 참조할 수 있는 능력을 의미합니다.
  • 런타임 시점에서 실행되는 메소드가 결정됩니다.

업캐스팅 (Upcasting)

  • 자식 클래스 객체를 부모 클래스 타입으로 변환.
  • 자동 변환이 일어나며 명시적으로 캐스팅할 필요가 없습니다.

다운캐스팅 (Downcasting)

  • 부모 클래스 타입의 객체를 자식 클래스 타입으로 변환.
  • 명시적 캐스팅이 필요하며, 잘못된 캐스팅은 ClassCastException을 발생시킬 수 있습니다.

예제 코드

class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        // Upcasting
        Animal animal = new Dog();
        animal.sound(); // "Dog barks" 출력

        // Downcasting
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.sound(); // "Dog barks" 출력
        }
    }
}

4. 추상 클래스와 인터페이스

추상 클래스 (Abstract Class)

  • 인스턴스화할 수 없는 클래스입니다.
  • 추상 메소드(구현되지 않은 메소드)를 포함할 수 있습니다.
  • abstract 키워드로 선언됩니다.

추상 클래스 상속

  • 부분적 구현을 제공할 수 있습니다.
  • 추상 메소드는 자식 클래스에서 반드시 구현해야 합니다.
  • 상속받을 때 extends 키워드 사용.
abstract class Animal {
    public abstract void sound(); // 추상 메소드

    public void sleep() { // 일반 메소드
        System.out.println("Sleeping...");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // "Dog barks" 출력
        animal.sleep(); // "Sleeping..." 출력
    }
}

인터페이스 (Interface)

  • 클래스가 구현해야 하는 메소드의 집합입니다.
  • interface 키워드로 선언됩니다.
  • 모든 메소드는 기본적으로 추상적이고 public입니다.

인터페이스 구현

  • 다중 상속이 가능하며, implements 키워드로 구현합니다.
  • 자바 8부터 디폴트 메소드와 정적 메소드를 사용할 수 있습니다.
interface Animal {
    void sound(); // 추상 메소드
    default void sleep() { // 디폴트 메소드
        System.out.println("Sleeping...");
    }
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // "Dog barks" 출력
        animal.sleep(); // "Sleeping..." 출력
    }
}

상속, 오버라이딩, 다형성, 추상클래스와 인터페이스는 Java의 객체지향 프로그래밍(OOP)에서 핵심적인 역할을 하며, 실무 및 인터뷰에서도 자주 등장하는 주제입니다.

8. 예외(Exception) 처리

1. 예외의 개념과 종류

예외의 개념

  • 예외(Exception)는 프로그램 실행 중 발생하는 예상치 못한 상황이나 오류를 의미합니다.
  • 예외 처리를 통해 프로그램이 비정상적으로 종료되는 것을 방지하고, 오류를 적절히 처리할 수 있습니다.
  • 예외는 체크 예외(Checked Exception)비체크 예외(Unchecked Exception)로 나뉩니다.

예외의 종류

  1. 체크 예외 (Checked Exception)

    • 컴파일 시점에 반드시 처리해야 하는 예외.
    • IOException, SQLException 등 파일 입출력이나 데이터베이스 작업에서 자주 발생.
    • 예외 처리가 강제되므로 try-catch 구문 또는 throws 키워드로 처리해야 함.
  2. 비체크 예외 (Unchecked Exception)

    • 실행 중(Runtime)에 발생하며, 컴파일러가 강제하지 않는 예외.
    • NullPointerException, ArrayIndexOutOfBoundsException 등 프로그래머의 실수로 인해 발생.
  3. 에러 (Error)

    • 시스템 레벨에서 발생하며, 복구할 수 없는 심각한 문제.
    • OutOfMemoryError, StackOverflowError 등.

2. try-catch 구문과 예외 처리 방법

try-catch 구문

  • 예외 발생 가능성이 있는 코드를 try 블록에 작성합니다.
  • 예외가 발생하면 catch 블록에서 해당 예외를 처리합니다.
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try {
            // 예외 발생 가능성 있는 코드
            File file = new File("example.txt");
            Scanner scanner = new Scanner(file);
            while (scanner.hasNextLine()) {
                System.out.println(scanner.nextLine());
            }
        } catch (FileNotFoundException e) {
            // 예외 처리
            System.out.println("File not found: " + e.getMessage());
        }
    }
}
  • 하나의 try 블록에 여러 개의 catch 블록을 작성할 수 있습니다.
  • 예외를 처리하지 않으면 프로그램이 비정상적으로 종료됩니다.

3. finally 블록

개념

  • finally 블록은 예외 발생 여부와 상관없이 항상 실행되는 블록입니다.
  • 자원을 해제하거나 정리(clean-up) 작업을 수행할 때 사용됩니다.
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = null;
        try {
            File file = new File("example.txt");
            scanner = new Scanner(file);
            while (scanner.hasNextLine()) {
                System.out.println(scanner.nextLine());
            }
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + e.getMessage());
        } finally {
            // 자원 정리
            if (scanner != null) {
                scanner.close();
                System.out.println("Scanner closed.");
            }
        }
    }
}
  • finally는 예외가 발생하든 발생하지 않든 실행됩니다.
  • 예외가 catch에서 처리되지 않고 다시 던져질 경우에도 finally는 실행됩니다.

4. 사용자 정의 예외

개념

  • Java에서 사용자가 직접 예외 클래스를 정의할 수 있습니다.
  • 사용자 정의 예외는 기존 예외 클래스를 상속받아 구현됩니다.
  • 일반적으로 체크 예외로 사용하려면 Exception을, 비체크 예외로 사용하려면 RuntimeException을 상속받습니다.
// 사용자 정의 예외 클래스
class CustomException extends Exception {
    public CustomException(String message) {
        super(message);
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            validateAge(15); // 유효하지 않은 나이
        } catch (CustomException e) {
            System.out.println("Exception caught: " + e.getMessage());
        }
    }

    // 사용자 정의 예외를 던지는 메소드
    public static void validateAge(int age) throws CustomException {
        if (age < 18) {
            throw new CustomException("Age must be at least 18.");
        }
    }
}
  • 사용자 정의 예외를 통해 프로그램의 특정 도메인에 맞는 명확한 에러 메시지를 제공할 수 있습니다.
  • throw 키워드로 예외를 던지고, throws 키워드로 호출자에게 예외를 전달합니다.

위에서 언급한 네 가지 예외 처리 개념은 Java에서 안정적이고 유지보수 가능한 프로그램을 작성하는 데 매우 중요한 요소입니다.

9. Generic 클래스

Generic 클래스는 데이터 타입을 일반화하여 다양한 타입의 객체를 처리할 수 있도록 설계된 클래스입니다.
클래스 내부에서 사용할 데이터 타입을 컴파일 시점에 지정합니다.
타입 안정성을 보장하고, 코드 중복을 줄이며, 재사용성을 높이는 데 도움을 줍니다.

1. Generic 클래스의 선언

  • 제네릭 클래스는 클래스 선언 시 <T>와 같은 타입 매개변수를 사용합니다.
  • T는 일반적으로 Type을 의미하며, 다른 이름(E, K, V, 등)을 사용할 수도 있습니다.

만드는 방법

class GenericClass<T> { // T는 타입 매개변수
    private T data;

    public GenericClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

사용 방법

public class Main {
    public static void main(String[] args) {
        // Integer 타입으로 제네릭 클래스 사용
        GenericClass<Integer> intObj = new GenericClass<>(100);
        System.out.println("Integer Value: " + intObj.getData()); // 100

        // String 타입으로 제네릭 클래스 사용
        GenericClass<String> strObj = new GenericClass<>("Hello");
        System.out.println("String Value: " + strObj.getData()); // Hello
    }
}

2. 여러 개의 타입 매개변수

  • 제네릭 클래스는 여러 타입 매개변수를 지원합니다.
class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<>("Age", 30);
        System.out.println("Key: " + pair.getKey()); // Key: Age
        System.out.println("Value: " + pair.getValue()); // Value: 30
    }
}

3. 제한된 타입 매개변수

  • 제네릭 타입 매개변수에 특정 클래스 또는 인터페이스를 상속받도록 제한할 수 있습니다.
class Box<T extends Number> { // Number의 하위 클래스만 허용
    private T data;

    public Box(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

public class Main {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>(10); // Integer는 Number의 하위 클래스
        Box<Double> doubleBox = new Box<>(5.5); // Double도 가능

        // Box<String> strBox = new Box<>("Hello"); // 컴파일 오류
        System.out.println("Integer Box: " + intBox.getData()); // 10
        System.out.println("Double Box: " + doubleBox.getData()); // 5.5
    }
}

10. 기본 클래스

1. String 클래스

  • Java의 String 클래스는 문자열을 다룰 수 있는 불변(immutable) 객체를 제공합니다.
  • 문자열을 한 번 생성하면 그 값을 변경할 수 없습니다.
  • java.lang.String 패키지에 포함되어 있으며, import 없이 사용할 수 있습니다.
  • 문자열 리터럴로 생성되거나, new String() 생성자를 사용하여 생성됩니다.

문자열 생성 예제

public class Main {
    public static void main(String[] args) {
        // 문자열 리터럴로 생성
        String str1 = "Hello";

        // new 키워드로 생성
        String str2 = new String("World");

        System.out.println(str1); // Hello
        System.out.println(str2); // World
    }
}

1. 주요 메소드

length()

  • 문자열의 길이를 반환합니다.
String str = "Hello World";
System.out.println(str.length()); // 11

charAt(int index)

  • 지정된 인덱스에 있는 문자를 반환합니다.
  • 인덱스는 0부터 시작합니다.
String str = "Hello";
System.out.println(str.charAt(1)); // e

substring(int beginIndex, int endIndex)

  • 지정된 범위의 문자열을 반환합니다.
  • endIndex포함되지 않습니다.
String str = "Hello World";
System.out.println(str.substring(0, 5)); // Hello

contains(CharSequence s)

  • CharSequence 는 String 클래스가 구현한 interface type 입니다.
  • 문자열에 특정 문자열이 포함되어 있는지 확인합니다.
  • 반환값은 boolean입니다.
String str = "Hello World";
System.out.println(str.contains("World")); // true
System.out.println(str.contains("Java"));  // false

equals(Object another)

  • 두 문자열이 같은지 비교합니다. 대소문자를 구분합니다.
String str1 = "Hello";
String str2 = "Hello";
String str3 = "hello";

System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // false

equalsIgnoreCase(String another)

  • 두 문자열이 같은지 비교하되, 대소문자를 무시합니다.
String str1 = "Hello";
String str2 = "hello";

System.out.println(str1.equalsIgnoreCase(str2)); // true

toUpperCase()toLowerCase()

  • 문자열을 대문자 또는 소문자로 변환합니다.
String str = "Hello World";

System.out.println(str.toUpperCase()); // HELLO WORLD
System.out.println(str.toLowerCase()); // hello world

indexOf(String s)

  • 문자열에서 특정 문자열의 첫 번째 위치를 반환합니다.
  • 문자열이 존재하지 않으면 -1을 반환합니다.
String str = "Hello World";
System.out.println(str.indexOf("World")); // 6
System.out.println(str.indexOf("Java"));  // -1

replace(CharSequence target, CharSequence replacement)

  • 문자열 내 특정 문자열을 다른 문자열로 대체합니다.
String str = "Hello World";
System.out.println(str.replace("World", "Java")); // Hello Java

trim()

  • 문자열의 앞뒤 공백을 제거합니다.
String str = "   Hello World   ";
System.out.println(str.trim()); // "Hello World"

split(String regex)

  • 문자열을 특정 구분자(정규 표현식 기준)로 나누어 문자열 배열로 반환합니다.
String str = "apple,banana,grape";
String[] fruits = str.split(",");

for (String fruit : fruits) {
    System.out.println(fruit);
}
// apple
// banana
// grape

startsWith(String prefix)endsWith(String suffix)

  • 문자열이 특정 문자열로 시작하거나 끝나는지 확인합니다.
String str = "Hello World";

System.out.println(str.startsWith("Hello")); // true
System.out.println(str.endsWith("World"));   // true

concat(String str)

  • 문자열을 이어 붙입니다.
  • + 연산자를 사용하는 것과 동일한 효과를 가집니다.
String str1 = "Hello";
String str2 = "World";

System.out.println(str1.concat(" ").concat(str2)); // Hello World

isEmpty() / isBlank()

  • isEmpty(): 문자열의 길이가 0인지 확인합니다.
  • isBlank(): 문자열이 비어있거나 공백만 포함되어 있는지 확인합니다. (Java 11+)
String str1 = "";
String str2 = "   ";

System.out.println(str1.isEmpty()); // true
System.out.println(str2.isEmpty()); // false
System.out.println(str2.isBlank()); // true (Java 11 이상)

join(CharSequence delimiter, CharSequence... elements)

  • 여러 문자열을 지정된 구분자로 결합합니다. (Java 8+)
String joined = String.join(", ", "apple", "banana", "grape");
System.out.println(joined); // apple, banana, grape

2. Immutable 과 문자열 조작

  • String은 불변(immutable) 객체이기 때문에, 문자열 조작 시 새로운 객체가 생성됩니다.
  • 문자열을 반복적으로 수정하거나 연결해야 하는 경우 StringBuilder 또는 StringBuffer를 사용하는 것이 효율적입니다.

예제: StringBuilder 사용

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" My");
sb.append(" World!");

System.out.println(sb.toString()); // Hello My World!

이와 같이 String 클래스는 다양한 메소드를 통해 문자열을 효과적으로 다룰 수 있습니다.

2. wrapper 클래스

  • Wrapper 클래스는 기본 데이터 타입(primitive type)을 객체(object)로 변환하기 위한 클래스를 말합니다.
  • Java의 모든 기본 데이터 타입(int, double, char 등)에 해당하는 Wrapper 클래스가 존재합니다.
  • 기본 타입을 객체로 다룰 수 있게 하여, 컬렉션 프레임워크(List, Set, Map 등)에서 활용하거나, 유틸리티 메소드를 제공받을 수 있습니다.

1. 기본 타입과 Wrapper 클래스의 매핑

기본 타입Wrapper 클래스
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

2. 주요 특징

Auto-boxing과 Unboxing

  • Auto-boxing: 기본 타입 → Wrapper 객체로 자동 변환.

  • Unboxing: Wrapper 객체 → 기본 타입으로 자동 변환.

    public class Main {
        public static void main(String[] args) {
            // Auto-boxing
            Integer num = 10; // int → Integer
            System.out.println(num); // 10
    
            // Unboxing
            int value = num; // Integer → int
            System.out.println(value); // 10
        }
    }

불변성

  • Wrapper 클래스는 불변(Immutable)입니다.
  • 객체 생성 후 내부 값이 변경되지 않습니다.

3. 주요 메소드

valueOf()

  • 기본 타입 값을 Wrapper 객체로 변환합니다.
public class Main {
    public static void main(String[] args) {
        Integer intObj = Integer.valueOf(100);
        Double doubleObj = Double.valueOf(12.34);

        System.out.println(intObj); // 100
        System.out.println(doubleObj); // 12.34
    }
}

xxxValue()

  • Wrapper 객체를 기본 타입 값으로 변환합니다. (intValue(), doubleValue(), 등)
public class Main {
    public static void main(String[] args) {
        Integer intObj = Integer.valueOf(100);

        int intValue = intObj.intValue(); // Integer → int
        System.out.println(intValue); // 100
    }
}

parseXXX()

  • 문자열(String)을 기본 타입으로 변환합니다. (parseInt(), parseDouble(), 등)
public class Main {
    public static void main(String[] args) {
        int intValue = Integer.parseInt("123");
        double doubleValue = Double.parseDouble("45.67");

        System.out.println(intValue); // 123
        System.out.println(doubleValue); // 45.67
    }
}

toString()

  • Wrapper 객체 또는 기본 타입 값을 문자열(String)로 변환합니다.
public class Main {
    public static void main(String[] args) {
        Integer intObj = Integer.valueOf(100);
        String str = intObj.toString();

        System.out.println(str); // "100"
    }
}

4. Wrapper 클래스의 단점과 해결

단점

  • 성능 문제: 기본 타입보다 메모리 사용량이 높고, 처리 속도가 느립니다.
  • 불필요한 객체 생성: Auto-boxing으로 인해 객체가 많이 생성될 수 있음.

해결 방법

  • 기본 타입을 가능한 한 직접 사용하거나, 많은 연산이 필요한 경우 int[] 등의 배열 사용을 고려.

결론
Wrapper 클래스는 기본 타입을 객체로 다루어야 하는 상황에서 유용하며, 자바의 컬렉션 프레임워크, 문자열 처리, 데이터 변환 등에 필수적입니다. 하지만 성능 문제를 고려해 적절한 상황에서 사용하는 것이 중요합니다.

11. 유틸(util) 클래스

1. ArrayList

ArrayList는 동적 배열을 구현한 Java의 컬렉션 클래스입니다.
순서가 중요한 데이터를 저장할때 사용 합니다.
인덱스를 사용해 요소에 접근할 수 있습니다.

1. 특징

  1. 크기 동적 증가:
    • 배열 크기를 명시하지 않아도 필요 시 자동으로 확장됩니다.
  2. 중복 허용:
    • 동일한 값을 여러 번 추가할 수 있습니다.
  3. 순서 유지:
    • 요소는 추가된 순서대로 저장됩니다.
  4. 인덱스 기반 접근:
    • 배열처럼 인덱스를 통해 요소를 빠르게 검색할 수 있습니다.
  5. Generics 지원:
    • 타입 안정성을 위해 제네릭을 사용할 수 있습니다.

2. ArrayList 와 제네릭

  • ArrayList는 기본적으로 제네릭 클래스로 설계되어 있으며, 원하는 데이터 타입을 제네릭으로 명시합니다.
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // String 타입 ArrayList
        List<String> stringList = new ArrayList<>();
        stringList.add("Apple");
        stringList.add("Banana");

        // Integer 타입 ArrayList
        List<Integer> intList = new ArrayList<>();
        intList.add(10);
        intList.add(20);

        // 데이터 출력
        System.out.println(stringList); // [Apple, Banana]
        System.out.println(intList);    // [10, 20]
    }
}

3. 타입 안정성

  • 제네릭을 사용하면, 컴파일 시점에서 잘못된 데이터 타입을 방지할 수 있습니다.

예제 (제네릭 사용 X)

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList(); // 제네릭 사용하지 않음
        list.add("Apple");
        list.add(10); // 서로 다른 타입 추가 가능

        // 런타임 시 타입 오류 발생 가능
        String fruit = (String) list.get(1); // ClassCastException
    }
}

예제 (제네릭 사용 O)

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        // list.add(10); // 컴파일 오류: String만 추가 가능

        String fruit = list.get(0); // 형 변환 불필요
        System.out.println(fruit); // Apple
    }
}

4. ArrayList 생성 및 초기화

기본 생성

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // ArrayList 객체를 List 타입으로 받기
        List<String> list = new ArrayList<>();
    }
}

초기 크기 지정

  • 초기 크기를 지정할 수도 있습니다(기본값은 10).
List<Integer> list = new ArrayList<>(20); // 초기 크기 20

5. 주요 메소드

add(E e)

  • 요소를 추가합니다.
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");

System.out.println(list); // [Apple, Banana, Cherry]

get(int index)

  • 지정된 인덱스의 요소를 반환합니다.
System.out.println(list.get(1)); // Banana

set(int index, E element)

  • 지정된 인덱스의 요소를 새 값으로 변경합니다.
list.set(1, "Blueberry");
System.out.println(list); // [Apple, Blueberry, Cherry]

remove(int index)remove(Object o)

  • 인덱스 또는 값을 기반으로 요소를 삭제합니다.
list.remove(1); // 인덱스 1의 요소 삭제
list.remove("Cherry"); // 값 "Cherry" 삭제
System.out.println(list); // [Apple]

size()

  • 리스트에 저장된 요소의 개수를 반환합니다.
System.out.println(list.size()); // 1

contains(Object o)

  • 리스트에 특정 값이 존재하는지 확인합니다.
System.out.println(list.contains("Apple")); // true
System.out.println(list.contains("Mango")); // false

isEmpty()

  • 리스트가 비어 있는지 확인합니다.
System.out.println(list.isEmpty()); // false

clear()

  • 리스트의 모든 요소를 제거합니다.
list.clear();
System.out.println(list); // []

addAll(Collection<? extends E> c)

  • 다른 컬렉션의 모든 요소를 추가합니다.
List<String> otherList = new ArrayList<>();
otherList.add("Mango");
otherList.add("Grape");

list.addAll(otherList);
System.out.println(list); // [Mango, Grape]

6. 반복문을 활용한 요소 참조

for 루프

List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
/*
	- 출력결과 -
    Apple
    Banana
    Cherry
*/

확장 for 루프

for (String item : list) {
    System.out.println(item);
}
/*
	- 출력결과 -
    Apple
    Banana
    Cherry
*/

forEach() 메소드 (람다 사용)

list.forEach(item -> System.out.println(item));
/*
	- 출력결과 -
    Apple
    Banana
    Cherry
*/

2. HashMap

HashMap은 Java의 컬렉션 클래스 중 하나로, 키(key)값(value) 쌍으로 데이터를 저장합니다.
Map 인터페이스를 구현하며, 데이터의 순서를 보장하지 않습니다.
키는 중복을 허용하지 않으며, 각 키는 고유합니다.
값은 중복을 허용합니다.

1. 객체 생성

  • HashMap 객체는 일반적으로 Map 타입으로 선언하여 생성합니다.
  • Generics를 사용하여 키와 값의 타입을 명시합니다.
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // HashMap 객체를 Map 타입으로 선언
        Map<String, Integer> map = new HashMap<>();
    }
}

2. HashMap과 Generics

  • HashMap은 제네릭을 사용하여 키와 값의 타입을 명확히 지정할 수 있습니다.
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // String 키와 Integer 값을 저장하는 HashMap
        Map<String, Integer> map = new HashMap<>();

        map.put("Apple", 100);
        map.put("Banana", 200);

        System.out.println(map); // {Apple=100, Banana=200}
    }
}

3. 주요 메소드

put(K key, V value)

  • 키-값 쌍을 추가합니다.
  • 동일한 키를 사용하면 기존 값이 덮어쓰기됩니다.
Map<String, Integer> map = new HashMap<>();

map.put("Apple", 100);
map.put("Banana", 200);
map.put("Apple", 150); // 기존 값(100)을 덮어씀

System.out.println(map); // {Apple=150, Banana=200}

get(Object key)

  • 특정 키에 매핑된 값을 반환합니다.
  • 키가 존재하지 않으면 null을 반환합니다.
System.out.println(map.get("Apple")); // 150
System.out.println(map.get("Cherry")); // null

containsKey(Object key)

  • 특정 키가 HashMap에 존재하는지 확인합니다.
System.out.println(map.containsKey("Apple")); // true
System.out.println(map.containsKey("Cherry")); // false

containsValue(Object value)

  • 특정 값이 HashMap에 존재하는지 확인합니다.
System.out.println(map.containsValue(150)); // true
System.out.println(map.containsValue(300)); // false

remove(Object key)

  • 특정 키와 매핑된 키-값 쌍을 삭제합니다.
map.remove("Apple");
System.out.println(map); // {Banana=200}

size()

  • HashMap에 저장된 키-값 쌍의 개수를 반환합니다.
System.out.println(map.size()); // 1

isEmpty()

  • HashMap이 비어 있는지 확인합니다.
System.out.println(map.isEmpty()); // false
map.clear(); // 모든 데이터 삭제
System.out.println(map.isEmpty()); // true

keySet()

  • HashMap에 저장된 모든 를 반환합니다.
System.out.println(map.keySet()); // [Banana]

values()

  • HashMap에 저장된 모든 을 반환합니다.
System.out.println(map.values()); // [200]

4. 반복문을 통한 데이터 처리

for-each 반복문

for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

forEach() 메소드 (람다 사용)

map.forEach((key, value) -> {
    System.out.println("Key: " + key + ", Value: " + value);
});

3. HashSet

HashSet은 Java의 컬렉션 클래스 중 하나로, 중복을 허용하지 않는 요소들을 저장하는 데 사용됩니다.
Set 인터페이스를 구현하며, 요소의 순서를 보장하지 않습니다.
내부적으로 HashMap을 사용하여 데이터를 저장하며, 빠른 검색과 삽입이 가능합니다.

1. 객체 생성

  • HashSet 객체는 일반적으로 Set 타입으로 선언하여 생성합니다.
import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        // HashSet 객체를 Set 타입으로 선언
        Set<String> set = new HashSet<>();
    }
}

2. HashSet과 Generics

  • HashSet은 제네릭을 사용하여 요소의 타입을 명확히 지정할 수 있습니다.
import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        // String 타입 요소를 저장하는 HashSet
        Set<String> set = new HashSet<>();

        set.add("Apple");
        set.add("Banana");

        System.out.println(set); // [Apple, Banana]
    }
}

3. 주요 메소드

add(E e)

  • 요소를 추가합니다.
  • 중복된 값은 추가되지 않습니다.
Set<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Apple"); // 중복 요소는 무시

System.out.println(set); // [Apple, Banana]

remove(Object o)

  • 특정 요소를 제거합니다.
set.remove("Apple");
System.out.println(set); // [Banana]

contains(Object o)

  • 특정 요소가 HashSet에 존재하는지 확인합니다.
System.out.println(set.contains("Banana")); // true
System.out.println(set.contains("Apple"));  // false

size()

  • HashSet에 저장된 요소의 개수를 반환합니다.
System.out.println(set.size()); // 1

isEmpty()

  • HashSet이 비어 있는지 확인합니다.
System.out.println(set.isEmpty()); // false
set.clear(); // 모든 요소 제거
System.out.println(set.isEmpty()); // true

clear()

  • HashSet의 모든 요소를 제거합니다.
set.clear();
System.out.println(set); // []

4. 반복문을 통한 데이터 처리

for 반복문

  • HashSet의 모든 요소를 순회하기 위해 향상된 for 반복문을 사용할 수 있습니다.
for (String item : set) {
    System.out.println(item);
}

forEach() 메소드 (람다 사용)

  • Java 8부터 제공되는 forEach() 메소드를 사용하면 람다 표현식으로 간결하게 순회할 수 있습니다.
set.forEach(item -> System.out.println(item));

Iterator를 이용한 순회

  • Iterator를 사용하면 컬렉션의 요소를 안전하게 반복할 수 있습니다.
  • 요소를 반복하면서 동시에 삭제가 가능합니다.
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("Apple");
        set.add("Banana");
        set.add("Cherry");

        // Iterator를 사용한 순회
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            String item = it.next();
            // Apple, Banana, Cherry 중에 하나가 무작위로 출력된다.
            System.out.println(item);
        }
    }

5. HashSet 활용

중복 제거

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Apple", "Cherry", "Banana"};

        Set<String> uniqueFruits = new HashSet<>();
        for (String fruit : fruits) {
            uniqueFruits.add(fruit);
        }

        System.out.println(uniqueFruits); // [Apple, Banana, Cherry]
    }
}

교집합, 합집합, 차집합

  • HashSet을 사용하여 집합 연산을 수행할 수 있습니다.
import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<Integer> set1 = new HashSet<>();
        set1.add(1);
        set1.add(2);
        set1.add(3);

        Set<Integer> set2 = new HashSet<>();
        set2.add(2);
        set2.add(3);
        set2.add(4);

        // 교집합
        Set<Integer> intersection = new HashSet<>(set1);
        intersection.retainAll(set2);
        System.out.println("Intersection: " + intersection); // [2, 3]

        // 합집합
        Set<Integer> union = new HashSet<>(set1);
        union.addAll(set2);
        System.out.println("Union: " + union); // [1, 2, 3, 4]

        // 차집합
        Set<Integer> difference = new HashSet<>(set1);
        difference.removeAll(set2);
        System.out.println("Difference: " + difference); // [1]
    }
}

0개의 댓글