IO중에서 객체의 직렬화에 관심이 있어 이 부분을 위주로 공부를 했다. 
먼저 직렬화가 어떤 개념인 것인지 잠깐 보고 지나가보자.

직렬화-serialize란 자바의 객체를 바이트로 변환 후에 database나 file, memory에 저장하는 개념이다.
이와 반대로 database나 file, memory로부터 바이트를 읽어와 자바 객체를 만드는 것을 역직렬화-deserialize라고 한다.

https://data-flair.training/blogs/serialization-and-deserialization-in-java/

직렬화를 실무에선 어떻게 활용하고 있을까? 
많이 사용하고 있는 Jackson 라이브러리에서 이미 사용이 되고 있다. (json을 외부 서버로부터 받고 객체화 하는 부분)
본인의 토이프로젝트에서도 이미 사용이 되고 있었다. (역직렬화)
아마 database저장할 때에도 많이 사용이 될 것 같은데 자세한 건 잘 모르겠다.

public void scrap() throws Exception {
		logger.info("process start.");
		
		String body = WebClient.create().get()
							  .uri(createUriComponent())
							  .accept(MediaType.APPLICATION_JSON)
							  .retrieve()
							  .bodyToMono(String.class)
							  .block();
		
		if (!body.isEmpty()) {
			try {
				JsonNode jsonNodeRoot = new ObjectMapper().readTree(body);
				if (jsonNodeRoot.get("list") != null) {
			    	String jsonItemRoot = jsonNodeRoot.get("list").toString();
			    	List<LeeumData> listItem = new ObjectMapper().readValue(jsonItemRoot, new TypeReference<List<LeeumData>>() {});
					
					for (LeeumData list : listItem) {
						ExhibitionDto data = new ExhibitionDto("leeum", 
																	list.getTitle(), 
																	list.getImage(),
																	Formatter.parse(list.getStartDate()),
																	Formatter.parse(list.getEndDate()));
						exhibitionList.add(data);
					}
		        }
			} catch (JsonProcessingException | ParseException | 
					HttpClientErrorException| HttpServerErrorException e) {
				throw new Exception(e.getMessage());
			}
		}
		
		logger.info("process end.");
		resultListener.executed();
	}


간단하게 jackson 관련한 코드를 작성해보았는데 직접 객체에 java.io.Serializable을 하지 않아도 되었다.
그러나 기본적으로 데이터를 쓸때 필요한 getter method가 있어야 에러가 나지 않았다.

package com.scraping.app.serialize;

import java.io.File;
import java.io.IOException;

import com.fasterxml.jackson.core.exc.StreamWriteException;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Serial {
	public static void main(String[] args) throws StreamWriteException, DatabindException, IOException {
		UserInfo userInput = new UserInfo("name", "password");
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.writeValue(new File("/Users/user/workspace/javaStudy/log/result.txt"), userInput);
	}
}
package com.scraping.app.serialize;

public class UserInfo {
	String password;
	String name;
	
	public UserInfo(String password, String name) {
		this.password = password;
		this.name = name;
	}
	
	public String getPassword() {
		return password;
	}
//	public void setPassword(String password) {
//		this.password = password;
//	}
	public String getName() {
		return name;
	}
//	public void setName(String name) {
//		this.name = name;
//	}
}


여기서 조금 더 나아가 custom한,
예를 들자면 json으로 보내기 전 특정 필드값을 추가하고 싶다면 어떻게 해야 할까?
커스텀하게 serializerProvider를 만들어주면된다.

public class Serial {
	public static void main(String[] args) throws StreamWriteException, DatabindException, IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("CustomStickerSrializer", new Version(1, 0, 0, null, null, null));
        module.addSerializer(Sticker.class, new CustomStickerSerializer());
        objectMapper.registerModule(module);

        Sticker sticker = new Sticker("paper", "cat");
        String stickerJson = objectMapper.writeValueAsString(sticker);

        System.out.println(stickerJson);
    }
}
package com.scraping.app.validation;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

public class CustomStickerSerializer extends StdSerializer<Sticker>{
	public CustomStickerSerializer() {
		this(null);
	}
	public CustomStickerSerializer(Class<Sticker> t) {
		super(t);
	}

	@Override
	public void serialize(Sticker value, JsonGenerator gen, SerializerProvider provider) throws IOException {
		gen.writeStartObject();
		gen.writeStringField("type", value.getType());
		gen.writeStringField("char", value.getCharacter());
		gen.writeStringField("brand", "A");
	}

	
}

 

결과는 다음과 같이 나온다.
{"type":"paper","char":"cat","brand":"A"}



참고
https://www.baeldung.com/jackson-object-mapper-tutorial

https://koocci-dev.tistory.com/25

먼저 애노테이션을 왜 사용하는 것일까? 이에 좋은 예제가 있어 작성해보았다.


예를 들어 클래스의 메서드의 갯수 및 에러없이 잘 동작하는지에 대한 확인을 하는 테스트 코드를 작성해본다고 하자. 

다음은 방금 언급한 테스트 메서드의 내용을 확인하는 클래스이다.

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Set;

public class TestRunnerTest {

    public void singleMethodTest() {
        TestRunner runner = new TestRunner(SingleMethodTest.class);
        Set<Method> testMethods = runner.getTestMethods();
        if (1 == testMethods.size())
            System.out.println("expected single test method");

        Iterator<Method> it = testMethods.iterator();
        Method method = it.next();

        final String testMethodName = "testA";
        if (testMethodName.equals(method.getName()))
            System.out.println("expected " + testMethodName + " as test method");

        runner.run();
        if (1 == runner.passed())
            System.out.println("expected 1 pass");

        if (0 == runner.failed())
            System.out.println("expected no failures");

    }

    public void multipleMethodTest() {
        TestRunner runner = new TestRunner(MultipleMethodTest.class);
        Set<Method> testMethods = runner.getTestMethods();
        if (2 == testMethods.size())
            System.out.println("expected multiple test method");

        Iterator<Method> it = testMethods.iterator();
        Method method = it.next();

        final String testMethodNameA = "testA";
        final String testMethodNameB = "testB";
        if (testMethodNameA.equals(method.getName()))
            System.out.println("expected " + testMethodNameA + " as test method");

        if (testMethodNameA.equals(method.getName()))
            System.out.println("expected " + testMethodNameB + " as test method");

        runner.run();
        if (2 == runner.passed())
            System.out.println("expected 2 pass");

        if (0 == runner.failed())
            System.out.println("expected no failures");

    }
}

class SingleMethodTest {
    public void testA() {}
}

class MultipleMethodTest {
    public void testA() {}
    public void testB() {}
}

 


다음 코드는 테스트 클래스를 수행하는 환경 및 각 클래스의 메서드 정보를 제공하는 클래스이다.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class TestRunner {
    private Class testClass;
    private int failed = 0;
    private Set<Method> testMethods = null;

    public static void main(String[] args) throws Exception {
        TestRunner runner = new TestRunner(args[0]);
        runner.run();
        System.out.println(
                "passed: " + runner.passed() + " failed: " + runner.failed
        );
        if(runner.failed() > 0)
            System.exit(1);
    }

    public TestRunner (Class testClass){
        this.testClass = testClass;
    }

    public TestRunner(String className) throws Exception {
        this(Class.forName(className));
    }

    public Set<Method> getTestMethods(){
        if (testMethods == null)
           loadTestMethods();
        return testMethods;
    }

    private void loadTestMethods() {
        testMethods = new HashSet<Method>();
        for (Method method : testClass.getDeclaredMethods()){
            testMethods.add(method);
        }
    }

    public void run(){
        for (Method method: getTestMethods()) {
            run(method);
        }
    }

    private void run(Method method) {
        try {
            Object testObject = testClass.newInstance();
            method.invoke(testObject, new Object[] {});
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
            Throwable cause = e.getCause();
            if (cause instanceof  AssertionError)
                System.out.println(cause.getMessage());
            else
                e.printStackTrace();
            failed++;
        }
    }

    public int passed() {
        return testMethods.size() - failed;
    }

    public int failed() {
        return failed;
    }

}

 

수행시 다음과 같은 옵션을 주면 된다.

 


하지만... 
TestRunnerTest 클래스의 경우 singleMethodTest 와 multipleMethodTest에 비슷한 내용이 많이 보인다. 
이 경우 중복 코드를 바깥으로 빼낼 수 있을 것 같다. 동시에 이 메서드들은 내가 확인하고 싶은 메서드가 아닐 때 어떻게 해야할까?

즉, 내가 표시한 메서드만 테스트 메서드로 인정하고 싶다. 이런 경우 어떻게 해야할까? 바로 애너테이션을 이용하면 된다. 

public @interface TestMethod {}

 

그리고 TestRunnerTest 클래스는 다음과 같이 수정해준다.

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class TestRunnerTest {
    private TestRunner runner;
    private static final String methodNameA = "testA";
    private static final String methodNameB = "testB";

    private void runTests (Class testClass) {
        runner = new TestRunner(testClass);
        runner.run();
    }

    private void verifyTests (String... expectedTestMethodNames) {
        verifyNumberOfTests(expectedTestMethodNames);
        verifyMethodNames(expectedTestMethodNames);
        verifyCounts(expectedTestMethodNames);
    }

    private void verifyCounts(String... testMethodNames){
        if (testMethodNames.length == runner.passed())
            System.out.println("expected 1 pass");

        if (0 == runner.failed())
            System.out.println("expected no failures");
    }

    private void verifyNumberOfTests(String... testMethodNames) {
        if (1 == testMethodNames.length)
            System.out.println("expected " + testMethodNames.length + " test method(s)");
    }

    private void verifyMethodNames(String... testMethodNames) {
        Set<String> actualMethodNames = getTestMethodNames();
        for (String methodName : testMethodNames) {
            if (actualMethodNames.contains(methodName)) {
                System.out.println("expected " + methodName + " as test method");
            }
        }
    }

    private Set<String> getTestMethodNames() {
        Set<String> methodNames = new HashSet<String>();
        for (Method method: runner.getTestMethods()){
            methodNames.add(method.getName());
        }
        return methodNames;
    }

    @TestMethod
    public void singleMethodTest(){
        runTests(SingleMethodTest.class);
        verifyTests(methodNameA);
    }

    @TestMethod
    public void multipleMethodTest(){
        runTests(MultipleMethodTest.class);
        verifyTests(methodNameA, methodNameB);
    }
}

class SingleMethodTest {
    @TestMethod public void testA() {}
}

class MultipleMethodTest {
    @TestMethod public void testA() {}
    @TestMethod public void testB() {}
}

 

 

그러나 실행을 하면 다음과 같이 에러가 뜬다. private 메서드로 정의된 함수를 호출하면서 에러가 발생되는 것인데 
이 경우 어떻게 해 줘야할까? 


바로 애노테이션 함수인 것만 테스트 메서드로 추가하면 된다. 

private void loadTestMethods() {
        testMethods = new HashSet<Method>();
        for(Method method : testClass.getDeclaredMethods()){
            if(method.isAnnotationPresent(TestMethod.class)){
                testMethods.add(method);
            }
        }
    }

 

 

에러가 발생하진 않긴 하지만 수행이 하나도 되지 않는다. 그 이유는 무엇일까?

 

바로 애노테이션의 유지 (Retention)과도 관련이 있다.
기본적으로 실행시 애노테이션 정보를 얻을 수 없다. 그렇기 때문에 실행시에도 애노테이션 정보를 유지해서 해당 메서드의 정보를 받아올 수 있도록 Retention을 설정해야 하는 것이다.

애노테이션을 다음과 같이 수정해준다.

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface TestMethod {
}

 

잘 작동되는 것을 확인할 수 있다!

 

추가적으로 애노테이션 형식이 붙을 수 있는 타입을 지정할 수 있다. 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestMethod {
}


즉 이 경우 클래스 앞에 애노테이션이 붙으면 에러가 나는 것을 확인할 수 있다.

 

애노테이션 활용
youtube.com/watch?v=P5sAaFY3O2w

https://techblog.woowahan.com/2684/

https://devonce.tistory.com/42

https://mangkyu.tistory.com/174
Custom한 validator를 적용해보려 했으나 실패하였다.

package com.scraping.app.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import shaded_package.javax.validation.Constraint;
import shaded_package.javax.validation.Payload;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UrlValidator.class)
public @interface ValidationUrl {
	public String message() default "Invalid url format";
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
package com.scraping.app.validation;

import java.util.regex.Pattern;

import org.springframework.web.bind.MethodArgumentNotValidException;

import shaded_package.javax.validation.*;
import shaded_package.javax.validation.constraintvalidation.*;

@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class UrlValidator implements ConstraintValidator<ValidationUrl, String[]>{
	private static final String pattern = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
	
	@Override
	public boolean isValid(String[] urls, ConstraintValidatorContext arg1) {
		throw new IllegalArgumentException("Illegal");
		//System.out.println("isValid is called..");
		//return Pattern.matches(pattern, urls[0]);
	}

}

 

public void setURL(@ValidationUrl String newURL) {
		this.URL = newURL;
	}
    
    ...
    

test code
@Override
public void inValidUrlTest() throws Exception{
    openApiCollector.setURL("://127.0.0.1:1080");
    //exceptionRule.expect(MethodArgumentNotValidException.class);
    exceptionRule.expect(IllegalArgumentException.class);
}


애노테이션의 리텐션 타입과 메모리를 비교하며 이해하기
소스 코드  (Source) -> 바이트코드 (Class) -> 클래스 로딩 in JVM (Runtime)

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}



참고 : 제프 랭어 - 자바프로그래밍 / 교학사 

멀티쓰레드 프로그래밍을 왜 하는걸까?
하나의 프로그램에서 (프로세서) 동시에 코드가 수행되어야 하는 경우 Thread를 여러개 사용하는 멀티쓰레드 프로그래밍을 할 수 있다. (예: 채팅 프로그램 - 채팅창에 텍스트 내용을 입력하면서 동시에 다른 사람의 챗팅 내용을 읽어와야 하는 경우) 또한 자원을 효율적으로 사용하면서 (하나의 객체를 공유해 사용) 다양한 작업을 수행하기 위해 멀티쓰레드 프로그래밍을 한다.

Thread의 동작방식에 대해 알아보자!

동시성의 경우 각 쓰레드가 짧은 시간동안 번갈아 수행된다.



 
Thread 클래스를 상속하거나 Runnable 인터페이스를 구현하여 Thread를 생성할 수 있다.


Core와의 관계
Core 1개당 하나의 작업만 수행할 수 있다. Core가 4개가 있으면 4개의 작업을 동시에 수행할 수 있다. 
아주 짧은 시간 동안 각 쓰레드(작업)을 번갈아 가면서 수행한다. (Core의 갯수보다 Thread의 갯수가 2개 이상 많은 경우)



싱글쓰레드 VS 멀티쓰레드 

싱글쓰레드(작업 A,B를 하나의 쓰레드에) vs 멀티쓰레드의 차이(작업 A, B를 각각의 쓰레드에)
단일 코어에서 Thread가 2개 있다면?  -> 

public class MultiThread {
	
	public static void main(String[] args) {
		
		long start = System.currentTimeMillis();
		for (int i=0; i<500; i++) {
			System.out.print("|");
		}
		long current = System.currentTimeMillis();
		System.out.println("총 수행 시간 " + (current - start));
		
		
		for(int i=0; i<500; i++) {
			System.out.print("-");
		}
		current = System.currentTimeMillis();
		System.out.println("총 수행 시간 " + (current - start));
		
	}
	
}

단일 코어의 경우 한번에 한 작업만 수행할 수 있기에 동시에 자원을 공유해서 생기는 문제가 없다.
(그러나 하나의 작업이 끝나야 다음 작업을 수행할 수 있다.)


코어에서 Thread를 2개 수행하는 경우?

public class MultiThread {
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		
		Thread th = new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<500; i++) {
					System.out.print("-");
				}
				long current = System.currentTimeMillis();
				System.out.println("총 수행 시간 " + (current - start));
			}
		});
		th.start();
		
		for (int i=0; i<500; i++) {
			System.out.print("|");
		}
		long current = System.currentTimeMillis();
		System.out.println("총 수행 시간 " + (current - start));
	}
}

 

쓰레드의 수행 순서나 수행 시간은 장담할 수 없다. (Thread Scheculer가 모든 것을 결정한다.)
코어에 할당된 쓰레드가 동시에 수행될 수 있다. (위의 예의 경우 콘솔창에 프린트 하려는 경우) 


start() 메서드의 의미 


+ heap 메모리를 공유한다


 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Search {
	private URL url;
	private String searchString;
	private Pattern pattern;
	private int matches;
	
	public String getUrl() {
		return this.url.toString();
	}

	public String getSearchString() {
		return this.searchString;
	}
	
	public int getMatches() {
		return this.matches;
	}

	public Search (String url, String searchWord) {
		try {
			this.url = new URL(url);
			this.searchString = searchWord;
			this.pattern = Pattern.compile(searchWord, Pattern.CASE_INSENSITIVE);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}
	
	public void searchString() throws IOException {
		InputStream input = url.openConnection().getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(input));
		
		String line;
		int result = 0;
		while ((line = reader.readLine()) != null) {
			Matcher matcher = pattern.matcher(line);
			while (matcher.find()) {
				result++;
			}
		}
		
		matches = result;
	}
}
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class Server extends Thread{

	private List<Search> searchList;
	private ResultListener listener;
	
	public Server(ResultListener listener) {
		searchList = Collections.synchronizedList(new LinkedList<Search>());
		this.listener = listener;
		start();
	}
	
	public void add(Search search) {
		this.searchList.add(search);
	}
	
	public void run() {
		while(true) {
			try {
				if(searchList.size() > 0) {
					Search search = searchList.get(0);
					executed(search);
				}
				Thread.yield();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void executed(Search search) throws IOException {
		search.searchString();
		listener.executed();
	}
	
}
public interface ResultListener {
	public void executed();
}

 

import static org.junit.Assert.assertTrue;

import java.io.IOException;

import org.junit.jupiter.api.Test;

public class ServerTest {

	private int numbefOfResults;

	class Result implements ResultListener {
		@Override
		public void executed() {
			numbefOfResults++;
		}
	}
	
	private final String url = "http://www.langrsoft.com/";
	private final String searchString = "langr";
	private final String[] URLS = new String[] {
		url,
		url,
		url
	};
	
	@Test
	void testSearch() throws IOException {
		Server server = new Server(new Result());
		
		for (String url : URLS) {
			server.add(new Search(url, searchString));
		}
		
		assertTrue(waitForResults());
	}
	
	private boolean waitForResults() {
		long start = System.currentTimeMillis();
		while (numbefOfResults < URLS.length) {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			if (System.currentTimeMillis() - start > 3000) {
				return false;
			}
		}
		return true;
	}
	
}

 

쓰레드의 상태
NEW (쓰레드가 처음 생성되었고, 아직 실행 대기 중인 상태)
RUNNABLE (실행중인 상태)
TERMINATED (종료 상태)
BLOCKED (I/O 대기 상태)
TIME WAITING (특정 시간 동안 대기 상태)
WAITING (대기 상태)




쓰레드의 우선순위
우선순위의 범위는 1 ~ 10이 있다. 기본으로는 5가 부여된다.
우선순위가 높을수록 할당하는 시간, 순서를  높게 부여된다고 한다.
그러나 운영체제별로 우선순위에 대한 정책이 다를 수 있으니 우선순위가 높다고 하더라도
설정 여부에 대한 예측이 어렵다. 

public class ThreadPriority {
	public static void main(String[] args) {
		Thread th1 = new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<300; i++) {
					System.out.print("|");
					for(int x=0; x<10000000; x++);
				}
			}
		});
		
		Thread th2 = new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<300; i++) {
					System.out.print("-");
					for(int x=0; x<10000000; x++);
				}
			}
		});
		th2.setPriority(7);

		th1.start();
		th2.start();
	}
}

위의 경우도 실행시간이 높은 경우는 단 한1번 뿐이었다. (4코어에서 테스트)




 

 

 

쓰레드에서 자원을 다루는 법 with synchronized

문제가 생기는 코드 

class Bank {
	private int money = 10000;
	
	int getMoney() {
		return this.money;
	}
	
	void withdraw(int amount) {
		money -= amount;
	}
}

public class MyThread {

	public static void main(String[] args) {
		Bank myBank = new Bank();
		
		Thread mobile = new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<15; i++) {
					if (myBank.getMoney() > 0) {
						try {
							Thread.sleep(2);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						myBank.withdraw(1000);
						System.out.println("mobile withdraw success! money : " + myBank.getMoney());
					}
				}
			}
		});
		
		Thread atm = new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<15; i++) {
					if (myBank.getMoney() > 0) {
						try {
							Thread.sleep(2);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						myBank.withdraw(1000);
						System.out.println("atm withdraw success! money : " + myBank.getMoney());
					}
				}
			}
		});
		
		mobile.start();
		atm.start();
		
		while (myBank.getMoney() > 0) {
			Thread.yield();
		}
		
		System.out.println("current money = " + myBank.getMoney());
	}
}

Synchronized와 락?

 

 

리팩토링 및 문제 해결 코드

class Bank {
	private int money = 10000;
	
	int getMoney() {
		return this.money;
	}
	
	void withdraw(int amount) {
		money -= amount;
	}
	
	synchronized void run() {
		for(int i=0; i<15; i++) {
			if (getMoney() > 0) {
				try {
					Thread.sleep(2);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				withdraw(1000);
				System.out.println("atm withdraw success! money : " + getMoney());
			}
			if (getMoney() < 0) {
				System.out.println("current money : " + getMoney());
			}
		}
	}
}

public class MyThread {
	public static void main(String[] args) {
		Bank myBank = new Bank();
		
		Thread mobile = new Thread(new Runnable() {
			public void run() {
				myBank.run();
			}
		});
		
		Thread atm = new Thread(new Runnable() {
			public void run() {
				myBank.run();
			}
		});
		
		mobile.start();
		atm.start();
	}
}

 

synchronized 관련 재미있는 예제 

import java.util.Date;

public class Clock implements Runnable{
	private Listener listener;
	private boolean run = true;
	
	public Clock(Listener listener) {
		this.listener = listener;
		new Thread(this).start();
	}

	public void stop() {
		run = false;
	}
	
	public void run() {
		long lastTime = System.currentTimeMillis();
		
		while (run) {
			try {
				long now = System.currentTimeMillis();
				Thread.sleep(10);
				while (System.currentTimeMillis()/1000 - lastTime/1000 >= 1) {
					listener.update(new Date());
					lastTime = System.currentTimeMillis();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.junit.jupiter.api.Test;

class ClockTest {
	Clock clock;
	Object monitor = new Object();
	private final int seconds = 5;
	
	@Test
	void testClock() {
		List<Date> tics = new ArrayList<Date>();
		
		clock = new Clock(new Listener() {
			int count = 0;
			
			public void update(Date date) {
				tics.add(date);
				if (++count == seconds) {
					synchronized(monitor) {
						monitor.notifyAll();
					}
				}
			}
		});
		
		try {
			synchronized(monitor) {
				monitor.wait();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		clock.stop();
		verify(tics, seconds);
	}
	
	void verify(List<Date> tics, int seconds) {
		assertEquals(tics.size(), seconds);
		for (int i=1; i<seconds; i++) {
			assertEquals(1, getSeconds(tics, i));
		}
	}
	
	int getSeconds(List<Date> tics, int index) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(tics.get(index));
		int now = cal.get(Calendar.SECOND);
		cal.setTime(tics.get(index-1));
		int then = cal.get(Calendar.SECOND);
		if (then == 0) {
			then = 60;
		}
		
		System.out.println("time = " + (now - then));
		return now - then;
	}
}

 

import java.util.Date;

public class Listener {
	public void update(Date date) {
		
	}
}

 

 

 

 

 

 

 




동기화
synchronized 메서드 혹은 Block으로 시작하는 이름의 자료구조로 설정할 수 있다.
데이터를 다른 쓰레드에서 동시에 쓰지 못하도록 메서드를 한 쓰레드에서만 선점하는 역할을 한다.
락을 건다고 한다.

데드락 
교착 상태 

Main 쓰레드
Main 쓰레드의 경우 call stack에 가장 하위, 즉 맨 처음 호출되는 것이 아닐까..?

'Development > Java' 카테고리의 다른 글

[white ship - 12주차] IO  (1) 2024.03.16
[white ship - 11주차] 애노테이션  (0) 2024.03.09
[white ship - 8주차] 인터페이스  (0) 2024.02.14
[white ship - 7주차] 패키지  (0) 2024.02.14
[white ship - 6주차] 상속  (0) 2024.02.06

먼저 인터페이스의 상속 및 default, static 관련한 개념부터 간단하게 파악하고 가자.

 

package interfaceTest;

interface A extends B, C{
	public int memA = 1;
	public static final int memB = 2;
	
	default void deA() {
		System.out.println("This is default method");
	}
	
	static void stA() {
		System.out.println("This is static method");
	}
	
	public abstract void abA();
	public abstract void abB();
	
}



(생각) interface에도 멤버변수와 몸통이 있는 (static, defalut) 메서드를 구현할 수 있다는 규칙을 알 수 있었다. 
멤버변수의 경우 상수이므로 공통적으로 사용할 변수가 있는 경우에 사용하면 될 것 같다.
static메서드도 위와 같은 경우로 생각하면 될 것 같은데.. default는 왜 필요한 것일까?
(자문자답 -> 그 이유는 추상 메서드를 사용하는 여부에 있는 것 같다. 다음 예제를 보자.)

package interfaceTest;

public interface Fightable {
	public void fight();
	public String getWeapon();
	
	default void printWeapon() {
		System.out.println(getWeapon());
	}
}

 

package interfaceTest;

public class Chracter implements Fightable{

	@Override
	public void fight() {
		System.out.println("fight");
	}
	
	@Override
	public String getWeapon() {
		return "magic wand";
	}

	public static void main(String[] args) {
		Chracter ch = new Chracter();
		ch.fight();
		
		Fightable ch2 = ch;
		ch2.fight();
		
		ch2.printWeapon();
	}
	
}

 

몇개월 전에 Open Api 및 Leeum 서버의 Api를 호출해 database에 저장하는 scraper service를 개발한 적이 있었다. 
자바 관련해서 정말 얕게 알고 있던터라 리팩토링하면서 앞서 배운 개념들을 적용하는 작업을 진행했다.


변경내용은 크게 2가지이다. 
1. interface로 되어 있던 Collector를 abstract class로 변경
2. service class를 interface로 변경 

1번을 수행하면서 abstract와 interface의 차이를 명확히 알게되었다.
초기에 작업을 할 때에는 중복적인 메서드가 있다면 interface를 구현하여 개발한다. 만 알고 있던 상태였기에
그저 이대로 작업했었다. 근데 지금 다시 코드를 보니 interface의 구현체 클래스들에서 중복적인 내용이 많이 보였다. 

서버의 정보 및 API 호출 관련 중복 멤버들

그리고 인터페이스와 구현체 클래스 타입의 관계적으로 보아도
1. A는 B다 관계가 성립되었다. (OpenApiCollector는 Collector이다.)
2. 앞서 말했듯 중복적인 코드가 있었고 (URL, URI, UriComponent 관련 코드)
3. scrap() 메서드는 각 구현체 클래스에서 알아서 구현해야 했다. (abstract 메서드로)

이 경우는 abstract 클래스로 작업을 하는 것 같았고, 나도 이렇게 리랙토링을 시도해보았다. 


* 조금 더 간단한 예제 코드가 있나?
다음 링크를 참고하면 좋다. 
https://www.geeksforgeeks.org/difference-between-abstract-class-and-interface-in-java/
abstract은 앞서 언급한 3가지가 해당된다. interface의 경우는 오직 메서드의 signature부분만 정의되어 있다. 


인터페이스 타입의 Collector

 

추상 타입의 Collector

 

자식 클래스의 코드의 중복 사항은 부모 클래스가 가지고 있기에, 코드의 량이 줄어든 것을 알 수 있다.



서비스의 경우도 로직이 바뀔 수 있을 수도 있기에 interface의 메소드로 빼내어 이를 구현하는 식으로 변경하였다.



+ 추가적으로 test class의 경우도 중복인 경우가 많이있었다. 중복 코드는 추상클래스로 빼내는 식으로 작업을 진행하였다.

 



  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9
    위 내용은 https://catch-me-java.tistory.com/44 을 참고하여 공부하도록 하자!
  • 르package 키워드
    • (배운 것) 이름 관례

https://www.oracle.com/java/technologies/javase/codeconventions-namingconventions.html

    • (내가 알던 것) 연관된 소스들의 최소 그룹 단위 (service, vo, dto, dao, entity 등) / 소스명의 중복관리
    • (내가 알던 것) 도메인은 유일하기에 회사별 도메인을 거꾸로한 패키지명을 최상위에 붙인다.
      회사 내부 규칙을 정해 패키지명 하위의 폴더/소스명을 붙인다.
    • (내가 알던 것) 소스 코드내 상위 맨 처음에 한 줄로 넣는다. 

 

  • (궁금한 것) 다른 회사/오픈소스에서는 어떤 식으로 패키지를 생성하고 관리할까?
  • (새로배운 것) 
    1. package 명령어 : <소스명> 타입에서 <패키지명.소스명> 타입으로 만든다.
    패키지가 다른 소스를 사용하고 싶을 때 <패키지명.소스명>을 import해서 사용할 수 있다.
    2. 논리적인 package와 물리적인 폴더위치가 같아야 한다.  
    3. java로 실행할 때 <패키지명.소스명>으로 실행해야 한다. 

  • import 키워드
    • (알고 있는 것)
      다른 패키지의 소스를 가져와서 사용하고 싶을 때 사용한다. 

    • (배운 것)
      static 멤버의 경우 import 앞에 static을 붙인다.

  • 클래스패스
  • CLASSPATH 환경변수
    jvm이 사용하는 환경변수에는 크게 JAVA와 CLASSPATH 환경변수가 있다. (아마 다른 환경변수도 많이 있을 것이다.)

PATH

 

CLASSPATH

      

  • -classpath 옵션

  • 접근지시자
    리팩토링을 하면서 아~ 내가 직접 사용해본 경험이 적어 개념적으로만 알고 있구나 싶었다. 
    그리고 저번주에 공부했던 상속과의 개념에서 부모의 멤버에 대해 다 사용이 가능하다~ 라는 것으로 뭉뚱그려
    생각했었었는데 이렇게만 설명하고, 알고있으면 안되는 것이었다.
    접근지시자가 어떤 것이 붙느냐에 따라 자식이 접근할 수 있는 멤버변수가 달라진다.
    public의 경우 부모클래스 + 자식클래스 내부 + 같은/외부 패키지의 클래스에서 접근 가능 
    default의 경우 부모클래스 + 자식클래스 내부 + 같은 패키지의 외부 클래스에서 접근 가능
    protected의 경우 부모클래스 + 자식클래스 내부에서만 접근 가능 
    private의 경우 부모클래스 내부에서만 접근 가능 

    다음과 같은 문제가 발생했는데 처음에는 왜 난 것인지 잘 이해가 안되었기 때문이다. 
    protected 접근지시자를 사용하면 다른 패키지에 위치한 클래스에서
    (다른 패키지에서 클래스에서 접근하려면 public으로 바꾸어야 한다.) 
    자식 클래스 객체를 생성해서 사용하더라도 사용가능한 것으로 이해했는데 잘못이해한 것이었다. 

 

 

(생각) intellij나 sts에서는 어떻게 class path를 관리할까? build 하면 target 하위에 .class 파일들이 생성되는 것 같은데
해당 폴더의 위치가 CLASSPATH에 추가되는 것일까?


참고:
https://docs.oracle.com/javase/tutorial/java/package/QandE/packages-questions.html

https://docs.oracle.com/javase/tutorial/essential/environment/paths.html

Q. 자바에서의 상속은 어떤 의미일까?
A. 부모 클래스의 메서드를 사용하겠다라는 의미이다. 

 

예를 들어 다음과 같은 text 파일 기반의 퀴즈를 푸는 애플리케이션을 개발한다고 하자. 
먼저 UI구성부터 진행해보자. Java Swing의 JPanel을 활용해
Q. <질문> A. <유저의 응답> 4가지 하위 UI로 구성된 패널을 구성하고 싶은데 (퀴즈 갯수에 따라 100개로 늘어날 수도 있다)
이런 경우 어떻게 해야 할까?

퀴즈 애플리케이션

*여기서 잠깐 Java Swing의 JPanel은 어떻게 쓰는거야?

JPanel p = new JPanel(new BorderLayout()); //PREFERRED!
aFlowPanel.add(aComponent);


참고: https://docs.oracle.com/javase/tutorial/uiswing/components/panel.html


이 경우 JPanel의 기능을 사용하면서 커스텀하게 UI를 배치하고 싶은 것이니
JPanel을 상속받은 GuiQuiz를 작성하면 된다.

 

package quiz3.gui;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import quiz3.Quiz;

public class GuiQuiz extends JPanel{
	
	private String question;
	private String answer;
	
	private JLabel questionLabel;
	private JLabel questionTextLabel;
	private JLabel userAnswerLabel;
	private JTextField userAnswerTextField;
	private JLabel answerLabelField;
	private JLabel resultLabel;
	
	public GuiQuiz (Quiz quiz, int x, int y, int width, int height) {
		this.question = quiz.getQuestion();
		this.answer = quiz.getAnswer();
		initialize(x, y, width, height);
	}
	
	public void initialize(int x, int y, int width, int height) {
		setLayout(null);
		setBounds(x, y, width, height);
		
		questionLabel = new JLabel("Q. ");
		questionTextLabel = new JLabel(this.question);
		questionLabel.setBounds(20, 10, 61, 16);
		questionTextLabel.setBounds(100, 10, 179, 16);
		userAnswerLabel = new JLabel("A. ");
		userAnswerTextField = new JTextField();
		userAnswerLabel.setBounds(20, 40, 61, 16);
		userAnswerTextField.setBounds(100, 40, 179, 16);
		
		answerLabelField = new JLabel(this.answer);
		answerLabelField.setBounds(120, 60, 100, 16);
		answerLabelField.setVisible(false);
		
		resultLabel = new JLabel("");
		resultLabel.setBounds(120, 80, 100, 16);
		resultLabel.setVisible(false);
		
		add(questionLabel);
		add(questionTextLabel);
		add(questionLabel);
		add(questionTextLabel);
		add(userAnswerLabel);
		add(userAnswerTextField);
		add(answerLabelField);
		add(resultLabel);
		
		setVisible(true);
	}
	
	public void setAnswerTextToVisible(boolean bool) {
		answerLabelField.setVisible(bool);
	}
	
	public void setResultLabelText(String text) {
		resultLabel.setText(text);
	}
	
	public void setResultLabelToVisible(boolean bool) {
		resultLabel.setVisible(bool);
	}
	
	public String getUserAnswer() {
		return userAnswerTextField.getText();
	}
}


이대로 완성인줄 알았지만 퀴즈 리스트를 mysql이라는 database에서도 가져오고 싶다는 요구사항이 추가되었다. 
이 경우 어떻게 해야할까? 그냥 클래스를 추가해주면 되지 않을까?

퀴즈 애플리케이션

File에서 데이터를 읽어오는 FileDao.java와 Mysql에서 데이터를 읽어오는 MysqlDao.java를 작성했다. 
하지만 각각 코드를 작성하다보니 두 클래스에 각각 내용은 다르지만 하나의 동작으로 묶일 수 있는
메서드 4개로 추출이 되는 것을 확인했다.

- 데이터를 생성한다. 
- 데이터를 읽는다.
- 데이터를 삭제한다.
- 데이터를 추가한다.

이경우 해당 코드를 다른 클래스로 추출하고 이를 클래스를 상속하여 각자 알아서 구현하여 사용하면 되는 것이다.

 

public abstract class Dao {
	
	public abstract ArrayList<Quiz> read() throws Exception;
	public abstract int create(Quiz quiz) throws Exception;
	public abstract int update(Quiz src, Quiz dst) throws Exception;
	public abstract int delete(Quiz src) throws Exception;
	
	public void printStart() {
		System.out.println("========== Dao start =========");
	}
	
	public void printEnd() {
		System.out.println("========== Dao end =========");
	}
}


FileDao.java

@Override
	public ArrayList<Quiz> read() {
		printStart();
		
		ArrayList<Quiz> result = new ArrayList<Quiz>();
		
		try {
			BufferedReader read = new BufferedReader(new FileReader("/Users/user/Documents/workspace/whitship/quiz3/src/quiz3/quiz.txt"));
			String line = "";
			
			while((line = read.readLine()) != null) {
				if(line.isEmpty()) continue;
				String[] quiz = line.split("\\|");
				result.add(new Quiz(quiz[0], quiz[1]));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		printEnd();
		return result;
	}



MysqlDao.java

@Override
	public ArrayList<Quiz> read() throws Exception {
		printStart();
		
		ArrayList<Quiz> quiz = new ArrayList<Quiz>();
		
		ResultSet rs = executeQuery("select * from quiz_tbl");
		while (rs.next()) {
			System.out.printf("question = %s , answer = %s %n", rs.getString(2), rs.getString(3));
			quiz.add(new Quiz(rs.getString(2), rs.getString(3)));
		}
		
		printEnd();
		return quiz;
	}


Q. 상속의 이점은 무엇일까?
A. 크게 3가지가 있다고 한다.
1.코드의 중복을 방지한다. 2.공통적인 규약을 정의한다.(사용할 메서드) 3.다형성 (Polymorphism)


Q. 자바언어에서의 상속의 특징이 따로 있나?
A. 다중 상속이 안된다고 한다. 다른 언어에서는 다중상속이 된다고 하는데 다중상속 시 다이아몬드 문제를 겪을 수 있다고 한다.
(예를 들어 같은 이름의 메서드를 상속할 때 어떤 부모의 메서드인지 헷갈릴 수 있다고 하는데, 정확히 이해한 것인지는 모르겠다.)
이를 보완하기 위해 나온 개념이 interface 라고 한다. 


Q. 상속받은 클래스에서 객체를 생성하면 메모리 구조가 어떻게 되는가?
A. 부모 인스턴스 변수를 포함하여 객체를 생성한다.



Q. 다형성이란 무엇일까?
A. 객체를 가리키는 레퍼런스 변수를 선언하거나, 매개변수에 객체를 전달할 때
자식클래스의 타입뿐 아니라, 부모클래스의 타입으로도 전달할 수 있는 것을 의미한다.
즉 그럴 수 있는 이유는 부모클래스를 상속한 자식클래스에서 당연히 부모클래스의 메서드를 사용할 수 있기 때문이다.

자바에서 저장할 수 있는 값은 크게 두종류로 나뉜다. (프리미티브와 레퍼런스)
프리미티브의 경우 값 자체가 복사되어 변수(메모리)에 저장되기 때문에 값의 손실이 없어야 하므로
값의 타입과 변수의 타입을 동일하게 맞춰 값을 변수에 할당해야 했다.
(물론 값의 타입이 다르더라도 자동형변환이나 캐스팅을 사용해 타입을 맞출 수 있다.) 

레퍼런스의 경우 객체의 주소가 복사되는데 이때는 주소값이 가리키는 객체의 메서드가 중요한 데이터다.
아까 언급했듯이 이미 자식이 부모클래스의 메서드를 가지고 있고 사용할 수 있기 때문에
객체를 레퍼런스의 형에 맞게 부모 형으로 upcasting해도 괜찮은 것이다.

 

A a = new A();
Dao da = (Dao) a; 
Object o = (Object) a;



(복습) 자바는 크게 2가지 값의 종류가 있다. 

 

타입이 다른 경우 컴파일 에러가 발생한다.




Q. 만약 부모 클래스 타입으로 객체를 가리켰지만,
실제 그 객체의 인스턴스의 메서드를 호출하고 싶다면 어떻게 해야할까?
A. instanceof로 실제 호출할 클래스의 타입이 맞는지 확인 후에 캐스팅하여 메서드를 호출하면 된다.
-> 하지만 이 경우 if문이 계속해서 추가될 수 있는 단점이 있다. 자식 클래스를 인터페이스로 묶어주고, 메서드를 호출한다.
(다이나믹 메서드 디스패치)

 

ArrayList에서 사용되는 Object


Q. 자바의 상속하면 Object 클래스가 가장 최상위 부모라고 많이 나오는 것 같다. 
Object 클래스가 왜 최상위 부모인 것일까?

A. 앞서 말했듯 Java에서 상속이란 우리가 알고 있는 "물려준다"에서 끝나지 않는다 (inheritance).
자식에서 부모의 메서드를 사용하기 위해 상속을 사용하는 것이다 (extend).
(자바에서는 부모 메서드를 활용하면서 확장하라는 것을 권장하는 것 같다.)
즉 Object가 모든 클래스의 최상위 부모 클래스라는 것은 모든 클래스에서 사용할 메서드를 정의해 놓은 클래스라는 의미이다.
+ 그리고 모든 객체를 하나의 타입으로 묶어주어야 매개변수 등으로 활용할 수 있다고 한다. (일급객체?와 관련이 있는 것 같기도 하다)

https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html


Q. Object 클래스에서 제공되는 메서드는 어떤 것이 있지?
A. 객체와 관련된 메서드 (finalize(), clone(), hashCode(), toString()) / 쓰레드 관련된 메서드 (notify(), wait() ... ) / 클래스 메서드 (getClass) 가 있다.

https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html


Q. Object와 Class는 어떤 관계지?

  • (다른 질문) Q. runtime class of this Object에서 runtime class란?
    Java언어에서는 compiletime, runtime 2가지 환경이 있는 것으로 알고 있다.
    compiletime은 문법적인 것만 확인하는 것이고, runtime은 소스를 실행하는 환경을 말하는 것이다. 
    실제 실행할 때의 (그렇다면 실행 직전에 다른 Object로 replace될 수도 있나보다!)
    객체의 class를 리턴하는 문법인 것 같다. 
  • (다른 질문) Q. static synchronized는 뭐지?
  • (다른 질문) Q. <?> 는 뭐지?


Q. super 키워드란?
하위 클래스에서 상위 클래스 객체를 가리킬 때 사용하는 키워드이다.
(super.super는 안되는 것 같다. 다중상속이 안되므로 하나만 가리키는 것이 당연할 수도?)

명시적으로 호출하는 경우 맨 처음에 위치되어있어야 한다.


Q. super가 어느 경우에 쓰이는 걸까? 
메소드 오버라이딩할 때 완전히 처음부터 새로운 개발을 하는 것이 아닌 (new) 부모에서 확장한다는 개념으로 (extend) 
작성할 때, 부모의 메서드를 호출하기 위해 지칭한다.

스프링 부트의 내부 동작을 따라하는 코드

 

Q. 어느 경우에 부모/자식 클래스를 만들고 추상 클래스 및 인터페이스를 선언하는 것일까?
부모 클래스 : 중복적인 코드가 있으면서 A는 B다 라고 했을 때 B라면 부모 클래스로 정의한다.
자식 클래스 : A는 B다라고 했을 때 A라면 자식클래스로 한다. 혹은 그냥 클래스로 정의한다. 
추상 클래스 : new로 생성해야 할 필요가 없는 클래스, 즉 자신을 상속한 자식이 구체적인 메서드를 정의해야하는 경우 이면서 자식외에 기본적으로 수행해야할 코드는 자신이 가지고 있는 경우 
인터페이스 : 상속 관계에 얽매이지 않고 자신을 구현한 클래스가 구체적인 역할을 수행해야 할 때

public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
		implements ConfigurableWebApplicationContext, ThemeSource {
        ...

        @Override
            public void setServletContext(@Nullable ServletContext servletContext) {
                this.servletContext = servletContext;
            }
        ...
}

 

package org.springframework.context.annotation;

/**
 * Common interface for annotation config application contexts,
 * defining {@link #register} and {@link #scan} methods.
 *
 * @author Juergen Hoeller
 * @since 4.1
 */
public interface AnnotationConfigRegistry {

   /**
    * Register one or more component classes to be processed.
    * <p>Calls to {@code register} are idempotent; adding the same
    * component class more than once has no additional effect.
    * @param componentClasses one or more component classes,
    * e.g. {@link Configuration @Configuration} classes
    */
   void register(Class<?>... componentClasses);

   /**
    * Perform a scan within the specified base packages.
    * @param basePackages the packages to scan for component classes
    */
   void scan(String... basePackages);

}

 

Q. 다이나믹 메소드 디스패치 (Dynamic Method Dispatch) 란 무엇일까?
메소드 디스패치는 크게 "정적"과 "다이나믹" 두개로 나뉜다고 한다. 
정적의 경우, 컴파일 시에 어떤 객체에서 호출하는 메서드인지 명확히 아는 것이다.
다이나믹은 컴파일 시에는 모르고 (상속 관계에 있는 클래스 중 부모를 호출하는지 자식을 호출하는지 아직은 모른다. 
실행이 되어야 JVM의 메모리에 생성이 되기 때문이다.) 런타임 시에 알 수 있는 것이라고 한다.


(생각) 아직은 개념 정도만 이해한 것 같다.
https://velog.io/@eddy_song/dynamic-dispatch

Q. 더블 디스패치 (Dynamic Method Dispatch) 란 무엇일까? (다이나믹 메서드 디스패치를 두번 한 것이라고 한다.)

POST - 2가지 종류, SNS - 2가지 종류 가 있을때 위 4가지 내용을 출력하고 싶다고 하자. 
Post interface {
  doPost(Sns s);
}
Text implement Post {
  doPost(Sns s) {
     1) 만약 s의 타입이 facebook이라면 -> 
     ...     
  }
}
Picture implement Post {
  doPost(Sns s) {
     1) 만약 s의 타입이 facebook이라면 -> 
     ...     
  }

List<Post> posts = Arrays.asList(new Text(), new Picture());
List<Sns> sns = Arrays.asList(new Facebook(), new Instagram());

posts.forEach(post -> sns.forEach(_sns->post.postOn(_sns)));
// 컴파일 시에는 post.postOn이 어떤 객체의 메소드를 호출하는지 모른다.


위 1) 번 부분을 어떻게 수정할 수 있을까? -> 이 경우도 메서드 디스패치를 활용한다.
Sns interface {
  post(Text);
  post(Picture);
}

Facebook implements Sns {
   post(Text) {
  } 
  ...
}

Text implement Post {
  doPost(Sns s) {
     s.post(this); // 컴파일 시에는 this가 어떤 Sns객체인지 모르기에 어떤 오버로딩한 메서드가 호출될지 모른다.
  }
}
Picture implement Post {
  doPost(Sns s) { 
    s.post(this);
}


한번만 수행하는 다이나믹 디스패치

package dispatch;

import java.util.Arrays;
import java.util.List;

public class DoubleDispatch1 {
	
	interface Post {
		void postOn(Sns sns);
	}
	
	static class Text implements Post {
		public void postOn(Sns sns) {
			if (sns instanceof Facebook) {
				System.out.println("facebook - text");
			}
			if (sns instanceof Instagram) {
				System.out.println("instagram - text");
			}
		}
	}
	
	static class Picture implements Post {
		public void postOn(Sns sns) {
			if (sns instanceof Facebook) {
				System.out.println("facebook - picture");
			}
			if (sns instanceof Instagram) {
				System.out.println("instagram - picture");
			}
		}
	}
	
	interface Sns {	
	}
	
	static class Facebook implements Sns {	
	}
	
	static class Instagram implements Sns {	
	}
	
	public static void main(String[] args) {
		List<Post> posts = Arrays.asList(new Text(), new Picture());
		List<Sns> sns = Arrays.asList(new Facebook(), new Instagram());
		
		posts.forEach(post -> sns.forEach(_sns->post.postOn(_sns)));

	}
}


더블 디스패치

package dispatch;

import java.util.Arrays;
import java.util.List;

public class DoubleDispatch2 {
	
	interface Post {
		void postOn(Sns sns);
	}
	
	static class Text implements Post {
		public void postOn(Sns sns) {
			sns.post(this);
		}
	}
	
	static class Picture implements Post {
		public void postOn(Sns sns) {
			sns.post(this);
		}
	}
	
	interface Sns {
		void post(Text text);
		void post(Picture picture);
	}
	
	static class Facebook implements Sns {

		@Override
		public void post(Text text) {
			System.out.println("facebook - text");
		}

		@Override
		public void post(Picture picture) {
			System.out.println("facebook - picture");
		}
		
	}
	
	static class Instagram implements Sns {

		@Override
		public void post(Text text) {
			System.out.println("instagram - text");
		}

		@Override
		public void post(Picture picture) {
			System.out.println("instagram - picture");
		}
		
	}
	
	public static void main(String[] args) {
		List<Post> posts = Arrays.asList(new Text(), new Picture());
		List<Sns> sns = Arrays.asList(new Facebook(), new Instagram());
		
		posts.forEach(post -> sns.forEach(_sns->post.postOn(_sns)));
	}
}




(생각) Visitor 패턴과도 연관되어 있다고 하는데 자세한 건 천천히 알아봐야할 것 같다!
토비의 봄 : https://www.youtube.com/watch?v=s-tXAHub6vg&t=2520s



참고: Head First Java, 자바 프로그래밍, 자바의 정석
https://catch-me-java.tistory.com/26

https://velog.io/@maigumi/Dynamic-Method-Dispatch

https://zaksimcoding.blogspot.com/2018/05/blog-post.html#google_vignette

https://doompok.tistory.com/21


Q. 이진트리가 루트가 맨 꼭대기에 있고 그 다음 왼쪽, 오른쪽 자식으로 구성하는 식 그리고 깊게, 넓게 순차하는 방식이 있다는 것은
알겠다. 그런데 왜 만들어진 것이고, 현실세계에선 어떤 경우에 필요한 걸까?
A.


최소 힙 소스  (생각: 혼자서 무작정 작성해보았다. 다른분이 작성한 것과 비교하며 다시 수정해보자.)

public class MinHeapTreeV1 {

	private final int SIZE = 10;
	private int[] array;
	private int index;
	
	public MinHeapTreeV1() {
		this.array = new int[SIZE];
	}

	private void swap(int src, int dst) {
		int temp = array[src];
		array[src] = array[dst];
		array[dst] = temp;
	}
	
	private void change(int index) {
		if (index == 0) return;
		else {
			int parent = (index - 1) / 2;
			if (array[index] > array[parent]) return;
			else {
				swap(index, parent);
				change(parent);
			}
		}
	}
	
	private void change2(int parent) {
		int childLeft = (parent+1)*2 - 1;
		int childRight = (parent+1)*2;

		if (parent < 0 || childRight >= SIZE) return;
		else {
			int child = array[childLeft] < array[childRight] ? childLeft : childRight;
			if (array[parent] < array[child]) return;
			else {
				swap(parent, child);
				change2(child);
			}
		}
	}
	
	public void add (int data) {
		if (index == SIZE) {
			System.out.println("데이터를 추가할 공간이 없습니다.");
		} else if (array.length == 0) {
			array[index++] = data;
		} else {
			int currentIndex = index;
			int parent = array[(currentIndex - 1) / 2];
			array[index] = data;
			
			if (data < parent) {
				change(index);
			}
			index++;
		}
	}
	
	public int delete () {
		if (index == 0) {
			System.out.println("데이터가 이미 비어있습니다.");
			return -1;
		} else {
			int parent = 0;
			int result = array[parent];
			array[parent] = array[--index];
			
			change2(parent);
			return result;
		}
	}
	
	public int getRoot() {
		return array[0];
	}
	
}


테스트 코드

import static org.junit.Assert.assertEquals;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class MinHeapTreeV1Test {

	MinHeapTreeV1 heap = new MinHeapTreeV1();
	
	@BeforeEach
	void before() {
		heap.add(5);
		heap.add(4);
		heap.add(3);
		heap.add(2);
		heap.add(1);
	}
	
	@Test
	void testAdd() {
		assertEquals(1, heap.getRoot());
	}
	
	@Test
	void testDelete() {
		int result = heap.delete();
		assertEquals(1, result);
		assertEquals(2, heap.getRoot());
	}

}

 

참고
https://www.youtube.com/watch?v=C5gY8cV3GIo

Q. 클래스란? 
A. 객체의 속성과 행동이 기술되어 있는 것이다. 설계도, 붕어빵 틀, 기술서 등을 떠올리면 이해가 될 것이다. 

Q. 클래스 정의하는 방법
A. 맨 처음의 경우 다음과 같이 생성 가능하다. 파일명의 경우 보통 관련된 주제를 나타내는 명사로 짓는다.
맨 처음 대문자의 영어만올 수 있다. 

https://www.oracle.com/java/technologies/javase/codeconventions-namingconventions.html

 

public, default 접근제어자 및 abstract, final, extends, implements 키워드를 추가할 수 있다.



Q. 클래스에 붙이는 접근제어자란 무엇이며 어떤 것이 있는가?
클래스의 사용 범위와 관련된 키워드라 이해했다. 즉 클래스의 사용을 어디까지 할 수 있느냐와 연관된 제어자이다.
클래스의 경우 default, public 키워드만 등록할 수 있다.

접근제어자 다른 클래스 같은 패키지
public o o
default (생략) x o

 


Q. abstract 키워드란 무엇인가?
클래스별 공통적인 것을 묶어 만든 클래스라고 한다. 다음 상속편에 더 자세히 다루겠다.

Q. final 키워드란 무엇인가?
상속하지 못하도록 만든 클래스라고 한다.
예를 들어 String 이라는 클래스의 경우 java에서 정해진 룰에 따라서만 동작을 해야 하는데, 
어떤 다른 클래스에서 상속을 받아 새롭게 만든다면 동작을 다르게 할 여지가 있다. 이를 막기 위해 만든다고 한다.

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];


클래스 실제 내용은 다음과 같이 구성될 수 있다.

class Class3 {
        public String toString2() {
                return "InnerClass2";
        }
}

public class ClassTest {

        static int a;
        int b;
        interface TestObj{
                public void say();
        }

        public ClassTest(int b){
                this.b = b;
        }

        static class InnerClass1 {
                InnerClass1() {
                        System.out.println("InnerClass1 is created.");
                        System.out.println("Access Outer Class's a " + a);
//                      System.out.println("Access Outer Class's b " + b);
                }
        }

        class InnerClass2 {
                InnerClass2() {
                        System.out.println("InnerClass2 is created.");
                        System.out.println("Access Outer Class's a " + a);
                        System.out.println("Access Outer Class's b " + b);
                }
                public String toString() {
                        return "InnerClass2";
                }

                public void method() {
                        class Local2{

                        }

                        Local2 Local2 = new Local2();
                }
        }

        public static void main(String[] args) {
                InnerClass1 obj1 = new ClassTest.InnerClass1();
                InnerClass2 obj2 = new ClassTest(1).new InnerClass2();

                Class3 obj3 = new Class3() {
                        public String toString2() {
                                return "InnerClas3";
                        }
                };

                class Local {

                }
                Local obj4 = new Local();

                obj2.method();
        }

}


Q. 객체 만드는 방법은 어떻게 되는가?
A. 크게 new 키워드를 사용하거나 Reflection을 사용해 클래스를 로딩해 인스턴스를 생성하는 두가지 방법이 있는 것 같다.  


Q. Reflection을 통해 어떻게 객체를 생성하지?
A. import 구문없이 다음과 같이 객체 생성이 가능하다.
java의 Refection 기능을 사용해 로딩된 class 파일의 정보를 읽어와 객체를 생성할 수 있는 것이다. 

 

Q. new 키워드는 어떻게 동작하는가?
A. 크게 3가지 단계로 나뉜다고 볼 수 있다. 
    1. 만약 JVM에서 해당 클래스의 .class 파일을 로딩한 적이 없다면 클래스 로더를 통해 로딩한다. 
    2. Heap 영역에 인스턴스 변수의 공간만큼 할당한다. 
    3. 생성자 메서드를 호출한다. 

new 키워드의 동작 방식

(알게된 것) class 파일은 다음과 같이 생겼다.
생성자가 정의되지 않은 경우 기본 생성자가 컴파일러가 자동으로 추가해준다. (예: 1-2)

1-2

내부에 정의된 클래스들은 다른 파일로 추출되어 컴파일된다. (예: 1-3)

1-3

ClassTest.java 내부에 생성자가 정의되어 있다면 컴파일러가 디폴트 생성자를 추가하진 않는다. (예: 1-4)

1-4


(생각) 근데 한가진 ClassTest$1은 어디서도 생성한 적이 없는데 컴파일러가 생성을 했다. 왜 그런걸까?


(알게된 것) .java파일에는 해당 파일 이름과 동일한 클래스만 public으로 지정할 수 있으며, (예: 1-5)
마찬가지로 class 앞에 static이 붙은 경우 static 멤버들끼리만 서로 접근이 가능하다. (예: 1-6)

1-5

 

1-6



Q. 클래스 초기화 부분의 순서가 어떻게 되는가?
앞서 생성자를 통해 각 인스턴스 변수가 초기화된다고 했는데, 그 전에 한번더 초기화 작업을 진행할 수 있는 문법이 있다.
바로 클래스 초기화 블록, 인스턴스 초기화 블록이다. 맨 처음 클래스가 로딩될 때 및 instance 생성시 생성자 호출 직전에 호출된다. 

1-6


Q. 클래스 내부에 선언할 수 있는 클래스도 있다고 들었다. 
A. 이너 클래스, 익명 클래스 등등이 있다.
(생각) 찾아보니 아우터 클래스에서만 필요한 경우에 이너 클래스를,
일회용으로 한번만 필요한 경우 익명클래스를 사용한다고 한다. 코드를 많이 작성하고 보면서 사용 예시를 익혀야 할 것 같다!


Q. 메소드를 정의하는 방법은 어떻게 되는가?
A. 리턴값 타입 + 메서드명() + 블록 부분(바디) 으로 구성된며, static을 붙일 수 있다.


Q. 생성자 정의하는 방법은 어떻게 되는가?
A. 클래스명() + 블록 부분(바디) 으로 구성된다. 갯수와 형이 다른 인수를 추가할 수 있다. (오버로딩 가능, 예 1-6 참고)


Q. this 키워드가 왜 필요한 것인가?
A. 먼저 this란 클래스에서 필수적으로 필요한 개념인 것 같다. 앞서 말했듯 클래스란 결국 "객체"를 만들기 위한 것이다. 
모든 객체엔 같은 멤버변수, 메서드가 있겠지만 상태는 각각 고유하다고 이해했다.
그러므로 현재 객체를 가리키는 myself와 같은 개념이 필요하지 않았을까 싶다. 


즉 this란 현재 객체를 가리키는 문법이다. 크게 2가지 경우에 필요하다고 생각했다.  
1. 생성자 초기화 시 매개변수명이 멤버변수명과 동일할 경우
2. 타입이 같은 객체와 다른 객체의 멤버의 값 비교시 
3. Java Swing을 사용해 코드를 작성하면서 다음과 예를 보았다. 클래스명.this 이 경우는 어떤 것일까?
(<클래스명.> 은 static하게 참조하는 경우로 이해를 했는데 잘 모르겠다..)

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 

package components;

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.*;

/*
 * FileChooserDemo.java uses these files:
 *   images/Open16.gif
 *   images/Save16.gif
 */
public class FileChooserDemo extends JPanel
                             implements ActionListener {
    static private final String newline = "\n";
    JButton openButton, saveButton;
    JTextArea log;
    JFileChooser fc;

    public FileChooserDemo() {
        super(new BorderLayout());

        //Create the log first, because the action listeners
        //need to refer to it.
        log = new JTextArea(5,20);
        log.setMargin(new Insets(5,5,5,5));
        log.setEditable(false);
        JScrollPane logScrollPane = new JScrollPane(log);

        //Create a file chooser
        fc = new JFileChooser();

        //Uncomment one of the following lines to try a different
        //file selection mode.  The first allows just directories
        //to be selected (and, at least in the Java look and feel,
        //shown).  The second allows both files and directories
        //to be selected.  If you leave these lines commented out,
        //then the default mode (FILES_ONLY) will be used.
        //
        //fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        //fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

        //Create the open button.  We use the image from the JLF
        //Graphics Repository (but we extracted it from the jar).
        openButton = new JButton("Open a File...",
                                 createImageIcon("images/Open16.gif"));
        openButton.addActionListener(this);

        //Create the save button.  We use the image from the JLF
        //Graphics Repository (but we extracted it from the jar).
        saveButton = new JButton("Save a File...",
                                 createImageIcon("images/Save16.gif"));
        saveButton.addActionListener(this);

        //For layout purposes, put the buttons in a separate panel
        JPanel buttonPanel = new JPanel(); //use FlowLayout
        buttonPanel.add(openButton);
        buttonPanel.add(saveButton);

        //Add the buttons and the log to this panel.
        add(buttonPanel, BorderLayout.PAGE_START);
        add(logScrollPane, BorderLayout.CENTER);
    }

    public void actionPerformed(ActionEvent e) {

        //Handle open button action.
        if (e.getSource() == openButton) {
            int returnVal = fc.showOpenDialog(FileChooserDemo.this);

            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File file = fc.getSelectedFile();
                //This is where a real application would open the file.
                log.append("Opening: " + file.getName() + "." + newline);
            } else {
                log.append("Open command cancelled by user." + newline);
            }
            log.setCaretPosition(log.getDocument().getLength());

        //Handle save button action.
        } else if (e.getSource() == saveButton) {
            int returnVal = fc.showSaveDialog(FileChooserDemo.this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File file = fc.getSelectedFile();
                //This is where a real application would save the file.
                log.append("Saving: " + file.getName() + "." + newline);
            } else {
                log.append("Save command cancelled by user." + newline);
            }
            log.setCaretPosition(log.getDocument().getLength());
        }
    }

    /** Returns an ImageIcon, or null if the path was invalid. */
    protected static ImageIcon createImageIcon(String path) {
        java.net.URL imgURL = FileChooserDemo.class.getResource(path);
        if (imgURL != null) {
            return new ImageIcon(imgURL);
        } else {
            System.err.println("Couldn't find file: " + path);
            return null;
        }
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event dispatch thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("FileChooserDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Add content to the window.
        frame.add(new FileChooserDemo());

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event dispatch thread:
        //creating and showing this application's GUI.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                //Turn off metal's use of bold fonts
                UIManager.put("swing.boldMetal", Boolean.FALSE); 
                createAndShowGUI();
            }
        });
    }
}

 



참고
https://medium.com/codimis/understanding-the-new-keyword-in-java-be571dec090b

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B5%EB%AA%85-%ED%81%B4%EB%9E%98%EC%8A%A4Anonymous-Class-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%95%98%EA%B8%B0

https://gsgdvxhx.tistory.com/3

http://zaksimcoding.blogspot.com/2018/05/blog-post.html#google_vignette

https://lkhlkh23.tistory.com/95

+ Recent posts