1. 객체
-세상에 존재하는 물체를 뜻하며 식별이 가능한 것을 의미
-객체는 속성과 행위로 구성
-Java에서는 이러한 속성과 행위를 필드와 메서드로 정의하여 구현
-객체지향 프로그래밍의 특징 : 캡슐화, 상속, 다형성, 추상화
2. 클래스
-클래스는 객체를 생성하기 위한 설계도
-클래스의 구성 멤버에는 필드, 생성자, 메서드가 있다.
-클래스를 토대로 생성된 객체를 해당 클래스의 ‘인스턴스’라고 부르며 이 과정을 ‘인스턴스화’라고 부른다.
-동일한 클래스로 여러 개의 인스턴스를 만들 수 있다.
*클래스를 만들기 위한 4 STEP
1) 만들려고 하는 설계도를 선언(클래스 선언)
public class Car { }
2) 객체가 가지고 있어야 할 속성(필드)을 정의, 데이터를 저장하는 역할
String company; // 자동차 회사
String color; // 자동차 색상
double speed; // 자동차 속도 , km/h
char gear; // 기어의 상태, P,R,N,D
boolean lights; // 자동차 조명의 상태
3) 객체를 생성하는 방식을 정의(생성자)
public Car() {}
생성자는 반환 타입이 없고 이름은 클래스의 이름과 동일하다.
괄호( ) 안에 아무것도 없는 생성자를 기본 생성자라 한다.
4) 객체가 가지고 있어야 할 행위(메서드)를 정의
char changeGear(char type) {
gear = type;
return gear;
}
double brakePedal() {
speed = 0;
return speed;
}
3. 객체 생성과 참조형 변수
객체 생성 연산자인 ‘new’를 사용하면 클래스로부터 객체를 생성할 수 있다.
Car car1 = new Car(); // Car클래스의 객체인 car1 인스턴스 생성
객체 배열
객체는 참조형 변수와 동일하게 취급되기 때문에 배열 또는 컬렉션에도 저장하여 관리할 수 있다.
public class Main {
public static void main(String[] args) {
Car[] carArray = new Car[3];
Car car1 = new Car();
car1.changeGear('P');
carArray[0] = car1;
Car car2 = new Car();
car2.changeGear('N');
carArray[1] = car2;
Car car3 = new Car();
car3.changeGear('D');
carArray[2] = car3;
for (Car car : carArray) {
System.out.println("car.gear = " + car.gear);
}
}
}
// 출력
//car.gear = P
//car.gear = N
//car.gear = D
4. 객체의 속성 : 필드
-객체의 데이터를 저장하는 역할
-객체의 필드는 크게 3가지로, 고유한 데이터 / 상태 데이터 / 객체 데이터로 분류
-‘필드를 사용한다’라는 의미는 필드의 값을 변경하거나 읽는 것을 의미
- 우리가 클래스에 필드를 정의하여 선언했다고 해서 바로 사용할 수 있는 것은 아니다.
- 클래스는 설계도일 뿐 실제로 필드의 데이터를 가지고 있는 것은 객체이다.
- 따라서 객체를 생성한 후에 필드를 사용할 수 있다.
-접근
▶ 외부 접근
Car car = new Car();
이렇게 객체를 생성했다면 우리는 참조 변수 car를 이용하여 외부에서 객체 내부의 필드에 접근하여 사용할 수 있다.
이때 객체의 내부 필드에 접근하는 방법은 도트(.) 연산자를 사용하면 된다.
car.color = "blue";
▶ 내부 접근
도트 연산자를 사용하여 외부에서 객체 내부에 접근할 수 있을 뿐만 아니라 객체 내부 메서드에서도 내부 필드에 접근할 수 있다.
double brakePedal() {
speed = 0;
return speed;
}
이처럼 brakePedal() 메서드 내부에서 객체의 필드 speed를 바로 호출해서 사용할 수 있습니다.
public class Car {
String company; // 자동차 회사
String model = "Gv80"; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
double speed; // 자동차 속도 , km/h
char gear; // 기어의 상태, P,R,N,D
boolean lights = true; // 자동차 조명의 상태
Tire tire = new Tire();
Door door;
Handle handle;
public Car() {} // 기본 생성자
double gasPedal(double kmh) {
speed = kmh;
return speed;
}
double brakePedal() {
speed = 0;
return speed;
}
char changeGear(char type) {
gear = type;
return gear;
}
boolean onOffLights() {
lights = !lights;
return lights;
}
void horn() {
System.out.println("빵빵");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // 객체 생성
// 초기값과 기본값 확인하기
System.out.println("car.model = " + car.model); // 초기값 "Gv80"이 출력됩니다.
System.out.println("car.color = " + car.color); // 기본값 null이 출력됩니다.
System.out.println();
System.out.println("car.speed = " + car.speed); // 기본값 0.0이 출력됩니다.
System.out.println("car.gear = " + car.gear); // 기본값 \u0000(공백)이 출력됩니다.
System.out.println("car.lights = " + car.lights); // 초기값 true가 출력됩니다.
System.out.println();
System.out.println("car.tire = " + car.tire); // 초기값 인스턴스의 주소가 출력됩니다.
System.out.println("car.door = " + car.door); // 기본값 null이 출력됩니다.
System.out.println();
// 필드 사용
car.color = "blue"; // 필드 color에 "blue" 데이터를 저장합니다.
car.speed = 100; // 필드 speed에 100 데이터를 저장합니다.
car.lights = false; // 필드 lights에 false 데이터를 저장합니다.
System.out.println("car.color = " + car.color); // 저장된 "blue" 데이터가 출력됩니다.
System.out.println("car.speed = " + car.speed); // 저장된 100.0 데이터가 출력됩니다.
System.out.println("car.lights = " + car.lights); // 저장된 false 데이터가 출력됩니다.
}
}
5. 객체의 행위 : 메서드
-메서드는 객체의 행위를 뜻하며 객체 간의 협력을 위해 사용된다.
1) 메서드 선언
리턴타입 메서드명(매개변수, ...) {
실행할 코드 작성
}
▶ 리턴 타입
-메서드에 리턴 타입을 선언하여 반환할 값이 있다면 반드시 return 문으로 해당하는 리턴 타입의 반환값을 지정해야 한다.
-반환할 값이 없을 때는 리턴 타입에 void를 작성해야 한다.
-메서드는 실행할 때 return문을 만나면 그대로 종료하게 되는데 void 타입일 때 return; 이렇게 return문을 사용하여 원하는 지점에서 메서드를 종료할 수도 있다.
▶ 매개변수
-해당 매개변수에 값을 전달하기 위해서는 순서와 타입에 맞춰 값을 넣어주면 된다.
-전달하려는 값이 없다면 생략 가능하다.
-가변 길이의 매개변수도 선언할 수 있다.
void carSpeeds(double ... speeds) {
for (double v : speeds) {
System.out.println("v = " + v);
}
}
double … speeds 이렇게 … 을 사용하면 아래처럼 매개값을 , 로 구분하여 개수 상관없이 전달 가능하다.
carSpeeds(100, 80);
carSpeeds(110, 120, 150);
2) 메서드 호출 방법
-‘메서드를 호출한다’라는 의미는 메서드의 블록 내부에 작성된 코드를 실행한다.
- 필드와 마찬가지로 클래스의 메서드를 정의하여 선언했다고 해서 바로 사용할 수 있는 것은 아니다.
- 클래스는 설계도일 뿐 메서드는 객체의 행위를 정의한 것이다. 따라서 객체를 생성한 후에 메서드를 사용할 수 있다.
▶ 외부 접근
Car car = new Car();
이렇게 객체를 생성했다면 우리는 참조 변수 car를 이용하여 외부에서 객체 내부의 메서드에 접근하여 호출할 수 있다.
이때 객체의 내부 메서드에 접근하는 방법은 도트(.) 연산자를 사용하면 된다.
car.brakePedal();
또한 메서드가 매개변수를 가지고 있다면 반드시 호출할 때 매개변수의 순서와 타입에 맞게 매개값을 넣어줘야 한다.
car.gasPedal(100, 'D');
▶ 내부 접근
도트 연산자를 사용하여 외부에서 객체 내부에 접근할 수 있을 뿐만 아니라 객체 내부 메서드에서도 내부 메서드에 접근하여 호출할 수 있다.
double gasPedal(double kmh, char type) {
changeGear(type);
speed = kmh;
return speed;
}
이처럼 gasPedal(double kmh, char type) 메서드 내부에서 해당 객체의 changeGear(type); 메서드를 호출할 수 있다.
▶ 반환 값 저장
메서드의 리턴 타입을 선언하여 반환할 값이 있다면 변수를 사용하여 받아줄 수 있다.
반드시 리턴 타입과 변수의 타입이 동일하거나 자동 타입 변환될 수 있어야 한다.
double speed = car.gasPedal(100, 'D');
double 타입의 변수 speed를 사용하여 double gasPedal(double kmh, char type) 메서드의 double 타입의 반환값을 받아 저장할 수 있다.
public class Car {
String company; // 자동차 회사
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
double speed; // 자동차 속도 , km/h
char gear = 'P'; // 기어의 상태, P,R,N,D
boolean lights; // 자동차 조명의 상태
public Car() {} // 기본 생성자
double gasPedal(double kmh, char type) {
changeGear(type);
speed = kmh;
return speed;
}
double brakePedal() {
speed = 0;
return speed;
}
char changeGear(char type) {
gear = type;
return gear;
}
boolean onOffLights() {
lights = !lights;
return lights;
}
void horn() {
System.out.println("빵빵");
}
void carSpeeds(double ... speeds) {
for (double v : speeds) {
System.out.println("v = " + v);
}
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // 객체 생성
// 메서드 호출 및 반환값 저장
double speed = car.gasPedal(100, 'D');
System.out.println("speed = " + speed);
boolean lights = car.onOffLights();
System.out.println("lights = " + lights);
System.out.println();
// gasPedal 메서드 내부에 호출된 changeGear(type); 메서드의 결과 확인
// gear의 초기값은 'P'
System.out.println("car.gear = " + car.gear); // 'D' 출력
System.out.println();
// 가변길이 매개변수 확인
car.carSpeeds(100, 80);
System.out.println();
car.carSpeeds(110, 120, 150);
}
}
//출력
//speed = 100.0
//lights = true (필드에서 초기화하지 않은 기본값이 false라서 해당 메소드 호출 후 true로 변경
//car.gear = D
//v = 100.0
//v = 80.0
//v = 110.0
//v = 120.0
//v = 150.0
3) 메서드 오버로딩
함수가 하나의 기능만을 구현하는 것이 아니라 하나의 메서드 이름으로 여러 기능을 구현하도록 하는 Java의 기능.
즉, 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도, 매개변수의 개수 또는 타입, 순서가 다르면 동일한 이름을 사용해서 메서드를 정의할 수 있다.
▶ 조건
- 메서드의 이름이 같고, 매개변수의 개수, 타입, 순서가 달라야 한다.
- '응답 값만' 다른 것은 오버로딩을 할 수 없다.
- 접근 제어자만 다른 것도 오버로딩을 할 수 없다.
- 결론, 오버로딩은 매개변수의 차이로만 구현할 수 있다.
▶ 장점
- 메서드 이름 하나로 상황에 따른 동작을 개별로 정의할 수 있다.
예를 들면 메세지 출력할 때 쓰는 println() 이 있다.
println()의 매개변수로는 int, double, String, boolean 등 다양하게 넣을 수 있다.
- 메서드의 이름을 절약할 수 있다.
만약 오버로딩이 안된다면 println()는 printlnInt(), printlnDouble()처럼 메서드명이 길어지고 낭비 되었을 것이다.
// 메서드 오버로딩 예시
public class PrintStream extends FilterOutputStream
implements Appendable, Closeable
{
...
public void println() {
newLine();
}
public void println(boolean x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(char x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(int x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(long x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(float x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(double x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(char[] x) {
if (getClass() == PrintStream.class) {
writeln(x);
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(String x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(Object x) {
String s = String.valueOf(x);
if (getClass() == PrintStream.class) {
// need to apply String.valueOf again since first invocation
// might return null
writeln(String.valueOf(s));
} else {
synchronized (this) {
print(s);
newLine();
}
}
}
...
}
4) 기본형 & 참조형 매개변수
▶ 기본형 매개변수
-메서드를 호출할 때 전달할 매개값으로 지정한 값을 메서드의 매개변수에 복사해서 전달한다.
-매개변수의 타입이 기본형일 때는 값 자체가 복사되어 넘어가기 때문에 매개값으로 지정된 변수의 원본 값이 변경되지 않는다.
▶ 참조형 매개변수
-메서드를 호출할 때 전달할 매개값으로 지정한 값의 주소를 매개변수에 복사해서 전달한다.
-매개변수를 참조형으로 선언하면 값이 저장된 곳의 원본 주소를 알 수 있기 때문에 값을 읽어 오는 것은 물론 값을 변경하는 것도 가능하다.
-메서드의 매개변수뿐만 아니라 반환 타입도 참조형이 될 수 있습니다. 반환 타입이 참조형이라는 것은 반환하는 값의 타입이 “실제 값의 주소”라는 의미이다.
public class Car {
String company; // 자동차 회사
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
double speed; // 자동차 속도 , km/h
char gear; // 기어의 상태, P,R,N,D
boolean lights; // 자동차 조명의 상태
Tire tire;
Door door = new Door();
Handle handle = new Handle();
public Car() {} // 기본 생성자
double gasPedal(double kmh, char type) {
changeGear(type);
speed = kmh;
return speed;
}
double brakePedal(char type) {
speed = 0;
type = 'P'; // 정지 후 매개변수 type을 어떤 타입으로 전달 받았는지 상관없이 'P'로 고정시키기
changeGear(type);
return speed;
}
char changeGear(char type) {
gear = type;
return gear;
}
boolean onOffLights() {
lights = !lights;
return lights;
}
void horn() {
System.out.println("빵빵");
}
Tire setTire(Tire tireCompany) {
tireCompany.company = "KIA"; // 금호 타이어를 전달 받았지만 강제로 KIA 타이어로 교체
tire = tireCompany;
return tire;
}
}
public class Tire {
String company; // 타이어 회사
public Tire() {}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // 객체 생성
// 기본형 매개변수
char type = 'D';
car.brakePedal(type);
// 메서드 실행 완료 후 전달할 매개값으로 지정된 type 값 확인
System.out.println("type = " + type); // 기존에 선언한 값 'D' 출력, 원본 값 변경되지 않음
// 메서드 실행 완료 후 반환된 car 인스턴스의 gear 타입 확인
System.out.println("gear = " + car.gear); // 객체 내부에서 type을 변경하여 수정했기 때문에 'P' 출력
System.out.println();
// 참조형 매개변수
Tire tire = new Tire();
tire.company = "금호"; // 금호 타이어 객체 생성
// 차 객체의 타이어를 등록하는 메서드 호출한 후 반환값으로 차 객체의 타이어 객체 반환
Tire carInstanceTire = car.setTire(tire);
// 메서드 실행 완료 후 전달할 매개값으로 지정된 참조형 변수 tire의 company 값 확인
System.out.println("tire.company = " + tire.company); // "KIA" 출력
// 전달할 매개값으로 지정된 tire 인스턴스의 주소값이 전달되었기 때문에 호출된 메서드에 의해 값이 변경됨.
// 메서드 실행 완료 후 반환된 car 인스턴스의 tire 객체 값이 반환되어 저장된 참조형 변수 carInstanceTire의 company 값 확인
System.out.println("carInstanceTire.company = " + carInstanceTire.company); // "KIA" 출력
}
}
6.인스턴스 멤버와 클래스 멤버
▶ 멤버 = 필드 + 메서드
인스턴스 멤버 = 인스턴스 필드 + 인스턴스 메서드
클래스 멤버 = 클래스 필드 + 클래스 메서드
-인스턴스 멤버 & 클래스 멤버
필드와 메서드는 선언하는 방법에 따라서 인스턴스 멤버와 클래스 멤버로 구분할 수 있다.
인스턴스 멤버는 객체 생성 후에 사용할 수 있고 클래스 멤버는 객체 생성 없이도 사용할 수 있다.
객체의 인스턴스 필드는 객체 생성 후 각각의 인스턴스마다 고유하게 값을 가질 수 있다.
클래스 멤버란 메서드 영역의 클래스와 같은 위치에 고정적으로 위치하고 있는 멤버를 의미하기에 객체의 생성 필요 없이 바로 사용이 가능하다.
-클래스 멤버 선언
필드와 메서드를 클래스 멤버로 만들기 위해서는 static 키워드를 사용하면 된다.
- 일반적으로 인스턴스마다 모두 가지고 있을 필요 없는 공용적인 데이터를 저장하는 필드는 클래스 멤버로 선언하는 것이 좋다.
- 또한 인스턴스 필드를 사용하지 않고 실행되는 메서드가 존재한다면 static 키워드를 사용하여 클래스 메서드로 선언하는 것이 좋다.
⚠️ 주의할 점
- 클래스 멤버로 선언된 메서드는 인스턴스 멤버를 사용할 수 없다.
- 반대로 인스턴스 멤버로 선언된 메서드는 클래스 멤버를 사용할 수 있다.
- 클래스 멤버는 객체 생성 없이 바로 사용 가능하기 때문에 객체가 생성되어야 존재할 수 있는 인스턴스 멤버를 사용할 수 없다.
-클래스 멤버 사용
클래스 멤버를 사용하려면 클래스의 이름과 함께 도트(.) 연산자를 사용하면 된다.
Car.company = "Audi";
String companyName = Car.setCompany("Benz");
참조형 변수를 사용하여 클래스 멤버에 접근은 가능하지만 추천하지 않습니다. 클래스 이름으로 접근하는 것이 좋다.
Car car = new Car(); // 객체 생성
car.company = "Ferrari";
String companyName2 = car.setCompany("Lamborghini");
public class Car {
static String company = "GENESIS"; // 자동차 회사 : GENESIS
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
double speed; // 자동차 속도 , km/h
char gear; // 기어의 상태, P,R,N,D
boolean lights; // 자동차 조명의 상태
public Car() {} // 기본 생성자
double gasPedal(double kmh, char type) {
changeGear(type);
speed = kmh;
return speed;
}
double brakePedal() {
speed = 0;
return speed;
}
char changeGear(char type) {
gear = type;
return gear;
}
boolean onOffLights() {
lights = !lights;
return lights;
}
void horn() {
System.out.println("빵빵");
}
String getCompany() {
return "(주)" + company;
}
static String setCompany(String companyName) {
// System.out.println("자동차 모델 확인: " + model); // 인스턴스 필드 사용 불가
company = companyName;
return company;
}
}
public class Main {
public static void main(String[] args) {
// 클래스 필드 company 확인, 객체 생성 없이 클래스 멤버 접급
System.out.println(Car.company + "\n"); // 출력 GENESIS
// 클래스 필드 변경 및 확인
Car.company = "Audi";
System.out.println(Car.company + "\n"); // 출력 Audi
// 클래스 메서드 호출
String companyName = Car.setCompany("Benz");
System.out.println("companyName = " + companyName); // 출력 companyName = Benz
System.out.println();
// 참조형 변수를 사용하여 클래스 멤버 접근(가능하지만 NO 추천)
Car car = new Car(); // 객체 생성
car.company = "Ferrari";
System.out.println(car.company + "\n"); // 출력 Ferrari
String companyName2 = car.setCompany("Lamborghini");
System.out.println("companyName2 = " + companyName2); // 출력 companyName2 = Lamborghini
}
}
▶ 지역변수
-메서드 내부에 선언한 변수를 의미한다.
-메서드가 실행될 때마다 독립적인 값을 저장하고 관리하게 된다.
-지역 변수는 메서드 내부에서 정의될 때 생성되어 메서드가 종료될 때까지만 유지된다.
public class Main {
public static void main(String[] args) {
Main main = new Main();
// 메서드 호출 : main.getClass()
System.out.println("main.getClass() = " + main.getNumber());
System.out.println("main.getClass() = " + main.getNumber());
System.out.println("main.getClass() = " + main.getNumber());
}
public int getNumber() {
int number = 1; // 지역 변수
number += 1;
return number; // 메서드 종료되면 지역변수 제거됨
}
}
// 출력
//main.getNumber() = 2
//main.getNumber() = 2
//main.getNumber() = 2
▶ final 필드
- final 필드는 초기값이 저장되면 해당값을 프로그램이 실행하는 도중에는 절대로 수정할 수 없다.
- 또한 final 필드는 반드시 초기값을 지정해야 한다.
final String company = "GENESIS";
..................................
Car car = new Car();
System.out.println(car.company);
- 필드 타입 앞에 final 키워드를 추가하여 final 필드를 선언할 수 있다.
- 사용방법은 일반적인 인스턴스 필드와 동일합니다. 다만 수정이 불가능하다.
- car.company = "Benz"; 이렇게 수정하려 하면 오류가 발생하다.
▶ 상수
- 상수의 특징은 값이 반드시 한 개이며 불변의 값을 의미한다.
- 따라서 인스턴스마다 상수를 저장할 필요가 없다.
static final String COMPANY = "GENESIS";
..................................
System.out.println(Car.COMPANY);
- final 앞에 static 키워드를 추가하여 모든 인스턴스가 공유할 수 있는 값이 한 개이며 불변인 상수를 선언할 수 있다.
- 사용방법은 일반적인 클래스 필드와 동일합니다. 다만 수정이 불가능하다.
- Car.COMPANY = "Benz"; 이렇게 수정하려 하면 오류가 발생한다.
- 일반적으로 상수는 대문자로 작성하는 것이 관례이다.
7. 생성자
생성자는 객체가 생성될 때 호출되며 객체를 초기화하는 역할을 수행한다.
생성자는 반환 타입이 없고 이름은 클래스의 이름과 동일하다.
new 연산자에 의해 객체가 생성되면서 Car(); 즉, 생성자가 호출된다.
public Car() {} // 선언
..................................
Car car = new Car(); // 호출
▶ 기본 생성자
선언할 때 괄호( ) 안에 아무것도 넣지 않는 생성자를 의미한다.
- 모든 클래스는 반드시 생성자가 하나 이상 존재한다.
- 만약 클래스에 생성자를 하나도 선언하지 않았다면 컴파일러는 기본 생성자를 바이트코드 파일에 자동으로 추가시켜준다. 따라서 이러한 경우는 기본 생성자 생략이 가능하다.
- 반대로 단 하나라도 생성자가 선언되어 있다면 컴파일러는 기본 생성자를 추가하지 않는다.
public class Car {
public Car(String model) {} // 생성자 선언
// 생성자가 한개 이상 선언되었기 때문에 기본 생성자를 추가하지 않음.
}
- 컴파일러에 의해 생성되는 기본 생성자는 해당 클래스의 접근 제어자(public, …)를 따른다.
public class Car {
public Car() {} // 컴파일러가 추가시켜줌
}
class Car {
Car() {} // 컴파일러가 추가시켜줌
}
▶ 필드 초기화
생성자는 객체를 초기화하는 역할을 수행한다.
- 객체를 만들 때 인스턴스마다 다른 값을 가져야 한다면 생성자를 통해서 필드를 초기화할 수 있다.
- 예를 들어 만들어지는 자동차마다 모델, 색상, 가격이 다르다면 생성자를 사용하여 필드의 값을 초기화하는 것이 좋다.
- 반대로 인스턴스마다 동일한 데이터를 가지는 필드는 초기값을 대입하는 것이 좋다.
- 예를 들어 자동차가 만들어질 때마다 기어의 상태를 ‘P’로 고정해야 한다면 초기값을 직접 대입하는 것이 좋다.
▶ 생성자 오버로딩
생성자를 통해 필드를 초기화할 때 오버로딩을 적용할 수 있다.
- 예를 들어 우리가 자동차를 생성할 때 모델, 색상, 가격이 다른 자동차를 여러 대 생성할 수도 있고 색상만 다른 자동차를 여러 대 생성할 수도 있기 때문에 오버로딩을 사용하면 이를 효율적으로 처리할 수 있다.
⚠️ 주의할 점
- 오버로딩을 할 때 개수, 타입, 순서가 동일한데 매개변수명만 다르게 하는 경우는 오버로딩 규칙에 위배되기 때문에 오류가 발생한다!
public Car(String modelName, String colorName, double priceValue)
public Car(String colorName, String modelName, double priceValue)
- modelName과 colorName 매개변수의 위치가 다르기 때문에 가능할 것처럼 보이지만
- String, String, double : 매개변수의 개수, 타입, 순서가 동일하기 때문에 중복이 불가능하다.
public class Car {
static final String COMPANY = "GENESIS"; // 자동차 회사 : GENESIS
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
double speed; // 자동차 속도 , km/h
char gear = 'P'; // 기어의 상태, P,R,N,D
boolean lights; // 자동차 조명의 상태
public Car(String modelName) {
model = modelName;
}
public Car(String modelName, String colorName) {
model = modelName;
color = colorName;
}
public Car(String modelName, String colorName, double priceValue) {
model = modelName;
color = colorName;
price = priceValue;
}
double gasPedal(double kmh, char type) {
changeGear(type);
speed = kmh;
return speed;
}
double brakePedal() {
speed = 0;
return speed;
}
char changeGear(char type) {
gear = type;
return gear;
}
boolean onOffLights() {
lights = !lights;
return lights;
}
void horn() {
System.out.println("빵빵");
}
}
public class Main {
public static void main(String[] args) {
// 기본 생성자 호출 오류 확인
// 한 개 이상의 생성자가 존재하기 때문에 컴파일러가 자동으로 기본 생성자를 추가해 주지 않기 때문에 기본 생성자가 존재하지 않아 오류가 발생
// Car car1 = new Car(); // 오류 발생
// 생성자 오버로딩을 통해 여러 상황에서 자동차 생산
// 제네시스 자동차를 생산 : static final String COMPANY = "GENESIS"; 상수 고정
// 모든 자동차는 생산시 기어의 최초 상태 'P' 로 고정 : char gear = 'P'; 직접 대입하여 초기화
// GV60 모델만 기본으로 선택
Car car2 = new Car("GV60");
System.out.println("car2.model = " + car2.model); // car2.model = GV60
System.out.println("car2.gear = " + car2.gear + "\n"); // car2.gear = P
// GV70 모델, 색상 Blue 만 기본으로 선택
Car car3 = new Car("GV70", "Blue");
System.out.println("car3.model = " + car3.model); // car3.model = GV70
System.out.println("car3.color = " + car3.color); // car3.color = Blue
System.out.println("car3.gear = " + car3.gear + "\n"); // car3.gear = P
// GV80 모델, 색상 Black, 가격 50000000 으로 완전하게 고정된 경우
Car car4 = new Car("GV80", "Black", 50000000);
System.out.println("car4.model = " + car4.model); // car4.model = GV80
System.out.println("car4.color = " + car4.color); // car4.color = Black
System.out.println("car4.price = " + car4.price); // car4.price = 5.0E7
System.out.println("car4.gear = " + car4.gear + "\n"); // car4.gear = P
}
}
8. this와 this()
-this는 객체 즉, 인스턴스 자신을 표현하는 키워드이다.
- 객체 내부 생성자 및 메서드에서 객체 내부 멤버에 접근하기 위해 사용될 수 있다.
- 객체 내부 멤버에 접근할 때 this 키워드가 필수는 아니지만 상황에 따라 필수가 될 수 있다.
public Car(String model, String color, double price) {
model = model;
color = color;
price = price;
}
- 만약 위처럼 생성자를 선언하는데 매개변수명과 객체의 필드명이 동일할 경우 오류가 발생하지는 않지만 생성자 블록 내부에서 해당 변수들은 객체의 필드가 아닌 가장 가까운 매개변수명을 가리키게 됨으로 자기 자신에게 값을 대입하는 상황이 되어 버린다.
- 이럴 경우에 this 키워드를 사용하여 아래와 같이 해결할 수 있다. this 키워드를 통해 변수명에 해당하는 객체의 필드에 접근하여 받아온 매개변수의 매개값을 객체의 필드에 대입하여 저장할 수 있다.
public Car(String model, String color, double price) {
this.model = model;
this.color = color;
this.price = price;
}
- 또한 this는 인스턴스 자신을 뜻하기 때문에 객체의 메서드에서 리턴 타입이 인스턴스 자신의 클래스 타입이라면 this를 사용하여 인스턴스 자신의 주소를 반환할 수도 있다.
Car returnInstance() {
return this;
}
-this(…)는 객체 즉, 인스턴스 자신의 생성자를 호출하는 키워드이다.
- 객체 내부 생성자 및 메서드에서 해당 객체의 생성자를 호출하기 위해 사용될 수 있다.
- 생성자를 통해 객체의 필드를 초기화할 때 중복되는 코드를 줄여줄 수 있다.
public Car(String model) {
this.model = model;
this.color = "Blue";
this.price = 50000000;
}
- 위처럼 생성자를 선언한다고 했을 때 코드의 중복이 발생한다.
- 이때 this() 키워드를 사용하여 아래와 같이 코드의 중복을 제거할 수 있다.
public Car(String model) {
this(model, "Blue", 50000000);
}
⚠️ 주의할 점
- this() 키워드를 사용해서 다른 생성자를 호출할 때는 반드시 해당 생성자의 첫 줄에 작성되어야 한다.
- 아래처럼 this() 키워드로 다른 생성자 호출 이전에 코드가 존재하면 오류가 발생한다.
public Car(String model) {
System.out.println("model = " + model);
this(model, "Blue", 50000000);
} // 오류 발생
public class Car {
static final String COMPANY = "GENESIS"; // 자동차 회사 : GENESIS
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
double speed; // 자동차 속도 , km/h
char gear = 'P'; // 기어의 상태, P,R,N,D
boolean lights; // 자동차 조명의 상태
public Car(String model) {
this(model, "Blue", 50000000);
}
public Car(String model, String color) {
this(model, color, 100000000);
}
public Car(String model, String color, double price) {
this.model = model;
this.color = color;
this.price = price;
}
double gasPedal(double kmh, char type) {
changeGear(type);
speed = kmh;
return speed;
}
double brakePedal() {
speed = 0;
return speed;
}
char changeGear(char type) {
gear = type;
return gear;
}
boolean onOffLights() {
lights = !lights;
return lights;
}
void horn() {
System.out.println("빵빵");
}
Car returnInstance() {
return this;
}
}
public class Main {
public static void main(String[] args) {
// 생성자 오버로딩을 통해 여러 상황에서 자동차 생산
// 제네시스 자동차를 생산 : static final String COMPANY = "GENESIS"; 상수 고정
// 모든 자동차는 생산시 기어의 최초 상태 'P' 로 고정 : char gear = 'P'; 직접 대입하여 초기화
// 모델을 변경하면서 만들 수 있고 색상 : Blue, 가격 50000000 고정
Car car1 = new Car("GV60");
System.out.println("car1.model = " + car1.model); // car1.model = GV60
System.out.println("car1.color = " + car1.color); // car1.color = Blue
System.out.println("car1.price = " + car1.price); // car1.price = 5.0E7
System.out.println("car1.gear = " + car1.gear + "\n"); // car1.gear = P
// 모델, 색상을 변경하면서 만들 수 있고 가격 100000000 고정
Car car2 = new Car("GV70", "Red");
System.out.println("car2.model = " + car2.model); // car2.model = GV70
System.out.println("car2.color = " + car2.color); // car2.color = Red
System.out.println("car2.price = " + car2.price); // car2.price = 1.0E8
System.out.println("car2.gear = " + car2.gear + "\n"); // car2.gear = P
// GV80 모델, 색상 Black, 가격 120000000 으로 완전하게 고정된 경우
Car car3 = new Car("GV80", "Black", 120000000);
System.out.println("car3.model = " + car3.model); // car3.model = GV80
System.out.println("car3.color = " + car3.color); // car3.color = Black
System.out.println("car3.price = " + car3.price); // car3.price = 1.2E8
System.out.println("car3.gear = " + car3.gear + "\n"); // car3.gear = P
// this 키워드를 통해 car3 인스턴스 자신을 반환 : car3.returnInstance() = 인스턴스의 주소
System.out.println(car3.returnInstance().model); // car3의 model GV80
System.out.println(car3.returnInstance().color); // car3의 color Black
System.out.println(car3.returnInstance().price); // car3의 price 1.2E8
}
}
9. 접근 제어자
제어자는 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여해 준다.
하나의 대상에 여러 개의 제어자를 조합해서 사용할 수 있으나, 접근 제어자는 단 하나만 사용할 수 있다.
- 접근 제어자 : public, protected, default, private
- 그 외 제어자 : static, final, abstract
접근 제어자는 멤버 또는 클래스에 사용, 외부에서 접근하지 못하도록 제한한다.
클래스, 멤버 변수, 메서드, 생성자에 사용되고, 지정되어 있지 않다면 default이다.
- public : 접근 제한이 전혀 없다.
- protected : 같은 패키지 내에서, 다른 패키지의 자손 클래스에서 접근이 가능하다.
- default : 같은 패키지 내에서만 접근이 가능하다.
- private : 같은 클래스 내에서만 접근이 가능하다.
▶ 사용 가능한 접근 제어자
- 클래스 : public, default
- 메서드 & 멤버 변수 : public, protected, default, private
- 지역변수 : 없음
▶ 접근 제어자를 이용한 캡슐화(은닉성)
- 접근제어자는 클래스 내부에 선언된 데이터를 보호하기 위해서 사용한다.
- 유효한 값을 유지하도록, 함부로 변경하지 못하도록 접근을 제한하는 것이 필요하다.
▶ 생성자의 접근 제어자
- 생성자에 접근 제어자를 사용함으로 인스턴스의 생성을 제한할 수 있다.
- 일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치한다.
▶ Getter 와 Setter
객체의 무결성 즉, 변경이 없는 상태를 유지하기 위해 접근 제어자를 사용한다.
- 이때 외부에서 필드에 직접 접근하는 것을 막기 위해 필드에 private, default 등의 접근 제어자를 사용할 수 있다.
- 그렇다면 우리는 어떻게 객체의 private 필드를 읽어오거나 저장할 수 있을까?
- 우리는 Getter 와 Setter를 사용하여 이를 해결할 수 있다.
*Getter
-외부에서 객체의 private 한 필드를 읽을 필요가 있을 때 Getter 메서드를 사용한다.
-자동차 클래스의 필드에 이처럼 private 접근 제어자로 지정한 필드가 있을 때 Getter 메서드를 통해 값을 가져올 수 있다.
private double speed; // 자동차 속도 , km/h
private char gear = 'P'; // 기어의 상태, P,R,N,D
private boolean lights; // 자동차 조명의 상태
-메서드 이름의 규칙은 : get + 필드 이름(첫 글자 대문자)이다.
-사용하는 방법은 인스턴스 메서드 호출과 동일하다.
public String getModel() {
return model;
}
public String getColor() {
return color;
}
public double getPrice() {
return price;
}
*Setter
-외부에서 객체의 private 한 필드를 저장/수정할 필요가 있을 때 Setter 메서드를 사용한다.
-자동차 클래스의 필드에 이처럼 private 접근 제어자로 지정한 필드가 있을 때 Setter 메서드를 통해 값을 저장하거나 수정할 수 있다.
private double speed; // 자동차 속도 , km/h
private char gear = 'P'; // 기어의 상태, P,R,N,D
private boolean lights; // 자동차 조명의 상태
-메서드 이름의 규칙은 : set + 필드 이름(첫 글자 대문자)이다.
-사용하는 방법은 인스턴스 메서드 호출과 동일하다.
public void setModel(String model) {
this.model = model;
}
public void setColor(String color) {
this.color = color;
}
public void setPrice(double price) {
this.price = price;
}
▶ 제어자의 조합
사용 가능한 제어자
- 클래스 : public, default, final, abstract
- 메서드 : public, protected, default, private, final, abstract, static
- 멤버 변수 : public, protected, default, private, final, static
- 지역 변수 : final
⚠️ 주의 사항
- 메서드에 static과 abstract를 함께 사용할 수 없다.
- 클래스에 abstract와 final을 동시에 사용할 수 없다.
- abstract메서드의 접근 제어자가 private일 수 없다.
- 메서드에 private와 final을 같이 사용할 필요는 없다.
10. package와 import
패키지란 클래스의 일부분이면서 클래스를 식별해 주는 용도이다.
- 패키지는 상위 패키지와 하위 패키지를 도트(.)로 구분한다.
- package 상위 패키지.하위 패키지; 이렇게 선언할 수 있다.
- 예를 들어 oop.pk1이라는 패키지와 oop.pk2라는 패키지가 있다고 가정해 보겠다.
두 패키지에 모두 Car 클래스가 존재한다면? 그리고 이를 사용하려고 한다면?
Java는 패키지의 경로를 통해 이를 구분한다.
package oop.pk1;
public class Car {
public void horn() {
System.out.println("pk1 빵빵");
}
}
package oop.pk2;
public class Car {
public void horn() {
System.out.println("pk2 빵빵");
}
}
package oop.main;
public class Main {
public static void main(String[] args) {
oop.pk1.Car car = new oop.pk1.Car();
car.horn(); // pk1 빵빵
oop.pk2.Car car2 = new oop.pk2.Car();
car2.horn(); // pk2 빵빵
}
}
▶ import
import는 다른 패키지에 있는 클래스를 사용하기 위해 명시하는 키워드이다.
- 위에서 살펴본 oop.pk1이라는 패키지와 oop.pk2라는 패키지를 import로 명시해 보겠다.
- import oop.pk1.Car;, import oop.pk2.Car;
- 클래스 이름을 생략하고 *를 사용하여 import oop.pk1.*; 이렇게 표현하면 oop.pk1 패키지 아래에 있는 모든 클래스를 사용할 수 있다.
- 다만 서로 다른 패키지에 있는 같은 이름의 클래스를 동시에 사용하려면 해당 클래스에 패키지 명을 전부 명시해야 한다.
package oop.pk1;
public class Car {
public void horn() {
System.out.println("pk1 빵빵");
}
}
package oop.pk2;
public class Car {
public void horn() {
System.out.println("pk2 빵빵");
}
}
package oop.main;
import oop.pk1.Car;
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.horn(); // pk1 빵빵
oop.pk2.Car car2 = new oop.pk2.Car();
car2.horn(); // pk2 빵빵
}
}
'개발 > 부트캠프' 카테고리의 다른 글
본캠프 : 개인 과제(계산기 만들기) 트러블 슈팅 (0) | 2025.01.08 |
---|---|
본캠프 : Java 문법 종합반 3주차_2 (0) | 2025.01.07 |
(일요일)주말에도 쓰는 TIL : Java 문법 종합반 2주차 (0) | 2025.01.05 |
(토요일)주말에도 쓰는 TIL : JAVA 기초 유튜브 강의 수강_chap06_01-08(feat. 무료 강의 완강!) (0) | 2025.01.04 |
본캠프 : JAVA 기초 유튜브 강의 수강_chap05_01-05 (0) | 2025.01.03 |