학습 목표
- 자바의 4가지 특징을 이해할 수 있다.
1.운영체제 독립적
-자바 이전의 언어들은 특정 CPU에서만 작동하거나 특정OS에 따라 다르게 작성해야하는 언어들이 대부분이였지만,
자바는 JRE(Java Runtime Environment: VM(자바 가상머신) + 표준 클래스 라이브러리)가 설치되어 있는 모든 운영체제에서 실행이 가능하도록 만들어졌습니다.
2.객체 지향 언어(Object Oriented Programming : OOP)
모든 기능을 객체로 만들어서 사용해야함.장점 : 유지보수가 쉽고 확장성이 높음.
3.함수형 프로그래밍 지원
람다식과 스트림이 추가됨, 이를 사용하면 컬렉션의 요소를 필터링 ,매핑,집계처리하기 쉬워지고 코드가 간결해짐.
4.자동 메모리관리 (Garbage Collerction)
자동으로 메모리를 관리해주는 기능을 추가해줌
*JVM의 기능과 필요성을 이해할 수 있다.
자바는 컴파일러(Compiler)를 통해 기계어(Machine Language)로 변환되는 언어입니다.
자바 설치와 관련하여, JDK와 JRE의 차이에 대해 먼저 알 필요가 있습니다. JDK와 JRE는 다음의 내용을 포함합니다.
- JRE(Java Runtime Environment) : JVM + 표준 클래스 라이브러리
- JDK(Java Development Kit) : JRE + 개발에 필요한 도구
- main 메서드가 무엇인지 이해한다.
어떤 기능을 하는 코드를 묶음으로 묶은것을 의미, 즉 어떠한 기능을 수행하기 위한 일련의 코드들의 집합체이다.
함수의 개념과 가까움
함수 : 특정 기능을 수행하는 코드들을 묶은것,
메서드 : 클래스 내에 포함되어있는 함수
A main(B C ){
D
}
- A는 반환 타입을 의미합니다.
- 위의 ‘메서드란’에서 메서드는 처리 결과값을 반환한다고 했습니다. 즉, 처리 결과는 일종의 ‘값’의 형태로 반환되며, 이를 반환값이라고 합니다.
- 값에는 종류가 나뉘어져 있습니다. 예를 들면, 정수를 나타내는 정수형, 한 개의 문자를 나타내는 문자형 등과 같이 말이죠.
- 이러한 값의 유형 및 종류를 프로그래밍에서는 ‘데이터 타입’ 또는 그냥 ‘타입’이라고 합니다. 타입은 오늘 여러분들이 학습하게 될 주요 내용 중 하나입니다.
- 다시 말해, 반환 타입은 메서드가 반환할 반환값의 종류 및 유형을 의미하며, 메서드를 정의할 때 메서드 이름 앞에 반환 타입을 명시해주어야 합니다.
- B = 매개변수의 타입, C = 매개변수의 이름
- ‘메서드란’의 설명에서 메서드는 어떤 데이터를 입력받는다고 했습니다. 즉, 메서드 외부에서 메서드 내부로 전달되어진 값이죠.
- 이 값은 어딘가에 저장되지 않고서는 사용할 수 없습니다. 이 때, 이 값을 ‘변수’라는 것에 저장할 수 있습니다.
- 변수에 어떤 값을 저장하는 것을 ‘할당한다’고 합니다.
- 이처럼 외부로부터 전달받은 값을 저장하고 있는 변수를 매개변수라고 합니다.
- 즉, 매개변수는 메서드 외부에서 메서드 내부로 전달한 값을 할당받아 저장함으로써, 메서드 내부에서 사용할 수 있게 해줍니다.
- 매개변수는 메서드 외부와 메서드 내부를 매개해주는 변수라는 의미에서 매개변수라고 부릅니다. 매개변수도 일종의 변수이며, 오늘 여러분은 변수에 대해 학습하게 됩니다.
- 메서드를 정의할 때에는 메서드 이름 옆에 소괄호를 열고, 그 안에 매개변수의 타입과 매개변수의 이름을 공백으로 구분하여 명시해야 합니다.
- 매개변수의 타입은 어떤 유형의 값을 메서드가 입력받을 것인지를 명시하는 것을 의미하며, 매개변수의 이름은 입력받은 값에 어떤 이름을 붙여 사용할 것인지를 명시하는 것을 의미합니다.
- D = 메서드 바디
변수와 타입
컴퓨터도 데이터를 다룰 때, 그 데이터를 메모리라는 곳에 임시로 기억해둡니다.
그러나, 컴퓨터의 기억 과정은 사람의 기억 과정과 다릅니다. 여러분은 a가 1이고, b가 2라는 것을 기억하고자 할 때, 그냥 ‘a는 1이고 b는 2이다’로 자연스럽게 기억할 수 있을 것입니다. 반면, 컴퓨터는 메모리에 어떤 값을 저장할 때 아래의 과정을 통해 값을 저장합니다.
- 기억하고자 하는 값이 얼만큼의 메모리 공간을 필요로 하는지 파악합니다.
- 기억하고자 하는 값이 차지하는 용량만큼의 메모리 공간을 확보합니다.
- 값을 저장한 공간에 이름을 붙입니다.
- 확보한 메모리 공간에 기억하고자 하는 값을 저장합니다.
위 과정에서 1 ~ 3번이 중요합니다.
- 1번 : 어떤 값을 기억하기 위해서는 그 값을 저장하기 위해 필요한 메모리 공간의 크기를 알아야 합니다. 이를 위해 필요한 것이 바로 이제부터 학습할 타입입니다. 타입, 또는 데이터 타입은 데이터의 유형을 의미하며, 데이터의 유형별로 차지하는 메모리 공간의 크기가 각각 다릅니다.
- 2번 & 3번 : 어떤 값을 메모리에 저장한 다음, 해당 값에 접근할 수 있으려면 값을 저장한 곳에 이름이 있어야 합니다. 그래야 그 이름을 통해서 값에 접근할 수 있으니까요. 이처럼, 어떤 값을 저장하고 나서 해당 값에 접근할 수 있도록 값이 저장된 메모리 공간에 사람이 지은 이름을 붙인 것을 변수라고 합니다.
1 ~ 3번의 과정을 프로그래밍에서는 변수를 선언한다고 하며, 4번과 같이 선언한 변수에 값을 저장하는 것을 변수에 값을 할당한다고 합니다.
이번 챕터에서는 데이터의 종류와 크기를 결정하는 타입과, 데이터 저장과 사용을 위한 변수에 대해 학습합니다.
학습 목표
- 타입의 개념을 이해하고 설명할 수 있다.
타입은 어떤 값의 유형 및 종류를 의미하며 타입에 따라 값이 차지하는 메모리 공간의 크기와 값이 저장되는 방식이 결정됨 - 기본 타입과 참조 타입의 차이를 설명할 수 있다.
기본타입 -값을 저장할 때 데이터의 실제 값이 저장됨 , 정수타입 ,실수타입, 문자타입, 논리타입
참조타입 -값을 저장할 때 데이터가 저장된 곳을 나타내는 주소값이 저장됨, 객체의 주소를 저장, 8개의 기본형을 제외한 나머지 타입 - 정수 타입, 실수 타입, 논리 타입, 문자 타입을 이해한다.
- 변수, 상수, 리터럴이 무엇인지 설명할 수 있다.
변수 : 값이 변할수 있는 데이터를 임시적으로 저장하기 위해 값을 넣어줄 주소
예를들면int num 에서 타입은 int 변수는 num
상수(Constant) : 변하지 말아야 할 데이터를 임시적으로 저장하기 위한 수단,즉 재할당이 금지된 변수 final이라는 키워드로 선언
관례적으로 대문자에 언더바를 넣어 구분하는 SCREAMING_SNAKE_CASE를 사용
리터럴 : 문자 그대로의 라는 뜻, 즉 문자가 가리키는 값 그자체를 의미함(리터럴은 변수 또는 상수에 할당할 수 있음) - 자동 타입 변환이 어떤 경우에 발생하는지 설명할 수 있다.
- 수동 타입 변환을 어떤 경우에 사용할 수 있는지, 그리고 어떻게 사용할 수 있는지 설명할 수 있다.
아래 순서도의 화살표는 화살표를 기준으로 좌측의 타입이 우측의 타입으로 자동으로 변환될 수 있음을 의미합니다. 각각의 타입 옆의 소괄호에 타입이 가지는 크기를 적어두었으니, 위의 두 가지 원칙을 기반으로 아래의 순서도를 스스로 이해해보세요.
원칙을 이해하는 것이 중요합니다. 아래의 내용을 천천히 이해해보시고, 이해한 내용을 연습문제 또는 추후 실습을 통해 적용하면서 익숙해지시기 바랍니다.
byte(1) -> short(2)/char(2) -> int(4) -> long(8) -> float(4) -> double(8)
하나만 짚자면, 위의 순서도에서 float은 4byte인데 int와 long보다 더 뒤쪽에 있습니다. 이는 float이 표현할 수 있는 값이 모든 정수형보다 더 정밀하기 때문입니다.
// float이 long보다 정밀하므로, 자동으로 타입이 변환됩니다.
long longValue = 12345L;
float floatValue = longValue;
System.out.println(floatValue); // 12345.0이 출력됩니다.
수동 타입 변환
차지하는 메모리 용량이 더 큰 타입에서 작은 타입으로는 자동으로 타입이 변환되지 않습니다. 이때 더 큰 데이터 타입을 작은 데이터 타입의 변수에 저장하기 위해서는 수동으로 타입을 변환해주어야만 합니다. 이를 캐스팅(casting)이라고 합니다.
수동으로 타입을 변환할 때에는 캐스팅 연산자 ()를 사용하며, 캐스팅 연산자의 괄호 안에 변환하고자 하는 타입을 적어주면 됩니다.
//int 타입으로 선언된 변수 intValue를 더 작은 단위인 byte로 변환합니다.
int intValue = 128;
byte byteValue = (byte)intValue;
System.out.println(byteValue); // -128
위 예제에서 intValue가 저장하고 있는 int형의 값 128을 byte형으로 캐스팅하여 byte형 변수 byteValue에 할당해주었습니다. byte형의 표현 범위는 -128 ~ 127이므로, 128을 byte형으로 변환하면 표현 범위를 벗어나게 되어 오버플로우가 발생합니다. 따라서 최종적으로 저장되는 값은 -128이 됩니다.
타입 변환은 예전에는 중요한 개념이었지만, 메모리 용량이 넉넉해진 지금은 일반적으로 정수는 int 또는 long, 실수는 double로 사용하기 때문에 예전에 비해 수동 타입 변환의 사용 빈도가 많이 줄었습니다. 따라서 지금은 ‘타입 변환이란 이런 것이구나’ 정도로만 이해해주시면 됩니다.
public class Example {
public static void main(String[] args) {
int primitive = 1;
Object reference = new Object();
System.out.println(primitive);
System.out.println(reference);
}
}
- primitive의 출력 결과는 왜 1인가?
primitive는 정수 1이라는 값을 나타내고 있으므로 - reference의 출력 결과 java.lang.Object@626b2d4a가 의미하는 것은 무엇이며, 왜 이러한 결과가 출력된 것인가?
reference는 Object() 참조타입변수는 무언가를 저장할 때, 저장하고자 하는 것이 존재하는 위치를 저장합니다. 예제의 객체는 참조타입의 데이터에 해당하며, 따라서 객체를 어떤 변수에 저장한다면 그 변수에는 객체가 존재하는 메모리 주소를 값으로 가집니다. 즉, 객체의 주소값이 변수에 저장되어 있는 것이죠. 따라서, 출력(참조타입변수);의 결과는 참조타입변수가 저장하고 있는 값,
정수형의 오버플로우와 언더플로우
- 오버플로우
- 자료형이 표현할 수 있는 범위 중 최대값 이상의 값을 표현한 경우 발생합니다.
- 최대값을 넘어가면 해당 데이터 타입의 최소값으로 값이 순환합니다.
- 예 : 어떤 값이 byte형이고, byte형의 최대값인 127을 값으로 가지는 경우, 이 값에 1을 더하면 128이 되는게 아니라, 최소값인 -128이 됩니다.
- 언더플로우
- 자료형이 표현할 수 있는 범위 중 최소값 이하의 값을 표현한 경우 발생합니다.
- 최소값을 넘어가면 해당 데이터 타입의 최대값으로 값이 순환합니다.
- 예 : 어떤 값이 byte형이고, byte 형의 최소값인 -128을 값으로 가지는 경우, 이 값에 1을 빼면 -129가 되는게 아니라, 최대값인 127이 됩니다.
실수형의 오버플로우와 언더플로우
실수형에서도 오버플로우와 언더플로우가 발생합니다. 다만, 오버플로우와 언더플로우가 발생했을 때의 결과가 다릅니다.
- 오버플로우
- 값이 음의 최소 범위 또는 양의 최대 범위를 넘어갔을 때 발생하며, 이 때 값은 무한대가 됩니다.
- 언더플로우
- 값이 음의 최대 범위 또는 양의 최소 범위를 넘어갔을 때 발생하며, 이 때 값은 0이 됩니다.
String
학습 목표
- String이 무엇인지 이해한다.
- String을 선언하고 사용하는 법을 이해한다
- String 메서드들을 이해한다.
- StringTokenizer가 무엇인지 이해한다.
- String Builder, Buffer가 무엇인지 이해한다.
// 문자열 리터럴을 String 타입의 변수 name에 할당하는 방법
String name1 = "Kim Coding";
// String 클래스의 인스턴스를 생성하는 방법
String name2 = new String("Kim Coding");
그리고, 어떤 클래스를 통해 인스턴스를 생성하면 해당 인스턴스의 타입은 자신을 생성해낸 클래스를 타입으로 가집니다. 그러므로, String 클래스를 통해 만들어진 인스턴스는 String 타입의 변수에 할당할 수 있습니다.
- String 타입의 변수는 String 변수명;으로 선언할 수 있다.
- 선언한 변수에 문자열을 할당하는 방법은 두 가지가 있다.
- 문자열 리터럴을 할당하는 방법 : 변수 = “문자열”;
- String 클래스의 인스턴스를 생성하여 할당하는 방법 : 변수 = new String(”문자열”);
1번과 2번 방법은 공통점과 차이점을 가집니다.
먼저, 두 방법의 공통점에 대해서 살펴봅시다. 문자열 리터럴을 직접 할당하는 방식이든, String 클래스의 인스턴스를 생성하여 할당하는 방법이든 공통적으로 참조 타입의 변수에 할당됩니다. 즉, 위의 예제에서 name1과 name2는 실제 문자열의 내용을 값으로 가지고 있는 것이 아니라, 문자열이 존재하는 메모리 공간 상의 주소값을 저장하고 있습니다.
그러나, 문자열을 출력해보면 주소값이 아니라 문자열의 내용이 출력됩니다.
String name1 = "Kim Coding";
String name2 = new String("Kim Coding");
System.out.print(name1); // "Kim Coding"
System.out.print(name2); // "Kim Coding"
이는 String 타입의 변수를 참조하면 String 클래스의 메서드인 toString()이 자동으로 호출되기 때문입니다. toString()이 자동으로 호출되면 String 타입의 변수가 저장하고 있는 주소값에 위치한 String 인스턴스의 내용을 문자열로 변환해줍니다.
이제 두 방법 간의 차이점에 대해서 살펴보겠습니다. 아래의 예제를 먼저 살펴봅시다.
String name1 = "Kim Coding";
String name2 = "Kim Coding";
String name3 = new String("Kim Coding");
String name4 = new String("Kim Coding");
boolean comparison1 = name1 == "Kim Coding"; // true
boolean comparison2 = name1 == name2; // true
boolean comparison3 = name1 == name3; // false
boolean comparison4 = name3 == name4; // false
boolean comparison5 = name1.equals("Kim Coding"); // true
boolean comparison6 = name1.equals(name3); // true
boolean comparison7 = name3.equals(name4); // true
위 예제를 설명하기 전에 먼저 등가 비교 연산자 ==와 equals() 메서드에 대해서 간략하게 설명하겠습니다. 등가 비교 연산자는 다음 유닛 [연산자]에서 학습합니다.
등가 비교 연산자는 좌항 == 우항의 형태로 사용할 수 있으며, 좌항의 값과 우항의 값이 일치하는지 검사하여 일치하면 true를, 일치하지 않으면 false를 반환합니다. 양쪽의 항에는 변수 또는 값 등이 위치할 수 있습니다.
equals()메서드는 간단히 설명하면, .앞의 변수가 저장하고 있는 문자열의 내용과 () 안의 문자열의 내용이 같은지 비교하여 같으면 true를 다르면 false를 반환해줍니다.
이제 예제를 보며 다시 두 방법 간의 차이를 알아봅시다.
name1과 name2는 1번의 방법, 즉 문자열 리터럴을 String 타입의 변수에 직접 할당하는 방법을 사용하고 있습니다. 이처럼 동일한 문자열 리터럴을 두 변수에 할당하는 경우, 두 변수는 같은 문자열의 참조값을 공유합니다. 즉, name1과 name2가 저장하게 되는 문자열의 주소값은 같습니다.
반면, name3과 name4는 2번의 방법, 즉 String 클래스의 인스턴스를 생성해서 String 타입의 변수에 할당하는 방법을 사용하고 있습니다. 이처럼 String 클래스의 인스턴스를 생성하게 되면 문자열의 내용이 같을지라도, 별개의 인스턴스가 따로 생성됩니다. 따라서, name3과 name4가 할당받게 되는 인스턴스의 참조값은 서로 다릅니다. 즉, name3과 name4는 서로 다른 인스턴스의 주소값을 저장하고 있게 됩니다.
이제 comparison1 ~ 7을 살펴봅시다.
- comparison1
- name1은 Kim Coding이라는 문자열 리터럴을 직접 할당받았으며, 비교의 대상이 되는 우항 또한 Kim Coding이라는 내용이 같은 문자열입니다. 문자열 리터럴은 내용이 같다면 같은 주소값을 가지기 때문에 true가 반환됩니다.
- comparison2
- name1과 name2는 내용이 같은 문자열 리터럴을 직접 할당받은 변수입니다. 따라서 두 변수는 같은 문자열의 참조값을 가지므로, true가 반환됩니다.
- comparison3
- name1은 문자열 리터럴을 할당 받은 변수이며, name3은 String 클래스를 통해 인스턴스를 생성하여 할당받은 변수입니다. String 클래스로 인스턴스를 생성하면 항상 별개의 인스턴스가 생성됩니다. 따라서 name1과 name3은 다른 주소값을 저장하고 있게 되므로 false가 반환됩니다.
- comparison4
- name3과 name4는 둘 다 String 클래스로 인스턴스를 생성하여 할당받은 변수입니다. String 클래스로 인스턴스를 생성하면 항상 별개의 인스턴스가 생성되므로 두 변수는 서로 다른 주소값을 저장하게 됩니다. 따라서 결과값으로 false가 반환됩니다.
- comparison5
- equals() 메서드를 활용하여 name1과 “Kim Coding"의 내용이 같은지 비교하고 있습니다. 이 둘은 참조값도 같고 내용도 같습니다. equals() 메서드는 내용이 같은지만을 비교합니다. 따라서 결과값으로 true가 반환됩니다.
- comparison6 :
- equals() 메서드를 활용하여 name1과 name3의 문자열 내용이 같은지 비교하고 있습니다. name1과 name3은 참조값은 서로 다르지만 내용은 같습니다. 따라서 결과값으로 true가 반환됩니다.
- comparison7
- equals() 메서드를 활용하여 name3과 name4의 문자열 내용이 같은지 비교하고 있습니다. comparison6의 경우와 마찬가지로, name3과 name4는 서로 다른 참조값을 가지지만, 이 둘이 가리키고 있는 인스턴스의 내용은 같습니다. 따라서 결과값으로 true가 반환됩니다.
String 클래스의 메서드
String 클래스는 문자열을 조작할 수 있는 유용한 메서드들을 가지고 있습니다. 클래스가 메서드를 가지고 있다는 표현이 지금은 이해가 어려우실 수 있습니다. 추후 [객체지향 프로그래밍 기초]를 학습하면서 클래스와 메서드를 배우고 나면 클래스가 메서드를 가진다는 표현을 충분히 이해하실 수 있습니다. 여기에서는 메서드 사용 방법을 중점적으로 학습해주시기 바랍니다.
- charAt() 메서드
charAt() 메서드는 해당 문자열의 특정 인덱스에 해당하는 문자를 반환합니다. 만약 해당 문자열의 길이보다 큰 인덱스나 음수를 전달하면, 오류가 발생합니다.
다음 예제는 문자열의 각 문자를 charAt() 메서드를 이용하여 하나씩 출력하는 예제입니다.
String str = new String("Java");
System.out.println("문자열 : " + str); // "문자열 : Java"
System.out.println(str.charAt(0)); // 'J'
System.out.println(str.charAt(1)); // 'a'
System.out.println(str.charAt(2)); // 'v'
System.out.println(str.charAt(3)); // 'a'
System.out.println("\ncharAt() 메서드 호출 후 문자열 : " + str);
- compareTo() 메서드
compareTo() 메서드는 해당 문자열을 인수로 전달된 문자열과 사전 편찬 순으로 비교합니다. 이 메서드는 문자열을 비교할 때 대소문자를 구분하여 비교합니다. 만약 두 문자열이 같다면 0을 반환하며, 해당 문자열이 인수로 전달된 문자열보다 작으면 음수를, 크면 양수를 반환합니다. 만약 문자열을 비교할 때 대소문자를 구분하지 않기를 원한다면, compareToIgnoreCase() 메서드를 사용하면 됩니다.
다음 예제는 compareTo() 메서드와 compareToIgnoreCase() 메서드를 이용하여 두 문자열을 비교하는 예제입니다.
String str = new String("abcd");
System.out.println("문자열 : " + str);
System.out.println(str.compareTo("bcef"));
System.out.println(str.compareTo("abcd") + "\n");
System.out.println(str.compareTo("Abcd"));
System.out.println(str.compareToIgnoreCase("Abcd"));
System.out.println("compareTo() 메서드 호출 후 문자열 : " + str);
- concat() 메서드
concat() 메서드는 해당 문자열의 뒤에 인수로 전달된 문자열을 추가한 새로운 문자열을 반환합니다. 참고로, concat은 concatenate의 약자로, 사전적으로 연결한다는 의미를 가집니다. 만약 인수로 전달된 문자열의 길이가 0이면, 해당 문자열을 그대로 반환합니다.
다음 예제는 concat() 메서드를 이용하여 두 문자열을 연결하는 예제입니다.
String str = new String("Java");
System.out.println("문자열 : " + str);
System.out.println(str.concat("수업"));
System.out.println("concat() 메서드 호출 후 문자열 : " + str);
- indexOf() 메서드
indexOf() 메서드는 해당 문자열에서 특정 문자나 문자열이 처음으로 등장하는 위치의 인덱스를 반환합니다. 만약 해당 문자열에 전달된 문자나 문자열이 포함되어 있지 않으면 -1을 반환합니다.
다음 예제는 indexOf() 메서드를 이용하여 특정 문자나 문자열이 처음 등장하는 위치의 인덱스를 찾는 예제입니다.
String str = new String("Oracle Java");
System.out.println("문자열 : " + str);
System.out.println(str.indexOf('o'));
System.out.println(str.indexOf('a'));
System.out.println(str.indexOf("Java"));
System.out.println("indexOf() 메서드 호출 후 원본 문자열 : " + str);
- trim() 메서드
trim() 메서드는 해당 문자열의 맨 앞과 맨 뒤에 포함된 모든 공백 문자를 제거해 줍니다.
다음 예제는 trim() 메서드를 이용하여 문자열에 포함된 띄어쓰기와 탭 문자를 제거하는 예제입니다.
String str = new String(" Java ");
System.out.println("문자열 : " + str);
System.out.println(str + '|');
System.out.println(str.trim() + '|');
System.out.println("trim() 메서드 호출 후 문자열 : " + str);
- toLowerCase()와 toUpperCase() 메서드
toLowerCase() 메서드는 해당 문자열의 모든 문자를 소문자로 변환시켜 줍니다. 또한, toUpperCase() 메서드는 해당 문자열의 모든 문자를 대문자로 변환시켜 줍니다.
다음 예제는 toLowerCase() 메서드와 toUpperCase() 메서드를 이용하여 문자열의 대소문자를 변경하는 예제입니다.
String str = new String("Java");
System.out.println("문자열 : " + str);
System.out.println(str.toLowerCase());
System.out.println(str.toUpperCase());
System.out.println("두 메서드 호출 후 문자열 : " + str);
아래는 String 클래스의 문자열 조작 메서드를 자세히 설명하고 있는 레퍼런스입니다.
'TIL' 카테고리의 다른 글
[Java]컬렉션 (0) | 2022.09.14 |
---|---|
[Java]캡슐화(Encapsulation) (0) | 2022.09.13 |
[Java]상속(Inheritance) (0) | 2022.09.13 |
[Java]객체지향 기초 (0) | 2022.09.12 |
느슨해진 마음가짐을 바로잡고자 시작하는 기록일지. (0) | 2022.09.07 |