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

2017년 7월 6일 목요일

[Java] 메소드 호출(Method Call)과 다이나믹 바인딩(Dynamic Binding)

우리가 메소드를 호출할 때 어떤 일이 일어날까, 왜 수퍼클래스의 메소드를 호출했는데 상속받은 객체의 메소드가 호출되는지를 알려면 자바(Java)에서의 메소드 호출법을 이해해야한다. 예를들어 우리가 클래스 c의 인스턴스인 x 객체의 f(y)라는 함수를 호출한다 가정할 때, x.f(y)이런식으로 호출할것이다. 이때 우리눈에 보이지 않는 컴퓨터세상에서는 어떤일이 일어나는지를 알아보자.
  1. 컴파일러가 선언된 객체의 형(type)과 메소드 이름을 찾아본다. 아마 파라미터만 다르고 f 라는 이름을 가진 메소드들이 많을것이다. 컴파일러는 클래스 c와 c의 수퍼클래스의 모든 메소드f 를 종합한다. 이제 컴파일러가 호출 대상 메소드들을 모두 종합했다.
  2. 컴파일러가 메소드 호출시 제공된 아규먼트(argument)들의 형(type)을 분석한다. 제공된 아규먼트들과 일치하는 파라미터를 가진 메소드 f가 있다면 해당 메소드를 호출한다. 이 과정을 overloading resolution이라고 한다. 참고로 overloading이란 이름은 같으나 파라미터가 틀린, 즉 method signature(메소드 이름+파라미터)가 틀린 메소드들을 선언하는것을 의미한다. 우리 가정상에서는 x가 String 타입일시 f(String x)라는 시그니쳐를 가진 메소드가 호출되고, x가 int 타입이라면 f(int x) 시그니쳐를 가진 메소드가 호출된다. 만약 double형에서 int형으로, 섭클래스 객체에서 수퍼클래스 객체로 타입변환이 필요하다면 이 과정은 조금 더 복잡해지게된다. 만약 컴파일러가 타입변환을 적용하고 서도 파라미터가 일치하는 메소드를 찾지 못했다면 에러가 발생한다. 그렇지 않다면, 컴파일러가 호출할 메소드의 이름과 파라미터 타입을 결정하게된다. 
  3. 만약 메소드가 private, static, final 속성을 가지거나, 생성자(Constructor) 메소드라면, 컴파일러가 바로 해당 메소드를 호출한다. 이 과정을 static binding이라고 한다.
  4. 만약 3번의 경우가 아니라면, 메소드는 implicit parameter, 즉 x 객체의 실제 타입에따라 결정된다.  이 과정을 dynamic binding이라고 한다.
  5. 프로그램이 실행되는동안 메소드 호출을 위해 dynamic binding이 진행될 때, 자바 가상머신은 implicit parameter, x가 참조하는 객체의 실제 타입에 맞는 메소드를 호출하게된다. x의 실제 타입이 클래스 X가 되고, 클래스 X는 클래스 W를 상속한다고 할때, 2번과정에서 찾은 메소드 f들 중에 클래스 X에 선언된 메소드 f가 있다면 그 메소드를 호출한다. 만약 아니라면, X가 상속하는 클래스, 즉 클래스 W에 선언된 메소드 f를 찾아본다. 그게 아니라면 하위 -> 상위 클래스로 선언된 메소드들을 찾아간다. 이 과정은 매우 많은 반복이 이뤄지는 과정이라, 가상머신이 미리 각 클래스의 메소드 시그니쳐들을 모아둔 데이터베이스를 작성하고, 실제로는 데이터베이스를 찾아보는 형식으로 진행된다. 
이게 간략하게 요약한 메소드호출의 과정이다. 물론 더 세세하게 파고든다면 엄청 긴 과정이되겠지만... 

다이나믹 바인딩(Dynamic Binding)은 매우 중요한 특징이다. 다이나믹 바인딩은 원래 있던 코드를 수정하지 않고도 프로그램을 확장할 수 있는 확장성을 준다. 언제든 상속(Inheritance)을 통해 설계를 확장할 수 있기 때문인데, 다이나믹 바인딩이 없다면 메소드 오버라이딩(method overriding)과 같은 상속만의 특성도 작동하기 때문이다.

2017년 7월 5일 수요일

[Java] 상속(Inheritance)과 다형성(Polymorphism)

상속(Inheritance)


객체지향프로그래밍(Object Oriented Programming)에서 중요한 개념인 상속(Inheritance)은 말그대로 객체가 다른 객체를 상속하는 개념이다. 객체가 다른 객체를 상속함으로써, 상속받는 객체는 상속하는 객체의 데이터필드, 메소드에 접근해 사용할 수 있다. 자바도 객체지향 프로그래밍 언어인만큼 상속(Inheritance)이라는 컨셉을 적용한 언어이다. 차량 관리 프로그램에서 차량 객체를 만드는 클래스를 생각해보자. 아마 대부분 차량 객체 하나로 다 잘 만들 수 있을것이다. 하지만, 특수 차량이 존재하고, 특수 차량은 그냥 차량과 달리 특수한 관리가 필요하다고 할때, 아마도 대부분의 경우에는 차량용 객체뿐만 아니라 특수차량용 객체가 별도로 필요하게된다. 이 경우에 객체 설계자는 특수차량 클래스를 완전 새로 적고 기능을 추가할수도 있지만, 특수차량 클래스와 "차량"이라는 특징을 공유하는 유사한 클래스, 즉 차량 클래스에서 프로그래밍 해놓은 데이터필드나, 메소드들을 상속하여 가져와서 사용할수도 있다. 이 상속으로 인해서 특수챠량 클래스와 그냥 차량 클래스는 "is-a" 관계를 가진다. 그러니까 모든 "특수차량"은 "차량"이다라는 관계가 된다. 그리고 추후 차량 클래스를 상속하는 대형차량, 소형차량 등의 다른 섭클래스(Sub-Class)들도 같은 관계에 놓이게 된다. 그리고 상속(Inheritance)은 한 세대에서 끝나는것이 아니라 쭉 이어질 수 있다. 즉 최상위 클래스에서 하위 클래스 몇십개로 이어지는것도 가능하다.

섭클래스(Subclass)는 extends 키워드를 이용해 쉽게 선언할 수 있다.


public class SpecialVehicle extends Vehicle{
...
}

extends 키워드와 상속할 클래스명을 붙임으로써 섭클래스는 상속하는 클래스의 메소드와 데이터필드에 접근할 수 있다. 보통 상속받는 클래스는 subclass, derived class, child class라고 불리며 상속하는 클래스는 superclass, base class, parent class라고 불린다. 보통은 수퍼클래스(superclass)와 섭클래스(subclass)라는 용어가 많이 쓰인다. 위 경우에서는 Vehicle 클래스가 수퍼클래스이며, SpecialVehicle 클래스가 섭클래스이고, 섭클래스가 수퍼클래스인 Vehicle 클래스보다 더 많은 기능을 가진다. SpecialVehicle 클래스가 Vehicle 클래스보다 더 많은 데이터필드와 메소드들을 캡슐화하고 있다. 특수차량은 다른 차량들에 장착되지않은 부스터를 장착하고 있다 가정하자. 그렇다면 SpecialVehicle 클래스는 새로운 데이터필드와 메소드를 가지게된다


public class SpecialVehicle extends Vehicle{
   private String boosterModel;
   ...
   public void setBoosterModel(String b){
      this.boosterModel = b;
   }
   ...
}

사실 메소드와 데이터필드를 추가하는 것은 일반 Vehicle 클래스와 다를게 없다. 하지만 boosterModel 데이터필드와 이 필드의 accessor, mutotar 메소드는 오직 SpecialVehicle 객체를 통해서만 접근이 가능하다. 하지만 원래 Vehicle 클래스가 가지고 있는 model, brand 등의 accessor, mutator 메소드는 SpecialVehicle 객체를 통해서도 접근이 가능하다. 그리고 SpecialVehicle 클래스에서는 Vehicle 클래스의 private 데이터 필드들과 메소드들에도 접근이 가능하다. 이때문에 보통 객체 설계시, 수퍼클래스는 보편적인(General) 메소드들과 데이터필드를 가지고, 섭클래스는 조금 특화된(Specialized) 메소드들과 데이터필드를 가지게된다. OOP에서 공통적인 기능들을 모아 수퍼클래스를 만들어 섭클래스의 부담을 줄이는게 상속의 핵심기능이다.

만일 수퍼클래스가 가진 몇몇 메소드들이 현재 클래스에 맞지 않아 현재 클래스에 특화시킬 필요가 있다면 메소드를 오버라이딩(Overriding) 하면 된다. 예를들어 특수차량에는 일반 보혐료와 추가적인 특수 보험료가 붙는다 가정할 때, 메소드를 오버라이딩하여 수정할 수 있다.


//수퍼클래스
public class Vehicle {
 ...
 private double insuranceFee;
 ...
 public double getInsuranceFee() {
  return insuranceFee;
 }
 ...
}

// 섭클래스
public class SpecialVehicle extends Vehicle{
 ...
 private double specialInsuranceFee;
 ...
 public double getInsuranceFee() {
  return super.getInsuranceFee() + specialInsuranceFee;
 }
 ...
}

우선 Vehicle 클래스의 insuranceFee는 private 변수이다. 그러기 때문에 Vehicle 클래스 내부에서 선언된 메소드들만 접근이 가능하다. 그래서 SpecialVehicle 클래스에서는 직접적인 접근이 불가능하다. 대신 간접적으로 accessor 메소드를 이용해 접근해야 한다. 하지만 메소드의 이름이 둘다 일치한다. 그래서 그냥 해당메소드를 부를 경우에는 그 메소드가 수퍼클래스에 존재해도 현재 클래스의 메소드를 우선적으로 호출하기 때문에, 재귀(Recurrence)현상이 일어나게 된다. 참고로 오버라이딩(overriding)되는 메소드는 수퍼클래스의 메소드 접근자가 public일시 같은 접근자를 가지고 있어야된다. 즉 public이면 public 접근자를 가져야 되고, 그렇지 않다면 컴파일시 에러가 발생한다. 여기서 상위 클래스, 즉 수퍼클래스를 가리키는 super 키워드를 이용해서 상위 클래스를 참조하도록 하면 해결이 된다. 다만 super 키워드는 this 키워드와는 달리 현재 객체를 참조하도록하는게 아니라, 컴파일러에게 직접적으로 수퍼클래스의 메소드, 변수를 참조하도록 요청하는 키워드이기 때문에 this와는 달리 생각해야한다.

super 키워들 이용하면 수퍼클래스의 생성자함수를 이용해 섭클래스의 생성자함수를 완료할 수 있다.


public class SpecialVehicle extends Vehicle{
 ...
 private double specialInsuranceFee;
 ...
 public SpecialVehicle(String brand, String model, double insuranceFee, double specialInsuranceFee,...) {
  super(brand, model, insuranceFee, ...);
                this.specialInsuranceFee = specialInsuranceFee;
                ...
 }
 ...
}

이렇게 super 키워드를 이용해 수퍼클래스의 생성자함수 호출도 가능하다. 참고로 만약 섭클래스의 컨스트럭터가 수퍼클래스의 컨스트럭터를 호출하는 구문이 없다면, 컴파일러는 자동적으로 파라미터 없이 수퍼클래스의 컨스트럭터를 호출하며, 수퍼클래스에 파라미터를 받지 않는 컨스트럭터가 없다면 에러가 발생한다

이제 한번 사용해보자.


Vehicle[] vs = new Vehicle[2];
vs[0] = new Vehicle("Hyundai", "Sonata", 44.6);
vs[1] = new SpecialVehicle("Volvo", "X841", 30, 100.5); //일반보험료 30에 특수보험료 100.5
for(Vehicle e:vs){
 System.out.println(e.getInsuranceFee()); 
}
// 44.6
// 130.5

놀랍게도 Vehicle 타입으로 생성된 배열이지만 Vehicle 타입에 종속된 SpecialVehicle 객체도 배열에 저장할 수 있다.
그리고 보험료 출력시 vs[0] 객체는 일반 보험료를 출력했지만, vs[1] 객체는 특수보험료를 포함한 총 보험료값을 출력했다. SpecialVehicle 타입이 아닌 Vehicle 타입 e 객체를 이용해 메소드를 호출했는데, 프로그램이 알맞은 메소드를 호출해 맞는 결과를 가져왔다. 이렇게 e와 같은 객체 변수가 여러 타입을 참조할 수 있는것을 다형성(Polymorphism)이라고 한다. 그리고 프로그램이 실행되는 시점에서 알맞는 메소드를 호출하는것을 Dynamic Binding이라고 한다.


public class SpecialVehicle extends Vehicle{

 private double specialInsuranceFee;
        ...
 public void setSpecialInsuranceFee(double specialInsuranceFee) {
  this.specialInsuranceFee = specialInsuranceFee;
 }
        ...
}

public class Test {
 public static void main(String[] args) {
  Vehicle[] vs = new Vehicle[2];
  vs[0] = new Vehicle("Hyundai", "Sonata", 44.6);
  vs[1] = new SpecialVehicle("Volvo", "X841", 30, 100.5);
  vs[1].setSpecialInsuranceFee(55.5); 
                // 컴파일 에러가 발생한다
 }
}

한가지 주의할점은 vs[1]을 통해서는 SpecialVehicle 클래스의 메소드를 호출하는것은 불가능하다.
vs[1]이 선언된 타입은 Vehicle이지만 setSpecialInsuranceFee 메소드는 SpecialVehicle 클래스에만 존재하기때문에 그렇다. 즉 Vehicle 타입의 객체는 SpecialVehicle 메소드들중 오버라이딩(Overriding)된 것들만 호출 가능하다.

만약 클래스가 상속되지 못하게 하고싶다거나 상속은 되나 메소드를 수정하지 않았으면 좋겠다면 final 키워드를 이용하면 된다. 예를들어 SpecialVehicle 클래스를 가장 하위 클래스로 만들고 싶다면


public final class SpecialVehicle extends Vehicle{
  ...
}
이 경우에는 클래스 상속도 불가능하고 자동적으로 모든 메소드들이 final 속성을 지니게 된다.

만약 상속되는 SpecialVehicle 클래스나, specialize 메소드를 하위클래스에서 오버라이딩하지 않게 하고싶다면

public final class SpecialVehicle extends Vehicle{
  ...
  public final void specilize(int x){
  ...
  }
}
이 경우에는 클래스 상속은 가능하나 해당 메소드를 오버라이딩하는것은 불가능하다.

다른 자료형처럼 상속관계에 놓여있는 자료형끼리도 캐스팅(Casting), 형변환이 가능하다. 예를들면 double 에서 int로 변환하고 싶다면 double 변수앞에 (int)라는 키워드를 놓아주기만 하면 되듯이, Vehicle형 객체를 SpecialVehicle형 객체로 변환하고 싶다면 (SpecialVehicle) 키워드를 앞에 적으면 된다.


Vehicle[] vs = new Vehicle[2];
vs[0] = new Vehicle("Hyundai", "Sonata", 44.6);
vs[1] = new SpecialVehicle("Volvo", "X841", 30, 100.5);
SpecialVehicle a = (SpecialVehicle) vs[1];

우리는 vs[1] 객체를 통해서는 SpecialVehicle 클래스의 모든 기능에 접근하지 못하지만, a 객체를 통해서는 SpecialVehicle 클래스의 모든 기능을 사용할 수 있다. 자바에서 모든 변수는 형(type)을 가진다. 타입은 변수가 참조하는 객체의 종류와 무었을 할수있는지를 알려준다. 그렇기 때문에 Vehicle 타입의 객체인 vs[1]를 통해서는 SpecialVehicle의 메소드를 호출하지 못한것이다. 그리고 만약 String과 Vehicle 타입처럼 아무런 관계에 있지않은 객체끼리 변환을 시도하면 에러를 얻게 된다.

다형성(Polymorphism)


앞의 상속(Inheritance) 예제를 보면 B 클래스가 A 클래스를 상속할시에 "B는 A다(B is A)" 관계에 놓이게 된다. 만약 이 관계가 아니게되면 A 클래스는 B 클래스를 상속하는것이 아니다. 다른 관계가 있다면, 두 클래스가 교체 법칙(Substitution Principle)에 적용된다는것인데, 만약 적용된다면 프로그램이 언제든디 수퍼클래스의 객체를 원할 때, 섭클래스의 객체가 사용될 수도 있게된다. 정리하자면, B 클래스가 A 클래스를 상속할 때, 프로그램이 B 객체를 A 객체처럼 사용할수도 있다는뜻이다. 이는 자바프로그램에서 객체형(Object Type) 변수가 다형적(Polymorphic)이기 때문이다. 그래서 모든 Vehicle 형의 변수는 위의 예제에서 본것처럼 모든 하위클래스의 객체도 참조할수있다. 위 예제를 보면 Vehicle 타입의 배열에도 SpecialVehicle 타입의 객체가 저장된다.

2017년 7월 4일 화요일

[Java] 패키지(Package)의 개념

자바에서 패키지(Package)란 클래스들의 모음이다. 패키지를 통해 편리하게 프로젝트를 관리할 수 있고 내 코드들을 다른 라이브러리로부터, 또 다른 라이브러리들끼리 서로 구분가능하다. 기본적인 자바 라이브러리 또한 java.lang, java.util. java.net java.awt와 같은 다양한 패키지들를 통해 분류된다. 자바 라이브러리는 우리가 일반 폴더 구조에서 볼수있는 계층적 패키지구조를 가지고 있다. 상위 패키지에 하위패키지가 종속되는 형식이다. 그래서 모든 자바 패키지들은 가장 상위 패키지인 java와 javax 패키지에 종속되어 있다.

패키지(Package)를 사용하는 가장 큰 이유는 클래스명의 고유성을 보장하기 위해서이다. 만약 다른 두 프로그래머가 서로 다른 목적을 가진 클래스를 생성했는데 이름 같다 가정할 때, 서로 다른 패키지를 사용함으로써 충돌을 피할수 있다. 그리고 패키지 이름의 고유성을 보장하기 위해 최상위 패키지명에 "com.muckycode"와 같은 방식으로 인터넷 도메인 주소를 꺼꾸로 사용하기도 한다. 인터넷 도메인은 이미 고유성을 가지고 있기때문에 다른 충돌을 걱정할 필요가 없기 때문이다.

클래스는 같은 패키지내 모든 클래스들과 다른 패키지내 모든 public 클래스들을 사용할수있다. 다른 클래스를 사용하는데는 2가지 방법이 있다.
하나는 클래스 선언전 import 키워들 이용해서 패키지 전부 또는 클래스 1개만을 가져와 사용하는것과, 패키지 전체 주소를 가져와 사용하는것이다.


// 패키지 주소 전체를 이용하는 방식

java.util.Date today = new java.util.Date();
java.util.Date yesterday = new java.util.Date();
위방식은 클래스 사용시마다 매번 주소를 입력해야되기 때문에 엄청 귀찮다. 그래서 보통은 import 방식을 쓰게된다.


// import 키워드를 이용하는 방식

import java.util.*
import java.math.BigInteger;

public class Test {
...
}

import 방식은 하나의 클래스에만 적용가능하지만, * 문자를 이용해서 패키지의 모든 하위 클래스에도 적용이 가능하다.
import 방식을 이용하는것은 편리하지만, 만약 서로다른 패키지가 같은 이름을 가진 클래스를 가지고 있다면 이는 컴파일시 에러로 이어진다. 컴파일러가 무슨 클래스를 로드해야될지 모르기 때문인데, 이를 피할려면 특정 클래스의 주소를 import하는 방식을 사용해야한다. 만약 같은 이름을 가진 클래스들을 모두 사용해야한다면 패키지 전체 주소를 이용하는 방식을 사용해야한다. 예를들면 Date라는 클래스는 java.util 패키지에도 존재하고, java.sql 패키지에도 존재한다. 두 패키지를 모두 사용한다는 가정하에 import 방식을 이용하면


import java.util.*;
import java.sql.*;

public class Test {

 public static void main(String[] args) {
  Date newDate = new Date();
 }
}
// Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
// The type Date is ambiguous
// The type Date is ambiguous

위와 같은 컴파일 에러를 얻게된다. 그래서 이럴때는 import 방식 말고 전체 주소를 이용하는 방식을 이용해야한다.


java.util.Date utilDate = new java.util.Date();
java.sql.Date sqlDate = new java.sql.Date(0);
}

import 방식은 static 클래스, 메소드, 데이터 필드들도 적용이 가능하다.
자주쓰는 System.out 객체는 static 클래스인 System을 import 하거나, static 객체인 System.out을 import하여 간소화할수 있다.


import static java.lang.System.out;
import static java.lang.Math.*;
public class Test {

 public static void main(String[] args) {
  out.println("Test"); //Test
  double x = sqrt(2); // Math.sqrt(2)가 간소화됐다.
  out.println(x); // 1.4142135623730951
 }
}
}

클래스를 패키지에 종속시키려면 클래스 선언전에 package 키워드를 이용해 어떤 패키지에 속해있는지 정해줘야한다. 만약 정해주지 않는다면, 클래스는 기본적으로 "default package"에 속하게 되고 "default package"는 아무런 패키지 이름을 가지지 않는다. 그리고 해당 클래스를 파일 디렉토리상으로도 패키지 구조와 일치하게 이동해야 한다.


package com.muckycode.java.app

public class myMathApp {
...
}

위 클래스는 com.muckycode.java.app 패키지에 속하게 되고, com폴더 -> muckycode폴더 -> java폴더 -> app폴더 내에 존재해야한다.

2017년 7월 3일 월요일

[Java] 메소드 파라미터(Paratmeter) 전달, call by Reference vs Value

메소드를 호출할 때 우리는 파라미터를 전달 할 수 있다. 예를들어, 우리가 자주 쓰는 메소드인 "System.out.println(파라미터)" 도 파라미터를 받아 실행된다. 이런 파라미터를 전달하는 박식은 "call by reference", "call by value"라는 2가지 방식으로 나뉜다.

Call by Value 방식은 간단하다. 단순히 메소드를 호출하는 호출문(caller)이 전달하는 값(value)의 복사값이 메소드에 전달된다. 그러므로 호출된 메소드는 전달받은 변수 값을 변경해도 원래 변수에는 적용되지 않는다. 그에비해 Call by Reference 방식에서는, 메소드가 호출문에서 제공된 변수가 저장된 메모리 참조(Reference)값을 전달받는다. 그러므로 메소드에서 파라미터값을 변경한다면 해당 변수에 모두 적용된다.

이런 Call by ... 단어는 단순이 자바에서만이 아니라 프로그래밍 이론적으로도 메소드 파라미터의 습성을 다룰 때 쓰이는 단어이니 잘 알아두자. 옛 프로그래밍 언어인 Algol 언어에서는 Call by name이라는 개념도 존재했다.

자바(Java)에서는 언제나 Call by Value 방식으로 메소드를 호출한다. 그래서 메소드 내부에서 전달받은 파라미터를 수정하거나 해도 실제 변수에는 적용되지 않는다.


public static void main(String[] args) {
 int test = 0;
 raiseIntBy(test, 3);
 System.out.println(test); // 0
}

public static void raiseIntBy(int x, int y){
 x+=y;
}

test 변수의 값은 변하지 않는다. 우선 raiseIntBy 메소드의 변수 x는 test 변수의 복사값인 0으로 초기화된다. 그리고 x에 y가 더해져서 x는 3이 되었지만, 변수 test의 값은 그대로 0이게 된다. 그리고 메소드가 종료되고, 변수 x의 스코프에서 벗어나게 된다. 원시자료형(Primitive Type)의 경우에는 이런 시나리오로 진행이 되게 된다. 하지만 자바에는 2가지 자료형이 있고, 객체 자료형에서는 다른 결과를 내게 된다.
이는 객체 자료형이 저장하는 값이 원시자료형처럼 자료값이 아닌 자료가 저장된 메모리 참조값(Memory Reference)을 저장하기 때문이다.


// Person 객체를 설계해보자
public class Person {
 private String name;
 private int age;
 public Person(String name, int age) {
  this.name = name;
  this.age = age;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public int getAge() {
  return age;
 }
 public void setAge(int age) {
  this.age = age;
 } 
}

// Test 클래스에서 객체를 사용해보자
public class Test {

 public static void main(String[] args) {
  Person p = new Person("Jason", 22);
  System.out.printf("Name: %s, Age: %d \n", p.getName(), p.getAge());
  // Name: Jason, Age: 22

  changeName(p, "Eva");
  changeAge(p, 33);

  System.out.printf("Name: %s, Age: %d \n", p.getName(), p.getAge());
  // Name: Eva, Age: 33

  Person q = new Person("David", 45);
  System.out.printf("Name: %s, Age: %d \n", q.getName(), q.getAge());
  // Name: David, Age: 45

  swap(p, q);

  System.out.printf("Name: %s, Age: %d \n", p.getName(), p.getAge());
  // Name: Eva, Age: 33

  System.out.printf("Name: %s, Age: %d \n", q.getName(), q.getAge());
  // Name: David, Age: 45
 }

 public static void changeName(Person s, String newName) {
  s.setName(newName);
 }

 public static void changeAge(Person s, int a) {
  s.setAge(a);
 }

 private static void swap(Person a, Person b) {
  Person temp = a;
  a = b;
  b = a;
 }
}

위 코드를 실행하면 객체의 name 변수와, age 변수의 값이 바뀌게 된다. 메소드가 호출될 대, Person sp의 값의 복사값을 받게 된다. 그리고 그 복사값은 p가 가진 객체 참조값과 동일한 값이다. 결국 s와 p는 둘다 같은 참조값을 가지게되고 같은 인스턴스를 참조하게된다. 그리고 둘이 같은 값을 참조하게 되니 둘중 한 객체에 적용되는 변경사항은 둘 모두에게 적용된다.

하지만 swap 메소드를 실행새서 나온 결과를 알다시피 객체를 서로 교체하는 메소드에서는 변경사항이 적용되지 않는것처럼 보인다. 약간 헷갈릴수도 있겠지만, Call by Reference 방식이라면 두 인스턴스가 서로 뒤바뀌는게 맞다. 하지만 Call by Value 방식에서는 앞서 말했듯이 파라미터가 변수의 복사값을 가진다. swap 메소드를 자세히 보자.
  1. 메소드 호출시에 Person 인스턴스 a 와 b는 또다른 Person 인스턴스 p 와 q의 참조값을 복사해 초기화 된다.
  2. 다른 Person temp라는 새로운 인스턴스가 생성되고 a의 참조값을 가지게 된다.
  3. Person 인스턴스 a는 b의 참조값을 가지게 된다.
  4. b는 a의 참조값을 가지게 된다.
  5. 그렇다면 서로 참조값이 바뀌었으니, 서로 바뀌어야하는게 아닌가 싶은데, 실질적으로 인스턴스 a, b는 p, q와는 서로가 참조하는 메모리 주소만 같지 완전 다른 인스턴스이다. 그러니 그 참조값에 저장하는 정보에 적용되는 변경사항이 아니고 인스턴스의 참조값이 변경되는건 적용되지 않는다. 그러면 swap 메소드를 마지막단계까지 진행해보자.

  6. 메소드가 끝나고 인스턴스 a와 b의 스코프(Scope)가 종료되어 a와 b에 접근할 수 없게 된다.
  7. a와 b가 저장하는 참조값은 변했지만 실질적으로 원래 인스턴스인 p와 q는 달라진게 없다.

Call by Value방식을 요약하게되면
  • 메소드는 원시자료형(Primitive Data Type) 파라미터를 변경하는게 불가능하다.
  • 메소드는 객체의 상태(State)를 변경할수있다.
  • 메소드는 객체가 또다른 객체를 참조하게 하는것은 불가능하다.

[Java] static 키워드

자바는 메소드나 데이터를 static 키워드르 붙여 static으로 정의할 수 있다. static은 기본적으로 변수나 메소드가 인스턴스가 아닌 클래스에 속하게 한다.
즉, 인스턴스를 통해 변수에 접근하는게 아니라, 클래스를 통해 접근하고, 이때문에 여러개의 인스턴스에 서로 달리 존재하는 일반적인 변수,메소드와 달리 한개의 클래스에 존재하기 때문에 한개밖에 존재하지 않는다.
위 한문장만 읽으면 조금 헷갈리는데, static 변수와 static 메소드를 각각보면 좀 이해하기 쉽다.


private static int staticVariable;

public static void staticMethod(){
}

일반적인 변수가 객체(인스턴스)당 1개씩 존재하는 반면 static으로 정의된 변수는 클래스당 1개씩 존재한다. 예를들어 vehicle 객체 중 4륜객체가 1이라는 코드를 가지고 이 코드를 저장하는 정수형 변수가 id라고 할때 vehicle 객체는 아래와 같을거다.


public class Vehicle {

   private static int id = 1;
   public static String brand = "Hyundai";
   private String model;

//생성자함수와 다른 mutator, accessor 메소드는 생략
...
}

위의 객체를 생성하고 id 변수와, model 변수에 접근하고, 수정할때 결과를 본다면 아래와 같은 결과가 나온다.


Vehicle a = new Vehicle("Avantte");
Vehicle b = new Vehicle("Prius");

System.out.printf("ID: %d || Model: %s\n",a.getId(),a.getModel());
//ID: 1 || Model: Avantte

System.out.printf("ID: %d || Model: %s\n",b.getId(),b.getModel());
//ID: 1 || Model: Prius

//id 및 model 값 변경
a.setId(2);
a.setModel("Sonata");
 
System.out.printf("ID: %d || Model: %s\n",a.getId(),a.getModel());
//ID: 2 || Model: Avantte

System.out.printf("ID: %d || Model: %s\n",b.getId(),b.getModel());
//ID: 2 || Model: Prius

System.out.printf("브랜드: %s", Vehicle.brand);
//브랜드: Hyundai

위 코드를 실행하게되면, a 객체의 id 변수를 변경했는데, b 객체의 id 변수도 변경된것을 알 수 있다. 이는 id 변수가 static이기 때문이고, static 변수가 아닌 model 변수는 a 객체가 변경되도 b 객체에서 변하지 않는다. 그러니 한 클래스에서 인스턴스화된 모든 객체가 static 변수를 공유한다고 생각하면 된다.
또한 static 변수가 public 으로 선언된다면, 객체생성을 하지 않고, Vehicle.brand처럼 그냥 클래스를 통해 바로 호출이 가능하다.
이런 static 변수들은 보통 final 키워드와 함께 선언되어 Math.PI 변수 처럼 불변(immutable)하는 상수로 이용된다.
우리가 자주쓰는 System.out객체도 static과 final 속성을 가지고 있다. 또한 public 속성과 final 속성을 함께가지는 변수는 객체의 캡슐화를 훼손하지 않는다.

데이터 또는 메소드를 static으로 정의한다면 해당 메소드는 객체에서 실행되지 않는다. 대표적인 예로 Math.pow같은 Math 클래스의 메소드들이 있다. x의 제곱을 계산하는데 우리는 아무 객체도 생성않고 바로 pow 메소드를 호출해 결과값을 반환받는다. 그러니 결과적으로 implicit 파라미터가 필요하지 않다.


public class Vehicle {

   private static int id = 1;
   private static String brand = "Hyundai";
   private String model;

   public static String getBrand(){
   return brand;
   }
//생성자함수와 다른 mutator, accessor 메소드는 생략
...
}

위의 Vehicle 객체에 brand 변수가 private 속성을 가지고 brand 변수에 대한 static accessor 메소드가 존재할 때 우리는 아무런 객체를 생성하지 않고 brand 변수에 접근이 가능하다. 만약 brand 변수의 accessor 메소드가 static 속성을 가지고 있지 않다면 아래와 같이 객체 생성없이 brand 값에 접근할수는 없다.


System.out.println("브랜드: " + Vehicle.brand);
// 브랜드: Hyundai

static 메소드들은 다음과 같은 상황에서 사용하면 된다


  • 메소드가 필요한 모든 파라미터들이 explicit 파라미터로 제공되어 객체의 상태(State)에 접근할 필요가 없을 때
  • 메소드가 클래스의 static 필드에만 접근하면 될때


static 메소드의 대표적인 예는 main 메소드가 있다. main 메소드는 어떤 객체에서도 실행되지 않는다. 다만 프로그램이 실행될때, 아무런 객체도 생성되지 않았기 때문에 main 메소드가 실행되고 프로그램에 필요한 객체들을 생성한다. main 메소드는 어느 클래스든 사용될수 있으니, 객체 설계 후 테스트를 할때도 사용할수있다.

2017년 7월 2일 일요일

[Java] 객체 생성과 캡슐화(Encapsulation)

앞서 말했듯이 객체는 클래스에서 생성된다. 기본적으로 클래스는 아래 형식으로 이루어져있다.

class 클래스이름
{
field1,field2,field3...
constructor1
constructor2
...
method1
method2
...
}

field는 객체가 저장하는 데이터
constructor는 객체를 생성하는 생성자 함수이다.

위의 형식에 따라서 간단하게 중고 자동차 판매 관리 프로그램에 사용될 자동차(vehicle) 객체의 클래스를 만들어보자
간단하게 설계할때, vehicle 객체는 자동차의 브랜드, 모델, 엔진, 사고횟수, 가격등을 저장하게 된다.
생성될때 위 값을 가지고 생성되고, 그 값에 접근하기 위한 accessor 메소드들이 존재한다.
그리고 추후 사고기록을 추가할 경우를 위해 사고기록을 +1하는 메소드를 생성했다.
그외에 다른 정보를 수정할 필요는 없기에, 다른 필드에대한 mutator 메소드들은 존재하지 않는다.
그리고 객체정보를 간략하게 String으로 표현해주는 toString 메소드가 있다.
이 기준에 맞춰 객체를 설계하게되면 아래와 같은 클래스를 적게된다.


public class Vehicle {
 //field
 private String brand, model, engineModel;
 private int price, accidentHistory;

 //constructor
 public Vehicle(String brand, String model, String engineModel, int price) {
  this.brand = brand;
  this.model = model;
  this.engineModel = engineModel;
  this.price = price;
  this.accidentHistory = 0;
 }

 public Vehicle(String brand, String model, String engineModel, int price, int accidentHistory) {
  this.brand = brand;
  this.model = model;
  this.engineModel = engineModel;
  this.price = price;
  this.accidentHistory = accidentHistory;
 }
 
 public String toString(){
  return String.format("브랜드: %s, 모델: %s, 엔진명: %s, 가격: %d," + 
                                      " 사고기록: %d", brand, model, engineModel, price, accidentHistory);
 }
 
 public void addAccidentHistory(int x){
  this.accidentHistory+=Math.abs(x);
 }

 public String getBrand() {
  return brand;
 }

 public String getModel() {
  return model;
 }

 public String getEngineModel() {
  return engineModel;
 }

 public int getPrice() {
  return price;
 }

 public int getAccidentHistory() {
  return accidentHistory;
 }
}

모든 메소드들은 public 키워드를 가진다. public 키워드는 접근 제한자의 한가지로 어떤 클래스의 어떤 메소드든 public 키워드를 가진 메소드를 호출할수 있다는 뜻이다. 하지만 모든 데이터필드를은 private 키워드를 가진다. public과 동일하게 접근제한자이고 private 키워드를 가진 변수는 같은 클래스 안에서만 접근이 가능하다.

생성자 함수는 클래스 이름과 동일한 메소드 이름을 가진다.


public Vehicle(String brand, String model, String engineModel, int price) {
 this.brand = brand;
 this.model = model;
 this.engineModel = engineModel;
 this.price = price;
 this.accidentHistory = 0;
}

public Vehicle(String brand, String model, String engineModel, int price, int accidentHistory) {
 this.brand = brand;
 this.model = model;
 this.engineModel = engineModel;
 this.price = price;
 this.accidentHistory = accidentHistory;
}

이 생성자 함수들은 객체가 생성될때 호출되며, 파라미터로 데이터를 받아 객체에 저장한다.
클래스는 파라미터만 여러개의 다르다면 생성자 함수를 가질 수 있다. 다른 메소드와의 차이점이 있다면, 생성자(Constructor) 함수는 반환(return) 값을 가질 수 없으며, 언제나 new 연산자와 함께 호출된다. 그러므로 이미 생성된 객체의 생성자 함수를 호출할수없다. 그리고 키워드 this는 implicit parameter를 가리킨다.
parameter는 explicit, implicit parameter로 나뉘는데, 예를들어 이 객체에서 addAccidentHistory메소드를 호출할 때에


car.addAccidentHistory(3);

implicit parameter란 Vehicle 형을 가진 객체 car을 뜻하고, explicit parameter란 메소드 이름 뒤 괄호 내부에 있는 값을 뜻한다. implicit 파라미터와 달리 explicit 파라미터는 메소드 선언시, 데이터 타입과 이름을 같이 선언하게된다(public void addAccidentHistory(int x)).
인스턴스의 this 키워드는 모두 implicit parameter를 가리킨다.

인스턴스의 모든 데이터필드는 private 키워드를 가지고 있는데, 이는 자동적으로 인스턴스를 캡슐화한다. 앞서 말했듯이 private 키워드를 가진 변수들은 오직 같은 클래스에서만 접근이 가능하다. 객체가 생성될때 어싸인된 값들은 get 메소드를을 이용해 불러올수는 있지만, private 키워드를 가지고 있기때문에 직접적인 수정이 불가능하다. 만약 필요하다면 addAccidentHistory 메소드 처럼 메소드를 통해서 수정할 수 있다. 만약 개발자가 위의 경우와 같이 사고기록 횟수를 줄이는것을 불가능하게 설계했다면, addAccidentHistory메소드를 통해 사고기록을 늘리는것만 가능하고 줄이는것은 불가능하다.

캡슐화(Encapsulation)을 얻기위해서는 3가지 기준만 충족하면 된다.

  • private 데이터 필드
  • public 접근자(Accessor) 메소드
  • public 수정자(Mutator) 메소드

이로써 얻어지는 이점들은, 우선 클래스 설계에 수정이 필요할 때 해당 접근자(accessor), 수정자(mutator) 메소드의 이름을 제외하고는 모두 다 변경이 가능하다. accessor 메소드란 getBrand처럼 객체의 데이터 필드를 가져오는것이고 mutator 메소드란 객체의 데이터 필드를 변경하는 메소드이다.
예를들어 위 객체에서 price 데이터 필드를 int 에서 double로 변경하고 싶다면, accessor와 mutator 메소드 내용만 변경하면 된다. 객체 사용자의 입장에서는 이 변경을 체감할 수 없다. 그리고 addAccidentHistory에서 주어진 정수의 절대값을 이용해 사고기록 줄이기가 불가능하게 한것처럼, 설계자가 원하는대로 에러를 예방할 수도 있다. 다만 accessor 메소드에서 값을 반환할때 객체는 clone()메소드를 이용해서 복사한 클론을 반환해야한다. 그냥 객체를 반환한다면, 예를들어 Date 객체는 자체에 mutator 메소드를 가지기 때문에 캡슐화(Encapsulation)를 부숴버린다.

또 한가지 알아둬야될것은, 같은 클래스를 이용하는 모든객체들은 서로의 private 데이터 필드에 접근이 가능하다. 그렇기 때문에 아래와 같은 메소드를 위의 객체에 추가해도 제대로 작동하게된다.


public boolean equalPrice(Vehicle v){
   return this.price==v.price;
}


이제 위의 객체를 생성하고 사용해 사용해보자.


Vehicle[] list = new Vehicle[3];
list[0] = new Vehicle("Hyundai", "Sonata", "GDI2.0", 170000000);
list[1] = new Vehicle("Toyota", "Prius", "TOI1.2", 150000000);
list[2] = new Vehicle("Hyundai", "Avantte", "GDI2.0", 80000000, 2);

// 알고보니 현대 소나타 모델이 사고기록이 2번 있었다.
// addAccidentHistory() 메소드를 이용해 기록을 추가해주자
list[0].addAccidentHistory(2);

System.out.println("소나타와 프리우스 가격 일치 여부 : " + list[0].equalPrice(list[1]));
//소나타와 프리우스 가격 일치 여부 : false

// 차량 정보들을 한번보자
for (Vehicle i : list) {
 System.out.println(i.toString());
}

// 브랜드: Hyundai, 모델: Sonata, 엔진명: GDI2.0, 가격: 170000000, 사고기록: 1
// 브랜드: Toyota, 모델: Prius, 엔진명: TOI1.2, 가격: 150000000, 사고기록: 0
// 브랜드: Hyundai, 모델: Avantte, 엔진명: GDI2.0, 가격: 80000000, 사고기록: 2

2017년 6월 27일 화요일

Object Oriented Programming(객체지향프로그래밍) 소개

객체지향프로그래밍(Object Oriented Programming), 줄여서 OOP는  전세대(1970년)의 절차지향프로그래밍(Procedural Programming)과 구조적프로그래밍(Structured Programming)을 갈아치우고 현세대를 지배하는 프로그래밍 패러다임이다. 대부분의 프로그래밍 언어들은 객체 지향적(Object Oriented)이므로 Java, C#, Python 등을 사용한다면 이 컨셉에 익숙해질 필요가 있다.

흔히 객체 지향적이라 말해지는 프로그램들은 객체(Object)들로 이루어진다. 각 객체들은 목적에 맞는 특별한 기능들을 가지고 있고, 사용자들은 그런 기능들을 사용할수 있다. 물론 객체 디자이너가 몇몇 기능들을 사용자들에게서 숨길수도 있다. 과거의 절차지향적, 구조적 프로그래밍들은 문제를 풀기위한 절차(알고리즘)을 설계하는것이였고, 그런 절차들이 결정되면 절차에 알맞게 자료를 저장할수있는 방법을 찾았다. 프로그래밍 언어인 Pascal을 개발한 Nikalus Writh는 "Algorithms + Data Structures = Programs" 라는 책을 1975년 발행하기도 했다. 책 이름을 보면 알고리즘이 앞에오고 자료구조가 뒤에오는데 이는 당시 프로그래밍 컨셉이 "알고리즘(절차) 설계가 데이터 관리보다 선행한다" 였다는 것을 알려준다. 하지만 OOP의 등장은 이 순서를 서로 뒤바꿔 놓아서, "데이터 관리가 알고리즘(절차)에 선행한다" 라는 패러다임을 만들었다.

간단한 프로그램의 경우 절차적 프로그래밍이 훨씬 쉽게 접근할 수 있다. 객체지향 프로그래밍은 더 큰 프로젝트에 적용된다. 간단하게 이 포스트를 보고있는 웹브라우져를 생각해보자. 일반 사용자가 사용하는 기능만 수백게에 달하며, 백그라운드에서 실행되는 기능들을 포함한다면 몇천개가 존재할텐데 만약 객체의 존재 없이 이 코드를 관리한다면 머리가 빠질거다. 객체지향 프로그래밍이 적용된다면 각기 목적에 맞는 클래스들과, 그 클래스들에 맞는 기능들이 잘 분류되기 때문에 훨씬 쉽게 관리가 가능하다.

그렇다면 클래스(Class)란 무었일까?


우리가 자바 프로그래밍에서 javac 라는 명령어로 .java 파일을 컴파일했을때 출력되는 그 클래스(Class)파일이다 클래스(Class)란 객체 생성을 위한 설계도와 같다. 클래스라는 설계도에 맞춰 객체가 생성(Construct)된다. 이때 생성된 객체를 클래스(Class)의 인스턴스(Instance)라 한다. 당연하게도 java파일에 적힌 모든 코드들이 class파일에 들어있다. Java API는 다양한 목적들을 위한 라이브러리를 제공하고, 라이브러리에는 Date, Math, Arrays등 수천개의 클래스들이 존재한다. 그리고 사용자는 본인만의 객체 생성을 위해 직접 클래스를 생성할 수 있다.

여기서 Encapsulation(캡슐화)이라는 컨셉이 등장하는데, 정보 은닉이라고도 알려진 이 컨셉은 말그대로 사용자에게서 객체가 저장하는 정보(Instance
Fields)들에대한 직접적인 접근을 막고, 메소드(method)를 통한 간접적인 접근만 가능케하여 실제 구현내용을 숨기는것이다. 그리고 객체의 현재 상태(State) 그런 Instance Fields들의 상태이며, method들이 호출하여 state를 변경할수있다. 이 캡슐화라는 속성은 클래스가 데이터를 관리하는법을 변경할 수는 있지만, 사용자 입장에서는 언제나 같은 메소드를 호출하므로 아무런 영향을 받지 않는다.

객체(Object)

앞서 말한 class가 인스턴스화된게 객체(Object)이다. 객체의 사용을 위해서는 3가지 특성을 파악해야한다.

  • 객체의 습성(Behavior) - 객체를 어떻게 활용하고, 어떤 메소드를 적용 가능한지
  • 객체의 상태(State) - 특정 메소드를 호출했을때 어떻게 상태가 변하는지
  • 객체의 정체성(Identity) - 동일한 동작과 상태를 가진 다른 객체와 어떻게 구별되는지

같은 클래스에서 인스턴스화된 객체들은 모두 같은 습성을 가지고 있다. 객체의 습성(Behavior)이란 사용자가 어떤 메소드를 호출할수 있느냐에 따라 결정된다. 그리고 각 객체는 데이터들을 저장하는데, 이게 객체의 상태(State)이다. 객체의 상태 변경은 메소드 호출의 결과여야 하고, 그 때문에 객체가 저장하는 데이터들이 모두 동시에 바뀌지는 않는다. 만약 두 객체의 State가 같다해도, 두 객체가 동일하다는것은 아니다. 이는 모든 객체의 정체성(Identity)이 서로 다르기 때문이다

[Java] 배열(Array)

자바에서 배열(Array)는 같은 형(type)의 값들을 저장하는 자료구조이다. index 값을 통해 각 값에 접근이 가능하다. 예를들어, 배열 a 가 존재할 때 a[i]는 a 배열의 i번째 값이다. 배열의 선언은 어떤 형의 배열인지를 표기하고, 초기화시는 배열의 길이를 정해주면 된다. 그리고 바로 값을 어싸인하여 배열을 생성할수도 있다

double[] a = new double[3]; //double 3개를 저장할 수 있는 배열
int b[] = new int[10]; //인터져 10개를 저장할 수 있는 배열
b[0] = 113; //배열 b의 첫번째 값에 113을 저장
System.out.println(a[0]); //0.0
System.out.println(b.length); //10

int[] c = { 1, 2, 3, 4, 5 };
int[] d = new int[] { 6, 7, 8, 9, 10 };

배열의 인덱스는 0부터 시작한다. 길이가 10인 배열은 0 ~ 9까지의 인덱스를 가지고, 100이라면 0 ~ 99까지의 인덱스를 가진다.
배열 초기 생성시, 정수나 소수형 배열은 모든 값에 0이 저장되고, 객체형 배열은 null 값이 저장된다. array.length 프로퍼티를 이용해 배열의 길이를 알 수 있다.
배열은 한번 생성되게되면 그 길이를 수정할 수 없다. 만약 배열의 길이가 수정되어야할 필요가 있다면 ArrayList라는 객체를 이용해야한다.

어레이의 모든 값을 대상으로 하는 for-each 루핑이 존재한다. for-each loop를 이용하면 굳이 인덱스를 통하지 않고 배열의 모든 값에 접근이 가능하다.


int[] a = new int[10];
for(int i = 0; i <a.length; i++){
 System.out.print(a[i] + " ");
}
//0 0 0 0 0 0 0 0 0 0 
System.out.println();


for(int i : a){
 System.out.print(i + " ");
}
//0 0 0 0 0 0 0 0 0 0

//둘다 같은 결과값을 낸다.

배열은 객체이기 때문에 한 배열을 다른 배열에 어싸인하는 것은 2개의 배열이 같은 메모리값를 참조하게 만든다. 그렇기 때문에 배열의 값이 수정되면 2 배열이 같은 메모리 위치를 참조하기 때문에 모두 수정된 값을 가지게 된다. 그렇기 때문에 배열을 복사하거나 배열의 길이를 늘리고 싶다면 Arrays.copyOf 메소드를 이용해야한다.


int[] a = { 1, 2, 3, 4, 5 };
int[] b = a;
for (int i = 0; i < a.length; i++) {
 a[i]++;
}
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));

a = Arrays.copyOf(a, a.length * 2);
// 배열 a와 같은 값을 가지고 크기가 2배인 배열 생성후 어싸인
b = Arrays.copyOf(a, a.length);
// 배열 a 복사 후 b에 어싸인

for (int i = 0; i < a.length; i++) {
 a[i]++;
}

System.out.println(Arrays.toString(b));
// [2, 3, 4, 5, 6, 0, 0, 0, 0, 0]

System.out.println(Arrays.toString(a));
// [3, 4, 5, 6, 7, 1, 1, 1, 1, 1]

배열 정렬


손쉽게 배열을 정렬하고 싶다면 Arrays.sort() 메소드를 이용하면 된다. 기본적인 Quick Sort 알고리즘을 적용한 메소드이다.


int[] a = new int[5];
for (int i = 0; i < a.length; i++) {
 a[i] = (int)(Math.random()*10);
}
System.out.println(Arrays.toString(a));
//[4, 9, 1, 2, 2]
Arrays.sort(a);
System.out.println(Arrays.toString(a));
//[1, 2, 2, 4, 9]

다차원 배열(Multidimensional Array)


다차원 배열은 2개 이상의 인덱스를 사용해 값에 접근해야하는 배열이다. 1차원 배열이 부족할 때 사용된다. 예를들어 3*3 매트릭스를 만들고 싶다고 하면
원리는 단순히 1차원 배열의 각 값에 또다른 배열이 들어있다 생각하면 된다. 그렇기 때문에 접근방법은 동일하다. 그렇기 때문에 각 2차원 배열의 크기가 틀리게 생성할수도 있다.


int[][] a = new int[3][3];//3*3 이차원 배열
a[1][1] = 10;
for(int[] i : a)
{
 System.out.println(Arrays.toString(i));
}
// [0, 0, 0]
// [0, 10, 0]
// [0, 0, 0]

// 2차원 배열이 다른 크기를 가진 배열 생성
int[][] b = new int[6][];
for(int i = 0;i

[Java] String(문자열)

char형이 문자 1개밖에 저장하지 못하기 때문에 탄생한 String형, 유니코드 문자열을 저장하며 Java의 빌트인 자료형이 아니라, Java 라이브러리에서 지원되는 자료형이다. 큰따옴표("") 사이에 들어가는 문자는 모두 String 클래스의 인스턴스이다. 예를들자면 "Java\u2122"는 유니코드 문자, J, a, v, a, ™으로 이루져있다.

String emptyOne = "";
String javaTM = "Java\u2122";
System.out.println(emptyOne); //
System.out.println(javaTM); //Java™

String형은 저장된 자료를 변경하는게 불가능한다. 객체형 자료이기 때문인데, 우리가 e 라는 String 객체를 생성하고 "eTest"라는 문자열을 어싸인하면 e라는 변수가 "eTest"라는 값을 저장하는게 아닌, "eTest"라는 값이 저장된 메모리의 주소를 저장한다. 그렇기 때문에 String형의 값을 변경하려면 새로운 String 객체를 어싸인해주는 수밖에 없다. int, float과 같은 원시자료(Primitive Data Type)형과 비교를 하자면, 원시자료형은 저장하고 있는 메모리의 주소가 바뀌지 않는다. 만약 int i 가 a라는 메모리에 숫자 111을 저장하고 있고 이걸 112로 변경한다면 a라는 메모리에서 변경되지만, String은 a라는 메모리에 "eTest"를 저장하고 있는데, "aTest"로 변경한다면, b라는 메모리에 "aTest"가 저장되고 String형에서 저장하고있는 메모리주소가 a에서  b로 바뀌게 된다. 이 때문에 String은 저장된 자료가 변경 불가능하다는 뜻에서 "Immutable Object"이라고 불리기도 한다.

String형은 자바 라이브러리에서 지원하는만큼, 사용을 위해 다양한 메소드들을 지원한다.

부분 문자열(Substrings)


String.substring(a,b) 메소드를 이용해 원래 문자열에서 부분만 추출 가능하다. a는 추출할 문자열의 시작 위치, b는 추출할 문자열의 끝 + 1이다.
만약 문자 1개만 추출하고 싶다면 String.charAt(index) 메소드를 이용하면 된다

String javaTM = "Java\u2122";
String av = javaTM.substring(1, 3);
System.out.println(av); //av
String v = javaTM.charAt(2);
System.out.println(v); //v

substring 메소드에 1 과 3을 파라미터로 호출했다. 인덱스 0 부터 시작해, 인덱스 1과 2의 위치에 있는 'a'와 'v'가 추출되고, 인덱스 3부터인 "a™"은 추출되지 않는다. 즉 인덱스 a는 포함하나, b는 포함하지 않고 추출한다. substring의 길이는 b - a를 연산하면 쉽게 구할 수 있다.

문자열 접합(Concatenation)


다른 프로그래밍 언어처럼, + 연산자르 이용해 2개의 문자열을 합할 수 있다. 또한 String형이 아닌 다른 자료형과 합하게되면 자동으로 String형으로 변환된다.

String welcome = "Welcome to ";
String java = "Java ";
double version = 1.8;
String greeting = welcome + java + version;
System.out.println(greeting); //Welcome to Java 1.8

또는 String.join 메소드를 이용해도 가능하다. 특정 딜리미터(delimiter)를 설정해 기존 문자열을 합하여 새 문자열을 만든다.


String welcome = "Welcome to";
String java = "Java";
double version = 1.8;
String greeting = String.join(" ", welcome, java, Double.toString(version));//딜리미터는 " "이다
System.out.println(greeting); //Welcome to Java 1.8

문자열 비교


2개의 서로 다른 String형은 equals 메소드를 이용하면 된다. 다만 원시자료형처럼 "=="를 이용해 비교하지는 말자. 이 경우에 String은 객체이기 때문에 객체가 저장된 위치를 비교하게된다. 그렇기 때문에 실제 저장된 값을 비교하지 않는다.
equals 메소드는 대소문자를 구별하므로 대소문자 구별이 불필요하면 equalsIgnoreCase메소드를 사용한다.


String a = "equal";
String b = "Equal";
String c = a;
System.out.println(a.equals(b)); //false
System.out.println(a==b.toLowerCase()); //false
System.out.println(a==c); //true
System.out.println(a.equalsIgnoreCase(b)); //true

String형을 비교할때 null 값을 가진 String과 "" 값을 가진 String을 조심해야한다. null 값은 String 객체에 아무런 값도 어싸인되지 않음을 뜻하고, ""값은 길이가 0인 문자열 값이 어싸인됨을 뜻한다.


String a = "";
String b = null;
System.out.println(a.equals(b)); //false
System.out.println(a==b); //false
System.out.println(a.equals("")&&a.length()==0); //true
System.out.println(b==null); //true

이 외에도 많은 메소드들이 있다. 더 자세한걸 알고 싶으면 오라클 JDK 1.8 Documentation을 참조하자.


2017년 6월 26일 월요일

[Java] 자료형(Data Type)과 변수

자바에서 모든 변수(Variable)는 그에 맞는 Type(형태)를 가지고 있다. 자바에는 총 8개의 원시형(Primitive Type)이 있으며, 정수(Integer)형 4개[int, short, long, byte], 소수(Floating-point)형 2개[float, double], 문자(Character)형 1개[char], Boolean형 1개로 이루어져 있다. 그리고 문자열 형식(String)은 String 객체를 통해 표현한다.

모든 자바의 변수들은 그에 맞는 type(형)을 가진다. 변수의 이름은 언제나 문자로 시작해야하며 문자 1개만 사용해서는 안된다. 다만 '$' 문자는 자바 컴파일러에 의해 사용되기도 하니 사용을 피하도록 하자. 또한 Java reserved word 들은 사용하면 안되는데, 예를들면 int, double, for, if 와 같이 자바에서 구문으로 사용되는것들은 사용할수 없다.

Integer Type(정수형)


정수형이란 소수점 아래로 표현이 불가능한 양수와 음수들을 말한다.


Type저장공간표현 가능 수
long8 bytes-2^63 ~ +2^63
int4 bytes-2^31 ~ +2^31
short2 bytes-2^15 ~ +2^15
byte1 bytes-2^7 ~ +2^7

각 타입의 저장공간에서 가장 첫번째 비트(bit)는 수가 음수인지, 양수인지를 의미하므로, 나머지 비트만큼 수를 표현 할 수 있다. 대부분의 경우에 int 타입이 가장 실용적이며, 이보다 더 큰 수를 저장해야 할 경우에는 long 타입을 이용할 수 있다. short 타입과 byte 타입은 low level file 사용 등의 특별한 경우에 사용된다.

long 타입 정수들은 값의 마지막에 L 또는 l을 붙인다. 16진수들은 수의 첫자리에 0x 또는 0X를 붙이며, 8진수는 수의 첫자리에 0을 붙인다. 또한 Java SE7 부터는 수의 첫자리에 0b, 0B붙이며 2진수로 수를 표현할 수도 있다. 수가 너무 커지거나 길어져서 읽기 힘들면, ','(반점)으로 구분하듯이 '_'(underscore, 밑줄문자)를 이용해 구분할 수도 있다. 이는 단순히 개발자가 쉽게 구분하기 위한 용도이며, 자바 컴파일러는 컴파일시 밑줄문자를 그냥 무시한다.


//값 정의
long lExample1 = 100L, lExample2 = 233l;
int hexaExample1 = 0x143, hexaExample2 = 0X233;
long octExample1 = 010, octExample2 = 034;
int biExample1 = 0b10101, biExample2 = 0B1101;
int million = 1_000_000; // 백만

//값 확인
System.out.println(lExample1);// 100
System.out.println(lExample2);// 233
System.out.println(hexaExample1);// 323
System.out.println(hexaExample2);// 563
System.out.println(octExample1);// 8
System.out.println(octExample2);// 28
System.out.println(biExample1);// 21
System.out.println(biExample2);// 13
System.out.println(million);// 1000000

각 프로세서 별로 맞는 정수형 데이터 타입을 사용해야하는 C, C++와는 달리 자바에서는 그런 걱정을 할 필요가 없다. 이러한 요소는 자바가 Platform-Independent Language(플랫폼 독립 언어)라 불리는 이유 중 하나 이기도 하다.


Floating-Point Type(부동소수형)


말 그대로 소수형이다. 정수형과 달리 소수들도 표현 가능하다. IEEE754 표준에 기초한다.


Type저장공간표현 가능 수
double8 bytes±3.40282347E+38
float4 bytes±1.79769313486231570E+308

double이라는 자료형은 float이 32bit single precision을 쓰고 double 형이 그 2배를 저장할수 있다는 뜻에서 double로 유래됬다. float형 소수는 언제나 숫자 뒤에 F 또는 f가 붙는다. 소수이나 f 또는 F가 붙지 않았다면 double형으로 처리된다. double형에 대해 특별히 표시를 하고 싶다면 D 또는 d를 뒤에 붙이면 된다.

모든 부동소수형(Floating Point) 연산은 IEEE 754기준에 따라 연산된다. 그렇기 때문에 좀 애매한 결과가 나올때도 있는데 예를 들어 "2.0-1.1" 을 연산하게 되면 결과값이 0.9가 나와야 하지만 실제 결과는 0.8999999999999999이 나오게 된다.


이는 2진수에서 10진수로 변환할때 생기게 되는 문제다. 예를들어, 0.5라는 10진수를 2진수로 표현하게 되면 0.1로 표현할 수 있다. 0.75는 0.11이되고, 0.875는 0.111(2^-1 + 2^-2 + 2^-3)이 된다. 2진수 연산이기 때문인데, 그렇기 때문에 모든 분수들은 2의 배수가 분모가 되는 분수들로 표현된다. 우리가 10진수의 세상에서 1/3을 소수형으로 딱 떨어지게 표현하지 못하는것처럼 2진수의 세계에서 1/10을 정확하게 표현하지 못한다. 만약 이런 에러들을 피하고 싶다면 원시 자료형인 double형이나 float형 말고 BigDecimal이라는 클래스를 이용하도록 하자.


System.out.println(2.0-1.1); //0.8999999999999999

BigInteger & BigDecimal


기본적인 정수형과 소수형이 원하는 수를 저장하지 못한다면 자바 라이브러리 java.math 패키지의 BigInteger 클래스와 BigDecimal 클래스를 이용하면 된다.
다만 연산을 위해서는 +,-와 같은 일반 연산자가 아닌 BigInteger, BingDecimal 클래스의 메소드를 이용해야 한다.

BigDecimal a = BigDecimal.valueOf(2.0);
BigDecimal b = BigDecimal.valueOf(0.1);
BigDecimal c = a.divide(b);
System.out.println(c); //20


char Type(문자형)


char형은 말그대로 문자 1개를 표현하기 위해 만들어졌다. 2 byte의 데이터를 저장할 수 있다. char형에 데이터를 어싸인하기 위해서는 작은따옴표('') 사이에 문자 1개를 집어넣어주면 된다. 문자 1개밖에 못저장한다. 2개넣으면 신텍스 에러가 뜬다. 작은따옴표만 사용해야 한다. 아스키(ASCII) 값 65를 가진 알파벳를 어싸인할 때 'A'"A"는 큰 차이가 있다.  작은따옴표('') 사이의 A는 문자형 A 이지만 큰따옴표("")사이의 A 는 String 형 A 이기 때문이다. 그렇기 때문에 큰따옴표를 써도 신텍스 에러가 난다. 유니코드 또는 아스키 코드로 값을 어싸인할수도 있다.


char asciiA = 65; //ASCII 코드 A
char unicodeA = '\u0041'; //UTF-8(유니코드) A
System.out.println(asciiA); //A
System.out.println(unicodeA); //A


boolean Type(boolean형)


boolean 형은 2가지 값 true, false, 둘중 하나밖에 저장하지 못한다. 단순히 논리문의 조건을 저장하기 위해 존재한다.


boolean falseTest = false;
boolean trueTest = true;
System.out.println(falseTest); //false
System.out.println(trueTest); //true

2017년 6월 16일 금요일

아파치 메이븐(Apache Maven) 시작하기

Apache Maven 프로젝트는 Java 어플리케이션의 빌드 과정을 간략화하기 위해 시작된 프로젝트이다. 그 전까지는 Apache Ant 를 이용해서 빌드 했는데, 이 경우에 여러개의 섭프로젝트로 이뤄진 하나의 프로젝트를 진행할 때 각 섭프로젝트가 서로 다른 Ant 빌드 파일을 가지고, 빌드된 JAR 파일들이 만들어지게 된다. Apache 개발팀은 프로젝트를 빌드하는데 있어 프로젝트의 구성 정보를 확실하게 전달하고, 여러 프로젝트에서 쉽게 JAR 파일을 공유할 수 있는 방식의 필요성을 느꼈고, 그에 대한 결과물로 Maven 이 탄생하게 된다. 그러니까 Maven은 기존의 "Ant + @기능들" 이라 생각하면 된다.

Maven을 우선 설치해야 공부하니까 설치하자. 가장 최신버젼은 3.5 버젼이고, 3.3 버젼 이상부터는 JDK 1.7이상이 설치되어있어야 한다. 설치가 안되있다면 [Java] 자바 시작하기(개발 환경 구성) 글을 보고 설차하도록 하자.

그리고 "JAVA_HOME" 이라는 환경변수 설정을 해주자. 위 글을 보고 따라하는 Ubuntu 사용자의 경우에는 나와 같은 위치에 설치했을테니 쉘을 열고 아래와 같이 타이핑하면 된다. 아니면 각기 jdk 설치한 위치로 하자. 윈도우 사용자의 경우에는 내컴퓨터 옵션에서 환경변수를 설정하자.

export JAVA_HOME= /opt/java/jdk1.8 #jdk폴더위치

그리고 나서는 https://maven.apache.org/download.cgi에 들어가 Maven 파일을 다운로드 해주자. 영어로 복잡하게 많겠지만 Link 칼럼에 있는것중에 Binary Archive를 다운받으면 된다. Source라 적힌것들은 직접 개발할때 쓰라는거다.

나의 경우에는 3.5버젼 apache-maven-3.5.0-bin.tar.gz 파일을 받았다. 다운 다 했으면 설치할 위치에 압축 풀고 놔주자. 편한데 놔두면 된다. 나는 /opt 폴더에 놔뒀다.

그리고 maven 폴더의 bin 폴더를 $PATH 변수에 추가해주면 된다. 그리고 쉘에서 mvn -v 명령어를 실행해주면 아래와 같은 결과가 나오고, 그러다면 설치가 완료된것이다.


user@user-desktop:~$ mvn -v
Apache Maven 3.5.0 (ff8f5e7444045639af65f6095c62210b5713f426; 2017-04-04T04:39:06+09:00)
Maven home: /opt/apache/apache-maven
Java version: 1.8.0_112, vendor: Oracle Corporation
Java home: /opt/java/jdk1.8/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.4.0-79-generic", arch: "amd64", family: "unix"

설치를 완료했다면 프로젝트를 하나 만들어보자. 아래 명령어를 실행해준다


user@user-desktop:~$ mvn archetype:generate 
# 첫 실행시 필요한 플러그인들과 파일들을 다운받는라 좀 오래걸릴거다.

Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 984: #그냥 엔터치면 기본값으로 설정된다
Choose org.apache.maven.archetypes:maven-archetype-quickstart version: 
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
Choose a number: 6: #마찬가지, 엔터
Define value for property 'groupId': com.muckycode.app
Define value for property 'artifactId': maven-tutorial
Define value for property 'version' 1.0-SNAPSHOT: : #엔터
Define value for property 'package' com.muckycode.app: : #엔터
Confirm properties configuration:
groupId: com.muckycode.app
artifactId: maven-tutorial
version: 1.0-SNAPSHOT
package: com.muckycode.app
 Y: : y #설정을 확인하고 맞으면 y를 쳐주자, 그리고 나면 아래와 같이 빌드가 만들어졌다는 문장들을 볼수있을거다.
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: /home/user
[INFO] Parameter: package, Value: com.muckycode.app
[INFO] Parameter: groupId, Value: com.muckycode.app
[INFO] Parameter: artifactId, Value: maven-tutorial
[INFO] Parameter: packageName, Value: com.muckycode.app
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /home/user/maven-tutorial
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:26 min
[INFO] Finished at: 2017-06-16T10:58:19+09:00
[INFO] Final Memory: 18M/200M
[INFO] ------------------------------------------------------------------------

Maven에 의해 생성된 프로젝트 폴더로 가보자. basedir내부에 artifactID폴더에 있을거다. 폴더 내부 구성을 보게되면


user@user-desktop:~/maven-tutorial$ ls -LR
.:
pom.xml  src

./src:
main  test

./src/main:
java

./src/main/java:
com

./src/main/java/com:
muckycode

./src/main/java/com/muckycode:
app

./src/main/java/com/muckycode/app:
App.java

./src/test:
java

./src/test/java:
com

./src/test/java/com:
muckycode

./src/test/java/com/muckycode:
app

./src/test/java/com/muckycode/app:
AppTest.java

기본적으로 maven은 프로젝트 폴더안에 pom.xml, Project Object Model 파일인데, 프로젝트의 구성정보를 전달하는 파일이다.
그리고 src 폴더안에 main 폴더와 test 폴더를 생성하고, 각 폴더 내부에 java 폴더를 생성, java 폴더 내부는 groupID에 입력했던 패키지 형식 계층으로 폴더가 구성된다.
위는 maven-archetype-quickstart라는 archetype을 선택(기본값이다)했을때이고 다른 옵션을 정하면 그에 따라 만들어진다.
Maven에서 소개하는 기본 디렉토리 구조는 아래와 같다. 혹시 이 중에 필요하나 안만들어진게 있다면, 직접 폴더를 만들면 된다.


디렉토리설명
src/main/java자바 소스 파일
src/main/resources리소스 파일
src/main/filters리소스 필터 파일
src/main/webapp웹앱 소스 파일
src/test/java테스트 소스 파일
src/test/resources테스트 리소스 파일
src/test/filters테스트 리소스 필터 파일
src/it확장(Integration) 테스트(플러그인용)
src/assemblyAssembly descriptors
src/siteSite
LICENSE.txt프로젝트 라이센스
NOTICE.txtNotice 파일
README.txtreadMe 파일

위의 폴더 구조와 내부 파일 설명은 Maven 규칙이기도 하니 잘 지켜주자. 모든 자바 소스 파일들은 src/main/java 폴더에 존재해야하며, 모든 테스트 소스 파일들은 src/test/java 폴더에 존재해야하고, 같은 방식으로 리소스들은 resources 폴더에 저장된다. 또 필수사항은 아니지만, 소스파일들은 groupId와 같은 패키지 구조를 가지는게 좋고, 기본적으로 만들어지는 App.java, AppTest.java 파일은 Maven 작동 테스트를 위한것임을 알아두자.

기본적으로 Maven 설치는 최소한의 Maven 기능만 설치한다. 추가기능들은 모드 plug-in 방식으로 제공되며, plug-in의 이름만 주어지면, Maven이 알아서 다운받고 설치한다. 위 프로젝트 생성의 경우에는 Maven이 archetype이라는 플러그인을 다운받았고, archetype 플러그인이 의존하는 다른 플러그인까지 알아서 다 다운받았다. 이런 플러그인들은 로컬에 저장되며, 한번 다운받았으면 다시 다운받을 필요 없다.

자 그러면 다시 우리 프로젝트 폴더로 이동해서 mvn package 명령어를 실행해주자. 그럼 아까와 같이 플로그인 다운로드가 끝난이후 아래와 같은 결과를 보게 될것이다.
설명하자면, mvn package 명령에서 package 파라미터는 build 사이클의 한 단계(Phase)를 의미한다. 각 phase 들은 개별 플러그인이며, 이 플러그인들이 실행하는 하나의 행위들을 goal이라고 표현한다. 예를들어 package 단계의 어떤 goal을 실행하고 싶다면 mvn package:anygoal 이런식을 콜론을 이용해서 명령어를 만들어주면 된다. 각 단계는 실행되기위해 그 전단계들의 실행을 필요로 한다. 그렇기 때문에 이 경우에, 우리는 package 단계를 실행했고, 그 전 단계인 Validate, Compile, Test 단계가 다 실행되고 Package단계가 실행된다. 물론 Validate, Compile, Test 단계모두 각각 실행 할 수도 있다.

  • Validate - pom.xml 파일을 조회해 프로젝트 구성과, 필요 정보에 이상 없나 확인한다.
  • Compile - 소스파일을 컴파일한다
  • Test - 테스트 소스를 컴파일하고 테스트 프레임워크를 실행해 테스트한다
  • Package - pom.xml 파일에 설정된 포맷으로 프로젝트를 패키징한다. 여기서는 jar 포맷이다.


user@user-desktop:~/maven-tutorial$ mvn package
[INFO] Scanning for projects...

[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building maven-tutorial 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------

################################################################################
# Validate 단계
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-tutorial ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/user/maven-tutorial/src/main/resources
[INFO] 

################################################################################
# 메인 소스 파일 변경을 감지하고 새로 컴파일한다 - Compile 단계
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-tutorial ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/user/maven-tutorial/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-tutorial ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/user/maven-tutorial/src/test/resources
[INFO] 

################################################################################
# 테스트 소스 파일 변경을 감지하고 새로 컴파일한다
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-tutorial ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/user/maven-tutorial/target/test-classes

################################################################################
# 테스트를 진행한다 - Test 단계
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-tutorial ---
[INFO] Surefire report directory: /home/user/maven-tutorial/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.muckycode.app.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

################################################################################
# 테스트 결과가 이상 없고, 마지막 단계인 package 단계를 실행하고 jar 파일을 생성한다.
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ maven-tutorial ---
[INFO] Building jar: /home/user/maven-tutorial/target/maven-tutorial-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.654 s
[INFO] Finished at: 2017-06-16T13:13:28+09:00
[INFO] Final Memory: 17M/195M
[INFO] ------------------------------------------------------------------------

여기까지가 CLI로 Maven을 이용하는 법이다. 근데 아마 대부분의 개발자들은 본인 취향의 IDE를 이용해 할것이니, 조금만 더 힘을내서 Maven과 Eclipse 연동하는 법을 알아보도록 하자.

Eclipse IDE는 Maven을 자체 내장해서 나온다. 엥 그면 위에서 설치 괜히한거~는 아니고, Eclipse 내장 Maven말고 별도로 설치한 다른 버젼의 Maven과도 연동할 수 있으니 타버젼 Maven을 사용하고 싶으면 아래와 같은 방법으로 하면 된다.

우선 Help 메뉴에서 About Eclipse를 클릭하게되면 아래와 같은 창이 보일것이다.

About Eclipse 창

그림에서 빨간 사각형 안에 들어가있는 m2라고 적힌 버튼이 보이는가, 그게 바로 Eclipse Maven 플러그인이다. 클릭하면 자세한 정보도 볼 수 있다. 만약 안보인다면 Eclipse 플러그인 검색으로 설치해주자.

설치가 되있는것을 확인했으면 Window 메뉴에서 Preferences 창으로 들어간다음 Maven 검색후 Installations 를 클릭해준다.

Eclipse Preference Maven Installations 창

그면 위와같은 창을 보게 될텐데, 우선은 내장 Maven을 사용하고 있기에 Embedded에 체크되있다. 옆의 Add 버튼을 눌러 위에서 설치했던 최신버젼의 Maven을 추가해주자.

Eclipse Maven Runtime 추가창

올바른 디렉토리를 추가해주고 적당한 이름도 붙여주기만 하면 끝난다. 그럼 아까 그 창이 아래와 같이 변하게 되고, 체크박스만 바꿔주면 연동이 완료된다.

추가 완료 후 Maven Installations 창

새로운 Maven 프로젝트를 생성해보자. File - New-Maven Project를 선택하면 아까 CLI로 했던 과정을 쉽게 할 수 있다.

Eclipse New Maven Project 창 - 01

Create a simple project(skip archetype selection) 을 선택해주자, 아까처럼 복잡한 옵션을 건너뛰고 기본값으로 프로젝트를 생성시켜 준다. 다음을 누르면

Eclipse New Maven Project 창 - 02

CLI랑 별 다른건 없다. 프로젝트 설정을 완료해주고, 완료를 누르면 패키지 탐색기에 새로운 프로젝트가 추가된다.

Eclipse m2 플러그인은 Maven을 통해 위 과정을 완료하기때문에, CLI와 과정이나 결과로 별달리 차이가 없다.

이미 생성된 프로젝트를 가져오기 위해서는 File - Import 에서 Existing Maven Project 선택 후 프로젝트 디렉토리 위치를 입력하면 된다. pom.xml 파일을 통해 프로젝트를 구성하기 때문에 pom.xml 파일에 입력된 설정, 의존성(Dependency)를 재구성한다.

2016년 12월 10일 토요일

[Java] 자바 시작하기(개발 환경 구성)

자바(Java)프로그래밍을 시작하기위해 가장 선행되어야 할 것은, JRE(Java Runtime Environment, 자바 실행 환경) 설치, 즉 JVM(Java Virtual Machine, 자바 가상 머신)라 불리는 가상머신과 각종 Java API들을 포함하고 있는 JDK(Java Development Kit, 자바 개발 키트)의 설치이다. Java API는 각종 기능의 자바 클래스들이 포함된 자바 라이브러리이고 JVM은 컴파일된 자바 클래스파일들을 실행하는 가상 머신이다. 단순한 자바  프로그램 이용자라면 JVM만이 포함된 JRE를 설치하면 되겠지만 자바 개발자들은 이 2개가 모두 필요하기 때문에 Oracle에서는 JDK에 이 2가지를 포함하여 쉽게 설치할 수 있도록 하고 있다.

JDK를 다운받을려면 오라클 JDK 다운로드 페이지로 이동하자. 링크를 클릭하면 아래와 같은 페이지를 보게될것이다.

Oracle Java SE 다운로드 페이지

좌측 상단에는 각종 Java 제품들의 종류별로 카테고리가 나눠져 있고, 중앙 상단에는 JDK 설치와 NetBeans와 JDK를 함께 설치할 것인지 선택 옵션이 있다. 그리고 중앙 하단 우측에는 JDK, Server JRE, JRE 3개 선택 페이지가 있다. 참고로 형광펜으로 색칠된것을 보면 현재 Java SE는 8u112버젼이 최신 버젼인것을 알 수 있다. Java 8의 112번째 Update 라는 뜻이다.

참고로 JAVA SE는 Standard Edition, 데스크톱 어플리케이션과 간단한 서버 어플리케이션 개발 사용되며 EE는 Enterprise Edition, 복잡한 서버 어플리케이션 개발에, ME는 Micro Edition, 스마트폰과 기타 장치들에 사용된다. NetBeans는 Oracle에서 제공하는 IDE이다.

우리는 JDK가 필요하니 중앙 상단의 JDK설치만 설치를 클릭하자(왼쪽 옵션이다).
클릭 후 새 페이지로 이동하면 OS 별 JDK 리스트를 볼 수 있을텐데 본인 OS에 맞는 JDK를 설치하자. 나는 Ubuntu 16.04 64bit 버젼을 사용하므로 Linux x64(tar.gz)를 다운받았다. 윈도우 사용자들은 Window x32, x64를 윈도우 버젼에 맞게 설치하면 된다. 윈도우 사용자들은 설치파일을 단순히 실행하면 설치가 가능하고, linux 사용자들은 아래와 같이 설치하면 된다.


#파일 다운로드가 완료된 디렉토리로 이동 후 ~
#파일 압축해제
user@user-desktop:~/Downloads$ tar -xvf jdk-8u112-linux-x64.tar.gz
#파일 이름변경
user@user-desktop:~/Downloads$ mv jdk1.8.0_112 jdk1.8
#opt 폴더 내 java 디렉토리 생성
user@user-desktop:~/Downloads$ sudo mkdir /opt/java
#jdk1.8 폴더를 /opt/java 로 이동
user@user-desktop:~/Downloads$ sudo mv jdk1.8 /opt/java

이렇게 하면 다운받은 JDK가 /opt/java 폴더 내로 이동된다. 이후에 리눅스 사용자들은 jdk1.8/bin 디렉토리를 executable path에 추가해 주어야 한다. 윈도우 사용자들도 설치된 jdk를 환경변수에 등록해주어야 한다.


user@user-desktop:~$ sudo vi .profile
#.profile 파일을 열어 export PATH=/opt/java/jdk1.8/bin:$PATH 구문을 추가해주자

그리고 로그아웃 후 다시 로그인 하면 아래와 같이 자바가 설치되었음을 알 수 있을것이다.


user@user-desktop:~$ java -version
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)

2015년 2월 7일 토요일

[Eclipse] 이클립스 Papyrus(파피루스) UML 클래스 다이어그램을 자바(Java) & C++ 코드로 변환하기

papyrus 로고
papyrus 로고

지난번에 설명했던 Eclipse 개발팀에서 만든 모델링 툴인 Papyrus(파피루스), 파피루스는 추가 확장 소프트웨어로써 프로그램 모델링 툴을 지원한다. 보통 UML 다이어그램을 그릴 때 사용하며, UML 클래스다이어그램을 코드로 변환시키거나 코드를 UML 다이어그램으로 변환하는 기능도 제공한다. 언어는 현재 C++와 J링ava만을 지원한다.

Eclipse 파피루스 간단 사용법은 다른글을 참고하도록 하자, 이 글은 파피루스로 그린 클래스다이어그램을 코드로 변환시키는 기능에 대한 글이다.

우선 추가확장 프로그램을 설치하자. 새로운 소프트웨서 설치창에 Papyrus 업데이트 사이트를 등록해준다. 참고로 파피루스 버젼별 업데이트 사이트는 여기서 확인이 가능하다.

현재 최신 버젼 이클립스인 Luna 용은 주소가 요렇다.
http://download.eclipse.org/modeling/mdt/papyrus/updates/releases/luna
저 주소를 등록해주도록 하자.

파피루스 확장 repository 추가

등록을 끝마친 후 사이트를 골라 아래 체크박스 옵션에서 Group items by category 체크를 해제해주면 아래와 같은 추가 확장프로그램 리스트들을 보게 될것이다. 




자바(Java) 프로그래머들은 필요에 따라 아래 2개를 선택한 후 설치하고
  • Papyrus Java class Generator (Incubation)
    • 다이어그램 -> 자바코드로
  • Papyrus Java Reverse (Incubation)
    • 자바 코드 -> 다이어그램으로
C++ 프로그래머들은 
  •   Papyrus C++ profile, view and code generation (Incubation)
를 설치하도록 하자.




아직 설치가 끝난게 아니다. Papyrus Java classes Generator & Reverse 는 QVT Operational SDK 라는 추가 라이브러리를 요구한다. 만약 이걸 설치하지 않을시에는, 코드 변환을 하려고 버튼을 누른 순간 아래와 같은 에러 메시지를 보게될것이다.

No classes or packages produced from the transformation. Nothing is generated.
and exception "org.eclipse.papyrus.java.generator.metamodel.jdt.jdtmm.visitor.JDTVisitorException"

그렇니 다시 Install New Software 페이지에 들어가서, 이번에는 본인의 이클립스 업데이트 사이트를 선택한 후에 Modelling 카테고리 아래에서 QVT Operational SDK를 찾아 설치해주자자자




이 설치를 완료했다면, 이클립스 재시작후 UML 클래스 다이어그램 페이지로 가서




코드로 변환하기 원하는 클래스들을 선택한 후에, 우클릭 Java -> Generate Java Code를 선택하면 자동으로 자바클래스 파일들이 생성된다.