Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[Java] 예제 소스 정리 - 클래스(인스턴스, public, default, 중첩 클래스) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[Java] 예제 소스 정리 - 클래스(인스턴스, public, default, 중첩 클래스)

끈기JK 2023. 1. 3. 09:56

com.eomcs.oop.ex01

 

예제 소스 정리

 

OOP

 

 

# 클래스 사용 : 1) 일반 변수 사용

낱개의 변수를 사용하여 한 사람의 성적 정보를 저장하라!
=> 식탁에 밥, 국, 반찬1, 반찬2, 반찬3 을 각각 따로 가져오는 상황.

학생의 성적 정보를 다루고 싶다.
학생의 성적 정보는 이름, 국어, 영어, 수학, 합계, 평균 값들로 되어 있다.
자바는 우리가 원하는 형식의 값(성적 데이터)을 저장할 수 있는 메모리 유형을 제공하지 않는다.
단 데이터의 최소 형식인 byte, short, int, long, float, double, boolean, char, String 등의 값을 담을 수 있는 메모리만 제공한다.
해결책?
- 낱개의 데이터를 저장할 변수를 여러 개 선언하는 수 밖에 없다!
- 그래서 성적 데이터를 저장할 변수를 다음과 같이 낱개로 선언한다.

public class Exam0110 {
  public static void main(String[] args) {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;

    name = "홍길동";
    kor = 100;
    eng = 90;
    math = 85;
    sum = kor + eng + math;
    aver = (float) sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", name, kor, eng, math, sum, aver);

    name = "임꺽정";
    kor = 90;
    eng = 80;
    math = 75;
    sum = kor + eng + math;
    aver = (float) sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", name, kor, eng, math, sum, aver);

    name = "유관순";
    kor = 80;
    eng = 70;
    math = 65;
    sum = kor + eng + math;
    aver = (float) sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", name, kor, eng, math, sum, aver);
  }
}

 

 

# 클래스 사용 : 2) 메서드 활용

합계와 평균 계산을 메서드로 분리한다.

public class Exam0111 {
  public static void main(String[] args) {

    String name;
    int kor;
    int eng;
    int math;

    name = "홍길동";
    kor = 100;
    eng = 90;
    math = 85;

    // 메서드 추출
    // - 학생 정보를 출력하는 명령어를 별도의 블록으로 뺐다.
    // - 성적을 출력하는 명령어를 별도의 블록으로 빼는 이유는 유지보수를 쉽게 하기 위함이다.
    // - 출력 형식을 바꾸고 싶으면 그 블록으로 가서 변경하면 된다.
    //
    printScore(name, kor, eng, math);

    name = "임꺽정";
    kor = 90;
    eng = 80;
    math = 75;

    printScore(name, kor, eng, math);

    name = "유관순";
    kor = 80;
    eng = 70;
    math = 65;

    printScore(name, kor, eng, math);
  }

  static void printScore(String name, int kor, int eng, int math) {
    int sum = kor + eng + math;
    float aver = sum / 3f;
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", name, kor, eng, math, sum, aver);
  }
}

 만약 과목이 12개라면 어떨것 같아?
 => 음.. 그건 좀.. 값이 많아지면, 메서드의 파라미터가 많아지고,
 => 어... 메서드 선언할 때 복잡할 것 같은데!
 => 근데... 이 성적 데이터 있지.. 그냥 한 봉다리에 담을 수는 없나?

 자바는 모든 경우를 고려해서 다양한 종류의 값을 담을 수 있는 변수를 제공하지는 않는다. 대신 여러 종류의 데이터를 묶어 새로운 형태의 데이터 타입을 만들 수 있는 문법을 제공한다. 그 문법의 이름이 "클.래.스"이다.

 클래스란?
 - 개발자가 자신이 개발하는 프로그램에서 사용할 특별한 형식의 데이터를 다룰 수 있는 새로운 데이터 타입을 정의하게 해주는 문법이다.
 - 즉 byte, short, int, long, float, double, boolean, char 외에 새 형식의 새로운 메모리 구조를 갖는 데이터 타입을 정의할 수 있다.
 - 이렇게 정의한 새 데이터 타입을 "사용자 정의 데이터 타입(user defined data type)" 이라 한다.

 사용자?
 - 아니~~ 자바 언어를 사용하는 사람이 누군가?
 - 개발자를 가리킨다.

 클래스 문법의 활용
 1) 사용자 정의 데이터 타입과 그 타입의 값을 다루는 연산자를 정의할 때 사용한다.
 2) 서로 관련된 일을 하는 메서드를 관리하기 쉽게 분류하여 묶는 용도로 사용한다.

 

 

# 클래스 사용 : 3) 클래스 사용

여러 개의 변수(메모리)를 묶어서 한 사람의 성적 정보를 저장하라!
=> 밥, 국, 반찬1, 반찬2, 반찬3 을 쟁반에 담아서 가져오는 상황.

1) 클래스 문법으로 성적 데이터를 저장할 메모리 구조를 설계한다.
[클래스]
- 다양한 타입의 메모리를 묶어서 새로운 형태의 메모리를 설계하는 문법이다.
- 개발자가 새롭게 정의한 데이터 타입이다.
- 그래서 "사용자 정의 데이터 타입" 이라 부른다.
- 문법:
     class 새_데이터_타입_이름 {
       변수 선언;
       ...
     }

[새_데이터_타입의_이름]
- 대문자로 시작한다.
- 여러 단어가 결합된 이름인 경우 각 단어의 시작도 대문자로 작성한다.
- 보통 명사형으로 짓는다.

public class Exam0120 {
  public static void main(String[] args) {

    // 다음은 성적 정보를 저장할 메모리 구조를 클래스로 설계한 것이다.
    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    // 위에서 작성한 설계도에 따라 메모리를 준비해보자!
    Score s; // int 변수 선언하듯이 이렇게 하면 될까?
    //
    // => 이 선언은 설계도에 따라 메모리를 준비시키는 명령이 아니다!!!
    // => Score 설계도에 따라 만든 메모리의 주소를 저장할 변수를 만드는 명령이다.
    // => 이렇게 메모리의 주소를 저장하는 변수를 "레퍼런스(reference)"라 부른다.
    // => int와 같은 primitive data type인 경우
    //    위와 같이 그냥 변수를 선언하면 되지만,
    //    클래스 문법으로 설계한 메모리는 이런 방식으로 만들 수 없다.
    //
    // - 레퍼런스 선언 문법:
    //      클래스명 변수명;
    //

    // 2) 개발자가 새롭게 정의한 메모리 설계도에 따라 메모리를 준비한다.
    // - 문법:
    //      new 클래스명();
    // - 클래스 설계도에 따라 메모리를 생성(사용할 수 있도록 확보)한다.
    // - 이렇게 설계도에 따라 준비된 메모리를 "인스턴스(instance)"라 부른다.
    // - 확보된 메모리를 사용하려면 주소를 보관할 필요가 있다.
    // - 위에서 준비한 레퍼런스(s)에 주소를 보관한다.
    //
    s = new Score();


    // 3) 클래스 설계도에 따라 만든 메모리에 값을 넣어 보자.
    // - 레퍼런스에 저장된 주소를 이용하여 인스턴스 메모리에 접근한다.
    // - 의미:
    //   => s에 저장된 주소로 찾아가서 그 메모리의 각 항목 값을 설정한다.
    //   => s에 저장된 주소로 찾아가서 그 인스턴스의 각 변수에 값을 설정한다.
    //   => s가 가리키는 인스턴스의 각 변수에 값을 설정한다.
    //   => s가 가리키는 인스턴스의 각 필드 값을 설정한다.
    //   => s 인스턴스의 필드 값을 설정한다.
    //   => s 객체의 필드 값을 설정한다.
    //
    s.name = "홍길동"; // s에 저장된 주소로 찾아가서 name 항목에 값 저장
    s.kor = 100; // s에 저장된 주소로 찾아가서 kor 항목에 값 저장
    s.eng = 90;
    s.math = 80;
    s.sum = s.kor + s.eng + s.math;
    s.aver = s.sum / 3;

    // 4) 메모리의 값을 꺼내 보자.
    // - "s에 저장된 주소로 찾아가서 name 항목의 값"
    // - "s가 가리키는 메모리의 name 항목의 값"
    // - "s가 가리키는 인스턴스의 name 값"
    // - "s 인스턴스의 name 값"
    // - "s 객체의 name 값"
    //
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }
}

## 자바 기본 데이터 타입의 변수 선언 vs 클래스의 변수 선언
- 자바 기본 데이터 타입은 변수를 선언하는 순간 메모리에 생성된다.
    int a;  // 바로 int 값을 저장할 메모리가 준비된다.
- 클래스의 변수 선언은 주소를 담는 레퍼런스이다. 따라서 주소를 담는 메모리만 준비된다.
    Score s;  // 아직 Score 설계도에 따라 변수들이 준비된 상태가 아니다.
- 클래스의 설계도에 따라 메모리를 준비하려면 따로 new 명령을 사용해야 한다.
    new Score();
- 생성된 메모리를 사용하려면 주소를 잘 보관해 두어야 한다.
    s = new Score();

## 클래스 vs 배열
클래스(class)
- 여러 타입을 묶어서 사용자 정의 데이터 타입을 만드는 문법이다.
- 관련된 기능(메서드, 함수)을 관리하기 편하게 묶는 문법이다.

배열(array)
- 단일한 타입의 메모리를 묶는 문법이다.

## primitive 데이터 타입 변수와 레퍼런스
- primitive type(byte, short, int, long, float, double, boolean, char)의
  메모리를 만들 때 변수 선언 만으로 완료된다.
  변수 이름이 곧 메모리를 가리키는 이름이 된다.
    예) int age;

- 클래스 이름으로 지정한 변수는 메모리 주소를 저장하는 변수(레퍼런스)이다.
    예) Score s; <=== "레퍼런스"라 부른다.

- 클래스(사용자 정의 데이터 타입)로 메모리를 만들 때는
  반드시 new 명령을 사용해야 한다.
    예) new Score(); <=== "인스턴스"라 부른다.

- 메모리를 만든 후에는 그 주소를 변수에 저장해야만 그 메모리를 사용할 수 있다.
  예) Score s; <==== 메모리의 주소를 저장할 레퍼런스 준비
      s = new Score(); <=== 메모리를 확보한 후 그 주소를 레퍼런스에 저장

## 인스턴스의 각 변수(항목; field)에 접근하기
- 문법:
  레퍼런스명.항목명 = 값;
  예) Score s = new Score();
      s.name = "홍길동";

 

 

# 클래스 사용 : 4) 메서드 활용

출력 부분을 메서드로 분리한다.

public class Exam0121 {

  // 여러 메서드에서 클래스를 사용한다면
  // 이렇게 메서드 밖에 선언해야 한다.
  // => static 메서드에서 사용할 수 있게 클래스도 static으로 선언한다.
  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;
  }

  public static void main(String[] args) {

    Score s = new Score();

    s.name = "홍길동";
    s.kor = 100;
    s.eng = 90;
    s.math = 80;

    // 성적 데이터를 클래스로 묶어 놓으면 값을 다루기가 편하다.
    // => 다음과 같이 성적 데이터를 한 번에 넘길 수 있다.
    printScore(s);

    Score s2 = new Score();
    s2.name = "임꺾정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 70;
    printScore(s2);

    Score s3 = new Score();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 60;
    printScore(s3);
  }

  static void printScore(Score s) {
    s.sum = s.kor + s.eng + s.math;
    s.aver = s.sum / 3;
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }
}

 

 

# 클래스 사용 : 5) 메서드 활용 II

인스턴스 생성 및 값 담을 메서드로 분리한다.

public class Exam0122 {

  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;
  }

  public static void main(String[] args) {

    // 클래스를 이용하면 성적 정보와 같은 여러 개의 값을 한 번에 리턴 받을 수 있다.
    Score s = createScore("홍길동", 100, 100, 100);
    printScore(s);

    Score s2 = createScore("임꺽정", 90, 80, 70);
    printScore(s);

    Score s3 = createScore("유관순", 80, 70, 60);
    printScore(s);
  }

  static void printScore(Score s) {
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }

  // 클래스를 이용하면 성적 정보를 하나로 묶어 리턴할 수 있다.
  // - Score 인스턴스를 생성하여 리턴한다.
  // - 더 정확하게 표현하면, Score 인스턴스를 생성한 후 그 주소를 리턴한다.
  static Score createScore(String name, int kor, int eng, int math) {
    Score s = new Score();

    s.name = name;
    s.kor = kor;
    s.eng = eng;
    s.math = math;
    s.sum = s.kor + s.eng + s.math;
    s.aver = s.sum / 3;

    return s; // s에 저장된 인스턴스의 주소를 리턴한다.
  }
}

 강사님! 로컬 변수는 메서드 호출이 끝난 다음에 삭제된다고 했는데 위에서 생성한 s 변수도 삭제되고, 파라미터 name, kor, eng, math도 삭제되는게 아닌가요?
 => 예 삭제됩니다.
 => 모든 로컬 변수는 메서드 호출이 끝나면 스택 메모리에서 삭제됩니다.

 하나 더요?
 Score 인스턴스도 삭제되는게 아닌가요?
 => 삭제되지 않습니다. 인스턴스는 힙(heap) 메모리에 생성됩니다.
    어떤 메서드에서 인스턴스를 생성하든지 간에 힙에 생성된 것은 메서드 호출이 끝나더라도 삭제되지 않습니다.
    오직 스택(stack) 메모리에 생성된 로컬 변수만이 삭제됩니다.

 

 

# 레퍼런스 배열 - 사용 전
public class Exam0210 {
  public static void main(String[] args) {
    // 여러 개의 인스턴스 주소 저장하기

    // 1) 성적 정보를 저장할 메모리를 설계한다.
    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    // 2) 메모리 주소를 저장할 레퍼런스를 준비한다.
    Score s1, s2, s3;

    // 3) 설계도에 따라 메모리를 준비하고 그 주소를 각 레퍼런스에 저장한다.
    s1 = new Score();
    s2 = new Score();
    s3 = new Score();

    // 4) 각각의 레퍼런스를 통해 인스턴스에 접근하여 특정 항목에 값을 넣는다.
    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 100;
    s1.math = 100;
    s1.sum = s1.kor + s1.eng + s1.math;
    s1.aver = s1.sum / 3f;

    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 90;
    s2.math = 90;
    s2.sum = s2.kor + s2.eng + s2.math;
    s2.aver = s2.sum / 3f;

    s3.name = "유관순";
    s3.kor = 100;
    s3.eng = 100;
    s3.math = 100;
    s3.sum = s3.kor + s3.eng + s3.math;
    s3.aver = s3.sum / 3f;

    // 5) 각각의 레퍼런스를 통해 인스턴스에 접근하여 특정 항목의 값을 꺼낸다.
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", s1.name, s1.kor, s1.eng, s1.math, s1.sum, s1.aver);
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", s2.name, s2.kor, s2.eng, s2.math, s2.sum, s2.aver);
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", s3.name, s3.kor, s3.eng, s3.math, s3.sum, s3.aver);
  }
}

 수 십 개의 인스턴스를 저장한다면 레퍼런스도 그 개수에 맞춰 선언해야 한다. 코딩하기가 매우 불편하다. 만약 수 백 개라면 더더욱 불편할 것이다.
 해결책?
 - 레퍼런스를 배열로 만드는 것이다.

 

 

# 레퍼런스 배열 - 사용 후
public class Exam0220 {
  public static void main(String[] args) {
    // 여러 개의 인스턴스 주소 저장하기

    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    // 배열 문법을 이용하면 한 번에 여러 개의 레퍼런스를 선언할 수 있다.
    // - 문법:
    //     클래스명[] 배열명 = new 클래스명[레퍼런스개수];
    // - 주의!
    //   레퍼런스 배열이다. 인스턴스 배열이 아니다!
    //
    Score[] arr = new Score[3];
    // 강사님, 인스턴스 배열을 만들 수는 없나요?
    // => 없다!

    // 레퍼런스 배열을 생성하면 모든 항목이 null로 초기화 된다.
    // 아직 레퍼런스 배열의 각 항목에 인스턴스 주소가 없는 상태이다.
    System.out.println(arr[0]);
    System.out.println(arr[1]);
    System.out.println(arr[2]);

    System.out.println("------------------------");

    // 레퍼런스 배열의 각 항목에 인스턴스를 저장한 후 사용해야 한다.
    arr[0] = new Score();
    arr[1] = new Score();
    arr[2] = new Score();

    System.out.println(arr[0]);
    System.out.println(arr[1]);
    System.out.println(arr[2]);
  }
}

 결론!
 - 여러 개의 인스턴스의 주소를 관리할 때는 레퍼런스 배열을 사용하는 게 편하다.

 잊지말자!
 - 인스턴스 배열을 만들 수 없다.
 - 레퍼런스 배열만 가능하다.
 - 단 자바 기본 타입(byte, short, int, long, float, double, boolean, char)은 해당 타입의 메모리를 배열로 만들 수 있다.
 - 그 외 모든 타입은 오직 레퍼런스 배열만 생성한다.

 

 

public class Exam0221 {
  public static void main(String[] args) {
    // 여러 개의 인스턴스 주소 저장하기

    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    Score[] arr = new Score[3];

    arr[0] = new Score();
    arr[1] = new Score();
    arr[2] = new Score();

    // 배열의 각 항목에 저장된 인스턴스는 '배열명[인덱스]'를 사용하여 지정할 수 있다.
    arr[0].name = "홍길동";
    arr[0].kor = 100;
    arr[0].eng = 100;
    arr[0].math = 100;
    arr[0].sum = arr[0].kor + arr[0].eng + arr[0].math;
    arr[0].aver = arr[0].sum / 3f;

    arr[1].name = "임꺽정";
    arr[1].kor = 90;
    arr[1].eng = 90;
    arr[1].math = 90;
    arr[1].sum = arr[1].kor + arr[1].eng + arr[1].math;
    arr[1].aver = arr[1].sum / 3f;

    arr[2].name = "유관순";
    arr[2].kor = 80;
    arr[2].eng = 80;
    arr[2].math = 80;
    arr[2].sum = arr[2].kor + arr[2].eng + arr[2].math;
    arr[2].aver = arr[2].sum / 3f;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        arr[0].name, arr[0].kor, arr[0].eng, arr[0].math, arr[0].sum, arr[0].aver);
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        arr[1].name, arr[1].kor, arr[1].eng, arr[1].math, arr[1].sum, arr[1].aver);
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        arr[2].name, arr[2].kor, arr[2].eng, arr[2].math, arr[2].sum, arr[2].aver);
  }
}

 

 

# 레퍼런스 배열 - 반복문 결합
public class Exam0230 {
  public static void main(String[] args) {
    // 여러 개의 인스턴스 주소 저장하기
    //
    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    // 레퍼런스 배열 준비
    Score[] arr = new Score[3];

    // 반복문을 이용하여 레퍼런스 배열에 인스턴스 주소를 저장한다.
    for (int i = 0; i < arr.length; i++) {
      arr[i] = new Score();
    }

    // 값을 저장할 때는 일일이 값을 지정해야 한다.
    arr[0].name = "홍길동";
    arr[0].kor = 100;
    arr[0].eng = 100;
    arr[0].math = 100;
    arr[0].sum = arr[0].kor + arr[0].eng + arr[0].math;
    arr[0].aver = arr[0].sum / 3f;

    arr[1].name = "임꺽정";
    arr[1].kor = 90;
    arr[1].eng = 90;
    arr[1].math = 90;
    arr[1].sum = arr[1].kor + arr[1].eng + arr[1].math;
    arr[1].aver = arr[1].sum / 3f;

    arr[2].name = "유관순";
    arr[2].kor = 80;
    arr[2].eng = 80;
    arr[2].math = 80;
    arr[2].sum = arr[2].kor + arr[2].eng + arr[2].math;
    arr[2].aver = arr[2].sum / 3f;

    // 반복문을 사용하면 배열의 인스턴스 값을 꺼내기 쉽다.
    for (int i = 0; i < arr.length; i++) {
      System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
          arr[i].name, arr[i].kor, arr[i].eng, arr[i].math, arr[i].sum, arr[i].aver);
    }
  }
}

 결론!
 - 배열은 반복문과 함께 쓸 때 특히 유용한다.

 

 

# 레퍼런스 배열 - 메서드 활용
public class Exam0240 {

  // 여러 메서드에서 공유하려면 클래스를 메서드 밖에 선언해야 한다.
  // => static 메서드들이 사용할 수 있게 클래스도 static으로 선언한다.
  // => static에 대한 의미는 나중에 설명한다.
  //
  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;
  }

  public static void main(String[] args) {

    Score[] arr = new Score[3];

    // 메서드에서 생성한 Score 객체를 레퍼런스 배열의 각 항목에 저장한다.
    arr[0] = createScore("홍길동", 100, 100, 100);
    arr[1] = createScore("임꺽정", 90, 90, 90);
    arr[2] = createScore("유관순", 80, 80, 80);

    // 메서드에 배열을 통째로 넘길 수 있다.
    // => 정확하게 표현하면, arr 변수에 저장된 레퍼런스 배열의 주소를 넘긴다.
    printScoreList(arr);

  }

  // 클래스를 이용하면 성적 정보를 하나로 묶어 리턴할 수 있다.
  // 참고!
  // - 다음과 같이 메서드를 통해 인스턴스를 생성하는 코딩 기법을 
  //   "팩토리 메서드(factory method)" 패턴이라 부른다.
  static Score createScore(String name, int kor, int eng, int math) {
    Score s = new Score();

    s.name = name;
    s.kor = kor;
    s.eng = eng;
    s.math = math;
    s.sum = s.kor + s.eng + s.math;
    s.aver = s.sum / 3f;

    return s;
  }

  static void printScoreList(Score[] arr) {
    // main()에서 넘겨준 레퍼런스 배열의 주소를 arr 변수에 받는다.
    // 결국 main()에서 만든 레퍼런스 배열을 사용하는 것이다.
    //
    for (int i = 0; i < arr.length; i++) {
      System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
          arr[i].name, arr[i].kor, arr[i].eng, arr[i].math, arr[i].sum, arr[i].aver);
    }
  }
}

 

 

# 레퍼런스와 인스턴스 - 인스턴스 주소 주고 받기
public class Exam0310 {
  public static void main(String[] args) {
    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    // Score 레퍼런스 선언 + 인스턴스 생성(사용할 메모리 확보)
    Score s1 = new Score();

    // s1에 저장된 주소를 s2에도 저장한다.
    // => s1과 s2는 같은 메모리를 가리킨다.
    Score s2 = s1;

    s1.name = "홍길동";

    // s1이 가리키는 메모리는 s2를 사용하여 접근할 수 있다.
    System.out.println(s2.name);
  }
}

 

 

# 레퍼런스와 인스턴스 - 가비지
public class Exam0320 {
  public static void main(String[] args) {
    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    // Score 레퍼런스 선언
    Score s1;

    // 인스턴스를 만들어 그 주소를 레퍼런스에 저장한다.
    s1 = new Score();

    // 새 인스턴스를 만들어 s1에 주소를 저장한다.
    s1 = new Score();

    // 그러면 기존에 들어 있던 주소는 잃어 버린다.
    // 주소를 잃어버려 사용할 수 없는 메모리를
    // "가비지(garbage)"라 부른다.

    // 가비지는 가비지 컬렉터에 의해 메모리에서 해제된다.
    // 가비지 컬렉터(garbage collector)의 실행
    // - 메모리 부족할 때
    // - CPU가 한가할 때
    // - System.gc()를 호출하여 가비지 실행을 요청할 때
    //   물론 이 경우에 바로 실행하는 것이 아니라
    //   빠른 시간 내에 실행할 것을 종용하는 것이다.
    //   아무래도 원래의 시간보다는 앞 당겨 청소를 하게 될 것이다.
    //   반드시 청소를 한다는 보장은 없다.

  }
}

 ## 가비지 컬렉터(garbage collector)
 - 힙 메모리에 존재하는 가비지를 찾아 제거(?)하는 일을 한다.
 - 다른 용도로 사용할 수 있도록 메모리를 해제하는 일을 한다.

 ## 언제 작업하는가?
 - JVM이 관리하는 메모리가 부족할 때
 - CPU가 한가할 때

 

 

# 레퍼런스와 인스턴스 - 레퍼런스 카운트와 가비지
public class Exam0330 {
  public static void main(String[] args) {
    class Score {
      String name;
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    Score s1 = new Score();
    Score s2 = new Score();

    s2 = s1;
    // s1의 주소는 s2에도 저장되었다.
    // 즉 s1이 가리키는 객체는 s2도 가리키게 되었다.
    // JVM은 객체의 참조 상태를 관리하기 위해 "레퍼런스 카운트(reference count)"를 이용한다.
    // s1이 가리키는 객체처럼 주소를 알고 있는 변수가 늘어나면 레퍼런스 카운트를 증가시키고
    // s2이 이전에 가리켰던 객체처럼 주소를 알고 있는 변수가 줄어들면 레퍼런스 카운트를 감소시킨다.
    // 레퍼런스 카운트가 0인 상태가 "가비지(garbage)"이다.

  }
}

 

 

# 패키지 멤버 클래스
public class Exam0410 {

  public static void main(String[] args) {
    // 바깥 쪽에 별도 선언한 클래스를 사용하기
    Score s = new Score();

    s.name = "홍길동";
    s.kor = 100;
    s.eng = 90;
    s.math = 80;
    s.sum = s.kor + s.eng + s.math;
    s.aver = s.sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }
}

 Exam0410.java 컴파일 오류!
 - 콘솔에서 이 소스 파일을 컴파일 할 때 Score 클래스를 찾을 수 없다고 오류가 발생할 것이다.
   > javac -encoding UTF-8 -d bin/main src/main/java/com/eomcs/oop/ex01/Exam0410.java
 - 이유?
   Exam0410.java 에서 Score 클래스를 사용하는데, 컴파일할 때 Score 클래스 정보가 필요하다.
   그런데 컴파일러 Score 클래스에 대한 정보를 모르기 때문에 Exam0410.java를 제대로 컴파일 할 수 없는 것이다.
 - 해결책?
   컴파일러에 Score 클래스에 대한 정보를 알려줘라!
 방법1) Score.class 파일을 갖고 있다면 그 파일이 있는 위치를 알려줘라.
   > javac -encoding UTF-8 -d bin/main -classpath bin/main src/main/com/eomcs/oop/ex01/Exam0410.java

 방법2) Score 클래스의 소스 파일이 있다면 소스 파일의 위치를 알려줘라.
   > javac -encoding UTF-8 -d bin/main -sourcepath src/main/java src/main/com/eomcs/oop/ex01/Exam0410.java

 언제 이렇게 해야 하는가?
 - 자바에서 기본으로 제공하는 클래스가 아닌 다른 클래스를 사용할 때!

 

 

# 패키지 클래스

 - 다른 클래스 안에 선언되지 않고 별도로 선언된 클래스를 "패키지 클래스"라 부른다.
 - 한 소스 파일에 여러 개의 클래스를 정의할 수 있지만, 유지보수를 쉽게 하기 위해 보통 한 소스 파일에 한 클래스를 정의한다.
 - 패키지 클래스란? 패키지에 직접 소속된 클래스라는 의미다.
 - 이전에 만든 Score 클래스는 main() {} 블록 안에 정의하였다.
   main() {} 블록 안에 정의한 클래스는 main() {} 블록 안에서만 사용할 수 있다.

public class Score {
  public String name;
  public int kor;
  public int eng;
  public int math;
  public int sum;
  public float aver;
}

 

 

# 패키지 멤버 클래스와 중첩 클래스

 패키지 멤버 클래스
 - 단독으로 선언하는 클래스이다.
 - "패키지 멤버 클래스"라 부른다.
 - 물론 이 예제처럼 한 파일에 여러 개의 클래스를 선언할 수 있지만, 보통은 한 파일에 한 클래스를 선언한다.
 - 패키지 멤버 클래스는 접근 권한이 있다면 누구든 사용할 수 있다.

// bin/main/com/eomcs/oop/ex01/A.class
class A {}

// => bin/main/com/eomcs/oop/ex01/Exam0510.class
public class Exam0510 {

  // 중첩 클래스(nested class)
  // - 다른 클래스 안에 정의된 클래스
  // - 그 클래스 안에서만 사용된다.
  // - 종류:
  //   1) 스태틱 중첩 클래스(static nested class)
  //   2) 논-스태틱 중첩 클래스(non-static nested class = inner class)
  //   3) 로컬 클래스(local class)
  //   4) 익명 클래스(anonymous class)
  //

  // 1) 스태틱 중첩 클래스
  // - 이 클래스를 소유하고 있는 클래스 뿐만 아니라 다른 클래스도 사용할 수 있다.
  //
  // bin/main/com/eomcs/oop/ex01/Exam0510$B.class
  static class B {}

  // 2) 논-스태틱 중첩 클래스 = inner class
  // - 특정 인스턴스에 종속된 클래스인 경우 논-스태틱 중첩 클래스로 정의한다.
  // - .class 파일 명으로 스태틱인지 논-스태틱인지 알아낼 방법은 없다.
  // bin/main/com/eomcs/oop/ex01/Exam0510$C.class
  class C {}

  public static void main(String[] args) {
    // 3) 로컬 클래스(local class)
    // - 메서드 블록 안에 정의된 클래스
    // - 오직 그 메서드 블록 안에서만 사용된다.
    //
    // bin/main/com/eomcs/oop/ex01/Exam0510$1D.class
    class D {}

    // 4) 익명 클래스(anonymouse class)
    // - 클래스 이름이 없는 중첩 클래스이다.
    // - 딱 한 개의 인스턴스를 생성할 때 사용한다.
    // - 클래스를 정의할 때 수퍼 클래스나 인터페이스를 지정해야 한다.
    // - 클래스를 정의할 때 new 연산자를 사용하여 즉시 인스턴스를 생성해야 한다.
    //
    // bin/main/com/eomcs/oop/ex01/Exam0510$1.class
    Object obj = new Object() {
      String name;
      int age;
    };
  }

  static void m1() {
    // 패키지 멤버 클래스는 그 패키지에 소속된 누구라도 사용할 수 있다.
    // 같은 패키지가 아니라도 공개된 패키지 멤버는 누구라도 사용할 수 있다.
    A obj1 = new A();

    // 같은 스태틱 멤버라면 스태틱 중첩 클래스를 사용할 수 있다.
    B obj2 = new B();

    // 스태틱 멤버는 논-스태틱 중첩 클래스(인스턴스 멤버)를 사용할 수 없다.
    //    C obj3 = new C(); // 컴파일 오류!

    // 다른 메서드에 정의된 로컬 클래스는 사용할 수 없다.
    //    D obj4 = new D(); // 컴파일 오류!

  }

  void m2() {
    // 패키지 멤버 클래스는 그 패키지에 소속된 누구라도 사용할 수 있다.
    // 같은 패키지가 아니라도 공개된 패키지 멤버는 누구라도 사용할 수 있다.
    A obj1 = new A();

    // 논-스태틱 멤버(인스턴스 멤버)는 스태틱 중첩 클래스를 사용할 수 있다.
    B obj2 = new B();

    // 인스턴스 멤버는 다른 인스턴스 멤버(논-스태틱 중첩 클래스)를 사용할 수 있다.
    C obj3 = new C(); // OK!

    // 다른 메서드에 정의된 로컬 클래스는 사용할 수 없다.
    // D obj4 = new D(); // 컴파일 오류!

  }
}

 

 

# public 클래스와 기본 클래스(default class = package private class)

 

public class Exam0610 {
  public static void main(String[] args) {

    // 같은 패키지에 소속된 클래스 사용
    // - 공개, 비공개 상관없이 사용할 수 있다.
    com.eomcs.oop.ex01.X obj1;
    com.eomcs.oop.ex01.Y obj2;


    // 다른 패키지에 소속된 클래스 사용
    //
    // - X2 클래스는 public 이 아니다.
    // - 따라서 다른 패키지의 클래스는 X2를 사용할 수 없다.
    //    com.eomcs.oop.ex01.sub.X2 obj3; // 컴파일 오류!

    // - Y2 클래스는 public으로 공개된 클래스이다.
    com.eomcs.oop.ex01.sub.Y2 obj4;
  }
}

 

 

# import : 사용 전

# 패키지 클래스 
 다른 패키지의 클래스 사용
 => 패키지 이름을 항상 붙여야 한다.
    그래야만 컴파일러가 해당 클래스를 찾을 수 있다.
 => 패키지 전체 이름을 붙여야 한다.
 => 현재 패키지를 기준으로 상대 경로를 사용할 수 없다.
    예) sub.Y2 obj = new sub.Y2(); // 컴파일 오류!

public class Exam0710 {
  public static void main(String[] args) {

    com.eomcs.oop.ex01.sub.Y2 obj;
    obj = new com.eomcs.oop.ex01.sub.Y2();

  }
}

 

 

# import : 사용 후

 다른 패키지의 클래스를 사용할 때 마다 패키지명을 적는다면 코드가 너무 길어진다.

 이를 해결하기 위해 자바는 import 라는 명령을 제공한다.
 클래스를 사용하기 전에 미리 해당 클래스가 어느 패키지에 있는지 지정하는 것이다.
 문법:
      import 패키지명.클래스명;

// - import 명령은 package 명령 다음에 와야 한다.
// - 클래스 선언 전에 와야 한다.
import com.eomcs.oop.ex01.sub.Y2;

public class Exam0720 {
  public static void main(String[] args) {

    Y2 obj;
    obj = new Y2();
  }
}

 

 

# import - 사용 후 II

 특정 패키지에 소속된 여러 클래스를 사용한다면 다음과 같이 패키지명 다음에 wildcard(*)를 지정하면 편리하다.
 문법:
      import 패키지명.*;

 - 이 경우 사용하는 클래스 마다 import를 따로 지정할 필요가 없다.
 - (주의!) 서브 패키지는 해당이 안된다.
 - 단 소스 코드를 읽을 때 어떤 클래스가 어떤 패키지 있는지 바로 확인할 수 없는 불편함이 있다.
   그래서 대부분의 자바 개발자들은 널리 알려진 클래스가 아닌 경우
   가능한 wildcard(*)를 사용하지 않고 패키지명과 클래스명을 정확하게 명시한다.

// # import - 사용 후 II
//
import com.eomcs.oop.ex01.sub.*;

public class Exam0730 {
  public static void main(String[] args) {

    Y2 obj;
    obj = new Y2();

    Z2 obj2;
    obj2 = new Z2();
  }
}

 결론!
 - 가능한 import 문을 선언할 때 * 대신 구체적인 클래스명을 적어라!
 - 같은 패키지의 여러 클래스를 사용하더라도 귀찮지만 클래스명을 적어라!
 - 왜?
   소스 코드를 읽을 때 어떤 클래스가 어떤 패키지에 있는지 빠르게 파악할 수 있기 때문이다.
   wildcard(*) 를 사용하게 되면 어떤 패키지인지 바로 확인할 수 없다.

 

 

# import - java.lang 패키지
public class Exam0740 {
  public static void main(String[] args) {
    java.lang.Integer obj1; // OK!
    java.lang.System obj2; // OK!
    java.lang.reflect.Array obj3;
    java.io.File obj4; // OK!

    Integer obj5; // OK!
    System obj6; // OK!
    //    Array obj7; // 컴파일 오류!
    //    File obj8; // 컴파일 오류!

    // java.lang 패키지에 있는 클래스는 패키지를 지정하지 않아도 된다.
    // 즉 패키지명을 명시하지 않아도 컴파일 오류가 발생하지 않는다.
    // 그 외 다른 모든 패키지의 클래스는 패키지명을 명시하지 않으면
    // 컴파일 오류가 발생한다.
  }
}

 결론!
 - java.lang 패키지에 소속된 클래스를 사용할 때는
   패키지 이름을 명시하지 않아도 된다.
 - 주의!
   - java.lang 패키지의 하위 패키지는 해당되지 않는다.
   - 예) java.lang.annotation, java.lang.invoke 등
 - java.lang 패키지의 클래스들은 가장 많이 사용하는
   기본 클래스이기 때문에 자바 컴파일러는 해당 클래스를 자동으로 찾는다.
 - 그 외의 모든 패키지 클래스들은 반드시 패키지명을 지정해야 한다.
 - 지정하기 싫으면 클래스 선언 전에 import 명령으로 패키지를 지정해야 한다.