1. 인터페이스 (Interface)
객체의 사용 방법을 정의한 타입으로 다양한 객체를 동일한 사용 방법으로 이용할 수 있다.
인터페이스는 객체로 생성할 수 없으므로 생성자를 가질 수 없다. 또한 데이터를 저장할 인스턴스 혹은 정적 필드 선언이 불가하며 상수 필드만 선언 가능하다.
인터페이스에서 작성하는 메소드는 실행 블록이 없는 추상 메소드이며, 인터페이스를 구현(implement)한 클래스에서 재정의해서 사용된다.
[public] interface 인터페이스이름 {
[public static final] 타입 상수이름 = 값; //public static final이 생략될 수 있다.
[public abstract] 리턴타입 메소드이름(매개변수, ...) //추상메소드
}
위 예시와 같이 인터페이스는 필드, 매개 변수, 로컬 변수의 타입으로 선언하여 사용이 가능하다.
1.1 구현(implement) 클래스
인터페이스에서 정의한 추상 메소드를 재정의해 실행내용을 가지고 있는 클래스이다. 객체는 다수의 인터페이스 타입으로 사용 가능하다.
public class 구현클래스이름 implements 인터페이스이름1, 인터페이스이름2 {
//인터페이스에 선언된 추상 메소드를 모두 구현해야 함
//하나라도 없으면 abstract 클래스
}
2. 인터페이스의 다형성
자동 타입 변환 (promotion)
구현 객체와 자식 객체는 인터페이스 타입으로 자동 타입 변환 된다. (상속에서의 자동 타입 변환과 동일)
인터페이스 변수 = 구현객체;
실행 클래스에서 6번째 줄을 부면 CarTwo 객체를 생성해 CarTwo 타입인 myCar 변수에 대입했다. 객체 생성 과정에서 HankookTire 4개의 객체가 생성되었으며, 실행 클래스 8번째 줄에서 myCar.run()을 통해 run메서드가 실행되고 HankookTire 객체의 roll 메서드가 실행된다. 실행 클래스 10,11번째 줄에서 CarTwo에 있는 변수들에 새로운 KumhoTire 객체 2개를 생성하여 대입했다. 그결과 실행 클래스 16번째 줄에서 myCar.run()을 통해 run메서드가 실행될 때, KumhoTire의 roll메서드와 HankookTire의 roll이 실행된다.
이러한 결과가 가능한 이유는 CarTwo 클래스의 변수들의 타입이 HankookTire와 KumhoTire의 상위 타입인 Tire이기 때문이다. 같은 패키지 안에 있는 다른 클래스인 MasicTire 객체를 myCar의 한 변수에 대입해보면 에러가 나는 것을 알 수 있다. (실행 클래스 12번째 줄)
강제 타입 변환 (casting)
구현 클래스에만 선언된 필드나 메소드를 사용할 경우 강제 타입 변환을 한다.
구현클래스 변수 = (구현클래스) 인터페이스변수
위 예시를 보면 DriverTwo의 drive 메서드를 통해 강제 타입 변환이 이뤄지고 있다(DriverTwo 클래스 5~8번째 줄). 이와 같이 강제 타입 변환을 진행할 때 에러 발생의 우려가 있으니 instanceof를 이용해 해당 변수가 강제 타입 변환으로 바꿔줄 객체를 가리키고 있는지 확인을 해야한다.
vehicle은 bus와 taxi가 구현한 인터페이스이다(상위 타입). 때문에 Bus로 강제 타입 변환을 할 수 있고 그 결과 bus는 인터페이로부터 Override한 메소드 뿐만 아니라 자신이 만든 checkFare 메소드도 사용할 수 있다. taxi의 경우는 drive 메소드로 인해 자동 타입 변환이 이뤄졌고 그 뒤 강제 타입 변환을 진행하지 않았다. 때문에 vehicle.run을 통해 인터페이스로부터 Override한 taxi의 run 메소드가 호출된다.
3. 인터페이스 상속
인터페이스는 클래스와 다르게 다중 상속을 할 수 있다. 하위 인터페이스를 구현하는 클래스는 하위 인터페이스 뿐만 아니라 상위 인터페이스들의 내용 또한 구현해야 한다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 {...}
ImplementationC는 인터페이스C를 구현한 클래스 인터페이스C가 인터페이스A와 B를 상속받았지 때문에 메소드 A,B,C를 구현했다. 그 결과 imp1은 메소드 A,B,C를 모두 사용할 수 있다.
Example 클래스의 11번째 줄을 보면 imp1를 InterfaceA 타입인 ia에 대입한다. 그 결과 ia는 InterfaceA의 메소드만 사용 가능하다.
4. 중첩 클래스 (nested class)
클래스 내부에 선언한 클래스를 말한다. 두 클래스의 멤버들은 서로 쉽게 접근하게 하고, 외부에는 클래스를 감춤으로 정보 은닉의 효과도 있다.
- 멤버 클래스 : 클래스의 멤버로서 선언되는 중첩 클래스
- 로컬 클래스 : 메소드 내부에서 선언되는 중첩 클래스로 메소드 실행할 때만 사용되어 메소드가 종료되면 사라짐
인스턴스 멤버 클래스 안에서는 정적 필드, 메소드 선언 불가
-> 인스턴스는 객체를 생성해 인스턴스 멤버에 접근 가능. 객체를 이용해 정적 멤버 접근 불가
정적 멤버 클래스 안에서는 인스턴스 필드, 메소드, 정적 필드, 메소드 사용 가능
-> 정적은 클래스 이름으로 정적 멤버에 접근 가능. 또한 정적을 객체로 생성해 인스턴스 멤버에 접근 가능
메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때에 제한이 존재한다. 아래와 같이 매개 변수나 로컬 변수는 final을 선언하지 않아도 final 특성이 부여된다. (java 7은 final을 명시적으로 선언해야 함.)
로컬 클래스의 객체는 메서드 실행이 끝나도 heap에 남아있어 계속 사용될 수 있다. but 매개 변수나 로컬 변수는 메서드의 실행이 끝나면 stack 영역에서 사라지기 때문에 로컬 객체에서 사용할 수 없다. 때문에 로컬 클래스에서 사용될 때는 final 특성이 붙는다.
public class Outter {
public void method1 ( [final] int arg) {
[final] int localVariable = 1;
// arg = 100; 내부에서 변경할 수 없으며 외부에서 받아온 값으로만 사용하기 위함
// localVariable = 100;
class Inner {
public void method2() {
int result = arg + localVariable;
} // method2 end
} // class inner end
} // method1 end
} // class Outter end
메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때에는 제한이 있지만 메소드에서만 사용하면 제한이 생기지 않는 걸 알 수 있다.(7번)
중첩 클래스에서 바깥 클래스 참조를 얻는 방법은 바깥 클래스의 이름을 this 앞에 붙이는 것이다. (아래 사진 19번재 줄)
5. 익명(anonymous) 객체
이름이 없는 객체를 말한다.
일반적인 경우는 부모 타입의 필드나 변수를 선언하고 자식 객체를 초기값으로 대입하는 경우를 말하며 여러번 사용할 때를 말한다.
자식 클래스를 재사용하지 않고 특정 위치에서만 사용하려는 경우(실무에서는 활용X)가 있다. 익명 자식 객체를 생성하여 사용하는 방법, 필드를 선언할 때 초기값으로 익명 자식 객체를 생성하여 대입하는 방법, 메소드 내에서 로컬 변수 선언 시 초기값으로 익명 자식 객체를 생성하여 대입하는 방법(1회성) 등이 있다. 이렇게 생성된 익명 자식 객체에 새롭게 정의된 밀드 및 메소드는 익명 자식 객체 내부에서만 사용할 수 있으며 외부에서는 접근할 수 없다. 사용 예시는 다음과 같다.
필드를 선언할 때 초기값으로 익명 자식 객체를 생성하여 대입하는 방법이다. 실행 클래스에서 Anonymous 객체를 생성한다(6). new 연산자가 Anonymous의 자동 생성자를 호출하여 객체가 만들어지며 그 과정에서 필드 초기값으로 대입되는 익명 객체가 생성된다(6). Person() 객체가 생성되는데 이 객체가 생성될 때 Person으로 부터 재정의한 wake와 익명객체가 생성하는 work 메소드를 포함하여 객체가 생성되고 주소 값을 field에 저장한다(라이브러리 클래스A 5~14). 실행 클래스로 돌아와서 9번 줄과 같이 익명 객체 필드를 사용하여 부모로부터 재정의한 메소드 wake를 사용할 수 있다(9). 하지만 익명 객체가 만든 work는 내부에서만 사용할 수 있다(라이브러리 클래스A 12).
위 설명과 같이 이 익명 객체는 부모로부터 상속받아 재정의한 메소드와 자신이 만든 메소드를 포함한 채로 만들어지는 객체라고 할 수 있다.
6. 익명 구현 객체
구현 클래스가 재사용되지 않고 특정 위치에서만 사용되는 경우에 사용한다. 사용 예시는 익명 객체와 비슷하다.
인터페이스 [필드||변수] = new 인터페이스() {
//인터페이스에 선언된 추상 메소드의 실제 메소드 선언
// 필드
// 메소드
} ;
// 인터페이스 [필드||변수] = new 인터페이스(); 인터페이스는 생성자X
인터페이스는 생성자가 없다. 위와 같은 예시를 보면 new 연산자를 통해 인터페이스의 생성자를 사용한다고 착각할 수 있다. new 인터페이스()는 인터페이스의 생성자가 아니라 인터페이스를 implement하는 익명 객체의 생성자를 통해 객체를 만들고 주소를 가져와 인터페이스 형의 필드||변수에 저장하는 과정이다.
7. 예외(Exception) -> 예외를 잘 다루는게 초급과 중급의 차이라고 하셨다
- NullPointerException
객체 참조가 없는 상태의 참조 변수로 객체 접근 연산자(.)를 사용할 경우 발생한다. 6, 8번째 줄에서 data와 arr이 객체 참조가 없는 상태에서 .toString()을 했기 때문에 발생한다. 이 경우 9, 11번째 줄과 같이 객체를 참조해주면 예외가 발생하지 않는다.
- ArrayIndexOutOfBoundsException
배열에서 인덱스 범위를 초과할 경우 발생한다. 위 예제에서 배열의 크기는 3이지만 for문에서 인덱스 범위를 초과했기 때문에 발생한다. i는 0으로 data1.length는 3으로 0, 1, 2, 3의 경우기 때문에 인덱스 범위를 초과한다.
- NumberFormatException
문자열을 숫자로 변환하는 경우에 숫자로 변환될 수 없는 문자가 포함되었을 때 발생한다. 문자열을 저장하는 data3을 숫자로 변환하기 위해 parseInt를 사용했지만 a는 숫자로 변환할 수 없기 때문에 예외가 발생한다.
8. 예외 처리 (try-catch-finally)
try 블록에는 예외 발생 가능 코드가 위치하고 예외가 발생하면 catch가 실행된다. finally는 예외가 발생하든 발생하지 않든 항상 실행이 되며 생략 가능하당. 예외 발생 없이 정상실행되면 catch 블록은 실행되지 않는다.
매개값으로 "가", "나"를 줬을 때 두 번째 try-catch 구문에서 catch가 실행되어 "숫자로 변환할 수 없습니다."가 출력됩니다.
위와 같이 예외가 여러가지 발생할 것이라 예상되면 다중 catch를 작성할 수 있고 이 때, 상위 예외 클래스가 하위 예외 클래스보다 아래 위치해야 한다.
예외 처리의 가장 상위에 있는 Exception은 맨 아래에 위치하여 프로그래머가 예상하지 못한 예외를 처리해야 한다.
8.1 예외 떠넘기기 (throws)
메소드 선언부 끝에 작성하여 메소드에서 처리하지 않은 예외를 호출한 곳으로 넘기고 자신은 정상 종료한다.
'Develop' 카테고리의 다른 글
[Concept] JAVA (입출력 스트림, OutputStream, InputStream, Writer, Reader, 보조 스트림, 입출력 관련 API) (0) | 2022.07.20 |
---|---|
[Concept] JAVA (제네릭스, 컬렉션 프레임워크, Set, Map, Stack, Queue) (0) | 2022.07.04 |
[Concept] JAVA (java.lang 패키지, java.system 패키지, 동기화, 스레드) (0) | 2022.06.22 |
[Concept] JAVA (객체, 싱글톤, 상속, 다형성, 생성자, Getter/Setter) (0) | 2022.06.13 |
[Concept] JAVA (데이터 타입, 타입 변환, 배열, for) (0) | 2022.06.01 |