클래스나 메소드 혹은 변수를 선언할 때 final 제어자가 붙을 수 있는데 각각 다음과 같은 제한이 생기게 된다.


[표 1] final 제어자의 효과

대상

제한

클래스

다른 클래스에서 상속을 하지 못 한다.

메소드

상속 받은 클래스에서 오버라이딩 하지 못한다.

클래스 변수

선언과 동시에 값을 지정하여야하며 이후 수정이 불가하다.

인스턴스 변수

선언과 동시에 초기화하거나 생성자 내에서 값을 지정할 수 있다. 이후에는 수정이 불가하다.

지역 변수

선언과 동시에 초기화하거나 그 이후에 값을 지정할 수 있다. 이후에는 수정이 불가하다.


예를 들어보자 .


final class CA {

   private static final int ia = 11;
   private final int ib = 22;
   private final int ic;

   public CA() {
       ic = 22;
   }

   public final void func() {
       final int id = 22;
       final int ie;
       System.out.println(id);
       ie = 22;
       //이후에 id는 수정 불가
   }

}

클래스 CA는 파이널 클래스이므로 이것을 상속 받아서 파생클래스를 만들지 못한다. 그리고 func()메소드는 final이므로 상속 받은 클래스에서 오버라이딩하지 못한다. (이 경우는 클래스 자체가 final이므로 별 의미가 없으나 일반 클래스의 final 메소드는 자식 클래스가 오버라이딩하지 못한다.) 많이 사용되는 String 클래스나 Math클래스도 final클래스이므로 이것을 상속받아 새로운 클래스를 파생시키지 못한다.


 그리고 final 정적 변수 ia는 반드시 선언하면서 그 값을 지정해 주어야 하며 다른 곳에서 초기화하지 못한다. 하지만 final 인스턴스 변수(위 예에서 ib와 ic)는 선언부에서 값을 지정하거나 생성자에서 값을 지정해 줄 수도 있으며 이후에 값을 변경하지 못하다는 점은 동일하다. 이 점을 이용해서 final 멤버 변수가 인스턴스마다 서로 다른 값을 가지도록 할 수 있다.


 func()메소드의 id, ie와 같이 메소드 내부의 final 변수는 메소드가 실행되면서 생성되고 메소드가 종료되면 소멸되는 것은 일반 변수와 같지만 한 번 초기화되면 그 이후에는 값을 변경할 수 없다. 지역 변수는 인스턴스 변수와 유사하게 선언하면서 값을 지정할 수도 있고 선언한 후에 별도로 초기화할 수도 있다. 따라서 이 점을 이용하면 메소드가 호출될 때마다 final 지역 변수가 다른 값을 가지도록 할 수 있다.


 만약 어떤 클래스의 인스턴스가 final로 생성되었다고 가정하자.


final ClassA ca = new ClassA();

ca는 클래스의 인스턴스로서 내부적으로는  참조값(주소)을 갖는 변수인데 final로 제한되면 한 번 참조가 생성된  이후에는 새로운 참조값을 가지지 못한다.


final ClassA ca = new ClassA();

….

….

ca = new ClassA(); //에러 발생


따라서 위와 같이 다른 곳에서 ClassA()의 새로운 인스턴스(참조값)를 대입하려고 하면 에러를 발생시킨다.



Posted by 살레시오

 자바에서 문자열을 저장하는데 사용되는 객체는 String, StringBuilder, StringBuffer 세 가지가 있다. 보통 문자열을 저장하는데 String 객체를 사용하지만 문자열을 빈번하게 수정할 필요가 있는 경우 String 객체보다는 StringBuilder 객체가 성능면에서 더 유리하다.


[표 1] 문자열 저장 객체간 비교

클래스명

특징

성능

메모리

String

불변하는 문자열 저장시 주로 사용


가장 낮다.

StringBuilder

수시로 가변하는  문자열 저장시 주로 사용

가장 높다.


StrungBuffer

다른 쓰레드 간 동기화가 필요한 경우 StringBuilder대신 사용




가변하는 문자열을 다루는데 있어서의 속도를 알아보기 위해서 다음과 같은 간단한 벤치마크 프로그램을 실행해 보았다. (JDK8 이용)



package tut_20;
import static java.lang.System.nanoTime;
public class Tut_20 {
 public static void main(String[] args) {
     long ls, le;
     long iter = 100000;

     ls = nanoTime();
     String str = "";
     for (int k = 0; k < iter; k++) {
         str += 'a';
     }
  System.out.println("String       :"+(nanoTime()-ls)/1e9+ "sec.");
       
       ls = nanoTime();
       StringBuilder strb = new StringBuilder();
       for (int k = 0; k < iter; k++) {
           strb.append('a');
       }
 System.out.println("StringBuilder:"+(nanoTime()-ls)/1e9+ "sec.");
       
       ls = nanoTime();
       StringBuffer strbf = new StringBuffer();
       for (int k = 0; k < iter; k++) {
           strbf.append('a');
       }
 System.out.println("StringBuffer :"+(nanoTime()-ls)/1e9+ "sec.");     
   }
}
실행 결과
String       :4.884195203sec.
StringBuilder:0.001943612sec.
StringBuffer :0.0015706sec.


결과는 보다시피 StringBuilder가 가장 빨랐고 StingBuilder 와 StringBuffer 는 차이가 별로 나지 않았다. (JDK5 이상에서는 String 객체도 수정이 일어날 때 내부적으로 StringBuffer로 변환되어 사용된다고 알고 있었는데 좀 의외다.)


 다음으로 문자열 내에서 검색하는 속도를 마찬가지로 간단한 프로그램을 실행하여 측정해 보았다. (JDK8 사용)


package tut_20;
import static java.lang.System.nanoTime;
import java.util.Random;

public class Tut_20 {
   public static void main(String[] args) {
       long ls, le;
       long iter = 100000000;

       String str = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";
       int len = str.length();
       char ch;
       Random rand = new Random();

       ls = nanoTime();
       for (int k = 0; k < iter; k++) {
           ch = str.charAt(rand.nextInt(len));
       }
 System.out.println("String       :"+(nanoTime()-ls)/1e9+ "sec.");
       
       StringBuilder strb = new StringBuilder(str);
       ls = nanoTime();
       for (int k = 0; k < iter; k++) {
           ch = strb.charAt(rand.nextInt(len));
       }
 System.out.println("StringBuilder:"+(nanoTime()-ls)/1e9+ "sec.");
       
       StringBuffer strbf = new StringBuffer(str);
       ls = nanoTime();
       for (int k = 0; k < iter; k++) {
           ch = strbf.charAt(rand.nextInt(len));
       }
 System.out.println("StringBuffer :"+(nanoTime()-ls)/1e9+ "sec.");
   }
}
실행 시간
String       :1.258389526sec.
StringBuilder:1.414282314sec.
StringBuffer :3.205902914sec.


결과에서 나타났듯이 String 이 근소하게 StringBuilder 에 비해서 빨랐다.


 따라서 다음과 같이 정리해 볼 수 있겠다.


  1. 수정될 필요가 없는 문자열은 String 객체로 사용한다.

  2. 빈번에게 수정되는 문자열을 다룰 때는 StringBuilder 객체를 사용한다.

  3. 빈번에게 수정될 뿐만 아니라 서로 다른 쓰레드간 동기화를 시켜야 할 경우에는 StringBuffer를 사용한다.





Posted by 살레시오

 외부 패키지의 클래스를 불러 사용하고자 할 경우 원래는 클래스명 앞에 패키지를 명시해야 한다. 예를 들어서 Random이라는 클래스가 java.util 패키지에 속해 있다. 이것을 불러 사용하려면 다음과 같이 해야 한다.


java.util.Random rand = new java.util.Random();


즉, Random 클래스 앞에 java.util 이라는 소속 패키지의 이름을 명시해야 하는데 여러 군데에서 Random클래스가 사용된다면 패키지 이름이 중복되어 불편한다. 그래서 import 명령을 사용하면 클래스가 속한 패키지 명을 생략할 수 있다.


package tut_20;

import java.util.Random;

public class Tut_20 {
   public static void main(String[] args) {
       Random rand = new Random();
   }
}


이와 같이 import 뒤에 패키지명을 포함한 클래스의 전체 경로를 명시해 주면 코드에서는 클래스 이름만 써주면 된다.


 만약 java.util.Data 라는 클래스를 사용하고 싶다면 import 문을 하나 더 추가하면 된다.


package tut_20;

import java.util.Random;
import java.util.Date;

public class Tut_20 {
   public static void main(String[] args) {
       Random rand = new Random();
       Date dt = new Date();
   }
}


이런 식으로 현재 파일에서 사용하는 외부 클래스는 얼마든지 import문으로 불러와서 사용할 수 있다.


 동일한 패키지의 모든 클래스를 불러오고 싶다면 와일드카드(*)문자를 사용하면 된다. 예를 들어서 java.util 패지키의 모든 클래스를 불러오고 싶다면 다음과 같이 하면 된다.


import java.util.*;


이렇게 하면 java.util 패키지의 모든 클래스를 사용할 수 있다.


package tut_20;

import java.util.*;

public class Tut_20 {
   public static void main(String[] args) {
       Random rand = new Random();
       Date dt = new Date();
       List<Long> al = new List<>();
   }
}


이 예에서 Random, Data, List 클래스는 모두 java.util 패키지에 속해 있으르모 하나의 import 문으로 충분하다. 하지만 와일드카드를 사용한 import 가 하위 패키지의 클래스까지는 포함하지 않는다는 점에 유의해야 한다.


 같은 패키지 내의 클래스는 import를 할 필요가 없이 바로 사용 가능하다. 그리고 java.lang 패키지의 클래스도 import할 필요가 없다. 따라서 String 클래스는 java.lang 패키지 내에 있지만 import하지 않아도 바도 사용할 수 있는 것이다.



Posted by 살레시오

 가상 머신(virtual machine)이라는 것은 소프트웨어로 작성된 기계 (영어권에서는 컴퓨터 하드웨어를 machine이라고 지칭하기도 한다.) 즉, 코드로 작성된 가상의 물리적인 하드웨어라는 의미이다. 가상 머신은 컴퓨터 안에 존재하는 소프트웨어로 구현된 컴퓨터를 의미하며 광범위하게 사용되고 있는 기술 분야이다.


[그림 1] 윈도 머신에서 가상 머신으로 데비안 리눅스를 실행하고 있는 모습


 자바 가상 머신 (java virtual machine, JVM)은 자바 애플리케이션을 수행하기 위한 가상 머신이다. 일반적으로 보통의 응용 프로그램은 운영 체제를 거쳐서 물리적인 머신(machine)에서 수행된다.


[그림 1] 일반적인 응용 프로그램의 수행 단계


하지만 자바 애플리케이션은 운영 체제와 물리적인 머신이 아니라 JVM 위에서만 수행되고 그 밑단의 모든 동작은 JVM이 알아서 처리하는 방식으로 동작한다. 따라서 자바로 작성된 프로그램은 윈도우즈용 JVM이 설치된 윈도 머신에서도 동작하며 맥용 JVM에 설치된 맥 머신에서도 동일한 코드가 동일하게 수행된다.(Write once, run everywhere.)


[그림 2] 자바 가상 머신에서의 자바 프로그램 동작 단계


윈도만을 혹은 맥만을 주로 사용하는 일반 사용자들은 JVM을 별도로 설치해야 하는 번거로움이 있기 때문에 별 의미가 없을 수 있다. 하지만 개발자 입장에서는 이것은 커다란 장점이다. 자바로 한 번 작성해 놓은 코드는 OS와 피지컬 머신에 상관없이 어디에서나 동일하게 수행되기 때문이다.


 이런 동작 방식의 단점은 JVM이라는 단계를 하나 더 거치기 때문에 성능은 일반적인 프로그램(예를 들어서 컴파일된 C/C++ 프로그램)에 비해서 떨어진다는 점이다. 컴파일된 자바 프로그램은 JVM에서 수행되는 바이트 코드(byte code)로 구성되고 이것을 JVM이 하나씩 해석하여 수행하는 방식으로 동작하기 때문이다. 하지만 요즈음에는 JVM 자체의 성능도 상당히 향상되어 네이티브 프로그램의 수행 속도와 거의 동등한 결과를 보여주고 있다. 이는 바이트 코드를 수행하기 전에 컴파일하는 JIT (just-in-time) 컴파일링과 자바 바이트 코드를 피지컬 머신에 최적화된 네이티브 기계어 코드로 변환하는 핫스팟(hotspot) 기능이 JVM에 도입되었기 때문이다.



Posted by 살레시오

 자바의 패키지(package)는 클래스(interface, enum..)의 묶음으로서 하나의 디렉토리(폴더)이다. 패키지의 이름과 같은 디렉토리 내에 클래스 파일들을 포함하며 하위 패키지도 포함하는 중첩된 구조도 가능하다. ( C/C++에 비해서 무척 단순한 구조를 가지고 있다.)


[그림 1] 자바의 패키지 구조. 패키지는 디렉토리임.


퍼블릭 클래스(퍼블릭 인터페이스, 퍼블릭 이넘..)가 물리적으로 하나의 파일인 것과 유사하게 패키지는 물리적으로 하나의 디렉토리이다. 즉, 같은 폴더 내의 이러한 파일들이 패키지를 구성한다. 예를 들어서 java.lang.System 클래스는 java패키지의 하위 패키지인 lang에 속한다. 따라서 물리적으로 java디렉토리 밑의 lang디렉토리 밑의 System.class 파일이다.


 예를 들어서 netbean에서 tut01이라는 프로젝트를 생성하면 처음에 다음과 같으 구조를 가진다. tut01이라는 프로젝트 폴더 밑에 src/tut01 폴더가 tut01 패키지가 된다. 그리고 그 안에 Tut01.java파일이 자동으로 생성되며 맨 첫 줄에 패지지가 명시되어 있다.


[그림 2] tut01 프로젝트 생성


클래스 파일의 (주석과 공백을 제외한) 맨 처음에는 소속 패키지를 선언하는 단일문이 위치해야 한다. [그림 2]에서도 맨 첫 줄에 ‘package tut01;’이라고 소속 패키지가 명시되어 있다.


 하나의 프로젝트는 일반적으로 하나 이상의 패키지를 포함한다. <프로젝트 디렉토리>/src 디렉토리 안에 패키지를 생성할 수도 있고 특정 패키지 안에도 하위 패키지를 생성할 수 있다.


[그림 3] 새로운 패지키들의 생성


해당 폴더에 들어가서 확인해 보면 새로운 패키지를 생성하면 같은 이름의 폴더가 생성된 것을 알 수 있다.


[그림 4] 생성된 폴더


위 그림에는 안 보이지만 tut01 폴더 하위에 sub01 패키지(폴더)가 생성되어 있고 src 밑에는 tut02, tut03 이라는 폴더가 생성된 것을 알 수 있다.


 그리고 자바 프로젝트를 빌드하면 <프로젝트디렉토리>/dist 디렉토리 밑에 jar 파일이 생성되는데 이것은 프로젝트 내의 모든 컴파일된 패키지를 포함한 압축파일이다. 예를 들어서 위의 프로젝트를 빌드하면 tut01/dist/tut01.jar 파일이 생성된다. 이 파일을 열어보면 이 프로젝트에 포함된 패키지들이 생성되어 있음을 알 수 있다.


[그림 5] winrar로 본 tut01.jar의 내용. tut01, tut02, tut03 패키지(디렉토리)가 보인다.


이것을 실행하려면 명령창에서 다음과 같이 하면 된다.


java -jar tut01.jar


[그림 6] 일반적인 자바 프로젝트(jar파일) 구조


 이와 같이 프로젝트는 패키지의 모음이며 패키지는 클래스 파일들의 묶음이라는 자바 디렉토리의 구조를 이해해야 한다.



Posted by 살레시오

 자바 인터페이스(interface)는 추상 클래스와 비슷한 점이 있지만 한 단계 더 추상화된 클래스이다. 기본적으로 다음과 같이 정의한다.


interface 인터페이스명 {
   public static final 자료형 변수명 = 변수값;
   public abstract 반환자료형 함수명(입력인자들);
}


인터페이스의 멤버 변수는 public static final 로만 지정 가능하고 이것은 생략할 수 있다. 인터페이스 변수는 final 이므로 반드시 변수값을 선언부에 지정해 주어야한다.  멤버는 public abstract 가 기본형으로서 이것도 생략할 수 있다. 예를 들면 다음과 같다.


interface IA {
   int ia = 11; // 앞에 public static final 이 생략됨
   void fun();  // 앞에 public abstract 가 생략됨
}


인터페이스도 추상 클래스와 마찬가지로 직접 객체를 생성할 수 없으며 이것을 구현하는 클래스는 객체화 할 수 있다.


class CA implements IA {
   public void fun() {
       int ib = ia + 11; // ia는 인터페이스에서 정의된 정적 변수이다.
       System.out.println(“fun() redefined.”);
   }
}


추상클래스와 다르게 인터페이스는 이를 구체화하기 위해 implements 라는 키워드를 사용하고 그 뒤에 구현할 인터페이스 명을 써야한다. IA를 구체화하기 위해서는 반드시 추상 메소드인 fun()의 본체를 위와 같이 정의하여야 한다. 그리고 fun()의 접근 지정자는 반드시 public이어야 한다. 그 이유는 인터페이스에서 (생략되었다 할 지라도) public abstract 으로 정의되었기 때문이다.


 실행 가능한 예를 들면 다음과 같다.


package tut03;

public class Tut03 {
   public static void main(String[] args) {
       Violin v1 = new Violin();
       v1.ready();
       v1.play();

       Guitar g1 = new Guitar();
       g1.ready();
       g1.play();
   }
}

interface Playable {
   String player = "살레시오";
   void ready();
   void play();   
}

class Violin implements Playable {
   public void ready() {
       System.out.println(player+"의 바이올린이 준비되었다.");
   }
   public void play() {
       System.out.println("낑~깡~낑~깡~");
   }
}

class Guitar implements Playable {
   public void ready() {
       System.out.println(player+"의 기타가 준비되었다.");
   }
   public void play() {
       System.out.println("디리링~디리링~");
   }    
}


이 예에서는 Playable 이라는 인터페이스를 구현한 Violin과 Guitar 클래스를 작성하였다. 인터페이스의 이름은 관례적으로 ~able 로 끝나는 것들이 많다.


 이와 같이 인터페이스는 구현하고자하는 여러 클래스의 공통적인 부분(정적 변수와 public 함수)만 기술해 놓은 기초 설계도와 같고 인터페이스를 구현하는 클래스에서 추상 메소드의 몸체를 모두 정의하도록 강요한다. 만약 인터페이스 구현하는 어떤 클래스가 모든 추상 메소드를 구현하지 않는다면 그 클래스는 추상 클래스가 되어야 한다는 점도 알아두어야 한다. JAVA8에서는 인터페이스에 대한 규약이 좀 더 확장되었는데 이것은 다른 포스트에서 다루도록 하겠다.



Posted by 살레시오

 자바도 클래스의 멤버 각각에 외부에서 접근할 수 있는 범위를 지정하는 접근 지정자(access modifier)를 둘 수 있다. 다음과 같은 네 가지가 있다.



접근 지정자

접근 범위

동일
클래스

동일
패키지

다른
패키지의
자식
클래스

다른
패키지

public

접근 제한 업음

O

O

O

O

protected

동일 패키지와 상속 받은 클래스 내부

O

O

O


default

동일 패지키 내에서만

O

O



private

동일 클래스 내에서만

O





public

 공개 정도가 가장 높고 어디에서든 자유롭게 접근할 수 있다.

protected

 같은 패키지 내에서 접근이 가능하고 다른 패키지에서도 상속을 받은 클래스 내부에서는 사용 가능하다. public과 다른 점은 다른 패키지의 자식 클래스 외부에서는 접근할 수 없다는 것이다.

default (package private)

 같은 패키지 내에서만 접근이 가능하다. 아무런 접근 지정자도 없을 경우 이 옵션이 자동으로 적용된다.

private

 동일 클래스 내에서만 접근이 가능한 가장 낮은 단계의 자유도를 갖는다.


다음과 같이 tut02 패키지의 CA클래스와 이를 상속받은 CB클래스가 있다고 하자.


package tut02;
public class CA {
   public int ia;
   protected int ib;
   int ic;
   private int id;
   
   public static int ie;
   protected static  int ig;
   static int ih;
   private static int ii;
}


같은 패키지(즉 tut02 패키지) 내에서는 클래스 외부에서 변수 ia, ib, ic를 자유롭게 접근할 수 있고 id는 외부에서는 접근이 안된다. 이 클래스가 같은 패키지 내에서는 public, protected, default 가 같은 효과를 갖는다. 이것을 상속받은 자식 클래스에서도 ia,ib,ic는 접근 가능하다.


 만약 외부에서 tut02패키지의 CA 클래스를 불러서 사용하는 경우를 보자.


package tut03;
import tut02.CA;

public class Tut03 {
   public static void main(String[] args) {
       CA a = new CA();// ia만 접근 가능
       CC c = new CC();// ia만
   }   
}

class CC extends CA {
   //ia, ib, ie, ig 접근 가능
}


객체 a와 c는 외부에서 ia 멤버만 접근할 수 있다. 즉 클래스 외부에서 접근할 수 있는 멤버는 public으로 지정된 것만이다. protected로 선언된 멤버들은 CA를 상속하는 CC 내부에서만 사용 가능하다. 즉, protected로 선언된 멤버는 다른 패키지에서 이것을 상속받은 자식 클래스의 private 멤버가 되는 것이다. 클래스 CC내부에서도 CA의 ic, id는 접근할 수 없는데 default, private 멤버는 다른 패키지에서는 접근이 안 되기 때문이다.




Posted by 살레시오

 추상 클래스(abstract class)란 하나 이상의 추상 메소드(abstract method)를 포함하는 클래스이다. 추상 메소드는 선언만 있고 본체는 없는 함수이며 선언부에 ‘abstract’ 라는 키워드를 붙인다. 추상 메소드가 포함되었다면 클래스도 추상 클래스이므로 클래스명 앞에도 ‘abstract’키워드를 붙여야 한다.


abstract class Animal {
   public String sName; //일반 멤버 변수
   ….
   public void move() { …} // 일반 메소드
   abstract void howl(); //추상 메소드
   …
}


추상 클래스는 추상 메서드를 포함하고 객체화 할 수 없다는 점만 제외하고 일반 클래스와 다르지 않으며 생성자, 멤버변수와 일반 메서드도 가질 수 있다. 추상 클래스 자체로는 클래스로의 역할을 하지 못하며 객체를 생성할 수 없지만 새로운 클래스를 작성하는데 있어서 부모 클래스로서 중요한 역할을 갖는다. 위의 예에서 Animal 클래스는 직접 객체를 생성하지 못하고 이를 상속받는 자식 클래스에서는 추상 메소드의 구체적인 본체를 가질 수 있다.


package tut02;

abstract class Animal {
   public String sName;
   public void move() {
       System.out.println("어슬렁 어슬렁");
   }
   abstract void howl();
}

class Dog extends Animal {
   public void move() {
       System.out.println("팔짝 팔짝");
   }

   void howl() {
           System.out.println("멍멍");
   }
}

class Cat extends Animal {
   void howl() {
           System.out.println("냐옹");
   }
}

public class Tut02 {

   public static void main(String[] args) {
       Dog happy = new Dog();
       Cat julia = new Cat();
       happy.move(); // 오버라이드된 멤버함수 호출
       happy.howl(); // 구현된 멤버함수 호출
       jular.move(); // 일반 멤버함수 호출
       julia.howl(); // 구현된 멤버함수 호출
   }
}
실행 결과
팔짝 팔짝
멍멍

어슬렁 어슬렁

냐옹


추상 메소드의 접근 지정자로 private는 사용할 수 없는데 이는 자식 클래스에서 받아서 구현되어야 하므로 당연하다. 다른 접근 지정자(public, protected)는 사용할 수 있고 생략되면 default (즉, 같은 패키지 안에서만 접근 가능)인 것은 일반 클래스와 동일하다.


 위의 예에서 처럼 추상 클래스는 다른 클래스들에게서 공통으로 가져야하는 메소드들의 원형을 정의하고 그것을 상속받아서 구현토록 하는데 사용된다. 메소드 오버라이드(override)와 유사해서 혼동하기 쉬우나 오버라이드는 안해도 상관없지만 추상 메소드는 자식 클래스에게 그 구현을 강요하는 기능을 한다. 위 예에서도 Dog 클래스는 move()메소드를 오버라이드 했지만 Cat클래스는 그러지 않았다. 하지만 howl()메소드는 반드시 구현해야 한다. 그리고 만약 어떤 추상클래스를 상속 받은 자식 클래스에서 추상 메소드를 구현하지 않았다면 자식 클래스도 추상 클래스가 되어야 한다는 점도 알아 두자.



Posted by 살레시오

 이전 포스트에서 설명한 포장형(wrapper class)의 생성자를 이용하면 문자열을 기본형 값으로 바꿀 수 있다.


[표 1] 포장형의 기본형 값을 반환하는 객체 메쏘드

메소드명

기능

byteValue()

byte값을 반환한다.

shortValue()

short값을 반환한다.

intValue()

int값을 반환한다.

longValue()

long값을 반환한다.

floatValue()

float값을 반환한다.

doubleValue()

double값을 반환한다.


예를 들어서 다음과 같이 Integer 형 객체에서 다양한 기본형 값을 얻을 수 있다.


package tut02;
public class Tut02 {
   public static void main(String[] args) {
       int ia = new Integer("1234567").intValue();
       float fa = new Integer("1234567").floatValue();
       short sa = new Integer("1234567").shortValue(); //(*)

       System.out.println(ia);
       System.out.println(fa);
       System.out.println(sa);
   }
}
실행 결과
1234567
1234567.0
-10617


위의 (*)에서와 같이 손실 변환(lossy conversion)의 경우에도 별다른 경고나 오류를 발생하지 않는다. 따라서 같은 형의 데이터의 변환에만 쓰는 것이 바람직하다.


 포장형으로 부터 기본형 값을 얻는 메소드로 parse 로 시작하는 정적 메소드들이 있다. 이 메소드를 이용하면 정수형을 반환하는 메소드는 기수를 지정해 줄 수도 있다.


[표 2] 포장형의 기본형 값을 반환하는 메쏘드

메소드명

기능

Byte.parseByte(String)

Byte.parseByte(String, radix)

문자열을 byte값으로 변환한다.

기수 radix인 문자열을 byte값으로 변환한다.

Short.parseShort(String)

Short.parseShort(String, radix)

문자열을 short값으로 변환한다.

기수 radix인 문자열을 short값으로 변환한다.

Integer.parseInt(String)

Integer.parseInt(String, radix)

문자열을 int값으로 변환한다.

기수 radix인 문자열을 int값으로 변환한다.

Long.parseLong(String)

Long.parseLong(String, radix)

문자열을 long값으로 변환한다.

기수 radix인 문자열을 long값으로 변환한다.

Float.parseFloat(String)

문자열을 float값으로 변환한다.

Double.parseDouble(String)

문자열을 double값으로 변환한다.


실제 프로그램에서는 주로 [표 2]의 정적 메소드를 자주 사용하게 된다.


package tut02;
public class Tut02 {
   public static void main(String[] args) {
       int ia = Integer.parseInt("1234567");
       int ib = Integer.parseInt("11110010",2); //이진수
       int ic = Integer.parseInt("1ab3f",16); //십육진수

       System.out.println(ia);
       System.out.println(ib);
       System.out.println(ic);
   }
}
실행 결과
1234567
242
109375


 포장형 객체를 반환하는 valueOf() 이라는 정적 메소드도 있다.


[표 3] 문자열로 부터 포장형 객체를 반환하는 메쏘드

메소드명

기능

Byte.valueOf(String)

Byte.valueOf(String,radix)

Byte.valueOf(byte)

문자열을 Byte 객체로 변환

기수 radix인 문자열을 Byte 객체로 변환한다.

byte값을 Byte 객체로 박싱

Short.valueOf(String)

Short.valueOf(String,radix)

Short.valueOf(short)

문자열을 Short 객체로 변환

기수 radix인 문자열을 Short 객체로 변환한다.

short값을 Short 객체로 박싱

Integer.valueOf(String)

Integer.valueOf(String,radix)

Integer.valueOf(int)

문자열을 Integer 객체로 변환

기수 radix인 문자열을 Interger 객체로 변환한다.

int값을 Interger 객체로 박싱

Long.valueOf(String)

Long.valueOf(String,radix)

Long.valueOf(long)

문자열을 Long 객체로 변환

기수 radix인 문자열을 Long 객체로 변환한다.

long값을 Long 객체로 박싱

Float.valueOf(String)

Float.valueOf(float)

문자열을 Float 객체로 변환한다.

float값을 FLoat 객체로 박싱

Double.valueOf(String)

Double.valueOf(float)

문자열을 Double 객체로 변환한다.

float값을 Double 객체로 박싱


예를 들어서


int ia = Integer.valueOf("1234567");


이렇게 하면 자동 언박싱이 일어나서 변수 ia에 1234567이란 값이 저장된다. 하지만 parseInt()함수보다는 당연히 성능이 떨어진다.




Posted by 살레시오

 자바에는 int, double과 같은 기본형 자료형(primitive type)의 포장 클래스(wrapper class)가 있어서 기본형을 객체로 다루어야 할 경우에 사용할 수 있다. 다음 표에서와 같이 포장 클래스명은 기본형의 첫 문자를 대문자로 바꾸면 된다.( char형과 int형만 이름이 다르다.)


[표 1] 포장 클래스

기본형

포장 클래스

생성 예

boolean

Boolean

Boolean bA = new Boolean(true);

Boolean bB = new Boolean(“false”);

char

Character

Character cA = new Character(‘a’);

byte

Byte

Byte byA = new Byte(10);

Byte byB = new Byte(“127”);

short

Short

Short sA = new Short(1234);

Short sB = new Short(“1234”);

int

Integer

Integer iA = new Integer(1234);

Integer iB = new Integer(“1234”);

long

Long

Long lA = new Long(1234);

Long lB = new Long(“1234”);

float

Float

Float fA = new Float(12.34f);

Float fB = new Float(“12.34f”);

double

Double

Double dA = new Double(12.34);

Double dB = new Double(“12.34”);


생성자는 위와 같이 해당하는 기본형 값을 줄 수도 있고 문자열로 줄 수도 있다. 문자열로 주는 경우 해당 데이터형의 형식에 맞아야 한다.


 박싱(boxing)이란 기본형을 참조형으로 변환하는 것이고 언박싱(unboxing)이란 반대로 참조형을 기본형으로 바꾸는 것이다. 그리고 JDK 1.5부터는 이것을 자동으로 해주는 기능이 추가되었다.


package tut02;
public class Tut02 {
   public static void main(String[] args) {
       Integer iA = new Integer(123);
       Integer iB = new Integer(123);
       
       int ia = (int)iA; //(1) 언박싱(unboxing)
       int ib = iB; //(2) 오토언박싱(auto unboxing)
       Integer iC = (Integer)456; //(3)박싱(boxing)
       Integer iD = ia; //(4)오토 박싱(auto boxing)
   }
}


위 예세서 (1)은 명시적으로 언박싱을 해주는 것이고 (2)는 자동으로 수행해 주는 것이다. 박싱의 경우도 자동으로 처리해 준다. 오토박싱과 오토언박싱은 대응되는 자료형 사이에만 일어난다는 점도 유의해야 한다.


 포장 클래스 객체간 연산도 가능하며 기본형과 포장형 간 연산도 가능하다.


package tut02;
public class Tut02 {
   public static void main(String[] args) {
       Integer iA = new Integer(123);
       Integer iB = new Integer(123);
       
       int ia = (int)iA; //(1) 언박싱(unboxing)
       int ib = iB; //(2) 오토언박싱(auto unboxing)
       Integer iC = (Integer)456; //(3)박생(boxing)
       Integer iD = ia; //(4)오토 박싱(auto boxing)
       
       Integer iE = iA + iB; // 포장형끼리의 연산
       Integer iF = ia - ib; // 기본형끼리의 연산 결과를 오토박싱
       int ic = ia * iB; // 기본형과 포장형 간 연산
   }
}


자바에서는 연산자가 오버로딩되지 않는다는 점을 생각하면 의아할 것이다. 하지만 이 코드는 내부적으로 포장형인 피연산자가 오토언박싱 되어서 기본형 끼리의 연산으로 수행되는 것이라고 이해할 수 있다.


 비교 연산도 가능하지만 내용물의 동치 여부를 검사할 때 ==기호대신 equals() 메소드를 이용해야 한다.


package tut02;
public class Tut02 {
   public static void main(String[] args) {
       Integer ia = new Integer(123);
       Integer ib = new Integer(123);
       Integer ic = new Integer(456);
       System.out.println("ia>=ib:"+(ia>=ib));
       System.out.println("ib>=ic:"+(ib>=ic));
       System.out.println("ia==ib:"+(ia==ib));
       System.out.println("ia.equals(ib):"+ia.equals(ib));
   }
}


ia>=ib:true
ib>=ic:false
ia==ib:false
ia.equals(ib):true


왜냐면 포장형고 객체이기 때문에 ==연산은 두 객체 인스턴스의 참조(주소값)을 비교하게 되는 것이다. 내용물의 동치 여부는 문자열 객체와 마찬가지로 equals() 메소드를 이용해야 한다.




Posted by 살레시오