내부 클래스(Inner Class)
- 하나의 클래스 내부에 선언된 또 다른 클래스를 의미한다.
- 내부 클래스는 클래스 내에 선언되어 사용되며, 내부에 정의된다는 점을 제외하고는 일반적인 클래스와 다르지 않다.
- 보통 두 클래스가 서로 긴밀한 관계가 있거나, 하나의 클래스 또는 메소드에서만 사용되는 클래스일 때 이용되는 기법이다.
예시// Creature 클래스는 내부 클래스들의 외부 클래스 class Creature { int life; // Animal 클래스는 Creature 클래스의 내부 클래스 class Animal { } // Insect 클래스는 Creature 클래스의 내부 클래스 class Insect { } public void method() { Animal animal = new Animal(); Insect insect = new Insect(); // ... } }
내부 클래스의 장점
클래스를 논리적으로 그룹화
- 내부 클래스와 외부 클래스를 함께 관리하는 것이 가능해 유지보수 면에서나 코드 이해성 면에서 편리해진다.
- 내부 클래스로 인해 새로운 클래스를 생성하지 않아도 되므로 패키지를 간소화할 수 있다.
더욱 타이트한 캡슐화의 적용
- 내부 클래스에
private
제어자를 적용해줌으로써, 캡슐화를 통해 클래스를 내부로 숨길 수 있다.- 외부에서의 접근을 차단하면서도, 내부 클래스에서 외부 클래스의 멤버들을 제약 없이 쉽게 접근할 수 있어 구조적인 프로그래밍이 가능해 진다. 그리고 클래스 구조를 숨김으로써 코드의 복잡성도 줄일 수 있다.
가독성이 좋고 유지 관리가 쉬운 코드
- 시각적으로 읽기가 편해질 뿐 아니라 유지보수에 있어 이점을 가지게 된다.
- 한 클래스를 다른 클래스의 내부 클래스로 선언하면 두 클래스 멤버들 간에 서로 자유로이 접근할 수 있고, 그리고 외부에는 불필요한 클래스를 감춰서 클래스간의 연관 관계 따지는 것과 같은 코드의 복잡성을 줄일 수 있다는 장점이 있다.
종류
- 클래스의 멤버 변수 선언부에 위치하고 static 키워드가 없는 내부 클래스
- 외부 클래스의 멤버로 취급되기 때문에 외부 클래스의 객체 먼저 생성한 후 내부 클래스의 객체를 생성이 가능하다
- 인스턴스 클래스 내부에는
instance
멤버만 선언할 수 있다. (static 멤버는 선언 불가)- 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다.
사용 예시class PocketBall { // 인스턴스 변수 int size = 100; int price = 5000; // 인스턴스 내부 클래스 class PocketMonster { String name = "이상해씨"; int level = 10; // static int cost = 100; - 에러! 인스턴스 내부 클래스에는 static 변수를 선언할 수 없다. static final int cost = 100; // final static은 상수이므로 허용 public void getPoketMember() { // 별다른 조치없이 외부 클래스 맴버 접근 가능 System.out.println(size); System.out.println(price); // 내부 클래스 멤버 System.out.println(name); System.out.println(level); System.out.println(cost); } } } -------------------------------------------------------------------------------------------- public class Main { public static void main(String[] args) { // 먼저 외부 클래스를 인스턴스화 해주고 PocketBall ball = new PocketBall(); // 외부클래스.내부클래스 형식으로 내부클래스를 초기화 하여 사용할 수도 있다 PocketBall.PocketMonster poketmon = ball.new PocketMonster(); poketmon.getPoketMember(); // 위의 단계를 한줄로 표현 PocketBall.PocketMonster poketmon2 = new PocketBall().new PocketMonster(); } }
static
키워드가 붙은 내부 클래스- 단, 일반적인 static 필드 변수나 static 메서드와 똑같이 생각하면 안된다.
- static 클래스 내부에는 instance 멤버와 static 멤버 모두 선언 할 수 있다.
- 그러나 일반적인 static 메서드와 동일하게 외부 클래스의 인스턴스 멤버에는 접근이 불가하고, 정적(static) 멤버에만 접근할 수 있다.
사용 예시class PocketBall { int size = 100; static int price = 5000; // static 내부 클래스 static class PocketMonster { static String name = "이상해씨"; int level = 10; public static void getPoketMember() { // 외부 클래스 인스턴스 맴버 접근 불가능 // System.out.println(size); // 외부 클래스 스태틱 멤버 접근 가능 System.out.println(price); // 내부 클래스 멤버도 스태틱 맴버만 접근 가능 System.out.println(name); // System.out.println(level); } } } -------------------------------------------------------------------------------------------- public class Main { public static void main(String[] args) { // 스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다. PocketBall.PocketMonster poketmon = new PocketBall.PocketMonster(); System.out.println(poketmon.level); System.out.println(PocketBall.PocketMonster.name); // 클래스.정적내부클래스.정적메소드() PocketBall.PocketMonster.getPoketMember(); } }
- 내부 인스턴스 클래스처럼 외부 인스턴스를 먼저 선언하고 초기화해야 하는 선수 작업 필요 없이, 내부 클래스의 인스턴스를 바로 생성할 수 있다는 차이점이 존재한다.
- 내부 클래스는
static
클래스로 선언하자- 만일 내부 클래스를 이용하는데, 내부 클래스에서 바깥 외부의 인스턴스를 사용할 일이 없다면 static 클래스로 선언해주어야 한다.
- 메소드 내부에 위치하는 클래스 (지역 변수와 같은 성질을 지님)
- 지역 변수처럼 해당 메서드 내부에서만 한정적으로 사용된다. (해당 메소드 실행 외에는 클래스 접근 및 사용 불가)
접근제한자
와static
을 붙일 수 없다.- 메소드 내부에서만 사용되므로 접근을 제한할 필요가 없고, 원래 메소드 내에는 static 멤버를 선언할 수 없기 때문이다.
사용 예시class PocketBall { int size = 100; int price = 5000; public void pocketMethod() { int exp = 5000; // 메소드 내에서 클래스를 선언 class PocketMonster { String name = "이상해씨"; int level = 10; public void getPoketLevel() { System.out.println(level); // 인스턴스 변수 출력 System.out.println(exp); // 메소드 지역 상수 출력 } } // 메소드 내에서 클래스를 선언 class PocketMonster2 { String name = "리자몽"; int level = 50; } new PocketMonster().getPoketLevel(); System.out.println("메소드 실행 완료"); } }
- 로컬 클래스 지역 상수 접근
- 메소드 내의 로컬 클래스에서 지역 변수에 접근해서 값을 사용하려고 할 때 반드시
final
상수화 된 지역 변수만 사용이 가능하다.
- 익명 클래스는 클래스 이름이 존재하지 않는 이너 클래스다.
(자바스크립트의 익명 함수로 생각해도 된다)- 단 하나의 객체만을 생성하는 일회용 클래스
- 클래스의 선언과 동시에 객체를 생성
- 익명 클래스는 생성자가 존재하지 않는다.
- 익명 클래스는 기존에 존재하는 클래스를 메서드 내에서 일회용으로 클래스 내부 구성을 선언하여 필요한 메서드를 재정의 하여 사용하는 기법이다.
사용 예시public class Main { public static void main(String[] args) { // Object 클래스를 일회성으로 익명 클래스로 선언하여 변수 o 에 저장 Object o = new Object() { @Override public String toString() { return "내 마음대로 toString 바꾸기 ~"; } }; // 익명 클래스의 객체의 오버라이딩한 메서드를 사용 String txt = o.toString(); System.out.println(txt); // 내 마음대로 toString 바꾸기 ~ } }
- 인터페이스를 작성하는 것은 추상 클래스를 작성하는 것과 같다고 보면 된다. (추상 메서드 집합)
- 인터페이스도 필드를 선언할 수 있지만 변수가 아닌
상수(final)
로서만 정의할 수 있다.public static final
과public abstract
제어자는 생략이 가능하다.
인터페이스에 정의된 모든 멤버에 적용되는 사항이기 때문에 편의상 생략 가능하게 지원하는 것이다.- 생략된 제어자는 컴파일 시에 컴파일러가 자동으로 추가해 준다.
interface 인터페이스 이름{ public static final 타입 상수이름 = 값; public abstract 타입 메서드이름(매개변수목록); }
사용 예시package Example; public interface Animal { public static final String name = "동물"; public abstract void move(); public abstract void eat(); public abstract void bark(); } --------------------------------------------------------------------------- package Example; public class Dog implements Animal{ @Override public void move() { System.out.println("슥슥~~"); } @Override public void bark() { System.out.println("멍멍!"); } } --------------------------------------------------------------------------- package Example; public class Cat implements Animal{ @Override public void move() { System.out.println("사뿐사뿐~~"); } @Override public void bark() { System.out.println("야옹~~"); } } --------------------------------------------------------------------------- package Example; public class Main { public static void main(String[] args) { Dog dog = new Dog(); Cat cat = new Cat(); dog.move(); dog.bark(); cat.move(); cat.bark(); } }
- 다중 상속 가능
- 인터페이스는 껍데기만 존재하여 클래스 상속 시 발생했던 모호함이 없기에 다중 상속이 가능하다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2, ... { ... }
[ 인터페이스의 extends는 상속이 아니다 ]
인터페이스는 하나의 타입이나 규격일 뿐이지 그 자체가 하나의 객체가 되는 것이 아니다.
따라서 엄밀히 말하자면, 인터페이스의 상속은 클래스의 상속처럼 부모의 속성과 동작을 물려받는 것이 아니다.
정확히 말하면, 인터페이스의 상속은 규격이나 스펙 자체 혹은 기능 자체의 선언을 물려받은 것이다.
규격이나 스펙을 물려받아서 새로운 스펙을 만든다면 기존 여러 개의 스펙을 조합해서 하나로 묶거나 기존의 스펙을 고스란히 물려받은 후에 다시 추가적인 기능을 가지게 하는 것이다.
- 추상 메서드와 상수만 사용 가능
- 인터페이스에는 구현 소스를 생성할 수 없다. 상수와 추상 메소드만 가질 수 있다.
- 생성자 사용 불가
- 인터페이스 객체가 아니므로 생성자를 사용할 수 없다.
- 메서드 오버라이딩 필수
- 자식클래스는 부모 인터페이스의 추상 메소드를 모두 오버라이딩해야 한다.
- 부모클래스 타입으로 자식 클래스 타입을 포함 시킬수 있다는 다형성의 법칙도 인터페이스에 그대로 적용이 가능하다.
다형성(polymorphism)이란 같은 자료형에 여러가지 타입의 데이터를 대입하여 다양한 결과를 얻어낼 수 있는 성질을 의미한다. 자바에선 대표적으로 오버로딩, 오버라이딩, 업캐스팅, 다운캐스팅, 인터페이스, 추상메소드, 추상클래스 방법이 모두 다형성에 속하다고 생각하면 된다. 즉, 다형성은 클래스가 상속 관계에 있을때 나타나는 다채로운 성질 인 것이다.
- 클래스가 여러 개의 인터페이스를 구현하게 되면, 결과적으로 변수의 타입으로도 다양하게 쓰일 수 있다는 것을 의미하게 된다. 인터페이스 타입으로 변수를 선언하게 되면 사용하는 입장에서는 뒤에 오는 모든 객체는 간단히 인터페이스만 구현한 객체이면되기 때문에 좀 더 시스템이 유연해지는 계기를 마련하게 된다.
예 시interface Keyboard { } class Logitec_Keyboard implements Keyboard { } class Samsung_Keyboard implements Keyboard { } class Apple_Keyboard implements Keyboard { } public class Main { public static void main(String[] args) { // 인터페이스 타입 배열로 여러가지 클래스들을 한번에 타입 묶음을 할 수 있다. Keyboard[] k = { new Logitec_Keyboard(), new Samsung_Keyboard(), new Apple_Keyboard(), }; } }
- 인터페이스의 다형성의 가장 큰 특징은 바로 다중 구현을 통한 자유로운 상속 관계를 만들어 클래스의 다형성보다 더욱 다채롭게 그리고 자유롭게 사용이 가능하다는 것이다.
- 형제 관계
인터페이스는 아무 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어줄 수 있다. (일부만 묶고 싶은 클래스들을implements
키워드로 등록시키면, 각기 다른 부모클래스를 상속하고 있는 자식 클래스에 인터페이스를 구현(상속) 시켜줌)
- 타입, 메소드 접근 제한
이외에도 인터페이스는 일종의 접근 제한 역할도 할 수 있다.
만일 똑같은 부모를 상속하고 있는 3개의 자식들중, 2개의 자식 클래스 타입만 받을 수 있는 메서드를 구현한다고 했을때 이용된다.
예 시interface Machine { } // SCV, Tank 클래스를 통합한 타입으로 이용하는 인터페이스 class GroundUnit { } class Marine extends GroundUnit{ } class SCV extends GroundUnit implements Machine{ } class Tank extends GroundUnit implements Machine{ } public class Main { public static void main(String[] args) { repair(new Marine()); // ! ERROR } static void repair(Machine gu) { // SVG와 탱크 타입만 받을 수 있게 인터페이스를 타입으로 하여 다형성을 적용 } }
인터페이스 vs 추상클래스 비교
인터페이스 정리
- 내부의 모든 메서드는
public abstract
로 정의 (default 메소드 제외)- 내부의 모든 필드는
public static final
상수- 클래스에 다중 구현 지원.
- 인터페이스 끼리는 다중 상속 지원.
- 인터페이스에도
static
,default
,private
제어자를 붙여 클래스 같이 구체적인 메서드를 가질 수 있음.- 인터페이스는 부모 자식 관계인 상속에 얽매이지 않고, 공통 기능이 필요할 때마다 추상 메서드를 정의해놓고
구현(implement)
하는 식으로 추상클래스보다 자유롭게 붙였다 땟다 사용- 다중 구현이 된다는 점을 이용해, 내부 멤버가 없는 빈 껍데기 인터페이스를 선언하여 마커 인터페이스 로서 이용 가능
- 보통
xxxable
이런 형식으로 인터페이스 네이밍 규칙을 따름
추상 클래스 정리
- 추상클래스는 하위 클래스들의 공통점들을 모아 추상화하여 만든 클래스
- 추상클래스는 다중 상속이 불가능하여 단일 상속만 허용한다.
- 추상클래스는 추상 메소드 외에 일반클래스와 같이 일반적인 필드, 메서드, 생성자를 가질 수 있다.
- 이러한 특징으로, 추상클래스는 추상화(추상 메서드)를 하면서 중복되는 클래스 멤버들을 통합 및 확장을 할 수 있다.
- 같은 추상화인 인터페이스와 다른점은, 추상클래스는 클래스간의 연관 관계를 구축하는 것에 초점을 둔다.
람다식(Lambda Expression)이란 함수를 하나의 식(expression)으로 표현한 것이다. 함수를 람다식으로 표현하면 메소드의 이름이 필요 없기 때문에, 람다식은 익명 함수(Anonymous Function)의 한 종류라고 볼 수 있다.
// 기존의 방식 반환티입 메소드명 (매개변수, ...) { 실행문 } // 람다 방식 (매개변수, ... ) -> { 실행문 ... }
사용 예시
package test.mypac; // 추상 메소드를 1개만 만들도록 강제하는 역할(()->{} 형태로 사용할 수 있도록 보장) @FunctionalInterface public interface Calc { // 메소드로 두 개의 실수를 전달받아서 어떤 연산을 하고 결과 값을 리턴해주는 메소드 // 어떤 연산을 할 지는 개발자가 알아서 정하기 public double execute(double num1, double num2); } -------------------------------------------------------------------------------------------- public class MainClass05 { public static void main(String[] args) { Calc add1= new Calc() { @Override public double execute(double num1, double num2) { return num1 + num2; } }; Calc add2 = (num1, num2) -> { return num1 + num2; }; Calc add = (num1, num2) -> num1 + num2; Calc sub = (num1, num2) -> num1 - num2; Calc mul = (num1, num2) -> num1 * num2; double result1 = add.execute(10, 20); double result2 = sub.execute(10, 20); double result3 = mul.execute(10, 20); } }