이번 달의 시작일로 시작하는 한달~두달치 달력을 만드는 중이다.
보통은 이번 달의 남은 날짜만 달력에 보여주지만, 남은 날짜가 2주 이하일 경우 다음달까지 출력한다.
로컬 데이터를 변경하지 않고 덧셈만으로 다음 월의 요일을 구하는 로직을 만들었는데, time 클래스가 실시간 기반 클래스이다보니 다음 월의 마지막 일을 구하는 게 쉽지 않았다. 결국 객체 하나를 더 생성해 로컬 데이터의 월을 바꾸었는데, 앞서 만든 로직이 아까워 기록한다.
private static void printCalender(){
LocalDate now = LocalDate.now();
int thisYear = now.getYear(); // 연도
int thisMonth = now.getMonthValue(); // 월
int thisDay = now.getDayOfMonth(); // 일
int thisDayOfWeek = now.getDayOfWeek().getValue(); // 월요일부터 (1,2,3,4,5,6,7)
int endDay = now.lengthOfMonth(); // 마지막 일
System.out.println();
System.out.println("---------------------");
System.out.println(" "+thisYear+"년 "+thisMonth+"월");
System.out.println(" SU MO TU WE TH FR SA");
for(int i = 1 ; i<=thisDayOfWeek ; i++)
System.out.print(" ");
for(int day = thisDay, n = thisDayOfWeek ; day <= endDay ; day++, n++){
System.out.print((day<10)? " "+day : " "+day);
if((n+1)%7==0) System.out.println();
}
System.out.println();
if(endDay - thisDay < 14){ // 당월 달력의 노출 날짜가 2주 이하일 경우
int nextMonth = now.getMonthValue() + 1; // 다음 월
int nextYear = now.getYear() + 1;
int a = (endDay - thisDay) % 7; // 당월의 마지막 요일 구하기_1 : 마지막 날짜와 당일의 뺄셈 값을 구해, 7로 나눈 나머지 = 당일의 요일과 마지막 날짜 요일 값의 차잇값
int b = (thisDayOfWeek + a) % 7; // 당월의 마지막 요일 구하기_2 : 당일의 요일 값으로부터 마지막 요일과의 차잇값을 더하고, 그 수가 7이 넘을 경우를 대비해 다시 구한 차잇값
// 월~토의 경우 b가 요일 값이나, 나머지값이 0인 경우 일요일이라는 뜻이므로 7 대입
int thisLastDayOfWeek;
if(b==0){
thisLastDayOfWeek = 7;
} else {
thisLastDayOfWeek = b;
}
int nextMonthStartDayOfWeek; // 다음 월 시작 요일
int c = (thisLastDayOfWeek + 1) % 7;
if(c==0){
nextMonthStartDayOfWeek = 7;
} else {
nextMonthStartDayOfWeek = c;
}
LocalDate temp = now.plusMonths(3); // 다음 달로 로컬데이터 변경
int nextEndDay = temp.lengthOfMonth(); // 다음 월 마지막 날짜. 변경점이 안 먹었음
if(thisMonth == 12){ // 당월의 마지막 월일 경우
System.out.println();
System.out.println("---------------------");
System.out.println(" "+nextYear+"년 1월");
System.out.println(" SU MO TU WE TH FR SA");
for(int i = 1 ; i<=nextMonthStartDayOfWeek ; i++)
System.out.print(" ");
for(int day = 1, n = nextMonthStartDayOfWeek ; day <= nextEndDay ; day++, n++){
System.out.print((day<10)? " "+day : " "+day);
if((n+1)%7==0) System.out.println();
}
} else { // 연도 변경이 필요 없을 경우
System.out.println();
System.out.println("---------------------");
System.out.println(" "+thisYear+"년 " + nextMonth+ "월");
System.out.println(" SU MO TU WE TH FR SA");
for(int i = 1 ; i<=nextMonthStartDayOfWeek ; i++)
System.out.print(" ");
for(int day = 1, n = nextMonthStartDayOfWeek ; day <= nextEndDay ; day++, n++) {
System.out.print((day < 10) ? " " + day : " " + day);
if ((n + 1) % 7 == 0) System.out.println();
}
}
}
}
temp 객체를 if문 상단으로 올리고 다시 해보자.
참고로! 자바의 정석 예제를 참고해 만들었다.
달력 완성 코드
import java.time.LocalDate;
public class CalenderBook {
public void printCalender() {
LocalDate now = LocalDate.now();
int thisYear = now.getYear(); // 연도
int thisMonth = now.getMonthValue(); // 월
int thisDay = now.getDayOfMonth(); // 일
int thisDayOfWeek = now.getDayOfWeek().getValue(); // 월요일부터 (1,2,3,4,5,6,7)
int endDay = now.lengthOfMonth(); // 마지막 일
System.out.println();
System.out.println("---------------------");
System.out.println(" " + thisYear + "년 " + thisMonth + "월");
System.out.println(" SU MO TU WE TH FR SA");
for (int i = 1; i <= thisDayOfWeek; i++)
System.out.print(" ");
for (int day = thisDay, n = thisDayOfWeek; day <= endDay; day++, n++) {
System.out.print((day < 10) ? " " + day : " " + day);
if ((n + 1) % 7 == 0) System.out.println();
}
System.out.println();
if (endDay - thisDay < 14) { // 당월 달력의 노출 날짜가 2주 이하일 경우
LocalDate temp = now.plusMonths(1).withDayOfMonth(1); // 다음 달 1일로 로컬데이터 변경
int nextYear = temp.getYear(); // 다음 월의 연도
int nextMonth = temp.getMonthValue(); // 다음 월
int nextEndDay = temp.lengthOfMonth(); // 다음 월 마지막 날짜
int nextMonthStartDayOfWeek = temp.getDayOfWeek().getValue(); // 다음 월 시작 요일
System.out.println();
System.out.println("---------------------");
System.out.println(" " + nextYear + "년 " + nextMonth + "월");
System.out.println(" SU MO TU WE TH FR SA");
for (int i = 1; i <= nextMonthStartDayOfWeek; i++)
System.out.print(" ");
for (int day = 1, n = nextMonthStartDayOfWeek; day <= nextEndDay; day++, n++) {
System.out.print((day < 10) ? " " + day : " " + day);
if ((n + 1) % 7 == 0) System.out.println();
}
System.out.println();
System.out.println();
}
}
}
튜터님이 하나하나 알려주셔서 진정하고 conflict를 해결해볼 수 있었다.
어떻게 해결하는지 이제야 알게 되었다...
내가 수동으로 지우는 거였구나!
그리고 로그 넘버 확인하는 법도 알게 되었다.
이제야 git이 조금 덜 무섭다. 로컬과 원격의 차이, 브랜치 간의 관계를 항상 염두에 두고 쓰자.
오늘 배운 내용 중에 가장 유용했던 내용은 래퍼클래스의 내부를 들여다보는 방법이었다. 컨트롤 클릭. 조금 더 문법을 알게 되면 이 문서를 읽으면서 공부를 할 수도 있을까?
보면서 너무 졸아서 다시 듣는다.
예외처리가 필요한 이유?
사용자가 예상 밖의 데이터를 입력할 수 있어요.
이런 부분을 미리 대비해야 해요.
에러는 회복이 불가능한 시스템 레벨의 문제 등을 말해요.
예외는 회복이 가능하다는 전제 하에 우리가 처리할 수 있는 것들을 말해요.
코드 레벨에서 대비할 수 있어요.
예외
컴파일 에러는 .java를 .class 로 컴파일 할 때 발생하는 에러입니다. 인텔리제이는 미리미리 알아서 체크해줘요. 보통 문법을 틀려서 생겨요.
문제는 런타임 에러예요.
프로그램 실행 도중에 맞닥뜨리게 되는 예외입니다.
예외의 종류는 두 가지입니다.
checked exeption
컴파일 시점에 확인해야 하는 예외입니다. 반드시 해줘야 해요.
컴파일 시점? 컴파일 에러와는 달라요. 코드를 작성하며 미리 확인할 수 있는 예외라는 뜻이에요.
unchecked exeption
런타임 시점에 확인되는 예외입니다.
예외 처리가 반드시 필요하지는 않다고 해요.
예외는 정의 → 사용자에게 고지 → 사용자가 핸들링 흐름으로 진행해야 합니다. 사용자에게 고지한다는 것은 예외가 발생할 부분에 flag를 단다고 할 수 있어요.
try, catch, finally, throw
throw
예외 처리 할 로직을 가진 클래스는 Exception 을 상속해야 합니다.
위험한 메소드가 있다면 throw를 이용해 다음과 같이 적으면 됩니다.
public class OurBadException extends Exception{
public OurBadException(){
super("위험한 행동을 하면 꼭 예외처리를 해야 함!");
} // 생성자 안에 내용 넣기
}
public class Our class{
public void thisMethodIsDangerous () throws OurBadException{
if (true){
throw new OurBadException();
}// throws로 호출, throw로 플래그!
}
}
throws는 메서드 이름 뒤에 붙어 이 이 메서드가 어떤 예외사항을 던질 수 있는지 알려주는 예약어입니다. 여러 종류의 예외상황을 적을 수 있습니다.
Exception을 사용해도 적을 수 있으나, Exception을 상속해 구체적으로 내용을 적는 것이 좋습니다.
throw는 메서드 안에서 실제로 예외 객체를 던질 때 사용하는 예약어입니다.
코드 상의 어떤 흐름이 있을 때 이 친구를 만나면 종료됩니다.
실제로 던지는 예외 객체 하나와 같이 써야 합니다. (생성자)
OurClass 객체를 메인 함수에 생성하고 thisMethod... 이 메서드를 호출하면 빨간 밑줄이 뜹니다.
이 경우에 try ~ catch를 사용합니다.
일단 try하고, 예외가 발생하면 그걸 catch합니다.
그리고 수행되든 예외가 생기든 finally 수행해야 하는 로직을 작성합니다.
try {}
catch() {}
finally {}
-------------------------
try {thisMethod...;}
catch(OurBadException e){System.out.println(e.getMessage());} // ()안에는 예외를 인스턴스화해 호출해야 합니다. e.GetMessage()는 오류 메시지를 호출합니다.
finally{최종 실행 로직}
방금 전까지 checked exception을 정의하고 알렸으니 이 메서드를 사용할 때 예외처리를 하지 않으면 컴파일 에러가 발생합니다.
모든 객체의 원형인 object 클래스에서 시작합니다.
Throwable Class의 자식으로 Error와 Exception 클래스가 있습니다.
각각 IOError 와 RuntimeException와 같이 구분하여 처리됩니다.
RuntimeException을 상속한 예외들은 UncheckedException으로 구현되어 있습니다.
추상화
객체에서 공통된 속성과 행위를 추출함.
코드구현할 때 생각할 것들을 줄일 수 있다.
중요한 정보만 남겨 프로그램을 간단하게 만들 수 있다.
캡슐화
객체를 사용하는 입장에서 로직을 전부 알 필요 없이 다루는 방법을 객체와 묶는 것. 간단하게 쓸 수 있게 만드는 것.
상속
하위 클래스가 상위 클래스의 속성과 행위를 물려받는 것.
새로운 클래스가 기존 클래스의 데이터와 연산을 이용할 수 있게 하는 기능.
상위 클래스의 변경이 어렵다. 상속이 잘못 사용되거나 불필요한 클래스가 늘어날 수 있다.
다형성
하나의 변수명, 함수명이 상황에 따라 다른 의미로 해석될 수 있는 것!
어떠한 요소에 여러 개념을 넣어 놓는 것.
오버라이딩 : 상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의해서 사용하는 것.
오버로딩 : 같은 이름의 메소드가 인자의 개수나 자료에 따라 다른 기능을 하는 것.
### **SOLID (객체 지향 설계 원칙)**
객체 지향적으로 설계하기 위해 `SOLID` 라 불리는 다섯 가지 원칙이 있다.
### **1. 단일 책임 원칙 (SRP, Single Responsibility Principle)**
- 하나의 클래스는 단 하나의 책임만 가져야 한다.
- 단일 책임 원칙을 지키지 않을 경우 한 책임의 변경에 의해 다른 책임과 관련된 코드에 영향이 갈 수 있다.
### **2. 개방-폐쇄 원칙 (OCP, Open/Closed Principle)**
- 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
- 기능을 변경하거나 확장할 수 있으면서 기능을 사용하는 코드는 수정하지 않는다.
### **3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)**
- 프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
- 상위 타입의 객체를 하위 타입의 객체로 치환해도, 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
### **4. 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)**
- 범용 인터페이스 하나보다 클라이언트를 위한 여러 개의 인터페이스로 구성하는 것이 좋다.
- 인터페이스는 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.
- 클라이언트가 필요로 하는 인터페이스로 분리함으로써 각 클라이언트가 사용하지 않는 인터페이스에 변경이 있어도 영향을 받지 않도록 만들어야 한다.
### **5. 의존관계 역전 원칙 (DIP), Dependency Inversion Principle)**
- 추상화에 의존해야지 구체화에 의존하면 안된다.
- 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되고 저수준 모듈은 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.
이제는 공부뿐이야.