A클래스의 메소드안에 있는 지역클래스를 B클래스에서 쓰려면 별도의 인터페이스를 통해서 A와 B가 서로 연결될 수 있게 인터페이스를 통로로 쓰면됩니다.
지역 클래스의 메소드들을 외부에서 사용하는 방법
- 지역클래스의 메소드들을 선언한 인터페이스 정의
- 지역클래스가 인터페이스를 구현하도록 정의
- 다형성을 사용해 메소드의 리턴타입으로 인터페이스를 사용
앞에서는 인스턴스를 생성할 때 타입과 생성자가 다른 경우를 다형성이라고 배웠는데요
또다른 형태로 리턴타입으로 인터페이스 타입을 지정한 메소드에 인터페이스를 구현한 지역클래스의 인스턴스가 리턴될 수 있는 것도 다형성이라고 합니다!
interface PersonInterface{
public abstract void showInfo();
public abstract void hello();
}
class Person{
//멤버변수
private String name;
//생성자
public Person(String name) {
this.name=name;
}
//메소드
public PersonInterface setAge(int age) {
//지역클래스
class PersonWithAge implements PersonInterface{
private int age; //지역클래스 멤버변수
public PersonWithAge(int age) {
this.age=age;
}
@Override
public void showInfo() {
System.out.println("이름:"+name);
System.out.println("나이:"+age);
}
@Override
public void hello() {
System.out.println("안녕하세요");
}
}//end PersonWithAge
//지역 클래스 객체를 모두 저장함
PersonWithAge instance=new PersonWithAge(age);
//지역클래스 객체를 리턴
return instance;
}//end setAge()
}//end Person
public class InnerClass {
public static void main(String[] args) {
Person p=new Person("이름");
PersonInterface instance=p.setAge(25);
instance.showInfo();
instance.hello();
}
}
지역클래스가 인터페이스를 implements하면 인터페이스랑 지역클래스랑 관계가 생기는데요 그래서 메소드에서 생성한 지역클래스의 객체를 메소드에서 리턴할 수 있어요.
그러면 리턴된 객체는 곧 지역클래스의 객체지만 인터페이스를 구현한 덕분에 외부클래스를 다른 클래스가 참조할 경우 우회적으로 지역클래스의 내부에도 접근할 수 있답니다.
지금까지 클래스를 구현하려면 class A{}를 생성한 다음에 Main에서 사용했는데요. 익명클래스는 클래스를 따로 만들지 않고 바로 구현을 할 수 있는 클래스를 의미합니다.
인터페이스로 정의된 메소드를 사용하고 객체를 한 번만 생성한다면 익명클래스를 사용하는걸 추천합니다! 원래 객체 생성은 클래스만 가능합니다만 인터페이스를 생성자 위치에 쓰고 바로 객체를 생성할 수 있어요
interface Hello{
public abstract void hello();
}//end Hello
<Main클래스>
Hello cat=new Hello() {
@Override
public void hello() {
system.out.println("야옹");
}
};
익명클래스 안의 공간은 인터페이스의 형태를 따라가요. 인터페이스 안에서 만든 상수나 메소드를 사용할 수 있어요. 익명클래스안에서 별도의 변수나 메소드를 생성해도 오류는 안나지만 보통은 추상 메소드를 구현하기위해서 쓴답니다.
new 인터페이스(){ 인터페이스를 구현한 익명클래스 내용 };
익명클래스를 사용하는 이유
- 인터페이스의 메소드가 한개인 경우 클래스를 implements하지 않고 바로 사용하기 위해
- 클래스를 외부에서 생성하지않아도 된다.
- 객체를 한 번만 생성하고 더 이상 생성하지 않을 경우 사용
클래스 안에 클래스를 만들 수 있다면 클래스 안에 인터페이스도 만들 수 있어요.
인터페이스는 외부의 기능끼리의 소통을 담당하는데요 만약 확인버튼과 취소버튼을 만든다면, 확인과 취소를 각각 인식하는 기능을 만들어야 할까요? 가능하지만 할 일이 많아집니다. 그래서 클릭을 하고 나서야 [확인/취소]를 구분하는 방법을 사용하는데요. 이 때 클릭하는 것을 내부 인터페이스로 구현해요!
public class Button {
//내부 인터페이스
public interface OnClickListener{
public abstract void onClick();
}
//멤버변수
private OnClickListener listener;
//생성자
public void setOnClickListener(OnClickListener listener) {
this.listener=listener;
}
//메소드
public void click() {
listener.onClick();
}
}//end Button
class OkListener implements Button.OnClickListener{
@Override
public void onClick() {
System.out.println("<<<확인>>>");
}
}//end OkListener
class CancelListener implements Button.OnClickListener{
@Override
public void onClick() {
System.out.println("<<<취소>>>");
}
}//end CancelListener
사용자가 취소든 확인이든 어떤 버튼을 클릭한다면 발생하는 클릭 이벤트를 내부 인터페이스가 인식합니다.
Button btnCancel=new Button();
btnCancel.setOnClickListener(new CancelListener());
btnCancel.click(); //<<<취소>>>
내부 인터페이스도 익명클래스의 인터페이스처럼 쓸 수 있어요.
Button btn=new Button();
Button.OnClickListener l=new Button.OnClickListener() {
@Override
public void onClick() {
System.out.println("테스트 클릭");
}
};
btn.setOnClickListener(l);
btn.click(); //테스트 클릭
//같은 방식 new를 어디서 만드느냐 차이
btn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick() {
System.out.println("테스트 클릭2");
}
});
btn.click(); //테스트 클릭2
@FunctionalInterface : 추상메소드가 하나뿐인 인터페이스
람다 표현식은 익명클래스를 사용하는 것처럼 별도의 인터페이스를 구현한 클래스를 외부에 만들지 않고 바로 클래스안에서 인터페이스를 구현할 수있는 방법인데요. 추상메소드가 하나뿐인 인터페이스만 사용할 수 있어요!
인터페이스타입 변수이름 = (매개변수1,2,...) -> { 본문; };
(매개변수) -> 리턴값; < 으로도 쓸 수 있어요
@FunctionalInterface
interface Test1{
public abstract void printMax(int x, int y);
}//end Test1
@FunctionalInterface
interface Test2{
public abstract int findMax(int x, int y);
}//end Test2
public static void main(String[] args) {
Test1 test1=(x,y)->{
int max;
if(x>y) {
max=x;
}else {
max=y;
}
System.out.println("최대값:"+max);
};
test1.printMax(100, 101);
Test2 test2 = (x,y)-> ( x > y ) ? x : y; //삼항연산자
System.out.println("최대값 : " + test2.findMax(10, 5));
}//end main()
컴파일 에러의 경우 컴파일이 아예 되지 않기때문에 오류를 발견하기가 쉬워요 그러나 예외의 경우 프로그램이 실행된 다음에야 알기 때문에 예외처리가 필요합니다.
특정 오류 상황마다 if문으로 예외처리를 해줄수도 있지만 어느정도 예외가 발생할 것 같은 상황을 포괄적으로 묶어 Exception이 발생하면 똑같은 예외처리가 실행되도록 만들 수 있습니다.
try{
본문;
}catch(Exception e){
예외처리;
}
try 안에서 예외가 발생하면 프로그램이 비정상적으로 종료되는게 아니라 catch에서 예외처리 코드를 실행합니다. try안에 예외가 여러개 있어도 소스코드 읽는 순서에 따라 먼저 나오는 에러 하나만 인식해요
Exception 클래스 - 예외처리를 위한 최상위 클래스
- NullPointerException : null값 예외
- ArithmeticException : 산술연산 예외
- ArrayIndexOutOfBoundsException : 배열인덱스 예외
... 기타 등등 엄청 많아요!
이런 catch를 여러 개 만들 수도 있는데요 하나의 try에서 여러개의 catch를 사용할 땐 자식 클래스의 Exception를 먼저 쓰고 가장 마지막에 최상위 부모클래스인 Exception catch를 작성해야합니다.
try {
int x=12345;
int y=1;
int result=x/y;
System.out.println("result="+result);
int[] array=new int[10];
array[11]=10;
System.out.println("array[11]:"+array[11]);
String name=null;
System.out.println("문자열길이:"+name.length());
} catch (ArithmeticException e) {
System.out.println("산술연산 예외:"+e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("배열인덱스 예외:"+e.getMessage());
} catch (Exception e) { //최상위 Exception
System.out.println("예외:"+e.getMessage());
}
자바 7버전부터 적용된 catch 구현 방법으로 catch를 하나씩 작성하지 않고도 한줄로 끝낼 수 있어요~
try{
정상일 때 실행할 코드;
} catch(Ex1 | Ex2 | ... e) {
예외 상황에서 실행할 코드;
} catch(Exception e) {
...
}
finally은 catch와 상관없이 try 후 항상 실행됩니다.
try {
정상적인 경우 실행하는 코드;
} catch (Exception e) {
예외상황 시 실행할 코드;
} finally {
정상적인 경우든 예외상황이든 상관없이 항상 실행하는 코드;
}
보통 데이터를 입력하는 방식에서 에러가 많이 발생하는데요 에러는 발생할수있는 경우의 수가 매우 많아서 개발자가 미처 고려하지못한 에러가 있을 수도 있어요
숫자를 사용자한테 입력받을 때 보통 숫자보다 문자형 숫자로 넘어오는 경우가 더 많은데요. 숫자 1과 문자 1은 다른거 아시죠? 우리는 숫자가 필요하기 때문에 Integer.ParseInt로 변환해줘야합니다. 그러나 만약 문자형 숫자도 아닌 진짜 String이 들어온다면 예외가 발생해요! 그때 예외처리로 NumberFormatException를 사용합니다.
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while(true) {
try {
System.out.println("메뉴선택>");
int choice=Integer.parseInt(sc.nextLine());
System.out.println("choice:"+choice);
} catch (NumberFormatException e) {
System.out.println("숫자가 아닙니다");
}
}
}