
오늘은 Java의 String, 반복문, static과 non-static을 배웠다.
지난 수업에서 클래스와 객체를 배운 뒤라 그런지 오늘은 문법만 외우는 것보다 이 값은 누가 가지고 있는지, 이 객체는 매번 새로 만들어야 하는지, 반복은 어떤 기준으로 돌려야 하는지를 생각하면서 코드를 보게 됐다.
특히 문자열 비교에서 ==와 .equals()가 다른 이유와 static이 클래스 소유라는 개념이 기억에 남았다.
마지막에는 오늘 배운 반복문과 Scanner를 사용해서 1~100 사이 숫자를 맞추는 게임도 직접 만들었다.
==가 아니라 .equals()오늘 가장 먼저 본 코드는 문자열 비교였다.
String str01 = new String("lg cns");
String str02 = new String("lg cns");
if(str01 == str02) {
System.out.println("str01 == str02");
}else {
System.out.println("str01 != str02");
}
if(str01.equals(str02)) {
System.out.println("str01.equals(str02)");
}else {
System.out.println("str01.!equals(str02)");
}
결과는 다음과 같다.
str01 != str02
str01.equals(str02)
str01과 str02에는 똑같이 "lg cns"가 들어있는데 ==는 false가 나온다.
이유는 두 비교 방식이 확인하는 대상이 다르기 때문이다.
str01 == str02
는 두 변수가 같은 객체를 가리키는지 비교한다.
반면
str01.equals(str02)
는 문자열 안의 내용이 같은지 비교한다.
그래서 문자열의 값을 비교할 때는 .equals()를 사용해야 한다.
new String()문자열을 만드는 방법에 따라서도 차이가 있다.
String str01 = "lg cns";
String str02 = "lg cns";
문자열 리터럴을 사용하면 Java는 이미 같은 문자열이 존재하는지 확인하고 같은 값을 재사용할 수 있다.
반면:
String str01 = new String("lg cns");
String str02 = new String("lg cns");
처럼 new를 사용하면 각각 새로운 String 객체가 만들어진다.
그래서 내용은 같아도 서로 다른 객체가 된다.
정리하면:
| 비교 방식 | 확인하는 것 | 문자열 비교에 적합한가 |
|---|---|---|
== | 같은 객체인지 | X |
.equals() | 같은 값인지 | O |
String은 immutable이라는 특징을 가진다.
즉 한 번 만들어진 문자열 자체가 수정되는 것이 아니다.
String str = "mnseokim";
str = str + "!!";
위 코드를 보면 기존 "mnseokim"에 "!!"가 붙는 것처럼 보인다.
하지만 실제로는:
"mnseokim"
↓
새 문자열 생성
↓
"mnseokim!!"
처럼 새로운 문자열이 만들어지고 str이 그 값을 다시 가리킨다.
문자열을 계속 수정해야 하는 경우에는 StringBuffer나 StringBuilder를 사용할 수 있다.
오늘은 StringBuffer를 사용했다.
StringBuffer sb = new StringBuffer(str01);
sb.append("!!");
sb.append("!!");
sb.append("!!");
sb.append("!!");
String s = sb.toString();
append()로 문자열을 이어 붙이고 마지막에 toString()으로 다시 String으로 변환할 수 있다.
Java의 문자열은 배열이 아니기 때문에:
str[0]
처럼 사용할 수 없다.
특정 위치의 문자를 가져오려면 .charAt()을 사용한다.
String name = "minseo";
char first = name.charAt(0);
System.out.println(first);
결과:
m
문자열을 문자 단위로 접근할 수는 있지만 실제 배열과 동일한 자료형은 아니다.
오늘은 for, while, do-while을 배웠다.
셋 다 반복을 위한 문법이지만 사용하는 상황이 조금씩 다르다.
| 반복문 | 조건 확인 시점 | 최소 실행 횟수 | 언제 사용하는가 |
|---|---|---|---|
for | 실행 전 | 0번 | 반복 횟수를 알고 있을 때 |
while | 실행 전 | 0번 | 조건을 기준으로 반복할 때 |
do-while | 실행 후 | 최소 1번 | 무조건 한 번은 실행해야 할 때 |
for반복 횟수가 정해져 있을 때 사용하기 좋다.
for(int i = 1; i <= 10; i++){
System.out.println(i);
}
흐름은:
i = 1
↓
i <= 10 확인
↓
코드 실행
↓
i++
↓
다시 조건 확인
숫자 맞추기 게임처럼 기회가 정확히 10번인 경우 for가 잘 맞는다.
whilewhile은 조건이 참인 동안 계속 반복한다.
int data = 1;
while (data <= nan) {
result += data;
data++;
}
data <= nan이 참인 동안 실행된다.
조건이 처음부터 거짓이라면 한 번도 실행되지 않을 수 있다.
do-whiledo-while은 코드를 먼저 실행한 뒤 조건을 확인한다.
do {
result += data;
data++;
} while(data <= nan);
그래서 조건이 처음부터 거짓이어도 최소 한 번은 실행된다.
일단 지금은 이렇게 기억하려고 한다.
while
→ 조건부터 확인
do-while
→ 일단 한 번 실행하고 조건 확인
오늘 가장 많이 생각했던 개념이다.
실습 코드는 다음과 같다.
public class StaticDemo {
public String message = "인스턴스 소유의 변수";
public static String staticMessage = "클래스 소유의 변수";
public static final double PI = 3.14;
public StaticDemo(){
}
}
사용하는 쪽에서는:
public static void main(String[] args) {
StaticDemo demo = new StaticDemo();
System.out.println(demo.message);
// StaticDemo.message;
// error
System.out.println(StaticDemo.staticMessage);
}
가장 중요한 차이는 누가 이 값을 가지고 있는가였다.
public String message = "인스턴스 소유의 변수";
static이 붙지 않은 값은 각 객체가 가지고 있다.
그래서 먼저 객체를 만든다.
StaticDemo demo = new StaticDemo();
그리고:
demo.message
처럼 접근한다.
즉:
클래스
↓
new
↓
객체 생성
↓
객체가 자신의 값 보유
이다.
public static String staticMessage = "클래스 소유의 변수";
static이 붙으면 객체가 아니라 클래스가 가지고 있는 값이다.
그래서 객체를 만들지 않고:
StaticDemo.staticMessage
처럼 클래스 이름으로 바로 접근할 수 있다.
정리하면:
non-static
→ 객체 소유
static
→ 클래스 소유
static은 객체를 만들기 전에도 사용할 수 있다.
하지만 instance 멤버는 실제 객체가 생성돼야 존재한다.
new StaticDemo();
가 실행되기 전에는 어떤 message를 가리켜야 하는지 정할 수 없다.
반대로 객체가 만들어진 시점에는 static 멤버가 이미 존재하므로 instance에서 static에 접근하는 것은 가능하다.
지금은 이렇게 이해했다.
static
→ 먼저 존재 가능
instance
→ new 이후 존재
그래서:
instance → static
가능
static → instance
바로 접근 불가능
이다.
static finalpublic static final double PI = 3.14;
static과 final은 서로 다른 의미다.
static
→ 누구 소유인가?
final
→ 값을 다시 바꿀 수 있는가?
둘을 같이 사용하면 클래스 전체에서 하나만 존재하고 값도 다시 바꿀 수 없는 상수가 된다.
상수는 보통 대문자로 작성한다.
PI
MAX_SIZE
DEFAULT_VALUE
오늘 배운 내용을 이용해서 숫자 맞추기 게임을 만들었다.
조건은 다음과 같다.
1~100 사이 난수 생성
기회는 총 10번
입력한 값이 정답보다 크면
→ down
작으면
→ up
정답이면
→ "x번째 정답을 맞췄습니다"
10번 모두 실패하면
→ "10번의 기회를 모두 사용하였습니다."

GuessGameApp.javaimport features.game.GuessGame;
public class GuessGameApp {
public static void main(String[] args) {
GuessGame game = new GuessGame();
String result = game.gameFor();
System.out.println(result);
}
}
지난번에 배운 객체 개념도 자연스럽게 다시 사용했다.
GuessGame game = new GuessGame();
GuessGame 객체를 만들고:
game.gameFor();
객체가 가진 gameFor() 메서드를 호출한다.
반환 타입이 String이기 때문에:
String result = game.gameFor();
로 결과를 받을 수 있다.
GuessGame.javapackage features.game;
import java.util.Scanner;
public class GuessGame {
public GuessGame(){
}
public String gameFor(){
int answer = (int)(Math.random()*100) + 1;
Scanner sc = new Scanner(System.in);
String msg = "";
for (int i = 1; i <= 10; i++){
System.out.print(">>>> 생각하는 숫자를 입력하세요!");
int guess = sc.nextInt();
int num = i;
if(guess > answer){
System.out.println("down");
}else if(guess < answer){
System.out.println("up");
}else{
msg = String.format(
"%d번째 정답을 맞췄습니다",
num
);
break;
}
if(num == 10){
msg = "10번의 기회를 모두 사용하였습니다.";
}
}
return msg;
}
public String gameWhile(){
// 미완
return null;
}
public String gameDoWhile(){
// 미완
return null;
}
}
int answer = (int)(Math.random()*100) + 1;
Math.random()을 사용해서 1~100 사이의 값을 만들었다.
현재는 이런 형태로 기억하려고 한다.
(int)(Math.random() * 범위) + 시작값
Scanner sc = new Scanner(System.in);
처음에는 입력을 받을 때마다 Scanner를 새로 만들어야 하나 싶었는데 역할이 달랐다.
new Scanner(System.in)
은 입력을 받을 통로를 만드는 것이다.
실제 입력은:
sc.nextInt();
로 받는다.
그래서:
Scanner sc = new Scanner(System.in);
for (...) {
int guess = sc.nextInt();
}
처럼 한 번 만든 Scanner를 계속 사용하면 된다.
앞으로 객체를 반복문 안에 둘지 밖에 둘지 고민될 때는:
이 객체를 매 반복마다 새로 만들 이유가 있는가?
를 먼저 생각해볼 것 같다.
String.format()몇 번째 시도에서 정답을 맞혔는지 문자열로 만들기 위해 사용했다.
msg = String.format(
"%d번째 정답을 맞췄습니다",
num
);
예를 들어 num이 3이면:
3번째 정답을 맞췄습니다
가 된다.
%d를 문자열에 그냥 넣는다고 값이 자동으로 들어가는 것은 아니고 String.format()을 사용해야 한다.
break정답을 맞히면 더 이상 반복할 이유가 없다.
}else{
msg = String.format(
"%d번째 정답을 맞췄습니다",
num
);
break;
}
break가 실행되면 현재 반복문을 바로 빠져나간다.

오늘 배운 내용을 짧게 다시 정리하면 다음과 같다.
str01.equals(str02);
문자열의 내용을 비교할 때는 .equals()를 사용한다.
String str = "hello";
str = str + "!";
기존 문자열을 수정하는 것이 아니라 새로운 문자열이 만들어진다.
수정이 많다면 StringBuffer나 StringBuilder를 사용할 수 있다.
str.charAt(0);
Java의 문자열은 배열이 아니기 때문에 str[0]을 사용할 수 없다.
for
→ 반복 횟수를 알 때
while
→ 조건을 기준으로 반복할 때
do-while
→ 무조건 한 번은 실행해야 할 때
demo.message
객체가 가지고 있는 값이다.
StaticDemo.staticMessage
클래스가 가지고 있는 값이다.
public static final double PI = 3.14;
클래스 소유이면서 값이 바뀌지 않는 상수다.
Scanner sc = new Scanner(System.in);
int guess = sc.nextInt();
Scanner 객체는 한 번 만들고 입력 메서드는 반복해서 호출할 수 있다.
매번 새로 만들 이유가 없다
→ 반복문 밖
매 반복마다 새로운 객체가 필요하다
→ 반복문 안
이 기준을 기억해두려고 한다.
다음에는 오늘 만든 숫자 맞추기 게임을 그대로 두고 반복문만 바꿔서 다시 구현해볼 예정이다.
public String gameWhile(){
// while 버전 구현
return null;
}
public String gameDoWhile(){
// do-while 버전 구현
return null;
}
같은 기능을 for, while, do-while로 각각 작성해보면서 세 반복문의 차이를 코드로 익히고 싶다.
그리고 반복문은 문법만 봐서는 익숙해지기 어려운 것 같아서 간단한 코딩테스트 문제도 반복해서 풀어볼 생각이다.
특히 우선 익숙해지고 싶은 것은:
for로 범위 순회하기while을 이용한 조건 반복break, continue 사용하기오늘은 새로운 문법이 많았지만 지난번에 배운 객체 개념이 Scanner와 GuessGame에서도 계속 등장한다는 게 조금씩 보이기 시작했다.
다음에는 반복문을 직접 여러 번 작성하면서 코드를 봤을 때 어떤 반복문을 써야 할지 바로 판단할 수 있을 정도로 익숙해지는 것을 목표로 해야겠다.