728x90

 

 

자바에는 8대 자료형이라 불리는 기본 자료형이 있다.

이 기본 자료형을 특별하게 분류하는 이유는 클래스가 아니기 때문이다.

클래스가 아니라는 것은 다른 말로 참조타입(reference type)이 아니라는 뜻이다.

 

당장 적당한 예시가 떠오르지 않지만, 이 기본 타입들에 대해 객체로 표현해야 하는 경우가 있는데 문제는 참조타입이 아니기 때문에 객체를 생성하지 못하는 문제가 있다.

 

그래서 Java에는 Wrapper(래퍼) 라고 부르는 클래스가 존재한다.

이 클래스들은 기본 자료형을 참조타입으로 객체를 생성할 수 있도록 해준다.

 

기본 타입 (primitive type) 래퍼클래스 (Wrapper class)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

 

Java에서는 primitive type 과 Wrapper 타입의 변환을 자동으로 해주는데, 이걸을 AutoBoxing 이라고 한다.

다음 코드는 박싱과 언박싱, 그리고 자동 박싱과 자동 언박싱의 예를 보여준다.

 

package com.tistory.xxxelppa.section_001;

public class BoxingUnboxing {
    public static void main(String[] args) {
        Integer myWrapperNumber = new Integer(21);    // Boxing
        int myPrimitiveNumber = myWrapperNumber.intValue();  // Unboxing
        
        Integer myAutoBoxingNumber = 21;                // AutoBoxing
        int myAutoUnBoxingNumber = myAutoBoxingNumber;  // AutoUnBoxing
    }
}

 

꽤나 편리해 보이는데 왜 되도록 피하라고 했을까?

 

 

728x90

 

 

다음 코드를 보자

 

    private static long sum_with_autoboxing() {
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; ++i) {
            sum += i;
        }
        return sum;
    }

 

그다지 문제가 있어보이지 않는 0부터 4바이트 정수 자료형 int 의 최대 표현 범위의 값의 누적 합을 계산해 long 타입의 변수에 담는 코드이다.

 

조금 더 정확히 얘기하면 long 타입의 i 값을 Long 타입의 변수에 누적 합을 구하고 있다.

문제는 sum += i; 이 부분이다.

Wrapper 타입의 변수에 값을 할당할 때 우리 눈에는 그저 sum += i; 만 보이지만

컴파일러는 이 코드를 sum += Long.valueOf(i); 로 바꾸기 때문이다.

 

즉, 저 라인이 실행 될 때마다 Long 타입의 객체를 생성 한다는 것을 의미한다.

지금의 경우 21억개의 무의미한 객체를 생성했다고 생각하면 될 것 같다.

 

 

그래서 위의 코드에서 딱 한 문자인 L 을 l 로 고쳐보았다.

 

    private static long sum_without_autoboxing() {
        long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; ++i) {
            sum += i;
        }
        return sum;
    }

 

그럼 이게 얼마나 차이가 나는걸까?

 

package com.tistory.xxxelppa.section_001;

/**
 * autoboxing 테스트
 *
 * [실행 결과]
 * use autoboxing : 6644 ms
 * not use autoboxing : 609 ms
 *
 * 되도록 박싱된 기본 타입보다 기본 타입을 사용하고
 * 의도하지 않은 오토박싱이 사용되지 않도록 주의 필요
 *
 */
public class Main {
    
    public static void main(String[] args) {
        long startTime, endTime;
        
        startTime = System.currentTimeMillis();
        sum_with_autoboxing();
        endTime = System.currentTimeMillis();
        System.out.println("use autoboxing : " + (endTime - startTime) + " ms");
        
        startTime = System.currentTimeMillis();
        sum_without_autoboxing();
        endTime = System.currentTimeMillis();
        System.out.println("not use autoboxing : " + (endTime - startTime) + " ms");
        
        
    }
    
    private static long sum_with_autoboxing() {
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; ++i) {
            sum += i;
        }
        return sum;
    }
    
    private static long sum_without_autoboxing() {
        long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; ++i) {
            sum += i;
        }
        return sum;
    }
}

 

코드의 맨 위에 주석으로 실행 결과를 기록해 두었다.

매변 결과는 조금씩 다르겠지만, 6000ms 즉 6초나 더 오래 걸린것을 확인할 수 있었다.

 

 

물론 이렇게 극단적인 상황이 자주 발생하지는 않겠지만, Auto Boxing 을 사용했을 때 내부에서 어떤 일이 생기는지 알고 있으면 좋을것 같아서 정리해 보았다.

 

 

 

 

728x90
728x90

 

자바에서 인터페이스를 사용하는 이유가 무엇일까?

아마 인터페이스를 처음 접해보는 분들이 많이 궁금해 하는 부분일거라고 생각한다.

그냥 그 기능을 클래스로 구현해도 될 것 같은데 번거롭게(?) 인터페이스까지 사용해서 또 인터페이스에 대한 학습도 해야하기 때문이다.

 

이세상 모든건 그게 왜 필요한지를 알면 이해하는데 도움이 많이 된다.

 

interface라고 쓰는 이 인터페이스는 결론부터 얘기하면 '공동 작업시 충돌을 방지하기 위해서'라고 한다.

다른 많은 이점이 있지만, 처음 내가 배울 당시 인터페이스 사용 이유는 공동 작업을 할때 유연함을 위해서다.

 

다음과 같은 상황을 생각해보자.

A씨는 필기도구를 사용해서 글씨를 쓰는 프로그램을 개발하는 프로젝트를 진행하고 있다.

그러던 중 같은 팀의 연필을 개발하는 김개발씨와 볼펜을 개발하는 박개발씨에게 '쓴다'라는 기능을 개발하라고 지시했다.

 

김개발씨의 코드는 다음과 같다.

 

class Pencil {
    public void Write() {
        System.out.println("연필로 쓴다.");
    }
}

 

박개발씨의 코드는 다음과 같다.

 

class Ballpoint {
    public void Writing() {
        System.out.println("볼펜으로 쓴다.");
    }
}

 

두 사람에게 개발을 완료했다는 말을 들은 A씨는 당황했다.

각자의 단위에서는 잘 실행이 되지만, 메인 프로그램에 붙여서 사용하자니 서로 이름이 너무 달라서 사용하기에 너무 불편한 것이다.

왜냐면 다음주에 형광펜, 네임펜 등의 필기구에 대해서도 개발을 해야하기 때문에 이대로는 안되겠다 싶었다.

 

class MainClass {
    public static void main(String[] ar) {
        Pencil pencil = new Pencil();
        BallPoint ballpoint = new Ballpoint();

        pencil.write();
        ballpoint.writing();
    }
}

 

 

728x90

 

 

그래서 미안하지만 김개발, 박개발씨에게 다음과 같은 인터페이스를 주며 다시 한 번 개발해달라고 부탁했다.

 

interface Frindle {
    public void Write();
}

 

그리고나서 개선된 김개발씨의 코드이다.

 

class Pencil implements Frindle {
    @Override
    public void Write() {
        System.out.println("연필로 쓴다.");
    }
}

 

박개발씨의 코드는 다음과 같다.

 

class Ballpoint implements Frindle {
    @Override
    public void Write() {
        System.out.println("볼로 쓴다.");
    }
}

 

이로써 A씨는 다음과 같은 메인 클래스를 작성할 수 있게 되었다.

 

package com.tistory.xxxelppa;

public class MainClass {
    public static void main(String[] ar) {
        Frindle frindle = new Pencil();
        frindle.write();
        
        frindle = new Ballpoint();
        frindle.write();
        
    }
}

 

이제는 형광펜으로 써도, 네임펜으로 써도 A씨는 클래스 이름만 알면 된다.

그러면 write라는 메서드를 호출했을 때 어떤 결과를 받아볼 것이라는 기대를 할 수 있게 된다는 장점이 있다.

 

(더 좋고 유연하게 작성할 수 있겠지만, 지금 당장 생각나는 예제가 이것 뿐이다.)

 

 

만약 형광펜으로 쓴다는 기능을 추가한다고 할 때, 이 interface를 상속받아 사용하게 된다면

A씨는 형광펜 클래스 내부에서 어떤 작업을 하는지 모르겠지만, write()를 호출했을 때 형광펜으로 쓴다는 기능을 한다는 것을 기대할 수 있다는 것이다.

 

다른 여러 장점을 가지고 있지만(인터페이스가), 내가 처음 배울 당시 인터페이스를 사용해야 하는 이유로 공동작업의 예를 들었기 때문에

위와같은 예시를 들어 보았다.

 

 

 

 

728x90
728x90

 

Q > 예를 들어 int[] a = new int[5]가 있으면 이 배열도 객체라는데 무슨말인지 잘 모르겠습니다.

    int라는 이름의 클래스가 있다는 말과 동일하나요? Integer Wrapper 클래스와 연관성은 없는 것 인가요? 알기 쉽게 설명해주세요.

 

 

A >

배열을 객체라고 부른다. 그 이유가 무엇일까? 보통 객체를 생성한다라고하면 클래스의 객체를 많이 떠올린다. 그렇다고 클래스부터 설명을 할 수는 없는 노릇이다. 클래스에 대해서는 안다고 가정하고 클래스와 객체와의 관계에 대해 생각해보자.

 

클래스는 자료형이다. 이 사실을 잊어선 안된다. 왜냐면 자료형은 그 자료가 가진 형태를 나타내는 것이기 때문에 스스로는 사용될 수 없다. 누군가가 그 형태를 가지는 실체를 만들어 내야만 사용할 수 있다. 이런 관점에서 배열과 배열 객체를 바라보자. 배열도 동일한 자료형의 값을 담을 수 있는 공간이다. 그렇기 때문에 int[] a; 라고한다면, a라는 변수는 이제부터 int라는 자료형의 값을 담을 수 있어. 딱 여기까지다. 그 이상도 이하도 아니다. '담을 수 있다'는 것과 '사용할 수 있다'는 다르다. (전달이 잘 되고 있는지 모르겠다.)

 

728x90

 

int[] a; 는 어떤 의미에서 추상적인 표현이다. int라는 자료형의 값을 여러개 담을 수 있는 것. 더 나아가 이것을 실체화 시켜야 사용 가능하다는 것이다. 바로 그 실체화 하는 코드가 new int[5] 인 것이다. (다형성에 대해서 알고 있으면 더 좋을 것 같다.) 이 new int[5]라는 코드 덕분에 a는 배열 객체가 되는 것이다. 그 이전에는 그저 int[] 타입의 변수 a를 정의했는데 아직 객체가 할당되지 않은 상태인 것이다.

 

 

그리고 다른 관점에서 한번 더 생각해보자. 배열은 다른 리터럴 값들과 달리, '배열은 어떠한 값이다.'라고 정의할 수 없다. 즉, new라는 연산자를 통해 heap 메모리 영역에 생성된 공간을 참조하는 주소값을 가지고 있기 때문이다. a는 배열에 할당된 값의 주소를 가리키고 있지 배열의 값을 가리키고 있지 않다는 얘기이다. 그렇기 때문에 이 배열도 객체라고 한다.

 

 

 

int라는 이름의 클래스가 있다는 말과 동일하다는 얘기는 클래스도 결국 자료형임을 알고 있는 것 같다. 자료형의 관점에서 int[] 타입의 클래스 라고도 볼 수 있다고 생각한다. 하지만 그렇게는 잘 얘기하지 않는다. 흔히 primitive 타입이라고 부르는 원시타입에 대해서 자료형이라고 하지 클래스라고는 잘 하지 않기 때문이다.

 

배열 객체의 얘기를 함에 있어서 Integer Wrapper 클래스와는 관련성이 부족한것 같다. Integer는 primitive 타입인 int의 reference타입으로, 흔히 8대 자료형이라 부르는 primitive 타입들의 reference 타입의 클래스를 Wrapper 클래스라고 하기 때문이다. 이런 연관성을 생각한 이유는, 내 생각에, 배열 객체 -> 객체 -> 클래스 -> int -> primitive type -> Wrapper class 이런 흐름이었지 않나 생각한다. Wrapper class는 primitive type을 클래스화 한것이다.

 

 

 

 

728x90
728x90

 

나는 개인적으로 클래스와 객체를 설명할 때 붕어빵 틀을 비유하는걸 싫어한다.

마음에 들지 않는다. 내가 배울 당시 이렇게 배우지 않았을 뿐만 아니라 개인적으로 잘 와닿지도 않았다.

그래서 내가 배운 방법으로 클래스란 무엇이며 이 클래스와 객체와의 관계를 어떻게 이해하면 좋은지 정리해보려 한다.

 

클래스는 우리가 일상 생활속에서 볼 수 있는 모든 것들을 표현할 수 있는 도구라고 생각하는 것도 좋은 발상이다.

클래스를 구성하고 있는 큰 두가지 개념이 있다. 하나는 멤버필드라는 개념이며 다른 하나는 멤버 메서드라는 개념이다.

 

멤버 필드란, 그 클래스가 표현하고자 하는 대상이 가지는 속성들을 의미한다. 높이, 색상, 속도, 속력, 방향, 무게 등 일반적인 사물들이 가질 수 있는 모든 속성들을 말한다.

멤버 메서드란, 그 클래스가 표현하고자 하는 대상이 할 수 있는 어떤 동작, 행위들을 의미한다. 클래스가 사람을 표현하고 싶다면, 앞으로 걷기, 뒤로 걷기, 밥먹기 등이 될 수 있다. 강아지라면 밥먹기, 짖기, 뛰기, 재롱부리기 등이 해당한다.

 

실제로 사람은 훨씬 복잡하지만 클래스로 사람을 표현한 예시이다.

 

class Person {
    private String gender;
    private int tall;
    private double weight;
    private double footSize;

    public void walk() { }
    public void eat() { }
}

 

이 클래스에서 정의하고 있는 사람이라는 것은, 성별, 키, 몸무게, 발 사이즈를 알 수 있으며, 걷거나 먹는 행위를 할 수 있다.

앞서 말했듯 이정도로 사람을 표현하기엔 턱없이 부족하지만 간단한 예시를 들자면 이렇다는 것이다.

하지만 이렇게 정의했다고 해서 바로 사용하고 있는것은 아니다. 이건 마치 설계도면과도 같은 것이다.

스스로는 할 수 있는게 아무것도 없기 때문이다.

김춘수 시인의 꽃이라는 시에서 처럼

내가 클래스의 객체를 만들어 주기 전에는 클래스는 다만 하나의 설계 도면에 지나지 않는다.

 

클래스의 개념에 대해 찾아본다면 기본적인 자바 프로그램을 만들어 보면서 알게 되었을 것이다.

모든 자바 프로그램은 main 메서드에서 시작한다고.

이 main 메서드 안에서 클래스의 객체를 만들어주어야 비로소 나에게 사용할 수 있는 객체로서의 의미를 가지게 된다.

 

 

728x90

 

 

자꾸 객체 객체 하는데, 그래서 객체가 뭔지 답답할지 모르겠다.

다음 예시를 보기 전에 클래스와 객체의 관계에 대해서 알아보자.

 

우리가 많이 사용하는 스마트폰. 이건 클래스다. 하지만 내가 손에 들고 있는 스마트폰은 클래스가 아니다. 객체다.

컴퓨터를 하기 위해서 지금 내가 손으로 쥐고있는 마우스는 객체다. 하지만 마우스는 객체가 아니가. 클래스다.

지금 책상위에 놓여있는 달력은 객체다. 하지만 달력은 객체가 아니다. 클래스다.

'머릿속에 손목 시계를 떠올려보세요' 해서 떠오르는 일반적으로 알고있는 손목시계는 클래스다. 하지만 지금 손목에 차고 있는 이 시계는 객체다.

 

무슨 말장난이냐 싶을지 모르겠지만. 위에서 말한 그대로다.

클래스란 이렇게 우리가 특정 사물에 대해서 일반적으로 떠올릴수 있는 개념적인 것이라면, 객체는 이것이 실제 눈앞에 형태를 갖추고 있는 것 그 자체이다. 즉, 클래스는 '이 사물은 이렇게 생겼고 어떤어떤 기능을 가지고 있어요'라면 객체는 클래스가 설명하고 있는 것을 토대로 만든 것이라고 할 수 있겠다. 그래서 마우스는 클래스지만 지금 내 손 안에 들어와 있는 눈에 보이는 이 마우스는 클래스가 아니고 객체라는 것이다.

 

 

 

위에 작성한 Person 클래스를 사용하는 main 메서드를 작성해 보자.

 

public class ClassTest {
    public static void main(String[] ar) {
        Person p1 = new Person();
        Person p2;
        p2 = new Person();
    }
}

 

ClassTest라는 이름의 클래스는 main 메서드를 담고 있다.

3라인과 4-5라인이 의미하는 바는 똑같다.

하지만 굳이 4라인과 5라인으로 나눈데는 이유가 있다.

3라인의 의미는, p1변수는 Person클래스에서 정의한 형태를 가지는 객체를 담을 변수야. 그리고 난 이 변수에 Person 객체를 담았어. 라는 말이다.

4라인의 의미는, p2변수는 Person클래스에서 정의한 형태를 가지는 객체를 담을 공간이야. 라는 말이다.

5라인의 의미는, Person객체를 하나 만들었고, 나는 이 형태를 가지는 변수 p2에 담았어. 라는 말이다.

 

건축물에 비유하자면 단순히 클래스를 만들기만 해서는, 객체화를 하지 않는다면, 그저 설계도만 만들고 건물은 올리지 않은것이 된다.

하지만 설계도만 가지고 있다면 수십 수백 수천개라도 객체를 만들어낼 수 있다. 너무나 당연하게도. 그렇기 때문에 자바는 재사용성이 뛰어난 언어라는 얘기와도 일맥상통한다.

 

이번에는 클래스와 객체의 관계에 대해서 다루었기 때문에 이정도만 정리해두려 한다.

다음에는 자바의 특징중에 하나인 다형성에 대해 정리하면서 재사용성에 대해서 자세히 정리 해둬야 겠다.

 

 

 

 

728x90
728x90

java 개발을 하려면 환경변수를 설정해야한다.
아니 더 정확히는 명령 프롬프트에서 컴파일하고 실행하려면 환경변수를 설정해야 한다.
명령 프롬프트는 cmd창으로 알고 있는 보통 검정색 있어보이는 화면을 말한다.

우선 환경변수라는것이 무엇인지부터 알아보자.

 

IT(정보기술) 용어로, OS의 셸(shell) 등에 설정되어 있다. 변수의 이름과 의미는 미리 정해져 있기 때문에 환경변수를 읽으면 시스템의 설정을 어느 정도 알 수 있다.

 OS의 환경변수는 시스템의 실행파일이 놓여 있는 디렉토리의 지정 등 OS 상에서 동작하는 응용소프트웨어가 참조하기 위한 설정이 기록된다.
응용소프트웨어로부터는 시스템 콜(system call:프로그래밍 언어에서 지원하지 않는 기능에 대하여 운영체계의 루틴을 호출하여 이용하는 것)이나  OS의 표준 API 등을 통하여 간단히 값을 얻을 수 있도록 되어 있다.

또한 웹 브라우저의 내부 데이터의 일부를 환경변수라고 하는 경우도 있는데, 이것은 HTTP를 요청할 때 송신되는 것으로, 브라우저의 종류나 링크되어 있는 웹 페이지 등 웹 서버가 웹 브라우저에 대하여 최적의 처리를 하기 위해 송신되는 것이다. HTTP를 요청하는 응용소프트웨어는 모두 환경변수를 송신하고 있다고 할 수 있다.

 사용자가 의도적으로 변환할 수 있는 환경변수도 많으며, 특히 웹 브라우저에서는 자신의 정체를 숨길 목적으로 브라우저 등의 변수를 변환하는 경우도 있다.

 그러나 사실과 다른 값을 환경변수에 설정하면 그 환경변수를 사용하고 있는 서버나 응용소프트웨어가 올바르게 작동하지 않을 수 있다. 특히, 셸의 환경변수를 변환했을 경우에는 심각한 오작동을 일으킬 수 있기 때문에 이를 취급하는 경우에는 주의가 필요하다.

 

위 본문은 네이버 검색을 통해 가져온 내용이다.

위 본문을 읽어도 잘 모르겠다면,
"여기여기에(사용할 것들이있는 위치) 있는 것들을 내가 사용할건데, 미리 쓰기 편하게 등록해 놓는거야"
정도로 생각해도 될 것 같다.

환경변수를 설정하기 위해서는 설정하는 곳으로 가야한다.

 

 

[내 컴퓨터]를 마우스 우클릭을 해서 속성을 클릭한다.
혹은 키보드의 윈도우키와 오른쪽 위에 pause/break 키를 동시에 눌러주면 바로 아래 이미지와 같은 화면이 뜬다.

 

 

728x90

 

 

새로 뜬 화면의 좌측에 있는 '고금 시스템 설정' 을 클릭한다.

 

 

그리고 뜬 화면의 아래쪽에 '환경 변수' 를 클릭하면 바로 위 이미지의 앞쪽에 뜬 팝업이 하나 더 뜬다.
바로 이곳에서 환경변수를 설정할 수 있다.


바로 위의 그림에 보이는 것처럼 등록할 수 있는 영역이 두군데가 있다.
1. 특정 사용자에 대한 사용자 변수
2. 시스템 변수

 

사실 말이 특정 사용자이지, 로그온 되어있는 현재 사용자 계정에서만 사용하려면 1번의 위치에 등록을 시키고
이 컴퓨터를 사용하는 모든 사용자 계정에 대해 동일한 환경변수를 설정하려면 2번의 위치에 등록을 하면 된다.

 

그리고 java에서 설정하는 환경변수는 두가지 종류가 있다.
PATH와 CLASSPATH이다.
PATH는 실행 프로그램의 위치만을 나타내며
CLASSPATH는 실행 프로그램에서 사용하는 라이브러리의 위치를 나타낸다.

 

그래서 PATH는 java에서 사용할 실행 프로그램들이 위치하고있는
[자바 설치 경로] 하위의 bin 이라는 디렉토리로 설정한다.
그리고 CLASSPATH는 [자바 JRE 설치 경로]하위의 lib 디렉토리의 위치로 설정한다.

 

/**
 * 책을 보던 중 새로 추가할 만한 내용이 있어서 수정하게 되었다.
 * 이 부분이 java version이 달라지면서 바뀌었을 수도 있지만,
 * 5.0 버전때를 기준으로 CLASSPATH에 대한 설명을 더하자면
 *
 * 자바 라이브러리 클래스들이 저장된 디렉토리를 설정하는 것으로
 * J2DJK(Java 2 Development Kit)는 필요한 클래스 라이브러리를  ..\jre\lib\ext 디렉토리에 복사하면 사용 가능하므로
 * ​특별한 경우가 아니면 CLASSPATH는 설정하지 않는 것이 좋다.​
 */


환경변수를 등록하거나 수정하는 작업은 간단하다.
[새로 만들기] 버튼을 눌러 이름과 값을 정해주면 된다.
이미 존재한다면 (아마 path는 존재하고 있을 것이다.) 해당 값을 클릭해둔 상태로 [편집] 버튼을 누르면 된다.


보통 java에 대한 환경변수를 설정할때는
1. JAVA_HOME 이라는 변수를 생성하고, [java 설치 경로]의 jdk 버전이 명시된 디렉토리까지 설정하고
2, PATH에서 JAVA_HOME이라는 1에서 만든 변수를 가져다 사용하여 등록하는데
   PATH에는 이렇게 작성하면 된다.
   %JAVA_HOME%\bin;

환경변수의 경로는 항상 세미콜론(;)으로 구분되어야 하며
환경변수 설정이 바뀌었을 경운에는 명령 프롬프트를 반드시 재시작 해야 수정한 내용이 반영된다.​


만약 환경변수가 제대로 설정되었는지 확인해보고 싶다면
(java의 경우) 명령프롬프트를 띄우고 "javac" 라고 입력했을 때
해당 명령어 사용법이 뜨면 제대로 설치 및 설정이 완료된 것이고
해당 명령어를 찾을 수 없다라는 문구가 나오면
어딘가가 잘못된 것아다.

 

(제대로 설정되지 않은 예)


그리고 또 하나, Java는 설치할 때 환경변수 세팅이 자동으로 등록되지 않는다.
그렇기 때문에 Hello World를 하기 전에 까먹지 말고 세팅해 주어야 한다.
일반적으로 jdk를 설치하면 jre가 자동으로 설치 되지만
jre를 설치하면 jdk는 자동으로 설치되지 않는다.

:jre -> Java Runtime Environment 로 자바 실행 환경이다.
:jdk -> Java Development Kit 으로 개발을 하기 위한 도구이다.

 

이렇게 환경변수 세팅을 마쳤다면 드디어 Hello World 를 출력할 준비가 ? 되었다.
이왕 세팅한거 티키신이 분노하지 않게 '안녕 세상아' 한 번 찍어주자.

 

import java.lang.*;

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

 

메모장에서 작성해도 좋다.
대신, ​파일이름과 public class 뒤의 이름이 대소문자까지 완벽하게 동일해야 한다.​
위의 경우 파일을 Example.java 로 저장해야만 한다.

그리고 해당 파일이 있는 위치로 이동한후
javac Example.java
라고 컴파일 명령을 내린다.
정상적으로 컴파일이 되었다면, .java파일이 있던 위치에 동일한 이름의 .class 파일이 생겼을 것이고
명령프롬프트에는 아무런 문구 없이 사용자의 입력을 기다리며 커서가 껌뻑이고 있을 것이다.

컴파일이 완료되었으면 다음 명령어로 실행시켜보자.
java Example
만약 위의 과정을 순서대로 잘 수행했다면 "Hello, World!" 라는 문구가 떡하니 출력되어 있을 것이다.

 

환경변수에 대해 알아보다가 Hello, World! 까지 출력해 보았다.
이제 환경변수가 무엇이며, 그렇기 때문에 왜 설정해줘야 하는지
그리고 어떻게 확인할 수 있는지 까지 알아보았다.

다음엔 무엇을 정리해두지

** 현재 상황이 직접 java를 설치할 수 있는 환경이 아니라 이미지 자료가 부족하다.

 

 

 

 

728x90
728x90

 

자바에는 문자열을 처리하기 위한 클래스로 String, StringBuilder, StringBuffer 클래스가 있다.

단순히 문자열을 처리하기 위함이라면 (예를 들면 System.out.println("문자열");  이런 경우) 어떤 것을 사용해도 별다른 차이는 없다고 알고 있다. 하지만 문자열을 더하는 (+) 연산을 할 경우에 퍼포먼스상의 차이가 발생한다.

 

백번 듣느니 한 번 보는게 낫다.

실제로 아래와 같은 예제는 많은 곳에서 흔히 볼 수 있는 예제일 것이다.

 

package xxxelppa.tistory.com;

public class StringTest {
    public static void main(String[] ar) {
        long StartTime = 0;
        long EndTime = 0;
        
        String str;
        StringBuilder strBdr;
        StringBuffer strBfr;
        
        for(int k = 0; k < 9; ++k) {
            str = new String("문자열");
            strBdr = new StringBuilder("문자열");
            strBfr = new StringBuffer("문자열");
            
            System.out.println("============== " + (k + 1) + "번째 비교 결과" + " ==============");
            
            StartTime = System.nanoTime();
            for(int i = 0; i < 10000; ++i) {
                str += i;
            }
            EndTime = System.nanoTime();
            System.out.println("String의 실행 시간\t\t: " + (EndTime - StartTime));
            
            StartTime = System.nanoTime();
            for(int i = 0; i < 10000; ++i) {
                strBdr.append(i);
            }
            EndTime = System.nanoTime();
            System.out.println("StringBuilder의 실행 시간\t: " + (EndTime - StartTime));
            
            StartTime = System.nanoTime();
            for(int i = 0; i < 10000; ++i) {
                strBfr.append(i);
            }
            EndTime = System.nanoTime();
            System.out.println("StringBuffer의 실행 시간\t: " + (EndTime - StartTime));
            System.out.println("=============================================");
            System.out.println();
            
        }
        
        
    }
}

 

위 예제는 똑같이 문자열을 더하는 연산을 했을 때 실행 속도를 비교하는 소스이다.

실행 결과는 다음과 같다.

 

============== 1번째 비교 결과 ==============
String의 실행 시간             : 192313252
StringBuilder의 실행 시간    : 417904
StringBuffer의 실행 시간     : 613102
=======================================

============== 2번째 비교 결과 ==============
String의 실행 시간             : 165631465
StringBuilder의 실행 시간    : 427854
StringBuffer의 실행 시간     : 460046
=======================================

============== 3번째 비교 결과 ==============
String의 실행 시간             : 178263707
StringBuilder의 실행 시간    : 276554
StringBuffer의 실행 시간     : 340644
=======================================

============== 4번째 비교 결과 ==============
String의 실행 시간             : 184807363
StringBuilder의 실행 시간    : 371959
StringBuffer의 실행 시간     : 376934
=======================================

============== 5번째 비교 결과 ==============
String의 실행 시간             : 178070851
StringBuilder의 실행 시간    : 436048
StringBuffer의 실행 시간     : 479068
=======================================

============== 6번째 비교 결과 ==============
String의 실행 시간             : 133046927
StringBuilder의 실행 시간    : 268945
StringBuffer의 실행 시간     : 361423
=======================================

============== 7번째 비교 결과 ==============
String의 실행 시간             : 153004492
StringBuilder의 실행 시간    : 210708
StringBuffer의 실행 시간     : 355277
=======================================

============== 8번째 비교 결과 ==============
String의 실행 시간             : 137168318
StringBuilder의 실행 시간    : 289139
StringBuffer의 실행 시간     : 418782
=======================================

============== 9번째 비교 결과 ==============
String의 실행 시간             : 136230374
StringBuilder의 실행 시간    : 309039
StringBuffer의 실행 시간     : 373129
=======================================

 

728x90

 

 

실행 시간 결과는 String > StringBuffer > StringBuilder 순서이다.

그렇다면 왜 이런 결과가 나왔을까?

 

String은 문자열을 더하기 위해 String 공간을 하나 더 만드는 작업을 한다.

이게 무슨 소리일까? String이라는 클래스가 가지는 특성이라고 받아들일 수 밖에 없다.

String 클래스형 자료형으로 선언된 문자열이 메모리상에 할당이 되면, 이 값을 바꿀 수 없는 값이 된다.

만약 이 문자열이 더해지거나 빼는 연산을 하게 될 경우 기존에 할당한 메모리에 저장된 값이 바뀌지 않는다는 소리다.

 

String addr100 = new String("문자열A");
String addr101 = new String("문자열B");

addr100 = addr100 + addr101;

 

이 소스가 실행되면 메모리상에서 어떤 일이 생길까?

 

 

위에 첨부한 이미지를 함께 보자.

만약 메모리상에 위처럼 문자열이 저장 되어있다고 했을 때, 100번지의 "문자열A"와 101번지의 "문자열B"를 더해 새로운 문자열 "문자열A문자열B"를 만든 경우이다.

addr100 변수는 100번지를 바라보고 있다가 새로운 102번지를 바라보게 된다. (바라본다 보다 참조한다는 말이 더 옳다)

그럼 100번지는?

가비지 컬렉터가 나중에 알아서 처리해주니 신경쓰지 말자.

 

String 클래스의 경우 기존 값에 새로운 값을 더하는 것이 아닌, 위와 같이 추가 연산이 필요하기 때문에 StringBuilder나 StringBuffer보다 퍼포먼스가 떨어진다.

그렇기 때문에 문자열 연산에서는 StringBuilder나 StringBuffer 클래스의 사용을 권장한다.

 

(내용이 너무 길어질 것 같아서 추가하지 않으려 했지만, 급하면 이 부분은 넘어가도 좋다.)

지금까지 문자열을 더하는 연산이 빈번하게 발생한 부분은 배치 프로그램에서 쿼리를 작성할 때였다.

다음과 같은 쿼리가 있다고 하자. (다소 과장된 부분이 있다.)

 

SELECT
    AAA
    , BBB
    , CCC
    , DDD
    , EEE
FROM TEST
WHERE
    1=1
    AND AAA > 100
    AND BBB > 200
    AND CCC > 300
    AND DDD > 400

 

이 때 이 쿼리문을 다음과 같이 변수에 담을 수 있다.

 

String query = "";

query += "SELECT           ";
query += "    AAA          ";
query += "    , BBB        ";
query += "    , CCC        ";
query += "    , DDD        ";
query += "    , EEE        ";
query += "FROM TEST        ";
query += "WHERE            ";
query += "    1=1          ";
query += "    AND AAA > 100";
query += "    AND BBB > 200";
query += "    AND CCC > 300";
query += "    AND DDD > 400";

 

단순히 더하는게 아니라고 생각해보면 이게 얼마나 재앙인지 이제는 알게 되었으리라 생각한다.

(위와 같은 경우 반드시 StrinfBuilder 클래스나 StringBuffer 클래스를 사용하기를..)

 

 

 

그럼 마지막으로, String과 StringBuilder, StringBuffer의 차이는 알겠는데 StringBuilder와 StringBuffer의 차이는 무엇일까?

StringBuilder와 StringBuffer 클래스는 사실 하는 일은 똑같다. 다만 하나의 차이가 있는데 동기화 처리의 문제이다.

 

StringBuffer클래스는 동기화 (Synchronized)를 지원하며 멀티 스레드 환경에서도 동기화를 지원한다. 그렇기 때문에 단일 스레드일 경우 StringBuilder 클래스를, 멀티 스레드일 경우 StringBuffer 클래스의 사용이 권장된다. (StringBuffer 클래스의 경우 혼용해도 상관없지만, 동기화 처리 문제로 퍼포먼스상의 이슈가 있다고 한다.)

실제로 맨 위에 예제로 만든 소스의 실행 결과를 보면 단일 스레드 환경에서 StringBuilder 클래스가 StringBuffer클래스보다 퍼포먼스가 좋은 것을 확인할 수 있다.

 

동기화라는 개념은 어려운게 아니다.

마치 한칸짜리 공중 화장실이라고 생각하면 될 것 같다.

누구나 사용할 수 있지만, 동시에 사용할 수 없도록 하는 것이 동기화이다.

 

 

 

흔히 찾아볼 수 있는 예제이지만, 보다 정확하게 확인하고 싶어서 주소값을 직접 출력 해보고 싶었는데 그렇게 하지 못해 아쉬움이 남는다.

 

 

 

 

728x90
728x90

 

정규표현식 자체만으로도 정말 많은 이야기를 할 수 있지만.

기본적인 정규표현식의 개념과 언제 사용하면 좋은지에 대해서는 어느정도 알고 있다고 가정한다.

(나중에 기회가 된다면 정규표현식에 대해서 정리를 할 계획인다.)

 

자바에서 정규표현식을 사용하기 위해서는 두 개의 라이브러리를 import해야 한다.

 

import java.util.regexp.Matcher;
import java.util.regexp.Pattern;

 

바로 util package에 있는 클래스 이다.

 

 

두 개의 클래스를 import 했다면, 다음처럼 작성해보자.

 

// 비밀번호 유효성 검사식1 : 숫자, 특수문자가 포함되어야 한다.
String regExp_symbol = "([0-9].*[!,@,#,^,&,*,(,)])|([!,@,#,^,&,*,(,)].*[0-9])";
// 비밀번호 유효성 검사식2 : 영문자 대소문자가 적어도 하나씩은 포함되어야 한다.
String regExp_alpha = "([a-z].*[A-Z])|([A-Z].*[a-z])";

// 정규표현식 컴파일
Pattern pattern_symbol = Pattern.compile(regExp_symbol);
Pattern pattern_alpha = Pattern.compile(regExp_alpha);

// 문자 매칭
Matcher matcher_symbol = pattern_symbol.matcher("검사할문자열");
Matcher matcher_alpha = pattern_alpha.matcher("검사할문자열");

// 매칭 결과 출력
System.out.println("matcher_symbol : " + matcher_symbol.find());
System.out.println("matcher_alpha : " + matcher_alpha.find());

 

비밀번호 작성 규칙은 다음과 같다.

1. 숫자가 포함되어야 한다.

2. 특수문자가 포함되어야 한다.

3. 영문 소문자가 포함되어야 한다.

4. 영문 대문자가 포함되어야 한다.

 

728x90

 

 

실제로 사용할 때 수정해야 할 부분이 있다면 다음과 같다.

 

1. 2라인과 4라인에서 정의한 정규표현식을 수정하여 유효성 검사 조건을 달리 한다.

2. 11라인과 12라인에서 "검사할문자열"에 들어갈 부분에 진짜 검사할 신규 비밀번호 매개변수를 담아 넣어준다.

3. 15라인과 16라인에서 단순히 console에 출력한 부분을 .find()의 리턴타입이 boolean이므로 if절 안에 넣어 수정 가능 여부를 판별한다.

 

 

자바에서 정규표현식을 사용하여 유효성을 검사하는 방법이 위와 같으므로 응용하는건 개발자의 몫이다.

 

 

마지막으로 그냥 이렇게 끝내면 작동하지 않는 것을 첨부한 것 같아서,

사용자로부터 입력 받는것은 아니지만, 배열에 임의의 암호값을 넣어보고 실행해본 소스와 결과를 첨부한다.

 

package xxxelppa.tistory.com;

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

public class RegExpTest {
    public static void main(String[] ar) {
        
        String arrPw[] = new String[] {"qwer1234", "Qwer1234", "qwer!234", "Qwer!234"};
        
        // 비밀번호 유효성 검사식1 : 숫자, 특수문자가 포함되어야 한다.
        String regExp_symbol = "([0-9].*[!,@,#,^,&,*,(,)])|([!,@,#,^,&,*,(,)].*[0-9])";
        // 비밀번호 유효성 검사식2 : 영문자 대소문자가 적어도 하나씩은 포함되어야 한다.
        String regExp_alpha = "([a-z].*[A-Z])|([A-Z].*[a-z])";
        
        // 정규표현식 컴파일
        Pattern pattern_symbol = Pattern.compile(regExp_symbol);
        Pattern pattern_alpha = Pattern.compile(regExp_alpha);
        
        
        for(int i = 0; i < arrPw.length; ++i) {
            // 문자 매칭
            Matcher matcher_symbol = pattern_symbol.matcher(arrPw[i]);
            Matcher matcher_alpha = pattern_alpha.matcher(arrPw[i]);
            
            // 매칭 결과 출력
            System.out.println("========== " + (i+1) + "번째 비밀번호 : [" + arrPw[i] + "] ==========");
            
            if(matcher_symbol.find() && matcher_alpha.find()) {
                System.out.println("비밀번호로 적절합니다.");
            } else {
                System.out.println("비밀번호로 부적절합니다.");
            }
            System.out.println();
        }
    }
}

 

========== 1번째 비밀번호 : [qwer1234] ==========
비밀번호로 부적절합니다.

========== 2번째 비밀번호 : [Qwer1234] ==========
비밀번호로 부적절합니다.

========== 3번째 비밀번호 : [qwer!234] ==========
비밀번호로 부적절합니다.

========== 4번째 비밀번호 : [Qwer!234] ==========
비밀번호로 적절합니다.

 

 

RegExpTest.java
다운로드

 

 

 

 

728x90
728x90

 

답답하면 니들이 뛰라는 말이 있다.

그래서 정리해봤다.

 

javaFX를 사용해서 MDI (Multi Document Interface)를 개발해야할 경우가 간혹 생길수 있다.

검색을 해보았지만 마땅히 내가 원하는 스타일이 없었다.

그렇다고 이제 와서 javaFX를 (나는 처음부터 한 번 쭉 훑어 봤지만)처음부터 볼 수는 없는 노릇이다.

 

갑자기 javaFX를 사용하게 되어서 아주 급하게 공부를 하고 있었는데, 사용하지 않게 될 가능성이 높아졌다.

그래서 지금까지 알아본 내용이 너무(?) 아까워서 나름의 정리를 해보려 한다.

 

 


 

 

우선 javaFX가 무엇인지 간략하게 살펴보는 것으로 시작한다.

javaFX는 RIA (Rich Client Application)을 개발하기 위한 것으로, 크로스 플랫폼을 지원하는 그래픽 패키지 라이브러리 이다. java 7 부터 JDK에 포함되어 있기 때문에 별도의 설치는 필요하지 않다. 그렇다면 왜 javaFX일까?

 

사실 javaFX이전에 java를 사용하여 UI애플리케이션을 개발할 수 있었다. 아마 많이 들어봤을 AWT와 Swing이 바로 그 기술이다. AWT는 Abstract Window Toolkit의 약어로 OS에서 제공하는 네이티브 UI 컴포넌트를 사용하는 java 라이브러리 이다. 그렇다보니 이 라이브러리의 단점중에 하나가, 어떤 OS환경에서 동작하느냐에 따라 컴포넌트 구성이 변하고 또 그 종류도 많지 않았다. 그리고 나서 바통을 이어받은 것이 Swing이다. Swing의 주된 컨셉은, AWT에서 문제가 되었던, OS에서 제공하는 UI 컴포넌트 사용을 지양하자는 곳에서 부터였다. 쉽게 말해서 서로 다른 OS에서도 동일한 UI 컴포넌트를 제공하기 위해서 였다. 하지만 이게 오히려 단점이 되었고, Swing을 쓰기는 하지만 핵심 가치인 독자적 UI 를 사용하지 않고 기존 AWT처럼 OS에서 제공하는 UI를 억지로 사용했다고 한다. 그러다보니 당연하게도 퍼포먼스가 떨어지고 메모리 낭비가 심해져서 점점 버림받는(?) 기술로 전락하고 말았다.

 

이런 역사 속에서 MS의 sliverlight와 Adobe의 flash가 성장하자 이들과 경쟁에서 살아남기위해(?) javaFX라는 새로운 것을 들고 나왔다. 하지만 최초 등장했을 당시에는 또 다른 새로운 것을 학습해야하는 부담감에 사용자들로부터 외면을 당한다. (처음에는 java 언어와 별개로 javaFX 스크립트 언어를 새로 배웠어야 한다고 한다.) 2007년의 실패(?)를 교훈삼아 별도의 언어 필요 없이 java언어만 알아도 개발할 수 있도록 2011년에 새로운 버전이 release되었다. 그리고 지금은 IOS 애플리케이션 개발을 해보신 분들은 알겠지만, 화면과 비즈니스 로직을 독립시켜 개발할 수 있는 환경을 제공하고 있다.

 

 


 

본 내용은 아래 사항들이 이미 갖춰져 있음을 가정한다.

1. jdk 1.7 이상이 설치되어 있다. (나는 1.8 버전을 설치했다.)

1. eclipse IDE를 사용한다. (netBeans를 사용하는 곳이 많았지만 나는 eclipse기준으로 만들었다.)

2. e(fx)clipse 플러그인이 설치되어 있다.

3. Scene Builder 툴이 설치되어 있고, eclipse에서 화면 개발을 할 수 있도록 설정 되어 있다.

 

 


 

 

그럼 본격적으로 javaFX를 사용해서 MDI 예제 소스를 정리해보자.

 

 

이 라이브러리를 사용하게 된 배경이 있다.

사실 버튼이나 메뉴에서 클릭 이벤트를 통해 새로운 화면을 띄워 보는건 복잡하거나 어렵지 않다.

(스테이지를 Modal창으로 만들면 되기 때문이다.)

하지만 내가 원하던 화면은 메인화면 안에서 보여지는 화면이었기 때문에 사용할 수 밖에 없었다.

 

아래 참고자료에는 내 목적에 부합하지 않는 두 가지가 있다.

1. 화면을 fxml로 분리시키지 않고 java소스로 그리고 있다.

2. 메인 화면에 메뉴가 없다.

 

주로 이 두가지 문제를 해소하기 위해 나름의 노력을 했다.

(더 좋은 방법이 분명히 있기 때문에 추후에 다시 한 번 살펴볼 예정이다.)

 

눈으로 확인하는게 가장 빠를테니 예제 프로젝트를 실행부터 해보자.

 

 


 

 

참고로, 내려받은 원본 소스 파일 트리는 다음과 같았다.

 

/src

/main

/java/br/com/supremeforever

/mdi

/Exception

PositionOutOfBoundsException.java

MDICanvas.java

MDIEvent.java

MDIIcon.java

MDIWindow.java

Utility.java

Main.java

MyContentController.java

/resources

/assets 

WindowIcon.png

close.png

maximize.png

minimize.png

restore.png

/style

ㄴDarkTheme.css

ㄴDefaultTheme.css

MyContent.fxml

/test/java/br/com/supremeforever/mdi

MDICanvasTest.java

SupremeForeverMdiTestSuit.java

 

 

그래서 테스트 관련된 부분을 제외하고 jar로 묶기 위해 좀더 단순하게 묶었다.

 

/com/tistory/xxxelppa/mdi

MDICanvas.java

MDIEvent.java

MDIIcon.java

MDIWindow.java

PositionOutOfBoundsException.java

Utility.java

 

(resources들은 생략했다. 그리고 굳이 트리 구조를 설명한 이유는 참고 문서와 내가 작성한 문서가 상이하기 때문이다.)

 

명령 프롬프트에서 해당 디렉토리로 이동한 다음

jar -cvf mdilib.jar .

이 명령어를 통해 jar 파일로 묶은 결과이다.

 

** jar 파일 :

mdilib.jar
다운로드

 

** 예제 프로젝트 :

xxxelppa_MDI.zip
다운로드

 

 

이 예제 파일 exlipse에서 Import > Existing Project into Workspace 로 추가해준 다음.

jar파일을 프로젝트의 Java Build Path의 Libraries에 추가해주면 된다.

 

 

 

실행이 되었다면 두 가지 문제를 어떻게 해결했는지 보자.

(추가로 주석을 달았기 때문에 아래 첨부한 소스와 첨부 파일의 소스가 조금 상이할 수 있으나 실행시에 영향을 주는 부분은 없다.)

 

 

728x90

 

 

1. 화면을 fxml로 분리시키지 않고 java소스로 그리고 있다.

-> 40라인, 118라인

2. 메인 화면에 메뉴가 없다.

-> 46 ~ 64 라인 : 메뉴 생성

-> 70 ~ 94 라인 : 메뉴 등록

-> 97 ~ 136 라인 : 메뉴 클릭 이벤트 바인딩

 

package com.tistory.xxxelppa.view;

import java.io.IOException;
import java.io.InputStream;

import com.tistory.xxxelppa.mdi.MDICanvas;
import com.tistory.xxxelppa.mdi.MDIWindow;

import javafx.application.Application;
import javafx.application.HostServices;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class MainAppController extends Application {
    
    int count = 0;
    int i = 1;
    public static HostServices hostServices;
    
    private Stage primaryStage;
    public static MDICanvas mdiCanvas;
    BorderPane mainPane = new BorderPane();
    
    // constructor
    public MainAppController() {
    }
    
    @Override
    public void start(Stage primaryStage) throws IOException {
        
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("MainApp.fxml"));

        // main화면을 BorderPane으로 만들고 fxml 편집툴인 scene builder에서 상단에 menuBar를 추가한다.
        mainPane = (BorderPane)loader.load();
        

        // 메뉴 초기화
        final String pMenu[][] = new String[][] {
            // 메뉴 타이틀 
            {
                "menu_1"            // 첫 번째 메뉴
                , "menu_2"          // 두 번째 메뉴
            },
            // 첫 번째 메뉴 목록
            {
                "TestPage_1"
                , "TestPage_2"
                , "TestPage_3"
                , "Exit"
            },
            // 두 번째 메뉴 목록
            {
                "TestPage_4"
            }
        };
        
        mdiCanvas = new MDICanvas(MDICanvas.Theme.DARK);    // CSS 설정
        mdiCanvas.setPrefSize(1500, 800);                   // main 화면 (BorderPane)의 크기 설정


        /*
         * 상단 메뉴 생성 _ 2017.05.30 HSM
         */
        MenuBar menuBar = new MenuBar();
        // 상위 메뉴 생성
        Menu menu[] = new Menu[pMenu[0].length];
        for(int i = 0; i < menu.length; ++i) {
            menu[i] = new Menu(pMenu[0][i]);
        }
        // 하위 메뉴 생성
        MenuItem mi[][] = new MenuItem[pMenu[0].length][];
        for(int i = 0; i < pMenu[0].length; ++i) {
            mi[i] = new MenuItem[pMenu[i+1].length];
            for(int j = 0; j < pMenu[i+1].length; ++j) {
                mi[i][j] = new MenuItem(pMenu[i+1][j]);
            }
        }
        // 하위 메뉴 상위 메뉴에 등록
        for(int i = 0; i < pMenu[0].length; ++i) {
            menu[i].getItems().addAll(mi[i]);
        }
        // 메뉴바에 상위 메뉴 등록
        menuBar.getMenus().addAll(menu);
        // 메뉴바 화면에 등록
        mainPane.setTop(menuBar);


        // 메뉴 이벤트 등록
        for(int i = 0; i < pMenu[0].length; ++i) {
            for(int j = 0; j < mi[i].length; ++j) {
                final String pViewName = mi[i][j].getText();
                
                mi[i][j].setOnAction(event -> {
                    Node content = null;
                    MDIWindow mdiWindow;
                    try {
                        
                        if(pViewName.equals("Exit")) {
                            System.exit(0);
                        } else {
                            /*
                             * 메뉴 화면은 /view 디렉토리 하위에 생성한다.
                             * 화면 이름 작명 규칙
                             *    1. 위에서 생성한 메뉴 이름의 공백을 제거한다.
                             *    2. 화면 이름 뒤에 _View 를 붙여 화면임을 명시한다.
                             *    ex> 메뉴 이름이 'Personal Date Input' 이라면 이 화면은 'PersonalDataInput_View.fxml'파일명을 가져야 한다.
                             *    마음에 들지 않는다면 다른 규칙을 적용해도 된다.
                             */
                            content = FXMLLoader.load(getClass().getResource("../view/" + pViewName.replace(" ", "") + "_View.fxml"));
                            
                            InputStream in = getClass().getResourceAsStream("../resources/assets/" + "windowIcon.png");
                            Image imgWindowIcon = new Image(in);
                            ImageView imvWindowIcon = new ImageView(imgWindowIcon);
                            
                            mdiWindow = new MDIWindow(pViewName, imvWindowIcon, pViewName, content);
                            
                            // 화면별 사이즈는 switch case 태워도 괜찮을 듯... ? 번호 or 화면 명으로 분기
                            mdiWindow.setPrefSize(500, 400);
                            
                            mdiCanvas.addMDIWindow(mdiWindow);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
        }
        
        mainPane.setCenter(mdiCanvas);
        
        Scene scene = new Scene(mainPane);
        
        // 메인 화면 타이틀
        primaryStage.setTitle("xxxelppa MDI TST PRJ");
        
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

 

각 화면에서 발생하는 이벤트에 대해서는 따로 controller class를 만들어서 scene builder를 통해 @FXML 로 선언한 이벤트를 등록해주면 된다. (첨부한 예제 소스에는 MDI화면당 controller class를 만들지 않았다.)

 

메뉴를 등록하는 부분을 라이브러리에 포함시키려고 했지만, 무슨 이유에서인지 포함시키지 않았다. (기억나지 않는다.. 따로 수정해도 좋을 것 같다..) 마지막으로 새로운 메뉴를 만드는 방법에 대해 말하려 한다.

메뉴를 생성한 부분의 소스를 따로 살펴보자.

 

        // 메뉴 초기화
        final String pMenu[][] = new String[][] {
            // 메뉴 타이틀 
            {
                "menu_1"            // 첫 번째 메뉴
                , "menu_2"          // 두 번째 메뉴
            },
            // 첫 번째 메뉴 목록
            {
                "TestPage_1"
                , "TestPage_2"
                , "TestPage_3"
                , "Exit"
            },
            // 두 번째 메뉴 목록
            {
                "TestPage_4"
            }
        };

 

위와같이 pMenu 배열을 생성하면 다음과 같은 메뉴가 만들어진다.

 

 menu_1

 menu_2

 TestPage_1

 TestPage_4

 TestPage_2

 

 TestPage_3

 
 Exit  

 

말로 길게 설명하는 것 보다 세번째 munu를 생성한 소스를 보는게 이해가 더 빠를 것 같다.

 

        // 메뉴 초기화
        final String pMenu[][] = new String[][] {
            // 메뉴 타이틀 
            {
                "menu_1"            // 첫 번째 메뉴
                , "menu_2"          // 두 번째 메뉴
                , "menu_new"        // 세 번째 메뉴
            },
            // 첫 번째 메뉴 목록
            {
                "TestPage_1"
                , "TestPage_2"
                , "TestPage_3"
                , "Exit"
            },
            // 두 번째 메뉴 목록
            {
                "TestPage_4"
            },
            // 세 번째 메뉴 목록
            {
                "TestPage_NEW"
            }
        };

 

바뀐 부분은 7 라인, 14 라인, 20 ~ 23 라인 이다.

 

수정 이후 실행 결과 메뉴는 다음과 같이 만들어진다.

 

 menu_1

 menu_2

 menu_new

 TestPage_1

 TestPage_4

 TestPage_NEW

 TestPage_2

   

 TestPage_3

   
 Exit    

 

 

아래는 실행시켜본 결과 화면이다.

메인 화면 안에서 MDI 화면들이 보이고

각 화면 크기 조절이 가능하고

메뉴는 보이는 것과 같이 등록이 되고

최소화 했을 경우 좌측 하단에 보이는 것과 같이 나타나진다.

 

 

 

** 중간에 예제 프로젝트 소스를 첨부 했으니 원하는 대로 만들어 사용하면 될 것 같다.

 

 

 

소스 참고 : github _ lincolnminto

영상 참고 : youtube _ Lincoln Minto

 

 

 

 

728x90

+ Recent posts