# JAVA Ch06. 객체지향 프로그래밍1_2

uuuu.jini·2022년 2월 18일
0

JAVA -자바의 정석

목록 보기
6/18
post-thumbnail

목차

  1. 오버로딩
  2. 생성자
  3. 변수의 초기화

4. 오버로딩(overloading)

4.1 ] 오버로딩이란 ?

메서드는 각기 다른 이름을 가져야 한다. 그러나 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메서드를 정의할 수 있다.

한 클래스 내에서 같은 이름의 메서드를 여러개 정의하는 것을 메서드 오버로딩또는 간단히 오버로딩 이라고 한다. 하나의 메서드 이름으로 여러기능을 구현하기 때문에 붙여진 이름이다.

4.2 ] 오버로딩의 조건

  • 메서드 이름이 같아야 한다.
  • 매개변수의 개수 또는 타입이 달라야 한다.

메서드의 이름이 같아도 매개변수가 다르면 서로 구별할 수 있기 때문에 오버로딩이 가능하다. 위의 조건을 만족하지 않으면 중복 메서드 에러가 발생한다. 오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있으므로 반환타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다는 것에 주의하자.

4.3 ] 오버로딩의 예

대표적인 예는 println메서드이다. 실제로 println메서드의 호출시 매개변수 값의 타입에 따라 호출되는 println메서드가 달라진다.

매개변수의 이름만 다르고 타입과 수가 같은 경우 오버로딩이 성립하지 않는다. --> ... is already defined 에러가 발생한다.

매개변수의 타입과 개수가 같고 반환타입만 다른 경우에도 어떤 메서드가 호출되었는지 확인이 불가하므로 오버로딩으로 간주하지 않는다. --> 위와 동일한 에러가 발생한다.

서로 다른 매개변수 타입의 순서가 다른 메서드의 경우 오버로딩으로 간주한다.

	long add(int a, long b) { return a+b;}
    long add(long a, int b) {return a+b;}

같은 일을 하지만 매개변수를 달리해야하는 경우에 , 이름은 같고 매개변수를 다르게 하여 오버로딩을 구현하다.

4.4 ] 오버로딩의 장점

만일 메서드를 이름으로만 구분한다면, 한 클래스내의 모든 메서드들은 이름이 달라야 한다. 근본적으로 같은 기능을 하는 메서드들이지만, 서로 다른이름을 가져야 하기 때문에 이름을 짓고 어렵고 이름을 일일이 구분해서 기억해야 하기 때문에 서로 부담이 된다.

오버로딩을 사용하는 경우 하나의 이름으로만 기억하면 되므로 기억하기도 쉽고 이름도 짧게 할 수 있어 오류의 가능성을 많이 줄일 수 있다. 메서드의 이름만을 보고도 이 메서드들은 이름이 같으니 같은 기능을 하겠구나 라는 예측이 가능하다.

또하나의 장점은 이름을 절약할 수 있다. ( 이름 짓는 고민 하지 않는다 )

4.5 ] 가변인자(varargs) 와 오버로딩

기존에는 메서드의 매개변수의 개수가 고정적이었으나 JDK1.5부터 동적으로 지정해 줄 수 있게 되었다. 이 기능을 가변인자(varaiable arguments) 라고 한다.

타입... 변수명과 같은 형식으로 선언하면 printf()가 대표적인 예이다.

	public PrinStream printf(String format,Object.. args){...}

위와 같이 가변인자 외에도 매개변수가 더 있다면, 가변인자를 매개변수 중에서 가장 마지막에 선언해야 한다. 그렇지 않으면, 컴파일 에러가 발생한다. ( 가변인자인지 아닌지를 구별할 방법이 없기 때문이다. )

가변인자를 사용한 메서드에서는 인자로 배열을 받을 수 도 있다.

	String concateneate(String... str){...}
    concatenate(new String[]{"A","B"});

가변인자는 내부적으로 배열을 이요하는 것이다. 그래서 가변인자가 선언된 메서드를 호출 시 마다 배열이 새로 생성된다. (이런 비효율 -> 필요시만 사용 ! )

String concatenate(String[] str){ }처럼 매개변수의 타입을 배열로 하면, 반드시 인자를 지정해줘야 하기 때문에, 인자를 생략할 수 없다. null 이나 길이가 0인 배열을 인자로 지정해줘야 하는 불편함이 있다.

가변인자를 사용하는 메서드를 오버로딩하는 경우에 두 메서드가 구분되지 못하느 경우가 발생하기 쉽기 때문에 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋다.


5. 생성자 ( Constructor )

5.1 ] 생성자란 ?

생성자는 인스턴스가 생성될떄 호출되는 인스턴스 초기화 메서드 이다. 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성시에 실행되어야 할 작업을 위해서도 사용된다.

생성자 역시 메서드처럼 클래스 내에서 선언되며, 구조도 유사하지만 리턴값이 없다. void를 사용하지 않고 아무것도 적지 않는다.

  • 생성자의 이름은 클래스의 이름과 같아야 한다.
  • 생성자는 리턴 값이 없다.

생성자도 오버로딩이 가능하여 여러 생성자가 존재 할 수 있다.

	클래스이름( 타입 변수명, 타입 변수명, ...) {
    	//인스턴스 생성시 수행될 코드, 주로 초기화 코드
	}
	
    Card(){
    ...
    }
    Card(String k , int num){
    ...
    }

연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다. 생성자는 단순히 인슽너스변수들의 초기화에 사용되는 조금 특별한 메서드일 뿐이다.

생성자가 수행되는 과정을 단께별로 나누어보면 다음과 같다.

  1. 연산자 new에 의해 메모리(heap)에 클래스의 인스턴스가 생성된다.
  2. 생성자가 호출되어 실행된다.
  3. 연산자 new의 결과로 생성된 인스턴스의 주소가 반환되어 참조변수에 저장된다.

인스턴스를 생성할 떄는 반드시 클래스 내에 정의된 생성자 중의 하나를 선태갛여 지정해주어야 한다.

5.2 ] 기본 생성자 (default constructor )

모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다. 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었던 이유는 컴파일러가 제공한는 기본 생성자 덕분이었다.

생성자가 하나도 정의되어있지 않은 경우 기본생성자를 추가하여 컴파일 한다. 클래스이름() {} 형태이다.

매개변수도 없고 아무런 내용도 없는 아주 간단한 것이다. 특별히 인스턴스를 초기화 하지 않아도 되는 경우에 기본 생성자를 사용해도 좋다.

만약 클래스에 생성자를 정의하는 경우 기본생성자를 컴파일러가 자동으로 생성해주지 않는다. 그러므로 정의한 생성자로 인스턴스를 생성해야한다. 즉, 기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을때 뿐이다.

5.3 ] 매개변수가 있는 생성자

생성자도 메서드처럼 매개변수를 선언하여 호출 시 값을 넘겨받아서 인스턴스의 초기화작업에 사용할 수 있다. 인스턴스가 각기 다른 값으로 초기화되어야 하는 경우가 많기 때문에 메서드를 사용한 초기화는 매우 유용하다.

매개변수가 있는 생성자를 사용하면 인스턴스를 생성하는 동시에 원하는 값으로 초기화 할 수 있게 된다. ( 코드를 간결&직관적으로 만든다 . )

5.4 ] 생성자에서 다른 생성자 호출 - this() , this

생성자 간에도 서로 호출이 가능하다.

  • 생성자의 이름으로 클래스 이름 대신 this를 사용한다.
  • 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
	Car(String color){
    	door = 5; //첫 번째 줄
        Car(color,"auto",4); //에러 - 생성자의 두번쨰줄에서 다른 생성자 호출
        // 에러 - Car가 아닌 this(color..)로 생성자 호출 
        

생성자 내부에서 다른 생성자 호출시에는 클래스이름이 아닌 this를 사용해야 하며, 첫 줄 에서만 호출해야 한다. 첫 줄에서만 호출이 가능한 이유는 초기화 작업 도중에 다른 생성자를 호출하게 되면 호출된 다른 생성자 내에서도 멤버변수들의 값을 초기화 할 것이므로 다른 생성자를 호출하기 이전의 초기화작업이 무의미해질 수 있기 때문이다.

같은 클래스 내의 생성자들은 일반적으로 서로 관계가 깊은 경우가 많아서 서로 호출하도록 하여 유기적으로 연결해주면 더욱 좋은 코드를 얻을 수 있다. 수정이 필요한 경우에도 적은 코드만을 변경하면 되므로 유지보수가 쉬워진다.

Car(String color,String gearType,int door){
		this.color = color;
		this.gearType = gearType;
		this.door = door;
	}

위의 코드를 보면 생성자의 매개변수로 선언된 지역변수의 이름과 인스턴스변수의 이름이 같다. 이럴 경우 이름만으로는 두 변수가 서로 구별이 되지 않는다. 이런경우에는 인스턴스변수에 this 를 사용한다. 이렇게 하면 this.color는 인스턴스변수이고 color는 지역변수인 것을 쉽게 알 수 있다.

이처럼 생성자의 매개변수로 인스턴스 변수들의 초기값을 지정받는 경우가 많으므로 이름을 달리하는 것보다 인스턴스변수에 this를 붙이는 것이 더 명확하게 표현된다.

this 는 참조변수로 인스턴스 자신을 가리킨다. 참조변수를 통해 인스턴스 변수에 접근할수 있는 것처럼 this로 인스턴스 변수에 접근 할 수 있는 것이다. 하지만, this를 사용할 수 있는 것은 인스턴스 멤버뿐이다. static메서드에서는 인스턴스 멤버들을 사용하지 않는 것처럼 this역시 사용할 수 없다. ( static메서드는 인스턴스 생성없이 호출할 수 있으므로 인스턴스가 존재하지 않을 수 있기 때문 )

생성자를 포함한 모든 인스턴스 메서드에서는 자신이 관련된 인스턴스를 가리키는 참조변수 this가 숨겨진 채로 존재한다.

this : 인스턴스 자신을 가리키느 참조변수, 인스턴스의 주소가 저장되어 있다. 모든 인스턴스 메서드에 지역변수로 숨겨진 채로 존재한다.
this(), this(매개변수) : 생성자 , 같은 클래스의 다른 생성자를 호출할 떄 사용한다.

5.5 ] 생성자를 이용한 인스턴스의 복사

현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나더 만들고자 할 때 생성자를 이용할 수 있다. 두 인스턴스가 같은 상태를 갖는다는 것은 모든 인스턴스 변수가 동일한 값을 가지고 있다는 것이다.

	Car(Car c){
    	color = c.color;
        gearType = c.gearType;
        door = c.door;
	}

Car클래스의 참조변수를 매개변수로 선언한 생성자이다. 매개변수로 넘겨진 참조변수가 가리키는 Car인스턴스의 인스턴스 변수의 값을 인스턴스 자신으로 복사하는 것이다. ( Java Api 의 많은 클래스들이 인스턴스의 복사를 위한 생성자를 정의해놓고 있다. )

위와 같이 복사하여 인스턴스를 생성하면 서로 다른 메모리공간을 가지므로 서로 영향을 끼치지 않는다.

인스턴스를 생성할때는 다음의 2가지 사항을 결정해야 한다.

  1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
  2. 생성자 - 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?

6. 변수의 초기화

6.1 ] 변수의 초기화

변수를 선언하고 처음으로 값을 지정하는 것을 변수의 초기화 라고 한다. 필수적이기도 선택적이기도 하지만, 가능하면 선언과 동시에 적절한 값으로 초기화하는 것이 바람직하다. 멤버변수는 초기화를 하지않아도 자동으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지지만, 지역변수는 사용하기 전에 반드시 초기화해야한다.

멤버변수(클래스변수&인스턴스변수)와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적이다.

각 타입의 기본값

자료형기본값
booleanfalse
char'\u0000'
byte,short,int0
long0L
float0.0f
double0.0d 또는 0.0
참조형 변수null

멤버변수의 초기화 방법

  1. 명시적 초기화
  2. 생성자
  3. 초기화 블럭
  • 인스턴스의 초기화 블럭: 인스턴스변수를 초기화하는데 사용
  • 클래스 초기화 블럭: 클래스변수 초기화하는데 사용

6.2 ] 명시적 초기화 ( explicit initalization )

변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다. 가장 기본적이면서 가장 간단한 초기화 방법이므로 여러 초기화 방법 중에서 가장 운선적으로 고려되어야 한다.

	class Car{
    	int door = 4; //기본병 변수의 초기화
        Engine e = new Engine(); //참조형 변수의 초기화
        //...
	}

명시적 초기화는 간단하고 명료하지만 보다 복잡한 초기화 작업이 필요할 때는 초기화 블럭 또는 생성자를 사용해야 한다.

6.3 ] 초기화 블럭( initialization block )

초기화 블럭에는 인스턴스 초기화 블럭클래스 초기화 블럭 두가지 종류가 있다.

  • 클래스 초기화 블럭 : 클래스변수의 복잡한 초기화에 사용
  • 인스턴스 초기화 블럭 : 인스턴스변수의 복잡한 초기화에 사용

초기화 블럭을 작성하려면, 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}을 만들고 그 안에 코드를 작성하기만 하면 된다. 클래스 초기화블럭은 인스턴스 초기화 블럭 앞에 단순히 static을 붙이기만 하면 된다.

초기화 블럭내에서는 조건문,반복문 등을 자유롭게 사용할 수 있으므로, 복잡한 초기화 작업이 필요한 경우 사용한다.

	class InitBlock{
    	static{ //클래스 초기화 블럭 }
        { //인스턴스 초기화 블럭 } 
	}

클래스 초기화 블럭은 메모리 처음 로딩시 한번만 수행되며, 인스턴스 초기화 블럭은 인스턴스 생성시마다 수행된다. 생성자보다 인스턴스 초기화블럭이 먼저 수행된다. ( 인스턴스 변수의 초기화에는 주로 생성자를 사용하고 , 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행되어야 하는 코드를 넣는데 사용한다. )

초기화 블럭은 코드의 중복을 제거하며 코드의 신뢰성을 높여주고 , 오류의 발생가능성을 줄여준다는 장점이 있다. 재사용성을 높이고 중복을 제거하는 것이 바로 객체 지향 프로그래밍이 추구하는 궁극적인 목표이다.

package ch06_2;

public class BlockTest {
	static { 
		System.out.println("static {} ");
	}
	{
		System.out.println("{ }");
	}
	public BlockTest() {
		System.out.println("생성자");
	}

	public static void main(String[] args) {
		System.out.println("BlockTest bt1 = new BlockTest()");
		BlockTest bt1 = new BlockTest();
		System.out.println("BlockTest bt2 = new BlockTest()");
		BlockTest bt2 = new BlockTest();
		
		
	}

}

위의 예제가 실행되면 먼저 클래스가 로딩되면서 클래스초기화블럭이 실행되고 이후 인스턴스 생성시마다 인스턴스 초기화 블럭생성자가 차례로 수행된다. --> 클래스 초기화 블럭은 처음 클래스 로딩시에만 한번 수행되고 인스턴스 초기화 블럭은 인스턴스 생성시마다 매번 수행되는 것을 확인할 수 있다.

6.4 ] 멤버변수의 초기화 시기와 순서

  • 클래스변수의 초기화 시점 : 클래스가 로딩시 단 한번 초기화
  • 인스턴스변수의 초기화시점 : 인스턴스 생성시마다 인스턴스별로 초기화
  • 클래스 변수 초기화 순서 : 기본값-> 명시적 초기화-> 클래스 초기화 블럭
  • 인스턴스 변수 초기화 순서 : 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자

클래스는 클래스에 대한 정보가 요구 될때 메모리에 로딩된다. 즉, 해당 클래스가 이미 메모리에 로딩되어 있다면 또다시 로딩하지 않는다.

profile
멋쟁이 토마토

0개의 댓글