안녕하세요! 지난 글까지 JAVA의 전반적인 내용에 대해 다뤄보았습니다. C언어와 파이썬(python)과 겹치는 내용들이 많았는데요.
이번에는 JAVA 프로그래밍의 핵심인 클래스와 상속에 대해 정리해보았습니다. JAVA에서 가장 기본이자 중요한 내용이기 때문에 꼭 알아두셨으면 좋겠습니다. 혹시 이해가 안가시는 부분이 있다면 댓글 달아주시면 감사하겠습니다.
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() 메소드를 재정의하였다.
- 이를 통해 각 클래스의 특성에 맞는 동작을 구현할 수 있다.
- 이처럼 추상 클래스를 사용하면 공통적인 기능을 정의하고, 자식 클래스에서 구체적인 구현을 강제할 수 있어 코드의 일관성과 재사용성을 높일 수 있다.
혹시 이해가 어려우시다면, 이전 글을 먼저 참고해보시면 좋을 것 같습니다.
JAVA 변수, 연산자, 데이터 입출력
안녕하세요! 문과 출신 방구석 데이터 전문가와 함께하는 JAVA 기초 글에 오신걸 환영합니다. 지난 글까지는 파이썬과 C언어를 다뤄보았는데 JAVA는 C언어와 비슷한 부분이 많아서 C언어를 잘 아시
actshiny.com
JAVA 조건문, 반복문, 참조 타입(배열) 정리
안녕하세요! 지난 글에서 JAVA의 기본적인 내용에 대해 알아보았습니다. 이번에는 조건문, 반복문, 참조타입, 배열에 대해서 정리해보았습니다. C언어를 공부하신 분들은 JAVA와 크게 다르지 않다
actshiny.com
반응형
'IT & AI > AI 지식' 카테고리의 다른 글
JAVA 기본 API, 스레드, 컬렉션 프레임워크 (0) | 2024.11.22 |
---|---|
JAVA 인터페이스, 중첩 클래스, 예외 처리 방법 (0) | 2024.11.22 |
JAVA 조건문, 반복문, 참조 타입(배열) 정리 (2) | 2024.11.21 |
JAVA 변수, 연산자, 데이터 입출력 (1) | 2024.11.20 |
C 언어 메모리 동적 할당, 구조체, 파일 입출력, 전처리 (1) | 2024.11.20 |