자바에는 크게 2가지의 데이터 타입이 있다.
사실 모든 프로그래밍언어가 그렇지만 누구나 하나쯤 가지고 다니는 변수, 즉 데이터를 담는 그릇인데.
기본으로 제공하는 8가지의 기본 타입이 있고 그 외의 다른 데이터를 담는 그릇은 모두 참조 타입으로 분류된다.
byte, char, short, int, long
은 정수 타입
float,double
은 실수 타입
boolean
은 논리 타입 이렇게 8개가 기본 타입이고
그 외에 String, Array, Scanner
등등은 모두 참조 타입이다.
참조 타입과 기본 타입의 가장 큰 차이점은
참조 타입은 모두 객체의 메모리 주소(번지)를 참조한다는 것이다.
그래서 기본 타입으로 선언한 변수에는 값 자체가 담겨 있지만 참조 타입으로 선언한 변수에는 해당 객체를 땡겨올 수 있는 주소가 담겨있다.
//기본 타입
int age = 25;
double price = 10.3;
//참조 타입
String name = "신용재"
String hobby = "노래"
이렇게 변수를 만들었다면 실제 메모리상에서는 이렇게 작동한다.
<Stack> 영역
변수명 | 저장하는 값 |
---|---|
name | 0x00401000 |
hobby | 0x00402000 |
price | 10.3 |
age | 25 |
<Heap> 영역
주소 | 저장하는 값 |
---|---|
0x00401000 | "신용재" |
0x00402000 | "노래" |
변수들은 기본적으로 Stack
이라는 메모리 영역에 생성되는데, 기본 타입인 price와 age는 변수 자체에 값이 담긴다. 하지만 객체는 생성 시 Heap
영역에 존재하게 되고 이를 변수에 담게 되면 저렇게 힙 메모리 영역에서 해당 String의 주소를 저장해 놓는다.
여기서 Heap 영역의 주소값은 프로그램을 실행할 때 마다 바뀐다.
그렇다면 이제 앞서 말한 Stack, Heap 메모리 영역들에 대해 자세히 알아보자.
JVM이 java 프로그램을 컴파일 하게 되면 운영체제에서 할당받은 메모리 영역(Runtime Data Area)을 사용하게 된다.
메모리 영역에는 크게 3가지의 공간으로 분류된다.
Method 영역
, Heap 영역
, Stack 영역
Method 영역은 JVM이 바이트코드 파일을 읽은 내용이 저장된다. 상수, 정적 필드, 메소드 코드, 생성자 코드 등이 저장된다. 쉽게 말해 일단 실행되면 바뀌지 않는 것들을 담는다고 생각하면 된다. 메소드 코드나 생성자 코드가 실행 도중 코드 구조가 바뀔 수 없듯 상수 처럼 고정된 뭔가를 저장한다고 생각하면 쉽다.
Heap 영역은 객체가 생성되는 영역이다. 여기서 생성된 객체의 주소값을 Method 영역이나 Stack 영역에서 참조할 수 있다.
Stack 영역은 메소드를 호출할 때 마다 생성되는 프레임이 저장되는 영역이다. 메소드 호출이 끝나면 프레임은 자동으로 사라지는데, 이는 스택 구조를 따른다. 여기서 기본 타입 변수와 참조 타입 변수들이 생성되고 사라진다.
for(int i = 0; i<10; i++)
{
int number += i * 10
}
System.out.println(number)
이렇게 하면 에러가 나는 이유도 사실 Stack 영역의 프레임에 의해 int number가 for문이 끝나면 자동으로 사라지기 때문이다.
==과 != 연산자는 변수의 담겨 있는 값이 같은지 아닌지를 조사한다.
그리고 비교할 때는 반드시 동일한 타입끼리만 비교할 수 있다.
변수명 | 저장하는 값 |
---|---|
name | 0x00401000 |
hobby | 0x00402000 |
price | 10.3 |
age | 25 |
그렇다면 여기서 String끼리 비교하면 아무리 들어있는 값이 같더라도 실제 변수에는 주소값만 들어있기 때문에 ==
을 쓰면 무조건 false가 나온다. 한 주소값에는 하나의 데이터만 들어가기 때문이다.
int[] arr1;
int[] arr2;
int[] arr3;
arr1 = new int[] {1,2,3}; // {1,2,3}을 arr1에 대입
arr2 = new int[] {1,2,3}; // {1,2,3}을 arr2에 대입
arr3 = arr2; // arr3에 arr2의 주소값을 대입
System.out.println(arr1 == arr2); //false
System.out.println(arr2 == arr3); //true
이렇게 하면 arr3 와 arr2는 같은 주소값을 담게 되고 실제로 존재하는 배열 객체는 2개지만 arr3가 arr2와 같은 주소값을 쓰게 되는 것이다.
참조 타입 변수들은 아직 주소값을 정하지 못했다는 뜻으로 null
이라는 값을 담을 수 있다.
null도 초기값으로 사용할 수 있기 때문에 null로 초기화된 참조 변수를 만들면 Stack 영역에 생성되긴한다.
<Stack>
변수명 | 저장하는 값 |
---|---|
refVar1 | 0x00401000 |
refVar2 | null |
<Heap>
주소 | 저장하는 객체 |
---|---|
0x00401000 | "Hello" |
그래서 참조 타입 변수가 null인지 아닌지 확인 할 때는 ==이나 !=으로 확인 할 수 있다.
그리고 이 참조 타입 변수에 null이 들어 있는 상태인데 해당 변수를 참조하면 NullPointerException 예외가 발생한다.
int[] arr = null;
arr[0] = 1; //NullPointerException
코딩하다 보면 자주 일어나는 예외이므로 잘 알아둘 필요가 있다.
하지만 때에 따라 참조 타입 변수에 일부러 null을 넣을 때도 있다.
변수를 더 이상 사용하지 않을 때 null을 대입하면 변수가 담고 있던 객체의 주소값을 잃게 되므로 더 이상 쓸 수 없게 된다.
그렇다면 참조를 받던 주소값에 있는 객체는 JVM이 자동으로 Garbage Collector를 실행해 제거한다. 메모리 관리할 때 유용하다.
String hobby = "여행";
hobby = null;
String kind1 = "자동차";
String kind2 = kind1; // kind1 변수에 저장되어 있는 주소값을 kind2에 대입
kind1 = null; // kind1의 주소값을 null로 바꿔서 객체를 지움
System.out.println("kind2: "+kind2); // 그러나 그 전에 주소값이 대입되어 있기 때문에 "자동차" 출력
이런 식으로 쓸 수 있다.
자바의 String은 객체로 생성된다.
String을 ==으로 비교하면 안되는 이유는 앞서 한 설명으로도 충분히 이해할 수 있다.
String은 객체 이므로 변수에 주소값을 담고 있고, 그래서 ==로 비교할 시 다르게 나온다.
하지만 만약 동일한 리터널이라면 같은 주소값을 가르키기 때문에 true가 나오기는 한다.
String a = "Hello";
String b = "Hello";
System.out.println(a==b); //true
이렇게 선언할 때 리터널이 같다면 같은 변수로 취급한다.
하지만 이렇게 코딩할 일은 거의 없다.
저렇게 리터널로 하드코딩 해서 문자열을 다루지는 않기 때문이다.
여기서는 String에서 제공하는 다양한 기능들을 알아보겠다.
객채끼리 동일 비교는 equals()
로 비교하면 된다.
String a = "Hello";
String b = new String("Hello");
System.out.println(a.equals(b)); // true
이렇게 쓰면 된다.
문자열에서 특정 인덱스의 한 글자만 알고 싶다면 charAt()
을 이용하면 된다.
String subject = "자바 프로그래밍!"
char charValue = subject.charAt(3); // '프'
자바에서 인덱스는 항상 0부터 시작하기 때문에 3은 0,1,2,3 해서 4번째 글자를 가져온다.
문자열의 총 길이를 알고 싶다면 leagth()
메소드를 쓰면 된다.
String subject = "자바 프로그래밍!";
int length = subject.length(); // 9
문자열에서 특정 문자열을 다른 문자열로 대체하고 싶다면 replace()
메소드를 쓰면 된다.
기존의 문자열은 그대로 두고 새로운 문자열을 생성한다.
String oldStr = "자바 프로그래밍";
String newStr = oldStr.place("자바", "Java"); //"Java 프로그래밍"
특정 범위까지의 문자열을 잘라내는 방법은 substring()
이다.
메소드 | 설명 |
---|---|
substring(int beginIndex) | beginIndex에서 끝까지 잘라내기 |
substring(int beginIndex, int endIndex) | beginIndex에서 endIndex 앞까지 잘라내기 |
String ssn = "880815-123456";
String firstNum = ssn.substring(0,6); // 0~6까지 이므로 "880815"
String secondNum = ssn.substring(7); // 7 ~ 끝까지 이므로 "1234567"
문자열에서 특정 문자열의 위치를 찾고자 할 때는 indexOf()
를 쓴다.
String subject = "자바 프로그래밍";
int index = subject.indexOf("프로그래밍"); // "프로그래밍"이 시작되는 인덱스를 리턴한다. (3)
못찾으면 -1
을 리턴한다.
특정 문자열을 기준으로 나누고 싶을 땐 split()
을 쓴다.
String board = "A,B,C,D";
String[] arr = board.split(",");
변수는 오직 하나의 값만 저장할 수 있는데, 필요한 만큼 계속 변수를 선언하다가는 끝이 없기 때문에 연속된 공간에 값을 나열시키고 인덱스를 기준으로 꺼내 쓰는 배열을 쓸 수 있다.
자바에서 배열의 특징은 두 가지가 있다.
- 같은 타입의 값만 담을 수 있다.
- 한번 정해진 배열의 길이는 늘이거나 줄일 수 없다.
배열을 선언하는 방법은 여러가지가 있다.
String[] arr1 = {"A","B","C","D"} //크기가 고정된 배열
String[] arr2 = new String[5]; // 크기가 5인 비어있는 배열
일반적으로 두번째 방법을 가장 많이 쓴다.
첫 번째 방법은 선언과 동시에 중괄호를 써줘야 한다. 그렇지 않으면 에러가 난다.
String arr1;
arr1 = {"A","B","C"}; //<- 컴파일 에러
그래서 배열의 값이 나중에 바뀐다면 두 번째 방법인 new를 이용해 객체를 생성해 줘야 한다.
두 번째 방법은 크기가 5인 빈 배열을 만드는데, 데이터 타입에 따라 초기값으로 차게 된다.
종류 | 초기값 |
---|---|
byte[] | 0 |
char[] | '/u0000' |
short[] | 0 |
int[] | 0 |
long[] | 0L |
float[] | 0.0F |
double[] | 0.0 |
boolean[] | false |
클래스[] | null |
인터페이스[] | null |
그리고 배열의 길이는 배열변수.length;
로 알 수 있다.
주로 for문에서 배열의 길이만큼 순회할 때 사용한다.
배열 안에 담을 수 있는 값에는 배열도 있다.
int[][] scores = {
{80,90,96}, //index 0번인 학생의 점수 3개
{76,88,100} //index 1번인 학생의 점수 3개
}
주로 사용되는 용도는 이렇다.
학생이 10명 정도 있는데, 학생마다 과목별로 점수가 3개 정도 있다.
그럼 학생 10명을 담는 배열을 만들고 그 배열 안에 점수 3개를 담는 배열을 만들면 다차원 배열이 완성된다.
다차원 배열에서 length
는 어떤 인덱스인지에 따라 다르게 나온다.
scores.length; // {80,90,96} 을 하나의 값으로 인식한다. 그래서 2개
scores[0].length; // scores[0]은 {80,90,96} 이므로 3개를 반환한다.
근데 이것도 경험상 new 키워드를 쓰는 방식으로 주로 쓰게 된다.
int[][] arr = new int[2][3];
new 키워드로 만들었을 때 초기값은 앞서 설명한 배열과 동일하다.
만약 2차원 배열의 길이가 서로 다를 경우
int[][] englishScores = new int[2][];
englishScores[0] = new int[2];
englishScores[1] = new int[3];
이런 식으로 만들어 주면 된다.
기본 타입과 다르게 객체를 참조하면 해당 인덱스에 값이 바로 들어가는 것이 아닌 해당 객체들의 메모리 주소 값을 저장한다. 그래서 마찬가지로 == 으로 배열의 요소를 검사하면 원하는 결과가 나오지 않을 수도 있다.
String[] strArray = new String[3];
strArray[0] = "Java";
strArray[1] = "Java";
strArray[2] = new String("Java");
System.out.println(strArray[0] == strArray[1]); //리터널이 같으므로 true
System.out.println(strArray[0] == strArray[2]); // false
System.out.println(strArray[0].equals(strArray[2])); //true
배열은 한 번 생성 시 크기를 바꿀 수 없으므로 더 많은 저장공간이 필요하다면 더 큰 길이의 새로운 배열을 만들고 거기에 기존의 배열을 복사하는 방법밖에 없다.
for문으로 하나씩 복사하는 방법도 있고 System의 arraycopy()
를 써도 된다.
for문 사용
int[] oldIntArray = {1,2,3};
int[] newIntArray = new int[5];
for(int i = 0; i<newIntArray.length; i++){
newIntArray[i] = oldIntArray[i];
}
arraycopy()
사용
String[] oldStrArray = {"java","array","copy"};
String[] newStrArray = new String[5];
System.arraycopy(oldStrArray,0,newStrArray,0,oldStrArray.length);
for(int i = 0; i<newStrArray.length; i++){
System.out.print(newStrArray[i]+" ");
}
for ( 타입 변수 : 배열){
실행문
}
타입변수가 배열의 처음부터 끝까지 돌면서 해당 값을 가진다.
int[] scores = {95,71,84,93,87};
int sum = 0;
for(int score : scores){
sum += score;
}
System.out.println("점수 총합 : "+sum);
System.out.println("점수 평균 : "+(double)sum/scores.length);
이렇게 변수 score가 scores 길이만큼 돌면서 하나씩 대입된다.
가장 기본인 main() 메소드에서 안에 붙는 String[] args의 용도를 알아보자.
자바 프로그램을 실행할 때 시작부터 요구하는 값이 있을 수가 있다.
$ java Sum 10 20
이런 식으로 프로그램을 시작하기 전에 인자를 전달하기 위해 존재하는 것이 String[] args다.
그럼 처음 main() 메소드에 String[] 배열인 {"10","20"}이 인자로 들어오게 된다.
String x = args[0];
String y = args[1];
이렇게 가져올 수도 있고
if(args.length != 2){
System.out.println("실행 시 2개의 값이 필요합니다.");
}
이렇게 인자를 넣지 않고 실행 했을 때 막을 수도 있다.
데이터를 한정된 값으로 다루고 싶을 때가 있다. 특정 객체에 대한 고유한 색깔, 요일, 계절 등 상수를 편리하게 다룰 수 있게 해주는 것이 Enum이다.
public enum Week
{
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
이렇게 enum
을 생성할 수 있다.
그리고 보통 이런 상수들은 대문자로쓰고 단어와 단어가 이어질 경우는 언더바(_
)를 이용해 표현 하는 것이 국룰이다.
LOGIN_SUCCESS,
LOGIN_FAILED,
PI,
...
enum도 하나의 데이터 타입이기 때문에 사용하려면 변수처럼 선언해 줘야 한다.
Week today;
Week tommorrow;
그리고 지정한 상수를 대입해 값을 정할 수 있다.
Week today = Week.WEDNESDAY;
4번 null 값으로 초기화 할 수 있습니다. 기본 타입이 안되지요.
3번 Garbage Collector에 의해 자동으로 없어집니다.
2번 equals로 해야 합니다.
2번 중괄호를 통해 생성할 때는 선언과 동시에 해줘야 합니다.
3번 false가 기본 세팅입니다.
3
5
배열 자체에 length를 찍으면 저 3개의 뭉탱이가 각 요소로 치고 둘째줄 처럼 특정 인덱스를 찍어주면 글로 들어가서 갯수를 카운팅 합니다.
int ans = 0;
for(int i : array) ans = Math.max(ans,i);
Sytem.out.println(ans);
int sum = 0;
int d = 0;
for(int i = 0; i < array.length; i++){
d += array[i].length;
for(int j = 0; j < array[i].length; j++){
sum += array[i][j];
}
}
System.out.println("평균 : "+(double)sum/d);
Scanner sc = new Scanner(System.in);
int n = 0;
int[] scores = null;
while (true){
System.out.println();
System.out.println("-------------------------------------------------------");
System.out.println("1. 학생 수 | 2. 점수입력 | 3. 점수리스트 | 4. 분석 | 5. 종료");
System.out.println("-------------------------------------------------------");
System.out.print("선택> ");
String userSelect = sc.nextLine();
if(userSelect.equals("1")){
System.out.print("학생수>");
n = sc.nextInt();
scores = new int[n];
}else if(userSelect.equals("2")){
for(int i = 0; i<n; i++){
System.out.print("scores["+i+"]> ");
scores[i] = sc.nextInt();
}
}else if(userSelect.equals("3")){
for(int i = 0; i<n; i++){
System.out.println("scores["+i+"] : "+scores[i]);
}
}else if(userSelect.equals("4")){
System.out.println("분석");
int sum = 0;
int max = 0;
for(int i = 0; i<n; i++){
sum +=scores[i];
max = Math.max(scores[i],max);
}
System.out.println("최고 점수: "+max);
System.out.println("평균 점수: "+(double)sum/n);
}else if(userSelect.equals("5")){
System.out.println("프로그램 종료");
break;
}
}