* 공부하면서 알게된 것 
1. new 그리고 생성자를 호출한다는 것은? 
2. 어떤 식으로 메모리에 인스턴스가 생성이 되는 것이지?
3. 상속관계에서 부모 클래스 역시 새로운 인스턴스로 만들어지는 것인가? 

자바 상속의 특징
기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.
즉 기존 클래스의 멤버 변수와 메서드를 사용하면서 새로운 멤버 변수와 메서드를 정의하여 클래스를 만들겠다는 것이다.

interface이긴 하지만, extends의 개념은 비슷해보여 그려보았다.

 

예를 들어 r (반지름)  멤버변수를 가진 Circle 클래스가 있다고 하자. (또한 기본으로 0, 0에 위치한다고 가정)
추가적인 요구사항이 들어와 Circle에 0, 0이 아닌 cx, cy (위치 정보) 및 위치 정보를 출력하는 기능을 지닌 클래스가
필요하다고 할 때 어떻게 해야 할까? 이 경우 위치 정보를 멤버 변수로 가지는 새로운 클래스를 정의하는 것이 낫다.
또한 반지름 정보가 필요하므로 Circle을 상속받아 해당 멤버 변수와 관련 메서드를 사용할 수 있는 것이다. 

멤버 변수 가리기와 메서드 오버라이딩은 다르다!


super 키워드
상속관계에 있는 경우 조상 클래스의 멤버변수가 초기화되어야 하기 때문에
조상 클래스의 생성자가 반드시 호출되어야 한다.
즉 상속관계인경우 부모클래스의 인스턴스 멤버를 포함한 채
인스턴스가 생성되어야 하기 때문이다.

만약, super()가 명시되어있지 않다면 컴파일러에서 해당 값을 추가해준다. 
하지만 다음의 경우 부모 클래스에 이미 정의된 생성자가
있기에 default constructor는 없어서 에러가 난 경우이다.

 

메소드 오버라이딩

 

  • 추상 클래스
  • final 키워드
  • Object 클래스
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

import하지 않고도 다음과 같은 식으로 인스턴스 생성이 가능하다.
앞서 접근제어자에서 배웠듯 만들어야할 클래스가 다른 패키지에 있는 경우 클래스 및 생성자의 접근제어자는 public으로 되어있어야겠다!

 

* 클래스의 접근제어자가 public이면서 생성자가 정의되어 있지 않다면 컴파일러가 
public 클래스명() {} 의 디폴트 생성자를 자동으로 추가해준다! 

접근제어자의 종류는 크게 4가지가 있다. 

제어자  같은 클래스 같은 패키지 자손 클래스 그 외
public o o o o
protected o o o x
(default) o o x x
private o x x x


그리고 클래스와 생성자, 메서드, 멤버변수별로 사용가능한 접근제어자들이 있다.

대상 사용가능한 접근 제어자
클래스 public, (default)
메서드 public, protected, (default), private
멤버변수
지역변수 없음

 

위에서부터 하나씩 코드로 살펴보자. 클래스에는 private과 protected가 쓰일 수 없다. 실제로 작성하면 에러가 나는 것을 볼 수 있다.

그 이유는 무엇일까? 답하기에 앞서 private과 protected이 왜 필요한지부터 생각해볼 필요가 있을 것 같다. 
private같은 경우는 데이터를 은닉하는 효과의 문법이고 protected 역시 다른 클래스에서 해당 데이터를
은닉하기 위한 효과의 문법이다. 이는 객체지향개념의 캡슐화에 해당하는 내용이다.
캡슐화란 데이터가 유효한 값을 유지하기 위해 혹은 외부에서 수정할 수 없도록 외부로부터 데이터를 제한하는 것이다.
그렇기에 두 문법은 class보다는 data단에 속하는 메서드 및 멤버변수에 적용되는 것이 알맞다.

만약 다른 패키지에서 클래스를 사용하려면 반드시 (default)가 아닌 public을 붙여줘야한다.
그래야 다음 에러가 없어진다!

그리고 클래스는 public이나 메서드는 그렇지 않다면 이 경우도 에러가 발생한다. 
그 이유는 인스턴스 생성시 결국 생성자 메서드를 호출하는 것인데 이 경우에도 다른 패키지에서 해당 생성자 메서드를
호출하기 위해선 public 접근제어자가 꼭 필요하다.

 

 

 

 

 

위와 같은 간단한 도구(data를 csv파일로 내보내고, 다시 표로 읽어들이는 간단한 도구)를 Java Swing을 통해 만들어보려고 한다. 
근데 그 전에 어떻게 UI를 구성하는지, Window Builder 추가,
CSV 파일로 저장하고 읽는 방법에 대한 약간의 공부가 필요해서 정리중이다.

Window Builder 추가하는 방법 : 
본인의 경우 Eclipse 버전이 다음과 같다. Version: Oxygen.3 (4.7.3)
이 경우 아래 링크에서 1.9 버전의 WindowBuilder를 다운받으면 된다.
https://eclipse.dev/windowbuilder/download.php 

그리고 Eclipse 상단 우측 Help -> Install New Software -> Add -> Archive를 선택해 
다운로드 받은 zip파일을 추가한다.
download progress가 뜨고 Restart 할 것인지 물어야 최종적으로 사용이 가능한 환경이 된다!


Swing csv 파일로 내보내기 코드 : 

https://github.com/isfpcat/study-diary/tree/master/Diary

Q. this가 어떤 문법이고 왜 필요한 것일까?
A. 현재의 객체를 지칭하는 문법이며 인스턴스별로 다른 필드의 값을 구분하기 위해 필요하다.
하나의 클래스에서 만들어진 것이 인스턴스인데, 각 인스턴스의 필드내용은 다른 값을 가진다.

public class Circle {
	// An instance field
	public double r; // The radius of the circle
     
	Circle (double r) {
		this.r = r;
	}
    
}

예를 들어 현재 인스턴스와 다른 인스턴스 필드의 값을 비교하는 메서드를 만든다고 하자.
두 값이 모두 같은 필드를 참조하는데 어떻게 비교를 해야 할까? 이 경우에 this를 사용한다.

public Circle bigger (Circle that) {
     if (this.r > that.r) return this; 
     else return that;
}

 

혹은 앞서봤듯이 생성자와 같이 지역변수명이 중복이거나
명시적으로 해당 필드의 값을 참조해야 할 필요가 있을때 필요하다.

 

(다음 내용이 맞는지는 정확한 확인이 필요하다.)
인스턴스 메서드에서의 this의 개념은 크게 2가지로 나뉜다.
바로 컴파일러가 추가해주는 implicit this (보이지 않는) 와 명시적으로 사용하는 eplicit this (개발자가 직접 사용하는) 이다. 

인스턴스 메서드에서 다른 인스턴스 메서드 호출 또는 필드를 참조하고 싶다면, 
해당 메서드 이름을 호출하거나 필드명을 작성하면 된다.

조금 더 깊이 들어가보면 컴파일러를 통해 암묵적으로 추가된 this를 통해
현재 객체가 가지고 있는 메서드 호출 혹은 필드를 참조하는 것이다.

public class Circle {
	// 클래스 필드
	public static final double PI = 3.1459;
	
	// 인스턴스 필드
	double r;
		
	// 인스턴스 메서드
	public double circumference() { return 2 * PI * r; }
	public double radius() {return r;}
	
	// 인스턴스 메서드 - 암묵적/명시적/중복 필드 참조
	public double areaWithImplicitThis() {return PI * r * r;}
	public double areaWithExplicitlyThis() {
		return Circle.PI * this.r * this.r;
	}
	public double areaWithDouplicatedThis() {
		double r = 2.0;
		return Circle.PI * r * r;
	}
	public void printArea() {
		System.out.println("Area is ... " + this.areaWithDouplicatedThis());
	}
}

 

How the this Reference Works
- The implicit this parameter is not shown in method signatures because it is usually not needed; whenever a Java method accesses the instance fields in its class, it is implicit that it is accessing fields in the object referred to by the this parameter. The same is true when an instance method invokes another instance method in the same class—it’s taken that this means
“call the instance method on the current object. -

 

 

  • 클래스 정의하는 방법
    • 클래스란 데이터 필드와 메서드의 묶음을 말한다.
    • 크게 signature와 body로 구성된다.
      • signature란, 클래스의 이름상속구현 클래스 이름으로 구성되어 있다. 
      • body란, 중괄호로 묶인 멤버 집합을 말하며 필드, 메서드, 생성자등이 있다.
package myclass;

public class Circle {
	static int count;
	int serialNo;
	
	static {
		double x = 0.2; // 오직 이곳에서만 생성된다.
		count = 1;
		System.out.printf("1. Static Constructor is called%n");
	}
	
	{
		double x = 0.1; // 오직 이곳에서만 생성된다.
		count++;
		serialNo = count;
		System.out.printf("2. This instance serialNo = %d%n", serialNo);
	}
	
	Circle (double r) {
		this.r = r;
		System.out.printf("3. Constructor called%n");
	}
	// A class field
	 public static final double PI= 3.14159; // A useful constant
	 
	 // A class method: just compute a value based on the arguments
	 public static double radiansToDegrees(double radians) {
		 return radians * 180 / PI;
	 }
	 
	 // An instance field
	 public double r; // The radius of the circle
	 
	 public static double areaV1(double r) { 
		 return PI * r * r; 
	 }
	 
	 // Two instance methods: they operate on the instance fields of an object
	 public double area() { // Compute the area of the circle
		 return PI * r * r;
	 }
	
	 public double circumference() { // Compute the circumference
		 // of the circle
		 return 2 * PI * r;
	 }
	 
	 public static Circle bigger(Circle a, Circle b) {
		 return a.r > b.r ? a : b;
	 }
	 
	 public Circle bigger (Circle that) {
		 if (this.r > that.r) return this; 
		 else return that;
	 }
}

 

  • 객체 만드는 방법 (new 키워드 이해하기)
    • 객체란 클래스의 인스턴스를 말하며, new 키워드를 통해 1. heap에 인스턴스 생성 및 2. 생성자가 실행된다. 
package myclass;

class Data1 {
	int value;
}

class Data2 {
	int value;
	
	Data2 (int x) {
		value = x;
	}
}

public class BasicClass {
	public static void main(String[] args) {
		Circle circle1 = new Circle(2.0);
		Circle circle2 = new Circle(4.0);
		
		Circle bigCircle1 = circle1.bigger(circle2);
		System.out.printf("1. Big circle radius is = %.1f\n", bigCircle1.r);
		
		Circle bigCircle2 = Circle.bigger(circle1, circle2);
		System.out.printf("2. Big circle radius is = %.1f\n", bigCircle2.r);
		
		
		Data1 dt1 = new Data1();
//		The constructor Data2() is undefined is occured.
//		Data2 dt2 = new Data2();
	}
}
  • 메소드 정의하는 방법 (Class, Instance 메소드로 구성할 수 있다.)
    • Class methods 
      • static이 앞에 붙여진 메서드
      • 클래스에 묶인 메서드
      • 내부의 경우 모든 메서드에서 호출할 수 있으나,
        클래스 외부에서 호출 시 <클래스명.> 을 붙여서 호출해야 한다. 
// A class field
public static final double PI= 3.14159; // A useful constant
	 
// A class method: just compute a value based on the arguments
public static double radiansToDegrees(double radians) {
 	return radians * 180 / Circle.PI;
}
      • Instance methods 
        • static이 없는 나머지 메서드
        • 객체에 묶인 메서드
        • 클래스 내부의 경우 인스턴스 메서드에서 호출할 수 있으나,
          클래스 외부에서 호출시 <인스턴스명.>을 붙여서 호출해야 한다.
 // Two instance methods: they operate on the instance fields of an object
public double area() { // Compute the area of the circle
     return PI * r * r;
}
  • 변수 정의하는 방법 (Class, Instance 변수로 구성할 수 있다.)
    • Class fields
    • Instance fields
    • 위 두 종류의 필드의 경우 초기값을 명시하지 않는 경우, 디폴트 값으로 셋팅된다.
public class Circle {
	// A class field
	public static final double PI = 3.14159;
	// An instance field
	public double r;
	...    
}

* The use of public static fields that are not final is almost never a good practice—as multiple threads could update the field and cause behavior that is extremely hard to debug. (멀티쓰레드 환경에서는 클래스 필드를 공유하므로 해당 필드가 업데이트 될 수 있기 때문에 꼭 final을 붙여사용하라는 것 같다.)

  • 생성자 정의하는 방법
    • <클래스명>() {}, <클래스명>() {}
    • 클래스 내부에 생성자가 하나도 정의되지 않은 경우에만 컴파일러가 default constructor를 추가한다. 
      • 현재의 클래스명과 동일하다.
      • 리턴타입이 없다. (void라는 키워드사용하지 않는다. 아예 명시하지 않는다.)
      • 생성자의 body는 인스턴스를 초기화할때 사용하며 값을 리턴하지 않는다. 
      • 매개변수가 다른 생성자를 선언할 수 있다.
      • 매개변수가 다른 생성자가 선언되어 있을 때, this(x); 를 사용해 다른 선언자를 지칭할 수 있다.
     

 

  • this 키워드 이해하기
    • 현재 객체의 인스턴스 메서드 호출 혹은 인스턴스 필드를 가리킨다는 뜻이다.
  • 클래스 초기화 
public class Circle {
	static int count;
	int serialNo;
	
	static {
		count = 1;
		System.out.printf("Static Constructor is called%n");
	}
	
	{
		count++;
		serialNo = count;
		System.out.printf("This instance serialNo = %d%n", serialNo);
	}
	
	Circle (double r) {
		this.r = r;
		System.out.printf("Constructor called%n");
	}
    ...
}

위와 같은 코드가 있을 경우 초기화가 어떤 식으로 이루어질까?
class 변수가 default 값으로 초기화되고 static 초기화 블럭에서 1로 값이 변경된다.
그리고 인스턴스 생성시 인스턴스 초기화 블럭에서 serialNo가 초기화 되고 그 후에 생성자가 실행된다.

Field modifiers

  • public, protected, private
  • static
  • final
  • transient
  • volatile


참고:
자바의 정석,
http://www.r-5.org/files/books/computers/languages/java/main/Benjamin_Evans_David_Flanagan-Java_in_a_Nutshell_6th_ed-EN.pdf

add와 offer, remove와 poll, element와 peek의 차이를 처음 알았다. 
문제가 될 만한 상황에서 에러를 리턴할 것인지 null을 리턴할 것인지의 에러 핸들링 차이였다.
예를 들자면 꺼내는 상황이라 가정하자.

데이터가 아무것도 없을 때에 remove는 NoSuchElementException을 poll은 null을 리턴한다.

 

 

왜 이런 차이가 있는 것이며 우리는 앞으로 어떤 것 사용해야 할까?
이 질문에 대한 대답은 각 상황에 맞게 선택을 하면 되는 것이다. 
그리고 선택을 하더라도, 일관성있는 방식을 택하라는 백기선님의 말이 인상깊었다.

유투브 리뷰 영상을 보던 중 백기선님이 작성한 코드도 일부 볼 수 있었다. - Github 이슈에 코멘트 단 사용자의 통계 계산하기
선장님은 사용자, 즉 Participant라는 클래스에 각 이슈 번호별로 참가/불참가 여부를 나타내는 식으로 만들었다.

나같은 경우에는 사용자보다 통계 기능의 관점에서 자료구조를 만들었었다.  

위 자료구조는 여러 문제점이 있다.
이슈를 단 사용자가 많아질수록 사용자의 이름도 중복되어 생성된다는 것이다.
그리고 위와 같은식으로 만들면 항상 중간과정의 연산이 필요하다. 


하지만 객체지향적인 관점에서 사용자 클래스를 따로 생성하면
위 문제점이 다 사라지는 마법같은 현상(?)을 볼 수 있다. 통계를 구할 때에도 참가자의 전체 이슈의 참가 갯수만 세면 된다.

public class Participant {
	private String userName;
	private Map<Integer, Boolean> attendance;
	
	Participant(String userName) {
		this.userName = userName;
		this.attendance = new HashMap<Integer, Boolean>();
	}
	
	public void setMark(int issue) {
		attendance.put(issue, true);
	}
	
	public int getTotalParticipationCount() {
		int total = 0;
		for (boolean attend : attendance.values()) {
			if (attend == true) total++;
		}
		return total;
	}
}


앞서 공부했던 "객체지향 디자인"에서도 보았듯이 결국 사용자가 (백기선님) 원하는
핵심 목표가 무엇인지?에 따라 입각해서 생각하는 것이 코딩과 디자인에서 바라보는 "객체지향"이 아닐까? 

참가자의 통계를 구하시오. 라는 문장에서 중요한 것이 "통계를 구하시오"인 것처럼 보였지만, 
결국 "참가자"의 정보가 핵심이었던 것을 알아채고 설계하는 것이 객체지향 아닐까?

참가자를 오브젝트로 설정한 후에 이름, 참여 현황(이슈별 참가여부)의 속성을 부여하고
통계 구하기 라는 기능도 같이 넣어 생각하면 코딩과 디자인도 훨씬 효율적이고 쉬워질 수 있겠구나 생각했다. 

전체 코드는 깃에 올리고 다시 업데이트 하겠다. 

+ Recent posts