QueryDsl like 와 contains 의 차이



QueryDsl 을 사용하다가 like 기능이 필요한 일이 있었습니다. 



query.where(qTest.testName.like(searchOptions.getTestName()));



위와 같이 코드를 사용하고 테스트를 하니 원하는 결과가 나오지 않았습니다.

그래서 검색과 코드를 들어가서 살펴본 결과 이유를 알 수 있었습니다.


위의 like 는 완전히 일치하는 경우를 찾을 때 사용합니다.



query.where(qTest.testName.contains(searchOptions.getTestName()));



위의 contains 는 Mysql 에서의 like 처럼 일부를 통해 검색할 때 사용합니다.




'Database > QueryDsl' 카테고리의 다른 글

QueryDsl 사용하기.  (0) 2018.08.13



QueryDsl 사용하기.



이번에 QueryDsl 을 사용할 기회와 필요성이 있어 적용하고 사용해봤습니다.

그 과정이 쉽지는 않다는 것을 느꼈기에 정리하고자 합니다.


우선 QueryDsl 을 사용하기 위해 dependency 를 추가할때 어떤 글은 com.querydsl 이고 어떤 글은 com.mysema.querydsl 으로 나와있습니다.

이유는 QueryDsl 이 4.x 버전으로 올라가며 package 명이 바뀌어서 그 이전글은 그대로 남아 있는 것으로 생각됩니다.


처음으로 build.gradle 파일에 아래와 같이 내용을 써줍니다.



dependencies {
compile("mysql:mysql-connector-java")
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile ("com.querydsl:querydsl-core")
compile ("com.querydsl:querydsl-apt")
compile ("com.querydsl:querydsl-jpa")
testCompile group: 'junit', name: 'junit', version: '4.12'
}

sourceSets {
main {
java {
srcDirs 'src/main/java', 'src/main/generated'
}
}
}

task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
file(new File(projectDir, "/src/main/generated")).deleteDir()
file(new File(projectDir, "/src/main/generated")).mkdirs()
source = sourceSets.main.java
classpath = configurations.compile + configurations.compileOnly
options.compilerArgs = [
"-proc:only",
"-processor", "com.querydsl.apt.jpa.JPAAnnotationProcessor",
"-processor", 'com.querydsl.apt.jpa.JPAAnnotationProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor'
]
destinationDir = file('src/main/generated')
}

compileJava {
dependsOn generateQueryDSL
}

clean.doLast {
file(new File(projectDir, "/src/main/generated")).deleteDir()
}



위에서 clean.doLast 라는 부분이 의아하실 것 같은데 컴파일할 때 이전에 생성되었던 Q Class 가 남아있다면 에러를 발생시키기 때문에 이를 방지하기 위해 clean.doLast 에서 지우고 있습니다.





위 사진에 보이는 Tasks - application - bootRun 을 실행하면 





위와 같이 지정한 위치에 안에는 Q Class 들이 들어가 있는 상태의 generated 폴더가 생성됩니다.

이젠 CustomRepository 와 CustomRepositoryImpl 을 만들어야 합니다.



public interface CustomTestRepository {
Page<Test> findTestBySearchFilter(Long testId, SearchOptions searchOptions, Pageable pageable);

}



@Component
public class TestRepositoryImpl extends QueryDslRepositorySupport implements CustomTestRepository {

public TestRepositoryImpl() {
super(Test.class);
}

@Override
public Page<Test> findTestBySearchFilter(Long testId, SearchOptions searchOptions, Pageable pageable) {
QTest qTest = QTest.test;
JPQLQuery query = from(qTest);

if(testId != null) {
query.where(qTest.testId.eq(testId));
}

do something...

final List<Test> tests = getQuerydsl().applyPagination(pageable, query).fetch();
final long totalCount = query.fetchCount();

return new PageImpl<>(tests, pageable, totalCount);
}



위의 코드는 예시로 필요에 따라 추가, 수정하여 사용하면 됩니다.





'Database > QueryDsl' 카테고리의 다른 글

QueryDsl like 와 contains 의 차이  (0) 2018.08.13



JPA JsonMappingException: Infinite recursion (StackOverflowError) - 무한루프



 JPA를 사용해 개발을 하던 도중 낯선 에러를 보게 되었습니다.



Caused by: com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain:



JsonMapping 을 하던 도중 무한 순환을 하여 StackOverflow 에러가 났다는 에러였습니다.

뭔가 하고 찾고 분석한 결과 원인을 알아내고 수정할 수 있었습니다.

문제의 컨트롤러에서 아래와 같이 ResponseEntity 를 리턴타입으로 가지고 있었습니다.



public ResponseEntity<?> searchOrders() {



return ResponseEntity.ok(new ApiResponse(123, "Success", entityObject));



Http Response body 에 object 를 serialized 할 때 jackson 을 사용하는데 jackson 이 데이터를 뽑을 때 getter 기준으로 뽑아낸다고 합니다.

그런데 제가 한 실수가 일단 성공하고 return 이 잘되는지 확인을 먼저 하려는 마음에 사용하던 Entity 객체를 return 하였는데 

이 Entity가 OneToMany 로 자식 엔티티를 참조하고 자식 Entity는 ManyToOne 으로 부모 Entity를 참조하고 있는 상태였습니다.



@Entity
@Table(name = "test")
public class Test {
    

...


@OneToMany(mappedBy = "test")
private List<SubTest> subTests = new ArrayList<>();
}



@Entity
@Table(name = "biz_order_products")
public class BizOrderProduct {

...


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "testId")
private Test test;

}



그래서 양방향 참조를 하는 관계에서 getter 를 하게 되니 서로 참조하고 있고 get 을 하면 순환을 하여 무한루프에 빠지게 되고 이 데이터가 메모리의 Stack 영역에 계속해서 쌓이게 되어 메모리의 Heap 영역을 침범하게 되는 StackOverflow 에러가 발생하게 된 것 이였습니다.


이를 해결하기 위해선 


1. 새로운 객체를 만들어 이를 return.

2. @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "순환이 일어나는 참조객체에 맵핑되는 컬럼") 으로 순환을 끊기.

3. 양방향 참조를 단방향 참조로 만들기.

4. fetch join을 사용하기.


가 있습니다.

해결 방법이 더 있을 수 있다면 댓글로 알려주시면 감사하겠습니다.





'Database > JPA & JPQL' 카테고리의 다른 글

[JPA] Id Annotation과 return cached data  (0) 2018.07.05
Pageable로 Limit 걸기  (0) 2018.06.23
JPQL이란?  (0) 2018.06.18



BiFunction과 TriFunction


Java 8BiFunction 이라는 것이 추가되었습니다.



private BiFunction<Integer, String, String> createStatusErrorMessage = (code, status) -> {
StringBuilder sb = new StringBuilder();

//do something.

return sb.toString();
};



기본적인 생김새는 위와 같습니다.

앞의 두개의 타입으로 파라미터를 받고 3번째 타입으로 return 하겠다는 의미입니다.

TriFunction도 있을 거라고 생각을 했는데 없어서 따로 만들어야 합니다.



@FunctionalInterface
public interface TriFunction<A, B, C, R> {

R apply(A a, B b, C c);

default <V> TriFunction<A, B, C, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (A a, B b, C c) -> after.apply(apply(a, b, c));
}
}



위와 같이 FunctionalInterface annotation을 넣은 TriFunction interface를 만듭니다.

생김새는 BiFunction과 거의 같습니다.



private TriFunction<String, String, String, String> createStatusErrorMessage = (paymentStatus, pgStatus, pgType) -> {
StringBuilder statusErrorBuilder = new StringBuilder();

//do something.

return statusErrorBuilder.toString();
};



BiFunction과 TriFunction의 사용방법도 같습니다.

예를 들어 Parameter를 분류하여 ErrorMessage 를 생성하는 용도로 사용한다고 하겠습니다.



private static Map<String, TriFunction<String, String, String, String>> errorBuilderMap = new HashMap<>();

public ErrorMessageBuilder() {
errorBuilderMap.put("STATUS", createStatusErrorMessage);
}


public String build() {
return errorBuilderMap.get(errorMessageBuilder.errorType)
.apply(errorMessageBuilder.data1, errorMessageBuilder.data2, errorMessageBuilder.data3);
}



위와 같이 Map<String, TriFunction> 에 Key로 사용할 에러 메세지 타이틀과 에러 메세지를 만들 TriFunction을 넣습니다.

그리고 파라미터를 입력받아 TriFunction을 get하여 TriFunction.apply(parameter1, parameter2, parameter3) 하면 해당 TriFunction이 호출되어 실행되고 값을 리턴합니다. 







[JPA] Id Annotation과 return cached data


JPA로 Query method를 작성해 사용하던 도중 이상함을 느꼈습니다.



List<UserTrack> findAll();



예를 들어 위와 같은 Query method를 사용하여 쿼리를 작성하여 사용했는데 이상하게 같은 데이터를 중복, 반복해서 return 하는 현상이 일어났습니다.

해당 문제는 Entity에서 @Id Annotation을 건 컬럼이 DB Table에서 유니크한 값을 가지고 있지 않은 경우에 발생합니다.



@Entity
public class UserTrack {
@Id
private Long age;

private String name;
}



만약 위와 같이 @Id Annotation을 걸었는데 테이블에서 해당 컬럼의 데이터가 중복될 경우 애매하여 이전에 찾아서 Cache 되었던 data가 있는지 찾습니다.


이를 해결하려면 


1. 해당 테이블의 PK에 Id Annotation을 겁니다.

2. PK가 없을 경우에는 EmbeddedId 를 만들어 복합키로 유니크한 Id를 만들어 줍니다.


이렇게 하면 제대로 select 해서 return 하는 것을 확인할 수 있습니다.



'Database > JPA & JPQL' 카테고리의 다른 글

JPA JsonMappingException: Infinite recursion (StackOverflowError) - 무한루프  (0) 2018.08.02
Pageable로 Limit 걸기  (0) 2018.06.23
JPQL이란?  (0) 2018.06.18



Pageable로 Limit 걸기


JPA, JPQL 을 사용하다가 Limit이 필요한 경우가 생겼었습니다.


JPQL에 limit을 사용하니 에러가 발생하여 찾아보니 JPA, JPQL 에서 limit의 기능을 사용하려면 다른 방법을 사용해야 합니다.



Pageable limitOneHundred = new PageRequest(startPosition, 100);
List<SelectAll> tempList = requestRepository.findAllByDate(startDate, targetDate, limitOneHundred);

@Query(value = "SELECT n " +
"FROM LogRequest n " +
"WHERE DATE(n.regDate) >= :date " +
"AND DATE(n.regDate) = :targetDate " +
"ORDER BY n.no")
public List<SelectAllNaver> findAllByDate(@Param("date") Date date,
@Param("targetDate") Date targetDate,
Pageable limitOneHundred);



Pageable 을 사용하면 limit을 사용하는 것처럼 범위를 지정하여 페이징을 할 수 있습니다.

혹은 아래와 같이 Query annotation의 nativeQuery 옵션을 true로 하여 limit을 사용하는 방법도 있습니다.



@Query(nativeQuery = true, value = "select * from SLSNotification s where s.userId = :userId ORDER BY snumber desc LIMIT 20")
List<SLSNotification> getUserIdforManage(@Param("userId") String userId);




참조.

http://www.baeldung.com/spring-data-jpa-query

https://stackoverflow.com/questions/47616482/how-to-limit-result-in-query-used-in-spring-data-repository



JPQL이란?


JPQL(Java Persistence Query Language) 는 JPA (Java Persistence API) 의 일부로 정의된 플랫폼 독립적인 객체지향 쿼리 언어 입니다.


JPQL 은 관계형 데이터베이스의 엔티티에 대한 쿼리를 만드는데 사용됩니다.


SQL 에 크게 영향을 받아 SQL 문과 비슷하지만 데이터베이스의 테이블에 직접 연결되는 것이 아니라 JPA 엔티티에 대해 동작합니다.


그래서 JPQL 의 쿼리에는 테이블이 아닌 엔티티에서 표현하고 있는 컬럼의 이름을 써줘야 합니다.


아래의 Spring을 통한 예시에 나와있습니다.



@Entity
public class UserStatistics {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String host;

private String href;

private String pathName;

private String referrerHost;

private String referrerPath;

private Long visitCount;

private LocalDate date;
}



@Query(value = "SELECT u.pathName as pathName, DATE(u.date) as date, u.visitCount as visitCount " +
"FROM UserStatistics u GROUP BY DATE(u.date), u.pathName, u.visitCount")
List<UserStatisticsDateCount> findVisitCountGroupByDate();




참조

https://en.wikipedia.org/wiki/Java_Persistence_Query_Language

http://www.baeldung.com/spring-data-jpa-query







[직관적인 클린 코드를 위해] IF - ELSE IF 문 줄이기 2



이번 블로그 글은 저번에 이은 '[직관적인 클린 코드를 위해] IF - ELSE IF 문 줄이기 2' 입니다.


저번에는 동시에 확인해야 하는 조건이 하나일 경우에 IF, ELSE IF 문을 줄이는 방법을 봤습니다.


하지만 만약 동시에 확인해야 할 조건이 2개면 어떨까요?



public static void main(String[] args) {
Map<String, Alphabet> typeMap = new HashMap<>();
typeMap.put("A", new A());
typeMap.put("B", new B());
typeMap.put("C", new C());

String type = "B";

if(첫번째 조건) {
typeMap.get(type).test();
}
else if(첫번째 조건) {
typeMap.get(type).test();
}
else if(첫번째 조건) {
typeMap.get(type).test();
}
}



어쩔 수 없이 IF, ELSE IF 문을 사용해야 하는 것일까요?


예를 들어 상황을 가정하겠습니다.

DB의 여러 테이블에서 데이터를 받는데 이를 테이블 명과 Insert, Update, Delete 로 동시에 구분해서 수행해야 한다고 가정하겠습니다.


공통적인 부분으로 묶어보겠습니다.


  • 각각의 테이블은 Insert, Update, Delete가 일어납니다.

interface Insert {
public void insert();
}

class A implements Insert{
@Override
public void insert() {

}
}

class B implements Insert{
@Override
public void insert() {

}
}

class C implements Insert{
@Override
public void insert() {

}
}



Insert 패키지를 만들고 insert 추상매서드를 가진 인터페이스를 생성했습니다.

그리고 이 인터페이스를 implements 하는 3개의 클래스를 각각 만들었습니다.

Update와 Delete도 똑같이 했습니다.


하지만 이렇게 해도 1번 글과 같이 Map에 넣어 한번에 알아서 분류되고 원하는 value가 나오게 하려면 Insert, Update, Delete 인터페이스 타입을 value로 가진 각각의 Map이 필요합니다.

다시 공통적인 요소를 생각해보면 Insert, Update, Delete는 DB의 Crud 중 하나입니다.



interface Crud {
public void execute();
}

interface Insert extends Crud{
public void insert();
}

class A implements Insert{
@Override
public void execute() {
insert();
}

@Override
public void insert() {

}
}



Crud 인터페이스를 만들어 이를 Insert, Update 인터페이스에 상속 시키고 A 클래스에서는 이를 구현합니다.

그리고 두가지 조건을 처리하기 위해 Enum 클래스를 만들어 사용했습니다.


enum CommandEnum {
INSERT_A("A", "insert"),
UPDATE_A("A", "update"),
INSERT_B("B", "insert"),
UPDATE_B("B", "update"),
INSERT_C("C", "insert"),
UPDATE_C("C", "update");

private String tableName;
private String queryType;

CommandEnum(String tableName, String queryType) {
this.tableName = tableName;
this.queryType = queryType;
}

public String getTableName() {
return this.tableName;
}

public String getQueryType() {
return this.queryType;
}
}



그리고 이런 Enum을 사용하기 위해 따로 Mapper 클래스를 만들었습니다.

Mapper 클래스에서는 EnumMap을 생성하고 이를 for - each 문으로 값을 두가지 조건에 합당하는 Crud 객체를 리턴합니다.



class QueryTypeMapper {
private EnumMap<CommandEnum, Crud> commandEnumMap = new EnumMap<CommandEnum, Crud>(CommandEnum.class);

QueryTypeMapper() {
commandEnumMap.put(CommandEnum.INSERT_A, new InsertA());
commandEnumMap.put(CommandEnum.UPDATE_A, new UpdateA());
commandEnumMap.put(CommandEnum.INSERT_B, new InsertB());
commandEnumMap.put(CommandEnum.UPDATE_B, new UpdateB());
commandEnumMap.put(CommandEnum.INSERT_C, new InsertC());
commandEnumMap.put(CommandEnum.UPDATE_C, new UpdateC());
}

public Crud getCommand(String tableName, String type) {
for(CommandEnum commandEnum : commandEnumMap.keySet()) {
if(commandEnum.getTableName().equals(tableName) && commandEnum.getQueryType().equals(type)) {
return commandEnumMap.get(commandEnum);
}
}
throw new IllegalStateException("Classify command not found. ");
}
}



이렇게 만들어진 QueryTypeMapper 클래스와 CommandEnum enum클래스를 이용해 IF - ELSE IF 문으로 어지러워 질뻔한 코드가 단 두줄로 변하게 됩니다.


아래에 전체 코드가 있습니다. InsertA 클래스만 있지만 나머지 클래스들도 같은 방식이라 넣지 않았습니다.


간략하게 짜고 바쁘게 하다보니 클래스와 매서드, 변수의 네이밍에 신경 못쓴점은 양해 부탁드립니다.



public class CleanCode {

public static void main(String[] args) {
String tableName = "B";
String type = "insert";

QueryTypeMapper queryTypeMapper = new QueryTypeMapper();
queryTypeMapper.getCommand(tableName, type).execute();
}
}



class QueryTypeMapper {
private EnumMap<CommandEnum, Crud> commandEnumMap = new EnumMap<CommandEnum, Crud>(CommandEnum.class);

QueryTypeMapper() {
commandEnumMap.put(CommandEnum.INSERT_A, new InsertA());
commandEnumMap.put(CommandEnum.UPDATE_A, new UpdateA());
commandEnumMap.put(CommandEnum.INSERT_B, new InsertB());
commandEnumMap.put(CommandEnum.UPDATE_B, new UpdateB());
commandEnumMap.put(CommandEnum.INSERT_C, new InsertC());
commandEnumMap.put(CommandEnum.UPDATE_C, new UpdateC());
}

public Crud getCommand(String tableName, String type) {
for(CommandEnum commandEnum : commandEnumMap.keySet()) {
if(commandEnum.getTableName().equals(tableName) && commandEnum.getQueryType().equals(type)) {
return commandEnumMap.get(commandEnum);
}
}
throw new IllegalStateException("Classify command not found. ");
}
}



enum CommandEnum {
INSERT_A("A", "insert"),
UPDATE_A("A", "update"),
INSERT_B("B", "insert"),
UPDATE_B("B", "update"),
INSERT_C("C", "insert"),
UPDATE_C("C", "update");

private String tableName;
private String queryType;

CommandEnum(String tableName, String queryType) {
this.tableName = tableName;
this.queryType = queryType;
}

public String getTableName() {
return this.tableName;
}

public String getQueryType() {
return this.queryType;
}
}



interface Crud {
public void execute();
}



interface Insert extends Crud{
public void insert();
}



class InsertA implements Insert{
@Override
public void execute() {
insert();
}

@Override
public void insert() {
System.out.println("Class A insert method!");
}
}



제가 준비한 내용은 여기까지 입니다.

부족함이 많은 글임에도 끝까지 읽어주셔서 감사합니다.

다음에도 이와 같이 기본적인 내용이지만 괜찮다고 생각드는 것이 있으면 잘 정리해서 공유드리겠습니다.

감사합니다.


[직관적인 클린 코드를 위해] IF - ELSE IF 문 줄이기



이번 블로그 글은 '[직관적인 클린 코드를 위해] IF - ELSE IF 문 줄이기' 입니다.


직장에서 코딩을 하고 집에 와서는 친구와 함께하는 개인적인 프로젝트를 진행하며 직관적이고 깔끔한 코드를 위해 많은 고민을 했었습니다.

정답은 아니지만 그 경험을 공유하고자 하는 글이니 편하게 봐주셨으면 좋겠습니다.


코딩을 하다 보면 깔끔하고 보기 좋게 짜겠다는 처음 생각과 다르게 지저분해지는 경우가 많습니다.

그 주요 요인 중 하나로 수많은 IF 문과 그에 이어지는 ELSE IF 문 을 많이 꼽으실 겁니다. (Switch - case 도..)

처음 생각과 다르게 여러가지 조건이 생기고 그 조건을 검사해서 분류해야 하는 경우 가장 쉽게 할 수 있는 방법이기에 더욱 그런 것 같습니다.

하지만 그 간편함과 다르게 점점 IF 문이 자라날수록 코드는 분간하기 어렵고 직관적인 코드와는 거리를 두게 됩니다.



public static void main(String[] args) {
String type = "?";

if("A".equals(type)) {

}
else if("B".equals(type)) {

}
else if("C".equals(type)) {

}
}



위 예시같은 경우 String 변수 type에 들어있는 값을 분류하고 그에 따라 다르게 실행하기 위해 IF문과 ELSE IF 문이 걸려있습니다.

지금은 검사할 조건이 3가지라서 보기 어렵지 않을 수 있지만 조건이 증가함에 따라 보기 어려워지게 됩니다.



public static void main(String[] args) {
String type = "?";

if("A".equals(type)) {
for(int i = 0; i < 10;i++) {
if() {

}
}
}
else if("B".equals(type)) {
for(int i = 0; i < 10;i++) {
if() {

}
}
}
else if("C".equals(type)) {
for(int i = 0; i < 10;i++) {
if() {

}
}
}
}



위 예시처럼 보기 어려운 코드가 될 수 있습니다.

이를 해결하기 위해선 여러가지 방법이 있을 겁니다.

그 중 저는 Map과 해당 부분을 다른 클래스로 분리를 하는 방법을 섞어썼습니다.


String 변수 type에 특정한 값이 들어오고 그 값을 분류하여 그에 따라 실행해야 하는 로직이 각각 있다고 가정하겠습니다.



public static void main(String[] args) {
Map<String, Object> typeMap = new HashMap<>();

typeMap.put("A", new A());
typeMap.put("B", new B());
typeMap.put("C", new C());

String type = "?";

typeMap.get(type).test();
}



  • 이 예시에서는 HashMap을 이용하였고 메인문 안에서 생성했지만 편의상 그렇게 한 것이니 본인의 상황과 코드에 맞춰 구현하면 됩니다.


"A" 조건을 분류하는 IF 문의 내부 로직은 클래스 A의 test 메서드에, 

"B" 조건을 분류하는 ELSE IF 문의 내부 로직은 클래스 B의 test 메서드에, 

"C" 조건을 분류하는 ELSE IF 문의 내부 로직은 클래스 C의 test 매서드에 넣었습니다.


그럼 testMap.get() 에 type을 넣음으로써 원하는 분류되어있는 로직을 가지고 있는 객체가 리턴될 것 입니다.

하지만 여러 객체를 담을 수 있도록 Map의 Value 부분을 Object로 선언하여 원하는 매서드를 호출하지 못하게 되었습니다.

마우스를 가져다 대면 'Cannot resolve method test()' 라는 메세지가 뜰 것 입니다.


이유는 Object 에는 test() 라는 매서드가 없는데 호출을 하려고하니 에러가 나는 것 입니다.

이를 해결하기 위해선 간단하게 interface를 추가하면 됩니다.



interface Alphabet {
public void test();
}



위와 같은 Alphabet 인터페이스를 추가하고 해당 인터페이스 내부에는 test() 라는 추상매서드를 추가하고 Map 의 value 자리에는 Object 대신 인터페이스 Alphabet 을 써줍니다.

그리고 해당 인터페이스를 각각 분류한 클래스에 implements 해주고 test() 메서드를 Overriding 해주면 끝입니다.



class A implements Alphabet{

@Override
public void test() {
System.out.println("Alphabet A!!");
}
}

class B implements Alphabet{

@Override
public void test() {
System.out.println("Alphabet B!!");
}
}

class C implements Alphabet{

@Override
public void test() {
System.out.println("Alphabet C!!");
}
}



이제 main 문의 typeMap.get(type).test(); 에 있던 빨간색은 사라졌을 겁니다.

그리고 돌려보면 type의 값에 따라서 출력을 하는 것을 볼 수 있습니다.



public static void main(String[] args) {
Map<String, Alphabet> typeMap = new HashMap<>();

typeMap.put("A", new A());
typeMap.put("B", new B());
typeMap.put("C", new C());

String type = "B";

typeMap.get(type).test();
}



제가 준비한 내용은 여기까지 입니다.

부족함이 많은 글임에도 끝까지 읽어주셔서 감사합니다.

다음에도 이와 같이 기본적인 내용이지만 괜찮다고 생각드는 것이 있으면 잘 정리해서 공유드리겠습니다.

감사합니다.



+ Recent posts