생성자 활용
자바에서 제공하는 클래스를 사용하여 생성자의 활용을 이해해보자. 자바는 인스턴스 변수를 초기화시키기 위해 여러 개의 생성자를 만들어 제공한다. 필요에 맞는 적절한 생성자를 호출하여 인스턴스를 초기화시킨 후 사용해야 한다.
java.lang.String: 문자코드표
String 인스턴스 초기화 방법
- 문자열 리터럴을 사용
- char 배열을 사용
- byte 배열을 사용 (한글 주의!)
단, 한글 문자 코드의 바이트 배열을 가지고 String 인스턴스를 초기화시킬 때는 주의해야 한다. String 클래스는 파라미터로 넘겨 받은 바이트 배열을 가지고 유니코드(UCS2) 문자열로 변환할 때, 바이트 배열이 운영체제의 기본 문자 코드로 되어 있다고 간주하기 때문이다. Windows는 MS949을, Unix 계열은 UTF-8을 기본 문자 코드로 사용한다. 다른 코드의 규칙대로 바이트코드를 작성하면 한글이 깨질 수 있다.
이클립스에서는 JVM을 실행할 때 입출력 기본 인코딩을 UTF-8로 설정한다.
따라서 바이트 배열로 String 클래스의 생성자를 호출할 때 바이트 배열과 인코딩 정보를 함께 받는 생성자를 사용해야 한다. 그러면 JVM은 바이트 배열에 들어 있는 값을 제대로 유니코드로 바꿀 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 문자열 리터럴
String s1 = new String("Hello");
// 2. char[]
char[] chars = new char[] {'H', 'e', 'l', 'l', 'o'};
String s2 = new String(chars);
// 3. (1) 바이트 배열 (영문 알파벳)
byte[] bytes1 = {
// EUC-KR 코드
(byte)0x48, // H
(byte)0x65, // e
(byte)0x6c, // l
(byte)0x6c, // l
(byte)0x6f // o
};
String s3 = new String(bytes1);
// 3. (2) 바이트 배열 (한글): EUC-KR 코드의 경우
byte[] bytes2 = {
(byte)0xb0, (byte)0xal, //가
(byte)0xb0, (byte)0xa2 //각
}
String s4 = new String(bytes2, "EUC-KR");
3의 (2)처럼 바이트 배열이 기본으로 사용하고 있는 문자 코드표(Character Set)이 OS, IDE와 다를 경우, 한글이 깨진다. 따라서 위에서는 바이트 배열과 인코딩 정보를 함께 받는 생성자를 사용하여 문제를 해결하였다.
바이트 배열로 문자열을 넘기거나 바이트로 문자열을 변환하는 것은 실무에서 정말 많이 사용된다. 특히 한글은 어떤 문자 코드표을 사용하느냐에 따라 달라지기 때문에 이에 대한 이해가 반드시 필요하다. 잘못된 규칙을 알려주면 한글이 깨져 나온다. 네트워크 세계에서는 기본이 바이트이다..
실제 프로그램을 만들 때는 사용자가 프로그램을 사용할 때 운영체제에서든지 깨지지 않도록 바이트 배열만 넘겨주지 말고 인코딩 정보도 함께 넘겨주는 생성자를 사용해야 한다.
추가적으로, Windows CLI에서 한글이 깨지지 않도록 하기 위해서는 다음과 같이 하자.
- JVM 실행 옵션 추가:
-Dfile.encoding=UTF-8
java -Dfile.encoding=UTF-8 -cp bin/main 클래스명
- Command 창에서 실행: Powershell은
-Dfile.encoding
에서 도트(.)를 분리문자로 인식하기 때문에 제대로 처리하지 못한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
String s1 = new String("ABC가각");
for (byte b : bytes) {
System.out.println(Integer.toHexString(b));
}
//41 => A
//42 => B
//43 => C
//ffffffea
//ffffffb0
//ffffff80 => 가
//ffffffea
//ffffffb0
//ffffff81 => 각
//0xff를 &했을 때
for (byte b : bytes) {
System.out.println(Integer.toHexString(b & 0xff));
}
//41 => A
//42 => B
//43 => C
//ea
//b0
//80 => 가
//ea
//b0
//81 => 각
java.util.Date
날짜 데이터를 다루는 클래스이고, 이에 해당하는 다양한 메서드가 들어 있다.
Date()
: 기본 생성자로, 메모리를 오늘 날짜 값으로 초기화시킨다.1 2 3 4
//인스턴스를 만드는 시점에 현재 날짜와 시분초를 설정한다 (초기화한다) Date d1 = new Date(); System.out.println(d1.toString()); // Mon Aug 10 19:23:54 KST 2020
new Date(118, 2, 20)
: 년, 월, 일 값으로 날짜 인스턴스를 초기화시킨다.1 2 3
Date d2 = new Date(120, 8, 10); System.out.println(d2); // Thu Sep 10 00:00:00 KST 2020
- 해당 생성자는 deprecated 생성자이다. => 이에 대해서는 별도 포스팅하였다
java.util.Calendar: 생성자에와 접근 권한
생성자가 있어도 접근 권한이 없으면 생성자를 호출할 수 없다. Java API에서 볼 수 있듯, java.util.Calendar 클래스의 생성자는 접근제어자 protected
로 보호되고 있다. Calendar를 상속 받은 자식 클래스만 해당 생성자를 호출할 수 있다는 뜻이다. 따라서 new 명령을 통해 바로 인스턴스를 생성할 수 없다.
1
2
3
4
5
6
// Java API
protected Calendar()
{
this(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
sharedZone = true;
}
- 생성자에 접근할 수 없을 때, 어떻게 인스턴스를 생성할 수 있을까?
- 다른 클래스의 도움을 받는다.
- 해당 클래스에서 제공하는 클래스 메서드를 사용해 간접적으로 생성한다.
Calendar 클래스는 2번째로, 클래스 메서드를 통해 생성하도록 유도하고 있다.
1
2
3
4
5
6
//Calendar c = new Calendar(); => 컴파일 오류
Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
System.out.println(c1 == c2); // false
getInstance()
를 사용하면 오늘 날짜 및 시간 정보를 저장한 객체를 만들어 리턴한다. 호출 시점의 시각이 다르기 때문에 두 객체의 주소는 다르다.
- 왜 생성자로의 접근을 막을까?
- 같은 값을 갖는 객체를 여러개 생성하지 못하도록 하고 메모리를 절약할 수 있다 =>
Singleton1
기법 - 객체 생성과정이 복잡할 때, 객체 생성 코드를 단순하게 만들 수 있고, 생성된 객체를 재활용할 수 있다. =>
Factory Method
- 같은 값을 갖는 객체를 여러개 생성하지 못하도록 하고 메모리를 절약할 수 있다 =>
싱글턴이란? 전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴. getInstance 메서드를 통해 모든 클라이언트에게 동일한 인스턴스를 반환한다. ex) 레지스트리와 같은 설정 파일(객체가 여러개 생성되면 설정 값이 변경될 위험이 있다.)
팩토리 메서드란? 객체를 만들어내는 부분을 서브 클래스에 위임하는 패턴이다. 즉, new 키워드를 호출하는 부분을 서브 클래스에 위임하는 것이다. 객체를 만들어내는 공장을 만드는 패턴
생성(Creational) 패턴이란? 객체 생성에 관련된 패턴으로, 객체의 생성과 조합을 캡슐화해 특정 개체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공한다. 즉 인스턴스를 만드는 절차를 추상화하는 패턴이다.
인스턴스 메서드 활용
get()
메서드에 상수를 파라미터로 넘겨주면 해당 값을 리턴받을 수 있다.
1
2
3
4
5
6
7
8
9
10
// 상수의 활용
System.out.println(c.get(Calendar.YEAR)); // 년도
System.out.println(c.get(Calendar.MONTH) + 1); // 월(0 ~ 11)
System.out.println(c.get(Calendar.DATE)); // 일
System.out.println(c.get(Calendar.DAY_OF_WEEK)); // 요일(1 ~ 7)
System.out.println(c.get(Calendar.WEEK_OF_MONTH)); // 그 달의 몇 번째 주
System.out.println(c.get(Calendar.HOUR)); // 시(0 ~ 11)
System.out.println(c.get(Calendar.HOUR_OF_DAY)); // 시(24시)
System.out.println(c.get(Calendar.MINUTE)); // 분
System.out.println(c.get(Calendar.SECOND)); // 초
인스턴스를 1개만 생성하도록 제한해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Car {
String model;
int cc;
static Car obj;
// 외부에서 호출 못하는 생성자
private Car() {}
// 이미 인스턴스가 있다면 해당 인스턴스 리턴
// 없다면 객체 생성 후 리턴
static Car getInstance() {
if (obj == null) {
obj = new Car();
}
return obj;
}
}
인스턴스와 클래스 메서드의 활용
java.lang.String
인스턴스 메서드
charAt()
1 2 3 4 5 6 7 8 9 10
// Java API 소스코드 // int값으로 생성 public char charAt(int index) { if (isLatin1()) { return StringLatin1.charAt(value, index); } else { return StringUTF16.charAt(value, index); } }
compareTo()
contains()
concat()
: 문자열 연결하여 새 문자열 만들기equals()
: 두 인스턴스에 들어 있는 문자열이 같은지 비교getBytes()
: 인스턴스에 들어 있는 문자 코드를 바이트 배열로 만들어 리턴- 인코딩 문자집합을 지정하지 않으면 JVM 기본 문자집합(UTF-8)으로 인코딩(‘encode this string into a sequence of using the platform’s default charset’)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
String s3 = new String("ABC가각"); // Default byte[] bytes = s3.getBytes(); for (int i = 0; i < bytes.length; i++) System.out.printf("%x, ", bytes[i]); System.out.println(); // 41,42,43,ea,b0,80,ea,b0,81, // EUC-KR bytes = s3.getBytes("EUC-KR"); for (int i = 0; i < bytes.length; i++) System.out.printf("%x,", bytes[i]); System.out.println(); // 41,42,43,b0,a1,b0,a2,
스태틱(클래스) 메서드: 작업하는 데 필요한 모든 것들을 매개변수로 받아야 한다.
format()
: 형식을 갖춘 문자열 만들기String str = String.format("%s님 반갑습니다.", "모니카")
join()
: 구분자와 문자열을 파라미터로 받아서 새 문자열 생성String str = String.join(":", "서울", "부산", "전주")
- => 서울:부산:전주
valueOf()
: primitive 값을 문자열로 만든다.charAt()
: 인스턴스 메서드로도 사용할 수 있다.
java.lang.[래퍼(wrapper) 클래스]
wrapper 클래스란?
기본타입 데이터를 객체로 취급해야 하는 경우가 있다. 예를 들어, 메서드의 인수로 객체 타입만이 요구되면, 기본형 타입의 데이터를 그대로 사용할 수는 없다. 이때 8개 기본 타입에 해당하는 데이터를 객체로 포장해주는 wrapper class(래퍼 클래스)를 사용할 수 있다. 래퍼 클래스는 각각의 타입에 해당하는 데이터를 아규먼트로 전달 받아, 해당 값을 가지는 객체로 만들어준다.
기본 타입 | 래퍼 클래스 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
객체 생성
new Integer(값)
: 생성자를 통해 Integer 객체를 생성할 수 있지만 사용하지 말라고 권고하였기 때문에 가능한 사용하지 않는 것이 좋다.Integer.valueOf(값)
: 클래스 메서드를 사용하여 Integer 인스턴스를 생성하자.- valueOf으로 인스턴스를 만들 때 값이 같으면 heap에 각각 따로 메모리를 만들지 않는다.
1
2
3
4
5
6
7
8
9
Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
System.out.println(i1.toString() == i2.toString());
// false
Integer i3 = Integer.valueOf(100);
Integer i4 = Integer.valueOf(100);
System.out.pritnln(i1.toString() == i2.toString());
// true
인스턴스 메서드
compareTo()
intValue()
클래스 메서드
parseInt()
toBinaryString()
toOctalString()
toHexString()
valueOf()
1 2 3 4 5
// 문자열에 있는 수를 10진수로 간주한다. Integer x1 = Integer.valueOf("44"); // 16진수라고 지정한다. Integer x2 = Integer.valueOf("44", 16);
parseFloat()
parseBoolean()
mini pms 프로젝트
- 08 버전: 클래스로 메서드 분류
- Prompt, MemberHandler, ProjectHandler, TaskHandler 추가
- 09 버전: 패키지로 메서드 분류
- Prompt, MemberHandler, ProjectHandler, TaskHandler 클래스 이동
이전
버전09