자바 공부 기록 2회독(1) - 2024.1.16

동준·2024년 1월 16일
0

개인공부(자바)

목록 보기
3/16
post-thumbnail

1. 기초 개념

1) String과 char 구분하기

사실 웬만하면 String을 주로 쓸 것이고, 굳이 char에 대해 깊게 생각해야 되나 싶었는데 하필 2회독 시작 전에 푼 알고리즘 트레이닝 문제에서 문자열을 문자로 분해해서 유니코드로 치환하는 문제가 있어서... 겸사겸사 String에 대해서 생각할 겸...

(1) String은 참조 타입

데이터 타입은 기본 타입과 참조 타입으로 분류됨.

  • 기본 타입 : int, long, double, boolean, char...
  • 참조 타입 : 배열, 열거형, 클래스, 인터페이스

String이 참조 타입이라는 점에서, 같은 문자열 내용인지 비교하려면, 중요해서 다시 말하자면 같은 인지를 비교하려면 동등성의 개념으로 들어간다.

모든 클래스의 조상님은 Object 클래스다.
그리고 Object 클래스는 toString(), hashCode(), equals(Object object) 메소드를 지니고 있다.

두 개의 참조를 비교할 때, 두 참조가 동일한 객체를 가리키고 있는지, 즉 물리적으로 완벽히 동일한 객체인지를 확인하려면 eqauls(Object 매개변수 참조)를 사용해서 두 문자열 클래스가 같은 클래스인지를 판별한다.

여담으로, ==, !=는 변수의 값(즉, 객체의 번지)를 비교하는 셈이 된다.

2) 자바의 메모리 사용 영역

프로그램을 구동하려면 구동하기 위한 실행 환경이 갖춰져야 한다.
예를 들면 JavaScript는 웹 브라우저 상에서 돌아가기 위해서 구글이 개발한 V8 엔진이 필요하다 (혹은 Node JS 처럼 아예 엔진을 뜯어(...) 와서 돌린다던가...)

Java(이하 자바)는 특정 운영 체제에 종속되지 않는다는 특징이 있다.
이게 가능한 이유는, 자바를 실행시키기 위한 환경을 가상 머신을 통해서 구현하기 때문이다. 이를 JVM(Java Virtual Machine)이라고 한다.

자바 명령어로 JVM이 구동되면, JVM이 본래 운영체제에서 할당받은 메모리 영역을 메소드 영역, 힙 영역, 스레드 등으로 분배해서 사용한다.

  • 메소드 영역 : 바이트코드 파일(.class 파일)의 내용이 저장되는 영역(사실 클래스별 상수, 정적 필드, 메소드 코드, 생성자 코드 등이 저장되지만, 뭉뚱그려서 바이트코드 파일 내용이 저장된다고 알고 있자)
  • 힙 영역 : 객체 생성 영역. 객체 번지는 메소드 영역과 스택 영역의 상수와 변수에서 참조 가능.
  • 스택 영역 : 메소드 호출 때마다 생성되는 프레임이 저장되며, 메소드 호출이 끝나면 프레임이 자동 제거.

여담으로, 자바의 컴파일러가 .java 파일을 .class로 변환하는 역할을 맡고 있다.

3) 메인 메소드

클래스 등에서 필드로 둔 변수를 등록만 하고 할당은 하지 않으면, 기본값으로 초기화된다.

  • 정수 타입 : 0
  • 실수 타입 : 0.0
  • 논리 타입 : false
  • 문자 타입 : '\u0000' (null character)
  • 참조 타입 : null

배열의 타입을 지정할 때, 내부 내용은 별도로 지정하지 않으면 기본값으로 배열 요소가 할당된다.

여기서 고려될 부분은 내부 요소가 기본 타입이냐, 참조 타입이냐에 따라 생각을 좀 달리해야 한다. 왜냐하면 참조 타입의 변수는 번지를 가리키는 것은 똑같을 것이므로

String[] strings = new String[3] // 요소 3개 전부 null로 초기화

strings[0] = "A";
strings[1] = "A";
strings[2] = new String("A");

strings[0] == strings[1] // true
strings[0] == strings[2] // false(다른 객체를 참조하므로)
strings[0].equals(strings[2]) // true(같은 내부 내용이므로)

자바스크립트의 배열과는 다르게 자바의 배열은 상당히 정적이며, 길이 변경도 안 된다. 그렇기 떄문에 자바에서의 배열 복사 역시 꽤 복잡하게(?) 이뤄지는 편이다. 방법은 두 가지가 있지만... 아마 코드를 짤 때 컬렉션을 주로 활용하게 될 터라서 개념만 대략적으로 알고만 있자.

  • for문 사용
  • System 정적 클래스의 arraycopy(원본 배열, 원본 배열 복사 시작 인덱스, 새 배열, 새 배열 붙여넣기 시작 인덱스, 복사 항목 수) 메소드를 사용

사실 위의 내용들 때문에 배열 내용을 정리한 건 아니고... 모든 자바 프로그램의 실행은 메인 메소드에서 이뤄진다. 근데 잘 보면 정적 메소드의 매개변수로 String[] args가 들어있다. 근데 args는 메인 메소드 내부에서 쓰인 걸 본 적이 없다. 얘 때문에 정리를 하려고 한다.

public class Main {
	public static void main(String[] args) {
    	// 프로그램 실행...
    }
}

하나하나 뜯어보자.

  • public : main 메소드 실행 시점에서 JVM이 클래스를 로드하기 위해서는 당연히 접근에 있어 제약이 있으면 안 된다.
  • static : 인스턴스 생성이 필요하지 않다. static으로 선언한 속성과 메서드는 어차피 인스턴스와 상관없이 항상 일정한 값을 가지니까, 굳이 인스턴스를 생성하지 않아도 사용할 수 있다.
  • void : 메인 메소드의 모든 마지막 과정은 프로그램 종료가 된다. 즉, 리턴값이 존재할 이유가 없다.
  • String[] args : 얘가 문젠데...

String[] args 매개변수는 일단 메인 메소드의 매개변수 역할을 잘 수행한다. 간단하게 터미널(나는 iterm을 사용한다)을 사용해서 간단하게 작성한 메인 메소드를 실행시켜보자.

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        System.out.println("총 " + args.length + "개의 값을 입력받았습니다.");
        System.out.println("입력 내용 : " + Arrays.toString(args));
    }
}

에러는 무시하기
바이트코드 파일을 컴파일러로 변환시켜서 JVM이 읽을 수 있도록 한 다음, iterm에서 해당 메인 메소드에 인자의 값을 넘겨줌으로써 실행 결과를 확인한다.

main 메서드가 전달받는 String 배열은, 사용자가 커맨드라인에 입력하는 문자열들을 가리키는 것이다. 입력받은 데이터를 배열로 만들어 힙 영역에 저장하고 배열의 주소를 String[] args에 저장한다. 그리고 그 주소를 main 메소드가 파라미터를 통해 넘겨받는 것이다.

즉, main 메소드가 첫 실행 스레드라면 사용자가 main 메소드에게 데이터를 넘겨주고 싶은 경우도 있을 것이다. main()에게 데이터를 넘겨주려면 main()가 시작되기 전에 데이터를 입력받는 스레드가 필요하다.

하지만 스택 영역에 처음으로 쌓이는 스레드는 main()이다. 이를 위해 자바는 JVM이 클래스 파일을 처리하기전 커맨드 라인을 통해 데이터를 사용자로부터 입력받을 수 있도록 인자를 부여한 것이다.

참조 링크
https://lordofkangs.tistory.com/12
https://atomicliquors.tistory.com/7

4) 형변환(캐스팅)

사실 형변환에 대한 고찰은 제네릭을 공부할 때 주로 이뤄졌다. 그떄는 개념 체킹의 수준으로 익혔는데, 지금 나온 김에 정리해야겠다.

앞서 변수라는 것은 번지를 가리키는 것이라고 했다. 나는 이것을 내용물을 담는 그릇으로 생각했다. 즉,

Object 변수(그릇) = new Object(내용);

이런 식이 아닐까...

각설하고, 그릇에 내용물을 담는 데에 있어 내용물의 양이 그릇의 범위를 초과하면 안 된다. 이걸 자바스럽게(?) 표현하면

내용의 범위가 변수의 범위를 초과하면 안 된다.

수학적으로 표현하자면 우변의 범위가 좌변의 범위를 넘어서면 안 된다(?)
왜 이렇게 공부하고자 하냐면...

Box<T> 클래스는 제네릭으로 선언된 클래스이며, 접근 제약이 없는 필드와 getter를 가지고 있다.

  • 1번 케이스
public static void main(String[] args) {
	Box intBox = boxing(100);
    int intBoxValue = (int) intBox.getT(); 
	// 타입 추론 없이 Object 타입으로 지정돼서 강제 타입 캐스팅 필요

1번 케이스의 마지막 라인을 보면, 좌변은 int 기본 타입이고, 우변은 강제 캐스팅을 뺸다면 제네릭 타입이기 때문에 모든 타입이 될 수 있어서 우변 범위가 좌변 범위를 초과한다. 그래서 강제 캐스팅으로 억지로 내용물의 범위를 그릇의 범위로 한정시키는 것이다.

  • 2번 케이스
public static void main(String[] args) {
    Box box1 = new Box();
    box1.content = "100";
	// ...

2번 케이스의 마지막 라인을 보면, 좌변은 구체적인 타입이 정해지지 않음으로써 원시 타입이고, 우변은 String 타입으로 우변 범위가 좌변 범위를 초과해서는 안 된다는 공식(?)을 지키고 있어서 문제가 없다. 문제가 없을 수 있는 이유는 바로 묵시적 형변환 때문이다.

profile
scientia est potentia / 벨로그 이사 예정...

0개의 댓글