1. 클래스
객체 지향 프로그래밍
- 객체 지향 프로그래밍(OOP)은 객체 간의 상호작용을 통해 프로그램을 설계하는 방식이다.
- 객체는 현실 세계의 사물이나 개념을 모델링한 것으로, 속성(필드)과 행동(메소드)을 가진다.
- 객체는 서로 메시지를 주고받으며 상호작용할 수 있다.
- 객체는 '연관 관계', '상속 관계', '의존 관계' 등 다양한 관계를 통해 상호작용한다.
- 이러한 관계는 프로그램의 구조를 이해하고 설계하는 데 중요한 요소이다.
- 클래스는 객체를 생성하기 위한 청사진이다.
- 클래스는 객체의 속성과 행동을 정의하며, 이러한 클래스를 기반으로 여러 개의 객체를 생성할 수 있다.
- 객체는 클래스의 인스턴스라고 부른다.
- 클래스를 선언하려면 class 키워드를 사용하고 클래스 이름을 지정한다.
- 클래스 이름은 관례적으로 대문자로 시작한다.
public class Car {
String color;
int speed;
}
- 객체를 생성하려면 new 키워드를 사용한다.
- 생성된 객체는 클래스의 인스턴스이며, 클래스 변수를 통해 객체의 속성에 접근할 수 있다.
Car myCar = new Car();
myCar.color = "Red";
myCar.speed = 100;
- 클래스는 필드, 메소드, 생성자 등의 구성 요소를 가진다.
- 필드는 객체의 상태를 나타내고, 메소드는 객체의 행동을 정의하며, 생성자는 객체를 초기화하는 역할을 한다.
필드
- 필드는 클래스의 속성을 저장하는 변수이다.
- 필드는 클래스 내에서 선언되며, 객체가 생성될 때마다 별도로 할당된다.
public class Car {
String color;
int speed;
}
- 필드는 객체를 통해 접근할 수 있다.
- 필드에 값을 할당하거나 읽어올 수 있다.
Car myCar = new Car();
myCar.color = "Blue";
System.out.println("색상: " + myCar.color);
생성자
- 생성자는 객체를 생성할 때 호출되는 메소드로, 주로 필드를 초기화하는 데 사용된다.
- 기본 생성자는 명시적으로 정의하지 않으면 컴파일러가 자동으로 추가해준다.
public class Car {
Car() {
System.out.println("자동차가 생성되었습니다.");
}
}
- 생성자는 클래스 이름과 동일해야 하며, 반환 타입이 없다.
public Car(String color, int speed) {
this.color = color;
this.speed = speed;
}
- 생성자를 통해 필드를 초기화할 수 있다.
- 이를 통해 객체가 생성될 때 초기 상태를 설정할 수 있다.
Car myCar = new Car("Green", 150);
- 하나의 클래스에 여러 개의 생성자를 정의하는 것을 생성자 오버로딩이라고 한다.
- 각 생성자는 서로 다른 매개변수를 가진다.
public Car() {
this("Black", 0);
}
public Car(String color, int speed) {
this.color = color;
this.speed = speed;
}
- this()는 동일 클래스 내에서 다른 생성자를 호출할 때 사용한다.
- 생성자 내에서 코드를 중복하지 않도록 도와준다.
메소드
- 메소드는 객체의 행동을 정의하는 멤버이다.
- 메소드는 반환 타입, 이름, 매개변수 목록, 그리고 메소드 본체로 구성된다.
public void drive() {
System.out.println("자동차가 주행 중입니다.");
}
- return문은 메소드의 실행을 종료하고 결과값을 반환하는 역할을 한다.
public int getSpeed() {
return speed;
}
- 메소드는 객체를 통해 호출할 수 있다.
- 호출된 메소드는 객체의 행동을 실행한다.
myCar.drive();
- 메소드 오버로딩은 동일한 이름의 메소드를 여러 개 정의하는 것을 의미하며, 각 메소드는 서로 다른 매개변수를 가진다.
public void drive(int distance) {
System.out.println(distance + "km를 주행합니다.");
}
인스턴스 멤버와 정적 멤버
- 인스턴스 멤버는 객체가 생성될 때마다 별도로 존재하는 멤버이다.
- this 키워드는 현재 객체의 인스턴스를 참조할 때 사용된다.
public void setColor(String color) {
this.color = color;
}
- 정적 멤버는 클래스에 소속된 멤버로, 객체를 생성하지 않고도 사용할 수 있다.
- static 키워드를 사용하여 정의한다.
public static int wheelCount = 4;
- 싱글톤 패턴은 클래스의 인스턴스를 하나만 생성하도록 보장하는 디자인 패턴이다.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
- final 키워드를 사용하여 필드를 상수로 정의할 수 있다.
- 이는 한 번만 값을 할당할 수 있음을 의미한다.
public static final double PI = 3.14159;
패키지와 접근 제한자
- 패키지는 관련 있는 클래스들을 논리적으로 묶어주는 역할을 한다.
- package 키워드를 사용하여 패키지를 선언한다.
package com.example;
- 접근 제한자는 클래스, 필드, 메소드의 접근 범위를 제어하는 키워드이다.
- public, protected, default, private가 있다.
- 클래스에는 public과 default 접근 제한자를 사용할 수 있다.
- public 클래스는 모든 곳에서 접근 가능하지만, default 클래스는 같은 패키지 내에서만 접근 가능하다.
- 생성자에 접근 제한자를 사용하여 객체 생성 범위를 제한할 수 있다.
- 필드와 메소드에도 접근 제한자를 사용할 수 있다.
- private 필드는 클래스 외부에서 접근할 수 없으며, 이를 위해 Getter와 Setter 메소드를 제공한다.
public class Car {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
- Getter와 Setter 메소드는 private 필드에 대한 접근을 제공하기 위해 사용된다.
- 이를 통해 외부에서는 필드를 안전하게 접근하고 수정할 수 있다.
2. 상속
상속
- 상속은 객체 지향 프로그래밍(OOP)의 중요한 특징 중 하나로, 기존 클래스를 확장하여 새로운 클래스를 만드는 방식이다.
- 이를 통해 코드의 재사용성을 높이고, 유지보수를 쉽게 할 수 있다.
- 자바에서는 extends 키워드를 사용하여 상속을 구현한다.
public class Car {
String color;
int speed;
}
public class SportsCar extends Car {
boolean turbo;
}
- 위 예제에서 SportsCar 클래스는 Car 클래스를 상속받아 turbo라는 새로운 필드를 추가하였다.
- 상속을 통해 Car 클래스의 속성과 기능을 재사용할 수 있다.
- 자식 클래스는 객체 생성 시 부모 클래스의 생성자를 호출해야 한다.
- 이를 통해 부모 클래스의 필드를 초기화할 수 있다.
- 자바에서는 super() 키워드를 사용하여 부모 생성자를 호출한다.
public class Car {
String color;
int speed;
public Car(String color, int speed) {
this.color = color;
this.speed = speed;
}
}
public class SportsCar extends Car {
boolean turbo;
public SportsCar(String color, int speed, boolean turbo) {
super(color, speed); // 부모 클래스 생성자 호출
this.turbo = turbo;
}
}
- super()는 반드시 자식 클래스 생성자의 첫 번째 줄에서 호출되어야 한다.
- 이를 통해 부모 클래스의 초기화 작업이 먼저 완료된 후 자식 클래스의 초기화가 이루어진다.
- 메소드 재정의(오버라이딩)는 부모 클래스의 메소드를 자식 클래스에서 재정의하여 사용하는 것이다.
- 자바에서는 @Override 애노테이션을 사용하여 메소드가 재정의된 것임을 명시할 수 있다.
- 오버라이딩된 메소드는 부모 클래스의 메소드를 대신하여 자식 클래스의 로직으로 실행된다.
public class Car {
public void drive() {
System.out.println("자동차가 주행 중입니다.");
}
}
public class SportsCar extends Car {
@Override
public void drive() {
System.out.println("스포츠카가 고속으로 주행 중입니다.");
}
}
- 위 코드에서 SportsCar 클래스는 Car 클래스의 drive() 메소드를 재정의하여 고유의 동작을 정의하였다.
- final 키워드를 사용하여 클래스나 메소드를 선언할 수 있다.
- final 클래스로 선언된 클래스는 상속될 수 없으며, final 메소드로 선언된 메소드는 자식 클래스에서 재정의할 수 없다.
public final class Utility {
// 이 클래스는 상속될 수 없음
}
public class Car {
public final void stop() {
System.out.println("자동차가 멈췄습니다.");
}
}
- 이렇게 final 키워드를 사용하면 상속을 통해 변경되어서는 안 되는 기능을 보호할 수 있다.
- protected 접근 제한자는 같은 패키지 내에서, 또는 다른 패키지에 있는 상속 관계에 있는 클래스에서 접근할 수 있도록 한다.
- protected는 상속받은 자식 클래스에서 부모 클래스의 멤버에 접근하는 데 유용하다.
public class Car {
protected int speed;
protected void accelerate() {
speed += 10;
}
}
public class SportsCar extends Car {
public void boost() {
accelerate(); // 부모 클래스의 protected 메소드 호출 가능
speed += 20; // 부모 클래스의 protected 필드 접근 가능
}
}
- 위 예제에서 SportsCar 클래스는 Car 클래스의 protected 필드와 메소드에 접근할 수 있다.
타입 변환과 다형성
- 자동 타입 변환(업캐스팅)은 자식 클래스의 객체가 부모 클래스 타입으로 자동 변환되는 것을 의미한다.
- 자바에서는 부모 타입 변수로 자식 객체를 참조할 수 있으며, 이를 통해 다형성을 구현할 수 있다.
Car myCar = new SportsCar(); // SportsCar 객체가 Car 타입으로 자동 변환
- 자동 타입 변환을 통해 부모 클래스 타입으로 자식 클래스의 객체를 참조할 수 있지만, 이 경우 부모 클래스에 정의된 멤버만 접근할 수 있다.
- 부모 타입의 필드는 다양한 자식 타입의 객체를 참조할 수 있다.
- 이를 통해 여러 객체를 같은 타입으로 처리할 수 있어 코드의 유연성과 재사용성을 높인다.
Car car1 = new SportsCar();
Car car2 = new Truck();
- 이와 같이 하나의 부모 타입 필드를 통해 다양한 자식 객체를 관리할 수 있다.
- 메소드의 매개변수를 부모 클래스 타입으로 선언하면 자식 클래스의 객체를 매개변수로 전달할 수 있다.
- 이를 통해 코드의 일반화가 가능해진다.
public void operateCar(Car car) {
car.drive();
}
operateCar(new SportsCar()); // SportsCar 객체 전달
operateCar(new Truck()); // Truck 객체 전달
- operateCar() 메소드는 Car 타입을 매개변수로 받기 때문에, 다양한 자식 객체를 전달받아 처리할 수 있다.
- 강제 타입 변환(다운캐스팅)은 부모 타입으로 변환된 객체를 다시 자식 타입으로 변환하는 것을 의미한다.
- 이때 instanceof 연산자를 사용하여 변환이 가능한지 확인한 후 안전하게 캐스팅해야 한다.
Car myCar = new SportsCar();
if (myCar instanceof SportsCar) {
SportsCar mySportsCar = (SportsCar) myCar;
mySportsCar.turbo = true;
}
- instanceof 연산자를 사용하여 객체가 특정 타입인지 확인한 후 캐스팅을 수행하면 런타임 오류를 방지할 수 있다.
- 객체가 특정 클래스의 인스턴스인지 확인하려면 instanceof 연산자를 사용한다.
- 이를 통해 타입 변환이 가능한지 확인할 수 있다.
if (myCar instanceof SportsCar) {
System.out.println("myCar는 SportsCar 타입입니다.");
}
추상 클래스
- 추상 클래스는 공통적인 특성을 가진 객체들의 부모 역할을 하며, 직접 객체를 생성할 수 없다.
- 주로 다른 클래스들이 상속받아 사용할 수 있도록 공통의 필드와 메소드를 정의한다.
- 추상 클래스는 abstract 키워드를 사용하여 선언한다.
- 추상 클래스는 일반 필드와 메소드를 가질 수 있으며, 하나 이상의 추상 메소드를 포함할 수 있다.
public abstract class Animal {
String name;
abstract void sound(); // 추상 메소드
}
- Animal 클래스는 추상 클래스이므로 직접 객체를 생성할 수 없으며, 이를 상속받은 클래스에서 추상 메소드를 구현해야 한다.
- 추상 메소드는 선언만 하고 구현하지 않으며, 자식 클래스에서 반드시 재정의해야 한다.
- 이를 통해 자식 클래스마다 서로 다른 동작을 구현할 수 있다.
public class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍");
}
}
public class Cat extends Animal {
@Override
void sound() {
System.out.println("야옹");
}
}
- 위 코드에서 Dog와 Cat 클래스는 Animal 클래스를 상속받아 각각 sound() 메소드를 재정의하였다.
- 이를 통해 각 클래스의 특성에 맞는 동작을 구현할 수 있다.
- 이처럼 추상 클래스를 사용하면 공통적인 기능을 정의하고, 자식 클래스에서 구체적인 구현을 강제할 수 있어 코드의 일관성과 재사용성을 높일 수 있다.
'IT & AI > AI 지식' 카테고리의 다른 글
JAVA 조건문, 반복문, 참조 타입(배열) 정리 (2) | 2024.11.21 |
---|---|
JAVA 초보자를 위한 변수, 연산자, 데이터 입출력 이해 (1) | 2024.11.20 |
C 언어 메모리 동적 할당, 구조체, 파일 입출력 (1) | 2024.11.20 |
C 언어 변수, 다차원 배열, 응용 포인터 정리 (1) | 2024.11.19 |
C 언어 배열, 포인터, 문자열 활용 심화 (0) | 2024.11.19 |