포스트

(Day13) - 클래스, 메서드, 컴파일러, Call By Value & Reference

이 글은 제가 교육을 수강하며 기록하고 추가한 내용입니다. 강사님과 무관하게 잘못된 내용이 있을 수 있습니다.


클라우드 기반 웹 데브옵스 프로젝트 개발자 교육 과정 (5기)

  • 비트캠프 엄진영 강사님 (https://github.com/eomjinyoung/)
  • 훈련기관 : 네이버클라우드주식회사
  • 기간: 2023-11-14 ~ 2024-5-22
  • 남은 일자 : 116 일 ( 13/129 )

13일차(2023-11-30, 목)


아침복습

표준 스트림에 대해

입력, 출력, 오류 3가지 스트림이 자바에서 기본으로 있다.

스트림

데이터의 흐름이라는 의미를 강조하기 위해서 붙는 단어이다. 개울이 흐르는 것처럼 데이터가 흐른다고 하여 Steram 이라는 용어가 붙는 것이다.

1
2
3
# wc는 표준 입력 스트림으로 터미널의 키보드 입력을 받고 있다.
$ wc
# wc 명령을 넣으면 키보드 입력이 종료되기까지 blocking 된다.

wc(word count) 명령어를 입력하면 무슨 일이 일어날까? Shell 이 wc 명령어를 받고, OS에 요청하고, OS가 하드웨어 자원을 사용하여 명령을 처리한다.

1
$ cat test.txt | wc

위와 같은 명령어는 무엇인가? 파이프라인을 이용하여 cat의 출력을 wc로 이어준 것이다. 흐름, 스트림을 이어준 것이다. 위 명령어는 test.txt 를 cat으로 concatenate 한 출력을 파이프라인을 통해 wc 명령어로 전달한다. wc 는 라인, 토큰, 글자의 수를 반환한다.

Scanner 클래스를 써야 하는 이유는?

System.in 이라는 클래스를 사용해도 표준 입력 스트림으로 키보드 입력을 받을 수 있다. 그런데 System.in 에는 .nextLine() 혹은 .nextInt() 와 같이 입력된 데이터를 다양한 형식으로 가공해주거나 한 토큰, 한 라인을 통째로 받는 도구가 없다. Scanner 클래스가 없으면 UTF-16 BE (유니코드) 한 글자씩 받고 데이터타입을 변환하는 과정을 프로그래머가 직접 해야 하기에 Scanner 클래스를 쓴다. 참고로 Scanner은 java.util 패키지에 들어가있다.

스캐너 클래스를 사용하여 키보드 입력을 처리할 수 있는가?

Scanner keyIn = new .nextLine() .nextInt() …

for 반복문으로 배열의 값을 다룰 수 있는가?

기능 단위로 명령문을 묶어 사용할 수 있는가?

메서드를 다룰 수 있는가?


학습내용

메서드를 왜 쓰나

메서드를 안 쓰면 main() 에 모든 내용이 다 있어야 한다.

  • 이해하기가 어렵다.
  • 각 코드가 무엇을 하는지 알기 어렵다.

그래서 주석을 많이 적어야 한다. 그런데 메서드로 뽑아두면 (추출하면)

  • main() 의 가독성이 좋아진다. 라인 수 자체가 줄어든다. 프로그램을 이해하기 쉬워진다.
  • 메서드 이름 자체가 의미를 갖게 된다. 그래서 주석을 덜 적어도 직관적으로 이해가 된다.

유지보수란?

유지보수는 프로그램의 성능을 높이는 것만을 이야기하는게 아니다. 비용은 줄이고 효용(가치)는 높이는게 유지보수다. 대부분의 경우 관리 비용을 줄이는 게 가장 중요하다 관리 비용은 대부분 인건비다. 그래서 소스 코드를 읽기 좋게 만드는 게 엄청나게 중요하다. Refactoring 이라는 책이 바이블이 된 이유가 있다.

메서드 이름짓기

동사구로 짓는 것이 기본이며, 명사구나 전치사구로 적는 경우도 있다.

메서드의 일반화

추출만큼 중요한 게 일반화 함수를 잘 쪼갠 다음에, 일반화까지 잘 시키면 다른 프로젝트에서도 재사용할 수 있게 할 수 있다. 그것이 기술력이다.

메서드의 호출이란

메서드명() 은 그 메서드의 위치로 가라는 뜻이다. 호출한다는 게 별 게 아니다. 프로그램의 실행 순서의 이동이다.

메서드의 구조

1
2
3
4
int plus(int a, int b) {
  int r = a + b;
  return r;
}

여기서 Method body,

함수가 클래스 블럭 안에 있냐, 밖에 있냐에 따라 안에 있으면 메서드, 밖에 있으면 함수라고 하는데 자바는 애초에 클래스 블럭 밖에 함수를 못 만드므로 메서드밖에 없는데, 그냥 함수라고도 부른다. 엄격하게 구분하면 메서드 뿐이나, 같이 쓴다.

메서드는 무엇인가?

메서드는 문법이다. 코드를 관리하기 쉽게 하고 재사용하기 쉽게 하기 위해서 기능 단위로 묶어 놓는 문법이 메서드이다.

메서드의 종류

static이 붙은 클래스 메서드와 static이 붙지 않은 인스턴스 메서드 딱 두 가지로 나뉜다.

메서드를 잘못 사용한 예

컴파일 오류: 파라미터 변수가 없기 때문에 호출할 때 값을 넣는 경우 컴파일 오류: 변수에 값을 넣기 위해 메서드를 호출한 경우, 값을 리턴하지 않으면 오류

Argument VS Parameter

Argument
메서드를 호출할 때 넘겨주는 값을 Argument라고 한다.
Parameter
Argument를 받는 로컬 변수를 Parameter라고 한다.
단어번역의미
Parameter매개변수함수와 메서드 정의 시 입력 변수(Variable) 명
Argument전달인자함수와 메서드 호출 시 입력 값(Value)

참고로 이 두 단어는 자주 혼용되므로 잘못 사용하는 경우를 보더라도 태클 걸지 말자.

파라미터의 타입, 개수, 순서

java에서는 엄격하게 지켜야 한다.

파라미터에 대한 오해

파라미터는 변수 자체를 넘기는 게 아니다. 변수에 저장된 값 만을 넘긴다. 레퍼런스 변수를 넘기면? 레퍼런스 변수가 가진 주소의 메모리에 있는 값을 넘긴다.

문자열 String class

String.format()

printf() 와 같이 동적인 문자열을 출력하고 싶을 때 바로 String으로 넣지 않고 String 변수에서 만든 다음 반환하는 경우 String.format() 메서드를 사용하여 동적으로 문자열을 만드는 것이 좋다.

메서드에 관하여

임시변수는 쿼리 메서드로 치환하자

리턴 값을 변수로 받지 않아도 된다.

그러면 리턴한 값은 버려진다.

Parameter

가변 Parameter

method1(String... names) {...} 이런 코드를 보면 배열로 된 argument를 줄 수 있다. 여러 개의 argument를 ,로 구분하여 줘도 된다. 그러면 argument로 준 배열의 길이나 argument의 개수만큼 parameter가 그 값들을 배열로 받는다. String[] String… 무슨 차이가 있나? 차이가 있으니까 이런 문법이 있지 않겠나? String[] 은 배열을 가리키는 주소를 받아야 하는데 String…은 배열을 그대로 받을 수도 있다. String[] 은 배열의 주소만 받을 수 있다.

String… 이 배열 주소를 넘기면 새로 배열을 만들지 않고, 기존 배열을 그대로 쓴다. 그래서 이 메서드 내에서 문자열 배열 내에 수정을 가하면 그 변경 내용이 그대로 남는다. 배열로 된 값을 넘기면 새로운 배열을 만든다. 이 경우는 메서드의 종료시 해당 배열이 삭제된다.

가변 파라미터가 무조건 더 좋은 방식인가?

그런 건 아니다. 가변 파라미터는 메서드의 파라미터로 단 하나만 사용할 수 있다. [배열의 주소를 받는 레퍼런스] 파라미터는 여러 개를 쓸 수 있다. 메서드 정의 시 가변 파라미터는 파라미터들의 순서 중 맨 끝에만 올 수 있다.

왜 그런가?
복잡한 사용을 막기 위해서, 컴파일러의 성능을 위해서 그렇다고 한다.

가변 파라미터는 편리한 만큼 제한점이 더 많다. 상황에 맞게 사용하면 된다.

Call by value

스택(Stack) 영역

메서드의 로컬 변수를 만드는 메모리 영역을 스택 영역이라고 한다. main() 메서드를 포함한 메서드들이 사용할 로컬변수를 생성하고, 다른 메서드가 호출되었을 때 해당 메서드가 사용하는 로컬변수가 메모리에 생성되면 그 또한 스택 영역에 새롭게 생성된다.

call by value 란 무엇인가? 메서드를 호출할 때 값을 넘기는 것을 말한다. 주소를 넘기는 것이 아니다!!

이 때 예제로 자주 사용되는 메서드가 swap() 메서드를 구현하는 것이다. main() 메서드의 로컬 변수와 swap() 메서드의 로컬 변수는 별개다.

C에서는 함수를 호출할 때 argument로 메모리 주소를 넘길 수 있다. java에서는 이렇게 메모리의 주소를 직접 넘기는 방법이 없다. ONLY call by value 만 지원한다는 것이다. 그러나 걱정할 것 없다. Call by reference 가 가능하기 때문이다.

Call by reference

메서드 호출 시 레퍼런스(힙 영역에 있는 데이터의 주소)를 넘기면서 호출하는 것

힙(Heap) 영역

1
int[] arr = new int[] { 100, 200 };

위와 같은 문장에서 new 라는 특별한 키워드가 있다. 이 키워드를 사용하면 힙 영역에 메모리를 할당한다. new 키워드는 int[] { 100, 200 }으로 정의된 배열을 인스턴스화한다. 이 인스턴스는 메모리에서 힙 영역에 생성된다. 인스턴스의 메모리 주소는 arr이라는 레퍼런스에 저장된다.

클래스(Class)

클래스는 나중에 더 설명하겠지만…

  1. 클래스는 메모리의 구조를 설계하는 문법이다.
  2. 클래스 또한 new 를 통해 힙 영역의 메모리에 구현된다.
  3. 클래스가 메모리에 만들어질 때 마치 배열처럼 연속해서 만들어진다.
  4. 클래스명 레퍼런스이름 = new 클래스명();
    • 레퍼런스에는 힙 영역에서 생성된 메모리의 시작 주소가 저장된다.
    • 생성된 메모리는 메서드와 마찬가지로 인스턴스(Instance)라는 용어로 쓰인다.

컴파일러가 해 주는 것

스택 영역에 로컬 변수가 먼저 생성된다. 자바에서는 쓰레드에서 메서드가 호출될 때 프레임단위로 스택 영역이 생긴다. 근데 자바 소스를 작성할 때는 맨 처음에 로컬 변수를 선언하는 게 아니고 중간중간에 만들면서 바로 쿼리 메서드를 통해서 값을 넣고 그러는데 이건 어떻게 된 걸까?

원래는 모든 로컬 변수가 맨 처음 선언이 되어야 스택영역에 변수들을 생성하고 그로써 관리를 하는 것이다. 그래서 초기 고급언어는 로컬변수를 가장 먼저 선언해야만 했었다. (C) 근데 그렇게 하면 안 쓰는 변수들이 먼저 선언되기에 가독성이 떨어지고 불편했다. 그래서 컴파일러야 너가 알아서 내가 쓴 로컬 변수들 소스코드에서 알아서 찾아서 메서드 호출될 때 처음에 로컬변수로 가져오도록 컴파일할 때 알아서 해 이렇게 한거다. 나중에.

받아들여라

이상하다 비효율적이다? 그럼 진작에 바뀌었을 것 일단 받아들여라. 안그러면 학습 효율이 떨어진다. 믿고 받아들여라.

강의 자료에 대한 경험

예전에는 PPT나 PDF 같은 강의자료들을 썼었다. 그러니까 강의는 집중 안하고 자료만 보고 넘기고 그러더라. 전달력도 떨어지고, 강의 자체도 자료 범위의 밖을 벗아나지 못하더라. 그래서 말하고 백지에 쓰면서 강의를 한다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.