🔑월요일이 좋아질 수는 없는 걸까
💡 최상위 클래스 Object
📃 자바의 모든 클래스는 기본적으로 Object 클래스를 상속받습니다. 만약 어떤 클래스가 명시적으로 다른 클래스를 상속받지 않는다면, 컴파일러가 자동으로 해당 클래스가 Object를 상속받도록 처리합니다
💻CODE
class A {
// A는 Object 클래스를 자동으로 상속받습니다.
}
class B extends A {
// B는 A를 상속받고, A를 통해 Object도 간접적으로 상속받습니다.
}
// 컴파일러에 의해 변환된 구조
class A extends Object {
// Object를 명시적으로 상속한 것과 동일
}
class B extends A {
// 그대로 유지
}
⬆️ 모든 클래스는 Object 클래스를 상속받아 이를 최상위 클래스로 간주합니다
💡 객체의 equals 비교와 toString 메서드
📃 기본적으로 Object 클래스의 equals 메서드는 두 객체의 참조값(주소)을 비교합니다. 즉, 객체의 값이 같더라도 참조값이 다르면 false를 반환합니다. 이를 원하는 값 비교로 변경하려면 equals 메서드를 @Override하여 직접 비교 로직을 정의해야 합니다.
또한, toString 메서드는 객체를 문자열로 표현할 때 호출되며, 기본적으로 클래스 이름과 해시코드를 반환합니다. 이를 우리가 원하는 형식으로 출력하려면 역시 @Override로 재정의해야 합니다
💻equals & toString
class Example {
private int id;
private String name;
public Example(int id, String name) {
this.id = id;
this.name = name;
}
// equals 메서드 재정의
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 같은 참조인지 확인
if (obj == null || getClass() != obj.getClass()) return false; // 클래스 비교
Example other = (Example) obj;
return id == other.id && name.equals(other.name);
}
// toString 메서드 재정의
@Override
public String toString() {
return "Example{id=" + id + ", name='" + name + "'}";
}
}
public class Main {
public static void main(String[] args) {
Example e1 = new Example(1, "Alice");
Example e2 = new Example(1, "Alice");
System.out.println(e1.equals(e2)); // true (값 비교)
System.out.println(e1.toString()); // Example{id=1, name='Alice'}
}
}
⬆️ 위 코드에서 equals와 toString 메서드는 Object 클래스에 기본적으로 정의되어 있습니다.
Example 클래스는 명시적으로 상속을 표현하지 않았지만, 자바의 모든 클래스는 Object 클래스를 상속받으므로 equals와 toString을 사용할 수 있습니다. 따라서 우리가 원하는 동작(값 비교나 커스텀 문자열 출력)을 구현하려면 @Override를 통해 명시적으로 재정의해야 합니다
💡 final 제어자
📃 final은 클래스, 메서드, 변수에 사용되는 제어자입니다. 이를 통해 수정 불가능한 요소를 정의할 수 있습니다. final로 선언된 클래스는 상속, 오버라이딩, 그리고 값 변경이 불가능합니다
💻CODE
// 1. final 클래스
final class FinalClass {
void display() {
System.out.println("This is a final class.");
}
}
// class SubClass extends FinalClass {} // 오류: FinalClass는 상속할 수 없습니다.
// 2. final 메서드
class Parent {
final void show() {
System.out.println("This is a final method.");
}
}
class Child extends Parent {
// void show() {} // 오류: final 메서드는 오버라이딩할 수 없습니다.
}
// 3. final 변수
class FinalVariableExample {
final int MAX_VALUE = 100; // 초기화 후 변경 불가
void display() {
// MAX_VALUE = 200; // 오류: final 변수는 값 변경 불가
System.out.println("MAX_VALUE: " + MAX_VALUE);
}
}
⬆️ final 제어자는 코드의 안전성을 높이고, 변경 불가능한 요소를 명확히 설정할 때 유용합니다
💡 추상 제어자 Abstract
📃 abstract라는 단어는 "추상적"이라는 의미를 가지고 있습니다. 마찬가지로, 자바에서 abstract 제어자는 구체적인 로직은 구현하지 않지만, 자식 클래스가 어떤 메서드를 구현해야 하는지 형식(format)을 제공하는 역할을 합니다. 이를 통해 자식 클래스가 반드시 특정 메서드를 구현하도록 강제할 수 있습니다
💻좋지 않은 사례
class Animal {
void cry(){
};
}
class Cat extends Animal {
void cry() {
System.out.println("야옹");
}
}
class Dog extends Animal {
void CRY() { // 좋지 않은 사례
System.out.println("멍멍");
}
}
public class MyTest {
public static void main(String[] args) {
Dog dog = new Dog();
dog.CRY();
}
}
⬆️ 위의 Dog 클래스는 부모 클래스와 동일한 이름의 cry 메서드를 구현하고 있습니다. 반면, Cat 클래스는 부모 클래스와 다른 이름으로 메서드를 작성했음에도 오류가 발생하지 않습니다
하지만 이렇게 메서드 이름이 통일되지 않으면, 다른 사람들과 협업하거나 유지보수를 할 때 혼란을 초래할 수 있으므로 권장되지 않는 방식입니다
💻abstract 사용 사례
abstract class Animal {
abstract void cry();
}
class Cat extends Animal {
void cry() {
System.out.println("야옹");
}
}
class Dog extends Animal {
void cry() {
System.out.println("멍멍");
}
}
public class MyTest {
public static void main(String[] args) {
Dog dog = new Dog();
dog.cry();
}
⬆️ 따라서 위 코드와 같이 abstract 제어자를 사용하면, 자식 클래스에서 동일한 메서드 이름을 사용하도록 강제할 수 있습니다
💡 인터페이스 Interface
📃 인터페이스는 객체지향 프로그래밍에서 중요한 역할을 합니다. 인터페이스에 정의된 필드는 기본적으로 public static final로 선언되며, 메서드는 기본적으로 public abstract로 정의됩니다
💻CODE
interface Animal {
void shout(); // 기본적으로 public abstract void shout();와 동일
}
class Dog implements Animal {
@Override
public void shout() {
System.out.println("멍멍");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.shout(); // 멍멍
}
}
⬆️ 위 코드에서 Animal 인터페이스는 shout 메서드를 정의하며, 이를 구현한 Dog 클래스는 shout 메서드를 실제로 구현합니다. 인터페이스는 메서드의 구체적인 구현을 강제하지만, 그 구현 방식은 각 클래스에서 다를 수 있습니다