📌상속
상속이란 말 그대로 물려준다는 뜻이다. 상속은 기존에 존재하는 유사한 클래스로부터 속성과 동작을 이어받고 자신이 필요한 기능을 추가하는 기법이다. 이것이 상속에 사용되는 키워드가 extends인 이유이다.
- 상속되는 클래스를 상위 클래스(super class) (=조상, 부모, 수퍼, 기반 클래스)
- 상속을 받는 클래스를 하위 클래스(sub class) (=자손, 자식, 서브, 파생된 클래스)라고 한다.
상속은 클래스 정의 다음에 extends를 써주고 수퍼 클래스 이름을 적어주면 된다.
( 하위 클래스 extends 상위 클래스 )
class Calculator {
int left, right;
public void setOprands(int left, int right) {
this.left = left;
this.right = right;
}
public void sum() {
System.out.println(this.left + this.right);
}
public void avg() {
System.out.println((this.left + this.right) / 2);
}
}
class SubstractionableCalculator extends Calculator { //상속
public void substract() {
System.out.println(this.left - this.right);
}
}
class MultiplicationableCalculator extends Calculator { //상속
public void multiplication() {
System.out.println(this.left * this.right);
}
}
class DivisionableCalculator extends MultiplicationableCalculator { //상속
public void division() {
System.out.println(this.left / this.right);
}
}
public class CalculatorDemo1 {
public static void main(String[] args) {
SubstractionableCalculator c1 = new SubstractionableCalculator();
c1.setOprands(10, 20);
c1.sum();
c1.avg();
c1.substract();
DivisionableCalculator c2 = new DivisionableCalculator();
c2.setOprands(10, 20);
c2.sum();
c2.avg();
c2.multiplication();
c2.division();
}
}
서브클래스인 SubstractionableCalculator는 Calculator에서 정의한 메소드 setOprands, sub, avg를 사용할 수 있다.
또한 상속 받은 MultiplicationableCalculator를 또 상속 받는거도 가능하다. ex) DivisionableCalculator
→자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.
📌클래스 간의 관계 - 포함 관계
상속을 통해 클래스간에 관계를 맺어주고 클래스를 재사용하는 방법이 있고, 상속이외에도 클래스를 재사용하는 방법이 있다. 그것은 바로 클래스간에 '포함' 관계를 맺어주는 것이다.
class Point{
int x;
int y;
}
class Circle{
int x;
int y;
int r;
}
//다음과 같이 나타낼 수 있다.
class Circle{
Point p = new Point();
int r;
}
클래스간의 관계 결정
클래스를 작성할때 상속관계를 맺어 줄 것인지 포함관계를 맺어 줄 것인지 결정하는 것이 혼란스러울 때가 있다.
이럴 때는 '~은 ~이다.(is~a)' 와 '~은 ~을 가지고 있다.(has~a)' 를 넣어서 문장을 만들어 보면 된다.
ex)
Circle 은 Shape이다. → is a → 상속 관계
Circle 은 Shape을 가지고 있다.
Circle 은 Point 이다.
Circle 은 Point를 가지고 있다. → has a → 포함 관계
단일 상속
Java에서는 다중 상속을 허용하지 않고 단일 상속만을 허용한다. 그래서 하나 이상의 클래스로부터 상속을 받을 수 없다.
다중상속을 허용하면 여러 클래스로부터 상속받을 수 있어 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있지만, 클래스간의 관계가 매우 복잡해진다는 것과, 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할수 있는 방법이 없다는 단점을 갖고 있다.
→ 단일 상속을 사용하여 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다.
📌오버라이딩(overriding)
상속은 상위 클래스의 기능을 하위 클래스에게 물려주는 기능이다. 그렇다면 하위 클래스는 상위 클래스의 메소드를 주어진 그대로 사용해야 할까? 만약 상속받은 메소드의 변경이 필요하면 어떻게 하면될까? 상속받은 메소드를 하위 클래스에서 다시 작성해주면 된다. 이를 오버라이딩(overriding)이라 한다.
오버라이딩 조건
- 메소드의 이름이 같아야 한다.
- 메소드 매개변수의 숫자와 데이터 타입 그리고 순서가 같아야 한다.
- 메소드의 리턴 타입이 같아야 한다.
오버라이딩 제한
- 접근 제어자를 조상 클래스의 메소드보다 좁은 범위로 변경할 수 없다.
- 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
- 인스턴스 메소드를 static메소드로 또는 그 반대로 변경할 수 없다.
1) 오버라이딩이 올바르지 않은 경우
class Calculator {
int left, right;
public void setOprands(int left, int right) {
this.left = left;
this.right = right;
}
public void avg() {
System.out.println((this.left + this.right) / 2); //리턴 타입이 void
}
}
class SubstractionableCalculator extends Calculator {
public int avg() {
return (this.left + this.right)/2; //리턴 타입이 int
}
}
2) 바르게 오버라이딩을 한 경우
class Calculator {
int left, right;
public void setOprands(int left, int right) {
this.left = left;
this.right = right;
}
public int avg() {
return ((this.left + this.right) / 2);
}
}
class SubstractionableCalculator extends Calculator {
public int avg() {
//return ((this.left + this.right) / 2); 은 상위 클래스와 중복이므로
return super.avg(); // super 중복을 제거하여 간결하게 나타낼 수 있다.
}
}
super()은 밑에서 다시 설명하도록 하겠다.
📌오버로딩(overloading)
overloading이란 매개변수의 개수가 다른 메소드를 중복으로 선언하는 것이다.
즉, 메소드 오버로딩은 매개변수가 다르면 이름이 같아도 서로 다른 메소드가 되는 것임을 이용한 것 이다.
overloading의 규칙
- 매개변수의 개수가 달라도 사용할 수 있다.
- 매개변수의 데이터 타입이 달라도 사용할 수 있다.
- 리턴타입이 다르면 오류가 발생한다.
class Calculator{
int left, right;
int third = 0;
public void setOprands(int left, int right){
System.out.println("setOprands(int left, int right)");
this.left = left;
this.right = right;
}
public void setOprands(int left, int right, int third){ //overloading
System.out.println("setOprands(int left, int right, int third)");
this.left = left;
this.right = right;
this.third = third;
//중복을 최소화하기 위해 다음과 같이 작성도 가능하다.
//this.setOprands(left,right);
//System.out.println("setOprands(int left,int right,int third)");
//this.third=third;
}
public void sum(){
System.out.println(this.left+this.right+this.third);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 20);
c1.sum();
c1.setOprands(10, 20, 30);
c1.sum();
}
}
가변인자(varargs)와 오버로딩
기존 메소드를 선언할 때 매개변수 개수가 고정적이었으나 이제는 동적으로 지정해줄 수 있게되었다. 이 기능을 가변인자라고 한다.
가변인자는 타입... 변수명과 같은 형식으로 선언된다.
String concatenate(String s1, String s2) {...}
String concatenate(String s1, String s2, String s3) {...}
String concatenate(String s1, String s2, String s3 String s4) {...}
//이럴 때, 가변인자를 사용하면 메서드 하나로 간단히 대체할 수 있다.
String concatenate(String... str) {...}
//이 메서드를 호출할 때는 아래와 같이 인자의 개수를 가변적으로 할 수 있다.
System.out.println(concatenate()); //인자가 없음
System.out.println(concatenate("a")); //인자가 하나
System.out.println(concatenate("a","b")); //인자가 둘
System.out.println(concatenate(new String[]{"a", "b"})); //배열도 가능하다.
// 가변인자는 내부적으로 배열을 이용한다.
// 즉, 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성된다.
// 이처럼 가변인자가 편리하지만, 비효율이 숨어있으므로 필요한 경우에만 가변인자를 사용하는것이 좋다.
상속 관계에서 오버로딩
public class Calculator2 extends Calculator{
public void setOprands(int left, int right, int third, int fourth){
System.out.println("setOprands(int left, int right, int third, int fourth)"); //상속 관계에서 오버로딩
this.left=left;
this.right=right;
this.third=third;
this.fourth=fourth; //매개변수 개수가 다르다.
}
}
📌overriding vs overloading
overriding (오버라이딩)
상속받은 메소드의 내용을 변경하는 것 (change, modify)
부모가 정의한 메소드를 자식 메소드에서 동작 방법을 변경하는 것
overloading (오버로딩)
기존에 없는 새로운 메서드를 정의하는 것 (new)
다른 매개변수를 가진 이름이 같은 메소드를 여러개 정의하는 것
📌super
super는 상속 관계에서 자손 클래스에서 조상 클래스의 메소드나 필드를 명시적으로 참조하기 위하여 사용된다.
멤버변수와 지역변수의 이름이 같을 때 this를 붙여서 구별했듯이 상속받은 멤버와 자신의 클래스에 정의된 멤버의 이름이 같을 때는 super를 붙여서 구별할 수 있다.
this와 마찬가지로 super역시 static 메소드에서는 사용할 수 없고 인스턴스 메서드에서만 사용가능하다.
super키워드는 부모 클래스를 의미하고, super()는 부모 클래스의 생성자를 의미한다.
부모 클래스의 기본 생성자가 없어져도 오류가 발생하지 않는다.
보통 메소드를 재정의 할 때, 조상 클래스에 내용을 추가하는 경우가 많다. 이런 경우에는 super 키워드를 이용하여 조상 클래스의 메소드를 호출해준 후에 자신이 필요한 부분을 추가해주는 것이 좋다. 그럼 후에 조상 클래스의 메소드가 변경되더라도 변경된 내용이 자손클래스의 메소드에 자동적으로 반영되기 때문이다.
class Point{
int x;
int y;
String getLocation(){
return "x: "+ x + ",y: "+ y;
}
}
class Point3D extends Point{
int z;
String getLocation(){
//return "x: "+ x + ",y: "+ y + ",z: " + z;
return super.getLocation() + ",z: " + z; //조상 메서드 호출
}
}
super( ) 조상 클래스의 생성자
this( )와 마찬가지로 super( )역시 생성자이다. this( )는 같은 클래스의 다른 생성자를 호출하는데 사용하지만, super( )는 조상 클래스의 생성자를 호출하는데 사용된다.
자손 클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다. 그래서 자손 클래스의 인스턴스가 조상 클래스의 멤버들을 사용할수 있는 것이다. 이 때 조상클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.
생성자의 첫 줄에서 조상클래스의 생성자를 호출해야하는 이유는 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다.
그래서 모든 클래스의 최고 조상인 Object클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다. 그렇지 않으면 컴파일러는 생성자의 첫줄에 super(); 를 자동적으로 추가한다.
- Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자, this( ); 또는 super( );를 호출해야 한다. 그렇지 않으면 컴파일러가 자동적으로 super( );를 생성자의 첫 줄에 삽입한다.
- 하위 클래스의 생성자에서 super를 사용할 때 주의할 점은 super가 가장 먼저 나타나야 한다. 이는 부모가 초기화되기 전에 자식이 초기화되는 일을 방지하기 위함이다.
//ex1)
class Point{
int x,y;
//해결1
// Point( ) { }
Point(int x, int y){
//%%%%%%%%%%%%%%%%%%%%
//%%%%%%%%%%%%%%%%%%%%
this.x=x;
this.y=y;
}
}
class Point3D extends Point{
int z;
Point3D(){
this(100,200,300); //Point3D(int x,int y,int z)를 호출한다.
}
Point3D(int x,int y ,int z){
//////////////////////////////
//////////////////////////////
this.x=x;
this.y=y;
this.z=z;
}
//해결2
//이때 Point3D(int x, int y, int z)의 생성자를 바꿔준다.
//Point 3D(int x,int y,int z){
// super(x,y); //조상클래스의 생성자 Point(int x, int y)를 호출한다.
// this.z=z;
//}
}
//////////// 부분에서 생성자 첫 줄에서 다른 생성자를 호출하지 않기 때문에 컴파일러가 super( );를 여기서 호출한다.
super( )은 Point3D의 조상인 Point클래스의 기본 생성자인 Point( )를 의미한다. 하지만 Point클래스에서 생성자 Point( )가 정의되있지 않기 때문에 컴파일 에러가 발생한다. 이를 해결할 방법은 다음과 같다.
- 해결1. Point클래스에 생성자 Point( )를 추가해준다.
Point 클래스에 생성자가 정의되어 있어 컴파일러가 기본 생성자(여기서는 Point())를 자동적으로 추가 안하기 때문이다.
- 해결2. 생성자 Point3D(int x, int y, int z)의 첫 줄에서 Point(int x, int y)를 호출하도록 변경하면 된다.
%%%%%부분에서는 생성자 첫 줄에서 다른 생성자를 호출하지 않기 때문에 컴파일러가 'super( );'를 여기에 삽입한다. super( )는 Point의 조상인 Object클래스의 기본 생성자인 Object( )를 의미한다.
즉 다음과 같은 순서로 생성자가 호출된다.
해결2
Point3D ----------------> Point3D(int x, int y, int z) ------------------> Point(int x, int y) ------------------> Object( )
this(100,200,300); super(x,y); super( );
Object 클래스
우리가 상속을 했던 하지 않았던 기본적으로 Object 클래스가 수퍼클래스가 되며 상속을 하게 된다. 즉 자바에서 모든 클래스는 공통으로 Object를 암시적으로 상속을 받고 있다.
Object 클래스에 대한 자세한 설명은 아래 링크를 읽으면 알 수 있다.
https://yeo-computerclass.tistory.com/33
📌final 키워드
final은 '마지막의' 또는 '최종'의 의미를 가지고 있다. final 키워드가 나타날 수 있는 곳은 세 군데이다. 사실 객체 지향 언어의 구성 요소도 딱 세 가지 뿐이다. 바로 클래스, 메소드, 변수이다.
제어자 | 대상 | 의미 |
final | 클래스 | 변경될 수 없는 클래스, 확장(상속) 될 수 없는 클래스가 된다. 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다. 종단 클래스 내부의 메소드는 모두 재정의 될 수 없다. |
메서드 | 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다. | |
변수 | 변수 앞에서 final이 붙으면, 값을 변경할 수 없는 상수가 된다. 다른 언어에서는 읽기 전용인 final 키워드 대신 const 키워드를 사용하기도 하는데 Java는 이런 혼동을 피하기 위해 const를 키워드로 등록해놓고 쓰지 못하게 하고 있다. |
선언하는 법
//종단 필드(final field)
class Circle{
static final double PI = 3.14;
...
}
//종단 클래스(final class)
final class String{
...
}
//종단 메소드(final method)
class String{
final void A(){}
}
'Java' 카테고리의 다른 글
[Java] 예외처리 (0) | 2021.05.04 |
---|---|
[Java] 제어자 , abstract , interface , 다형성, 내부 클래스 (0) | 2021.05.02 |
[Java] 생성자 (0) | 2021.04.08 |
[Java] 객체와 클래스(멤버 변수/메소드) (0) | 2021.03.25 |
[Java] 배열 (0) | 2021.03.25 |