[Java] 오버라이딩, this/super, Object Class, 캡슐화
자바 복습 및 정리 4P
오버라이딩
객체 지향 프로그래밍에서 하위 클래스(sub class)가 상위 클래스(super class)의 메서드를 동일한 이름과 파라미터로 재정의(다시 구현)하는 것을 말한다.
이를 통해 하위 클래스는 상위 클래스의 메서드를 자신에 맞게 변경하거나 확장할 수 있다.
오버라이딩을 사용하면 상속 관계에서 메서드의 동작을 재정의할 수 있어 다형성을 구현하는데에 중요한 역할을 한다.
규칙
-
오버라이딩되는 메서드는 상위 클래스에서 선언되있어야 한다.
-
메서드 이름, 파라미터 타입, 파라미터 개수는 모두 동일해야 한다.
-
접근 제어자는 상위 클래스 메서드와 같거나 더 넓은 범위로 변경할 수 있다.
-
예외 던지기(throws) 부분도 상위 클래스와 같거나 더 구체적인 예외를 던질 수 있다.
-
오버라이딩된 메서드는 상위 클래스의 메서드 시그니처와 동일한 시그니처를 가져야 한다.
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
// 상위 클래스의 makeSound() 메서드를 오버라이딩
@Override
void makeSound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
// 상위 클래스의 makeSound() 메서드를 오버라이딩
@Override
void makeSound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.makeSound(); // 출력: Dog barks
animal2.makeSound(); // 출력: Cat meows
}
}
this / super
this
현재 객체를 가리키는 키워드이다
주로 같은 클래스 내에서 인스턴스 변수와 메서드를 참조할 때 사용된다.
this는 같은 클래스 내에서 사용되므로 클래스 내에서 변수 이름이 메서드 파라미터와 같을 때 이들을 구분하는 데 유용하다.
class Person {
private String name;
Person(String name) {
this.name = name; // 인스턴스 변수 name에 파라미터 name의 값을 대입
}
void printName() {
System.out.println(this.name); // 현재 객체의 name 출력
}
}
super
상위 클래스를 가리키는 키워드이다.
하위 클래스에서 상위 클래스의 멤버에 접근할 때 사용된다.
super는 상속 관계에서 주로 사용되며, 상위 클래스의 생성자를 호출할 때도 사용할 수 있다.
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
super.makeSound(); // 상위 클래스의 makeSound() 메서드 호출
System.out.println("Dog barks");
}
}
Object Class
자바의 클래스 상속계층도에서 최상위에 위치한 상위클래스이다.
자바의 모든 클래스는 Object 클래스로부터 확장된다는 명제는 항상 참이다.
따라서 모든 객체는 Object 클래스의 멤버와 메서드를 사용할 수 있다.
class ParentEx { // 컴파일러가 "extends Object" 자동 추가
}
class ChildEx extends ParentEx {
}
Object 클래스는 자바의 기본 클래스 계층 구조에서 다음과 같은 몇 가지 중요한 메서드를 제공한다.
toString() / 반환 타입 : String
객체의 문자열 표현을 반환하는 메서드.
기본적으로는 클래스 이름과 객체의 참조 주소를 반환하지만,
클래스마다 이 메서드를 오버라이딩하여 원하는 형식의 문자열을 반환하도록 할 수 있다.
equals (Object obj) / 반환 타입 : boolean
객체의 내용이 같은지 비교하는 메서드.
기본적으로 이 메서드는 객체의 참조 주소가 같은지를 비교하지만,
클래스마다 이 메서드를 오버라이딩하여 내용 비교를 지원하도록 할 수 있다.
hashCode() / 반환 타입 : int
객체의 해시 코드를 반환하는 메서드로,해시 기반의 컬렉션에서 사용된다.
wait() / 반환 타입 : void
스레드가 객체의 락을 해제하고 대기 상태로 들어간다.
이 때 다른 스레드가 해당 객체의 락을 얻고 작업을 수행할 수 있다.
wait() 메서드는 예외처리가 필요하며, 객체의 락을 반드시 확보한 뒤에 호출되어야 한다.
notify() / 반환 타입 : void
대기 중인 스레드 중 하나를 깨워서 실행 가능한 상태로 만든다.
어떤 스레드가 깨어날지는 보장되지 않는다.
notify() 역시 객체의 락을 확보한 뒤에 호출되어야 하며,
해당 객체의 락을 가진 스레드만이 notify() 를 호출할 수 있다.
getClass() / 반환 타입 : java.lang.Class
객체의 클래스를 나타내는 Class 객체를 반환한다.
finalize() / 반환 타입 : void
객체가 가비지 컬렉션되기 전에 호출되는 메서드이다.
이를 오버라이딩하여 객체가 소멸되기 전에 수행해야 할 작업을 정의할 수 있다.
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// equals 메서드 오버라이딩
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Student student = (Student) obj;
return age == student.age && name.equals(student.name);
}
// hashCode 메서드 오버라이딩
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// toString 메서드 오버라이딩
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student("Alice", 20);
Student student2 = new Student("Alice", 20);
// equals 메서드 사용
System.out.println("student1 equals student2: " + student1.equals(student2));
// hashCode 메서드 사용
System.out.println("HashCode of student1: " + student1.hashCode());
System.out.println("HashCode of student2: " + student2.hashCode());
// toString 메서드 사용
System.out.println(student1);
System.out.println(student2);
}
}
캡슐화
객체 지향 프로그래밍(OOP) 개념 중 하나로,
데이터와 해당 데이터를 조작하는 메서드를 하나의 클래스 안에 묶는 것을 의미한다.
이를 통해 데이터에 직접적으로 접근하는 것을 제한하고,
데이터의 무결성을 보호하며 코드의 유지 보수성을 높일 수 있다.
데이터를 외부로부터 보호하는 목적을 가지고 있다.
보통 멤버 변수를 private으로 선언하여 외부에서 직접 접근하지 못하도록 하고,
메서드를 public 또는 protected로 선언하여 해당 데이터에 접근하고 조작할 수 있는 인터페이스를 제공한다.
public class Person {
private String name;
private int age;
public void setName(String newName) {
if(newName != null && !newName.isEmpty()) {
name = newName;
}
}
public void setAge(int newAge) {
if(newAge >= 0) {
age = newAge;
}
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
위 예제에서 name과 age 멤버 변수는 private으로 선언되어 클래스 외부에서 직접 접근할 수 없다.
대신 setName, setAge, getName, getAge 메서드를 통해 데이터에 접근하고 변경할 수 있다.
접근 제어자
클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여하는 키워드를 의미함.
접근제어자
public, private, default, protected
기타 제어자
static, final, abstract, native, transient, synchronized 등
public
접근 제한 없음
private
동일 클래스에서만 접근 가능
default
동일 패키지 내에서만 접근 가능
protected
동일 패키지 + 다른 패키지의 하위 클래스에서 접근 가능
getter, setter
캡슐화와 관련된 개념으로, 객체 내의 private 멤버 변수에 접근하고 값을 설정하기 위해 사용되는 메서드이다.
getter는 멤버 변수의 값을 반환하고, setter는 멤버 변수의 값을 설정한다.
public class Person {
private String name;
public String getName() { // getName() <- getter
return name;
}
public void setName(String newName) { //setName() <- setter
name = newName;
}
}
댓글남기기