22일차_추상클래스,인터페이스, 회원관리_배열
------------------------------------
추상 클래스
추상의 사전적 의미 :
여러 가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용.
1. 추상 클래스는 추상 메소드를 포함하고 있는 클래스이다. 단, 추상 메소드가 없어도 추상 클래스로 선언할 수 있다. 추상 클래스로 지정되면 객체 생성을 할 수 없다.
2. 추상 메소드는 메소드 정의만 있고, 구현이 없는 메소드이다. 메소드 구현을 하위 클래스에서 의무적으로 하도록 하기 위함이다. 추상 메소드는 현재 클래스를 상속받는 클래스가 공통적으로 사용하게 될 메소드를 정의만 하고, 실제 구현은 하위 클래스에서 하도록 만든 것이다.
3. 추상 메소드 기본 형식
접근지정자 abstract 반환자료형 메소드이름(매개변수리스트);
4. 추상 클래스 기본 형식
public abstract class 클래스이름 {
//추상 메소드 멤버
}
예를 들어, 프린터 클래스가 있다면, 인쇄 기능은 모든 프린터의 공통 기능이지만 프린터마다의 방식이 다르므로 하위 클래스에서 구현하도록 하는 것이다.
public abstract class 프린터 {
public abstract void 인쇄();
}
public class 잉크젯프린터 extends 프린터 {
@Override
public void 인쇄() {
//잉크젯 방식 인쇄 실행
}
}
public class 레이져프린터 extends 프린터 {
@Override
public void 인쇄() {
//레이저 방식 인쇄 실행
}
}
//Printer.java
package com.test;
//프린터 클래스들에 대한 공통적 요소를 모은 클래스
//-> 추상 클래스 정의
//-> 자기 자신은 객체 생성 불가
public abstract class Printer {
//일반 메소드 형식
/*
public void print() {
//기능 구현 X
}
*/
//추상 메소드 형식
//-> 기능 구현은 없고, 메소드 이름만 존재하는 상태
//-> 모든 프린터 클래스가 가져야될 공통 기능.
//-> 추상 메소드 추가
//-> 추상 메소드가 있는 클래스는 추상 클래스로 지정.
public abstract void print();
//일반 메소드 추가
//추상 클래스는 객체 생성이 안되므로 직접 호출 불가
//->하위 클래스의 객체를 통해서 호출 가능
public int count() {
int result = 0;
//출력 매수를 계산하는 과정
return result;
}
}
//InkJetPrinter.java
package com.test;
//상위 클래스가 추상 클래스인 경우는
//추상 클래스의 추상 메소드를 반드시 오버라이딩 구현.
public class InkjetPrinter extends Printer {
//의무적 오버라이딩
@Override
public void print() {
//잉크젯 프린터의 인쇄 기능 구현
}
//선택적 오버라이딩
@Override
public String toString() {
return "InkjetPrinter 클래스의 toString() 메소드";
}
}
//LaserPrinter.java
package com.test;
public class LaserPrinter extends Printer {
//추상 클래스를 상속 받은 하위 클래스는
//상위 클래스의 추상 메소드 멤버를 의무적으로 오버라이딩해야 한다.
//메소드 내부에서 구체적인 구현 내용을 채워야 합니다.
@Override
public void print() {
System.out.println("레이저 방식으로 인쇄 실행!");
}
}
//Main.java
package com.test;
public class Main {
public static void main(String[] args) {
//추상 클래스는 객체 생성 불가
//Printer printer = new Printer(); //X
//printer.count(); //X
//printer.print(); //X
//하위 클래스는 객체 생성 가능
InkjetPrinter printer = new InkjetPrinter();
//Printer 클래스의 count() 메소드를
//하위 클래스인 InkjetPrinter 클래스의
//멤버로 인식.
System.out.println(printer.count());
printer.print();
}
}
Wrapper 클래스의 상위 클래스인 Number 클래스(추상 클래스) 테스트
//Main.java
package com.test;
public class Main {
public static void main(String[] args) {
//Number 클래스는 Wrapper 클래스의 상위 클래스입니다.
//Number 클래스는 추상 클래스입니다.
//-> 추상 메소드 포함된 상태
//-> 객체 생성 불가
//-> 상속한 하위 클래스를 통해서 멤버 접근 가능
//추상 클래스이므로 객체 생성이 불가합니다.
//Number number1 = new Number();
//Integer 클래스는 기본생성자가 없기 때문에
//매개변수 있는 생성자를 통해서 객체 생성 해야 합니다.
//Number number1 = new Integer();
Number number1 = new Integer(10);
System.out.println(number1.intValue()); //10
System.out.println(number1.doubleValue()); //10.0
//Number 클래스에서의 doubleValue() 메소드
/*
public abstract double doubleValue();
*/
//Integer 클래스에서의 doubleValue() 메소드
/*
public double doubleValue() {
return (double)value;
}
*/
}
}
--------------------------------------
인터페이스(Interface)
1. 추상 메소드와 상수만을 멤버로 가질 수 있는 추상 클래스. 모든 멤버가 추상이므로 abstract 키워드를 사용하지 않는다. 추상 클래스이므로 객체 생성이 안된다. 인터페이스 이름은 일반적으로 ~able 형태로 작성한다.
2. 인터페이스 기본 형식
public interface 인터페이스명 {
//상수 멤버
//추상 메소드 멤버
}
3. 모든 멤버 변수는 public static final 인 상태이다. 생략 가능.
4. 모든 메소드 멤버는 public abstract 인 상태이다. 생략 가능.
5. 인터페이스는 인터페이스만 상속 받을 수 있고, 다중 상속 받을 수 있다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2, ... {
}
6. 클래스는 상위 클래스를 한 개만 상속 받을 수 있지만, 인터페이스는 다중 상속 받을 수 있다.
public class 하위클래스 extends 상위클래스 implements 상위인터페이스1, 상위인터페이스2,... {
}
7. 인터페이스를 상속 받은 클래스는 인터페이스가 가진 멤버를 의무적으로 구현(implement)해야 한다.
8. 인터페이스는 클래스의 추상화에 필수적인 조건이다. 서로 관계 없는 클래스들끼리도 관계를 맺어줄 수 있다.
예를 들어,
A -> 커피숍 주인. 원두 커피 사용. B에게서 공급 받고 있다.
B -> 원두 커피 공급업자
=> A 와 B은 강한 결합 관계가 된다.
=> 강한 결합이기 때문에 B의 상황과 능력이 A에게 영향을 준다.
=> 약한 결과 관계를 만들기 위해서 중간 역할자를 부여한다.
A -> 커피숍 주인. 원두 커피 사용. C에게서 공급 받고 있다.
C -> 중간 역할자(도매상)
B1, B2,... -> 원두 커피 공급업자
=> 약한 결합이기 때문에 B1의 상황과 능력이 A에게 직접적으로 영향을 주지 않는다.
B1을 대체할 수 있는 방법이 많이 존재하기 때문이다.
A 객체-> B1 객체를 사용해야 하는 상황. -> B1 객체를 직접 사용하지 않고, ( 인터페이스 ) 를 통해서 사용하도록 조정. -> B1 객체 대신 B2, B3 객체로 바꿔서 사용할 수 있다.
인터페이스 -> 중간 역할. B1, B2, B3 객체의 추상 클래스.
B1 객체 -> 대체 객체로서 B2, B3, ... 등이 존재.
--------------------------------------------
추상화
- 클래스간의 결합에서 A와 B가 직접적인 연결을 하는 것이 아니라, 인터페이스를 통해서 연결함으로써 A와 B의 연결을 간접적으로 연결하고, B의 상태가 변화(C로 바뀌는 경우) 하더라도 A에는 영향을 최소화 할 수 있는 상태를 만드는 것.
추상화 전 상태
//Sample35.java
package com.test;
public class Sample35 {
private Sample36 sample;
//Sample36에서 Sample37로 대체하려면
//-> private Sample37 sample;
//Sample35 객체의 생성자에서
//Sample36 클래스의 객체 생성 과정이 포함되어 있다.
//Sample35와 Sample36은 강한 결합 상태이다.
public Sample35() {
sample = new Sample36();
//Sample36에서 Sample37로 대체하려면
//-> sample = new Sample37();
}
public String method() {
return sample.toString();
}
}
//Sample36.java
package com.test;
public class Sample36 {
@Override
public String toString() {
return String.format("Sample36 클래스의 toString() 메소드");
}
}
//Sample37.java
package com.test;
//Sample36 클래스의 대체 클래스 예정
public class Sample37 {
@Override
public String toString() {
return String.format("Sample37 클래스의 toString() 메소드");
}
}
//Main.java
package com.test;
public class Main {
public static void main(String[] args) {
//사용자는 Sample35 객체만 생성했지만
//내부적으로는 Sample36 객체도 생성된다.
Sample35 sample = new Sample35();
System.out.println(sample.method()); //"Sample36 클래스의 toString() 메소드"
}
}
추상화 후 상태 (인터페이스 추가)
//Sample.java
package com.test;
public interface Sample {
void method();
}
//Sample35.java
package com.test;
public class Sample35 {
private Sample sample;
//Sample36에서 Sample37로 대체하려면
//->수정 사항 없음
//Sample35 객체의 생성자에서
//외부에서 Sample36, Sample37 클래스의 객체를 얻어오는 과정이 포함되어 있다.
//Sample35와 Sample36, Sample37은 약한 결합 상태이다.
public Sample35(Sample sample) {
this.sample = sample;
//Sample36에서 Sample37로 대체하려면
//-> 수정 사항 없음
}
public String method() {
return sample.toString();
}
}
//Sample36.java
package com.test;
public class Sample36 implements Sample {
@Override
public String toString() {
return String.format("Sample36 클래스의 toString() 메소드");
}
@Override
public void method() {
}
}
//Sample37.java
package com.test;
//Sample36 클래스의 대체 클래스 예정
public class Sample37 implements Sample {
@Override
public String toString() {
return String.format("Sample37 클래스의 toString() 메소드");
}
@Override
public void method() {
}
}
//Main.java
package com.test;
public class Main {
public static void main(String[] args) {
//Sample36 객체는 별도 생성합니다.
Sample36 sample36 = new Sample36();
//Sample37 객체는 별도 생성합니다.
Sample37 sample37 = new Sample37();
//사용자는 Sample35 객체를 생성하는 과정에서
//Sample36 객체를 주입하는 과정이 실행 된다.
//Sample36에서 Sample37로 대체하려면
//생성자 매개변수 값으로 sample36 -> sample37로 수정하면 된다.
Sample35 sample = new Sample35(sample37);
System.out.println(sample.method()); //"Sample36 클래스의 toString() 메소드"
}
}
-------------------------------------------------
과제)외부에서 데이터(이름, 전화번호)를 입력 받아서 메모리에 저장하고 출력, 검색할 수 있는 회원 입력, 출력 프로그램 작성.
회원 입력시 배열 저장소의 공간이 모자란 경우 배열 저장소의 크기를 확장(2배)하는 기능 추가.
실행 예)
---- 회원관리 ----
1. 회원 전체 출력
2. 회원 입력
3. 회원 검색
선택(1~3, 0 종료)?
//Student.java -> 회원 한 명분의 정보 저장용 클래스. 이름, 전화번호 항목으로 구성.
//MenuAction.java -> 메뉴 액션 클래스. 최초 입력시 회원 n명까지 저장할 수 있는 배열 생성. 배열의 자료형은 Student 클래스로 지정.
//Main.java -> 메뉴 출력
//Student.java
package com.sist;
public class Student {
private String name, tel;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
//toString() 메소드 오버라이딩 구현
//멤버변수의 데이터를 출력하는 용도로 변경
@Override
public String toString() {
return String.format("%s %s"
, this.getName()
, this.getTel());
}
//문제) equals() 메소드 오버라이딩
//equals()는 Object 클래스의 메소드
//equals()는 원래 객체의 참조주소를 비교하는 기능이지만
//객체의 멤버(name)을 비교하도록 변경
@Override
public boolean equals(Object obj) {
//원본 -> this.name
//비교 대상 -> obj.getName(); //X
//비교 대상 -> ((Student)obj).getName(); //O
//비교 방법 -> 같은지, 다른지 ? -> true or false -> 비교 대상의 자료형 -> String -> equals() 메소드 -> 문자열 비교
return this.name.equals(((Student)obj).getName());
}
}
//MenuAction.java
package com.sist;
import java.util.Scanner;
public class MenuAction {
//학생 정보 저장용 배열 준비->데이터 저장소
private Student[] students;
public MenuAction() {
//배열 생성 및 초기화
students = new Student[5];
this.init();
}
private void init() {
Student student0 = new Student();
student0.setName("hong");
student0.setTel("010-123-1234");
students[0] = student0;
Student student1 = new Student();
student1.setName("kim");
student1.setTel("010-432-4321");
students[1] = student1;
Student student2 = new Student();
student2.setName("park");
student2.setTel("010-987-9876");
students[2] = student2;
Student student3 = new Student();
student3.setName("choi");
student3.setTel("010-567-5678");
students[3] = student3;
Student student4 = new Student();
student4.setName("hwang");
student4.setTel("010-876-8765");
students[4] = student4;
}
//1. 회원 입력
public void menuInsert(Scanner sc) {
//문제) 신규 정보 등록 과정 추가
//배열 크기 및 여분 확인 -> 입력 가능 여부 결정
//입력-> 새로운 name, tel
//처리-> 저장소(배열)에 신규 저장
//출력-> "신규 데이터 입력!" or "저장소 full!" 메시지 출력
if (students.length > this.count()) {
System.out.print("이름 입력 : ");
String name = sc.next();
System.out.print("전화번호 입력 : ");
String tel = sc.next();
Student stu = new Student();
stu.setName(name);
stu.setTel(tel);
students[this.count()] = stu;
System.out.printf("신규 데이터 입력 완료! %n%n");
} else {
System.out.printf("저장소 Full! %n");
//확장 여부 확인
System.out.print("저장소를 확장하시겠습니까(y/n)?");
String response = sc.next();
if (response.equalsIgnoreCase("Y")) {
//문제) 확장 액션 진행
//-> private 메소드
this.expand();
System.out.printf("저장소가 확장되었습니다.%n%n");
}
}
}
//저장소 확장 액션 메소드
private void expand() {
//신규 임시 배열 준비
//-> 크기는 기존 배열의 2배 또는 5칸 증가
//기존 배열의 데이터를 임시 배열로 복사 -> 반복문
//신규 임시 배열의 참조주소를
//기존 배열 참조주소를 저장한 변수(students)에 덮어쓰기.
}
//2. 회원 전체 출력
public void menuSelect() {
//출력 -> 저장소(배열)에 있는 모든 데이터
/*
for (int i=0; i<students.length; ++i) {
Student student = students[i];
if (student != null) {
//System.out.println(student.toString());
System.out.println(student);
}
}
*/
System.out.printf("인원수:%d %n", this.count());
System.out.println("---------------");
System.out.println("이름 전화번호");
System.out.println("---------------");
for (Student student : students) {
if (student != null) {
System.out.println(student);
}
}
System.out.println("---------------");
System.out.println();
}
//인원수 메소드 -> private
private int count() {
int result = 0;
//문제) 저장소(배열)에 들어있는 데이터의 갯수 확인
for (int i=0; i<students.length; ++i) {
Student student = students[i];
if (student != null) {
++result;
}
}
return result;
}
//3. 회원 검색
public void menuSearch(Scanner sc) {
//외부 입력(name)
System.out.print("name?");
String name = sc.next();
Student value = new Student();
value.setName(name);
//처리 -> 검색 액션
//-> 배열 저장소 내에서 name이 일치하는 자료 검색
//-> Student 자료형을 가진 객체 한 개
Student result = null;
for (int i=0; i<students.length; ++i) {
Student temp = students[i];
/*
if (temp.getName().equals(name)) {
result = temp;
break;
}
*/
if (value.equals(temp)) {
result = temp;
break;
}
}
//결과 출력
if (result == null) {
System.out.printf("검색 결과가 없습니다.%n%n");
} else {
/*
System.out.printf("%s %s %n%n"
, result.getName()
, result.getTel());
*/
/*
System.out.println(result); //객체 정보
System.out.println(result.toString()); //객체 정보
*/
//toString() 메소드 호출시 객체 정보가 아니라
//멤버변수의 데이터를 출력하는 용도로 변경
//->오버라이딩
System.out.println(result);
System.out.println();
}
}
}
//Main.java
package com.sist;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
MenuAction menu = new MenuAction();
do {
System.out.println("--- 회원 정보 관리 ---");
System.out.println("1. 회원 전체 출력");
System.out.println("2. 회원 입력");
System.out.println("3. 회원 검색");
System.out.print("선택(1, 0-exit)?");
int m = sc.nextInt();
if (m==0) {
break;
}
switch (m) {
case 1: menu.menuSelect(); break;
case 2: menu.menuInsert(sc); break;
case 3: menu.menuSearch(sc); break;
}
}while(true);
sc.close();
}
}
------------------------------------------
요약
1. 추상 클래스의 특징
- 추상 메소드가 포함된 클래스
- 추상 메소드 : 메소드 정의만 있고, 구현 내용이 없는 메소드.
- abstract 키워드 사용
- 추상 클래스는 객체 생성 안된다.
- 추상 클래스에 일반 멤버(생성자, 멤버변수, 메소드) 포함할 수 있다.
- 추상 클래스를 상속 받은 하위 클래스는 반드시 추상 메소드에 대한 오버라이딩을 구현해야 한다.
2. 인터페이스
- 클래스의 추상화
- 인터페이스를 이용하면 클래스간의 관계를 약한 결합으로 만들 수 있다.
- 인터페이스는 모든 멤버가 추상이므로, abstract 키워드를 생략한다.
public interface 인터페이스명 {
//추상 멤버
}
---------------------------------------------------