본문 바로가기
dev/기본쌓기

[JAVA] 빠르게 정리하는 자바 문법 (6)

by dev-everyday 2024. 12. 31.
반응형
더보기

해당 글은 아래 링크를 참고하여 작성되었습니다.

https://www.tcpschool.com/java/java_polymorphism_concept

11. 다형성

1. 다형성의 개념

다형성(polymorphism)이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미한다.

자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있다.

클래스는 상속을 통해 확장될 수는 있어도 축소될 수는 없으므로, 자식 클래스에서 사용할 수 있는 멤버의 개수가 언제나 부모 클래스와 같거나 많게 된다.

class Parent { ... }
class Child extends Parent { ... }
...
Parent pa = new Parent(); // 허용
Child ch = new Child();   // 허용
Parent pc = new Child();  // 허용
Child cp = new Parent();  // 오류 발생.

 

자바에서는 참조 변수도 다음과 같은 조건에 따라 타입 변환을 할 수 있다.

1) 서로 상속 관계에 있는 클래스 사이에만 타입 변환을 할 수 있다.

2) 자식 클래스 타입에서 부모 클래스 타입으로의 타입 변환은 생략할 수 있다.

3) 하지만 부모 클래스 타입에서 자식 클래스 타입으로의 타입 변환은 반드시 명시해야 한다.

 

2. 추상 클래스

1) 추상 메소드

추상 메소드(abstract method)란 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메소드를 의미한다.

자바에서 추상 메소드를 선언하여 사용하는 목적은 추상 메소드가 포함된 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 하기 위함이다.

2) 추상 클래스

자바에서는 하나 이상의 추상 메소드를 포함하는 클래스를 가리켜 추상 클래스(abstract class)라고 한다.

이러한 추상 클래스는 객체 지향 프로그래밍에서 중요한 특징인 다형성을 가지는 메소드의 집합을 정의할 수 있도록 해준다.

반드시 사용되어야 하는 메소드를 추상 클래스에 추상 메소드로 선언해 놓으면, 이 클래스를 상속받는 모든 클래스에서는 이 추상 메소드를 반드시 재정의해야 한다.

 

3. 인터페이스

자식 클래스가 여러 부모 클래스를 상속받을 수 있다면 다양한 동작을 수행할 수 있다는 장점을 가지게 된다.

하지만 클래스를 이용하여 다중 상속을 할 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있어 자바에서는 클래스를 통한 다중 상속은 지원하지 않는다.

하지만 다중 상속의 이점을 버릴 수는 없기에 자바에서는 인터페이스라는 것을 통해 다중 상속을 지원하고 있다.

인터페이스(interface)란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미한다.

 

자바에서 추상 클래스는 추상 메소드뿐만 아니라 생성자, 필드, 일반 메소드도 포함할 수 있다.

하지만 인터페이스(interface)는 오로지 추상 메소드와 상수만을 포함할 수 있다.

자바에서 인터페이스를 선언하는 방법은 클래스를 작성하는 방법과 같다.

접근제어자 interface 인터페이스이름 {
    public static final 타입 상수이름 = 값;
    ...
    public abstract 메소드이름(매개변수목록);
    ...
}

단, 클래스와는 달리 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메소드는 public abstract이어야 한다.

이 부분은 모든 인터페이스에 공통으로 적용되는 부분이므로 이 제어자는 생략할 수 있다.

이렇게 생략된 제어자는 컴파일 시 자바 컴파일러가 자동으로 추가해 준다.

 

인터페이스는 추상 클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수는 없어서 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해야만 한다.

자바에서 인터페이스는 다음과 같은 문법을 통해 구현한다.

class 클래스이름 implements 인터페이스이름 { ... }

 

인터페이스를 사용하면 다중 상속이 가능할 뿐만 아니라 다음과 같은 장점을 가질 수 있다.

1) 대규모 프로젝트 개발 시 일관되고 정형화된 개발을 위한 표준화가 가능하다.

2) 클래스의 작성과 인터페이스의 구현을 동시에 진행할 수 있으므로, 개발 시간을 단축할 수 있다.

3) 클래스와 클래스 간의 관계를 인터페이스로 연결하면, 클래스마다 독립적인 프로그래밍이 가능하다.

 

4. 내부 클래스

내부 클래스(inner class)란 하나의 클래스 내부에 선언된 또 다른 클래스를 의미한다.

이러한 내부 클래스는 외부 클래스(outer class)에 대해 두 개의 클래스가 서로 긴밀한 관계를 맺고 있을 때 선언할 수 있다.

 

내부 클래스를 사용하면 다음과 같은 장점을 가질 수 있다.

1) 내부 클래스에서 외부 클래스의 멤버에 손쉽게 접근할 수 있다.

2) 서로 관련 있는 클래스를 논리적으로 묶어서 표현함으로써, 코드의 캡슐화를 증가시킨다.

3) 외부에서는 내부 클래스에 접근할 수 없으므로, 코드의 복잡성을 줄일 수 있다.

 

내부 클래스는 필드와 마찬가지로 선언된 위치에 따라 다음과 같이 구분된다.

1) 정적 클래스(static class)

2) 인스턴스 클래스(instance class)

3) 지역 클래스(local class)

4) 익명 클래스(anonymous class)

12. 자바 API 클래스

1. Object 클래스

1) java.lang 패키지

java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스들의 집합으로 java.lang 패키지의 클래스들은 import 문을 사용하지 않아도 클래스 이름만으로 바로 사용할 수 있도록 하고 있다.

2) java.lang.Object 클래스

java.lang 패키지 중에서도 가장 많이 사용되는 클래스는 바로 Object 클래스다.

Object 클래스는 모든 자바 클래스의 최고 조상 클래스가 된다.

따라서 자바의 모든 클래스는 Object 클래스의 모든 메소드를 바로 사용할 수 있다.

3) toString() 메소드

toString() 메소드는 해당 인스턴스에 대한 정보를 문자열로 반환한다.

이때 반환되는 문자열은 클래스 이름과 함께 구분자로 '@'가 사용되며 그 뒤로 16진수 해시 코드(hash code)가 추가된다.

16진수 해시 코드 값은 인스턴스의 주소를 가리키는 값으로, 인스턴스마다 모두 다르게 반환된다.

4) equals() 메소드

equals() 메소드는 해당 인스턴스를 매개변수로 전달받는 참조 변수와 비교하여, 그 결과를 반환한다.

이때 참조 변수가 가리키는 값을 비교하므로, 서로 다른 두 객체는 언제나 false를 반환하게 된다.

5) clone() 메소드

clone() 메소드는 해당 인스턴스를 복제하여, 새로운 인스턴스를 생성해 반환한다.

하지만 Object 클래스의 clone() 메소드는 단지 필드의 값만을 복사하므로, 필드의 값이 배열이나 인스턴스면 제대로 복제할 수 없다.

따라서 이러한 경우에는 해당 클래스에서 clone() 메소드를 오버라이딩하여, 복제가 제대로 이루어지도록 재정의해야 한다.

 

2. String 클래스

C언어에서는 문자열을 char형 배열로 표현하지만, 자바에서는 문자열을 위한 String이라는 클래스를 별도로 제공한다.

String  클래스에는 문자열과 관련된 작업을 할 때 유용하게 사용할 수 있는 다양한 메소드가 포함되어 있다.

이러한 String 클래스는 java.lang 패키지에 포함되어 제공된다.

 

String 인스턴스는 한 번 생성되면 그 값을 읽기만 할 수 있고, 변경할 수는 없는데 이러한 객체를 자바에서는 불변 객체(immutable object)라고 한다.

즉, 자바에서 덧셈(+) 연산자를 이용하여 문자열 결합을 수행하면, 기존 문자열의 내용이 변경되는 것이 아니라 내용이 합쳐진 새로운 String 인스턴스가 생성된다.

 

1) charAt() 메소드

charAt() 메소드는 해당 문자열의 특정 인덱스에 해당하는 문자를 반환한다.

2) compareTo() 메소드

compareTo() 메소드는 해당 문자열을 인수로 전달된 문자열과 사전 편찬 순으로 비교한다.

3) concat() 메소드

concat() 메소드는 해당 문자열의 뒤에 인수로 전달된 문자열을 추가한 새로운 문자열을 반환한다.

4) indexOf() 메소드

indexOf() 메소드는 해당 문자열에서 특정 문자나 문자열이 처음으로 등장하는 위치의 인덱스를 반환한다.

5) trim() 메소드

trim() 메소드는 해당 문자열의 맨 앞과 맨 뒤에 포함된 모든 공백 문자를 제거한다.

String[] split(String regex) 해당 문자열을 전달된 정규 표현식(regular expression)에 따라 나눠서 반환함.
String substring(int beginIndex) 해당 문자열의 전달된 인덱스부터 끝까지를 새로운 문자열로 반환함.
String substring(int begin, int end) 해당 문자열의 전달된 시작 인덱스부터 마지막 인덱스까지를 새로운 문자열로 반환함.
String toLowerCase() 해당 문자열의 모든 문자를 소문자로 변환함.
String toUpperCase() 해당 문자열의 모든 문자를 대문자로 변환함.
String trim() 해당 문자열의 맨 앞과 맨 뒤에 포함된 모든 공백 문자를 제거함.
length() 해당 문자열의 길이를 반환함.
isEmpty() 해당 문자열의 길이가 0이면 true를 반환하고, 아니면 false를 반환함.

 

3. StringBuffer 클래스

String 클래스의 인스턴스는 한 번 생성되면 그 값을 읽기만 할 수 있고, 변경할 수는 없는 반면 StringBuffer 클래스의 인스턴스는 그 값을 변경할 수도 있고, 추가할 수도 있다.

이를 위해 StringBuffer 클래스는 내부적으로 버퍼(buffer)라고 하는 독립적인 공간을 가진다.

버퍼 크기의 기본값은 16개의 문자를 저장할 수 있는 크기이며, 생성자를 통해 그 크기를 별도로 설정할 수도 있다.

하지만 인스턴스 생성 시 사용자가 설정한 크기보다 언제나 16개의 문자를 더 저장할 수 있도록 여유 있는 크기로 생성된다.

StringBuffer 인스턴스를 사용하면 문자열을 바로 추가할 수 있으므로, 공간의 낭비도 없으며 속도도 매우 빠르며 java.lang 패키지에 포함되어 제공된다.

 

String 클래스와 같이 인스턴스가 한 번 생성되면 그 값을 변경할 수 없는 클래스를 불변 클래스(immutable class)라고 하고, StringBuffer 클래스와 같이 자유롭게 인스턴스의 값을 변경할 수 있는 클래스를 가변 클래스(mutable class)라고 한다.

String 클래스와 같은 불변 클래스는 StringBuffer 클래스의 append()나 insert() 메소드와 같이 값을 변경하는 set 메소드를 포함하지 않는다.

 

이렇게 불편하기만 할 것 같은 불변 클래스를 사용하는 이유는 멀티 스레드 환경에서 객체가 변화되는 상황이라면 불변 인스턴스를 사용하는 것이 좀 더 신뢰할 수 있는 코드를 작성할 수 있기 때문이다.

하나의 객체에 접근하면서 각각의 객체가 서로 영향을 주어서는 안 되는 경우에 불변 인스턴스를 사용하면 값이 변하지 않는다는 점이 보장된다.

 

1) append 메소드

append() 메소드는 인수로 전달된 값을 문자열로 변환한 후, 해당 문자열의 마지막에 추가한다.

이 메소드는 String 클래스의 concat() 메소드와 같은 결과를 반환하지만, 내부적인 처리 속도가 훨씬 빠르다.

2) capacity 메소드

capacity() 메소드는 StringBuffer 인스턴스의 현재 버퍼 크기를 반환한다.

3) delete 메소드

delete() 메소드는 전달된 인덱스에 해당하는 부분 문자열을 해당 문자열에서 제거한다.

deleteCharAt() 메소드를 사용하면 특정 위치의 문자 한 개만을 제거할 수도 있다.

4) insert 메소드

insert() 메소드는 인수로 전달된 값을 문자열로 변환한 후, 해당 문자열의 지정된 인덱스 위치에 추가한다.

 

4. Math 클래스

Math 클래스는 수학에서 자주 사용하는 상수들과 함수들을 미리 구현해 놓은 클래스다.

Math 클래스의 모든 메소드는 클래스 메소드(static method)이므로, 객체를 생성하지 않고도 바로 사용할 수 있다.

이러한 Math 클래스는 java.lang 패키지에 포함되어 제공된다.

 

1) random 메소드

random() 메소드는 0.0 이상 1.0 미만의 범위에서 임의의 double형 값을 하나 생성하여 반환한다.

이 메소드는 내부적으로 java.util 패키지의 Random 클래스를 사용한 의사 난수 발생기(pseudorandom-number generator)를 사용하여 임의의 수를 생성한다.

자바에서는 Math 클래스의 random() 메소드뿐만 아니라 java.util 패키지에 포함된 Random 클래스의 nextInt() 메소드를 사용해도 난수를 생성할 수 있다.

2) abs 메소드

abs() 메소드는 전달된 값이 음수이면 그 값의 절댓값을 반환하며, 전달된 값이 양수이면 전달된 값을 그대로 반환한다.

3) floor 메소드, ceil 메소드, round 메소드

floor() 메소드는 인수로 전달받은 값과 같거나 작은 수 중에서 가장 큰 정수를 반환한다.

또한, ceil() 메소드는 반대로 인수로 전달받은 값과 같거나 큰 수 중에서 가장 작은 정수를 반환한다.

round() 메소드는 전달받은 실수를 소수점 첫째 자리에서 반올림한 정수를 반환한다.

4) max 메소드, min 메소드

max() 메소드는 전달된 두 값을 비교하여 그중에서 큰 값을 반환하며, min() 메소드는 그중에서 작은 값을 반환한다.

5) pow 메소드, sqrt 메소드

pow() 메소드는 전달된 두 개의 double형 값을 가지고 제곱 연산을 수행한다.

반대로 sqrt() 메소드는 전달된 double형 값의 제곱근 값을 반환한다.

 

5. Wrapper 클래스

프로그램에 따라 기본 타입의 데이터를 객체로 취급해야 하는 경우가 있다.

예를 들어, 메소드의 인수로 객체 타입만이 요구되면 기본 타입의 데이터를 그대로 사용할 수는 없다.

이때에는 기본 타입의 데이터를 먼저 객체로 변환한 후 작업을 수행해야 한다.

자바의 기본 타입에 대응하여 제공하고 있는 래퍼 클래스는 다음과 같다.

byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

 

래퍼 클래스(Wrapper class)는 산술 연산을 위해 정의된 클래스가 아니어서 인스턴스에 저장된 값을 변경할 수 없다.

단지, 값을 참조하기 위해 새로운 인스턴스를 생성하고, 생성된 인스턴스의 값만을 참조할 수 있다.

위의 그림과 같이 기본 타입의 데이터를 래퍼 클래스의 인스턴스로 변환하는 과정을 박싱(Boxing)이라고 하고 래퍼 클래스의 인스턴스에 저장된 값을 다시 기본 타입의 데이터로 꺼내는 과정을 언박싱(UnBoxing)이라고 한다.

 

JDK 1.5부터는 박싱과 언박싱이 필요한 상황에서 자바 컴파일러가 이를 자동으로 처리해 주는데 이렇게 자동화된 박싱과 언박싱을 오토 박싱(AutoBoxing)과 오토 언박싱(AutoUnBoxing)이라고 부른다.

 

6. Enum 클래스

C언어와 C++에서는 열거체를 사용할 수 있지만 JDK 1.5 이전의 자바에서는 열거체를 사용할 수 없었다.

하지만 JDK 1.5부터는 C언어의 열거체보다 더욱 향상된 성능의 열거체를 정의한 Enum 클래스를 사용할 수 있다.

 

이와 같은 자바의 열거체는 다음과 같은 장점을 가집니다.

1) 열거체를 비교할 때 실제 값뿐만 아니라 타입까지도 체크한다.

2) 열거체의 상숫값이 재정의되더라도 다시 컴파일할 필요가 없다.

 

열거체를 정의하는 방법은 아래와 같다.

enum 열거체이름 { 상수1이름, 상수2이름, ... }
// enum Rainbow { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }

Enum 클래스는 모든 자바 열거체의 공통된 조상 클래스로 Enum 클래스에는 열거체를 조작하기 위한 다양한 메소드가 포함되어 있다.

1) values 메소드

values() 메소드는 해당 열거체의 모든 상수를 저장한 배열을 생성하여 반환한다.

이 메소드는 자바의 모든 열거체에 컴파일러가 자동으로 추가해 주는 메소드다.

2) valueOf 메소드

valueOf() 메소드는 전달된 문자열과 일치하는 해당 열거체의 상수를 반환한다.

static E values() 해당 열거체의 모든 상수를 저장한 배열을 생성하여 반환함.
static E valueOf(String name) 전달된 문자열과 일치하는 해당 열거체의 상수를 반환함.
protected void finalize() 해당 Enum 클래스가 final 메소드를 가질 수 없게 됨.
String name() 해당 열거체 상수의 이름을 반환함.
int ordinal() 해당 열거체 상수가 열거체 정의에서 정의된 순서(0부터 시작)를 반환함.

 

7. Arrays 클래스

Arrays 클래스에는 배열을 다루기 위한 다양한 메소드가 포함되어 있다.

Arrays 클래스의 모든 메소드는 클래스 메소드(static method)이므로, 객체를 생성하지 않고도 바로 사용할 수 있다.

이 클래스는 java.util 패키지에 포함되므로, 반드시 import 문으로 java.util 패키지를 불러오고 나서 사용해야 한다.

 

1) binarySearch() 메소드

binarySearch() 메소드는 전달받은 배열에서 특정 객체의 위치를 이진 검색 알고리즘을 사용하여 검색한 후, 해당 위치를 반환한다.

이 메소드는 이진 검색 알고리즘을 사용하므로, 매개변수로 전달되는 배열이 sort() 메소드 등을 사용하여 미리 정렬되어 있어야만 제대로 동작한다.

2) copyOf() 메소드

copyOf() 메소드는 전달받은 배열의 특정 길이만큼을 새로운 배열로 복사하여 반환한다.

copyOf() 메소드는 첫 번째 매개변수로 원본 배열을 전달받고, 두 번째 매개변수로 원본 배열에서 새로운 배열로 복사할 요소의 개수를 전달 받는다.

int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = Arrays.copyOf(arr1, 3);
for (int i = 0; i < arr2.length; i++) {
    System.out.print(arr2[i] + " ");
}
int[] arr3 = Arrays.copyOf(arr1, 10);
for (int i = 0; i < arr3.length; i++) {
    System.out.print(arr3[i] + " ");
}
// 1 2 3 
// 1 2 3 4 5 0 0 0 0 0

3) fill 메소드

fill() 메소드는 전달받은 배열의 모든 요소를 특정 값으로 초기화해 준다.

fill() 메소드는 첫 번째 매개변수로 초기화할 배열을 전달받고, 두 번째 매개변수로 초기값을 전달받는다.

4) sort() 메소드

sort() 메소드는 전달받은 배열의 모든 요소를 오름차순으로 정렬한다.

 

8. Calendar 클래스

JDK 1.0에서는 Date 클래스를 사용하여 날짜에 관한 간단한 처리만을 수행할 수 있었다.

하지만 Date 클래스는 현재 대부분의 메소드가 사용을 권장하지 않고(deprecated) 있다.

JDK 1.1부터는 새롭게 제공되는 Calendar 클래스를 이용하여 날짜와 시간에 관한 처리를 수행하게 된다.

하지만 이러한 Calendar 클래스는 다음과 같은 문제점을 안고 있다.

 

1. Calendar 인스턴스는 불변 객체(immutable object)가 아니라서 값이 수정될 수 있다.

2. 윤초(leap second)와 같은 특별한 상황을 고려하지 않는다.

3. Calendar 클래스에서는 월(month)을 나타낼 때 1월부터 12월을 0부터 11까지로 표현해야 하는 불편함이 있다.

 

따라서 많은 자바 개발자들은 Calendar 클래스뿐만 아니라 더 나은 성능의 Joda-Time이라는 라이브러리를 함께 사용해 왔다.

Java SE 8 버전에서는 이러한 Joda-Time 라이브러리를 발전시킨 새로운 날짜와 시간 API인 java.time 패키지를 제공한다.

java.time 패키지는 위와 같은 문제점을 모두 해결했으며, 다양한 기능을 지원하는 다수의 하위 패키지를 포함하고 있다.

 

Calendar 클래스는 자바에서 날짜와 시간에 관한 데이터를 손쉽게 처리할 수 있도록 제공하는 추상 클래스다.

이 클래스가 추상 클래스로 선언된 이유는 나라마다 사용하는 달력 체계가 조금씩 다를 수 있기 때문이다.

이러한 Calendar 클래스에는 날짜와 시간을 처리하기 위한 다양한 필드와 메소드가 포함되어 있고, Calendar 클래스의 모든 필드는 클래스 변수(static variable)이므로, 객체를 생성하지 않고도 바로 사용할 수 있다.

 

1) add 메소드

add() 메소드는 전달된 Calendar 필드에서 일정 시간 만큼을 더하거나 빼준다.

즉, 특정 시간을 기준으로 일정 시간 전후의 날짜와 시간을 알 수 있다.

2) before와 after 메소드

두 시간상의 전후 관계만을 알고 싶을 경우에는 before()와 after() 메소드를 사용할 수 있다.

before() 메소드는 현재 Calendar 인스턴스가 전달된 객체가 나타내는 시간보다 앞서는지를 판단한다.

반대로 after() 메소드는 현재 Calendar 인스턴스가 전달된 객체가 나타내는 시간보다 나중인지를 판단한다.

3) get 메소드

get() 메소드는 전달된 Calendar 필드에 저장된 값을 반환한다.

4) roll 메소드

roll() 메소드는 전달된 Calendar 필드에서 일정 시간 만큼을 더하거나 빼준다.

하지만 add() 메소드와는 달리 다른 Calendar 필드에는 영향을 주지 않는다.

즉, 계산 결과가 해당 필드의 최댓값이나 최솟값을 넘어가도 다른 필드에 영향을 주지 않는다.

반응형