자바의 인터페이스에 대해서 알아보자.
인터페이스는 어떤 기능의 메소드를 구현해야하는지 미리 알려주면서 이 메소드를 구현하자고 약속하는 명세서의 역할을 한다.
클래스는 imolement라는 예약어를 통해 인터페이스를 구현하고
실제 사용시에는 클래스로 생성, 인터페이스 참조변수에 대입해서 다형성을 구현한다.
즉 "밥먹는 방법"이라는 인터페이스에 "밥먹기" 추상메소드를 구현,
강아지 클래스, 고양이 클래스, 인간 클래스에서 "밥먹기"메소드를 구현
각 클래스 생성 후 인터페이스 참조변수에 대입후 "밥먹기"코드를 실행하면
겉보기에는 다 같은 코드지만 다 다른 구현을 한다는 다형성을 구현할 수 있는 것이다.
인터페이스에서도 defult메소드, static메소드를 가질 수 있는데 각 예약어의 뜻을 생각해서 어떤 역할을 할지 생각해보자.
default
예약어왠지 default
예약어가 굉장히 낯설게 느껴질텐데 그런 이유가 있다!!
바로 접근제어자를 별도로 설정하지 않았을 때 자동으로 적용되는 예약어로 직접 쓸 일이 지금까지는 없었기 때문이다.
하지만 이번 인터페이스에서 default
예약어를 쓰게 되는데, 이는 동일한 패키지안에서 접근이 가능하도록 하는 접근제어자임을 알고 가자. 다른 패키지에서는 사용할 수 없다.
접근 제어자 | 같은 클래스의 멤버 | 같은 패키지의 멤버 | 자식 클래스의 멤버 | 그 외의 영역 |
---|---|---|---|---|
public | ○ | ○ | ○ | ○ |
protected | ○ | ○ | ○ | X |
default | ○ | ○ | X | X |
private | ○ | X | X | X |
interface
예약어를 사용한다.✍️ 사용방법
implements
예약어를 통해 추상메소드를 각각 구현한다.interface Calc {
//상수
//public static final예약어가 자동 컴파일 (인스턴스를 생성하지 않고도 접근할 수 있다.)
double PI = 3.14;
int ERROR = -9999999;
//클래스에서 구현해야하는 추상 메소드
int add (int num1,int num2);
int substract (int num1, int num2);
int times (int num1, int num2);
int divide(int num1,int num2);
}
public class CalculatorTest {
public static void main(String[] args) {
//3같은 패키지에 있으므로 인터페이스명.상수로 접근 가능, 다른 패키지면 import해야함.
System.out.println(Calc.PI); //3.14
implement
예약어를 사용한다.public class CompleteCalc implements Calc{
//인터페이스의 추상메소드 모두 구현
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int substract(int num1, int num2) {
return num1 - num2;
}
@Override
public int times(int num1, int num2) {
return num1 * num2;
}
@Override
public int divide(int num1, int num2) {
if (num2 != 0) {
return num1 / num2;
} else {
return Calc.ERROR;
}
}
}
✍️ 예시코드
클래스에서 showInfo()
라는 메소드 추가 구현
public class CompleteCalc implements Calc{
...
public void showInfo(){
System.out.println("CompleteCalc 구현");
}
}
public class CalculatorTest {
public static void main(String[] args) {
int num1 = 10;
int num2 = 5;
Calc calc1 = new CompleteCalc();
CompleteCalc calc2 = new CompleteCalc();
// calc1.showInfo(); // 실행오류
calc2.showInfo(); // 실행가능 CompleteCalc 구현
//상수는 인터페이스로 접근 가능.
System.out.println(Calc.PI);
System.out.println(calc1.add(num1,num2));
System.out.println(calc1.substract(num1,num2));
System.out.println(calc1.times(num1,num2));
System.out.println(calc1.divide(num1,num2));
}
}
👉 실행화면
CompleteCalc 구현
3.14
15
5
50
2
public abstract class Calculator implements Calc {
//전부 구현하지 않았기 때문에 추상메소드로 존재
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int substract(int num1, int num2) {
return num1 - num2;
}
}
한 인터페이스에 A라는 메소드가 있다고 생각해보자.
이 A라는 메소드는 모든 클래스에서 동일하게 작용한다. 그러면 클래스마다 같은 메소드를 여러번 작성해야해서 생산성이 떨어진다.
이때 디폴트 메소드를 사용하면 클래스마다 공통적인 메소드를 인터페이스에서 구현할 수 있다.
default
예약어를 통해 구현한 메소드default메소드
또한 구현되어있다고 사용할 수 없고, 클래스를 생성하고 나서 사용할 수 있다. interface interfaceEx {
default void defaultMethod(){
System.out.println("defaultMethod 실행");
}
}
class classImplement implements interfaceEx{
}
public class InterfaceEX1 {
public static void main(String[] args) {
classImplement classImplement = new classImplement();
classImplement.defaultMethod(); //defaultMethod 실행
}
}
상위클래스를 상속하고 상위메소드를 오버라이딩하여 재정의하였을 때, 상위 메소드를 호출할 일이 생긴다면 super
키워드를 통해 부모 메소드를 호출 할 수 있었는데,
인터페이스의 디폴트 메소드 오버라이딩 역시 super
예약어를 사용할 수 있다.
다만 인터페이스명.super.메소드명
으로 문법이 다르다.
인터페이스의 디폴트 메소드 오버라이딩
interface interfaceEx {
default void defaultMethod(){
System.out.println("인터페이스 디폴트 메소드 실행");
}
}
class classImplement implements interfaceEx{
@Override
public void defaultMethod() {
System.out.println("이 디폴트 메소드는 구현클래스에서 재정의되었습니다.");
interfaceEx.super.defaultMethod(); //인터페이스명.super.메소드명
}
}
public class InterfaceEX1 {
public static void main(String[] args) {
classImplement classImplement = new classImplement();
classImplement.defaultMethod();
}
}
👉 실행화면
이 디폴트 메소드는 구현클래스에서 재정의되었습니다.
인터페이스 디폴트 메소드 실행
일반 클래스에서 메소드 오버라이딩
class superClass{
void methodTest () {
System.out.println("상위클래스메소드 실행");
}
}
class subclass extends superClass{
@Override
void methodTest() {
System.out.println("이 메소드는 하위클래스에서 재정의 되었습니다.");
super.methodTest();
}
}
public class superTest {
public static void main(String[] args) {
subclass subclass = new subclass();
subclass.methodTest();
}
}
👉 실행화면
이 메소드는 하위클래스에서 재정의되었습니다.
상위클래스메소드 실행
앞에서 인터페이스의 상수는 static 상수로 인터페이스명으로 접근할 수 있는 것을 배웠다.
그렇다면 메소드에 static을 붙여 클래스를 생성하지 않고도 인터페이스로 접근가능한 static메소드를 만들어보자.
static
를 사용해 정적 메소드를 만든다.interface interfaceEx {
static void staticMethod() {
System.out.println("staticMethod 실행");
}
}
class classImplement implements interfaceEx{
}
public class InterfaceEX1 {
public static void main(String[] args) {
//인스턴스를 생성하지 않고 인터페이스명으로 메소드호출
interfaceEx.staticMethod(); //staticMethod 실행
}
}
private 메소드는 해당 클래스에서만 사용이 가능한 메소드이다.
즉 인터페이스내에서만 사용이 가능하도록 하는 메소드이다.
또한 접근이 불가능하기 때문에 구현클래스에서 재정의할 수 없다.
private 메소드
→ default
메소드 내부에서 사용 가능private static메소드
→ static
메소드에서만 사용 가능하다. interface interfaceEx {
default void defaultMethod(){
System.out.println("인터페이스 디폴트 메소드 실행");
prevateMethod();
}
private void prevateMethod(){
System.out.println("private 메소드");
}
static void staticMethod() {
System.out.println("인터페이스 명으로 사용가능한 static메소드 실행");
privateStaticMethod();
}
private static void privateStaticMethod(){
System.out.println("static메소드에서 사용가능한 privateStatic 실행");
}
}
class classImplement implements interfaceEx{
}
public class InterfaceEX1 {
public static void main(String[] args) {
classImplement classImplement = new classImplement();
classImplement.defaultMethod();
//인터페이스 디폴트 메소드 실행
//private 메소드
interfaceEx.staticMethod();
//인터페이스 명으로 사용가능한 static메소드 실행
//static메소드에서 사용가능한 privateStatic 실행
}
}
import java.util.Scanner;
interface Animal {
void cry();
}
class Cat implements Animal{
@Override
public void cry() {
System.out.println("냐옹");
}
}
class Dog implements Animal{
@Override
public void cry() {
System.out.println("멍멍");
}
}
public class interfacePolymorphism {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Animal animal = null;
System.out.println("멍멍이(1) 고양이(2)");
switch (sc.nextInt()){
case 1 : {
animal = new Dog();
break;
}
case 2 : {
animal = new Cat();
break;
}
default: break;
}
animal.cry(); //사용자의 입력에 따라 냥냥인지 멍멍인지 결정된다.
}
}
extends
예약어 사용단일 상속의 경우 꼭 써야하나🤔 라는 생각이 들 것 같다. 하지만 여러 인터페이스를 다중상속할 수 있다면?!
자바는 클래스 상속에 대해서 다중상속을 허용하지 않지만 인터페이스에 한해서는 다중상속을 허용하고 있다.
인터페이스상속은 인터페이스가 받을 수 있다. 클래스가 인터페이스를 상속받을 수는 없다.
<인터페이스 다중 상속>
→ 인터페이스를 인터페이스가 entends
예약어를 통해 다중 상속
interface interface3 extends interface1,interface2 {...}
→ 상속받은 인터페이스를 클래스로 구현
class class2 implements interface3{..}
😆 인터페이스의 다중 상속의 장점
interface interface1 {
void in1();
void in3();
}
interface interface2 {
void in2 ();
void in3();
}
interface interface3 extends interface1,interface2 { }
class class1 implements interface3{
@Override
public void in1() {
System.out.println("in1 실행");
}
@Override
public void in2() {
System.out.println("in2 실행");
}
@Override
public void in3() {
System.out.println("in3 실행");
}
}
public class InterfaceInheritance {
public static void main(String[] args) {
class1 class1 = new class1();
class1.in1();
class1.in2();
class1.in3(); //동일한 메소드명이 있어도 오류나지 않는다.
}
}
동일한 메서드 명이 겹칠 수 있고, 이는 다이아몬드 문제로 오류가 발생하기 때문에
java에서는 다중 상속을 금지하고 있다.
어처피 구현은 클래스에서 하기 때문에 충돌 가능성이 없다.
그런데 default메소드명이 동일할 경우 문제가 발생한다. 이에 대해서 더 자세하게 알아보자.
interface interface1 {
default void in3(){
System.out.println("interface1의 in3 실행");
}
}
interface interface2 {
default void in3(){
System.out.println("interface2의 in3 실행");
}
}
interface interface3 extends interface1,interface2 {
//오버라이딩을 해야지 컴파일이 가능함.
@Override
default void in3() {
interface1.super.in3();
}
}
class class1 implements interface3{
}
public class InterfaceInheritance {
public static void main(String[] args) {
class1 class1 = new class1();
class1.in3(); //interface1의 in3 실행
}
}
case1. 부모클래스의 메소드 선택
interface interface1 {
default void in3(){
System.out.println("interface1의 in3 실행");
}
}
class class1 {
public void in3() {
System.out.println("class1의 in3 실행");
}
}
class class2 extends class1 implements interface1{
}
public class InterfaceInheritance {
public static void main(String[] args) {
class2 class2 = new class2();
class2.in3(); //class1의 in3 실행
}
}
case2. 인터페이스의 메소드 오버라이딩
interface interface1 {
default void in3(){
System.out.println("interface1의 in3 실행");
}
}
class class1 {
public void in3() {
System.out.println("class1의 in3 실행");
}
}
class class2 extends class1 implements interface1{
@Override
public void in3() {
interface1.super.in3();
}
}
public class InterfaceInheritance {
public static void main(String[] args) {
class2 class2 = new class2();
class2.in3(); //interface1의 in3 실행
}
}