CS 면접대비 - 프로그래밍 언어(자바)
❓Call by Value 와 Call by reference의 차이가 무엇인가요?
참고 : 현대의 CS개론에서 call by value 와 call by reference 에 대한 구분은
더이상 활발하게 이루어지지 않는다.
Pass by reference로 정의되던 방식이 사용되는 경우가 드물기 때문.
Value 와 Variable의 차이는 무엇인가?
이 질문에 정확히 대답을 할 수 있어야 한다.
- Value(값)란, 표현식의 결과값을 의미한다
- Variable(변수)이란, value를 담는 그릇이다
- 즉 int arg = 1; 라는 표현식에서 value는 1, variable은 arg라고 볼 수 있다.
Parameter 와 Argument의 차이는 무엇인가?
- Parameter란 함수 호출을 위해 caller가 공급하는 variable
- Argument란 함수 호출에 사용되는 parameter에 제공되는 value
Call by Value
Call by Value에서, 함수의 parameter들은 함수의 호출을 위해 함수의 argument로 초기화되어 새롭게 생성된 variable들이다.
“값”으로 초기화된 “변수” 들의 관계와 동일하다.
int arg = 1;
int another_variable = arg;
//another_variable = 2; 가 arg에 영향을 주는가? 그렇지 않다.
void foo(int param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
// 해당 함수의 호출 결과는 일반적인 변수들의 관계와 동일하다
// "새롭게 생성된 변수" 라는걸 기억하자 !
int arg = 1;
int param = arg;
param += 1;
// 이때 arg 와 param은 서로 다르다!
Call by Reference
Call by reference에서는 함수의 parameter들은 단순히 “alias”로 취급된다.
int arg = 1;
// foo 함수 진입
// param 은 arg 랑 같네?
arg /* param */ += 1;
여기까지 이해했다면 자바로 다시 돌아가보자
public void foo(Dog d) {
d = new Dog("Fifi");
}
Dog aDog = new Dog("Max");
foo(aDog);
// 함수 호출 이후 aDog가 가리키는 값은 여전히 "Max"이다
// d 가 aDog의 alias로 작동하는가? 그렇지 않다.
혹자는 이렇게 반론할지도 모른다.
public void foo(Dog d) {
d.setName("Fifi");
}
// 개의 이름이 변경되었다.
하지만 이것 또한 call by reference를 의미하는것은 아니다.
어디까지나 Dog 객체에 대한 “포인터”를 전달한것일 뿐, 실제 Dog 객체를 전달한 것이 아니기 때문이다.
즉 “포인터” 와 “reference”는 서로 다른 개념이다.
public void foo(Dog someDog) { // AAA
someDog.setName("Max"); // BBB
someDog = new Dog("Fifi"); // CCC
someDog.setName("Rowlf"); // DDD
}
Dog myDog = new Dog("Rover");
foo(myDog);
Dog객체가 메모리 주소 0x42에 있다고 가정해보자.
- AAA
- someDog라는 인자값이 0x42로 설정 (새로운 변수!)
- BBB
- someDog는 포인터를 따라가면 Dog객체가 있는 0x42에 도달하고, 이름 변경을 요청
- CCC
- someDog의 값은 0x74로 변경
- DDD
- 0x74에 있는 Dog객체의 이름 변경을 요청
함수 호출이 된 이후의 결과를 생각해보자 myDog라는 변수가 변경되었는가? 그렇지 않다. 42를 유지하고 있다.
Thinking Point
Java로 전형적인 swap함수를 작성할 수 있는가?
설명이 많이 복잡했는데, “자바는 primitive의 경우 call by value, 이외는 call by reference 로 동작합니다”
라는 답변이 왜 잘못되었는 지 알 수 있길 바란다.
출처
- https://stackoverflow.com/a/34971934
- https://www.javadude.com/articles/passbyvalue.htm
</div></details>
String, StringBuilder, StringBuffer
String
String s = new String("Hello");
안좋은 선언이라고 알려져 있는데, 왜 쓰지 말라는 걸까?
두 객체의 차이는 뭘까?
String s1 = "Hello";
String s2 = new String("Hello");
펼치기
이유는 String의 특성에 있다.
자바의 String 객체는 불변객체이다.
덕분에 JVM은 굳이 동일한 내용을 가진 문자열들을 따로 힙에 저장하지 않고 String Pool이라는 곳에 하나만 저장하고 해당 문자열의 주소값만 전달하는 방식으로 메모리를 최적화할 수 있다.
이 과정을 “Interning” 이라고 한다.
반면 new String 의 문자열 생성자를 사용한 경우는 힙에 해당 문자열을 생성하게 되고, 서로 다른 주소값을 보유하게 된다. 이로 인해 메모리 낭비가 이루어지기 때문에 피하는 것이 좋다.
//자바의 String은 intern() 메소드를 제공한다
String s1 = "Hello";
String s2 = new String("Hello").intern();
// 하지만 두 표현식이 동일하지는 않다.
// intern() 메소드를 사용하더라도 힙에 올라간 메모리는 GC가 회수할때까지 남아있게 된다.
String Pool 의 위치
Java7 이전까지는 String Pool이 고정된 크기를 가진 PermGen 영역에 존재하였다.
지나치게 많은 문자열들이 intern 될 경우, JVM이 OutOfMemoryError를 만들어내는 경우가 있어,
Java7 이후부터는 Heap 영역에 저장되도록 변경되었다.
https://muratakkan.medium.com/understanding-and-using-the-java-string-pool-in-java-d60d3176716
StringBuffer & StringBuffer
String의 불변성은 앞서 말한 바와 같이 장점이 존재하지만, 단점 또한 존재한다.
바로 문자열 연산에 약하다는것인데, 문자열 두개를 더하는 연산같은 경우 불필요하게 두개의 문자열이 모두 Heap에 올라가야하는 비효율이 존재한다.
Java는 이 문제를 StringBuilder 와 StringBuffer라는 클래스를 두어 해결한다.
가변성을 보유한 StringBuilder 클래스는 append, reverse 등 다양한 mutate method를 제공한다.
또한, String 간의 연산 또한 연산자 오버로딩을 통해서 내부적으로는 StringBuilder를 사용한다.
StringBuffer와 StringBuilder의 차이는 무엇일까?
StringBuffer의 경우, 동기화를 지원하며 이에 대한 tradeoff로 연산속도가 느리다. StringBuilder의 경우, 동기화를 지원하지 않지만, 연산속도가 빠르다.
왜 이런 결과가 나올까?
String str1="str";
String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string"); //false
vs
final String str1="str";
final String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string"); //true
펼치기
최적화 할 수 있을까?
String s = "";
for (int i = 0; i < 100; i++) {
s += ", " + i;
}
펼치기
❓접근제어자
- public : 어디서든 접근 가능
- protected : 동일 패키지 + 상속받은 클래스 내부
- default : 동일 패키지 내부
- private : 동일 클래스 내부
package tut02;
public class CA {
public int ia;
protected int ib;
int ic;
private int id;
}
//tut2 패키지 내의 클래스 CA 외부에서 접근 가능한 변수들은 ?
//tut2 패키지 내의 클래스 CA 내부에서 접근 가능한 변수들은 ?
//tut2 패키지 내의 클래스 CA를 상속받은 CAA 내부에서 접근 가능한 변수들은 ?
package tut03;
import tut02.CA;
public class Tut03 {
public static void main(String[] args) {
CA a = new CA(); -> 여기서 접근 가능한 변수들은?
CC c = new CC(); -> 여기서 접근 가능한 변수들은?
}
}
해답
- ia, ib, ic
- ia, ib, ic, id
- ia, ib, ic
같은 패키지 내에서는 public protected default가 같은 효과를 갖는다!
❓Lambda & Stream
자바스크립트를 접해봤다면 함수형 패러다임을 구현하는데 유용한 map, reduce, filter등의 유용한 메소드들에 익숙할 것이다.
자바도 버전 8부터 Lambda 익명함수들을 지원하기 시작했다.
Priority Queue의 구현이나 Custom sort 등에서 이미 많이 사용하는걸 보았을 것이다
//Instead of this
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getAge() - o2.getAge();
}
});
//We can do this
listDevs.sort((o1, o2)->o1.getAge()-o2.getAge());
자바스크립트에서 이런 코드를 본적이 있을것이다.
function calculate(x, y, operatorFunction) {
return operatorFunction(x, y);
}
function add(a, b) {
return a + b;
}
calculate(1, 2, add);
자바스크립트는 함수가 일급객체이기때문에 아무 조작 없이 해당 코드가 실행 가능하다.
하지만 엄격한 자바의 세계에서는 그렇지 않다.
람다식을 사용하기 위해서는 어떤게 필요할까?
함수형 인터페이스
단 하나의 추상 메소드만 정의되어있는 인터페이스를 함수형 인터페이스라고 부른다
람다식으로 사용하기 위해서는 대상이 함수형 인터페이스여야만 한다
이유는 당연하게도(?) 메소드가 하나만 있어야 람다식이 수행하는 대상을 알 수 있기 때문이다.
@FunctionalInterface
interface OperatorFunction {
int operate(int x, int y);
}
public static int calculate(int x, int y, OperatorFunction operatorFunction) {
return operatorFunction.operate(x, y);
}
int result1 = calculate(1, 2, (a, b) -> a + b);
매번 유사한 함수형 인터페이스를 선언하는건 번거롭기때문에 자바에서는 util 패키지에서 사전에 정의된 인터페이스들을 제공한다.
//example : IntBinaryOperator를 사용하면 굳이 정의안해도 됨
//package java.util.function
@FunctionalInterface
public interface IntBinaryOperator {
/**
* Applies this operator to the given operands.
*
* @param left the first operand
* @param right the second operand
* @return the operator result
*/
int applyAsInt(int left, int right);
}
public static int calculate(int x, int y, IntBinaryOperator operator) {
return operator.applyAsInt(x, y);
}
돌이켜보면 Sort에서 람다를 사용할 때는 별도의 인터페이스를 선언하지 않았다. 왜일까?
이유는 Sort 메소드의 두번째 인자인 Comparable
❓Wrappper Class
자바에서는 일반적으로 알려진 Primitive Type들 (int, boolean, char … ) 에 대해서 Wrapper 클래스들을 제공한다.
똑같은 자료형인거같은데, 왜 필요할까?
- ArrayList 등의 Collection 프레임워크들은 객체들의 메모리 위치를 저장하기 때문이다
- 실제로 ArrayList 구현의 생성자를 보면 다음 코드가 들어있다
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = (E[])new Object[initialCapacity]; }
- 실제로 ArrayList 구현의 생성자를 보면 다음 코드가 들어있다
- 멀티쓰레딩의 동기화 지원
- null 값의 존재
- 값 컨텍스트가 초래하는 영향
- 한번 값을 사용하면 어딘가에서는 계속 필요
❓Overriding & Overloading
-
superclass 에 이미 존재하는 메소드를 subclass에서 동일한 이름으로 사용하고 싶지만, 구현체는 바꾸고 싶을 경우 Overriding을 사용
-
Overloading은 superclass에 존재하지 않는 새로운 메서드를 subclass에서 선언하는것을 의미
생각해볼거리
- 왜 자바는 static method에 대한 오버라이딩을 허용하지 않을까?
- 왜 자바는 operator overloading이 없을까?
댓글남기기