chapter 6
클래스의 멤버 종류, 패키지, 접근 제한자.
멤버 종류
인스턴스 멤버와 this
- 인스턴스 멤버란 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드를 말하는데, 이들을 각각 인스턴스 필드, 인스턴스 메소드라고 부른다. 외부 클래스에서 이들을 사용하기 위해서는 우선 객체(인스턴스)를 생성하고 .을 이용해 필드와 메소드를 사용하자.
public class Car{
//필드
int gas;
//메소드
void setSpeed(int speed){
//...
}
}
// 외부 클래스
Car myCar = new Car();
myCar.gas = 10;
myCar.setSpeed(60);
=> 인스턴드 필드 gas는 객체마다 따로 존재하고, 인스턴트 메소드 setSpeed()는 객체마다 존재하지 않고 메소드 영역에 저장되고 공유된다.
- 객체 내부에서 인스턴트 멤버에 접근하기 위해 this를 사용할 수 있다. 주로 생성자와 메소드의 매개변수와 필드의 이름이 동일한 경우, 필드임 명시하고자 할 때 사용한다.
// 생성자
Car(String model){
this.model = mode; // 필드, 매개변수
}
// 메소드
void setModel(String model){
this.model = model;
}
정적 멤버와 static
-
정적이란 ‘고정된’ 이란 의미를 가지고 있다. 정적 멤버는 클래스에 고정된 멤버로서 객체를 생성하지 않고 직접 클래스 이름으로 접근하여 호출해야 한다. ex) Calculator.pi
-
정적 필드를 사용할때는 그 필드과 메소드가 각 객체마다 가지고 있을 필요가 없는 공용적인 데이터인 경우 정적 필드를 사용한다. => 예) pi는 변하지 않는 상수와 같은 개념으로 정적 필드를 사용하는 것이 좋다: static double pi = 3.14159;
-
정적 메소드의 경우, 인스턴트 필드, 즉 클래스 내부의 필드를 사용하지 않고 외부의 값만 사용한다면 정적 메소드를 사용하자. => 인스턴트 필드가 사용될 경우 인스턴트 메소드를 작성하고, 인스턴트 필드가 사용되지 않을 경우 정적 메소드를 작성하자.
public class Calculator {
String color; //인스턴트 필드
void setColor(String color){ //인스턴트 메소드
this.color= color;
}
static int plus(int x , int y){ // 정적(static) 메소드
return x+y;
}
static int minus(int x, int y){ // 정적 메소드
return x-y;
}
// 인스턴트 필드를 사용할때는 인스턴트 메소드,
// 인스턴트 필드를 사용하지 않을 경우에는 정적(ststic) 메소드를 사용하자.
// 그 이유는 인스턴트 필드를 사용하지 않을 경우, 굳이 객체를 생성하지 않고도
// 메소드를 부를 수 있기 때문이고 이게 적절하다.
}
정적 멤버 선언
- 정적 필드와 정적 메소드는 클래스에 고정된 멤버이므로 클래스 로더가 클래스(바이트 코드)를 로딩해서 메소드 메모리 영역에 적재 할 때 클래스별 로 관리된다. 따라서 클래스의 로딩이 끝나면 바로 사용할 수 있다.
- 정적 필드는 변하지 않고 공유되는 필드이기에 인스턴트 필드처럼 힙(heap) 영역에 있는 것이 아니라 메소드 영역에 할당됨을 명심하자.
정적 멤버 사용
//외부 클래스 double result1 = 10*10*Calurator.pi; int result2 = Calculator.plus(10,5); int result3 = Calculator.minus(10,5);
=> 사용은 위와 같이 객체를 생성해서 사용하는 것이 아니라 직접 클래스 이름으로 접근해야 하고 이것이 원칙이다. (객체참조 변수로도 접근이 가능하지만 이는 경고 표시(Warning)가 나므로 반드시 클래스 이름으로 접근하자.)
정적 초기화 블록
정적 필드는 다음과 같이 필드 선언과 동시에 초기값을 주는 것이 보통이다.
static double pi = 3.14159;
그러나 정적 필드가 계산이 필요한 초기화 작업이 있을 수 있고, 이런 경우 정적 필드는 new 연산자와 생성자를 통해 초기화할 수 없는데 어떻게 해야 할까? 해답은 정적 블록(static block)에 있다. 자바는 정적 필드 의 복잡한 초기화 작업을 위해서 정적 블록을 제공한다.
public class Television {
static String company = "samsung";
static String model = "LCD";
static String info;
static{
info = company + "-" +model;
}
public static void main(String[] args) {
System.out.println(Television.info);
}
}
=> 위와 같이 정적 필드 중 계산이 필요한 초기화 작업이 있는 경우 static 블록을 사용한다. 위의 경우 company = “samsung”, model = “LCD”이면 info는 “samsung-LCE”가 되고, company = “LG”, model = “LED” 이면 info는 “LG-LED”가 된다.
정적 메소드와 블록 선언 시 주의할 점
- 정적 메소드와 정적 블록 선언시 객체가 없어도 실행된다는 특징 때문에, 이들 내부에 인스턴트 필드나 인스턴트 메소드를 사용할 수 없고, 객체 자신의 참조인 this 키워드도 사용 불가능하다. => 사용하면 컴파일에러! (=> 정적 메소드와 정적 블록 자체가 객체에 대한 참조가 없으므로 이는 당연한 얘기다. 인스턴트 필드는 특히 힙 영역에 할당되어있기에.)
public class Calculator{
String color; //인스턴트 필드
static String mycolor;// 정적 필드
static void abs(String color){ // 정적 메소드
this.color = color; // 컴파일 에러
}
static { // 정적 블록
mycolor = this.color; // 컴파일 에러
}
}
싱글 톤
- 가끔 전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있다. 단 하나만 생성된다고 해서 이 객체를 singleton(싱글톤)이라 한다.
- 싱글톤을 만들려면 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 생성자 앞에 private를 붙여준다. 그리고 클래스 외부에서 정적 필드의 값을 바꾸지 않게 하기 위해 정적 필드에도 private를 붙여준다.
-
대신 외부에서 이 싱글톤을 호출할 수 있도록 정적 메소드인 getInstance를 선언하고 정적 필드에서 참조하는 자신의 객체를 리턴해준다. 내부의 필드를 건드리지 않고 외부에서 호출할 수 있는 방법이다. ```java public class Singleton { private static Singleton singleton; //싱글톤, private 붙여준다.
private Singleton(){ // 생성자 앞에 private 붙여준다.
}
static Singleton getInstance(){ //외부에서 싱글톤을 사용할 수 있도록 정적 메소드 getInstance 구현. return singleton; }
}
public class SingletonExample { public static void main(String[] args){ /* Singleton obj1 = new Singleton(); // 생성자 앞에 private 있으므로 사용 x, 컴파일 에러. Singleton obj2 = new Singleton(); */ Singleton obj1 = Singleton.getInstance(); Singleton obj2 = Singleton.getInstance();
//obj1 과 obj2는 같은 객체를 참조.
if(obj1 == obj2){
System.out.println("obj1과 obj2는 같은 객체를 참조합니다.");
}else{
System.out.println("obj1과 obj2는 서로 다른 객체를 참조합니다.");
}
//obj1과 obj2는 같은 객체를 참조합니다.
} }
#### final 필드와 상수
> final 필드
* final의 의미는 최종적이란 뜻을 가지고 있고, 따라서 final 필드는 초기값이 저장되면 이것이 최종적인 값이 되어서 프로그램 도중에 수정할 수 없는 필드라는 의미를 나타낸다.
```java
final 타입 필드명 [=초기값];
final 필드의 초기값을 줄 수 있는 방법은 두가지 밖에 없다.
- 필드 선언시에 초기화하는 방법
- 생성자로 초기화하는 방법
=>따라서 메소드로 final 필드를 초기화할 수 없다.
public class Person {
final String nation = "korea";
final String ssn;
String name;
public Person(String name, String ssn){
this.name = name;
this.ssn = ssn;
}
}
public class PersonExample{
public static void main(String[] args) {
Person person = new Person( "김도형","930928-1999999" ); //final 필드는 생성자로 초기화 가능 , 메소드 x!
System.out.println(person.nation); //korea
System.out.println(person.name); // 김도형
System.out.println(person.ssn); // 930928-1999999
//person.nation = "japan"; final 필드는 변경 불가능!
//person.ssn = "930928-1111111"; final 필드는 변경 불가능!
person.name = "옥자"; // 인스턴트 필드는 변경 가능.
}
}
상수(static final)
- 상수는 불변의 값으로 static final를 써서 사용한다. final 필드와 헷갈리지 말아야 하는데 final 필드는 초기화가 한번 필요한 필드인 것이고, 상수는 공용성을 띠고 있는 초기화가 필요없는 수이다. (final 필드는 여러 객체 생성시 한번 초기화하면 변하지 않는 값이지만 상수는 여러 객체에서 초기화 없이 사용할 수 있는 수이다. 개념 자체가 다르다.) => 상수는 주로 pi, 지구의 무게 및 둘레 처럼 변하지 않는 수의 개념이다.
static final 타입 상수1 [=초기값];
//선언만 하고 초기화 하지 않은 경우 static 블록 사용
static {
static final 타입 상수2 = 상수1 * 2;
}
패키지
- 패키지의 존재이유: 이름 충돌을 방지 위해서 존재한다. 클래스 이름이 같더라도 패키지가 다르다면 환경변수(path)가 달라 이름 충돌이 일어나지 않는다.
- 또 수백개의 클래스가 있을때 클래스관의 관계가 뒤엉켜서 복잡하고 난해한 프로그램이 될 수 있는 데 패키지를 만들어 따로 클래스를 만드다면, 질서정연하게 클래스들을 관리할 수 있다.
상위패키지.하위패키지.클래스
// 패키지는 클래스의 일부분이고 이는 클래스를 유일하게 만들어주는 **식별자 역할**을 한다.
패키지 선언
- 패키지는 클래스를 컴파일하는 과정에서 자동적으로 생성되는 폴더이다. 컴파일러는 포함 되어 있는 패키지 선언을 보고, 파일 시스템의 폴더로 자동 생성시킨다. 다음은 패키지를 선언하는 방법이다. ```java package 상위패키지.하위패키지;
public class ClasssName {…}
> 패키지 이름의 규칙
1. 숫자로 시작해서는 안되고, _,$를 제외한 특수 문자를 사용해서는 안된다.
2. java로 시작하는 패키지는 자바 표준 API에서만 사용하므로 사용해서는 안된다.
3. **모두 소문자로 작성하는 것이 관례이다.
> 패키지 선언이 포함된 클래스 컴파일
* 패키지 선언이 포함된 클래스를 터미널에서 컴파일할 경우, 단순히 javac ClassName.java로 컴파일 해선 안된다. 패키지 폴더가 자동으로 생성되려면 **javac 명령어 다음에 -d 옵션을 추가하고 패키지가 생성될 환경 변수를 지정하여 컴파일 하자.**
```java
javac -d . ClassName.java //<- 현재 폴더를 기준으로 패키지 생성 ( 상대 경로 사용)
javac -d /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
// <- 현재 폴더를 기준으로 패키지 생성 (절대 경로 사용)
import문
같은 패키지에 속하는 클래스들은 아무런 조건 없이 다른 클래스를 사용할 수 있지만 다른 패키지에 속하는 클래스를 사용하려면 두 가지 방법 중 하나를 선택해야 한다.
- 첫번째 방법: 패키지와 클래스를 모두 기술한다. ```java package com.mycompany;
public class Car{ com.hankook.Tire tire = new com.hankook.Tire(); // com.mycompany와 다른 패키지인 com.hankook. } // 불편하므로 두번째 방법(import)을 이용한다.
2. 두번째 방법: import문 사용하기.
```java
package com.mycompany;
import com.hankook.Tire; // import를 사용하여 다른 패키지인 com.hankook의 Tire 클래스를 불러온다.
public class Car{
Tire tire = new Tire(); // import를 선언 했으므로 패키지는 생략한다.
}
접근 제한자 : public, protected, default, private
접근 제한자 | 적용 대상 | 접근 할수 없는 클래스 |
---|---|---|
public | 클래스, 필드, 생성자, 메소드 | 없음 |
protected | 필드, 생성자, 메소드 | 자식 클래스가 아닌 다른 패키지에 소속된 클래스(자식이면 다른 패키지여도 가능!) |
default | 클래스, 필드, 생성자, 메소드 | 다른 패키지에 소속된 클래스 |
private | 필드, 생성자, 메소드 | 모든 외부 클래스 |