본 포스트는 카카오 테크 캠퍼스 1기에서 제공하는 패스트캠퍼스 강의에서 배운 내용을 토대로 정리하였습니다.
인터페이스(Interface)는 클래스 또는 객체의 기능을 외부에 노출시키는 방법 중 하나이다.
즉, 인터페이스는 객체 간의 상호작용을 돕는 규약이나 계약, 틀이라고 볼 수 있다.
인터페이스는 일종의 추상화된 클래스로, 클래스와 비슷한 형태를 갖지만 실제로는 구현된 코드가 없다.
대신 인터페이스는 메소드, 상수 등의 특정 요소를 선언하여, 해당 인터페이스를 구현한 클래스가 반드시 가져야 하는 특정한 기능이나 속성을 정의한다.
이를 통해 객체 간의 상호작용에서 규격화된 방식으로 데이터를 주고받을 수 있다.
ex)
ClickAble 이라는 인터페이스를 상속한 클래스는 외부에서 상호작용 할떄,
이 클래스는 클릭이 가능한 기능을 가지고있고, 그 기능(메서드)의 인풋(매개변수) 과 아웃풋(결과) 를 통해 대략적으로 어떠한 기능인지 알 수 있다.
클래스나 프로그램이 제공하는 기능을 명시적으로 선언
일종의 클라이언트 코드와의 약속이며 클래스나 프로그램이 제공하는 명세(specification)
클라이언트 프로그램은 인터페이스에 선언된 메서드 명세만 보고 이를 구현한 클래스를 사용할 수 있음
어떤 객체가 하나의 인터페이스 타입이라는 것은 그 인터페이스가 제공하는 모든 메서드를 구현했다는 의미임
인터페이스를 구현한 다양한 객체를 사용함 - 다형성
모든 메서드가 추상 메서드로 선언됨 public abstract
모든 변수는 상수로 선언됨 public static final
인터페이스를 클래스가 상속할 경우, 그 인터페이스의 모든 추상메소드를 구현해야함.
interface 인터페이스 이름{
public static final float pi = 3.14F;
public void makeSomething();
}
사칙연산 기능이 있음을 알리는 calc 인터페이스 구현
1차 기능인 더하기 ,빼기 기능만 구현된 Calculator 추상 클래스 구현
=> 인터페이스를 상속했는데 전부 구현하지 않아서 추상 클래스임
calc의 모든 기능이 구현된 CompleteCalc 클래스 구현
=> CompleteCalc 클래스는 추상클래스가 아니라서 인스턴스 생성가능
Calc.java
public interface Calc {
double PI = 3.14;
int ERROR = -99999999;
//사칙연산의 기능이 있음을 알리는 추상 메서드 선언
//외부에 다른 클래스와 상호작용할때 이 인터페이스를 상속한
//클래스가 어떤 기능이 있는지 1차적으로 이해가 가능
int add(int num1, int num2);
int substract(int num1, int num2);
int times(int num1, int num2);
int divide(int num1, int num2);
}
Calculator.java
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;
}
}
CompleteCalc.java
public class CompleteCalc extends Calculator{
@Override
public int times(int num1, int num2) {
//인터페이스의 메서드 구현
return num1 * num2;
}
@Override
public int divide(int num1, int num2) {
//인터페이스의 메서드 구현
if( num2 == 0 )
return ERROR;
else
return num1 / num2;
}
public void showInfo() {
System.out.println("모두 구현하였습니다.");
}
}
CalculatorTest.java
public class CalculatorTest {
public static void main(String[] args) {
Calc calc = new CompleteCalc();
int num1 = 10;
int num2 = 2;
System.out.println(num1 + "+" + num2 + "=" + calc.add(num1, num2));
System.out.println(num1 + "-" + num2 + "=" +calc.substract(num1, num2));
System.out.println(num1 + "*" + num2 + "=" +calc.times(num1, num2));
System.out.println(num1 + "/" + num2 + "=" +calc.divide(num1, num2));
}
}
결과
위의 계산기 예시에서
Calc calc = new CompleteCalc();
하나의 인터페이스를 여러 객체가 구현하게 되면 클라이언트 프로그램은 인터페이스의 메서드를 활용하여 여러 객체의 구현을 사용할 수 있음 (다형성)
인터페이스를 이용한 DAO(data access object)를 구현 해보자.
DB에 회원 정보를 넣는 dao(data access object)를 여러 DB 제품이 지원될 수 있게 구현함
환경파일(db.properties) 에서 database의 종류에 대한 정보를 읽고 그 정보에 맞게 dao 인스턴스를 생성하여 실행될 수 있게 함
source hierachy
우선 유저 정보 Model 클래스인 UserInfo클래스를 만들고,
UserInfoDao인터페이스를 구현해서 db종류에 맞게 구체화시킨 오라클Dao, MySqlDao 파일을 각각 구현한다.
마지막으로 서버(이번 예시에는 단순 UserInfoDao 파일)와 상호작용하는 클라이언트인 UserInfoClient 파일로 테스트를 해본다.
1.유저 정보 Model 클래스인 UserInfo클래스를 만들자.
UserInfo.java (사용자정보 모델 클래스)
public class UserInfo {
private String userId;
private String passwd;
private String userName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
UserInfoDao.java
( dao 에서 제공되어야 할 메서드를 선언한 기본 인터페이스 )
public interface UserInfoDao {
void insertUserInfo(UserInfo userInfo);
void updateUserInfo(UserInfo userInfo);
void deleteUserInf(UserInfo userInfo);
}
UserInfoMySqlDao.java (UserInfoDao 인터페이스를 구현한 MySql 버전 dao)
public class UserInfoMySqlDao implements UserInfoDao{
@Override
public void insertUserInfo(UserInfo userInfo) {
System.out.println("insert into MYSQL DB userId =" + userInfo.getUserId() );
}
@Override
public void updateUserInfo(UserInfo userInfo) {
System.out.println("update into MYSQL DB userId = " + userInfo.getUserId());
}
@Override
public void deleteUserInf(UserInfo userInfo) {
System.out.println("delete from MYSQL DB userId = " + userInfo.getUserId());
}
}
UserInfoOracleDao.java (UserInfoDao 인터페이스를 구현한 Oracle 버전 dao)
public class UserInfoOracleDao implements UserInfoDao{
public void insertUserInfo(UserInfo userInfo){
System.out.println("insert into ORACLE DB userId =" + userInfo.getUserId() );
}
public void updateUserInfo(UserInfo userInfo){
System.out.println("update into ORACLE DB userId = " + userInfo.getUserId());
}
public void deleteUserInf(UserInfo userInfo){
System.out.println("delete from ORACLE DB userId = " + userInfo.getUserId());
}
}
UserInfoClient.java (UserInfoDao 인터페이스를 활용하는 클라이언트 프로그램)
public class UserInfoClient {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("db.properties");
//파일입출력 스트림에 db.properties파일을 추가
Properties prop = new Properties();
//프로퍼티 자료형을 하나 선언해서
prop.load(fis);
// 위에 선언한 fis 파일을 로드한다.
String dbType = prop.getProperty("DBTYPE");
// 그 fis파일, 즉 db.properties 파일의
// DBTYPE 을 dbType변수에 저장
UserInfo userInfo = new UserInfo();
userInfo.setUserId("12345");
userInfo.setPasswd("!@#$%");
userInfo.setUserName("이순신");
//위에 처럼 더미 유저정보를 넣어주고
UserInfoDao userInfoDao = null;
//아래와 같이 dbType에 따라 다른 Dao를 실행시킨다.
// => 다형성 을 보여줌.
if(dbType.equals("ORACLE")){
userInfoDao = new UserInfoOracleDao();
}
else if(dbType.endsWith("MYSQL")){
userInfoDao = new UserInfoMySqlDao();
}
else{
System.out.println("db support error");
return;
}
userInfoDao.insertUserInfo(userInfo);
userInfoDao.updateUserInfo(userInfo);
userInfoDao.deleteUserInf(userInfo);
}
}
결과
db.properties 환경파일이 MYSQL 일때
"DBTYPE=MYSQL"
db.properties 환경파일이 ORACLE 일때
"DBTYPE=ORACLE"
구현을 가지는 메서드, 인터페이스를 구현하는 클래스들에서 공통으로 사용할 수 있는 기본 메서드
인터페이스를 상속한 인터페이스가 부모 인터페이스를 오버라이딩하려면 디폴트 메서드를 이용하면 된다.
=> 밑에 인터페이스의 인터페이스 다중 상속 항목에서 예제의 MyInterface.java 참조
default 키워드 사용
default void description() {
System.out.println("정수 계산기를 구현합니다.");
myMethod();
}
@Override
public void description() {
System.out.println("CompleteCalc에서 재정의한 default 메서드");
//super.description();
}
A.java
public interface A {
void Test(){
System.out.println("A의 디폴트 메서드");
}
}
B.java
public interface B {
void Test(){
System.out.println("B의 디폴트 메서드");
}
}
C.java
public interface C extends A, B{
//1. A의 메서드 불러옴
@Override
void Test(){
A.super.Test();
}
//2. B의 메서드 불러옴
@Override
void Test(){
B.super.Test();
}
//3. 새로 재정의 함.
@Override
void Test(){
System.out.println("C의 디폴트 메서드");
}
//4. 당연히, 1,2,3 조합도 가능. 어차피 재정의 하는거니까.
@Override
void Test(){
A.super.Test();
B.super.Test();
System.out.println("C의 디폴트 메서드");
}
}
인스턴스 생성과 상관 없이 인터페이스 타입으로 사용할 수 있는 메서드
클래스명으로 접근
인스턴스 안만들고 접근 가능
static int total(int[] arr) {
int total = 0;
for(int i: arr) {
total += i;
}
mystaticMethod();
return total;
}
인터페이스를 구현한 클래스에서 사용하거나 재정의 할 수 없음
인터페이스 내부에서만 사용하기 위해 구현하는 메서드
default 메서드나 static 메서드에서 사용함
=> 당연하지. 왜냐면 이 두 유형의 메서드만 구현부가 있으니까.
private void myMethod() {
System.out.println("private method");
}
private static void mystaticMethod() {
System.out.println("private static method");
}
자바에서 클래스와 달리 인터페이스는 한 클래스가 다중 구현 할 수 있음
디폴트 메서드가 중복 되는 경우는 구현 하는 클래스에서 재정의 하여야 함
여러 인터페이스를 구현한 클래스는 인터페이스 타입으로 형 변환 되는 경우 해당 인터페이스에 선언된 메서드만 사용 가능 함
Sell.java
public interface Sell {
void sell();
}
Buy.java
public interface Buy {
void buy();
}
Customer.java
public class Customer implements Buy, Sell{
@Override
public void sell() {
System.out.println("customer sell");
}
@Override
public void buy() {
System.out.println("customer buy");
}
public void sayHello() {
System.out.println("Hello");
}
}
CustomerTest.java
public class CustomerTest {
public static void main(String[] args) {
Customer customer = new Customer();
customer.buy();
customer.sell();
customer.sayHello();
//Customer 클래스의 모든 메서드 실행가능
Buy buyer = customer;
buyer.buy();
//Buy 클래스에 buy메서드만 선언돼 있어서 buy메서드만 실행가능
//가상메서드 원리에 의해 Customer의 buy메서드 로직 실행
Sell seller = customer;
seller.sell();
//Buy 클래스에 sell메서드만 선언돼 있어서 sell메서드만 실행가능
//가상메서드 원리에 의해 Customer의 sell메서드 로직 실행
}
}
인터페이스 사이에도 상속을 사용할 수 있음
extends 키워드를 사용
인터페이스는 다중 상속이 가능하고 구현 코드의 상속이 아니므로 타입 상속 이라고 함
X.java
public interface X {
void x();
}
Y.java
public interface Y {
void y();
}
MyInterface.java
public interface MyInterface extends X, Y{
void myMethod();
@Override
default void x(){
//default 키워드로 통해 인터페이스X의 메서드 오버라이딩
//유의미한 행위는 아님. 어차피 Myinterface 를 구현하는 클래스에서 x메서드를 구현할텐데
//키워드 의미 그대로 해당 인터페이스를 구현할떄 기본값으로 두기위해 사용할 수도?
System.out.println("this is MyInterFace X);
};
}
MyClass.java
public class MyClass implements MyInterface{
@Override
public void y() {
System.out.println("y()");
}
@Override
public void myMethod() {
System.out.println("myMethod()");
}
}
MyClassTest.java
public class MyClassTest {
public static void main(String[] args) {
MyClass mClass = new MyClass();
X xClass = mClass;
xClass.x();
//this is MyInterFace X 출력됨
Y yClass = mClass;
yClass.y();
//y() 출력됨
MyClass iClass = mClass;
iClass.x();
//this is MyInterFace X 출력됨
iClass.y();
//y() 출력됨
iClass.myMethod();
//myMethod()
}
}