추상 메소드란? - 선언부는 있지만 구현부가 없는 메소드로 추상 메소드를 하나라도 갖고 있는 클래스는 반드시 추상 클래스로 선언해야 한다.
public abstract class 동물 {
abstract void 울어보세요();
// 이때 동물 클래스를 기존과 같이 새로운 인스턴스로 생성하면 오류가 발생한다.
동물 포유류 = new 동물(); // 오류발생 : Cannot instanitate the type 동물
이렇게 추상 메소드와 추상 클래스를 선언하게되면 해당 클래스를 상속 받는 클래스에서는 반드시 추상 메소드를 구현하는 구현체가 필요하다. 만약 상속을 받았지만 구현하지 않는다면 "The type XX must implement the inherited abstract method AA.Method of AA()" 같은 형식의 오류를 맞이하게 될 것이다.
- 추상 클래스는 인스턴스, 즉 new 클래스() 와 같은 객체를 만들 수 없다.
- 추상 메소드는 하위 클래스에게 메소드 구현을 강제한다.
- 추상 메소드를 포함하는 클래스는 반드시 추상 클래스여야 한다.
여태껏 앞선 글에서도 그랬듯 클래스의 새로운 인스턴스를 만들 때마다 new 클래스명() 과 같은 키워드를 사용했다.
이때 () 소괄호는 어떤 역할일까? 클래스명도 일종의 메소드이기 때문에 반환값이 없고 클래스명과 같은 이름을 가진 메소드 객체를 생성하는 객체 생성자 메소드다.
안에 따로 인자 값을 입력 해주지 않아도 자바가 알아서 기본 생성자를 만들어 주었기 때문에 따로 입력하지 않아도 그동안 사용할 수 있었다.
public class 캐릭터 {
}
public class Driver {
public static void main(String[] args) {
캐릭터 코난 = new 캐릭터();
}
}
위와 같은 클래스가 있다고 가정하자. 이때 우리는 캐릭터 클래스에 아무 것도 입력하지 않았지만 '캐릭터 코난 = new 캐릭터();' 구문을 통해서 새로운 캐릭터 인스턴스인 코난을 생성할 수 있었다. 이는 다음과 같은 기본 생성자를 자바에서 만들어주었기 때문이다.
public class 캐릭터 {
public 캐릭터() {}
}
만약 필요시에는 인자를 갖는 클래스를 따로 생성해주면 된다.
public class 캐릭터 {
public 캐릭터(String name) {
System.out.println(name);
}
}
public class Driver {
public static void main(String[] args) {
캐릭터 코난 = new 캐릭터("코난");
캐릭터 코난 = new 캐릭터();
}
}
이때는 위에서 String name을 인자로 받는 생성자를 만들어 주었기 때문에 자바가 자동으로 기본 생성자를 만들어주지 않는다.
따라서 기본생성자를 입력해주면 기본 생성자를 비롯해 이름을 인자로 받는 생성자까지 모두 사용할 수 있다.
클래스는 static 영역에 배치될 때 실행되는 static 블록이 있다.
public class 캐릭터 {
static {
System.out.println("캐릭터 클래스 불러오기!"};
}
}
public class 만화세상 {
public static void main(String[] args) {
캐릭터 코난 = new 캐릭터();
System.out.pirntln("만화세상이다!");
}
}
만약 위와 같은 클래스와 메소드가 있다고 가정해보자. 실행을 하게되면 콘솔창에는 어떤 메세지가 출력될까? 결과는
캐릭터 클래스 불러오기!
만화세상이다!
가 될 것이다. 그렇다면 아래와 같다면 어떻게 될까?
public class 만화세상 {
public static void main(String[] args) {
System.out.pirntln("만화세상이다!");
}
}
결과는 아래와 같다.
만화세상이다!
만약 만화세상 클래스에서 캐릭터라는 클래스를 사용하지 않는다면 T메모리의 스태틱 영역에 자리 잡지도 않는다. 따라서 static 블록을 실행하지 않는다.
즉, 해당 패키지 또는 해당 클래스가 처음으로 사용될 때 로딩된다.
또한 여러번 인스턴스를 생성하여도 static 블록은 단 한 번만 실행된다.
static 영역도 메모리이기 때문에 메모리는 최대한 늦게 사용을 시작해서 최대한 빨리 반환하는 것이 정석이다. static 영역은 가비지컬렉터가 관여하지 않기 때문에 사용을 하지 않더라도 프로그램이 종료되기 전까지는 해당 메모리를 반환하지 않는다. 따라서 최대한 늦게 로딩하여 메모리 사용을 최대한 늦추기 위해서다.
** 참고로 인스턴스 생성시마다 실행되는 {} 블록도 존재한다.
final은 말 그대로 최종이라는 뜻으로 클래스, 변수, 메소드 이 3곳에 사용된다.
변수에 final이 붙었다면 변경 불가능한 상수가 된다. 이는 static과 함께 사용하는 final static(정적 상수) 와 final(객체 상수)가 있다. 이때 상수의 '상'은 常(항상 상)의 상이다. 이들은 각각 선언시에 최초 한 번만 초기화가 가능하다.
메소드에 final이 붙었다면 오버라이딩을 금지한다.
-> Cannot override the final method from 'CLASS Method name' 와 같은 오류 메세지를 맞이하게 될 것이다.
instanceof는 만들어진 객체가 특정 클래스의 인스턴스인지 boolean 값으로 반환하는 연산자다. (true 또는 false)
class 동물 {
}
class 조류 extends 동물 {
}
class 펭귄 extends 조류 {
}
public class Test {
public static void main(String[] args) {
동물 동물객체 = new 동물();
조류 조류객체 = new 조류();
펭귄 펭귄객체 = new 펭귄();
System.out.println(동물객체 instanceof 동물);
System.out.println(조류객체 instanceof 동물);
System.out.println(조류객체 instanceof 조류);
System.out.println(펭귄객체 instanceof 동물);
System.out.println(펭귄객체 instanceof 조류);
System.out.println(펭귄객체 instanceof 펭귄);
System.out.println(펭귄객체 instanceof Object);
동물 동물객체 = new 동물();
동물 조류객체 = new 조류();
동물 펭귄객체 = new 펭귄();
System.out.println(동물객체 instanceof 동물);
System.out.println(조류객체 instanceof 동물);
System.out.println(조류객체 instanceof 조류);
System.out.println(펭귄객체 instanceof 동물);
System.out.println(펭귄객체 instanceof 조류);
System.out.println(펭귄객체 instanceof 펭귄);
System.out.println(펭귄객체 instanceof Object);
}
}
위와 같은 코드가 있다고 할 때 결과 값은 모두 true이다.
instanceof 연산자는 클래스들의 상속 관계뿐만 아니라 인터페이스의 구현 관계에서도 동일하게 적용되며 변수 타입이 아닌 실제 객체의 타입에 의해 처리하기 때문이다.
** Object 는 모든 객체들의 최상위다.
interface Speakable {
double PI = 3.14159l
final double absoluteZeroPoint = -275.15;
void sayYes();
}
interface Speakable {
public static final double PI = 3.14159l
public static final double absoluteZeroPoint = -275.15;
public static final void sayYes();
}
사실 위의 두 코드는 동일한 코드다.
인터페이스는 추상 메소드아 정적 상수만 가질 수 있기 때문에 따로 메소드에 public, abstract, static, final을 붙이지 않아도 자동으로 자바다 붙여준다.
class 캐릭터 {
int age = 10;
void test() {
int age = 20;
System.out.println(age);
System.out.println(this.age);
}
}
public class 만화세상 {
public static void main(String[] args) {
캐릭터 코난 = new 캐릭터();
코난.test();
}
}
위의 코드를 실행하게 되면 첫번째의 출력문과 두번째 출력문에서 어떤 값이 출력될까?
답은 20 그리고 10이 출력된다.
왜일까? 첫번째의 출력문에서 age는 해당 메소드 안에있는 가까운 변수를 사용하게 되고, 두번째 출력문의 this.age는 this라는 키워드를 통해 지역 변수가 아닌 객체 변수 age를 사용하게 된다.
- 지역 변수와 속성(객체 변수, 정적 변수)의 이름이 같은 경우 지역 변수가 우선한다.
- 객체 변수와 이름이 같은 지역 변수가 있는 경우 객체 변수를 사용하려면 this를 접두사로 사용한다.
- 정적 변수와 이름이 같은 지역 변수가 있는 경우 정적 변수를 사용하려면 클래스명을 접두사로 사용한다.
class 캐릭터 {
void say() {
System.out.println("캐릭터");
}
}
class 코난 extends 캐릭터 {
void say() {
super.say();
System.out.println("내 이름은 코난.");
}
}
위와 같은 코드가 있다고 할 때 코난 클래스에서는 super라는 키워드를 통해 상위 클래스인 캐릭터의 say() 메소드를 호출할 수 있다.
하지만 super.super.say(); 와 같은 방식의 두 번 이상의 호출은 불가능하다.
출처 : 스프링 입문을 위한 자바 객체지향의 원리와 이해