본문 바로가기

JAVA/혼공자

[혼공자] Chapter 06-4~6 메소드, 인스턴스 멤버, 패키지와 접근 제한자

06-4 메소드


자바에서 메소드의 선언 방법은 여느 프로그래밍 언어에서의 메소드 선언 방법과 비슷하다.

리턴타입 메소드이름 (매개변수타입 매개변수) { 실행문 }
ex. int add(int a, int b) { return a+b; }
* 메소드 이름의 길이는 실행 속도나 메모리와는 무관하니 가독성을 위해 길게 줘도 괜찮음

클래스 안에서 선언한 메소드는 dot연산자와 ( )를 이용해 호출할 수 있다.
Ex. Car.setNumber(9101);

▶ 매개 변수의 개수를 모르는 경우

=> 어찌보면 당연한 소리지만 기발한 아이디어 같아 정리함
매개변수의 개수를 모르는 경우,
Step 1 : 일단 메소드의 매개변수를 배열 형태로 선언한 다음
Step 2 : 전달할 변수를 배열로 만들어서 메소드에 전달하면 됨

배열을 메소드의 매개변수로 받는 방법 ① - classic ver.
이 경우 메소드에 전달하기 위한 배열이 반드시 필요.
없으면 new 연산자를 이용해서 만들어야 함
int sum(int[ ] values){ }
int result = sum(new int[ ] {1,2,3});

배열을 매소드의 매개변수로 받는 방법 ② - ... 이용
매개변수 선언문에서 [ ]대신 ... 사용
이 경우 배열을 생성하지 않고도 콤마를 이용해 배열의 컴포넌트들을 전달할 수 있음
int sum(int ... values){ }
int result = sum(1,2,3);
* classic 방법처럼 new int[ ] {컴포넌트}로 값을 전달해도 오류가 생기거나 하진 않음

→ 그냥 아이디어만 이해하고 세부적인 문법은 ①번 방법만 기억하면 될 듯!

▶ 메소드에서 return의 의미
return은 결과 값을 반환한다는 의미도 되지만, 메소드를 강제종료시키는 break 역할도 한다.
void 함수임에도 마지막에 return; 이 붙는 경우가 종종 있는데 그게 이런 이유 때문이다. 
따라서 return 이후에 실행문이 오면 Unreachable 오류가 발생한다.

05-5 인스턴스 멤버와 정적 멤버

☆ 약간 개념 어려울 수 있으므로 반복해서 보기! ☆
인스턴스 멤버 - 객체(인스턴스)마다 가지고 있는 고유의 멤버. 우리가 지금껏 배운 것들
정적(static) 멤버 - 모든 객체가 공유하는 멤버. 하나의 메모리에 있는 값을 여러 객체들이 참조한다고 생각하면 됨

필요 이유 : 모두가 같은 값을 갖게 하기 위해 (Ex. 전체 객체의 개수)
객체마다 메모리를 할당하지 않고 메모리를 딱 한번만 할당하게 하기 위해 → 메모리 절약

스태틱 멤버 선언 방법 : 변수나 메소드 앞에 static을 붙이면 됨
* static 필드의 값을 고정하고 싶으면 final을 붙이면 됨 → 값 변화 x

Q. static 필드는 값 통일을 위해 필요하다는건 알겠는데, static 메소드는 왜 필요함?🤔
=> 메모리 절약을 위해서, 모든 객체에서 메소드를 가지고 있게 하는게 아니라, 한 번만 메모리에 저장하기 위함임
즉, 인스턴스 필드를 필요로 하지 않는 (=개별 객체와 상관 없는) 메소드들은 static으로 선언하는게 좋음
* 만약 this.~~ 처럼 인스턴스 필드(=static 선언을 안한 필드)를 사용하는 메소드들에 static 선언을 한다면 컴파일 에러 발생

▶ static 멤버와 메모리 영역
⭐약간 어려움⭐
정적 멤버는 클래스에 고정된 멤버이므로 
클래스 로더가 클래스(바이트코드)를 로딩할 때 메소드 영역에 저장된다.
(복습) - 메소드 영역에는 클래스에 대한 정보가 분류되어 저장된다. 
따라서 객체를 선언하지 않아도 클래스 이름으로 접근하여 사용할 수 있다. (객체와 무관하게 동일하므로)
이렇게 static 메소드를 클래스 이름으로 접근하여 사용하게 하는게 권장되고 있다.
이클립스에서도 클래스 이름으로 접근하지 않고 객체로 접근했을 때는 경고 표시가 나타난다.

// Calculator.java
public class Calculator {
	static double pi = 3.14;

	static int plus(int x, int y) { // static 메소드
		return x+y;
	}

	static int minus(int x, int y) { // static 메소드
		return x-y;
	}
}
public class CalculatorExample {
	public static void main(String[] args) {
		
		// [클래스이름.정적멤버]로 정적 멤버에 접근
		double result1 = 10*10*Calculator.pi; 
		int result2 = Calculator.plus(10, 5);
		int result3 = Calculator.minus(10, 5);
		
		System.out.println("result1 : "+result1);
		System.out.println("result2 : "+result2);
		System.out.println("result3 : "+result3);
	}

}


▶ 싱글톤

싱글톤이란? 전체 프로그램에서 단 하나의 객체만 사용하는 것 (디자인 패턴의 일종이라고 한다.)
단 하나의 객체를 사용한다면, 메모리 절약과 데이터 공유라는 점에서 이득을 볼 수 있다.
static 필드, static 메소드와 같은 원리로 하나의 객체를 static하게 만든다고 이해하면 될 것 같다.

원리 : 생성자를 private으로 둔다면 new를 이용해서 새로운 객체를 만들 수 없다. 
따라서 객체를 사용하려면, 클래스 안에서 private static 필드로 공유할 객체를 만들고,
객체를 리턴하는 정적 메소드를 이용해야 한다. 
이때 이 정적 메소드가 리턴하는 객체는 static 필드이므로 언제나 동일하다. 
즉, 어디에서, 몇번을 호출하더라도 똑같은 객체만 리턴되므로 전체 프로그램에서 공통된 하나의 객체를 사용하게 된다. 

// Singleton.java
public class Singleton {
	
	// 외부에서 객체를 생성하지 못하게 생성자를 private으로 선언
	private Singleton() {};
	
	// 공유하게 될 하나의 객체 생성 - 외부 접근 막으므로 private, 공유할 것이므로 static
	private static Singleton singleton = new Singleton();
	
	// Singleton 객체를 반환하는 정적 메소드
	static Singleton getInstance() {
		return singleton;
	}

}
public class SingletonExample {

	public static void main(String[] args) {
		
		// Singleton obj1 = new Singleton(); - 생성자를 private으로 뒀으므로 생성자로 객체 생성 불가
		
		Singleton obj1 = Singleton.getInstance();
		Singleton obj2 = Singleton.getInstance();
		
		String massage = (obj1==obj2) ? "참조하는 객체가 동일하다." : "참조하는 객체가 다르다.";
		System.out.print(massage);
		
		// 동일한 객체를 참조하고 있음을 알 수 있음
	}

}


▶ final 키워드
final 키워드를 갖는 필드는 초기화 이후 값을 수정할 수 없다.
final 필드를 초기화하는 방법 
① 선언과 동시에 초기화
static final PI = 3.14;
② 필드를 선언할 때는 초기화하지 않고, 생성자에서 외부값을 받으면 초기화
final birth;
Person(String birth) {this.birth = birth;} // 생성자

▶ 상수와 final의 차이
상수 = 어떤 상황에서도 변하지 않고 일정한 값을 갖는 수
final = 초기화 이후에는 수정 불가능
이때 final은 생성자에서 어떻게 초기화 하는지에 따라 객체마다 달라질 수 있다.
(ex. 생일을 외부에서 받아 생성자에서 final로 저장하는 경우)
객체마다 달라진다는 것은 '어떤 상황에서도 같은 값을 갖는다'라는 상수의 정의를 만족하지 못하므로
final 만으로는 상수라고 하기 어렵다.

하지만, final의 속성을 갖으며 모든 객체에서 동일(static)하다면 상수라고 할 수 있다.
static final => 모든 객체에서 동일하며 변하지 않는 '상수'
이런 static final 변수는 관례적으로 모두 대문자로 작성한다.
ex. static final double EARTH_RADIUS = 6400; 

06-6 패키지와 접근 제한자

▶ 패키지가 필요한 이유
복습 - 패키지는 클래스를 담는 물리적인 디렉토리이다.
앞에서 우리는 객체지향 프로그래밍이란, 객체를 잘 조립하는 것이라고 배웠다. 
이런 객체 즉, 클래스의 유지 보수를 체계적으로 하기 위해서 패키지가 필요하다.

일례로, 패키지는 클래스의 식별자 역할을 한다. 
클래스의 이름이 같더라도 패키지가 다르면 다른 클래스로 인식한다.
심지어 JDK 11 이후 버전부터는 패키지가 없는 클래스를 생성하면 컴파일 에러가 발생하므로 
클래스는 반드시 패키지 안에서 생성해줘야 한다.

그냥 명분 상 '유지 보수를 위해' 있으면 좋은 정도가 아니다.
객체지향 프로그램인 자바에서는 클래스 관리가 핵심이므로 패키지는 필수적이다.

▶ 클래스의 전체 이름
클래스의 전체 이름은 [상위패키지.하위패키지.클래스]로 표시한다. 
패키지까지 포함하여 클래스의 전체 이름으로 보는 것이다.
Ex. sec05.verify.exam03

▶ import
다른 패키지에 있는 클래스를 사용하기 위해 import를 사용한다.
import문은 패키지 선언문과 class 선언문 사이에 작성한다.
(import가 외부에 있는걸 가져온다는건 알았지만, '외부'의 기준이 뭔지 잘 몰랐는데, 
이 외부가 '속해있는 패키지의 외부'라는 걸 기억하면 될 듯!)

* import 특이사항 ①
상위 패키지를 import 했다고 해서 하위 패키지의 클래스까지 import 되는 것은 아니다. 
자바는 패키지 전체 이름으로 패키지를 식별하므로 com.hankook과 com.hankook.project 를 다른 패키지로 인식한다.
따라서 import com.hankook.*; 으로 임포트 되는 클래스, import com.hankook.project*; 로 임포트 되는 클래스가 다르다는 것을 인지하고 있어야 한다.

com
   ㄴ hankook
       ㄴ a.class
       ㄴ b.class
       ㄴ project
           ㄴ c.class
           ㄴ d.class
위와 같은 상황에서 import com.hankook.*;는 a, b 클래스를
import com.hankook.project*; 는 c,  d클래스를 임포트 한다.

 

* import 특이사항 ②
보통 import를 하면 패키지 없이 클래스의 이름만 써도 된다.
하지만 다른 패키지에서 import한 두 클래스의 이름이 같은 경우, 패키지가 포함된 클래스의 '전체 이름'을 써야 한다.

▶ 접근 제한자
public - 외부 클래스가 자유롭게 사용 가능
protected - 같은 패키지에서 사용 가능 or 다른 패키지라면 자식 클래스에서 사용 가능
private - 속하는 클래스 안에서만 사용 가능
위의 접근 제한자가 적용되지 않으면 default가 적용됨
default - 같은 패키지에서만 사용 가능

* 클래스의 경우 public, default 접근 제어자를 갖을 수 있음
* 생성자를 선언하지 않아 추가되는 기본 생성자의 경우, 클래스의 접근 제어자를 따라감. -> public or default
* 생성자, 필드, 메소드는 위 4개의 접근 제어자를 모두 갖을 수 있음
* static과 함께 사용하는 경우, 접근 제어자를 더 앞에 사용함 ex. public static void main( )


▶ Setter, Getter
필드를 외부에서 접근할 수 있게 하면 무결성이 깨질 수 있기 때문에 
대부분 필드를 private으로 선언하고, setter, getter 메소드를 public으로 선언하여
외부에서는 메소드를 통해서만 필드에 접근할 수 있게 한다.
객체 지향 프로그래밍의 중요 원칙
Ex. car1.speed = -100의 경우, 속도는 0이 될 수 없으므로 객체의 무결성이 깨진다. 
하지만 setSpeed( ) 메서드를 이용하면, 수정하려는 속도가 0보다 작은지 if 문으로 검사 후 수정할 수 있으므로 무결성을 지킬 수 있다.

필드를 수정하는 메소드 : set___( )
필드의 값을 리턴하는 메소드 : get___( )
* 필드 타입이 boolean인 경우 is___( )를 getter로 사용한다.