Java의 정적 초기자(static initializer)에 대해서 살펴보고자 한다. 

다음과 같은 코드가 있을 때 static { ... }으로 감싸줘 있는 부분을 static initializer라고 한다. 


Oralcle 공식 문서를 보면 "이 정적 초기자 block은 클래스 body 어디에서 나타나도 되며 하나의 클래스가 임의의 갯수의 static initialization block(정적 초기자)를 가질수 있다"고 되어 있다.


그리고 정적 초기자 영역의 코드는 단 한번만 실행되는데 그 실행시점은 

-. 해당 클래스의 객체를 생성할때

-. 해당 클래스의 static 멤버(변수 혹은 메소드)에 최초로 접근할 때 한번 실행이 된다. 이 경우는 심지어 해당 클래스의 객체를 생성하지 않았을 지라도 해당 클래스의 멤버에 최초 접근시 정적 초기자가 실행이 된다.


또한 정적 초기자는 생성자 이전에 실행이 된다.

아래 코드는 확장자를 넘겨 주었을 때 해당 확장자가 어떤 MIME 타입인지를 반환해주는 코드이다. 이때 정적 초기자가 어떻게 실행되는지를 보도록 하자.


public class MyMimeType {

private static Map<String, String> mimeMap;

  //이 영역이 정적 초기자(static initializer)

static {

System.out.println("$$$$$$$ Hello, This is MyMimeType의 정적 초기자입니다");

mimeMap = new HashMap<String, String>();


mimeMap.put("JSON", "application/json");

mimeMap.put("JPG", "image/jpeg");

mimeMap.put("PDF", "application/pdf");

mimeMap.put("ZIP", "application/zip");

mimeMap.put("JAR", "application/java-archive");

//그 외의 mime 타입은 생략...

}

public static String getMimeType(String mType) {

System.out.println("$$$$$$$ Hello, This is MyMimeType의 getMimeType()입니다");

//위의 정적 초기자에서 입력해 놓은 Map의 key 값으로 Mime Type을 반환해낸다.

//만일에 위에서 지정한 5가지 이외의 mType(Map 객체의 key 역할) 값이 오면 null이 반환된다.

return mimeMap.get(mType.toUpperCase());

}

}


만일에 프로그램의 특정 영역에서 MyMimeType 클래스의 객체 생성 없이 아래와 같이 호출했다고 하면


String mType = MyMimeType.getMimeType("ZIP");


해당 메소드가 실행되기 전에 정적 초기자가 먼저 실행되고 이후에 해당 메소드가 실행이된다. 

다음과 같은 순서로 로그가 출력될 것이다.


$$$$$$$ Hello, This is MyMimeType의 정적초기자입니다

$$$$$$$ Hello, This is MyMimeType의 getMimeType()입니다


이후에 아래와 같이 getMimeType() 메소드를 다시 호출하면 


String mType_2 = MyMimeType.getMimeType("jpg");


이제는 정적 초기자는 실행되지 않고 해당 메소드만 실행이되어 다음과 같은 로그를 출력하게 될 것이다.


$$$$$$$ Hello, This is MyMimeType의 getMimeType()입니다



Java varargs 메소드에 대해서


Java에서 다음과 같은 형태의 메소드를 보게 될것이다.


private void makeDir(String... args) {

   ...

}


위의 메소드에서 매개인자(argument)의 형태가 특이하다. String... args

String 타입인건 알겠는데 3개의 ...(three dots)로 표현된 부분이 의미하는 것이 무엇인가하는 것이다.


이러한 메소드를 이름하여 Java varargs라고 한다.  varargs란 또 뭔가? 사전에 찾아도 나오지 않는단어이다. 이건 variable arguments의 줄임 표현같다. 그래서 '가변 인자'라고 표현하면 되겠다.


이러한 형태의 메소드는 Java version 5부터 소개된 파라미터 타입니다.

아마도 Android를 해본 사람은 AsyncTask 클래스에서 이런 모양의 메소드 파라미터를 보았을 것이다.


varargs 파라미터는 해당 데이터 타입의 argument를 0개 이상의 임의의 갯수의 argument를 받을수 있다는 뜻이다.

위의 경우 String... args에는 makeDir() 메소드를 호출하는 곳에서 아래의 경우가 모두 가능하다.


makeDir("aaa", "bbb", "ccc", "ddd", "eee");

makeDir("ddd", "eee");

makeDir("ccc");

makeDir("aaa", "bbb", "eee");

makeDir();

makeDir("aaa", "bbb", "ccc", "ddd", "eee", "fff");


이와같이 임의의 갯수의 argument를 전달할수 있다.

다음과 같이 int형 varargs도 당연히 가능하다.


public String myAddr(int... number) {

  ...

}


varargs 메소드 내부에서 사용법은 다음과 같다.


public int sum(String name, int... args) {

int sum = 0;

for(int i=0; i<args.length; i++){

sum += args[i];

}


return sum;

}


varargs 파라미터 사용시 주의 사항은 varargs 파라미터는 파라미터들의 순서상 맨 마지막에 위치해야된다는 점이다.


다음의 경우는 올바른 사용법이다.


public int sum(String a, int b, int... args) { ... }


그러나 다음의 두 경우는 잘못된 경우이다. 허용되지 않는다.


//varargs가 맨 앞에 온 경우이다.

public int sum(int... args, String a, int b) { ... }


//varargs가 가운데 온 경우이다.

public int sum(String a, int... args, int b) { ... }



왜 interface인가, interface가 갖는 의의와 목적


Java 등에서 interface가 갖는 의의는 interface를 "구현(implements)"하는 하위 클래스들 모두에 동일한 기능(메소드)를 갖도록 함을 통해서 동일한 사용자 경험을 갖도록 강제하는 의의를 가지고 있는 것이 interface이다.

이에 대한 자세한 이야기는 여기를 클릭


이와 함께 또 하나 interface가 갖는 의의는 전체적으로 어떤, 어떤 기능이 있어야 할 것인지 전체적인 그림을 그릴수 있도록 하는 것이 interface가 하는 역할이다.


inteface는 각각의 메소드의 몸체를 구현하지 않아도 되기에 전체적으로 있어야 할 기능들에 대한 메소드 이름만 지정하면서 필요한 기능들의 전체 그림을 그릴수 있도록 하는 역할을 하는 것이 interface이다.


이렇게 지정한 각 메소드들의 detail한 실제 기능구현은 interface를 "구현(implements)"하는 하위 클래스들에서 숙고하고 고려하면 되므로 interface는 전체 방향 설정 역할 혹은 나아갈 방향에 대한 지도(map) 혁할을 한다는데 또 하나의 중요한 의의가 있다.


전체적인 숲의 모양을 그려주고 그 숲의 세부적인 부분들은 하위 클래스에서 구현하면 되도록 함을 통해서 즉 전체 그림과 세부적인 실제 기능을 분리함을 통해서 개발을 보다 용이하게 혹은 배가 산으로 가는 일이 없도록 하는 역할을 하는 것이 interface가 갖는 의의이고 목적이다.





인터페이스나 혹은 추상클래스에 있는 추상메소드의 목적

추상메소드에 대해 제대로 이해하기가 쉽지는 않는듯 하다.

추상메소드와 관련된 일반적인 '지식'에 해당되는 내용들은 얼마든지 있다.

그러나 이것이 어디에 쓰기위해 존재하는지에 대해서 명확한 정립이 되어있지 않으면 남들이 만들어 놓은 추상메소드를 코딩은 하지만 정작 내가 클래스를 만들면서 추상 메소드가 가진 목적을 발휘하는 그런 클래스를 디자인하고 만들어 쓰는데까지는 가지 못하는 것을 보게된다.

오늘은 추상 메소드(abstract method)가 존재하는 목적이 뭔지에 대해 뿌리를 캐보고자 한다.


가령 예를 들어서 우리가 어떤 디바이스를 켜는(power on) 행위(method)를 한번 생각해 보자.

power on에는 다양한 종류가 있을 것이다.

PC를 켜거나(Power On), 스마트폰을 켜거나, TV를 켜거나, ... 이럴 때 켤때마다 첫번째 초기 화면으로 그 제품을 만든 회사의 로고 이미지가 뜨도록 강제하고 싶을 경우가 있을 것이다.

PC를 켤때 삼성에서 만들었으면 삼성 로고가 뜨고 LG에서 만들었으면 LG 로그가 뜨고... 스마트폰도 마찬가지로...

이와같이 Power On 할때 초기 화면에 제조사 로고를 뜨도록 강제하고 싶을 경우 이때 이 목적을 위해서 존재하는 것이 추상 메소드(abstract method)이다.


이것을 클래스로 만들어 PowerOn이라는 클래스로 만들어보자. 

이때 PowerOn을 상속받는 하위 클래스들에서는 power on할 때(켤 때) 반드시 제조사의 로고 이미지를 뜨도록 강제하고자 하면 그에 해당하는 기능을 추상 메소드로 만들어 두면 하위 클래스에서는 반드시 로고 이미지가 뜨는 기능(method)을 만들어야만 되도록 되어 있다. 그렇지 않으면 컴파일 단계에서 에러를 발생시킨다. 따라서 그것을 상속 받는 하위 클래스에서는 무조건 로고 이미지 뜨는 기능을 구현해야만되고 이것은 하위 클래스들은 이 면에서 동일한 공통성을 띄게 강제하게 되는 결과가 된다.

말하자면 사용자들에게 동일한 인터페이스를 제공해 주게 되는 결과를 창출하게 된다.

이런 목적으로 존재하는 것인 추상 메소드이다.

예제 코드를 만들어 보자.


abstract class PowerOn

{

//하위 클래스들은 무조건 로고를 띄우는 기능을 만들도록 하고 싶다.

//그럴 경우 로고를 띄우는 메소드를 추상메소드로 만들면 된다.

abstract void showLogo(); 


public String getName() {

...

return name;

}

}



class SamsungPhone extends PowerOn

{

public void showLogo() {

//여기서 삼성 로그 띄우게

}

}


class LGPC extends PowerOn

{

public void showLogo() {

//여기서 LG 로그 띄우게

}

}


그래서 결론적으로 interface나 abstract class에 있는 abstract method의 목적은 어떤 특정 기능을(메소드를) 하위 클래스에서 반드시 만들도록 강제하고자 할때 사용하는 것이다.


그런 점에서 interface라는 것의 존재 목적이 보이는 것이다.

interface는 interface가 가지고 있는 모든 메소드는 전부 abstract 메소드이다.

추상 메소드(abstract 메소드)의 목적은 하위 클래스에 특정 기능을 반드시 만들도록 강제하고자 하는 것이 그 목적이다.

그로 인해 창출되는 결과(효과)는 하위 클래스들은 모두가 특정 기능(메소드)이 다 공통적으로 존재하게 된다는 점이다. 따라서 사용자는 어떤 하위 클래스를 사용하더라도 동일한 사용자 인터페이스를 경험하게 되는 것이다.


따라서 abstract 클래스와 유사하게 interface는 그 이름 그대로 이 interface를 상속받는(구현하는 implements하는) 하위 클래스들은 모두가 공통된 기능들을 갖추게 된다. 즉 interface에 있는 메소드들을 모든 하위 클래스들은 모두다 그 기능을(그 메소드를) 만들어야 하므로 사용자는 특정 interface를 상속받은(implements한) 모든 하위 클래스들에서는 동일한 사용자 인터페이스를 경험하게될 것이다.

이것이 interface가 노리는 목적이다.





Java의 Reference 타입 자료형에 대해


자바의 자료형은 크게 기본형과 레퍼런스 타입으로 분류할 수 있다. 

레퍼런스 타입은 new 연산자를 통해서 힙(heap) 영역에 생성되는 자료형들을 의미한다. 

레퍼런스 타입으로는 클래스, 배열, 인터페이스 들이 있다.


따라서 arr1, arr2라는 2개의 배열이 있을 때 

arr1 = arr2;

를 하면 arr2에 있는 데이터를 arr1에 대입하는 것이 아니라(call-by-value가 아니라)

arr2가 가리키는 메모리 주소 값을 대입한다(call-by-reference이다). 


따라서 arr1에서 값을 변경하면arr2에서도 값이 변경된다. 

반대로 arr2에서 값을 변경하면 arr1의 값도 동시에 바뀐다.


그러나 

int arr1, int arr2;


가 있다면 이때 

arr1 = arr2;


라고 하면 이건 값(데이터)을 대입한다. 즉 call-by-value이다. 

따라서 arr1의 값을 변경해도 arr2에는 아무런 영향이 없고

arr2의 값을 변경해도 arr1에는 아무런 영향이 없다.


그런데 배열은 근본적으로 call-by-reference이다. 

따라서 아래 코드에서 

mRGBData를 chageData(byte[] rgb)로 넘기면 

mRGBData와 rgb는 동일한 기억 장소를 가리키게 된다. 

따라서 changeData()에서 mRGBData에 값을 대입하는 것이 전혀

없을지라도 mRGBData를 출력해 보면 changeData()에서 수정한 값이 잘 출력된다.


public class MainActivity extends Activity {

private TextView txt;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

txt = (TextView)findViewById(R.id.txt);

byte[] mRGBData = null;

mRGBData = new byte[5];

changeData(mRGBData);

String str = "";

for (int i=0; i<mRGBData.length; i++) {

str += mRGBData[i] + ", ";

}

txt.setText(str);

} //onCreate

private void changeData(byte[] rgb) 

{

for(int j=0; j<rgb.length; j++) {

//여기서 mRGBData에 값을 대입하지 않고 rgb에만 값을 넣었는데도

//onCreate()에서 TextView에 mRGBData를 출력해 보면 0, 2, 4, 6, 8이 출력된다.

rgb[j] = (byte)(j*2);

}

}

}



아래와 같이 class도 reference type이다. class 참조변수(객체)에 대입을 하면 value가 대입되는 것이 아니라

reference가(해당 클래스의 기억장소 주소 값이) 대입 되게된다.

그래서 class의 두 참조 변수는 동일한 기억 장소를 가리키게 된다.


public class MainActivity extends Activity {

private TextView txt;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

txt = (TextView)findViewById(R.id.txt);

MyClass myClass = new MyClass();


//아래 로그를 출력해 보면 다음과 같이 My2클래스에서 myc와 mc를 출력했을 때와 같이

//동일한 기억장소를 가리키게 된다.

// myClass : com.example.test.MainActivity$MyClass@411e71c0

Log.e("$$$$$$$", "myClass : "+myClass.toString());

My2 my2 = new My2(myClass);

String str = myClass.name + "\n" + myClass.age;

txt.setText(str); 

} //onCreate

class MyClass

{

private String name;

private int age;

}

class My2

{

private long kkk;

MyClass myc;

public My2(MyClass mc)

{

myc = mc;

//아래 로그를 출력해 보면

//myc : com.example.test.MainActivity$MyClass@411e71c0

Log.e("$$$$$$$", "myc : "+myc.toString());


//아래 로그를 출력해 보면

//mc : com.example.test.MainActivity$MyClass@411e71c0

Log.e("$$$$$$$", "mc : "+mc.toString());

mc.name = "홍길동";

mc.age = 29;

}

}

}




BufferedReader에 대해서


BufferedReader는 인터넷에서 데이타를 읽어올 때 유용하다. 왜냐하면 인터넷은 원격에서 가져오기 때문에

속도가 늦을 수 있다. 따라서 버퍼를 이용해서 읽는 것이 필요하다.

따라서 BufferedReader의 객체를 생성하는 법을 아는 것은 요긴하다.


BufferedReader의 핵심은 이것이다.

InputStream이 입력을 위한 가장 기본인데 

BufferedReader는 InputStream으로부터 버퍼에로

읽어온다는 개념이 BufferedReader의 핵심 개념이다. 

따라서 BufferedReader의 생성자 자체가 InputStream을 매개인자로 갖는다.


BufferedReader의 생성자를 보면 아래와 같이 2종류가 있다.


BufferedReader(Reader in)

BufferedReader(Reader in, int size)


1) 생성자(다른 것은 API 참조)

BufferedReader(Reader in)


2) Reader 클래스의 Known Direct Subclasses들은 다음과 같다

BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader


3) 따라서 BufferedReader의 생성자 매개인자로 InputStreamReader의 객체가 올수 있다.


4) InputStreamReader의 생성자(다른 것은 API 참조)

public InputStreamReader (InputStream in, Charset charset)

==> Charset charset는 한글 코드에 대한 디코딩 정보이다. 보통 웹은 euc-kr(2바이트 한글)이고 자바에서는 유니코드 체계인 utf-8이다


5) InputStreamReader의 객체를 생성할 수 있는 메소드

  -. URLConnection의 getInputStream();

  -. HttpURLConnection이 URLConnection을 상속받았기 때문에 여기서도 가능하다



String을 byte[] 배열로 변환하는 법

 

String 문자열을 byte[] 배열로 바꾸는데는 String 클래스에서 제공하는 getBytes() 메소드를 이용하면 된다.

이 메소드는 overloading 된 4개의 메소드가 있다.


public byte[] getBytes ()

public byte[] getBytes (String charsetName)


첫 번째 메소드는 문자열을 byte[] 바꿀때 시스템의 디폴트 charset을 이용해서 변환한다.

두 번째 메소드는 지정된 charsetName으로 인코딩된 형태로 변환한다.


String str = "Hello world";

byte[] info = str.getBytes("utf-8");





Map과 List에 대한 간단한 이해


List는 원소들을 가지고 있는 데이터 집합이다. 순차 자료구조로 순차적으로 메모리에 저장되는 형태이다. 쉽게 배열로 바꾸어서 생각해도 된다. 

따라서 List의 원소에 접근할 때는 index(첨자)로 접근한다.


List는 Generic이라는 옵션이 존재하는데 Generic 옵션이 설정된 List는 Generic에 해당되는 데이터 형만을 담을 수 있다.


List<Integer> myList = new ArrayList<Integer>();


위와 같이 선언된 List는 Generic 옵션으로 Integer 형을 받았다.

myList에 add할 수 있는 자료들은 Integer형의 데이터만 담을 수 있게 된다.


myList.add("data")와 같이 문자열을 넣으려 한다면 에러가 발생하게 된다.


myList.get(i)과 myList.remove(i) 메소드를 사용해서 myList 안에 있는 원소들을 인텍스로 접근, 삭제 등을 할수 있다.


Map은 List와 같은 원소들을 모아 놓은 집합이다.

List와는 달리 index(첨자)로 접근하는 것이 아니라 key로써 데이터를 제어할수 있다.

Map 또한 Generic 옵션을 설정할 수 있다.


Map<String, Object> myMap = new HashMap<String, Object>();

와 같이 Generic을 key와 data형으로 지정한다.


myMap.put("elementA", "Data_1");

과 같은 형태로 String의 key로 myMap에 저장된 Object 데이타를 제어할 수 있게 되며 Generic 형식에 따라야 한다.


put으로 저장된 데이터는 put할 때 지정했던 key로 접근이 가능하며

myMap.get("elementA")을 호출하면 저장된 "Data_1"이 반환된다.

myMap.remove("elementA")을 호출하면 해당하는 key를 가진 원소가 삭제된다.





자바 제네릭스(Generics)에 대해서 - T[], List<T> 등의 의미


ArrayAdapter의 두 생성자를 보면 다음과 같이 되어있다.

그런데 세 번째 매개인자의 데이터 타입이 T[] objects로 되어 있다.


ArrayAdapter(Context context, 

                    int textViewResourceId, T[] objects)

⇒ T[] objects가 의미하는 것은 어떤 배열이 여기에 올수 있다는 뜻이다.

T[]가 의미하는 것은 배열은 배열인데 data type은 어떤 data type도 가능하다는 뜻이다.


ArrayAdapter(Context context, 

                         int textViewResourceId, List<T> objects)

⇒ List<T> objects가 의미하는 것은 이 매개 인자의 자리에 List라는 interface의 객체가 올수 있다는 뜻이다. 

List<T>이므로 List가 담는 내용물의 data type은 T타입이므로 사용자가 정의하는 data type 뭐든지 올수 있다. 


그런데 List라는 interface를 보면 List를 implements한(상속받은) 하위 클래스들이 있는데 이들 중 어느 하나가 올수 있다는 뜻이다. 

대표적으로 ArrayList<E>가 올수 있다는 뜻이다.


public interface List implements Collection<E>

java.util.List<E>


Known Indirect Subclasses

AbstractList<E>, AbstractSequentialList<E>, ArrayList<E>, CopyOnWriteArrayList<E>,

LinkedList<E>, ObservableArrayList<T>,ObservableList<T>, Stack<E>, Vector<E>



+ Recent posts