오버로딩
git/ eomcs-java-basic/ src/main/java com.eomcs.oop.ex06.b
오버로딩: 파라미터의 형식은 다르지만 같은 기능을 수행하는 메서드에 대해 같은 이름을 부여함으로써 프로그래밍의 일관성을 제공하기 위한 문법
C언어에서는 같은 이름에 각각 다른 파라미터를 준 function를 선언할 수 없다. 즉, 오버로딩이 불가능하다.
따라서 같은 기능의 function이라도 이름을 조금씩 달리 해야 한다.
plusi(int, int) / plusf(float, float)
function prototype - 메서드를 선언하기 전에 개략적으로 소개하는 명령어
-> ex) int plus(int, int)
method signature - 메서드의 선언부
자바에서는 같은 기능을 해도 파라미터로 받는 값에 따라 메서드 이름이 달라, 사용하기가 번거로운 문제를 해결하기 위해 이것이 오버로딩을 허용했다.
오버로딩의 조건
1) 메서드의 이름이 같다.
2) 파라미터의 데이터 타입이나 순서, 개수가 다르다.
조건이 아닌 것 !
- 파라미터 변수명만 다른 것으로는 안된다.
(파라미터 값의 타입으로 메서드를 구별하기 때문이다.) - 리턴 타입과는 상관 없다.
sub 클래스 안에서 super 클래스가 갖는 메서드를 오버로딩할 수도 있다.
public class A {
static public void m() {
System.out.println("m()");
}
}
public class B extends A {
static void m(int a, int b, int c) {
System.out.println("m(int,int,int)");
}
}
println()과 toString()이 전형적인 오버로딩의 사례이다.
System.out.println(100);
System.out.println(true);
System.out.println("hello");
Integer obj1 = Integer.valueOf(100);
Integer obj2 = Integer.valueOf("100");
Integer obj3 = Integer.valueOf("64", 16);
오버라이딩
git/ eomcs-java-basic/ src/main/java com.eomcs.oop.ex06.c,d
오버라이딩은 super 클래스에 있는 메서드를 변경하지 않고 sub 클래스에서 해당 메서드를 변경할 수 있는 방법이다.
같은 이름과 같은 파라미터을 가진 메서드를 sub 클래스에 맞춰 재정의하는 것이 오버라이딩이다.
오버로딩과 오버라이딩의 차이점은 물론이고, 각 기능의 목적을 잘 알아둬야한다.
오버로딩은 다른 파라미터를 갖고 같은 기능을 하는 메서드의 이름을 같게 하여 일관성을 주기 위함이다.
오버라이딩은 상속받은 메서드를 서브 클래스의 역할에 맞게 재정의하기 위함이다.
오버라이딩의 조건
- 부모 클래스의 메서와 같은 메서드 시그니처(method signature = 메서드명, 파라미터 타입/개수, 리턴 타입)를 가져야한다.
필드 오버라이딩
슈퍼클래스 내의 변수와 같은 이름의 변수를 서브클래스에서 재정의할 수도 있다.
객체지향을 잘 이해하는 방법
1) 메서드를 객체를 피연산자로 갖고 실행하는 연산자로 생각해야한다.
2) 모든 객체들은 사람처럼 생각하고 다뤄야 한다. 메서드를 객체에게 명령을 내리는 것으로 생각해야한다. 초보자가 가장 많이 오해하는 것은 인스턴스 안에 메서드가 있다고 생각하는 것이다. 그러나 인스턴스는 변수 덩어리이기 때문에 메서드는 명령일 뿐이다.
필드 오버라이딩이 사용되어 객체 안에 같은 이름과 타입의 두 변수가 존재한다면,
public class A {
String name;
void print() {
System.out.printf("'%s'님 반갑습니다.\n", this.name);
}
}
public class A3 extends A {
int age;
void print() {
System.out.printf("'%s(%d)'님 반갑습니다!\n", this.name, this.age);
}
}
ublic class A4 extends A3 {
// 필드 오버라이딩
String age;
// this.필드명 => 현재 클래스에서 해당 필드를 찾는다. 없으면 상위 클래스로 따라 올라가면서 찾는다.
// super.필드명 => 상위 클래스에서부터 해당 필드를 찾는다. 없으면 계속 상위 클래스로 따라 올라간다.
void print3() {
System.out.printf("'%s(%s, %d)'님 반갑습니다!\n", this.name, this.age, super.age);
}
}
public class Exam0140 {
public static void main(String[] args) {
A4 obj1 = new A4();
obj1.name = "홍길동";
obj1.age = "20";
// obj1.age = 20; // 컴파일 오류
// obj1.super.age = 20; // 컴파일 오류
obj1.print();
obj1.print3();
// 결과 ! '홍길동(0)'님 반갑습니다!
// '홍길동(20, 0)'님 반갑습니다!
}
}
sub 클래스의 인스턴스로는 다른 클래스에서 오버라이딩 이전의 인스턴스 필드를 사용할 수 없다.
메서드도 마찬가지다. 다른 클래스에서 sub 클래스의 인스턴스를 갖고 오버라이딩 이전의 메서드를 실행할 방법은 없다.
오버라이딩의 목적이 sub클래스에 적절하게 메서드를 변형하는 것이기에 애초에 오버라이딩 전의 것을 꺼낼 필요 조차 없기 때문이다.
sub 클래스에 있는 인스턴스 메서드의 super 내장변수를 통해 꺼내는 방법 뿐이다.
필드에 관해서는 방법이 하나 있긴 하다. super 클래스로 형변환을 시켜주는 것이다.
package com.eomcs.oop.ex06.c;
public class Exam0140 {
public static void main(String[] args) {
A4 obj1 = new A4();
obj1.name = "홍길동";
obj1.age = "20";
System.out.println(((A3)obj1).age);
// 형변환한 레퍼런스의 클래스를 기준으로 인스턴스 변수를 찾는다.
// 결과 ! 0
}
}
알아두어야할 것은, 인스턴스 필드를 오버라이딩 하는 것 자체가 실무에서 보기 힘들다. 너무 헷갈리고 복잡한 코드가 되기 쉽기 때문이다. 따라서 이런 경우도 아마 보기 힘들 것이다.
비슷한 방법이 메서드에서는 통하지 않는다.
package com.eomcs.oop.ex06.c;
public class Exam0430 {
public static void main(String[] args) {
X4 x4 = new X4();
x4.m1();
// 인스턴스 필드와 달리 메서드의 경우는
// 상위 레퍼런스가 하위 인스턴스 가리킬 경우
// 호출하는 메서드는 그 인스턴스의 클래스에서 찾는다.
// 없으면 상위 클래스로 따라 올라가면서 찾는다.
((X3)x4).m1();
((X2)x4).m1();
((X)x4).m1();
X3 x3 = x4;
X2 x2 = x4;
X x = x4;
x3.m1();
x2.m1();
x.m1();
// 결과 ! X4의 m1()
// X4의 m1()
// X4의 m1()
// X4의 m1()
// X4의 m1()
// X4의 m1()
// X4의 m1()
}
}
@Override 애노테이션
오버라이딩을 하려다가 실수로 파라미터의 타입이나 개수, 순서를 달리해서 오버로딩이 되는 경우가 있다. 이것은 문법적으로 전혀 틀린 것이 없기 때문에 정상 컴파일이 된다. 따라서 문제가 더 커지기 전에 이를 방지하기 위해 오버라이딩을 하는 메서드 앞에 @Override 애노테이션을 붙임으로써 오버라이딩을 제대로 했는지 검사를 하여 버그를 줄일 수 있다. 따라서 오버라이딩 하는 경우엔 반드시 붙여라.
오버라이딩된 메서드의 접근 범위 조정
오버라이딩한 메서드는 기존 메서드보다 접근범위를 넓히는 것이 가능하다. 그러나 반대로 접근 범위를 좁히는 것은 안된다.
기존 메서드가 private 인 경우는 오버라이딩 자체가 불가능하기 때문에 접근 범위를 넓히는 것은 더 말이 안된다.
프로그래밍을 할 때는 어떻게 접근제어자를 하면 좋을까?
-> 일단은 무조건 private으로 접근을 막아놓은 다음에 공개할 대상만 풀어라.
초보자는 이렇게 해야하지만, 나중에는 경험이 쌓여서 어느 정도로 접근을 풀어야할지 감이 온다.
그 전까지는 최대한 접근을 막아놓고 필요할 때에만 공개하는 것이 좋다.
접근제어자가 private이어서 혹은, sub 클래스가 접근할 수 없는 상태여서 원본 메서드에 접근할 수 없는 경우, 오버라이딩이 불가하다. 따라서 오버라이딩 한답시고 sub 클래스에서 같은 이름의 같은 메서드를 만들더라도 오버라이딩이 아니라 새로운 메서드를 만들어낸 것 뿐이다. 따라서 @Override 애노테이션은 필수이다.
오버라이딩과 내장 변수 super
어떤 클래스를 상속받은 sub 클래스의 인스턴스 메서드에는 자기 자신의 주소를 참조하는 this와 인스턴스 자신 중에서도 super 클래스에서 상속받은 멤버만을 참조하는 super가 있다.
public class X {
void m1() {
System.out.println("X의 m1()");
}
void m2() {
System.out.println("X의 m2()");
}
}
public class X2 extends X {
@Override
void m1() {
System.out.println("X2의 m1()");
}
}
public class X3 extends X2 {
@Override
void m2() {
System.out.println("X3의 m2()");
}
}
public class X4 extends X3 {
@Override
void m1() {
System.out.println("X4의 m1()");
}
void test() {
this.m1(); // X4의 m1()
super.m1(); // X2의 m1()
this.m2(); // X3의 m2()
super.m2(); // X3의 m2()
//super.super.m1(); // 컴파일 오류! 이런 문법은 없다! 무협지 문법!
}
}
public class X4 extends X3 {
@Override
void m1() {
System.out.println("X4의 m1()");
}
void test() {
this.m1(); // X4의 m1()
super.m1(); // X2의 m1()
this.m2(); // X3의 m2()
super.m2(); // X3의 m2()
//super.super.m1(); // 컴파일 오류! 이런 문법은 없다! 무협지 문법!
}
}
메서드 안에서 this 인스턴스 변수를 가리키는 방법과 super가 인스턴스 변수를 가리키는 방법은 상이하다.
- this.필드명 : 현재 클래스(호출된 메서드가 있는 클래스)에서 해당 필드를 찾는다. 없으면 상위 클래스로 따라 올라가면서 찾는다.
- super.필드명 : 현재 클래스의 상위 클래스에서부터 해당 필드를 찾는다. 없으면 계속 상위 클래스로 따라 올라간다.
호출할 메서드를 찾는 방법도 마찬가지이다.
- this.메서드() : 현재 클래스부터 호출할 메서드를 찾는다.
- super.메서드() : 현재 클래스의 상위 클래스부터 호출할 메서드를 찾는다.
다형적 변수와 오버라이딩
상위 레퍼런스가 하위 클래스 인스턴스를 가리킬 때 하위 클래스 인스턴스로 오버라이딩된 메서드를 호출하면,
레퍼런스가 실제로 가리키는 인스턴스인 하위 클래스에서부터 찾아 올라 간다.
그렇지만 레퍼런스의 클래스 타입인 상위 클래스에 있는 메서드만 호출이 가능하다.
public class A {
public void m() {
System.out.println("A의 m() 호출!");
}
}
public class A2 extends A {
@Override // 컴파일러에게 오버라이딩을 제대로 했는지 검사하라고 명령한다.
public void m() {
System.out.println("A2의 m() 호출!");
}
public class A3 extends A2 {
public void y() {
System.out.println("A3에서 추가한 메서드 y()");
}
}
public void x() {
System.out.println("A2에서 추가한 메서드 x()");
}
}
public class Exam01 {
public static void main(String[] args) {
A a = new A();
a.m(); // A의 멤버 호출. OK!
//((A2)a).x(); // A 객체를 A2 객체라 우기면, 컴파일러는 통과! 실행은 오류!
System.out.println("--------------------");
A2 a2 = new A2();
a2.m(); // A2가 수퍼 클래스인 A의 메서드 호출! OK!
a2.x(); // A2의 메서드 호출! OK!
System.out.println("----------------------");
A a3 = new A2();
a3.m(); // A2의 m() 호출.
//a3.x(); // 컴파일 오류!
((A2)a3).x(); // OK!
System.out.println("----------------------");
}
}
정리하자면
레퍼런스 변수가 해당 클래스의 하위 클래스를 가리킬 때 (다형적 변수)
1) 호출 범위: 레퍼런스 변수의 클래스 타입인 상위 클래스가 갖는 메서드
2) 실행 방법: 인스턴스의 클래스 타입인 클래스부터 찾아 가장 첫번째로 만난 메서드
실제로 실행하는 것과 호출 가능한 범위가 다른 이유는 자바 컴파일러가 단순해서 레퍼런스의 클래스 타입만 보고 메서드가 호출 가능한지 따지기 때문이다.
final 사용법
git/ eomcs-java-basic/ src/main/java com.eomcs.oop.ex06.e
1. 클래스 앞
클래스에 final을 붙이면 해당 클래스를 상속받을 수 없다.
public static final class 이 순서로 적기
2. 메서드 앞
메서드 앞에 final을 붙이면 오버라이딩이 불가능하다.
혹시라도 개발자가 의도치 않게 같은 이름과 같은 파라미터로 오버라이딩할 경우까지 포함하여, 모든 오버라이딩의 가능성을 막기 위해서 final을 붙인다.
final을 붙일만한 메서드는 다음 예시와 같다.
- 보안에 관련된 일을 하는 메서드
- 템플릿 메서드 디자인 패턴에서처럼 전체적인 작업 흐름을 정의한 메서드
- String 클래스
3. 필드 앞
final은 필드를 상수 필드로 만든다.
상수 필드는 변수 초기화 문장이나 변수초기화 블록, 혹은 생성자에서 초기화되어야한다.
상수 필드는 초기화 이후로 값이 바뀌지 않기 때문에 인스턴스 별로 각각 생성될 필요가 보통 없다.
따라서 대부분의 상수 필드는 static 필드이다.
4. 파라미터 앞
final을 파라미터 변수에 붙여서 파라미터 변수 값을 안에서 바꿀 수 없도록 파라미터 변수 앞에 final을 붙이도록 권고하고 있다.
방어적인 프로그래밍을 추구하고 안정성을 중요시 하는 개발자는 final을 꼭 붙이려고 한다.
캡슐화(encapsulation)
git/ eomcs-java-basic/ src/main/java com.eomcs.oop.ex07
자바는 개발자가 처음 의도한 대로 프로그래밍을 계속 유지할 수 있도록 특정 필드에 접근을 제어할 수 있도록 했다.
캡슐화는 인스턴스 변수에 추상화 목적에 맞는 유효한 값만 넣을 수 있도록 외부 접근을 제한하는 문법이다.
필드 접근 제어에 따른 메서드 생성
1. getter
접근 제어자를 필드에 붙여 필드에 대한 외부 접근을 차단했다.
따라서 다른 클래스에서 값을 변경하지 못하게 하는 대신 값을 직접 조회도 못하게 된다.
이런 경우에는 값을 조회하는 메서드를 통해 간접적으로 접근할 수 있다.
class Score2 {
private int sum;
private float aver;
public int getSum() {
return this.sum;
}
public float getAver() {
return this.aver;
}
}
public class Exam0210 {
public static void main(String[] args) {
System.out.printf("%d, %.1f\n",
s1.getSum(), s1.getAver());
}
}
보통 이렇게 필드의 값을 조회하는 용도로 사용하는 메서드의 경우
메서드의 용도를 이해하기 쉽도록 getxxx() 형태로 이름을 짓는다.
=> 메서드의 이름이 get으로 시작하기에 getter라고 부른다.
2. setter
필드에 대한 접근을 막았으니 필드의 값을 바꾸는 메서드도 설정할 수 있다.
보통 필드의 값을 설정하는 메서드는 setxxx()으로 이름을 짓는다.
=> 이런 메서드를 "세터(setter)"라 부른다.
* 실무에서는 보통 필드의 직접 접근을 막는다.
가능한 세터를 통해 값을 설정하고 게터를 통해 값을 조회하도록 유도한다.
package com.eomcs.oop.ex07.a;
class Score4 {
private String name;
private int kor;
private int eng;
private int math;
private int sum;
private float aver;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setKor(int kor) {
this.kor = kor;
this.compute();
}
public int getKor() {
return this.kor;
}
public void setEng(int eng) {
this.eng = eng;
this.compute();
}
public int getEng() {
return this.eng;
}
public void setMath(int math) {
this.math = math;
this.compute();
}
public int getMath() {
return this.math;
}
public int getSum() {
return this.sum;
}
public float getAver() {
return this.aver;
}
void compute() {
this.sum = this.kor + this.eng + this.math;
this.aver = this.sum / 3f;
}
}
public class Exam0311 {
public static void main(String[] args) {
Score4 s1 = new Score4();
s1.setName("홍길동");
s1.setKor(100);
s1.setEng(90);
s1.setMath(80);
System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
s1.getName(),
s1.getKor(), s1.getEng(), s1.getMath(),
s1.getSum(), s1.getAver());
}
}
* 겟터,셋터는 public으로 접근을 모두에게 공개한다..
그 클래스 내부에서만 사용되는 메서드는 private으로 접근을 제한한다.
자바에서 지금은 필요없지만 나중에 필요하게 될 것을 대비하여 코드를 짜는 것을 확장성이라고 한다.
겟터와 셋터가 필요한 것은 그냥 변수를 넣지않고 그 전에 변수 값의 유효성을 검증하는 조건문을 집어넣기 위한 것이지만, 실무에서는 셋터에서 유효성을 검증하는 코드가 없고, 그냥 변수에 값을 집어넣는 경우가 많다. 따라서 셋터, 겟터의 무용론을 주장하는 개발자들이 있지만, 나중에 혹시라도 검증 코드가 필요할 경우를 대비해 일단 쓰는 것이다. 이것을 확장성이라고 한다.
그러니까 코드의 무용함을 떠나서 겟터와 셋터 등 변수값을 다루는 메서드를 만드는 것이 좋다.
인스턴스 접근 제어에 따른 메서드 생성 - Factory Method
인스턴스의 생성과정이 꽤 복잡한 경우에는 외부에서 호출하는 것이 번거롭다. 이럴때에는 인스턴스의 생성을 막기 위해 private이라는 접근 제어자를 붙인다. 인스턴스를 직접 생성하는 것을 막고 인스턴스를 생성해주는 다른 메서드를 사용하는 것이다.
ex) Calendar 생성자는 private으로 막혀있다. 따라서 getInstance() 메서드를 통해 인스턴스를 생성하고 공급한다
// 김밥은 직접 싸먹지 말고 김밥집에서 사먹자
// 김밥집을 직접 만들지 말고 인테리어 업자보고 만들어달라하자
인테리어 업자 builder = new 인테리어 업자();
김밥집 factory = new 김밥집();
김밥 obj = 김밥집.create();
package com.eomcs.oop.ex07.b;
public class Car {
String model;
String maker;
int cc;
int valve;
// 외부에서 직접 인스턴스를 생성하는 것을 막기 위해 생성자를 private으로 선언
private Car() {}
public static Car create(String name) {
Car c = new Car(); // private은 클래스 안에서 사용할 수 있다.
switch (name) {
case "티코":
c.model = "티코";
c.maker = "대우";
c.cc = 800;
c.valve = 16;
break;
case "소나타":
c.model = "소나타";
c.maker = "현대자동차";
c.cc = 1980;
c.valve = 16;
break;
default:
c.model = "모델S";
c.maker = "테슬라";
c.cc = 0;
c.valve = 0;
}
return c;
}
}
이와 비슷한 구조는 singletone 패턴이 있으나 목적이 조금 다르다.
이는 인스턴스 생성 과정이 복잡하기 보다 인스턴스를 한개만 만들어 사용하기 위함이기 때문이다.
.
접근 제어자의 접근 범위
-
private : 클래스 안에서만 접근 가능
중첩 클래스는 private을 붙여도 함께 있는 같은 클래스 아래의 다른 클래스에서 사용이 가능하다.
패키지 멤버 클래스일때만 pirvate을 붙여야 다른 클래스에서 접근이 봉쇄된다.
- (default) : private + 같은 패키지 소속
- protected : (default) + sub 클래스로 만든 변수일 경우 서브 클래스에서 접근 가능
- public : 모두 접근 가능
public class Exam0210 extends C {
public static void main(String[] args) {
C obj3 = new C();
obj3.privateVar = 100; // 접근 불가! 오직 그 클래스 안에서만 사용 가능.
obj3.defaultVar = 100; // 접근 불가! 같은 패키지까지만 접근 가능.
obj3.protectedVar = 100; // 접근 불가! 같은 패키지 또는 자식 클래스 접근 가능
// 자식 클래스인데 접근 불가?
// 이유 => 자기의 인스턴스 변수가 아니다.
obj2.publicVar = 100; // OK! 모두 다 접근 가능.
Exam0210 obj4 = new Exam0210();
//obj4.privateVar = 100; // 접근 불가! C 클래스에서만 접근 가능
//obj4.defaultVar = 100; // 접근 불가! C 클래스와 같은 패키지가 아니다.
obj4.protectedVar = 100; // OK! Exam02_1는 C의 자식 클래스이며,
// 또한 C로부터 상속 받아서 만든 자기 변수이다.
obj4.publicVar = 100;
}
void m1(C obj) {
//obj.privateVar = 100;
//obj.defaultVar = 100;
//obj.protectedVar = 100;
obj.publicVar = 100;
}
void m2(Exam0210 obj) {
//obj.privateVar = 100;
//obj.defaultVar = 100;
obj.protectedVar = 100;
obj.publicVar = 100;
}
}
자바는 클래스 기반으로 돌아가므로 class oriented program / object oriented program
클래스가 등장한 이유는 프로그램의 규모가 커질수록 class 별로 나누어 관리하는 것이 더 효율적이라는 판단이 있었기 때문이다.
C는 function 위주로 돌아가므로 functional program
작은 프로그램은 클래스까지 다룰 필요가 없으므로 C가 적절하다.
웹페이지는 하나하나가 작은 프로그램이므로 요즘에는 과거에 유행했던 funtional program이 다시 유행이 시작됐다.
개발 팀 안에서 개발이 다 끝난 후 고객들이 자주 사용할 시나리오를 갖고 테스트를 하는 것이 알파 테스트이다. 의도하지 않은 방법으로 고객들이 또 사용할 것을 고려하여 사용자들에게 직접 사용하게 하고 마지막으로 테스트를 하여 디버그하는 과정을 베타 테스트라고 부른다.
일본은 내부에서 알파 테스트를 철저히 하는 대신 베타 테스트를 잘 안하는 편이다. 우리나라와 일본의 개발과 테스트 과정이 상이하다.
'국비 교육' 카테고리의 다른 글
2020.8.14일자 수업 : String, Wrapper, ArrayList 구현 실습 (0) | 2020.08.15 |
---|---|
2020.8.11일자 수업 : 상속과 다형적 변수 (0) | 2020.08.12 |
2020.8.10일자 수업 : 클래스와 메서드 활용 (0) | 2020.08.11 |