본문 바로가기

Java/Study

Ch6 객체지향프로그래밍 - 필드, 생성자

Object-Oriented Programming

 

객체

객체란 처리할 하나의 자료로, 이 객체를 속성(필드)과 동작(메소드)으로 나누는 것을 객체 모델링이라고 한다.

각각의 부품객체를 만들어서 조립하여 요구사항을 처리한다.

 

cf. 추상화 : 프로그램을 만드는 데 필요한 자료만 추출하는 것으로, 비즈니스에서 필요한 객체만을 뽑아내는 과정

 

물리적 vs. 논리적 자료

물리적 : 실제자료 (ex.홍길동) 논리적 : 개념자료 (ex.선생님,강의)

 

객체 간의 관계

집합관계 : 완성품이 부품들을 포함한다

상속관계 : 상위 객체(주로 종류)를 기반으로 하위 객체(구체적인 사물)를 생성하는 관계

사용관계 : 객체 간의 상호작용(메소드 호출)

 

객체 지향의 프로그래밍

캡슐화(Encapsulation) : 객체의 필드, 메소드를 하나로 묶고 실제 구현 내용을 감추는 것으로, 감춰준 자료(필드,메소드)를 클래스 안에서만 쓸 수 있게 한다. 캡슐화된 멤버를 노출시킬 것인지, 숨길 것인지를 결정하기 위해 접근제한자(Access Modifier)를 사용한다. 즉, 객체의 필드와 메소드의 사용 범위를 제한한다.

 

클래스의 접근제한자 : 만드는 클래스를 다른 클래스에서 사용할 때 어디까지 접근권한을 줄 것인가

package - 해당하는 패키지에서만 | private - 쓸 수 없음 

protected - 상속받은 클래스에서만 

final - 마지막 클래스에서만

 

 

다형성(Polymorphism) : 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질이다. 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있게 한다. 부모 타입에는 모든 자식 객체가 대입될 수 있고 인터페이스 타입에는 모든 구현 객체가 대입될 수 있다. 그러므로 객체의 부품화가 가능하다.  (자식 객체가 생성되면 부모 타입으로 만들어주어 사용한다는데, 이해가 안감)

 

상속(Inheritance) : 상위 객체는 자기가 가지고 있는 필드와 메소드를 하위 객체에게 물려주어 하위 객체가 사용할 수 있도록 해준다. 반복된 코드의 중복을 줄여주고, 수정 시에도 함께 수정되어 효율적이다.

 

객체와 클래스

객체를 생성하기 위한 설계도를 클래스라고 한다. 클래스에는 필드와 메소드가 정의되어 있다. 클래스로부터 만들어진 객체를 해당 클래스의 인스턴스라고 하고, 클래스로부터 객체를 만드는 과정을 인스턴스화라고 한다. 

 

힙메모리에 올라간 필드나 메소드 영역에 올라간 메소드를 인스턴스(=객체)라고 한다. 클래스를 객체로 만들기 위해 new연산자를 쓰고, 이 과정을 인스턴스화라고 한다. 하나의 클래스로부터 여러 개의 인스턴스를 만들 수 있다. (New생성자를 쓸때마다 새로운 인스턴스가 생성)

 

객체지향 프로그래밍 개발의 단계 1. 클래스 설계 2. 설계된 클래스를 가지고 객체를 생성 3. 생성된 객체를 이용

 

더보기

개발자는 설계도(클래스)를 통해 진짜 자동차(인스턴스)를 만든다.

ex)현대아파트 설계도를 가지고 인천, 상봉에 아파트 만드는 것이 가능. 실제 아파트는 인스턴스(객체)

- 즉 설계도가 먼저이고 객체는 그 다음이다.

cf. Object: 인스턴스로 만든 객체(객체로 다른 객체를 만들 수 있나? 이해불가)

 

 

 

클래스 선언 (클래스 설계)

일반적으로 소스 파일당 하나의 클래스를 선언한다. 두 개 이상의 클래스도 선언 가능하지만, 대부분의 객체지향 프로그램은 실행 클래스(main메소드를 포함한 클래스, 비즈니스 클래스)와 라이브러리 클래스(부품 클래스)가 나누어져 있기 때문에 관례적으로 소스 파일을 분리한다. 

 

하나의 소스파일 안에 여러 클래스를 선언할 때 public 접근제한자는 한번만 사용 가능하다. 1개의 .java파일(소스파일)에 1개의 public class만이 있어야 하고 파일 이름과 퍼블릭 클래스의 이름은 동일해야 한다. 단, public을 쓰지 않고 class 두 개를 하나의 소스파일에 쓰는 것은 가능하다.

 

cf. 소스파일은 하나여도, 컴파일하면 그 안에 클래스의 수 만큼 바이트코드파일(.class)이 생성된다.  즉 소스파일은 클래스 선언을 담고 있는 저장 단위에 불과하다. 

 

1개의 애플리케이션 = 1개의 실행클래스+ n개의 라이브러리 클래스

 

 

main()메소드를 가지고 있는 클래스는 실행할 목적을 가지고 있다. 다른 부품 클래스들에는 변수와 메소드만 포함되어 다른 클래스에서 이용할 목적으로 만들어진다.

 

cf. 프로그램이 단 하나의 클래스로 구성된다면  라이브러리 클래스인 동시에 실행 클래스로 만들 수도 있다. 

 

더보기

클래스 선언의 작성 규칙

  • 하나 이상의 문자로 이루어져야 한다 ex. Car, SportsCar

  • 첫 번째 글자는 숫자가 올 수 없다 ex. Car, 3Car(x)

  • $, _ 이외의 특수 문자는 사용할 수 없다. ex. $Car, _Car, @Car(x), #Car(x)

  • 자바 키워드(예약어)는 사용할 수 없다. ex. int(x),for(x)

 

객체 생성과 클래스 변수

클래스를 선언한 다음 컴파일을 하면(이클립스의 저장) 객체를 생성할 설계도가 만들어진다. 클래스로부터 객체를 생성하는 방법은 new 연산자를 이용하고, 이렇게 생성된 객체는 힙(heap) 메모리 영역에 생성된다. 동일한 클래스에서 new연산자를 사용한 만큼 객체가 메모리에 생성되고 각 객체들은 고유의 데이터를 가지며 독립적이다. 

 

클래스의 구성 멤버

필드(Field)

객체의 고유 데이터 (ex. 제작 회사, 모델, 색깔, 최고속도)

부품객체 (ex.엔진,타이어)

상태정보 (ex.현재속도, 엔진 회전속도)

 

생성자(Constructor)

New 연산자로 호출되는 특별한 중괄호 { }블록

객체 생성 시 초기화 담당 (필드를 초기화하거나 메소드 호출)

클래스 이름으로 되어있고 리턴 타입이 없음

 

메소드(Method)

필드를 읽고 수정, 다른객체 생성, 객체 간의 데이터 전달 등 다양한 기능 수행

 

필드(인스턴스영역)

필드 선언은 클래스 중괄호 블록 어디서든 할 수 있다. 생성자 선언과 메소드 선언의  앞과 뒤에서도 가능하다. 단, 생성자와 메소드 내부에는 불가한데, 이 안에서 선언된 것은 필드가 아닌 로컬 변수이다. 클래스 멤버 변수라고도 불리지만, 엄격히는 변수와 필드는 다르다. 변수는 생성자와 메소드가 실행 종료되면 자동 소멸되지만 필드는 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 계속해서 존재한다. 

 

초기값이 주어지지 않은 필드들은 객체 생성시 힙영역에 올라가면서 자동으로 기본 초기값으로 설정된다. 기본타입이라 할지라도 필드로 선언된다면 초기값을 가진다. 타입별 배열의 초기값과 같다.

 

더보기

필드의 초기값

기본타입(정수) : byte, short, int  - 0   |   char - '\u0000'("") : 공백    |    long[] - 0L

기본타입(실수) : float - 0.0F                   |   doulbe - 0.0

기본타입(논리) : boolean - false

참조타입 : 클래스(String 포함), 인터페이스,배열(정수,실수배열이라도) - null (힙영역 안에서 실제 값을 가진 객체를 참조하고 있지 않은 상태)

 

힙메모리에 올라가는 클래스의 멤버필드는 기본타입이라 해도 초기값이 자동으로 붙는다.

힙메모리는 넣었다 빼는 경우가 많아 남아있는 자료가 메모리에 많기 때문에  올리자마자 자동으로 청소하고 값을 넣는다.

cf. 스택메모리에 올라가는 기본타입 변수들은 초기값이 자동으로 들어가지 않기 때문에 출력하면 오류가 난다.

 

필드 사용

같은 클래스 내부의 생성자나 메소드에서 사용할 경우 단순히 필드 이름으로 읽고 변경할 수 있지만, 클래스 외부에서 사용할 경우 객체 생성이 필요하다. 필드는 객체에 소속된 데이터이므로 객체가 존재하지 않으면 필드도 존재하지 않는다.  따라서 필드가 선언이 아닌 생성이 되어 사용가능한 시점은 객체 생성의 시점이다.

 

Q. 라이브러리 클래스 안 메소드 영역(메소드, 생성자)의 변수들은 로컬변수들인데, 어느 시점에 생성되는 것일까?

메소드영역은 컴파일 시점(static 메모리)이나 객체 생성의 시점(heap 메모리)이 아닌 메소드 호출 시에 메모리에 올라간다. 실행클래스의 메인 메소드에서 프로그램이 실행되면, 다른 클래스의 메소드를 호출하기 위해서는  new 연산자를 통해 객체를 생성해줘야 한다. 객체생성이 먼저고 그 다음이 메소드 호출이다. 

 

메모리에 올라가는 순서 : 인스턴스 영역 →  메소드영역

 

cf.  변수가 스택 영역에 생성되는 시점은(실행 클래스에서 선언된 로컬 변수들의 생성 시점)은 초기값이 주어질 때이다. 엄밀히 말해서 변수 선언과 생성은 다르다. 

 

 

생성자(Constructor)

new 연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한다. 객체의 초기화란 필드를 초기화하거나 메소드를 호출해서 객체를 사용할 준비를 하는것이다. 생성자를 실행시키지 않고는 클래스로부터 객체를 만들 수 없다. 생성자가 성공적으로 실행되면 heap영역에 객체가 생성되고 객체의 주소가 리턴된다. 이렇게 리턴된 주소는 클래스 타입 변수에 저장되어 객체에 접근할 때 이용된다.

 

모든 클래스는 생성자가 반드시 존재하며, 하나 이상을 가질 수 있는데 사용자가 생성자 선언을 생략했다면 컴파일 시 바이트 코드파일에 자동으로 수행문이 비어있는 기본생성자가 생성된다.  이 때 클래스의 접근제한자에 따라 생성자도 같은 접근제한자를 자동으로 가진다. 

 

그러나 클래스에 명시적 생성자가 하나라도 있다면 컴파일러는 기본 생성자를 추가하지 않는다. 당연하게도 기본생성자로 바로 명시적 생성자를 호출할 수 없다.

 

명시적 생성자 선언

생성자를 명시적으로 선언하는 이유는 객체를 다양하게 초기화하기 위해서다.  클래스에 명시적 생성자가 선언된다면 반드시 선언된 명시적 생성자를 호출하여 객체를 생성해야만 한다. 

public class Car{
	Car(String color, int cc){ //명시적 생성자
    }
}
//컴파일러는 명시적 생성자가 있기 때문에 기본생성자를 자동으로 생성하지 않는다.

public class CarExample{
	public static void main(String[] args){
    Car myCar = new Car("검정", 3000); //명시적 생성자의 호출
	//Car myCar = new Car(); (x) 명시적 생성자만 존재하는 경우, 기본 생성자를 호출할 수 없다.
}

 

필드 초기화와 생성자

클래스로부터 객체가 생성될 때 필드는 기본 초기값으로 자동 설정된다. 그러나 다른 값으로 초기화를 하고 싶다면 두 가지 방법이 있다.

 

1. 필드를 선언할 때 초기값 입력 : 동일한 클래스로부터 생성되는 객체들은 모두 같은 데이터를 가지게 된다. (객체 생성 후 변경 가능하다)

    → 생성자 하나에서 연산된 값을 다른 생성자에서도 쓸 수 있게 하고 싶다면 생성자 내부에서 변수 선언이 아닌, 밖에서 필드 선언이 이루어져야 한다.

 

2. 생성자에서 초기화 : 외부에서 제공되는 다양한 값들로 초기화하고 싶을 때

관례적으로 매개변수의 이름을 정할 때 필드와 동일한 이름을 사용한다. 이 경우 동일한 이름의 매개변수가 필드보다 가까이서 선언되었기 때문에 사용 우선순위가 높아 필드의 값을 초기화하려해도 접근할 수 없는데, 이때 필드 앞에 "this."를 붙이면 된다. this는 객체 자신의 참조로서 필드를 찾을 참조변수의 역할을 한다.

 

1.필드 선언 시 초기값
public class Korean {
	String nation = "대한민국";
    String name;
    string ssn;
}
//실행클래스
Korean k1 = new Korean();
Korean k2 = new Korean();
//k1 객체와 k2 객체의 nation 필드는 모두 같은 값을 가진다.


2. 생성자를 통한 초기값
public class Korean {
	String nation = "대한민국";
    String name;
    string ssn;
}

public Korean(String name, String ssn){
	this.name = name; //실행클래스로부터 입력된 매개변수 name, ssn은 우변
	this.ssn = ssn;   //좌변 필드에 담아주기 위해 this.라는 필드를 식별하는 일종의 참조변수를 이용한다.
}
//실행클래스
Korean k1 = new Korean("박자바", "011225-1234567");
Korean k2 = new Korean("김자바", "930525-0654321");
//k1 객체의 name, ssn과 k2객체의 name, ssn은 다르다.

3. 생성자 내부에서 임의의 값 또는 계산된 값으로 초기화

4. 객체 생성 후 필드값을 별도로 저장

 

 

생성자 오버로딩(Overloading)

매개변수를 달리하는 생성자를 여러 개 선언하는 것을 말한다. 같은 이름의 생성자로 매개변수의 타입, 개수, 순서 중 하나라도 다르면 오버로딩에 해당한다. 생성자가 오버로딩 되어 있다면, new 연산자로 생성자를 호출할 때 제공되는 매개값의 타입과 순서, 수에 의해 호출될 생성자가 결정된다. 

Car(String model, String color) {...}
Car(String color, String model) {...} //매개 변수의 순서가 달라도 타입과 개수가 같으면 오버로딩이 아니다.

 

다른 생성자 호출 this()

생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다. 매개변수의 수만 달리하고 필드 초기화 내용이 비슷한 생성자에서 이러한 현상이 많다. 이 때 this()코드를 사용하면 생성자에서  자신의 다른 생성자를 호출함으로써 중복된 코드를 생략할 수 있는데, 이 때 반드시 생성자의 첫줄에 코드를 작성하여야 한다. this()다음에는 추가적인 실행문이 올 수 있는데, 이는 생성자의 호출이 끝나고 원래 생성자로 돌아와서 실행을 진행한다는 뜻이다.

 

package BookExampleHomeWork;

public class Car {
	//필드
	String company = "현대자동차";
	String model;
	String color;
	int maxSpeed;
	
	//생성자
	Car (){
	}
	
	Car(String model){
		this(model, null, 0);
//		this.model = model; 이줄의 코드를 윗줄의 다른 생성자 호출로 대신한다.
	}
	
	Car(String model, String color){
		this(model,color,0);
//		this.model = model;
//		this.color = color; //두 줄의 코드를 윗줄의 다른 생성자 호출로 대신한다.
	}
	
	Car(String model, String color, int maxSpeed){
		this.model = model; //필드 초기화 내용이 비슷한데 조금씩 늘어나는 경우 
		this.color = color; //생성자를 오버로딩하면 코드의 중복이 늘어나기 때문에
		this.maxSpeed = maxSpeed; //하나의 생성자에만 초기화 내용을 제대로 쓰고 나머지는 이 생성자를 호출해서 대신한다.
	}
}
=========================================
	package BookExampleHomeWork;
	
	public class CarEaxmple {
	
		public static void main(String[] args) {
			Car car1 = new Car();
			System.out.println("car1.company : " + car1.company);
			System.out.println();
			
			Car car2 = new Car("자가용");
			System.out.println("car2.company : " +car2.company);
			System.out.println("car2.model : " +car2.model);
			System.out.println();
			
			Car car3 = new Car("자가용", "빨강");
			System.out.println("car3.company : " + car3.company);
			System.out.println("car3.model : " + car3.model);
			System.out.println("car3.color : " + car3.color);
			System.out.println();
			
			Car car4 = new Car("택시", "검정", 200);
			System.out.println("car4.company : " + car4.company);
			System.out.println("car4.model : " + car4.model);
			System.out.println("car4.color : " + car4.color);
			System.out.println("car4.maxSpeed : " + car4.maxSpeed);
			
		}
	}

 

정리. 생성자의 특징

더보기

생성자 특징

1. 생성자는 여러 개 만들 수 있다.

2. 생성자의 매개변수의 자료형과 개수는 달라야 한다.

3. 생성자 호출의 자료와 생성자 정의 매개변수 자료는 1:1 대응관계

4. 생성자 오버로딩(매개변수 타입을 다르게 해서 재사용)

5. 명시적 생성자가 하나라도 존재한다면,  컴파일러는 기본 생성자를 안만든다.

6. 생성자가 1개도 정의되어 있지 않으면 컴파일러가 기본생성자를 자동으로 만든다.

7. 생성자안에서 다른 생성자를 호출할 수 있다. this(매개변수);

단, this(매개변수)는 첫째줄에만 나와야 한다. 그러므로 반드시 한번밖에 호출할 수 없다. 다른 것을 수행하고 할 수도 없다.

8. 7번에서 생성자끼리 호출할 때 원(써클)이면 안된다.

가독성을 위해 매개변수의 이름을 정확히 썼을 때, 멤버 필드의 변수명과 헷갈리는 걸 방지하기 위해 멤버필드의 변수명을 this.변수명으로 적는다.

9. 생성자는 메소드와 비슷하지만, 객체 생성 시에 한번만 수행된다.

 

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

Ch6 클래스 - 정적 멤버와 Static  (0) 2021.03.04
Ch6 객체지향프로그래밍 - 메소드  (0) 2021.03.04
Ch5 - 열거타입  (0) 2021.03.02
Ch5 - 배열 2  (0) 2021.03.02
Ch5 - 배열 1  (0) 2021.02.27