기강 자바-01
자바의 타입
자바에서 사용하는 변수의 타입은 크게 두 가지로 분류할 수 있다.
- 기본형 타입 (Primitive Type)
- 참조형 타입 (Reference Type)
기본형 타입
- Primitive Type
- 원시형 또는 기본형 타입이라고 부른다.
- 자바에는 여덟 가지의 기본형 타입이 존재한다.
- 기본형은 한 번에 한 가지 타입을 나타낼 수 있다.
- 기본형 타입은 자바에 의해 정의된 타입이며, 사용자가 직접 정의하거나 재정의할 수 없다.
- 기본형 타입은 리터럴을 갖는 변수의 타입을 의미한다.
- 리터럴이란 변하지 않는 값, 그 자체를 의미한다.
- 기본형 타입의 변수를 사용하는 것은 그 변수가 갖고있는 데이터를 그대로 사용하는 것이다.
- 정수형, 실수형, 논리형, 문자형, null 등이 있다.
- 기본형 타입의 값들은 JVM의 메모리 영역 중에서 Stack 영역에 저장된다.
- == 같은 비교 연산자를 사용해 값을 비교할 수 있다.
기본형 타입 | 메모리 크기 | 타입 | 값의 범위 | 기본 값 |
---|---|---|---|---|
boolean | 1byte | 논리형 | true, false | false |
char | 2byte | 문자형 | \u0000 ~ \uffff (Unicode) | \u0000 |
byte | 1byte | 정수형 | -2^7 ~ 2^7 - 1 | 0 |
short | 2byte | 정수형 | -2^15 ~ 2^15 - 1 | 0 |
int | 4byte | 정수형 | -2^31 ~ 2^31 - 1 | 0 |
long | 8byte | 정수형 | -2^63 ~ 2^63 - 1 | 0 |
float | 4byte | 실수형 | 1.4E–45 ~ 3.4028235E+38 | 0.0f |
double | 8byte | 실수형 | 4.9E–324 ~ 1.7976931348623157E+308 | 0.0d |
2의 보수법
- 자바는 부호 비트와 2의 보수법으로 음수를 표현한다.
- 부호 비트란,
- 숫자 타입의 MSB를 부호를 나타내는 비트로 사용한다.
- 0이면 양수
- 1이면 음수
- 2의 보수법이란
- 어떤 수 x에 y를 더했을 때, 주어진 자릿수에서 가장 큰 값이 되도록 하는 y를 보수라고 한다.
- 주로 컴퓨터에서 음수를 나타낼 때 사용된다.
- 2의 보수법을 사용해 부호화된 이진수에서 양수, 음수를 모두 나타낼 수 있다.
- 1의 보수 : 각 비트를 모두 반전 시킨 것
- 2의 보수 : 1의 보수에서 1 더한 것
- 예를 들어 6이 있을 때, (4비트까지만 표현, 앞의 1111.. 생략)
- 1의 보수 : 0110 -> 1001
- 2의 보수 : 0110 -> 1010
부동 소수점
- 실수를 표현하는 방식에는 두 가지가 있다.
- 고정 소수점
- 부동 소수점
- 자바는 기본형 타입으로 실수형을 표현하는데 부동 소수점을 사용한다.
먼저 고정 소수점을 알아본다. 예시는 32비트이다.
1
0111 1111 1111 1111 2222 2222 2222 2222
- 위의 32비트는 다음과 같이 이뤄져 있다. (소수점의 위치는 내가 임의로 정했음)
- 부호 비트 (0) - 1bit
- 정수 비트 (1) - 15bit
- 실수 비트 (2) - 16bit
- 정수와 실수 부분이 나눠져 있고, 소수점의 위치가 변하지 않는다.
- 정수 비트에 여유가 있어도, 실수를 표현하는데 사용할 수 없다.
- 반대의 경우도 마찬가지이다.
이번엔 부동 소수점을 알아본다.
1
0111 1111 1222 2222 2222 2222 2222 2222
- 위의 32비트는 다음과 같이 이뤄져 있다.
- 부호 비트 (0) - 1bit
- 지수 비트 (1) - 8bit
- 가수 비트 (2) - 23bit
- 부동 소수점은 소수점의 위치가 변할 수 있다.
- 수를 정규화하여 최대한 많은 수를 표현할 수 있도록 한다.
- 부동 소수점으로 수를 나타내는 방법
- 10진수인 59.125가 있다고 할 때,
- 10진수를 2진수로 변환한다. (59.125 => 111011.001)
- 2진수를 정규화한다. (111011.001 => 1.11011001 * 2^5)
- 2의 지수를 지수 비트에, 나머지 실수 부분을 가수 비트에 담아 표현할 수 있다.
- 부호 비트 : 0
- 지수 비트 : 100 0100 1
- 가수 비트 : 110 1100 1000 0000 0000 0000
- 합치면 : 0100 0100 1110 1100 1000 0000 0000 0000
과제
- 38.25를 부동 소수점으로 나타내 보세요.
- 부동 소수점의 부정확성에 대해 알아보고 설명해 보세요.
- 연산 순서
- 정확한 실수 계산을 위한 대안
참조형 타입
- Reference Type
- 참조형 데이터 타입
- 참조형 타입은 사용자가 정의할 수 있으므로, 그 개수가 무한하다.
- 참조형 타입은 0개 이상의 기본형 혹은 참조형 타입을 나타낼 수 있다.
- 참조형 타입은 객체를 참조하는 타입을 의미한다.
- 객체의 메모리 주소를 저장하고 있고, 그 메모리 위치에 실질적인 데이터가 들어있는 모양이다.
- 참조형 타입의 객체를 사용한다는 것은 해당 객체가 갖고있는 메모리 주소에 위치한 데이터를 참조하는 것이다.
- 자바의 참조형 타입의 종류로는
- 클래스, 인터페이스, 열거형, 배열 등이 있다.
- 참조형 타입의 객체는 JVM의 메모리 영역 중에서 Heap 영역에 저장된다.
- 참조형 타입의 인스턴스를 만들기 위해서는 new 연산자를 사용해야 한다.
- 참조형 타입의 초기화 과정이 없다면 기본적으로 null이 들어간다.
- new 연산자를 사용해 만든 두 인스턴스는 갖고 있는 값이 같더라도 서로 다른 인스턴스이다.
- == 연산자를 사용하여 값을 비교할 때 주의해야 한다.
- 위에서 말했듯이 인스턴스는 내부적인 값이 같더라도 서로 다른 존재이다.
- 참조형 객체끼리 == 연산자를 사용하면 둘의 참조값을 비교하는 꼴이기 때문에 같은 인스턴스를 참조하지 않는 이상 무조건 false이다.
- 값이 ‘동등한지’ 알고 싶다면 Object의 equals()를 사용해야 한다.
과제
- 본인이 알고있는 내용만으로 기본형 타입과 참조형 타입에 대해 각각 설명해 보세요.
String
- 자바에서 문자열을 다루기 위한 클래스이다.
- 스트링은 다음과 같은 특징들이 있다.
- 참조형 타입이지만, 문자열 리터럴로 초기화가 가능하다.
- + 연산자를 사용할 수 있다.
- 불변 클래스이다. (Immutable Class)
- 문자열 리터럴은 상수 풀(Constant Pool)에 저장된다.
- 상수 풀을 사용해 문자열 리터럴을 캐싱한다.
String의 초기화
- 보통의 참조형 타입들은 new 연산자를 사용해 인스턴스를 할당한다.
- 그러나 String은 사용상 편의를 위해 리터럴을 사용해 값을 할당할 수 있다.
1
2
3
4
5
MyClass myClass = new MyClass(); // 일반적인 참조형 타입의 인스턴스 할당
String s1 = "abc"; // 리터럴을 직접 할당
String s2 = new String("abc"); // new 연산자를 사용한 인스턴스 생성
String s3 = String.valueOf("abc"); // String의 valueOf() 스태틱 메서드를 사용한 할당
- 일반적으로 참조형 타입의 초기화는 위의 MyClass처럼 new 연산자를 사용해 인스턴스를 생성해 할당하는 방식이다.
- String은 문자열 리터럴을 줘서 간단하게 초기화 할 수 있다.
- 물론 String도 위의 s2처럼 new 연산자를 사용해서 초기화 할 수도 있다.
- s3처럼 String의 valueOf() 스태틱 메서드를 사용할 수도 있다.
- 그 외에도 몇 가지 방법이 있다.
String의 더하기 연산자
- 기본형 타입에서 사용하듯이 String이나 문자열 리터럴에도 더하기 연산자가 사용 가능하다.
1
2
3
4
String s1 = "abc";
String s2 = "def";
System.out.println(s1 + s2); // abcdef
- 매우 편리해 보이지만, 문자열 더하기 연산은 성능에 부정적인 영향을 끼칠 수 있다.
- 그 이유는 String이 불변 객체이기 때문인데, 정확한 이유는 뒤에서 알아보도록 한다.
String은 불변 객체
- 불변이란 변하지 않는 것을 의미한다.
- 즉, String은 한 번 생성하면 그 값을 변경할 수 없다.
- 왜 굳이 불변하도록 만들었나요?
- 보안성 향상
- 어떤 객체를 다른 객체가 참조하고 변경해도 원래 객체에는 값의 변경이 일어나지 않음.
- Thread-Safe
- 상태 변경이 불가능하기 때문에, 여러 스레드에서 동시에 접근해도 안전하다.
- 보안성 향상
- 아닌데요?
- 내가 해보니까 바뀌던데요??
- 더하기 연산자는 그럼 뭔데요???
1
2
3
4
5
6
7
8
9
String s = "abc";
s = "abcd"; // s가 갖고 있던 abc란 문자열이 abcd로 변한 것이 아닌,
// abc란 문자열 대신 abcd란 문자열을 생성해 s에게 할당한 것이다.
s = s + "e"; // abcde를 생성해 할당
s = s + "f"; // abcdef를 생성해 할당
s = s + "g"; // abcdefg를 생성해 할당
s = s + "h"; // abcdefgh를 생성해 할당
- 모두 기존의 문자열 값을 변경하는 것이 아닌, 새로운 문자열을 만들어내는 것이다.
- String에 새로운 문자열을 할당하는 행위도
- 더하기 연산자를 이용해 새로운 문자열을 만드는 행위도
- 이러면 메모리에 여러 개의 문자열이 생성되니까 성능적으로 부담되겠지?
- 엥? 참조형은 원래 새로 생성할 때마다 메모리에 쌓이는 거 아니었어?
String Constant Pool
- 자바에서 사용된 문자열 리터럴은 기본적으로 Constant Pool에 캐싱된다.
- 이를 통해 문자열 리터럴을 재사용 할 수 있다.
- 재사용을 통해 메모리 절약을 할 수 있다.
- 문자열 리터럴 생성 방식은 다음과 같다.
- 해당 문자열 리터럴이 상수 풀에 존재하는지 확인한다.
- 있다면, 그걸 그대로 참조한다.
- 없다면, 생성하여 상수 풀에 담는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
String s = "abc";
String s1 = "abc";
String s2 = new String("abc");
String s3 = String.valueOf("abc");
String s4 = s;
sout(s == s1); // true
sout(s == s2); // false
sout(s == s3); // true
sout(s == s4); // true
s4 = "abcd";
sout(s == s4); // false
- 위의 예시를 보면 s와 s1이 == 같냐고 물었을 때 true가 나왔다.
- 엥 참조형인데 왜 같은거야?
- 자바에서 문자열 리터럴은 기본적으로 Constant Pool에 캐싱하고 재사용하기 때문이다.
- 그러나 s와 s2는 서로 다르다고 나왔다.
- 왜? Constant Pool에서 똑같은걸 참조한다며?
- new 연산자를 사용하면 기존의 것을 참조하지 않고 새로 생성하기 때문이다.
- s3는 s와 같다는걸 보니, String.valueOf()는 Constant Pool에서 참조하는 것을 알 수 있다.
- s4는 s를 그대로 참조하였기 때문에 당연히 true가 나온다.
- 그러나 s4의 값을 변경하고 다시 비교해보면 서로 다르다고 나온다.
- 왜?? s의 참조값을 그대로 가져왔는데 s랑 다를 수가 있나?
- String은 불변 객체이기 때문이다.
- s4에 다른 값을 할당해준 순간에 “abcd”라는 리터럴이 Constant Pool에 캐싱되고,
- s와 s4는 서로 다른 값을 참조하고 있는 모양이다.
1
2
3
4
5
String s = "";
for(int i = 0 ; i < 100 ; i++) {
s = s + i;
}
- 만약 더하기 연산을 해서 새로운 문자열을 계속 생성한다면?
- Constant Pool에 수많은 문자열이 캐싱될 것이고,
- 사용되지 않는 문자열이 잔뜩 쌓인 것은 메모리 낭비이고,
- 사용되지 않는 리터럴을 위해 가비지 컬렉터가 작동할 것이고,
- 이 모든 것이 성능에 영향을 미칠 것이다.
과제
- String, StringBuilder, StringBuffer 세 가지를 알아보고 설명하세요.
- Immutable
- Mutable
- Thread-Safe
- JDK8 에서 개선된 문자열 더하기 연산의 처리 방식을 알아보고 설명하세요.
- StringBuilder
- JDK9 에서 문자열 처리 속도를 개선하기 위해 사용된 방식을 알아보고 설명하세요.
- Compact Strings
- Compressed Strings
배열
- 배열은 같은 타입의 변수를 여러 개 주주죽 늘여놓은 자료 구조이다.
- 배열은 처음 생성 시점에 길이를 정해줘야 한다.
- 메모리 상에서도 주주주죽 순서대로 이어져 있다.
- 인덱스를 통해 각각의 값에 접근할 수 있다.
- 배열은 기본적으로 참조형 타입이다.
- 배열은 기본형 타입과 참조형 타입 둘 다 사용해 만들 수 있는데,
- 기본형 타입의 배열도 참조형 타입이다.
- 배열 변수는 배열의 시작점에 대한 참조값을 갖고 있다.
- 인덱스를 통해 요소에 대한 메모리 주소값을 계산할 수 있다.
- 실제 배열도 Stack 영역에 적재되어 있으며, 순서대로 주우욱 연속되게 메모리를 할당 받는다.
- 기본형 타입이라면 각각 요소들이 값을 담고 있을 것이고,
- 참조형 타입이라면 각각의 요소들이 힙 영역의 참조값을 갖고 있을 것이다.
배열의 생성
1
2
3
4
5
6
7
int[] intArray = new int[3]; // 3개의 길이를 할당받은 int형 배열
String[] stringArray = new String[] {"a", "b", "c"}; // 값을 직접 할당받은 String 배열
char[] charArray = {'a', 'b', 'c'}; // 배열 리터럴을 할당받은 char 배열
int[][] ints2d1 = new int[3][4]; // 2차원 배열 3 * 4
int[][] ints2d2 = new int[2][2] {new int[2], new int[2]}; // 2차원 배열 2 * 2
int[][] ints2d3 = {{1,2,3}, {4,5,6}, {7,8,9}}; // 2차원 배열 3 * 3
- 가장 간단한 방법으론 배열 리터럴을 할당하는 방법이다.
- 그 다음은 new 연산자를 사용할 수 있는데,
- 뒤에 배열 리터럴을 달아주지 않으면 길게 자리만 잡아두고 실제 값은 각 타입의 기본값이 들어간 배열이 된다.
- 뒤에 배열 리터럴을 달아줬다면,
- 배열 리터럴의 길이에 맞게 메모리 할당을 받고,
- 각각의 요소가 할당된 값으로 채워진 상태의 배열이 된다.
- 맨 아래처럼 다차원 배열을 만들 수도 있다.
- 예시는 2차원 배열이지만 3차원, 4차원 배열을 만들 수도 있다.
- 다차원 배열 역시 주루룩 이어진 모양으로 메모리를 할당 받는다.
- 다차원 배열 역시 여러 가지 방식으로 생성할 수 있다.
배열 요소의 접근
- 배열 요소에 접근은 인덱스를 사용해 할 수 있다.
- 배열의 인덱스는 0부터 시작한다.
- 0부터 시작하기 때문에 마지막 인덱스는 (배열의 길이 - 1)이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8}; // 길이 8의 배열
sout(arr.length); // 8
// length를 사용해 배열의 길이를 알 수 있다.
sout(arr[0]); // 1
sout(arr[1]); // 2
sout(arr[2]); // 3
sout(arr[3]); // 4
sout(arr[4]); // 5
sout(arr[5]); // 6
sout(arr[6]); // 7
sout(arr[7]); // 8
arr[0] = 100; // 이런식으로 배열 요소의 값을 변경할 수도 있다.
과제
1
2
3
4
5
3, 4, 5, 6,
5, 5, 4, 1,
2, 3, 4, 7,
4, 1, 0, 9,
6, 10, 6, 8
- 위의 모양과 같은 배열을 만들어 보세요.
- 모든 값의 합과 평균을 구하고 출력해 보세요.
- 배열에서 최대값과 최소값을 출력해 보세요.
- 최대값과 최소값의 자리를 서로 바꿔 보세요.
- 2 * 13 모양의 빈 문자형 배열을 만들어 보세요.
- 메모리에 배치된 순서에 따라 알파벳 ‘a’부터 ‘z’까지 순서대로 할당해 보세요.
자바의 값 전달
- 메서드 호출 시 값 전달 방식에는 세 가지 정도가 있다.
- Call By Value
- Call By Reference
- Call By Name
- 자바는 Call By Value 방식을 사용해 값을 전달한다.
- 여기서 말하는 값의 전달이란, 메소드 호출 시 인자로 넘기는 값에 대한 것을 말한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Main {
public static void main(String[] args) {
int a = 100;
sout(a); // 100
add10(a);
sout(a); // 100
}
private static void add10(int num) {
num = num + 10;
}
}
- 위의 예시를 보면 add10 메서드의 인자로 a를 넘겨 호출했지만
- a의 값이 110이 아닌 100으로 변하지 않은 것을 볼 수 있다.
- 자바는 기본적으로 Call By Value 방식을 사용한다고 했다.
- Call By Value란 인자의 값만을 복사하여 넘기는 방식이다.
- 따라서 100이란 정수값을 보낸 것이지 a 그 자체의 메모리 주소를 넘긴 것이 아니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass {
int num;
// constructor, toString(), getter, setter
}
class Main {
public static void main(String[] args) {
MyClass a = new MyClass(100);
sout(a); // 100
add10(a);
sout(a); // 110
}
private static void add10(MyClass o) {
o.setNum(o.getNum() + 10);
}
}
- 엥? 값만 복사해서 보냈는데 왜 이번엔 값이 바꼈지?
- 참조형 타입의 경우 그 참조값을 보내기 때문이다.
- 참조값을 보내기 때문에 파라미터를 사용해 값을 변경하면, 해당 인스턴스의 값이 변경된다.
과제
- 기본형 타입과 참조형 타입을 메소드의 인자로 넘겨보고, 값을 변경해본 뒤에 출력해보세요.
This post is licensed under CC BY 4.0 by the author.