지난밤에 잘려고 누웠다가 뜬금없는 생각이 들었다.

Interface를 구현한 Class에서 같은 이름의 변수를 재정의 하면 어찌될까?

원래 알았던거 같은데, 긴가민가한 문제 ^^;;




Interface 하나와 그를 구현한 Class 하나를 만들고 테스트 해보았다.

먼저 작성한 Interface와 Class

interface INagarryInterface

{

    String I_NAME = "ITestInterface";

    String GetClassName();

}


class NagarryClass implements INagarryInterface

{

    public static final String I_NAME = "TESTClass";

    public String GetClassName()

    {

       return I_NAME;

    }

}


interface와 class모두 맴버로 I_NAME이란 변수와 GetClassName이라는 함수가 정의 되어있다.

그럼 이를 사용해서 아래와 같이 실험해보자

public class AppMain

{

    public static void main(String[] args)

    {

        System.out.println(INagarryInterface.I_NAME); //(1)

        System.out.println(NagarryClass.I_NAME); //(2)

        

        INagarryInterface test1 = new NagarryClass();

        System.out.println(test1.I_NAME); //(3)

        System.out.println(test1.GetClassName()); //(4)

        

        NagarryClass test2 = new NagarryClass();

        System.out.println(test2.I_NAME); //(5)

        System.out.println(test2.GetClassName()); //(6)

    }

}



총6번의 출력이 있다.

(1)번의 출력결과야 보나마나

ITestInterface

이고, (2)번 출력결과야 보나마나

TestClass일 것 이다.


결과값은 다음과 같다.

ITestInterface

TESTClass

ITestInterface

TESTClass

TESTClass

TESTClass



(3)번과 (4)번의 출력결과가 재미지다.

똑같은 test1이라는 객체를 이용해서 I_NAME 변수를 출력한것인데,

결과가 다르니 말이다.


(3)번의 이유는 이렇다.

INagarryInterface test1 = new NagarryClass();

이라고 선언했으니....

test1은 NagarryClass의 Heap메모리를 갖게되지만,

엄연히 INagarryInterface타입이다.


I_NAME 변수는 인터페이스나 클래스에서 static으로 정의 되어있다.

(인터페이스의 변수는 무조껀 public static final이다. public static final을 명시하든 하지않든 말이다.) 

test1이 인스턴스 객체이지만, I_NAME은 Heap영역에 있는 것이 아니라,

static 데이터 영역에 있다.

따라서 test1의 I_NAME에 접근하게 되면 NagarryClass이 아닌

INagarryInterface에 접근하게 되는 것이다.

test1는 INagarryInterface타입이니 말이다.


(4)번의 이유는 반대로...

test1의 함수 GetClassName()를 이용하여 I_NAME 변수에 접근했는데,

이 함수는 Heap영역에 있고, 이건 명시적인 NagarryClass에 접근하게 되는것이다.

따라서 이 함수안에서 접근한 I_NAME는 INagarryInterface의 데이터 영역이 아니라 아니라 NagarryClass영역이다.



결과 인증샷



Google AdSense

오라클 10g부터  쓸만한 함수가 추가된게 있다.

wm_concat이라는 건데특정컬럼의 여러 Row() 하나로 concat해준다.



자, 아래와 같은 택배 테이블이 있다고 치자.

나가리라는 유저 앞으로 4개의 물건이 등록되어있는데,

이걸 조회하면 총 4개의 Row가 나온다.


근데 이걸 wm_concat을 이용하면 깔끔하게 1개의 Row로 조회할 수 있다.

콤마를 이용해 concat되어 있는데,

이건 사용하는 프로그램에서 splite같은 함수를 통해 손쉽게 배열로 끊어 담을 수도 있을 것이다.



한가지 주의해야 할 것은...

아래와 같이 여러 컬럼을 wm_concat하는 경우인데,

concat된 내용들이 정렬되어 들어가지 않고,

컬럼간 순서가 뒤죽박죽이 될 수 있다.

무심결에 2개 이상의 컬럼을 이런식으로 사용하다간...

맨붕이 올 수 있으니 주의



Google AdSense

FarPoint의 Spread를 통해 Excel파일 생성을 알아본다.



스프레드에서 자체적으로 Excel형식을 지원하기 때문에
몇줄의 코딩만으로 간단히 작성 가능하다.

아래와 같이 스프레더 사용선언을 하고 나면...
using FarPoint.Win.Spread

아래의 오버로딩된 SaveExcel함수를 사용할 수 있게된다.

1. public bool SaveExcel(Stream stream);
2. public bool SaveExcel(string fileName);
3. public bool SaveExcel(Stream stream, ExcelSaveFlags saveFlags);
4. public bool SaveExcel(Stream stream, ExcelWarningList warningList);
5. public bool SaveExcel(string fileName, ExcelSaveFlags saveFlags);
6. public bool SaveExcel(string fileName, ExcelWarningList warningList);
7. public bool SaveExcel(string fileName, IncludeHeaders includeHeaders);
8. public bool SaveExcel(Stream stream, ExcelSaveFlags saveFlags, ExcelWarningList warningList);
9. public bool SaveExcel(string fileName, ExcelSaveFlags saveFlags, ExcelWarningList warningList);
10. public bool SaveExcel(string fileName, IncludeHeaders includeHeaders, ExcelWarningList warningList);

이 SaveExcel함수를 통해 스프레드에 뿌려진 내용들을
엑셀파일로 생성시킬 수 있다.
우리가 할 일은 엑셀파일을
무슨이름으로 어디에 저장시킬지 결정하는 것 뿐이다.


3번 SaveExcel함수를 통해
스프레드를 인자로 받아서 엑셀을 저장하는 함수를 만들어 보자.
세련되게 다이얼로그박스를 띄워서...
저장위치와 파일이름을 입력하기로 한다.

public void export2XLS(FarPoint.Win.Spread.FpSpread pSpdList)
{
    SaveFileDialog mDlg = new SaveFileDialog(); 
    mDlg.InitialDirectory = Application.StartupPath;
    mDlg.Filter = "Excel files (*.xls)|*.xls|All files (*.*)|*.*";
    mDlg.FilterIndex = 1;

    if (mDlg.ShowDialog() == DialogResult.OK)
    {
        pSpdList.SaveExcel(mDlg.FileName,
                                         FarPoint.Excel.ExcelSaveFlags.SaveCustomColumnHeaders);
        MessageBox.Show("저장이 완료 되었습니다.", frmMainForm.Text,
                                         MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
}


자, 그럼 export2XLS함수를 사용하기만 하면 된다.
하단에 윈도우에 스프레드가 있다. 이걸 엑셀로 뽑아보자.
(스프레드의 이름은 spdMain, 시트뷰의 이름은 spsMain이라고 하자.)


우선 엑셀파일을 저장할 버튼을 하나 추가하고, 이름을 btnExportExcel이라고 하자.

btnExportExcel 버튼의 클릭 이벤트를 아래와 같이 정의하고,
그곳에서 위에서 작성한 export2XLS를 호출하면 된다.
private void btnExportExcel_Click(object sender, EventArgs e)
{
    export2XLS(spdMain);
}

그러나, 스프레더 값이 하나도 없을 때 굳이 저장함수를 호출할 필요는 없으니...
아래와 같이 작성한다.

private void btnExportExcel_Click(object sender, EventArgs e)
{
    if (spsMain.RowCount > 0)
    {
        export2XLS(spdMain);
    }
}


빌드해서....
버튼을 클릭하면 아래의 다이얼로그 박스가 뜨고


저장을 하게 되면 아래와 같은 엑셀파일을 얻을 수 있다.


너무 간단한가???
Google AdSense

간단하게... 로그파일 남기는  클래스를 만들어 보자.



작성할 로그파일기록 클래스는....
아래의 3가지 원칙을 지키는 것으로 하자.

1. 날짜별 로그파일 생성 : 로그파일은 1일 1건으로 생성시킨다.
2. 로그가 기록되는 시간을 표시한다.
3. 멀티쓰레드 환경에서 안전하게 동작한다.

1. 파일명은...
예를 들어 오늘이 2012년01월13일 이니까
"20120113_Log.Log"
뭐 이런식으로 생성하기로 하자.

2. 파일속 로그내용은...
"무궁화 꽃이 피었습니다."라는 내용을 로그에 남길때는
"[2012-01-13 17:04:59]:무궁화 꽃이 피었습니다."라고 남기면 되겠다.

3. 멀티쓰레드환경에서 안전하게 작동해야 하니까
대충 함수를 static synchronized로 만들어버리자.



이렇게 해서 클래스를 짜보면....
이렇게 나오겠네

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class FileLog {
    public static synchronized void writelog(String sLog)
    {
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat formatter2 = new SimpleDateFormat("yyyyMMdd");
        String sToday = formatter.format(cal.getTime()); //for log-time
        String sDate = formatter2.format(cal.getTime()); //for file-name

        String sFileName = sDate +"_Log.Log";
        sLog = "[" + sToday + "]:" + sLog;

        try
        {
            BufferedWriter bw = new BufferedWriter(new FileWriter(sFileName, true));
            bw.write(sLog);
            bw.newLine();
            bw.close();
        }
        catch(IOException ie)
        {
            ie.printStackTrace();
        }
    }
}



추가적으로 고려할게 있는데...
현재는 하루1건의 로그파일만 생성하는데,
경우에 따라 하루에 로그가 100MB이상도 쌓이는 프로그램이 있다.
그럴때는 파일용량이 일정수준을 넘으면 새로은 로그파일을 만들어 쓰는게 좋다.
하루에 100MB가 쌓인다면 10MB정도로 잘라서 10개 정도를 생성시키는 것이 좋지 않겠는가~

그렇게 할려면 아래의 2가지 정도를 추가해주면 되겠다.
첫째, 마지막에 생성된 파일의 용량을 체크하는 로직 추가
둘째, 파일이름에 시간이나 순번을 넣어서 파일명이 겹치지 않게 처리하는 구문 추가

하지만 귀찮아서 그냥 여기까지.... ㅡ,.ㅡ;;
Google AdSense

일요일 별로 할일도 없는김에 포스팅이나 하나 더 올린다.
.NET Framework에서 가비지콜렉터의 작동방식을 얘기하면서,
덤으로 .NET Compact Framework쪽도 언급한다.

힙메모리에 생성시킨 객체는 가비지컬랙터의 관리대상이 된다.
NagarryClass라는 클래스가 있다고 가정하고,
객체를 생성해서 힙메모리에 올려보자.
아래와 같이 하면 된다.
NagarryClass nagarry = new NagarryClass();

이렇게 하면 nagarry라는 객체가 생성되었고,
NagarryClass에서 정의했던 만큼 힙메모리 영역을 할당시킬 수 있다.
.NET에서는 이 메모리영역을 Managed Heap이라고 부르고있다.

.NET Framework의 Managed힙은 삼세대(3 generation)로 이루어져있고,
.NET Compact Framework에서는 한세대만 존재한다.

(Windows Mobile이나 Xbox는 .NET Compact Framework으로 되어있으므로, 그쪽에 관심있는 어린이는 한세대만으로 작동하는 가비지콜랙터에 대해 좀더 알아두는 것이 신상에 좋을 것이다.)

첫째세대에는 최근 할당된 메모리들이 저장된다.
이 곳에 있는 메모리는 NULL로 설정되면 비활성상태로 마킹된다.
프로그램이 구동되다가 이곳이 가득차면 가비지콜랙터가 작동하게 되는데,
가비지콜랙터는 아직 활성상태인 메모리를 둘째세대로 넘긴다음, 첫째세대의 모든 메모리를 반환시킨다.
둘째세대에는 첫째세대에서 할당되었다가 아직 반환되지 않는 메모리들이 저장된다.
활동시간이 비교적 긴 객체들인 것이다.
첫째세대에서와 마찬가지로 이곳 역시 가득차게 되면 가비지콜랙터에 의해
샛째세대로 메모리들이 이동하게 된다.
셋째세대에는 활동시간이 아주 긴 객체의 메모리들만 저장되게 된다.
마지막 세대인 이곳마저 가득차게 된다면...
가비지콜랙터는 Full가비지콜랙션을 수행하는데,
힙 전체 메모리영역을 싹~ 점검해서 활성/비활성을 판단하고,
비활성이면 반환하는 작업이 진행되기 때문에 무거운 연산일 수 밖에 없다.

Full가비지컬랙션이란 것이 발생하면 CPU의 처리능력이 일시적으로 떨어질 수밖에 없다.
여기에 Finalizer까지 호출해야하는 객체가 몇개 끼어있다면 CPU의 성능은 바닥으로 떨어질 것이다.
Finalizer가 왜 CPU성능을 떨어트리는지는 언급해보자면...
가비지콜랙터는 객체에 Finalize메서드가 있다면 힙에 있는 메모리가 비활성 상태로
표시되기 이전에 객체를 별도의 큐로 이동시켜야만 한다.
그곳에는 추가적인 메모리 반환작업이 필요하기 때문이다.
큐로 이동시킨 후에야 가비지콜랙터가 다시 작동할때 종료대상큐를 통해 메모리를 반환할 수 있다.
그래서 많은 .NET전문가들이 Finalize메서드는 사용하지 말라고 충고하는 것이다.
물론 Unmanaged 객체를 사용하지 않는다면 Finalize메서드는 전혀 사용할 필요가 없지만 말이다 ^^


추가로 알아야할 것은
새로 생성되는 객체에 너무나 큰 메모리가 필요하다면...
곧장 셋째세대에 저장되어버릴 수 있다는 거다.
첫째세대에 담을 수 없는 크기라면 곧장 셋째세대로 넘겨버림으로서,
대용량의 메모리 할당과 반환작업이 두세대에 걸쳐 반복적으로 일어나는 것을 막기위함이다.


Managed힙이 삼대로 구성된 것은 어디까지나 .NET Framework이고,
.NET Compact Framework은 한세대만으로만 이루어져있다.
첫째,둘째없이 셋째세대만 존재하는 것이다.
가비지콜랙터는 기본적으로 .NET Framework의 셋째세대가 하는 것과 동일한 역할을 한다.
.NET Compact Framework의 가비지콜랙터가 추가적으로 하는 일은 힙조각모음과 코드파기이다.

.NET Compact Framework의 가비지콜랙터는 주기적으로 힙영역을 조사해서
영역들이 너무 조각나 있다고 판단되면 힙영역을 빈공간없이 채우는 작업을 수행한다.
그래도 새로운 객체가 들어설 빈공간이 없다면 컴파일되었던 코드마저 파기해서 힙영역을 넓힌다.
한세대만 존재하는 .NET Compact Framework의 고육지책인 것이다.
이로 인해 파기된 코드들은 다시 컴파일되어야 하므로 CPU는 더욱 부담을 가질 것이다.



결국 "결론은 버킹검"이라고...
우찌됐든 다쓴 객체는 null로 바로바로 반환해주고,
클래스 디자인할때 너무많은 맴버들과 객체를 참조하게 하지말고,
(객체 활동주기가 긴건도 봐줄 수 있고, 큰것도 봐줄수는 있다. 그렇지만 크고 오래남아있는 객체는 봐 줄 수 없다!)
가능하면 제네릭타입을 사용해서 객체의 사본생성을 최소화하는게 좋다.

오늘은 여기까지~

Google AdSense

+ Recent posts