이전 글과 이어집니다.
다음 예시를 보자.
class Employee {
private String name;
private int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public final String getName() {
return this.name;
} // subclass cannot override this method.
public int getSalary() {
return this.salary;
}
}
class Manager extends Employee {
private int bonus;
public Manager(String name, int salary) {
super(name, salary);
bonus = 10000;
}
public void setBonus(int bonus) {
this.bonus = bonus;
}
public String getName() {
return "Sir " + super.getName();
} // Error: overriding a final method
}
getName
메서드는 final 메서드이기 때문에 Manager 클래스에서 사용될 수 없다. 메서드뿐 아니라 final 클래스도 만들 수 있는데, 메서드와 비슷하게 어떤 subclass도 이 클래스를 상속할 수 없다.
클래스 정의에서, 구현 없이 메서드를 선언하는 게 가능하다. 이를 abstract 메서드라고 부르며, 메서드 헤더에 abstract
이라는 키워드를 붙여 주어야 한다.
abstract
키워드를 붙여 주어야 한다.다음 예시를 보자.
abstract class Person {
private String name;
public Person(String name) {
this.name = name;
}
public final String getName() {
return name;
}
public abstract int getId();
}
이 클래스는 abstract 클래스이다.
Person 클래스를 상속한 subclass는
getId()
메서드를 구현하거나한다.
abstract 클래스는 interface와 상당히 유사해 보인다. 하지만 그 둘은 다음과 같은 차이점이 있다.
abstract 클래스의 인스턴스를 생성할 수 없다.
Person p = new Person("Tom"); // Error: cannot create instances of an abstract class.
하지만 abstract 클래스를 상속하고 모든 abstract 메서드를 구현한 subclass의 인스턴스는 만들 수 있다.
class Student extends Person {
private int id;
public Student(String name, int id) {
super(name);
this.id = id;
}
public int getId() {
return id;
}
}
Student s = new Student("Tom", 0303);
System.out.println(s.getName() + " " + s.getId();
또, subclass의 객체를 abstract 클래스인 superclass에 할당할 수도 있다.
protected
접근 제어자를 사용해서 변수가 메서드를 선언하면, 그 변수나 메서드는 subclass에서 접근 가능하다.
class Employee {
private String name;
protected int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return this.name;
}
public int getSalary() {
return this.salary;
}
}
class Manager extends Employee {
private int bonus;
public Manager(String name, int salary) {
super(name, salary);
bonus = 10000;
}
public void setBonus(int bonus) {
this.bonus = bonus;
}
public int getSalary() {
return salary + bonus;
} // can access superclass protected variable
}
자바는 다양한 종류의 구현/상속이 가능하다.
class Student implements Named {
...
}
class Student implements Named, Registered {
...
}
class Student extends Person {
...
}
class Student extends Person, Animal { // Error: cannot extent multiple classes
}
interface Name {
default String getName() {
return "NoName";
}
}
abstract class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract int getId();
}
class Student extends Person implements Name {
private int id;
public Student(String name, int id) {
super(name);
this.id = id;
}
public int getId() {
return id;
}
}
만약 다음과 같은 클래스와 interface가 있다고 해 보자. 아래 코드를 실행하면 어떻게 될까?
Student s = new Student("Fred", 1729); System.out.println(s.getName()); // prints "Fred"
한 클래스가 superclass를 확장하고 interface를 구현했을 때, superclass와 interface에 같은 이름과 매개변수를 가진 메서드가 존재하면
getName()
메서드가 호출 되었을 때 superclass에 있는 메서드가 호출된다.이제, 조금 독특한 클래스에 대해 알아본다.
자바에서 모든 클래스는 암시적으로 Object 클래스를 상속한다.
class Student {
...
}
class Student extends Object {
...
}
Object 클래스에는 몇 가지의 메서드들이 정의되어 있다. (후술할 메서드는 그중 일부이다)
String toString()
boolean equals(Object other)
int hashCode()
Class<?> getClass()
protected Object clone()
protected void finalize()
객체의 문자열 표현식을 반환한다.
class Employee {
private String name;
protected int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return this.name;
}
public int getSalary() {
return this.salary;
}
}
Employee empl = new Employee("John", 50000);
System.out.println(empl.getName() + " " + empl.getSalary());
System.out.println(empl.toString()); // prints internal representation of emp1
객체를 출력하려고 하면 자동적으로 객체의 toString
메서드가 호출된다.
System.out.println(emp1);
자신의 클래스에서 이 메서드를 오버라이드할 수 있다.
class Employee {
private String name;
protected int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String toString() {
return getClass().getName() + "[name=" + name + ",salary=" + salary + "]";
}
public String getName() {
return this.name;
}
public int getSalary() {
return this.salary;
}
}
만약 위의 클래스를 상속한 어떤 클래스가 있다면, toString
메서드는 그 클래스에서도 마찬가지 양식으로 호출된다.
equal
메서드는 두 객체가 같은지 확인한다.
아래의 코드는 false를 반환한다. 왜냐하면 두 객체는 다르기 때문이다.
Employee empl1 = new Employee("John", 50000);
Employee empl2 = new Employee("John", 50000);
System.out.println(empl1.equals(empl2));
equals
메서드는 두 객체의 "같음"을 비교하기 위해 자주 오버라이드된다.
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.eqauls(s2));
우리가 직접 작성한 클래스도 equals 메서드를 오버라이드할 수 있다.
class Employee {
private String name;
protected int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String toString() {
return getClass().getName() + "[name=" + name + ",salary=" + salary + "]";
}
public boolean equals(Object otherObject) {
if (this == otherObject)
return true;
if (otherObject == null)
return false;
if (getClass() != otherObject.getClass())
return false;
Employee other = (Employee) otherObject;
return (this.name == other.name && this.salary == other.salary);
}
public String getName() {
return this.name;
}
public int getSalary() {
return this.salary;
}
}
그런데 equals 메서드를 오버라이드할 때 주의할 점들이 있다.
equals()
를 사용할 수도 있다.해시 코드(hashCode)는 객체로부터 계산되는 정수이다.
아직 우리가 equals()
를 오버라이드하지 않았다고 가정해 보자.
class Employee {
private String name;
protected int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String toString() {
return getClass().getName() + "[name=" + name + ",salary=" + salary + "]";
}
public String getName() {
return this.name;
}
public int getSalary() {
return this.salary;
}
}
아래의 코드에서 empl1과 empl2는 다른 객체이기에, 다른 해시 코드를 가진다.
Employee empl1 = new Employee("John", 50000);
Employee empl2 = new Employee("John", 50000);
System.out.println(empl1.hashCode() + " " + empl2.hashCode());
만약 equals
메서드를 오버라이드하면, 같음의 정의에 부합하게 hashCode
메서드도 오버라이드해야 한다.
예를 들어, String 클래스는 equals 메서드와 hashCode 메서드 모두를 오버라이드해서 두 String 객체가 같으면 그들의 해시코드도 같다.
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2));
System.out.println(s1.hashCode() + " " + s2.hashCode());
오랜만이네요. 오늘도 좋은 정보 감사합니다 :)