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
'2024_UIUX 국비 TIL' 카테고리의 다른 글
UIUX _국비과정 0528 [람다식] (0) | 2024.06.24 |
---|---|
UIUX _국비과정 0527 [스레드] (1) | 2024.06.24 |
UIUX _국비과정 0523 [Map, 배열 정렬] (0) | 2024.06.11 |
UIUX _국비과정 0522 [스택과 큐, 정렬, Set] (1) | 2024.06.11 |
UIUX _국비과정 0521 [피그마 마무리, 자바2] (1) | 2024.06.11 |