'자바의 정석 3rd Edition'를 공부하며 정리한 내용입니다.
1. java.lang 패키지
- 자바프로그래밍에 가장 기본이 되는 클래스들을 포함하기 때문에 import문 없이도 사용 가능
1.1 Object 클래스
- 멤버변수는 없고 11개의 메서드만 가짐
- 메서드들은 모든 인스턴스가 가져야할 기본적인 것들
1. equals(Object obj)
public boolean equals(Object obj){
return (this==obj);
}
- 매개변수로 객체의 참조변수를 받아 비교하여 그 결과를 boolean값으로 알려 주는 역할
- 서로 다른 두 객체를 equals 메서드로 비교하면 항상 false를 결과로 얻음
- 이유: 두 객체의 같고 다름을 참조변수의 값으로 판단
- 객체를 생성할 때, 메모리의 비어있는 공간을 찾아 생성하므로 서로 다른 두 개의 객체가 같은 주소를 갖을 수 없음
- 두 개 이상의 참조변수가 같은 주소값을 갖는 것(한 객체를 참조하는 것)은 가능
- equals 메서드는 결국 두 개의 참조변수가 같은 객체를 참조하고 있는지, 즉 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능만 함
- equals를 사용해 저장된 값을 비교하고 하고자 하나면 equals 메서드를 오버하이딩해 주소가 아닌 객체에 저장된 내용을 비교하도록 변경해주어야 함
- String, Date, File, wrapped 클래스의 equals메서드는 주소값이 아닌 내용을 비교하도록 오버라이딩 되어 있음
- StringBuffer 클래스는 오버라이딩 되어있지 않음
2. hashCode()
- 해싱(hashing) 기법에 사용되는 '해시함수(hash function)'를 구현한 것
- 해싱: 데이터관리기법 중 하나. 다량의 데이터를 저장하고 검색하는 데 유용
- 해시함수: 찾고자하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드(hash code)를 반환
- 해시코드가 같은 두 객체가 존재하는 것은 가능하지만, Object 클래스에 정의된 hashCode 메서드는 객체의 주소값으로 해시코드를 만들어 반환하기 때문에 32bit JVM에서는 서로 다른 두 객체는 결코 같은 해시코드를 가질 수 없없지만, 64bit JVM에서는 8byte 주소값으로 해시코드(4byte)를 만들기 때문에 해시코드가 중복될 수 있음
- 클래스의 인스턴스변수 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals 메서드와 hashCode 메서드도 적절히 오버라이딩해야 함
- 같은 객체라면 hashCode 메서드를 호출했을 때의 결과값인 해시코드도 같아야하기 때문
3. toString()
public String toString() {
return getClass().getName()+"@"+Integer.toHexString(hashcode());
}
- 인스턴스에 대한 정보를 문자열(String)로 제공할 목적으로 정의한 것
- 인스턴스 정보를 제공한다는 것은 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 의미
- 클래스 작성시 toString()을 오버라이딩하지 않으면 클래스이름에 16진수의 해시코드를 얻게 됨
- String 클래스의 toString()은 String 인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩됨
- Date 클래스의 경우 Date 인스턴스가 갖고 있는 날짜와 시간을 문자열로 변환하여 반환하도록 오버라이딩됨
4. clone()
- 자신을 복제하여 새로운 인스턴스를 생성
- 어떤 인스턴스에 대해 작업할 때, 원래의 인스턴스는 보존하고 clone 메서드를 이용해 새로운 인스턴스를 생성하여 작업하면, 작업이전의 값이 보존되므로 작업에 실패해서 원래의 상태로 되돌리거나 변경되기 전의 값을 참고하는데 도움이 됨
- Object 클래스에 정의된 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전히 인스턴스 복제가 이루어지지 않음
- clone()을 사용하려면
- 복제할 클래스가 Cloneable 인터페이스를 구현해야 하고
- clone()을 오버라이딩하면서
- 접근 제어자를 protected에서 public으로 변경 (상속관계가 없는 다른 클래스에서 clone() 호출 가능하도록)
- 조상클래스의 clone()을 호출하는 코드가 포함된 try-catch문 작성
공변 반환타입
- 공변 반환타입(covariant return type): 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것
- 장점: 조상의 타입이 아닌 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어 형변환이 줄어듦
얕은 복사와 깊은 복사
- 얕은 복사(shallow copy): 원본을 변경하면 복사본도 영향을 받음
- 깊은 복사(deep copy): 원본이 참조하고 있는 객체까지 복제하는 것
- 원본과 복사본이 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않음
5. getClass()
- 자신이 속한 클래스의 Class 객체를 반환하는 메서드
- Class 객체는 이름이 'Class'인 클래스 객체
- Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재. 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때 자동으로 생성됨
- 클래스 로더: 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 수행
- 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스 패스에 지정된 경로를 따라 클래스 파일을 찾음
- 못 찾으면
ClassNotFoundException
이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class 객체로 변환
- 파일 형태로 저장되어 있는 클래스를 읽어서 Class 클래스에 정의된 형식으로 변환하는 것
- 클래스 파일을 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체
- 클래스 로더: 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 수행
Class 객체를 얻는 방법
- 클래스 정보가 필요할 때, 가장 먼저 Class 객체에 대한 참조를 얻어와야 함
forName()
은 특정 클래스 파일(예를 들어 데이터베이스 드라이버를 메모리에 올릴 때)에 주로 사용- Class 객체를 이용하면 클래스에 정의된 이름이나 개수 등 클래스에 대한 모든 정보를 얻을 수 있기 때문에 Class 객체를 통해 객체를 생성하고 메서드를 호출하는 등 동적인 코드 작성이 가능
1.2 String 클래스
- 문자열을 저장하고 다루는데 필요한 메서드를 제공
변경 불가능한(immutable) 클래스
- 문자열을 저장하기 위해 문자형 배열 참조변수(char[]) value를 인스턴스 변수로 정의해놓음
- 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 value에 문자형 배열로 저장됨
- 한번 생성된 String 인스턴스가 갖고 있는 문자열은 읽어 올 수만 있고, 변경은 불가능
- '+'를 사용해서 문자열을 결합하는 것은 매 연산 시 마다 새로운 문자열을 가지는 String 인스턴스가 생성되어 메모리 공간을 차지하게 되므로 결합횟수를 줄이는 것이 좋음
- 문자열간의 결합이나 추출 등 문자열을 다루는 작업이 많이 필요한 경우에는 String 클래스 대신 StringBuffer 클래스를 사용하는 것이 좋음
- StringBuffer 인스턴스에 저장된 문자열은 변경이 가능하므로 하나의 StringBuffer 인스턴스만으로도 문자열 다루는 것이 가능
문자열의 비교
문자열을 만드는 두 가지 방법: 문자열 리터럴을 지정하는 방법, String 클래스의 생성자를 사용해서 만드는 방법
String str1 = "abc"; // 문자열 리터럴 "abc"의 주소가 str1에 저장됨 String str2 = "abc"; // 문자열 리터럴 "abc"의 주소가 str2에 저장됨 String str3 = new String("abc"); // 새로운 String 인스턴스 생성 String str4 = new String("abc"); // 새로운 String 인스턴스 생성
String 클래스의 생성자를 이용한 경우에는 new 연산자에 의해서 메모리 할당이 이루어지기 때문에 항상 새로운 String 인스턴스가 생성됨
문자열 리터럴은 이미 존재하는 것을 재사용하는 것
- 문자열 리터럴은 클래스가 메모리에 로드될 때 자동적으로 미리 생성됨
equals()
를 사용했을 때눈 문자열의 내용을 비교하기 때문에 두 경우 모두 true를 결과로 얻지만, String 인스턴스의 주소를 '=='로 비교했을 때는 리터럴을 사용할 경우 true, String 인스턴스의 경우 false가 나옴
문자열 리터럴
- 자바 소스파일에 포함된 모든 문자열은 컴파일 시에 클래스 파일에 저장됨
- 같은 내용의 문자열 리터럴은 한번만 저장됨
- 이유: 문자열 리터럴도 String 인스턴스이고, 한번 생성하면 내용을 변경할 수 없으므로 하나의 인스턴스를 공유하면 되기 때문
- 클래스 파일에는 소스파일에 포함된 모든 리터럴의 목록이 있음
- 해당 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 이 리터럴의 목록에 있는 리터럴들이 JVM 내에 있는 상수 저장소(constant pool)에 저장됨
빈 문자열(empty string)
- 변수를 선언할 때, 각 타입의 기본값으로 초기화하지만 String은 참조형 타입의 기본값인 null 보다는 빈 문자열로 초기화하는 것이 일반적
문자 인코딩 변환
getBytes(String charsetName)
를 사용하면, 문자열의 문자 인코딩을 다른 인코딩으로 변경할 수 있음byre[] utf8_str = "가".getBytes("UTF-8"); // 문자열을 UTF-8로 변환 String str = new String(utf8{_str, "UTF-8";) // byte 배열을 문자열로 변환
서로 다른 문자 인코딩을 사용하는 컴퓨터 간에 데이터를 주고받을 때는 적절한 문자 인코딩이 필요
기본형 값을 String으로, String 값을 기본형 값으로 변환
- 기본형을 문자열로 변환하는 방법은 숫자에 빈문자열("")을 더해주면 됨.
valueOf()
을 사용하는 방법도 있음 - String을 기본형으로 변환하는 방법은
valueOf()
를 쓰거나parseInt()
을 사용하면 됨
1.3 StringBuffer 클래스와 StringBuilder 클래스
- StringBuffer 클래스는 인스턴스를 생성할 때 지정된 문자열을 변경할 수 있음
- 내부적으로 문자열 편집을 위한 버퍼를 가지고 있고, StringBuffer 인스턴스를 생성할 때 크기를 지정할 수 있음
- StringBuffer 인스턴스가 생성될 때, char형 배열이 생성되며 이 때 생성된 char형 배열을 인스턴스 변수 value가 참조하게 됨
StringBuffer의 생성자
- StringBuffer 클래스의 인스턴스를 생성할 때, 적절한 길이의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집하기 위한 공간으로 사용됨
- 생성자
StringBuffer(int length)
를 사용해서 StringBuffer 인스턴스에 저장될 문자열의 길이를 고려해 여유있는 크기로 지정하는 것이 좋음- 크기를 지정해주지 안흥면 16개의 문자를 저장할 수 있는 크기의 버퍼를 생성
- StringBuffer 인스턴스로 문자열을 다룰 때, 버퍼의 크기가 작업하려는 문자열의 길이보다 작은 경우 내부적으로 버퍼의 크기를 증가시키는 작업이 수행됨
- 배열의 길이는 변경될 수 없으므로 새로운 길이의 배열을 생성한 후에 이전 배열의 값을 복사해야 함
StringBuffer의 변경
- append(): 반환타입이 StringBuffer로 자신의 주소를 반환
StringBuffer의 비교
- StringBuffer 클래스는 equals 메서드를 오버라이딩하지 않아서 StringBuffer 클래스의 equals 메서드를 사용해도 '=='로 비교한 것과 같은 결과를 얻음
- toString()은 오버라이딩되어 있어 StringBuffer 인스턴스에 toString()을 호출하면, 담고있는 문자열을 String으로 반환
- StringBuffer 인스턴스에 담긴 문자열 비교를 위해선 StringBuffer 인스턴스에 toString()을 호출해서 String 인스턴스를 얻은 다음, 여기에 equals 메서드를 사용해서 비교해야 함
StringBuilder란?
- StringBuffer는 멀티쓰레드에 안전(thread safe)하도록 동기화되어 있음
- 동기화는 StringBuffer의 성능을 떨어트림
- 멀티쓰레드로 작성된 프로그램이 아닌 경우, StringBuffer의 동기화는 불필요하게 성능만 떨어트림
- StringBuffer에서 쓰레드의 동기화만 뺀 StringBuilder가 새로 추가됨
- StringBuilder은 StringBuffer와 완전치 똑같은 기능으로 작성되어 있어, 소스코드에서 StringBuffer대신 StringBuilder를 사용하도록 바꾸기만 하면 됨
1.4 Math 클래스
- Math 클래스는 기본적인 수학계산에 유용한 메서드로 구성되어 있음
- Math 클래스의 경우 생성자는 접근 제어자가 private이기 때문에 다른 클래스에서 Math 인스턴스를 생성할 수 없도록 되어 있음
- 이유: 클래스 내에 인스턴스변수가 하나도 없어서 인스턴스를 생성할 필요가 없기 때문
- Math 클래스의 메서드는 모두 static
올림, 버림, 반올림
round()
: 소수 첫째자리에서 반올림해서 정수값(long)을 결과로 돌려줌rint()
: 소수 첫째자리에서 반올림해서 double형으로 결과를 돌려줌- 두 정수의 정가운데 있는 값은 가장 가까운 짝수 정수를 반환
floor()
: 버림 연산을 수행
예외를 발생시키는 메서드
- JDK 1.8부터 메서드 이름에 'Exact'가 포함된 메서드들이 추가됨. 정수형간의 연산에서 발생할 수 있는 오버플로우를 감지하기 위한 것
- 연산자는 단지 결과를 반환할 뿐, 오버플로우의 발생여부에 대해 알려주지 않음. Exact가 붙은 메서드들은 오버플로우가 발생하면 예외를 발생시킴
1.5 래퍼(wrapper) 클래스
사진 출처: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=heartflow89&logNo=220975218499
- 기본형 변수를 객체로 다뤄하는 경우 사용하는 것이 래퍼(wrapper) 클래스
- 8개의 기본형을 대표하는 8개의 래퍼 클래스가 있고, 이 클래스들을 이용해 기본형 값을 객체로 다룰 수 있음
- 래퍼 클래스 생성자는 매개변수로 문자열이나 각 자료형의 값들을 인자로 받음
- 생성자의 매개변수로 문자열을 제공할 때, 각 자료형에 알맞는 문자열을 사용해야 함
- 래퍼 클래스들은 객체생성 시에 생성자의 인자로 주어진 각 자료형에 알맞은 값을 내부적으로 저장하고 있고, 이와 관련된 여러 메서드가 정의되어 있음
- 래퍼 클래스는 equals()가 오버라이딩되어 있어 주소값이 아닌 객체가 가지고 있는 값을 비교
- 래퍼 클래스는 toString()도 오버라이딩되어 있어 객체가 가지고 있는 값을 문자열로 변환하여 반환
Number 클래스
- 추상 클래스로 내부적으로 숫자를 멤버변수로 갖는 래퍼 클래스들의 조상
- BigInteger: long으로도 다룰 수 없는 큰 범위의 정수를 처리하기 위한 것
- BigDecimal: double로도 다룰 수 없는 큰 범위의 부동소수점수를 처리하기 위한 것
문자열을 숫자로 변환하기
타입.parse타입(String s)
형식과타입.valueOf()
메서드를 사용- parse는 반환값이 기본형이고 valueOf()는 반환값이 래퍼 클래스 타입
- 오토박싱(autoboxing) 기능 때문에 반환값이 기본형일 때와 래퍼 클래스일 때의 차이가 없어짐
오토박싱 & 언박싱 (autoboxing & unboxing)
- JDK 1.5 이전에는 기본형과 참조형 간의 연산이 불가능했기 때문에, 래퍼 클래스로 기본형을 객체로 만들어서 연산했어야 함
- 하지만 이제는 기본형과 참조형 간의 덧셈이 가능
- 이유: 컴파일러가 자동으로 변환하는 코드를 넣어주기 때문
- 내부적으로 객체 배열을 가지고 있는 Vector 클래스나 ArrayList 클래스에 기본형 값을 저장해야할 때나 형변환이 필요할 때도 컴파일러가 자동적으로 코드를 추가해 줌
기본형 값을 래퍼 클래스의 객체로 자동변환해주는 것을 '오토박싱(autoboxing)', 반대로 변환하는 것을 '언박싱(unboxing)'이라 함
예시
컴파일 전의 코드
Integer intg = (Integer) i; Object obj = (Object) i; Long lng = 100L;
컴파일 후의 코드
Integer intg = Integer.valueOf(i); Object obj = (Object) Integer.valueOf(i); Long lng = new Long(100L);
2. 유용한 클래스
2.1 java.util.Objects 클래스
- Object 클래스의 보조 클래스로 모든 메서드가 'static'
- 객체의 비교나 null check에 유용
종류 | 설명 |
---|---|
isNull() | 해당 객체가 널인지 확인해서 널이면 true를 반환하고 아니면 false를 반환 |
nonNull() | isNull()과 반대 |
requireNonNull() | 해당 객체가 널이 아니어야 하는 경우에 사용. 객체가 널이면 예외를 발생시킴 |
compare() | 두 비교대상이 같으면 0, 크면 양수, 작으면 음수를 반환 |
equals() | Object 클래스의 equals()와 같은 역할을 하지만 내부적으로 널 검사를 진행하기 때문에 따로 널 검사를 할 필요가 없음 (두 비교대상이 모두 널인 경우 참을 반환) |
deepEquals() | 객체를 재귀적으로 비교하기 때문에 다차원 배열의 비교가 가능 |
toString() | Object 클래스의 toString()과 같은 역할을 하지만 내부적으로 널 검사를 진행하기 때문에 따로 널 검사를 할 필요가 없음 |
hashCode() | 내부적으로 널 검사를 한 후 Object 클래스의 hashCode()를 호출 (널일 경우 0을 반환) |
2.2 java.util.Random 클래스
// 1. Math.random() 사용
double randNum = Math.random();
// 2. Random 클래스 사용
double randNum = new Random().nextDouble();
- Math.random()은 내부적으로 Random 클래스의 인스턴스를 생성해서 사용하는 것이므로 난수를 얻고 싶은 경우 두 방법 중 하나를 선택해서 사용하면 됨
- Math.random()과 Random의 차이점은 종자값(seed)를 설정할 수 있다는 것
- seed를 설정하면 seed가 갖은 Random 인스터스들은 항상 같은 난수를 같은 순서대로 반환
- seed는 난수를 만드는 공식에 사용되는 값으로 같은 종자값을 넣으면 같은 난수를 얻게 됨
2.3 정규식(Regular Expression) - java.util.regex 패키지
- 정규식: 텍스트 데이터 중에서 원하는 조건(패턴, pattern)과 일치하는 문자열을 찾아내기 위해 사용하는 것으로 미리 정의된 기호와 문자를 이용해 작성한 문자열을 의미
- 정규식에 사용되는 기호와 작성방법 참고
- Pattern은 정규식을 정의하는데 사용되고 Matcher는 정규식(패턴)을 데이터와 비교하는 역할을 함
정규식을 정의하고 데이터를 비교하는 과정
- 정규식을 매개변수로 Pattern 클래스의 static 메서드인 Pattern compile(String regex)을 호출하여 Pattern 인스턴스를 얻음
Pattern p = Pattern.compile("c[a-z]*");
- 정규식으로 비교할 대상을 매개변수로 Pattern 클래스의 Matcher matcher(CharSequence input)를 호출해서 Matcher 인스턴스를 얻음
Matcher m = p.matcher(data[i]);
- Matcher 인스턴스에 boolean matches()를 호출해서 정규식에 부합하는지 확인
if(m.matches())
group(int i)
: 정규식의 일부를 괄호로 묶어서 그룹화할 수 있는데 그룹화된 부분은 하나의 단위로 묶이게 되어 한 번 또는 그 이사의 반복을 의미하는 '+'나 '*'가 뒤에 오면 그룹화된 부분이 적용 대상이 됨. 그룹화된 부분은group(int i)
를 이용해서 나눌 수 있음find()
: 주어진 소스 내에서 패턴과 일치하는 부분을 찾아내면 true를 반환하고 찾지 못하면 false를 반환- find()를 호출해서 패턴과 일치하는 부분을 찾아낸 다음, 다시 find()를 호출하면 이전에 발견한 패턴과 일치하는 부분의 다음부터 다시 패턴매칭을 시작
- find()로 정규식과 일치하는 부분을 찾으면, 그 위치를 start()와 end()로 알아낼 수 있고
appendReplacement(StringBuffer sb, String replacement)
를 이용해서 원하는 문자열로 치환할 수 있음
2.4 java.util.Scanner 클래스
- Scanner는 화면, 파일, 문자열과 같은 입력소스로부터 문자데이터를 읽어오는데 도움을 줌
- Scanner는 정규식 표현을 이용한 라인단위의 검색을 지원하며 구분자(delimiter)에도 정규시 표현을 사용할 수 있어 복잡한 형태의 구분자도 처리가 가능
- 입력받을 값이 숫자라면 nextLine() 대신 nextInt() 또는 nextLong()과 같은 메서드 사용 가능
2.5 java.uril.StringTokenizer 클래스
- StringTokenizer는 긴 문자열을 지정된 구분자(delimiter)를 기준으로 토큰(token)이라는 여러 개의 문자열로 잘라내는데 사용됨
- String의 split(String regex) 혹은 Scanner의 useDelimiter(String pattern)를 사용할 수도 있지만, 이 두가지 방법은 정규식을 사용해야하므로 정규식에 익숙하지 않은 경우 StringTokenizer를 사용하는 것이 간단
- StringTokenizer는 구분자로 단 하나의 문자 밖에 사용하지 못하기 때문에 복잡한 형태의 구분자로 문자열을 나누어야 할 때는 정규식을 사용해야 함
StringTokenizer의 생성자와 메서드
생성자/메서드 | 설명 |
---|---|
StringTokenizer(String str, String delim) | 문자열(str)을 지정된 구분자(delim)로 나누는 StringTokenizer를 생성 (구분자는 토큰으로 구분되지 않음) |
StringTokenizer(String str, String delim, boolean returnDelims) | 문자열(str)을 지정된 구분자(delim)으로 나누는 StringTokenizer를 생성함. returnDelims의 값을 true로 하면 구분자도 토큰으로 간주됨 |
int countTokens() | 전체 토큰 수를 반환 |
boolean hasMoreTokens() | 토큰이 남아있는지 알려줌 |
String nextToken() | 다음 토큰을 반환 |
2.6 java.math.BigInteger 클래스
final int signum; // 부호. 1(양수), 0, -1(음수) 셋 중 하나
final int[] mag; // 값
- BigInteger는 내부적으로 int 배열을 사용해서 값을 다룸
- BigInteger는 String처럼 불변이고 2의 보수 형태로 표현
- 부호를 따로 저장하고 배열에는 값 자체만 저장
- 부호만 다른 두 값의 mag는 같고 signum은 다름
2.7 java.math.BigDecimal 클래스
private final BigInteger intVal; // 정수(unscaled value)
private final int scale; // 지수(scale)
private transient int precision; // 정밀도(precision) - 정수의 자릿수
- BigDecimal은 실수형과 달리 정수를 이용해서 실수를 표현
- 실수를 정수와 10의 제곱의 곱으로 표현 (정수 X 10^(-scale))
- scale: 0부터 Integer.MAX_VALUE 사이의 범위에 있는 값
- BigDecimal은 정수를 저장하는데 BigInteger를 사용
- scale은 소수점 이하의 자리수를 의미하고, precision은 정수의 전체 자리수를 의미
'Book > Java의 정석' 카테고리의 다른 글
[Chapter 8] 예외처리(exception handling) (0) | 2022.04.01 |
---|---|
[Chapter 7] 객체지향 프로그래밍 2 (0) | 2022.04.01 |
[Chapter 6] 객체지향 프로그래밍 1_2 (0) | 2022.04.01 |
[Chapter 6] 객체지향 프로그래밍 1_1 (0) | 2022.04.01 |
[Chapter 5] 배열 (Array) (0) | 2022.04.01 |
댓글