🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [10]일차
🚀 10일차에서는 내부클래스와 제네릭, 컬렉션 프레임워크에 대해 배웠는데 처음 알거나 중요하게 알아야할 것 같은
개념들을 제 나름의 언어대로 적어내려가다보니 정리해야할 회고가 많아졌습니다.
내부클래스와 제네릭에 대해서 깊고 넓게 배우다보니 컬렉션 프레임워크라는 개념이 눈에 안들어왔는데
알고보니 컬렉션 프레임워크는 자료구조를 의미하고 그 안에 ArrayList는 자주 써봤던 자료구조였습니다.
ArrayList의 내부 구조가 어떻게 동작하는지는 몰랐던 저는 그림판으로 노드도 그려보고 내부 구조에 대해
좀 더 잘 알게된 시간이었습니다!👀
이처럼 제네릭과 컬렉션 프레임워크는 ArrayList<String> 처럼 알고리즘 문제를 풀 때 자료구조를 선언하고
사용만 한 것이 전부였는데 개념에 대해 정확히 알게되고, 왜 사용하는지 등을 배울 수 있게되어서
뜻깊은 시간이었습니다!🦁
<내부클래스>
Inner Class, 자바에서 클래스 내부에 또 다른 클래스를 정의
- 내부클래스는 외부클래스의 "멤버처럼 취급"
- 특별한 접근권한과 기능을 가짐
- 주로 외부클래스와 긴밀한 작업을 수행하기 위해 사용
- 접근성과 은닉성
내부클래스는 외부클래스의 필드와 메소드에 직접 접근 가능 = private도 접근가능
(💡내부클래스의 가장 큰 장점)
하지만 외부클래스는 내부클래스의 private멤버에 접근할 수 없어 은닉성 제공
- 코드의 조직화 관련 기능을 외부 클래스 안에 묶어두어 조직화하여 코드의 가독성과 유지보수성을 향상
<내부 클래스의 종류>
- 멤버 내부 클래스
- 로컬 내부 클래스
- 익명 내부 클래스
- 정적 내부 클래스
<내부클래스는 어떨때 사용할까>
- 내부 클래스 단독으로 쓰기보다 외부 클래스와 관련된 클래스이면
같이 캡슐화하여 하나의 묶음으로 보는 경우. (ex. 차 - 엔진 / 학교 - 학생 등) - 내부클래스의 사용 이유 : 주로 코드의 조직화, 캡슐화, 유지보수성 향상에 중점
- 클래스간의 결합도를 줄이고, 각 클래스의 책임을 명확히함
- 관련된 기능이나 로직이 내부클래스에 있어 외부클래스와의 연관성 증가
- 복잡한 클래스를 여러개의 내부클래스로 나누어 관리
<멤버 내부 클래스>
▶️예제 - 책 프로그램
public class Book {
private String title;
private Author author; // 내부클래스를 타입으로 사용
// Book 생성자 - 만들어질때, title과 Author객체를 가지고 생성되도록함
public Book(String title){
this.title = title;
this.author = new Author(); // Author가 클래스이기에 인스턴스로 생성되어야함
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
public void printBookDetails(){
System.out.println("[Book]");
System.out.println("TITLE : " + title);
System.out.println("AUTHOR : " + getAuthor().getName());
}
// 내부클래스 Author
public class Author{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
public class BookUser {
public static void main(String[] args) {
Book book = new Book("java");
book.getAuthor().setName("김자바");
book.printBookDetails();
}
}
<다른 클래스 예제>
public class OuterClassExam01 {
// 외부 클래스
private int outerField = 10;
private InnerClass innerClass;
public void outerMethod(){
InnerClass inner = new InnerClass();
// 내부클래스의 메소드에 접근
inner.innerMethod();
}
class InnerClass{
// 내부클래스
public void innerMethod(){
System.out.println("outerField에 직접 접근 : " + outerField);
}
}
}
- innerMethod에서 outerField를 내부클래스이어서 접근이 가능하다.
- 외부에 있는 다른 클래스이면 접근 불가능 - private
이처럼 멤버변수는
private InnerClass innerclass;
처럼 멤버로 사용된 것을 멤버 내부 클래스라 한다.
<로컬 내부 클래스>
- 외부 클래스 메소드의 내부에 정의되는 클래스
- 주로 특정 메소드에서만 사용되는 기능을 구현할때 사용 > 즉 멤버는 아님
- 메소드의 지역변수처럼 사용되어 메소드가 시작될때 생성되었다가 끝날때 사라지는 특징
private int outerField = 10;
public void outerMethod(){
// 메소드 내에 클래스 선언 (=로컬 내부 클래스)
class InnerClass{
public void innerMethod(){
System.out.println("[내부클래스 메소드] outerField : " + outerField);
}
}
// 메소드 내에 클래스 정의 (=로컬 내부 클래스)
InnerClass inner = new InnerClass();
inner.innerMethod();
}
public static void main(String[] args) {
LocalOuterClass outerClass = new LocalOuterClass();
outerClass.outerMethod();
}
사용범위가 “메소드 내”로 한정되므로 클래스의 사용범위를 엄격하게 제한하고자할때 사용
<정적 내부 클래스>
- static을 붙이는 것 = 외부 클래스의 정적 멤버처럼 취급
- 외부클래스의 인스턴스와 독립적으로 존재가능
- 주로 외부클래스와 관련된 공통된 유틸리티, 헬퍼클래스로 사용
public class StaticOuterClass {
private int outerField = 10;
private static int staticOuterField = 20;
public void outerMethod(){
}
// 독자적으로 동작함
static class InnerClass{
public void innerMethod(){
// 접근불가, InnerClass는 static하지만, outerField는 static하지 않다. (private)
// System.out.println("[내부클래스] outerField : " + outerField);
// 접근가능, InnerClass는 static하고, staticOuterField도 static하기때문
System.out.println("[내부클래스 메소드] staticOuterField : " + staticOuterField);
}
public static void staticInnerMethod(){
System.out.println("[Static 내부클래스 메소드]");
}
}
public static void main(String[] args) {
// 정적 내부 클래스는 이렇게 외부클래스로 직접 접근해서 인스턴스 생성
StaticOuterClass.InnerClass inner = new StaticOuterClass.InnerClass();
inner.innerMethod();
// static한 클래스, 메소드는 이렇게 직접 접근으로 사용 가능
StaticOuterClass.InnerClass.staticInnerMethod();
}
}
- StaticOuterClass.InnerClass 처럼 생성하면 StaticOuterClass도 생성이 된 것이 아니다.
(InnerClass만 생성한 것) 따라서 InnerClass에서 outerField에 접근이 불가능하다.
<내부 인터페이스>
- 클래스 안에 클래스가 들어올 수 있듯
- 인터페이스 안에 인터페이스가 들어올 수도 있다.
- 클래스 내에서 인터페이스가 구현될 수도 있다.
public class SmartPhone{
public interface Camera{
void takePhoto();
}
public class BasicCamera implements Camera{
@Override
public void takePhoto() {
System.out.println("[인터페이스의 추상메소드 재정의] : 사진을 찍습니다.");
}
}
public void activateCamera(){
// 인터페이스 타입이지만, 인스턴스 생성은 인터페이스를 구현하는 클래스로
Camera camera = new BasicCamera();
camera.takePhoto();
}
public static void main(String[] args) {
SmartPhone smartPhone = new SmartPhone();
smartPhone.activateCamera();
}
}
<익명 객체>
- Anonymous Object : 이름 없이 선언되고 생성되는 객체
- 주로 일회성 작업
- 간결한 코드작성, 주로 인터페이스와 추상클래스의 구현 또는 확장에서 사용
- 인터페이스나 추상클래스를 기반으로 생성하므로
인터페이스나 추상클래스의 모든 추상 메소드를 구현해야함
인터페이스명 객체명 = new 인터페이스명(){
// 인터페이스의 메소드 구현
};
추상클래스명 객체명 = new 추상클래스명(){
// 추상클래스의 메소드 구현
};
익명객체는 선언과 동시에 사용, 바로 사용됨
이 객체들은 참조변수에 할당될 수 있고, 메소드의 인자로 직접 전달도 가능
▶️예제 - 익명 객체 활용 : 아침인사 프로그램
// 인사말 출력 메소드 (인터페이스)
public interface Greeter {
void greet();
}
public static void main(String[] args) {
// 익명 객체를 사용하여 Greeter 인터페이스를 구현
Greeter morningGreeter = new Greeter() {
@Override
public void greet() {
System.out.println("좋은 아침입니다.");
}
};
Greeter eveningGreeter = new Greeter() {
@Override
public void greet() {
System.out.println("좋은 저녁입니다.");
}
};
morningGreeter.greet();
eveningGreeter.greet();
}
<자주 사용하는 클래스>
<java.lang에서 자주 사용하는 클래스>
import 하지 않아도 사용가능한 java.lang패키지
1. Object
- 자바에서 모든 클래스의 조상 = 모든 자바 객체가 기본적으로 상속받음
- 가장 기본적인 메소드들 (toString(), equals(), hashCode()) 등을 제공
- toString() : 객체의 문자열 표현을 반환
- getClass(): : 객체의 런타임 클래스 반환
// 1. getClass() : 객체의 런타임 클래스를 반환, 패키지와 클래스 이름 출력
System.out.println(exam01.getClass());
- clone() : 객체의 복제본 생성, Cloneable 인터페이스를 구현 객체의 주소값만 복사하기때문에
Cloneable인터페이스를 재정의하여 객체를 잘 복사할 수 있도록 만들 수 있음 - wait(), notify(), notifyAll() : 스레드 동기화 관련 메소드들
2. String
- 이 객체는 불변 (Immutable), 문자열의 연결, 비교, 추출과 같은
다양한 문자열 처리 기능을 제공 - 문자열 리터럴은 자동으로 String객체로 관리
- 부분 문자열 : substring() : 문자열의 특정 부분 추출
- 문자열 비교 : equals(), equalsIgnoreCase() : 문자열 비교
- concat() : +연산자 혹은 이 메소드로 문자열을 연결 가능
- indexOf() 및 lastIndexOf() : 문자열 내부에서 특정 문자나 문자열의 위치를 찾을 수 있음
- 문자열 변환 및 처리 : toUpperCase(), toLowerCase(), trim()
<String : StringBuilder / StringBuffer>
- 불변 클래스 String가 메모리 상에서 String객체를 계속 만들어내지 않고
StringBuilder, StringBuffer의 도움을 받아 String으로 변환해서 사용하는 방법
"동기화 되었다"의 의미는 특정 객체가 사용되고있으면 다른 객체는 기다린다.
동시에 사용이 불가능한 것이다.
멀티 스레드에서 똑같은 객체를 가리키는 경우
1번스레드에서 바꿔버리고, 아직 1번이 모두 수행이 안되었는데 2번이 수행돼버리면 안되기때문이다.
따라서 동기화되었다는 것은 다른 스레드가 기다려야하므로 속도가 느릴 수 있다는 것이다.
조금 느리지만, 멀티스레드 환경에서는 안전하게 사용될 수 있다.
만약 스레드가 여러개가 일때 동시에 접근을해도 스레드가 작업 중인 내용을
다른 스레드가 값을 가져가는 등의 상황을 방지
StringBuilder와 StringBuffer 클래스는
- 변경가능한 (Mutable) 문자열을 처리한다.
- 두 클래스 모두 문자열을 추가 ,삭제, 수정하는 등의 작업을 효율적으로 수행 가능
- String객체는 매번 새로운 객체를 만들어내므로 동적으로 값이 변할 수 있다.
따라서 많은 String값을 써야할때는 StringBuilder나 StringBuffer를 쓰는 것이 좋다. - StringBuilder
"동기화되지않아" 성능이 빠르고, 단일 스레드 환경에서 적합, 멀티스레드 환경에서는 안전하지 않음 - StringBuffer
멀티스레드 환경에서 안전하도록 “동기화되어있음” 이로인해 StringBuilder보다는 성능이 느릴 수 있다.
▶️예제 - StringBuilder / StringBuffer
// 1. 일반 String 다루기
String str1 = "Java";
String str2 = "JAVA";
System.out.println(str1.equals(str2)); // 대소문자를 구분하여 비교
System.out.println(str1.equalsIgnoreCase(str2)); // 대소문자를 구분하지 않고 비교
String str = "hello";
str = str.concat(" world ");
// 2. StringBuilder 선언
StringBuilder sb = new StringBuilder("hello");
System.out.println("StringBuilder 기본문자열 : " + sb);
// StringBuilder의 append() : 문자열 끝에 데이터를 추가
sb.append(" world");
System.out.println("StringBuilder append() : " + sb);
// StringBuilder의 insert() : 특정 위치에 데이터 추가
sb.insert(3, "<INSERT>");
System.out.println("StringBuilder insert() : " + sb);
// StringBuilder의 delete() : 6~11까지의 순서를 뒤집음
sb.delete(6, 11);
System.out.println("StringBuilder delete() : " + sb);
// StringBuilder의 reverse()
sb.reverse();
System.out.println("StringBuilder reverse() : " + sb);
// StringBuilder의 toString() : 문자열 객체의 문자열 표현 반환
sb.toString();
System.out.println("StringBuilder toString() : " + sb);
// 문자열 (+)연산자로 연결
System.out.println("qwer" + 122 + 'a' + sb + "!!");
// StringBuilder 테스트메소드 호출
System.out.println("StringBuilder [TEST] toString() : " + test(str));
// 3. StringBuffer - StringBuilder와 거의 유사하게 사용된다.
StringBuffer buffer = new StringBuffer("Hello");
buffer.append(" hi!!");
System.out.println(buffer);
<Math>
- 수학적 연산과 관련된 메소드를 제공하는 유틸리티 클래스
- 최대값 max(), 최소값 min(), 제곱 pow(), 제곱근 sqrt(), 절대값 abs()
- 삼각함수 : sin, cos, tan
- 로그및 지수 함수 : 자연로그 log, 밑이 10인 로그 log10, 지수 exp
- 랜덤 수 생성 : random()메소드로 0.0~1.0 사이 난수 생성
- 반올림 및 정수 변환 : round(), ceil(), floor() : 숫자를 반올림하거나 특정방향으로 정수 변환
<System>
- 표준 입출력, 오류스트림 접근, 현재 시간 측정 등
- System.out.println() 메소드 등
<Throwable>
- 자바에서 모든 에러와 예외의 최상위 클래스
- Exception과 Error클래스의 상위클래스
- 사용자 정의 예외를 만들때도 Throwable 클래스를 상속받아 구현
- getMessage() : 예외에 대한 설명 반환
- printStackTrace() : 예외가 발생한 호출 스택의 추적 정보를 출력
- getCause() : 예외의 원인을 반환
- initCause(Throwable cause) : 예외의 원인을 설정
<java.util에서 자주 사용하는 클래스>
❓Date
- 날짜와 시간을 나타냄
- 특정 시점을 밀리초 단위로 표현
- 오래된 API로 시간대 처리가 불편하고 설계상 문제가 있어
java 8 이후에는 java.time 패키지의 새로운 날짜 및 시간 API를 사용하는 것이 권장
// 지정된 날짜를 출력, 년도, 달에서 오류발생
Date myBirthday = new Date(2025, 12, 23);
System.out.println(myBirthday);
// 현재 시간을 출력 (toString() 되어있음)
Date now = new Date();
System.out.println(now);
// after() : now객체가 다른 myBirthday객체보다 이후인지 검사
System.out.println(now.after(myBirthday));
// before() : now객체가 다른 myBirthday객체보다 이전인지 검사
System.out.println(now.before(myBirthday));
Date API를 가보면 메소드들에 Deprecated 되어있는 것을 확인할 수 있는데
사용을 추천하지 않음. (=없어질 기능이다 Deprecated)라고 알려주는 표시
❓Calendar
- Calendar는 추상클래스 = 추상클래스는 인스턴스 생성이 되지 않음
- 따라서 자손클래스인 GregorianCalendar를 사용하는데
그렇다고 꼭 GregorianCalendar를 사용하라고 명시하진 않음 - 날짜와 시간을 나타내고 조작하는 데 사용 Date클래스보다 유연
- 추상클래스로 구체적인 달력 시스템을 제공하는 서브클래스를 통해 사용된다. (ex. GregorianCalendar)
- 년, 월, 일, 시간 등을 개별적으로 설정하거나 가져오는 메소드 제공
- Calendar의 필드들이 static으로 “상수”로 정의됨. 타입은 int형
사용자로하여금 상수를 제공해서 DAY_OF_MONTH, DAY_OF_WEEK 등으로 쉽게 사용 가능
그 예제
System.out.println(calendar.get(Calendar.YEAR));
DAY_OF_MONTH로 사용자에겐 필드의 이름이 표시가 되고, 값은 상수로 가지고 있으므로 그 값을 출력
- 1번 방법) getInstance()를 제공하기때문에 인스턴스를 만들어서 사용 가능
// 1) getInstance()로 Calendar인스턴스를 생성하는 방법
Calendar calendar = Calendar.getInstance();
- 2번 방법) 자식 클래스로 인스턴스를 만든 예 (Gregorian Calendar 사용)
// 2) 추상클래스인 Calendar클래스의 자식클래스인 GregorianCalendar로 인스턴스를 생성하는 방법
Calendar gregorian = new GregorianCalendar();
❓Calendar클래스가 getInstance() 사용을 권장하는 이유
💡만약 GregorianCalendar가 아닌 다른 Calendar를 구현했을때
GregorianCalendar으로 사용되고 있던 코드들은 모두 수정해야한다.
하지만 getInstance()로 쓰고 있는 사용자는 수정할 필요가 없다.
또한 결합도가 낮아질 수 있는 장점이 있다.
결국 getInstance()를 하더라도 Calendar는 추상클래스이므로
자식클래스인 GregorianCalendar를 생성해서 주게되므로 getInstance()사용을 권장한다.
<Calendar클래스의 MONTH 사용 예제>
System.out.println("현재는 [" + calendar.get(Calendar.MONTH) + "]월 입니다.");
- calendar.get(Calendar.MONTH);
➡️11이 출력
월 데이터를 0월~11월까지 관리하고 있으므로, 0월이 1월인 것을 알아야함
따라서 ‘월 데이터’를 가져올때는 +1 을 해주고, ’월 데이터’를 넣을때는 -1 을 해줘야한다. - 이러한 방법은 번거롭기때문에
- 이러한 식으로 경고를 표현하고, ‘월 데이터’를 영문으로 표기하라는 추천이 표시
예를들어 2000년 4월 12일을 출력하고 싶을때는 밑의 코드처럼 넣어줘야한다.
calendar.set(2000, 3, 12);
<java.time에서 자주 사용하는 클래스>
- 자바 8에서 소개된 java.time패키지
- 날짜와 시간을 다루는 “새로운 API”를 제공
- 기존 java.util.Date, java.util.Calendar에 비해 향상된 시간대 처리와 불변 객체를 제공
날짜와 시간을 더 쉽고 안전하게 사용 가능
- LocalDate : 날짜를 나타내는 연, 월, 일 정보 (ex.생년월일 , 특정날짜 표현)
- LocalTime : 시간을 나타내는 시, 분, 초, 나노초 정보 (ex.일과 시간이나 특정 시각)
- LocalDateTime : 날짜와 시간을 모두 표현
LocalDate와 LocalTime의 조합 시간대 정보 없이 날짜와 시간 표현 (ex. 일정관리, 이벤트 로깅) - ZonedDateTime : 전세계의 다양한 시간 표현 (여러 국가의 서비스에 맞는 지역시간대 표시)
- now()
- of(LocalDateTime, ZoneId) : 주어진 LocalDateTime과 시간대를 사용하여 ZonedDateTime 객체를 생성
- withZoneSameInstant(ZoneId) : 다른 시간대로 시간을 변환
- format(DateTimeFormatter) : 지정된 포맷터를 사용해 날짜와 시간을 문자열로 변환
▶️실습 - java.time패키지의 API들
LocalDate date = LocalDate.now();
System.out.println("현재 날짜 : " + date);
LocalTime time = LocalTime.now();
System.out.println("현재 시각 : " + time);
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("현재 날짜,시각 : " + dateTime);
ZonedDateTime zoned = ZonedDateTime.now();
System.out.println("현재 지역과 날짜, 시각 : " + zoned);
//포맷팅해서 날짜값 출력
// ofPattern() : 패턴을 내가 정할 수 있다. YYYY-MM-DD 등의 형식
// Month는 MM으로 대문자, minute는 mm으로 소문자를 사용
// Hour는 대문자로 되어있을때 14시로 표현, 소문자로 되어있을때는 02시로 표현
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
System.out.println("Formatter사용 : " + zoned.format(formatter));
// 연도를 2자리 및 소문자 시간 출력
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yy-MM-dd hh z");
System.out.println("Formatter2사용 : " + zoned.format(formatter2));
// 뉴욕의 시간대를 출력 (포맷팅 설정)
ZonedDateTime nowInNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
DateTimeFormatter newYorkFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss z");
System.out.println("현재 [뉴욕] 시각 : " + nowInNewYork.format(newYorkFormatter));
이처럼 포맷팅을 활용할 수 있다.
👀 실습 - stream()을 활용하여 ZoneId 정보 출력하기
// 각 나라의 Zone ID를 알아내기
ZoneId.getAvailableZoneIds()
.stream()
.sorted()
.forEach(System.out::println);
// 아시아 지역의 Zone ID를 알아내기
ZoneId.getAvailableZoneIds()
.stream()
.filter(zone -> zone.startsWith("Asia"))
.sorted()
.forEach(System.out::println);
- getAvailableZoneIds를 보면 Set으로 리턴해주고 있기때문에
- Set을 출력해주어도 될 것이다.
// Set에 값들을 담아서 출력
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for(String zone : zoneIds){
System.out.println(zone);
}
<Duration>
- 두 시간 사이의 지속기간 (시간 간격 측정이나 지연시간 설정)
- Duration.ofseconds()
- Duration.ofMinutes()
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 0);
Duration duration = Duration.between(start, end);
System.out.println("근무 시간 : [" + duration.toHours() + "]시간");
<Period>
- 두 날짜 사이의 기간 (두 날짜 사이의 날짜수를 계산하거나 연간 이벤트 기획)
- Period.ofDays()
- Period.ofMonths()
- between() : 두 날짜나 시간사이의 간격을 Duration이나 Period 객체로 반환
LocalDate start = LocalDate.of(2020, 1, 1);
LocalDate end = LocalDate.now();
Period period = Period.between(start, end);
System.out.println("2020년 1월 1일부터의 기간 : [" + period.getYears() + "]년 ["
+ period.getMonths() + "]개월 [" + period.getDays() + "]일");
<제네릭 Generic>
- 자바 5에서 추가된 문법, 타입을 파라미터로 사용한다
- 클래스를 정의할때 구체적인 타입을 지정하지 않고, <T>, <V>, <I> 등 임의로 지정
- 객체를 생성할떄 타입을 지정가능
- 타입의 안전성을 높이고, 불필요한 캐스팅을 방지 (=불필요한 형변환 방지)
- 컴파일 시점에 타입을 체크할 수 있음
- 제네릭을 사용하지 않으면, 객체를 컬렉션에 저장하거나 꺼낼때
매번 타입변환을 하여 잘못된 타입변환이 이루어질수 있음.
이렇게 되면 런타임에 ClassCastException을 발생될 수 있음 - 코드의 재사용성을 높임 (타입만 바꿔주면 됨)
<제네릭이 필요한 이유>
ArrayList objectlist = new ArrayList();
objectList.add(”test”);
objectList.add(”test1”);
objectList.add(”test2”);
objectList.get(0)
- 0번째의 값을 꺼내고 싶을때 String타입들에서 쓰이는 toUpperCase와 같은 메소드를 사용할 수 없음
objectList.get(0).toUpperCase(); // (불가능)
- 이 메소드를 쓰기 위해서는 (String)으로 형변환 필요
((String)objectlist.get(0)).toUpperCase(); // 가능
- objectlist라는 컬렉션에 아무거나 넣었지만, 대체로 같은 타입의 데이터를 넣는다.
- 그럼에도 Object타입으로 넣다보니 데이터를 사용할때마다 각각 매번 형변환을 해야함
이를 위해 해결방법이 이 컬렉션을 정의할때부터 < > 안에 타입을 정의해서 넣는 것
ArrayList<String> genericList = new ArrayList<>(); // 제네릭을 의미
▶️예제 - 1번 방법) 일반 Object타입을 사용한 형변환
// 모든 객체를 담을 수 있는 ObjectBox 예제
public class ObjectBox {
Object item;
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
public static void main(String[] args) {
ObjectBox box = new ObjectBox();
// 다양한 데이터가 들어갈 수 있음
box.setItem(new Pen());
box.setItem(new PeriodExam());
// 이 아이템을 쓰기위해선 형변환해주어야함
( (Pen)box.getItem() ).write();
// 사실 이 아이템은 Pen타입으로 나와있겠지만,
// setItem Setter메소드를 보면 Object타입으로 쓰임을 확인할 수 있음
}
}
class Pen{
public void write(){
System.out.println("펜이 씁니다.");
}
}
- 이렇게 Object들을 담는 Box는 꺼낼때마다 형변환을 해야 그 타입의 메소드를 사용가능
public class PenBox {
Pen pen;
public Pen getPen() {
return pen;
}
public void setPen(Pen pen) {
this.pen = pen;
}
public static void main(String[] args) {
PenBox penBox = new PenBox();
penBox.setPen(new Pen());
penBox.getPen().write(); // 형변환이 필요없다.
}
}
- Pen타입만 담는 PenBox는 꺼낼때 바로 그 타입의 메소드를 사용 가능하다.
▶️예제 - 2번 방법) Generic타입을 사용
public class GenericBox<T> {
T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
- < > 안에 T, A, I 등 임의의 영어 대문자로 설정
- 임의의 타입의 이름을 미리 지정한다. 이 타입은 아직 존재하는 타입이 아닌 것이다.
- 이 타입은 GenericBox를 만들어서 사용할때 그 타입이 존재하게 된다.
GenericBox<String> genericBox = new GenericBox<String>();
- Generic타입을 선언할때는
- < >안에 원하는 타입을 넣어서 생성
- 그렇다면 String타입이 들어왔으므로 item은 String타입이므로 toUpperCase()와 같은 메소드를 사용가능
- Pen타입이 들어왔으면 item은 Pen타입이므로 write()와 같은 메소드를 사용가능
- 매번 형변환을 해줄 필요없이 제네릭으로 타입을 감싸주기만 하면 그 타입으로 사용가능하게되는 것이다.
<GenericBox 전체코드>
public class GenericBox<T> {
T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
public static void main(String[] args) {
GenericBox<String> stringGenericBox = new GenericBox<>();
stringGenericBox.setItem("[String] Generic");
System.out.println(stringGenericBox.getItem().toUpperCase());
GenericBox<Pen> penGenericBox = new GenericBox<>();
penGenericBox.setItem(new Pen());
penGenericBox.getItem().write();
}
}
<제네릭과 컬렉션프레임워크>
- 제네릭은 컬렉션프레임워크에서 가장 잘 쓰이고 있다고 볼 수 있다.
- 데이터를 담아야하기때문에 컬렉션이 생성될때 제네릭에게 알려준다.
- String타입으로 받으면 String타입의 공간을 만들어달라고 요청한다.
<제네릭이 늦게 도입된 이유>
- 호환성 유지 자바는 초기버전에 강력한 하위호환성을 목표로 삼았기에
제네릭을 도입할때 기존코드와의 호환성을 유지했어야함
<제네릭 인터페이스>
- 제네릭 인터페이스도 클래스와 유사하게 타입 파라미터를 가짐
- interface Comparable<T> : T 타입 객체를 비교하는 인터페이스 정의
- 이렇게 인터페이스를 제네릭으로 만들면 다른 클래스에서 구현될때 구체적인 타입을 지정해야함 (강제성)
class MyComparableClass implements Comparable<String>
<멀티 타입 파라미터>
- 클래스나 인터페이스는 둘 이상의 타입 파라미터를 가질 수 있음
class GenericPair <K, V>
GenericPair<Integer, String> myPair = new Pair<>();
- 사용시에는 이처럼 사용
<바운디드 타입 파라미터>
class NumericBox<T extends Number>
- Number클래스의 서브클래스만을 타입 파라미터로 가질 수 있음
- NumericBox에는 Number또는 그 서브 클래스의 인스턴스만 저장될 수 있음
<제네릭 메소드>
- 다양한 타입의 객체를 동일한 방식으로 처리할 수 있는 메소드
public <T> void genericMethod(T data){
// 구현
}
▶️실습 - GenericMethod : 다른 타입의 값이 입력되었을때 제네릭 메소드에서 처리
public static <T> void printArrayElements(T[] array){
for(T element : array){
System.out.println(element);
}
}
public static void main(String[] args) {
// Integer배열
Integer[] intArray = {1, 2, 3, 4, 5};
printArrayElements(intArray);
String[] stringArray = {"Hello", "World", "Java"};
printArrayElements(stringArray);
}
<컬렉션 프레임워크>
ArrayList
- 배열은 고정 길이
- 이 리스트는 가변 길이 (길이가 필요시 늘어남, 추가 시 자동으로 늘어남)
- 순서대로 저장하고, 중복을 허용 그렇지만 연속된 구조로 담기는 것이 아닌
여기저기 데이터들이 담겨있다 = 이렇게 여기저기 흩어진 데이터를 “노드”라고 함 - 참조변수가 이 자료구조를 가리키고,
“노드”자체적으로 값이 저장되는 영역과 다음의 노드, 이전의 노드가 누군지 표현하는 주소값을 가진 영역이 존재
- 노드 끼리 서로를 가리키고 있는 구조
- 값이 하나가 추가되더라도 list.add(”값”)
- 이렇게 가변적으로 추가해줄 수가 있음
<ArrayList의 용량>
ArrayList<String> capacityList = new ArrayList<>(5);
- 처럼 ArrayList의 용량을 지정해줄 수도 있다.
<ArrayList 값 꺼내기 - get() 메소드>
System.out.println(strList.get(0));
// 1. 리스트의 값을 forEach문으로 꺼내기
for(String x : strList){
System.out.println(x);
}
- 반복문을 통해 꺼낼 수도 있다.
<ArrayList 값 넣기 - add() 메소드>
ArrayList<Integer> intList = new ArrayList<>();
intList.add(1); // 이 1은 객체 or 기본데이터타입
- 기본데이터타입 [ 1 ] 이 ArrayList에 들어갈 수 있는지를 알아야한다.
intList.add(Integer.valueOf(1));
- 내부적으로는 Interger.valueOf()를 통해 값이 들어가고 있어야한다.
- Java 5부터 추가된 오토박싱 이라는 기능이 이를 도와주고 있다.
<오토박싱>
- 기본데이터타입대신 Wrapper클래스로 존재하는 객체들이 들어가야하는 경우
- 오토박싱 등장 이전 프로그래머가 직접 Wrapper클래스로 형변환하여 넣어주었던 것을
오토박싱이라는 기능을 통해 컴파일러가 자동으로 대신 기능함 - 만약 int형으로 정의된 메소드에 Integer라는 값이 들어가는 중이면
→ 오토박싱 / 오토언박싱을 컴파일러가 자동으로 대신해주고 있음을 알아야함
// Java 5 오토박싱 이전
Integer i = 10; // 에러
int ivalue = i.getValue(); // 이렇게 넣어주었어야함
// Java 5 오토박싱 이후
Integer i = 10; // 오토박싱
int ivalue = i; // 오토언박싱
- 오토박싱 등장으로 알아서 넣기 가능
ArrayList<Integer> intList = new ArrayList<>();
intList.add(1); // 이 1은 기본데이터타입 (오토박싱)
intList.add(Integer.valueOf(2)); // 오토박싱이 되지 않은 예
intList.add(3); // 오토박싱
// toString()을 오버라이딩했기때문에 보기편하게 출력
System.out.println(intList);
- ArrayList에서 toString()가 오버라이딩되어 사용자가 보기편하게 출력이된다.
💡 10일차에서는 개념을 따라가기가 어려울 정도로 배운 내용이 많았습니다.
막상 일반적으로 깊게 생각하지 못하고 썼던 클래스들이나 기능들이 다 개발된 이유가 있었던 개념들이었고,
그것들이 모두 자바의 객체지향과 연관이 있다는 것을 알게되었습니다.
더 많은 실습을 해볼 수 있으면 좋겠지만 오늘의 회고에서는 개념을 정리하는 것으로 머릿속을 정리해야겠다는
생각을 했습니다! 🚀
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_12일차_'MySQL+쿼리' (1) | 2024.12.17 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_11일차_'데이터베이스' (1) | 2024.12.16 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_8일차_'인터페이스와 예외처리' (0) | 2024.12.11 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_7일차_'String클래스와 추상클래스' (0) | 2024.12.10 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_6일차_'상속과 메소드 오버라이딩' (1) | 2024.12.09 |