JAVA

<JAVA 수업준비> 제네릭, wrapper, hashmap, arraylist

Technoqueen_X 2023. 12. 19. 00:19
728x90
반응형

<제네릭>

'데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법'이다.

생각해보자. 만약에 우리가 어떤 자료구조를 만들어 배포하려고 한다.
그런데 String 타입도 지원하고싶고
Integer타입도 지원하고 싶고
많은 타입을 지원하고 싶다.

그러면 String에 대한 클래스, Integer에 대한 클래스 등
하나하나 타입에 따라 만들 것인가?

그건 너무 비효율적이다.

이러한 문제를 해결하기 위해 우리는 제네릭이라는 것을 사용한다.

(정확히 말하자면 타입의 경계를 지정하고,
컴파일 때 해당 타입으로 만드는 것이다.)

제네릭이 뭐야?
-> 제네릭은 자바에서 객체의 타입을 지정하는 방법.
-> 컴파일 단계에서 자료형을 체크해주는 도구.

Generic(제네릭)의 장점. 왜 써야하나?

1. 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
2. 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다. 즉, 관리하기가 편하다.
3. 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.


제네릭을 사용하지 않는 경우 코드작성
 
문제가 뭘까?
 
-> 이 경우, 저장되는 객체의 타입을 하나하나 써줘야 한다. 귀찮고 불편함. 손이 많이 감.
 
제네릭을 사용하는 경우 코드작성
 
-> 이 때, 제네릭을 사용하면  여러 종류의 객체를 다룰 수 있는 유연한 코드를 작성할 수 있다. 제네릭을 이용하면 편리하게 String, Integer, Double 타입의 객체를 모두 저장할 수 있다.
 
-> 따라서 형변환을 줄여 코드 가독성을 높이기 위해서 제네릭을 사용한다.
 
이해하기 쉽게 정리하면,
제네릭은 옷장에 어떤 옷을 걸어둘지 미리 정하는 것과 같다.

옷장에 아무 옷이나 막 걸어두면, 옷을 찾을 때 꺼내보면서 옷의 종류를 하나하나 확인해야 한다.
하지만, 옷장에 걸어둘 옷의 종류를 미리 정해두면, 

예를 들어, 두껍고 무거운 패딩만 걸어놓겠다! 이런식으로 정해두면
옷을 찾을 때 옷의 종류가 패딩인지, 바지인지, 티셔츠인지 확인할 필요가 없다.

제네릭도 마찬가지.

제네릭을 사용하면 

객체의 타입을 미리 정해두는건데, 그렇게 함으로써
객체를 사용할 때 타입이 int인지 double인지 하나하나 확인할 필요가 없다.

이렇게 하면 코드가 더 간결하고 안전해진다.

 



 

<Wrapper>

wrapper가 뭐야?
-> wrapper는 기본형 데이터를 객체로 다루기 위한 클래스.

wrapper는 과자를 랩으로 감싸는 것과 같다.
과자 하나하나는 그냥 String b = 10; 같은거야.
만약 int a = 10; 얘랑 연산을 해주고 싶어.
그럼 이 무방비상태의 과자같은 문자열 타입 10을 랩으로 포장해
어떻게? Integer.parseInt(b) 이렇게
그러면 이 문자열타입10이 정수형으로 딱 포장되어서
int a = 10 하고 더하기 빼기 나누기 곱하기 쿵짜라 쿵짜 다 되지?

좀 더 자세히 설명해볼게.

 
왜 wrapper를 사용하는가?
-> 기본형 데이터만 사용할 때 생기는 제약을 해결하려고!!
 
우리는 일상생활에서 많은 수의 숫자를 사용한다.
예를 들어, 나이, 몸무게, 점수, 가격, 날짜 등등.

자바에서는 이러한 숫자를 표현하기 위해 기본형 데이터를 사용하는데,
기본형 데이터는 int, float, double, char, boolean 등이 있다.
 
그런데, 문제점이 뭐냐.
기본형 데이터를 다루는 데에는 몇 가지 제약이 있다.

1. 메소드를 사용할 수 없다.
기본형 데이터는 메소드를 사용할 수 없음.
예를 들어, int 타입의 기본형 데이터에 String 타입의 10을 더해서 20을 만들고 싶지만.
int는 숫자타입이고 String은 문자 타입이라
int 10 + String10 을 하면 1010 이렇게 찍힘.
 
그래서 형변환을 해줘야 함.
다음과 같이 코드를 작성해보자.

int a = 10;
String b = "10";
int c = a + Integer.parseInt(b);


이 코드는 Integer.parseInt() 라는 메서드를 사용하여 String타입의 문자 10을
int 타입의 숫자로 변환한 다음, int 타입의 숫자10과 더할 수 있게 만들어줌.

2.기본형 데이터는 null 값을 사용할 수 없다.

int a = null;

 
기본형 데이터는 null 값을 사용할 수 없다.
예를 들어, 위과 같은 코드는 컴파일 오류가 발생하지만

Integer a = null;

 
이렇게 Integer wrapper 클래스를 이용해 null값을 사용할 수 있다.
 
3. 객체로 다룰 수 없다. 
기본형 데이터는 객체로 다룰 수 없다. 예를 들어, 다음과 같은 코드는 컴파일 오류가 발생한다.

int a = 10;
System.out.println(a.toString());

 toString()은 객체의 정보를 문자열로 반환하는 메서드지?

이 코드가 컴파일 오류가 생기는 이유는 a가 int 타입이기 때문.
int 타입은 값형(primitive type)으로, 값을 직접 저장하기 때문에.
int 타입의 변수는 toString() 메서드를 사용할 수 없다.

toString() 메서드는 참조형(reference type)의 객체가 제공하는 메서드라
int 타입은 객체가 아니므로 toString() 메서드를 사용할 수 없는 것.

이 코드를 컴파일 오류 없이 실행하려면
int 타입의 변수를 Integer wrapper 클래스의 객체로 변환해야 함. 다음과 같이 코드를 수정해보자.

int a = 10;
Integer b = Integer.valueOf(a);
System.out.println(b.toString());

 
이러한 제약을 해결하기 위해 이런 식으로 wrapper 클래스를 사용하는 것.
 
wrapper 클래스는 기본형 데이터를 객체로 포장하여 제공한다.
 

int a = 10;
Integer b = Integer.valueOf(a);

 
1. a는 int 타입의 기본형 데이터이고, 값은 10.
2. b는 Integer wrapper 클래스의 객체.
3. Integer.valueOf(a)는 a의 값을 Integer wrapper 클래스의 객체로 만드는 메서드.
4. 따라서 b의 값은 a와 같고, 10.

정리하자면 wrapper 클래스는 다음과 같은 장점이 있어서 사용한다.

1. 메소드를 사용할 수 있다. 
wrapper 클래스는 기본형 데이터에 대한 다양한 메소드를 제공함.
예를 들어, Integer 클래스에는 parseInt(), toBinaryString(), compareTo() 등의 메소드가 제공됨.
이러한 메소드를 사용하면 기본형 데이터를 보다 편리하게 처리할 수 있다.

2. null 값을 사용할 수 있다.
wrapper 클래스는 null 값을 사용할 수 있다.
 
3. 객체로 다룰 수 있다.
wrapper 클래스는 객체로 다룰 수 있다.
wrapper 클래스를 사용하면 기본형 데이터를 보다 효율적이고 편리하게 사용할 수 있다.



 


 

<Hashmap>

해쉬맵이 뭐야?
-> HashMap은 키와 값을 한 쌍으로 저장하는 자료 구조.
여기서 포인트는 key는 유일해야 하며, value(값)은 어떤 값이든 될 수 있다.

HashMap<String, Integer> map = new HashMap<>();

map.put("apple", 100);
map.put("banana", 200);
map.put("orange", 300);

 
map은 HashMap 객체.
map.put("apple", 100)은 apple이라는 키에 100이라는 값을 저장.
map.put("banana", 200)은 banana이라는 키에 200이라는 값을 저.
map.put("orange", 300)은 orange이라는 키에 300이라는 값을 저장.
 
즉, 이 코드는 map이라는 HashMap 객체에 다음과 같은 데이터를 저장.
 

 
HashMap에서 키를 사용하여 값을 조회해보자.

int value = map.get("apple");
System.out.println(value); // 100

 
이 코드는 map 객체에서 apple이라는 키의 값을 조회하여 value 변수에 저장하는데,
value 변수의 값은 위에서 저장한 바와 같이 100이 된다.

HashMap은 키와 값을 빠르게 조회할 수 있는 자료 구조.
키로 값을 조회할 때, HashMap은 키를 해시 함수에 입력하여 해시 코드를 생성함.
해시 코드는 키에 대한 고유한 식별자로
HashMap은 해시 코드를 사용하여 값을 저장하는 공간을 찾는 거라고 보면 됨.
 
왜? 해쉬맵을 사용하는가?
자바에서 HashMap을 사용하는 이유는 다음과 같다.

조회 속도, 저장, 삭제속도가 빠름.
HashMap은 유일한 Key 값을 사용하여 데이터를 저장하기 때문에, Key 값으로 데이터를 조회할 때 속도가 매우 빠르다.
이는 리스트나 배열과 같은 자료구조에 비해 훨씬 빠른 속도임.



 


ArrayList란?
 
ArrayList는 자바의 컬렉션 프레임워크에서 제공하는 List 인터페이스를 구현한 클래스.
 
여기서 잠깐!
자바의 컬렉션 프레임워크란 간단하게 말해서 라이브러리 인데,
어떤 라이브러리이냐.
 
데이터를 저장하고 관리하기 위한 자료구조와 알고리즘을 제공하는 라이브러리 라고 보면 됨.
 
이 프레임워크는
데이터를 저장하는 여러 종류의 컬렉션 클래스를 제공하여
프로그래머가 효과적으로 데이터를 다룰 수 있도록 도움.
 
Java Collection Framework에는 주요한 인터페이스와 그를 구현한 클래스들이 있다.
 
List 인터페이스는 순차적으로 저장된 데이터를 다루는 자료 구조를 제공함.
 
ArrayList는 배열과 비슷한 특징을 가지고 있지만,
배열과는 달리 크기가 고정되어 있지 않음.
 
즉, 데이터가 추가되거나 삭제되면 ArrayList의 크기가 자동으로 조정되는 특징이 있음.
 
ArrayList의 장점
ArrayList는 다음과 같은 장점을 가지고 있다.

  • 크기가 가변적이므로, 데이터의 양에 따라 크기를 조절할 수 있다.
  • 다양한 메소드를 제공하여 데이터를 효율적으로 관리할 수 있다.
  • 인덱스를 사용하여 데이터를 관리하므로, 데이터를 빠르게 찾을 수 있다.

**ArrayList**는 내부적으로 배열(Array)을 이용하여 데이터를 관리함.
 
배열은 각 원소에 인덱스를 부여하고, 이 인덱스를 사용하여 원하는 위치에 빠르게 접근할 수 있는데,
이러한 특성으로 인해 **ArrayList**는 데이터를 빠르게 찾을 수 있는 장점이 있다.
 
간단한 예제를 보자.
아래는 **ArrayList**를 사용하여 문자열 데이터를 저장하고, 특정 인덱스의 데이터를 조회하는 예제.

javaCopy code
import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        // ArrayList 생성
        ArrayList<String> arrayList = new ArrayList<>();

        // 데이터 추가
        arrayList.add("Java");
        arrayList.add("Python");
        arrayList.add("C++");

        // 데이터 찾기
        int indexToFind = 1;
        if (indexToFind < arrayList.size()) {
            String foundData = arrayList.get(indexToFind);
            System.out.println("데이터 찾기: " + foundData);
        } else {
            System.out.println("인덱스가 범위를 벗어납니다.");
        }
    }
}

 
위 코드에서 **arrayList.get(indexToFind)**를 사용하여
특정 인덱스에 위치한 데이터를 빠르게 찾음.
 
배열은 인덱스를 이용한 직접 접근이 가능하기 때문에 데이터 조회 속도가 빠르다.
 
하지만 주의할 점은, 데이터를 추가하거나 삭제할 때마다 배열의 크기를 조절해야 하며
이 과정에서 복사 등의 작업이 필요할 수 있다.
 
따라서 **ArrayList**의 조회 속도는 매우 빠르지만,
데이터를 추가하거나 삭제하는 연산은 상대적으로 느릴 수 있다는 점을 참고.
 
ArrayList의 단점
ArrayList는 

  • 데이터의 순서가 유지되어야 하는 경우에는 적합하지 않을 수 있다.

ArrayList의 사용법
ArrayList를 사용하려면 먼저 다음과 같이 ArrayList 객체를 생성.

ArrayList<String> list = new ArrayList<>();

이렇게 생성된 ArrayList 객체에 데이터를 추가하려면 add() 메소드를 사용.

list.add("apple");
list.add("banana");

이렇게 하면 "apple"과 "banana"라는 문자열이 ArrayList에 추가됨.
데이터를 삭제하려면 remove() 메소드를 사용함.

list.remove(0);

이렇게 하면 ArrayList의 첫 번째 데이터인 "apple"이 삭제됨.
데이터를 검색하려면 get() 메소드를 사용.

String fruit = list.get(1);

이렇게 하면 ArrayList의 두 번째 데이터인 "banana"가 반환됨.

예제 코드
다음은 ArrayList를 사용하는 예제 코드.

import java.util.ArrayList;

public class ArrayListExample {

    public static void main(String[] args) {
        // ArrayList 객체 생성
        List<String> list = new ArrayList<>();

        // 데이터 추가
        list.add("apple");
        list.add("banana");
        list.add("orange");

        // 데이터 삭제
        list.remove(0);

        // 데이터 검색
        String fruit = list.get(1);

				// 인덱스를 이용한 데이터 검색
				int index = list.indexOf("banana"); 

        // 출력
        System.out.println("fruit: " + fruit);

				// 데이터 순회
				for (String fruit : list) { System.out.println(fruit); }
	}
}

이 코드는 다음과 같은 출력을 생성.

fruit: banana

728x90
반응형