회고록 블로그
[공부 필기] Java 기본 공부하기 (7) 본문
역시나 프로그래밍 언어 문법에 대해서 공부하는건 너무 재미없다.
하지만 A, B, C도 모르고 작문을 할 수는 없듯이, 적어도 기본은 알아야 무엇이든 프로그램을 만들 수 있는 법이다.
오늘도 노잼을 이겨내며 강의를 듣자..
공부 중인 강의 : 윤성우 선생님, 윤성우의 열혈 Java 프로그래밍 강의, https://cafe.naver.com/cstudyjava
1. 패키지(Package)
- 패키지 선언이 왜 필요할까
ㄱ. 개발자는 모든 클래스를 다 정의할 수가 없음
(가령, String 클래스도 개발자가 정의한게 아니라 기본적으로 제공하는 클래스인 것 처럼)
ㄴ. 사실 직접 정의하는 클래스보다 가져다쓰는 클래스가 더 많을 수도 있음
ㄷ. 클래스는 기본적으로 제공하는 클래스도 있지만, 기업에서 만든 클래스도 있음
그런 클래스를 가져와서 활용하는 경우가 정말 많음
ㄹ. 문제는 A사, B사, C사 등 많은 기업에서 만든 클래스의 '이름'이 충돌되지 않을수가 없음
(e.g. A사에서 Add 라는 클래스를 만들었는데, B사에도 Add 라는 클래스가 있을 수 있음)
ㅁ. 이때 클래스의 이름이 중복되기 때문에 문제가 몇가지 생김
→ 같은 공간(디렉터리)에 둘 수가 없음
→ 클래스를 사용하려 할 때, JVM은 A사에서 만든 클래스인지 B사에서 만든 클래스인지 구분을 하지못함
ㅂ. 결국 이를 모두 해결하기 위해서는 클래스를 구분해주는 것이 필요함
→ 경로를 다르게 해서 어떤 위치에 있는 어떤 클래스에 접근하겠다고 명시적으로 작성해줘야 함
- 즉, 패키지 선언은 클래스들의 경로를 달리하고
그 경로를 코드에 포함해서 작성해야만 클래스에 접근이 가능하도록 한 것임
- 사실 패키지 선언은 클래스들을 묶는다는 의미가 더 강함 (여러 비슷한 물건을 패킹하듯이)
- 예시
→ com 디렉터리 안의 example1 디렉터리 안의 project1에 있는 클래스
package com.example1.project1;
public class Add {
......
}
→ com 디렉터리 안의 example2 디렉터리 안의 aaa에 있는 클래스
package com.example2.aaa;
public class Add {
......
}
- 패키지 이름을 정할 땐, 관례가 있음 (패키지 이름의 중복을 방지하기 위함)
→ 패키지명은 "인터넷 도메인 이름의 역순"으로 구성해야만 함
→ 패키지 이름 끝에는 "클래스 정의한 주체나 팀의 이름"을 넣어야만 함
(같은 회사 안에서도 패키지 이름을 중복해서 제작할 가능성을 줄이기 위함)
- 패키지 선언 예시
public static void main(String[] args) {
com.example1.project1.Add a1 = new com.example1.project1.Add();
com.example2.aaa.Add a1 = new com.example2.aaa.Add();
}
- 유의할 점
→ JVM은 classpath를 기준으로 'com'디렉터리를 찾고, 그 안에서 'example1' 디렉터리를 찾고, 또 그 안에서 'project1'을 찾음
→ 따라서, 'com' 디렉터리가 포함되어 있는 디렉터리를 classpath로 설정해야함
→ 즉, 아래와 같은 경우라면 com 디렉터리가 포함된 'D:\Coding Folder'가 classpath가 되어야 함
- 명령 프롬프트에서 javac의 -d 옵션을 사용하면 패키지 경로에 맞춰 자동으로 디렉터리를 만들어줌
→ 개발자가 직접 디렉터리를 만들고 그곳에 클래스파일을 두어도됨
※ JVM 기본 라이브러리에 있는 패키지들의 이름을 보니 전부 '소문자'로만 작성하는 것 같음
대문자는 패키지 이름으로 잘 쓰지 않는 듯함
2. import
- 코드 안에서 패키지 이름을 명시하면서 작성하면 너무 번거로움(아래 참고)
public static void main(String[] args) {
com.example1.project1.Add a1 = new com.example1.project1.Add();
...
}
- 하지만 import 키워드를 이용하면 패키지 이름을 항상 언급할 필요가 없음
import com.example1.project1.Add;
public static void main(String[] args) {
Add a1 = new Add();
...
}
- package 키워드가 쓰이는 위치 주의할 것
→ 패키지 선언은 패키지로 묶을 소스파일 안에서 사용해야함
main 메소드 등 패키지를 가져다 쓸 위치에서 작성하는 것이 아님
- import 키워드의 위치는 인스턴스 생성 등 내가 패키지를 가져다 쓸 곳에서 선언하는 것임
- import로 클래스 하나를 선언했다면, 동일한 클래스명의 다른 패키지에 있는 클래스는 import 선언이 불가능함
→ 가급적 import 키워드는 제한적으로 사용해야한다고 말하지만, 실상 현업에서 많이 사용된다고 함
제한적으로(?) 적절하게 사용하라고 조언해주심(?)
- 아래와 같이 별(*)을 이용해서 사용할 수도 있음
→ 하지만 가급적 사용을 지양하고 있고, 실제 현업에서도 잘 사용하지 않는다고 함
(한 패키지 안에 있는 수십/수백개의 클래스를 모두 import 해버리면, 나중에 클래스 이름 충돌이 발생할 가능성이 높기 때문)
import com.example1.project1.*;
3. 정보은닉
- 어떤 클래스가 있다고 할 때, 다른 클래스에서는 이 클래스에 접근하지 못하도록(보이지 않도록),
즉 해당 클래스 내부에서만 접근할 수 있도록 하는 것이 정보은닉.
- 클래스를 정의할 때는 데이터(변수 등)와 기능(메소드 등)이 있음
- 정리하자면 정보은닉은
클래스 외부에는 데이터를 보이지 않게 가리고(클래스 외부에서 접근하지 못하게) + 해당 클래스 안에서만 사용할 수 있게 하고
클래스 외부에서 데이터에 접근해야한다면 '기능'을 통해 접근하도록 유도하는 것.
- 사용하는이유?
→ 안정성을 높이기 위해서
→ 데이터(변수 등)에 직접 접근하면 논리적 오류가 발생할 수 있음
(e.g. 특정 멤버 변수에 양수의 값만 저장되도록 (if문을 사용해서) 메소드를 정의했는데,
멤버 변수에 직접 접근해버리면 이런 조건을 모두 무시하고 음수값도 저장시킬 수 있는 문제가 생김)
→ 논리적 오류를 문법적 오류가 되도록 하는 방법 중 하나가 정보은닉임
- 정보은닉 필요한 경우 예시
→ 아래와 같은 클래스가 있다고 가정하며, 양수만 입력 받도록 하고 싶다고 가정
→ 클래스 외부에서 number2 멤버 변수의 값을 음수로 변경해버리면, 컴파일 에러 없이 변경되어버림
→ 멤버 변수 number2가 외부에서 접근 가능하기 때문
package com.example.project1;
public class Calculator {
int number1 = 0;
public int number2 = 0; // 외부에서 접근 가능한 상황 연출 위해서 public 설정함
char operator = ' '; // char 타입은 공백으로 초기화
public Calculator(int n1, int n2, char op) {
setExpression(n1, n2, op);
}
public void setExpression(int n1, int n2, char op) {
if((n1 < 0) || (n2 < 0)) {
number1 = 0;
number2 = 0;
operator = ' ';
System.out.println("양수를 입력하세요.");
return;
}
number1 = n1;
number2 = n2;
operator = op;
}
public int getResult() {
switch(operator) {
case '+' :
return (number1+number2);
case '-' :
return (number1-number2);
case '*' :
return (number1*number2);
default :
return 0; // 그 외에는 '0' 반환
}
}
}
- 정보은닉 방법은 멤버 변수 앞에 'private' 키워드를 입력해주면 됨
→ 클래스 내부의 식구들은 해당 멤버 변수에 접근할 수 있지만 외부에서는 접근 불가능함
→ 만약 외부에서 private 멤버 변수에 접근하려고 하면 컴파일 에러가 발생함
※ 어떠한 멤버 변수의 값을 수정하기 위한 멤버 메소드를 'Setter'라고 함
※ 반대로 멤버 변수의 값을 가져오기 위한 멤버 메소드를 'Getter'라고 함
4. 접근수준 지시자
- Java에서 제공하는 접근수준 지시자 : public / protected / default / private
- private : '외부에서의 접근을 제한한다' 정도로 해석해봄
→ 보통 인스턴스 변수는 private로 선언하고, Getter와 Setter를 public으로 설정하는 경우가 많음
- default : 'default'라는 키워드가 아님. 그냥 '어떠한 접근수준 지시자도 선언되지 않음' 정도로 생각하면 될 듯
- 클래스에서 쓸 수 있는 접근수준 지시자와 인스턴스 변수/메소드에서 사용할 수 있는 접근수준 지시자가 다름
클래스 정의할 때 : public, default
인스턴스 멤버(변수/메소드) 선언할 때 : public, protected, default, private
4.1. 클래스 정의 시 접근수준 지시자
- public과 default의 차이
→ public으로 선언하면? : 어디에서든지 인스턴스 생성 가능
→ default 라면? : 그 클래스가 묶여있는 패키지(package) 안에서만 인스턴스 생성이 가능함
→ 예시
- 하나의 소스파일(*.java) 안에는 하나의 "public" 클래스만 둘 수 있음
→ JAVA에는 소스파일의 이름과 클래스 이름이 일치 해야하는 제약사항이 있음
→ public으로 선언된 클래스의 외부 노출도를 높이기 위해서
(개발자는 소스파일의 이름만 보고도 어떤 public 클래스가 있는지 단번에 알 수 있음)
4.2. 인스턴스 멤버 선언 시 접근수준 지시자
- public 선언 시 클래스 외부 어디에서든지 접근 가능
- private 선언 시 클래스 외부에서 접근 불가능 , 클래스 내부에서 접근 가능
- default 선언 시 "해당 인스턴스 멤버가 소속된 패키지 안에서는" 접근 가능
- protected는 '상속'에 대해서 이해 해야지만 알 수 있음
4.2.1. protected 접근수준 지시자
- 우선, Java는 패키지 선언이 안된 클래스들은 하나의 패키지로 묶어버림 ('디폴트 패키지'라고 함)
→ 패키지를 선언하지 않은 부모 클래스와 자식 클래스도 마찬가지임
- 그리고 상속 관계에 대해서 간단히 이해해야함
→ 부모 클래스의 인스턴스 멤버를 자식 클래스가 물려 받는 것과 비슷한 개념임
→ (코드 상에서는 보이지 않아도) 자식 클래스 안에 '부모 클래스의 인스턴스 멤버'가 선언된 것과 동일한 상태가 됨
- 아래와 같은 상속 관계의 두 클래스가 있다고 가정
→ 일반적으로 A는 B의 'num' 인스턴스 변수에 접근할 수 없어야 하는게 맞음
(왜냐하면, A에 있는 num이 B의 것인지 명시해주지 않았기 때문)
하지만, 둘은 같은 패키지로 묶이고(= 디폴트 패키지) 상속 관계이기 때문에 num에 접근이 가능함
public class B {
int num; // 접근수준 지시자는 default
...
}
public class A extends B { // A는 B(부모)의 인스턴스 멤버를 상속받음
public void init(int n) {
num = n; // B의 'num' 변수에 값을 넣으려는 의도
...
}
}
- 그렇다면 상속 관계이지만 '패키지가 다르다'면?
→ 아래 코드에서 A는 'num'에 접근할 수 없음
(아무리 상속 관계라고 하더라도 B의 'num' 인스턴스 변수가 default로 선언되어 있고,
두 클래스가 서로 다른 패키지에 속해있기 때문)
→ 이를 해결하려면 'protected' 지시자를 사용해야함
package com.example1.aaa;
// com.example1.aaa 패키지로 묶이게 됨
public class B {
int num; // 접근수준 지시자는 default
...
}
// 디폴트 패키지에 묶이게 됨
public class A extends B { // A는 B(부모)의 인스턴스 멤버를 상속받음
public void init(int n) {
num = n; // B의 'num' 변수에 값을 넣으려는 의도
...
}
}
- 즉, protected는 (같은 패키지로 묶이지 않았더라도) 상속 관계에 있는 클래스에 접근을 하도록 해주는 지시자임
→ 아래 코드는 실행이 가능함 ('num'이 protected로 선언되었기 때문)
package com.example1.aaa;
// com.example1.aaa 패키지로 묶이게 됨
public class B {
protected int num; // 접근수준 지시자는 protected (상속 관계의 클래스에서 접근 가능함)
...
}
// 디폴트 패키지에 묶이게 됨
public class A extends B { // A는 B(부모)의 인스턴스 멤버를 상속받음
public A(int n) {
super();
this.num = n; // B의 'num' 변수에 값을 넣으려는 의도
...
}
}
※ 접근수준 지시자 정리
# 클래스 정의 시 사용할 수 있는 접근수준 지시자 : public, default
- public : 어디에서든지 인스턴스 생성 가능
- default : 해당 클래스가 속한 패키지 안의 클래스에서만 인스턴스 생성 가능
# 인스턴스 멤버 선언 시 사용할 수 있는 접근수준 지시자 : public, protected, default, private
- public : 어디에서든지 인스턴스 멤버에 접근 가능 (클래스 내부에서도, 클래스 외부에서도 모두 가능)
- protected : 같은 패키지 안에서 접근 가능 + (부모 클래스와 자식 클래스가 같은 패키지 안에 속하지 않았더라도) 상속 관계의 클래스에서 접근 가능
- default : 동일한 패키지 안에 있는 클래스에서만 접근 가능
- private : 클래스 내부에서만 접근 가능
'2. 프로그래밍 언어 공부 > Java' 카테고리의 다른 글
[공부 필기] Java 기본 공부하기 (8) (0) | 2021.09.25 |
---|---|
[공부 필기] Java 기본 공부하기 (패키지) (0) | 2021.09.22 |
[공부 필기] Java 기본 공부하기 (6) (0) | 2021.09.15 |
[공부 필기] Java 기본 공부하기 (5) (0) | 2021.09.05 |
[공부 쉬어가기] Java: 변수명도 메모리 어딘가에 저장되어 있을까 (1) | 2021.09.01 |