오늘은 몸상태 어제보다는 조금 더 낫은 상태? 이다.
오늘은 어제 강의에 이어 Enum 2회차 + 컬렉션 1회차 진행 예정이다.
내용 정리 또한, Enum 만 진행할 예정이고, 컬렉션은 키워드 정도만 적을 생각이다.
오전엔 어제 gpt성님에게 얻은 코드를 내것으로 만들기 위해 계속 연습해 볼 생각이다.
나열한 항목(상수)들만 사용할 수 있다. - > 그외는 사용 불가
우리가 아는 Enum과 다르게 기본 클래스에 작성한다. - > 인스턴스를 만들 수 있다.
- > 각 상수는 모두 서로 다른 인스턴스를 참조하기 때문에 참조 값이 다르다.
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade();
public static final ClassGrade GOLD = new ClassGrade();
public static final ClassGrade DIAMOND = new ClassGrade();
}
타입안정성 향상
정해진 객체만 사용할 수 있기 때문에, 잘못된 값을 입력하는 문제를 근본적으로 방지할 수 있다.
데이터 일관성
정해진 객체만 사용하므로 데이터의 일관성이 보장된다.
public class ClassGradeEx2_2 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
ClassGrade newClassGrade = new ClassGrade(); //생성자 private으로 막 아야 함
int result = discountService.discount(newClassGrade, price);
System.out.println("newClassGrade 등급의 할인 금액: " + result);
}
}
위 처럼 main에 객체를 생성하면 빈 깡통 객체가 생성되어, 위 discount 메서드에 들어가도 원하는 값을 얻을 수 없다.
- > 위 문제 해결을 위해 class에 생성자를 private로 두어 main에서 인스턴스 생성 못하게 막을 수 있긴하다.
타입 안전 열거형 패턴을 쉽게 한것이 Enum 이다. + 타입 안전 열거형 패턴의 장점은 가지고오고, 단점은 보완!
해당 enum클래스는 객체를 생성 못한다! 그래서 특히나 static의 개념을 잘 알아야한다. < - 물론 개인전인 생각이다.
밑에 코드와 같이 상수 옆에 변수를 달 수 있다.
물론 선택 사항이다. 인스턴스 변수를 달라고 할적엔,
생성자에 매개변수와 실행문에 넣으려는 변수를 입력해야하고, enum 필드에도 마찬가지로 변수를 선언해준다.
ps
상수만 정의 할 적엔 상수정의 부분 끝에 ;(세미콘론)이 필요 없지만, 상수의 변수가 필요해서 정의를 할 적엔, 상수 정의 부분 끝에 세미콘론이 필요하다.
public enum Grade {
BASIC(10), GOLD(20), DIAMOND(30); // < - 인스턴스 변수를 선언하여 ';' 이 필요하다!
private final int discountPercent; // < -
Grade(int discountPercent) { // < -
this.discountPercent = discountPercent;
}
/모든 ENUM 반환
Grade[] values = Grade.values(); // < - 값들이 배열로 나와서 Grade[]
System.out.println("values = " + Arrays.toString(values));
// < - 이렇게해야 enum의 상수들이 출력된다.
// System.out.println("values = " + values); - > 하면 참조값이 출력됨
for (Grade value : values) { // < - 밑에 코드 리뷰? 에서 공부한 내용이랑 같은 내용
System.out.println("name=" + value.name() + ", ordinal=" + value.ordinal());
// value.name()은 상수 값 = 상수의 이름
// ex PLU 가 상수로 enum클래스에 객체라면 , 해당 매세드로 출력 결과 - > PLU
String input = "GOLD"; // < - String - > enum(열거혈)
Grade gold = Grade.valueOf(input);
// < - Grade 는 타입(열거형)! , gold 는 변수명
System.out.println("gold = " + gold);
배열 ,인덱스 ,빅오 ,순차 검색 ,조회 ,변경 ,알고리즘 ,성능 ,상수 시간 ,선형 시간 ,제곱 시간 ,로그 시간 ,선형 로그 ,데이터 추가 ,배열의 생성시점 ,정적
동적,리스트,순서,중복 허용,copyof(),데이터 추가,삭제
코드를 보기에 앞서 해당 코드의 큰 흐름은
fromId(int id) 에서 나온 상수 를, calculate 메서드의 매개변수인 OperationEnum operation 자리에 매개인자로써 들어가면서 산술 기능을 수행하는 흐름입니다.
package PracticeEnum;
public enum OperationEnum {
PLU("+", 1),
MIN("-", 2),
MUL("*", 3),
DIV("/", 4);
private final String symbol;
private final int id;
OperationEnum(String symbol, int id) {
this.symbol = symbol;
this.id = id;
}
// ID로 enum 찾기
public static OperationEnum fromId(int id) {
for (OperationEnum op : values()) {
if (op.id == id) {
return op;
}
}
throw new IllegalArgumentException("Invalid operation ID: " + id);
}
public String getSymbol() {
return symbol;
}
public int getId() {
return id;
}
// 연산 메서드
public static int calculate(int num1, int num2, OperationEnum operation) {
switch (operation) {
case PLU:
return num1 + num2;
case MIN:
return num1 - num2;
case MUL:
return num1 * num2;
case DIV:
return num1 / num2;
default:
throw new IllegalArgumentException("Invalid operation: " + operation);
}
}
}
위 코드를 3번 정도 연습한 나의 코드
package Practice.P1;
public enum P3 {
PLU(1,"+"),MIN(2,"-"),MUL(3,"*"),DIV(4,"/");
private final int sign;
private final String signal;
P3 (int sign,String signal){
this.sign=sign; // 후에 필드를 지정
this.signal=signal;
}
public static P3 hashMapEnumSign(int sign){
for(P3 p3 : values()){
if(p3.sign == sign){
return p3;
}else{
throw new IllegalArgumentException("exception : " + sign);
}
}
}
public int getSign(){
return sign;
}
public String getSignal(){
return signal;
}
public static int operation(int num1,int num2,P3 p3,int sign){
switch (p3.hashMapEnumSign(sign)){
case PLU -> return num1 + num2;
case MIN -> return num1 - num2;
case MUL -> return num1 * num2;
case DIV -> return num1 / num2;
default -> throw new IllegalArgumentException();
}
}
}
내가 연습하며 쓴 코드에서 모르는 부분을 다시 질문!
질문 외에 틀린 부분은 숙지했고, 위 코드는 3회차 연습한 결과이니 오해 ㄴㄴ
public static P3 hashMapEnumSign(int sign){
for(P3 p3 : values()){
if(p3.sign == sign){
return p3;
}else{
throw new IllegalArgumentException("exception : " + sign);
}
}
}
// ID로 enum 찾기
public static OperationEnum fromId(int id) {
for (OperationEnum op : values()) {
if (op.id == id) {
return op;
}
}
throw new IllegalArgumentException("Invalid operation ID: " + id);
}
values()는 enum 클래스에 포함된 모든 상수를 배열로 반환하는 메서드이고, 호출 방식은 두가지이다.
- 클래스 이름을 붙여서 호출 - 클래스명.메서드
- 메서드 단독 호출 가능 - 컴파일러가 자동으로 클래스명.메서드 로 처리하기 때문
🚩 ps
fromId() 메서드 < - > getSymbol()
우선 static 클래스 내에 객체없이 이용할 때 쓰고, heap영역이 아닌 메서드 영역에 저장된다. 를 가지고 질문을 했다.
fromId()에서 static을 쓴 이유 :
객체를 생성할 필요가 없기( = 클래스 내에 객체없이 이용 ) 때문에 사용했다.
getSymbol()은 그럼 왜 static을 쓰지 않을까?
getSymbol()에서 static을 쓴 이유 :
getSymbol()은 특정 상수 객체(PLU, MIN, ...)가 존재할 때만 의미가 있는 동작이다.
객체(상수)의 인스턴스 변수(symbol)에 접근해서 ( - > 표현이 헛갈리지만 상수의 변수 symbol을 말하는 것 ) 그 값을 반환하는 메서드이기 때문에, 특정 객체 없이 호출하는 건 불가능해하다( 코드에서 a = 상수 이다.를 표현할 때, 무조건 상수를 가지고 와야한다. ).
각 상수는 고유한 symbol 값을 가지며, 이를 반환하기 위해선 해당 객체에 접근하는 인스턴스 메서드이다.
| 메서드 | static or instance | 이유 |
|---|---|---|
| fromId() | static | 클래스 전체 데이터를 다루고, 특정 객체와 무관하게 동작하기 때문에 |
| getSymbol() | instance | 특정 객체의 상태(symbol)에 의존 하며, 객체가 있어야 의미 있는 동작이기 때문에 |
ps
static and non-static
static
static 메서드는 클래스 단위로 동작한다.
- > static 메서드에서 사용하는 데이터는 메서드 영역의 클래스 정보와 정적 변수를 기반으로 작동한다.
정적 변수란?
정적 변수(static variable)는 static 키워드가 붙은 클래스의 필드이다.
이 변수는 클래스 수준에서 하나만 생성할 수 있고, 모든 객체가 하나의 동일한 정적 변수를 공유해 사용하게 된다.객체와 독립적
정적 변수는 특정 객체가 아닌 클래스 자체에 속해. 따라서 객체를 생성하지 않고도 사용할 수 있다.
예시 코드
class Counter { // 정적 변수: 모든 객체가 공유 static int count = 0; // 인스턴스 변수: 객체마다 독립적으로 존재 int instanceCount = 0; // 정적 변수 증가 static void incrementStatic() { count++; } // 인스턴스 변수 증가 void incrementInstance() { instanceCount++; } } public class Main { public static void main(String[] args) { // 정적 변수 접근 (객체 생성 없이도 가능) Counter.incrementStatic(); // count: 1 System.out.println("Static count: " + Counter.count); // 인스턴스 변수는 객체를 통해 접근 Counter obj1 = new Counter(); obj1.incrementInstance(); // obj1의 instanceCount: 1 System.out.println("Instance count (obj1): " + obj1.instanceCount); // 또 다른 객체 생성 Counter obj2 = new Counter(); obj2.incrementInstance(); // obj2의 instanceCount: 1 (독립적) // 정적 변수는 두 객체에서 공유 System.out.println("Static count: " + Counter.count); System.out.println("Instance count (obj1): " + obj1.instanceCount); System.out.println("Instance count (obj2): " + obj2.instanceCount); } }
non-static
non-static 메서드는 객체의 속성(필드)에 접근하며, 메서드 영역의 클래스 정보를 참조해 동작한다.
| 특성 | static (fromId) | non-static (getSymbol) |
|---|---|---|
| 호출 방식 | 클래스명으로 호출 (객체 불필요) | 객체(인스턴스)를 통해 호출 |
| 객체와의 관계 | 특정 객체와 독립적으로 동작 | 특정 객체(인스턴스)에 의존 |
| 데이터 접근 범위 | 클래스 수준 데이터 (values() 등) | 인스턴스 수준 데이터 (symbol, id 등) |
PLU(1,"+"),MIN(2,"-"),MUL(3,"*"),DIV(4,"/"); // < -
private final int sign;
private final String signal;
P3 (int sign,String signal){ // < -
this.sign=sign; // 후에 필드를 지정
this.signal=signal;
}
public int getSign(){
return sign;
}
public String getSignal(){
return signal;
}
해당 부분 메서드의 매개변수에 필드인 sign 혹은 signal과 같은 변수를 쓰지 않기에 굳이 this를 붙일 필요가 없다.
자! 여기까지가 Enum 코드 분석 및 연습
오늘은 이상하게 느낀 것이 많은 날이다. 공부를 잘 했다는 뜻인가.. 시간을 잘 쓰지 못했다는 것인가...
클래스를 많이 나눈다고 좋은 것이 아니다!
- > 나눌수록 클래스안에 메서드와 필드를 공유하게 되어 캡슐화에 맞지 않다.
- > 그래서 웬만하면 해당 클래스 안에 피연산자나 매서드에 필요한 것들이 다있다면, 연산을 마치고, 외부에 그 값만 전달( getter 로)하는 것이 좋다.
그리고 클래스를 나누었던 안 나누었던, 외부에 보여질 필요없는 것은 private 를 생활화하자(매서드도 마찬가지!)
알고 있다고 넘어가지말고, 다른 예제로 연습해보자
gpt를 이용하려거든, 위에 작성한 것처럼 제대로 분석하고 연습해보자
... 등
째든 그래도 gpt가 작성한 코드의 리뷰 그리고 Enum 강의 마지막으로 배열 강의 (일부)의 키워드 정리...
내일은 더 열심히 하게 빨리 자야겠다...
ps
커맨드
ctrl + alt +shift + -> 는 코드가 좌우로 이동