# 6. 클래스
# 6.1 객체지향프로그래밍
- 소프트웨어를 만들때 부품에 해당하는 객체를 먼저 만들고, 이 객체들을 하나씩 조립해서 완성된 프로그램을 만드는 기법
# 객체(object)란?
- 물리적으로 존재하거나 개념적인 것 중에서 다른 것과 식별 가능한 것.
- 객체는 속성(필드
field
)과 동작(메소드method
)으로 구성된다. - 객체모델링(object modeling) : 현실 세계 객체의 대표 속성과 동작을 추려내어 소프트웨어 객체의 필드와 메서드로 정의하는 과정.
# 객체의 상호작용
메소드(매개값1, 매개값2, ...);
- 메소드 : 객체들 간의 상호작용 수단. 객체가 다른 객체의 기능을 이용할때 메소드 호출한다.
- 매개값 : 메소드 이름과 함께 전달하고자 하는 데이터. 메소드가 실행할때 필요한 값.
- 리턴값 : 메소드의 실행의 결과이며, 호출한 곳으로 돌려주는 값 이다.
# 객체 간의 관계
대부분의 객체는 다른 객체와 관계를 맺고 있다.
# 집합관계
- 완성품과 부품의 관계를 말한다.
- 자동차 <- 엔진, 타이어, 핸들
# 사용관계
- 다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계
- 사람 -> 자동차 달린다, 멈춘다
# 상속관계
- 부모와 자식 관계를 말한다.
- 기계 <- 자동차
# 객체지향 프로그램의 특징
# 캡슐화(Encapsulation)
- 객체의 데이터(필드), 동작(메소드)을 하나로 묶고 실제 구현 내용을 외부에 감추는 것.
- 외부 객체는 객체 내부의 구조를 알지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다.
- 필드와 메소드를 캡슐화하여 보호하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는데 잆다.
- 접근제한자 : 캡슐화된 멤버를 노출시킬 것인지 숨길 것인지 결정
# 상속
- 부모 객체는 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 한다.
- 상속을 하는 이유
# 코드의 재 사용성을 높여준다
잘 개발된 부모 객체의 필드와 메소드를 자식이 그대로 사용할 수 있어 자식 객체에서 중복 코딩을 하지 않아도 된다.# 유지보수 시간을 최소화시켜 준다
부모 객체의 필드와 메소드를 수정하면 모든 자식 객체들은 수정도니 필드와 메소드를 사용할 수 있다.
# 다형성
- 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질
- 프로그램을 구성하는 객체(부품)를 바꾸면 프로그램의 실행 성능이 다르게 나올 수 있다.
- 상속, 인터페이스 구현을 통해 얻어진다
# 6.2 객체와 클래스
- 클래스 : 객체지향 프로그래밍에서 객체를 생성하는 설계도에 해당
- 인스턴스 : 클래스로부터 생성된 객체
- 인스턴스화 : 클래스로부터 객체를 만드는 과정
# 6.3 클래스 선언
클래스 선언 : 객체 생성을 위한 설계도를 작성하는 작업.
- 생성자 - 어떻게 객체를 생성할지
- 필드 - 객체가 가져야 할 데이터가 무엇인지
- 메소드 - 객체의 동작은 무엇인지 정의
public class 클래스명 {}
- public class : 공개 클래스 선언
- 공개 클래스 : 어느 위치에 있든지 패키지와 상관없이 사용할 수 있는 클래스.
- 하나의 파일에 복수개의 클래스를 선언할때 소스 파일명과 동일한 클래스만 공개 클래스로 선언 가능.
# 6.4 객체 생성과 클래스 변수
클래스 변수 = new 클래스();
- 객체생성연산자 new : 클래스로부터 객체생성시킨후 객체의 주소 리턴.
# 클래스의 두가지 용도
- 라이브러리(library) 클래스 : 실행할 수 없으며 다른 클래스에서 이용하는 클래스
- 실행 클래스 : main() 메소드를 가지고 있는 실행 가능한 클래스
일반적인 자바 프로그램은 하나의 실행 클래스와 여러개의 라이브러리 클래스로 구성됨. 실행 클래스는 실행하면서 라이브러리 클래스를 내부에서 이용한다
# 6.5 클래스의 구성 맴버(필드, 생성자, 메소드)
public class ClassName{
int fieldName; // 필드 : 객체의 데이터가 저장되는곳
ClassName() {...} // 생성자 : 객체 생성시 초기화 역할 담당
int methodName() {...} // 메소드 : 객체의 동작으로 호출 시 실행하는 블록
}
1
2
3
4
5
2
3
4
5
# 6.6 필드 선언과 사용
구분 | 필드 | (로컬)변수 |
---|---|---|
선언위치 | 클래스선언블록 | 생성자, 메소드 선언블록 |
존재위치 | 객체 내부에 존재 | 생성자,메소드 호출시에만 존재 |
사용위치 | 객체내,외부 어디든사용 | 생성자,메소드 블록 내부에서만 사용 |
타입 필드명 [=초기값];
- 타입 : 필드에 저장할 데이터의 종류를 결정.
- 초기값을 제공하지 않을 경우 필드는 객체 생성 시 자동으로 기본값으로 초기화 된다.
# 필드사용
- 클래스로부터 객체가 생성된 후에 필드 사용
- 생성자와 메소드는 객체가 생성된 후 호출되므로 내부에서 필드 사용 할 수 있고, 객체 외부에서도 접근해서 사용할 수 있다.
- 도트(.) 연산자 : 외부 객체에서 참조변수와 도트 연산자를 이용해서 필드를 읽고 변경. 객체가 가지고 있는 필드나 메소드 뒤에 붙여 객체가 가지고 있는 필드나 메소드에 접근
# 6.7 생성자 선언과 호출
클래스변수 = new 클래스(); // 생성자 호출
- new 연산자는 객체를 생성한 후 연이어 생성자를 호출해서 객체를 초기화
# 기본생성자
[public] 클래스() {}
- 모든 클래스는 생성자가 존재하며, 하나 이상 가질 수 있다.
- 클래스에 생성자 선언이 없으면 컴파일러는 기본생성자(Default constructor)를 바이트코드 파일에 자동으로 추가시킨다.
- 클래스가 public class 로 선언되면 기본 생성자도 public이 붙지만, 클래스가 public 없이 class 로만 선언되면 기본생성자에도 public 이 붙지 않는다.
- 개발자가 명시적으로 선언한 생성자가 있다면 컴파일러는 기본 생성자를 추가하지 않는다.
# 생성자 선언
/** 생성자 블록 */
클래스(매개변수, ... ) {
// 객체의 초기화 코드
}
- 메소드 모양과 비슷하나 리턴 타입이 없고 클래스 이름과 동일.
# 필드 초기화
public class Korean {
String nation = "대한민국";
String name;
String ssn;
public Korean(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 생성자 오버로딩
- 생성자 오버로딩 : 매개변수의 타입, 개수, 순서를 달리하는 생성자를 여러 개 선언하는 것.
- 매개변수의 타입, 개수, 선언된 순서가 똑같을 경우 컴파일 에러 발생
# 다른 생성자 호출
Car(String model) {
this(model,"은색",250);
}
Car(String model, String color){
this(model, color, 250);
}
Car(String model, String color, int maxSpeed){
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- this(매개값,...) : 생성자의 첫 줄에 작성되며 다른 생성자를 호출하는 역할을 한다. 호출하고 싶은 생성자의 매개변수에 맞게 매개값을 제공하면 된다.
# 6.8 메소드 선언과 호출
- 메소드 선언 : 객체의 동작을 실행 블록으로 정의. 객체간의 상호작용 하는 방법 정의
- 메소드 호출 :
타입변수 = 메소드();
# 가변길이 매개변수
int sum(int ... values) {}
int result = sum(1, 2, 3);
int result = sum(1, 2, 3, 4, 5);
int[] values = {1,2,3};
int result = sum(values);
int result = sum(new int[] {1,2,3});
# 메소드 오버로딩
- 메소드 이름은 같되 매개변수의 타입, 개수, 순서가 다른 메소드를 여러개 선언
# 6.9 인스턴스 멤버
구분 | 설명 |
---|---|
인스턴스(instance)멤버 | 객체에 소속된 멤버(객체를 생성해야만 사용) |
정적(static)멤버 | 클래스에 고정된 멤버(객체 없이도 사용) |
public class Car {
int gas; // 인스턴스 필드 선언
void setSpeed(int speed){...} // 인스턴스 메소드 선언
}
1
2
3
4
2
3
4
- gas 필드는 객체에 소속된 멤버가 분명하지만, setSpeed() 메소드는 객체에 포함되지 않는다.
- 메소드는 코드의 덩어리 이므로 객체마다 저장한다면 중복 저장으로 인해 메모리 효율이 떨어진다.
- 따라서 메소드 코드는 메소드 영역에 두되 공유해서 사용하고, 이때 객체 없이는 사용하지 못하도록 제한을 걸어둔 것이다.
- this 키워드 : 인스턴스 필드임을 강조하기 위해
# 6.10 정적 멤버
- 자바는 클래스 로더를 이용해서 클래스를 메소드 영역에 저장하고 사용한다.
- 정적(static) 멤버 : 메소드 영역의 클래스에 고정적으로 위치하는 멤버. 정적 멤버는 객체를 생성할 필요 없이 클래스를 통해 바로 사용이 가능하다.
# 정적 멤버
public class 클래스 {
// 정적 필드 선언
static 타입 필드 [=초기값];
// 정적 메소드
static 리턴타입 메소드 (매개변수, ...) {...}
}
- 객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다. ex. 파이(3.14...)
- 인스턴스 필드를 이용하지 않는 메소드는 정적 메소드로 선언하는 것이 좋다.
- 클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있다.
- 정적 요소는 클래스 이름으로 접근하는 것이 정석이다.
# 정적 블록
static int field;
static void method(){}
// 정적 블록 선언
static {
field = 10;
method();
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 복잡한 초기화 작업.
- 정적 블록은 클래스가 메모리에 로딩될 때 자동으로 실행된다. 정적 블록이 클래스 내부에 여러개가 선언되어 있을 경우에는 선언된 순서대로 실행된다.
- 정적 필드는 생성자에서 초기화 작업을 하지 않는다. 생성자는 객체 생성 후 실행되기 때문
- 정적 메소드와 정적 블록은 객체가 없어도 생성된다는 특징 때문에 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 또한 객체 자신의 참조인 this도 사용할 수 없다.
public static void main(String[] args){
className obj = new ClassName();
obj.field1 = 10;
obj.method1();
}
1
2
3
4
5
2
3
4
5
# 6.11 final 필드와 상수
# final 필드 선언
final 타입 필드 [=초기값];
- final 필드는 초기값이 저장되면 최종적인 값이 되어 프로그램 실행 도중에 수정할 수 없게 된다.
- final 필드에 초기값을 줄 수 있는 방법
- 필드 선언 시에 초기값 대입 (고정된값)
- 생성자에서 초기값 대입 (복잡한 초기화코드, 객체 생성시 외부에서 전달된 값으로 초기화)
- 이 두가지 방법을 사용하지 않고 final 필드를 그대로 남겨두면 컴파일 에러가 발생하게 된다.
# 상수 선언
static final 타입 상수 [= 초기값];
- 상수 : 객체마다 저장할 필요가 없고, 여러개의 값을 가져도 안됨.
- 선언 시에 초기화 하는 것이 일반적이지만, 복잡한 초기화가 필요할 경우 정적 블록에서 초기화 할 수도 있다.
- 상수 이름은 대문자로 작성하는것이 관례이다.
- 상수는 정적 필드 이므로 클래스로 접근해서 읽을 수 있다.
# 6.12 패키지
- 자바의 패키지(package) : 클래스의 일부분이며, 클래스를 식별하는 용도로 사용된다. 주로 개발 회사의 도메인 이름의 역순으로 만든다.
- 패키지는 클래스를 식별하는 용도로 사용되기 때문에 클래스의 전체 이름에 포함된다.
- 패키지에 속한 바이트코드파일(~.class)은 따로 떼어내어 다른 디렉토리로 이동할 수 없다.
# 패키지 선언
package 상위패키지.하위패키지;
public class 클래스명 [...]
- 패키지 디렉토리는 클래스를 컴파일 하는 과정에서 자동으로 생성된다
- 컴파일러는 클래스의 패키지 선언을 보고 디렉토리를 자동 생성 시킨다.
- 도메인 역순 + 프로젝트 이름
- 소스 파일(~.java)이 저장되면 이클립스는 자동으로 컴파일 해서 <blahblah>/bin 디렉토리에 패키지 디렉토리와 함께 바이트코드 파일(~.class)을 생성한다.
- 만약 패키지 선언이 없다면 이클립스는 클래스를 (default package)에 포함시킨다. default package란 패키지가 없다는 뜻 이다.
# import문
- 같은 패키지에 있는 클래스는 아무런 조건 없이 사용할 수 있지만, 다른 패키지에 있는 클래스를 사용하려면 import 문을 이용해서 어떤 패키지의 클래스를 사용하는지 명시해야 한다.
- import문이 작성되는 위치는 패키지 선언과 클래서 선언사이다.
- import 키워드 뒤에는 사용하고자 하는 클래스의 전체 이름을 기술한다.
- 동일한 패키지에 포함된 다수의 클래스를 사용해야 한다면 클래스 이름을 생략하고 * 사용.
- import문은 하위 패키지를 포함하지 않는다.
// 둘다 사용해야 한다면 두개의 import 문이 필요하다.
import com.hankook.*;
import com.hankook.project.*;
1
2
3
2
3
- 서로 다른 패키지에 동일한 클래스 이름이 존재하는 경우 클래스의 전체 이름 사용
com.hankook.Tire tire = new com.hankook.Tire();
1
# 이클립스 import 자동 추가 기능
- import 전체클래스이름 : Ctrl + shift + O
- import 패키지.* : preference 설정 변경 code style > organize imports > number of imports needed for.* 의 99 를 1로 변경
# 6.13 접근제한자
- 객체의 필드를 외부에서 변경하거나 메소드를 호출 할 수 없도록 막아야 할 필요가 있다.
- default : 접근 제한자가 붙지 않는 상태
접근제한자 | 제한대상 | 제한범위 |
---|---|---|
public | 클래스, 필드, 생성자, 메소드 | 없음 |
protected | 필드, 생성자, 메소드 | 같은 패키지이거나, 자식 객체만 사용 가능 |
(default) | 클래스, 필드, 생성자, 메소드 | 같은 패키지 |
private | 필드, 생성자, 메소드 | 객체 내부 |
# 클래스의 접근 제한
[public] class 클래스 {...}
- public 과 default 접근 제한을 가질 수 있다
- public을 생략하면 default 접근 제한을 가짐
- default : 같은 패키지 내에서는 아무런 제한 없이 사용할 수 있지만 다른 패키지에서는 사용할 수 없다
- public : 같은 패키지 뿐만 아니라 다른 패키지에서도 사용 가능
# 생성자의 접근 제한
public class ClassName {
// 생성자 선언
[public | private] ClassName(...) {...}
}
접근 제한자 | 생성자 | 설명 |
---|---|---|
public | 클래스(...) | 모든 패키지에서 생성자를 호출할 수 있다 =모든 패키지에서 객체를 생성할 수 있다 |
클래스(...) | 같은 패키지 에서만 생성자를 호출할 수 있다 =같은 패키지 내에서만 객체를 생성할 수 있다 | |
private | 클래스(...) | 클래스 내부에서만 생성자를 호출할 수 있다 =클래스 내부에서만 객체를 생성할 수 있다 |
# 필드와 메소드의 접근 제한
// 필드 선언
[ public | private ] 타입 필드;
// 메소드 선언
[ public | private ] 리턴타입 메소드(...) {...}
접근제한자 | 생성자 | 설명 |
---|---|---|
public | 필드 메소드(...) | 모든 패키지에서 필드를 읽고 변경할 수 있다 모든 패키지에서 메소드를 호출할 수 있다 |
필드 메소드(...) | 같은 패키지에서만 필드를 읽고 호출할 수 있다 같은 패키지에서만 메소드를 호출할 수 있다 | |
private | 필드 메소드(...) | 클래스 내부에서만 필드를 읽고 변경할 수 있다 클래스 내부에서만 메소드를 호출할 수 있다 |
# 6.14 Getter 와 Setter
- 객체의 필드(데이터)를 외부에서 마음대로 읽고 변경할 경우 객체의 무결성(결점이 없는 성질)이 깨질 수 있다.
- 이러한 문제점 때문에 객체 지향 프로그래밍에서는 직접적인 외부에서의 필드 접근을 막고 대신 메소드를 통해 필드에 접근하는 것을 선호
private 타입 fieldName;
//Getter
public 타입 getFildName() {
return fieldName;
}
//Setter
public void setFieldName(타입 fieldName) {
this.fieldName = fieldName;
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 필드 타입이 boolean 인 경우 is로 시작하는것이 관례.
# 6.15 싱글톤 패턴
- 애플리케이션 전체에서 단 한 개의 객체만 생성해서 사용
- 생성자를 private 접근 제한해서 외부에서 new 연산자로 생성자를 호출할 수 없도록 막는 것 이다.
- 생성자를 호출할 수 없으니 외부에서 마음대로 객체를 생성하는 것이 불가능하다 대신 싱글톤 패턴이 제공하는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.
public class 클래스 {
// private 접근 권한을 갖는 정적 필드 선언과 초기화
private static 클래스 singleton = new 클래스();
// private 접근 권한을 갖는 생성자 선언
private 클래스() {}
// public 접근 권한을 갖는 정적 메소드 선언
public static 클래스 getInstance() {
return singleton;
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드를 호출하는 것 이다.
getInstance() 메소드가 리턴하는 객체는 정적 필드가 참조하는 싱글톤 객체이다.
클래스 변수1 = 클래스.getInstance(); 클래스 변수2 = 클래스.getInstance();