1. 객체 지향 프로그래밍이란?
- 위키백과에서의 정의
- 객체 지향 프로그래밍(OOP)은 일종의 프로그래밍 패러다임으로 객체(objects)의 개념에 기반하고 있다. 객체는 데이터를 포함하며 필드의 형태를 띠고 있으며, 이를 속성(attributes 혹은 properties)이라 부르기도 한다. 객체는 코드를 포함하며 이는 프로시저(procedures) 형식으로 되어 있다. 종종 메서드(methods)라고 부른다.
- 객체는 데이터와 코드를 포함한다. (not 코드와 데이터)
- 어떤 데이터? 객체의 데이터는 변수이다.
- 객체가 담을 수 있는 변수 종류? 객체의 데이터는 실제로 상수, 변수, 객체 (e.g. 표준 API, 사용자가 정의한 클래스들) 를 포함한다.
- 객체의 코드는 객체의 데이터를 사용한다. (= 기본적으로 함수와 같지만 한 가지 차이점)
- 좋은 객체는 적절한 데이터를 포함하며 그 데이터를 활용하는 코드를 포함한다.
2. 객체는 클래스다
- 자바 언어는 객체를 만들 때 class 키워드를 사용한다.
- 객체는 일종의 개념임. class는 자바 언어에서 객체를 구현하는 방법
- class는 계층 혹은 신분의 개념을 담고 있다. 간단하게 말해서 클래스는 객체의 추상형(abstraction)을 의미
- 클래스의 데이터는 멤버 변수(member variable)라고 부른다.
- 클래스의 코드는 메서드(method=사용법)라고 부른다.
- 객체는 method를 호출하여 사용한다.
- 항상 자바 파일명과 public class명이 동일해야 하는 이유
- 그래야 JVM에서 진입점이 되기 때문이다.
- 만약 그렇게 하지 않을 경우 JAVA소스 파일을 컴파일 한 후 자바 인터프리터가 해석해야하는 클래스와 진입점 포함하는 클래스를 쉽게 식별할수가 없다
- public이 아닌 class는 파일명과 관련없이 class명 선언이 가능하다
용어 정리
public class ClassName{ // class
// field
int fieldName;
// constructor
ClassName(){ ... }
ClassName(자료형 변수명){ ... } //overloading
// method
void methodName() { ... }
void methodName(자료형 변수명) { ... } // overloading
}
- class: 객체를 만들어내기 위한 설계도 혹은 틀, 객체의 속성(state)과 행동(behavior) 포함
- 클래스 선언: public class 클래스이름 {}
- object(객체) : 클래스의 모양 그대로 찍어낸 실체
- 프로그램 실행 중에 생성되는 실체 = 메모리 공간을 갖는 구체적인 실체 = instance라고도 부른다.
- 객체 생성: new 객체
- field: 객체의 고유 데이터가 저장되는 곳, 클래스 멤버 변수라고도 한다.
- in class {} block
- sth in method {} block is local variable
- constructor(생성자): 객체 생성 시 초기화를 위해 실행됨
- method: 객체의 동작에 해당하는 실행 블록
3. 생성자와 접근 제어자
생성자
public class PersonExampleV2 {
// 멤버 변수
String firstName = "자바";
String lastName = "김";
int age = 25;
int height = 182;
// 생성자
PersonExampleV2(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
PersonExampleV2(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
PersonExampleV2(String firstName, String lastName, int age, int height) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.height = height;
}
// method
void goToOffice(String destination) {
System.out.println(getKoreanName() + "님이 " + destination + "으로 출근합니다.");
}
String getKoreanName() {
return lastName + " " + firstName;
}
public static void main(String[] args) {
// PersonExampleV2 p = new PersonExampleV2(); // error
PersonExampleV2 p = new PersonExampleV2("Java", "Kim");
p.goToOffice("서울역");
}
}
- 객체를 생성할 때는 new 키워드를 사용한다.
- 생성자는 말 그대로 객체를 생성하는 방법(method)을 제공한다. 생성자의 목적은 객체 초기화
- 생성자는 객체가 생성될 때 반드시 호출된다 (하나 이상 선언되어야 한다)
- 생성자와 메서드 차이
- 생성자: 리턴 타입을 지정할 수 없고 클래스 이름과 반드시 동일하다. e.g.
Person(String a, String b) {객체 초기화 코드}
- 메서드: 리턴 타입을 명시 e.g.
void Person(String a, String b) {}
- 생성자: 리턴 타입을 지정할 수 없고 클래스 이름과 반드시 동일하다. e.g.
- this 키워드는 객체 자신을 의미한다. this는 생성자에서도 쓰이고 일반 메서드에서도 활용한다.
- 클래스에서 생성자를 선언하지 않으면 자바 컴파일러가 기본 생성자를 자동으로 생성한다. 위 예제는 명시적으로 생성자를 3개 추가하였기 때문에 기본 생성자가 추가되지 않음에 따라 컴파일 오류가 발생한다.
- 클래스에 생성자가 명시적으로 선언되어 있을 경우에는 반드시 선언된 생성자를 호출해서 객체를 생성해야 한다.
- 모든 클래스는 생성자가 반드시 존재하며, 하나 이상을 가질 수 있다. (=생성자 여러 개 작성 가능(overloading))
- public class 클래스명: 소스파일명.java에서 소스파일명과 같은 클래스
- class 클래스명: 소스파일명.java에서 소스파일명과 다른 클래스
다른 생성자 호출
- 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다. 필드 초기화 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있다.
this()
: 자신의 다른 생성자를 호출하는 코드로 반드시 생성자의 첫 줄에서만 허용된다. 호출되는 생성자의 실행이 끝나면 원래 생성자로 돌아와서 다음 실행문을 진행한다.- 변경 전
public class Car {
String company;
String model;
String color;
int maxspeed;
int speed;
Car(){
this.company="현대자동차";
this.model = "그랜저";
this.color = "검정색";
}
Car(String color){
this.company="현대자동차";
this.model = "그랜저";
this.color = color;
}
Car(String color,int maxspeed){
this.company="현대자동차";
this.model = "그랜저";
this.color = color;
this.maxspeed = maxspeed;
}
}
- 변경 후
public class Car {
String company;
String model;
String color;
int maxspeed;
int speed;
Car(){
this("현대자동차","검정색",250);
}
Car(String color){
this("현대자동차",color,250);
}
Car(String color,int maxspeed){
this("현대자동차",color,maxspeed);
}
Car(String model,String color,int maxspeed){
this.company="현대자동차";
this.model = model;
this.color = color;
this.maxspeed = maxspeed;
}
}
접근 제어자
- [ 접근 제어자 ]: 앞서 만든 멤버 변수, 생성자, 메서드에 모두 적용할 수 있다. public, private, protected, (default)
- public: 공개함. 어디에서나 접근 가능
- private: 비공개함. 같은 클래스에서만 접근할 수 있음
- protected: 비공개함. 기본적으로 private과 동일하지만 상속한 하위 클래스에서는 공개함
- (default): 같은 패키지에만 공개함. 생략 시 기본 적용
- [ 멤버 변수의 접근 제어자 ]는 private을 원칙으로 한다. (=encapsulation; 캡슐화)
- 멤버 변수를 왜 감출까? 가장 큰 이유는 객체를 사용하는 방법의 통일. 객체를 사용할 때는 생성자와 메서드만 사용하는 것을 원칙으로 하니까.
- [ 생성자의 접근 제어자 ]는 기본적으로 public을 사용하며 그 외는 특별한 이유가 있을 때 사용한다.
- public: 프로그램 어디서나 그 객체를 생성할 수 있음
- private: 해당 클래스 내에서만 그 객체를 생성할 수 있음. singleton pattern과 같이 특수한 경우에 사용함
- protected: 부모 클래스의 생성자를 자식 클래스에서 그대로 사용해야 할 때
- (default): 같은 패키지에서만 해당 클래스의 객체를 생성할 때
- [ 메서드의 접근 제어자 ] - 필요한 메서드만 클래스의 외부로 공개해야 한다. 공개 메서드를 최소로 유지할수록 그 코드의 유지보수성(maintainability)이 좋아진다.
- public: 객체를 생성하면 어디에서나 호출할 수 있다
- private: 해당 클래스 내에서만 호출 가능함. 내부 메서드가 됨
- protected: 부모 클래스의 메서드를 자식 클래스에서 호출해야 할 때
- (default): 같은 패키지에서만 메서드를 호출할 수 있다
4. static, final 키워드
public class PersonExampleV4 {
// V4. 좀 더 객체지향적으로 바꿈
// 상수
private static final String DEFAULT_DESTINATION = "집";
private static final int DEFAULT_AGE = 0;
private static final int DEFAULT_HEIGHT = 0;
// 멤버 변수
private String firstName ;
private String lastName ;
private int age = DEFAULT_AGE;
private int height = DEFAULT_HEIGHT;
private String destination = DEFAULT_DESTINATION;
// 생성자
public PersonExampleV4(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public PersonExampleV4(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public PersonExampleV4(String firstName, String lastName, int age, int height) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.height = height;
}
// method
public void goToOffice(String destination) {
this.destination = destination;
System.out.println(getKoreanName(firstName, lastName) + "님이 " + destination + "으로 출근합니다.");
}
public String getPlace() {
return destination;
}
private static String getKoreanName(String firstName, String lastName) {
return lastName + " " + firstName;
}
public static void main(String[] args) {
PersonExampleV4 p = new PersonExampleV4("Java", "Kim");
String now = p.getPlace();
System.out.println("현재 " + now + "에 있습니다.");
if ("집".equals(now)) {
p.goToOffice("회사");
}
}
}
- 멤버 변수나 메서드르 static(=정적)으로 선언하게 된다면 그 의미는 객체를 생성하지 않고 사용할 수 있다는 것이다.
- static의 용도 중 하나: 상수(constants) 선언 (final 키워드와 함께 씀) e.g.
private static final double PIE = 3.14;
- 전체 멤버 변수가 5개인데 왜 3개만 초기값을 갖을까? firstName과 lastName은 모든 생성자들에서 정의하고 있기 때문에 초기화를 할 필요가 없다.
- static은 일반적으로 생성자에 사용하지 않는다.
getKoreanName()
method는 firstName과 lastName 멤버 변수에 굳이 의존할 필요가 없다 -> [ utility method ]라고 부르며 별도의 클래스에 모아두기도 한다. 일반적으로 utility class는 static methods만 포함되어 있다.- 반면 getPlace() method는 새로 추가된 destination 멤버 변수를 반환하므로 static method가 되어서는 안 된다.
// 상수
private static final String DEFAULT_DESTINATION = "집";
// method
public void goToOffice(final String destination) {
}
// 지역 변수
public static void main(String[] args) {
final String firstName = "Java";
final String lastName = "Kim";
}
- final 키워드: 한번 값을 정의하면 더 이상 다른 값을 넣을 수 없다. 자바에서는 가능한 final을 붙여주는 것이 좋다.
- 상수에 사용
- 메서드의 인자와 지역 변수에 충분히 붙인다. 변경되지 않음을 보장하기 떄문에 코드 로직 파악이 용이하다.
변수 종류 정리
- 변수의 종류를 경정짓는 중요한 요소는 변수가 선언된 위치이다.
- 클래스 영역에서 선언한 것이 멤버변수(클래스변수, 인스턴스변수)
[접근지정자] [기타제어자] 자료형 필드명;
- 멤버변수 = 필드
- 멤버변수 중 static 기타제어자가 붙은 것은 cv, 붙지 않은 것은 iv
- 클래스 영역 이외의 영역(메서드, 생성자, 초기화 블록 내부)에서 선언한 것이 지역변수
- 클래스 영역에서 선언한 것이 멤버변수(클래스변수, 인스턴스변수)
- 비교
- class variable(cv; 클래스 변수) : 클래스가 메모리에 올라갈 때 생성됨. 선언@클래스 영역. 인스턴스의 공통 속성일 시 사용.
- 클래스는 인스턴스를 생성하지 않고도 바로 사용할 수 있다. (클래스이름.클래스변수)
- 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때까지 유지된다.
- public을 붙이면 같은 프로그램 내에서 어디서나 접근 가능한 전역변수의 성격을 갖는다.
- instance variable(iv; 인스턴스 변수) : 인스턴스가 생성되었을 때 생성됨. 선언@클래스영역. 인스턴스의 개별 속성일 시 사용.
- local variable(lv; 지역 변수) : 변수 선언문이 수행되었을 때 생성됨. 선언@그외.
- class variable(cv; 클래스 변수) : 클래스가 메모리에 올라갈 때 생성됨. 선언@클래스 영역. 인스턴스의 공통 속성일 시 사용.
5.method overloading
class Car {
private static final int DEFAULT_ACCEL = 10;
static final String SPEED_UNIT = "km/h";
static final String DISTANCE_UNIT = "km";
private String carNumber;
private int speed = 0;
private int distance = 0;
public Car(String carNumber) {
this.carNumber = carNumber;
}
public void start() {
System.out.println("");
accelerate();
}
public void accelerate() {
accelerate(DEFAULT_ACCEL);
}
public void accelerate(int km) {
speed += km;
distance += speed;
printDistance();
}
public void decelerateHalf() {
speed *= 0.5;
distance += speed;
printDistance();
}
public void decelerateAs(int km) {
speed -= km;
distance += speed;
printDistance();
}
public String getNumber() {
return carNumber;
}
public int getDistance() {
return distance;
}
public void printDistance() {
String msg = "차량[]" + carNumber + "] " +
"시속: " + speed + SPEED_UNIT +
" >> 이동거리: " + distance + DISTANCE_UNIT;
System.out.println(msg);
}
}
public class MethodOverloading {
public static void main(String[] args) {
Car car = new Car("GREEN");
car.start();
car.accelerate();
car.accelerate(70);
car.decelerateAs(60);
car.decelerateHalf();
}
}
- method signature: 메서드의 식별 단위로, 메서드 이름과 인자의 종류, 개수를 포함하여 각각을 구분한다.
- [ method overloading ]: 자바 클래스는 메서드 이름이 같지만 인자의 개수와 종류가 다른 다수의 메서드를 가질 수 있다.
- 장점: 코드의 표현력을 높인다.
- 본질적으로 같은 메서드를 묶어야 한다.
- 두 accelerate 메서드는 서로 다른 메서드이다. 하지만 본질적으로 같다(하나가 다른 하나를 내부적으로 호출함)
- decelerate 계열의 두 메서드는 speed 멤버 변수를 다루는 방식이 다르므로 서로 다른 메서드가 되는 것이 맞다.
6. 객체 배열
public class ObjectArray {
private static final int CAR_MAX = 3;
// 1. Declare object array
private Car[] cars;
public ObjectArray() {
registerCars();
}
private void registerCars() {
// 2. Initialize object array
cars = new Car[CAR_MAX];
for (int i=0; i<cars.length; ++i) {
String carName = "SKY-" + i;
cars[i] = new Car(carName);
}
}
public void start() {
for (Car car : cars) {
car.start();
}
}
public void race() {
cars[0].accelerate(70);
cars[1].accelerate(50);
cars[2].accelerate();
cars[0].decelerateAs(20);
cars[1].decelerateHalf();
}
public void showResult() {
for (Car car : cars) {
String msg = "차량[" + car.getNumber() + "] " +
" >> 이동거리: " + car.getDistance() + Car.DISTANCE_UNIT + "입니다.";
System.out.println(msg);
}
}
public static void main(String[] args) {
ObjectArray race = new ObjectArray();
race.start();
race.race();
race.showResult();
}
}
- 배열 각 인덱스(cars[i]에 새로운 Car 객체(같은 패키지 다른 파일에서 Car class 정의함)를 넣었다.
- 객체 배열을 생성하는 것은 객체를 다루기 위한 참조변수들을 만든 것일 뿐, 객체가 저장되어있지는 않다. 따라서 객체 배열 생성 후, 추가적으로 객체를 저장해야 한다.
- 배열은 객체를 담는 컨테이너 역할을 할 뿐, 배열에서 가져온 객체는 일반 객체와 동일하게 메서드를 호출할 수 있다. (car 객체의 start(), accelerate() 등의 methods를 호출함)
7. null의 개념
- null: 값이 없음, 혹은 값이 정해지지 않았다는 의미로 실제로는 모호한 상태
- 자바 앱에서 NullPointerException이 발생하면 즉시 앱이 중단된다
- 객체, 배열 등을 선언만 할 시 초기값이 null 상태이다. 명시적으로 null 선언도 가능하다. e.g.
Car car1 = null;
- null 상태를 최소화해야 한다. 객체 변수는 선언 후 최대한 빠르게 실제 값(객체)을 할당한다.
println()
에 변수를 넣는 경우 그대로 출력, 배열 변수를 넣는 경우 그 변수의toString()
method를 호출한 결과가 출력된다.
null의 대응
if(name == null)
...- Guarded clause(보호절):
return;
skip logic - 다른 값(e.g. 기본값)으로 대체
- Guarded clause(보호절):
'Java' 카테고리의 다른 글
4. 함수와 String class (0) | 2024.06.17 |
---|---|
3. 제어문 (0) | 2024.06.13 |
2. 자바 언어 기본 (0) | 2024.06.12 |
VSCode에서 Java 실행환경 세팅하기 (0) | 2024.06.11 |
[Java] Java 11 설치 (0) | 2024.03.20 |