07-1 / 상속

부모 클래스(상위클래스)의 멤버를 자식 클래스(하위클래스, 파생클래스)에게 물려주는 것을 상속이라고 한다. 상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스를 수정되는 효과도 있어 유지보수 시간을 최소화 할 수 있다.

 


클래스 상속) 

자식 클래스에서 클래스를 선언할 때 어떤 부모 클래스로 부터 상속 받을 것인지 선택한다.

선택된 부모 클래스는 extends 와 함께 선언된다.

class 자식클래스명 extends 부모클래스명 {
}

 

자바 상속의 특징

- 다중 상속할 수 없다. 부모 클래스는 단 하나.

- 부모 클래스에서 private의 필드와 메소드는 상속 대상에서 제외.

- 부모와 자식이 다른 패키지에 존재한다면 default 접근 제하 멤버도 상속 대상에서 제외.

 

 


부모 생성자 호출) 

상속을 한 객체를 생성하면 부모 객체가 먼저 생성되고 그다음에 자식 객체가 생성된다

부모객체 생성자는 자식 생성자의 맨 첫 줄에서 호출 된다.

자식 생성자가 명시적으로 선언하지 않았다해도 컴파일러는 상속된 관계를 알게되면 자식 클래스 생성자 첫줄에 super()를 추가하게 된다. 이는 부모의 기본 생성자를 호출하는 메소드. 만약 직접 선언하고 싶다면 자식 클래스 생성자 첫줄에 명시적으로 선언하면 된다.

class 자식클래스 extends 부모클래스 {
    public 자식클래스 (){
    	super(); 
    }
}

직접 호출시 매개값을 주어 호출할 수도 있는데 매개값 타입과 일치하는 부모 생성자를 호출한다.

 

자식 클래스에서 super을 명시하지 않으면 자동으로 부모의 "기본" 생성자를 호출해야 하기 때문에 부모는 꼭 기본 생성자가 존재해야 한다. 자동으로 생성되는 super에서는 매개값이 있는 생성자를 호출하지 않는다. 

 

 


메소드 재정의 : 오버라이딩(Overiding) ) 

상속된 일부 메소드를 자식 클래스에서 다시 수정해서 사용하는 기능

 

  • 오버라이딩 방법

* 규칙

- 부모의 메소드와 동일한 시그너처(리턴타입, 메소드 이름, 매개 변수 목록)를 가져야 한다.

- 접근 제한을 더 강하게 재정의 할 수 없다. (반대로 자식이 더 약한 접근제한을 갖는 것은 가능하다.) 

- 새로운 예외(Exception)을 throw할 수 없다.

 

메소드가 재정의 즉 오버라이딩 되었다면 부모의 메소드는 숨겨서 메소드 호출시 오버라이딩된 자식 메소드가 호출된다.

오버라이딩 된 메소드위에 @Override 어노테이션을 쓰면 메소드가 재정의 된 것인지 컴파일러가 확인 해주어 개발자의 실 수를 줄일 수 있다.

 

@Override
부모보다약한접근제한자 부모와같은리턴타입 부모와같은이름 부모와같은매개값 {
	재정의
}

 


부모 메소드 호출) 

메소드를 오버라이딩하여 부모 메소드가 숨겨졌는데 부모 메소드를 호출해야하는 경우에는 super키워드를 사용하여 호출할 수 있다. 

super.부모메소드();

 


final 클래스와 final 메소드) 

final 키워드는 클래스, 필드, 메소드를 선언할 때 사용할 수 있는데, 해당 선언이 최종 상태이고 결코 수정될 수 없다는 의미이다.

 

상속할 수 없는 final 클래스

final은 최종 클래스이기 때문에 상속이 불가능해 부모클래스가 될 수 없다.

 

재정의 할 수 없는 final 메소드

메소드 역시 final키워드가 붙으면 최종 메소드이기 때문에 재정의 할 수 없다. 따라서 자식 클래스에서 오버라이딩이 불가능 하다. 

 

? final 필드는 어떨까
final 필드 경우는 final 클래스의 멤버가 아니라면 또는 접근 제한자 적 제한이 없다면 호출은 가능하지만 final속성에 맞게 새로운 값을 줄 수는 없다

protected 접근 제한자) 

protected는 public과 default 접근 제한의 중간에 해당 하는데, 같은 패키지의 경우에는 제한 제한이 없지만,

다른 패키지에서는 자식 클래스만 protected에 접근할 수 있다. 

자식 클래스라면 부모인 protected 필드, 생성자, 메소드에 접근이 가능하다.

단, new 연산자를 사용해서 생성자를 직접 호출 할 수는 없고, 자식 생성자에서 super()로 생성자를 호출해 사용할 수 있다.

 

 

 

 

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

자바에서는 클래스를 체계적으로 관리하기 위해 패키지(package)를 사용한다.

패키지의 물리적인 형태는 파일 시스템에 폴더에 해당하지만 그 뿐만 아니라 클래스의 일부분으로, 클래스를 "유일하게" 만들어주는 식별자 역할도 한다. 이름이 달라도 패키지가 다르면 다른 클래스로 인식된다.

상위패키지.하위패키지.클래스

 

 


패키지 선언) 

패키지 선언하는 방법은 아래와 같다.

package 상위패키지.하위패키지;

public class ClassName {...}

 

패키지는 클래스으 일부이기에 클래스만 따로 복사해서 다른곳으로 이동하면 클래스를 사용할 수 없다. 만약 이동해야한다면 패키지 전체를 이동해야한다.

 

패키지 이름에도 규칙이 있다

- 숫자로 시작 불가, $,_를 제외한 특수문자 불가

- java로 시작 불가 (자바 표준 API가 이런 형태로 사용하기 때문)

-모두 소문자 작성이 관례

 

  • import문

사용하고자 하는 클래스 또는 인터페이스가 다른 패키지에 소속되어 있다면 import문으로 해당 패키지의 이름과 함께 클래스 또는 인터페이스를 가져와야 한다.

import 상위패키지.하위패키지.클래스이름;
import 상위패키지.하위패키지.*; // 동일한 패키지 소속의 여러 클래스 사용시 이렇게 묶어줄 수 있다.

 

import문은 package선언문과 클래스 선언문 사이에 작성한다. import개수에 제한은 없다.

주의할 점은 상위패키지를 import했다고 하위패키지까지 모두 import되는 것은 아니다. 자바는 패키지 이름으로 식별하기 때문에 아래 두개를 별도의 패키지로 인지한다. 따라서 사용하려면 둘 모두 import해야한다. 

import 상위패키지;
import 상위패키지.하위패키지;

 

만약 이름이 동일한 클래스가 존재하고 두 패키지 모두 import되어 있다면 클래스 사용시 컴파일러가 어느것을 사용해야할 지 몰라 에러를 발생시키는 경우가 있다. 이때에는 어느 패키지의 어느 클래스를 사용한 것인지 실행문에서 명확하게 밝혀 사용해야 에러 발생을 피할 수 있다.

com.littlezero.coding.study study1 = com.littlezero.coding.study();
com.littlezero.programming.study study2 = com.littlezero.programming.study();

 

 


접근 제한자) 

클래스 및 인터페이스 그리고 이들이 가지고 있는 멤버의 접근을 제한하기 위한 제한자를 접근 제한자(Access Modifier)라고 한다.

객체 생성을 막기위해 생성자 호출을 막거나, 필드나 메소드를 사용하지 못하도록 막아야 하는 경우도 있다. 이때 사용한다.

 

종류

접근제한자 설명
public 외부 클래스가 자유롭게 사용할 수 있다.
protected 같은 패키지 또는 자식 클래스에서 사용할 수 있다.
private 외부에서 사용 될 수 없다.
위 3개 중 하나도 적용되지 않으면 default 접근 제한을 갖는다.
default 같은 패키지에 소속된 클래스에서만 사용할 수 있다.

 


클래스의 접근 제한) 

접근 제한자 같은 패키지 다른패키지 예외사항
default 제한없이 사용 가능 사용 불가능  
public 제한없이 사용 가능  

+ 클래스 선언시 public을 생략했다면 default 접근 제한을 갖는다.

 

 


생성자의 접근 제한) 

객체를 생성하기 위해서는 new 연산자로 생성자를 호출한다. 그러나 생성자가 어떤 접근 제한을 갖느냐에 따라 호출여부가 결정된다.

 

클래스에 생성자를 선언하지 않으면 컴파일러에 의해 자동으로 기본 생성자가 추가된다. 자동으로 생성되는 기본 생성자의 접근 제한은 클래스의 접근 제한과 동일하다. 

 

접근 제한자 같은 패키지 다른패키지 예외사항
default 제한없이 호출 가능 호출 불가능  
public 제한없이 호출 가능  
protected 제한없이 호출 가능 호출 불가능 *(예외있음) 다른 패키지에 속한 클래스가 해당 클래스의 자식 클래스라면 호출 가능
private 호출 불가능 *(예외있음) 클래스 내부에서만 호출 가능

 

 


필드와 메소드의 접근 제한) 

필드와 메소드 선언시 사용 범위 지정

접근 제한자 같은 패키지 다른패키지 예외사항
default 제한없이 사용 가능 사용 불가능  
public 제한없이 사용 가능  
protected 제한없이 사용 가능 사용 불가능 *(예외있음) 다른 패키지에 속한 클래스가 해당 클래스의 자식 클래스라면 사용 가능
private 사용 불가능 *(예외있음) 클래스 내부에서만 사용 가능

+ 필드 메소드 선언시 public을 생략했다면 default 접근 제한을 갖는다.

 

 


Getter와 Setter 메소드) 

객체 지향 프로그래밍에서는 객체의 필드를 객체 외부에서 직접적으로 접근하는 것을 막는다.

외부에서 마음대로 변경이 가능하다면 객체의 무결성이 깨질 수 있기 때문이다.

이런 문제점에 객체 지향 프로그래밍에서는 메소드를 통해서 필드를 변경하는 방법을 선호한다. 메소드를 통해 매개값을 검증하여 유효한 값만 객체의 필드로 저장할 수 있게 하는데 이런 역할을 하는 메소드가 Setter이다.

 

선언 방법은 void set + 필드이름(필드이름첫글자 대문자) 로 명명하며 넣을 값을 매개값으로 받는다.

 

void setSpeed(int speed){
	if(speed < 0) {
    	this.speed = 0;
        return;				// 진짜 왜 넣지?
    } else {
    	this.speed = speed;
    }
}

public void setStop(boolean stop){
	this.stop = stop;
}

 

외부에서 객체 데이터를 읽을 때도 메소드를 사용하는 것이 좋다. 메소드로 필드값을 사용하기 적절하게 필드값으로 가공한 후 외부로 전달하는 역할을 하는 메소드는 Getter이다. 

 

선언 방법은 필드타입 get + 필드이름(필드이름첫글자 대문자) 로 명명한다.

받으려는 필드타입이 boolean타입일 경우는 get으로 시작하지 않고 is로 시작하는 것이 관례다.

double getSpeed(){
	double km = speed * 1.6;
    return km;
}

boolean isStop(){
	return stop;
}

 

클래스 선언시 가능하다면 필드를 private 선언해 외부로 부터 보호하고 필드에 대한 Setter와 Getter 메소드를 작성해 필드값은 안전하게 변경/사용하자.

 

 

 

06-5 / 인스턴스 멤버와 정적 멤버

클래스에 선언된 필드와 메소드가 모두 객체 내부에 포함되는 것은 아니다. 일부는 객체에 포함되고 일부는 객체에 포함되지 않고 클래스에 남아 있는 것도 있다. 왜 그럴까?

객체마다 모든 필드와 메소드가 동일하다면 경우에 따라 메모리 낭비가 되거나 불필요한 처리가 이루어 질 수도 있다. 자바는 이런 경우를 위해 객체마다 가지고 있는 인스턴스 멤버와 클래스에 위치시키고 객체들이 공유하는 정적 멤버로 구분해 선언할 수 있게 하고 있다. 

 


인스턴스 멤버와 this) 

  • 인스턴스 멤버 정의

객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드를 말하며,  각각 인스턴스 필드, 인스턴스 메소드라 한다.

 

  • 인스턴스 멤버 선언
public class 클래스이름 {
    //필드
    타입 변수명;
    
    //메소드
    타입 메소드명 ([매개값..]){...}
}

 

인스턴스 필드는 객체마다 따로 존재하고, 인스턴스 메소드는 메소드 영역(메모리 영역 중 Method Area 또는 Class Area라고 불리는 영역)에 저장되고 공유된다. 인스턴스는 객체에 소속된 멤버이지만 메소드는 코드 블록이므로 객체마다 동일한 코드 블록을 가질 필요가 없기 때문에 객체와 함께 힙 영역에 저장되는 것이 아니라, 메소드 영역에 저장되고 공유된다. 

그럼 왜 이 메소드에 인스턴스라는 이름이 붙을까?

바로 이 메소드는 인스턴스 필드를 사용하기 때문이다. 인스턴스 필드는 객체(인스턴스)가 없으면 사용 불가능하다. 따라서 이 필드를 사용하기 위해 메소드도 객체가 필요해지기 때문에 그 영향력으로 인스턴스 이름이 붙은 것.

 

  • this

객체 외부에서 인스턴스 멤버에 접근하기 위해 참조변수를 사용하는 것처럼 객체 내부에서는 인스턴스 멤버에 접근하기 위해 this를 사용할 수 있다. this는 주로 매개변수와 필드 이름이 동일할 경우 인스턴스 멤버인 필드를 명시할 때 사용.

public class Car{

    String model;

    Car (String model){
        this.model = model;
    }
    
    void setModel (String model){
    	this.model = model;
    }
}

 

 


정적 멤버와 static) 

정적(static)은 고정된 이라는 뜻이다. 정적 멤버는 클래스에 고정된 멤버로 객체를 생성하지 않고 사용할 수 있는 필드와 메소드이다. 각각 정적 필드, 정적 메소드라고 한다. 

 

  • 정적 멤버 선언

정적 멤버 선언시 static 키워드를 붙이면 된다. 

public class 클래스 {
    // 정적 필드
    static 타입 필드 [= 초기값];
    
    // 정적 메소드
    static 리턴타입 메소드 ([매개값...]) {...}
}

 

정적 멤버는 클래스 로더가 클래스(바이트 코드화된 파일)을 로딩하여 메모리 영역에 적재할 때 클래스 별로 관리한다. 따라서 클래스 로딩이 끝나면 바로 사용 가능하다.

 

 

  • 정적 멤버와 인스턴스 멤버를 선언하는 기준

    * 필드

           -정적 필드 : 객체 마다 가질 필요 없는 변하지 않는 공용 데이터

           -인스턴스 필드 : 객체마다 가지고 있으면 다른 데이터

    * 메소드

           -정적 메소드 : 인스턴스 필드를 포함하고 있지 않다면 정적 메소드로 선언

           -인스턴스 메소드 : 인스턴스 필드를 포함하고 있다면 인스턴스 메소드로 선언

 

 

  • 정적 멤버 사용

클래스 이름과 함께 도트(.)연산자로 접근하여 사용한다. 

클래스.필드;
클래스.메소드([매개값...]);

객체 이름으로 접근하는 것도 가능하지만 원칙적으로는 클래스 이름으로 접근하는 것이 좋다. 

 

 

 

  • 정적 메소드 선언 시 주의할 점

객체가 없이도 실행된다는 특징 때문에 정적 메소드는 내부에 인스턴스 필드나 인스턴스 메소드를 그냥 사용할 수 없다.

또한 객체 자신의 참조인 this 키워드도 사용할 수 없다.

따라서 정적 메소드에서 인스턴스 멤버를 사용하고 싶다면 같은 클래스에 있어도 객체를 먼저 생성하고 참조 변수로 접근해야 한다. 

우리가 실행을 위해 매일 작성하면 main() 메소드도 정적 메소드이기 때문에 인스턴스 멤버를 사용하기 위해 객체를 생성하는 과정을 거친다.

 

 


싱글톤(Singletone)) 

프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있는 데 이 경우에서 만들어진 단 하나의 객체를 싱글톤이라고 한다. 

싱글톤을 만들려면 클래스 외부에서 new연산자로 생성자를 호출할 수 없도록 막아야 한다. 생성자 호출 수 만큼 객체가 생성되기 때문인데, 호출을 막기위해 생성자 앞에 private 접근 제한자를 붙여주면 막아줄 수 있다.

그리고 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화한다. (클래스 내부에서도 new 연산자로 생성자 호출이 가능하다. )

정적 필드 역시 외부에서 접근하여 값을 수정하는 걸 막기위해 그 앞에 private 접근 제한자를 붙인다.  

이렇게 필드와 생성자의 접근을 막아 하나의 객체만을 생성하게 했다면 메소드를 통해 정적 필드가 참조하고 있는 자신의 객체를 리턴하여 외부에서 이 싱글톤을 얻게 한다.

public class 클래스 {
    // 정적 필드
	private static 클래스 변수명 = new 클래스 ();  // 예시나 문제에서 변수명을 singleton으로 통일했는데 그렇게 쓰는 게 관례인가?
    
    // 생성자
    private 클래스 () {...}
    
    // 정적 메소드 
    static 클래스 메소드이름 (){
    	return 위의정적필드;
    }
}

//------------------------- 예시

public class Singleton {
    private static Singleton singleton = new Singleton();
    
    private Singleton() {...}
    
    static Singleton getInstance() {
    	return singleton;
    }	
}

 

이렇게 싱클톤으로 만든 객체는 getInstance()를 통해 단 하나의 객체만 리턴하기 때문에 메소드를 여러번 호출해 저장해도 동일한 객체를 참조한다.

클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();

 

+ 예시나 문제에서 변수명을 singleton으로 통일했는데 싱글톤 사용할 때 그렇게 쓰는 게 관례인가?

 


final 필드와 상수) 

  • final 필드

최종적이라는 의미와 같이 초기값이 한번 저장되면 이후 수정될 수 없는 최종값이라는 뜻이다.

final 타입 필드명 [= 초기값];

* final에 초기값을 주는 방법

     - 필드 선언시 함께 지정

     - 필드 선언시 초기값이 지정되지 않았다면 생성자에서 초기값 지정 (만약 생성자 안에서 final 필드가 최종 초기화 되지 않고 남았다면 컴파일 에러 발생)

 

 

  • 상수

불변의 값 (static final) 을 저장하는 변수.

변하지 않는 다는 점에서 final도 상수인가 생각할 수 있지만

final 필드는 "객체"에 저장되고 생성자의 매개값을 통해서 여러값을 가질 수 있기 때문에 상수가 될 수 없다.

 

상수는 객체에 존재하지 않고 "클래스"에만 존재한다. 또한 한번 저장하면 변경될 수 없다. 

static final 타입 상수 = 초기값;

 

상수 이름은 모두 대문자로 작성하는 것이 관례이며 여러단어 혼합시 _(언더바)로 단어를 연결한다. 

static final double PI = 3.14159;
static final double EARTH_RADIUS = 6400;

 

 


 

'Programming > Java' 카테고리의 다른 글

혼공자바] 07-1 상속  (0) 2022.11.30
혼공자바] 06-6 패키지와 접근 제한자  (0) 2022.11.29
혼공자바] 06-4 메소드  (0) 2022.11.26
혼공자바] 06-3 생성자  (1) 2022.11.24
혼공자바] 06-2 필드  (0) 2022.11.23

 

06-4 / 메소드

  • 메소드의 정의

객체의 동작에 해당하는 중괄호 {} 블록을 말하며 이 {}의 이름이 메소드 이름이다. 

메소드의 이름으로 호출하게 되면 중괄호 내의 모든 코드들이 일괄적으로 실행된다. 

 

메소드 선언은 선언부와 실행 블록으로 구성되는데 아래와 같이 선언되며 {}를 제외한 나머지 부분이 선언부며 이를 메소드 시그니처라고 한다.

리턴타입 메소드이름 ([매개변수1, 매개변수2...]){
}
리턴 타입 메소드가 리턴하는 결과의 타입 표시
메소드 이름 기능을 알 수있게 그리고 식별자 규칙에 맞게 명명해야 한다.
매개 변수 선언 선택사항으로 메소드 실행시 필요한 데이터를 받기 위한 변수.
메소드 실행 블록 실행할 코드를 작성

 

 


매소드 선언) 

  • 리턴 타입

메소드를 실행한 후의 결과값인 리턴 값의 타입을 말한다. 

 

메소드는 리턴 값의 자료형에 따라 메소드 이름 앞에 그 타입을 적어두게 되는데 이때의 패턴은 크게 두가지로 나눌 수 있다.

리턴값이 없는 메소드 void 무효라는 의미로 리턴이 없는 자료형을 의미
리턴값이 있는 메소드 해당 자료형 돌려줄 리턴값의 타입에 따라 다르게 선언

 

 

  •  메소드 이름

식별자 규칙에 맞게 작성해야 한다.

- 숫자로 시작 불가

- $ (달러사인), _(언더바) 제외한 특수 문자 사용 불가

- 관례적으로 메소드이름은 소문자 사용, 단 여러단어 혼합시 뒤이어 오는 단어 첫글자는 대문자.

void run(){...}
void startEnging(){...}
int[] getScore(){...}
String getName(String name){...}

 

 

  • 매개 변수 선언

메소드가 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용된다. 필요한 경우에만 매개변수를 선언해 사용하며, 매개변수가 필요없는 경우도 있다.

double divide(int x, int y){
	double result = (double)x / (double)y;
    return double;
}
double result = divide(10, 20);
System.out.println(result);
// 0.5

 

 

  • 매개 변수의 개수를 모를 경우

변수의 개수를 입력해야 될지 모를때에는 매개변수를 배열 타입으로 선언하는 것으로 해결할 수 있다. 

int sum(int[] values){ }

매개값인 배열 항목 수는 호출할 때 결정된다.

 

매개 변수를 배열 타입으로 선언하면 메소드 호출전에 배열을 생성해야하는 불편함이 있는데 이는 매개값 위치에 값 목록을 바로 넣어 줌으로써 값을 바로 넘기는 방법도 있다.

- 원래라면

int[] arr = {1,2,3};
int result = sum1(arr);
//또는
int result = sum1(new int[]{1,2,3});

- 바로 값 목록을 넘긴다면

int sum1 (int ... values) {} // 매개값의 타입 뒤에 ...은 생략이 아닌 저 표기로 써야 적용
int result = sum1(1,2,3);

 

 


리턴(return) 문) 

  • 리턴값이 있는 메소드

리턴 타입이 있는 메소드는 반드시 리턴문을 사용해서 리턴값을 지정해야한다. (없으면 컴파일 에러 발생)

이런 메소드는 return문이 실행되면 메소드는 리턴값을 반환하고 즉시 종료 된다.

return 리턴값;

return문의 리턴값은 리턴 타입이거나 리턴 타입으로 변환 될 수 있어야 한다.

(ex. byte, short, int는 int값으로 리턴되는 메소드의 리턴값이 될 수 있다.)

 

 

  • 리턴값이 없는 메소드 : void

리턴값이 없는 메소드는 타입으로 void를 선언한다. 그러나 이 메소드에서도 return문을 사용할 수 있는데 용도는 값을 반환하는 용도가 아니라, 메소드를 종료시키기 위한 용도이다.

return;

 

 


메소드 호출) 

메소드는 클래스 내.외부의 호출에 의해 실행된다.

같은 클래스 안에서는 메소드의 이름만으로 호출이 되지만 클래스 외부에서 호출할 경우에는 우선 클래스로부터 객체를 생성한 뒤 참조 변수를 이용해 메소드를 호출해야 한다. 객체가 있어야 메소드도 존재하기 때문이다.

 

  • 객체 내부에서 호출

같은 클래스 안에서는 아래와 같이 호출이 가능하다.

메소드이름 ([매개값1, 매개값2 ... ]);

리턴값이 없어도, 리턴값이 있어도 호출이 가능하다. 다만, 리턴값이 있는 경우에는 앞에 변수를 선언하고 리턴값을 대입한다. (이때 변수는 리턴값과 동일하거나 리턴값이 자동 변환가능한 타입이어야 한다.)

타입 변수명 = 메소드([매개값1, 매개값2...]);

 

  • 객체 외부에서 호출

외부 클래스에서 메소드를 호출하려면 우선 다음과 같이 클래스로부터 객체를 생성해야 한다. 메소드는 객체에 소속된 멤버이므로 객체가 존재하지 않으면 메소드도 존재하지 않는다. 

클래스이름 참조변수명 = new 클래이스이름 ([매개값1, 매개값2, 매개...]);

위와 같이 선언되었다면 도트(.)연산자를 사용해서 메소드를 호출할 수 있다. 도트 연산자는 객체 접근 연사자로 객체가 가지고 있는 필드나 메소드에 접근할 때 사용된다.

참조변수명.메소드이름([매개값1,매개...]); // 리턴값이 없거나 리턴값을 받을 필요 없을때
타입 변수명 = 참조변수명.메소드이름([매개값1,매개...]); // 리턴값이 있고, 리턴값을 받고 싶은 때

 

 


메소드 오버로딩) 

클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것을 메소드 오버로딩이라 한다.

오버로딩은 매개값을 다양하게 받아 처리할 수 있도록 하기 위해 사용된다. 

하나의 메소드 이름으로 여러기능을 담는다 하여 붙여진 이름으로

오버로딩 조건은 매개 변수의 타입, 개수, 순서 가 전부 달라야 한다는 점에 있다. 

// 모두 오버로딩 되어 별개 기능 사용가능
int plus (int x, int y) {...}
int plus (int x, int y, int z) {...}
int plus (double x, double y) {...}
int plus (int x, double y) {...}
int plus (double x, int y) {...}

위의 메소드들은 모두 plus라는 이름을 가지고 있지만 다른 메소드들로 별개의 기능이 가능하다.

그러나 아래는 각각 동일한 메소드다. 타입과 개수, 순서 모두 동일하기 때문 변수명을 달리했다고 달라지는 것은 아니다. 

// 아래 두개 동일
int plus (int x, int y) {...}
int plus (int num1, int num2) {...}

// 아래 두개 동일
int plus (String operater, int number) {...}
int plus (String calculator, int num) {...}

JVM은 메소드를 호출할 때 먼저 매개값을 체크해 메소드를 선택한다.

그렇다면 아래와 같은 경우는 어떨까

int plus (int x, int y) {...}
double plus (double x, double y) {...}

int num1 = 10;
double num2 = 30.0;

plus(num1, num2); 

// double 타입으로 반환하는 plus가 실행되며,
// 컴파일 에러가 발생하지 않고 num1이 double로 자동변환되어 메소드를 실행한다.

들어가는 변수와 매개값의 타입이 일치하지 않는 상황에 모두 컴파일 에러가 뜨는 건 아니다. 

매개값의 타입으로 자동 변환이 가능한 변수의 경우, 자동변환 여부를 검사해 가능하면 변환하여 그에 맞는 메소드를 선택해 실행한다.

 

오버로딩된 메소드들 중 우리가 가장 친근하게 본 것을 꼽자면 System.out.println(); 일텐데 우리는 다 같은 거라고 생각했지만 실제로는 들어가는 매개값 타입에 따라 여러 System.out.println 중 하나를 호출해서 사용한 것이다.

void println() {...}
void println(boolean x) {...}
void println(char x) {...}
void println(char[] x) {...}
void println(double x) {...}
void println(float x) {...}
void println(int x) {...}
void println(long x) {...}
void println(Object x) {...}
void println(String x) {...}
...

 

 

 

 

 


Question
+ 매개변수 개수를 모를 때 배열로 넘기는데 main 메소드의 args라는 이름의 String[] 배열도 그런걸까? 
그럼 args에는 뭐가 넘어오는 걸까? 

 

 

 

06-3 / 생성자

  • 생성자의 정의

생성자는 new 연산자로 클래스로투버 객체를 생성할 때 호출되어 객체의 초기화를 담당한다. 객체 초기화란 필드를 초기화하거나 메소드를 호출해 객체를 사용할 준비를 하는 것을 말한다. new 연산자에 의해 생성자가 성공적으로 실행되면 힙 영역에 객체가 생성되고 객체의 번지가 리턴된다. 리턴된 객체 번지는 클래스 변수에 저장된다. 


기본 생성자) 

모든 클래스는 생성자가 반드시 존재하며 하나 이상 가질 수 있다. 만약 생성자 선언을 생략했다면 컴파일러는 다음과 같이 기본 생성자를 바이트 코드에 자동 추가한다.

public class Main {
	public Main(){ } // 기본 생성자 자동 추가
}

 

그래서 클래스에 생성자를 선언하지 않아도 new 연산자 뒤에 기본 생성자를 호출해서 객체를 생성할 수 있다.

만약 클래스에 명시적으로 선언한 생성자가 1개라도 있다면 기본 생성자를 추가하지 않는다. 그런데 따로 명시적으로 생성자를 선언하는 이유는 다양한 값으로 초기화하기 위해서이다.

 

 

 


생성자 선언) 

클래스이름과 동일한이름 (매개변수, ... ) {

} // 생성자 블록

생성자는 메소드와 비슷한 모양잊만 리턴 타입이 없고, 클래스 이름과 동일하다. 

매개변수 선언은 생략할 수도 있고 여러개를 선언할 수도 있다. 매개변수는 new연산자로 생성자를 호출할 때 외부값을 생성자 블록 내부로 전달하는 역할을 한다.

 

만약 클래스에 명시적으로 선언된 생성자에 매개변수가 있다면 기본 생성자로 호출해서 객체를 생성할 수 없고 매개변수를 포함해 호출해야 객체를 생성해야 한다.

public class Car{
	Car(String model, String color, int maxSpeed){...}
}

// new Car(); - X
// new Car(casfer, kaki, 100); - O

 

 

 


필드 초기화) 

객체가 생성될 때 필드는 기본 초기값으로 자동설정.

다른 값을 주고 싶다면, 필드 선언할때 초기값을 따로 주는 방법, 또는 생성자에서 초기값을 주는 방법이다.

 

- 초기값을 준 경우

public class Korean {
	String nation = "대한민국";
    String name;
    String ssn;
}

nation이라는 필드에 대한민국이라는 초기값을 선언했다. 이는 Korean클래스로 부터 객체를 생성될 때 마다 동일하게 적용된다. 

 

- 객체 생성시 생성자에서 초기화 하며 초기값을 주는 경우

public class Korean {
	String nation = "대한민국";
    String name;
    String ssn;

	public Korean (String name, String ssn){
    	this.name = name;	// this.필드 = 매개변수
    	this.ssn = ssn;		
    }
}

매개변수 일름은 헷갈리지 않게 사용될 필드와 비슷하게 또는 동일하게 사용하는 것이 좋다. 

 

하지만 매개변수와 필드 이름이 동일하다면 생성자 내부에서 해당 필드에 접근 할 수 없다. 동일한 이름일 경우 우선순위는 매개 변수가 더 높기 때문. 이럴때 필드에 접근하기 위해 this를 붙이게 된다. 이 this는 객체 자신을 의미한다. 

 

생성자에서 초기화를 할때는 중요한 몇개 필드만 매개 변수를 통해 초기화되고 나머지 필드는 선언시에 초기화 하거나 생성자 내부에서 임의의 값 또는 계산 된 값으로 초기화 한다.

 

 

 


생성자 오버로딩) 

외부로 부터의 다양한 데이터들을 이용하기 위해 객체를 초기화 하려면 생성자도 다양해야 할 필요가 있다. 

여러가지 요구 조건을 수용할 수 있는 다양한 객체를 만들기 위해 자바에서는 생성자 오버로딩(Overloading)을 제공한다. 

이는 매개변수를 달리하는 생성자를 여럿 선언하는 것을 말한다.

public class Car{
	Car(){...}
    Car(String model){...}
    Car(String model, String color){...}
    Car(String model, String color, int maxSpeed){...}
}

주의) 매개변수의 타입과 개수 그리고 순서가 동일하다면 변수이름을 바꾼다고 생성자 오버로딩이 되는 것은 아니다. 결국 동일한 생성자.

Car(String model, String color){...}
Car(String color, String model){...}
// 타입, 개수, 순서도 다를거 없어서 결국 동일

 

 

 

 


다른 생성자 호출: this() )

생성자는 또다른 생성자를 호출할 수도 있다.

필드 초기화 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지는 방법으로도 사용할 수 있다.

this()는 자신의 다른 생성자를 호출하는 코드는 반드시 생성자의 첫 줄에서만 허용된다.

this()의 매개값은 호출되는 생성자의 매개 변수에 맞게 제공해야한다. this()로 호출된 생성자 실행이 끝나면 다시 원래 생성자로 돌아와 다음 실행문을 진행한다. 

 

 

//  Car.java

public class Car {
    String company = "현대자동차";
    String model;
    String color;
    int maxSpeed;

    Car(){
        model = "casfer";
        color = "kaki";
        maxSpeed = 100;
    }

    Car(String model){
        this();
        this.model = model;
    }

    Car(String model, String color){
        this();
        this.model = model;
        this.color = color;
    }

    Car(String model, String color, int maxSpeed){
        this();
        this.model = model;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }
}
// CarExample.java

public class CarExample {
    public static void main(String[] args){
        Car car1 = new Car();
        System.out.println(car1.company);
        System.out.println(car1.model);
        System.out.println(car1.color);
        System.out.println(car1.maxSpeed);

        System.out.println("----------------------------");
        Car car2 = new Car("avante");
        System.out.println(car2.model);

        System.out.println("----------------------------");
        Car car3 = new Car("sonata","black");
        System.out.println(car3.model);
        System.out.println(car3.color);

        System.out.println("----------------------------");
        Car car4 = new Car("tucson","navy",130);
        System.out.println(car4.model);
        System.out.println(car4.color);
        System.out.println(car4.maxSpeed);
    }
}

- 출력결과

 

+ Recent posts