2011년 1월 25일 화요일

Java로 Twitter4j를 이용해서 트위터읽기

우선은 코드만 올려두고 나중에 인증과정에 대한 설명이나 실행 방법 설명 추가할 예정


import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;

import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.http.AccessToken;
import twitter4j.http.RequestToken;


public class OAuthEx {

 public static void main(String args[]) throws Exception{
   // The factory instance is re-useable and thread safe.
   Twitter twitter = new TwitterFactory().getInstance();
   twitter.setOAuthConsumer("소비키", "소비비밀키");
   RequestToken requestToken = twitter.getOAuthRequestToken();
   AccessToken accessToken = null;
   BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
   while (null == accessToken) {
     System.out.println("Open the following URL and grant access to your account:");
     System.out.println(requestToken.getAuthorizationURL());
     System.out.print("Enter the PIN(if aviailable) or just hit enter.[PIN]:");
     String pin = br.readLine();
     try{
        if(pin.length() > 0){
          accessToken = twitter.getOAuthAccessToken(requestToken, pin);
        }else{
          accessToken = twitter.getOAuthAccessToken();
        }
     } catch (TwitterException te) {
       if(401 == te.getStatusCode()){
         System.out.println("Unable to get the access token.");
       }else{
         te.printStackTrace();
       }
     }
   }
   //persist to the accessToken for future reference.
   storeAccessToken(twitter.verifyCredentials().getId() , accessToken);
   Status status = twitter.updateStatus("아... 이거 때문인가????");
   System.out.println("Successfully updated the status to [" + status.getText() + "].");
 

   System.exit(0);
 }
 private static void storeAccessToken(int useId, AccessToken accessToken){

 System.out.println("useId : " + useId);
 System.out.println("AccessToken : " + accessToken);

 FileOutputStream fos = null;
 ObjectOutputStream oos = null;
 try{
 fos = new FileOutputStream("key.data");
 oos = new ObjectOutputStream(fos);

 oos.writeInt(useId);
 oos.writeObject(accessToken);

 }catch(Exception e){
 e.printStackTrace();
 }finally{

 try {
oos.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

 try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
 }
 }

}

2011년 1월 24일 월요일

초간단 이미지 만들기

JDK1.4에서부터는 공식 API에서 이미지 입출력 기능이 제공되기 시작한다.
따라서 아주 간단한 몇 라인의 코드만으로 이미지를 생성하는 것이
가능한데..

약간만 활용하면 카페나 사이트에서 사용하는 해킹 방지용 문자를 만들때 유용하다.




package com.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
public class TempPasswordMaker {

 public static void makeImage(OutputStream out, String msg)throws Exception{
  BufferedImage image = null;
  image = new BufferedImage(300,150, BufferedImage.TYPE_INT_RGB);
  Graphics g = image.createGraphics();
  g.setColor(Color.WHITE);
 
  g.setFont(new Font("Courier",Font.BOLD,22));
  g.fillRect(0, 0, 300, 150);
  g.setColor(Color.black);
  g.drawString(msg, 10, 70);
 
  Iterator imageWriters = ImageIO.getImageWritersBySuffix("jpg");
  ImageWriter imgWriter = null;
 
  while(imageWriters.hasNext()){
   imgWriter = (ImageWriter)imageWriters.next();
   System.out.println("IMGWRITER:"+ imgWriter.toString());
  }
  ImageIO.write(image, "jpg", out);
 }

 public static void main(String[] args)throws Exception{
 
  FileOutputStream fos = new FileOutputStream("aaa.jpg");
  makeImage(fos,"AAAAAAAAAAA");
 
 }
}

2011년 1월 18일 화요일

[도서추천] Writing Effective Use cases


절판되어서 너무나 아쉬웠던 책이 다시 나왔다.
예전에 단순코더에서 개발자의 세계로 나를 이끌어 주었던 책!

중간에 잃어버려서 너무 맘아팠던 책!

프로그래밍이란 하나의 글쓰기이다.
그리고 그 글을 어떻게 써야하는지를 설명해 준 책이다.

단순히 프로그래밍 뿐 만이 아니라
논리적인 업무를 하는 사람이라면 한번쯤 읽어보라고 추천하고 싶은 책이다.

왜 수많은 디자인패턴은 인터페이스로 구현하게 되는가?

아마도 Java를 공부하는데 있어서 가장 험난한 고비는 역시 interface가 아닌가 싶다.
하긴 나도 인터페이스를 10년이 넘은 지금도 '이게 최선입니까? , 확실해요? '라는 질문을 던지고 있으니 초급자들에게는 가장 넘기 어려운 산일 것이다.

상속이라는 것은 초급자들이 좀 쉽게 적응을 할 수 있다는 것은 너무나 명확한 사실인듯하다.

하지만 인터페이스의 경우는 어떤가? 솔직히 전혀 아니올시다..

그럼 여기서 우리가 한번 생각해 봐야하는 것은 '왜 그 수많은 객체지향 디자인 패턴은 상속보다는 인터페이스를 사용하는가?'이다.

우선 인터페이스의 몇 가지 특징을 생각해 보자.

인터페이스는 컴파일러가 인식하는 하나의 코드에 대한 스펙을 의미한다.

즉 어떤 객체이건 간에 특정 메소드를 오버라이드하고 있는 객체라면 컴파일러는 지나칠 수 있다는 뜻이다.
사실 이 특징은 다형성이라는 것이 가지는 장점이라고 생각해도 되고, 상속 역시 다형성의 일부이기 때문에 굳이 인터페이스에 국한된 설명이라고 할 수는 없다.


인터페이스는 온갖 다른 클래스를 하나의 타입과 동일한 메소드의 오버라이드라는 놀라운 효과를 가져온다.
아무리 다른 로직들(메소드들)과 데이터를 가진 객체들이라고 할 지라도 동일한 인터페이스를 구현하므로 인해서 모든 객체들을 하나의 타입으로 아우룰수 있는 최고의 방법이다.
흔히들 Java에서 인터페이스를 'Java의 꽃'이라고 부르는 것도 이러한 이유이다.

켄트 백의 말처럼 인터페이스의 가장 무서운 점은 '인터페이스를 바라보고 코드를 작성할 때에는 실제 인터페이스 너머에는 어떤 객체가 존재하는지 신경쓰지 않아도 된다'는 점이다.

-------------------------------------------------------------------------------
인터페이스가 왜 유연한지 생각해보자면 가장 쉽게 점심메뉴를 생각해 보시라..

'오늘은 카레를 먹을꺼야' VS '오늘은 따끈한 국물요리를 먹을꺼야'

'카레'를 먹는 방식은 클래스와 객체 방식이다. 왜냐하면 이 말을 통해서 우리가 바로 어떤
실체를 떠올릴 수 있기 때문이다.

반면에 '따끈한 국물요리'는 인터페이스 방식이다. 이 말을 통해서는 실체를 떠올릴 수는 없다. 왜냐하면 너무나 많은 국물 요리 후보 객체가 생겨버리기 때문이다.

객체지향이 현실세계를 모델로 한다고 하자. 우리가 점심메뉴는 어떤 방식을 선택하는가?
클래스인가? 아님 인터페이스 인가?

2011년 1월 12일 수요일

상속은 위험하다(1)

자바나 C#등 모든 OOP에서 모든 개발자들이 가장 쉽게 접근하여 사용할 수 있는 개념은 바로 상속이다.

상속은 쉬울뿐 아니라 강력한 기능을 가지고 있다는 데에는 의견이 없다.

하지만 이러한 구현 클래스에 의한 상속은?

-----------------------------------------------------------------------------
public class PClass {
 
 
 private PClass target;
 
 public void setTarget(PClass target){
  this.target = target;
 }
 
 public void doA(){
  target.doA();
 }
}

-----------------------------------------------------------------------------

public class SClass extends PClass {
 
 public void init(){
  super.setTarget(this);
 }
 public void doA(){
  System.out.println("sub....");
  super.doA();
 }
 
 
 public static void main(String[] args) {
  
  SClass s = new SClass();
  s.init();
  s.doA();
  
 }
}
-----------------------------------------------------------------------------

위의 소스를 보자.
물론 예제를 만들기 위한 편협한 소스이지만 분명한 건 컴파일 시에는 위의 코드는 문제가 없다.

만일 당신이 이 소스의 문제가 클래스라고 말한다면 어쩔 수 없다. 반쯤은 동의할 의향이 있기 때문에..
하지만 좀 더 생각해 보면 상속이라는 구조가 성립되지 않는다면 위의 소스는 컴파일 자체가 불가능하다는 사실을 인정하기 바란다.

실행결과는 명확하다. StackOverflow가 발생한다.

이러한 상속을 소위 '깨지기 쉬운 상속'이라고 얘기할 수 있다.


구현 클래스를 이용해서 하는 코딩이라면 극단적으로 위와 같은 방식의 코딩도 가능하다는 얘기다.
따라서 상속을 이용하는 경우에는 하위에서 어떤 방식으로 오버라이딩이 되었는지 모르기 때문에 발생하는 문제가 더 크다는 얘기다.

추상클래스는 객체생성이 안될까?

사람들은 흔히들 추상클래스는 객체 생성이 불가능하다고 알고 있습니다.
하지만 정말 그럴까요?

public  abstract class AAA {

 {
  System.out.println("aaaa");
 }

 private String name;
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }


}

우선 부모 클래스  AAA 라고 만들었습니다(네이밍룰은 레드썬!)
자세히 보면 기본블럭(default block) 을 이용해서 객체가 생성되면 자동적으로 aaaa 라는 문자열을 찍도록 해 두었습니다.

자식 클래스는 당연히 AAA클래스를 상속해서 만들었습니다.

public class BBB extends AAA {
 {
  System.out.println("BBB");
 }


 public static void main(String[] args) {

  BBB obj1 = new BBB();
  System.out.println(obj1);

  BBB obj2 = new BBB();
  System.out.println(obj2);

 }
}
별다른 내용은 없고 메인 메소드를 작성해서 객체를 두개 만들어 보았습니다. 다만 객체 생성을 확인하기 위해서 BBB클래스 역시 기본 블럭으로 BBB라는 문자열을 출력하도록 수정했습니다. 이 코드의 컴파일 결과 부터 살펴봅니다.
[parsing started BBB.java]
[parsing completed 32ms]
[search path for source files: .]

역시 BBB.java 파일을 메모리로 파싱합니다.
그런데 BBB클래스는 AAA를 상속하기 때문에 AAA역시 필요하게 됩니다.

[loading .\AAA.java]
[parsing started .\AAA.java]
[parsing completed 0ms]
[loading java\lang\Object.class(java\lang:Object.class)]
[loading java\lang\String.class(java\lang:String.class)]
[checking BBB]
[loading java\lang\System.class(java\lang:System.class)]
[loading java\io\PrintStream.class(java\io:PrintStream.class)]
[loading java\io\FilterOutputStream.class(java\io:FilterOutputStream.class)]
[loading java\io\OutputStream.class(java\io:OutputStream.class)]
[checking AAA]
[wrote .\AAA.class]
[checking BBB]
[wrote BBB.class]
[total 281ms]

이 코드를 가지고 실행하게 될 코드는 어떻게 될까요?
javap 명령을 이용해서 역컴파일 시킨 결과는 다음과 같습니다.


Compiled from "BBB.java"
public class BBB extends AAA
  SourceFile: "BBB.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method       #8.#17; //  AAA."<init>":()V
const #2 = Field        #18.#19;        //  java/lang/System.out:Ljava/io/PrintS
tream;
const #3 = String       #20;    //  BBB
const #4 = Method       #21.#22;        //  java/io/PrintStream.println:(Ljava/l
ang/String;)V
const #5 = class        #20;    //  BBB
const #6 = Method       #5.#17; //  BBB."<init>":()V
const #7 = Method       #21.#23;        //  java/io/PrintStream.println:(Ljava/l
ang/Object;)V
const #8 = class        #24;    //  AAA
const #9 = Asciz        <init>;
const #10 = Asciz       ()V;
const #11 = Asciz       Code;
const #12 = Asciz       LineNumberTable;
const #13 = Asciz       main;
const #14 = Asciz       ([Ljava/lang/String;)V;
const #15 = Asciz       SourceFile;
const #16 = Asciz       BBB.java;
const #17 = NameAndType #9:#10;//  "<init>":()V
const #18 = class       #25;    //  java/lang/System
const #19 = NameAndType #26:#27;//  out:Ljava/io/PrintStream;
const #20 = Asciz       BBB;
const #21 = class       #28;    //  java/io/PrintStream
const #22 = NameAndType #29:#30;//  println:(Ljava/lang/String;)V
const #23 = NameAndType #29:#31;//  println:(Ljava/lang/Object;)V
const #24 = Asciz       AAA;
const #25 = Asciz       java/lang/System;
const #26 = Asciz       out;
const #27 = Asciz       Ljava/io/PrintStream;;
const #28 = Asciz       java/io/PrintStream;
const #29 = Asciz       println;
const #30 = Asciz       (Ljava/lang/String;)V;
const #31 = Asciz       (Ljava/lang/Object;)V;
{
public BBB();
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method AAA."<init>":()V
   4:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   7:   ldc     #3; //String BBB
   9:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   12:  return
  LineNumberTable:
   line 2: 0
   line 5: 4
   line 6: 12

public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=3, Args_size=1
   0:   new     #5; //class BBB
   3:   dup
   4:   invokespecial   #6; //Method "<init>":()V
   7:   astore_1
   8:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:  aload_1
   12:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   15:  new     #5; //class BBB
   18:  dup
   19:  invokespecial   #6; //Method "<init>":()V
   22:  astore_2
   23:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   26:  aload_2
   27:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   30:  return
  LineNumberTable:
   line 12: 0
   line 13: 8
   line 15: 15
   line 16: 23
   line 18: 30

}

코드를 보면 내부적으로  AAA클래스 역시 로딩하는 것을 확인할 수 있습니다.
실행된 결과는 더욱 재밌습니다.

aaaa
BBB
BBB@c17164
aaaa
BBB
BBB@1fb8ee3

객체를 두 개 만들었더니 AAA안의 기본 블럭 역시 두번(즉 AAA클래스의 객체도 두번 만들어진다) 실행되는 것을 볼 수 있습니다.

Java에서 추상 클래스는 문법적으는 객체 생성을 제약하는 것이 맞습니다. 즉 직접 객체를 생성할 수 없도록 하는 장치라는 겁니다.

하지만 상속이라는 것이 실제로 객체를 한번에 부모 클래스까지 만들어 내기 때문에 추상클래스 역시 그 예외가 아니라는 겁니다.

2011년 1월 10일 월요일

java.lang.Object에 대한 단상

Java에서 상속과 관련해서 반드시 언급해야 하는 클래스가 java.lang.Object클래스일 것이다.

초급자의 입장에서는 가장 많이 의문을 가지는 클래스이기도 할 것이다.
어째서 java.lang.Object라는 클래스가 존재하는가?

어째서 java.lang.Object클래스에 몇 가지 메소드들이 정의되어 있는가?

    public native int hashCode();
    public final native Class<?> getClass();
    public boolean equals(Object obj) {
      return (this == obj);
    }

native라는 키워드는 언제 사용되는 것인가?

native 키워드는 JNI를 이용한 통신에 사용되는 키워드라고 생각하면 된다.
JNI를 이용하는 프로그래밍의 순서는 다음과 같다
  • Write Java code
  • Compile Java code
  • Create C header (.h file)
  • Create C stubs file
  • Write C code
  • Create shared code library (or DLL)
  • Run application

native 메소드가 java.lang.Object에 정의되어 있다는 것은 간단하게 말해서
소위 말하는 운영체제와 연결되는 코드를 가지고 있다는 것을 의미한다.

상속에서 가장 상위의 클래스는 java.lang.Object이다. 그렇다면 어떤 객체를 만들때 마다
java.lang.Object 클래스의 객체 역시 만들어 진다는 것을 의미한다.

상속이 가장 많이 사용되는 것은 당연히 원래의 코드를 그대로 물려주는 기능이다.
그렇다면 모든 객체가 운영체제와 통신하는 native 메소드를 물려주었다는 것은 Java에서 만들어진 모든 객체는 직접 운영체제와 통신할 필요가 있다는 것을 의미한다.


운영체제와 직접 통신해야 하는 부분이 무엇인가?
이 질문에 해답은 운영체제의 스레드에 있다.

즉 JVM을 벗어나 운영체제와 통신하는 코드가 있다는 것은 스레드와 같은 이슈의 해결을 위해서는 결과적으로 직접 호출하는 방식을 사용한다는 것을 의미한다.

결국에 JVM에서 객체가 생성되는 것은 순수하게 Java Virtual Machine만을 사용하는 것이 아니라 운영체제의 메모리의 구조도 같이 사용하게 된다는 것을 의미한다.

2011년 1월 7일 금요일

오버라이딩의 실체

동영상 강의 제작하다가 문득 내용이 괜찮을 듯 해서 블로그로 다시 정리합니다. 


public class Book2 {

public void doA(){
System.out.println("book2 class doA method.... ");
}
}

라는 클래스가 있습니다. 
그리고 이것을 상속받는 하위클래스가 있습니다. 


public class Magazine2 extends Book2{

public void doA(){
System.out.println("Magazine2 class doA method.... ");
}
}

물론 이것을 테스트 하는 존재도 만들었습니다. 

public class Magazine2Test {

public static void main(String[] args) {
Book2 obj  = new Magazine2();
obj.doA();
}
}

이 코드를 실행하는 엔진의 해석은 다음과 같습니다. 

public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=2, Args_size=1
   0: new #16; //class Magazine2
   3: dup
   4: invokespecial #18; //Method Magazine2."<init>":()V
   7: astore_1
   8: aload_1
   9: invokevirtual #19; //Method Book2.doA:()V
   12: return
  LineNumberTable: 
   line 6: 0
   line 8: 8
   line 10: 12

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      13      0    args       [Ljava/lang/String;
   8      5      1    obj       LBook2;


}

컴파일러가 체크하는 로직을 보면 상당히 재밌습니다. 

[loading java/lang/Object.class(java/lang:Object.class)]
[loading java/lang/String.class(java/lang:String.class)]
[checking Magazine2Test]
[loading ./Book2.java]
[parsing started ./Book2.java]
[parsing completed 1ms]
[loading ./Magazine2.java]
[parsing started ./Magazine2.java]
[parsing completed 1ms]
[wrote Magazine2Test.class]
[checking Book2]
[loading java/lang/System.class(java/lang:System.class)]
[loading java/io/PrintStream.class(java/io:PrintStream.class)]
[loading java/io/FilterOutputStream.class(java/io:FilterOutputStream.class)]
[loading java/io/OutputStream.class(java/io:OutputStream.class)]
[wrote ./Book2.class]
[checking Magazine2]
[wrote ./Magazine2.class]



컴파일러는 보는 바처럼 Book2클래스부터 메모리로 체크하기 시작합니다. 
즉 변수의 타입에 나온 것을 보고 메모리로 올리는 겁니다. 

클래스라는 것은 컴파일러가 사용하는 정보이기 때문입니다. 

즉 Java가 overriding이라는 기법을 사용하는 것은 컴파일러가 보는 클래스와 실제 실행엔진이 실행하는 클래스의 객체와의 불일치를 가져오게 됩니다. 

음..좀 더 길게 쓰고 싶었는데.. 우선 커피숖에서 원고쓰기는 너무 힘들어요... 



2011년 1월 6일 목요일

음.. 제 동영상 강의가 형편없다는 리뷰를 보았습니다.

강의를 잘 한다고도..

강사라고도 ..

생각해 본 적은 없었습니다.

근데 막상 인터넷에 그런 글을 보니..
확 간단하게 해 버릴까라는 생각도 들긴 하더군요..

근데.. 몇 시간 지나고 나서 생각해 보면..

어차피 여러 명 맘에 들고 싶어서 강의 하는 거 아니거든요..
그리고 뭐 좋아하는 사람도 한명은 있겠다 싶기도 하고...

외로울때 보면 좋지 않나? 라는 생각도 해보고..

역시 스타일을 바꾸지는 않을꺼 같네요..


게다가 그런 글을 쓰신 분은 아직 학생인듯하고..
그분이 세상에서 가장 좋다는 책은.. 내 취향도 아닌듯하니..
이런 이유로 상처받고 싶지 않아요.. ㅠㅠ

인터넷 강의는 낙도나 지방에 있는 분들에게 좀 도움이 되려고 하는 것이니..
절대로 양을 줄이지는 않을 겁니다.

2011년 1월 5일 수요일

접근제한(1)- 우리는 메소드를 정확히 이해하는가?

객체지향에서에서 객체가 가지는 데이터는 좀 특별한 의미를 가지게 된다.

대게의 경우 인스턴스 변수의 성격은 다음과 같다.

'객체의 메소드의 결과를 누적하기 위해서 사용하는 경우'
'객체의 메소드들 간에 데이터를 공유하기 위해서 사용하는 경우'
'객체의 메소드가 실행될 때 현재의 데이터를 기준으로 해서 분기하는 경우'

즉 객체지향에서는 데이터는 메소드의 결과라는 것이지... 데이터 그 자체는 보관의 대상일 뿐이다.

다음의 코드를 생각해 보자.



public class AccessTest {

private int result;
public int getResult(){
return result;
}
}

이 코드를 보면 마치 getResult( ) 라는 메소드를 실행하면 실제로 result변수를 반환해 주는 것처럼 오해하기 딱 좋은 코드다. 

그럼 정말로 result라는 변수를 반환해 주는 것일까? 
불행하게도 자바는 현재 변수의 정확한 메모리 주소를 알아낼 수 있는 방법을 제공하지는 않는다. 뭐.. 로직을 작성할 때 가능하면 헷갈리게 하고 싶지 않았던 설계자의 의도이니 할 말은 없지 않은가? 

메소드의 코드를 보면 재밌는 것은 메소드의 리턴 타입에 int라는 타입이 사용되었다는 점이다. 

이 점이 뭐가 특이한가?라고 생각할 수도 있겠지만, 조금만 더 생각해보면 이것이 바로 메소드의 리턴타입의 비밀일 지도 모른다. 

메소드는 실행되기 위해서는 메소드가 실행되는 Stack에 들어가야만 실행이 되는 구조이다. 그렇다면 이 실행을 하기 위해서 만들어지는 결과물 역시 어떤 메모리 공간을 차지 하지 않을까? 

우리가 변수의 선언 앞에 int i; 라고 선언하는 것을 생각해 보자. 
이때 알고 있는 사실은 메모리상에 int의 공간만큼 메모리 공간이 할당된다는 것이다. 
그렇다면 메소드의 리턴 타입도 같은 원리가 아닐까?

즉 메소드의 리턴 타입은 '실행된 결과를 메모리 공간에 담기위한 설정'이라는 것이다. 
위의 코드를 실해하면 메소드가 실행된 결과를 위해서 메모리 공간이 하나 할당 되고, 실행되는 결과물은 변수의 연산처럼 메모리 공간을 사용하게 될 것이다. 

메소드의 리턴타입이란 참 재밌게도 연산자와 달리 그 실행 결과를 특정 변수에 담거나 접근해서 소비하는 일은 하지 않는다.


int a = 10;
int b = 20; 
//컴파일 에러
a + b;

반면에 메소드란 참 어이 없게도 같은 결과값을 반환해도 별도의 변수로 받지 않아도 아무런 문제를 일으키지 않는다. 

즉 우리가 판단할 수 있는 사실은 객체의 메소드의 리턴타입이라는 것은 실제로 무언가 결과를 반환하는 것이 아니라 메소드의 특정 결과물을 메모리상에 변수처럼 공간을 만들어서 넣겠다는 뜻으로 해석해야 한다. 

다시 앞에서 나온 코드를 살펴보자. 

public class AccessTest {

private int result;
public int getResult(){
return result;
}
}

getResult( )메소드의 실행은 결과적으로 메모리 공간에 int에 해당하는 공간을 만들어 두고, 이 공간에 변수 result를 복사(Java에서는 복사라는 개념만이 존재하기 때문에)해 두는 방식을 사용하게 된다. 

잊지 말아야 할 것은

'메소드의 리턴타입이라는 것은 결국 메모리에 공간 할당하고 리턴 결과를 담아두는 것'






2011년 1월 4일 화요일

접근제한자가 객체지향의 중요한 특징이라고?

흔히들 객체지향 언어의 특징중에 하나로 '정보 은닉'이라는 것을 든다.

접근제한이라는 설명을 하면서 이런 특징을 설명하는데..
사실 이것은 객체지향 패러다임과 관련이 있다고 할 수 있는지에 대해서는 아직도 의문이 든다.

왜 내가 이런 생각을 하는지 한번 얘기를 풀어보도록 하자.

우선 객체지향 패러다임에서 객체(object)라는 것은 언어로 말하자면 변수를 별도의 공간에 각 각 저장하는 방식을 의미한다. 즉 Java의 경우라면 클래스의 복사본의 구조를 그대로를 메모리 상에 별도의 영역에 할당하는 방식이다.

이렇게 되면 각 메모리의 공간을 접근하기 위해서 포인터(pointer)를 활용하게 될 것이고,
그것을 Java에서는 레퍼런스라는 이름으로 부르고 있다.

접근제한이라는 것은 간단히 말하자면 이 공간에 접근하는 것을 특정한 키워드를 이용해서 제어하는 것을 의미한다.

즉 접근 제한이라는 것은 메모리 공간을 마음대로 접근하지 못하게 하는 행위를 의미하는 것이지, 객체지향에서만 사용되는 특별한 개념은 아니라는 것이다.


접근 제한이라는 말 보다는 오히려 데이터나 로직에 접근할 수 있는 하나의 울타리나 상황(Context)라는 개념이 더욱 적합한 개념이 아닐까?

-----------------------------------------------------------------
사실 접근 제한을 보면 상당히 말도 안되는 구성을 가지고 있다.

우선 가장 접근제한 레벨이 높은 private을 한번보자.

private은 외부 클래스에서는 접근이 불가능하다는 판단이다. 재밌는 것은 이 범위라는 것이 단순히 클래스의 '{ }'에 대해서만 한정적이라는 사실이다.


public class Save {

private int total;
public void addMoney(int amount){
total = total + amount;
}
public void withdraw(int amount){
total = total - amount;
}
public void displaySave(){
System.out.println("CURRENT :" + total);
}
}

위의 코드를 보자. 
total이라는 데이터가 private으로 정의되어 있다. 따라서 외부의 클래스에서는 이 데이터에 access를 할 수 없게 되어 있다. 

하지만 이 접근 제한은 만일 객체를 사용하는 main 메소드를 안쪽에 만들어 버리면 참으로 무용지물이 된다. 


public class Save {

private int total;

---- 중략
public void displaySave(){
System.out.println("CURRENT :" + total);
}

public static void main(String[] args) {
Save s1 = new Save();
s1.total = 1000;
s1.displaySave();
}
}


위의 코드는 실제로 에러가 발생하지도 않고, 실행되 제대로 된다.
이 코드를 보면 객체지향에서의 '정보의 은닉'이라는 것은 절대 환상이다.
만일 정말로 객체의 정보가 은닉되어서 감추어 진다면 main메소드가 실행되는 환경에서조차도 s1이 가진 정보는 보호되어야 하지 않을까?

-----------------------------------------------------------------------
그렇다면 우리는 도대체 private을 뭐라고 이해해야만 하는 것일까?

일단은 우리가 쉽게 Java책들에서 찾아볼 수 있는 개념을 한번 정리해 보자.

* 접근제한을 하면 외부 클래스에서 접근할 수 없다(private)

* 하지만 private이라고 해도 동일한 클래스 내에서는 접근이 가능하다. static이건 아니건 상관은 없다.

이 두가지 사실만으로 판단해 보면 결국 접근제한을 한다는 것은 마치 코드의 영역을 표시해 주는 '{ }'와 유사하지 않을까?

예를 들어 조금은 과장된 방식의 코드를 작성해 보자.


public class ForEx {

{
int j = 10; 
for(int i = 0; i < 10; i++){
System.out.println(i + j);
}
}
}



이 코드는 전혀 문제가 없는 코드이다. '{ }'를 이용해서 변수의 범위에 대한 제한을 걸어 둔 것 뿐이다.


public class ForEx {

{
int j = 10; 
for(int i = 0; i < 10; i++){
System.out.println(i + j);
}
}
//이러면 에러 
j++;
}
재밌는 것은 접근제한 보다도 오히려 위와 같은 '{ }'가 변수나 로직에 대한 접근을 더욱 엄격하게 할 수 있다는 것이다. 


이제 슬슬 결말로 가보자. 

우리가 배우는 접근제한자 라는 것은 우리가 생각했던 것만큼 객체지향적이지도 않을 뿐더러 프로그래밍에서 영역을 구분하는 '{ }'보다도 못하다는 것이다. 

그럼 접근제한자를 왜 생각해 냈을까?  
클래스의 내부에서만 사용되는 변수? 

동일한 정보(클래스는 일종의 메타데이터이므로)를 가지는 것들만의 일종의 필터(filter)가 아닐까? 

필터(filter)라는 가정을 해 보자. 

모든 객체는 자신이 속한 클래스의 정보를 알아 낼 수가 있다. 흔히 reflection기술을 이용하면 이 사실을 좀 더 알수 있을 것이다. 

만일 A라는 객체와 B라는 객체가 동일한 클래스에서 나온 객체라면 이 두 객체는 서로가 동족(? 동일한 클래스에서 생성되었음)이라는 것을 알수 있을 것이다. 

그렇다면 이 동족들 사이에서만 사용될 수 있는 일종의 변수의 이름을 생각해 보면 어떨까? 
private이 붙은 변수는 즉 이런 동일한 족속들이 사용하는 일종의 정해진 변수의 이름이라고만 생각해 보면 어떨까? 

default 접근제한자의 경우라면 동일한 패키지의 이름으로  정보를 가진 객체만 사용하는 '은어'와 같은 존재라고 보면 어떨까? 


내가 생각하는 접근제한자라는 것은 이런 개념이다.