Generics / Generic

 

클래스 맴버의 타입을 컴파일 시에 정의하는 방법이다.

사용가능한 데이터의 타입을 제한한다.

특정타입으로 데이터를 쓸 수 있게 해준다.

타입의 안정성 → 형변환 불필요

 

 

파일 생성 시 이름 : Box<T>

public class Box<T> {
	// 클래스명 -> 원시타입이라고 한다
	// <T> -> 제네릭스타입
}

 

제네릭타입 사용 전

// 일반적인 사용 
// 타입을 정한 상태

	String item;
	
	void setItem(String item) {
		this.item = item;
	}
	
	String getItem() {
		return item; 
	}

 

제네릭타입 사용

// 제네릭클래스 
// 타입이 정해지지 않은 상태

	T item;
	
	void setItem(T item) {
		this.item = item;
	}
	
	T getItem() {
		return item; 
	}

 

BoxTest에서 타입을 지정해 제네릭클래스를 사용하는 예시

public class Box<T> {
	// 클래스명 -> 원시타입이라고 한다
	// <T> -> 제네릭스타입
	
	// 제네릭스(generic)
	// 클래스 맴버의 타입을 컴파일 시에 정의하는 방법 
	// 미리 타입을 지정하는 게 아니라 필요할 때 타입을 정의함.
	// 타입의 안정성을 주기 위해 사용
	// <T> (제네릭스 타입) 은 참조형만 가능 = class 타입만 가능
	
	
	// 제네릭클래스 
	// 타입이 정해지지 않은 상태
	T item;
	
	void setItem(T item) {
		this.item = item;
	}
	
	T getItem() {
		return item; 
	}
}
public class BoxTest {

	public static void main(String[] args) {
		// Box 클래스를 생성
		// 타입을 지정
		Box<String> box = new Box<String>();
		
		box.setItem("Hello");
		System.out.println(box.getItem()); // Hello

	}

}

 

 

Box 클래스에서 만든 제네릭타입을 ArrayListTest 에서 사용

import java.util.ArrayList;

public class Box<T> {
	ArrayList<T> list = new ArrayList<T>();
}
import java.util.ArrayList;

public class ArrayListTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ArrayList<String> list = new ArrayList<String>();

		list.add("1");
		list.add("2");
		list.add("3");

		// 타입 불일치
		// list.add(new Integer(1)); // 타입제한이 생겨 오류가 남

		for (int i = 0; i < list.size(); i++) {
			System.out.println(list.get(i)); // 1, 2, 3
		}

	}

}

 

형변환을 할 필요가 없다!

Test 클래스들에서 이렇게 타입을 지정해주는 것을 ‘구체화(Specialization)’라고 한다.

int형, double형 받을 수 없음.

→ Integer형, Double형 가능

Integer형이어도 자바의 오토박싱과 언박싱 덕분에 연산 시 기본형(int) 로 변환된다.

 

 

 

 

박스에 물건을 담는 프로그래밍

- List 를 사용해 제네릭 클래스를 적용하는 방법

 

박스에 담을 수 있는 물건 4개 -> Fruit, Apply, Grape, Toy

사과와 포도는 fruit을 상속, Toy는 상속 X

 

 

물건을 담을 박스

import java.util.ArrayList;

public class Box<T> {
	// 클래스명 -> 원시타입이라고 한다
	// <T> -> 제네릭스타입
	
	// 제네릭스(generic)
	// 클래스 맴버의 타입을 컴파일 시에 정의하는 방법 
	// 미리 타입을 지정하는 게 아니라 필요할 때 타입을 정의함.
	// 타입의 안정성을 주기 위해 사용
	// <T> (제네릭스 타입) 은 참조형만 가능 = class 타입만 가능
	
	
	// 제네릭클래스 
	// 타입이 정해지지 않은 상태
	// T에 들어가는 타입으로 타입 제한
	
	// 물건을 담을 박스(공간)
	ArrayList<T> list = new ArrayList<T>();
	
	// 박스에 물건 담기
	void add(T item) {
		list.add(item);
	}
	
	// 박스에서 물건 꺼내기
	T get(int i) {
		return list.get(i);
	}
	
	// 박스 사이즈
	int size() {
		return list.size();
	}
	
	// 박스 안에 물건이 몇개인지 
	public String toString() {
		return list.toString();
	}
}

 

박스에 담을 물건들)

apple

public class Apple extends Fruit {
	
	public String toString() {
		return "Apple";
	}
    
}

 

grape

public class Grape extends Fruit {
	
	public String toString() {
		return "Grape";
	}

}

 

toy

public class Toy {
	
	public String toString() {
		return "Toy";
	}

}

 

Fruit

public class Fruit {

	public String toString() {
		return "Fruit";
	}
	
}

 

 

박스에 물건 담기

public class FruitTest {

	public static void main(String[] args) {

		// 물건을 담을 박스 만들기
		// 어떤 물건을 담겠디고 제한해야함.

		Box<Fruit> fruitBox = new Box<Fruit>();
		Box<Apple> appleBox = new Box<Apple>();
		Box<Toy> toyBox = new Box<Toy>();
		// Box<Grape> grapeBox = new Box<apple>(); // 타입불일치

		fruitBox.add(new Fruit());
		fruitBox.add(new Apple()); // 상속관계에 의한 다형성 적용으로 데이터를 담을 수 있다

		appleBox.add(new Apple());
		// appleBox.add(new Toy()); // 타입 불일치

		toyBox.add(new Toy());
		// toyBox.add(new Apple()); // 타입 불일치

		System.out.println(fruitBox); // [Fruit, Apple]
		System.out.println(appleBox); // [Apple]
		System.out.println(toyBox); // [Toy]

	}
}

 

 

 

제네릭타입의 범위를 제한

- 제네릭타입의 범위를 제한하는 방법을 알아보기 위한 클래스와 인터페이스

 

Eatable

public interface Eatable {
	// 타입을 이용한 다형성 구조를 위한 용도
	// 내용 없음
}

 

FruitBox

public class FruitBox<T extends Fruit & Eatable> extends Box<T>{
// 제네릭한 클래스를 상속받는 클래스도 제네릭 해야 한다.
	
	
	// T라는 타입을 제네릭하게 쓰려면 반드시 Fruit를 상속해야한다 -> 범위 지정
	//public class FruitBox<T extends Fruit> extends Box<T>
	
	// 추가적인 제한
	// 인터페이스와 클래스를 구분하지 않고 extends를 쓴다
	// <T extends Fruit & Eatable>
	
	
	
	// 제네릭 타입의 범위를 제한하는 클래스
}

 

 

Toy를 제외하고 Fruit 상속

Apple

public class Apple extends Fruit {
	
	public String toString() {
		return "Apple";
	}

}

 

Grape

public class Grape extends Fruit {
	
	public String toString() {
		return "Grape";
	}

}

 

Toy

public class Toy {
	
	public String toString() {
		return "Toy";
	}

}

 

제네릭타입의 범위를 제한해서 박스에 물건담기

public class FruitTest2 {

	public static void main(String[] args) {

		FruitBox<Fruit> fruitbox = new FruitBox<Fruit>();
		FruitBox<Apple> applebox = new FruitBox<Apple>();
		FruitBox<Grape> grapebox = new FruitBox<Grape>();
		// FruitBox<Toy> toybox = new FruitBox<Toy>(); // 상속 관계가 아니기 때문에 불가능 -> 타입 불일치

		fruitbox.add(new Fruit());
		fruitbox.add(new Apple());
		fruitbox.add(new Grape());
		applebox.add(new Apple());
		// applebox.add(new Grape()); // 타입 불일치
		grapebox.add(new Grape());
		// toybox 는 생성 자체가 불가능

		System.out.println(fruitbox); // [Fruit, Apple, Grape]
		System.out.println(applebox); // [Apple]
		System.out.println(grapebox); // [Grape]
	}

}

 

 

 

Map 를 사용해 제네릭 클래스를 적용하는 방법

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class HashMapTest {

	public static void main(String[] args) {
		// Map 를 사용해 제네릭 클래스를 적용하는 방법
		
		Map<String, String> cities = new HashMap<>();
		cities.put("서울","대한민국");
		cities.put("도쿄","일본");
		cities.put("베이징","중국");
		cities.put("워싱턴","미국");
		cities.put("브라질리아","브라질");
		
		Iterator<Map.Entry<String, String>> it = cities.entrySet().iterator();
		
		// Map.Entry => getKey(), getValue()  제네릭타입
		
		while(it.hasNext()) {
			Map.Entry<String,String> entry = it.next(); // 반환타입 Map.entry<String, String>
			System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue());
		}
		
	}

}

 

HashMap<K, V> → 이렇게 자체적으로 제네릭하게 정의되어 있음.

HashMap<String, String> 이렇게 지정해주면 K = String , V = String 으로 쓸 수 있다.

 

 

Iterator 가 데이터를 읽어오는 방식 → hasNext(), next()

Iterator 는 인터페이스, 역시 제네릭하게 정의되어 있다.

 

 

next() → 반환타입이 Map.entry 데이터를 직접 받아오는게 아님.

Map.entry의 getKey(), getValue() 로 가지고 올 수 있는 Key, Value

 

 

모든 객체가 화면에 찍힐 때는 toString()이 생략되어짐. 내부에 toString()이 없어도 Object에서 상속.

String, Integer 는 내부에 toString()이 정의되어있기 때문에 String, Integer 타입은 오버라이딩이 된다.

 

// 향상된 for문
		for (Map.Entry<String, String> entry :cities.entrySet()) {
			System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue());
		}
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class HashMapTest {

	public static void main(String[] args) {
		// Map 를 사용해 제네릭 클래스를 적용하는 방법

		Map<String, String> cities = new HashMap<>();
		cities.put("서울", "대한민국");
		cities.put("도쿄", "일본");
		cities.put("베이징", "중국");
		cities.put("워싱턴", "미국");
		cities.put("브라질리아", "브라질");

		// 향상된 for문
		for (Map.Entry<String, String> entry : cities.entrySet()) {
			System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue());
		}

	}

}

 

 

 


 

 

스레드 : 한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위

 

pc. 휴대폰에 설치된 상태 → 프로그램

실행하는 상태 → 프로세스 (하드웨어의 자원을 사용한다.)

멀티프로세스 → 여러 개의 프로세스

 

스레드의 개념

: 스레드는 한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위입니다.
각 스레드는 독립적인 실행을 위한 자신만의 프로그램 카운터, 레지스터 집합, 그리고 스택을 갖습니다.
하나의 프로세스 내의 스레드들은 해당 프로세스의 코드, 데이터 및 기타 운영 체제 자원들을 공유하게 됩니다.
스레드는 프로세스 내에서 병렬 작업 수행을 가능하게 하므로, 복잡한 통신을 필요로 하는 여러 분야에서 유용하게 사용됩니다.

 

  • 싱글스레드  

: 하나의 프로세스가 하나의 작업만을 처리하는 것을 싱글 스레드라고 합니다. 한 번에 하나의 명령어만을 처리할 수 있으며, 작업을 순차적으로 진행하게 됩니다. 싱글 스레드의 경우, 특정 작업이 끝나야 그 다음 작업이 시작될 수 있습니다. 이런 방식은 프로그램의 흐름을 쉽게 이해할 수 있게 해줍니다. 그러나, 한 작업이 오래 걸리는 경우 다음 작업이 지연될 수 있어 효율성이 떨어질 수 있습니다.

  • 멀티스레드 (대부분의 프로그램 → 동시다발적으로 사용해야하는 프로그램)

: 멀티스레드란 하나의 어플리케이션에서 두 개 이상의 스레드를 동시에 실행하는 것을 의미합니다. 각각의 스레드는 독립적으로 실행되며, 자신만의 작업을 수행합니다. 이를 통해 하나의 프로세스 내에서 여러 작업을 동시에 처리할 수 있습니다. 멀티스레딩은 CPU의 이용률을 향상시키고, 사용자와의 상호작용성을 높이며, 병렬 작업을 가능하게 합니다. 하지만, 스레드 간의 동기화와 데이터 공유 문제를 해결해야 합니다.

 

스레드, 싱글스레드, 멀티스레드 개념 출저 : 노션 AI

 

+ Recent posts