레이블이 자바인 게시물을 표시합니다. 모든 게시물 표시
레이블이 자바인 게시물을 표시합니다. 모든 게시물 표시

2015년 7월 27일 월요일

자바(JAVA)의 final 제어자


  클래스나 메소드 혹은 변수를 선언할 때 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()의 새로운 인스턴스(참조값)를 대입하려고 하면 에러를 발생시킨다.

2015년 7월 15일 수요일

자바(JAVA)의 문자열 객체 String, StringBuilder, StringBuffer 비교


 자바에서 문자열을 저장하는데 사용되는 객체는 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를 사용한다.

2015년 7월 14일 화요일

자바(JAVA)의 import 명령어

 외부 패키지의 클래스를 불러 사용하고자 할 경우 원래는 클래스명 앞에 패키지를 명시해야 한다. 예를 들어서 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하지 않아도 바도 사용할 수 있는 것이다.


2015년 7월 13일 월요일

자바 가상 머신 (java virtual machine, JVM) 개요

 가상 머신(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에 도입되었기 때문이다.


자바(JAVA)의 패키지(package) 이해

 자바의 패키지(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파일) 구조

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