이 섹션의 다중 페이지 출력 화면임. 여기를 클릭하여 프린트.

이 페이지의 일반 화면으로 돌아가기.

JVM 기반 프로그래밍 언어

1 - Java

자바는 썬 마이크로시스템즈의 제임스 고슬링과 다른 연구원들이 개발한 객체 지향적 프로그래밍 언어

1.1 - Java 입문

자바를 처음하는 사람을 위한 입문서

Java 입문

자바(Java)는 썬 마이크로시스템즈의 제임스 고슬링(James Gosling)과 다른 연구원들이 개발한 객체 지향적 프로그래밍 언어이다. 많은 분야에서 사용되고 있다. 이 언어를 기초부터 차근히 배워보도록 하자.

1.1.1 - Java 입문 | 자바의 개요

자바는 객체지향 프로그램이다.

1.1.1.1 - Java 입문 | 자바의 개요 | 자바 소개

자바(Java) 언의의 탄생

  • 1991년 Sun Microsystem사의 James Gosling Patrick Naughton, Chris Warth, Ed Frank Mike Sheridna에 의해서 개발되었다.
  • 1991년에 오크(Oak)라는 이름으로 불렸으나 1996년에 발표된 1.0.2 버전부터 자바(Java)라는 이름을 사용하게 된다.
  • World Wide Web의 출현으로 자바는 컴퓨터 언어 설계를 하는데 더욱 확발하게 발전하게 되었다.
  • 이후 2009년에 썬 마이크로시스템즈사가 오라클과 인수 합병됨에 따라 자바 또한 오라클로 소유권이 넘어간다.

프로그래밍 언어 : 자바

  • 단순(Simple)하다
  • 객체지향(Object-oriendted)적이다
  • 분산(Distributed)환경의 응용에 적합하다
  • 인터프리터(Interpreter)에 의해 실행된다.
  • 견고(Robust)한 기능을 제공한다.
  • 안전(Secure)하다
  • 구조중립(Architecture-neutral)적이고 이식성(Potable)이 높다.
  • 높은 성능(High-performance)를 제공한다.
  • 다중 스레드(Multithread)를 제공한다.
  • 동적(Dynamic)이다.

자바의 특성

  • 플랫폼에 독립적인 프로그램을 작성할 수 있다.
  • 자바는 완벽한 객체 지향적 언어이다.

JVM(Java Virual Machine)

자바 바이트 코드를 기계어로 사용하는 컴퓨터를 자바 가상 머신이라 한다.

  • 스택 영역(Rumtime stack)
  • 동적 할당 메모리 영역 (Garbage Collection Heap)
  • 상수 & Method 영역 (Constant & Code Segment)
  • Register 영역 (Process Register)

자바(Java) 플랫폼 종류

Java SE(Java Platform - Standard Edition)

  • Desktop이나 Server에서 Java Application/Applet등을 개발, 배치, 실행 할 수 있는 환경을 제공(Software Platform)
  • Compiler, Interpreter, 표준 API 등 제공
  • Java SE Development Kit (JDK)

Java EE(Java Platform - Enterprise Edition)

  • Java SE를 기반으로 대규모 기업용 서버를 구축하고, 실행 할 수 있는 환경을 제공
  • Web Application Server(GlassFish)와 Servlet, JSP, JDBC, DataSource, JPA, JTA, JNDI, RMI, EJB, JMS 등 다수의 API 제공
  • Java EE SDK 다운로드

Java ME(Jaava Platform - Micro Edition)

  • 휴대폰, PDA 등에서 동작하는 무선 어플리케이션을 개발하고 실행할 수 있는 환경을 제공
  • Compiler, Emulator, 표준 API 등 제공
  • Java ME SDK

1.1.1.2 - Java 입문 | 자바의 개요 | 자바 설치 및 개발 환경 만들기

자바 설치 - 실행 및 컴파일 환경 만들기

각 OS별 설치 방법이 다르기 때문에, 각 환경에 맞는 설정 필요하다.

Window 설치

  • 준비중입니다.

Mac 설치

  • 준비중입니다.

Linux 설치

  • 준비중입니다.

Java SE Development Kit의 각종 개발 도구

JDK를 설치 하면 각종 개발 도구 {JAVA_HOME}/bin에 위치한다.

  • javac : 자바 컴파일러
  • java : 자바 인터프리터
  • javadoc : 자바 HTML Document 생성기
  • javap : 자바 역컴파일러
  • appletviewer.exe : 자바 애플릿뷰어
  • jar : 자바 압축기
  • jdb : 자바 디버거

개발 환경 만들기 - IDE 다운로드 및 설치

1.1.1.3 - Java 입문 | 자바의 개요 | 자바 시작하기

첫 어플리케이션(Application) 프로그램 작성

package com.devkuma.tutorial;

public class HelloWorld { // 클래스 시작

    public static void main(String[] args) { // 메소드 시작
        System.out.println("Hello World! Java.");
    }
}

소스코드 작성시 주의 사항

  • 영문 대소문자를 구별한다.
  • 저장시 파일명을 클래스명과 동일하여 하며 확장자는 .java이다.
  • HelloWorld.java
  • 컴파일
    • javac HelloWorld.java
  • 실행
  • java HelloWorld

클래스 정의

  • 자바는 클래스 단위로 프로그램을 작성하기 때문에 소스파일 안에 반드시 클래스를 정의해야 한다.
  • 클래스의 이름은 첫 문자를 대문자로 시작하는 것이 관례이다.
  • 클래스의 구성요소(속성, 메소드 등…)들은 { } 안에 위치한다.
  • 소스파일 저장 시 파일명이 클래스명과 반드시 일치해야 한다. (한 파일에 클래스가 여러개가 있는 경우는 제외)

main(String[] args) 메소드 정의

  • 어플리케이션 프로그램이 실행되려면 최소 1개 존재하여야 한다.
  • 프로그램의 진입점으로 JVM에 의해 최초 호출되며 메인 메소드 블록 내부에 기술된 명령문들을 순차적으로 실행한다.
  • JVM은 세미콜론(;)으로 끝나는 문장을 하나의 명령문으로 인식한다.
  • 주석은 프로그램 소스코드를 쉽게 이해 하기 위해서 사용하며, 컴파일 및 실행에 영향을 미치지 않는다.

1.1.2 - Java 입문 | 자바 기본 구조

자바에 대한 기본 구조를 기술한다.

1.1.2.1 - Java 입문 | 자바 기본 구조 | 식별자 (Identifier)

식별자(Identifier)

프로그램 구성 요소인 변수, 상수, 배열, 메소드, 클래스 등을 구분하기 위해 사용자가 정의하는 이름

식별자 규칙

  • 대문자, 소문자, 숫자, 밑줄 문자(_)와 달러($) 기호 문자를 이용하여 작성할 수 있다.
  • 첫 글자로 숫자로 시작해서는 안되지만 숫자가 문자 뒤에 오는 거 가능하다.
  • 문자 사이에 공백을 가질 수 없다.
  • 대소문자를 구별하기 때문에 VALUE와 Value는 다른 식별자이다.
  • 예약어(this, true, null 등…)는 식별자로 사용할 수 없다.
  • 16비트 유니코드를 지원하므로 한글도 식별자로 사용 가능하다(비권장)
    • 아스키코드 : ANSI(American National Standards Institute: 미국규격협회)에서 제정한 8비트 문자코드로 256개의 문자를 코드화한 코드 문자다.
    • 유니코드 : 유니코드(Apple, IBM, MS등의 컨소시엄)에서 제정한 16비트로 확장한 문자코드로 전세계의 모든 문자를 표현하기 위한 표준 문자 코드이다.
    • 유니코드는 현재 34,168개의 글자들을 코드화 하고 있으며 최대 65,536개의 글자를 코드화 할 수 있다.

식별자 관례

  • 클래스 이름은 대문자로 시작하고, 변수, 메소드 등의 이름은 소문자로 시작하는 것이 관례이다
  • 두 단어를 조합하여 이름을 정 할 때는 조합하는 문자의 첫 글자는 대문자로 한다
    • 낙타(Camel) 표기법

가능한 예

  • fileName (추천)
  • file_name (비추천)
  • _fileName (비추천)
  • $fileName (비추천)

불가능한 예

  • user name : 빈공백(“ ”)이 올 수 없다.
  • 3d_Studio : 숫자로 시작할 수 없다.
  • this : 키워드는 사용할 수 없다.
  • #arg : #를 사용할 수 없다.

1.1.2.2 - Java 입문 | 자바 기본 구조 | 주석문(Comment)

주석문(Comment)

주석(comment)은 로직에 대한 설명이나 코드를 비활성화 할 때 사용한다. 주석은 코드에 영향을 미치지 않으며 컴파일(프로그램 해석)의 대상에서 제외된다.

자바에서 사용할 수 있는 주석문은 3가지 종류가 있다.

단일 라인 주석 (Single-line comment)

//을 시작하는 그 라인의 끝까지 주석 처리 된다.

public static void main(String[] args) {
    // 정수 변수를 선언 할때는 아래와 같이 코드를 작성한다.
    int num1;
}

블럭 주석 (Multi-line Comment)

/* 에서 */까지의 모든 내용이 주석 처리 된다.

public static void main(String[] args) {
    int num1 = 100;
    int num2 = 200;
   
    /* 아래 코드는 선언한 정수의 합을 계산해서 표시해 준다.
       num1은 100이고, num3는 200이어서 결과는 300이 나온다. */
    System.out.println(num1 + num2);
}

문서 주석 (Document comment)

/**에서 */ 까지의 모든 내용이 주석처리 된다. 프로그램을 문서화 javadoc에 위해 생성되는 HTML 파일에서 사용된다. 주석 내용에 HTML 태그 사용 가능하다.

/**
 * main 메소드는 자바 응용 프로그램의 실행 시작점입니다.
 */
public static void main(String[] args) {
    String str = null;
}

1.1.2.3 - Java 입문 | 자바 기본 구조 | 예약어(Keyword)

예약어(Keyword) 목록

다음은 Java 프로그래밍 언어의 키워드 목록이다.

프로그램에서 다음 식별자를 식별자로 사용할 수 없다. 키워드 const 및 goto는 현재 사용되지는 않았지만 예약되어 있다. true, false 및 null은 키워드처럼 보일 수 있지만 실제로는 리터럴(literal)이다. 프로그램의 식별자로 사용할 수 없다. 모든 예약어는 소문자로 되어 있다.

abstract continue for new switch
assert*** default goto* package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum**** instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp** volatile
const* float native super while
* 사용하지 않음
** 1.2 버전에서 추가
*** 1.4 버전에서 추가
**** 5.0 버전에서 추가

참조

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/keywords.html

1.1.2.4 - Java 입문 | 자바 기본 구조 | 변수(Variable)

변수

프로그램을 작성할 때, 프로그램의 동작에 필요한 데이터를 저장할 필요가 있다. Java는 이러한 데이터를 메모리에 저장한다.
변수(Variable)는 값(Value)이 저장된 메모리의 위치에 주어진 대표 이름이다.
변수는 데이터가 저장된 공간의 이름을 지정하여, 하나의 데이터만 저장할 수 있다.
프로그램에서 사용하는 데이터(숫자, 문자, 문자열, 논리값)들을 저장하기 위한 메모리 공간으로 그릇에 비유할 수 있다.
변수를 변수라고 부르는 이유는 변수 저장된 데이터가 처리에 따라 달라지기 때문이다.

변수 선언 및 초기화

Java에서 변수를 사용하려면 “변수 선언"과 “변수 초기화"를 수행해야 핟다.

변수 선언

선언 은 프로그램에서 사용하려는 변수를 프로그램에 알리는 것이다. 프로그램은 선언된 변수만 제어할 수 있다. 변수를 사용하기 전에 저장하고자 하는 데이터 형태나 크기에 따라 반드시 자료형(Data type)을 붙여서 변수를 선언하여야 한다.

변수 선언의 방법과 구조

int variable;

변수를 선언하기 위해서는 데이터 유형(int)과 변수 이름(variable)를 작성해야 한다.

  • 데이터 유형: 입력할 데이터 유형을 결정하는 것.
  • 변수 이름: 변수의 이름. 중복 금지.

변수 초기화

변수 초기화는 앞에서 선언한 변수에 데이터를 저장하는 동작이다. 초기화하는 방법은 아래와 같은 2종류의 방법이다.

선언과 초기화를 다른 열에서 수행하는 방법

int value;

value = 10;

선언과 초기화를 동일한 열에서 수행하는 방법

int result = 5;

위의 방법 중 어떤 방법을 사용해도 상관 없다.

1.1.2.5 - Java 입문 | 자바 기본 구조 | 리터럴(Literal)

리터럴(Literal)

리터럴은 데이터 그 자체를 뜻 한다. 변수에 넣는 변하지 않는 데이터를 의미하는 것이다.

아래의 예제를 보자.

int a = 1;

int 앞에 a는 변수이고, 여기서의 1은 리터럴이다.
즉, 1과 같이 변하지 않는 데이터(boolean, char, double, long, int, etc…)를 리터럴(literal)이라고 부른다.

정수 - integer

가장 일반적으로 사용되는 데이터 자료형이다. 모든 임의의 정수 값은 정수 리터럴이다. 예를 들어 1, 2, 3, 42는 정수 리터널이다. 이런한 값들은 10진수이나, 8진수나 16진수를 사용할 수도 있다.
8진수는 숫자 0을 앞에 표시하고 숫자를 표시한다. 일반 10진수에는 0을 표시하지 않기 때문에 07과 7은 다른게 인식한다.
16진수는 0x(또는 0X)를 먼저 적고 16진수 상수를 지정한다. 범위는 0~16까지 이며 a~f(A~F)가 10~15까지를 대신한다.
모든 정수형 데이터가 기본적으로 int형이기 때문에 long 데이터 자료형에 정확한 long 리터럴을 지정하기 위해서는 숫자 뒤에 알파벨 l(또는 L)를 추가 해줘야 한다.
byte와 short 변수에 숫자를 저장할 때 저장되는 숫자의 범위가 해당 데이터 형의 자료형에 포함된다면 에러가 발생하지 않는다.

부동 소수점 - floating point

부동 소수점 리터널은 소수점 이하(분수)를 가진 10진 값들이다. 예를 들어 2.0, 3.1415, -0.6667은 모두 부동 소수점 리터널이다.
double형 자료형은 부동 소수점의 기본형이다. 숫자 뒤에 알파벨 d(또는 D)를 추가할 수도 있다.
float형 자료형은 부동 소수점의 기본형이다. 숫자 뒤에 알파벨 d(또는 D)를 추가할 수도 있다.

부울 - boolean

부울 상수는 두개의 논리적인 값, true(참), false(거짓)만 있다. C/C++에서와 같이 참, 거짓을 숫자로 표시 할 수는 없다.

문자 - character

자바의 모든 문자들은 Unicode를 사용한다. 정수로 변환될 수 있으며, 더하고 빼는 거와 같은 연산도 가능하다. 정부 형태가 아닌 문자 리터럴을 표시하고자 할 때는 단일 인용 부호(`, `)를 사용한다. 유니코드나 직접 입력이 불가능한 문자들에 대해서는 역슬래쉬( \ )를 이용하여 표시할 수 있다.

escape 시퀀스 설명
\ddd 8진 문자 (ddd)
\uxxxx 16진수 Unicode 문자 (xxxx)
\` 단일 인용 부호
\" 이중 인용 부호
\ 역슬래쉬
\r 캐리지 리턴
\n 뉴 라인(또는 라인 피드)
\f 폼 피드(form feed)
\t 탭(tab)
\b 백스페이스(backspace)

문자열 - string

문자열 리터널은 이중 인용 부호(", “)로 지정하여 사용한다.

String str = "안녕하세요.";

1.1.2.6 - Java 입문 | 자바 기본 구조 | 자료형(Data type)

Java 언어 데이터 유형

각 변수들은 그들이 가질 있는 자료형에 따라 다양한 형태으로 분류될 수 있다.

기존 자료형(Data type)에는 정수, 실수, 문자, 논리값 등과 같은 자료형이 있으며, 참조 자료형에는 배열, 클래스, 인터페이스 등이 있다. 기본 자료형 변수는 그것이 나타내고자 하는 기본 자료형의 값을 가지고 있는 반면, 참조 자료형 변수는 값이 그에 대한 참조 즉 메모리 주소를 가지고 있다. 이러한 차이는 다른 객체의 메소를 호출할 때 나타난다.

  • 상수(Constant) : 항상 일정한 값을 유지하는 데이터이다.
  • 변수(Variable) : 특정 상황에 따라 값이 변하는 데이터이다.
  • 자료형(Data Type) : 상수나 변수를 유동적인 데이터를 저장하기 위해 지성는 데이터의 형태를 말한다. 자료형은 그 종류에 따라서 각각 다른 메모리의 크기를 갖는다.

자료형 분류

Java의 데이터 유형은 8종류의 기본형(primitive type)과 참조형(reference type)으로 구성되어 있다.

분류를 하자면 아래와 같다.

  • Java Data Type
    • Primitive Type
      • Boolean Type(boolean)
      • Numeric Type
        • Integral Type
          • Integer Type(short, int, long)
          • Floating Point Type(float, double)
        • Character Type(char)
    • Reference Type
      • Class Type
        • String Type
        • Wrapper Class
      • Interface Type
      • Array Type
      • Enum Type

기본 자료형: Primitive Type

기본형은 다음과 같은 특징이 있다.

  • 기본 자료형은 반듯시 사용하기 전에 선언이 되어야 한다.
  • OS에 따라 자료형의 길이가 변하지 않는다.
  • 객체가 아니기 때문에 null을 가질 수 없다.

자바 기본 자료형의 크기와 범위는 아래와 같다.

타입 설명 크기(bit) 범위
char 16비트 유니코드(Unicode) 문자 데이터 16 ‘\u0000’ ~ ‘\uFFFF’
boolean 참/거짓 값 8 true 또는 false
byte 부호를 가진 8비트 정수 8 -128 ~ +127
short 부호를 가진 16비트 정수 16 -32,768 ~ +32,767
int 부호를 가진 32비트 정수 32 -2,147,483,638~+2,147,483,647
long 부호를 가진 64비트 정수 64 -9223372036854775808~+9223372036854775807
float 부호를 가진 32비트 부동 소수점 32 -3.402932347e+38~+3.40292347e+38
double 부호를 가진 64비트 부동 소수점 64 -179769313486231570e+308~1.79769313486231570e+08
  • 문자형: char
  • 논리형: boolean
  • 정수형: byte, short, int, long
  • 부동 소수점형: float, double

BigInteger

long 타입을 사용을 한다면 가장 큰값은 9223372036854775807이다. 이보다 정말 큰 숫자가 필요하다면 BigInteger을 사용해야 된다.
BigInteger는 일반적인 오퍼레이터 연산자는 사용할 수 없고, .add(), .subtract(), .multiply(), .divide() 메소드가 제공이 되고, 매개변수 또한 BigInteger를 받는다.

참조형: Reference Type

참조형은 기본형 8종류를 제외한 모든 데이터 유형이고, 기본적으로 java.lang.Object를 상속 받는다.
참조 유형의 변수는 데이터를 그대로 저장하지 않고 데이터를 저장하는 메모리의 주소만 저장한다. 즉, 참조형의 변수는 데이터를 다른 영역에 저장하고 그 주소만을 이용한다.

Class Type

클래스형은 기본형과 다르게 객체를 참조하는 형태이다.

예제를 통해서 알아보자.

아래 객체는 단순히 str 변수를 갖은 클래스이다.

package com.devkuma.basic.datatype.ex1;

class MyObject {
    private String str;

    public MyObject(String str) {
        this.str = str;
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }
}

아래 객체는 MyObject를 활용하여 str 값을 표시해 주고 있다.

package com.devkuma.basic.datatype.ex1;

public class MyObjectMain {
    public static void main(String[] args) {
        MyObject a = new MyObject("a");
        MyObject b = new MyObject("b");

        // 초기값 표시
        System.out.println(a.getStr()); // a
        System.out.println(b.getStr()); // b

        // a를 b에 대입
        a = b;
        System.out.println(a.getStr()); // b

        // b의 str에 값 "c"를 대입
        b.setStr("c");
        System.out.println(a.getStr()); // c
    }
}

실행 결과:

a
b
b
c

처음 객체 b를 a에 대입하고, a를 표시하면 “b"가 표시되는 것을 확인 할 수 있다. 그러고, b의 str 변수에 값 “c"를 대입해서 a를 표시하니 “c"가 표시되었다.

이는 객체 a와 b라는 변수가 가지는 것은 실제 객체가 아닌 객체의 주소를 갖기 때문이다. a와 b는 같은 객체의 주소를 가지고 있기 때문에 어느 한쪽이 변하더라도 값이 동일한 것이다.

String Type

클래스형에서도 String 클래스는 조금 특별하다. 이 클래스는 참조형에 속하지만 기본적인 사용은 기본형처럼 사용한다. 그리고 불변(immutable) 객체이다.
String 클래스에는 값을 변경해주는 메소드들이 존재하지만, 해당 메소드를 통해 데이터를 변경한다면 새로운 String 클래스 객체를 만들어내는 것이다.

일반적으로 기본형 비교는 == 연산자를 사용하지만 String 객체간의 비교는 .equals() 메소드를 사용해야 한다.

Wrapper Class

기본형은 앞에서 언급했듯이 객체가 아니기 때문에 null을 넣을 수 없지만, Wrapper Class를 사용하면 null을 넣어야 있게 된다. 이를 래퍼 클래스는 기본형을 클래스로 감싼 형태의 객체이기에 null이 선언할 수 있게 된다.

기본형 래퍼 클래스
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Char
boolean Boolean

Interface Type

아래처럼 인터페이스를 만들어 보자. 여기서 T는 제내릭 타입으로 옵션이다.

interface MyInterface<T> {
    void add(T value);
}

인터페이스를 만들게 되면 새로운 참조 자료형을 만드는 것과 같다. 그리고 인터페이스도 자료형이기 때문에 자료형으로써 자신을 구현한 객체의 주소를 가질 수 있다. 다만, 인터페이스에 정의된 메소드만 사용할 수 있게 된다.

배열형: Array Type

배열형은 기본형으로도 만들 수 있고 참조형으로도 만들 수 있다.

int[] num1 = new int[2];
Integer[] num2 = new Integer[3];

자료형에 대해 []를 선언함으로 배열을 지정할 수 있다. 배열형 변수 또한 배열의 주소를 가지고 있는 것이기 때문에 클래스형의 특징과 동일하다. 그래서 같은 객체의 주소를 참조하게 되면 동일한 배열을 가리키게 된다.

그리고 아래 코드처럼 [][]등으로 중첩 괄호를 사용하게 된다면 다중 배열로 사용할 수 있다.

Object[][] obj = new Object[2][3];

Enum Type

…준비중…

형변환(Casting)

묵시적 형 변환(Wide type convert)

int i = 10;
double d = i;
long l = i;
float f = 3.14f;
d = f;

명시적 형 변환

int a;
byte b;
b = (byte) a;

1.1.2.7 - Java 입문 | 자바 기본 구조 | 연산자(Operator)

대입 연산자

대입 연산자는 프로그래밍을 처음하는 사람이라면 조금 헤깔릴 수 있다. 일반 수학에서의 단일 등호 기호 = 는 같다는 표현으로만 사용되지만, 프로그래밍에서는 변수에 할당 즉, 대입을 의미한다.

아래 예제를 보면 값이나 표현식을 변수에 대입을 한다.

  • 변수 = 값
int a = 1; // 변수 a에 1이 대입된다.
  • 변수 = 표현식
int a = 1 + 2; // 변수 a에 1+2를 하여 3이 대입된다.
  • 변수 = 변수 = 변수… = 값(혹은 표현식)
int a, b, c;
a = b = c = 100 // a, b, c 변수에 동일하게 100이 대입된다.

변수의 표현식의 데이터 타입과 같거나 호환될 수 있어야 한다.

int a = 1;
boolean b = true;
int c = a + b;  // 컴파일 error

산술 연산자

산술 연산자(arithmetic operators)는 일반 수학에 사용되는 것과 같은 방식으로 산술식에서 사용한다.

기본적인 산술 연산자

뎃셈, 뺄셈, 곱셈 나눗셈과 같이 모든 숫자형에 대한 산술 연산을 할 수 있다.

연산자 기능 사용법 사용 설명
+ 덧셈 op1 + op2 op1과 op2를 더한다.
- 뺄셈 (또는 단항 마이너스) op1 - op2 op1과 op2를 뺀다.
* 곱셈 op1 * op2 op1과 op2를 곱한다
/ 나눗셈 op1 / op2 op1을 op2로 나눈다.
% 나머지 op1 % op2 op1을 op2로 나눈 나머지를 구한다.

예제 1)

package com.devkuma.tutorial.operators;

public class Arithmetic {

    public static void main(String[] args) {
        int a = 1 + 3;
        int b = a - 2;
        int c = b * 5;
        int d = c / 2;
        int e = d % 3;

        System.out.println("a=" + a);
        System.out.println("b=" + b);
        System.out.println("c=" + c);
        System.out.println("d=" + d);
        System.out.println("e=" + e);
    }
}

실행 결과:

a=4
b=2
c=10
d=5
e=2

증가 연산자와 감소 연산자

증가 연산자는 하나를 증가시키며, 감소 연산자는 하나를 감소시킨다.

연산자가 어디에 위치에 있나에 따라 다른 결과를 가져오게 된다. 예를 들면 op가 초기 값이 2라고 가정하면 ++op 처럼 연산자를 앞에 오게 되면 증가를 시킨 후인 값 3를 반환하게 되고, op++처럼 연산자가 뒤에 오게 되면 증가 시키기 전 값인 2를 반환하고, op값은 3이 된다.

연산자 기능 사용법 사용 설명
++ 전위(prefix) 증가 ++op op에 값 1 증가, op값이 증가시킨 다음 값을 반환한다.
후위(postfix) 증가 op++ op에 값 1 증가, op값이 증가시키기 전 값을 반환한다.
전위(prefix) 감소 –op op에 값 1 감소, op값이 감소시킨 다음 값을 반환한다.
후위(postfix) 감소 op– op에 값 1 감소, op값이 감소시키기 전 값을 반환한다.

예제 2)

package com.devkuma.tutorial.operators;

public class Increase {

    public static void main(String[] args) {

        int a = 10;

        System.out.println(a++);
        System.out.println(++a);
        System.out.println(a--);
        System.out.println(--a);
    }
}

실행 결과:

10
12
12
10

산술적인 대입 연산자

대입 연산자와 다른 산술 연산자들과 결합하여 쓰이는 형태의 연산자이다. 예를 들면 a += 2이라고 하면 a에 2를 더하고 a에 2를 더한 값을 대입한다.

연산자 기능 사용법 의미
+= 덧셈 대입 op1 += op2 op1 = op1 + op2
-= 뺄셈 대입 op1 -= op2 op1 = op1 - op2
*= 곱셈 대입 op1 *= op2 op1 = op1 * op2
/= 나눗셈 대입 op1 /= op2 op1 = op1 / op2
%= 나머지 대입 op1 %= op2 op1 = op1 % op2

예제 3)

package com.devkuma.tutorial.operators;

public class AssignmentOperator {

    public static void main(String[] args) {
        int a = 4;
        int b = 5;
        int c = 6;

        a += 4;
        b -= 2;
        c += a * b;
        c %= 7;

        System.out.println("a=" + a);
        System.out.println("b=" + b);
        System.out.println("c=" + c);
    }
}

실행 결과는 아래와 같다.

a=8
b=3
c=2

비트 연산자

기본적인 비트 연산자

비트 연산자는 이진 비트연산을 한다.

3과 6을 &(AND) 연산을 하게 되면 0010(3) 과 0110(6)에 대해 각 비트를 AND 를 적용하여 0010 인 결과값 2가 된다.

  • 0011(3) & 0110(6) = 0010(2)
연산자 기능 사용법 사용 설명
& AND op1 & op2 비트 단위의 논리곱
I OR op1 I op2 비트 단위의 논리합
^ XOR op1 ^ op2 비트 단위의 배타적 논리합
~ NOT op1 & op2 비트 단위의 부정

예제 4)

package com.devkuma.tutorial.operators;

public class Bit {

    public static void main(String[] args) {
        int a = 3;
        int b = 6;
        int c = a & b;
        int d = a | b;
        int e = a ^ b;
        int f = (~a & b) | (a & ~b);

        System.out.println("c=" + c);
        System.out.println("d=" + d);
        System.out.println("e=" + e);
        System.out.println("f=" + f);
    }
}

실행 결과:

c=2
d=7
e=5
f=5

시프트 연산자

  • 쉬프트 연산자(shift operators)는 왼쪽 쉬프트(left shift), 오른쪽 쉬프트(left shift), 부호없는 오른쪽 쉬프트(Unsigned Right Shift)가 있다.
  • 부호없는 오른쪽 쉬프트(Unsigned Right Shift) 연산자는 32bit와 64bit에서만 값에서만 사용된다.
연산자 기능 사용법 사용 설명
» NOT op1 » op2 op1을 op2만큼 오른쪽으로 쉬프트
« NOT op1 » op2 op1을 op2만큼 왼쪽으로 쉬프트
»> NOT op1 »> op2 op1을 op2만큼 오른쪽으로 쉬프트하면서 왼쪽에는 항상 부호에 무관하게 0이 채워짐

예제 5)

package com.devkuma.tutorial.operators;

public class Shift {

    public static void main(String[] args) {
        // 1000(8)를 2만큼 오른쪽으로 비트를 옮기면 100000(32)가 된다.
        byte a = 8 << 2;

        // 1000(8)를 2만큼 왼쪽으로 비트를 옮기면 10(2)가 된다.
        byte b = 8 >> 2;

        // -1를 32bit로 표현하면 11111111111111111111111111111111이 되고,
        // 이를 2만큼 오른쪽으로 쉬프트하면서 왼쪽에는 항상 부호에 무관하게 0이 채워짐
        // 그래서 값은 01111111111111111111111111111111(2147483647)이 된다.
        int c = -1 >>> 1;

        System.out.println("a=" + a);
        System.out.println("b=" + b);
        System.out.println("c=" + c);
    }
}

실행 결과:

a=32
b=2
c=2147483647

비트 대입연산자

대입 연산자와 다른 비트 연산자들과 결합하여 쓰이는 형태의 연산자이다.

연산자 기능 사용법 의미
&= AND 대입 op1 &= op2 op1 = op1 & op2
I= OR 대입 op1 I= op2 op1 = op1 I op2
^= XOR대입 op1 ^= op2 op1 = op1 ^ op2
>>= » 대입 op1 »= op2 op1 = op1 « op2
<<= « 대입 op1 «= op2 op1 = op1 « op2
>>>= »> 대입 op1 »>= op2 op1 = op1 »> op2

비트 연산자의 논리 결과표

A B A&B AIB A^B ~A
0 0 0 0 0 1
1 0 0 1 1 0
0 1 0 1 1 1
1 1 1 1 0 0

관계 연산자

관계 연산자는 이항 연산자로서 한 연산항이 다른 연산항에 대해 가지는 관계를 결정한다.

이 연산들의 결과 값은 항상 부울형(true 또는 false)의 형태이기 때문에 if문과 같은 제어문이나 for문 같은 반복문장에 자주 사용된다.

연산자 기능 사용법 사용 설명
> 보다 크다 op1 > op2 op1이 op2보다 큰 경우 true, 아니면 false
>= 보다 크거나 작다 op1 >= op2 op1이 op2보다 크거나 같은 경우 true, 아니면 false
< 보다 작다 op1 < op2 op1이 op2보다 작은 경우 true, 아니면 false
<= 보다 작거나 같다 op1 && op2 op1이 op2보다 작거나 같은 경우 true, 아니면 false
== 같다 op1 II op2 op1과 op2가 같은 경우 true, 아니면 false
!= 같지 않다 !op op1과 op2가 같지 않은 경우 true, 아니면 false
instanceof 인스턴스가 같다 op1 instanceof op2 op1이 op2의 인스턴스(객체)인 경우 true, 아니면 false

예제 6)

package com.devkuma.tutorial.operators;

public class Relation {

    public static void main(String[] args) {

        int a = 10;
        int b = 20;
        int c = 10;

        boolean d = a > b;
        boolean e = a >= b;
        boolean f = a < b;
        boolean g = a <= c;
        boolean h = a == c;
        boolean i = a != b;

        System.out.println("d=" + d);
        System.out.println("e=" + e);
        System.out.println("f=" + f);
        System.out.println("g=" + g);
        System.out.println("h=" + h);
        System.out.println("i=" + i);
    }
}

실행 결과

d=false
e=false
f=true
g=true
h=true
i=true

논리 연산자

논리 연산자는 피연산자(operand)의 값을 평가하여 결과로 true 또는 false값을 반환한다.

특이 사항으로 &&(논리곱)와 ||(논리합) 단축 논리 연산자(Short-circuit logical operator)가 있다. 부하가 1개인 &와 |과는 다르게 하나의 첫 번째 피연산자를 평가한 결과 두 번째 피연산자를 평가할 필요가 없을 경우 바로 결과를 반환하는 연산자이다. 그러기 때문에 두 번째 피연산자에 뭔가 처리가 있는 로직인데 평가를 하지 않게 되면 처리가 되지 않는다.

예를 들면, 아래와 같은 수식에서 flag가 true이면 “count > index++“의 수식은 처리가 되지 않아 index는 증가하지 않는다. 반대로 flag가 false이면 index가 1만큼 증가를 한다.

(flag == true) || (count > index++)
연산자 기능 사용법 사용 설명
& 논리적 AND op1 & op2 op1과 op2가 모두 true인 경우 true, op1과 op2를 모두 평가한다.
I 논리적 OR op1 I op2 op1과 op2중 둘 중 하나 이상이 true인 경우 true, op1과 op2를 모두 평가한다.
^ 논리적 XOR(배타적 OR) op1 ^ op2 op1과 op2중 둘 중 하나 이상이 true인 경우 false, op1과 op2를 모두 평가한다.
&& 짧은 순환 AND op1 && op2 op1과 op2가 모두 true인 경우 true, op1가 false이면 op2를 평가하지 않는다.
II 짧은 순환 OR op1 II op2 op1과 op2중 둘 중 하나 이상이 true인 경우 true, op1가 true이면 op2를 평가하지 않는다.
! 논리적 단항 NOT !op op가 true이면 false, false이면 true

예제 7)

package com.devkuma.tutorial.operators;

public class Logic {

    public static void main(String[] args) {

        boolean a = true;
        boolean b = false;
        boolean c = true;

        boolean d = a & b;
        boolean e = a | b;
        boolean f = a ^ b;
        boolean g = a && b;
        boolean h = a && c;
        boolean i = a || b;
        boolean j = a || c;
        boolean k = !a;

        System.out.println("d=" + d);
        System.out.println("e=" + e);
        System.out.println("f=" + f);
        System.out.println("g=" + g);
        System.out.println("h=" + h);
        System.out.println("i=" + i);
        System.out.println("j=" + j);
        System.out.println("k=" + k);
    }
}

실행 결과:

d=false
e=true
f=true
g=false
h=true
i=true
j=true
k=false

논리 연산자의 논리 결과표

A B A&B AIB A^B ~A
false false false false false true
true false false true true false
false true false true true true
true true true true false false

논리 대입 연산자

대입 연산자와 다른 논리 연산자들과 결합하여 쓰이는 형태의 연산자이다.

연산자 기능 사용법 의미
&= 논리적 AND 대입 op1 &= op2 op1 = op1 & op2
I= 논리적 OR 대입 op1 I= op2 op1 = op1 I op2
^= 논리적 XOR대입 op1 ^= op2 op1 = op1 ^ op2

삼항 연산자

3개의 피연산자 (operand) 가진 3항 연산자 “:?“는 선택문의 if-when-else 문을 축약해서 사용할 수 있다.

  • 조건 ? 수식1(조건이 true인 경우) : 수식2 (조건이 false인 경우)

조건을 평가하여 true인지 false인지 판별하여 true이면 수식1을 false이면 수식2를 실행하여 결과가 반환된다. 조건문(if) 대신에 대입 연산자와 함께 유용하게 사용된다.

예제 3)

public class Three {

    public static void main(String[] args) {
        int a = 2;
        int b = 3;
        int max = (a > b) ? a : b;

        System.out.println("max=" + max);
    }
}

실행 결과:

max=3

연산자 우선순위

우선순위에 상관 없이 수식의 일부분을 괄호 ()로 묶어서 우선순위를 지정할 수 있다.

우선순위 연산자
1 (), [], .
2 ++, --, ~, !
3 *, /, %
4 +, -
5 >>, >>>, <<
6 >, >= , <, <=
7 ==, !=
8 &
9 ^
10 I
11 &&
12 II
13 ?:
14 =, op=

참조

1.1.2.8 - Java 입문 | 자바 기본 구조 | 문자열(String)

문자열(String)

문자열은 String 클래스의 객체로 구현되어 사용된다. 클래스에 대한 자세한 설명은 클래스를 배우고 난 후에 하도록 하고, 여기에서는 간단하게 선언하는 방법과 사용하는 것에 대해서 알아보자.

문자열 표시

문자열은 간단히 선언하고 바로 사용할 수 있다. (이에 반해, C언어는 메모리 관련해서 문자열을 사용하는 것이 쉽지 않다)

StringJava.java

package com.devkuma.tutorial.string;

public class StringJava {

  public static void main(String[] args) {
    String str = "This is java.";
   System.out.println(str);
  }
}

실행 결과:

This is java.

문자열 결함

문자열(String)은 다른 데이터 자료형과도 결합을 시킬수 있다.

package com.devkuma.tutorial.string;

public class StringConcat {

    public static void main(String[] args) {
        int i = 7;
        System.out.println("The value of variable this " + i);
    }
}

실행 결과:

The value of this i variable is 7

정수 i의 값은 우선 문자열로 변환되고 다음 문자열과 결함(concatenation)된다.

문자열 메소드

문자영를 클래스 타입이기에 여러 가지 메소스를 가지고 있다. 대표적으로 문자열 길이를 구할 수 있는 length(), 문자열 지정된 인덱스에서 하나의 문자열을 얻는 charAt(), 문자열이 같은지 비교할 수 있는 equal() 등의 메소드가 포함되어 있다. 자세한 설명은 클래스 배우고 알아보도록 하겠다.

package com.devkuma.tutorial.string;

public class StringMethod {

    public static void main(String[] args) {
        String str1 = "abcdefghijklmnopqrstuvwxyz";
        String str2 = "This is java";
        String str3 = "abcdefghijklmnopqrstuvwxyz";

        System.out.println("length=" + str1.length());
        System.out.println("charAt=" + str1.charAt(3));

        System.out.println("str1 equals str2=" + str1.equals(str2));
        System.out.println("str1 equals str3=" + str1.equals(str3));
    }
}

실행 결과:

length=26
charAt=d
str1 equals str2=false
str1 equals str3=true

1.1.2.9 - Java 입문 | 자바 기본 구조 | 배열(Array)

자바에서는 같은 종류의 데이터르 저장하기 위한 자교 구조로 배열이 가지고 있다. 즉, 배열은 같은 자료형의 데이터들을 저장하는 하나의 기억 공간이다.

1차원 배열

배열을 선언하기 위해 우선 배열에 저장할 데이터 자료형을 선언하고 배열명과 공간의 크기를 선언해야 한다.

data-type var-name[];
data-type[] var-name;

배열의 선언시 배열의 공간 크기는 지정하지 않아도 된다. 배열을 선언한다고 해서 바로 메모리에 생성되지 않는다. 선언만 하고 배열을 사용을 하게 되면 널 참조 에러가 발생한다. 배열 객체를 생성하기 위해서는 반듯이 new 문장을 이용해서 배열을 생성해야 한다. (new에 대해서는 클래스 객체에 관련된 장에서 자세히 알아보도록 한다.)

var-name = new data-type[size];

앞에 두문장을 하나의 문장으로 사용 할수 있다.

data-type[] var-name = new data-type[size];
data-type var-name[] = new data-tapa[size];

즉, 아래와 같이 하나의 문장으로 선언과 생성을 동시에 수행할 수 있다.

int[] a = new int[10];

아래 예제는 3개의 배열을 초기화하고, 값을 대입하고 출력하는 예제이다.

package com.devkuma.tutorial.arry;

public class Array1 {

    public static void main(String[] args) {
        int[] a = new int[3];
        a[0] = 1;
        a[1] = 2;
        a[2] = 3;

        System.out.println("a[0] : " + a[0]);
        System.out.println("a[1] : " + a[1]);
        System.out.println("a[2] : " + a[2]);
    }
}

아래 예제는 3개의 배열을 초기화와 값을 한번에 대입해서 출력하는 예제이다.

package com.devkuma.tutorial.arry;

public class Array2 {

    public static void main(String[] args) {
        int[] a = {1, 2, 3};

        System.out.println("a[0] : " + a[0]);
        System.out.println("a[1] : " + a[1]);
        System.out.println("a[2] : " + a[2]);
    }
}

아래 예제는 3개의 배열을 초기화와 값을 한번에 대입하고, 반복문을 사용하여 출력하는 예제이다.

package com.devkuma.tutorial.arry;

public class Array3 {

    public static void main(String[] args) {
        int[] a = { 1, 2, 3 };

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

배열의 길이는 위에 예제처럼 length로 구할 수 있다.

다차원 배열

다차원 배열을 1차원 배열을 여러개 사용하여 구현한다. 즉, 1차원 배열은 선언한 후에 각각의 인덱스 안에 새로운 배영을 할당하는 방식이다. 다차원 배열의 선언은 1차원 배열

data-type var-name[][] = new data-type[size][size];
data-type[][] var-name = new data-type[size][size];
data-type[] var-name[] = new data-type[size][size];
int a[][] = new int[4][5];
int[][] a = new int[4][5];
int[] a[] = new int[4][5];

행열을 가진 배열에서의 인덱스 값

[0][0] [0][1] [0][2] [0][3] [0][4]
[1][0] [1][1] [1][2] [1][3] [1][4]
[2][0] [2][1] [2][2] [2][3] [2][4]
[3][0] [3][1] [3][2] [3][3] [3][4]

오른쪽 지수는 열을 결정하고 왼쪽 지수는 행을 결정한다.

1.1.2.10 - Java 입문 | 자바 기본 구조 | static 키워드

static이란?

static은 클래스의 구성 요소에 부여할 수 있는 수식자로서, 메서드와 필드에 부여할 수가 있다.
부여된 필드 및 메서드는 프로그램이 실행될 때 메모리 할당을 받아 프로그램이 종료될 때까지 사라지지 않고 유지된다. 그리고 그 값은 모든 인스턴스가 공유한다.

static 필드

일반적인 필드는 인스턴스가 생성될 때마다 새로 생성되며 다양한 값을 가지지만, static가 선언된 필드는 프로그램이 실행될 때 생성되어 모든 인스턴스가 공유한다.

먼저 아래와 같은 Student라는 클래스가 있다고 하자. Student.java

package com.devkuma.basic.statickeyword.ex1;

public class Student {
    static int number = 100;
    String name;

    Student(String name) {
        this.name = name;
    }
}

이 Student 클래스에서는 number 변수가 static으로 선언되어 있다.

StudentSample1.java

package com.devkuma.basic.statickeyword.ex1;

public class StudentSample {

    public static void main(String[] args) {
        Student devkuma = new Student("devkuma");
        Student araikuma = new Student("araikuma");
        Student kimkc = new Student("kimkc");

        System.out.println(++devkuma.number);
        System.out.println(++araikuma.number);
        System.out.println(++kimkc.number);
    }
}

실행 결과:

101
102
103

위의 얘제에서는 Student 클래스의 인스턴스를 생성하여, 각 인스턴스의 number 값을 출력한다.

각 인스턴스는 각각 101, 102, 103을 출력되었다. 이는 모든 인스턴스는 static 필드인 number를 공유하기 때문이다.
즉, 아래 그림 처럼 3개의 인스턴스가 1개의 필드를 바라보고 있는 것이다.

static 변수 공유

static 필드는 인스턴스와 다른 시점에 생성되므로, 아래와 같이 인스턴스가 아닌 클래스의 이름으로 참조할 수 있다.

package com.devkuma.basic.statickeyword.ex1;

public class StudentSample {

    public static void main(String[] args) {
        Student devkuma = new Student("devkuma");
        Student araikuma = new Student("araikuma");
        Student kimkc = new Student("kimkc");

        System.out.println(++devkuma.number);
        System.out.println(++araikuma.number);
        System.out.println(++kimkc.number);

        System.out.println(Student.number);
    }
}

실행 결과:

101
102
103
103

static 메서드

static는 클래스의 구성 요소에 선언되기에, 메서드에도 static를 선언할 수도 있다.

이런 메서드를 static 메서드라고 한다. static 메서드에서는 static 필드와 로컬 변수 밖에 사용할 수 없다.

Student.java

package com.devkuma.basic.statickeyword.ex2;

public class Student {
    static int number = 100;
    String name;

    Student(String name) {
        this.name = name;
    }

    public static void printNumber() {
        System.out.println("지금 Student의 number는" + number + "입니다.");
    }
}

StudentSample.java

package com.devkuma.basic.statickeyword.ex2;

public class StudentSample {

    public static void main(String[] args) {
        Student.printNumber();
        Student.number++;
        Student.printNumber();
    }
}

실행 결과:

지금 Student의 number는 100입니다.
지금 Student의 number는 101입니다.

위에 예제 같이 인스턴스를 생성하지 않아도 클래스의 이름을 참조하여 호출할 수 있다.

static 메서드로 사용할 수 있는 변수

package com.devkuma.basic.statickeyword.ex2;

public class Student {
    static int number = 100;
    String name;

    Student(String name) {
        this.name = name;
    }

    public static void printNumber() {
        int localVariable = 100; // ok
        name = "a"; // error
        System.out.println("지금 Student의 number는 " + number + "입니다.");
    }
}

위에 예제에서는 로컬 변수(localVariable) 선언시에는 에러가 발생하지 않았지만, 클래스 맴버 변수(name)에서는 에러가 발생하고 있다.

static은 앞에서 설명한 것처럼 로컬 변수와 static 필드만 사용할 수 있다.
static 메서드는 인스턴스를 생성하지 않아도 사용할 수 있는 메서드이다. 그래서 일반 필드는 인스턴스가 생성 될 때 생성되므로, static 메서드에서는 사용할 수 없다.

1.1.2.11 - Java 입문 | 자바 기본 구조 | final 키워드

final 키워드

Java 프로그램에서 final의 의미는 마지막으로 선언했기 때문에 수정할 수 없다는 의미이다.
즉, final가 부여된 요소는 초기화 이후에 수정할 수 없다.

final은 변수, 메서드, 클래스에 부여할 수 있는데, 어느 위치에 선어되는지에 따라 기능이 달라진다.

선언되는 위치 설명
variable 변수의 데이터를 상수화 하기위해 사용하며, 값의 변경이 불가능 해진다.
Method overriding이 불가능 해진다.
Class 상속이 불가능 해진다.

final 변수

final 데이터형 변수명 = ;

final이 부여된 변수는 상수가 된다.
상수는 변하지 않는 수를 의미하며 상수로 선언한 변수는 값을 변경할 수 없다. 필터 상수명은 관례적으로 영어 대문자와 _로 선언한다.
일반적으로 final 변수는 static과 조합하여 여러 곳에서 공유하는 고정 값을 지정하여 사용한다.

public class FinalSample {      
    final int INDEX_LIMIT = 100;    
 
    public void display() { 
        System.out.println(INDEX_LIMIT);
        int[] arr = new int[INDEX_LIMIT];
        INDEX_LIMIT = 1005; // Error: 값을 다시 할당 할 수 없다.
    }   
}

객체 변수인 경우

객체 변수의 경우 필드를 변경할 수 있지만, 객체 변수에 객체를 새로 생성하여 할당할 수 없다.

class SampleClass {     
    int a;  
}

public final class FinalSample {        
    public static void main(String[] args) {    
        final int RESULT = 10;
        RESULT++;
 
        final SampleClass SAMPLE = new SampleClass();
        SAMPLE.a = 10;
        SAMPLE.a = 15;
 
        SAMPLE = new SampleClass(); // 객체를 새로 생성하여 할당할 수 없다.
 
    }   
}

인수인 경우

인수에도 final을 선언할 수 있다. 선언하면 그 인수는 사용되는 메서드에서는 값을 다시 할당 할 수 없게 된다.

public final class FinalSample {        
    public void finalTestMethod(final int a) {
        a = 10; // Error: 값을 다시 할당 할 수 없다.
    }   
}

final 메서드

final 메서드는 상속된 자식 클래스에서 다시 정의(overriding)을 할 수 없다.

class FinalMethodTest {
    int result = 10;    
    public final void printResult() {
        System.out.println(result);
    }
}

public class ExampleCalss extends FinalMethodTest {
    @Override   
    public final void printResult() { // Error: 오버라이딩을 할 수 없다.
 
    }
}       

final 클래스

클래스를 final을 부여해 작성하면 그 내용을 수정할 수 있으므로 상속을 할 수 없다.
final은 변수나 메서드를 재정의하면 기능이 정상적으로 동작하지 않는 클래스에 부여해 사용한다.

final class ExtendTest {    
    int a;
}

public class ExampleCalss extends ExtendTest { // Error: ExtendTest를 상속할 수 없다.
    public static void main(String[] args) {
 
    }
}   

1.1.2.12 - Java 입문 | 제네릭 (Generics)

Generics란?

제네릭스(Generics)는 Java5에서 도입된 것으로, 데이터형을 **일반화 한다(Generalize)**라는 의미이다.
제네릭스는 클래스 또는 메소드에서 사용하는 데이터형을 미리 지정하는 방법이다.
제네릭스를 사용하면 데이터 형식 불일치 문제를 컴파일 과정에서 예방할 수 있다.
그리고 제네릭스를 사용하면 형 캐스트를 할 필요가 없어져 프로그램의 성능이 향상된다.

BadGenericsSample.java

package com.devkuma.basic.generics;

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

public class BadGenericsSample {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        arrayList.add("test");

        String test = arrayList.get(0); // error
    }
}

위의 예제과 같이 제네릭으로 데이터 유형을 지정하지 않고, 데이터를 저장하게 되면 저장할 데이터는 Object 형식으로 저장된다. 그래서 가져올 시에는 형변환를 반듯시 해야 한다.

GoodGenericsSample.java

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

public class GoodGenericsSample {
    public static void main(String[] args) {
        List<String> arrayList = new ArrayList();
        arrayList.add("test");

        String test = arrayList.get(0); // ok
    }
}

제네릭으로 데이터형을 미리 지정하면 저장되는 데이터가 지정된 데이터로 저장되므로, 데이터를 가져올 시에는 형변환을 하지 않아도 된다.

제네릭스를 쓰는 법

제네릭스은 아래와 같이 선언한다.

public class 클래스명<T> 
public interface 인터페이스명<E>

<>괄호 안에는 정해진 규칙은 없지만, 일반적으로 대문자와 알파벳으로 표현한다.

클래스에 제네릭 사용

아래는 클래스에 제레릭을 사용한 예제이다.

package com.devkuma.basic.generics.ex2;

public class GenericsSampleClass<T> {

    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

위와 같이 작성한 클래스는 인스턴스를 생성해 사용할 때, 데이터 형식을 지정하여 사용한다.

package com.devkuma.basic.generics.ex2;

public class GenericsSample {

    public static void main(String[] args) {
        GenericsSampleClass<String> classSample = new GenericsSampleClass();
        classSample.setT("SetItem");
        String getItem = classSample.getT();
    }
}

인터페이스에 제네릭 사용

아래는 인터페이스에 제레릭을 사용한 예제이다.

package com.devkuma.basic.generics.ex3;

public interface GenericInterfaceSample<T> {
    T testMethod(T t);
}

위와 같이 작성된 인터페이스는 구현 클래스에서 데이터형을 지정할 수 있다.

package com.devkuma.basic.generics.ex3;

public class GenericInterfaceSampleImpl implements GenericInterfaceSample<String> {

    @Override
    public String testMethod(String t) {
        return null;
    }
}

제네릭스의 범위 지정

제네릭으로 지정하는 데이터형의 범위도 상속을 이용하면 제한할 수 있다.

package com.devkuma.basic.generics.ex4;

public class GenericClassSample<T extends Number>{
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

위와 같이 작성되고 있는 클래스는 제네릭스로 데이터형을 지정할 때, Number 클래스를 상속하는 클래스만 지정할 수 있다.

package com.devkuma.basic.generics.ex4;

public class GenericSample {
    public static void main(String[] args) {
        GenericClassSample<String> classSample = new GenericClassSample(); // error
        classSample.setT("SetItem");
        String getItem = classSample.getT();
    }
}

위와 같이 제네릭로 데이터형을 String을 지정하였지만, StringNumber 클래스의 하위 클래스가 아니기 때문에 컴파일 에러가 발생한다.

1.1.2.13 - Java 입문 | 자바 기본 구조 | 어노테이션(Annotation)

자바 어노테이션(Java Annotation)

자바 어노테이션(Java Annotation)은 자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종이다.
보통 @ 기호를 앞에 붙여서 사용한다.
JDK 1.5 버전 이상에서 사용 가능하다.
자바 어노테이션은 클래스 파일에 임베디드되어 컴파일러에 의해 생성된 후 자바 가상머신에 포함되어 작동한다.

내장 애너테이션

자바는 언어에 내장된 애터네이션들의 집합을 정의한다. 7개의 표준 어노테이션 중에 3개가 java.lang의 일부이며, 나머지 4개는 java.lang.annotation으로부터 가져온다.

자바 코드에 적용되는 내장 어노테이션

  • @Override
  • @Deprecated
  • @SuppressWarnings

기타 어노테이션에 적용되는 어노테이션(메타 애터네이션)

  • @Retention
  • @Documented
  • @Target
  • @Inherited

자바 7부터 추가 어노테이션이 언어에 추가되었다.

  • @SafeVarargs
  • @FunctionalInterface
  • @Repeatable

예제

다음은 @override 어노테이션의 예제이다.

public class Animal {
    public void speak() {
    }

    public String getType() {
        return "Generic animal";
    }
}

public class Cat extends Animal {
    @Override
    public void speak() { // This is a good override.
        System.out.println("Meow.");
    }

    @Override
    public String gettype() { // Compile-time error due to mistyped name.
        return "Cat";
    }
}

참고

1.1.3 - Java 입문 | 제어문(Control statement)

자바에 대한 제어문은 크게 조건문, 반복문, 분기문이 3가지가 있다.

조건문

  • if ~ else문
  • switch ~ case문

반복문

  • for문
  • for ~ each 문
  • while 문
  • do ~ while문

분기문

  • break문
  • coutinue문
  • return문

1.1.3.1 - Java 입문 | 제어문(Control statement) | 조건문(Conditional statement)

조건문(Conditional statement)

자바는 if문과 switch문 두 개의 조건문을 가지고 있다. 이런한 문장들을 사용하여 실행 중에 조건들을 토대로 프로그램의 실행의 흐름을 제어할 수 있다.

if~else문

if문은 조건에 따라 두 대의 문장중에 하나가 수행되는 조건문이다.

if(조건식)
    실행 문장1;
else
    실행 문장2;

if 문은 조건식에 내용에 따란 true나 false값을 반환한다. else문은 필요시 기술을 하며 필요 없을 경우에는 생략할 수 있다.

package com.devkuma.tutorial.control.statement;

public class IfElse {

    public static void main(String[] args) {

        int a = 0;
        if (a == 1) {
            System.out.println("a는 1이다.");
        } else {
            System.out.println("a는 1 아니다.");
        }
    }
}

if ~ else if문

if ~ else if문은 if문을 이용하여 다중 선택을 가능하게 해준다.

if(조건식1)
    실행 문장1;
else if(조건식2)
    실행 문장2;
...
else
    실행 문장3;

자바의 모든 구문은 위에서 아래로 (top-down) 순차적으로 수행한다. 처음 조건식이 참으로 평가되면 해당 식을 수행한 후, if문장을 빠져 나가게 된다. 처음 조건절이 거짓으로 평가되면 계속해서 else if 조건절을 평가하여 수행한다.

package com.devkuma.tutorial.control.statement;

public class IfElseIf {

    public static void main(String[] args) {

        int month = 3;

        String season = null;
        if (month == 1) {
            season = "winter";
        } else if (month == 2) {
            season = "winter";
        } else if (month == 3) {
            season = "spring";
        } else if (month == 4) {
            season = "spring";
        } else if (month == 5) {
            season = "spring";
        } else if (month == 6) {
            season = "summer";
        } else if (month == 7) {
            season = "summer";
        } else if (month == 8) {
            season = "summer";
        } else if (month == 9) {
            season = "autumn";
        } else if (month == 10) {
            season = "autumn";
        } else if (month == 11) {
            season = "autumn";
        } else if (month == 12) {
            season = "winter";
        }

        System.out.println("month=" + month + ", season=" + season);
    }
}

실행 결과는 아래와 같다.

month=3, season=spring

switch ~ case문

switch ~ case문은 다중 선택을 가능하게 해준다.

switch(수식) {
    case 값1:
        실행 문장1;
       break;
    case 값2:
         실행 문장2;
        break;
  .....
   case 값n:
        실행 문장n;
        break;
   defulat:
        디폴트 실행 문장;
}

각 case문의 break문은 선택사항이다. 만일 break를 생략하게 된다면, 실행은 바로 밑에 있는 case문으로 넘어가게 된다. 이를 이용하여 다수개의 case절을 하나의 시행문으로 지정할 수 있다.

package com.devkuma.tutorial.control.statement;

public class SwitchCase1 {

    public static void main(String[] args) {

        int a = 257;
        int result = a % 3;

        switch (result) {
        case 1:
            System.out.println(a + "는 3의 배수가 아니다.");
            break;
        case 2:
            System.out.println(a + "는 3의 배수가 아니다.");
            break;
        default:
            System.out.println(a + "는 3의 배수이다.");
            break;
        }
    }
}

case문의 break문는 선택사항이다. 만일 break문을 생략하게 된다면, 실행은 바로 아래에 있는 case문으로 넘어가게 된다. 따라서 위에 예제는 아래과 같아 변경 가능하다.

package com.devkuma.tutorial.control.statement;

public class SwitchCase2 {

    public static void main(String[] args) {

        int a = 257;
        int result = a % 3;

        switch (result) {
        case 1:
        case 2:
            System.out.println(a + "는 3의 배수가 아니다.");
            break;
        default:
            System.out.println(a + "는 3의 배수이다.");
            break;
        }
    }
}

case 문자의 실행 문장은 break를 만나거나 마지막 case문 또는 default문에 도달할 때까지 계속 실행된다.

위에 if~else문 예제를 변경하면 아래와 같아 변경할 수 있다.

package com.devkuma.tutorial.control.statement;

public class SwitchCase3 {

    public static void main(String[] args) {

        int month = 3;

        String season = null;

        switch (month) {
        case 1:
        case 2:
        case 12:
            season = "winter";
            break;
        case 3:
        case 4:
        case 5:
            season = "spring";
            break;
        case 6:
        case 7:
        case 8:
            season = "summer";
            break;
        case 9:
        case 10:
        case 11:
            season = "autumn";
            break;
        default:
            break;
        }

        System.out.println("month=" + month + ", season=" + season);
    }
}

중첩된 switch문

switch문 내에 다른 switch문을 포함시킬 수 있다. 중첩된 상태의 switch문들은 같은 값을 가지는 case문을 자유롭게 사용할 수 있다.

package com.devkuma.tutorial.control.statement;

public class SwitchCase4 {

    public static void main(String[] args) {

        int a = 1;
        int b = 2;

        switch (a) {
        case 1:
            switch (b) {
            case 0:
                System.out.println("a=1, b=0");
            case 1:
                System.out.println("a=1, b=1");
            }
            break;
        case 2:
            System.out.println("a=2");
            break;
        default:
            break;
        }
    }
}

1.1.3.2 - Java 입문 | 제어문(Control statement) | 반복문(Loop Control)

반복문(Loop Control)

자바에는 for, while, do-while문과 J2EE 5.0에서 추가된 for~each 문이 있다. 반복문들은 지정된 문장을 조건이 만족할 때까지 반복한다. 조건을 지정하는 방법에 따라 for, while, do-while, for~each문이 구분되어 사용된다. 반복될 문장이 한 문장일 경우에는 블럭으로 지정하지 않아도 되지만, 한 문장 이상일 경우에는 중괄호({ })를 사용하여 블록으로 지정해야 한다.

for

for문은 주어진 초기값을 시작으로 조건을 만족하는 동안 for 블록을 반복 실행하는 명령문이다.

for (초기값; 조건식; 증감값) {
   반복 실행 문장;
}

for문이 시작되면, 초기값이 부분이 한번만 실행된다.다음 조건식이 실행되는데, 이 조건식은 반듯이 부울 식이어야 한다. 이 조건식이 false가 되면 반복문은 종료된다. 증감값은 반복 제어 변수를 증가시키거나 감소시키는 식이 온다. 처음 조건식이 실행된 다음에 실행 문장이 실행되고, 증감값이 실행을 한 다음에 조건식을 실행하고 실행 문장이 실행되는 반복을 하게 된다.

다음은 1부터 100까지 합을 구하는 예제이다.

package com.devkuma.tutorial.control.statement;

public class For1 {

    public static void main(String[] args) {

        int sum = 0;

        for (int i = 1; i <= 100; i++) {
            sum += i;
        }

        System.out.println("sum=" + sum);
    }
}

for문의 초기값, 조건식 증감값에서 하나 이상의 문장을 나열하고 싶은 경우에는 콤마(,)를 사용하여 선언 할 수 있다.

아래 두 예제는 동일한 결과를 출력한다.

package com.devkuma.tutorial.control.statement;

public class For2 {

    public static void main(String[] args) {

        int a, b;
        b = 4;

        for (a = 1; a < b; a++) {
            System.out.println("a=" + a + ", b=" + b);
            b--;
        }
    }
}
package com.devkuma.tutorial.control.statement;

public class For3 {

    public static void main(String[] args) {

        int a, b;

        for (a = 1, b = 4; a < b; a++, b--) {
            System.out.println("a=" + a + ", b=" + b);
        }
    }
}

for ~ each

JDK 1.5에 처음으로 for으로 또는 또 다른 형식인 for~each 문장을 추가되었다.

for~each문은 컬렉션 타입(collection type)의 데이터에서 데이터가 존재하는 횟수 만큼 반복적으로 실행하는 명령문이다.

for (변수 : 컬렉션) {
    반복 실행 문장;

아래 예제는 배열과 컬렉션 타임의 데이터를 사용한 예제이다.

package com.devkuma.tutorial.control.statement;

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

public class ForEach {

    public static void main(String[] args) {

        String array[] = { "A", "B", "C" };
        for (String a : array) {
            System.out.println("a=" + a);
        }

        List<String> list = new ArrayList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        for (String l : list) {
            System.out.println("l=" + l);
        }
    }
}

while

while 문은 조건절이 참일 동안 while 블록을 반복 실행한다.

while (조건식) {
    반복 실행 문장;     
}

while문을 사용한 예제는 아래와 같다.

package com.devkuma.tutorial.control.statement;

public class While {

    public static void main(String[] args) {

        int sum = 0;
        int i = 1;
        while (i <= 100) {
            sum += i;
            i++;
        }

        System.out.println("sum=" + sum);
    }
}

do ~ while

적어도 한번은 수행될 수 있는 반복문이다.

do {
    반복 실행 문장;  
} while (조건식)

do ~ while문을 사용한 예제는 아래와 같다.

package com.devkuma.tutorial.control.statement;

public class DoWhile {

    public static void main(String[] args) {

        int sum = 0;
        int i = 1;
        do {
            sum += i;
            i++;
        } while (i <= 100);

        System.out.println("sum=" + sum);
    }
}

while문과 do ~ while문의 차이는 조건식을 언제 확인하는지가 다르다.

1.1.3.3 - Java 입문 | 제어문(Control statement) | 분기문(Branching statement)

프로그램 제어를 이동시키기 위해 break, countiune, return 문이 있다. 이 문장들은 프로그램의 수행을 순서를 변화시키는 역확을 한다.

break문

break문은 3가지 역할을 한다

  1. switch문에서 switch문을 벗어나는데 사용된다.
  2. 반복문에서 반복루프를 벗어나는데 사용된다.
  3. 기존 프로그램에서 사용되는 goto문의 개선된 형태로 사용된다.

coutinue문

반복문 내에서 continue문을 만나면 수행을 중지하고 제어를 반복문의 처음으로 옮긴다.

아래 예제는 coutinue문을 사용하여 1~100에서 홀수의 합계를 구한다.

package com.devkuma.tutorial.control.statement;

public class Coutinue {

    public static void main(String[] args) {

        int sum = 0;

        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                continue;
            }
            sum += i;
        }

        System.out.println("sum=" + sum);
    }
}

return문

현재 수행중인 메소드를 중단하고 제어를 현재의 메소드를 호출한 곳으로 반환한다.

아래 예제는 메소드에서 숫자를 입력받아서 3의 배수인 여부를 체크하고 있다.

package com.devkuma.tutorial.control.statement;

public class Return {

    public void check(int i) {
        if (i % 3 == 0) {
            System.out.println(i + "는 3의 배수이다.");
            return;
        }
        System.out.println(i + "는 3의 배수가 아니다.");
    }

    public static void main(String[] args) {
        Return r = new Return();
        r.check(6);
    }
}

1.1.4 - Java 입문 | 객체지향 프로그래밍(OOP)

객체지향(object oriented)와 절차지향(procedural oriented)

프로그래밍의 초기 방식은 절차지향이었다. 말 그대로 처음부터 코드가 기술된대로 차례대로 실행이 되는 방식으로 컴퓨터의 처리 구조와 비슷해 실행 속도가 빠르다. 하지만 프로그램이 더 커지고 복잡하게 되면 유지 보수가 어려워지고, 정해진 순서대로 입력을 해야 하므로 순서가 바뀌면 결과값을 도출하기 어렵다. 그래서 대형 프로젝트에 적합하지 않다.

객체 지향 프로그래밍은 객체라는 논리적인 단위로 실행되는 방식으로 데이터의 처리방법보다는 프로그램이 사용하고 있는 데이터를 중요하게 생각한다. 처리속도는 상대적으로 느리고 설계에 많은 시간이 투자되기도 하지만, 코드의 재활용성이 높고 유지보수가 쉽다. 분석 설계의 전환이쉽기도 하여 대형 프로젝트에 적합하다.

객체지향 프로그래밍은 대표적인 언어로는 자바, C#, Object-C 등이 있고, 절차지향언어로는 C, Pascal, Fortran 등이 있다.

객체 (object)

객체란 정보를 효율적으로 관리하기 위한 논리적인 단위이라고 할 수 있다. 객체는 속성(attribute) 또는 필드(field)라고 부르는 변수와 정보를 처리하는 절차를 기술하고 있는 메소드(method)로 이루워져 있다.

현실 세계에서 우리가 흔하게 보는 모든 사물 및 생물, 유무형의 존재 등에 비유되며 객체로 표현이 가능하다. 예를 들면, 사람은 이름, 키, 몸무게, 혈액형 등의 특징을 가지고 있고 식사, 일, 공부 등의 행위을 한다. 여기서 특징과 행위를 프로그래밍적으로 표현하면 속성과 메소드가 된다.

클래스(class)

프로그래밍을 작성하다 보면 같은 형태의 객체를 생성해야 하는 경우가 있다. 예를 들면, 회사에서 인사 시스템을 만든다고 하자. 이때 여러 사원들의 속성이 같은 형태의 데이터들로 존재하고 이들은 같은 형태의 일을 하게 될 것이다. 여러 사원들에 대한 객체를 생성하기 위해 프로그래밍을 해야 한다면, 사원의 인사 변동이 생길 때마다 프로그램이 변경되는 사태가 벌어질지도 모른다.

그래서 객체지향에서는 같은 객체를 만들기 위해 형틀(template) 즉, 클래스를 이용한다. 객체는 클래스를 바탕으로 생성이 되고 같은 클래스로 생성된 객체는 모두 같은 속성과 메소드를 가지게 된다.

인사 시스템으로 따지면 각 사원마다 실제 이름과 경력들의 속성에 각각 다른 값이 들어가 있어서 이름으로 검색하거나 하는 일이 조금씩 난위도 및 역활이 다른 일을 할수 있게 업무 배치를 할 수 있게 된다.

보통 프로그래밍을 건물에 비교하는 경우가 많다. 여기서도 건축에 비교해서 설명을 하자면, 클래스(Class)는 건물 설계도 이고, 건축 설계도에 의해 만들어진 건물을 객체(Object)라고 볼수 있다.

상속 (inheritance)

상속은 상위에서 가지고 있는 것을 하위에게 물려주거나, 하위에서 물려받는 것이다. 객체 지향 프로그래밍에서도 동일하게 상위 객체가 가지고 있는 속성과 메소드를 하위 객체에서 물려주거나 물려바는 것이다.

상속을 받으므로써 코드의 재사용이 가능해지고 코드의 중복을 줄여준다. 이렇게 되므로써 유지 보수의 편의성도 제공한다.

캡슐화(encapsulation)

객체는 여러 속성과 여러 기능을 가지고 있다. 객체를 사용하는 입장에서는 하나의 기능이 내부에서 실제 어떻게 처리가 되는지 구지 알 필요도 없고 어떻게 처리가 되는지 몰라도 객체 사용하는데 아무런 지장이 없다. 단지, 어떤 값을 입력해서 어떤 출력 값이 나오는지만 알면 된다. 이런 개념이 캡슐화이다.

더 쉽게 설명을 하자면 자동차를 운전하는 사람이 자동차의 엔진구동이나 바퀴가 돌아가는 원리 등과 같은 모든 동작 방법을 알지 못하지만, 핸들 (handle)을 이용해서 방향을 조정하고 엑셀과 브레이크를 이용해서 속도를 제어할 수 있다는 것은 알 수 있다. 이처럼 운전자는 구지 자동차의 내부 동작 원리는 몰라도 운전을 하는데 아무런 지장이 없는 것과 캡슐화가 이와 같은 이치이다.

캡슐화는 클래스 내부에 여러 속성과 여러 기능을 함께 묶고 외부로부터 임의로 접근이 되지 못하게 하는 보호 장치라 할수 있다. 자바에서의 캡슐화의 기초는 클래스(class)이며, 이 클래스에 숨겨야 하는 정보(private)와 공개해야 하는 정보(public) 구분하여 기술할 수 있다. 사용자는 공개가 된 정보에만 접근이 가능하기에 정보의 은닉(Information Hiding)을 할 수 있게 된다.

캡슐화에 대해서 다시 정리를 하자면 아래와 같다.

  • 객체의 속성, 메소드를 하나로 묶고, 실제 구현 내용을 외부에 숨기는 것을 말한다.
  • 외부 객체는 객체 내부의 구조를 알지 못하며 객체가 공개를 하여 제공하는 속성과 메소드만 사용할 수 있다.
  • 속성과 메소드를 캡슐화하여 보호하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는데 있다.
  • 자바는 캡슐화된 멤버를 공개할 것인지 숨길 것인지를 결정하기 위해 접근 제한자(Access Modifier) 키워드를 사용한다.

다형성(polymorphism)

다형성은 하나의 인터페이스를 서로 다른게 구현을 하여 상황에 따라 다른 의미로 동작될 수 있는 것을 의미한다. 앞으로 배우게 될 오버라이딩(overriding)이 여기에 해당된다.

객체 지향 프로그래밍에서는 추상(abstract) 클래스로 먼저 생성하고 추상 클래스를 상속받아 하위 클래스가 상위 추상 클래스에서 정의한 추상 메소드를 재정의 즉, 오버라이딩한다. 또 다른 하위 클래스가 이 추상 클래스를 다시 상속을 받아 추상 메소드를 재정의를 하게 되었다고 하자. 이때 각 두개의 하위 클래스에서는 각자 추상 메소드를 재정의 했기에 호출 다른 행위를 할 수 있게 되는 것이다.

예를 들면, 모형이라는 추상 클래스가 있고 이를 상속 받은 삼각형, 사각형, 원형이 하위 클래스가 있다고 하자. 도형에서는 draw()이라는 추상 메소드가 있고 이를 각 하위 클래스가 상속을 하여 재정의를 하게 되면 똑같은 draw()를 했어도 하위 클래스가 무엇인가에 따른 다른 형태의 그림이 나오게 될 것이다.

다형성

모형 클래스는 하위 클래스에서 모형을 그리는데 사용될 수 있는 추상 draw 메소드를 가지고 있다. 이 추상 메소드를 각 삼각형, 사각형, 원형 클래스에서 재정의하여 각각 상속 받은 클래스 따라 메소드를 호출시에 다른 형위를 하게 된다.

참조

http://hunit.tistory.com/151
http://searchstory.tistory.com/entry/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90
http://pacs.tistory.com/entry/C-%EC%83%81%EC%86%8D%EA%B3%BC-%EB%8B%A4%ED%98%95%EC%84%B1-Inheritance-Polymorphism

1.1.5 - Java 입문 | 클래스(Class)

자바의 핵심은 클래스이다. 클래스는 객체의 형식과 특성을 정의하기 때문에 전체 자바 언어에서의 논리적인 구성이며, 객체 지향 프로그램의 기초를 형성한다. 클래스는 인스턴스의 객체를 생성하여 프로그램이 작성된다.

보통 프로그래밍을 건물이 비교하는 경우가 많다. 여기서도 건축에 비교해서 설명을 하자면, 클래스(Class)는 건물 설계도 이고, 건출 설계도에 의해서 만들어진 건물을 객체(Object)라고 볼수 있다. 객체는 우리가 흔하게 보는 모든 사물과 생물체 등등 비유된다.

클래스 멤버

멤버(member)는 구성원이라는 뜻을 가지고 있다. 클래스도 구성원이 있는데 크게 클래스 속성에 해당하는 멤버 변수 부분과 클래스의 기능에 해당하는 생성자 및 메소드 정의 부분을 구성된다.

  • 클래스
    • 멤버 변수(Member variable)
    • 멤버 메소드(Member method)
    • 생성자(Constructor)
    • 일반 메소드

클래스 정의

클래스의 아래와 구성 정의 된다.

[public/private] [final/abstrct] class 클래스명 {
    // 멤버 변수
    // 생성자
    // 메소드
}

간단한 코드로 아래와 같다.

package com.devkuma.tutorial.clazz;

public class MyClass {

   // 멤버 변수
   int a;

   /**
    * 생성자
    */
   public MyClass() {
       // 생성자 내용
   }

   /**
    * 메소드
    */
   public void method() {
       // 메소드 내용
   }
}

클래스 선언

클래스를 선언하는 것은 새로운 타입을 생성하는 것과 같다.

클래스명은 대문자로 시작하는 것이 관례이다.

접근 제어 한정자(Access control modifiers)

  • public

    • public는 모든 클래스에서 객체 생성이 가능하다.
  • private

    • private는 다른 클래스에서 내부(inner)클래스에서만 생성이 가능하다.
  • 접근 한정자 사용 안 함

    • 접근 한정자를 사용하지 않고 선언된 클래스는 같은 패키지 내의 클래스에서만 객체 생성이 가능하다.

final 클래스

final이 붙은 클래스는 하위 클래스를 가지지 못한다. 즉, 상속이 되지 않는 클래스를 뜻한다. 현재의 클래스를 다른 클래스에서 상속받지 못하도록 하는 것은 정보의 보호 측면에서 유용하다.

상속 개념은 다른 장에서 다시 자세히 설명을 하도록 하겠다.

abstract 클래스

abstract이 붙은 클래스는 추상 클래스를 의미한다. 추상 클래스 자체로는 객체를 생성할 수 없는 클래스로써 하위 클래스에서 꼭 상속을 받아야 사용할 수 있는 클래스이다.

추상 클래스 개념은 다른 장에서 다시 자세히 설명을 하도록 하겠다.

객체의 생성 - new 키워드

객체를 생성한다는 것은 객체의 메모리를 동적을 할당하는 것을 의미한다.

클래스를 이용하여 객체를 생성하는 방법은 먼저 일반적인 데이터 타입 선언과 동일하게 클래스 타입의 변수를 선언한다.

클래스(Class)와 객체(Object)는 혼용해서 사용되기도 하지만 서로 다른 항목이다. 클래스는 객체의 형식을 정의하지만 객체 자체는 아니다. 객체는 클래스에 기반을 둔 구체적 엔터티이고 클래스의 인스턴스(instance)라고도 한다.

객체를 선언하려면 변수를 선언하는 것과 동일하다. 먼저 클래스명이 앞에 오고 객체 변수명이 온다.

클래스명 객체변수명;
MyClass myclass;

객체를 생성하기 위해서는 new 키워드를 사용한다. new 키워드 뒤에는 개체의 기반이 되는 클래스명이 온다.

객체변수명 = new 클래스명();
myclass = new MyClass();

객체 선언과 생성을 한번에 할 수도 있다.

클래스명 객체변수명 = new 클래스명();
MyClass myclass = new MyClass();

1.1.5.1 - Java 입문 | 클래스(Class) | 멤버 변수(Member Variable)

멤버 변수(member variable)는 메소드 밖에서 선언된 변수를 말한다. 메소드 안에 선언된 변수는 지역(local) 변수라고 한다.

  • 맴버 변수
    • 객체 변수
      • 객체 속성 변수
      • 객체 참조 변수
    • 클래스 변수
    • 종단(final) 변수

멤버 변수의 구성

멤버 변수 선언은 접근 한정자(public, private, protected)를 제외하고는 일반 변수의 구성과 동일하다.

[public/private/protected] [static/final] 변수 타입 변수명;

멤버 변수 선언

멤버 변수 선언은 접근 한정자를 제외하고는 일반 변수의 선언과 동일하다.

int a;
public int b;
private MyClass myclass;

선언과 초기화 및 생성을 같이 할 수 있는데 코드로 보면 아래와 같다.

public int b = 0;
private MyClass myclass = new MyClass();

멤버 변수명은 일반 변수명과 동일하게 소문자로 시작하는 것이 관례이다. 단, static final으로 선언된 변수인 경우 모두 대분자를 하는 경우도 많다.

private static final int MY_NUM;

이는 일종에 상수같은 역활을 한다고 생각하면 된다.

접근 제어 한정자(Access control modifiers)

  • public

    • public는 모든 클래스에서 접근 가능하다.
  • private

    • final은 한번 선언이 되면 변경이 불가능 하다.
  • protected

    • 같은 클래스나 해당 클래스에서 하위 파생 클래스에서만 접근 할 수 있다.
  • 접근 한정자 사용 안 함

    • 한정자를 사용하지 않고 선언된 멤버 변수는 같은 패키지 내의 클래스에서만 접근 가능하다.

객체변수

객체 변수는 객체가 가질 수 있는 특성을 표현한다. 객체 변수를 표현하는 값에 따라 객체 속성 변수와 객체 참조 변수로 2가지 형태로 구분된다.

객체 속성 변수

객체 속성 변수는 기본 자료형(int, float, char, byte 등)의 값을 가지는 변수이다.

public int b;

변수의 값이 복사되어 전달된다.

객체 참조 변수

객체 참조 변수는 생성된 객체를 지정하는 변수이다. 객체에 대한 참조(reference) 즉, 객체가 생성 저장된 주소(address)를 가진다.

private MyClass myclass;

객체에 대한 주수가 복사되어 전달되므로 결국 같은 객체를 가리키게 된다.

클래스 변수 - static 키워드

클래스 변수는 다른 말로 정적 변수라고도 한다. static을 사용하여 선어하고 전역변수(global variable)로써의 개념을 가진다.

  • static을 사용하여 선언. 전역변수(global variable)의 개념을 가짐

  • 그 클래스로부터 생성된 모든 객체들이 하나의 클래스 변수를 공유. 클래스변수는 하나의 클래스로부터 생성된 객체들 사이의 통신이나 객체들 사이의 공통된 속성을 표현하는데 사용가능

  • 일반변수와 달리 객체이름을 통해 접근 불가 → 클래스이름을 통해서 접근

final 변수

  • final를 사용하여 변수를 지정하면 사용하여 선언. 변할 수 없는 상수값을 가짐
  • final이 붙은 변수는 단 한번 초기화 할 수 있으며 그 이후에는 그 값을 변경할 수 없음
  • 변수이름 사용의 관례상 final변수는 대문자를 많이 사용

1.1.5.2 - Java 입문 | 클래스(Class) | 생성자(Constructor)

생성자는 클래스로부터 객체가 생성될 때 객체의 초기화 과정을 기술하는 특수한 메소드이며 객체가 생설될 때 무조건 수행된다.

[public/private/protected] 클래스 이름(매개 변수) {
    // 초기화 문장들
}

접근 제어 제한자(Access Control Modifiers)

  • Visible to the class only (private).
  • Visible to the world (public).

생성자 오버로딩(Overloading)

클래스는 여러 개의 생성자를 가질 수 있다.

1.1.5.3 - Java 입문 | 클래스(Class) | 메소드(Method)

메소드의 이름은 소문자로 시작하는 것이 관례이다.

[public/private/protected] [static/final/abstract/synchronized] 반환값유형 메소드이름([매개변수들]) {
    // 메소드 기능
}

접근 제어 한정자(Access control modifiers)

  • public

    • public는 모든 클래스에서 접근 호출 가능하다.
  • private

    • private는 오직 클래스 내부에서만 접근 호출 가능하다.
  • protected

    • protected는 같은 패키지 또는 하위 클래스에서만 접근 호출이 가능하다.
  • 접근 한정자 사용 안 함

    • 접근 한정자를 사용하지 않고 선언된 메소드는는 같은 패키지 내의 클래스에서만 접근 호출이 가능하다.

static 메소드

메소드에 static이 선언 되면 클래스 메소드를 의미한다. 프로그램이 시작되는 동안 한번만 메모리에 할당이 되기에 전역(Global) 메소드로서 사용된다. 사용은 따로 객체를 생성하지 않고 클래스에서 바로 메소드를 호출하여 사용한다.

final 메소드

final이 붙은 메소드는 상속이 되지 않는 메소드이다. 현재의 클래스를 다른 하위 클래스에서 상속받지 못하도록 하는 것을 뜻한다. 이는 정보의 보호 측면에서 유용하다.

상속 개념은 다른 장에서 다시 자세히 설명을 하도록 하겠다.

abstract 메소드

abstract가 붙은 메소드는 추상 메소드라고 한다. 상위 클래스에서 선언 부분만 있고 구현 부분이 없는 메소드를 미리 지정할때 사용한다. 이 추상 메소드가 하나라도 존대하면 이는 추상 클래스가 되어야 한다.

추상 클래스 개념은 다른 장에서 다시 자세히 설명을 하도록 하겠다.

sychronized

스레드의 동기화를 위한 메소드

반환값 유형(Return data-type)

메소드가 수행 후 반환될 값을 유형을 의미한다.

매개 변수(Parameter variable)

매개 변수는 메소드에 전달되는 값을 전달하는 변수이다.

지역 변수(Local variable)

  • 지역 변수는 메소드 안에서 선언된 변수이다. 해당 메서드에서만 사용 가능.
  • 메소드 실행시 메모리에 생성되어 메서드 종료시 자동 삭제.
  • 메소드 내 어디서든 호출 가능. 단, 선언한 다음 사용함.

메소드 오버로딩(Overloading)

1.1.5.4 - Java 입문 | 클래스(Class) | 상속(Inheritance)

상속은 계층적인 분류를 생성해 주는 객체지향 프로그램에서 중요한 개념 중에 하나이다. 여러 클래스들이 가져야 할 공통된 항목들만 모와 일반적인 클래스를 생성할 수 있는 모듈의 재사용성(reusing)과 코드의 간결성을 제공해준다.

상속되는 상위 클래스를 슈퍼 클래스(Super Class) 또는 부모 클래스(Parent Class)라 하고, 상속을 받는 하위 클래스를 (서브 클래스, Sub Class), 또는 자식 클래스(child class)라고 한다.

기존 상위 클래스로 부터 모든 속성과 메소드를 상속 받고, 더 필요한 속성과 메소드를 추가하여 새로운 클래스를 생성 할수 있다.

자바에서의 모든 클래스는 상위 클래스를 가진다. 자바 프로그램에서 사용할 수 있는 클래스중에 최상위 클래스는 java.lang.Object 클래스이다. 즉 자바 프로그램에서 모든 클래스는 Object 클래스의 하위 클래스가 되는 것이다. 명시적으로 상위 클래스를 명시하지 않으면 묵시적으로 Object를 상속하는 것으로 간주한다.

자바에서 상속을 받게 하려면 새로운 클래스 이름 뒤에 extends와 상속받고자 하는 클래스를 입력해주시면 된다.

[public/final/abstract class 클래스 이름 extends 상위클래스 이름 {
    // 맴버변수 선언
    // 생성자
    // 메소드 선언
}

예제 1)

package com.devkuma.tutorial.inheritance;

public class Parent {

    int a;
    int b;

    public int sum() {
        return a + b;
    }
}
package com.devkuma.tutorial.inheritance;

public class Child extends Parent {

    public int multiply() {
        return a * b;
    }
}
package com.devkuma.tutorial.inheritance;

public class Inheritance {

    public static void main(String[] args) {

        Child child = new Child();

        System.out.println("sum=" + child.sum());
        System.out.println("multiply=" + child.multiply());
    }
}

실행 결과:

sum=5
multiply=6

예제를 살펴보면 Parent 클래스를 Child 클래스가 상속을 받았고, Inheritance에서 Child를 상속 받아서 sum 메소드와 multiply 메소드의 결과를 표시해주었다. 이는 Child 클래스가 Parent의 맴버 변수 a, b와 메소드 sum을 상속 받았기에 sum 메소드를 호출 할 수 있었고, multiply메소드에서 Parent 클래스의 맴버 변수 a, b를 사용할 수 있었다.

super 예약어

super는 상속 관계에서 하위 클래스가 상위 클래스의 맴버에 접근하기 위해서 사용하는 예약어(keyword)이다.

  • 하위 클래스의 의해 가려진 상위 클래스의 맴버변수나 메소드에 접근할 때 사용한다.
  • 상위 클래스의 생성자를 호출하기 위해 사용한다.
super.객체변수
super.메소드(매개변수)

메소드 오버라이딩(Overriding)

상속관계에서 하위 클래스의 메소드가 상위 클래스의 메소드와 같은 이름으로 선언될 때, 서브 클래스의 메소드는 상위 클래스의 메소드를 오버라이딩(Overriding)한다고 한다.

오버라이딩은 상위 클래스의 메소드와 하위 클래스의 메소드가 메소드 이름은 물론 매개변수의 타입과 개수, 리턴 타입까지도 같아야 한다. 만약 메소드의 이름은 일치하는데 메소드의 매개변수의 개수나 타입이 다르다면 상속 관계이기 때문에 메소드의 오버로딩(Overloading)이 되는 것이다.

상속 관계의 클래스에서 메소드가 오버라이딩 되었다면 상위 클래스의 메소드가 하위 클래스에 의해 숨겨지게 된다. 하위 클래스의 객체에서 상위 클래스에서 오버라이딩된 메소드를 사용하려면 예약어 super를 이용한다.

1.1.5.5 - Java 입문 | 클래스(Class) | 추상 클래스(Abstract class)

추상 클래스(abstract class)

추상 클래스(abstract class)는 하위 클래스에서 구현될 추상적인 기능이 포함된 클래스이다. 즉, 추상 클래스는 ‘추상적인 형태’만 제안하고, 실제 구현은 하위 클래스로 미루기 위한 용도로 사용된다.

추상 메소드(abstract method)는 추상 클래스 내에 정의되는 선언 부분만 있고 구현 부분이 없는 메소드이다. 이 메소드는 추상 클래스를 상속한 하위 클래스에서 구현을 강제화하여 재정의(Overriding)을 하도록 한다. 만약 상속 받은 추상 클래스에서 추상 메소드를 재정의를 하지 않으면 그 하위 클래스도 추상 클래스가 되어야 한다. 단, 한개라도 추상 메소드를 갖은 클래스는 무조건 추상 클래스가 된다.

추상 클래스로만 객체 인스턴스를 생성할 수 없다. 즉, new키워드를 이용해서 객체를 선언할 수 없다는 것을 뜻한다. 불안적인 메소드인 추상 메소드를 포함하고 있기 때문이다. 따라서 추상 클래스를 상속 받은 하위 클래스에서 추상 메소드를 재정의 한 후에 하위 클래스의 객체로서 생성이 가능하다.

추상 클래스 및 메소드 정의

추상 클래스와 추상 메소드는 abstract 키워드를 사용한다. 추상 메소드는 구현 부분이 없기 때문에 메소르를 선언 후 바로 세미콜론(;)으로 마무리 한다.

abstract class 클래스명 {
    // 맴버변수(일반 클래스 맴버변수와 동일)
    // 메소드(일반 클래스 메소드와 동일)
   
    abstract 리턴타입 추상메소드명(매개타입 매개변수);
}

1.1.6 - Java 입문 | 인터페이스(Interface)

인터페이스(interface)는 상수와 메소드 선언들의 집합이다. 인터페이스를 이용하여 완벽하게 추상화를 시킬 수 있다. 인터페이스는 클래스와 유사 하지만, 인스턴스 변수가 부족하고 모든 메소드가 추상 메소드로 선언이 된다. 즉, 인터페이스는 추상 메소드와 상수만 가진다.

인터페이스는 존재하는 모든 멤버 변수는 상수로 사용되며, 모든 메소는 추상 메소드이다. 따라서 모든 변수에 final static을 붙이거나, 선언되는 메소드에 abstract 키워드를 붙일 필요가 없다. 그리고, 추상 클래스와 동일하게 인스턴스를 생성할 수 없다.

인터페이스 왜 사용하는가?

이런 인터페이스는 왜 사용하는 것인가? 자바에서는 클래스 상속은 하나의 상위 클래스만 가질 수 있다. 다시 말해서 자바는 다중 상속(multiple inheritance)를 지원하지 않는다. (그에 비해 C++ 같은 경우는 다중 상속이 가능 하다.) 그래서 완벽한 다중 상속은 아니지만 다중 상속과 비슷하게 구현을 할 수가 있다. 다른 이유는 기능의 통일을 위하여 인터페이스를 사용한다. 여러 클래스를 메소드를 통일하고 기능만 다르게 구현을 하고 싶은 경우가 있다. 이때 인터페이스를 사용하면 인터페이스를 데이터 타입 동일하게 선언하고 각 다른 클래스로 객체를 선언하여 같은 메소드를 호출하더라도 다른 기능이 동작하는 효과를 볼 수 있다.

인터페이스 정의

인터페이스는 클래스와 비슷하게 정의된다.

public interface 인터페이스이름 [extends 인터페이스이름, ...] {
   // 상수 선언
   // 메소드 선언
}

인터페이스의 모든 메소드의 접근 제한자는 public이기에 따로 선언하지 않아도 된다.

인터페이스 안에는 변수와 메소드만 선언되는데, 선언된 모든 변수는 final static 속성을 가지고 있다. 메소드는 기본적으로 public 접근 제한자가 포함되어 있다.

모든 인터페이스는 독립적으로 인스턴스를 생성할 수 없다. 반드시 파생 클래스에서 인터페이스의 메소드를 재정의 한 후에 파생 클래스의 인스턴스를 통해 사용해야 한다.

인터페이스 사용

클래스에서 인터페이스를 사용하기 위해서는 implements 키워드를 사용한다. 다음은 인터페이스를 포하는 클래스의 구문 형태이다. implements는 extends 키워드와 다르게 여러 개의 인터페이스를 나열하여 다중 상속을 구현할 수 있다. 클래스에 하나 이상의 인터페이스를 구현한다면 인터페이스들은 콤마(,)로 구분된다.

[public/final/abstact] class 클래스명 extends 상위클래스명 implements 인터페이스명[, 인터페이스명, ...] {
    // 멤버변수
    // 생성자
    // 메소드
    // 인터페이스에 선언된 모든 메소드 오버라이딩하여 반드이 선언
}

인터페이스 사용되었다면 사용된 인터페이스에 정의된 모든 메소드가 클래스 내에 반드시 오버라이딩되어 구현이 되어야 한다. 그리고 인터페이스의 메소드를 재정의 할 때는 반드시 public 접근 제한자로 지정해 줘야 한다.

인터페이스간에 상속

인터페이스도 클래스처럼 다른 인터페이스를 상속 받을 수 있다. 인터페이스들의 상속에도 extends 키워드를 사용한다.

public interface 인터페이스명 extends 인터페이스명[, 인터페이스명, ...] {
    // 상수 선언
    // 메소드 선언
}

인터페이스는 클래스와 동일하게 계층 구조를 가질 수 있다. 클래스는 하나의 상위 클래스만 상속이 가능 하지만 인터페이스는 여러개의 인터페이스을 상속 받을 수 있다.

1.1.7 - Java 입문 | 패키지(Package)

클래스들을 분류하고 사용의 편리성을 제공하기 위해서 관련 클래스와 인터페이스들을 하나로 묶을 수가 있는데 이를 **패키지(package)**이라고 한다. 즉, 패키지는 비슷한 종류의 클래스나 인터페이스들을 하나의 집단으로 묶어 놓은 것을 말한다.

  • 클래스 들을 하나로 묶어놓은 것이다.
  • 클래스 간의 이름 중복으로 발생하는 충돌을 막아준다.
  • 클래스를 기능 별로 분류할수 있어 필요한 클래스의 식별이 용이하다.

패키지는 이름 지정과 전급 제어성에 관현 기술이다. 패키지 내부에는 그 패키지의 외부에 있는 코드가 액세스 할 수 없는 클래스를 생성할 수도 있다. 또한 같은 패키지의 다른 맴버들에게 액세스 할 수 있는 클래스의 맴버를 구성 할 수도 있다.

패키지의 이름은 도메인 이름의 역순으로 구성하는 것이 일반적이다. 수 많은 개발자에 의해 자바 클래스들과 인터페이스들이 작성되고 이를 패키지로 분류되어 많은 사람들이 사용하고 있다. 패키지의 이름을 자유롭게 정하다 보면 같은 이름의 패키지가 중복 발생하여 혼란을 야기시킬수 있다. 그래서 보통 어느 회사 및 단체에서 만든 패키지는 com.devkuma.tutorial 같은 식으로 작성해서 패키지의 이름을 최대한 중복을 줄일 수 있다.

패키지 정의

패키지는 소스의 최상단에 위치하게 된다. 관례적으로 패키지명은 모두 소문자로 작성되며 경로 및 단위는 (.)으로 구분된다.

package 패키지명[.패키지명[.패키지명]...];

class 클래스명 { ...
package com.devkuma.tutorial;

class HelloWorld { ...

패키지가 있는 객체 선언 및 생성

패키지가 있는 객체를 사용하기 위해서는 패키지명을 기입을 해줘야 한다. 어느 패키지에 속해 있는 어떤 클래스인지를 모두 기입을 해야 명확해지기 때문이다.

그러기에 아래와 같은 형식으로 객체를 선언 및 생성이 가능하다.

패키지명[.패키지명[.패키지명]...].클래스명 객체변수명 = new 패키지명[.패키지명[.패키지명]...].클래스명();

아래 코드를 보면 HelloWorld 객체는 com.devkuma.tutorial에 속해 있기에 com.devkuma.tutorial.HelloWorld로 객체변수를 선언을 하고 com.devkuma.tutorial.HelloWorld 생성자를 호출하여 객체를 생성하였다.

com.devkuma.tutorial.HelloWorld helloWorld = new com.devkuma.tutorial.HelloWorld();

패키지가 있는 객체 선언 및 생성 - import 키워드

클래스를 사용할 때 매번 패키지명을 같이 사용하는 것은 상당히 불편하다. 자바에서는 이런한 문제점을 해결하기 위해서 import 키워드를 사용한다.

형식은 import로 패키지가 있는 클래스를 명시하고 클래스를 사용하는 곳에서는 단순히 클래스명만 기술하면 된다.

import 패키지명[.패키지명[.패키지명]...].클래스명;

클래스명 객체변수명 = new 클래스명();

아래 코드를 보면 HelloWorld 객체는 com.devkuma.tutorial에 속해 있기에 import를 com.devkuma.tutorial.HelloWorld로 객체변수를 선언하고 HelloWorld만 기술하고 생성자를 호출하여 객체를 생성하였다.

import com.devkuma.tutorial.HelloWorld;

HelloWorld helloWorld = new HelloWorld();

패키지 컴파일

준비중입니다.

1.1.8 - Java 입문 | 예외 처리(Exception)

예외 처리(Exception)

예외(Exception)란 프로그램 실행 중에 발생되는 비정상적인 상황을 말한다.

프로그램밍에서 에러(Error)는 2가지가 있다. 컴파일(Complie) 시 발생하는 에러와 런타임(Runtime) 시에 발생하는 에러이다. 컴파일 시에 발생하는 에러는 문법적인 오류이거나 문맥에 맞지 않은 코드를 작성하여 발생하는 에러이기 때문에 프로그램 자체가 실행조차 하지 못한 상태이다. 그에 비해 런타임시 발생하는 에러는 프로그램이 실행하다가 예기치 않는 상황에서 발생하는 에러이기에 오작동하거나 프로그램이 실행 종료되는 현상이 발생하게 된다.

예를 들면, 객체를 선언만 하고 생성하기 전에 참조를 한 경우, 0으로 정수를 나눴을 경우, 파일을 접근을 하려고 하는데 실제 파일이 없는 경우, 배열의 크기에 벗어난 인덱스을 접근한 경우 등 많은 예외 상황이 존재한다.

자바는 이런 런타임 시에 발생하는 에러을 예외(Exception)이라고 부르고, 예외 상황을 처리할수 있게 해준다.

예외 관련 클래스

  • java.lang.Object
    • java.lang.Throwable
      • java.lang.Error
      • java.lang.Exception
      • java.lang.RuntimeException
        • Unchecked Exception
      • Other Exception
        • Checked Exception

예외(Exception)도 결국은 클래스이다. 그러기에 모든 예외(Exception) 클래스는 Throwable 클래스를 상속받고 있으며, Throwable은 최상위 클래스 Object의 자식 클래스이다.

Throwable 상속받는 클래스는 Error와 Exception이 있다. Error는 주로 하드웨어 관련 예외 처리하는 클래스로써 시스템 레벨의 심각한 수준의 에러이다. 예를 들면, 메모리 문제(OutofMemoryError), 스택 오버플로우, 스레드가 인터럽트가 발생한 경우 등의 에러가 있다. 이런 종류의 예외는 코드에에서 처리하기 보다는 시스템에 변화를 주어 문제를 처리해야 하는 경우가 일반적이다.

Exception는 크게 RuntimeException을 상속 받은 예외와 상속을 받지 않은 예외로 나눌 수 있다. RuntimeException을 기준으로 나누는 이유는 예외 지정을하는가 안하는 하는가에 따른다. RuntimeException과 관련된 예외들은 따로 지정을 하지 않아도 크게 문제가 되지 않는데, 예외 지정에 소요되는 노력이 예외를 지정하여 얻는 효율보다 크기 때문이다. 이런 예외 처리는 JVM에게 맡기는 것이 더 효율적이다.

RuntimeException과 관련된 예외들도 Error와 같이 일반적으로 따로 지정하지 않는 경우가 많다. 일일히 소요되는 노력이 예외를 지정하여 얻는 효율보다 더 크기 때문이다.

기본적인 예외처리 - try~catch~finally

예외 처리를 메소드 내에서 직접 처리하기 위해서는 try, catch, finally 키워드를 이용한다.

try {
  // try 블럭
} catch (예외타입1 매개변수1) {
  // try 예외 처리 블럭1
} catch (예외타입2 매개변수2) {
  // try 예외 처리 블럭2
}
...
} catch (예외타입N 매개변수N) {
  // try 예외 처리 블럭N
} finally {
  // finally 블럭
}

try 블럭은 예외가 발생할 가능성이 있는 범위를 지정하는 블럭이다. 프로그램 실행 중에 예외가 발생할 수 있는 범위를 try 블럭으로 지정하면 되다. try 블록은 최소한 한개 이상의 catch 블럭이 있어야 한다.

catch 블럭은 try블럭에서 예외가 발생할 시에 실행이 되는 블럭이다. catch 블럭의 예외타입의 매개변수는 try 블럭에서 발생한 예외에 관한 정보를 담고 있다. try 블럭에서는 다양한 예외가 발생할 수 있으며, 이러한 예외의 종류에 따라 여러 개의 catch 블럭을 지정될 수 있다.

finally 블럭은 선택사항으로 예외가 발생과 상관없이 무조건 실행이 되는 블럭이다. 즉, try 블럭이 실행되면서 예외가 발생을 하던 예외가 하지 않던가에 무조건 실행이 되는 블럭이다.

보통 객체를 생성하고 close를 꼭 해야 하는 경우에 많이 사용이 된다. 예를 들면 파일을 열고 닫거나 데이터베이스를 열고 꼭 닫아야 하는 경우 등이 있다.

예제

package com.devkuma.tutorial.exception;

public class TryCatch {

    public static void main(String[] args) {

        try {
            int i = Integer.parseInt(args[0]);

            System.out.println("i=" + i);

        } catch (NumberFormatException e) {
            System.out.println("NumberFormatException");
            throw e;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("ArrayIndexOutOfBoundsException");
            throw e;
        }
    }
}

향상된 try~catch 에서 다중 예외

JDK1.7의 향상된 기능으로 try~catch 에서 다중 예외를 받아 처리할 수 있게 되었다. 서로 다른 상속 계층구조에 있는 예외끼리 가능하다. 예를 들어 NumberFormatException과 ArrayIndexOutOfBoundsException은 동시 처리불가.

JDK1.7 이전에는 아래와 같은 방법으로 예외를 처리했다면

...중략...
} catch (NumberFormatException e) {
    System.out.println("NumberFormatException");
    throw e;
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("ArrayIndexOutOfBoundsException");
    throw e;
}

JDK1.7 7 이후에는 아래와 같은 방법이 가능하게 되었다.

...중략...
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
    System.out.println("Exception");
    throw e;
}

예외처리 넘겨주기 - throws

예외를 처리 해주는 방식은 try~catch 절로 메소드 내에서 처리하는 방식과 발생된 예외를 호출한 메소드에게 넘겨주는 방식이 있다.

이런 예외처리 방식은 한 메소드에서 모두 처리할 때 유용하며 throws 키워드 이용하여 예외를 지정한다. 예외 처리가 많을 시에는 콤마(,)로 구분하여 나열한다.

public void method() throws 예외클래스[, 예외클래스...]

인위적인 예외 발생 - throw

JVM은 자바 프로그램 수행 중에 예외가 발생하면 자동으로 해당되는 예외 객체를 발생시킨 다음 예외를 처리하기 위해 catch 루틴을 찾는다.

예외 발생은 JVM에 의해 실샣중 발생할 수도 있지만, 사용자 프로그램에서 인위적으로 예외를 발생시킬 수 있다. 자바는 예외를 인위적으로 발생 시키기 위해 throw 키워드를 사용한다.

throw 예외객체;

또는

throw new 예외클래스(매개변수);

사용자 정의 예외 클래스

사용자는 자바에서 제공하는 예외 클래스 외에 추가적으로 사용자가 직접 클래스를 만들 수도 있다. 사용자 정의 예외 클래스를 만들려면 Exception 클래스를 상속 받아서 새로운 예외 클래스를 만들면 된다.

참고

http://www.nextree.co.kr/p3239/

1.1.9 - Java 입문 | 스레드(Thread)

스레드(Thread)란 프로세스 내에서 실행되는 흐름의 단위를 말한다. 한 프로세스는 하나 이상의 스레드를 가지고 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 하며 자바는 이러한 멀티 스레드를 지원한다.

프로세스는 여러 스레드를 포함한 관계라고 생각하면 된다.

스레드를 생성하기 위해서는 두가지 방법이 있다. 하나는 스레드 클래스를 직접 상속(extends) 받는 방법이고, 또 다른 하나는 Runable 인터페이스를 이용 즉, implement를 하는 방법이다.

메소드 이름 설명
void run() 스래드가 실행할 부분을 기술하는 메소드. 하위 클래스에서 재정의 되어야 한다.

1.1.9.1 - Java 입문 | 스레드(Thread) | Thread 클래스

Thread 클래스

Thread 클래스를 이용하는 방식은 java.lang.Thread 클래스를 상속받아서 run 메소드를 재정의(Overriding)한다. run 메소드는 스레드가 실행 동작하는 메소드로서 개발자는 이 메소드에 실행 동작할 코드를 기술하면 된다.

예제 1) Thread 1개 실행

다음은 Thread를 상속 받아서 클래스를 구현 생성하는 예제이다.

package com.devkuma.tutorial.thread;

public class MyThread extends Thread {

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("This is thread. i=" + i);
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

실행 결과:

This is thread. i=0
This is thread. i=1
This is thread. i=2
This is thread. i=3
This is thread. i=4
This is thread. i=5
This is thread. i=6
This is thread. i=7
This is thread. i=8
This is thread. i=9

단순히 1개의 스레드 실행되기 때문에 일반 반복문과 동일한 결과를 볼 수 있다.

예제 2) Thread 2개 실행

다음은 두개의 스레드를 갖는 경우를 살펴보겠다.

package com.devkuma.tutorial.thread;

public class MyThread2 extends Thread {

    public MyThread2(String name) {
        super(name);
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(super.getName() + " thread. i=" + i);
        }
    }

    public static void main(String[] args) {
        MyThread2 thread1 = new MyThread2("first");
        MyThread2 thread2 = new MyThread2("second");
        thread1.start();
        thread2.start();
    }
}

실행 결과:

first thread. i=0
first thread. i=1
second thread. i=0
second thread. i=1
second thread. i=2
first thread. i=2
second thread. i=3
second thread. i=4
second thread. i=5
second thread. i=6
second thread. i=7
second thread. i=8
second thread. i=9
first thread. i=3
first thread. i=4
first thread. i=5
first thread. i=6
first thread. i=7
first thread. i=8
first thread. i=9

예제1)과 비교하면 첫번째 스레드(first thread)와 두번째 스레드(second thread)가 불규칙하게 실행 되면서 각자의 결과를 표시하는 것을 볼 수 있다.

1.1.9.2 - Java 입문 | 스레드(Thread) | Runable 인터페이스

Runable 인터페이스

Runable 인터페이스를 이용한 방식은 java.lang.Runnable 인터페이스를 implements로 정의를 하고 run메소드(Overriding)를 재정의한다. 먼저 살펴본 Thread 상속 방식과 동일하게 run 메소드에는 스레드 실행 동작할 코드를 기술하면 된다.

예제 1) Thread 1개

package com.devkuma.tutorial.thread;

public class MyRunnable implements Runnable {

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("This is thread. i=" + i);
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

실행 결과는 아래와 같다.

This is thread. i=0
This is thread. i=1
This is thread. i=2
This is thread. i=3
This is thread. i=4
This is thread. i=5
This is thread. i=6
This is thread. i=7
This is thread. i=8
This is thread. i=9

결과는 Thread 클래스의 예제1)번과 동일하다. 이것도 스레드가 1개이기에 단순히 반복을 실행 시킨 결과가 같다. 스레드가 여러개를 실행 시키게 되면 동시에 여러 스레드가 실행이 되면서 각각 결과를 표시하게 될 것이다.

1.1.9.3 - Java 입문 | 스레드(Thread) | Thread 우선순의

Thread 우선순의

자바 스레드에스는 스레드에 우선순위를 부여하여 높은 우선 순위를 가진 스레드에게 실행할 우선권을 주어 실행시킬 수 있다.

Thread 클래스 내부에서는 우선 순위를 정하기 위해 다음과 같은 상수를 제공하고 있다.

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

최하 1부터 최상 10까지 부여가 가능하고 디폴트로는 5가 부여된다.

JVM에서는 스레드를 수행하고 끝나면 다음에는 실행 가능한 스레드 중에 한개를 골라 실행을 하게 되는데 우선 순위가 높을 수록 실행될 확률이 높아진다. 우선 순위가 같을 때에는 스레드 스케줄러의 기준에 의하여 실행될 스레드가 결정된다.

예제1) Thread 우선순의 설정

다음 예제는 Thread 2개에 우선 순위를 최상과 최하를 설정했다.

package com.devkuma.tutorial.thread;

public class MyThreadPriority extends Thread {

    public MyThreadPriority(String name) {
        super(name);
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(super.getName() + " thread. i=" + i);
        }
    }

    public static void main(String[] args) {
        MyThread2 thread1 = new MyThread2("first");
        MyThread2 thread2 = new MyThread2("second");

        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MIN_PRIORITY);

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

실행 결과:

second thread. i=0
second thread. i=1
first thread. i=0
second thread. i=2
first thread. i=1
second thread. i=3
second thread. i=4
second thread. i=5
second thread. i=6
second thread. i=7
second thread. i=8
second thread. i=9
first thread. i=2
first thread. i=3
first thread. i=4
first thread. i=5
first thread. i=6
first thread. i=7
first thread. i=8
first thread. i=9

결과에서 보이듯이 first보다는 second가 먼저 더 많이 실행 된 것을 확인할 수 있다.

1.1.9.4 - Java 입문 | 스레드(Thread) | Thread 우선순의

Object 클래스의 동기 제어

  • final void wait()
    • 다른 스레드는 notify() 또는 notifyAll()가 호출 때까지 스레드를 대기시킨다.
    • 동기화 영역에서 락을 풀고 Wait-Set영역 (공유객체별 존재)으로 이동시킨다.
  • final void wait(long timeout)
    • wait()의 타임아웃 버전이다.
  • final void notify()
    • 이 객체의 대기중의 thread를 1개 재개한다. 재개하는 재개 대상은 선택할 수 없다.
    • Wait-Set 영역에 있는 쓰레드를 깨워서 실행할 수 있도록 한다.
    • notify()는 랜덤이라 내가 원하는 애 말고 다른 애들을 깨울 수도 있다.
  • final void notifyAll()
    • 이 객체의 대기중의 모든 thread를 재개한다.
    • notifyAll()은 Wait-Set에 있는 전부를 깨운다.
  • wait, notify, notifyAll 모두 동기화 영역에서만 실행 가능하다.
  • Object 클래스에서 제공하는 메서드이다
package com.devkuma.basic.thread.wait;

public class ThreadA extends Thread {
    // 해당 쓰레드가 실행되면 자기 자신의 모니터링 락을 획득
    // 5번 반복하면서 0.5초씩 쉬면서 total에 값을 누적
    // 그후에 notify()메소드를 호출하여 wiat하고 있는 쓰레드를 깨운다.
    int total;

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(i + "를 더합니다.");
                total += i;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            notify();
        }
    }

    public static void main(String[] args) {
        // 쓰레드 A를 생성후에 start한다.
        // 해당 쓰레드가 실행되면, 해당 쓰레드는 run 메소드 안에서 자신의 모니터링 락을 획득
        ThreadA a = new ThreadA();
        a.start();

        // b에 대하여 동기화 블럭을 설정
        // 만약 main 쓰레드가 아래의 블록을 위의 Thread보다 먼저 실행되었다면 wait를 하게 되면서 모니터링 락을 놓고 대기
        synchronized (a) {
            try {
                // b.wait()메소드를 호출.
                // 메인쓰레드는 정지
                // ThreadA가 5번 값을 더한 후 notify를 호출하게 되면 wait에서 깨어남
                System.out.println("b가 완료될때까지 기다립니다.");
                a.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 깨어난 후 결과를 출력
            System.out.println("Total is: " + a.total);
        }
    }
}

1.1.10 - Java 입문 | java.lang 패키지

java.lang 패키지는 모든 프로그램에서 자동으로 import되며, 모든 자바 프로그램의 기초인 클래스와 인터페이스에 포함하고 있다. 자바 프로그램에서 가장 많이 사용되는 패키지라고 할 수 있다.

1.1.10.1 - Java 입문 | java.lang 패키지 | String 클래스

String 클래스는 문자열을 생성하고 조작할 수 있도록 다양한 생성자와 메소드를 있다. (C언어와 비교해서는 메모리 관련 연사자를 사용하지 않기 때문에 문자열의 생성 및 조작이 편하다.)

String 생성자

생성자 설명
String() 디폴트 생성자. 문자열이 없는 객체를 생성한다.
String(String original) 다른 String 객체를 이용하여 문자열 생성한다.
String(char value[]) 문자 배열을 이용한 객체 생성한다.
String(char value[], int offset, int count) 문자 배열의 내용 중 첫 번째 인덱스부터 두번째 인덱스의 수 만큼의 내용을 객체로 생성한다.
String(int[] codePoints, int offset, int count) (J2SE5) 유니코드 포인트를 포함하는 배열을 이용하여 객체 생성한다.
String(byte bytes[]) 바이트 배열을 이용해서 객체 생성한다.
String(byte bytes[], String charsetName) 바이트 배열의 내용을 지정된 Charset명을 이용하여 객체로 생성한다.
String(byte bytes[], Charset charset) 바이트 배열의 내용을 지정된 Charset을 이용하여 객체로 생성한다.
String(StringBuffer buffer) StringBuffer 객체를 이용해서 객체 생성
String(StringBuilder builder) (J2SE5) StringBuilder 객체를 이용해서 객체 생성한다.

문자열 생성 - 생성자를 사용하지 않는다.

자바에서는 String 객체는 다른 객체와 다르게 생성자를 이용하지 않고도 문자열 객체를 생성할 수 있다.

다음은 new 사용하지 않고 리터럴을 사용하여 문자열을 생성하는 예제이다.

예제

package com.devkuma.tutorial.string;

public class StringJava {

    public static void main(String[] args) {
        String str = "This is java.";
        System.out.println(str);
    }
}

결과는 아래와 같다.

This is java.

자바의 클래스 중에 유일하게 String 클래스만 이런 특수성을 띄고 있다. String 클래스가 이런다고 해서 다른 클래스도 이럴 수 있다는 생각은 버려야 한다. 이와 비슷한 클래스도 없고 만들 수도 없다.

문자열 생성 - 왜 생성자를 사용하지 않는가?

위에서 String 객체는 생성을 할 시에 따로 new를 사용하지 않고도 리터럴 대입만으로 생성이 된다고 했다. 물론 new를 사용하여 리터럴을 파라미터로 넣어서도 가능 하다. 하지만 일반적으로 리터럴을 사용한 문자열을 생성하지 않는다. 왜? 그런 것인가? 코드를 줄이는 것이 좋아서도 있지만 내부적으로도 다른 이유도 있다.

아래 예제는 new 사용하여 리터럴을 대입하여 문자열 객체를 생성하였다.

예제

package com.devkuma.tutorial.string;

public class StringJava2 {

    public static void main(String[] args) {
        String str = new String("This is java.");
        System.out.println(str);
    }
}

먼저 제시한 예제와 동일하게 결과는 같다.

This is java.

결과는 같아도 내부적으로 메모리 주소는 2개가 생길 수 있다. 먼저 “This is Java.“로 생기고 new String(“This is java”)로 생길 수 있다는 것이다. 한번의 동작만으로 객체를 생성 할수 있는데 구지 2번의 작업을 하여 2개의 메모리 주소를 만든 필요는 없는 것이다. 물론 가비지콜렉터에서 메모리적으로 처리를 해주긴 하겠지만, 이것도 미세한 성능 관련이기에 될 수 있으면 피하도록 하자.

문자열 결합 - 메소드를 사용하지 않는다.

String 객체는 메소드 사용하지 않아도 문자열 연자자 “+” 연산자를 이용하여 결합시킬 수 있다.

package com.devkuma.tutorial.string;

public class StringConcat2 {

    public static void main(String[] args) {
        String age = "30";
        System.out.println("He is " + age + " years old");
    }
}

문자열이 아닌 다른 데이터 타입과도 결합이 가능하다.

package com.devkuma.tutorial.string;

public class StringConcat3 {

    public static void main(String[] args) {
        int age = 30;
        System.out.println("He is " + age + " years old");
    }
}

기존의 문자열이 변경될 때마다 변경된 문자열의 새로운 String 객체가 생성된다. 원래의 문자열은 변경되지 않은 채로 그대로 남아 있다. 이는 가비지콜렉터에 의해서 관리될 것이다.

String 주요 메소드

메소드 설명

문자열 길이

문자열 길이는 문자열에 포함되어 있는 문자들의 수를 말한다. 문자열의 길이는 length() 메소드를 이용하여 알아 낼수 있다.

준비중...

문자열에서 문자열 추출

  • char charAt(int index)
  • void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)

문자열 비교

  • boolean equals(Object anObject)
  • boolean startsWith(String prefix, int toffset)
  • boolean startsWith(String prefix)
  • boolean endsWith(String suffix)
  • int compareTo(String anotherString)

문자열 검색

문자열에 포한된 서브 문자열을 검색하기 위해서 사용한다.

  • int indexOf(int ch)
  • int indexOf(int ch, int fromIndex)
  • int lastIndexOf(int ch)
  • int lastIndexOf(int ch, int fromIndex)
  • int indexOf(String str)
  • int indexOf(String str, int fromIndex)
  • int lastIndexOf(String str)
  • int lastIndexOf(String str, int fromIndex)

문자열 변환

  • String substring(int beginIndex)
  • String substring(int beginIndex, int endIndex)
  • String concat(String str)
  • String replace(char oldChar, char newChar)
  • String replaceAll(String regex, String replacement)
  • String replace(CharSequence target, CharSequence replacement)
  • String trim()
  • String toLowerCase()
  • String toUpperCase()

형변환

  • String valueOf(Object obj)
  • String valueOf(char data[])
  • String valueOf(char data[], int offset, int count)
  • String valueOf(boolean b)
  • String valueOf(char c)
  • String valueOf(int i)
  • String valueOf(long l)
  • String valueOf(float f)
  • String valueOf(double d)

1.1.10.2 - Java 입문 | java.lang 패키지 | StringBuffer 클래스

StringBuffer 생성자

생성자 설명
StringBuffer() 디폴트 생성자. 초기 16개의 문자를 저장할 수 있는 객체를 생성한다.
StringBuffer(int capacity) 용량 크기(capacity)로 지정된 문자라를 저장할 수 있는 객체를 생성한다.
StringBuffer(String str) str로 지정된 문자열과 추가로 16개의 문자를 더 저장할 수 있는 객체를 생성한다.

StringBuffer 주요 메소드

메소드 설명
StringBuffer append(boolean b) b를 현재의 문자열에 끝에 첨부한다.
StringBuffer append(char c) c를 현재의 문자열에 끝에 첨부한다.
StringBuffer append(double d) d를 현재의 문자열에 끝에 첨부한다.
StringBuffer append(float f) f를 현재의 문자열에 끝에 첨부한다.
StringBuffer append(int i) i를 현재의 문자열에 끝에 첨부한다.
StringBuffer append(long l) l를 현재의 문자열에 끝에 첨부한다.
StringBuffer append(Object obj) obj를 현재의 문자열에 끝에 첨부한다.
StringBuffer append(String str) str를 현재의 문자열에 끝에 첨부한다.
int capacity() 현재의 문자열 버퍼의 용량 크기를 반환한다.
char charAt(in index) index에 해당하는 문자를 반환한다.
StringBuffer delete(int start, int end) start부터 end까지의 범위 만큼 삭제한다.
StringBuffer deleteCharAt(int index) index의 문자를 삭제한다.
StringBuffer insert(int offset, boolean b) offset번째에 전에 b를 삽입한다.
StringBuffer insert(int offset, char c) offset번째에 전에 c를 삽입한다.
StringBuffer insert(int offset, int i) offset번째에 전에 i를 삽입한다.
StringBuffer insert(int offset, long l) offset번째에 전에 l를 삽입한다.
StringBuffer insert(int offset, Object obj) offset번째에 전에 obj를 삽입한다.
StringBuffer insert(int offset, String str) offset번째에 전에 str를 삽입한다.
int length() 문자 버퍼에 있는 문자의 개수를 반환한다.
StringBuffer replace(int start, int end, String str) start부터 end까지의 범위 만큼 문자열 str로 치환한다.
StringBuffer reverse() 문자열을 역순으로 반환한다.
void setCharAt(int index, char ch) index의 ch 문자의 값으로 설정한다.
void setLength(int newLength) 버퍼의 크기를 newLength 크기로 설정한다.
String toString() 현재의 문자열을 String 객체로 반환한다.

문자열 버퍼의 크기

  • int capacity()
  • int length()
  • void setLength(int newLength)

capacity 메소드는 StringBuffer 객체에 할당된 총 용량 크기를 구하고자 할 때 사용한다.
length 메소드는 StringBuffer 객체의 현재 길이를 알고자 할 때 사용한다.

예제

package com.devkuma.tutorial.stringbuffer;

public class StringBufferCapacity {

    public static void main(String[] args) {

        StringBuffer sb = new StringBuffer("abcdef");

        System.out.println("capacity : " + sb.capacity());
        System.out.println("length : " + sb.length());

        sb.setLength(2);
        System.out.println("setLength : " + sb);
    }
}

실행한 결과는 아래와 같다.

capacity : 22
length : 6
setLength : ab

위 결과에서 length 메소드는 “abcdef"의 길이 만큼 6이 표시가 되었지만, capacity 메소드는 “abcdef"의 길이 6에 추가로 16개의 문자를 더 저장할 수 있는 공간을 자동으로 추가하기에 용량 크기 22가 표시되었다. 그리고 setLength 메소드는 버퍼 크기를 2로 새로 변경하여 “ab"가 표시가 되었다.

문자열 버퍼의 문자

StringBuffer 객체의 문자(char)를 조회 및 교체가 가능하다.

  • char charAt(in index)
  • void setCharAt(int index, char ch)

예제

package com.devkuma.tutorial.stringbuffer;

public class StringBufferChar {

    public static void main(String[] args) {

        StringBuffer sb = new StringBuffer("abcde");
        System.out.println("charAt(3) : " + sb.charAt(3));

        sb.setCharAt(4, 'f');
        System.out.println("setChatAt(4, 'f') : " + sb);
    }
}

실행 겨로가는 아래와 같다.

charAt(3) : d
setChatAt(4, 'f') : abcdf

첫번째 charAt를 이용해서 3번 인덱스 값 ’d’를 표시하고, 두번째 setChatAt를 이용해서 4번 인덱스 값을 ‘f’로 변경하여 최종 abcdf가 표시 되었다.

문자열 버퍼에 데이터 첨부

StringBuffer 객체의 끝에 다른 데이터 타입의 데이터를 문자열로 변환하여 첨부하기 위해서는 append 메소드를 이용한다.

  • StringBuffer append(boolean b)
  • StringBuffer append(char c)
  • StringBuffer append(double d)
  • StringBuffer append(float f)
  • StringBuffer append(int i)
  • StringBuffer append(long l)
  • StringBuffer append(Object obj)
  • StringBuffer append(String str)

예제

package com.devkuma.tutorial.stringbuffer;

public class StringBufferAppend {

    public static void main(String[] args) {

        boolean b = true;
        char c = 'a';
        double d = 1.1;
        float f = 3.14f;
        int i = 1;
        long l = 2;
        String str = "A";

        StringBuffer sb = new StringBuffer();
        sb.append(b).append(", ");
        sb.append(c).append(", ");
        sb.append(d).append(", ");
        sb.append(f).append(", ");
        sb.append(i).append(", ");
        sb.append(l).append(", ");
        sb.append(str);

        System.out.println(sb);
    }
}

실행 결과는 아래와 같다.

true, a, 1.1, 3.14, 1, 2, A

append 메소드는 String 클래스의 객체의 concat과 비슷한 용도의 메소드이지만, String 클래스에서와는 달리 새로운 객체의 새 문자열이 할당되는 것이 아니라, 호출 문자열에 변경되어 저장된다. 주어진 문자열에 다른 문자열을 첨부하는 작업이 많은 경우니는 StringBuffer 객체를 사용하는 것이 성능 향상에 큰 도움이 된다.

다음 예제는 String 객체와 StringBuffer 객체를 생성한 후에 각각 문자열을 3000번씩 첨부를 할 것이다. 이때 각 객체의 속도를 측정해 보자. 측정을 할 시에는 현재 시간을 알 수 있는 메소드System.currentTimeMillis()를 이용할 것이다.

예제

package com.devkuma.tutorial.stringbuffer;

public class StringBufferAppend2 {

    public static void main(String[] args) {

        String str = "A";
        long currentStr = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            str += "A";
        }
        System.out.println("String :" + (System.currentTimeMillis() - currentStr) + "ms");

        StringBuffer sb = new StringBuffer("A");
        long currentSb = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            sb.append("A");
        }
        System.out.println("StringBuffer :" + (System.currentTimeMillis() - currentSb) + "ms");

    }
}

실행 결과는 아래와 같다.

String :68ms
StringBuffer :1ms

위 결과를 보면 String객체보다 StringBuffer객체가 월등히 속도가 빠른 것을 확인 할 수가 있다.

문자열 버퍼에 데이터 삽입

StringBuffer 객체의 끝에 다른 데이터 타입의 데이터를 문자열로 삽입하기 위해서는 insert 메소드를 이용한다.

  • StringBuffer insert(int offset, boolean b)
  • StringBuffer insert(int offset, char c)
  • StringBuffer insert(int offset, int i)
  • StringBuffer insert(int offset, long l)
  • StringBuffer insert(int offset, Object obj)
  • StringBuffer insert(int offset, String str)
package com.devkuma.tutorial.stringbuffer;

public class StringBufferInsert {

    public static void main(String[] args) {

        boolean b = true;
        char c = 'a';
        double d = 1.1;
        float f = 3.14f;
        int i = 1;
        long l = 2;
        String str = "A";

        StringBuffer sb = new StringBuffer("b=, c=, d=, f=, i=, l=, str=");
        sb.insert(2, b);
        sb.insert(10, c);
        sb.insert(15, d);
        sb.insert(22, f);
        sb.insert(30, i);
        sb.insert(35, l);
        sb.insert(42, str);

        System.out.println(sb);
    }
}

실행 결과는 아래와 같다.

b=true, c=a, d=1.1, f=3.14, i=1, l=2, str=A

문자열 버퍼에 문자열 역순

StringBuffer 객체의 문자열을 역순으로 반환한다.

  • StringBuffer reverse()

예제

package com.devkuma.tutorial.stringbuffer;

public class StringBufferReverse {

    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("abcdef");
        System.out.println(sb.reverse());
    }
}

실행 결과는 아래와 같다.

fedcba

문자열 버퍼에 문자열 삭제

StringBuffer 객체의 delete 메소드는 주어진 범위 만큼 삭제한다.
StringBuffer 객체의 deleteCharAt 메소드는 입력한 인덱스의 문자를 삭제한다.

  • StringBuffer delete(int start, int end)
  • StringBuffer deleteCharAt(int index)
package com.devkuma.tutorial.stringbuffer;

public class StringBufferDelete {

    public static void main(String[] args) {

        StringBuffer sb = new StringBuffer("abcxxxdef");
        sb.delete(4, 6);
        System.out.println(sb);
        sb.deleteCharAt(3);
        System.out.println(sb);
    }
}

실행 결과는 아래와 같다.

abcxdef
abcdef

문자열 버퍼에 문자열 치환

StringBuffer 객체의 문자열에서 주어진 범위 만큼 입력 받은 문자열 치환한다.

  • StringBuffer replace(int start, int end, String str)
package com.devkuma.tutorial.stringbuffer;

public class StringReplace {

    public static void main(String[] args) {

        StringBuffer sb = new StringBuffer("abcxxghi");
        sb.replace(3, 5, "def");
        System.out.println(sb);
    }
}

실행 결과는 아래와 같다.

abcdefghi

1.1.10.3 - Java 입문 | java.lang 패키지 | Object 클래스

Object 클래스는 자바 프로그램에서 최상의 클래스이다.

Object 주요 메소드

메소드 설명
Object clone() 객체를 복제하기 위해 사용하는 메소드이다.
boolean eqauls (Object object) 두 개의 객체가 같은 지를 비교하여 같으면 true, 아니면 false를 반환한다.
void finalize() 쓰레기 수집(garbage collection) 기능이 수행되지 전에 호출되며 객체 점유하고 있던 자원드를 해제하는데 사용되는 메소드 이다.
Class getClass() 객체의 클래스명을 Class형 객체로 반환한다.
int hashCode() 호출한 객체와 연관된 hash 코드를 얻는다.
String toString() 현재 객체의 문자열 표현을 반환한다.
void notify() 대기중인 스레드 중에 하나의 스레드를 다시 시작시킨다.
void notifyAll() 대기중인 모든 스레드를 다시 시작시킨다.
void wait() 실행을 중지하고 대기 상태로 간다.
void wait(long timeout) 지정된 시간이 경과 할 때까지, 현재의 thread를 대기시킨다.
void wait(long timeout, int nanos) 다른 스레드가 이 객체에 대해 notify() 또는 notifyAll()를 호출하거나 다른 스레드가 현재 스레드를 인터럽트하거나 특정 시간동안 실시간이 경과 할 때까지 현재 스레드를 대기시킨다.

equal() 메소드는 두 개의 객체가 가지고 있는 데이터를 비교하여 같으면 true 다르면 false를 반환하는 메소드 이다.

toString() 메소드는 객체가 가지고 있는 문자열 표현을 반환하기 위해서 사용한다. 대부분의 클래스에서 toString() 메소드를 오버라이딩하여 가지고 있는 객체의 정보를 문자열로 제공한다.

Object 예제

예제

아래 예제는 객체의 toString() 메소드를 재정의한다.

package com.devkuma.tutorial.java.lang;

public class User {

    private Integer id;
    private String name;
    private String email;

    public User(Integer id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public String toString() {
        return "id:" + id + ", name=" + name + ", email=" + email;
    }

    public static void main(String[] args) {
        User user = new User(1, "devkuma", "devkuma.com@gmail.com");
        System.out.println(user.toString());
    }
}

실행 결과는 아래와 같다

id:1, name=devkuma, email=devkuma.com@gmail.com

1.1.10.4 - Java 입문 | java.lang 패키지 | Wrapper 클래스

자바에서는 프로그램의 성능 때문에 int와 char와 같은 기본 데이터 타입을 사용한다. 그러나 이러한 데이터는 객체가 아니기 때문에 값에 의해 메소드에 전달되고 참조에 의해 직접 전달될 수 없다. 또한, 두 메소드가 int의 값은 인스턴스를 참조할 수 있는 방법이 없다. Wrapper 클래스는 기본형 데이터 타입을 객체처럼 생성할 수 있도록 도와주는 클래스이다.

Charactor 클래스

Boolean 클래스

Byte 클래스

Short 클래스

Integer 클래스

Long 클래스

Float 클래스

Double 클래스

1.1.10.5 - Java 입문 | java.lang 패키지 | Class 클래스

Class 클래스는 객체나 인터페이스의 런타임 상태를 저장한다. Class 타입의 객체는 클래스가 로드 될 때 자동으로 생성된다. Class 객체를 명시적으로 선언할 수 없으며, 일반적으로 Object 클래스의 getClass() 메소드를 이용하여 Class 객체를 얻는다.

Class 생성자

메소드 설명
static Class forName(String className) 매개변수로 className을 받아서 클래스를 로딩한 후에, 그 클래스에 해당하는 Class 객체를 반환한다.
String getName() 클래스명을 반환한다.
Class<? super T> getSuperclass() 상위 클래스를 반환한다.
T newInstance() Class가 포함하고 있는 클래스의 인스턴스를 생성한다.

Class 예제

예제 1)

package com.devkuma.tutorial.java.lang;

class Parent {
    int a = 2;
    int b = 3;
}

class Child extends Parent {
    int c = 4;
}

public class ClassClass {

    public static void main(String[] args) {
        Parent p = new Parent();
        Child c = new Child();

        Class<?> pc = p.getClass();
        Class<?> cc = c.getClass();

        System.out.println("pc ClassName:" + pc.getName());
        System.out.println("pc Super ClassName:" + pc.getSuperclass().getName());

        System.out.println("cc ClassName:" + cc.getName());
        System.out.println("cc Super ClassName:" + cc.getSuperclass().getName());
    }
}

실행 결과는 아래와 같다.

pc ClassName:com.devkuma.tutorial.javalang.Parent
pc Super ClassName:java.lang.Object
cc ClassName:com.devkuma.tutorial.javalang.Child
cc Super ClassName:com.devkuma.tutorial.javalang.Parent

예제를 보면 클래스명과 상위 클래스명으로 화면에 표시해 주고 있는걸 확인 할 수 있다.

예제 2)

package com.devkuma.tutorial.javalang;

class Calc {

    int a = 2;
    int b = 3;

    public int sum() {
        return a + b;
    }
}

public class ClassClass2 {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> clazz = Class.forName("com.devkuma.tutorial.javalang.Calc");

        Calc calc = (Calc) clazz.newInstance();

        System.out.println("sum : " + calc.sum());

    }
}

실행 결과는 아래와 같다.

sum : 5

여태까지 객체를 생성하기 위해서 new을 사용하여 객체를 생성했다. 위에 예제는 new를 이용하지 않고 Class.forName과 newInstance() 메소들 이용해서 객체를 생성하였다. 이는 클래스가 로딩을 할 때 로딩이 되는 것이 아니라 Class.forName()을 실행 시켰을 때 로딩이 된다. 이를 런타임 동적 로딩이라고 한다. 이 방식을 보안적이나 품질적으로 문제가 있을 수 있으니 jdbc로딩과 같은 특별한 경우를 제외하고는 이렇게 클래스를 생성을 하는 일을 없어야 한다.

1.1.10.6 - Java 입문 | java.lang 패키지 | Math 클래스

Math 클래스는 기하학 삼각법, 부동 수수점과 같은 수학과 관련된 메소드를 제공한다. Math 클래스는 final 클래스 이기 때문에 상속이 불가능하다. Math 클래스는 생성자는 private 접근 제한자로 선언되어 있어 인스턴스를 생성할 수 없고, 맴버 변수와 메소드는 모두 static으로 선언되어 있다.

Math 클래스 변수

변수 설명
E 자연 로그 상수(e: 2.718…) 값
PI 원주율(3.14…) 값

Math 주요 메소드

메소드 설명
static int abs(int a) a의 절대값을 int로 반환한다.
static long abs(long a) a의 절대값을 long로 반환한다.
static float abs(float a) a의 절대값을 float로 반환한다.
static double abs(double a) a의 절대값을 double로 반환한다.
static double ceil(double a) a의 소수점 이하를 올림을 한 값을 반환한다.
static double floor(double a) a의 소수점 이하를 버림을 한 값을 반환한다.
static int max(int a, int b) a, b 값 중 큰 값을 int로 반환한다.
static long max(long a, long b) a, b 값 중 큰 값을 long로 반환한다.
static float max(float a, float b) a, b 값 중 큰 값을 float로 반환한다.
static double max(double a, double b) a, b 값 중 큰 값을 double로 반환한다.
static int min(int a, int b) a, b 값 중 작은 값을 int로 반환한다.
static long min(long a, long b) a, b 값 중 작은 값을 long로 반환한다.
static float min(float a, float b) a, b 값 중 작은 값을 float로 반환한다.
static double min(double a, double b) a, b 값 중 작은 값을 double로 반환한다.
static double random() 임의의 값을 반환한다.
static double round(double a) a의 소수점 이하를 반올림을 한 값을 반환한다.
static double rint(double a) a에 가장 가까운 정수를 반환한다.
static double sin(double a) a의 싸인 값을 반환한다.
static double cos(double a) a의 코싸인 값을 반환한다.
static double tan(double a) a의 탄젠트 값을 반환한다.
static double sqrt(double a) a의 제곱근 값을 반환한다.

Math 예제

다음 프로그램은 Math 클래스의 메소드를 사용한 예이다.

package com.devkuma.tutorial.javalang;

public class Mathematics {

    public static void main(String[] args) {
        System.out.println("abs(10.6) :" + Math.abs(10.6));
        System.out.println("ceil(10.3) : " + Math.ceil(10.3));
        System.out.println("floor(10.3) : " + Math.floor(10.3));
        System.out.println("max(3, 5) : " + Math.max(3, 5));
        System.out.println("min(3, 5) : " + Math.min(3, 5));
        System.out.println("random() : " + Math.random());
        System.out.println("round(10.5) : " + Math.round(10.5));
        System.out.println("rint(10.3) : " + Math.rint(10.3));
    }
}

실행한 결과는 아래와 같다.

abs(10.6) :10.6
ceil(10.3) : 11.0
floor(10.3) : 10.0
max(3, 5) : 5
min(3, 5) : 3
random() : 0.3319476750319902
round(10.5) : 11
rint(10.3) : 10.0

1.1.10.7 - Java 입문 | java.lang 패키지 | System 클래스

System 클래스는 실행시간 환경과 관련된 속성과 메소드를 가지고 있다. System 클래스에서 제공되는 out과 in을 이용한 표준 입력과 출력, err을 이용한 에러 출력에 관한 클래스 변수를 가지고 있고 객체를 복사해 주는 메소드와 프로그램을 작성할 때는 사용할 수 있는 유용한 메소드를 제공한다.

System 변수

변수 설명
final static InputStream in 표준 입력에 사용된다.
final static PrintStream out 표준 출력에 사용된다. print(), println() 매개변수를 받아 모니터에 출력을 수행한다.
final static PrintStream err 에러 출력에 사용된다. print(), println() 매개변수를 받아 모니터에 에러를 출력을 수행한다.

System 주요 메소드

메소드 설명
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 배열을 복사한다. src와 dest은 복사될 배열의 변수이고, srcPos와 destPos는 복사가 시작될 위치이고, length는 복사될 배열의 크기이다.
static long currentTimeMillis() 1970년 1월 1일 자정부터 현재까지의 시간을 밀리초로 반환한다.
static void exit(int status) 현재 수행적인 응용 프로그래을 종료시킨다. status로 종료 상태를 반환한다. 보통 마이너스(-)는 비정상 종료를 뜻한다.
static void gc() 가비지 콜렉션(garbage collection)을 실행시킨다.
static String getPropertys() 시스템 환경 변수 목록을 얻어온다.
static String getProperty(String key) 시스템 환경 변수를 얻어온다.
static String getProperty(String key, String def) 프로그램 환경 변수를 설정한다.

System 예제

예제) System.currentTimeMillis()

다음은 System.currentTimeMillis() 메소드에 대한 예제이다.

package com.devkuma.tutorial.java.lang;

public class SystemCurrentTimeMillis {

    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();
        System.out.println("start time : " + System.currentTimeMillis());

        Thread.sleep(1000); // 1초간 중지

        long end = System.currentTimeMillis();
        System.out.println("end time : " + System.currentTimeMillis());

        System.out.println("duration : " + (start - end));
    }
}

실행 결과는 아래와 같다.

start time : 1497970891769
end time : 1497970892771
duration : -1002

예제) System.arraycopy()

다음은 System.arraycopy() 메소드에 대한 예제이다.

package com.devkuma.tutorial.java.lang;

public class SystemArraycopy {

    public static void main(String[] args) {
        char[] src = { 'a', 'b', 'c', 'd', 'e' };
        char[] dest = { 'x', 'x', 'x', 'x', 'x' };

        System.out.println("src=" + new String(src));
        System.out.println("dest=" + new String(dest));
        System.arraycopy(src, 0, dest, 0, src.length);
        System.out.println();
        System.out.println("src=" + new String(src));
        System.out.println("dest=" + new String(dest));

    }
}

실행 결과는 아래와 같다.

src=abcde
dest=xxxxx

src=abcde
dest=abcde

예제) System.getPropertys(), System.getProperty()

다음은 시스템 환경 변수에 관한 System.getPropertys(), System.getProperty() 메소드에 대한 예제이다.

package com.devkuma.tutorial.java.lang;

import java.util.Enumeration;
import java.util.Properties;

public class SystemProperties {

    public static void main(String[] args) {

        // Java Runtime Environment 버젼
        System.out.println("java.version=" + System.getProperty("java.version"));
        // Java Runtime Environment 버젼
        System.out.println("java.vendor=" + System.getProperty("java.vendor"));
        // Java Runtime Environment 벤더
        System.out.println("java.vendor.url=" + System.getProperty("java.vendor.url"));
        // Java Runtime Environment 설치 디렉토리
        System.out.println("java.home=" + System.getProperty("java.home"));

        // Java 가상 머신 사양
        System.out.println("java.vm.specification.version=" + System.getProperty("java.vm.specification.version"));
        // Java 가상 머신 사양 벤더
        System.out.println("java.vm.specification.vendor=" + System.getProperty("java.vm.specification.vendor"));
        // Java 가상 머신 사양명
        System.out.println("java.vm.specification.name=" + System.getProperty("java.vm.specification.name"));
        // Java 가상 머신 구현 버젼
        System.out.println("java.vm.version=" + System.getProperty("java.vm.version")); //
        // Java 가상 머신 구현 벤더
        System.out.println("java.vm.vendor=" + System.getProperty("java.vm.vendor")); //
        // Java 가상 머신 구현명
        System.out.println("java.vm.name=" + System.getProperty("java.vm.name"));

        // Java Runtime Environment 의 사양 버젼
        System.out.println("java.specification.version=" + System.getProperty("java.specification.version"));
        // Java Runtime Environment 의 사양의 벤더
        System.out.println("java.specification.vendor=" + System.getProperty("java.specification.vendor"));
        // Java Runtime Environment 의 사양명
        System.out.println("java.specification.name=" + System.getProperty("java.specification.name"));
        // Java 클래스의 형식의 버젼 번호
        System.out.println("java.class.version=" + System.getProperty("java.class.version"));
        // Java 클래스 패스
        System.out.println("java.class.path=" + System.getProperty("java.class.path"));
        // 라이브러리의 로드시에 검색하는 패스의 리스트
        System.out.println("java.library.path=" + System.getProperty("java.library.path"));
        // 디폴트 일시파일의 패스
        System.out.println("java.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
        // 사용하는 JIT 컴파일러의 이름
        System.out.println("java.compiler=" + System.getProperty("java.compiler"));
        // 확장 디렉토리의 패스
        System.out.println("java.ext.dirs=" + System.getProperty("java.ext.dirs"));

        // operating system 명
        System.out.println("os.name=" + System.getProperty("os.name"));
        // operating system 아키텍쳐
        System.out.println("os.arch=" + System.getProperty("os.arch"));
        // operating system 버젼
        System.out.println("os.version=" + System.getProperty("os.version"));

        // 파일 단락 문자 (UNIX 에서는 "/")
        System.out.println("file.separator=" + System.getProperty("file.separator"));
        // 경로 단락 문자 (UNIX 에서는 ":")
        System.out.println("path.separator=" + System.getProperty("path.separator"));
        // 행 단락 문자 (UNIX 에서는 "\n")
        System.out.println("line.separator=" + System.getProperty("line.separator"));

        // 사용자명
        System.out.println("user.name=" + System.getProperty("user.name"));
        // 사용자 홈 디렉토리
        System.out.println("user.home=" + System.getProperty("user.home"));
        // 사용자 현재의 작업 디렉토리
        System.out.println("user.dir=" + System.getProperty("user.dir"));
        System.out.println();

        // 전체 정보 조회
        Properties prop = System.getProperties();
        Enumeration<?> enu = prop.keys();
        while (enu.hasMoreElements()) {
            String key = (String) enu.nextElement();
            String value = (String) prop.get(key);
            System.out.println(key + "=" + value);
        }
    }
}

실행 결과는 아래와 같다.

java.version=1.8.0_161
java.vendor=Oracle Corporation
java.vendor.url=http://java.oracle.com/
java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre
java.vm.specification.version=1.8
java.vm.specification.vendor=Oracle Corporation
java.vm.specification.name=Java Virtual Machine Specification
java.vm.version=25.161-b12
java.vm.vendor=Oracle Corporation
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.specification.version=1.8
java.specification.vendor=Oracle Corporation
java.specification.name=Java Platform API Specification
java.class.version=52.0
java.class.path=/Library/Java/JavaVirtualMachines...(생략)...
java.library.path=/Users/kimkc/Library/Java/Extensions:...(생략)...
java.io.tmpdir=/var/folders/f2/s8f8bt7j2vdf4538y4lydp480000gn/T/
java.compiler=null
java.ext.dirs=/Users/kimkc/Library/Java/Extensions:...(생략)...
os.name=Mac OS X
os.arch=x86_64
os.version=10.13.6
file.separator=/
path.separator=:
line.separator=

user.name=kimkc
user.home=/Users/kimkc
user.dir=/Users/kimkc/dev/workspace/java-tutorial

java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path=/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib
java.vm.version=25.161-b12
gopherProxySet=false
java.vm.vendor=Oracle Corporation
java.vendor.url=http://java.oracle.com/
path.separator=:
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
file.encoding.pkg=sun.io
user.country=KR
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=unknown
java.vm.specification.name=Java Virtual Machine Specification
user.dir=/Users/kimkc/dev/workspace/java-tutorial
java.runtime.version=1.8.0_161-b12
java.awt.graphicsenv=sun.awt.CGraphicsEnvironment
java.endorsed.dirs=/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/endorsed
os.arch=x86_64
java.io.tmpdir=/var/folders/f2/s8f8bt7j2vdf4538y4lydp480000gn/T/
line.separator=

java.vm.specification.vendor=Oracle Corporation
os.name=Mac OS X
sun.jnu.encoding=UTF-8
java.library.path=/Users/kimkc/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
java.specification.name=Java Platform API Specification
java.class.version=52.0
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
os.version=10.13.6
http.nonProxyHosts=local|*.local|169.254/16|*.169.254/16|localhost|*.localhost
user.home=/Users/kimkc
user.timezone=
java.awt.printerjob=sun.lwawt.macosx.CPrinterJob
file.encoding=UTF-8
java.specification.version=1.8
java.class.path=/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/...(생략)...
user.name=kimkc
java.vm.specification.version=1.8
sun.java.command=com.devkuma.tutorial.java.lang.SystemProperties
java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre
sun.arch.data.model=64
user.language=ko
java.specification.vendor=Oracle Corporation
user.language.format=en
awt.toolkit=sun.lwawt.macosx.LWCToolkit
java.vm.info=mixed mode
java.version=1.8.0_161
java.ext.dirs=/Users/kimkc/Library/Java/Extensions:...(생략)...
java.vendor=Oracle Corporation
file.separator=/
java.vendor.url.bug=http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding=UnicodeBig
sun.cpu.endian=little
socksNonProxyHosts=local|*.local|169.254/16|*.169.254/16|localhost|*.localhost
ftp.nonProxyHosts=local|*.local|169.254/16|*.169.254/16|localhost|*.localhost
sun.cpu.isalist=

1.1.10.8 - Java 입문 | java.lang 패키지 | Runtime 클래스

Runtime 클래스는 실행 환경을 객체화하기 위해서 사용한다. Runtime 객체를 이용해 현 운영체제 시스템간의 상호 작용이 가능하다. JVM(자바 머신)이 작동하는 시스템과의 인터페이스를 제공하며 자바 클래스가 아닌 운영체제 기반의 프로그램을 실행시키거나 운영체제에 대한 정보를 제공한다.

Runtime 주요 메소드

메소드 설명
Process exec(String command) 명령(command)을 실행시키고, 실행시킨 프로세스의 래퍼런스를 반환한다.
static Runtime getRuntime() Runtime 객체의 레퍼런스를 반환한다.
void exit(int status) 상태 값(status) 반환하면서 JVM을 종료시킨다.
long freeMemory() JVM이 사용 가능한 메모리 양(bytes)을 반환한다.
long totalMemory() JVM이 사용하고 있는 전체 메모리를 반환한다.
long maxMemory() JVM이 사용할 수 있는 최대 메모리 양을 반환한다.

Runtime 예제

예제 1)

package com.devkuma.tutorial.java.lang;

public class RuntimeClass {

    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();

        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long usedMemory = totalMemory - freeMemory;

        System.out.println("Total Memory : " + totalMemory);
        System.out.println("Free Memory : " + freeMemory);
        System.out.println("Used Memory : " + usedMemory);
    }
}

실행 결과는 아래와 같다.

Total Memory : 128974848
Free Memory : 126929960
Used Memory : 2044888

예제 2)

아래 예제에서는 해당 OS의 코드만 작성하도록 한다. 모두 실행시키면 에러가 발생한다.

package com.devkuma.tutorial.javalang;

import java.io.IOException;

public class RuntimeClass1 {

    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();

        // Mac인 경우 아래 명령어를 실행한다.
        try {
            runtime.exec("open /Applications/Contacts.app");
        } catch (IOException e) {
            System.err.println("Error executing Contacts.");
        }

        // linux인 경우 아래 명령어를 실행한다.
        try {
            runtime.exec("gedit");
        } catch (IOException e) {
            System.err.println("Error executing gedit.");
        }

        // windows인 경우 아래 명령어를 실행한다.
        try {
            runtime.exec("calc.exe");
        } catch (IOException e) {
            System.err.println("Error executing calc.");
        }
    }
}

실행을 하면 각 OS에 맞는 어플리케이션 프로그램이 실행이 될 것이다. 실행 결과는 생략한다.

참조

http://itmore.tistory.com/entry/Runtime-%ED%81%B4%EB%9E%98%EC%8A%A4

1.1.11 - Java 입문 | java.util 패키지

자바는 편리한 기능들을 모와 java.util 패키지를 기본 제공하고 있다.

1.1.11.1 - Java 입문 | java.util 패키지 | Random 클래스

Random 클래스는 난수 발생시킬 수 있는 객체이다. 다양한 데이터 타입형(double, float, int, long 등) 발생 시키거나 일정한 범위에 존재하는 난수를 발생되게 할 수도 있다. 매번 실행할 때 마다 같은 수의 난수가 발생되게 할 수도 있다.

Random 클래스 생성자

생성자 설명
Random() 디폴트 생성자. 현재 시간을 초기 값으로 하는 난수 발생기 객체를 생성한다.
Random(long seed) long형의 seed값을 메게변수로 받아 난수 발생기 객체를 생성한다.

Random 객체를 생성할 때 seed 값을 지정하게 되면, 시작 순서 값을 지정한다는 것을 뜻한다. seed 값을 지정한 후에 난수를 발생시키게 되면 매번 동일한 난수가 발생하게 된다. 매번 실행 할 때마다 다른 난수를 얻을 수 있는 가장 좋은 방법은 seed 값으로 현재 시간을 사용하는 것이다. 시간은 계속 변하기 때문에 같은 난수가 발생하지 않는다.

Random 메소드

메소드 설명
void nextBytes(byte[] bytes) buffer를 난수로 채운다.
boolean nextBoolean() boolean 형태의 난수를 반환한다.
double nextDouble() double 형태의 난수를 반환한다.
float nextFloat() float 형태의 난수를 반환한다.
int nextInt() int 형태의 난수를 반환한다.
long nextLong() long 형태의 난수를 반환한다.
int nextInt(int bound) 한계 값(bound)을 받아서 난수를 반환한다.
double nextGaussian() 가우스의 형의 난수를 double 형태의 난수를 반환한다.
void setSeed(long seed) 난수 발생기의 seed 값을 새로 설정한다.

Random 예제

예제1)

seed에 따른 난수를 구하는 예이다.

package com.devkuma.tutorial.java.util;

import java.util.Random;

public class RandomClass {

    public static void main(String[] args) {
        Random r1 = new Random();
        Random r2 = new Random(10);

        for (int i = 0; i < 10; i++) {
            System.out.println("r1: " + r1.nextInt() + ", r2: " + r2.nextInt());
        }
    }
}

실행 결과는 아래와 같다.

r1: 2011236985, r2: -772289311, r3: -1157793070
r1: 1578624710, r2: -604617698, r3: 1913984760
r1: -262276649, r2: 612254308, r3: 1107254586
r1: 1706079238, r2: -1664224378, r3: 1773446580
r1: -583579103, r2: -809586602, r3: 254270492
r1: 1201978631, r2: 1898792462, r3: -1408064384
r1: 1897434446, r2: 1738277915, r3: 1048475594
r1: 1847101385, r2: -1454243203, r3: 1581279777
r1: 44315199, r2: -482107837, r3: -778209333
r1: 1881949787, r2: -221323214, r3: 1532292428

r1은 seed값을 넣지 않았고 r2은 현재 시간을 seed로 설정했다. r3는 고정적인 10으로 설정했다. 프로그램을 여러번 실행 시켜보면 r1과 r2은 주기적으로 변경이 되지만 r3은 변하지 않은 것을 확인 할 수 있다.

예제2)

1부터 100사이의 난수를 구하는 예이다.

package com.devkuma.tutorial.javautil;

import java.util.Random;

public class RandomClass2 {

    public static void main(String[] args) {
        Random r = new Random();
        for (int i = 0; i < 10; i++) {
            System.out.println(r.nextInt(100));
        }
    }
}

실행 결과는 아래와 같다.

42
38
33
14
21
41
11
47
96
7

nextInt 메소드에 한계 값(bound)을 100으로 넣고 실행을 하게 되면 100을 넘지 않는 수를 난수로 발생 시키는 것을 확인 할수 있다.

1.1.11.2 - Java 입문 | java.util 패키지 | Date 클래스

Date 클래스는 날짜, 시간에 관한 정보를 표현한다.

Date 생성자

생성자 설명
Date() 디폴트 생성자. 현재의 날짜와 시간을 가진 객체를 생성한다.
Date(long date) GMT 시간에서 date 밀리초(ms)가 경과한 시간을 가진 객체를 생성한다.
사용 중지 선언(Deprecated)된 생성자 설명은 생략한다.

Date 주요 메소드

메소드 설명
long getTime() 1970년 이후로 현재까지의 시간을 밀리초(ms)로 반환한다.
void setTime(long time) 현재 객의 날짜와 시간을 1970년 이후의 밀리초로 설정한다.
boolean before(Date when) 입력 받은 when 날짜 및 시간 객체도 보다 이전이면 true, 그렇지 않으년 false를 반납한다.
boolean after(Date when) 입력 받은 when 날짜 및 시간 객체도 보다 이후이면 true, 그렇지 않으년 false를 반납한다.
사용 중지 선언(Deprecated)된 메소드 설명은 생략한다.

Date 클래스의 많은 생성자와 메소드들이 사용 중지 선언(Deprecated)이 되었다. 대부분은 Calendar 클래스를 사용하도록 권고하고 있다.

Date 예제

예제

package com.devkuma.tutorial.java.util;

import java.util.Date;

public class DateClass {

    public static void main(String[] args) {
        Date now = new Date();
        System.out.println("Now : " + now);

        Date old = new Date(0);
        System.out.println("old : " + old);

        System.out.println("before : " + old.before(now));
        System.out.println("after : " + old.after(now));
    }
}

실행 결과는 아래와 같다.

Now : Thu Jun 22 21:51:17 KST 2017
old : Thu Jan 01 09:00:00 KST 1970
before : true
after : false

1.1.11.3 - Java 입문 | java.util 패키지 | StringTokenizer 클래스

StringTokenizer 클래스는 String 객체의 문자열을 특정한 구분 문자(Delimiter)로 여러 개의 토큰(Token) 문자열로 나눠준다. 구분 문자로는 특수 문자뿐 아니라 공백(space), 한 문자부터 여러 문자의 문자열로도 사용 가능하다. StringTokenizer 클래스는 Enumeration 인터페이스로 이용하였기 때문에, 나눠진 문자열은 열거형태로 존재한다.

StringTokenizer 생성자

생성자 설명
StringTokenizer(String str) 디폴트 생성자.
StringTokenizer(String str, delim) 구분 문자(delim)를 인자로 받는 생성자.
StringTokenizer(String str, String delim, boolean returnDelims) 구분 문자(delim)와 구분문자의 반환 여부를 인자로 받는 생성자.

StringTokenizer 메소드

메소드 설명
int countTokens 나눠진 토큰의 수를 반환한다.
boolean hasMoreElements() 반환할 다음 요소가 있으면 true, 없으면 false를 반환한다. hasMoreTokens()와 동일하다.
boolean hasMoreTokens() 반환할 다음 토큰이 있으면 true, 없으면 false를 반환한다.
Object hasMoreElements() 나눠진 토큰의 수를 반환한다. Object를 반환하지만 실제는 String이다.
String hasMoreTokens() 나눠진 토큰의 수를 반환한다.
String nextToken() 다음 토큰을 반환한다. 이전 토큰은 제거한다.
String nextToken(String delim) 구분 문자(delim)를 변경 후 변경 다음 토큰을 반환한다.

StringTokenizer 예제

예제 1)

package com.devkuma.tutorial.java.util;

import java.util.StringTokenizer;

public class StringTokenizerClass {

    public static void main(String[] args) {

        String str = "java,c,c++,c#,scala,xml,javascript";
        StringTokenizer st = new StringTokenizer(str, ",");

        while (st.hasMoreTokens()) {
            String lang = st.nextToken();
            System.out.println(lang);
        }
    }
}

실행 결과는 아래와 같다.

java
c
c++
c#
scala
xml
javascript

예제 2)

package com.devkuma.tutorial.javautil;

import java.util.StringTokenizer;

public class StringTokenizerClass2 {

    public static void main(String[] args) {

        String str = "java,c,c++,c#,scala,xml,javascript";
        StringTokenizer st = new StringTokenizer(str, ",", true);

        while (st.hasMoreTokens()) {
            String lang = st.nextToken();
            System.out.println(lang);
        }
    }
}

실행 결과는 아래와 같다.

java
,
c
,
c++
,
c#
,
scala
,
xml
,
javascript

1.1.11.4 - Java 입문 | java.util 패키지 | Pattern 클래스

java.util.rege.Pattern는 정규 표현식에 입력 문자열을 검증하는 기능의 matches() 메소드를 제공한다.

Pattern 예제

아래 예제는 입력 문자열에 숫자가 포함되어 있는지에 대한 검증하는 로직이다.

package com.devkuma.basic.java.util.regex;

import java.util.regex.Pattern;

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

        String regex = "[0-9]*$"; //숫자만

        String input1 = "0123456789"; //입력 문자열
        System.out.println(Pattern.matches(regex, input1));

        String input2 = "abcde56789"; //입력 문자열
        System.out.println(Pattern.matches(regex, input2));

        String input3 = "abcdefghij"; //입력 문자열
        System.out.println(Pattern.matches(regex, input3));
    }
}

실행 결과는 아래와 같다.

true
false
false

1.1.11.5 - Java 입문 | java.util 패키지 | Matcher 클래스

java.util.rege.Matcher 클래스는 입력 문자열의 패턴을 해석하고 주어진 패턴과 일치하는지 판별할 때 주로 사용된다.

Matcher 메소드

메소드 설명
matches() 패턴이 전체 문자열과 일치할 경우 true를 반환한다.
boolean find() 패턴이 일치하는 경우 true를 반환하고, 그 위치로 이동한다. 여러개가 매칭되는 경우 반복 실행 가능하다.
boolean find(int start) start 위치 이후부터 매칭 검색을 수행한다.
String group() 매칭된 부분을 반환한다.
String group(int group) 매칭된 부분 중 group번 그룹핑 매칭 부분을 반환한다.
int groupCount() 패턴내 그룹핑한(괄호 지정) 전체 갯수를 반환한다.
int start() 매칭되는 문자열 시작 위치를 반환한다.
int start(int group) 지정된 그룹이 매칭되는 시작 위치를 반환한다.
int end() 매칭되는 문자열 끝 다음 문자 위치를 반환한다.
ind end(int group) 지정되 그룹이 매칭되는 끝 다음 문자 위치를 반환한다.

Matcher 예제

아래 코드는 소문자로 이루어진 단어만 받아와 표시하는 로직이다. 예를 들어 I, it.와 같이 대문자와 특수문자가 포함된 경우는 제외하고 표시한다.

package com.devkuma.basic.macher;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherTutorial {
    
    public static void main(String[] args) {
        String regEx = "[a-z]*[a-z]";
        String text = "I never dreamed about success, I worked for it.";

        Pattern pattern = Pattern.compile(regEx);
        Matcher matcher = pattern.matcher(text);

        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }
}

결과는 아래와 같다.

never
dreamed
about
success
worked
for
it

1.1.12 - Java 입문 | java.util 패키지 - 콜렉션

Collections Framework란 배열과 유사하게 프로그램에서 객체를 집합으로 관리할 수 있는 방법을 말한다. Collection은 초기 자바 버전에서 빠져 있었으나, J2SE 1.2에서 추가되었다.

Collection 메소드

Collection 인터페이스는 모든 Collections Framework에서 사용할 수 있는 공통적인 메소드가 포한되어 있다.

메소드 설명
boolean add(E e) 마지막 번째에 객체(e)를 추가한다.
boolean addAll(Collection<? extends E> c) 콜렉션 객체를 추가한다.
void clear() Collection 객체를 비운다.
boolean contains(Object o) Collection에 입력한 객체(o)가 존재하는지에 대한 여부를 반환한다.
boolean equals(Object o) 입력한 Collection와 동일한지에 대한 여부를 반환한다.
boolean isEmpty() Collection 객체가 비웠는지 여부를 반환한다.
boolean remove(Object o) 입력한 객체(o)를 삭제하여 삭제 성공 여부를 반환한다.
int size() Collection 객체의 사이즈 크기를 반환한다.
Object[] toArray() Collection 에서 갖고 있는 객체를 배열로 반환한다.
<T> T[] toArray(T array[]) Collection 에서 갖고 있는 객체를 배열로 입력한 타입으로 반환한다.

1.1.12.1 - Java 입문 | java.util 패키지 - 콜렉션 | List 인터페이스 및 관련 클래스

List 인터페이스

List 인터페이스는 순차적인 객체 집합들을 다루기 위한 Java Collection Framework의 인터페이스이다. Java Collection Framework의 최상위 인터페이스인 Collection 인터페이스를 상속받았고, 중복된 데이터를 포함할 수 있다. List 인터페이스를 구현한 하위 클래스로는 ArrayList, LinkedList, Vector, Stack 등이 있다.

  • List
    • ArrayList
    • LinkedList
    • Vector
      • Stack

List 메소드

메소드 설명
void add(int index, E element) index에 객체(element)를 추가한다.
boolean addAll(int index, Collection<? extends E> c) index에 콜렉션 객체를 추가한다.
E get(int index) List의 index번째 객체를 반환한다.
int indexOf(Object o) List의 첫 번째 요소부터 순방향으로 검색하여 객체 o의 index(위치)를 반환한다. List에 객체가 없다면 - 1을 반환한다.
int lastIndexOf(Object o) List의 마지막 요소부터 역방향으로 검색하여 객체 o의 index(위치)를 반환한다. List에 객체가 없다면 - 1을 반환한다.
ListIterator<E> listIterator() List의 객체에 접근 할 수 있는 ListIterator를 반환한다.
ListIterator<E> listIterator(int index) index부터 시작한 List의 객체에 접근 할 수 있는 ListIterator를 반환한다.
E remove(int index) index번째 객체를 삭제하고 삭제된 객체를 반환한다.
Object set(int index, E element) index번째에 객체를 저장하고 index번째에 있었던 이전 객체를 반환한다.
List<E> subList(int fromIndex, int toIndex) fromIndex(포함)번째부터 toIndex(제외)번째까지 List 객체를 반환한다.

ListIterator 인터페이스

ListIterator는 Iterator를 상속한 인터페이스이다. Collection에서 제공하는 Iterator인터페이스는 Collection을 한방향으로 탐색하면서 객체에 대한 정보를 얻는다. ListIterator는 List에서 제공하는 인터페이스로 List에 포함된 모든 객체를 양방향으로 탐색하면서 객체를 꺼낼 수 있는 방법을 제공한다. ListIterator 이용하면 양방향으로 움직여가면서 수정이라던가, Iterator의 현재 위치를 알아낼 수 있다. List를 사용하는 경우에는 Collection에서 제공하는 Iterator는 물론이고 보다 확장된 기능을 갖고 있는 ListIterator를 사용할 수 있다.

ListIterator 메소드

메소드 설명
boolean hasNext() Iterator를 다음 방향으로 객체가 존재하는지 여부를 반환한다.
boolean hasPrevious() Iterator를 이전 방향으로 객체가 존재하는지 여부를 반환한다.
Object next() Iteration에서 다음 객체를 반환한다.
Object previous() Iteration에서 이전 객체를 반환한다.
void remove() next() 또는 previous()로 반환된 마지막 객체를 제거한다.
void set(Object o) next() 또는 previous()로 반환된 마지막 객체를 입력 받은 객체(o)로 변경한다.
int nextIndex() Iteration에서 다음 객체의 인덱스를 반환한다.
int previousIndex() Iteration에서 이전 객체의 인덱스를 반환한다.
void add(Object o) List에 객체(o)를 추가한다.

ArrayList 클래스

ArrayList는 크기가 가변 가능한 Array로서 구현된 클래스이다. 크기가 가변되는 점을 제외하고는 일반 Array와 동일하게 사용되어 질 수 있고 성능도 마찬가지이다. random access가 가능하므로 get()/set()의 성능이 좋은 반면 add()/remove()의 성능은 나쁘다.

ArrayList는 동적으로 크기가 변경이 되는데, 설정된 초기 크기보다 많은 데이터가 저장될 때는 자동으로 크기가 늘어나며, 저장된 데이터가 삭제될 경우에는 자동으로 크기가 줄어든다.

ArrayList 생성자

생성자 설명
ArrayList() 디폴트 생성자. 기본 크기의 객체를 생성한다.
ArrayList(Collection<? extends E> c) Collection을 입력받아 객체를 생성한다.
ArrayList(int initialCapacity) 용량을 지정하여 객체를 생성한다.

ArrayList 예제

package com.devkuma.tutorial.java.util;

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

public class ArrayListClass {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("E");
        list.add("F");

        System.out.println("list :" + list);
        System.out.println("size : " + list.size());
        System.out.println("get : " + list.get(0));

        list.set(2, "CC");
        System.out.println("set : " + list);

        list.add(1, "AA");
        System.out.println("add : " + list);

        list.remove(2);
        list.remove("F");
        System.out.println("remove : " + list);

        for (String str : list) {
            System.out.println("for : " + str);
        }
    }
}

실행 결과는 아래와 같다.

list :[A, B, C, D, E, F]
size : 6
get : A
set : [A, B, CC, D, E, F]
add : [A, AA, B, CC, D, E, F]
remove : [A, AA, CC, D, E]
for : A
for : AA
for : CC
for : D
for : E

LinkedList 클래스

LinkedList는 doubly linked list로서 구현된 클래스이다. List이므로 get()/set()에서의 성능이 좋지 않다.

Vector 클래스(Legacy)

java.util.Collection의 Legacy 클래스인 Vector는 ArrayList와 동일하다. 차이라면 Vector는 synchronized라는 것이다. 이로인해 ArrayList에 비해 성능적으로 불리하다. 많은 java 프로그래머들은 Vector대신 ArrayList를 사용하며, synchronized가 필요한 경우조차도 명시적으로 ArrayList를 synchronized하는 방법을 선호한다. 즉, Vector 클래스는 잘 사용되지 않는 클래스이다.

참고

http://hochulshin.com/java-collection-list/
http://scarlett.tistory.com/entry/자바공부-5List-인터페이스와-ListIterator-인터페이스

1.1.12.2 - Java 입문 | java.util 패키지 - 콜렉션 | Set 인터페이스 및 관련 클래스

Set 인터페이스

Set 인터페이스는 단일 데이터를 저장하는 Collection으로써, 중복된 데이터 저장을 허용하지 않고 순서가 보장되지 않는다. Set에서 새로 선언된 메소드는 없다. List 인터페이스를 구현한 하위 클래스로는 TreeSet, HashSet이 있다.

  • Set
    • SortedSet
    • TreeSet
  • HashSet

SortedSet 인터페이스

Set 인터페이스를 상속 받은 인터페이스로는 SortedSet 인터페이스가 있는데, SortedSet 인터페이스가 있는데, SortedSet 인터페이스에서는 오름 차순으로 정렬하여 데이터를 보관할 수 있다.

SortedSet 메소드

메소드 설명
Comparator<? super E> comparator() SortedSet의 설정된 Comparator(비교기)를 반환한다. 설정한 null이 반환되고 있다면 설정된 Comparator가 반환된다.
E first() SortedSet의 첫번째 객체를 반환한다.
SortedSet<E> headSet(E toElement) toElement 객체보다 작은 객체의 부분으로 구성한 SortedSet 객체를 반환한다.
E last() SortedSet의 마지막 객체를 반환한다.
SortedSet<E> subSet(E fromElement, E toElement) toElement(포함)보다 크고, fromElement(제외)보다 작은 객체로 구성한 SortedSet 객체를 반환하다.
SortedSet<E> tailSet(E fromElement) fromElement(포함)보다 큰 객체로 구성한 SortedSet을 반환한다.

HashSet 클래스

HashSet은 AbstractSet을 확장하고 Set 인터페이스를 구현한다. 저장소에 해시 테이블을 사용하는 Collection을 만든다.

해시 테이블은 해싱(Hashing)이라는 메커니즘을 사용하여 정보를 저장한다. 해시 테이블 저장소는 데이터를 저장하기 위해서 유일하게 구분되는 key 값과 value를 한 세트로 하여 저장하게 된다. 이 때 key 값은 개발자가 부여하는 것이 아니라 코드에서 자동으로 처리된다. 저장된 데이터를 순차적으로 관리하는 것이 아니라 hash code로 구분하여 저장되기 때문에 index를 이용하여 key값에 접근할 수 없다. 해싱에서는 해시 코드라는 고유 한 값을 결정하기 위해 키의 정보 콘텐츠가 사용된다. 해싱을 이용하게 되면 순차적으로 데이터를 관리하는 것보다 많은 양의 데이터를 관리할 때 추가, 삭제, 검색 등에서 속도가 향상된다.

HashSet은 정렬 기능을 지원하지 않기 때문에, 정렬기능을 지원하기 위해서는 SortedSet을 구현한 TreeSet 클래스를 사용하도록 하자.

HashSet 예제

package com.devkuma.tutorial.java.util;

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

public class HashSetClass {

    public static void main(String[] args) {

        Set<Integer> hs = new HashSet<Integer>();

        hs.add(85);
        hs.add(67);
        hs.add(58);
        hs.add(30);
        hs.add(46);

        for (Integer i : hs) {
            System.out.println(i);
        }
    }
}

실행 결과는 아래와 같다.

67
85
58
30
46

TreeSet 클래스

TreeSet은 Set 인터페이스와 SortedSet 인터페이스를 구현한 클래스이다. 정렬 기능을 지원하며, 트리 형태로 데이터를 관리한다. 요소가 저장될 때 오름차순으로 정렬되어 저장되기 때문에 데이터를 얻어 속도가 떨어지지는 않는다.

TreeSet 예제

package com.devkuma.tutorial.java.util;

import java.util.Set;
import java.util.TreeSet;

public class TreeSetClass {

    public static void main(String[] args) {

        Set<Integer> ts = new TreeSet<Integer>();

        ts.add(85);
        ts.add(67);
        ts.add(58);
        ts.add(30);
        ts.add(46);

        for (Integer i : ts) {
            System.out.println(i);
        }
    }
}

실행 결과는 아래와 같다.

30
46
58
67
85

이전 HashSet 클래스와는 달리 저장된 데이터를 정렬한 후에 출력해 준다.

1.1.12.3 - Java 입문 | java.util 패키지 - 콜렉션 | Map 인터페이스 및 관련 클래스

Map 인터페이스

Map 인터페이스는 데이터를 저장하기 위해서 다른 객체를 키 값으로 지정하여 key-value를 하나의 쌍으로 하여 저장하여 관리하는 Collection으로써, 키 객체의 중복을 허용하지 않지만, 데이터의 중복은 허용한다. 인덱스를 관리하지 않기 때문에 정렬 등의 작업에는 부적합하다. Map 인터페이스를 구현한 하위 클래스로는 HashMap, LinkedMap, TreeMap 이 있다.

  • Map
  • HashMap
    • LinkedHashMap
  • LinkedMap
  • SortedMap
    • TreeMap

Map 메소드

메소드 설명
void clear() Map에 저장된 객체를 삭제한다.
boolean containsKey(Object key) 지정한 키(key)가 존재하는지 여부를 반환한다.
boolean containsValue(Object value) 지정한 값(value)이 존재하는지 여부를 반환한다.
V get(Object key) 지정한 키(key)에 해당하는 데이터를 반환한다.
boolean isEmpty() Map이 비어 있는지의 여부를 반환한다.
Set<K> keySet() key 목록을 Set 객체 형태로 반환한다.
V put(K key, V value) 키(key)와 값으로 구성된 새로운 데이터를 추가한다.
void putAll(Map<? extends K, ? extends V> m) Map에 또 다른 Map를 추가한다.
V remove(Object key) 지정한 키(key)에 해당하는 데이터(value)를 삭제하고 삭제한 데이터(value)를 반환한다.
int size() Map의 요소 개수를 반환한다.
Collection<V> values() value 목록을 Collection 형태로 반환한다.

HashMap 클래스

Map 계열의 Collection으로써, 데이터를 저장하기 위해 해시 테이블을 사용한다. HashMap은 데이터를 저장할 때 저장하려는 요소 외에 key 값에 해당하는 객체를 지정하며, 데이터를 얻어 올 때도 저장했던 key 값에 의해 데이터를 얻게 된다. 따라서 HashMap 객체를 생성할 때도 key값과 value값을 지정하는 생성자를 사용할 수도 있다. HashMap은 정렬 기능을 지원하지 않는다.

HashMap 예제

package com.devkuma.tutorial.java.util.collection;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class HashMapClass1 {

    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<Integer, String>();

        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");

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

        System.out.println("\n-- Example2 -----");
        Iterator<Entry<Integer, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> entry = iterator.next();
            System.out.println("Key : " + entry.getKey() + " Value :" + entry.getValue());
        }

        System.out.println("\n-- Example3 -----");
        // Java8 only, forEach and Lambda
        map.forEach((k, v) -> System.out.println("Key=" + k + ", Value=" + v));

        System.out.println("\n-- Example4 -----");
        for (Integer key : map.keySet()) {
            System.out.println("key=" + key + ", value=" + map.get(key));
        }
    }
}

실행 결과는 아래와 같습니다.

-- Example1 -----
Key=1, Value=one
Key=2, Value=two
Key=3, Value=three

-- Example2 -----
Key : 1 Value :one
Key : 2 Value :two
Key : 3 Value :three

-- Example3 -----
Key=1, Value=one
Key=2, Value=two
Key=3, Value=three

-- Example4 -----
key=1, value=one
key=2, value=two
key=3, value=three

LinkedHashMap 클래스

LinkedHashMap 클래스는 HashMap 클래스를 상속한 클래스로써 입력한 순서대로 데이터를 저장 시키는 Collection이다. LinkedHashMap는 데이터를 key-value 한 쌍으로 저장되며, 데이터를 얻어 올 때도 입력한 순서대로 데이터를 얻게 된다. HashMap과는 달리 입력한 순서대로 정렬을 보장한다.

LinkedHashMap 예제

package com.devkuma.tutorial.java.util.collection;

import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapClass {

    public static void main(String[] args) {
        Map<String, Integer> map = new LinkedHashMap<String, Integer>();

        map.put("11", 104);
        map.put("23", 133);
        map.put("32", 111);
        map.put("24", 123);
        map.put("15", 144);
        map.put("36", 113);

        for (String key : map.keySet()) {
            System.out.println("key=" + key + ", value=" + map.get(key));
        }
    }
}

실행 결과는 아래와 같습니다.

key=11, value=104
key=23, value=133
key=32, value=111
key=24, value=123
key=15, value=144
key=36, value=113

TreeMap 클래스

TreeMap 클래스는 Map 인터페이스와 SortedMap 인터페이스를 구현한 클래스로써 정렬 기능이 지원되는 Map 형태의 Collection이다. TreeMap은 데이터를 key-value 한 쌍으로 저장되며, 정렬을 지원하고 데이터를 얻어 오는 속도도 빠르다. HashMap과는 달리 오름차순으로 key 값을 저장한다.

TreeMap 예제

package com.devkuma.tutorial.java.util.collection;

import java.util.Map;
import java.util.TreeMap;

public class TreeMapClass {

    public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<String, Integer>();

        map.put("11", 104);
        map.put("23", 133);
        map.put("32", 111);
        map.put("24", 123);
        map.put("15", 144);
        map.put("36", 113);

        for (String key : map.keySet()) {
            System.out.println("key=" + key + ", value=" + map.get(key));
        }
    }
}

실행 결과는 아래와 같습니다.

key=11, value=104
key=15, value=144
key=23, value=133
key=24, value=123
key=32, value=111
key=36, value=113

1.1.12.4 - Java 입문 | java.util 패키지 - 콜렉션 | Queue 인터페이스 및 관련 클래스

Queue 인터페이스

Queue 인터페이스는 J2SE 5.0에서 추가된 인터페이스로써 First-In, First-Out 형태를 가지고 있는 Queue 데이터 구조를 가진다. Queue 인터페이스는 저장된 데이터의 첫 번째 데이터만 삭제 할 수 있다. 다른 인터페이스와는 달리 데이터를 저장하기 위해서는 offer(), 삭제를 하기 위해서는 remove(), 데이터를 얻기 위해서는 element(), peek(), poll() 등의 메소드를 가지고 있다. Queue 인터페이스를 구현한 하위 클래스로는 LinkedList, PriorityQueue 이 있다.

Queue 메소드

메소드 설명
boolean add(E e) Queue(큐)에 객체를 넣고, 성공하면 true를 반환한다.
boolean offer(E e) Queue(큐)에 객체를 넣는다.
E poll() Queue(큐)에서 객체를 반환하고, 비어있다면 null 을 반환한다.
E peek() Queue(큐)의 맨 아래 있는 객체를 반환하고, 객체를 큐에서 제거하진 않는다.
E remove() Queue(큐)의 맨 아래 있는 객체를 반환하고, 객체를 큐에서 제거한다.

PriorityQueue

PriorityQueue 예제

package com.devkuma.tutorial.java.util.collection;

import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueClass {

    public static void main(String[] args) {

        Queue<String> queue = new PriorityQueue<String>();
        queue.add("a");
        queue.add("b");
        queue.add("c");
        queue.add("d");
        queue.add("e");
        System.out.println("head elemen t: " + queue.element());
        System.out.println("head peek : " + queue.peek());

        System.out.println("iterating the queue elements:");
        Iterator<String> itr = queue.iterator();
        while (itr.hasNext()) {
            System.out.println(itr.next());
        }

        System.out.println("head remove : " + queue.remove());
        System.out.println("head poll : " + queue.poll());

        System.out.println("after removing two elements:");
        Iterator<String> itr2 = queue.iterator();
        while (itr2.hasNext()) {
            System.out.println(itr2.next());
        }
    }
}

실행 결과는 아래와 같습니다.

head elemen t: a
head peek : a
iterating the queue elements:
a
b
c
d
e
head remove : a
head poll : b
after removing two elements:
c
d
e

1.1.13 - Java 입문 | java.io 패키지

  • Reader
    • BufferedReader
    • InputStreamReader
      • FileReader
  • Writer
    • BufferedWriter
    • InputStreamWriter
      • FileWriter

1.1.13.1 - Java 입문 | java.io 패키지 | File 클래스

File 클래스는 입출력에 필요한 파일 및 디렉토리에 관한 정보를 다를 수 있다. File 클래스는 파일과 디렉토리의 접근 권한, 생성된 시간, 마지막 수정 일자, 크기, 경로 등의 정보를 얻을 수 메소드를 가지고 있으며, 새로운 파일과 디렉토리 생성 및 삭제, 이름 변경 등의 조작 메소드를 가지고 있다.

File 클래스 변수

변수 설명
pathSeparator 경로 구분자 문자(윈도우: “", 리눅스 및 유니스 계열 : “/”)
separator 파일간이 구분자 문자(윈도우: “;”, 리눅스 : “:”)

자바는 앞서 설명을 했듯이, 플랫폼에 대해서 독립적이다. 이는 한번 컴파일을 하여 OS에 상관 없이 구동이 되기 때문에 각 OS에 따라 다르게 동작을 해야 할 필요가 있다. 파일은 OS마다 경로 및 파일간의 구분자가 다르기 때문에 문자열을 직접 입력하기 보다는 이 File 클래스 변수를 사용해야 OS에 상관 없이 오류 없는 프로그램을 구현할 수 있다.

File 생성자

생성자 설명
File(String pathname) 입력한 pathname(파일명 포함) 경로 파일의 객체를 생성한다.
File(String parent, String child) parent 디렉토리 경로의 child 파일에 대한 객체를 생성한다.
File(File parent, String child) 파일 객체 parent의 child 파일에 대한 객체를 생성한다.
File(URI uri) uri 경로에 대한 파일 객체를 생성한다.

File 메소드

파일 조회 관련 메소드

메소드 설명
File getAbsoluteFile() 파일의 절대 경로를 반환한다.
String getAbsolutePath() 파일의 절대 경로를 문자열로 반환한다.
File getCanonicalFile() 파일의 정규 경로를 반환한다.
String getCanonicalPath() 파일의 정규 경로를 문자열로 반환한다.
String getName() 파일이나 폴더의 이름을 넘겨준다.
String getParent() 부모 경로에 대한 경로명을 문자열로 반환한다.
File getParentFile() 부모 폴더를 File의 형태로 반환한다.
String getPath() 파일의 경로를 문자열의 형태로 반환한다.
long getTotalSpace() 하드디스크의 총 용량을 반환한다.
long getUsableSpace() 하드디스크의 사용 가능한 용량을 반환한다.
long getFreeSpace() 하드디스크의 남은 공간을 반환한다.
int hashCode() hash code를 반환한다.
long lastModified() 해당 경로 파일의 최종 수정 일자를 반환한다.
long length() 해당 경로 파일의 길이를 반환한다.
Path toPath() java.nio.file.Path 객체로 반환한다.
URI toURI() URI 형태로 파일 경로를 반환한다.
File[] listRoots() 하드디스크의 루트 경로를 반환한다.
String[] list() 경로의 파일들과 폴더를 문자열 배열로 반환한다.
String[] list(FilenameFilter filter) filter에 만족되는 파일들과 폴더 이름을 문자열 배열로 반환한다.
File[] listFiles() 해당 경로의 파일들과 폴더의 파일을 배열로 반환한다.
File[] listFiles(FileFilter filter) filter에 만족되는 파일들과 폴더를 File 배열로 반환한다.
File[] listFiles(FilenameFilter filter) filter에 만족되는 파일들과 폴더를 File 배열로 반환한다.

파일 조작(생성, 수정, 삭제) 관련 메소드

메소드 설명
boolean createNewFile() 주어진 이름의 파일이 없으면 새로 생성한다.
static File createTempFile(String prefix, String suffix) 파일명에 prefix와 suffix(null이면 .tmp)를 붙여 임시 파일을 기본 임시 파일 디렉토리에 생성한다.
static File createTempFile(String prefix, String suffix, File directory) 파일명에 입력한 prefix와 suffix(null이면 .tmp)를 붙여 directory에 임시 파일을 생성한다.
boolean delete() 파일이나 디렉토리를 삭제한다. 단, 디렉토리가 비어있지 않으면 삭제할 수 없다.
void deleteOnExit() 자바 가상 머신이 끝날 때 파일을 삭제한다.
boolean mkdir() 해당 경로에 폴더를 만든다. 생성 성공하면 true, 실패하면 false을 반환한다.
boolean mkdirs() 존재하지 않는 부모 디렉토리까지 포함하여 해당 경로에 폴더를 만든다.
boolean renameTo(File dest) 파일를 dest 로 변경한다. 이름뿐 아니라 경로도 변경된다.

파일 체크 관련 메소드

메소드 설명
boolean exists() 파일의 존재 여부를 반환한다.
boolean isAbsolute() 해당 경로가 절대 경로인지 여부를 반환한다.
boolean isDirectory() 해당 경로가 디렉토리인지 여부를 반환한다.
boolean isFile() 해당 경로가 file 인지 여부를 반환한다.
boolean isHidden() 해당 경로가 숨김 파일인지 여부를 반환한다.

파일 권한 관련 메소드

메소드 설명
boolean canExecute() 파일 실행 권한이 있는지 여부를 반환한다.
boolean canRead() 파일을 읽기 권한이 있는지 여부를 반환한다.
boolean canWrite() 파일을 씨기 권한이 있는지 여부를 반환한다.
boolean setExecutable(boolean executable) 파일 소유자의 실행 권한을 설정한다.
boolean setExecutable(boolean executable, boolean ownerOnly) 파일의 실행 권한을 소유자 또는 모두에 대해 설정한다.
boolean setReadable(boolean readable) 파일의 소유자의 읽기 권한을 설정한다.
boolean setReadable(boolean readable, boolean ownerOnly) 파일의 읽기 권한을 소유자 또는 모두에 대해 설정한다.
boolean setReadOnly() 파일을 읽기 전용으로 변경한다.
boolean setWritable(boolean writable) 파일의 소유자의 쓰기 권한을 설정한다.
boolean setWritable(boolean writable boolean ownerOnly) 파일의 쓰기 권한을 소유자 또는 모두에 대해 설정한다.

File 예제

예제 1)

아래 예제는 특정 파일에 대한 정보를 화면에 표시해 준다.

package com.devkuma.tutorial.java.io;

import java.io.File;
import java.io.IOException;

public class FileClass {

    public static void main(String[] args) {

        // file.txt 객체 생성한다.
        File file = new File("file.txt");

        // 파일이 존재 여부를 체크한다.
        if (file.exists()) {

            try {
                // 파일 이름을 표시한다.
                System.out.println("getName: " + file.getName());
                // 파일 경로을 표시한다.
                System.out.println("getPath: " + file.getPath());
                // 파일 절대 경로 표시한다.
                System.out.println("getAbsolutePath : " + file.getAbsolutePath());
                // 파일 정규 경로 표시한다.
                System.out.println("getCanonicalPath : " + file.getCanonicalPath());
                // 상위 디렉토리를 표시한다.
                System.out.println("getParent : " + file.getParent());

                // 파일의 쓰기/읽기 권한 체크한다.
                if (file.canWrite())
                    System.out.println(file.getName() + " can write.");
                if (file.canRead())
                    System.out.println(file.getName() + " can read.");

                // 객체의 파일, 폴더 여부 체크한다.
                if (file.isFile()) {
                    System.out.println(file.getName() + " is file");
                }
                if (file.isDirectory()) {
                    System.out.println(file.getName() + " is directory.");
                }

                // 파일 내용 길이를 표시한다.
                System.out.println(file.getName() + " length=" + file.length());

            } catch (IOException e) {
                e.printStackTrace();
            }

        } else {
            System.out.println("not found file");
        }
    }
}

실행 결과는 아래와 같습니다.

getName: file.txt
getPath: file.txt
getAbsolutePath : /Users/{user}/workspace/java-tutorial/file.txt
getCanonicalPath : /Users/{user}/workspace/java-tutorial/file.txt
getParent : null
file.txt can write.
file.txt can read.
file.txt is file
file.txt length=26

예제 2)

아래 예제는 특정 디렉토리에 대한 파일 및 디렉토리리 목록을 표시해 준다.

package com.devkuma.tutorial.java.io;

import java.io.File;

public class FileClass2 {

    public static void main(String[] args) {

        // 프로젝트 현재 디렉토리를 객체로 생성한다.
        File file = new File(".");

        // 폴더의 파일/폴더 목록을 문자열 배열로 반환
        String[] files = file.list();

        // 출력
        for (String fileName : files) {
            System.out.println(fileName);
        }
    }
}

실행 결과는 아래와 같습니다.

.classpath
.project
.settings
bin
file.txt
src

예제 3)

아래 예제는 루트 경로를 표시해 주고 하드 디스크의 용량을 화면에 표시해 준다.

package com.devkuma.tutorial.java.io;

import java.io.File;

public class FileClass3 {

    public static void main(String[] args) {

        // 하드디스크의 루트 경로(드라이브)들을 배열로 반환한다.
        File[] roots = File.listRoots();

        for (File root : roots) {

            // 루트 경로(드라이브)의 절대 경로
            String drive = root.getAbsolutePath();
            // 하드디스크 전체 용량
            long totalSpace = root.getTotalSpace();
            // 사용가능한 디스크 용량
            long usableSpace = root.getUsableSpace();
            // 여유 디스크 용량
            long freeSpace = root.getFreeSpace();
            // 사용한 디스크 용량
            long usedSpace = totalSpace - usableSpace;

            System.out.println("root : " + drive);
            System.out.println("Total Space : " + totalSpace);
            System.out.println("Usable Space : " + usableSpace);
            System.out.println("Free Space : " + freeSpace);
            System.out.println("Used Space : " + usedSpace);
        }
    }
}

실행 결과는 아래와 같습니다.

root : /
Total Space : 598000009216
Usable Space : 100067610624
Free Space : 100329754624
Used Space : 497932398592

1.1.13.2 - Java 입문 | java.io 패키지 | File 클래스 - 임시 파일 생성

아래 예제는 임시 파일을 생성하는 예제이다.

예제)

package com.devkuma.tutorial.java.io;

import java.io.File;
import java.io.IOException;

public class CreateTempFile {

    public static void main(String[] args) {
        try {
            // create a temp file
            File temp = File.createTempFile("temp_", ".tmp", new File("/Users/kimkc"));

            System.out.println("Temp file : " + temp.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

실행 결과는 아래와 같습니다.

Temp file : /Users/kimkc/temp_6504907352173891612.tmp

1.1.13.3 - Java 입문 | java.io 패키지 | FilenameFilter 인터페이스

FilenameFilter 인터페이스는 시스템에서 특정한 파일 이름을 포함하는 목록들만 얻기 위해서 필터링(filtering) 작업을 위해서 사용하며, File 클래스의 list() 메소드의 매개변수로 전달한다.

FilenameFilter 메소드

메소드 설명
boolean accept(File dir, String name) 경로 디렉토리(dir)와 파일명(name)을 입력받는다.

FilenameFilter 인터페이스는 리스트에서 각 파일에 대해 한번만 호출되는 accept() 메소드 한개만 존재한다. 필터링 하고자 하는 파일에 대해 accept() 메소드를 재정의 해야 한다. 필터링 포함을 하고자 하는 파일명에 대해서는 true를 반환하고, 제외를 하고자 하는 파일명에 대해서는 false를 반환하면 된다.

FilenameFilter 예제

아래는 디렉토리를 입력 받아 파일명이 .java로 끝나는 파일명을 표시하는 주는 예제이다.

package com.devkuma.tutorial.java.io;

import java.io.File;
import java.io.FilenameFilter;

class JavaFilenameFilter implements FilenameFilter {

    public boolean accept(File dir, String name) {
        return name.endsWith(".java");
    }
}

public class FilenameFilterInterface {

    public static void main(String[] args) {
        File file = new File("src/main/java/com/devkuma/tutorial/javaio");

        JavaFilenameFilter filter = new JavaFilenameFilter();

        for (String path : file.list(filter)) {
            System.out.println(path);
        }
    }
}

실행 결과는 아래와 같다.

FileClass.java
FilenameFilterInterface.java

1.1.14 - Java 입문 | java.io 패키지 - 바이트 스트림

바이트 스트림(Byte Stream)은 8비트인 바이트(byte) 단위로 입출력을 제어하므로 2진 데이터로 취급 할 수 있다. java.io 패키지의 클래스 중에 바이트 스트림 관련 클래스는 아래와 같다.

  • InputStream
    • FileInputStream
  • FilterInputStream
    • BufferedInputStream
    • DataInputStream
  • ObjectInputStream
  • OutputStream
    • FileOutputStream
    • FilterOutputStream
      • BufferedOutputStream
      • DataOutputStream
    • ObjectOutputStream

1.1.14.1 - Java 입문 | java.io 패키지 - 바이트 스트림 | 바이트 스트림과 문자 스트림

스트림(Stream)이란 순서가 있는 일련의 데이터를 의미하는 추상적인 개념으로 한 곳에서 다른 곳으로 자원을 흘려 보내는 일련의 데이터들을 말한다. 이런 데이터들은 bit, char, byte 단위로 전달 된다.

스트림에는 바이트 스트림(byte stream)와 문자 스트림(character stream) 두 가지 형태가 있다. 바이트 스트림은 8비트의 바이트를 읽고 쓰기 위한 스트임으로써 InputSterm, OutStream 클래스를 상속 받은 하위 클래스들을 이용한다. 문자 스트림은 16비트 문자나 문자열들을 읽고 쓰기 위한 스트림으로써 입출력은 위해서는 Reader, writer 클래스를 상속 받은 하위 클래스들을 이용한다.

1.1.14.2 - Java 입문 | java.io 패키지 - 바이트 스트림 | InputStream/OutputStream 추상클래스

InputStream 클래스와 OutputStream 클래스는 추상 클래스이기 때문에 실제 객체를 생성할 수는 없다.

InputStream 클래스

InputStream 클래스는 바이트 스트림의 입력 기능을 제공한다.

InputStream 메소드

InputStream 클래스는 입력 소스로부터 데이터를 읽어오는 메소드를 가지고 있다.

메소드 설명
int available() throws IOException 현재 읽기 가능한 바이트 수를 반환한다.
void close() throws IOException 입력 스트림을 닫는다.
void mark(int readlimit) 입력 스트림에서 현재 위치를 표시한다.
boolean markSupported() 해당 입력 스트림에서 mark()로 지정된 지점이 있는지 여부를 체크한다.
abstract int read() throws IOException 입력 스트림에서 한 바이트를 읽어서 int 값(0~255)으로 반환한다. 더 이상 읽을 값이 없을 경우 -1을 반환한다.
int read(byte[] b) throws IOException byte[] b 만큼의 데이터를 읽어서 b에 저장하고읽은 바이트 수를 반환한다.
int read(byte[] b, int off, int len) thrwos IOException len 만큼을 읽어 byte[] b의 off위치에 저장하고 읽은 바이트 수를 반환한다.
void reset() mark() 를 마지막으로 호출한 위치로 이동한다.
long skip(long n) throws IOException 입력 스트림에서 n 바이트 만큼 데이터를 스킴하고 바이트 수를 반환한다.

InputStream 클래스 중에서 추상 메소드인 read()는 데이터를 입력 위한 메소드이다. read() 메소드는 입력 스트림에서 1byte를 읽어 들이기 위한 메소드인데 EoF(End of File)을 만나면 -1 을 반환하여 읽기 작업이 끝났다는 것을 알린다.

read() 메소드가 1byte씩 읽어 들인다면 read(byte[] b) 메소드는 사용자가 지정한 byte[] 을 이용하여 한꺼번에 원하는 크기를 읽어 온다.

read() 메소드의 반환 데이터 타입형을 보면 byte가 아니라 int이다. 자바는 숫자 체계는 바이트를 기준으로 -128부터 127까지 표현할 수 있다. 하지만 read() 메소드를 통해서 읽는 값은 양수를 기준으로 표현하는데 이 경우에는 0에서 127까지 밖에 사용할 수 없다. 이를 해결하기 위해 더 큰 자료형을 반환하도록 사용하여 0부터 255까지 사용할 수 있게 하고, 음수인 -1을 특수한 입력 값으로 처리하도록 하였다.

available() 메소드는 스트림에서 읽을 수 있는 바이트 수를 얻은 후 이를 기준으로 byte[]를 생성하여 한꺼번에 읽을 수 있다.

OutputStream 클래스

OutputStream 클래스는 바이트 스트림의 출력 기능을 제공한다.

OutputStream 메소드

OutputStream 클래스는 바이트 스트림을 출력하는 메소드를 가지고 있다.

메소드 설명
void close() throws IOException 출력 스트림을 닫는다.
void flush() throws IOException 버퍼에 남은 출력 스트림을 모두 출력한다.
abstract void write(int i) throws IOException 정수 i의 하위 8비트를 출력한다.
void write(byte buf[]) throws IOException buf의 내용을 출력한다.
void write(byte buf[], int index, int size) throws IOException buf의 index 위치부터 size만큼의 바이트를 출력한다.

출력 메소드는 3가지 형태가 있는데, 그 중 첫번째로는 write(int i) 메소드가 있다. 이 메소드 1byte를 출력하는 메소드로써 인자를 byte가 아닌 정수형(0~255)을 받고, 추상 메소드로 선언이 되어 있어서 하위 클래스에서 재정의 되는 메소드이다. write(byte buf[]) 메소드는 byte를 배열로 받아서 출력한다. write(byte buf[], int index, int size) 메소드는 인자로 byte 배열과 함께 시작 위치 및 크기를 받는다.

flush() 메소드는 버퍼(buffer)와 관련이 있는데 버퍼는 입출력을 조금 더 빠르게 도와준다. write 메소드를 호출하여 데이터 출력을 하면 바로 데이터가 출력이 되는 것이 아니라 먼저 버퍼에 쌓인다. 버퍼에 데이터가 어느정도 쌓인 후에 flush() 메소드가 호출 되면 그때서야 데이터가 출력이 되고 버퍼는 비워 버린다. flush() 메소드를 호출하지 않으면 출력 데이터가 버퍼에만 쌓이기 때문에 출력이 되지 않은 경우가 발생할 수 있다. 이런 이유로 보통 하위 클래스에서 자동으로 flush() 메소드가 호출되도록 구현이 된다.

1.1.14.3 - Java 입문 | java.io 패키지 - 바이트 스트림 | FileInputStream/FileOutputStream

FileInputStream

#3# FileInputStream 생성자

생성자 설명
FileInputStream(String filePath) throws FileNotFoundException filepath로 지정한 파일에 대한 입력 스트림을 생성한다.
FileInputStream(File fileObj) throws FileNotFoundException fileObj로 지정한 파일에 대한 입력 스트림을 생성한다.
FileInputStream(FileDescriptor fdObj) throws SecurityException fdObj 로 기존의 접속을 나타내는 파일 시스템의 입력 스트림을 생성한다.

FileInputStream 메소드

메소드 설명
int available() throws IOException 현재 읽을 수 있는 바이트 수를 반환한다.
int close() throws IOException InputStream을 닫는다.
int read() throws IOException InputStream에서 한 바이트를 읽어서 int 값으로 반환한다.
int read(byte buf[]) throws IOException InputStream에서 buf[] 크기만큼을 읽어 buf에 저장하고 읽은 바이트 수를 반환한다.
int read(byte buf[], int offset, int numBytes) throws IOException InputStream에서 nnumBytes만큼을 읽어 buf[]의 offset 위치부터 저장하고 읽은 바이트 수를 반환한다.
int skip(long numBytes) throws IOException numBytes로 지정된 바이트를 스킵하고 스킵된 바이트 수를 반환한다.
protected void finalize() 더이상 참조하는 것이 없을 경우 close() 메소드를 호출한다.
FileChannel getChannel() FileInputStream의 유일한 FileChannel 객체를 반환한다.
FileDescriptor getFD() FileInputStream에서 실제 파일에 접속에 대한 FileDescriptor 객체를 반환한다.

FileInputStream 예제

준비중입니다.

FileOutputStream

FileOutputStream의 생성자

생성자 설명
FileOutputStream(String filepath) throws FileNotFoundException filepath로 지정한 파일에 대한 OutputStream을 생성한다.
FileOutputStream(String filepath, boolean append) throws FileNotFoundException 지정한 파일로 OutputStream을 생성한다. append 인자로 출력할 때 append 모드를 설정한다.
FileOutputStream(File fileObj) throws FileNotFoundException fileObj로 지정된 파일에 대한 OutputStream을 생성한다.
FileOutputStream(File fileObj, boolean append) throws FileNotFoundException fileObj로 지정된 파일에 대한 OutputStream을 생성한다. append 인자로 출력할 때 append 모드를 설정한다.
FileOutputStream(FileDescriptor fdObj) throws NullPointerException fdObj 로 기존의 접속을 나타내는 파일 시스템의 OutputStream을 생성한다.

FileOutputStream 메소드

메소드 설명
void close() throws IOException OutputStream을 닫는다.
void flush() throws IOException 버퍼에 남은 OutputStream을 출력한다.
void write(int i) throws IOException 정수 i의 하위 8비트를 출력한다.
void write(byte buf[]) throws IOException buf의 내용을 출력한다.
void write(byte buf[], int index, int size) throws IOException buf의 index 위치부터 size만큼의 바이트를 출력한다.
FileChannel getChannel() OutputStream과 연관된 유일한 FileChannel 객체를 반환한다.
FileDescriptor getFD() OutputStream과 연관된 FileDescriptor 객체를 반환한다.

FileOutputStream 예제

준비 중입니다.

1.1.14.4 - Java 입문 | java.io 패키지 - 바이트 스트림 | FilterInputStream/FilterOutputStream

FilterInputStream 생성자

생성자 설명
FilterInputStream(InputStream in) InputStream을 생성인자로 받아 특정 기능을 수행하는 FilterInputStream 객체를 생성한다.

FilterOutputStream 생성자

생성자 설명
FilterOutputStream(OutputStream out) OutputStream을 생성인자로 받아 특정 기능을 수행하는 FilterOutputStream 객체를 생성한다.

1.1.14.5 - Java 입문 | java.io 패키지 - 바이트 스트림 | BufferedInputStream/BufferedOutputStream

BufferedInputStream 생성자

생성자 설명
BufferedInputStream(InputStream in) InputStream에 대한 BufferedInputStream 객체를 생성한다.
BufferedInputStream(InputStream in, int size) InputStream에 대한 BufferedInputStream 객체를 생성하고 내부 버퍼의 크기를 Size 값으로 설정한다.

BufferedOutputStream 생성자

생성자 설명
BufferedOutputStream(OutputStream out) OutputStream에 대한 BufferedOutputStream 객체를 생성한다.
BufferedOutputStream(OutputSteam out, int size) OutputStream에 대한 BufferedOutputStream 객체를 생성하고 내부 버퍼의 크기를 Size 값으로 설정한다.

1.1.14.6 - Java 입문 | java.io 패키지 - 바이트 스트림 | DataInputStream/DataOutputStream

DataInputStream 생성자

생성자 설명
DataInputStream(InputStream in) inputStream을 인자로 DataInputStream을 생성한다

DataInputStream 메소드

메소드 설명
boolean readBoolean() throws IOException Stream으로부터 읽은 boolean을 반환한다.
byte readByte() throws IOException Stream으로부터 읽은 byte를 반환한다.
char readChar() throws IOException Stream으로부터 읽은 char를 반환한다.
double readDouble throws IOException Stream으로부터 double을 반환한다.
float readFloat() throws IOException Stream으로부터 float을 반환한다.
long readLong() throws IOException Stream으로부터 long을 반환한다.
short readShort() throws IOException Stream으로부터short를 반환한다.
int readInt() throws IOException Stream으로부터 int를 반환한다.
void readFully(byte[] buf) throws IOException Stream으로부터 buf 크기만큼의 바이트를 읽어 buf[]에 저장한다.
void readFully(byte[] buf, int off, int len) throws IOException Stream으로부터 len 길이만큼 바이트를 읽어 buf의 off 위치에 저장한다.
String readUTF() throws IOException UTF 인코딩 값을 얻어 문자열로 반환한다.
static String readUTF(DataInput in) throws IOException DataInput의 수정된 UTF 인코딩 값을 얻어 문자열로 반환한다.
int skipBytes(int n) throws IOException n 만큼 바이트를 skip 한다.

DataOutputStream 생성자

생성자 설명
DataOutputStream(OutputStream out) outputStream을 인자로 DataOutputStream을 생성한다.

DataOutputStream 메소드

메소드 설명
void flush() throws IOException 버퍼를 출력하고 비운다.
int size() Stream에 출력된 바이트 크기를 반환한다.
void write(int i) throws IOException int 형 i 값이 갖는 1바이트를 출력한다.
void write(byte buf[], int index, int size) throws IOException byte 배열 buf의 index 위치에서 size만큼 출력한다.
void writeBoolean(boolean b) throws IOException boolena을 1바이트 값으로 출력한다.
void writeByte(int i) throws IOException int를 4바이트 값으로 상위 바이트 먼저 출력한다.
void writeBytes(String s) throws IOException 문자열을 바이트 순으로 출력한다.
void writeChar(int i) throws IOException char를 2바이트 값으로 상위 바이트 먼저 출력한다.
void writeChars(String s) throws IOException String 문자열을 char형으로 출력한다.
void writeDouble(double d) throws IOException Double 클래스의 doubleToBits()를 사용하여 long으로 변환한 다음 long 값을 8바이트수량으로 상위바이트 먼저 출력한다.
void writeFloat(float f) throws IOException Float을 floatToBits()를 사용하여 변환한 다음 int 값을 4바이트 수량으로 상위 바이트 먼저 출력한다.
void writeInt(int i) throws IOException int의 상위 바이트 먼저 출력한다.
void writeLong(long l) throws IOException long 형의 인자값 출력한다.
void writeShort(shrot s) throws IOException short형의 인자값 출력한다.
void writeUTF(String s) throws IOException UTF-8 인코딩을 사용해서 문자열을 출력한다.

1.1.14.7 - Java 입문 | java.io 패키지 - 바이트 스트림 | ObjectInputStream/ObjectOutputStream

ObjectInputStream 생성자

생성자 설명
ObjectInputStream(InputStream in) in 으로부터의 unmarshalling을 위한 ObjectInputStream 객체를 생성한다.

ObjectInputStream 메소드

생성자 설명
int available() 객체에서 읽을 수 있는 바이트 값을 반환한다.
void close() 객체를 닫는다.
void defaultReadObject() 현재 Stream에서 static,transient가 아닌 객체를 읽는다.
protected boolean enableResolveObject(boolean enable) 현재 Stream에서 객체를 읽는 것 허용 여부를 설정한다.
int read() 데이터를 바이트 단위로 읽는다.
int read(byte[] buf, int off, int len) buf 바이트 배열에 off 부터 len까지 읽는다.
boolean readBoolean() 객체의 boolean 값을 읽는다.
byte readByte() 객체의 1 byte를 읽는다. (8 비트)
char readChar() 객체의 1 Char를 읽는다. (16 비트)
protected ObjectStreamClass readClassDescriptor() 직렬화된 스트림에서 Descriptor를 읽는다.
double readDouble() 객체에서 1 double을 읽는다. (64 비트)
ObjectInputStream.GetField.readFileds() 객체에서 영속성이 보장된 형의 이름을 가져온다.
float readFloat() 객체에서 1 float을 읽는다. (32 비트)
void readFully(byte[] buf) 객체에서 buf 만큼 바이트를 읽는다.
void readFully(byte[] buf, int off, int len) 객체에서 buf 만큼 off 부터 len만큼 읽는다.
int readInt() 객체에서 1 int를 읽는다. (32 비트)
Long readLong() 객체에서 1 Long을 읽는다. (64 비트)
Object readObject() 객체에서 Object를 읽는다.
Short readShort() 객체에서 1 Short를 읽는다. (16비트)
protected void readStreamHeader() 스트림의 헤더를 읽는다.
Object readUnshared() 스트림에서 “unshared” 객체를 읽는다.
String readUTF() String을 UTF-8 방식으로 읽는다.

ObjectOutputStream 생성자

생성자 설명
ObjectOutputStream(OutputStream out) out을 marshalling 하기 위한 ObjectOutputStream 객체를 생성한다.

ObjectOutputStream 메소드

생성자 설명
void close() 객체를 닫는다.
void defaultWriteObject() 현재 Stream에 static,transient가 아닌 객체를 쓴다.
protected void drain() ObjectOutputStream의 버퍼에 있는 객체를 내보낸다.
protected boolean enableReplaceObject(boolean enable) 현재 Stream에서 객체를 쓰는것 허용 여부를 설정한다.
void flush() 스트림에 데이터를 내보낸다.
protected Object replaceObject(Object obj) 객체에 Object를 교체한다.
void reset() 스트림을 리셋한다.
void useProtocolVersion(int version) 스트림을 내보낼때 사용하는 프로토콜의 버전을 설정한다.
void write(byte[] buf) buf를 쓴다.
void write(byte[] buf, int off, int len) buf의 off부터 len 길이만큼을 스트림에 쓴다.
void write(int val) val 바이트만큼 스트림에 쓴다.
void writeBoolean(boolean val) val을 스트림에 쓴다.
void writeByte(int val) val을 byte로 스트림에 쓴다. (8 비트)
void writeBytes(String str) str을 sequence 바이트로 스트림에 쓴다.
void writeChar(int val) val을 Char로 스트림에 쓴다. (16 비트)
void writeChars(String str) str을 sequence char로 스트림에 쓴다.
protected void writeClassDescriptor(ObjectStreamClass desc) 스트림에 desc를 쓴다.
void writeDouble(double val) val을 Double로 스트림에 쓴다. (64 비트)
void writeFilelds() 스트림에 버퍼에 있는 필드를 쓴다.
void writeFloat(float val) val을 Float으로 스트림에 쓴다. (32 비트)
void writeInt(int val) val을 Int로 스트림에 쓴다.(32 비트)
void writeLong(long val) val을 long로 스트림에 쓴다. (64 비트)
void writeObject(Object obj) Obj 객체를 스트림에 쓴다.
void writeShort(int val) val을 Short로 스트림에 쓴다.
protected void writeStreamHeader() 스트림에 StreamHeader를 쓴다.
void writeUnshared(Object obj) “unshared” 객체를 스트림에 쓴다.
void writeUTF(String str) 객체의 문자의 인코딩을 UTF-8로 설정한다.

1.1.15 - Java 입문 | java.io 패키지 - 문자 스트림

java.io 패키지의 클래스 중에 문자 스트림(character stream) 관련 클래스는 아래와 같다.

  • Reader
    • BufferedReader
    • InputStreamReader
      • FileReader
  • Writer
    • BufferedWriter
    • OutputStreamWriter
      • FileWriter

1.1.15.1 - Java 입문 | java.io 패키지 - 문자 스트림 | Reader/Writer 추상클래스

Writer 클래스와 Reader 클래스는 추상 클래스이기 때문에 실제 객체를 생성할 수는 없다.

Reader 추상클래스

Reader 클래스는 문자 스트림의 입력 기능을 제공한다.

Reader 주요 메소드

메소드 설명
abstract void close() throws IOException 문자 입력 스트림을 닫는다.
void mark(int limit) throws IOException 문자 입력 스트림의 현재 위치를 표시한다.
boolean markSupported() 문자 스트림이 mark() 메소드가 지정되어 있는지 여부를 반환한다.
int read() throws IOException 문자 입력 스트림에서 단일 문자를 읽는다.
int read(char buf[]) throws IOException 문자 입력 스트림에서 buf[] 크기만큼을 읽어 buf에 저장하고 읽은 문자 수를 반환한다.
abstract int read(char buf[], int off, int len) throws IOException 문자 입력 스트림에서 len 만큼을 읽어 buf[]의 off 위치에 저장하고 읽은 문자 수를 반환한다.
int read(CharBuffer target) throws IOException CharBuffer 형인 target에 문자열을 읽어 온다.
boolean ready() throws IOException 문자 입력 스트림이 준비되었는지 리턴한다.
void reset() throws IOException 문자 입력 스트림을 mark된 위치로 되돌린다.
long skip(long l) throws IOException 주어진 개수 l 만큼의 문자를 건너뛴다.

Writer 추상클래스

Writer 클래스는 문자 스트림의 출력 기능을 제공한다.

Writer 주요 메소드

메소드 설명
Writer append(char c) throws IOException Writer에 Character c 를 추가한다.
Writer append(CharSequence csq) throws IOException Writer에 CharSequence csq 를 추가한다.
Writer append(CharSequence csq, int start, int end) throws IOException Writer에 CharSequence csq 의 start 부터 end까지의 문자를 추가한다.
abstract void close() throws IOException 문자 출력 스트림을 닫는다.
abstract void flush() throws IOException 버퍼에 남은 출력 스트림을 출력한다.
void write(String str) throws IOException 주어진 문자열 str를 출력한다.
void write(char buf[]) throws IOException buf의 내용을 출력한다.
void write(char buf[], int off, int len) throws IOException buf의 off 위치부터 len 만큼의 문자를 출력한다.
void write(String str, int off, int len) throws IOException 주어진 문자열 str에 있는 문자들을 off 위치부터 len 만큼 출력한다.

1.1.15.2 - Java 입문 | java.io 패키지 - 문자 스트림 | BufferedReader/BufferedWriter

BufferedReader 생성자

생성자 설명
BufferedReader(Reader in) 주어진 문자 입력 스트림 in에 대해 기본 크기의 버퍼를 갖는 객체를 생성한다.
BufferedReader(Reader in , int size) 주어진 문자 입력 스트림 in에 대해 size 크기의 버퍼를 갖는 객체를 생성한다.

BufferedReader 메소드

BufferedReader 는 기본적으로 Reader 메소드를 상속받아 사용한다.

매소드 설명
String readLine() 한줄을 읽는다. 개행 문자( ‘\n’, ‘\r’)를 만날때 까지 읽어온다.

readLine() 메소드를 사용하면서 문자 읽기를 더 효율적으로 할 수 있게 됐다. 기존의 read() 메소드로 한 문자씩 읽어오는 것보다 한줄씩 읽어서 처리하기에 더 간편하다.

BufferedReader 예제

준비중입니다.

BufferedWriter 의 생성자

생성자 설명
BufferedWriter(Writer out) 주어진 문자 출력 스트림 out에 대해 기본 크기의 버퍼를 갖는 객체를 생성한다.
BufferedWriter(Writer out, int size) 주어진 문자 출력 스트림 out에 대해 size 크기의 버퍼를 갖는 객체를 생성한다.

BufferedWriter 메소드

BufferedWriter 는 기본적으로 Reader 메소드를 상속받아 사용한다.

생성자 설명
String newLine() 줄을 바꾼다.
newLine() 메소드로 문자를 출력할 때 줄 바꿈이 필요할 때, 버퍼에 newLine() 메소드를 사용하여 줄 바꿈을 할 수 있다.

BufferedWriter 예제

준비중입니다.

1.1.15.3 - Java 입문 | java.io 패키지 - 문자 스트림 | InputStreamReader/OutputStreamWriter

InputStreamReader 생성자

생성자 설명
InputStreamReader(InputStream in) 주어진 입력 바이트 스트림 in에 대해 기본 인코딩을 사용하는 객체를 생성한다.
InputStreamReader(InputStream in, Charset cs) cs 문자 집합의 인코딩을 사용해 in을 문자스트림으로 변환 객체를 생성한다.
InputStreamReader(InputStream in, CharsetDecoder dec) dec 문자 집합의 디코더를 사용해 in을 문자스트림으로 변환 객체를 생성한다.
InputStreamReader(InputStream in, String charsetName) charsetName을 명명하는 인코딩을 사용하는 객체를 생성한다.

InputStreamReader 메소드

메소드 설명
void close() InputStreamReader를 닫는다.
String getEncoding() 현재 사용하고 있는 문자 인코딩의 표준 이름을 얻는다.
int read() 문자하나를 읽는다. (없을 경우 -1 반환)
int read(char[] cbuf, int offset, int length) cbuf의 버퍼에 offset 부터 length 길이만큼의 문자를 읽는다.
boolean ready() InputStream에서 문자가 있는지 읽을 수 있는지 여부를 확인한다.

OutputStreamWriter 생성자

생성자 설명
OutputStreamWriter(OutputStream out) 주어진 출력 바이트 스트림 out에 대해 기본 인코딩을 사용하는 객체를 생성한다.
OutputStreamWriter(OutputStream out, Charset cs) cs 문자 집합의 인코딩을 사용해 out을 바이트스트림으로 변환 객체를 생성 한다.
OutputStreamWriter(OutputStream out, CharsetDecoder dec) dec 문자 집합의 디코더를 사용해 out을 바이트스트림으로 변환 객체를 생성 한다.
OutputStreamWriter(OutputStream out, String charsetName) charsetName을 명명하는 인코딩을 사용하는 객체를 생성한다.

OutputStreamWriter 메소드

메소드 설명
void close() OutputStreamWriter를 닫는다.
void flush() OutputStreamWriter의 버퍼를 비운다. (출력한다.)
String getEncoding() 현재 사용하고 있는 문자 인코딩의 표준 이름을 얻는다.
void write(char[] cbuf, int off, int len) cbuf의 버퍼에 off 부터 len 만큼을 담아 문자를 쓴다.
void write(int c) c개의 문자를 쓴다.
void write(String str, int off, int len) 문자열 str 의 off부터 len 만큼을 쓴다.

1.1.15.4 - Java 입문 | java.io 패키지 - 문자 스트림 | FileReader/FileWriter

FileReader 생성자

생성자 설명
FileReader(File file) throws IOException file로 지정된 파일에 대한 입력 스트림을 생성한다.
FileReader(FileDescriptor fd) throws IOException fd로 지정된 FileDescriptor에 대한 입력 스트립을 생성한다.
FileReader(String fileName) throws IOException fileName로 지정한 경로의 파일에 대한 입력 스트림을 생성한다.

FileReader 메소드

FileReader 메소드는 상위 클래스(java.io.Reader, java.io.OutputStreamReader)를 상속받아 동일한 메소드를 가지고 있고 추가된 메소드는 없다.

FileWriter 생성자

생성자 설명
ileWriter(File file) throws IOException file로 지정된 파일에 대한 출력 스트림을 생성한다.
FileWriter(File file, boolean append) file로 지정된 파일에 append 모드를 설정하여 출력 스트림을 생성한다.
FileWriter(FileDescriptor fd) throws IOException fd로 지정된 FileDescriptor에 대한 출력 스트립을 생성한다.
FileWriter(String fileName) throws IOException fileName로 지정한 경로의 파일에 대한 출력 스트림을 생성한다.
FileWriter(String fileName, boolean append) throws IOException fineName로 지정한 경로의 파일에 append 모드를 설정하여 출력 스트림을 생성한다.

FileWriter 메소드

FileWriter의 메소드는 상위 클래스 (java.io.Writer, java.io.OutputStreamWriter)를 상속받아 동일한 메소드를 가지고 있고 추가된 메소드는 없다.

1.1.16 - Java 입문 | jar 압축풀기

jar 압축을 푸는 예제이다.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

public class JarUtil {

    private static final int BUFFER = 8196;

    public static boolean extract(String jarfilename, String dest) {
        File jarfile = new File(jarfilename);

        if (!jarfile.exists()) {
            System.out.println("Jar File does not exist!");
            return false;
        }

        File destfile = new File(dest);
        if (!destfile.exists()) {
            if (!destfile.mkdir()) {
                System.out.println(dest + " directory make failed.");
                return false;
            }
        }

        boolean result = false;
        JarInputStream jis = null;
        FileOutputStream fos = null;
        try {
            jis = new JarInputStream(new FileInputStream(jarfile));

            File tmpDir = new File(dest + "META-INF");
            tmpDir.mkdir();
            File tmpFile = new File(dest + "META-INF/MANIFEST.MF");
            Manifest manifest = jis.getManifest();
            fos = new FileOutputStream(tmpFile);
            manifest.write(fos);
            fos.close();

            JarEntry entry = null;
            while ((entry = jis.getNextJarEntry()) != null) {
                String destFile = dest + entry.getName();
                if (entry.isDirectory()) {
                    if (!new File(destFile).mkdir())
                        System.out.println(destFile + " directory make failed.");
                    continue;
                }

                fos = new FileOutputStream(dest + entry.getName());
                byte data[] = new byte[BUFFER];
                int count;
                while ((count = jis.read(data, 0, BUFFER)) != -1) {
                    fos.write(data, 0, count);
                }
                System.out.println(destFile);
            }
            result = true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (jis != null)
                    jis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}

1.1.17 - Java 입문 | Lamda 함수

Java 8에서부터 도입된 Lambda에 대해서 알아보자.

Lambda 함수란?

Lambda 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어이다. 람다는 일시적으로 즉시 사용할 수 있기에 버릴수 있는 함수이다.

기본 작성법

(인수) -> {처리}
()-> {처리}
(인수) -> {처리}
(인수) -> {return 0;}

인수와 처리 ->으로 연결하는 것으로 쉽게 Lambda를 사용할 수 있다. 아래 다양한 예제를 통해서 구체적으로 알아보도록 하자.

기본형

Java7 이하 버전에서는 쓰레드 코드를 작성시 아래와 같이 작성을 하였다.

new Thread(new Runnable() {
   @Override
   public void run() { 
      System.out.println("Hello world lambda"); 
   }
}).start();

람다를 사용하게 되면 아래과 같이 코드가 간결해져서 가독성도 훨씬 좋아진 것을 볼 수 있다.

new Thread(()->{
      System.out.println("Hello world lambda"); 
}).start();

List에서의 Lambda

일반 for 문은 forEach함수에 Lambda로 대체할 수 있다.

아래와 같이 List 목록이 있다고 하자.

List<String> stringList = new ArrayList<String>();
stringList.add("apple");
stringList.add("orange");
stringList.add("strawberry");

Java7 이하 버전에서는 아래와 같이 for문을 사용했을 것이다.

for (String string : stringList) {
   System.out.println(string);
}

Java8이상부터는 Lambda를 사용하여 아래와 같이 작성할 수 있다.

stringList.forEach(string -> System.out.println(string));

Lambda는 인수와 처리의 생략이 가능하기에, 가능한 한 생략하는 형태로 작성할 수 있다.

생략 규칙은 다음과 같다.

  • 반환 값이 void이라면, return을 생략할 수 있다.
  • 처리 코드가 한 줄이라면, 중괄호 { }가 없이고 가능하고, 마지막에 세미콜론 ; 도 생략 가능하다.
  • 인수가 1개라면 괄호 ()을 생략할 수 있다.
  • 인수의 데이터 타입은 생략 가능하다. (모두 생략하거나 모든 작성하거나 하는 2가지 방법이 있다.)

이 밖에도 생략 가능한 규칙이 더 존재하지만, 여기서는 모두 언급하지 않겠다.

아래와 같이 메소드 참조를 사용하여 작성할 수도 있다.

stringList.forEach(System.out::println);

메소드 참조를 Lambda와 같이 사용하면, 간결하여 알기 쉬운 작성할 수 있게 된다. 다만 너무 많이 사용하게 되면 오히려 알아 볼수 없게 될수도 있으니 Ojbects::nonnull 라든지 XXXX::getId 등과 같이 명시적인 처리에서만 사용하길 바란다.

배열에서의 Lambda

배열에서도 위에 같이 작성하고 싶은 경우도 있겠지만, 배열에서는 foreach를 사용할 수 없기 때문에 List로 변환한 후 수행해야 한다.

String[] stringArray = { "apple", "orange", "strawberry" };
Arrays.asList(stringArray).forEach(string -> System.out.println(string));
  • 배열에서 Lambda를하는 경우에 Arrays.stream() 메소드를 사용하는 것도 가능하다.

Map에서의 Lambda

Map에서는 그대로 Lambda를 적응하는 것이 가능하다.

아래와 같이 Map 목록이 있다고 하자.

Map<String, String> stringMap = new HashMap<String, String>();
stringMap.put("apple", "red");
stringMap.put("orange", "orange");
stringMap.put("strawberry", "red");

Java7 이하 버전에서는 아래와 같이 for문을 사용했을 것이다.

for (Entry<String, String> entry : stringMap.entrySet()) {
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
}

Java8이상부터는 Lambda를 사용하여 아래와 같이 작성할 수 있다.

stringMap.forEach((key, value) -> {
    System.out.println(key);
    System.out.println(value);
});

함수형 인터페이스

함수형 인터페이스(Functional Interface)는 추상 메서드가 딱 하나만 존재하는 인터페이스를 말한다. 즉, 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할을 한다.

먼저, 함수형 인터페이스를 생성한다.

@FunctionalInterface
public interface Math {
    int calc(int first, int second);
}

인터페이스 메소드를 구현하여 실행해 보도록 하자.

public class MathCalc {

    public static void main(String[] args) {
        Math plus = (first, second) -> first + second;
        System.out.println(plus.calc(3, 2));

        Math minus = (first, second) -> first - second;
        System.out.println(plus.calc(3, 2));
    }
}

실핼 결과:

5
5

함수형 인터페이스 4가지

Java에는 자주 사용하게 될 함수형 인터페이스가 이미 정의되어 제공하고 있으며, 총 4가지 함수형 인터페이스가 존재한다.

함수형 인터페이스 매개 변수 반환값
Supplier<T> X O
Consumer<T> O X
Function<T, R> O O
Predicate<T> O Boolean

Supplier <T>

  • Supplier는 영어로 “공급자"를 의미하며, 매개변수 없이 반환값 만을 갖는 함수형 인터페이스이다.
  • T get()을 추상 메소드로 갖는다.

Supplier 함수형 인터페이스

package java.util.function;

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

사용 예제

Supplier<String> supplier = () -> "Hello World!";
System.out.println(supplier.get());

실행 결과:

Hello World!

Consumer<T>

  • Consumer는 영어로 “소비자"를 의미하며, 객체 T를 매개변수로 받아서 사용하며, 반환값은 없는 함수형 인터페이스이다.
  • void accept(T t)를 추상메소드로 갖는다.
  • 그리고 andThen이라는 함수를 제공하고 있는데, 이를 통해 하나의 함수가 끝난 후 다음 Consumer를 연쇄적으로 이용할 수 있다.

Consumer 함수형 인터페이스

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

사용 예제

Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("Hello World!");

실행 결과:

Hello
Hello World!

위에 예제에서는 먼저 accept로 받아들인 Consumer를 먼저 처리하여 “Hello"가 표시되고, andThen으로 받은 두 번째 Consumer를 처리하여 “Hello World!“가 표시되었다.
여기서 함수형에서 함수는 값의 대입 또는 변경 등이 없기 때문에 첫 번째 Consumer가 split으로 데이터를 변경했더라도 원본의 데이터는 유지된 것을 볼 수 있다.

Function<T, R>

  • Function는 영어로 “함수"를 의미하며, 객체 T를 매개변수로 받아서 처리한 후 R로 반환하는 함수형 인터페이스다.
  • R apply(T t)를 추상메소드로 갖는다.
  • Function은 Consumer와 마찬가지로 andThen을 제공하고 있으며, 추가적으로 compose를 제공하고 있다.
  • 앞에서 andThen은 첫 번째 함수가 실행된 이후에 다음 함수를 연쇄적으로 실행하도록 연결해 주었다면, compose는 첫 번째 함수 실행 이전에 먼저 함수를 실행하여 연쇄적으로 연결해준다는 점에서 차이가 있다.
  • 그리고 identity 함수가 존재하는데, 이는 자기 자신을 반환하는 static 함수이다.

Function 함수형 인터페이스

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

사용 예제

Function<String, Integer> function = str -> str.length();
int length = function.apply("Hello World!");
System.out.println(length);

실행 결과:

12

Predicate<T>

  • Predicate는 “(사실이라고) 단정하다"라는 의미이며, 객체 T를 매개 변수로 받아 처리한 후 Boolean을 반환하는 함수형 인터페이스다.
  • boolean test(T t)을 추상 메서드로 갖고 있다.

Predicate 함수형 인터페이스

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

    @SuppressWarnings("unchecked")
    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

사용 예제

Predicate<String> predicate = (str) -> str.equals("Hello World!");
boolean test = predicate.test("Hello World!");
System.out.println(test);

실행 결과:

true

1.1.18 - Java 입문 | Optional 클래스

Optional는 Java8에서 추가 된 클래스입니다. Optional 클래스는 null를 다룰 때 편리한 클래스이므로, 기본적 작성법을 알아보도록 하자.

Optional란

Optional는 메소드의 반환 값으로 null을 반환 할 수 있음을 명시적으로 표시하고자 할 때 사용한다. 메소드가 null을 반환 할 수 있음을 나타내는 것으로, 그 메소드를 사용하는 프로그램을 보다 안전하게 구현할 수 있도록 한다.

Optional 선언 방법

Optional를 사용하려면 형식을 래핑하여 사용한다.

Optional<String> optinoal;

사용자가 만든 클래스에서도 사용할 수 있다.

import java.math.BigDecimal;

public class Foo {
    private String str;
    private BigDecimal value;
}
Optional<Foo> opt;

Optional 인스턴스 생성

Optional 클래스는 인스턴스 생성의 static 메소드가 제공하고 있어, 이 메소드를 사용해 생성한다.

  • Optional.empty() : 값이 null인 Optional 인스턴스를 생성한다.
  • Optional.of(값) : 인자 값에 대한 Optional 인스턴스 생성한다
  • Optional.ofNullable(값) : 인자 값이 null의 경우는 empty, null가 아닌 경우는 of 메소드의 결과와 동일하다.

Optional.of는 아래와 같이 선언 할수 있다.

Optional<String> opt1 = Optional.of("test");

Optional.of에는 인수가 null의 경우 java.lang.NullPointerException이 발생하므로 주의가 필요하다.

Optional<String> opt1 = Optional.of(null);

그래서 주로 Optional.ofNullable을 사용한다.

Optional<String> opt1 = Optional.ofNullable("test");
Optional<String> opt2 = Optional.ofNullable(null);

Foo test = new Foo();
Optional<Foo> opt3 = Optional.ofNullable(test);

Optional 반환 메소드 사용 방법

Optional 반환 메소드에 대해서 알아보자.

or 메소드 사용법

or 메서드는 Optional.ofNullable 메소드의 인수로 지정된 객체가 null의 경우에만 처리를 실행한다.

import java.util.Optional;

public class Sample {
    public static void main(String[] args) {
        String str = null;
        Optional<String> value = Optional.ofNullable(str);
        System.out.println(value.or(() -> Optional.of("null입니다.")).get());
    }
}

실행 결과는 아래와 같다.

null입니다.

orEsle 메소드 사용법

Optional.ofNullable 메소드의 인수가 null의 경우, orElse 메소드의 인수에 값을 반환한다. null가 아닌 경우는 null이 아닌 값을 반환한다.

import java.util.Optional;

public class Sample {
    public static void main(String[] args) {
        String str = null;
        Optional<String> value = Optional.ofNullable(str);
        str = value.orElse("null입니다.");
        System.out.println(str);
    }
}

실행 결과는 아래와 같다.

null입니다.

orElseGet 메소드 사용법

Optional.ofNullable 메소드의 인수가 null의 경우, orElseGet 메소드의 suppelier 결과를 반환한다. null가 아닌 경우는 null이 아닌 값을 반환한다.

import java.util.Optional;

public class Sample {
    public static void main(String[] args) {
        String str = null;
        Optional<String> value = Optional.ofNullable(str);
        str = value.orElseGet(Sample::getDefaultValue);
        System.out.println(str);
    }
    
    private static String getDefaultValue(){
        return "Default 입니다.";
    }
}

실행 결과는 아래와 같다.

Default 입니다.

orElseThrow 메소드 사용법

orElseThrow 메서드는 인수의 있고 없고에 따라 처리가 다르다. 인수가 없는 경우는 null의 경우 NoSuchElementException를 throw하고, null가 아닌 경우는 값을 돌려 준다.

import java.util.NoSuchElementException;
import java.util.Optional;

public class Sample {
    public static void main(String[] args) {
        String str = null;
        Optional<String> value = Optional.ofNullable(str);
        try {
            str = value.orElseThrow();
            System.out.println(str);
        } catch (NoSuchElementException ex) {
            System.out.println("null입니다.");
        }
    }
}

실행 결과는 아래와 같다.

null입니다.

인수가 있는 경우에는 Throw를 통해 예외 클래스의 객체를 지정할 수 있다.

import java.util.Optional;

public class Sample {
    public static void main(String[] args) {
        String str = null;
        Optional<String> value = Optional.ofNullable(str);
        try {
            System.out.println(value.orElseThrow(() -> new RuntimeException()));
        } catch (RuntimeException e) {
            System.out.println("null입니다.");
        }    
    }
}

실행 결과는 아래와 같다.

null입니다.

ifPresent 메소드 사용법

null가 아닌 경우에만 인수에 지정된 작업을 실행한다.

import java.util.Optional;

public class Sample {
    public static void main(String[] args) {
        String str = null;
        Optional<String> value = Optional.ofNullable(str);
        value.ifPresent(System.out::println);
    }
}

실행을 하면 str가 null이기에 아무것도 처리되지 않는다.

ifPresentOrElse 메소드 사용법

null의 경우에도 처리를 할 수 있다. null가 아닌 경우의 처리를 첫번째 인자에 지정하고, null의 경우의 처리를 두번째 인자에 지정한다.

import java.util.Optional;

public class Sample {
    public static void main(String[] args) {
        String str = null;
        Optional<String> value = Optional.ofNullable(str);
        value.ifPresentOrElse(
            System.out::println, 
            () -> System.out.println("null입니다"));
    }
}

실행 결과는 아래와 같다.

null입니다.

1.1.19 - Java 입문 | Stream API

Stream API이란?

Stream API는 Java SE 8에서 추가된 반복(iteration)의 확장 API이다. 지금까지 컬렉션에 갔다 복잡한 처리를 알기 쉬운 코드로 작성하는 것이 가능하다.
여기에서는 람다 식의 설명은 하지 않기에 람다 식을 모르는 사람은 먼저 Lamda 함수를 먼저 이해하고 확인하고 오길 바란다.

stream은 컬렉션의 요소에 대한 액세스를 허용한 것, 컬렉션의 다른 형태라고 생각된다.

기본적인 흐름

스트림의 구조는 크게 3가지로 나뉜다.

  1. 컬렉션에서 Stream을 생성한다.
  2. stream에 원하는 만큼 “중간 연산"을 실행하고, 컬렉션의 내용을 합해서 변환한다.
  3. “최종 연산"으로 변환한 컬렉션의 내용에 대해 처리를 적용한다.

실제 사용법을 표기하면 Stream생성().중간연산().최종연산()와 같은 식으로 작성한다.

그럼, 실제 사용 예를 살펴 보자. 아래 예제는 “1~5에서 짝수만 표시"하도록 해보자

  • Stream API를 사용하지 않는 경우
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
for (Integer i : list) {
    if (i % 2 == 0) {
        System.out.println(i);
    }
}
  • Stream API를 사용한 경우
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream() // Stream 생성
        .filter(i -> i % 2 == 0) // 중간 연산
        .forEach(i -> System.out.println(i)); // 최종 연산

처리의 차이를 살펴 보도록 하자. Stream API를 사용하지 않는 경우는 “2로 나누어 나머지가 0인 경우에만 표시"하는 구조로 되어 있다. Stream API를 사용한 경우에는 “2로 나누어 떨어지는 것을 모은 요소를 모두 표시하는 구조로 되어 있다.

2개의 처리를 나누는 것이 가능해 졌다. 이로써 가독성 및 부품성이 높아지고, 코드의 추상화도 가능해 진다.

이번 예제는 stream 가져와 중간 연산으로 “filter"메소드를 사용하고 있다. 이름 그대로 컬렉션의 요소를 필터링하는 것이다. filter 메소드는 T -> boolean와 같은 람다 식을 전달해 주고 있다. (※ T의 의미를 모르는 사람은 제네릭에서 확인하길 바란다.) 람다 표현식이 true 인 요소만 남은 stream을 반환한다.

최종 연산으로 forEach 메소드를 사용하고 있다. 이것도 이름도 그대로 “요소를 하나씩 꺼내 오도록 하는 처리를 실행"하는 방법이다. 다른 언어에서 친숙한 구문일 것이다. Java에서의 확장 for 문에 해당한다.

예제는 “2로 나눈 나머지가 0이 되는 것만” 가져오는 중간 연산과 “하나씩 화면에 출력하는” 최종 연산을 나눠진다.

중간 연산 결과는 작업 적용 후에 stream이 반환되므로 예제처럼 연속으로 참조하도록 작성할 수도 있다.

물론, 아래와 같이도 작성 할 수도 있다.

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream(); // Stream 생성
Stream<Integer> stream2 = stream.filter(i -> i % 2 == 0); // 중간 연산
stream2.forEach(i -> System.out.println(i)); // 최종 연산

이어서 작성하는 것이 가독성 좋기에 특별한 이유가 없다면 연속해서 작성하도록 하자.

중간 연산

자주 사용하게 되는 대표적인 중간 연산에 대해서 설명하겠다.

filter

앞의 예제에서 언급 한 바와 같이 필터링하기 위한 중간 연산이다. 인수로는 T -> boolean와 같은 람다 식을 전달한다. 조건식이 true인 요소만 수집한다.

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream() //  Stream 생성
        .filter(i -> i % 2 == 0) // 중간 연산
        .forEach(i -> System.out.println(i)); // 최종 연산

map

map의 인수로는 T -> U와 같은 람다 식을 전달한다. 요소를 변환하는 중간 연산이다.

아래 예제와 같이 값을 2로 곱할 수도 있고,

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream() // Stream 생성
        .map(i -> i * 2) // 중간 연산
        .forEach(i -> System.out.println(i)); // 최종 연산

아래 예제와 같이 Integer로부터 String으로 다른 형식을 반환할 수도 있다.

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream() // Stream 생성
        .map(i -> "요소는" + i + "입니다.") // 중간 연산
        .forEach(i -> System.out.println(i)); // 최종 연산

또한, 다음과 같은 동물 인터페이스가 있다고 했을 때,

interface Animal {
    /**
	 * 울음 소리를 반환한다.
	 */
    String getCry();
}

동물 목록에 map을 사용하여 울음 소리를 출력해 볼 수도 있다.

List<Animal> animalList = Arrays.asList(dog, cat, elephant);
animalList.stream() // Stream 생성
        .map(animal -> animal.getCry()) // 중간 연산
        .forEach(cry -> System.out.println(cry)); // 최종 연산

sorted

sorted의 인수로는 (T, T) -> int와 같은 람다 식을 전달한다. 요소를 정렬 중간 연산이다. 요소를 2개씩 가지고, 람다 식을 전달하고 있다. 반환 값이 양수이면 내림차순이고, 음수이면 오름차순으로 정렬된다.

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream() // Stream 생성
        .sorted((a, b) -> Integer.compare(a, b)) // 중간 연산
        .forEach(i -> System.out.println(i)); // 최종 연산

이보다 더 간단한 정렬(오름차순, 내림차순) 방법으로는아래와 예제와 같이 Comparator 인터페이스 naturalOrder(), reverseOrder()을 넘겨주는 것이 가장 알기 쉽다.

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream() // Stream 생성
        .sorted(Comparator.reverseOrder()) // 중간 연산
        .forEach(i -> System.out.println(i)); // 최종 연산

이상 대표적인 3가지 자주 사용하는 중간 연산에 대해 알아보았다. 그 외에도 여러가지 더 있으므로 관심이 있는 분은 찾아 보길 바란다.

최종 연산

forEach

forEach 인수는(T) -> void와 같이 람다식을 전달 해주고 있다. 요소를 하나씩 꺼내 어떠한 처리를 하는 최종 연산이다. 사용법은 앞에 예제를 참고 바랍니다.

1.2 - Java 단위테스트

JUnit

1.2.1 - JUnit5의 @DisplayName

JUnit5 구성

  • JUnit Platform : JUnit Platform은 JVM에서 테스트 프레임워크를 실행하기 위한 기초적인 역할을 한다. 또한 테스트 개발을 위한 API를 제공한다.
  • JUnit Jupiter : JUnit5 테스트 및 Extension을 만들기 위한 새로운 프로그래밍 모델과 확장 모델의 조합 TestEngine을 제공한다.
  • JUnit Vintage : 하위 호환성을 위해 Junit4, Junit3을 실행할 수 TestEngine이다.

적용 방법

다음은 Gradle을 기준으로 작성되어 있다.

Spring boot 2.2.x 이상

testImplementation("org.springframework.boot:spring-boot-starter-test")
 
test {
    useJUnitPlatform()
}

Spring boot 2.2.x 이전

testImplementation("org.springframework.boot:spring-boot-starter-test") {
     exclude module : 'junit' 
}

testImplementation("org.junit.jupiter:junit-jupiter-api")
testCompile("org.junit.jupiter:junit-jupiter-params")
testRuntime("org.junit.jupiter:junit-jupiter-engine")

test {
     useJUnitPlatform() 
}

Junit5의 @DisplayName를 사용하면 아래와 같이 메소드명을 한글로 작성하는 것이 아니라,

@Test
public void 게시글저장() {
...

아래처럼 표기 할 수 있게 된다.

@Test
@DisplayName("게시글 저장")
public void savePost() {

1.2.2 - JUnit으로 private 메소드 테스트하는 방법

Java 테스트 단위로 Junit을 사용되는데, public 메소드 테스트에 대해서는 테스트 코드를 작성하지만, private, protected 메소드의 테스트는 소홀히 하는 경향있다.

화이트박스 테스트는 프로그래머의 의무를 생각하기에, 모든 코드에 대한 테스트가 필요하다. 그래서 모든 메소드 테스트 실시하는 방법을 설명하겠다.

테스트 소스 코드

먼저 테스트 할 코드를 만들어 보겠다.

Sample.java 파일을 생성하여 아래와 같이 작성하자.

public Sample {
    public int add(int x, int y) {
        return (x + y);
    }

    protected int minus(int x, int y) {
        return (x - y);
    }

    private int multiplication(int x, int y) {
        return (x * y);
    }
}

public 메소드 테스트

그리고 public 메소드 “plus"에 대한 테스트 케이스를 아래와 같이 작성한다.

package com.devkuma.tutorial;

import com.devkuma.tutorial.junit.Calc;
import org.junit.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static org.junit.Assert.assertEquals;

public class CalcTest {

    public void plus() {
        Calc calc = new Calc();
        
        int expected = 7;
        
        int actual = calc.plus(5, 2);

        assertEquals(expected, actual);
    }
}

간단한 덧셈의 테스트이다. 성공하는 예제만 있는 평범한 테스트 코드이다.

protected, private 메소드 테스트

인스턴스에서는 protected, private 메소드를 호출할 수 없기 때문에, 자라 리플렉션(reflection)을 이용하여 테스트 케이스를 작성한다.

참고

Sample sample = new Sample();
Method method = Sample.class.getDeclaredMethod("{메소드명}", {인수1}, {인수2}...);
method.setAccessible(true);
int actual = ({반환하는 })method.invoke(<인스턴스>, {인수1}, {인수2}...);

어느 클래스에도 존재하는 class 변수를 사용하여, getDeclaredMethod() 메소드을 사용하여 테스트 메소드를 가져온다.

반환된 메소드의 setAccessible(true)을 설정한다. 이는 외부에서 액세스 할 수 있도록하기 위한 설정이다. 그렇다면 method.invoke() 메소드로 테스트 메소드를 실행한다.

위에 작성한 코드에 protected, private 메소드인 “minus"와 “multiply” 메소드의 테스트 케이스를 추가로 작성해 보자.

package com.devkuma.tutorial;

import com.devkuma.tutorial.junit.Calc;
import org.junit.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static org.junit.Assert.assertEquals;

public class CalcTest {

    public void plus() {
        Calc calc = new Calc();
        
        int expected = 7;
        
        int actual = calc.plus(5, 2);

        assertEquals(expected, actual);
    }

    @Test
    public void minus() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Calc calc = new Calc();
        
        int expected = 3;

        Method method = Calc.class.getDeclaredMethod("minus", int.class, int.class);
        method.setAccessible(true);
        int actual = (int) method.invoke(calc, 5, 2);

        assertEquals(expected, actual);
    }

    @Test
    public void multiply() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Calc calc = new Calc();

        int expected = 10;

        Method method = Calc.class.getDeclaredMethod("multiply", int.class, int.class);
        method.setAccessible(true);
        int actual = (int) method.invoke(calc, 5, 2);

        assertEquals(expected, actual);
    }
    
}

결론

이제 작성한 메소드는 모든 테스트 할 수 있게 되었다.

프로그래머가 버그는 최대의 적이다. 가능한 버그를 최대한 제거를 하도록 노력 합시다.

1.3 - JSP/Servlet

JSP/Servlet

서버에서 Java 프로그램을 실행시키면 “Server side Java"라고 하며, 그 시작이라고 할 수 있는 것이 “JSP"이며 “서블릿"이다.

1.3.1 - JSP/Servlet | Google App Engine 개발 준비

서버 사이드 Java 및 Google App Engine에 대해 설명한다. 그리고 GAE를 이용하기 위한 준비를 갖추고 실제로 개발을 시작할 수 있도록 한다.

1.3.1.1 - JSP/Servlet | Google App Engine 개발 준비 | 서버 사이드 Java 사용

Web이 진화하고 여러가지 재미있을 것 같은 서비스가 점점 많아지면서 “스스로도 Web 응용 프로그램을 만들어 보고 싶다"고 생각하는 사람은 많아졌다. 단순한 Web 페이지와 달리, 재미있는 서비스와 편리한 서비스를 만들려고 생각하면 아무래도 “서버 사이드 개발"이 필요하다.

서버측 개발은 현재에는 다양한 언어를 사용할 수 있게 되었다. Perl, PHP, Ruby, Python 등 스크립트 언어도 있지만, 역시 어느 정도의 규모가 큰 개발이 되면 「Java」가 독보적이다. Java는 그 밖에도 다양한 현장에서 사용되고 있으며, “미래적으로 생각하면, 역시 Java 인가"라고 생각하는 사람은 분명 많을 것이다.

여러가지 이유로 “Java 서버 개발"을 결정한 것은 좋지만, 도대체 어디에서 서버 사이드 Java를 이용하면 좋은가. 렌탈 서버에서 Java에 지원을 하고 있는 곳은 사실 그리 많지 않다. Perl이나 PHP 등은 Web 서버에 통합 이용되지만, Java는 전용 Java 서버를 세워 운영하지 않으면 안되기 때문에, 렌탈 서버 등에서는 좀처럼 대응할 수 없는 것이다. (지원이 한다고 하더라도 다른 언어에 비해 지불해야 하는 비용이 비싸다)

“Java를 사용하고 싶다"라고 생각해도, 렌탈 서버 등 간편하게 사용할 수 있는 곳에서는 Java를 지원하는 곳이 거의 없다. 그래서 개인 수준에서는 좀처럼 서버 사이드 Java는 사용할 수 없다.

그럼, 그러한 상황 속에서 “Java로 서버를 개발하고 싶다!“라고 생각하는 사람들에게 구세주가 나타났다. 그것이 “Google App Engine (GAE)“이다.

Google App Engine은 Google에서 제공하는 클라우드 서비스이다. 이른바 PaaS (Platform as a Service)라는 것으로, Google의 빅 테이블 또는 Google 계정 등의 기능을 이용하여 응용 프로그램을 만들어 공짜로 시작할 수 있다.

이는 아마도 누구나 공짜로 사용할 수 있는 최초의 서버 사이드 Java 환경일 것이다. 서버 측의 섬세한 설정 등이 필요없이 그냥 Google에서 제공하는 도구를 사용하여 개발하고 배포하면 그대로 Web 응용 프로그램을 만들 것이다. 이런 사용하기 쉬운 서버 사이드 Java 환경은 지금까지 없었다고 해도 과언이 아니다.

앞으로 “서버 사이드 Java를 해보고 싶다"면, GAE는 안성맞춤의 환경이라고 할 수 있다. 모처럼 이런 좋은 환경이 공짜로 사용할 수 있기에 이를 이용하여 서버 사이드 Java를 시작해 보자.

시작하기 전에 준비 사항

그럼 “GAE에서 서버 사이드 Java를 시작하기” 위해서는 어떤 것이 필요한가? 준비해야 할 것을 정리해 보겠다.

JDK

Java 개발 환경이다. 이것은 현재 Java를 소유하고 있는 Oracle 사이트에서 배포되고 있다.

Eclipse

Java 개발 도구이다. 개발 도구는 여러가지 있는데, GAE를 이용한다면 Eclipse를 사용해야 한다. 이것도 개발처인 Eclipse Foundation 사이트에서 무료 배포되고 있다.

SDK/Plugin

GAE for Java SDK(개발 kit와 같은 것)과 Eclpse에서 GAE/Java 개발을 위한 기능을 추가하는 플러그인 등을 준비한다. 이것은 Eclipse를 시작하고 설치할 수 있다.

Google과 GAE 계정

소프트웨어 이외에 필요한 것이 Google 계정과 GAE 계정이다. GAE를 이용하기 때문에, 이것들을 사용할 수 있는 상태가 되어야 한다.

Java SE의 기초 지식

이 밖에 필요한 것은 “Java SE의 지식"이다. 이제부터 서버 사이드 Java의 설명을 하려는데, 기본적으로 “Java의 기본은 알고 있다"라는 전제에서 설명한다. 아직 Java의 기본을 모른다면, 먼저 Java의 기초를 익혀야 한다.

1.3.1.2 - JSP/Servlet | Google App Engine 개발 준비 | Google과 GAE의 준비

이제 순차적으로 준비를 갖춰보도록 하자. 먼저 Google과 GAE의 준비부터 하도록 한다. 먼저 계정 관계 가져 오기부터 설명한다. 우선 다음 주소를 방문해 보자.

https://console.cloud.google.com/appengine

◎ Google 계정으로 로그인되어 있는 경우

이미 Google 계정으로 로그인되어 있다면, “Google 전체에서 하나의 정책"이라고 Google 정책에 대한 설명이 표시되며 OK하면 이어 “Accept New Terms of Service"라는 표시가 나타난다. 여기에서 “accept these terms"라는 체크 박스를 ON으로하고 “Submit"버튼을 눌러 전송한다. 이미 정책 설명에 OK 해 있고, Accept New Terms of Service에서 accept되어 있던 경우에는 갑자기 GAE가 사용할 수 있게 관리 화면이 표시된다.

◎ 로그인하지 않은 계정은 가지고 있는 경우

아직 Google 계정으로 로그인하지 않은 경우에는 로그인 화면에 전환 이동된다. 여기에서 로그인하면, 다음은 동일하다.

◎ 계정을 가지고 있지 않은 경우

아직 Google 계정이없는 경우, 액세스하면 로그인 화면에 전환 이동된다. 이 화면에서 오른쪽 상단에 있는 ‘계정 만들기’라는 버튼을 클릭하면 계정을 만들 수 있는 화면으로 이동한다. 여기에서 Google 계정의 내용과 Google App Engine 계정의 내용이 표시되므로 필요 사항을 작성한다.

1.3.1.3 - JSP/Servlet | Google App Engine 개발 준비 | JDK와 Eclipse 설치

다음 작업은 필요한 소프트웨어를 설치한다. 우선 JDK부터 설치한다.

JDK 설치

뭐, 서버 사이드 Java를 하고자 하는 사람은 이미 일반적인 Java SE의 기초 정도는 공부했기 때문에, JDK는 이미 설치되어 있을 거라 생각된다.

그래도 일단 만약을 위해 간단하게 설명한다. JDK는 다음 주소에서 배포하고 있다.

http://www.oracle.com/technetwork/java/javase/downloads/index.html

이 페이지의 “Java SE Downloads"라는 곳에 2개의아이콘에서 맨 왼쪽의 “Java Platform (JDK) …“라는 아이콘을 클릭한다. 다음에 JDK 목록이 표시된 페이지로 이동한다. 여기에서 사용하는 환경을 맞는 JDK를 클릭하여 다운로드한다. 이때, 목록 위에 있는 “Accept License Agreement"라는 항목을 클릭하여 선택해야 한다.

다운로드된 파일은 인스톨러로 되어 있다. 그대로 더블 클릭하여 실행하여 기본 상태로 그대로 진행하면 설치가 된다. 섬세한 설정은 기본 상태로 하여도 상관 없다.

Eclipse 설치

이어서 Eclipse 이다. 이는 Eclipse Foundation이라는 곳의 사이트에서 배포되고 있다. 다음 주소를 액서스해 보자.

http://eclipse.org/downloads/

여기에서 Eclipse를 다운로드할 수 있다. 예전에는 패키지 별로 다운로드를 받아야 했는데, 최근에 설치 방법이 인스톨러로 변경되었다. 다운로드 받은 파일을 더블 클릭하면 Eclipse의 여러 종류의 항목이 표시가 된다. 이것은 Eclipse를 기반으로 각종 개발에 필요한 플러그인을 통합 이미 구성되어 것이다. 여기서에서 서버 사이드 Java 개발을 할 경우, 항목 중에 “Eclipse IDE for Java EE Developers"를 선택한다.

설치가 끝난 이후에는 설치된 폴더 안에 있는 “eclipse.exe"을 더블 클릭하면 Eclipse가 시작된다.

이제 기동하면, 최초에 “Select a directory as workspace"라는 대화 상자가 나타난다. 이는 설정 정보나 만드는 프로젝트 등을 보관하는 장소이다. 기본적으로 홈 디렉토리에 폴더를 만들고 거기에 작업 공간을 설정한. 특별한 이유가 없으면 기본값을 그대로 해도 상관 없다.

시작할 때 “Eclipse Java EE IDE for Web Developers"이라고 표시된 화면이 나타난다. 이는 “Welcome 화면"이라는 시작할 때 처음 나타나는 것으로, 특히 사용할 필요는 없다. 표시 영역의 왼쪽 상단에 있는 “Welcome"라고 표시된 작은 탭의 ×를 클릭하여 닫으면 일반 개발 화면이다.

1.3.1.4 - JSP/Servlet | Google App Engine 개발 준비 | SDK 및 Google 플러그인 설치

무사히 Eclipse가 시작되었다면, 다음 작업으로 GAE의 개발에 필요한 소프트웨어를 Eclipse에 설치하는 작업이다. Eclipse는 다양한 확장 기능을 “플러그인"으로 설치하고 기능을 추가 할 수 있다. GAE의 개발 기능는 플러그인을 사용하여 통합한다.

  1. Eclipse의 [Help] 메뉴에서 [Install New Software …]를 선택한다.

  2. 대화 상자가 나타나고 “Available Software"라는 표시가 된다. 여기에서 “Work with"입력 필드에 Eclipse 버전에 맞는 플러그인의 URL 주소를 작성하고 Enter 키를 누른다.

Eclipse version Installation instructions Direct plugin link
Eclipse 4.6 (Neon) Plugin for Eclipse 4.6 (Neon) https://dl.google.com/eclipse/plugin/4.6
Eclipse 4.5 (Mars) Plugin for Eclipse 4.5 (Mars) https://dl.google.com/eclipse/plugin/4.5
Eclipse 4.4 (Luna) Plugin for Eclipse 4.4 (Luna) https://dl.google.com/eclipse/plugin/4.4
  1. 잠시 기다리고 있으면 아래 목록 필드에 Google에서 제공하는 플러그인과 SDK의 목록이 표시된다. 여기에서 Android 항목만 빼고 모두 체크 ON 한다. 그리고 체크하면 설치가 오래 걸리므로 아래에 “Contact all update sites during install to find required"를 체크 OFF로 한다. 그리고 “Next>“버튼을 누른다. 다른 체크 등은 기본 상태로 둔다.

Available Software

  1. 계속 진행하면 “Install Detail"라는 표시가 나타납니다. 여기에서 설치하는 플러그인이 나열된다. 항목을 확인하고 아래의 “Next>“버튼을 눌러 계속 진행한다.

Install Detail

  1. “Review Licenses” 화면으로 이동한다. 이는 라이센스 확인한다. 오른쪽 하단 근처에 있는 “I accept the terms of licens agreements"이라는 라디오 단추를 클릭하여 선택하고 “Finish"버튼을 누른다. 이제 대화 상자가 닫히고 설치가 시작된다.

Review Licenses

  1. 잠시 다운로드 작업이 계속되고, 그것이 끝나면 화면에 “Security Warning"라는 경고가 표시된다. 이것은 “unsigned 콘텐츠를 설치하려고 하고 있지만 괜찮나?라고 묻는 경고이다. 여기서 “OK"버튼을 누르면, 설치가 계속된다.

다운로드 작업

Security Warning

  1. 조금 기다리면 설치가 완료하고 “다시 시작하지 않으면 사용할 수 있게 되지 않지만 괜찮나?“라고 묻는다. “Restart Now"버튼을 눌러 Eclipse를 다시 시작한다. 이제 플러그인을 사용할 수 있게 된다.

Restart Now

 

자, 이제 개발에 들어갈 준비가 되었다!

1.3.2 - JSP/Servlet | GAE 애플리케이션 생성

개발 준비가 되면 드디어 Eclipse에서 GAE 애플리케이션을 만들어 보자. 그리고 GAE 사이트에 배포하여 동작시켜 보자.

1.3.2.1 - JSP/Servlet | GAE 애플리케이션 생성 | GAE에 애플리케이션 준비

그럼, 실제로 Eclipse를 사용하여 Google App Engine에서 움직이는 Web 어플리케이션의 개발을 진행해 보자. 우선은 Web 어플리케이션의 기반이 되는 것을 만들어 실제로 GAE로 움직여 보는 정도만 해보도록 하자.

우선 Eclipse에서 개발에 들어가기 전에, GAE의 사이트에서 응용 프로그램의 등록을 해 둔다. GAE 사이트에서는 미리 응용 프로그램을 등록해 두고, 해당 응용 프로그램에 대해 Eclipse에서 개발한 Web 응용 프로그램을 배포(프로그램을 서버에 올려 공개하는 작업)을 하고 있다. 즉, GAE 사이트의 응용 프로그램은 “실제 Web 어플리케이션을 배치 위치” 같은 것으로 생각하면 좋을 것이다. 미리 Web 응용 프로그램을 게시할 장소를 확보 해두고, 거기에 배치하는 것이다.

https://console.cloud.google.com/appengine

그럼, 위의 주소(GAE 사이트)에 액세스 하자. 이 페이지는 응용 프로그램의 관리를 하는 페이지이다. 상단 프로젝트 목록 아이콘을 누르면, 거기에 자신이 등록한 응용 프로그램의 목록이 표시된다.

거기에서 플러스(+) 버튼을 누르면, 응용 프로그램의 등록 화면으로 이동한다. 여기에서 다음을 입력하여 등록한다.

Add Application

  • 프로젝트 이름 : 콘솔에서 프로젝트를 알아볼 수 있는 이름을 입력한다.
  • 프로젝트 ID : 프로젝트 ID는 프로젝트의 전역적 고유 식별자이다. 프로젝트를 만든 이후에는 프로젝트 ID를 변경할 수 없다.

GAE는 최대 10개까지 무료로 앱을 만들 수 있기 때문에, 필요하게 되면 또 새로운 앱을 등록하면 된다.

등록 앱 이름은 무엇이든 상관 없다. 단, GAE에서 만드는 응용 프로그램은 모두 다른 이름으로 해야 하기에 스스로 적당하다고 생각하는 이름으로 정해 둔다.

1.3.2.2 - JSP/Servlet | GAE 애플리케이션 생성 | 프로젝트 만들기

자, 드디어 Eclipse에서 Web 응용 프로그램을 만드는 작업에 들어간다. Eclipse에서는 프로그램의 개발을 할 때 먼저 “프로젝트"라는 것을 만든다. 이를 만들어 보도록 하자.

프로젝트라는 것은 만드는 프로그램에 필요한 다양한 것을 정리하고 관리하는 것이다. 프로그램 작성을 할 시에는 많은 파일을 만들어 지고, 다양한 설정 등을 하거나 필요한 라이브러리를 등록하거나 여러가지 정보를 관리를 해야 한다. 그런 것들을 곳곳에 기록하고 개발자가 직접 관리하는 것은 힘든 때문에 “프로젝트"의 형태로 모두를 일괄 관리할 수 있는 것이다. 그럼, 또 절차대로 작업을 해보록 하자.

  1. [File] 메뉴의 [New] 서브 메뉴에서 [Project …]를 선택한다.

  2. “Select a wizard"라는 대화 상자가 나타난다. 여기에서 만드는 프로젝트 유형을 선택한다. GAE에서 Java 개발을 할 경우 ‘Google’이라는 폴더에 있는 “Web Application Project"라는 항목을 사용하기에 이를 선택하고 “Next>“버튼을 클릭한다.

  3. “Create a Web Application Project"라는 화면이 표시된다. 여기에서 만드는 프로젝트를 설정한다. 이는 프로젝트 작성에 있어서 중요하므로 잘 내용을 확인하고 설정한다.

  • Project name : 프로젝트의 이름이다. 이것은 앞에서 GAE 사이트에서 만든 응용 프로그램의 이름과 관련이 없다. 하고 싶은 이름으로 해도 된다. 여기에서는 “MyGaeApp"으로 한다.

  • Package : 이것은 생성하는 Java 클래스에서 사용하는 패키지를 지정한다. 이것은 각각 적당히 설정될 수 있지만, 일단 여기서는 “com.devkuma.mygaeapp"으로 한다.

  • Location : 프로젝트를 배치하는 위치 설정이다. 기본적으로 “Create new project in workspace"라디오 버튼이 선택되어 있다. 작업 영역(workspace)에 프로젝트를 저장하는 것이다. 이는 그대로 두기로 한다

  • Google SDKs

    • Use GWT : 이는 Google에서 제공하는 “GWT(Google Web Toolkit)“라는 프레임워크를 사용할지 여부를 지정한다. 이것을 ON으로 하면 GWT를 사용하는 응용 프로그램이다. GWT는 Ajax 기반으로 서버간의 상호 작용을 하는 것으로, 파일이나 프로그램의 구성도 복잡하게 사용하려면 일정 수준 이상의 지식이 요구된다. 여기에서는 서버 사이드 Java의 기초를 공부하는 것이 목적이기 때문에, 여기에서는 체크 상자를 “OFF"로 하고, GWT를 사용하지 않도록 한다.

    • Use Google App Engine : 이는 “Google App Engine"을 이용하기 위한 것이다. 체크를 ON으로 하면, GAE를 사용하는 형태로 프로젝트가 생성된다. 이것은 반드시 “ON"으로 한다.

    • Use default SDK : “Use Google App Engine"을 ON으로 하면 그 아래에 이 라디오 버튼이 표시된다. 이것은 기본적으로 설정되어있는 SDK를 사용하는 것이다. 이것은 그대로 둔다.

  • Identifiers for Google App Engine : 이는 GAE 사이트 프로젝트 식별자 즉, 아이디를 넣은 것인데 일단 “Leave Project ID field Blank"인 채로 놔둔다.

  • Sample Code : 처음부터 샘플로 일부 소스 코드 및 파일 등을 준비해두기 위한 것이다. “Generate project sample code"를 선택하면 파일이 생성된다. 이것은 “ON"상태로 유지 좋을 것이다.

이상, 설정을 다하고 “Finish"버튼을 누르면 프로젝트가 생성된다.

GAE Eclipse

1.3.2.3 - JSP/Servlet | GAE 애플리케이션 생성 | 프로젝트의 구성 이해

프로젝트를 만들면 윈도우의 왼쪽에 있는 수직 공간에 “MyGaeApp"라는 폴더가 생성된다. 이 폴더의 왼쪽 ▽ 마크를 클릭하여 확장 보면 그 안에 여러가지 파일이나 폴더가 생성되어 있는 것을 확인 할 수 있다. 이 “MyGaeApp” 폴더가 생성한 프로젝트이다. 이 폴더에 있는 파일 등이 이번 만들 GAE 용 Web 응용 프로그램에 필요한 모든 기능이다.

이 왼쪽 길쭉한 지역은 “Project Explorer"라는 뷰이다. “뷰"라는 것은, Eclipse의 화면에 일부 배치되는 부품과 같다. Eclipse의 화면을 보면 몇 가지 사각형 영역의 조합으로 화면이 구성되어 있는지 알 수 있다. 이 하나 하나의 영역이 “뷰"이다. Eclipse는 개발의 내용이나 상황 등에 따라 필요한 뷰를 화면에 배치하여 작업을 진행하게 되어있는 것이다. 기본적으로 일반적인 서버 사이드 Java 개발에 필요한 뷰를 사용할 수 있도록 배치되어 있는 것이다.

왼쪽에 있는 Project Explorer라는 뷰는 프로젝트 내의 파일이나 라이브러리 등의 내용을 표시하고 관리하는 것이다. 여기에서 파일이나 폴더를 정리하고 두 번 클릭하여 파일을 열고 편집할 수 있다.

그럼 우선 이 Project Explorer에서 “MyGaeApp"폴더를 열고 안에 있는 항목을 살펴 보자. 다음과 같은 것이 있어야 한다.

MyGaeApp

  • src 폴더 : Java 소스 코드 파일을 배치하는 곳이다. 이를 확장하면 “com.devkuma.mygaeapp"라는 폴더가 나타나고, 그것을 더 확장하면 “MyGaeAppServlet.java"라는 Java 소스 코드 파일이 있을 것이다. 이는 기본적으로 자동 생성된 샘플 코드이다. 지금은 사용하지 않지만, “Java 클래스를 만들 때 여기에 소스 코드를 배치한다"는 것만은 기억하자.

  • App Engine SDK : 이것은 프로젝트에서 사용하는 GAE SDK의 라이브러리이다. GAE에는 Google 서비스를 이용하기 위해 독자적으로 제공하는 클래스가 많이 있다. 그것들을 사용할 수 있도록 준비된 라이브러리이다. 이것 자체를 무언가 조작하여 이용하는 일은 없다. GAE 프로젝트라면 자동으로 포함되므로 것으로 따로 만지지 않는다.

  • JRE System Library : 이것은 Java 시스템 라이브러리이다. Java의 기본이 되는 라이브러리이며 이것이 준비되어 있지 않으면 Java의 주요 기능은 사용할 수 없게 된다. Java 기반 프로젝트에 자동으로 포함되므로 이도 따로 만지지 않는다.

  • war 폴더 : 이것이 실제로 서버에 전개된 폴더이다. HTML과 스타일 시트, 이미지, 스크립트 등 Web에서 사용하는 파일들은 이 폴더에 넣어 둔다.

프로젝트에 우선 사용하는 것은 “war"폴더이다. 여기에 HTML 파일 등을 배치하고 서버에 접속하는 것이다. 이어서 Java 프로그램을 작성하게 되면 “src"폴더를 이용한다. 다른 두 라이브러리는 정말 아무것도 조작하지 않는다.

war 폴더

실제로 Web 어플리케이션으로 서버에 설치되는 것은 “war"폴더이다. 여기에는 기본적으로 다양한 폴더와 파일이 생성되어 있다. 이것들의 역할에 대해서 정리해 보겠다.

  • index.html : 기본적으로 샘플 파일로 생성되는 HTML 파일이다. Web 어플리케이션의 주소로 접속하면 이 파일이 가장 먼저 표시된다. 이 파일의 내용을 수정을 하여 첫 페이지를 작성할 수 있다.

  • favicon.ico : 응용 프로그램 아이콘 파일이다. 기본적으로 GAE의 작은 마크가 저장되어 있다. 나름대로 만든 이미지로 교체하여도 괜찮다.

  • WEB-INF 폴더 : 이는 Web 응용 프로그램 폴더에서 “공개되지 않은 폴더"이다. 이 속에 넣은 파일은 서버에 게시하고 액세스할 수 없다. 여기에 Web 어플리케이션의 중요한 정보가 저장된다.

  • lib 폴더 : WEB-INF에 있는 폴더이다. 이 안에는 Web 응용 프로그램에서 사용하는 다양한 라이브러리 파일들이 저장되어 있다. 기본적으로 GAE 관련 라이브러리가 포함된다. 물론 뭔가 라이브러리를 필요하면 여기에 파일을 추가하여 이용할 수 있다.

  • appengine-web.xml : GAE의 Web 어플리케이션에 관한 모든 설정을 작성한 XML 파일이다. 이 파일은 GAE 특유의 것으로, 다른 일반적인 Java의 Web 응용 프로그램은 없다. 여기에서 여러가지 설정을 할 수 있다.

  • logging.properties : 이 파일은 GAE 로그 설정 정보를 작성한다. 이 파일도 GAE 이외의 일반적인 Web 응용 프로그램은 없다. 이것은 편집하는 일은 거의 없을 거다.

  • web.xml : Web 어플리케이션에 대한 각종 정보를 작성하는 XML 파일이다. 이 파일은 GAE뿐만 아니라 일반적인 Java의 Web 응용 프로그램에서 사용할 수 있다.

대충 정리하면 서버 사이드 Java에 의해 Web 응용 프로그램은 다음과 같이 구성되어 있다.

  • 배포하는 응용 프로그램 폴더에 HTML 파일이 보통으로 배치된다. 이는 일반적인 Web과 동일하다.

  • 그 안에 “WEB-INF"라는 폴더가 있다. 이는 서버 사이드 Java 특성으로, 이 폴더는 외부에서 접근이 되지 않는다

  • WEB-INF 안에는 Web 응용 프로그램의 정보를 작성한 “web.xml"와 라이브러리를 배치하는 “lib"라는 폴더가 있다. (일반적인 Java의 Web 어플리케이션에서는, Java 프로그램인 클래스 파일도 여기에 저장된다)

1.3.2.4 - JSP/Servlet | GAE 애플리케이션 생성 | Web 응용 프로그램 배포

그럼 작성한 프로젝트를 GAE 서버에 배포해 보자. 아직 아무것도 만들지 않지만 샘플 파일이 포함되어 있기 때문에, 일단 이대로 배포를 하여도 동작은 한다.

먼저 프로젝트를 GAE에 설치한 응용 프로그램에 배포하는데 필요한 설정을 해 둔다. “WEB-INF"에 있는 “appengine-web.xml"를 두 번 클릭하여 연다. XML 편집기 (XML 데이터를 편집하는 전용 편집기)에서 파일이 열린다. 이 XML 편집기는 XML 태그 목록 내용을 편집할 수 있도록 되어 있다.

태그 목록은 왼쪽에는 항목의 이름이 표시되고, 오른쪽에는 그 값이 표시된다. 이 오른쪽 값 부분을 더블 클릭하면 값을 편집 할 수 있다. 수정을 한 후에는 [File] 메뉴의 [Save]로 저장한다. 혹은 단축키 Ctrl+S로 저장할 수 있다.

이 파일을 열면 안에 다음 항목이 있을 것이다. 이를 설정을 하여라.

  • application : 이것은 배포하는 GAE 응용 프로그램 이름을 지정한다. 먼저, GAE의 사이트에서 작성했었던 “프로젝트 ID” 를 여기에 입력한다.

만약 XML 편집기가 잘 작동하지 않는 경우는 편집기 영역 아래에있는 ‘Design’, ‘Source’라는 탭에서 “Source"를 클릭하여 선택한다. XML 소스 코드를 직접 텍스트 편집기로 편집할 수 있다. 여기서 시작 부분에 적혀 있는 다음 부분을 수정한다.

<? xml version = "1.0"encoding = "utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application> ... 여기에 프로젝트 ID를 쓴다 ... </application>
    <version>1</version>
    ...... 이하 생략 ......

여기서 <application> ~ </application> 태그 사이에 “프로젝트 ID"를 작성하면 된다.

배치 실행

XML 파일의 수정을 하였다면, 프로젝트를 배포를 해보자. 다음 단계에 따라 실행하면 된다.

  1. Package Explorer에서 프로젝트 폴더 (“MyGaeApp"폴더)를 클릭하여 선택한다.

  2. 메뉴 아래에 있는 도구바의 왼쪽에서 4번째 아이콘 (파란 동그라미 안에, Google의 ‘g’가 그려져 있는 녀석)를 클릭하면, 메뉴가 팝업 표시된다. 이 중에서 [Deploy to App Engine …] 항목을 선택한다.

  3. 화면에 Google 계정의 로그인 화면이 나타난다 (아직 Eclipse에서 로그인하지 않은 경우). 여기에서 GAE 계정으로 등록되어 있는 이메일 주소와 비밀번호를 입력하고 “로그인"을 클릭하여 로그인한다.

  4. “Google Plugin for Eclipse가 다음과 같은 권한을 요청하고 있다"라는 표시가 나타납니다. “허용"버튼을 누른다.

  • ※ 이 1 ~ 4의 부분은 Eclipse에서 Google에 로그인하고 있으면, 이후는 표시되지 않는다.
  1. “Delpoy"라는 대화 상자가 나타난다. 여기에서 배포하는 프로젝트를 지정한다. 여기에서는 “MyGaeApp"이라고 되어있는 것이므로, 그대로"Deploy “버튼을 누른다. 만약 프로젝트가 설정되어 있지 않으면, 오른쪽의 “Browse …“버튼을 눌러 프로젝트를 선택한다.

  2. 배포가 실행되면, 화면에 대화 상자가 나타나 진행 상황을 진행률 표시 줄에 표시된다. 대화 상자가 사라지면 무사 배포가 종료되면, 웹페이지가 열린다.

그럼 배포되면 GAE 사이트 (https://console.cloud.google.com/appengine/)에 액세스한다. 상단에서 해당 프로젝트를 선택하고 [대시 보드]를 선택하면 버전은 “1"로 표시가 되어 있고, 오른쪽 편에 URL(예 : 프로젝트아이디.appspot.com)이 표시되어 있다. 이 URL을 클릭하면 Web 응용 프로그램에 액세스되고, “Hello App Engine!“라고 표시된 페이지가 나타나면 배포 성공이다!

배포된 주소를 살펴보면 다음과 같이 되어 있는 것을 알 수 있다.

http://프로젝트아이디.appspot.com

GAE의 응용 프로그램은 모두 이런 식으로 주소가 할당된다. 물론 보통의 Web 사이트처럼 공개되어 누구나 액세스할 수 있다. 아직 단지 샘플이기 때문에 공개해도 의미는 없지만, 지금부터 공부해서 나름대로의 Web 어플리케이션이 된다면, 공개하면 많은 사람이 사용해 줄 것이다.

1.3.3 - JSP/Servlet | JSP(Java Server Pages)

서버 사이드 Java의 기본 중의 기본이라고 할 수 있는 것은 “JSP"이다. 이는 HTML에 특수 태그를 사용하여 Java 코드를 포함할 수 있다. 우선 JSP를 사용하여 서버 사이드 Java를 움직여 보자.

1.3.3.1 - JSP/Servlet | JSP(Java Server Pages) | Java Server Pages 생성

“서버에서 Java를 동작시킨다"라고 하면 아무래도 개념적으로는 모르다고 해도, 보통의 Java보다 월등히 어려울 것"이라고 생각할 수 있다. 뭐, 본격적으로 Java 서버 개발을 하려고 하면 나름대로 고급 지식이 필요하긴 하지만, “고급 지식이 없으면 서버 사이드 Java는 사용할 수 없다 “라는 것은 아니다.

Java는 “서버에서 쉽게 Java 코드를 실행하는 방법"이라는 것이 포함되어 있다. 그것이 “JSP(Java Server Pages)“라는 것이다.

JSP는 최근 서버 개발에 많이 사용되는 PHP와 같은 느낌으로 간편하게 Java 코드를 실행할 수 있는 기술이다. JSP는 “Java 소스 코드를 작성하여 컴파일하고 ……“와 같은 작업은 필요없다. 동작 원리는 브라우저에 표시되는 HTML 태그 내에 Java 코드를 작성하고 실행을 하는 것이다.

아무튼, 이는 우선 실제로 해보는 편이 훨씬 알기 쉬우므로 어쨌든 JSP 파일을 만들어 움직여 보기로 하자.

  1. [File] 메뉴의 [New]에서 [Other …]라는 항목을 선택한다.

  2. 화면에 “Select a wizard"라는 대화 상자가 나타난다. 여기에서 만들 항목을 선택한다. 목록에서 “Web"라는 곳에 있는 “JSP File"항목을 선택하고 다음 진행한다.

  3. “JSP Create a new JSP File"화면으로 이동한다. 여기에서 만들 파일의 위치와 파일 이름을 입력한다. 배치 위치는 “MyGaeApp/war"를 입력한다 (아래에 있는 프로젝트 “MyGaeApp"아이콘을 확장하고 그 안에 있는 “war"폴더를 클릭하면 설정된다). 또한 파일 이름은 여기에서 “helo.jsp"라고 한다. JSP 파일은 이처럼 “.jsp"확장자를 지정한다.

  4. 그러고 “Next>” 버튼을 눌려서 다음 진행을 하면 “Select JSP Template” 화면이 나타난다. 여기에서 사용하는 템플릿을 선택한다. 여기에서 목록의 위에서 4 번째에있는 “New JSP File (html) ‘라는 항목을 선택한다. 이것이 가장 일반적인 JSP 작성 방법이다. (뭐 다른 것을 선택해도 나중에 소스 코드를 편집하면 어떻게든 되기 때문에, 사실 뭐든지 상관 없지만…)

이제 “Finish"버튼으로 종료하면 “war"폴더에 “helo.jsp"라고 JSP 파일이 생성된다. 만약 실수로 다른 곳에 생긴 경우에는 그대로 파일의 아이콘을 드래그하여 “war"폴더에 넣어 버리면 된다.

JSP 파일 편집

먼저 작성한 JSP를 편집하여 페이지를 만들어 보자. 먼저 페이지의 인코딩을 UTF-8로 변경해야 한다.

Project Explorer에서 “helo.jsp"를 선택하고 [File] 메뉴 중에서 [Properties] 를 선택한다. 화면에 helo.jsp 파일 설정 창이 나타난다.

이 윈도우는 왼쪽에 설정 항목이 나열되어 표시되고, 여기에서 항목을 선택하면 그 구체적인 구성 내용이 오른쪽에 표시되도록 되어 있다. 이 목록에서 “Resource"를 선택한다(아마도 기본적으로 상단에 이 메뉴가 선택되어 있을 것이다).

화면에 파일에 대한 속성 등의 설정이 표시된다. 여기에서 하단에 있는 ‘Text file encoding’라는 곳을 보면, “Default” 라디오 버튼이 선택되어 있을 것이다. 이를 “Other"로 변경하고 오른쪽의 콤보 박스에서 “UTF-8"을 선택한다. 이것으로 “OK” 버튼을 눌러 대화 상자를 닫으면 파일의 인코딩이 변경된다.

properties_for_file

모든 소스 코드의 엔코딩의 “Default"를 변경할 수도 있다. 먼저 환경설정을 누르고 [Preferences] 화면이 나타나면, 왼쪽에 [Content Types]을 선택하고 [JSP]항목을 선택한다. 여기에 파일의 ‘Default encoding’을 UTF-8로 변경해주면 기본 엔코딩이 UTF-8로 변경된다.

preference

JSP 태그를 사용해 보자

그럼 helo.jsp을 더블 클릭하여 열어서 소스 코드를 수정하자. 기본적으로 간단한 코드가 적혀 있지만, 이는 “UTF-8"으로 되어 있지 않으므로 곳곳 수정해야 한다. 또한 실제로 표시되는 내용은 아무것도 없기 때문에 뭔가 적당히 작성해 보도록 하자.

아래와 같이 간단히 예제 코드를 작성해 보자.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <p><%= java.util.Calendar.getInstance().getTime() %></p>
</body>
</html>

우선은 이대로 수정하고 저장한다. 여기에서는 낯선 태그가 두개가 등장한다. 다음과 같다.

page 지시문

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

맨 처음에 있는 것은 <%@ ... %> 형태의 태그이다. 이 <%@으로 시작하여 %>로 끝나는 태그는 “지시문(directive)“라고 한다. 이것 자체는 Java 코드가 아닌 페이지에 관한 세부적인 지정을 기술하는데 사용한다.

여기에서는 직후에 “page"가 있다. 이는 “page 지시어"라고 하는데, 페이지의 여러 설정을 기술하는 것이다. 여기에서는 language, contentType, pageEncoding와 같은 속성이 있는데, 이는 각각 language는 사용 “언어"를 뜻하고, contentType은 “콘텐츠 형식"으로써 Http응답의 문자인코딩 방식을 의미한다. 이는 브러우저가 받는 응답의 헤더로 설정된다. pageEncoding은 JSP 페이지 자체의 페이지의 인코딩을 의미한다. 아무튼, 이는 반드시 “이 태그를 넣어야 한다"는 것을 기억해 두도록 하자.

<%= %> 태그

<%= java.util.Calendar.getInstance().getTime() %>

이 부분이 실제로 Java 코드를 실행하는 부분이다. <%=으로 시작해서 %>로 끝나는 태그는 등호(=) 다음에 작성된 Java 코드를 실행하고 그 결과를 그 자리에 써서 내보내는 동작을 한다. 여기에서는 java.util.Calendar.getInstance().getTime()의 결과를 페이지에 출력하고 있다.

실제로 확인해 보면 알 수 있듯이, 이것은 Calendar.getInstance().getTime()으로는 동작하지 않는다. java.util.Calendar …… 와 같이 패키지로 제대로 지정하지 않으면 안된다. (물론 패키지를 지정하는 방법도 있다. 이것은 나중에 설명한다)

1.3.3.2 - JSP/Servlet | JSP(Java Server Pages) | JSP 실행하기

그럼 프로젝트를 배포하고 동작을 확인해야 하겠지만, 매번 조금 수정할 때마다 배포를 한다면 느리기도 하고 귀찮을 것이다. 그래서 로컬 환경에서 프로젝트의 동작 확인을 하는 방법을 설명한다.

  1. Project Explorer에서 실행하는 프로젝트의 폴더( “MyGaeApp"폴더)를 선택한다.

  2. [Run]메뉴의 [Run As …]의 하위 메뉴에서 “Web Application"을 선택한다. 이것으로 내장 Java 서버가 실행되고 프로젝트가 그 곳에서 실행된다.

내장 서버가 시작되면 “http://localhost:8888/hello.jsp” 주소를 방문하여 본다. 만든 hello.jsp가 표시된다. <%= %>으로 이미 기술해 놓은 현재의 시간 값이 표시되는가? 이는 서버 측에서 <%= java.util.Calendar.getInstance().getTime()%>의 문장이 실행되어 그 결과(날짜 텍스트)가 표시되는 거다.

이런 식으로 내부 서버는 도메인에 “localhost:8888"으로 지정하여 액세스하면 프로젝트의 Web 응용 프로그램에 액세스 할 수 있다. “war"에 배치된 파일 종류도 다음에 파일 이름을 지정해 액세스할 수 있다.

기동된 서버를 종료하려면 아래쪽에 있는 가로 “Console"뷰를 선택하고 상단에 보이는 붉은 ■ 마크 버튼을 클릭한다. 이제 서버가 중지된다.

■ 빨간 × 마크가 표시된다면?

경우에 따라서는 프로젝트 폴더와 “war"폴더에 빨간 × 아이콘이 표시되어 프로젝트를 수행할 수 없어 곤란한 빠진 사람도 있을지도 모른다. war.xml과 appengine-web.xml을 무언가를 잘못 썼다던가, 프로젝트의 라이브러리를 마음대로 삭제해 버린 것이 아니라면 원인은 아마도 “JDK 대신 JRE를 사용하고 있기” 때문이다. JDK는 Java 프로그램을 빌드하는 것이다. 이에 비해 JRE는 프로그램을 실행하는 것 뿐이다. 프로젝트를 빌드하는데 JRE를 사용하게 되면 빌드할 수 없기 때문에 오류가 발생한다.

화면 하단의 “Markers"라는 뷰를 선택해 본다. 여기에 발생한 오류의 내용이 표시된다. 여기에 “Your project must be configured to use a JDK in order to use JSPs"라고 오류 메시지가 적혀 있으면, 이 “JRE를 사용하고 있기"때문이다. 다음과 같이 설정을 변경한다.

  1. [Window] 메뉴에서 하단의 [Preferences]를 선택한다. (맥에서는 [Eclipse] 메뉴에서 환경 설정을 선택한다.

  2. 화면에 Eclipse의 각종 설정할 창이 나타난다. 이 윈도우의 왼쪽에 설정 항목이 계층적 목록으로 표시되어 있다. 이 중에서 「Java」항목에 있는 “Installed JREs"라는 항목을 찾아 클릭한다.

  3. 설치되어 있는 JRE/JDK가 나열된다 (아마, 보통, 기본적으로 1개뿐). 현재 사용중인 항목은 체크가 ON으로 되어 있다. 이것이 JRE라고 프로젝트의 빌드가 잘되지 않고 오류 표시가 나타날 것이다.

  4. 만약 아직 JDK 설정이 없었다면 작성한다. “Add …“버튼을 클릭한다. 화면에 JDK 추가 대화 상자가 나타난다.

  5. “Installed JDK Types"표시가 나타난다. 이것은 기본적으로 선택되어있는 “Standard VM"을 선택한다.

  6. “JRE Definition"이라는 표시로 이동한다. 여기에서 JDK를 설정한다. 먼저 상단의 “JRE Home:“라는 항목의 오른쪽에 있는 “Directory …“버튼을 누른다. JRE/JDK 폴더를 선택하는 대화 상자가 나타난다. 여기에서 설치되어 있는 JDK 폴더를 선택하고 OK한다. JRE Home에 JDK의 경로가 설정되면 동시에 다른 항목도 자동으로 설정된다.

  7. 그대로 “Finish"버튼을 눌러 대화 상자를 종료한다. 이제 추가 한 JDK가 항목에 표시되게 된다.

  8. 새로 추가한 JDK의 체크를 ON으로하여 대화의 “OK"버튼을 눌러 닫는다.

이제 JDK를 사용하여 프로젝트가 빌드하려 고치면, 오류의 × 표시가 사라진다. 그 후는 그대로 정상적으로 프로젝트를 실행하거나 배포할 수 있다.

HTTP ERROR 500 가 발생한다면?

혹은 프로젝트를 수행은 되었으나 화면을 표시를 시도하면 아래와 같이 에러가 발생하고,

...생략...

Caused by:

org.apache.jasper.JasperException: Unable to compile class for JSP: 

An error occurred at line: 1 in the generated java file
The type java.io.ObjectInputStream cannot be resolved. It is indirectly referenced from required .class files

Stacktrace:
 at org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.java:92)
 at org.apache.jasper.compiler.ErrorDispatcher.javacError(ErrorDispatcher.java:330)
  at org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:439)
 at org.apache.jasper.compiler.Compiler.compile(Compiler.java:349)
 
...생략...

화면 하단의 “Console” 창에서 최신 SDK를 설치를 권고하는 에러가 발생하는 경우도 있다.

... 생략 ....

********************************************************
There is a new version of the SDK available.
-----------
Latest SDK:
Release: 1.9.58
Timestamp: Sat Oct 07 01:48:02 KST 2017
API versions: [1.0]

-----------
Your SDK:
Release: 1.9.34
Timestamp: Fri Feb 12 04:36:15 KST 2016
API versions: [1.0]

-----------
Please visit https://developers.google.com/appengine/downloads for the latest SDK.
********************************************************

... 생략 ....

이는 JDK와 GAE 사이에 충돌이 나서 발생한 것이다. 이런 경우에는 JDK를 버전을 변경 또는 GAE를 변경하면 된다. JDK를 변경하는 방법은 위에서 설명을 하였고 여기서는 GAE 변경 방법에서 대해 설명하겠다.

  1. 콘솔 창에서 설명했듯이 아래 URL(https://developers.google.com/appengine/downloads)에서 최신 SDK를 다운 받아서 적당한 곳에 저장한다.

  2. [Window] 메뉴에서 하단의 [Preferences]를 선택한다. (맥에서는 [Eclipse] 메뉴에서 환경 설정을 선택한다.

  3. 이 윈도우의 왼쪽에 설정 항목이 계층적 목록으로 표시되어 있는데, 이 중에서 「Google」항목에 있는 “App Engine"라는 항목을 찾아 클릭한다.

  4. App Engine 화면에서 “Add” 버튼을 눌려서 다운받은 SDK를 추가한다. 그리고 추가한 SDK를 선택한 후에 “OK” 버튼를 누른다.

이제 다시 프로젝트를 실행하면 에러가 발생하지 않는 것을 확인 할수 있을 것이다.

1.3.3.3 - JSP/Servlet | JSP(Java Server Pages) | 여러 줄의 코드 실행

이제는 JSP에서 Java 코드가 서버 측에서 실행되는 것은 알았다. 그러나 <%= ~ %>에서 가능한 것은 한 문장의 결과를 출력할 뿐이었다. 이렇게 밖에 할 수 없다면 조금 곤란하다. 더 긴 문장의 코드를 실행해야 하는 경우가 많을 것이다.

이러한 경우에 사용되는 것이 <% ~ %> 이 태그이다. 이것은 <%에서 %>사이에 Java 코드를 작성하면 작성된 내용이 실행된다. 이는 결과를 출력하는 것이 아니라, 단지 실행을 하는 것이기에, 필요에 따라 결과를 출력하는 처리는 직접 써줘야 한다.

또 하나 알아야 할 것이 “import” 이다. Java에서는 자주 사용하는 클래스는 패키지 이름을 지정하지 않고 클래스 이름만으로 사용할 수 있도록, 서두에 import 문을 추가하는 것이 일반적이다. 이는 page 지시문을 사용하여 지정한다.

<%@ page import="클래스 지정"%>

이와 같이 기술하면 지정된 클래스는 패키지를 생략하고 사용할 수 있다. 물론 와일드 카드( *)를 사용할 수 있다.

그럼 앞의 예제를 조금 수정해 보자.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page import="java.util.Calendar" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%
Calendar calendar = Calendar.getInstance();
SimpleDateFormat format = new SimpleDateFormat("yyyy년 MM월 dd일");
String result = format.format(calendar.getTime());
%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 쌤플입니다.</p>
    <p><% out.println(result); %></p>
</body>
</html>

위와 같이 수정한 후에 다시 실행해 보면, 오늘 날짜가 표시되는 것을 확인할 수 있다. 이번에는 SimpleDateFormat를 사용해 Date를 지정한 포맷으로 표시하고 있다.

암묵적인 객체 “out"에 대해

이번 예제에서는 포맷된 텍스트 출력을 하는 곳에 <%= ~ %>를 사용하지 않고, `out.println (result);와 같이 하면 출력된다. 이 “out"이라는 것은 도대체 무엇인가?

이것은 JSP에 특유의 것으로 “암묵적인 객체"라는 것이다. 암묵적인 객체는 JSP 코드에서 아무것도 선언 등을 하지 않고, 어째든 마음대로 사용할 수 있는 객체이다.

이 “out"은 JspWriter라는 클래스의 인스턴스인데, 쉽게 생각해서 System.out의 out의 동료라고 생각하면 된다. 이 중에 포함되어 있는 print와 println 같은 출력의 메소드를 호출하면 그것이 그대로 Web 페이지에 출력된다. 이는 자주 사용하므로 기억해 두자.

1.3.3.4 - JSP/Servlet | JSP(Java Server Pages) | 메소드와 필드 정의

이제 여러 줄의 코드 처리를 Java로 작성해서 실행시킬 수 있게 되었다. 하지만 이건 뭔가 이상한 느낌든다. Java라는 언어는 이런 식으로 문장을 쭉 써서 그것을 실행하는 언어가 아니다. 일반적으로 클래스를 정의하고 그 안에 메소드나 필드를 정의하고 실행하는 언어이다.

이런 식으로 실행하는 문장을 쭉 쓰는 방식으로는 메소드를 정의하고 호출하는 수 없는가?라고 생각할 수도 있다. 하지만 그런 걱정은 필요 없다. 제대로 메소드를 정의하기 위한 전용 태그가 준비되어 있다.

그 태그는 <%! ~ %> 이것이다. 이것은 <%!%>사이에 메소드 정의와 변수 선언 등을 기술하면 코드에서 바로 사용할 수 있다. 메소드뿐만 아니라 변수도 선언할 수 있는데, 일종의 전역 변수로 사용할 수 있다. 즉, <%! ~ %> 내에서 선언된 변수는 다른 메소드 등 모든에서 사용할 수 있다. 클래스의 필드와 같은 역할을 한다고 생각하면 된다.

그럼 간단한 사용 예제를 만들어 보자. 이전의 예제을 더 수정하도록 하자.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page import="java.util.Calendar" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%!
String formatPattern = "yyyy-MM-dd";
String result = "none.";

void setFormatPattern(String f) {
    formatPattern = f;
}
void printToday() {
    Calendar calendar = Calendar.getInstance();
    SimpleDateFormat format = new SimpleDateFormat(formatPattern);
    result = format.format(calendar.getTime());
}
%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
</head>
<body>
    <h1>Sample jsp page</h1>
     <p>이 페이지는 샘플입니다.</p>
    <% printToday(); %>
    <p><%=result %></p>
    <% setFormatPattern("yyyy년 MM월 dd일"); %>
    <% printToday(); %>
    <p><%=result %></p>
</body>
</html>

여기에서는 날짜 형식을 나타내는 텍스트가 저장되는 formatPattern 변수와 포맷 생성된 날짜 텍스트를 저장 result 변수로 선언하고 있다. 그리고 날짜 형식을 설정하는 setFormatPattern과 오늘 날짜를 포맷 변수 result에 대입하는 printToday의 두 가지 메소드를 제공하고 있다.

여기에는 텍스트를 2회 출력한다. 이 부분을 살펴 보면 이렇게 되어 있다.

<% printToday(); %>
<p><%=result %></p>
<% setFormatStr("yyyy년 MM월 dd일"); %>
<% printToday(); %>
<p><%=result  %></p>

setFormatPattern으로 포맷을 설정하고, printToday으로 포멧의 텍스트를 대입하고 <%=reslt %>로 출력하는 흐름으로 처리하고 있다. 각각의 메소드와 변수가 제대로 기능을 하고 있다는 것을 알 수 있다.

또한, 이 코드에서 또 하나의 중요한 것을 알 수 있다. 그것은 “JSP Java 코드는 여러 개의 태그로 나뉘어 작성되어도 모든 하나로 통합한 것으로 간주된다"는 점이다. 수행할 처리는 하나의 <% ~ %> 태그로 정리하여 작성 필요는 없다. 여러 태그에 나누어 있어도, 모든 하나로 연속되게 처리를 한다. 이는 매우 중요한 JSP 특성으로 꼭 기억해 두도록 하자.

1.3.3.5 - JSP/Servlet | JSP(Java Server Pages) | html의 공백 없애기