Posts :coffee: [Java] 이것이 자바다 #5: 참조 타입
Post
Cancel

:coffee: [Java] 이것이 자바다 #5: 참조 타입

참조 타입

데이터 타입 분류

  • 기본 타입
    • 정수 타입
    • 실수 타입
    • 논리 타입
  • 참조 타입
    • 배열 타입
    • 열거 타입
    • 클래스
    • 인터페이스
  • 기본 타입과 참조 타입의 차이점: 저장되는 값이 무엇인지
    • 기본 타입: 실제 값을 변수 안에 저장
    • 참조 타입: 배열, 열거, 클래스, 인터페이스를 이용해서 선언된 변수는 메모리의 번지를 값으로 갖는다.
  • 변수는 스택 영역에 생성되고 객체는 힙 영역에 생성된다. 스택 영역에 있는 참조 타입의 변수는 힙 영역의 실제 객체를 가리킨다.

메모리 사용 영역

  • JVM은 운영체제엇 할당 받은 메모리 영역(Runtime Data Area)을 메소드 영역, 힙 영역, 스택 영역으로 구분해서 사용한다.

Method Area

  • 코드에서 사용되는 클래스(~.class)들을 클래스 로더로 읽어 클래스 별로 런타임 상수풀(runtime constant pool), 필드(field) 데이터, 메소드(method) 데이터, 메소드 코드, 생성자(constructor)코드 등을 분류해서 저장한다.
  • 메소드 영역은 JVM이 시작될 때 생성되고 모든 스레드가 공유하는 영역이다.
  • 메소드 코드, 상수, 열거 상수는 정적(메소드) 영역에 생성된다.

Heap

  • 힙(Heap) 영역: 객체와 배열이 생성되는 영역이다. 힙 영역에 생성된 객체와 배열은 JVM 스택 영역의 변수나 다른 객체의 필드에서 참조한다.
  • Garbage: 참조하는 변수가 필드가 없다면 의미 없는 객체가 된다.
  • Garbage Collector: JVM은 GC를 동작시켜 가비지를 힙 영역에서 자동으로 제거한다. 자바에서는 가비지를 제거하기 위해 별도 코드를 작성할 필요도 없고, 할 수도 없다.

JVM Stack

  • 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 할당된다.
    • 추가적으로 스레드를 생성하지 않았다면 main 스레드만 존재하므로 JVM 스텍도 하나이다.
  • 메소드를 호출할 때마다 프레임을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)한다.
  • 예외 발생 시 printStackTrace()메소드로 보여주는 Stack Trace의 각 라인은 하나의 프레임을 표현한다.
  • 프레임 내부에는 로컬 변수 스택이 있다. 변수가 추가(push)되거나 제거(pop)된다. 변수는 초기화가 될 때 이 영역에 생성된다.

  • 변수는 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거된다.
1
2
3
4
5
6
7
8
9
char v1 = 'A'; //stack: v1

if (v1 == 'A'){
    int v2 = 100;
    double v3 = 3.14; //stack: v3 v2 v1
}
//stack: v1

boolean v4 = true; //stack: v4 v1
  • 참조 타입 변수는 값이 아니라 힙 영역이나 메소드 영역의 객체 주소를 가진다.

참조 변수의 ==, != 연산

  • 참조타입 변수 간의 ==, != 연사는 동일한 객체를 참조하는지, 다른 객체를 참조하는 지 알아볼 때 사용된다. 즉 동일한 주소값을 갖고 있는지, 다른 주소값을 갖고 있는지를 알아본다.

null과 NullPointerException

  • 참조 타입 변수는 힙 영역의 객체를 참조하지 않는다는 뜻으로 null 값을 가질 수 있다.
  • null로 초기화된 참조 변수는 스택 영역에 생성된다.
  • refVar == null, refVar != null로 확인 가능

  • NullPointer Exception
    • 참조변수를 사용하면서 가장 많이 발생하는 예외 중 하나
    • 참조 타입 변수가 null가지고 있을 경우, 사용할 수 없다.
    • 참조 타입 변수를 사용한다는 것은 곧 객체를 사용하는 것을 의미하는데, 참조할 객체가 없으므로 사용할 수가 없는 것이다.
1
2
3
4
5
int[] intArray = null;
intArray[0] = 10; //NullPointerException

String str = null;
System.out.println("총 문자 수: " + str.length()); // NullPointerException
  • 프로그램 실행 도중 NullPointerException이 발생하면, 예외가 발생된 곳에서 객체를 참조하지 않은 상태로 참조 타입 변수를 사용하고 있다는 뜻이다.

배열 타입

배열이란?

  • 같은 타입의 데이터를 연속된 공간에 나열시키고, 각 데이터에 인덱스(index)를 부여해 놓은 자료구조
  • 한 번 생성된 배열은 길이를 늘리거나 줄일 수 없다.

  • 선언 방법: 타입[] 변수. 타입 변수[]

값 목록으로 배열 생성

  • 배열 변수를 미리 선언한 후에 다른 실행문에서 중괄호를 사용해 배열을 생성할 수 없다.
  • 따라서 배열 변수를 미리 선언한 후 값 목록들이 나중에 결정되는 상황이라면 new 연산자를 사용해서 값 목록을 지정해준다.
1
2
3
4
타입[] 변수;
변수 = { 값0, 값1, 값2 }; //컴파일 에러

변수 = new 타입[] { 값0, 값1, 값2 }; // OK!
  • 메소드의 매개값이 배열일 경우에도 마찬가지로 new 연산자를 사용하여 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ArrayCreateByValueListExample2 {
  public static void main(String[] args) {
    int[] scores;
    int sum = add(new int[] {83, 90, 87});
    System.out.println("총합: " + sum);
  }

  public static int add(int[] scores) {
    int sum = 0;
    for (int score : scores)
      sum += score;
    return sum;
  }
}

new 연산자로 배열 생성

  • new 연산자로 배열을 처음 생성할 경우, 배열은 자동적으로 기본값으로 초기화된다.

  • 배열의 초기값

분류데이터 타입초기값
기본 타입 (정수)byte[]
char[]
short[]
int[]
long[]
0
‘\u0000’
0
0
0L
기본 타입 (실수)float[]
double[]
0.0F
0.0
기본 타입 (논리)boolean[]false
참조 타입클래스[]
인터페이스[]
null
null

배열 길이

  • 코드에서 배열의 길이를 얻으려면 배열 객체의 length 필드를 읽으면 된다.
  • length 필드는 읽기 전용 필드이므로 값을 바꿀 수 없다.

  • length필드는 for 문을 사용해서 배열 전체를 루핑할 때 유용하다.

커맨드 라인 입력

  • “java 클래스”로 프로그램을 실행하면 JVM은 길이가 0인 String 배열을 먼저 생성하고 main() 메서드를 호출할 때 매개값으로 전달한다.
1
java 클래스 문자열0 문자열1 문자열2 문자열3
1
2
3
String[] args = { 문자열0, 문자열1, 문자열2, 문자열3 }; 

public static void main(String[] args) {} //main() 메소드 호출 시 매개값으로 전달
  • main() 메소드는 String[] args 매개 변수를 통해서 커맨드 라인에서 입력된 데이터의 수(배열의 길이)와 입력된 데이터(배열의 항목 값)을 알게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainStringArrayArgument {
  public static void main(String[] args) {
    if (args.length != 2) {
      System.out.println("프로그램의 사용법");
      System.out.println("java MainStringArrayArgument num1 num2");
      System.exit(0);
    }

    String strNum1 = args[0];
    String strNum2 = args[1];

    int num1 = Integer.parseInt(strNum1);
    int num2 = Integer.parseInt(strNum2);

    int result = num1 + num2;
    System.out.println(num1 + "+" + num2 + "=" + result);
  }
}
1
2
int 변수 = Integer.parseInt("정수로 변환 가능한 문자열");
// 정수로 변환할 수 없는 문자열이 주어졌을 경우 NumberFormatException

다차원 배열

  • 값들이 행과 열로 구성된 배열을 2차원 배열이라고 한다.
1
2
3
4
5
int[][] scores = new int[2][3];

scores.length; //2
scores[0].length; //3
scores[1].length; //3
  • 이 코드는 세 개의 배열 객체를 생성한다
    • 길이 2인 배열객체: 다른 2개의 객체의 주소 저장
    • 길이 3인 배열객체 2개
  • 자바는 일차원 배열이 서로 연결된 구조로 다차원 배열을 구현한다.
  • 행렬 구조가 아닌 계단식 구조를 가질 수 있다.
1
2
3
4
5
6
7
int[][] scores = new int[2][];
scores[0] = new int[2];
scroes[1] = new int[3];

scores.length; //2
scores[0].length; //2
scores[1].length; //3
  • 그룹화된 값 목록을 가지고 있다면 중괄호 안에 중괄호를 사용해서 값 목록을 나열한다.
1
타입[][] 변수 = { {값1, 값2}, {값1, 값2} };
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
30
public class ArraysInArrayExample {
  public static void main(String[] args) {
    int[][] mathScores = new int[2][3];
    for (int i = 0; i < mathScores.length; i++) {
      for (int k = 0; k < mathScores[i].length; k++) {
        System.out.println("mathScores[" + i + "]" + "[" + k + "]" + " = " + mathScores[i][k]);
      }
    }
    System.out.println();

    int[][] englishScores = new int[2][];
    englishScores[0] = new int[2];
    englishScores[1] = new int[3];

    for (int i = 0; i < englishScores.length; i++) {
      for (int k = 0; k < englishScores[i].length; k++) {
        System.out.println("englishScores[" + i + "]" + "[" + k + "] = " + englishScores[i][k]);
      }
    }
    System.out.println();

    int[][] javaScores = { {95, 80}, {92, 96, 80} };
    for (int i = 0; i < javaScores.length; i++) {
      for (int k = 0; k < javaScores[i].length; k++) {
        System.out.println("javaScores[" + i + "]" + "[" + k + "] = " + javaScores[i][k]);
      }
    }
    System.out.println();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//결과
mathScores[0][0] = 0
mathScores[0][1] = 0
mathScores[0][2] = 0
mathScores[1][0] = 0
mathScores[1][1] = 0
mathScores[1][2] = 0

englishScores[0][0] = 0
englishScores[0][1] = 0
englishScores[1][0] = 0
englishScores[1][1] = 0
englishScores[1][2] = 0

javaScores[0][0] = 95
javaScores[0][1] = 80
javaScores[1][0] = 92
javaScores[1][1] = 96

객체를 참조하는 배열

배열 복사

for문 사용

1
2
3
4
5
6
7
8
9
public class ArrayCopyByForExample {
  public static void main(String[] args) {
    int[] oldIntArray = {1, 2, 3};
    int[] newIntArray = new int[5];

    for (int i = 0; i < oldIntArray.length; i++)
      newIntArray[i] = oldIntArray[i];
  }
}

System.arraycopy() 사용

1
System.arraycopy(Object src, int srcPros, Object dest, int destPos, int length);
  • src:원본배열, srcPos: 원본배열에서 복사할 항목의 시작 인덱스, dest: 새 배열, destPos: 새 배열에서 붙여넣을 시작 인덱스, length: 복사할 개수
  • arr1의 모든 항목을 arr2에 복사할 때
1
System.arraycopy(arr1, 0, arr2, 0, arr1.length)
  • 참조 타입 배열의 경우, 배열 복사가 되면 복사되는 값이 객체의 번지이므로 새 배열의 항목은 이전 배열의 항목이 참조하는 객체와 동일하다. 이것을 shallow copy라고 한다.
  • 반대로 deep copy는 참조하는 객체도 별도로 생성하는 것을 말한다.

향상된 for문

1
for ( (2)타입변수 : (1)배열 ) { (3)실행문 }
  1. (1)배열에서 가져올 첫 번째 값이 존재하는 지 평가한다.
  2. 가져올 값이 존재하면 해당 값을 (2)변수에 저장한다.
  3. (3)실행문을 실행한다.
  4. 블록 내부의 실행문이 모두 실행되면 다시 루프를 돌아 배열에서 가져올 다음 값이 존재하는지 평가한다.
  5. 값이 존재하면 2 -> 3 -> 1로 다시 진행하고, 가져올 다음 항목이 없으면 for문은 종료된다.

해당 값을 변수에 저장하는 것이다. 따라서 기본 타입의 배열이라면 변수에 값을 할당해서 배열의 값을 바꿀 수 없다.

열거 타입

  • 데이터 중에는 몇 가지로 한정된 값만 갖는 경우가 있다. (요일/계절 데이터)
  • 한정된 값만을 갖는 데이터 타입이 열거 타입(enumeration type)이다.
  • 열거 타입은 몇 개의 열거 상수(enumeration constant) 중에서 하나의 상수를 저장하는 데이터타입이다.

열거 타입 선언

  • 열거 타입 이름은 파스칼 케이스(Week, MemberGrade..)로 작성한다.
1
public enum 열거타입이름 { ... }
  • 열거 상수는 열거 타입의 값으로 사용되는데, 모두 대문자로 작성한다.
  • 열거 상수가 여러 단어로 구성될 경우 단어 사이를 밑줄(_)로 연결한다.
1
2
public enum Week { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY ... }
public enum LoginResult { LOGIN_SUCESS, LOGIN_FAILED }

열거 타입 변수

  • 열거 상수는 단독으로 사용할 수 없고 반드시 열거타입.열거상수로 사용된다.
  • 참조 타입이기 때문에 열거 타입 변수는 null을 저장할 수 있다.
1
2
열거타입 변수; //선언
열거타입 변수 = 열거타입.열거상수; //저장
1
2
3
Week reservationDay;
Week today = Week.SUNDAY;
Week birthday = null;
  • 참조 타입 변수는 객체를 참조하는 변수라고 알고 있는데, 그렇다면 열거 상수도 객체일까? 그렇다. 열거 상수는 열거 객체로 생성된다. 열거 타입 Week의 경우 MONDAY부터 SUNDAY까지의 열거 상수는 총 7개의 Week 객체로 생성된다. 그리고 메소드 영역에 생성된 열거 상수가 해당 Week 객체를 각각 참조하게 된다.
1
2
Week today = Week.SUNDAY;
today == Week.SUNDAY; // true. 같은 객체를 참조한다.
  • 위의 코드의 경우 열거 타입 변수 today는 스택 영역에 생성된다. today에 저장되는 값은 Week.SUNDAY 열거 상수가 참조하는 객체의 번지이다. 따라서 열거 상수 Week.SUNDAY와 today 변수는 서로 값은 Week 객체를 참조하게 된다.
1
2
3
Week week1 = Week.SATURDAY;
Week week2 = Week.SATURDAY;
week1 == week2; // true. 같은 객체를 참조한다.
  • 자바는 컴퓨터의 날짜, 요일, 시간을 프로그램에서 사용할 수 있도록 하기 위해 Date, Calendar, LocalDateTime 등의 클래스를 제공한다.
1
2
3
4
5
6
7
8
9
Calendar now = Calendar.getInstance(); // 

int year = now.get(Calendar.YEAR); //년
int month = now.get(Calendar.MONTH); //월(1~12)
int week = now.get(Calendar.DAY_OF_MONTH); //일
int hour = now.get(Calendar.DAY_OF_WEEK); //요일(1~7)
int minute = now.get(Calendar.DAY_OF_HOUR); //시간
int minute = now.get(Calendar.DAY_OF_MINUTE); //분
int minute = now.get(Calendar.DAY_OF_SECOND); //초
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
30
31
32
33
34
35
36
37
38
39
public class enumWeekExample {
  public static void main(String[] args) {
    Week today = null;

    Calendar cal = Calendar.getInstance();
    int week = cal.get(Calendar.DAY_OF_WEEK);

    switch (week) {
      case 1:
        today = Week.SUNDAY;
        break;
      case 2:
        today = Week.MONDAY;
        break;
      case 3:
        today = Week.TUESDAY;
        break;
      case 4:
        today = Week.WEDNESDAY;
        break;
      case 5:
        today = Week.THURSDAY;
        break;
      case 6:
        today = Week.FRIDAY;
        break;
      case 7:
        today = Week.SATURDAY;
        break;
    }
    System.out.println("오늘 요일: " + today);

    if (today == Week.SUNDAY) {
      System.out.println("일요일에는 축구를 합니다.");
    } else {
      System.out.println("열심히 자바 공부를 합니다.");
    }
  }
}

열거 객체의 메소드

  • 열거 객체는 열거 상수의 문자열을 내부 데이터로 가지고 있다.
  • 모든 열거 타입은 컴파일 시 java.lang.Enum 클래스를 상속하게 되어 있기 때문에 열거 객체에서 java.lang.Enum 클래스의 메소드를 사용할 수 있다.

name()

  • 열거 객체가 가지고 있는 문자열을 리턴한다. 리턴되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일하다.
1
2
Week today = Week.SUNDAY;
String name = today.name();

ordinal()

  • 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다.

  • 열거 객체의 순번은 열거 타입을 정의할 때 주어진 순번을 말하는데, 0부터 시작한다.

1
2
Week today = Week.SUNDAY;
int ordinal = today.ordinal(); //6

compareTo()

  • 매개값으로 주어진 열거 객체를 기준으로 전후로 몇 번째 위치하는 지 비교
  • 열거 객체가 매개값의 열거 객체보다 순번이 빠르다면 음수, 늦다면 양수 리턴
  • 즉 상대적 위치를 리턴한다.
1
2
3
4
Week day1 = Week.MONDAY;
Week day2 = Week.WEDNESDAY;
int result1 = day1.comparedTo(day2); //-2
int result2 = day2.comparedTo(day1); //2

valueOf()

  • 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다.
  • 외부로부터 문자열을 입력받아 열거 객체로 변환할 때 유용하게 사용할 수 있다.
1
2
Week weekDay = Week.valueOf("SATURDAY"); 
//weekDay 변수는 Week.SATURDAY 열거 객체를 참조하게 된다.

values()

  • 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다.
1
2
3
4
Week[] days = Week.values();
for(Week day : days) {
    System.out.println(day);
}

확인 문제

  • 7번: 배열의 항목에서 최대값 구하기
1
2
3
4
5
6
7
8
9
10
11
public class Exercise07 {
  public static void main(String[] args) {
    int max = 0;
    int[] array = {1, 5, 3, 8, 2};
    for (int i = 0; i < array.length; i++) {
      if (max < array[i])
        max = array[i];
    }
    System.out.println("max: " + max);
  }
}
  • 8번: 다차원 배열 전체 항목의 합과 평균값 구하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Exercise08 {
  public static void main(String[] args) {
    int[][] array = { {95, 86}, {83, 92, 96}, {78, 83, 93, 87, 88} };

    int sum = 0;
    double avg = 0.0;
    int count = 0;

    for (int i = 0; i < array.length; i++) {
      for (int k = 0; k < array[i].length; k++) {
        sum += array[i][k];
        count++;
      }
    }
    avg = sum / (double) count;

    System.out.println("sum: " + sum);
    System.out.println("avg: " + avg);
  }
}
  • 9번 키보드로 학생 수와 각 학생드르이 점수를 입력받아서, 최고 점수 및 평균 점수를 구하는 프로그램
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Exercise09 {
  public static void main(String[] args) {
    boolean run = true;
    int studentNum = 0;
    int[] scores = null;
    Scanner scanner = new Scanner(System.in);

    while (run) {
      System.out.println("-------------------");
      System.out.println("1.학생수 | 2.점수입력 | 3.점수리스트 | 4.분석 | 5.종료");
      System.out.println("-------------------");
      System.out.print("선택> ");

      int selectNo = scanner.nextInt();

      if (selectNo == 1) {
        System.out.print("학생수> ");
        studentNum = scanner.nextInt();
        scores = new int[studentNum];
      } else if (selectNo == 2) {
        for (int i = 0; i < scores.length; i++) {
          System.out.print("scores[" + i + "]> ");
          scores[i] = scanner.nextInt();
        }
      } else if (selectNo == 3) {
        for (int i = 0; i < scores.length; i++)
          System.out.println("scores[" + i + "]> " + scores[i]);
      } else if (selectNo == 4) {
        int max = 0;
        int sum = 0;
        for (int i = 0; i < scores.length; i++) {
          if (max < scores[i])
            max = scores[i];
          sum += scores[i];
        }
        System.out.println("최고 점수: " + max);
        System.out.println("평균 점수: " + sum / (double) scores.length);

      } else if (selectNo == 5) {
        run = false;
      }
    }
    System.out.println("프로그램 종료");
    scanner.close();
  }
}
This post is licensed under CC BY 4.0 by the author.

:book: 리팩토링 #2 (2): 리팩토링 개론

걸스인텍: 협업에 반드시 필요한 Git? Github? (1)

Loading comments from Disqus ...