개발자입니다
[Java] 예제 소스 정리 - 클래스(인스턴스, public, default, 중첩 클래스) 본문
[Java] 예제 소스 정리 - 클래스(인스턴스, public, default, 중첩 클래스)
끈기JK 2023. 1. 3. 09:56com.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 명령으로 패키지를 지정해야 한다.
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Java' 카테고리의 다른 글
[비트캠프] 44일차(9주차4일) - Java(리팩토링: 메서드 묶음으로써 클래스), myapp-07~08 (0) | 2023.01.04 |
---|---|
[비트캠프] 43일차(9주차3일) - Java(리팩토링: 데이터 타입 정의로써 클래) (0) | 2023.01.04 |
[비트캠프] 42일차(9주차2일) - Java(클래스) (0) | 2023.01.03 |
[비트캠프] 41일차(9주차1일) - Java(메서드) (0) | 2023.01.02 |
[Java] 예제 소스 정리 - 콘솔 출력 (1) | 2022.12.30 |