다형성의 활용
부모 클래스를 가져와 모든 자식 클래스를 한번에 다룰 수 있다.
public class ShapeTest {
public static void print(Shape s) {
System.out.println("x=" + s.x + " y=" + s.y);
}
public static void main(String arg[]) {
Rectangle s1 = new Rectangle();
Triangle s2 = new Triangle();
Circle s3 = new Circle();
print(s1); print(s2); print(s3);
}
}
Java
복사
Shape 클래스를 부모로 가지는 Circle, Rectangle, Triangle 클래스들은 Shape 객체를 매개변수로 받아와도 자식 클래스 셋 모두를 다룰 수 있기에 이에 편리하다.
instanceof 연산자로 실제 타입 확인 가능
public class ShapeTest4 {
public static void print(Shape obj) {
if (obj instanceof Rectangle) {
System.out.println("실제 타입은 Rectangle");
}
if (obj instanceof Triangle) {
System.out.println("실제 타입은 Triangle");
}
if (obj instanceof Circle) {
System.out.println("실제 타입은 Circle");
}
}
}
Java
복사
(a instanceof b) 사용 시, a객체가 b타입인지, 위의 경우 b 클래스 타입인지 확인하여 True or False의 정보를 제공합니다.
추상클래스
객체를 직접 생성할 수 있는 클래스를 실체 클래스 라고 하며, 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다.
추상(abstract) : 실체 간에 공통되는 특성을 추출한 것을 의미.
추상클래스란?
•
하나 이상의 추상 메소드를 포함하는 클래스.
•
추상 메소드를 포함하고 있다는 점을 제외하면 일반 클래스와 같다.
추상 클래스의 규칙
•
키워드 abstract를 클래스 이름 앞에 붙여 표현
•
new를 통해 객체를 직접 생성해서 사용 불가능
•
메소드에 abstract을 사용할 경우 구현부 없음
•
abstract로 선언한 메소드를 자식클래스에서 반드시 구현 (오버라이딩)
추상클래스의 용도
1.
실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적.
•
유지보수성 높이고 통일성 유지 가능
2.
실체 클래스 작성 시 시간을 절약.
•
주어진 필드와 메소드를 통해 구현에 집중 가능
추상 클래스 선언
public abstract class 클래스이름 { // (추상클래스) 클래스 앞에 abstract
// 필드
// 생성자
// 메소드
abstract 반환타입 메소드이름(); // (추상메소드) 메소드 리턴타입 앞에 abstract
...
}
Java
복사
예제
추상메소드란?
•
자식 클래스에서 반드시 오버라이딩 해야만 사용할 수 있는 메소드.
•
선언부만 존재, 구현부는 작성 X → 구현부는 자식 클래스에서 오버라이딩 하여 사용
•
자식 클래스에서 구현이 반드시 이루어져야 하기 때문에 private 접근제어자 사용 불가
추상메소드 선언
abstract 반환타입 메소드이름(매개변수, ...);
Java
복사
예제
추상 클래스와 인터페이스의 공통점과 차이점
공통점
•
메서드의 선언만 존재, 구현 내용 없음
•
new 키워드를 통해 객체 생성 불가능
•
상속 받은 클래스가 반드시 선언된 추상 메소드를 구현
차이점
추상클래스 | 인터페이스 | |
키워드 | extends | inplements |
다중상속 | 불가능 | 가능 |
구성원 | 일반변수, 일반메소드, 추상메소드 모두 가능 | 상수, 추상메소드 만 가능 |
추상클래스 : 자신의 기능들을 하위로 확장
인터페이스 : 정의된 메소드를 각 클래스의 목적에 맞게 동일한 기능으로 구현
인터페이스(interface)
인터페이스는 클래스 혹은 프로그램이 제공하는 기능을 명시적으로 선언하는 역할이다.
•
인터페이스는 객체의 사용 방법을 정의한 ‘약속’ 혹은 ‘표준’이라고 말할 수 있다.
•
인터페이스는 객체의 교환성을 높여주기 때문에 다형성을 구현하는 매우 중요한 역할을 한다.
•
클라이언트 프로그램을 짤 때 객체의 내부 구조를 알지 못해도, 인터페이스의 메소드만 알 수 있다면 프로그램을 짤 수 있다.
인터페이스 선언
접근제어자 interface 인터페이스명 {...}
Java
복사
상수 필드
•
인터페이스는 런타임 시 데이터를 저장할 수 있는 필드를 선언할 수 없다. 상수 필드만 선언이 가능하다. 상수를 선언할 때는 반드시 초기값을 대입해야 한다.
[public static final] 상수명 = 값;
Java
복사
추상 메소드
•
메소드의 바디 없이, 매개값과 리턴 타입 등만 기재한다. 인터페이스에서 선언된 모든 추상 메소드는 public abstract 특성이다.
[public abstract] int 함수명 (매개변수);
Java
복사
디폴트 메소드
•
디폴트 메소드는 인터페이스에서 구현 코드까지 작성한 메소드이다. 이 메소드는 인터페이스를 구현한 클래스에 기본적으로 제공된다.
[public] default int 함수명 (매개변수) {...}
Java
복사
정적 메소드
•
객체 생성과 관계없이 인터페이스 호출만으로 사용할 수 있는 메소드이다.
[public] static int 함수명 (매개변수) {...}
Java
복사
인터페이스 선언 예시
예제 코드
인터페이스 구현
선언한 인터페이스를 클래스가 사용하는 것을 “클래스에서 인터페이스를 구현한다(implements)”라고 표현한다.
•
클래스 간 상속을 할 때는 부모 클래스의 필드와 메서드를 자식 클래스에서 확장한다는 의미로 extends 예약어를 사용한다.
•
클래스에서 인터페이스를 사용할 때는 클래스가 인터페이스에 선언된 기능을 구현한다는 의미로 implements 예약어를 사용한다.
구현 클래스
•
객체는 인터페이스에서 정의된 메소드와 동일한 실체의 메소드를 가지고 있어야 한다.
public class 구현클래스명 implements 인터페이스명{
//인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}
Java
복사
•
만약 클래스에서 인터페이스의 추상 메소드에 대응하는 실체 메소드를 구현하지 않는다면, 해당 클래스는 자동으로 추상 클래스가 된다.
public abstract class Circle implements Calculator{
// 실체 메소드 없을경우
}
Java
복사
메서드 구현과 오버라이딩
•
인터페이스와 클래스 구현의 예시는 다음과 같다.
예제 코드
@Override
public void setRadius(int radius) {
this.radius = radius;
}
Java
복사
•
메소드를 구현할 때 붙는 @Override는 추상 메소드인지, 실체 메소드인지 컴파일러가 체크하도록 지시하는 어노테이션이다.
public static void main(String[] args) {
Calculator circleCalc
circleCalc = new Circle();
}
Java
복사
•
인터페이스로 구현 객체를 사용하려면 인터페이스 변수를 선언하고 구현 객체를 대입해야 한다.
•
인터페이스 변수는 참조 타입이기 때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장한다.
•
구현 객체가 인터페이스 타입으로 변환되는 것은 자동타입변환에 해당한다.
인터페이스 vs 추상클래스
추상 클래스 사용 권장
•
클래스마다 코드를 공유하는 것이 우선이라면 추상 클래스를 사용하는 것이 편리하다.
•
공통적인 필드나 메소드가 많은 경우 추상클래스가 유리하다.
•
public 이외의 접근 지정자를 사용해야 하는 경우 추상클래스를 사용한다.
•
상수가 아닌 필드를 선언하기를 원할 때 추상클래스를 사용한다.
인터페이스
•
클래스마다 동일한 동작을 구현하기를 원할 때 인터페이스를 사용한다.
•
어떤 클래스가 구현하는지와 무관하게 특정한 자료형의 동작을 지정해야 한다면 인터페이스 사용을 고려한다.
•
다중 구현이 필요할 때 사용한다.
다중 상속
•
자바는 다중 상속을 지원하지 않는다. 메소드 호출 시 애매한 상황이 발생하기 때문이다.
•
여러 개의 인터페이스를 구현하면 다중 상속의 효과를 낼 수 있다.
버블 정렬(단순 교환 정렬)
정상희
•
버블 정렬은 이웃한 데이터들의 크고 작음을 비교한 뒤, 정렬 조건에 맞추어 이동 시키며 정렬하는 알고리즘입니다.
•
최대 값이 서서히 뒤로 옮겨지는 모습이 마치 사이다의 거품이 올라가는 모습과 비슷하다는 뜻에서 버블 정렬이라고 불립니다.
오름차순으로 예시를 들겠습니다.
정렬하려는 배열은 정렬되지 않은 부분(앞부분)과 정렬된 부분(뒷부분)으로 나뉩니다.
정렬을 시작할 때는 정렬된 부분이 비어있는 상태이며, 정렬되지 않은 부분이 배열 전체를 차지합니다.
최대 값을 정렬되지 않은 부분의 끝으로 옮기는 순서를 떠올려봅시다.
순서
1단계 : 정렬되지 않은 부분의 1번째 데이터와 2번째 데이터를 비교한다 [ 이웃한 데이터 비교 ]
2단계 : 1번째 데이터 > 2번째 데이터라면, 두 값의 순서를 바꾼다. [ 큰 값을 뒤로 옮기는 중 ]
3단계 : 데이터를 비교하기 시작할 위치를 뒤로 1칸 옮긴다. [ i ++ ]
정렬되지 않은 부분의 데이터가 2개만 남을 때까지 위의 순서를 반복하면,
정렬되지 않은 부분의 마지막 요소에는 정렬되지 않은 부분의 ‘최대 값’이 저장됩니다.
이 다음 ‘정렬되지 않은 부분’의 크기가 ‘최대 값’이 들어있는 마지막 요소를 뺀 나머지 부분을 다시 정렬합니다.
점점 작아지는 정렬되지 않은 부분’에서의 ‘최대 값’이 차례대로 저장되어 갑니다.
따라서 전체적으로 ‘정렬된 부분’은 데이터 열의 끝 부분부터 커지고,
‘정렬되지 않은 부분’은 데이터 열의 시작 부분부터 줄어듭니다.
마지막에 ‘정렬된 부분’이 데이터 열 전체를 차지하게 되면 정렬이 완료됩니다.
실습
import java.util.Arrays;
import java.util.Scanner;
public class BubbleSort {
// a[index1]과 a[index2]의 값을 바꾸는 함수
static void swap(int[] arr, int index1, int index2) {
int t = arr[index1];
arr[index1] = arr[index2];
arr[index2] = t;
}
// 버블 정렬
static void bubbleSort(int[] arr, int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1; j++)
if (arr[j] > arr[j + 1])
swap(arr, j, j + 1);
System.out.println(Arrays.toString(arr));
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("버블 정렬");
System.out.print("요소 개수 : ");
int n = sc.nextInt();
int[] x = new int[n];
for (int i = 0; i < n; i++) {
System.out.print("X[" + i + "] : ");
x[i] = sc.nextInt();
}
System.out.println("정렬 과정");
bubbleSort(x, n);
System.out.println("오름차순으로 정렬했습니다.");
for (int i = 0; i < n; i++) {
System.out.println("X[" + i + "] :" + x[i]);
}
}
}
Java
복사
1.
배열 요소의 값을 모두 입력하면 버블 정렬과정을 순서대로 출력합니다.
2.
실제 버블 정렬이 이루어지는 코드는 //버블정렬 주석 위치입니다.