Spring에서 SuperTypeToken을 사용하고 싶다면, 이미 존재하는 org.springframework.core.ParameterizedTypeReference 사용하면 된다.

 

테스트

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class SpringbootAcApplication {
	
	public static void main(String[] args) {
		SpringApplication.run(SpringbootAcApplication.class, args);
	}
	@RestController
	public static class MyController{
		@RequestMapping("/")
		public List<User> users(){
			return DB.users();
		}
	}

	static class User{
		long id;
		String name;
		int age;
		String gender;
		int money;
		String email; 
		
		public User() {	}
		
		public long getId() {
			return id;
		}
		public String getName() {
			return name;
		}
		public int getAge() {
			return age;
		}
		public String getGender() {
			return gender;
		}
		public int getMoney() {
			return money;
		}
		public String getEmail() {
			return email;
		}
		
		public void setId(long id) {
			this.id = id;
		}
		public void setName(String name) {
			this.name = name;
		}
		public void setAge(int age) {
			this.age = age;
		}
		public void setGender(String gender) {
			this.gender = gender;
		}
		public void setMoney(int money) {
			this.money = money;
		}
		public void setEmail(String email) {
			this.email = email;
		}
		public User(long id,String name, int age, String gender, int money, String email) {
			this.id = id;
			this.name = name;
			this.age = age;
			this.gender = gender;
			this.money = money;
			this.email = email;
		}
		public String toString() {
			return "[id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender + ", money=" + money
					+ ", email=" + email + "]\n";
		}
	}
	
	static class DB{
		private static long num = 0L;
		static String[] nameDB = {
				"남재인", "한재인", "신이경", "오유리", "장경완", "봉숙자", "황여진", "심재인", "복미래", "신여진", 
				"배미란", "배영신", "이애정", "송여진", "남영애", "안미란", "문재규", "홍숙자", "장경님", "양경님", 
				"양장미", "추자경", "백장미", "권민서", "전재규", "윤재규", "전지해", "설재신", "배경완", "황보혜린",
				 "정무열", "조경구", "남성한", "조경모", "남치원", "유병헌", "최성한", "윤남규", "문성한", "강승헌", 
				 "백성한", "표경구", "조치원", "오재섭", "하경구", "정요한", "송광조", "백재범", "안남규", "배덕수", 
				 "노광조", "복일성", "안재범", "임경구", "유무영", "남궁덕수", "하치원", "하동건", "유무영", "유무영",
				 "성란", "임재", "심진", "설린", "강현", "전진", "이성", "사공리", "탁재", "복상", "하진", "이상", 
				 "심리", "송설", "조진", "문성", "문지", "임린", "예현", "손진", "유리", "전현", "유재", "배은", 
				 "예상", "황성", "임진", "심재", "백현", "한리", "남훈", "심광", "예철", "정건", "하웅", "권훈", 
				 "성훈", "한훈", "하훈", "심광", "성호", "봉건", "류혁", "노훈", "서훈", "권훈", "윤호", "송건", 
				 "윤광", "하웅", "노호", "이철", "문혁", "추철", "정광", "한호", "예철", "오훈", "사공웅", "고훈",
				 "제갈다솜", "하햇살", "양보다", "정자람", "전우리", "노보람", "최한울", "봉빛나", "장비", "전누리", 
				 "전두리", "전마음", "예한별", "김은별", "김민들레", "홍자람", "안꽃", "전나빛", "안아름", "고보름", 
				 "백나라", "전아람", "설빛나", "강나비", "문샛별", "유새벽", "성여름", "남다솜", "양하다", "권하루",
				 "손우람", "허버들", "봉미르", "남궁나라우람", "노힘찬", "황보달", "류빛가람", "윤나라우람", "유미르", 
				 "황보힘찬", "이믿음", "남궁샘", "남한길", "황보나길", "한한길", "전나라우람", "최한길", "권한길", "임믿음", 
				 "고한결", "설믿음", "황샘", "표나길", "안달", "양샘", "임달", "황빛가람", "홍한길", "제갈한결", "배버들",
				 "강광일", "송세은", "문준웅", "백은선", "설서우", "강경희", "권윤미", "봉재경", "표수민", "조동근", 
				 "추진석", "황민숙", "남원석", "심시준", "이선우", "조정우", "유태일", "추경윤", "권규환", "임은주", 
				 "표연웅", "류창기", "풍병수", "서인숙", "남궁명욱", "박시현", "전창현", "남궁주원", "이우태", "사공혜윤"
		};
		static String[] mailDB = {
				"@naver.com", "@nate.com" , "@daum.net" , "@kakao.com" , "@gmail.com" , "@outlook.com",
				"@hotmail.com" , "@yahoo.com"
		};
		static String[] gender = {
			"남자", "여자"	
		};
		
		static ThreadLocalRandom ran = ThreadLocalRandom.current();
		static List<User> users() {
			return IntStream.range(0, 20)
					.mapToObj(n-> getUser())
					.collect(Collectors.toList());
		}
		static User getUser() {
			return new User(num++,getUsername(), getAge(), getGender(), getMoney(), getEmail());
		}
		
		static String getUsername() {
			return nameDB[ran.nextInt(nameDB.length)];
		}
		
		static String getEmail() {
			StringBuilder sb = new StringBuilder();
			for(int i=0,len=ran.nextInt(5, 15);i<len;i++) 
				sb.append(getWord());
			sb.append(mailDB[ran.nextInt(mailDB.length)]);
			return sb.toString();
		}
		
		static char getWord() {
			return (char)ran.nextInt(97, 122+1);
		}
		static String getGender() {
			return gender[ran.nextInt(gender.length)];
		}
		static int getAge() {
			return ran.nextInt(10, 80);
		}
		static int getMoney() {
			return ran.nextInt(10_000, 1_000_000);
		}
	}
}

실행

import java.util.List;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import com.example.springbootac.SpringbootAcApplication.User;

public class Test {
	public static void main(String[] args) {
		RestTemplate rest = new RestTemplate();
		
		List<?> forObject = rest.getForObject("http://localhost:8080", List.class);
		
		System.out.println("타입 토큰 방식");
		System.out.println(forObject.get(0).getClass());
//		forObject.forEach(System.out::println);
		ResponseEntity<List<User>> exchange = rest.exchange(
				"http://localhost:8080", 
				HttpMethod.GET, 
				null, 
				new ParameterizedTypeReference<List<User>>() {});
		System.out.println("슈퍼 타입 토큰 방식");
		System.out.println(exchange.getBody().get(0).getClass());
//		System.out.println(exchange.getBody());
	}
}
20:16:45.500 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080
20:16:45.518 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
20:16:45.545 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
20:16:45.551 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.util.List<?>]
타입 토큰 방식
class java.util.LinkedHashMap
20:16:45.588 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080
20:16:45.589 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
20:16:45.592 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
20:16:45.593 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.util.List<com.example.springbootac.SpringbootAcApplication$User>]
슈퍼 타입 토큰 방식
class com.example.springbootac.SpringbootAcApplication$User

타입 토큰 방식은 기본적으로 지네릭 정보를 알 수 없기 때문에 JSON에 매칭되는 Map을 만들어 값을 반환한다.

슈퍼 타입 토큰은 슈퍼 타입의 지네릭 정보를 읽어 알맞게 DTO 객체로 바인딩한다.

 

DTO 클래스로 JSON이나, xml 같은 데이터를 받게 되면 validation을 쉽게 수행할 수 있고, IDE 코드 추천을 받을 수 있다는 것이 큰 장점이다.

'개발 > 자바(JAVA)' 카테고리의 다른 글

SuperTypeToken  (0) 2023.06.29
컴포지트 패턴 연습 - 2  (0) 2023.04.16
컴포지트 패턴 연습 - 1  (0) 2023.04.13
기본형 배열 List로 변환하기  (0) 2023.03.10
객체 지향 - 6  (0) 2023.02.03

https://www.youtube.com/watch?v=01sdXvZSjcI&list=PLv-xDnFD-nnmof-yoZQN8Fs2kVljIuFyC&index=16 

예시

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class SuperTypeToken02 {
	
	static class Sup<T>{
		T value;
	}
	//super 타입의 지네릭 정보는 런타임 시 지워지지 않고, 바이트 코드에 남아있다.
	static class Sub extends Sup<String>{	}
	
	public static void main(String[] args) throws Exception {
		Sup<String> sup = new Sup<>();
		sup.value = "asd";
		
		//java.lang.Object
		System.out.println(sup.getClass().getDeclaredField("value").getType());
		
		//지네릭 정보를 런타임에 읽을 수 있는 상황예시
		Sub sub = new Sub();
		Type type = sub.getClass().getGenericSuperclass();
		ParameterizedType pType = (ParameterizedType) type;
		//타입이 런타임에 지워지지 않는다.
		//java.lang.String
		System.out.println(pType.getActualTypeArguments()[0]);
		
		Sup<String> sup2 = new Sup<String>() {};
		Type type2 = sup2.getClass().getGenericSuperclass();
		ParameterizedType pType2 = (ParameterizedType) type2;
		//java.lang.String
		//바이트 코드에 정보가 남아 있기 때문에 아무리 중첩되도 정보를 다 가져올 수 있다.
		System.out.println(pType2.getActualTypeArguments()[0]);
		
		Sup sup3 = new Sup<List<Map<Class<String>,Number>>>() {};
		Type type3 = sup3.getClass().getGenericSuperclass();
		ParameterizedType pType3 = (ParameterizedType) type3;
		//java.lang.String
		System.out.println(pType3.getActualTypeArguments()[0]);
		
	}
}
class java.lang.Object
class java.lang.String
class java.lang.String
java.util.List<java.util.Map<java.lang.Class<java.lang.String>, java.lang.Number>>

지네릭 정보는 기본적으로 하위 호환성을 위해 컴파일 시 type eraser에 의해 지네릭 타입은 전부 Object로 바뀌고 그 자리에 형변환이 추가된다.

디컴파일러에 따라 저 형변환이 안보이는 경우가 있어 class 파일로 직접 확인

위 처럼 지네릭 타입 정보는 런타임 시 제거되고 그자리에 적절한 형변환 코드가 남는다.

 

모든 지네릭 정보가 다 지워지는 것은 아니다. 상위 타입 지네릭 정보는 지워지지 않는다.

그래서 예제 코드에서 상위 타입인 지네릭 정보를 읽어올 수 있던 것이다.

익명 클래스로 객체 선언과 동시에 인스턴스화 하면, 대상 클래스가 서브 타입이 된다. 즉, 하위 타입이 된다. 따라서 대상 타입의 지네릭 정보가 안지워진 것이다.

bin 폴더에 보면 위와 같이 익명클래스 클래스 코드가 존재하고, 타입 정보가 남아있다.

SuperTypeToken

익명 클래스 인스턴스를 인자로 넘겨 지네릭 정보를 얻는 방식을 슈퍼타입토큰이라 한다.

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class SuperTypeToken03 {
	static class TypesafeMap{
		Map<TypeReference<?>, Object> map = new HashMap<>();
		
		<T> void put(TypeReference<T> tr, T v) {
			map.put(tr, v);
		}
		<T> T get(TypeReference<T> tr) {
			//단순 타입이면 문제없지만, 컨테이너 타입이면 캐스팅을 못한다. 
			//<String> ok ,  <List<String>> not ok
			
			if(tr.type instanceof Class<?>)
				return ((Class<T>)tr.type).cast(map.get(tr));
			else 
				return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr));
				
		}
	}
	
	static class TypeReference<T>{
		//지네릭 정보까지 포함한 정보를 저장한 필드
		Type type;
		public TypeReference() {
			Type stype = this.getClass().getGenericSuperclass();
			//ParameterizedType인 경우는 <T> 에 정보가 들어와있는 것
			if(stype instanceof ParameterizedType) {
				this.type = ((ParameterizedType) stype).getActualTypeArguments()[0];
			} 
			else throw new RuntimeException();
		}
		
		//IDE에서 제공한 자동 생성 코드
		@Override
		public int hashCode() {
			return Objects.hash(type);
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj) return true;
			//getClass() != obj.getClass() 여기에 걸려 false를 리턴해 현재 동작하지 않는다.
			//우리는 현재 클래스가 아닌 클래스속 Type 이 목적이다.
			if (obj == null || getClass().getSuperclass() != obj.getClass().getSuperclass())
				return false;
			//이 클래스의 익명 클래스를 사용하기 때문에 슈퍼클래스가 익명클래스가 된다.
			TypeReference<?> other = (TypeReference<?>) obj;
			return Objects.equals(type, other.type);
		}
	}
	
	public static void main(String[] args) {
		TypesafeMap typesafeMap = new TypesafeMap();
		//되는 경우
		TypeReference<String> tr = new TypeReference<String>() {};
		typesafeMap.put(tr, "하나둘");
		System.out.println(typesafeMap.get(tr));
		//안되는 경우, equals()메서드를 오버라이딩 하지 않을 경우, 물리적 주소 값을 비교하게 되어 불가능
		typesafeMap.put(new TypeReference<Integer>() {}, 123);
		System.out.println(typesafeMap.get(new TypeReference<Integer>() {}));
		//컬렉션에서 요소가 같은 값인지 판단하는 기준은 Object.equals()메소드 결과다.
		//따라서 아래와 같은 경우는 다른 key로 인식하여 값을 가져오지 못한다.
		
		typesafeMap.put(new TypeReference<List<String>>() {}, Arrays.asList("하나","둘","셋"));
		System.out.println(typesafeMap.get(new TypeReference<List<String>>() {}));
		typesafeMap.put(new TypeReference<List<List<String>>>() {}, Arrays.asList(Arrays.asList("하나"),Arrays.asList("둘"),Arrays.asList("셋")));
		System.out.println(typesafeMap.get(new TypeReference<List<List<String>>>() {}));
        
        //get() 메서드 추가 설명 
		TypeReference<String> typeReference1 = new TypeReference<String>() {};
		TypeReference<List<String>> typeReference2 = new TypeReference<List<String>>() {};
		System.out.println(typeReference1.type);
		System.out.println(typeReference1.type instanceof ParameterizedType);
		System.out.println(typeReference2.type);
		System.out.println(typeReference2.type instanceof ParameterizedType);
		System.out.println(((ParameterizedType)typeReference2.type).getRawType());
	}
}

 

하나둘
123
[하나, 둘, 셋]
[[하나], [둘], [셋]]
class java.lang.String
false
java.util.List<java.lang.String>
true
interface java.util.List

TypesafeMap 개선

package javabasic.toby;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class SuperTypeToken03 {
	static class TypesafeMap{
		Map<Type, Object> map = new HashMap<>();
		
		<T> void put(TypeReference<T> tr, T v) {
			map.put(tr.type, v);
		}
		<T> T get(TypeReference<T> tr) {
			//단순 타입이면 문제없지만, 컨테이너 타입이면 캐스팅을 못한다. 
			//<String> ok ,  <List<String>> not ok
			
			if(tr.type instanceof Class<?>)
				return ((Class<T>)tr.type).cast(map.get(tr.type));
			else 
				return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr.type));
				
		}
	}
	
	static class TypeReference<T>{
		//지네릭 정보까지 포함한 정보를 저장한 필드
		Type type;
		public TypeReference() {
			Type stype = this.getClass().getGenericSuperclass();
			//ParameterizedType인 경우는 <T> 에 정보가 들어와있는 것
			if(stype instanceof ParameterizedType) {
				this.type = ((ParameterizedType) stype).getActualTypeArguments()[0];
			} 
			else throw new RuntimeException();
		}
	}
	
	public static void main(String[] args) {
		TypesafeMap typesafeMap = new TypesafeMap();
		
		typesafeMap.put(new TypeReference<String>() {}, "하나둘");
		System.out.println(typesafeMap.get(new TypeReference<String>() {}));
		typesafeMap.put(new TypeReference<Integer>() {}, 123);
		System.out.println(typesafeMap.get(new TypeReference<Integer>() {}));
		typesafeMap.put(new TypeReference<List<String>>() {}, Arrays.asList("하나","둘","셋"));
		System.out.println(typesafeMap.get(new TypeReference<List<String>>() {}));
		typesafeMap.put(new TypeReference<List<List<String>>>() {}, Arrays.asList(Arrays.asList("하나"),Arrays.asList("둘"),Arrays.asList("셋")));
		System.out.println(typesafeMap.get(new TypeReference<List<List<String>>>() {}));
		
	}
}

key로 Type을 사용하면, 따로 논리적 동치성을 보장하기 위한 코드가 필요없다. 

'개발 > 자바(JAVA)' 카테고리의 다른 글

ParameterizedTypeReference(SuperTypeToken)  (0) 2023.06.30
컴포지트 패턴 연습 - 2  (0) 2023.04.16
컴포지트 패턴 연습 - 1  (0) 2023.04.13
기본형 배열 List로 변환하기  (0) 2023.03.10
객체 지향 - 6  (0) 2023.02.03

컴포지트 패턴을 보니 문득 파일 시스템과 유사하게 느껴져

폴더를 하나 지정해, 그 폴더를 포함한 하위 요소를 간이 컴포지트 패턴 틀에 옮겨봤다.

package designpattern.structural.composite;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class 폴더파일 {
	static int cnt = 0;
	
	//component 
	abstract static class FolderOrFile{
		private String name;
		private boolean isFolder;
		
		protected FolderOrFile(String name, boolean isFolder) {
			this.name = name;
			this.isFolder = isFolder;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public boolean isFolder() {
			return isFolder;
		}
		public void setFolder(boolean isFolder) {
			this.isFolder = isFolder;
		}
		public void add(FolderOrFile folderOrFile) {
			throw new UnsupportedOperationException();
		}
		public void remove(FolderOrFile folderOrFile) {
			throw new UnsupportedOperationException();
		}
		public void print() {
			throw new UnsupportedOperationException();
		}
		
	}
	//composite
	static class MyFolder extends FolderOrFile{
		List<FolderOrFile> folderOrFiles = new ArrayList<>();
		
		@Override
		public void add(FolderOrFile folderOrFile) {
			this.folderOrFiles.add(folderOrFile);
		}
		
		public MyFolder(String name, boolean isFolder) {
			super(name, isFolder);
		}
		@Override
		public void print() {
			for(FolderOrFile fof : folderOrFiles) {
				System.out.println(cnt+++" "+fof);
			}
		}
		@Override
		public String toString() {
			return "[Folder]"+getName();
		}
		
	}
	//leaf
	static class MyFile extends FolderOrFile{
		private String description;
		
		public MyFile(String name, boolean isFolder, String description) {
			super(name, isFolder);
			this.description = description;
		}
		public String getDescription() {
			return description;
		}
		public void setDescription(String description) {
			this.description = description;
		}
		@Override
		public void print() {
			System.out.println(cnt+++" "+this);
		}
		@Override
		public String toString() {
			return "[File]"+getName()+" "+getDescription();
		}
	}
	
	public static void main(String[] args) {
		File file = new File("c:/git");
		
		FolderOrFile fof = new MyFolder(file.getName(), true);
		
		copy(file, fof);
		
		fof.print();
		
	}
	static void copy(File file, FolderOrFile folderOrFile) {
		if(file.isDirectory()) {
			folderOrFile.add(new MyFolder(file.getName(), true));
			for(File f : file.listFiles()) {
				copy(f , folderOrFile);
			}
		}else {
			folderOrFile.add(new MyFile(file.getName(), false, file.length()+"바이트" ));
		}
	}
}

 

824 [File]9de29bb2d1d6434b8b29ae775ad8c2e48c5391 15바이트
825 [Folder]ef
826 [File]7795d074c4633075a0a241bc9f86a4b402d584 145바이트
827 [Folder]info
828 [Folder]pack
829 [File]ORIG_HEAD 41바이트
830 [Folder]refs
831 [Folder]heads
832 [File]master 41바이트
833 [Folder]tags
834 [File]asd.txt 0바이트
835 [File]README.md 8바이트
836 [File]reset.txt 7바이트

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

ParameterizedTypeReference(SuperTypeToken)  (0) 2023.06.30
SuperTypeToken  (0) 2023.06.29
컴포지트 패턴 연습 - 1  (0) 2023.04.13
기본형 배열 List로 변환하기  (0) 2023.03.10
객체 지향 - 6  (0) 2023.02.03

컴포지트 패턴을 공부하다. 갑자기 파일 시스템이랑 비슷한 것 같아서 연습해봄.

 

컴포지트 패턴이란

개체를 트리 구조로 구성하여 부분-전체 계층을 나타냅니다.


이 패턴을 사용하면 클라이언트가 개별 개체와 개체 구성(컴포지트)을 동일하게 처리할 수 있습니다.

 

구성요소 

component

컴포지션의 모든 개체에 대한 기본 인터페이스입니다. 하위 조합을 관리하는 공통 메서드가 있는 인터페이스 또는 추상 클래스여야 합니다.


leaf

자식이 없는 노드를 의미합니다.


composite

자식이 있는 노드를 의미합니다. 이때 다른 composite도 포함될 수 있습니다.

 

코드 예시

import java.util.ArrayList;
import java.util.List;

public class SimpleComposite {
	
	abstract static class Component{
		String name;
		
		void addComponent(Component component) {
			throw new UnsupportedOperationException();
		}
		abstract void print();
	}
	
	static class Leaf extends Component{
		String name;
		
		public Leaf(String name) {
			this.name = name;
		}



		void print() {
			System.out.println("[Leaf]"+name);
		}
	}
	static class Composite extends Component{
		String name;
		List<Component> components = new ArrayList<>();
		
		public Composite(String name) {
			this.name = name;
		}
		@Override
		void addComponent(Component component) {
			components.add(component);
		}
		void print() {
			System.out.println("[Composite]"+name);
			for(Component component : components) {
				component.print();
			}
		}
	}
	
	public static void main(String[] args) {
		Component root = new Composite("회장님");
		root.addComponent(new Leaf("홍 비서"));
		root.addComponent(new Leaf("박 비서"));
		
		Component composite1 = new Composite("전무");
		composite1.addComponent(new Leaf("김 비서"));
		Component composite2 = new Composite("이사");
		composite2.addComponent(new Leaf("이 비서"));
		
		root.addComponent(composite1);
		root.addComponent(composite2);
		
		Component composite1_1 = new Composite("영업팀");
		composite1_1.addComponent(new Leaf("임꺽정 팀장"));
		
		Component composite2_1 = new Composite("기획팀");
		composite2_1.addComponent(new Leaf("홍길동 팀장"));
		
		composite1.addComponent(composite1_1);
		composite2.addComponent(composite2_1);
		
		
		root.print();
	}
	
}
[Composite]회장님
[Leaf]홍 비서
[Leaf]박 비서
[Composite]전무
[Leaf]김 비서
[Composite]영업팀
[Leaf]임꺽정 팀장
[Composite]이사
[Leaf]이 비서
[Composite]기획팀
[Leaf]홍길동 팀장

 

 

간단한 조직도를 만들어봤다. 

 

만든 김에 트리처럼 레벨로 출력해보기

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class SimpleComposite {
	
	abstract static class Component{
		
		abstract String getName();
		abstract boolean isLeaf();
		void addComponent(Component component) {
			throw new UnsupportedOperationException();
		}
		List<Component> getComponents(){
			throw new UnsupportedOperationException();
		}
		
		abstract void print();
	}
	
	static class Leaf extends Component{
		String name;
		public String getName() {
			return name;
		}
		boolean isLeaf() {
			return true;
		}
		public Leaf(String name) {
			this.name = name;
		}


		void print() {
			System.out.println("[Leaf]"+name);
		}
	}
	static class Composite extends Component{
		String name;
		List<Component> components = new ArrayList<>();
		
		public Composite(String name) {
			this.name = name;
		}
		public String getName() {
			return name;
		}
		boolean isLeaf() {
			return false;
		}
		void addComponent(Component component) {
			components.add(component);
		}
		public List<Component> getComponents() {
			return components;
		}
		void print() {
			System.out.println("[Composite]"+name);
			for(Component component : components) {
				component.print();
			}
		}
	}
	
	public static void main(String[] args) {
		Component root = new Composite("회장님");
		root.addComponent(new Leaf("홍 비서"));
		root.addComponent(new Leaf("박 비서"));
		
		Component composite1 = new Composite("전무");
		composite1.addComponent(new Leaf("김 비서"));
		Component composite2 = new Composite("이사");
		composite2.addComponent(new Leaf("이 비서"));
		
		root.addComponent(composite1);
		root.addComponent(composite2);
		
		Component composite1_1 = new Composite("영업팀");
		composite1_1.addComponent(new Leaf("임꺽정 팀장"));
		
		Component composite2_1 = new Composite("기획팀");
		composite2_1.addComponent(new Leaf("홍길동 팀장"));
		
		composite1.addComponent(composite1_1);
		composite2.addComponent(composite2_1);
		
		
//		root.print();
		BFS(root);
	}
	
	static void BFS(Component root) {
		Queue<Component> components = new LinkedList<>();
		components.offer(root);
		int lv = 1;
		
		while(!components.isEmpty()) {
			int len = components.size();
			System.out.print(lv++ +" ");
			for(int i=0;i<len;i++) {
				Component compo = components.poll();
				System.out.print(compo.getName()+" ");
				if(!compo.isLeaf()) {
					for(Component com : compo.getComponents()) {
						components.add(com);
					}
				}
			}
			System.out.println();
		}
	}
	
}
1 회장님 
2 홍 비서 박 비서 전무 이사 
3 김 비서 영업팀 이 비서 기획팀 
4 임꺽정 팀장 홍길동 팀장

 

 

 

 

 

 

 

 

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

SuperTypeToken  (0) 2023.06.29
컴포지트 패턴 연습 - 2  (0) 2023.04.16
기본형 배열 List로 변환하기  (0) 2023.03.10
객체 지향 - 6  (0) 2023.02.03
객체 지향 - 5  (0) 2023.02.01
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Test {
    public static void main(String[] args) {
        
        List<int[]> list = IntStream.rangeClosed(1, 10)
            .mapToObj(n->getIntArray())
            .collect(Collectors.toList());
        
        
        //Arrays.stream은 인자로 int[]을 받아 우리가 생각하는 int하나하나를 요소로하는 스트림을 만든다.
        IntStream intStream = Arrays.stream(getIntArray());
        //Arrays.asList는 얼핏보면 List<Integer>로 만들어 줄 것 같지만, 아니다.
        List<int[]> list2 = Arrays.asList(getIntArray());
        
        //방법 1 반복문 이용
        List<Integer> list3 = arrayToList(getIntArray());
        //방법 2 스트림 이용
        List<Integer> list4 = intStream.boxed().collect(Collectors.toList());
        
        //번외, List<int[]> List<Integer>로 변환
        List<Integer> list5 = list.stream()
            .map(Test::arrayToList)
            .flatMap(li->li.stream())
            .collect(Collectors.toList());
    }
    
    static List<Integer> arrayToList(int[] arr){
        List<Integer> tmpList = new ArrayList<>();
        for(int v : arr) {
            tmpList.add(v);
        }
        return tmpList;
    }
    
    static int[] getIntArray() {
        return ThreadLocalRandom.current()
                .ints(1, 101)
                .limit(50)
                .toArray();
    }
    
}

 

구아바 같은 라이브러리를 사용하지 않고는

기본형 배열에서 리스트로 변환은 방법1이 가장 현실적인 것 같다.

 

 

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

컴포지트 패턴 연습 - 2  (0) 2023.04.16
컴포지트 패턴 연습 - 1  (0) 2023.04.13
객체 지향 - 6  (0) 2023.02.03
객체 지향 - 5  (0) 2023.02.01
객체 지향 - 4  (0) 2023.01.31

다형성(polymorphism)

조상 클래스 타입 참조 변수로 자손 타입 객체를 가리키는

실제 생성된 인스턴스가 무엇인지가 가장 중요하다.

 

public class Test {
	public static void main(String[] args) {
		Tv tv = new SmartTv();
		tv.down();
		tv.up();
//		tv.search(); 사용 못한다. Tv클래스를 보면 search()없기 때문
		//실제 생성된 객체는 SmartTv이기에 타입을 변경하면 호출할 수 있다.
		((SmartTv)tv).search();
	}
}

class Tv{
	void up() {}//채널 업
	void down() {}//채널 다운
}
class SmartTv extends Tv{
	void search() {}//인터넷 검색기능
}

원래 생성된 객체가 SmartTv니까 문제가 없다. 만약 Tv라면 어떻게 될까?

컴파일러는 오류를 못잡는다. 즉, 런타임 예외가 발생하게 된다. (실행 중 예외) 

형변완 예외 발생

이 때문에 형변환 시에 무조건 instanceof 연산자로 형변환가능한 경우만 형변환되도록 하는 것이 중요하다.

 

위까지가 다형성의 사전 지식

public class Test {
	public static void main(String[] args) {
		Tv tv = new SmartTv();
		tv.down();
		tv.up();
	}
}
class Tv{
	void up() {
		System.out.println("일반 TV 채널 업");
	}//채널 업
	void down() {
		System.out.println("일반 TV 채널 다운");
	}//채널 다운
}
class SmartTv extends Tv{
	void up() {
		System.out.println("스마트 TV 채널 업");
	}//채널 업
	void down() {
		System.out.println("스마트 TV 채널 다운");
	}//채널 다운
	void search() {}//인터넷 검색기능
}
///////////////////////////////////////
스마트 TV 채널 다운
스마트 TV 채널 업

실제 동작은 생성된 객체 구현부로 동작되는 것을 알 수 있다. 이를 통한 다형성 사례

public class Test {
	public static void main(String[] args) {
		SmartTv tv1 = new LGSmartTv();
		SmartTv tv2 = new SamsungSmartTv();
		tvTest(tv1);
		tvTest(tv2);
	}
	//앞으로 새로운 스마트 TV가 추가되어도 기존 아래 코드는 수정할 일이 없다.
	static void tvTest(SmartTv smartTv) {
		smartTv.down();
		smartTv.up();
		smartTv.search();
	}
}
class Tv{
	void up() {
		System.out.println("일반 TV 채널 업");
	}//채널 업
	void down() {
		System.out.println("일반 TV 채널 다운");
	}//채널 다운
}
//객체 생성을 막음
abstract class SmartTv extends Tv{
	void up() {
		System.out.println("스마트 TV 채널 업");
	}//채널 업
	void down() {
		System.out.println("스마트 TV 채널 다운");
	}//채널 다운
	void search() {
		System.out.println("스마트한 검색");
	}//인터넷 검색기능
}

class LGSmartTv extends SmartTv{
	void up() {
		System.out.print("LG");
		super.up();
	}
	void down() {
		System.out.print("LG");
		super.down();
	}
	void search() {
		System.out.print("LG의 ");
		super.search();
	}
}
class SamsungSmartTv extends SmartTv{
	void up() {
		System.out.print("삼성");
		super.up();
	}
	void down() {
		System.out.print("삼성");
		super.down();
	}
	void search() {
		System.out.print("삼성의 ");
		super.search();
	}
}
////////////////////////////////
LG스마트 TV 채널 다운
LG스마트 TV 채널 업
LG의 스마트한 검색
삼성스마트 TV 채널 다운
삼성스마트 TV 채널 업
삼성의 스마트한 검색

객체지향 원칙 캡슐화, 추상화, 상속, 다형성을 기반으로 다양한 디자인 패턴들이 존재한다.

 

인터페이스(interface)

일종의 추상클래스

추상클래스 처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높다

 

개발 코드가 객체에 종속되지 않게 하여 객체를 교체할 수 있도록 한다. (OCP, 변경엔 닫혀있고, 확장엔 열려있어야한다)

객체 간 느슨한 결합을 위한 핵심

객체지향 원칙에 구현보다는 인터페이스에 맞춰서 프로그래밍한다는 원칙도 있다.

 

인터페이스 모든 멤버변수는 public static final이다. 그렇기에 생략해도 컴파일러가 일괄적으로 붙여준다.

모든 메서드는 public abstract다.

 

인터페이스 간 상속도 당연히 된다. expands 키워드, 인터페이스를 클래스가 구현하면 implements

 

느슨한 결합 예시

public class Test {
	public static void main(String[] args) {
		LgTv lgTv = new LgTv();
		tvTest(lgTv);
	}
	//지금 구조에서 기존 코드(tvTest)를 절대 건드리지 말고 SamSungTv로 바꿔 동작하게 할 수 있나? 
	public static void tvTest(LgTv lgTv) {
		lgTv.volDown();
		lgTv.volUp();
	}
}
class LgTv{
	void volUp() {}
	void volDown() {}
}

class SamSungTv{
	void volUp() {}
	void volDown() {}
}

절대 불가능하다.

public class Test {
	public static void main(String[] args) {
		LgTv lgTv = new LgTv();
		SamSungTv samSungTv = new SamSungTv();
		//자동 형변환된다.
		tvTest(lgTv);
		tvTest(samSungTv);
	}
	//지금 구조에서 기존 코드(tvTest)를 절대 건드리지 말고 SamSungTv로 바꿔 동작하게 할 수 있나? 
	public static void tvTest(Tv tv) {
		tv.volDown();
		tv.volUp();
	}
}
interface Tv{
	void volUp();
	/*public abstract*/void volDown();
}
class LgTv implements Tv{
	public void volUp() {}
	public void volDown() {}
}

class SamSungTv implements Tv{
	public void volUp() {}
	public void volDown() {}
}

객체지향을 시작으로 다양한 디자인 원칙, 더 나아가 디자인 패턴에서 중요한 핵심은 기존코드를 수정하지 않고 새로운 기능을 손쉽게 추가하는 것이다.

익명 클래스(anonymous class)

이름 없는 클래스, 이유는 선언과 객체 생성을 동시에 하기 때문이다.

일회용으로 쓰거나, 오직 하나의 객체만을 생성한다.

 

public class Test {
	public static void main(String[] args) {
		new AnonymousTest();
	}
}
class AnonymousTest {
	public AnonymousTest() {
		System.out.println("객체 선언과 동시에 생성");
	}
}
////////////////////
객체 선언과 동시에 생성
public class Test {
	public static void main(String[] args) {
		Runnable runnable = ()->Test.call("1");
		Thread thread = new Thread(runnable);
		thread.start();
		call("0");
	}
	static void call(String msg) {
		for(int i = 0; i< 10000 ; i++) {
			System.out.print(msg);
		}
	}
}

람다식은 익명 구현 객체이다.

 

 

 

 

 

 

 

 

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

컴포지트 패턴 연습 - 1  (0) 2023.04.13
기본형 배열 List로 변환하기  (0) 2023.03.10
객체 지향 - 5  (0) 2023.02.01
객체 지향 - 4  (0) 2023.01.31
객체 지향 - 3  (0) 2023.01.29

super

참조변수 super

super 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수

 

모든 인스턴스 메서드 내에는 자기 자신 객체의 주소this부모의 주소 super 지역 변수로 저장 되어있다.

참고로 매개변수도 그 메서드 블록안에 로컬 변수다.

public class Test {
	public static void main(String[] args) {
		SubMan man = new SubMan();
		man.call();
	}
}
class SuperMan {
	public int intValue = 1;
}
class SubMan extends SuperMan{
	int intValue = 12;
	void call() {
		int intValue = 123;
		System.out.println(intValue);
		System.out.println(this.intValue);
		System.out.println(super.intValue);
	}
}
/////////////////
123
12
1

super() - 조상 클래스의 생성자

Object 클래스를 제외한 모든 클래스의 생성자의 줄에는 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다. this(), super()

 

class Class1{
	//기본 생성자와 기본 조상 클래스 생성자( super() )는 컴파일러가 자동 추가해준다.
	public Class1() {
		super(); // 모든 생성자(this(), super() )는 무조건 다른 생성자를 "첫줄"에 호출해야한다
	}
}
//수동으로 기본생성자를 추가하는 것이 일반적으로 옳은 프로그래밍이다.

Object를 제외한 모든 클래스는 상속받은게 없다면 자동으로 Object를 상속한다.

만약 명시적으로 상속받은게 있다면, 부모 클래스가 Object를 상속받는다. 부모 클래스도 상속받은게 있다면 그 부모 클래스가 Object를 상속받는다. 

//상속받은게 없다면 자동으로 Object를 상속 받는다.
class A /*extends Object*/{
}
class B extends A{
}

이유는 조금만 생각해보면 당연하다.

구조적으로 상속받는 객체가 있어야 상속을 받을 수 있다. 부모 없이 자식이 있을 수 없는 것과 같다.

A.class 파일 속을 열어보면 위와 같은 정보를 볼 수 있다.

 

패키지(package)

서로 관련된 클래스의 묶음

관리 편리함

이름이 중복되도 패키지만 다르면 상관없다. (네임스페이스)

 

import

컴파일러에게 소스코드에 사용되는 클래스가 속한 패키지를 알려준다.

클래스를 사용할 패키지 이름을 생략할 있다.

패키지.* 컴파일 단에서 시간이 조금 걸리는 것이지 실행 성능 저하는 전혀 없다.

, 주의할 것은 해당 패키지에 모든 클래스를 표현하는 것이지 하위 디렉토리는 포함하지 않는다

 

Static import

static 멤버(static 변수&메서드) 사용할 클래스 이름을 생략할 있게 해준다.

 

 

제어자(modifier)

접근 제어자 : public, protected, default, private

그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

 

접근 제어자는 단 하나만 사용가능

나머지는 복수도 가능

 

제어자들 간 순서는 상관없지만, 접근 제어자는 관례상 가장 왼쪽에 둠

 

static

클래스가 메모리에 로드될 때 단 한번, 생성(또는 수행)된다. 클래스에 귀속된다.

public class Test {
	public static void main(String[] args) {
		//바로 사용 가능
		System.out.println(C.a);// 답은 뭘까?
		new C();
		new C();
		new C();
		C a = new C();
		//비권장법 사용법, 클래스 변수는 무조건 클래스이름으로 접근
		System.out.println(a.a);
		//권장 사용법
		System.out.println(C.a);
		System.out.println(a.b);// 답은 뭘까?
	}
}
class C{
	static int a = 0;
	static int b = 0;
	static {
		a++;
	}
	public C() {
		b ++;
	}
}
////////////////////////////
1
1
1
4

final

변경하지 못하게 한다. 이것만 알면 된다.

 

변수 => 상수, 메서드 => 오버라이드 불가 , 클래스 => 상속 불가

 

final 변수는 상수이기에 선언과 동시에 초기화해야 하나 

인스턴스 변수인 경우에 한에 생성자에서 초기화 되도록 있다.

 

 

abstract

메서드에만 붙는다고 생각하면 된다. 이유는 구현부가 없다는 것을 표현하기 때문이다.

클래스에 붙는 이유는 abstract 메서드가 존재함을 알리기 위한 성격이다.

abstract class C{
	abstract void call();
}

//이런식으로도 사용된다. 상속을 강제한다.
//(abstract붙으면 추상 클래스로 new연산자를 사용 못한다.)
//
abstract class D{
	void call() {}
}

접근제어자 final과 abstract 를 사용한 디자인 패턴도 있다.

템플릿 메서드

abstract class C{
    //골격에 해당하는 메서드는 오버라이딩 못하게 막는다.
	final void call() {
		System.out.println("무언가... 작업중...");
		call2(); // 서브클래스에게 행동을 위임한다.
		call3();
		hook();
	}
	abstract void call2();
	void call3() {
		System.out.println("다른 메서드 호출....");
	}
    //구현을 강제하지 않는 메서드를 hook메서드라 한다.
    //그냥 넘어가도 되고 향후 필요할 때 hook부분을 재정의해서 쓰면된다.
    //반드시 hook이름 안써도된다. 관례 아님. 이렇게 쓰는걸 hook메서드라 하는 것
	void hook() {}
}
class D extends C{
	@Override
	void call2() {
		System.err.println("나만의 메서드...");
	}
}

접근 제어자 

말그대로 접근 제어,

private 같은 클래스 내

default 같은 클래스 내, 같은 패키지 내

protected 같은 클래스 내, 같은 패키지 내, 다른 패키지라도 상속해서 사용 가능

public 전부 허용

 

접근제어자는 캡슐화에 핵심적인 역할을 한다. 

 

데이터가 유효한 값을 유지하도록, 또는 민감한 데이터를 외부에서 함부로 변경하지 못하도록 제한을 한다.

이를 정보 은닉, 캡슐화라 한다.

 

클래스 내부에서만 필요한 정보를 외부에 노출시키지 않아 복잡도를 줄인다.

 

접근제어자가 public이면 메서드를 변경한 후에 오류가 없는지 테스트해야하는 범위가 넓다.

private이면 클래스 내부에서만 확인하면 된다.

 

자바빈즈 패턴은 접근 제어자를 활용한 패턴이다.

모든 변수는 private으로 두고 메서드로만 접근하게 한다. 메서드는 동작을 정의하기 때문에 사용자 접근에 대한 검증과 데이터 유효성 검사 등을 수행할 수 있다.

 

쉽게 생각해보자, 특정 데이터를 권한 있는 사용자에게만 보여주고 싶다. 메서드 없이 변수로는 죽었다 깨어나도 구현 못한다. 

 

생성자의 접근 제어자 prviate을 두면 객체를 외부에서 생성을 못한다.

이말은 클래스 자체적으로 객체를 생성해서 외부에 건낸다. 이를 싱글턴 패턴이라 한다.

class Singleton{
    //제어자 중심으로 유심히 보자
	private static Singleton singleton;
	private Singleton() {
	}
	public static Singleton getInstance() {
		if(singleton == null) {
			singleton = new Singleton();
		}
		return singleton;
	}
}

객체 생성을 못하니 static 키워드를 쓴 것을 볼 수 있다.

싱글턴 목적은 어플리케이션 내에 반드시 하나의 객체만 존재하는 것을 보장하기 위함이다.

 

실제로 위 같이 사용은 안한다. (멀티 쓰레딩 문제)

enum Singleton{
	SINGLETON;
	void call() {
		System.out.println("이렇게 사용");
	}
}

객체 생성을 JVM에 위임해 멀티 쓰레드 문제에서 완전히 자유롭다.

 

 

 

 

 

 

 

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

기본형 배열 List로 변환하기  (0) 2023.03.10
객체 지향 - 6  (0) 2023.02.03
객체 지향 - 4  (0) 2023.01.31
객체 지향 - 3  (0) 2023.01.29
객체 지향 - 2  (0) 2023.01.27

상속 (Inheritance)

기존 클래스로 새로운 클래스를 작성하는 (코드의 재사용)

상속을 통해서 클래스를 작성하면 보다 적은 양의 코드로 새로운 클래스를 작성할 있다.

또한 코드를 공통적으로 관리할 있기 때문에 코드의 추가 변경이 용이하다

상속 특성상 상속하면 수록 기능이 많아진다.

 

extends 키워드로 상속한다

확장

생성자와 초기화 블록은 상속되지 않는다.

자바는 단일 상속 객체지향 언어이다.

 

접근제어자 private, default 멤버는 엄밀히 말하면 상속은 되지만, 서브 클래스에서 접근이 불가한 것

클래스의 관계 - 포함 관계(composite)

상속 이외에 클래스를 재사용하는 방법은 클래스의 멤버로 다른 클래스 타입 참조변수를 선언하는

단일 상속만 되는 자바에서 일반적으로 포함 관계가 훨씬 좋다고 여겨진다.

public class Ex1 {
	public static void main(String[] args) {
		//상위 클래스 타입으로 하위 클래스 타입을 다룰 수 있다. 다형성의 핵심
		SuperClass superClass = new SubClass();
		superClass.call();
	}
}
class  SuperClass{
	//일반적으로 멤버변수는 private이 옳다. 데이터 접근은 메서드로 하는 것이 일반적으로 옳다.
	private Composite composite;
	//슈퍼 클래스 생성자는 서브 객체 생성 시 자동으로 호출 된다. 즉, 
	//즉, 슈퍼클래스 객체와 서브 클래스를 묶어 하나 처럼 쓰는 것이다.
	public SuperClass() {
		composite = new Composite();
	}
	public void call() {
		composite.call();
	}
}

class SubClass extends SuperClass{
	//자동으로 컴파일러가 추가 1
//	public SubClass() {
	//자동으로 컴파일러가 추가 2
//		super();
//	}
}

class Composite{
	public void call() {
		System.out.println("구성과 위임");
	}
}

 

구성과 위임

클래스간의 관계 결정하기

클래스를 작성하는데 상속관계를 맺어 줄지, 포함관계를 맺어줄지

 

분류법

상속관계 , is-a 관계, ~는 ~이다, is-a-kind-of , ~는 ~의 분류이다. 이 표현이 더 정확하다는 책도 있다.

포함관계(구성관계) , has-a 관계, ~는 ~를 가지고 있다.

이 내용을 좀 더 이해하려면, 객체지향만을 주제로 한 책을 읽어봐야 한다.

 

앞자리에 하위클래스가 오고 뒤에 상위 클래스가 온다.

 

예시) 오리는 동물이다.  OK , 오리는 동물을 가지고 있다. Not OK

 

객체 지향의 상속이라는 특성을 올바르게 활용하면 리스코프 치환 원칙을 준수하게 된다.

 

단일 상속(single inheritance)

자바는 단일 상속만 허용한다.

다중 상속은 복합적인 기능을 가진 클래스를 쉽게 만들 있다는 장점이 있다

하지만 클래스 간의 관계가 매우 복잡해진다.

 

다중 상속이 된다면 문제점

조상 메소드 이름은 같지만 구현부가 다른 경우 어떤 것을 실행할 것인가? (충돌 문제)

 

자바는 인터페이스로 다중 상속과 유사한 효과를 낼 수 있다.

 

상속과 구성 관계를 확장한 다양한 디자인 패턴도 존재한다.

 

오버라이딩(overriding)

상속받은 조상의 메서드를 자신에 맞게 변경하는

메서드 시그니처는 완전히 동일해야 한다.(반환타입, 매개변수, 이름)

class A{
	A getInstance() {
		return this;
	}
}
class B extends A{
	//단, 자손 타입으로 반환하는 것은 예외적으로 허용된다.
	B getInstance() {
		return this;
	}
}

오버라이딩 조건

  • 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다
  • 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
    조상 클래스 메서드 예외보다 더 상위 예외를 선언할 수 없다는 뜻(예외도 계층이 존재한다)
  • 인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할 수 없다.
class AA{
	void method() throws Exception{	}
	protected void method2(){	}
	static void method3() {}
}


class BB extends AA{
	@Override
	//조상보다 더 많은 예외 안됨
	void method() throws Throwable{
		// TODO Auto-generated method stub
		super.method();
	}
	@Override
	//접근제어자 더 좁게 불가.
	private void method2() {
		// TODO Auto-generated method stub
		super.method2();
	}
	/**
	 * static 멤버는 클래스에 속한 것이지, 인스턴스에 속한게 아니다.
	 * 	따라서 오버라이딩이란 개념이 없다. 야에 별개다. 
	 * AA.method3(), BB.method3() 앞에 변수가 아니라 클래스로 명확히 구분이 가능하다.
	 */
	static void method3() {}
}

 

 

 

 

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

객체 지향 - 6  (0) 2023.02.03
객체 지향 - 5  (0) 2023.02.01
객체 지향 - 3  (0) 2023.01.29
객체 지향 - 2  (0) 2023.01.27
객체 지향 - 1  (0) 2023.01.25

클래스 메서드(static메서드) 인스턴스 메서드

static 붙으면 static 메서드

클래스는 '데이터와 데이터에 관련된 메서드 집합'

따라서 클내스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다.

 

1. 클래스를 설계할 때. 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다. 
생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수는 서로 다른 값을 유지한다. 그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스변수로 정의해야 한다. 

2. 클래스 변수(static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다. 
static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다. 

3. 클래스 메서드 (static 메서드)는 인스턴스 변수를 사용할 수 없다. 
클래스 메서드는 JVM로딩과 동시에 생성된다. 반면에 인스턴스 변수는 객체를 생성해야만 접근할 수 있다.

즉, 클래스 메서드가 존재할 때 반드시 인스턴스 변수가 존재할 거란 보장이 없다.

위와 같은 이유로 인스턴스 멤버는 반드시 클래스 멤버가 존재할 거란 보장이 되기에 아무런 문제가 없다.


4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다 
메서드의 작업내용 중에서 인스턴스변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스변수를 필요로 하지 않는다면 static을 붙이자. 메서드 호출시간이 짧아지므로 성능이 향상된다. static을 안 불인 메서드(인스턴스메서드)는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다. 

 

오버로딩(overloading)

같은 메서드 이름으로 메서드를 만드는 것, 구분 방법은 매개변수 뿐이다.

매개변수라 함은 매개변수 타입과, 개수이다.

가변인자

내부적으로 배열로 처리

가급적 가변인자는 사용하지 말 것, 명확히 배열 타입이나 컬렉션을 사용할 것

생성자(Constructor)

인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드, 메서드 이름은 클래스 이름과 같다.

 

초기화

초기화를 흔히 깨끗함, 0으로 만들기, 문자열이면 "" 같이 공백으로 넣기 등으로 생각하는 사람이 있다.

아니다. 바로 사용가능한 상태를 만드는 것이 초기화이다.

 

생성자도 근본적으로 메서드다. (반환 값이 없는 메서드)

메서드는 일련의 동작들이다. 즉, 복잡한 초기화를 할 수 있다. 

 

기본 생성자(default constructor)

매개변수가 없는 생성자

생성자가 클래스 안에 하나도 없을 자동으로 컴파일러가 추가해줌

하지만 무조건 직접 만들어주는 것을 권장

매개변수가 있는 생성자를 만들 경우 기본 생성자 추가가 자동으로 안된다. 따라서 무조건 기본 생성자는 수동으로 넣는 것이 좋다.

생성자에서 다른 생성자 호출하기 - this()

생성자 이름 대신 this를 사용하는 것

this는 객체 자신의 주소가 저장되어 있다. 

public class Constructor {
	public Constructor() {
		this(1);
		//반드시 한 개만 호출 가능
//		this(1,2);
	}
	public Constructor(int a) {
		//순환 구조가 생기면 안된다.
//		this();
		this(a,2);
	}
	public Constructor(int a,int b) {
//		super();
	}
}

변수 초기화

기본형은 초기화를 하지 않아도 기본 값이 존재한다.

참조형은 객체 주소이므로 초기값이 존재할 수가 없다. new 연산자로 힙영역에 객체를 생성해야 그 주소가 참조형 변수에 담기기 때문이다. 따라서 null 상태

char는 공백이 초기값이다.  \u0000

 

 

멤버변수의 초기화 방법

  • 명시적 초기화
    선언과 동시에 값을 대입하는 것
  • 생성자
    생성자 호출
  • 초기화 블록
    아무 이름 없이 { } 중괄호만 있는 것

public class Constructor2 {
	int a = 1;
	static {
		System.out.println("JVM에 이 클래스 적재 시 단 한번만 호출, 즉 모든 인스턴스 초기화보다 먼저 호출 됨");
//		System.out.println(a);당연히 사용 못함
	}
	{
		System.out.println(a);
		System.out.println("생성자보다 먼저 호출");
		a = 10;
	}
	public Constructor2() {
		System.out.println(a);
		System.out.println("생성자 호출");
		a = 100;
	}
	public static void main(String[] args) {
		Constructor2 c = new Constructor2();
		System.out.println(c.a);
	}
}

static 초기화 블록이 있는 이유

public class Constructor3 {
	static int a = 1;
	static {
		//클래스 멤버에 대해 복잡한 초기화가 필요할 수 있기 때문
		System.out.println("무언가 복잡한 방법으로 static a를 초기화하고 싶음");
	}
}
클래스 초기화 인스턴스 초기화
기본값 명시적 초기화블록 기본값 명시적 초기화블록 생성자

클래스 초기화는 JVM에 해당 클래스가 적재될 때 단 한 번만 수행 됨

 

 

 

 

 

 

 

 

 

 

 

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

객체 지향 - 5  (0) 2023.02.01
객체 지향 - 4  (0) 2023.01.31
객체 지향 - 2  (0) 2023.01.27
객체 지향 - 1  (0) 2023.01.25
파일 옮기기  (0) 2022.07.25

JVM 메모리 구조

JVM 시스템으로부터 메모리를 할당 받는다

 

1. 메서드 영역(method area) 
프로그램 실행 중 어떤 클래스가 사용되면.(지연로딩)

JVM은 해당 클래스의 클래스파일(*.class)을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장한다.

이 때. 그 클래스의 클래스변수(class variable)도 이 영역에 함께 생성된다. 

2. 힙(heap) 
인스턴스가 생성되는 공간. 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다. 
즉 인스턴스변수(instance vanable)들이 생성되는 공간이다. 

3. 호출스택 (call stack 또는 execution stack) 
호출스택은 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면,
 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과 등을 저장하는데 사용된다. 그리고 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워진다. 

 

호출 스택의 특징

메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다. 
메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다. 
호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다. 
아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다. 

public class CallStack {
	public static void main(String[] args) {
		System.out.println(factorial(10));
	}
	public static long factorial(int num) {
		if(num == 0) return 1;
		long result = 0;
		result = num * factorial(--num);
		return result;
	}
}

제일 위가 현재 실행 중 가장 아래가 제일 먼저 호출된 메서드

 

기본형 매개변수- 참조형 매개변수

자바에서 메서드를 호출할 매개변수로 지정한 값은 복사해서 넘겨준다

기본형이면 데이터 복사되지만 참조형이면 주소가 복사된다

 

기본형 매개변수는 변수의 값을 읽기만 할 수 있다.

참조형 매개변수는 변수의 값을 읽고 변경할 수 있다.

public class PrimitiveType {
	public static void main(String[] args) {
		int pValue = 10;
		System.out.println("기본형 변경 전 : "+ pValue);
		int copyPValue = pValue;
		copyPValue = 20;
		System.out.println("기본형 읽고읽은 값 변경 : "+ pValue + "원본에는 영향 안 감");
		System.out.println(pValue == copyPValue);
		
		Referencetype rValue = new Referencetype();
		System.out.println("참조형 변경 전 : "+ rValue);
		Referencetype copyRValue = rValue;
		copyRValue.value = 2000;
		System.out.println("참조형 읽고읽은 값 변경 : "+ rValue + "원본에 영향 감 ");
		System.out.println(rValue == copyRValue);
	}
}

class Referencetype{
	int value = 1000;
	@Override
	public String toString() {
		return value+"";
	}
}
기본형 변경 전 : 10
기본형 읽고읽은 값 변경 : 10원본에는 영향 안 감
false
참조형 변경 전 : 1000
참조형 읽고읽은 값 변경 : 2000원본에 영향 감 
true

재귀호출(recursive call)

메서드 입장에서는 자기 자신을 호출하는 것과 다른 메서드를 호출하는 것은 차이가 없다.

'메서드 호출'이라는 것이 그저 특정 위치에 저장되어 있는 명령들을 수행하는 것일 뿐이기 때문이다.

재귀호출은 무한 루프 위험성이 있어 종료 조건문이 필수적이다.

재귀호출은 반복문보다 성능이 떨어진다. 따라서 재귀호출은 성능적 이점보다 논리적 간결함이 주는 이점이 때만 사용한다.

 

 

 

 

 

 

'개발 > 자바(JAVA)' 카테고리의 다른 글

객체 지향 - 4  (0) 2023.01.31
객체 지향 - 3  (0) 2023.01.29
객체 지향 - 1  (0) 2023.01.25
파일 옮기기  (0) 2022.07.25
변수의 타입  (0) 2022.07.07

+ Recent posts