Java 시작하기

  • 44 minutes to read

1. 자바 프로그래밍 시작하기

Java 소개

Java는 객체 지향 프로그래밍 언어로, Sun Microsystems에서 개발되었습니다. Java는 C++ 언어의 장점을 계승하면서 C++의 단점을 보완하여 개발된 언어로, 자바의 특징은 다음과 같습니다.

  1. 플랫폼 독립성: Java는 플랫폼 독립성을 가지고 있습니다. 즉, Java로 작성된 프로그램은 어느 플랫폼에서나 실행될 수 있습니다. 이는 Java 컴파일러가 Java 바이트 코드를 생성하고, JVM(Java Virtual Machine)에서 바이트 코드를 실행하기 때문입니다.
  2. 객체 지향: Java는 객체 지향 프로그래밍 언어입니다. 객체 지향 프로그래밍은 객체를 중심으로 프로그래밍하는 방식으로, 코드의 재사용성이 높아지고 유지보수가 용이해집니다.
  3. 간결하고 안정적인 코드: Java는 C++과 비교하여 문법이 간결하고, 메모리 관리가 자동화되어 있어 안정적인 코드를 작성할 수 있습니다.
  4. 다양한 애플리케이션 개발: Java는 다양한 애플리케이션 개발에 이용됩니다. 대표적으로는 웹 애플리케이션, 모바일 애플리케이션, 게임 등 다양한 분야에서 사용됩니다.

Java의 기본 구성 요소는 클래스(class), 메서드(method), 변수(variable) 등입니다. 클래스는 객체를 생성하기 위한 설계도와 같은 역할을 하며, 메서드는 클래스 안에서 정의된 함수입니다. 변수는 메모리 공간을 할당하여 값을 저장하는 역할을 합니다.

Java는 많은 개발 도구가 존재하며, 대표적으로는 Eclipse, IntelliJ IDEA, NetBeans 등이 있습니다. 이러한 개발 도구를 이용하면 Java 코드 작성, 디버깅, 테스트 등을 쉽게 할 수 있습니다.

Java는 오픈 소스이며, 다양한 라이브러리와 프레임워크가 존재합니다. 이러한 라이브러리와 프레임워크를 이용하면 빠르고 쉽게 애플리케이션을 개발할 수 있습니다.

이상으로 Java 프로그래밍 언어의 간단한 소개였습니다. Java는 강력하고 안정적인 프로그래밍 언어로, 다양한 분야에서 사용됩니다.

Java 역사

ava는 1991년, Sun Microsystems의 James Gosling과 그의 팀이 Oak이라는 이름으로 개발을 시작하면서 탄생했습니다. 당시 Sun Microsystems는 초록색의 Oak 트리를 보면서 Java라는 이름을 짓게 되었습니다. Java는 초기에는 디지털 케이블 TV 셋탑박스와 같은 임베디드 시스템을 위해 개발되었으나, Web의 발전과 함께 인터넷 어플리케이션을 개발하는데 사용되기 시작했습니다.

Java의 초기 버전은 1995년에 출시되었습니다. 이후 Java 1.0 버전부터 Java 11 버전까지 많은 버전의 Java가 출시되었습니다. Java 2부터는 Java SE(Standard Edition), Java EE(Enterprise Edition), Java ME(Micro Edition)으로 분리되어 제공되었습니다. Java SE는 개인용 데스크탑 애플리케이션 개발에 사용되며, Java EE는 대규모 엔터프라이즈 애플리케이션 개발에 사용됩니다. Java ME는 모바일 기기를 위한 플랫폼입니다.

2009년에 Sun Microsystems는 Oracle에 인수되었으며, 이후 Java의 발전은 Oracle이 이끌어가고 있습니다. 최근에는 Java 8, Java 9, Java 10, Java 11, Java 12, Java 13, Java 14, Java 15, Java 16, Java 17 등 다양한 버전이 출시되어 Java의 발전은 계속되고 있습니다.

Java는 객체 지향 프로그래밍 언어로, 플랫폼 독립성, 안정성, 보안성, 이식성 등의 장점을 가지고 있습니다. Java는 다양한 분야에서 사용되고 있으며, 특히 대규모 시스템 및 인터넷 어플리케이션 개발에 많이 이용되고 있습니다.

Java 버전

아래는 Java의 버전 및 출시 연도, 기능 및 주요 변경 사항 등을 요약한 표입니다.

버전 출시 연도 기능 및 주요 변경 사항
JDK 1 1996 초기 버전
JDK 1.1 1997 내부적인 개선 및 JDBC(Java Database Connectivity) 추가
J2SE 1.2 1998 JIT 컴파일러, Swing API 등 추가
J2SE 1.3 2000 JNDI(Java Naming and Directory Interface) 등 추가
J2SE 1.4 2002 NIO(New Input/Output) 추가, assert 문 등 추가
J2SE 5.0 2004 자동 박싱/언박싱, 제네릭스, 열거형 등 추가
Java SE 6 2006 JAXB(Java Architecture for XML Binding) 등 추가
Java SE 7 2011 다이아몬드 연산자, try-with-resources 문 등 추가
Java SE 8 2014 람다식, 스트림 API 등 추가
Java SE 9 2017 모듈 시스템 추가, 인터페이스 개선 등
Java SE 10 2018 지역 변수 형 추론, 동적 클래스 파일 생성 등 추가
Java SE 11 2018 HTTP 클라이언트 표준화, var 키워드 사용 범위 확대 등 추가
Java SE 12 2019 스위치 표현식 개선, 마이크로벤치 마이크로 벤치마크 프레임워크 등 추가
Java SE 13 2019 동적 CDS(Class Data Sharing), 텍스트 블록 등 추가
Java SE 14 2020 instanceof 패턴 매칭, switch 표현식 확장 등 추가
Java SE 15 2020 Sealed 클래스, Pattern Matching for instanceof 등 추가
Java SE 16 2021 레코드 클래스, 복수의 null 값 처리 등 추가
Java SE 17 2021 기본 제공 암호화 서비스, 정적 메서드를 인터페이스에서 제공 가능 등 추가

2. 변수와 데이터 타입

  • 변수와 상수
  • 기본 데이터 타입
  • 래퍼 클래스

3. 연산자

  • 산술 연산자
  • 비교 연산자

관계 연산자

관계 연산자는 비교 연산자라고도 불리며, 두 개의 값을 비교하여 그 관계가 참(true)인지 거짓(false)인지를 판단하는 연산자입니다. 자바에서는 다음과 같은 관계 연산자를 제공합니다.

연산자 설명
== 두 값이 같은지 비교
!= 두 값이 다른지 비교
> 왼쪽 값이 큰지 비교
>= 왼쪽 값이 크거나 같은지 비교
< 왼쪽 값이 작은지 비교
<= 왼쪽 값이 작거나 같은지 비교

위 연산자는 모두 이항 연산자로, 두 개의 피연산자를 가지며, 결과로 boolean 자료형(true 또는 false)을 반환합니다. 이제 예제 코드를 통해 관계 연산자의 사용법을 살펴보겠습니다.

예제 코드

다음은 두 개의 변수를 선언하고, 관계 연산자를 사용하여 그 관계를 판단한 결과를 출력하는 예제 코드입니다.

코드: 관계연산자.java

public class 관계연산자 {
    public static void main(String[] args) {
        int first_num = 3, second_num = 5;
        boolean greater = first_num > second_num;
        boolean smaller = first_num < second_num;
        System.out.printf("first_num = %d second_num = %d "
                          + "first_num > second_num = %b "
                          + "first_num < second_num = %b\n", 
                          first_num, second_num, greater, smaller);
    }
}

위 코드에서는 먼저 int 자료형의 first_num과 second_num 변수를 선언하고, 각각 3과 5의 값을 대입합니다. 그리고 관계 연산자를 사용하여 두 변수 간의 관계를 판단한 후, 그 결과를 boolean 자료형의 greater와 smaller 변수에 대입합니다. 마지막으로, printf 메서드를 사용하여 변수들의 값을 출력합니다.

위 코드를 실행하면 다음과 같은 결과가 출력됩니다.

first_num = 3 second_num = 5 first_num > second_num = false first_num < second_num = true

결과에서는 first_num 변수의 값이 second_num 변수의 값보다 작으므로, first_num < second_num은 true가 됩니다. 반면, first_num > second_num은 false가 됩니다. 따라서 greater 변수의 값은 false, smaller 변수의 값은 true가 됩니다.

결론 이번 강좌에서는 자바에서 제공하는 관계 연산자와 그 사용법에 대해 설명해보았습니다. 관계 연산자를 사용하여 변수들 간의 관계를 판단하고, 그 결과를 boolean 자료형의 변수에 대입하여 사용할 수 있습니다. 이를 활용하여 다양한 프로그램을 개발할 수 있으며, 특히 조건문(if 문 등)에서 자주 사용됩니다. 관계 연산자뿐만 아니라, 다른 연산자들도 같이 공부하여 효율적인 프로그래밍 능력을 키워보세요!

논리 연산자

논리 연산자는 참(true) 또는 거짓(false) 값을 가지는 논리형(boolean) 표현식을 연산할 때 사용됩니다. Java에서는 다음과 같은 논리 연산자를 제공합니다.

  1. AND 연산자(&&): 두 피연산자 모두 참일 때만 참을 반환합니다.
  2. OR 연산자(||): 두 피연산자 중 하나 이상이 참일 때 참을 반환합니다.
  3. NOT 연산자(!): 피연산자의 논리 값을 반전시킵니다.

삼항 연산자

삼항 연산자(? :)는 조건식의 참 또는 거짓 여부에 따라 값을 결정하는 연산자입니다. 구문은 다음과 같습니다.

condition ? value_if_true : value_if_false;

예를 들어, 두 정수 중 큰 값을 찾는 삼항 연산자를 사용한 코드는 다음과 같습니다.

int a = 10;
int b = 20;
int max = (a > b) ? a : b;

데이터 입력 받기

Scanner 개체

Java에서 Scanner 클래스는 입력을 받기 위한 유용한 도구입니다. Scanner 클래스를 사용하면 키보드로부터 입력을 받거나 파일에서 입력을 받을 수 있습니다.

Scanner 클래스는 java.util 패키지에 속해 있으므로, import java.util.Scanner; 구문을 이용하여 클래스를 가져와야 합니다.

사용자로부터 입력을 받기 위해서는 Scanner 클래스 객체를 생성하고, Scanner 객체의 메서드를 이용하여 입력을 받아야 합니다. 예를 들어, Scanner 클래스의 nextLine() 메서드를 이용하여 한 줄의 문자열을 입력받을 수 있습니다. 이 메서드는 입력된 문자열에서 공백 문자를 포함한 모든 문자열을 읽어들입니다.

다음은 Scanner 클래스를 사용하여 사용자로부터 입력을 받는 예제 코드입니다.

import java.util.Scanner;

public class ScannerExample {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("이름을 입력하세요: ");
        String name = scanner.nextLine();
        System.out.print("나이를 입력하세요: ");
        int age = scanner.nextInt();

        System.out.printf("이름: %s, 나이: %d", name, age);
        scanner.close();
    }
}

위 예제 코드에서는 Scanner 클래스의 객체를 생성하고, nextLine() 메서드와 nextInt() 메서드를 이용하여 사용자로부터 문자열과 숫자를 입력받습니다. 마지막으로 입력받은 정보를 출력합니다. Scanner 클래스의 close() 메서드를 이용하여 Scanner 객체를 종료합니다.

Scanner 클래스를 이용하여 입력을 받을 때는 입력이 끝나면 반드시 Scanner 객체를 닫아주어야 합니다. 그렇지 않으면 프로그램이 종료되기 전에 계속해서 입력을 기다리게 됩니다.

Scanner 클래스는 문자열, 숫자, 불리언, 날짜 등 다양한 유형의 입력을 받을 수 있습니다. 이를 이용하여 사용자 입력을 처리하는 다양한 예제를 만들어 볼 수 있습니다.

원의 넓이와 둘레 구하기

이번에는 Java로 원의 넓이와 둘레를 구하는 예제 코드에 대해 간단히 설명해보겠습니다.

import java.util.Scanner;

public class CalculateAreaAndCircumferenceOfCircleExample {

    public static void main(String[] args) {
        // 상수 선언: PI = 3.141592654...
        final float PI = 3.141592654f;

        // 변수 선언
        float r;

        // 사용자로부터 반지름 입력
        Scanner scanner = new Scanner(System.in);
        System.out.print("반지름: ");
        r = scanner.nextFloat();

        // 처리 및 출력
        System.out.println("원의 넓이: " + (r * r * PI));
        System.out.println("원의 둘레 길이: " + (2 * r * PI));
        scanner.close();
    }
}

위 코드에서는 먼저, 상수 PI를 3.141592654...로 정의하고, 사용자로부터 반지름을 입력받습니다. 그리고 원의 넓이와 둘레 길이를 계산하여 출력합니다.

원의 넓이는 반지름의 제곱에 상수 PI를 곱한 값입니다. 원의 둘레 길이는 반지름에 2를 곱한 후 상수 PI를 곱한 값입니다.

위 코드를 실행하면, 다음과 같은 결과가 출력됩니다.

반지름: 5
원의 넓이: 78.53982
원의 둘레 길이: 31.41593

위 결과에서는 반지름이 5인 원의 넓이가 약 78.54이고, 둘레 길이가 약 31.42임을 확인할 수 있습니다.

Java는 객체지향적인 언어로, 클래스와 메서드, 속성 등의 개념이 등장합니다. 하지만 이 예제에서는 기초적인 입출력과 연산자 활용을 예제로 보여주는 것이므로, 객체지향적인 개념은 다루지 않았습니다.

Java는 다양한 기능을 제공하는 객체지향 프로그래밍 언어입니다. 이러한 기능들을 이용하여 더 복잡한 프로그램을 만들어 낼 수 있습니다. 관련된 강좌들을 학습하여 더욱 효과적인 Java 개발을 할 수 있도록 노력해보세요.

4. 조건문과 반복문

if문

if문은 조건에 따라 코드를 실행할지 결정하는데 사용됩니다. 기본 구조는 다음과 같습니다.

if (조건) {
    // 조건이 참일 경우 실행될 코드
}

if-else 구조를 사용하여 조건이 참이 아닐 경우에도 코드를 실행할 수 있습니다.

if (조건) {
    // 조건이 참일 경우 실행될 코드
} else {
    // 조건이 거짓일 경우 실행될 코드
}

switch문

switch문은 여러 가지 조건 중 하나를 선택하여 실행할 때 사용됩니다. 기본 구조는 다음과 같습니다.

switch (변수) {
    case 값1:
        // 변수가 값1일 경우 실행될 코드
        break;
    case 값2:
        // 변수가 값2일 경우 실행될 코드
        break;
    default:
        // 변수가 어떤 case에도 해당하지 않을 경우 실행될 코드
}

for문

for문은 정해진 횟수만큼 반복을 수행할 때 사용됩니다. 기본 구조는 다음과 같습니다.

for (초기화; 조건; 증감) {
    // 조건이 참인 동안 반복되는 코드
}

while문

while문은 조건이 참인 동안 반복을 수행할 때 사용됩니다. 기본 구조는 다음과 같습니다.

while (조건) {
    // 조건이 참인 동안 반복되는 코드
}

do-while문

do-while문은 조건을 확인하기 전에 한 번 실행하고, 이후에 조건이 참인 동안 반복을 수행할 때 사용됩니다. 기본 구조는 다음과 같습니다.

do {
    // 조건이 참일 동안 실행되는 코드
} while (조건);

이러한 조건문과 반복문을 사용하여 다양한 프로그래밍 상황에 대응할 수 있습니다. 이를 통해 효율적인 코드를 작성할 수 있습니다.

5. 배열

배열 선언과 초기화

배열은 동일한 타입의 여러 변수를 저장할 수 있는 자료구조입니다. 배열을 선언하고 초기화하는 방법은 다음과 같습니다.

타입[] 배열이름 = new 타입[크기];

또는 선언과 동시에 초기값을 지정할 수 있습니다.

타입[] 배열이름 = {값1, 값2, 값3};

예를 들어, 정수형 배열을 선언하고 초기화하는 방법은 다음과 같습니다.

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

다차원 배열

다차원 배열은 배열의 배열입니다. 예를 들어, 2차원 배열은 행렬과 같은 구조를 가집니다. 다차원 배열을 선언하고 초기화하는 방법은 다음과 같습니다.

타입[][] 배열이름 = new 타입[행의 개수][열의 개수];

또는 선언과 동시에 초기값을 지정할 수 있습니다.

타입[][] 배열이름 = {
    {값1, 값2, 값3},
    {값4, 값5, 값6}
};

예를 들어, 2차원 정수형 배열을 선언하고 초기화하는 방법은 다음과 같습니다.

int[][] matrix = new int[2][3];
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

배열을 이용한 알고리즘 구현

배열을 사용하여 다양한 알고리즘을 구현할 수 있습니다. 예를 들어, 정수형 배열의 요소를 모두 더하는 알고리즘은 다음과 같습니다.

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

for (int i = 0; i < numbers.length; i++) {
    sum += numbers[i];
}

System.out.println("배열의 합: " + sum);

이처럼 배열을 사용하여 데이터를 저장하고 처리하는 알고리즘을 구현할 수 있습니다.

6. 클래스와 객체

클래스의 구조

클래스는 객체를 생성하기 위한 틀로, 객체의 상태와 행동을 정의합니다. 클래스는 필드와 메서드로 구성되며, 기본 구조는 다음과 같습니다.

class ClassName {
    // 필드
    필드타입 필드이름;

    // 메서드
    반환타입 메서드이름(매개변수) {
        // 메서드 구현
    }
}

객체 생성과 사용

클래스를 이용하여 객체를 생성하고 사용하는 방법은 다음과 같습니다.

  1. 객체 생성: new 키워드를 사용하여 클래스의 인스턴스를 생성합니다.
ClassName 객체이름 = new ClassName();
  1. 객체 사용: 생성된 객체의 필드와 메서드에 접근할 수 있습니다.
객체이름.필드이름; // 필드에 접근
객체이름.메서드이름(매개변수); // 메서드 호출

생성자

생성자는 객체가 생성될 때 호출되는 메서드로, 객체의 초기화 작업을 수행합니다. 생성자는 클래스 이름과 동일하며 반환 타입을 지정하지 않습니다.

class ClassName {
    // 생성자
    ClassName(매개변수) {
        // 초기화 작업
    }
}

메서드

메서드는 클래스에서 정의한 작업을 수행하는 코드 블록입니다. 메서드는 다음과 같이 정의합니다.

반환타입 메서드이름(매개변수) {
    // 메서드 구현
    return 반환값;
}

static 키워드

static 키워드는 클래스에 속한 멤버를 정적 멤버로 만듭니다. 정적 멤버는 객체를 생성하지 않고 클래스 이름으로 직접 접근할 수 있습니다.

class ClassName {
    // 정적 필드
    static 필드타입 필드이름;

    // 정적 메서드
    static 반환타입 메서드이름(매개변수) {
        // 메서드 구현
    }
}

정적 멤버에 접근하는 방법은 다음과 같습니다.

ClassName.필드이름; // 정적 필드에 접근
ClassName.메서드이름(매개변수); // 정적 메서드 호출

7. 상속

클래스 상속

상속은 기존 클래스의 기능을 재사용하고 확장할 수 있는 기능입니다. 자식 클래스가 부모 클래스를 상속받는 방법은 다음과 같습니다.

class ChildClass extends ParentClass {
    // 자식 클래스 구현
}

메서드 오버라이딩

메서드 오버라이딩은 자식 클래스에서 부모 클래스의 메서드를 재정의하는 것입니다. 자식 클래스에서 오버라이딩하려는 메서드와 동일한 시그니처를 가진 메서드를 작성하면 됩니다.

@Override
반환타입 메서드이름(매개변수) {
    // 메서드 구현
}

super 키워드

super 키워드는 자식 클래스에서 부모 클래스의 멤버에 접근할 때 사용됩니다. super 키워드를 사용하여 부모 클래스의 필드나 메서드에 접근할 수 있습니다.

super.필드이름; // 부모 클래스의 필드에 접근
super.메서드이름(매개변수); // 부모 클래스의 메서드 호출

추상 클래스

추상 클래스는 미완성 메서드를 포함하고 있는 클래스로, 다른 클래스가 상속받아 기능을 완성해야 사용할 수 있습니다. 추상 클래스는 abstract 키워드를 사용하여 정의합니다.

abstract class AbstractClass {
    // 추상 메서드
    abstract 반환타입 메서드이름(매개변수);
}

인터페이스

인터페이스는 메서드의 시그니처만 정의하고, 구현은 하위 클래스에서 완성해야하는 규약입니다. 인터페이스는 interface 키워드를 사용하여 정의하며, 클래스에서 구현하려면 implements 키워드를 사용합니다.

interface InterfaceName {
    // 인터페이스 메서드
    반환타입 메서드이름(매개변수);
}

class ClassName implements InterfaceName {
    // 인터페이스 메서드 구현
}

상속을 사용하면 코드 재사용성을 높이고, 유지 보수를 용이하게 할 수 있습니다. 이를 통해 효율적인 코드를 작성할 수 있습니다.

8. 예외 처리

예외 처리의 개념

예외 처리는 프로그램 실행 중 발생하는 예외 상황을 처리하는 기능입니다. 예외 처리를 통해 프로그램의 안정성과 유지 보수성을 높일 수 있습니다. Java에서는 다양한 종류의 예외 클래스를 제공하며, 필요에 따라 사용자 정의 예외 클래스를 생성할 수 있습니다.

try-catch-finally 구문

try-catch-finally 구문을 사용하여 예외 처리를 수행할 수 있습니다.

try {
    // 예외 발생 가능성이 있는 코드
} catch (예외클래스1 변수명) {
    // 예외클래스1 처리 코드
} catch (예외클래스2 변수명) {
    // 예외클래스2 처리 코드
} finally {
    // 예외 발생 여부와 관계없이 실행되는 코드
}

예외 클래스 계층 구조

Java의 예외 클래스들은 계층 구조를 가지고 있으며, 모든 예외 클래스는 Throwable 클래스를 상속받습니다. 주요 예외 클래스는 다음과 같습니다.

  • Throwable
    • Error: 시스템 오류와 같은 심각한 문제를 나타내는 예외 클래스
    • Exception: 프로그램 실행 중 발생하는 일반적인 예외 클래스
      • RuntimeException: 프로그래머의 실수로 발생하는 예외 클래스
      • 그 외 여러 예외 클래스들

사용자 정의 예외 클래스

필요에 따라 사용자 정의 예외 클래스를 생성할 수 있습니다. 사용자 정의 예외 클래스는 Exception 클래스 또는 그 하위 클래스를 상속받아 작성합니다.

class CustomException extends Exception {
    // 사용자 정의 예외 클래스 구현
}

이렇게 생성된 사용자 정의 예외 클래스는 다른 예외 클래스와 동일한 방식으로 사용할 수 있습니다.

9. 입출력

표준 입출력

Java에서 표준 입력은 System.in을, 표준 출력은 System.out을 사용하여 처리합니다. System.inInputStream 객체이며, System.outPrintStream 객체입니다. 일반적으로 Scanner 클래스를 사용하여 표준 입력을 처리합니다.

import java.util.Scanner;

Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
System.out.println("입력한 내용: " + input);

파일 입출력

Java에서 파일 입출력은 FileInputStream, FileOutputStream, FileReader, FileWriter 등의 클래스를 사용하여 처리합니다. 다음 예제는 텍스트 파일을 읽고 쓰는 방법을 보여줍니다.

// 파일 쓰기
import java.io.FileWriter;
import java.io.IOException;

try (FileWriter writer = new FileWriter("output.txt")) {
    writer.write("Hello, World!");
} catch (IOException e) {
    e.printStackTrace();
}

// 파일 읽기
import java.io.FileReader;
import java.io.IOException;

try (FileReader reader = new FileReader("output.txt")) {
    int character;
    while ((character = reader.read()) != -1) {
        System.out.print((char) character);
    }
} catch (IOException e) {
    e.printStackTrace();
}

스트림의 개념

스트림은 데이터를 순차적으로 처리하기 위한 추상화된 개념입니다. 스트림은 입력 스트림과 출력 스트림으로 구분됩니다. 입력 스트림은 데이터를 읽어 들이는 방향으로, 출력 스트림은 데이터를 쓰는 방향으로 처리합니다.

바이트 스트림과 문자 스트림

스트림은 바이트 스트림과 문자 스트림으로 구분됩니다.

  • 바이트 스트림: 바이너리 데이터를 처리하는 스트림으로, InputStreamOutputStream을 상속한 클래스들이 있습니다.
  • 문자 스트림: 문자 데이터를 처리하는 스트림으로, ReaderWriter를 상속한 클래스들이 있습니다.

스트림을 사용하면 데이터 소스와 독립적으로 데이터 처리를 추상화할 수 있어, 효율적인 입출력 처리가 가능합니다.

10. 컬렉션 프레임워크

컬렉션 프레임워크 개요

컬렉션 프레임워크는 Java에서 다양한 자료구조를 표준화하여 제공하는 프레임워크입니다. 컬렉션 프레임워크를 사용하면, 데이터를 효율적으로 저장, 검색, 수정 및 삭제할 수 있습니다.

List 인터페이스와 ArrayList 클래스

List 인터페이스는 순서가 있는 데이터의 집합을 나타내며, 데이터의 중복을 허용합니다. ArrayList는 List 인터페이스를 구현한 가변 크기 배열입니다.

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

List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
System.out.println(list.get(0)); // 출력: Apple

Set 인터페이스와 HashSet 클래스

Set 인터페이스는 순서가 없는 데이터의 집합을 나타내며, 데이터의 중복을 허용하지 않습니다. HashSet은 Set 인터페이스를 구현한 해시 테이블 기반 집합입니다.

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

Set<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Cherry");
set.add("Apple");
System.out.println(set.size()); // 출력: 3 (Apple이 중복되므로)

Map 인터페이스와 HashMap 클래스

Map 인터페이스는 키와 값의 쌍으로 이루어진 데이터의 집합을 나타냅니다. 데이터는 키를 사용하여 검색, 수정 및 삭제할 수 있습니다. HashMap은 Map 인터페이스를 구현한 해시 테이블 기반 맵입니다.

import java.util.HashMap;
import java.util.Map;

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
System.out.println(map.get("Banana")); // 출력: 20

11. 제네릭 프로그래밍

제네릭 클래스와 제네릭 메서드

제네릭 프로그래밍은 데이터 타입을 매개변수화하여 코드의 재사용성을 높이는 기법입니다. 제네릭 클래스와 제네릭 메서드를 사용하여 다양한 데이터 타입에 대응하는 클래스와 메서드를 작성할 수 있습니다.

// 제네릭 클래스
public class Box<T> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

// 제네릭 메서드
public static <T> void printArray(T[] arr) {
    for (T item : arr) {
        System.out.print(item + " ");
    }
    System.out.println();
}

제네릭 클래스의 제한

제네릭 클래스를 사용할 때, 특정 타입에만 제한을 두고 싶다면 extends 키워드를 사용하여 상위 클래스를 지정할 수 있습니다.

public class NumberBox<T extends Number> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

와일드카드

와일드카드는 제네릭 타입의 범위를 제한하거나 확장할 때 사용합니다. 와일드카드는 ? 기호를 사용하여 표현합니다.

// 와일드카드를 사용하여 모든 타입의 리스트를 처리하는 메서드
public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

// 상한 와일드카드 (Number 클래스와 그 하위 클래스만 허용)
public static void printNumberList(List<? extends Number> list) {
    for (Number item : list) {
        System.out.println(item);
    }
}

// 하한 와일드카드 (Number 클래스와 그 상위 클래스만 허용)
public static void addObjectToList(List<? super Number> list) {
    list.add(10);
}

12. 스레드

12.1 스레드 개념

스레드는 프로세스 내에서 독립적으로 실행되는 작업의 단위입니다. 한 프로세스 내에서 여러 개의 스레드를 생성하여 병렬 처리를 수행할 수 있습니다. 이를 통해 프로그램의 실행 속도를 향상시키고, 자원을 효율적으로 활용할 수 있습니다.

12.2 스레드 생성과 실행

Java에서 스레드를 생성하고 실행하는 방법은 두 가지입니다.

  1. Thread 클래스를 상속받아서 사용하는 방법:
class MyThread extends Thread {
    public void run() {
        // 스레드가 실행할 작업 코드
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start(); // 스레드 실행
    }
}
  1. Runnable 인터페이스를 구현하는 방법:
class MyRunnable implements Runnable {
    public void run() {
        // 스레드가 실행할 작업 코드
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable myRunnable = new MyRunnable();
        Thread myThread = new Thread(myRunnable);
        myThread.start(); // 스레드 실행
    }
}

12.3 동기화

여러 스레드가 공유 자원에 동시에 접근할 때 발생할 수 있는 데이터 불일치 문제를 해결하기 위해 동기화가 필요합니다. Java에서는 synchronized 키워드를 사용하여 동기화를 구현할 수 있습니다.

public class SynchronizedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

12.4 데드락

데드락은 두 개 이상의 스레드가 서로가 가진 자원을 기다리며 무한정 대기하는 현상입니다. 이를 해결하기 위해서는 자원 할당 순서, 스레드 우선순위 변경 등 다양한 방법이 있습니다. 데드락을 피하는 가장 좋은 방법은 데드락이 발생할 수 있는 상황을 미리 예측하고 방지하는 것입니다.

예를 들어, 데드락이 발생하는 상황을 살펴봅시다.

public class DeadlockExample {
    public static void main(String[] args) {
        final Object resource1 = new Object();
        final Object resource2 = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: locked resource 1");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("Thread 1: locked resource 2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2: locked resource 2");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource1) {
                    System.out.println("Thread 2: locked resource 1");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

위의 예제에서는 두 스레드가 서로 다른 순서로 자원에 접근하려고 시도하여 데드락이 발생합니다. 이를 해결하기 위해 다음과 같이 자원에 접근하는 순서를 동일하게 변경할 수 있습니다.

public class FixedDeadlockExample {
    public static void main(String[] args) {
        final Object resource1 = new Object();
        final Object resource2 = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: locked resource 1");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("Thread 1: locked resource 2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 2: locked resource 1");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("Thread 2: locked resource 2");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

위와 같이 자원에 접근하는 순서를 변경하여 데드락을 해결할 수 있습니다.

13. 람다 표현식

13.1 람다 표현식 개요

람다 표현식은 Java 8부터 도입된 기능으로, 함수형 프로그래밍을 지원하기 위한 간결한 문법입니다. 람다 표현식을 사용하면 불필요한 코드를 줄이고 가독성을 향상시킬 수 있습니다.

13.2 함수형 인터페이스

함수형 인터페이스는 추상 메서드가 딱 하나만 있는 인터페이스를 의미합니다. 람다 표현식은 이러한 함수형 인터페이스를 표현하는 데 사용됩니다. Java에서는 java.util.function 패키지에 다양한 함수형 인터페이스를 제공합니다. 예를 들면, Predicate, Consumer, Supplier, Function 등이 있습니다.

13.3 람다 표현식 문법

람다 표현식은 매개변수 목록, 화살표(->), 그리고 실행문으로 구성됩니다. 간단한 예를 살펴봅시다.

Runnable runnable = () -> System.out.println("Hello, Lambda!");

위 예제에서는 매개변수 목록이 없는 람다 표현식을 사용하여 Runnable 인터페이스를 구현하고 있습니다.

13.4 람다 표현식과 익명 클래스의 차이점

람다 표현식과 익명 클래스 모두 인터페이스를 구현하는 방법이지만, 람다 표현식은 훨씬 간결한 문법을 제공합니다. 아래는 람다 표현식과 익명 클래스를 비교한 예입니다.

  1. 익명 클래스:
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, Anonymous Class!");
    }
};
  1. 람다 표현식:
Runnable runnable = () -> System.out.println("Hello, Lambda!");

위 예제에서 볼 수 있듯이, 람다 표현식을 사용하면 코드가 훨씬 간결해집니다.

14. 입문 실습

이 섹션에서는 Java를 활용한 간단한 입문 실습 예제와 프로그래밍 입문자들을 위한 실습 문제를 살펴봅니다.

14.1 간단한 콘솔 애플리케이션 만들기

간단한 콘솔 애플리케이션으로 "Hello, World!"를 출력하는 프로그램을 작성해봅시다.

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

14.2 간단한 그래픽 애플리케이션 만들기

Java를 사용해 간단한 그래픽 애플리케이션을 만들어 봅시다. 아래는 javax.swing 패키지를 사용한 간단한 예제입니다.

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class SimpleGraphicApplication {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Simple Graphic Application");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(300, 200);

            JLabel label = new JLabel("Hello, Java!");
            frame.add(label);

            frame.setVisible(true);
        });
    }
}

14.3 프로그래밍 입문자들을 위한 실습 문제

프로그래밍 입문자들이 자신의 기초 지식을 확장하고 연습할 수 있는 몇 가지 실습 문제를 제시합니다.

  1. 사용자로부터 정수를 입력받아 해당 정수의 팩토리얼을 계산하는 프로그램을 작성하십시오.
  2. 사용자로부터 문자열을 입력받아 해당 문자열을 거꾸로 출력하는 프로그램을 작성하십시오.
  3. 사용자로부터 두 개의 정수를 입력받아 최대공약수(GCD)와 최소공배수(LCM)를 구하는 프로그램을 작성하십시오.
  4. 1부터 100까지의 소수를 구하는 프로그램을 작성하십시오.
  5. 피보나치 수열의 첫 20개 항을 출력하는 프로그램을 작성하십시오.

이러한 문제들은 기본적인 프로그래밍 기술과 알고리즘 지식을 기르는 데 도움이 됩니다. 차츰 더 복잡한 문제를 풀어 보면서 실력을 향상시켜 나가십시오.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com