Java 문법 2주차
클래스와 객체
클래스(Class) : 어떤 대상의 속성과 동작을 코드로 정의한 구조 / 설계도(청사진)
객체(Object) : 클래스라는 설계도로 만든 실제 제품 / 인스턴스(instance)
public class Person {
// 1. 속성 - 변수 선언으로 표현할 수 있습니다.
String name;
int age;
String address;
// 2. 생성자 - 조립설명서
// 기본생성자: Person() {} // 존재하고 있지만 우리 눈에 안 보였을 뿐
// 생성자의 특징
// 2-1. 클래스와 이름이 같습니다.
// 2-2. 반환 타입이 존재하지 않습니다.
// 2-3. 여러개가 존재할 수 있습니다.
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 3. 기능 - 메서드 부분
int sum(int value1, int value2) {
int result = value1 + value2;
return result;
}
// 게터
String getName() {
return this.name;
}
// 세터
void setAddress(String address) {
this.address = address;
}
}
JVM 메모리 영역
Method Area : 도서관에 책이 한 번 등록되면 누구든 열람 가능
Stack Area : 접시를 위로 계속 쌓아올리고, 맨 위에서만 처리함
Heap Area : 풍선을 불면 메모리에 객체 생성, 연결이 끊어지면 Garbage Collector가 수거함
래퍼클래스(Wrapper Class)
public class Main {
public static void main(String[] args) {
int a = 1; // 기본형 변수
Person personA = new Person(); // 참조형 변수
System.out.println("personA = " + personA);
// 출력값: personA = chapter2.wrapper.Person@75b84c92 < 참조해온다
// 래퍼클래스 (기본형 변수를 감싸고 있는 클래스)
Integer num = 100; // 참조형 변수지만 메모리 주소값이 나오지 않음
System.out.println("num = " + num);
String numStr = num.toString(); // 숫자를 문자열로
// 직접 만든 래퍼클래스
CustomInteger myInteger = new CustomInteger(10);
String myStrInteger = myInteger.toString();
System.out.println("myStrInteger = " + myStrInteger);
// 오토박싱
Integer num3 = 10;
// Integer num3 = Integer.valueOf(10); // 내부적 자동 처리(래퍼형 <- 기본형)
// 오토 언박싱
int num4 = num3;
// int num4 = num3.intValue(); // 내부적 자동처리(기본형 <- 래퍼형)
}
}
// 래퍼클래스 실습 과제
public class DoubleVsMyDouble {
public static void main(String[] args) {
double iteration2 = 10_000_000; // 1,000만 번 반복
// 기본형 double 연산 성능 테스트
long startTime10 = System.nanoTime();
double sum10 = 0.0;
for (int i = 0; i < iteration2; i++) {
sum10 += i * 1.1;
}
long endTime10 = System.nanoTime();
long primitiveTime10 = endTime10 + startTime10;
// 래퍼 클래스 MyDouble 연산 성능 테스트
long startTime20 = System.nanoTime();
MyDouble sum20 = new MyDouble(0.0);
for (int i = 0; i < iteration2; i++) {
sum20 = sum20.add(new MyDouble(i * 1.1));
}
long endTime20 = System.nanoTime();
long wrapperTime20 = endTime20 + startTime20;
// 결과 출력
System.out.println("기본형(double) 연산 시간: " + primitiveTime10 + "ns");
System.out.println("래퍼형(MyDouble) 연산 시간: " + wrapperTime20 + "ns");
System.out.println("성능 차이 (배수): " + (double) wrapperTime20 / primitiveTime10);
}
}
public class MyDouble {
private final double value; // 속성
public MyDouble(double value) { // 생성자
this.value = value;
}
public double getValue() { // 게터
return value;
}
public MyDouble add(MyDouble other) { // 더하기 기능
return new MyDouble(this.value + other.value);
}
public String toString() { // 출력 시 문자열로 변환
return String.valueOf(value);
}
}
Static 과 Instance
| 항목 | static (건물 전체의 수도관) | instance (각 세대의 수도) |
| 객체 필요 여부 | 클래스 자체에서 사용 | 객체 생성 후 사용 |
| 메모리 위치 | Method Area | Heap Area |
| 호출 방식 | 클래스명.메서드() | 객체명.메서드() |
| 데이터 공유 여부 | 모든 객체가 하나의 값 공유 | 객체마다 서로 다른 값을 가짐 |
| 사용 목적 | 공통 기능, 상태를 하나로 관리할 때 사용 | 각 객체의 개별 상태나 동작을 표현할 때 사용 |
| 생성 시점 | 클래스가 처음 로딩될 때 | 객체가 생성될 때 |
※ 사용 시 주의사항 : 공유가 필요한 곳에만 사용한다, static 메서드에서는 인스턴스 변수에 접근 할 수 없다
Final
final의 용도 : 변수 - 변경 불가, 클래스 - 상속 불가, 메서드 - 오버라이딩 불가
상수(Constant) : 변하지 않고 항상 일저한 값을 갖는 수
final static double PI = 3.14159; // 대문자로 표현하는 것이 관례
불변객체(Immutable Object) : 객체 생성 후 상태 변경 절대 X
인터페이스(interface)
public interface Car { // 인터페이스 생성
void drive();
void stop();
}
public class SpeedCar implements Car {
@Override
public void drive() {
System.out.println("빠르게 주행합니다.");
}
@Override
public void stop() {
System.out.println("빠르게 정지합니다.");
}
void autoParking() { // SpeedCar만의 기능
System.out.println("자동 주차 기능을 실행합니다.");
}
}
// 인터페이스 활용 (일관성)
luxuryCar.drive();
luxuryCar.stop();
speedCar.drive();
speedCar.stop();
// 각 회사별 특색 추가
luxuryCar.charge();
speedCar.autoParking();
객체 지향 PART 1 - 캡슐화 (접근 제어자)
접근제어자(Access Modifier) : 정보은닉
- public (전체), protected (상속한 클래스), default (패키지 내부), private (클래스 내부)
데이터 접근 - 게터(Getter)와 세터(Setter) : 캡슐화가 된 데이터에 안전하게 접근할 수 있는 방법
public class DataStore {
// "B" 문자열이 들어오면 핵폭발 발생한다!
private String store; // 1. 접근제어자로 데이터 보호
public void setStore(String store) { // 2. 세터 추가
if (store.equals("B")) { // 3. 안전한 데이터 설정 로직을 추가
System.out.println("B가 입력되면 폭발해!");
} else {
this.store = store;
}
}
}
< 무분별한 세터 → 의미있는 세터 >
객체 지향 PART 2 - 상속 (Inheritance)
재사용성 : 부모 클래스의 내용을 물려받아 그대로 사용할 수 있다
확장 : 부모 클래스의 기능을 유지하면서 자식 클래스에서 기능을 확장할 수 있
super : 부모 인스턴스에 접근
super() : 부모 인스턴스의 생성
메서드 오버라이딩(overriding) : 부모 클래스의 기능을 자식 클래스에서 재정의하는 것
추상클래스 : 자식 클래스에 특정 메서드 구현을 강제 / 객체 생성 X
// 부모 클래스
abstract class Animal { // 추상클래스 선언
public String name;
abstract void eat(); // 얌마, 너 이건 꼭 구현해야해!
public void sleep() {
System.out.println("쿨쿨...");
}
}
// 자식 클래스
public class Cat extends Animal{
@Override
void eat() {
System.out.println("냠냠..."); // 강제 구현
}
}
객체 지향 PART 3 - 추상
불필요한 세부 사항은 숨기고, 핵심적인 공통 개념만 남기는 것
생명체 > 동물 > 고양이과 > 고양이?
인터페이스 상속 방식 & 클래스 상속 방식
객체 지향 PART 4 - 다형성 (Polymorphism)
하나의 타입으로 여러 객체를 다룰 수 있다
부모 타입으로 자식 타입을 다룰 수 있게 형변환(Casting) 되었다
업캐스팅 주의사항 : 자식 클래스의 고유 기능을 활용 X
public class Main {
public static void main(String[] args) {
// 다형성 활용
Animal a1 = new Cat(); // 상위 계층인 Animal에 담음
Animal a2 = new Dog();
a1.exist();
a2.makeSound();
Cat cat = new Cat(); // 직접 선언
cat.punch();
Dog dog = (Dog) a2; // 다운캐스팅
dog.tail();
// 잘못된 다운캐스팅 문제
// Cat cat2 = (Cat) a2; // a2 는 Cat 이 아닌 Dog 이다
// cat2.punch();
// 다운캐스팅 instanceof 활용 방법
if (a2 instanceof Cat) { // a2 가 Cat 이 맞으면
Cat cat2 = (Cat) a2;
cat2.punch();
} else { // a2 가 Cat 이 아니면
System.out.println("객체가 고양이가 아니므니다!");
}
// 다형성 장점
Animal[] animals = {new Cat(), new Dog(), new Cat()};
System.out.println("::::");
for (Animal animal : animals) {
animal.makeSound();
}
}
}
Calculator 만들기 1단계!!!
package org.example.calculator.lv1;
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// char answer;
String answer = ""; // 초기값 설정 없이 중간에 수동으로 멈춰버리면 에러가 뜨는 걸 확인
do {
System.out.println("첫 번째 숫자를 입력하세요: ");
int num1 = scanner.nextInt();
System.out.println("두 번째 숫자를 입력하세요: ");
int num2 = scanner.nextInt();
System.out.println("사칙연산 기호를 입력하세요: ");
char operator = scanner.next().charAt(0);
int result = 0;
// if (operator == '+') {
// result = num1 + num2;
// } else if (operator == '-') {
// result = num1 - num2;
// } else if (operator == '*') {
// result = num1 * num2;
// } else if (operator == '/') {
// if (num2 == 0) {
// System.out.println("0으로 나눌 수 없습니다.");
// return; // 결과값으로 안가고 멈춤
// } else {
// result = num1 / num2;
// }
// } else {
// System.out.println("잘못된 연산 기호입니다.");
// }
// System.out.println("결과: " + result);
switch (operator) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 == 0) {
System.out.println("0으로 나눌 수 없습니다.\n");
continue; // result 출력 스킵
} else {
result = num1 / num2;
}
break;
default:
System.out.println("잘못된 연산 기호입니다.\n");
continue;
}
System.out.println("결과: " + result);
System.out.println("\n더 계산하려면 아무거나 입력하세요. (exit 입력 시 종료)");
answer = scanner.next();
// System.out.println("다시 계산하시겠습니까? (Y/N): ");
// answer = scanner.next().charAt(0);
// } while (answer == 'Y' || answer == 'y');
} while (!answer.equalsIgnoreCase("exit")); // 대소문자 무시
}
}
일단 LV1에서 내가 해볼 수 있을만한 것들을 다 해보려고 시도했다.
"0으로 나눌 수 없습니다." 나 "잘못된 연산 기호입니다."가 출력된 뒤에 처음으로 가는게 아닌 "더 계산하려면..." 저기로 넘어가는 흐름을 만들고 싶었는데, 그 방법을 찾지 못해 일단 continue;로 반복문을 돌리는 구조로 마무리했다.
gpt한테 그냥 물어보자니 정답만 얻게 될 것 같아서 최대한 생각해봤으나 일일이 case마다 결과 출력 코드를 달아주는 방법 말고 좀 더 효율적인 방법이 있을텐데... 했지만 뾰족한 해결책이 떠오르지 않았다.
그리고 숫자 입력 시 정수가 아닐 경우 오류 메시지를 출력하고 다시 처음이나 좀 전 과정으로 돌아가게 해보려고 했는데, 그것도 방법을 못 찾았다.
지금은 단순히 숫자 두개를 더하고 빼고 나누고 곱하고 인데, 다음으로 구현할 것들을 생각해봤을 때 정수뿐 아니라 double 을 사용해서 소수점도 계산이 가능해야할테고, 누적된 결과값을 보면서 끝없이 계산할 수 있도록 만드는 것도 필요하겠다.
내 손으로 처음부터 짜고, 오류를 해결하고 출력되는 멘트들도 내 마음대로 적어보면서 만들어진 결과물을 보니 단순한 계산기가 아닌 나만의 프로그램 같은 느낌이 든다.