📔 본 포스팅은 자바의 정석(남궁성 저, 3판)을 읽고 정리한 글입니다.
// 오버라이딩 예제
class MyPoint3 {
int x;
int y;
String getLocation() {
return "x: " + x + ", y:" + y;
}
}
class MyPoint3D extends MyPoint3 { // MyPoint3 클래스 상속 받음
int z;
String getLocation() { // 오버라이딩. 선언부는 변경 불가. 내용물은 변경 가능
return "x: " + x + ", y:" + y + ", z: " + z;
}
}
// Object클래스 오버라이딩
class MyPoint3 {
int x;
int y;
MyPoint3(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() { // Object클래스의 toString메서드 오버라이딩
return "x: " + x + ", y: " + y;
}
}
public class OverrideTest {
public static void main(String[] args) {
MyPoint3 p = new MyPoint3(3, 4);
// 아래의 두 문장은 동일하게 수행됨
System.out.println(p); // x: 3, y: 4
System.out.println(p.toString()); // x: 3, y: 4
}
}
🚩 오버라이딩의 조건 3가지
- 선언부가 조상 클래스의 메서드와 일치해야한다.
- 접근 제어자를 조상클래스의 메서드보다 좁은 범위로 변경할 수 없다.
- 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
class Parent {
void parentMethod() throws IOException, SQLException { // 예외 2개 선언
// ...
}
}
class Child extend Parent {
void parentMethod() throws IOException { // 예외 1개 선언
// ...
}
}
class Parent {
void parentMethod() {}
}
class Chid extends Parent { // 상속
void parentMethod() {} // 오버라이딩
void parentMethod(int i) {} // 오버로딩
void childMethod() {} // 새로운 메서드 정의
void childMethod(int i) {} // 오버로딩
void childMethod() {} // 중복정의, **컴파일 에러**
}
super()
자체가 조상의 생성자를 호출함)// 자손의 생성자는 자기가 선언한 것만 초기화 해야함
class Point {
int x, y;
Point(int x, int y) {
this.x = x; // iv초기화
this.y = y; // iv초기화
}
}
class Point3D extends Point {
int z;
Point3D(int x, int y, int z) { // 자손의 생성자가 조상의 멤버를 초기화(지양할 것)
this.x = x; // 조상의 멤버를 초기화
this.y = y; // 조상의 멤버를 초기화
this.z = z; // 자손의 생성자는 자기가 선언한 것만 초기화 해야함
}
}
// 아래 코드처럼 바꾼다.
Point3D(int x, int y, int z) {
super(x, y); // 조상클래스의 생성자 Point(int x, int y)를 호출
this.z = z; // 자신의 멤버를 초기화
}
super()
를 사용함.super()
해서 초기화)this()
또는 super()
,를 호출해야한다!!!.this()
, super()
) 를 호출 하지 않으면 컴파일러가 super(); 를 생성자의 첫 줄에 삽입한다.클래스를 작성할 때 기본 생성자를 필수로 작성하라!
// 생성자 첫 줄에 다른 생성자를 반드시 호출해야하는 이유.java
package ch7_OOP2;
class Point {
int x;
int y;
// 생성자가 있으므로 컴파일러가 기본생성자 (Point() { }) 를 자동으로 추가해주지 않는다.
Point(int x, int y) {
// 컴파일시 컴파일러가 'super();' 자동추가. (생성자의 첫줄에 다른 생성자를 호출하지 않았기 때문)
this.x = x;
this.y = y;
}
String getLocation() {
return "x: " + x + ", y: " + y;
}
}
class Point3D extends Point {
int z;
Point3D(int x, int y, int z) {
// 1. 컴파일시 컴파일러가 'super();'를 자동으로 추가.
// 2. super()는 조상 클래스 Point의 기본 생성자 Point() 호출
// 3. Point()를 호출 했더니 Point()가 없다 -> 컴파일 에러 발생
// 에러발생 이유 : Point클래스에서 생성자가 있으므로 컴파일러가 기본생성자 Point() { } 를 자동으로 추가해주지 않았음
// 에러방지를 위해 'super(x, y);' (// 조상의 생성자인 Point(int x, int y)를 호출)를 작성한다.
this.x = x; // -> super.x로 변경권장
this.y = y; // -> super.y로 변경권장
this.z = z;
}
String getLocation() { // 오버라이딩
return "x: " + x + ", y: " + y + ", z:" + z;
}
}
💻 import문 추가 단축키 : ctrl + alt + O
(IntelliJ : Optimize import)
// import문을 추가하지 않은 코드
class ImportTest {
java.util.Date today = new java.util.Date();
// ...
}
// import문을 추가한 코드
import java.util.Date;
class ImportTest {
Date today = new Date();
// ...
}
import 패키지명.클래스명;
또는
import 패키지명.*; // 모든 클래스
import java.util.Calendar;
import java.util.Date;
import java.util.ArrayList;
// 위의 3문장처럼 사용하거나 import java.util.*; 로 한 문장을 사용하는 것은 프로그램
// 의 성능에 영향 없음
import java.util.*;
import java.util.*; // java.util패키지의 모든 클래스
import java.text.*; // java.text패키지의 모든 클래스
// *은 모든 '클래스'를 의미하는 것이지, 패키지는 포함을 하지 않음
import java.*; // java패키지의 모든 클래스(패키지는 포함 안 함)
import java.sql.*; // java.sql.Date
import java.util.*; // java.util.Date --> 패키지이름 동일!(Date)
public class ImportTest {
public static void main(String[] args) {
java.util.Date today = new java.util.Date(); // 클래스 앞에 패키지명을 직접 붙여준다.
}
}
static import문은 웬만해서는 사용하지마라. (꼭 필요할 때만 사용해라.)
코드가 줄어드는 장점은 있지만, 클래스이름을 사용하는 것이 코드의 의도가 더 명확하다.
- static멤버를 사용할 때 클래스 이름을 생략할 수 있게 해준다.
import static java.lang.Integer.*; // Integer클래스의 모든 static멤버를 사용할 때 클래스이름 생략 가능
import static java.lang.Math.random; // Math.random()만 클래스이름 생략가능. import시 괄호 안붙임
import static java.lang.System.out; // System.out을 out만으로 참조 가능
// System.out.println(Math.random()); // 원래는 이렇게 작성해야할 문장을
out.println(random()); // static import문을 사용하여 이렇게 클래스 이름을 생략가능함.
// static import문 예제
import static java.lang.System.out;
import static java.lang.Math.*;
class Ex7_6 {
public static void main(String[] args) {
// System.out.println(Math.random());
out.println(random());
// System.out.println("Math.PI :" + Math.PI);
out.println("Math.PI :" + PI); // 클래스 이름 Math 생략
}
}
🚩 제어자(modifier)
접근 제어자 : public, protected, (default), private
그 외 : static, final, abstract, native, transient, synchronize, volatile
- 클래스와 클래스의 멤버(멤버변수, 메서드)에 부가적인 의미 부여
- 접근 제어자와 그 외의 것으로 나뉨
- ‘접근 제어자’는 4가지 중 하나만 선택해서 사용가능
- (default) 접근 제어자는 아무것도 붙이지 않는 것을 의미
- 하나의 대상에 여러 제어자를 같이 사용 가능(접근 제어자는 하나만, 제일 먼저 작성)
static
제어자는 클래스의 멤버(멤버변수, 메서드)에 붙일 수 있음. 그 대상이final
제어자가 사용될 수 있는 곳 : 클래스, 메서드, 멤버변수, 지역변수 (거의 모든 대상에 사용가능함)final
제어자의 의미, 대상이final class FinalTest { // 조상이 될 수 없는 클래스(확장 불가능한 클래스)
final int MAX_SIZE = 10;
final void getMaxSize() { // 오버라이딩할 수 없는 메서드(변경 불가)
final int LV = MAX_SIZE; // 상수
return MAX_SiZE;
}
}
abstract
가 사용될 수 있는 곳 : 클래스, 메서드abstract
제어자의 의미, 대상이abstract class AbstractTest { // 추상 클래스(추상 메서드를 포함하는 클래스)
abstract void move(); // 추상 메서드(구현부가 없는 메서드)
}
AbstractTest a = new AbstractTset(); // **에러**. 추상 클래스의 인스턴스 사용 불가
🚩 접근 제어자
private : 같은 클래스 내에서만 접근 가능함.
(default) : 같은 패키지 내에서만 접근 가능함
protected : 같은 패키지 내에서 & 다른 패키지의 자손 클래스에서 접근이 가능함
public : 접근 제한이 전혀 없음, 하나의 소스파일에는 하나의 public만 사용가능
public 클래스의 이름과 소스파일의 이름이 같아야함!!!
*소스파일을 컴파일 시에는 같은 소스에 있더라도 클래스파일이 개별로 생성되니, 이를 이용해서 접근 제어자 범위의 개념을 익히는 연습을 할 것*
public
(접근제한없음) > protected
(같은 패키지 + 다른 패키지의 자손)> (default)
(같은 패키지) > private
(같은 클래스)대상 | 사용 가능한 접근제어자 |
---|---|
클래스 | public, (default) |
메서드 | public, protected, (default), private |
멤버변수 | public, protected, (default), private |
지역변수 | 없음 |
package ch7_OOP2.pkg1;
public class MyParent {
// class MyParent { // 접근 제어자 (default) 다른 패키지에서 사용 불가
private int prv; // 같은 클래스 내 사용가능
int dft; // 같은 패키지 내 사용가능
protected int prt; // 같은 패키지 내 사용가능 & 다른 패키지의 자손 클래스
public int pub; // 접근 제한 없음
public void printMembers() { // 인스턴스 메서드
System.out.println(prv); // OK
System.out.println(dft); // OK
System.out.println(prt); // OK
System.out.println(pub); // OK
}
}
class MyParentTest {
public static void main(String[] args) {
MyParent p = new MyParent();
// System.out.println(p.prv); // 에러. 다른 클래스임
System.out.println(p.dft); // OK. 같은 패키지(pkg)임
System.out.println(p.prt); // OK. 같은 패키지(pkg)임
System.out.println(p.pub); // OK. 제한 없음
}
}
package ch7_OOP2.pkg2;
// 클래스 앞에 패키지 이름 쓰기 싫으면 import해야한다.
import ch7_OOP2.pkg1.MyParent;
//class MyChild extends ch7_OOP2.pkg1.MyParent {
class MyChild extends MyParent {
public void printMembers() { // 인스턴스 메서드
// System.out.println(prv); // 에러. 다른 클래스
// System.out.println(dft); // 에러. 다른 패키지
System.out.println(prt); // OK. 다른 패키지의 자손 클래스임
System.out.println(pub); // OK
}
}
public class MyParentTest2 {
public static void main(String[] args) {
MyParent p = new MyParent();
// System.out.println(p.prv); // 에러. 다른 클래스임
// System.out.println(p.dft); // 에러. 다른 패키지
// System.out.println(p.prt); // 에러. 다른패키지의 자손 아닌 클래스
System.out.println(p.pub); // OK. 제한 없음
}
}
💯 💯
private
으로 멤버변수로의 직접 접근은 막고,public
으로 메서드를 이용한 간접 접근을 허용하라!
💯💯💯 접근 제어자는 최대한 좁혀라.먼저 좁혀놓고, 필요할 때 넓히는 방식이 좋다!
→ 코드를 수정 후 제대로 작동하는지 반드시 테스트를 해봐야 한다. 이 때, 클래스 내부에서 private으로 선언한 메서드보다 public으로 선언한 메서드가 테스트 해야할 것이 훨씬 많다. public메서드를 사용하는 외부 클래스에서도 이 public메서드가 제대로 작동해야하는지 테스트해야하기 때문. ( private으로 선언한 메서드는 클래스 내부에서만 테스트를 하면 되니까 테스트할 범위가 적다.)
private
으로 선언하여 멤버 변수에 대한 직접 접근을 막음.setter
와 getter
를 만들고, 이를 통해 메서드를 통한 간접 접근을 허용.class Time {
// 멤버변수의 접근 제어자를 private으로 하여, 외부에서 직접 접근 금지시킴
private int time;
private int minute;
private int hour;
// 메서드를 public으로 선언하여 간접 접근 허용
public void setHour(int hour) {
if (isValidHour(hour)) this.hour = hour;
}
public int getHour() {
return this.hour;
}
// Time클래스 내에서만 사용하기 때문에, private으로 선언
private boolean isValidHour(int hour) {
return hour >= 0 && hour <= 23;
}
}
public class T0_TimeTest {
public static void main(String[] args) {
Time t1 = new Time();
// t1.time = - 100; // private(=같은 클래스 내에서만 접근 가능)으로 멤버변수를 선언하여 외부 접근 차단
t1.setHour(100);
System.out.println(t1.getHour());
t1.setHour(19);
System.out.println(t1.getHour());
}
}
// 추가예정