2026년 1월 7일 수요일
코드를 분리하고 싶을때 가장 먼저 고려할 것은 메서드.
리턴 타입: 가지고 돌아갈 값의 타입을 정하는 것.
return : 값을 반환하고 탈출하는것.
리턴타입의 종류로는 8가지의 기본타입, 배열[], String, void 등이 있으며, 심지어는 클래스 이름도 들어갈 수 있다. (자기자신을 리턴)
- 기본타입: 타입에 맞는 값을 리턴함. ex) int는 정수형을 리턴값으로 반환
- 배열[]: 배열이 저장된 메모리의 주소를 반환
- String: 문자열이 저장된 메모리의 주소를 반환
- void: 리턴값이 존재하지 않다는 뜻.
- void가 아닌 리턴타입을 가지는 메서드는 마지막에 꼭 return을 해야 함 (안쓰면 문법오류)
- 리턴값에는 null, this, 인스턴스 생성문(new 클래스명()) 등이 들어갈 수 있다.
API: 프로그램을 만들 때 사용할 수 있도록 미리 만들어 놓은 클래스와 메서드(기능)들의 묶음이다. Java에서 제공하는것 뿐만이 아니라, 직접 작성한 메서드 중 Public으로 풀려있는것이 API가 된다.
- System.out.println(); : System클래스에 있는 out 필드가 참조하는 객체의 메소드 println을 호출
- Scanner scanner = new Scanner(System.in); : Scanner 이라는 이름을 가지는 클래스가 있는것. new 키워드로 인스턴스를 생성하고 아규먼트로 System.in을 준다. 그후 Scanner 타입 참조변수 scanner에 인스턴스의 주소를 대입 그렇기 때문에 Scanner라는 문법은 없는것
- scanner.nextLine(); : scanner가 참조하고 Scanner 인스턴스에 있는 메서드 nextLint을 호출.
- 메서드의 매개변수: 기능의 일반화를 위해 필요한 것
일반화: 하나의 고정된 값이 아니라, 여러 값을 받아 처리할 수 있도록 만드는 것
구체화: 실제 사용할 값을 넣어서 사용의 가능성을 제한는 것. (매개변수가 아닌 상수가 들어가면 구체화)
- 메서드 오버로딩 : 같은 이름을 가지는 메서드를 여러개 만드는 것이다. 이때, 멤버변수는 타입이 다르거나, 변수의 개수가 달라야 한다. 이렇게 만들어지는 메서드는 이름만 같고, 아예 다른 메서드다. (실제 컴파일하면 이름을 바꿔서 저장한다고 함
- 재귀 : 트리 구조의 데이터를 활용할때 사용한다. 잘 읽어보고 작동원리 이해하기. (읽어보면 그대로라고 함)
[P1]
public class P1 {
public static void main(String[] args) {
// 메서드
SumCalculator sumCalculator = new SumCalculator();
// 변수명은 보통 클래스의 이름을 그대로 쓴다. 단, 첫 문자를 소문자로
int result = sumCalculator.calculate(10, 20);
// sumCalculator.calculate() 메서드에서 받아온 리턴값을
// int 변수 result에 넣는다.
// 즉, int reult = 55; 가 됨. 메서드 안 for문의 연산결과가 55
System.out.println(result); // 리턴 값 활용
sumCalculator.checkValue(30, 10);
System.out.println(sumCalculator.calculate(50, 100));
// 문법적으로 문제 없음. 이럴때는 보통 괄호 안이 먼저 실행 되니까.
System.out.println(sumCalculator.calculate(100));
// 메소드 오버로딩. 매개변수가 1개인 메서드 실행
sumCalculator.process(5);
}
}
class SumCalculator {
// 리턴: 내 역할은 어디까지인가
// 매개 변수: 일반화 => 여러 상황에 대응되는 코드
int calculate(int start, int end) {
// 메서드 안에서 메서드 호출 가능
if(!checkValue(start, end)) {
// 정확한 문법은 this.checkValue
return -1; // -1은 보통 에러를 뜻한다. (관례)
}
int sum = 0;
for(int i = start; i <= end; i++) {
sum += i;
}
// 여기에 출력문이 있으면 계산 뿐만 아니라 출력도 하고있기 때문에,
// 하나의 매서드는 하나의 역할만 해야 한다는 SRP원칙에 위배됨.
// 이름에 기능을 다 명시하면 해결이 되긴 한다고 함.
// 아니면 역할을 잘라서 다른 함수로 넘기거나.
return sum;
}
// 메서드 오버로딩: 이름만 같고 아예 다른 메서드
int calculate(int end) {
return calculate(0, end);
}
// 재귀: 트리 구조의 데이터를 활용할때 사용.
void process(int value) {
System.out.println("재귀 앞: " + value);
if(value >= 10) {
return;
}
process(++value);
System.out.println("재귀 코드 뒤: " + value);
}
boolean checkValue(int start, int end) {
if(start < 0 || end < 0 || start > end) { // 예외처리
return false;
}
return true;
// if문을 실행하지 않을 경우의 return값이 필요.
// 일단 그냥 true로 주면 됨.
}
}
[static]
static은 시작부터 존재하기 때문에 인스턴스 생성과 관계가 없다. 그렇기 때문에 this 또한 사용 할 수 없다.
System.out.println("Hello World"); - System = 클래스, . = 접근 연산자, out = static 참조 변수, . = 접근 연산자, println() = 메서드 호출, "Hello World" = String 타입 아규먼트를 System클래스 내부의 println 메서드의 매개변수에 전달한다.
『println 메서드는 다양한 타입의 출럭을 위해 메서드 오버로딩을 잔뜩 해놔서 다양한 타입에 대응이 된다. 이때, 매개변수가 2개 이상인 경우의 메서드가 없기 때문에 아규먼트는 항상 한개여야 한다. 또한, 우리가 println을 사용하기 위해서 System 클래스의 인스턴스를 생성한 적이 없기 때문에 static의 개념을 알고나서 System.out.println();을 봤을때 정확히는 몰라도 static이 사용 되었을 것이라는 것을 예측할 수 있다.』
[P3] - static 예시
public class P3 {
public static void main(String[] args) {
// static
SomeData1 ref1 = new SomeData1();
SomeData1 ref2 = new SomeData1();
SomeData1.c = 30;
System.out.println(SomeData1.c); // 값을 넣고 바로 출력 가능
SomeData1.test2(); // static이라 인스턴스와 상관없이 실행가능
ref1.a = 10;
ref2.a = 20;
System.out.println(ref1.a);
System.out.println(ref2.a);
}
}
// static 장점(?) - 글로벌 함. 아무런 제약 없이 어디서든 다이렉트로 쓸수 있다
class SomeData1 {
// 3개 다 멤버 변수
int a; // 인스턴스 변수
int b; // 인스턴스 변수
static int c = 50;
// 클래스 변수(static) - 인스턴스 생성과 관계 없음. 시작부터 존재
// 이렇게 쓰면 메모리 관리를 하지 않는거지만,
// 일반적으로 static은 일반 변수랑 섞이지 않아서 괜찮다.
void test1() {
System.out.println("test1 실행");
}
static void test2() {
System.out.println("test2 실행");
}
}
[final]
문법적으로 컴파일을 제한 시킴 (문법적으로 더 나쁘게 만듬) - 변수를 상수로 만드는 것.
더 이상 값을 변경하지 못하게 만드는 것이며, 개발자의 실수를 막기 위해 사용한다. 어떠한 작업에 있어, 절대로 값이 바뀌면 안되는 값(예를들면 ip주소같은 값)을 바꾸지 못하게 하는 경우에 쓰인다. 사실상 매개변수들도 값이 바뀌면 안되기 때문에 final이 붙어야 하지만 문법이 귀찮아져서 잘 쓰이지 않는다.
- 변수: 프로그램 동작 중에 주기적으로 변하는 값
- 상수: 프로그램 동작 중에 한번 초기화 되면 절대로 바뀌지 않는 값.
[접근 제한자]
- public: 누구에게나 공개. (제한자 작성하지 않으면 기본으로 public)
- private: 비공개. 클래스 내부에서만 쓸수 있음
- protected, default는 거의 사용하지 않기 때문에 넘김
[키워드 정리]
- 소속 지정자 : static
- 변경 제한자 : final
- 접근 제한자 : public / private / protected / default
[P5]
public class P5 {
public static void main(String[] args) {
// 접근 제한자(중요): public. private
// 캡슐화, 정보은닉
SomeData3 aaa = new SomeData3();
aaa.a = 30;
// aaa.b = 40; 오류인 이유: 아래 클래스의 변수 b가 private기 때문에.
}
}
class SomeData3 {
public int a;
private int b;
SomeData3() { // 여기도 private 쓸순 있지만, 그럼 인스턴스 생성이 안됨.
}
public void test1() {
this.b = 10; // 여기는 b가 선언된 클래스 내부이기 때문에 사용가능
this.test2(); // private는 내부에서만 사용 가능한거니까.
}
private void test2() {
this.b = 20;
System.out.println(b);
}
}
[P6]
public class P6 {
public static void main(String[] args) {
// 정보 은닉(캡슐화) - private
Account aaa = new Account("민수");
Account bbb = new Account("철수");
aaa.deposite(3000);
bbb.deposite(5000);
System.out.println(aaa.getName() + "님의 잔고: " + aaa.getBalance());
int qqqq = aaa.getBalance();
qqqq -= 100000;
// 이거는 값을 복사해서 바꾸는거라 원래의 값에는 영향이 없다.
System.out.println(qqqq);
}
}
class Account {
private String name;
// 절대 꼭 private여야 함. 남들이 손을 대면 안되기 때문에 (정보은닉)
private int balance = 0;
public Account(String name) {
this.name = name;
}
// 입금
public void deposite(int value) {
if(checkBigMoney(value)) {
return;
}
balance += value;
}
private boolean checkBigMoney(int value) {
// private 이유: 내부에서 작동하는 코드라서, 외부에서 알 필요가 없다.
if(value > 100000000) {
System.out.println("돈의 출처를 취조하는 코드");
return true;
}
return false;
}
// 출금
public void withdraw(int value) {
if(value > balance) {
System.out.println("오류: 잔액이 부족합니다.");
return;
}
balance -= value;
}
// read 권한을 주겠다. name의 값을 주는거.
public String getName() {
return name;
}
public int getBalance() {
return balance;
}
}
DTO: 코드를 짤때 우선으로 작성하는 것이다. 기능 구현이 목적이 아닌 데이터를 묶어서 전달하는 것이 목적이다. 여기에서 많은 인스턴스가 생성되기 때문에 데이터 선언의 기준이 되며, 필드는 무조건 prvate로 선언하고 getter, setter로 데이터를 다뤄야 한다.
앞으로 DTO 클래스를 만들때는 클래스이름 뒤에 DTO를 붙여서 DTO임을 알기 쉽게 한다.
[P7] - setter, getter
public class P7 {
public static void main(String[] args) {
// setter, getter
Student s1 = new Student();
s1.setName("민수"); // s1,name = "민수" 와 의미가 같음
}
}
// DTO
class Student {
private String name;
private int age;
private int score; // 이 3개에 대한 setter, getter가 있어야 한다.
// setter
public void setName(String name) {
this.name = name;
}
// getter
public String getName() {
return name; // 정해져 있는 것
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setScore(int score) {
this.score = score;
}
public int getscore() {
return score;
}
}
- 컴포넌트: 기능 클래스(로직이 중요) 일반적으로는 여러 인스턴스가 생성될 일은 없음. 하나의 인스턴스를 생성해서 여러번 재사용 하는것
- 싱글톤 패턴(Singleton Pattern): 인스턴스를 한번만 생성시키기 위한 디자인 패턴
[P7]
public class P8 {
public static void main(String[] args) {
}
}
// 컴포넌트
class SomeComponent {
public void test1() { // API가 됨
// 중요한 로직...!
test2();
}
private void test2() {
// 중요한 로직...!
}
// 싱글톤 패턴(디자인 패턴의 한 종류)
private SomeComponent() { // 생성자
// 필요에 따라서는 초기화
}
/* private static SomeComponent instance = null;
public static SomeComponent getInstance() {
// SomeComponent 리턴타입을 가지는 getIntance 메서드
if(instance == null) {
instance = new SomeComponent();
}
return instance;
} */ // 이건 옛날 방식의 싱글톤
private static final SomeComponent instance = new SomeComponent();
public static SomeComponent getInstance() {
return instance;
} // 그나마 요즘의 싱글톤 패턴
}
- 클래스 선언하는 문법
- 멤버 변수 선언 - 인스턴스 생성 및 접근 (.), 스택 - 힙 (this) 중요
- 생성자 - 메서드랑 문법이 약간 다르지만 인스턴스가 생성시 자동(직접호출 불가)으로 실행되는 "메서드"라고 생각 해도 됨 (좀 틀린부분이 있긴 하지만)
- 메서드 기초 - 리턴타입, 호출 문법, 매개변수