Java 인스턴스, 오버로딩, 가변인자, 생성자, static!

클래스, 객체, 인스턴스

3개를 구분하기 전에 먼저 자바 메모리 구조에 대해 알 필요가 있다.

여기서 객체와 인스턴스를 구분하려면 힙영역과 스택 영역이 하는 역할을 알아야 한다.

참고: https://hoonmaro.tistory.com/19

String str1 = "WelcomeKorea";
String str2 = new String("WelcomeKorea");

만약 String객체 2개를 만들었는데 str1는 리터럴 상수(그냥문자열)로 초기화 했고
str2new연산자를 사용해서 초기화한 경우이다.

이상황에서 두개의 문자열 데이터가 어느 영역에 저장되는지 알아보자.

먼저 str2의 경우를 보자.

new로 초기화 한 str2라는 변수는 WelcomeKorea이라는 문자열을 갖고있는 11byte짜리 변수가 아니다,
저 문자열을 가리키고 있는 주소값을 갖고 있는 변수이다(아마도 4byte).

str2는 주소값을 가지고 (메모리)에 있는 "welcomeKorea"을 찾아갈수 있는 변수이기 때문에 참조변수라한다.

그리고 str2라는 참조변수는 스택영역에 저정된다(4byte크기).

스택에 저장된 str2는 자신이 선언한 함수가 return되면 스택 영역에서 사라져 버린다.
(지역변수들이 저장되는 스택은 항상 함수가 끝나면 싹 비워진다).

그럼 사라진 str2과 연결이 끊긴 "welcomKorea"문자열은 힙에서 외롭게 있다 java의 가비지콜렉터란 녀석에게 지워져 버린다.


이번엔 str1을보자.

str1 역시 참조변수이며 스택에 저장된다.
str1이 초기화될때 사용된 리터럴 상수 welcomeKorea는 어디에 저장될까?

사실 메모리 공간에는 힙영역 스택영역 말고 리터럴 상수들 저장공간인 ‘상수풀’ 이란 공간이 있다.
이녀석도 연결끊기면 java가 알아서 삭제해줄것이다.
즉 자바에선 실제 데이터들이 저장되는 힙, 상수 풀과 같은 영역이 있고
이 위치를 가리키고 있는 참조변수들이 저장되는 스택영역이 있다.


그럼 클래스, 객체, 인스턴스를 알아보자.

클래스는 객체를 만들기 위한 설계도이다.

class Point
{
	private int x,y; 
}

위의 클래스(설계도)를 가지고 객체(생산품)를 만들수 있게 되었다.

Point p1; 이런식으로,

사실 p1이란 객체는 참조변수로 스택에 존재하게 된다.

객체는 그냥 메모리 공간을 가리키는 4byte짜리 참조변수이기 때문에
우리가 설계한 Point(점) 역할을 수행하려면 그만큼의 작업공간을 힙영역에 할당해 주어야 한다.

현재 이 상태를 객체만 만든 상태라고 한다.

객체만 만든 상태에서 p1x, y값을 접근하거나 수정하려고 하면 NullPoint Exception 오류가 뜬다.

이유는 x, y(작업공간)가 아직 메모리(힙)에 공간이 할당되지 않았기 때문!

그래서 항상 맴버변수(필드)나 맴버함수(메소드)를 사용하려면 공간할당 후에 사용해야한다.

스택에는 참조변수(객체)를 만들어 놓았는데 참조할(가리킬) 데이터가 힙에 없으니 할당해줘야 한다.
p1 = new Point();

p1과 연결된 공간이 힙에 할당되었고 이 할당된 공간, 참조할 데이터를 인스턴스라 한다.
이 과정을 p1객체를 인스턴스화 한다라고도 하고 인스턴스화된 객체를 그냥 인스턴스라고도 부른다.

클래스 객체 인스턴스 설명 끝

자세한 내용: http://hoonmaro.tistory.com/19


오버로딩

오버로딩: 이름이 같은 메서드가 여러개 선언되는 경우, 단 매개변수의 타입이나 개수가 서로 달라야 한다.

대표적인 예가 System.out.print()함수,
괄호안에 각종 변수탑입을 달리해서 때려박아도 잘 출력해 준다.

이는 printStream클래스의 print()함수가 각종 오버로딩 조건을 만족한 메소드를 엄청 많이 만들어 놓았기 때문!


가변인자

JDK 1.5부터 만들어진 개념이다.

만약 add(int n, int m)이란 nm의 합을 돌려주는 함수라 할때
nm 2개만 더하는게 아니라 3개, 4개, 5개, 10개를 매개변수로 받아들여
모두 더해 반환해주고 싶은 함수를 작성하고 싶다할때

오버로딩만 알고있다면 매개변수가 다른 각종 add함수를 여러개 작성해야 한다.

하지만 가변인자를 알고있다면 매개변수 개수를 동적으로 받는 add함수 하나만 작성하면 된다.

public class Ex01 {
	public static void main(String[] args) {
		int result = add(1, 3, 5, 6, 87, 1,1);
		System.out.println(result);
	}
	static int add(int... n)   // ...이 가변인자를 지정하는 키워드!
	{
		int sum = 0;
		for(int i : n)
			sum+=i;
		return sum;
	}
	static int sum(int...args) { 
		//for each말고 for문으로도 가능하다. 아무래도 가변인자는 배열로 저장되는듯
		int sum = 0;
		for (int i = 0; i < args.length; i++) {
			sum+=args[i];
		}
		return sum;
	}
}

즉 매개변수 개수를 고정하지 않고 동적으로 정하는 것이다.

대표적으로 System.out.printf()함수가 있다. 처음 매개변수는 format이고 다음 매개변수부터는 format에 따라 몇개가 오던지 상관없다.
(당연히 가변인자는 마지막부분에만 올 수 있다.)

System.out.printf("%s, %d, %d, %d, %d, %f", stdName, kor, eng, mat, tot, avg);


생성자(constructor)

생성자명은 클래스명과 같아야함
객체의 생성과 필드(맴버변수)를 초기화 해주기 위해 사용함.
선언형식 [접근제어자][기타제어자] 생성자명 (매개변수) {"content"}

생성자는 오버로딩 될 수 있고 객체를 인스턴스화 시킬때 자동 실행된다.

인자가 없는 생성자는 디폴트 생성자라 부르고 생성자를 아무것도 만들지 않았을때 디폴트 생성자는 자바 컴파일러에 의해 자동 추가된다.

생성자를 선언하지 않고도 Point p1 = new Point(); 라고 잘 사용했는데 그 이유가 자바 컴파일러가 자동으로 디폴트 생성자를 추가해 주었기 때문!

그런데 만약 다른 생성자를 선언했는데 매개변수가 없는 생성자를 사용했을때 오류난다.

이유는 java 컴파일러가 다른 생성자가 있으면 디폴트 생성자를 안만들어주기 때문(C++은 그래도 생성해주는데….)


this 키워드

객체의 주소값을 갖는 참조변수
자기 인스턴스의 주소값을 가지고있는 참조변수이다.

컴파일러가 자동 생성해놓은 변수로 인스턴스가 생성되는 순간 자시 자신을 가리키는 주소값이 this에 들어간다.

this의 용도 3가지

  1. 클래스 멤버(필드,메소드)를 가리킬 때의 this
    • 보통 가독성을 위해 사용하거나 자판치기 귀찮아서 사용한다.
  2. 생성자에서 또 다른 생성자를 호출할 때의 this (단 반드시 첫출에 써야함)
    • 생성자간에, 혹은 메소드가 프로그래밍한대로 생성자를 호출할 수 있다!
    • 실무에선 맴버변수가 하도 많아서 생성자 오버로딩 할때마다 일일이 적지 않고 기존에 있던걸 재활용 할 수 있다.
  3. 홀로 사용될 때의 this
    • 매개변수나 리턴값으로 사용될때.
    • .점 찍고 계속 메소드 호출할 수 있다.편리!
      그리고 아래 예제의 this.addWindowListener(this); 에서도 사용된다.
class MyFrom extends Frame
{

	public MyFrom(String title) {
		this.setTitle(title);
		this.setSize(4000, 400);
		
		this.addWindowListener(new TestListener());
		this.setVisible(true);
	}
}

윈도우 폼을 자바에서 써 보았다.

자바는 윈도우 폼에서 발생하는 이벤트(닫기, 최대화, 최소화 등등)는 리스너가 있어야
이벤트를 듣고 폼에 적용할 수 있다.

class TestListener implements WindowListener
{
	@Override
	public void windowClosing(WindowEvent e) {  //닫히기 전(저장여부 검사 등등)
		System.out.println("종료!");
		System.exit(-1);		
	}

	@Override
	public void windowActivated(WindowEvent e) {
		// TODO Auto-generated method stub
		
	}
	
}

따로따로 구현하니까 귀찮다! 합쳐보자

class MyFrom extends Frame implements WindowListener
{ //Frame상속과 WindowListener를 같이 받음

	public MyFrom(String title) {
		this.setTitle(title);
		this.setSize(4000, 400);
		
		this.addWindowListener(this);
		this.setVisible(true);
	}

	@Override
	public void windowClosing(WindowEvent e) {
		// TODO Auto-generated method stub
		System.out.println("종료!");
		System.exit(-1);
	}
	
	@Override
	public void windowActivated(WindowEvent e) {
		// TODO Auto-generated method stub
		
	}
	
}

멤버변수 초기화

클래스의 멤버변수는 default값으로 자동 초기화 되지만(int경우 0, String경우 null)
메소드 안에 지역변수들은 꼭 초기화 과정을 거쳐야지 사용할 수 있다.(안그러면 에러남)

클래스 멤버변수처럼 알아서 자동으로 초기화 되는걸 암시적 초기화라고 하고
클래스 멤버변수를 항상 사용자가 지정해놓은 값으로 초기화 되도록 하는걸 명시적 초기화

그래서 멤버변수의 초기화 방법은 3가지다.

  1. (암)명시적 초기화
  2. 생성자를 통해 초기화
  3. 초기화 블럭

초기화 블럭은 인스턴스 생성시 무조건 생성자와 같이 호출되는 녀석이다,

class 클래스명
{
	필드
	...
	{   --초기화 블럭--
		여기서 초기화하면 각 생성자마다 초기화 구문을 넣을 필요가 없다.
	}
	생성자
	...
	메소드
	...
}

클래스에서의 static 키워드

static 키워드는 필드, 메서드, 클래스 앞에 붙을 수 있고
static { ... } 처럼 초기화 블럭에도 사용 가능하다.

객체를 인스턴스화할 때 마다 힙에 기억공간이 할당된다.
이중 클래스의 일반적인 맴버변수가 힙에 할당되는데 이 맴버변수를 인스턴스 필드라고도 한다.

그런데 항상 고정적인 값(모든 객체가 같은 값을 가지는… 통장 이자같은) 들을 생성한 인스턴스만큼의 여러 공간에 모두 할당하는것 보단 하나만 할당하고 각 인스턴스가 이 할당 공간을 기리킬수 있게 하는게 더 효율적이다.

이렇게 고정값은 인스턴스가 생성되기전 먼저 값을 정해놓고 인스턴스가 생성될때 마다 이 값을 가리키도록 하는 키워드가 static이다.

class Save
{
	
	String name;
	...
	static int rate;
	static
	{
		rate = 0.04;
	}
}

static은 객체생성 전에도 메모리(메소드영역)에 올라가있기 때문에 객체를 인스턴스화 시키지 않고 바로 접근해도 된다.
System.out.println(Save.rate);

static 키워드로 선언한 변수들을 static변수, 클래스 변수, 정적변수 라고 부른다.

카테고리:

업데이트: