목차
- 생성자와 설정자의 장단점 비교
- 오버로딩
- 파라미터
- static
- final
- 싱글톤 패턴
1. 생성자와 설정자의 장단점 비교
생성자(constructor)와 설정자(setter)의 장담점은 상대적이다.
실무적으로는 대표적인 매개변수 값을 생성자로 만들어놓고, 추가적인 부분은 설정자를 이용해 다루는 편이다.
1-1. 생성자(constructor)를 이용한 초기화
- 장점은 단 한번의 호출로 인스턴스를 생성 및 초기화할 수 있다는 점이다.
new User("java", "java", "java");
- 단점은 필드 초기화 위해 매개변수의 갯수를 경우의 수별로 모두 만들어야 한다.
- 호출 시 인자가 많다면 연속된 값들이 각각 어떤 필드를 의미하는지 알아보기 어렵다. 즉, 위와 같은 호출 구문을 예로 들었을 때 각각의 인자가 String name을 말하는지 String id를 말하는지 구분할 수 없다.
- 일부만 초기화하는 용도로는 사용하지 못한다. 이름만 초기화 원할 경우 이에 맞춰 생성자를 따로 생성해야 한다.
1-2. 설정자(setter)를 이용한 초기화
- 장점은 필드를 초기화하는 각각의 값들이 어떤 필드를 초기화하는 것인지 의미가 분명하다.
user.setName("java");
- 단점으로는 하나의 인스턴스를 생성하기 위해 여러 번 호출해야 한다는 점이 있다.
❗ DTO 작성 순서는 필드-생성자-메소드로 이어진다.
❗ 일반적으로 DTO(Data Transfer Object) 목적으로 설계된 클래스명은 명사 뒤에 DTO를 붙여서 쓴다.
UserDTO, MemberDTO, BoardDTO 등...
❗ 참고사항) 실무적으로 VO(Value Object)라는 용어와 혼용해서 사용하기도 한다. VO의 경우 정확히는 값이 고정(fix)되어 있는 상황을 말한다. VO와 DTO 모두 값을 담고 있는 객체 정도의 뜻으로 혼용하고 있음을 알아주면 된다.
기본 생성자는 명시적으로 작성한다.
- 매개변수 있는 생성자는 필요에 따라 만드는 선택사항이다. 이를 선택적으로 추가 및 사용하기 위해서는 기본 생성자를 우선 명시하여야 한다. 명시하지 않고 추후 매개변수 생성자를 추가할 시 에러 발생 가능성이 있기 때문이다.
- 입력 값을 강제하기 위해 의도적으로 제외하는 경우가 아니고는 기본 생성자를 반드시 작성해주어야 한다.
- 일반적으로 가장 많이 사용되는 생성자는 모든 필드를 초기화하는 생성자이다.
public String printInformation() {}
- 접근자(getter)로 하나씩 필드값을 확인해보기는 여간 번거롭기 때문에, 모든 필드의 값을 하나의 문자열로 반환하는 메소드를 필드값 확인용으로 사용한다.
A. 기본 생성자 호출
UserDTO userA = new UserDTO();
B. 설정자 호출
userA.setId("reminder");
userA.setPwd("reminder");
userA.setName("reminder");
userA.setEnrollDate(new java.util.Date());
System.out.println(userA.printInformation());
이처럼 설정자를 이용한 초기화는 호출 횟수가 상대적으로 많지만, 값의 의미들을 명확히 살필 수 있다는 특징이 있다.
2. 오버로딩(overloading)
#메소드 #시그니처
public UserDTO() {} 일종의 메소드이다.
public UserDTO(String name) {} 위와 다른 메소드로 선언된 예이다.
public UserDTO(String name, String id, String pwd) {} 앞선 예시들과 중복이 아니다.
- 오버로딩이란, 동일한 메소드명을 가졌대도 매개변수의 타입, 갯수, 순서가 다르게 작성돼 있다면 각각 다르게 정의하고 관리할 수 있도록 하는 기술을 말한다.
- 생성자 작성 시 이처럼 사용할 수 있었던 이유가 바로 오버로딩 때문이다.
매개변수 값마다 다르게 정의해야 한다면 과정이 번거롭고 복잡해질 것이다.
printChar
printDouble
...
메소드의 시그니처
public void method(int num) {}
- 메소드명(파라미터 선언부, parameter) 부분을 메소드의 시그니처(signature)라고 부른다.
- 즉 접근제한자와 반환형은 오버로딩 성립 요건에 해당하지 않는다.
- 메소드 시그니처가 동일한 경우 컴파일 에러가 발생한다.
- 접근제한자와 반환형은 메소드 시그니처에 해당하지 않는다.
public void test() {}public void test() {} 컴파일 에러가 발생한다: Duplicate method test() in type OverloadingTestprivate void test() {}접근제한자는 메소드 시그니처에 해당하지 않는다.public int test() {return 0;}반환형은 메소드 시그니처에 해당하지 않는다.
- 매개변수의 이름은 메소드 시그니처에 영향을 주지 못한다. 이름이 아무리 다르다한들 인자로 삼을 수 있는 건 같은 자료형이기 때문이다.
public void test(int num) {} 파라미터 선언부는 메소드 시그니처에 해당한다.public void test(int num2) {}매개변수의 이름은 메소드 시그니처에 영향을 주지 않는다.
3. 파라미터(parameter)
매개변수로 사용 가능한 자료형은 다음과 같다: 기본자료형, 기본자료형 배열, 클래스자료형, 클래스자료형 배열, 가변배열
기본 자료형 배열 int[] arr
클래스 자료형 UserDTO user
클래스 자료형 배열 UserDTO[] userarr
1. 기본자료형
- 기본자료형을 인자로 전달한다 함은 값을 넘겨준다는 것이다.
- 예를 들어 method 영역에서 +10씩 증가시킨 경우 method 영역에서 출력하면 30이지만 main 메소드에서는 여전히 20이다.
stack |
method - num(20) main - num(20) |
❗ 기본자료형은 값, 기본자료형 배열은 주소값을 전달하는 것이다.
2. 기본자료형 배열(얕은 복사)
- 기본자료형 배열을 배열의 주소가 전달된다.
- 즉 인자로 전달하는 배열과 매개변수로 전달 받은 배열은 서로 동일한 배열을 가리킨다.
- 메소드 영역에서 0번 index 값을 변경하거든 호출 시 배열의 값도 바뀌어 있다.
stack | heap |
testPrimaryTypeArrayParameter(iarr) (주소값 0x123) iarr (주소값 0x123) |
{1, 2, 3, 4, 5} → {99, 2, 3, 4, 5} (주소값 0x123) |
3. 클래스자료형(얕은 복사)
- 인스턴스를 통해 주소값을 넘겨준다.
- 정해진 값을 강제할 수 있다. 예를 들어 무조건 width, height를 입력하도록 기본생성자를 작성하지 않는다.
- 즉, 인자로 전달하는 인스턴스와 매개변수로 전달받은 인스턴스는 서로 동일한 인스턴스를 가리킨다.
Square.class
① 필드에 private 사용한다.
private double width;
private double height;
② 모든 필드를 초기화하는 생성자 추가하고, 초기값 입력해 인스턴스 생성하도록 한다.
public Square(double width, double height) {
this.width = width;
this.height = height;
}
package com.reminder.parameter;
public class Square {
private double width;
private double height;
public Square(double width, double height) {
this.width = width;
this.height = height;
}
/* 설정자(setter) */
public void setWidth(double width) {
this.width = width;
}
public void setHeight(double height) {
this.height = height;
}
/* 접근자(getter) */
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
public void clacArea() {
double area = width * height;
System.out.println("사각형 넓이 : " + area);
}
public void clacRound() {
double round = (width + height) * 2;
System.out.println("사각형 둘레 : " + round);
}
}
ParameterTest.class
① 매개변수명.메소드명 형태로 호출한다.
② 설정자 통해 값을 변경한다.
매개변수로 전달 받은 값 : (주소값)
package com.reminder.parameter;
public class ParameterTest {
public void testClassTypeParameter(Square square) {
System.out.println("매개변수로 전달 받은 값 : " + square);
System.out.println("변경 전 정사각형 넓이와 둘레 ===========");
square.clacArea();
square.clacRound();
/* 설정자(setter)로 값 변경 */
square.setWidth(200);
square.setHeight(200);
System.out.println("변경 후 정사각형 넓이와 둘레 ===========");
square.clacArea();
square.clacRound();
}
}
Application.class
package com.reminder.parameter;
public class Application {
public static void main(String[] args) {
ParameterTest param = new ParameterTest();
Square square = new Square(20.5, 20.5);
System.out.println(square);
param.testClassTypeParameter(square);
}
}
4. 클래스자료형 배열
다음 챕터에서 다룬다.
5. 가변인자
(String name, String... hobby)
- ...으로 표현된 길이는 값이 0개 이상인 가변을 의미한다.
- 가변인자는 반드시 마지막에 작성되어야 한다. 순서가 바뀌면 컴파일 에러가 나타난다: The variable argument type String of the method must be the last parameter
- 위와 같이 선언했을 때, 인자로 아무 것도 입력하지 않을 경우 있어야 할 name값이 전달되지 않아 에러 발생한다.
- 반면 가변인자는 0개여도 괜찮다. 전달되지 않아도 출력에 무리가 없다.
("신사임당", new String[] {"테니스", "서예"})
가변인자 데이터 자체를 배열 형태로 전달할 수 있다.
가변인자 사용 시 주의사항은 오버로딩 했을 때 호출 시 모호함이 따른다는 것이다.
(String name, String... hobby)
(String... hobby)
- 같은 String 값의 연속이기 때문에 어떤 메소드를 호출하는지 "신사임당"이라는 값이 name인지 hobby인지 불분명하게 읽히기 때문이다.
- 즉 가변배열을 매개변수로 사용한 메소드는 모호성을 피하기 위해 오버로딩 하지 않는 것이 좋다.
4. static
필드나 메소드에 사용하는 키워드이다. 특히 main 메소드에서 많이 볼 수 있었다.
public static int var;
public static void main(String[] args) {}
StudentDTO st1 = new StudentDTO();
StudentDTO st2 = new StudentDTO();
StudentDTO st3 = new StudentDTO();
stack | heap | static |
st1 st2 st3 |
name name name |
className |
- stack은 메모리들이 쌓여서 저장되는 공간이다. 메소드간 call, return 과정이 수행된다.
- heap 영역은 객체가 할당되는 공간으로 동적 영역이라고 표현한다. 프로그램 진행 흐름에 따라서 공간이 할당되었다가 더이상 참조되지 않으면 GC(가비지 컬렉터)가 삭제시킨다.
- static 영역은 정적 메모리 영역이라고 말한다. 프로그램이 시작될 시 할당돼 종료될 때까지 이어진다.
- static이란 여러 인스턴스가 공유해서 사용할 목적으로 만들어진 공간이다.
- 하지만 static 키워드의 남발은 유지보수의 추적이 힘든 코드를 낳는다.
- 분명한 목적 없이는 static 키워드 사용은 자제하는 것이 바람직하다.
- 의도적으로 static 키워드를 사용하는 대표적인 예는 싱글톤(singleton) 객체를 생성할 때가 있다.
1. 필드에서의 static
❗ static 필드에 접근하기 위해서는 클래스명.필드명 으로 접근한다.
일반 필드에 접근할 때와는 접근법에 차이가 있다.
*non-static의 경우*
public int getNonStaticCount() {
return this.nonStaticCount;
}
*static의 경우*
public int getStaticCount() {
return StaticFieldTest.staticCount; → 클래스명.필드명;
}
두 필드 값을 1씩 증가시키기 위한 용도의 메소드를 사용하는 것을 예시로 들자.
private int nonStaticCount; non-static 인스턴스 변수
private static int staticCount; static 클래스 변수
*non-static의 경우*
public void increaseNonsStaticCount() {
this.nonStaticCount++;
}
*static의 경우*
public void increaseStaticCount() {
StaticFieldTest.staticCount++;
}
stack | heap | static |
sft1 sft2 |
nonStaticCount nonStaticCount |
StaticCount |
- non-static 인스턴스 변수의 경우에는 두 개의 인스턴스가 서로 값을 공유하지 못하고 인스턴스를 생성할 때마다 0으로 초기화 되었다.
- 하지만 static 필드의 경우에는 클래스 변수 자체가 프로그램을 종료할 때까지 유지되고 있기 때문에 변경된 값을 유지하고 있다.
- 이러한 특성상 여러 개의 인스턴스가 같은 공간을 공유할 목적을 가진 필드에 static 키워드를 사용한다.
2. 클래스에서의 static
- static 안에서 this는 사용 불가하다: Cannot use this in a static context
- this는 객체의 주소값이다. 즉 인스터스 생성 이후에나 사용이 가능함을 말한다.
smt (주소값 0x999) |
count (주소값 0x999) |
staticMethod |
- 클래스명.메소드명으로 인스턴스를 생성하지 않고 호출할 수 있다.
StaticMethodTest.staticMethod();
- main 메소드는 프로그램이 시작될 때 정적 메모리 영역에 만들어져 있었음을 알 수 있다.
- main 메소드가 static이 아니었다면 아래와 같은 코드를 일일이 작성해야 했을지도 모른다.
Application app = new Application();app.main();
- static을 사용하는 대표적인 예로 Math 클래스의 메소드들이 있다.
smt.staticMethod();
- 위처럼 non-static 방식으로 로 호출해도 오류 대상은 아니다.
- 하지만 컴파일러는 static 접근 방식으로 접근해야 함을 경고한다. 권장되는 접근법이 아니기 때문이다: The static method staticMethod() from the type StaticMethodTest should be accessed in a static way
5. final
#Math클래스 #상수필드
- final은 변경 불가하다는 의미를 담고 있는 키워드이다.
- 필드와 메소드에서 사용된다. 메소드에서는 종단을 뜻한다.
private final int NON_STATIC_NUM = 1;
- 1. 선언과 동시에 초기화 또는 객체 생성과 동시에 초기화하여야 한다.
private final String NON_STATIC_NAME;
public FinalFieldTest(String nonStaticName) {
this.NON_STATIC_NAME = nonStaticName;
}
- 2. 생성자를 이용해서 초기화한다.
private final double pi1 = 3.14;
- final은 변경 불가하다. 따라서 초기 인스턴스가 생성되고 나면 기본값 0이 필드에 들어가게 되는데, 그 초기화 이후 값을 변경할 수 없기 때문에 선언하며 바로 초기화를 해주어야 한다.
- 그렇지 않으면 컴파일 에러가 발생한다: The blank final field may not have been initialized
- final 변수명은 대문자로 표기한다. camel-case 표기법으로는 단어간 구분이 불가하므로 언더바(_)를 사용한다.
- static final을 사용하는 필드들을 상수 필드라고 일컫는다.
- static final, final static 키워드간 순서는 상관 없으나 일반적으로 static final 사용한다.
- 인스턴스가 생성되기 전에 이미 static 영역이 만들어진 것이므로 생성자를 이용한 초기화 불가하다. 생성자는 인스턴스가 생성되는 시점에 호출이 되기 때문에 그 전에 초기화가 일어나지 못하는 것이다.
6. 싱글톤 패턴(singleton pattern)
직장인 A, B, C가 법인카드 하나를 공유해 사용하듯 하나의 인스턴스를 만들어 공유해 사용하는 디자인 패턴을 말한다.
6-1. 싱글톤 패턴 장점
- 인스턴스 생성 시간을 절약할 수 있다. 첫 번째 이용 시에는 인스턴스를 생성해야 하므로 속도 차이가 나지 않지만, 두 번째 이용 시에는 인스턴스 생성 시간 없이 사용할 수 있다.
- 인스턴스가 절대적으로 한 개만 존재하는 것을 보장할 수 있다.
6-2. 싱글톤 패턴 단점
- 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유하면 결합도가 높아진다. 이는 높은 유지보수성 유지거나 테스트를 치르는데 문제점으로 이어질 수 있다.
- 동시성 문제를 고려해서 설계해야 하기 때문에 난이도가 있다. 프로그램의 흐름이 하나가 아닐 때, 즉 멀티스레드 구조를 갖게 될 때 동시에 접근하는 모든 조건들과 상통하는지 따져봐야 한다는 제약이 따른다.
6-3. 싱글톤 구현 방법
A. 이른 초기화(Eager Initialization)
- 클래스가 생성되는 시점에 인스턴스를 생성한다.
private static EagerSingleton eager = new EagerSingleton();
- 생성자 호출을 통해 외부에서 인스턴스를 생성하는 것을 제한한다.
private EagerSingleton() {}
- 만들어 놓은 인스턴스를 반환한다.
public static EagerSingleton getInstance() {
return eager;
}
B. 게으른 초기화(Lazy Initialization)
- 클래스가 초기화 되는 시점에는 정적 필드를 선언해두고 null로 초기화한다.
private static LazySingleton lazy;
- 생성자 호출을 통해 외부에서 인스턴스를 생성하는 것을 제한한다.
private LazySingleton() {}
- 인스턴스를 생성한 적이 없는 경우 인스턴스를 생성해서 반환한다.
- 생성한 인스턴스가 있는 경우 만들어둔 인스턴스를 반환한다.
public static LazySingleton getInstance() {
if(lazy == null) {
lazy = new LazySingleton();
}
return lazy;
}
eager1의 hashCode : 1227229563
eager2의 hashCode : 1227229563
lazy1의 hashCode : 1910163204
lazy2의 hashCode : 1910163204
- 이른 초기화를 사용하면 클래스를 로드하는 속도가 느려지지만, 처음 인스턴스 반환 요청에서는 속도가 빠르다는 장점을 가진다.
- 게으른 초기화를 사용하면 null로 선언만 해놨기 때문에 클래스를 로드하는 속도는 빠르지만, 첫 번째 요청에 대한 속도가 두 번째 요청에 대한 속도보다 느리다는 특징을 가지고 있다. lazy1 때는 if문에 걸려서 new생성자 호출해 와야 하기 때문이다.
- 이처럼 static 키워드를 활용해 인스턴스가 반드시 한 개인 디자인 패턴을 적용할 수 있다.
- private을 적용해 인스턴스의 외부 생성을 제한할 수 있다.
- 한 번 만들어진 값을 lazy에 저장해서 불러오고 리턴할 수 있다.
프로그램을 작성할 때 만들어진 기능들을 파악하고, 어떤 기능들이 필요로 되는지 활용해갈 수 있어야 할 것이다.
'Java' 카테고리의 다른 글
[JAVA/수업 Quiz] 클래스와 객체 | 오버로딩 | DTO (0) | 2021.12.31 |
---|---|
[JAVA/수업 과제 practice] 클래스와 객체 Lv. 3~4 (0) | 2021.12.30 |
[JAVA/수업 과제 practice] 클래스와 객체 Lv. 1~2 (0) | 2021.12.29 |
[JAVA] 6-2. 객체 지향 언어, 캡슐화, 추상화, 생성자 (0) | 2021.12.29 |
[JAVA] 6-1. 클래스, 사용자 정의 자료형 (0) | 2021.12.29 |