...
정수 자료형
자바의 정수를 표현하기 위한 자료형은 대표적으로 int, long 이 있다. (byte, short 도 있지만 잘 사용하지 않는다.)
정수형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 |
byte | 1바이트 | -128 ~ 127 |
short | 2바이트 | -215 ~ (215 - 1) |
-32,768 ~ 32,767 | ||
int | 4바이트 | -231 ~ (231 - 1) |
-2,147,483,648 ~ 2,147,483,647 | ||
long | 8바이트 | -263 ~ (263 - 1) |
-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
int age = 10;
long countOfStar = 8764827384923849L;
long 변수에 값을 대입할 때는 대입하는 숫자 값이 int 자료형의 최대값인 2147483647 보다 큰 경우 8764827384923849L 과 같이 값 뒤에 L 접미사를 붙여주어야 한다.
만약 접미사를 누락하면 컴파일 에러가 발생한다.
소문자 l 를 써도 되지만 숫자 1과 비슷하게 보이므로 추천되지는 않는다.
정수 오버플로우 / 언더플로우
정수형 데이터의 타입을 사용할 때에는 반드시 자신이 사용하고자 하는 데이터의 최소/최대 크기를 고려해야 한다.
만약 해당 타입이 표현할 수 있는 범위를 벗어난 데이터를 저장하게 되면, 오버플로우(overflow)가 발생해 전혀 다른 값이 저장될 수 있기 때문이다.
- 오버플로우 : 해당 타입이 표현할 수 있는 '최대 표현 범위'보다 큰 수를 저장할 때 발생하는 현상
- 언더플로우 : 해당 타입이 표현할 수 있는 '최소 표현 범위'보다 작은 수를 저장할 때 발생하는 현상
byte max = 127;
byte min = -128;
System.out.println(max + 1000); // ERROR
System.out.println(min - 1000); // ERROR
자바에서 byte 타입이 표현할 수 있는 정수 크기 범위는 -128부터 127까지 이다.
하지만 위의 코드 처럼 바이트 타입의 변수에 크기 범위를 넘어서는 연산을 하게 되면 오버플로우, 언더플로우가 발생하여 잘못된 결과가 저장되게 된다.
underscore 표기법
언더스코어 표기법은 jdk 7 부터 지원하는 문법으로, 우리가 큰 숫자를 콤마 1000,000,000 로 표현하듯이 프로그래밍에선 콤마 대신 밑줄 문자로 표현해도 실제로는 숫자로 읽혀지게 된다.
int cost = 1000_000_000; // 1000000000
2진수 / 8진수 / 16진수
8진수와 16진수 정수는 int 자료형을 사용하여 표시하지만, 숫자 앞에 약속된 기호를 붙여주어서 이 둘을 구분한다.
// 0(숫자 '0')으로 시작하면 8진수
int octal = 023;
// 0x(숫자 '0' + 알파벳 'x')로 시작하면 16진수
int hex = 0xC;
// 0b(숫자 '0' + 알파벳 'b')로 시작하면 2진수
int binary 0b101
실수 자료형
자바의 실수를 표현하기 위한 자료형은 대표적으로 float, double 이 있다.
과거에는 실수를 표현할 때 float형을 많이 사용했지만, 하드웨어의 발달로 인한 메모리 공간의 증가로 현재에는 double형을 가장 많이 사용한다.
따라서 실수형 타입 중 기본이 되는 타입은 double형 이다.
실수형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 | 리터럴 타입 접미사 |
float | 4바이트 | (3.4 X 10^-38) ~ (3.4 X 10^38) | F 또는 f |
double | 8바이트 | (1.7 X 10^-308) ~ (1.7 X 10^308) | D 또는 d (생략 가능함) |
float pi = 3.14F;
double morePi = 3.14159265358979323846;
위의 정수 long 타입 처럼, float 변수에 값을 대입할 때에는 3.14F 와 같이 F접미사(또는 소문자 f)를 꼭 붙여 주어야 한다.
실수의 표현 오차
컴퓨터는 모든 데이터를 내부적으로 2진수로 처리한다는 지식은 모두들 알고 있을 것이다.
그런데 컴퓨터의 메모리는 한정적이기 때문에 정수를 표현할수 있는 수의 제한이 있듯이, 실수의 소숫점을 표현할 수 있는 수의 제한이 존재한다.
그래서 이러한 실수의 부정확한 연산의 한계를 최소화하기 위해 컴퓨터에서는 소수를 이진법으로 표현할때 고정 소수점 방식 이 아닌 부동 소수점 방식을 이용한다.
부동 소수점 방식을 사용하면 매우 큰 범위의 실수까지도 표현할 수 있어 보다 정밀하게 소수를 표현할수는 있지만 그래도 완전히 정확하게 표현하는 것이 아니라서, 소수 연산에 있어 부정확한 실수의 계산값을 초래하게 된다.
즉, 오차가 필연적으로 발생하게 된다는 뜻이다.
이것은 자바뿐만 아니라, 모든 프로그래밍 언어에서 발생하는 기본적인 문제이다.
double value1 = 12.23;
double value2 = 34.45;
// 기대값 : 46.68
System.out.println(value1 + value2); // 46.68000000000001
12.23 와 34.45 을 더했으니 기대 결과로 46.68 이 나와야 되지만, 실제로는 46.68000000000001가 출력되어 버린다.
이처럼 컴퓨터에서의 실수 연산에서는 소수점 단위 값을 정확히 표현하는 것이 아니라 근사값으로 처리하기 때문에, 정밀도의 문제로 인해 오차가 발생하게 되는 것이다.
물론 수 자체의 크기로서는 근사한 차이 정도 밖에 안되겠지만, 금융이나 로봇과 같은 관련 프로그램에서는 이 오차가 큰 영향을 미칠 수 있기 때문에 아주 정확한 계산이 필요하다.
따라서 이러한 컴퓨터의 실수 연산 문제를 해결 하기 위해 자바(Java)에서는 두가지 방법을 제공한다.
결론부터 말하자면 실수를 int, long 정수형 타입으로 치환하여 사용하거나, BigDecimal 클래스를 이용하면 된다.
예를 들어 12.23 과 34.45 에 100을 곱해서 1223 과 3445로 정수로 치환해서 더한 값 4668을 다시 100을 나누어서 소수 결과값 46.68을 도출하는 일종의 꼼수 방법이라고 보면 된다.
하지만 컴퓨터에서는 데이터의 표현 범위가 제한되어 있기 때문에, 소수의 크기가 9자리를 넘지 않으면 int 타입을 사용하면 되고, 18자리를 넘지 않으면 long 타입을 사용하면 되지만, 만일 18자리를 초과하면 BigDecimal 클래스를 사용해야 한다.
double a = 1000.0;
double b = 999.9;
System.out.println(a - b); // 0.10000000000002274
// 각 숫자에 10을 곱해서 소수부를 없애주고 정수로 형변환
long a2 = (long)(a * 10);
long b2 = (long)(b * 10);
double result = (a2 - b2) / 10.0; // 그리고 정수끼리 연산을 해주고, 다시 10.0을 나누기 하여 실수로 변환하여 저장
System.out.println(result); // 0.1
// BigDecimal 자료형을 사용
BigDecimal bigNumber1 = new BigDecimal("1000.0");
BigDecimal bigNumber2 = new BigDecimal("999.9");
BigDecimal result2 = bigNumber1.subtract(bigNumber2); // bigNumber1 - bigNumber2
System.out.println(result2); // 0.1
실수의 유효 자릿수 (정밀도)
자바에서는 실수의 소수 자리를 표현하는데 있어 타입별 유효 자릿수 제한이 존재한다.
유효자릿수는 소수점 이하 자리수를 뜻하는 것이 아니라 좌측부터의 숫자 갯수를 의미한다.
실수형 타입 | 지수의 길이 | 가수의 길이 | 유효 자릿수 |
float | 8 bit | 23 bit | 소수점 약 6~7자리까지 높은 확률로 정확히 표현 |
double | 11 bit | 52 bit | 소수점 약 15~16자리까지 높은 확률로 정확히 표현 |
위 처럼 유효자릿수가 나오는 이유는 부동소수점 표현 방식에 따른 결과로, 가수부를 표현할 수 있는 크기에 따라 결정되기 떄문이다.
첨고로 유효 자릿수 구하는 방법은 float형은 log10(2^23) = 7.224... (약7자리), double형은 log10(2^52) = 15.653...(약15자리) 가 된다.
이 유효자릿수는 곧 실수의 정밀도를 의미 하기도 한다.
자릿수까지 변수에 저장된 값을 보장하는 것을 정밀도라고 한다.
즉, 정밀도 약 6~7 자릿수라는 뜻은 6~7 자릿수 까지는 실제값과 동일하다라고 생각하면 된다.
참고로 자릿수가 범위인 이유는 저장된 값은 공식에 의해 실제값보다 더 근사할 수도 있고 덜 근사할 수도 있기 때문이다.
// 짧은 실수 저장
double var1 = 3.14;
float var2 = 3.14f;
System.out.println("var1 : " + var1); // var1 : 3.14
System.out.println("var2 : " + var2); // var2 : 3.14
// 긴 실수를 저장 (정밀도 테스트)
double var3 = 3.14159265358979;
float var4 = 3.14159265358979f;
System.out.println("var3 : " + var3); // var3 : 3.14159265358979 - 정확히 표현
System.out.println("var4 : " + var4); // var4 : 3.1415927 - 유효자리 제한때문에 잘리고 반올림 됨 (정밀도 ↓)
[ 실수형의 선택 기준 ]
만일 보다 정확한 실수의 정밀도가 필요하다면, 변수의 타입을 double로 하면 된다.
double타입은 float 타입보다 정밀도가 약 2배인, 10진수로 15자리의 유효자리를 가지기 때문이다.
하지만 연산속도의 향상이나 메모리를 절약하려면 float 타입을 선택하는 것도 나쁘지 않는 방법이다.
정리하자면, 연산속도 향상이나 메모리 절약하려면 float, 더 큰 값의 범위라던가 더 높은 정밀도를 필요로 한다면 double 을 선택하면 된다.
실수 오버플로우 / 언더플로우
실수형에서도 변수의 값이 표현범위의 최댓값을 벗어나면 오버플로우가 발생한다.
단, 정수형과 달리 실수형에서는 오버플로우가 발생하면 변수의 값은 무한대(infinity)가 된다. 그리고 언더플로우는 실수형으로 표현할 수 없는 아주 작은 값, 즉 양의 최솟값보다 작은 값이 되는 경우를 말하여 값은 0이 된다.
- 오버 플로우 : 무한대 (infinity)
- 언더 플로우 : 양의 최소값보다 작은 값으로 0이 된다.
지수(e) 표기법
지수 표기법은 아주 큰 숫자나 아주 작은 숫자를 간단하게 표기할때 사용되는 표기법으로, 과학적 표기법(scientific notation) 이라도고 불리운다.
길다란 실수 숫자를 나타내는 데 필요한 자릿수를 줄여 표현해준다는 장점이 있다.
double d2 = 1.234e2; // 1.234 x 10^2 = 123.4
double e1 = 1.7e+3; // 1700.0
double e2 = 1.7e-3; // 0.0017
논리형 자료형
참(true) 또는거짓(false)의 값을 갖는 자료형을 논리형(불리언) 자료형이라고 한다.
논리형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 |
boolean | 1바이트 | true, false |
boolean isSuccess = true;
boolean isTest = false;
불리언 자료형에는 다음과 같이 숫자 연산의 결과값이 대입 된다.
boolean bool = (2 > 1); // true
boolean bool = (1 == 2); // false
boolean bool = (3 % 2 == 1); // true (3을 2로 나눈 나머지는 1이므로 참이다.)
boolean bool = ("3".equals("2")); // false
그래서 이러한 참, 거짓을 판단하는불리언 연산을 이용해 if문과 같은 조건문에 자주 애용된다.
int base = 180;
int height = 185;
if (height > base) {
System.out.println("키가 큽니다.");
}
문자 자료형
한 개의 문자 값에 대한 자료형은 char 를 이용한다.
주의할 점은 아래 같이 문자값을 ' (단일 인용부호)로 감싸주어야 한다. 만일 " 쌍따옴표를 사용하면 에러가 난다.
문자형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 |
char | 2바이트 | 0 ~ 65,535 |
char a1 = 'a';
char 타입은 문자값을 표현하는 방식이 다양하다.
아래의 코드를 보면 숫자 97과 \u0061 이라는 값을 넣었더니 똑같이 a 라는 값을 출력하는 것을 볼 수 있다.
첫번째는 문자값, 두번째는 아스키코드값, 세번째는 유니코드값으로 표현한 것이다.
char a1 = 'a'; // 문자로 표현
char a2 = 97; // 아스키코드로 표현
char a3 = '\u0061'; // 유니코드로 표현
System.out.println(a1); // a 출력
System.out.println(a2); // a 출력
System.out.println(a3); // a 출력
예를들어 아스키 코드 개념을 알면 다음과 같이 응용할 수 있다.
char 문자 'C'는 아스키 코드 숫자로 103으로 표현 될 수 있다. 그리고 -2 한 숫자인 101은 문자 'A'로 표현될 수 있어서 다음과 같은 식이 성립 되는 것이다.
char a = 'C' - 2; // 103 - 2
System.out.println(a); // 'A'
[ 아스키코드 ]
1962년 안시(ANSI)가 정의한 미국 표준 정보교환 코드이며 1963년 미국표준협회(ASA)에 의해 결정되어 미국의 표준 부호가 되었다.
아스키 코드는 7비트의 이진수 조합으로 만들어져 총 128개의 부호를 표현할 수 있다.
아스키코드의 처음 32개(0~31)는 프린터나 전송 제어용으로 사용되고 나머지는 숫자와 로마 글자 및 도량형 기호와 문장 기호를 나타낸다.
[ 유니코드 ]
시간이 지나가면서 영어나 숫자 그리고 기본적인 기호뿐만 아니라 그 나라, 그 언어에서 쓰는 다양한 문자들을 처리하려면 1바이트로 정의된 아스키 코드를 확장할 필요가 있게 되었다.
그래서 각 나라별 언어를 모두 표현하기 위해 나온 코드 체계가 유니코드(unicode) 이다.
유니코드는 사용 중인 운영체제, 프로그램, 언어에 관계없이 문자마다 고유한 코드 값을 제공 하기 때문에, 언어와 상관없이 모든 문자를 16비트로 표현하므로 최대 65,536자까지 포현할 수 있다.
문자열 자료형
문자열이란 문장을 뜻한다.
문자는 하나의 글자로 작은 따옴표로 감싸고, 문자열은 글자의 집합으로서 쌍 따옴표로 감싼다는 차이가 있다.
자바스크립트나 파이썬 같은 언어는 문자와 문자열을 구분하지는 않지만, 자바는 강한 타입 언어이기 때문에 이 둘을 엄격히 구분한다.
"Happy Java"
"a"
"123"
String 클래스
C언어에서는 문자열을 char형 배열로 표현하지만, 자바에서는 문자열을 위한 String이라는 클래스를 별도로 제공한다.
하지만 String 클래스 구성 내부 요소를 보자면, 결국 String도 char[] 배열로 이루어진 데이터 자료라고 볼수 있다.
public final class String implements java.io.Serializable, Comparable {
/* The value is used for character storage */
priave char[] value; // String 데이터 생김새는 사실 char 배열로 이루어져 있다.
// ...
}
이러한 String 자료형을 이용해 문자열 변수를 표현하는 방법은 다음과 같이 두가지가 존재한다.
String a = "Happy Java"; // 리터럴 방식
String b = new String("Happy Java"); // 생성자 방식
보통 문자열을 표현할 때는 가급적 첫번째 방식(리터럴 표기)을 사용하는 것이 좋다.
첫 번째 처럼 사용하면 가독성에 이점이 있고 컴파일 시 최적화에 도움을 주기 때문이다.
String 자료형과 위에서 배운 int, double, char, boolean 자료형과의 가장 큰 차이점은, int, double, char는 원시(primitive) 자료형로 분류되고, String 자료형은 참조(reference) 자료형으로 분류 된다는 점이다.
그리고 원시 타입의 데이터는 stack 영역에 그대로 저장되지만, 참조 타입의 데이터(문자열)은 heap 영역에 저장되고 주소만 stack 영역에 저장된다.
원시 / 참조 타입과 메모리 저장 방식에 관해서는 다음 포스팅을 참고하길 바란다.
또한 String 인스턴스는 한 번 생성되면 그 값을 읽기만 할 수 있고, 변경할 수는 없다는 특징이 있다.
이러한 객체를 자바에서는 불변 객체(immutable object)라고 한다.
즉, 자바에서 덧셈(+) 연산자를 이용하여 문자열 결합을 수행하면, 기존 문자열의 내용이 변경되는 것이 아니라 내용이 합쳐진 새로운 String 인스턴스가 생성되게 된다.
String s = "ABC";
s += "DEF";
이 역시 자세한 내용은 다음 포스팅을 참고하길 바란다.
문자열 내장 메소드
String 클래스에는 문자열과 관련된 작업을 할 때 유용하게 사용할 수 있는 다양한 메소드가 포함되어 있다.
// 문자열 비교
String a = "hello";
String b = "java";
String c = "hello";
System.out.println(a.equals(b)); // false 출력
System.out.println(a.equals(c)); // true 출력
// 문자열 순서 비교 (오츰차순 또는 내림차순으로 어떤 문자열이 앞이나 뒤에 있는지 확인할때)
int value = "abc".compareTo("jzis"); // -9 (의미 abc가 더 앞이다)
int value = "jzis".compareTo("abc"); // 9 (의미 abc가 더 앞이다)
int value = "aa".compareTo("aaa"); // -1 (의미 aa가 더 앞이다)
String a = "Hello Java";
// 문자열에서 특정 문자열이 포함되어 있는지의 여부
a.contains("Java"); // true 출력
// 문자열 앞부분과 돌일 여부
a.startWith("Java"); // true 출력
// 문자열에서 특정 문자가 시작되는 위치(인덱스)를 리턴
// 만일 문자열이 포함되어 있지 않으면 -1을 반환
a.indexOf("Java"); // 6 출력
// 문자열에서 특정 위치의 문자(char)를 리턴
a.charAt(6); // "J" 출력
String a = "Hello Java";
// 문자열 중 특정 부분을 뽑아내고 싶을 때 (시작위치 <= a < 끝위치)
a.substring(0, 4); // Hell 출력
a.substring(4); // o Java 출력
// 문자열을 합칠 때
a.concat(" World"); // Hello Java World 출력
// 첫번째 매개 변수로 전달된 문자열을 모두 찾아, 두 번째 매개변수로 치환
a.replaceAll("Java", "World") // Java를 World 로 replace → Hello World 출력
String text = "카페라떼, 녹차라떼, 코드라떼";
text.replace("라떼", "AA"); // "카페AA, 녹차AA, 코드AA"
String a = "a:b:c:d";
// 문자열을 특정 구분자로 분리하여 배열로 만듬
String[] str = a.split(":"); // result는 {"a", "b", "c", "d"}
// 배열을 다시 하나의 문자열로 합침. 이때 배열 원소들을 . 구분자를 넣어 결합한다
str.join("."); // "a.b.c.d"
String a = "Hello Java";
// 문자열을 모두 대문자로 변경
a.toUpperCase(); // HELLO JAVA 출력
// 문자열을 모두 소문자로 변경
a.toLowerCase(); // hello java 출력
// 문자열의 맨 앞과 맨 뒤에 포함된 모든 공백 문자를 제거
String a2 = " Hello Java ";
a2.trim(); // Hello Java (앞뒤 공백 제거)
메서드 | 설명 |
String(String s) | 문자열 s에 대한 String 인스턴스 생성 |
String(char[] value) | 문자 배열 value를 갖는 String 인스턴스 생성 |
String(StringBuffer buf) | StringBuffer 인스턴스 buf와 같은 내용의 String 인스턴스 생성 |
char charAt(int index) | 지정된 위치 index(0부터시작하는 위치)의 문자를 리턴합니다. |
int compareTo(String anotherString) | 두 문자열을 사전식 순서로 비교하여 결과를 리턴합니다. 같다면 0, 호출한 문자열이 작으면 음수, 크면 양수 값을 리턴합니다. |
boolean endsWith(String suffix) | 문자열이 지정한 문자열로 끝나는지 검사하여 결과를 리턴합니다. |
boolean equals(Object obj) | 두 문자열의 내용이 같은지 검사하여 결과를 리턴합니다. |
boolean startsWith(String prefix) | 문자열이 지정한 문자열로 시작하는지 검사하여 결과를 리턴합니다. |
int indexOf(String str) | 문자열에서 지정한 문자열이 시작되는 첫 번째 위치를 리턴합니다. 없으면 -1을 리턴합니다. |
int length() | 문자열의 길이를 리턴합니다. |
String replace(char oldChar, char newChar) | 문자열에서 oldChar를 찾아 newChar로 변경한 문자열을 리턴합니다. |
String toLowerCase() | 문자열의 모든 문자를 소문자로 변환한 문자열을 리턴합니다. |
String toUpperCase() | 문자열의 모든 문자를 대문자로 변환한 문자열을 리턴합니다. |
String substring(int beginIndex) | 문자열에서 지정된 위치(beginIndex)부터 끝까지의 모든 문자를 포함하는 문자열을 리턴합니다. |
String substring(int beginIndex, int endIndex) | 문자열에서 지정된 범위(beginIndex부터 endIndex까지)의 문자들을 포함하는 문자열을 리턴합니다. endIndex는 포함되지 않습니다. |
String[] split(String regex) | 문자열을 주어진 정규표현식(regex)을 기준으로 분리하여 문자열 배열로 리턴합니다. |
String trim() | 문자열 양 끝의 공백을 제거한 문자열을 리턴합니다. |
byte[] getBytes() | 문자열을 바이트 배열로 변환하여 리턴합니다. 보통 인코딩 작업 시 사용됩니다. |
boolean contains(CharSequence s) | 문자열이 주어진 문자열(s)을 포함하는지 확인하여 결과를 리턴합니다. |
String[] split(String regex, int limit) | 문자열을 지정한 정규표현식(regex)을 기준으로 최대 limit 개수만큼 분할하여 문자열 배열로 리턴합니다. |
int lastIndexOf(String str) | 문자열에서 지정한 문자열이 시작되는 마지막 위치를 리턴합니다. 없으면 -1을 리턴합니다. |
String join(CharSequence delimiter, CharSequence... elements) | 여러 개의 문자열을 주어진 구분자(delimiter)로 이어 붙여 하나의 문자열로 리턴합니다. |
boolean isEmpty() | 문자열이 비어있는지(길이가 0인지) 확인하여 결과를 리턴합니다. |
int codePointAt(int index) | 지정한 위치(index)의 문자의 Unicode 코드 포인트를 리턴합니다. |
# 참고자료
https://wikidocs.net/276
https://wan-blog.tistory.com/29
https://velog.io/@inwookie/Studying-Java-Core-5
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=klp0712&logNo=220573144288
https://moz1e.tistory.com/208
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.