일일 정리

240611 자바(4) - 생성자/this/메소드/메소드 오버로딩/정적멤버/final 필드/상수

햠__ 2024. 6. 12. 08:51

어제 사용한 필드 프로젝트에서 User 클래스를 살짝 변경해 보자.

 

User 클래스에 있는 test 메소드를 매개변수가 있는 메소드로 변경하였다.

// USER class
public void test(int number) {
		int num = 20;
		
		// 지역변수는 반드시 사용하기 전에 초기화해야 한다.
		System.out.println(num);
		
		// 매개변수는 메소드 호출 시에 반드시 값이 전달되어 오기 때문에 출력이 가능하다.
		System.out.println(number);
        
        // 필드 출력
        System.out.println(password);
}

 

메소드 안에 생성된 num은 지역 변수로 해당 메소드 안에서만 사용이 가능하다.

반드시 변수 선언 후 초기화를 해주어야만 사용이 가능하다.

 

그런데 매개 변수인 number는 바로 사용이 가능하다.

메소드가 호출될 때 전달되어 오는 값이기 때문에 바로 사용이 가능하다.

 

또한, 앞서 User 클래스에 선언한 필드는 클래스 전역에서 사용이 가능하고,

초기화하지 않아도 JVM이 기본값으로 초기화한다.

 

실행 클래스에서는 변수 구분 테스트를 위해서 메소드를 호출해 주었다.

// Application class
user.test(10);

 

실행 클래스에 변수 구분 테스트를 위해서 메소드를 호출해 주었다.

 

출력 결과

 

초기화시켜준 지역 변수 num값과 매개 변수로 전달받은 number값,

그리고 null 값으로 JVM에서 기본값으로 초기화하여 출력되는 것을 확인할 수 있다.

 

 

생성자(Constructor)


생성자

new 연산자로 호출되는 특별한 메소드로 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당

 

모든 클래스는 생성자가 반드시 존재해야 하고 생성자를 하나 이상을 가질 수 있다.

생성자와 new 연산자에 의해 Heap 영역에 객체가 생성되고 생성된 객체의 주소가 리턴된다.

 

생성자 선언

[접근 제한자] 클래스명([매개변수]) { ... } 

 

메소드와 다르게 반환값이 없고 생성자명은 클래스명과 똑같이 지정해 주어야 한다.

클래스에 생성자 선언을 생략하면 컴파일러가 내용이 비어있는 기본 생성자(Default Constructor)를 자동으로 추가해 준다.

단, 클래스에서 생성자를 한 개라도 명시적으로 선언했다면 컴파일러는 기본 생성자를 추가하지 않는다.

 

생성자도 메소드이기 때문에 오버로딩이 가능하며 오버로딩의 조건은 메소드 오버로딩과 동일하다.

생성자 오버로딩을 통해서 여러 개의 생성자를 만들고 객체 생성 시 필요한 생성자를 호출해서 객체를 만들 수 있다.

public class Member {
  // 필드 정의
  private String name;
  private int age;
  
  // 생성자 정의
  // 기본 생성자
  public Member() {
  }

  // 매개변수가 있는 생성자 (필드 초기화)
  public Member(String name, int age) {
    this.name = name;
    this.age = age;
  }
  
  // 메소드 정의
  public String information() {
    return "이름은 " + this.name + ", 나이는 " +  this.age + "살 입니다.";
  }
}

다른 실습을 진행해 보자!

 

새로 생성한 프로젝트 내에서 User 클래스를 새로 생성하였고, 접근 제한자를 private로 설정하였다.

접근 제한자를 private로 설정했기 때문에 외부 클래스에서 접근이 불가능하여 Getter, Setter를 생성하여 이를 통해 접근할 수 있다.

(단축키 Alt + Shift + S)

기본 생성자

 

생성된 필드와 Getter Setter 사이 빈 공간에 Ctrl + Space를 눌러주면 위와 같이 뜨는데

맨 위에 뜨는 User를 통해서 기본 생성자를 생성해 준다. (객체 생성) 

 

테스트를 위해서 생성자 안에 문장 하나를 쓰고 출력해 보자.

package com.beyond.constructor.practice;

public class User {
	// 1. 필드 생성 (이제 기본적으로 private로 만듦)
	private int age;
	private char gender;
	private String id;
	private String name;
	private String password;
	
    // 2. 생성자 생성 (객체 생성)
	public User() {
		System.out.println("기본 생성자 호출");
	}
	
	// 3. 메소드 선언
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public char getGender() {
		return gender;
	}
	public void setGender(char gender) {
		this.gender = gender;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}

 

위는 User 클래스이고 아래는 Application 클래스이다.

package com.beyond.constructor;

import com.beyond.constructor.practice.User;

public class Application {
	
	public static void main(String[] args) {
		User user;
		
		// 기본 생성자로 객체 생성
		user = new User();
		
		System.out.println(user);
	}
}

 

외부 클래스에서 사용하기 위해서 여기서도 기본 생성자로 객체를 생성하면 아래와 같이 생성자가 호출된 것을 확인할 수 있다.

기본 생성자 호출 결과

 

만약 생성자를 private로 선언하면 어떻게 될까?

선언한 클래스 내부가 아닌 외부 클래스인 Application 클래스에서 호출할 수 있었던 생성자가

호출이 불가능해지며 객체를 생성할 수 없고 오류가 발생한다.


이번엔 필드 내용들을 출력해 보자!

필드들을 private로 선언해 주었기 때문에 필드들의 정보를 Getter와 Setter를 통해서 읽어와야만 하는데,

이것이 불편하기 때문에 Getter Setter 아래에 객체에 대한 정보를 문자열로 리턴해주는 메소드를 하나 작성해 보겠다.

public String information() {
	return String.format("%s, %s, %s, %d, %c",
			this.id, this.name, this.password, age, gender);
}

 

메인 메소드를 실행하면 생성자를 생성하고 다음 코드인 user.information()을 호출하는 순간에 information()에 요청 흐름이 넘어간다.

information()에서 return 키워드를 만나면 return 값의 주소와 함께 다시 메인 메소드에 요청 흐름이 넘어간다.

이후 print에 return 값의 주소가 전달되어 출력된다.

 

출력 결과

 

지금은 값을 정해주지 않았기 때문에 기본값으로 출력된다.


여기서 this는 무엇일까?

 

this

객체 내부에서 객체는 자기 자신을 this라고 표현한다.

객체 내부에서 다른 멤버에게 접근하기 위해 this를 사용한다.

주로 생성자와 메소드의 매개변수 이름이 필드와 동일한 경우 매개 변수와 필드를 구분하기 위해 사용한다.


다시 실습으로 돌아와서!!

information() 메소드를 생성했으면 매개 변수가 있는 생성자를 User 클래스에 생성해 주었다.

public User(String id, String password, String name) {
	this.id = id;
	this.password = password;
	this.name = name;
}

 

객체 생성과 동시에 전달된 값들로 필드를 초기화하는 목적으로 사용되며,

기본 생성자와 동일한 이름이지만 매개변수가 다르게 주면 생성이 가능하다. (이 부분을 오버로딩이라고 부른다.)

 

이후 메인의 실행 메소드로 돌아가 값을 전달해 보자.

// 매개변수가 있는 생성자로 객체 생성 (3개의 값을 생성자로 전달)
user = new User("hong", "1234", "홍길동");
System.out.println(user.information());

 

그럼 아래와 같이 출력된다.

출력 결과

 

비교를 위해서 맨 위의 기본 생성자의 출력값은 그대로 출력 되도록 놔두었으며,

나이와 성별은 값을 전달받지 않았기 때문에 기본값으로 초기화되어 나온다.

 

-

 

다음은 5개의 모든 값을 생성자로 전달해 보겠다.

바로 아래에 5개의 값을 전달받는 새 생성자를 만들어준다.

// 매개변수가 있는 생성자로 객체 생성 (5개의 값을 생성자로 전달)
public User(String id, String password, String name, int age, char gender) {
	this.id = id;
	this.password = password;
	this.name = name;
	this.age = age;
	this.gender = gender;
}

 

User 클래스에서 새 생성자를 작성해 주었으면, Application 클래스로 가서 값을 전달받아보겠다.

// 매개변수가 있는 생성자로 객체 생성 (5개의 값을 생성자로 전달)
user = new User("kim", "5678", "김철수", 24, '남');
System.out.println(user.information());
System.out.println();

 

실행 결과

 

위 결과와 다르게 나이와 성별까지 모두 초기화되어 값이 출력되는 것을 확인할 수 있었다!

 

이 부분들에서 메소드들의 이름은 같지만 전달 받는 매개변수의 개수가 다른데, 이걸 메소드 오버로딩이라고 부른다. (아래 참조)


다른 생성자 호출

생성자에서 다른 생성자를 호출할 때 this()를 사용한다. 단, 생성자의 첫 줄에서만 사용해야 한다.

this()를 통해서 생성자 간에 중복되는 코드를 제거할 수 있다.

 

이를 이용해서 매개변수 5개 받는 생성자를 다음과 같이 수정할 수 있다.

// 매개변수가 있는 생성자로 객체 생성 (5개의 값을 생성자로 전달)
public User(String id, String password, String name, int age, char gender) {
	// 클래스 내에서 문자열 매개변수 3개를 가지는 생성자를 호출한다.
	this(id, password, name);
	this.age = age;
	this.gender = gender;
}

 

앞서 생성한 생성자 내에서만 사용이 가능하고 만약 이것을 information() 메소드에 작성하면 에러가 발생한다.

메소드 내부에서는 this()를 사용해서 생성자를 호출할 수 없다.

 

 

메소드(Method)


메소드

객체의 기능, 동작에 해당하는 코드 블록

 

수학의 함수와 비슷하며 호출을 통해 사용한다.

메소드를 호출하게 되면 중괄호 블록에 있는 코드들이 순차적으로 실행한다.

메소드는 외부로부터 필요한 값을 전달받을 수도 있고 메소드 실행 후 결과 값을 반환할 수도 있다.

 

메소드 선언

[접근 제한자] [예약어] 반환형 메소드명([매개변수]) { ... }

메소드 선언은 선언부(리턴 타입, 메소드 이름, 매개변수)와 실행 블록으로 구성된다.

 

접근 제한자로는 public, private, default, protected 설정이 가능하다.

매개 변수는 메소드가 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용된다.

메소드를 호출한 곳으로 돌아가면서 결과값을 반환하기 위해 return문을 사용한다.

public class Member {
  // 필드 정의
  private String name;
  private int age;
  
  // 생성자 정의
  ...
  
  // 메소드 정의
  public String information() {
    return "이름은 " + this.name + ", 나이는 " +  this.age + "살 입니다.";
  }
}

 

메소드 호출

클래스 내부의 다른 메소드에서 호출할 경우에는 메소드 이름으로 호출하면 된다.

클래스 외부에서 메소드를 호출할 경우에는 클래스로부터 객체를 생성한 후 메소드를 호출해야 한다.

메소드를 호출하고 리턴 값을 받고 싶다면 변수를 선언하고 대입하면 된다.

접근 제한자가 public인 메소드의 경우 도트(.) 연산자를 사용해서 메소드를 호출할 수 있다.

String info = null;
Member member = new Member();

// 도트(.) 연산자를 사용해서 메소드에 접근
info = member.information();

 

메소드 오버로딩

클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것

 

매개값을 다양하게 받아서 필요한 처리를 할 수 있다.

메소드 오버로딩의 조건은 매개변수의 타입, 개수, 순서 중 하나가 달라야 한다.

매개변수 이름만 바꾸는 것은 메소드 오버로딩이 아니다. 또한 리턴 타입만 다르고 매개변수가 동일한 것도 메소드 오버로딩이 아니다.

 

Getter와 Setter 메소드

Getter: 메소드를 통해서 필드값을 가공한 후 외부로 전달하는 역할

Setter: 메소드를 통해서 검증된 유효한 값만 데이터로 저장하는 역할

 

객체 지향 프로그래밍에서 객체의 데이터는 객체 외부에서 직접적으로 접근하는 것이 불가능하다.

필드 타입이 boolean일 경우에는 Getter는 get으로 시작하지 않고 is로 시작하는 것이 관례이다.


메소드 실습을 진행해 보자.

 

6가지 종류의 메소드를 생성해 보겠다.

아래는 모두 동일한 클래스에서 생성된 메소드들이다.

 

1. 매개변수가 없고 반환값도 없는 메소드

// 1. 매개변수가 없고 반환값도 없는 메소드
public void method1() {
	System.out.println("매개변수와 반환값이 둘 다 없는 메소드입니다.");
	
	// return문 뒤에 반환값이 오면 에러가 발생한다.
	// return "hello";
	return;
//		System.out.println("리턴 후 실행"); 	// 에러 발생
	}

 

매개변수가 없고, 반환값이 없어 void로 생성되었다.

이는 어떤 값도 반환하지 않고 메소드 내용만 수행 후 종료된다.

 

반환값이 void로 존재하지 않기 때문에 return문 뒤에 반환값이 오면 에러가 발생한다.

return문을 사용해주지 않아도 되지만 반환값 없이 사용은 가능하다.

return문을 사용하면 바로 강제 종료가 되며, return 후에 적힌 코드들은 실행되지 않으므로 에러가 발생한다.

 

 

2. 매개변수가 없고 반환값이 있는 메소드 

// 2. 매개변수가 없고 반환값이 있는 메소드
public String method2() {

	String str = null;
	str = "매개변수가 없지만 반환값이 있는 메소드입니다.";
	
	return str;
}

 

반환 타입을 지정해 주었으면 반드시 return문에 반환값을 작성해주어야 한다.

해당 변환 타입이어야 하고 작성해주지 않으면 에러가 발생한다.

 

 

3. 매개변수가 있고 반환값이 없는 메소드

// 3. 매개변수가 있고 반환값이 없는 메소드
public void method3(int num1, int num2) {
	System.out.println("매개변수가 있고 반환값이 없는 메소드입니다.");
	System.out.println("입력받은 매개 변수의 합 : " + (num1 + num2));
}

 

매개변수로 정수 2개를 받고, 반환값이 void로 정해져 있지 않기 때문에 return값이 존재하지 않는다.

메소드 내 실행문만 실행되고 종료된다.

 

 

4. 매개변수가 있고 반환값이 있는 메소드

// 4. 매개변수가 있고 반환값이 있는 메소드
public int method4(int num1, int num2) {
	int result = 0;
	result = num1 * num2;
	
	return result;
}

 

매개변수로 정수 2개를 받고, 반환값으로 정수를 반환해줘야 하기 때문에 정수형 필드 result를 생성하여

매개변수로 받은 정수 2개의 곱을 값으로 반환해 주었다.

 

 

5. 매개변수로 객체를 전달받는 메소드 (= 객체의 주소값 전달)

// 5. 매개변수로 객체를 전달받는 메소드 (= 객체의 주소값 전달)
public void method5(Member member) {
	member.setName("이몽룡");
	member.setAge(22);
}

 

매개변수로 Member 클래스의 객체를 전달받아 해당 member 객체의 이름과 나이를 변경해 주었다.

 

실행 결과

 

메인의 실행 메소드에서 출력했을 때 변경된 값으로 출력되는 것을 확인하였다.

 

 

6. 매개변수로 가변인자를 전달받는 메소드

// 6. 매개변수로 가변인자를 전달받는 메소드
// 1) 배열을 사용하는 방법
public int method6(int[] numbers) {
	int sum = 0;
		
	for (int value : numbers) {
		sum += value;
	}
		
	return sum;
}

 

매개변수로 가변인자를 전달받으면 몇 개의 매개변수를 받을지 모르기 때문에

첫 번째 방법으로는 배열을 생성해서 매개변수를 전달받아준다.

 

이후 반복문을 통해서 값들을 배열에 넣어주고 실행문을 실행한다.

 

// 2. 가변 인자를 사용하는 방법
public int method6(int... numbers) {
	int sum = 0;
		
	// 확인할 때
//	System.out.println(numbers.length);
//	System.out.println(Arrays.toString(numbers));

	for (int value : numbers) {
		sum += value;
	}
	
	return sum;
}

 

두 번째 방법은 가변 인자를 사용하는 방법이다.

이는 자바 1.5부터 지원하는데, 메소드 호출 시 넘겨준 값의 수에 따라 자동으로 배열이 생성되고 매개값으로 사용된다.

단, 이 방법을 사용할 때에는 가변 인자는 항상 매개변수의 마지막에 위치해야 하며, 앞이나 중간에 위치할 때에는 에러가 발생한다.

 

이후 반복문을 통해 배열에 값을 넣어주는 것은 동일하며, 해당 배열로 실행문을 실행한다.


다음은 오버로딩 실습이다.

실습을 위해서 새 클래스 하나 먼저 생성해 주자!

package com.beyond.method.practice;

public class B_Overloading {

	public void test() {		
	}
	
	public void test(int a) {
	}
	
	public void test(int a, String s) {
	}
	
	public void test(String s, int a) {
	}
	
	public void test(int a, int b) {
	}
}

 

새 클래스에 이름이 동일한 여러 개의 메소드를 생성해 주었다.

 

메소드 이름이 동일한 건 괜찮지만 다음 아래 경우에는 적용되지 않는다.

/*
 * 매개변수의 이름만 다르다고 오버로딩이 적용되는 것은 아니다.
 * 매개변수 이름과 상관없이 자료형의 개수와 순서가 같아서 에러가 발생한다.
 */
//	public void test(int c, int d) {
//	}

	/*
	 * 접근 제한자가 다르다고 오버로딩이 적용되는 것은 아니다.
	 * 접근 제한자와 상관없이 자료형의 개수와 순서가 같아서 에러가 발생한다.
	 */
//	private void test(int a, int b) {	
//	}

	/*
	 * 반환형이 다르다고 해서 오버로딩이 적용되는 것은 아니다.
	 * 반환형과 상관없이 자료형의 개수와 순서가 같아서 에러가 발생한다.
	 */
//	public int test(int a, int b) {	
//	}

 

즉,

1. 매개변수의 이름만 다를 경우

2. 접근 제한자만 다를 경우

3. 반환형이 다를 경우

 

위 3가지가 다르고 자료형의 개수와 순서는 같을 경우에는 오버로딩이 적용이 되지 않아서 에러가 발생한다.

단, 다 다른데 매개변수의 순서가 다를 경우는 오버로딩이 적용된다.

 

 

정적(Static) 멤버


정적(Static) 멤버

클래스에 고정된 멤버로 객체를 생성하지 않고 사용할 수 있는 필드와 메소드

인스턴스에 소속된 멤버가 아니라 클래스에 소속된 멤버이기 때문에 클래스 멤버라고도 함

 

정적 멤버 선언

정적 멤버를 선언하는 방법은 필드와 메소드 선언 시 static 키워드를 붙여주면 된다.

필드 선언할 때 객체들이 공유할 목적의 데이터라면 정적 필드로 선언한다.

메소드를 선언할 때 메소드 내부에서 정적 멤버를 사용하거나 필드를 사용하지 않는다면 정적 메소드로 선언한다.


정적 필드/메소드 실습을 진행해 보자.

 

정적 필드는 프로그램 실행과 동시에 메모리에 생성되고 객체들이 공유하면서 사용할 목적으로 선언한다.

public class A_StaticField {
	public static int number = 2;
	
	private static String message = "A_StaticField에 선언된 정적 필드입니다.";

	// static 필드에 대한 Getter와 Setter 메소드 또한 static 키워드가 붙어야한다.
	public static String getMessage() {
		return message;
	}

	public static void setMessage(String message) {
		A_StaticField.message = message;
	}
}

 

위 코드에 static 필드를 생성해 주었다.

생성할 때 생성한 Getter와 Setter 메소드 또한 static 키워드가 붙어야 한다.

 

아래는 4가지 종류의 정적 메소드를 만들어서 실습하였다.

 

1. 매개변수와 반환값이 없는 정적 메소드

private static int num1 = 10;
private static int num2 = 20;
private int num3 = 30;
	
// 1. 매개변수와 반환값이 없는 정적 메소드
public static void method1() {
	System.out.println(num1 + num2++);
//		System.out.println(num3);
}

 

정적 메소드에서 static 필드에는 접근이 가능하지만 그냥 필드에 접근할 수 없다.

그렇기 때문에 맨 마지막 print문처럼 사용하면 에러가 발생한다.

이를 사용하려면 객체를 생성하고 사용해주어야 한다.

 

2. 매개변수가 없고 반환값이 없는 정적 메소드

// 2. 매개변수가 없고 반환값이 있는 정적 메소드
public static int method2() {
	// 지역 변수가 우선이 돼서 field에 접근이 불가능함
	int num1 = 1;
	int num2 = 2;
	
	return num1 + num2;
}

 

만약 static 필드 생성 후에 메소드 내 지역 변수를 같은 이름으로 생성한다면

지역 변수가 우선이 돼서 필드에 접근이 불가능하여 지역변수의 값으로 코드를 실행한다.

 

3. 매개변수가 있고 반환값이 없는 정적 메소드

// B_StaticMethod 클래스
// 3. 매개변수가 있고 반환값이 없는 정적 메소드
public static void method3(int num3) {
	System.out.println(B_StaticMethod.method2() + num3);
}
// Application 클래스

B_StaticMethod.method3(30);

 

매개변수로 받은 num3 값을 이용해서 method2() 메소드를 호출해서 실행하여 return값과 더하여 결과를 출력한다.

 

4. 매개변수가 있고 반환값도 있는 정적 메소드

// 4. 매개변수가 있고 반환값도 있는 정적 메소드
public static int method4(int... numbers) {
	int sum = 0;
		
	for (int i = 0; i < numbers.length; i++) {
		sum += numbers[i];
	}
	
	return sum;
}

 

매개변수로 가변인자를 사용하였고, 값 개수와 상관없이 결과가 출력되는 것을 확인하였다.

 

 

final 필드와 상수


final 필드

final 필드는 초기값이 저장되면 이후 값을 변경할 수가 없다.

final 필드의 초기값은 필드를 선언할 때 명시적으로 지정하는 방법과 생성자를 통해서 지정하는 방법이 있다.

 

상수(static final)

static final 필드는 객체마다 저장되지 않고, 클래스에만 포함되고 한 번 초기값이 저장되면 변경할 수 없기 때문에 static final 필드를 상수라고 부른다.

 

상수의 이름은 모두 대문자로 작성하는 것이 관례이다.

서로 다른 단어가 혼합된 이름이라면 언더바(_)로 단어들을 구분한다.

 

* final 필드는 한 번 초기화되면 수정할 수 없는 필드지만 객체마다 다른 값으로 초기화될 수 있기 때문에 fianl 필드를 상수라고 하지 않는다.

 

// final 필드를 초기화하는 방법
// 1) 선언 시 명시적으로 지정하는 방법
	private final String gender = "남자";
	
// 2) 생성자를 통해서 지정하는 방법
	private final String gender;
	
	public static final int MAX_LEVEL = 30;
	
	public C_StaticFinalField(String gender) {
		this.gender = gender;
	}

 

 

 

접근 제한자


자바에서 제공하는 접근 제한자는 public, protected, default, private 4가지 종류가 있다.

 

public : 외부에서 자유롭게 접근이 가능

protected : 같은 패키지 또는 자식 클래스에서만 접근 가능

default : 같은 패키지에 소속된 클래스에서만 접근 가능

private : 외부에서의 접근을 제한하고 선언된 클래스에서만 접근 가능