Chapter 06 클래스
6.1 객체 지향 프로그래밍
- 소프트웨어를 개발할 때에도 부품에 해당하는 객체들을 먼저 만들고, 이 객체들을 하나씩 조립해서 완성된 프로그램을 만드는 기법 객체 지향 프로그래밍 이라고 한다.
객체란?
- 객체란 속성(Data=field) + 동작(Method)로 이루어 진것.
- 즉 물리적으로 존재하거나, 개념적인 것 중에서 다른 것과 식별 가능한 것.
객체의 상호작용
- 현실 세계에서 일어나는 모든 현상은 객체와 객체 간의 상호작용으로 이루어져 있다.
객체 간의 관계
- 집합 관계
완성품과 부품의 관계를 의미한다 . 자동차 객체를 예로 들었을 경우 → 엔진, 타이어, 핸들 - 사용 관계
다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계 자동차 객체의 경우 → 사람 - 상속 관계
부모와 자식 관계를 말한다. 자동차 객체 → 기계 객체
객체 지향 프로그래밍의 특징
- 캡슐화
객체의 데이터(필드), 동작(메소드)를 하나로 묶고 실제 구현 내용을 외부에 감추는 것. → 객체를 보호하고 이를 위해 접근 제한자(Access Modifier)를 사용한다. - 상속
부모 객체는 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 한다. 이것을 상속 이라고 한다.
상속의 이유? - 코드의 재사용성을 높여 준다. → 잘 개발된 부모 객체의 필드와 메소드를 자식이 그대로 사용할 수 있어 중복 코딩을 하지 않아도 된다.
- 유지 보수 시간을 최소화 해준다. → 부모 객체의 필드와 메소드를 수정하면 모든 자식 객체들은 수정된 필드와 메소드를 사용 할 수 있다.
- 다형성
→ Polymorphism → 사용방법은 동일하나, 실행 결과가 다양하게 나오는 성질이다.
상속과 인터페이스 구현을 통하여 얻을수 있으며, 자동차에 어떤 타이어를 장착하던 사용방법은 동일하지만, 성능이 달라지는것이 이에 해당한다.
6.2 객체와 클래스
- 현실 세계에서 자동차를 생성 하기 위해선 설계도가 필요 하듯, 객체 지향 프로그래밍에서도 객체를 생성하려면 설계도에 해당하는 클래스가 필요하다.
- 개발자는 → 클래스를 설계 한다. → 클래스를 통해 생성된 객체를 인스턴스 (인스턴스화) 라고 한다.
- 클래스로부터 생성된 객체를 해당 클래스의 인스턴스 라고 부른다.
6.3 클래스 선언
- 클래스 선언은 객체를 생성하기 위한 설계도를 작성 하는 것 이다. 어떻게 객체를 생성(생성자) 하고, 객체가 가져야 할 데이터(필드) 가 무엇이고, 객체의 동작(메소드)는 무엇인지 정의하는 내용이 포함되어야 한다. 클래스 선언은 소스파일명과 동일하게 작성.
public class 클래스명{
} -> 클래스 명은 첫 문자를 대문자로 하고 캐멀 스타일로 작성한다.
- 하나의 소스 파일 안에는 여러개의 클래스를 선언할 수 있다, 하지만 공개 클래스는 1개만 선언할 수 있다.
public class SportsCar{
}
//공개 클래스는 1개만
class Tire{
}
//1개의 소스 파일에 여러개 생성 가능
→ 컴파일시 .class의 수 만큼 바이트 코드 파일이 bin폴더 안에 생성된다.
6.4 객체 생성과 클래스 변수
- 클래스로 부터 객체를 생성하려면 new 연산자가 필요하다.
클래스 변수 = new 클래스();
//클래스도 하나의 type이다.
클래스의 두가지 용도
- 라이브러리 클래스: 실행할 수 없으며 다른 클래스에서 이용하는 클래스
- 실행 클래스 : main() 메소드를 가지고 있는 실행 가능한 클래스
6.5 클래스의 구성 멤버
클래스의 구성요소 3가지
필드
: 객체의 데이터가 저장되는 곳 → Data
객체의 데이터를 저장하는 역할로서, 변수 선언과 비슷한 형태 이지만 쓰임새는 다르다생성자
: 객체 생성 시 초기화 역할 담당 → 어떻게 객체를 만들것인지 객체를 만드는 것
생성자는 new 연산자로 객체를 생성할 때 객체의 초기화 역할을 담당한다. 선언 형테는 메소드와 비슷하지만, 리턴 타입이 없고 이름은 클래스의 이름과 동일하다메소드
: 객체의 동작으로 호출 시 실행하는 블록 → 동작
객체가 수행할 동작으로서, 다른 언어에서는 함수라고도 부르며 객체 내부의 함수를 메소드라고 부른다.
public class Class Name{
//필드 선언
int fieldName;
//생성자 선언
ClassName(){...}
//메소드 선언
int methodName(){...}
}
6.6 필드 선언과 사용
- 필드(Field)는 객체의 데이터를 저장하는 역할로서, 객체의 데이터는 3가지로 분류된다. 고유한 데이터를 가지는 고유데이터, 수시로 값이 바뀌는 상태 데이터, 집합 관계에 있는 부품 데이터등이 있다.
//자동차 클래스를 예시로
public class Car{
//고유한 데이터를 저장하는 필드
String company;
String model;
String color;
int maxSpeed;
//수시로 바뀌는 상태 데이터를 저장하는 필드
int speed;
int rpm;
//부품 데이터를 저장하는 필드로서
//각기 다른 class에서, class를 type으로 사용
Body body;
Engine engine;
Tire tire;
}
필드 선언
- 필드를 선언하는 방법은 변수를 선언하는 방법과 동일하다. 클래스 블록에서 선언되어야만 필드 선언 이다.
타입 필드명 [ = 초기값 ] ;
class class Car{
Stirng model; //null
int speed = 300; //300
boolean start = true; //true
Tire tire; //null
→ 타입은 필드에 저장할 데이터의 종류를 결정하고, 초기값이 없는경우 객체 생성시 사용되는 기본값으로 초기화 된다.
필드와, 변수의 차이
- 변수는 생성자와 메소드 블록에서 선언되며, 생성자와 메소드 호출 시에만 생성,
- 필드는 클래스 블록에서 선언되며, 객체 내부에서 존재하고 내,외부에서 모두 사용가능
구분 | 필드 | 변수 |
---|---|---|
선언 위치 | 클래스 선언 블록 | 생성자, 메소드 선언 블록 |
존재 위치 | 객체 내부에 존재 | 생성자, 메소드 호출 시에만 존재 |
사용 위치 | 객체 내,외부 모두 사용 가능 | 생성자, 메소드 블록 내부에서만 사용 |
필드 사용
- 필드를 사용한다는 것은 필드 값을 읽거나, 변경 한다는 것을 말한다.
→ 클래스에서 필드에 초기값을 준다는 것은 보통 모든 객체가 동일한 필드 값을 가질때 이용한다.
6.7 생성자 선언과 호출
- new 연산자는 객체를 생성한 후 연이어 생성자를 호출해서 객체를 초기화하는 역할을 한다.
- 객체 초기화란? → 필드를 초기화 하거나, 메소드를 호출하여 객체를 사용할 준비를 하는 것
클래스 변수 = new 클래스();
//생성자 호출
생성자가 성공적으로 실행이 끝나면 new연산자는 객체의 주소를 리턴한다.
따라서 변수는 객체의 주소값이 대입되어 객체의 필드나, 메소드에 접근할때 이용된다.
기본 생성자
- 생성자를 선언하지 않을시 컴파일 단계에서 바이트코드 파일에 자동으로 생성되는 생성자.
Car.java
public class Car{
}
Car.class
public class Car{
public Car(){} //자동으로 추가된다.
}
따라서
Car myCar = new Car();
에서 기본 생성자를 호출할 수 있다.
생성자 선언
- 객체를 다양하게 초기화 하기 위해, 직접 생성자를 초기화 할수 있다.
- 생성자는 메소드와 비슷한 모양을 가지고 있으나, 리턴 타입이 없고, 클래스 이름과 동일하다.
public class Car{
Car(String model, String color, int maxSpeed){...}
-> 클래스에서 생성자를 개발자가 선언하면, 컴파일러는 기본생성자를 만들지 않는다.
}
-> car myCar = new Car("그랜저", "검정", 300);
-> 이렇게 생성자를 호출할때 3개의 값이 model, color, maxspeed 매개변수로 대입된다.
필드 초기화
- 객체마다 동일한 값을 가지고 있다면 필드 선언 시 초기값을 대입하는것이 좋고, 객체마다 다른 값을 가진다면 필드를 초기화 하는것이 좋다.
- 객체 안에서 자기 자신을 가르킬대는 this를 사용한다.
public Korean(String name, String ssn){
this.name = name;
this.ssn = ssn;
}
-> 매개변수로 받은 값을 name와 ssn에 저장한다.
public Korean(String name, String ssn){
name = name;
ssn = ssn;
}
-> 이렇게 해버리면 JVM에서 필드를 필드로 생각하지 않고 모두 지역 변수로 생각한다.
생성자 오버로딩
- 생성자 오버로딩 이란? → 매개변수를 달리하여 생성자를 여러개 선언 하는 것.
- 생성자 오버로딩이 필요한 이유 → 매개값으로 객체의 필드를 다양하게 초기화하기 위하여
- 매개변수의 타입, 개수, 순서가 다르게 여러 개의 생성자를 선언한다.
public class Car{
//이름 동일, ()존재, 매개변수 확인
Car(){..}
Car(String model){...}
Car(String model, String color){...}
Car(String model, String color, int maxSpeed){...}
}
-> 예시
Car car1 = new Car();
Car car2 = new Car("그랜저");
Car car3 = new Car("그랜저", "흰색");
Car car4 = new Car("그랜저", "흰색", 300);
---
Car(String model, String color){...}
Car(String color, String model){...} //타입과 갯수가 구분되지 않으므로 오버로딩이 아니다
//컴파일 오류 발생
다른 생성자 호출
- 생성자 오버로딩이 많아질 수록 생성자 간의 중복 코드가 많이 발생할수 있는데, 이때 this를 사용하여 공통 코드를 개선할 수 있다.
Car(String model){
this.model = model;
this.color = "은색";
this.maxSpeed = 250;
}
Car(String model, String color){
this.model = model;
this.color = color;
this.maxSpeed = 250;
}
Car(String model, String color, int maxSpeed){
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
------
this를 사용한 개선된 코드
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;
}
6.8 메소드 선언과 호출
- 메소드 선언 = 객체의 동작을 실행 블록으로 정의하는 것
- 메소드 호출 = 실행 블록을 실제로 실행 하는 것
메소드 선언
리턴타입 메소드명 (매개변수,...){
//실행 블록
실행 코드 작성
}
//리턴타입 메소드명 -> 선언부 라고 말하며, 리턴 타입에는 메소드가 리턴하는 값의 타입을 표시
//매개변수란 메소드가 실행할 때 필요한 매개값을 전달받기 위한 변수
int add(int x, int y){
int result = x+y;
return result;
}
add(3,5)-> 메소드 호출
리턴 타입
- 리턴 타입은 메소드가 실행한 후 호출한 곳으로 전달하는 결과값의 타입을 말한다.
- 리턴값이 없을때 메소드는 void로 작성한다
void powerOn(){...} //리턴값이 없는 메소드
double divide(int x, int y){...}// double형의 리턴값을 만환하는 메소드
-> return값으로 오는 type이 return type으로 자동형 변환을 할수 있다면 자동 타입 변환 가능
메소드명
- 메소드 명은 첫 문자를 소문자로 시작하는 캐멀 스타일 run(), setSpeed(), getName()
매개변수
- 매개변수는 메소드를 호출할 때 전달한 매개값을 받기 위해 사용한다. → 전달한 매개변수가 없다면 생략 가능
실행블록
- 메소드 호출 시 실행되는 부분.
메소드 호출
- 메소드를 호출한다는 것은 실행 블록을 실행시키는 것 이다.
- 메소드 호출은 클래스에서 메소드를 선언 하였다고 해서 바로 호출하는 것이아니라, 객체가 존재 하여야 호출이 가능하다. 메소드란 객체의 동작이므로 객체가 존재하여야 한다.
- 메소드의 경우 같은 객체 내,외부 모두에서 호출이 가능하다 사용방법이 다르다.
- 객체 내부에서 호출시 메소드명을 통한 호출이 가능하지만, 외부에서 호출할 경우 참조 변수와 도트(.) 연산자를 통하여 호출한다.
타입 변수 = 메소드();
//객체 내부
//생성자
Calculator(){
//메소드호출
powerOff();
}
void powerOn(){...}
void powerOff(){...}
int plus(int x, int y){...}
double divide(int x, int y){...}
void method(){
powerOn;
int r1 = plus(3,5); //메소드 호출
double r2 = divide(15,3); //메소드 호출
}
//객체 외부
void methoed(){
Calculator calc = new Calculator();
//객체 외부에서는 도트(.)연산자 이용
calc.powerOn();
int r1 = calc.plus(3.5);
double r2 = calc.divide(15,3);
}
가변길이 매개변수
- 메소드를 호출할 때에는 매개변수의 개수에 맞게 매개값을 제공해야 한다.
- 하지만 가변길이 매개변수를 가지고있다면 매개변수의 갯수에 영향이 없다.
//일반적인 2개의 매개변수
int sum(int x, int y){}
//가변길이 매개변수
int sum(int ... values){}
->
가변길이 매개변수의 경우 사전에 정한 타입만 배열형태로 저장되고, 배열로 넣을 수도 잇다.
int result = sum(new int[]{1,2,3);
return
- return문은 메소드의 실행을 강제 종료하고, 호출한 곳으로 돌아간다는 의미
return [리턴값];
-> 리턴값이 없다면 void로 메소드를 선언, 그렇지 않다면 리턴값의 type으로 선언
메소드 오버로딩
- 메소드 오버로딩은 메소드 이름은 같되, 매개변수 타입, 개수, 순서가 다른 메소드를 여러개 선언하는것. → 다양한 매개값들을 처리하기 위함.
class 클래스{
리턴타입 메소드이름 (타입 변수, ...){}
//무관 동일 타입, 갯수, 순서 가 달라야한다.
리턴타입 메소드이름 (타입 변수, ...){}
- 대표적으론 메소드 오버로딩의 예로는 System.out.println() 메소드가 있다.
- void println(), void println(double x), void println(int x), void println(String x)
6.9 인스턴스 멤버
- 필드와 메소드는 선언 방법에 따라 인스턴스 멤버와 / 정적 멤버로 분류할 수 있다.
- 인스턴스 멤버 → 객체 생성 후 사용 가능
- 정적 멤버 → 객체 없이도 사용 가능
구분 | 설명 |
---|---|
인스턴스 멤버 | 객체에 소속된 멤버 |
(객체가 가지고 있는 멤버 → 객체 생성 후 사용 가능) | |
정적 멤버 | 클래스에 고정된 멤버 |
(객체 없이도 사용 가능) |
인스턴스 멤버 선언 및 사용
- 인스턴스 멤버란 객체에 소속된 멤버로서, 객체가 있어야만 사용 가능한 멤버이다.
- 객체에 포함된과, 소속된의 차이를 주의하여야 한다.
- 인스턴스 멤버의 경우 객체에 포함되지 않더라고 객체가 존재하고, 그 객체에 소속 되면 사용 가능하다. 포함의 경우 항상 그 객체 내부에 존재하여야 하는 것이다. 따라서 차이가 있다.
this키워드
- 객체 내부에서 인스턴스 멤버에 접근하기 위해 this 키워드를 사용한다. 우리가 “나” 라고 자신을 칭하듯 객체는 자신을 this라고 칭한다.
- this는 객체에 소속된 멤버만 사용 가능하고, 정적 멤버는 사용할 수 없다.
6.10 정적 멤버
- 필드와 메소드를 의미한다.
- 자바는 클래스파일을 로더를 통하여 메소드 영역에 저장하고 사용하는데, 정적 멤버란 이때 메소드영역의 클래스에 고정적으로 위치하는 멤버를 말한다. 따라서 정적 멤버는 객체를 생성할 필요 없이 클래스를 통해 바로 사용 가능하다.
- 정적 필드와 메소드를 선언하려면 static 키워드를 추가하면 되고, 객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다.
public class 클래스{
static 타입 필드 [=초기값];
static 리턴타입 메소드 (매개변수,...){...}
}
public class Calculator{
String color; //색깔은 다를 수 있다. -> 인스턴스 필드
void setColor(String color){this.color = color;} -> 인스턴스 메소드 -> 객체가 있어야 사용가능
static int plus(int x, int y){return x+y;} //정적 메소드
static double pi = 3.14159; //파이값은 동일하다
정적 멤버 사용
- 정적 멤버를 사용할때에는 새롭게 객체를 생성하지 않고 바로 도트(.), 와 연산자로 접근 가능하다. 객체를 생성하여 참조변수로 접근도 가능은 하지만 사용하진 않는다.
public class Calculator{
static double pi = 3.14159;
static int plus(int x, int y){...}
static int minus(int x,int y){...}
}
double result1 = 10*10*Calculator.pi; //이렇게 사용
Calculator myCalc = new Calculator();
double result1 = 10*10*Calculator.pi; //이렇게 사용하지 않는다
인스턴스 멤버 사용 불가
- 정적 필드와, 메소드는 객체 없이도 사용이 가능하다는 특징 때문에, 내부에 인스턴스 필드나 인스턴스 메소드를 사용 할 수 없다. 또한 this 역시 객체가 필요하므로 사용할 수 없다.
static void Method3(){
//정적 메소드에서 인스턴스 멤버를 사용하기 위해선 객체를 생성하여야 한다.
ClassName obj = new ClassName();
obj.field1 = 10;
obj.method1();
}
6.11 final 필드와 상수
- 인스턴스 필드와 , 정적 필드는 언제든지 값을 변경할 수 있다. 값을 변견하는 것을 막고 읽기만 허용해햐 할 때 final 필드와 상수를 선언하여 사용한다.
final 타입 필드 [=초기값];
//final 필드에서 초기값을 주는 방법은 2가지 밖에 없다.
1. 필드 선언시 초기값 대입
2. 생성자에서 초기값 대입
상수 선언
- 우리 주변의 불변의 값 원주율(pi), 무게, 둘레등 이러한 불변의 값들을 상수 라고 부른다.
- 상수는 객체마다 저장할 필요가 없고, 여러개의 값을 가질수도 없으므로 static 이면서 final인 특성을 가져야 한다.
- 상수의 경우 이름의 모두를 대문자로 작성하는 것이 관례이다.
6.12 패키지
- 자바에서 패키지는 단순히 디렉토리만을 의미하는 것이 아닌, 클래스의 일부분으로서 클래스를 식별하는 용도로 사용된다.
패키지 선언
- 항상 소스 파일 최상단에 위치해야 한다.
- 패키지 이름은 모두 소문자로 작성하고, 회사 도메인 이름의 역순이 관례이다
import 문
- 다른 패키지에 있는 클래스를 사용하려면 import 문을 사용하여 어떤 패키지의 클래스를 사용하는지 명시해야 한다.
- 동일한 패키지에 포함되어있는 다수의 클래스를 사용할때에는 *을 사용할 수 있다. 단 하위 패키지들은 포함하지 않는다.
import com.hankook.* //honkook의 하위 패키지들은 포함하지 않는다.
import com.hankook.project.* //하위 패키지를 사용할꺼면 이렇게 해주어야 한다.
-> 만약 2가지 모두 동일한 클래스의 이름이 존재한다면, 어떤 패키지의 클래스를 사용할껀지
-> 전체 경로를 적어준다.
6.13 접근 제한자
- 중요한 필드와 메소드가 외부로 노출되지 않도록 하기 위해 객체의 무결성(결점이 없는 성질)을 유지하기 위해 접근제한자를 사용한다.
접근 제한자 | 제한 대상 | 제한 범위 |
---|---|---|
public | 클래스, 필드, 생성자, 메소드 | 없음 |
protected | 필드, 생성자, 메소드 | 같은 패키지 이거나, 자식 객체만 사용 가능 |
(default) | 클래스, 필드, 생성자, 메소드 | 같은 패키지 |
private | 필드, 생성자, 메소드 | 객체 내부 |
6.14 Getter와 Setter
- 객체의 필드를 외부에서 마음대로 읽고 변경할 경우 객체의 부결성이 깨질 수 있으므로, 이를 막기 위한 방법
- Getter : 어떤 메소드에서 값을 가지고 오는것
- Setter: 어떤 메소드 값을 변경하는 것.
- 즉 필드값을 private으로 외부에서 변경하지 못하게 하고, Setter로 변경하고, Getter로 가져온다.
private int maxSpeed; //필드 접근 제한자 private
//Getter
public int getMaxSpeed(){
return maxSpeed;
}
//메소드 이름을 get+필드이름(첫 글자 대문자)
//Setter
public void setMaxSpeed(int maxSpeed){
this.maxSpeed = maxSpeed;
}
//메소드 이름을 set+필드이름(첫 글자 대문자)
//만약 boolean 타입일 경우 get을 is로 시작하는 것이 관례이다. Setter은 동일
isMaxSpeed
6.15 싱글톤 패턴
- 애플리케이션 전체에서 단 한개의 객체를 만드는데 사용하는 패턴 이다.
- 핵심은 생성자를 private으로 접근 제한해서 외부에서 new연산자로 생성자를 호출할수 없게 하는 것이다.
public class 클래스{
private static 클래스 singleton = new 클래스();
//private 접근 권한을 갖는 정적 필드를 선언과 초기화 한다.
private 클래스(){}
//private 접근 권한을 갖는 생성자 생성
public static 클래스 getInstance(){
return singleton;
}
}
//public 접근 권한을 갖는 정적 메소드를 선언하여 객체를 얻는다.
//이 방법을 통해 getInstance() 메소드만을 통하여 외부에서 객체를 얻을 수 있다.
'JAVA > 이것이 자바다' 카테고리의 다른 글
자바 개념 정리 (0) | 2023.03.01 |
---|---|
[Chapter 05] 참조 타입 (0) | 2022.12.31 |
[Chapter 04] 조건문과 반복문 (0) | 2022.12.03 |
[Chapter 03] 연산자 (0) | 2022.12.03 |
[Chapter 02] 변수와 타입 (0) | 2022.12.03 |
댓글