https://bird-liver-ecc.notion.site/BufferedReader-BufferedWriter-StringBuilder-0ef39a74daab465299f2c368513da602?pvs=4

 

BufferedReader, BufferedWriter, StringBuilder 의 사용법과 속도, 주의사항

BufferedReader

bird-liver-ecc.notion.site

BufferedReader

  • 버퍼를 사용하여 입력을 받는다.
  • Scanner는 띄어쓰기, 개행문자를 기준으로 값을 받는다.

장점

  1. Buffer를 사용해 한번에 묶어서 처리하므로 Scanner에 비해 속도가 빠름
  2. BufferedReader 버퍼크기 : 8192 char / Scanner 버퍼크기 : 1024 char
  3. 따라서 입력이 많은 경우 속도면에서 유리
  4. Scanner는 동기화 미지원, BufferedReader는 동기화 지원 → 멀티스레드 환경에서 안전하다.

단점

  1. BufferedReader는 문자열만 입력받기 때문에 이후 Integer.parseInt() 같은 후처리가 필요함. Scanner는 nextInt() 와 같이 원하는 자료형으로 쉽게 처리가능.
  2. IOException 예외 처리 필요 : try / catch 혹은 throw 사용
  3. 공백 단위로 데이터를 처리하려면 StringTokenizer 혹은 String.split() 사용

Scanner와의 속도 비교

  • 정수를 한 줄씩 읽고 합치기 1000만번 반복

입력 방식 수행시간(초)

java.util.Scanner 6.068
java.io.BufferedReader 0.934

사용법

BufferedReader br = new BufferedReader(new InputStreamReader(System.in);
String s = br.readLine();

// 형변환 예시
int i = Integer.parseInt(s);

// 공백 단위 데이터 처리 예시 1
// 인자1: 문자열, 인자2: 구분자(옵션)
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
Stirng word = null;
while(st.hasMoreTokens()){
	word = st.nextToken();
}

// 공백 단위 데이터 처리 예시 2
String arr[] = br.readLine().split(" ");

BufferedWriter

  • 보통 사용하는 System.out.println() 대신 사용
  • 적은 양을 출력할 땐 System.out.println() 이 편리하지만 많은 양을 출력할 땐 이 방법은 속도가 느리다. 그래서 BufferedWriter 를 사용

개행 문자 출력

  • BufferedWriter는 개행을 자동으로 해주지 않는다.
  • BufferedWriter.newLine() 이나 BufferedWriter.write(”\n”) 사용

flush() / close()

  • 버퍼를 사용하기 때문에 더 이상 사용하지 않으면 flush()로 스트림을 비우고, close() 로 출력스트림을 닫아줘야한다.
  • 한번 출력 후 다른 것도 출력하려면 flush() 로 비우기

사용법

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String str = "hello";

// void write(char[] buf, int offset, int length)
// 버퍼 offset부터 length 크기만큼 write
bw.write(str + "\\n"); // 혹은 bw.newLine()
bw.flush();
bw.close();

StringBuilder (String, StringBuffer 와 비교)

참고한 링크 BufferedReader, BufferedWriter, StringBuilder 설명 Java 문자열 concat, +, StringBuilder 차이 Java의 String, StringBuffer, StringBuilder 차이, 실험 오라클 자바 문서 : String 클래스

  • String 클래스는 불변 속성, StringBuffer/StringBuilder 는 가변속성을 가짐
    • String 값을 문자열 연산으로 변경 → 원래 값을 버리고 새로운 값을 할당 ( 원래 값은 Heap 메모리에 남아있다가 GC에 의해 삭제 )
    • 주의 : 위 이유로 문자열 연산이 많이 일어나는 경우 String을 사용하면 Heap 메모리에 많은 Garbage가 쌓인다. 이는 메모리 낭비를 유발함
  • 따라서 문자열 연산이 많은 경우 StringBuffer / StringBuilder 사용
  • StringBuffer VS StringBuilder
    • 두 클래스가 제공하는 메소드는 같음
    • StringBuffer : 동기화를 지원 → 멀티스레드 환경에서 안전
    • StringBuilder : 동기화 지원 x → 멀티스레드 환경에 적합하지 않음. 단, 단일스레드에서는 StringBuffer 보다 빠름
    • 예를 들어, 멀티스레드 환경에서 문자열을 수정할 때 StringBuffer는 각 스레드에서 같은 문자열을 확인할 수 있지만, StringBuilder의 경우 동기화를 지원하지 않기 때문에 문제가 발생할 수 있다.
  • StringBuilder 주요 메소드
    • append(String s)
      • StringBuilder 뒤에 값을 붙임
    • delete(int start , int end)
      • 특정 인덱스부터 인덱스까지를 삭제
    • insert(int offet, any primitive of a char[])
      • 문자를 삽입함
    • replace(int start , int end , String s)
      • 일부를 String 객체로 치환
    • reverse()
      • 순서를 뒤집음
    • setCharAt(int index , char ch)
      • 주어진 문자를 치환
    • indexOf(String s)
      • 값이 어느 인덱스에 들어있는지 확인
    • subString(int start, int end)
      • start와 end 사이의 값을 잘라옴.

String “+” / StringBuilder.append() / StringBuffer.append() 비교 실험🧪🧪

  • 약 5000 밀리초 : 1밀리초 : 1밀리초
    • StringBuilder, StringBuffer가 String 연산에 비해 엄청난 속도를 보여준다.  (맨 아래 수정사항 참조)
  • StringBuilder 와 StringBuffer의 차이는 거의 없는 듯하다
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class Main {
    public static void main(String[] args) throws IOException {

       BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        String hello = "hello";
        String hellohello = "";
        StringBuilder sBuilder = new StringBuilder();
        StringBuffer sBuffer = new StringBuffer();
				long start = 0L;
        long end = 0L;

        // 1. String + 
        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            hellohello = hellohello + hello;
        }
        end = System.currentTimeMillis();
        bw.write("String + : " + String.valueOf(end - start) + "\\n");

        // 2. StringBuilder.append()
        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            sBuilder.append("hello");
        }
        end = System.currentTimeMillis();
			//bw.write(sBuilder.toString() + "\\n");
        bw.write("StringBuilder : " + String.valueOf(end - start) + "\\n");

        // 3. StringBuffer.append()
        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            sBuffer.append("hello");
        }
        end = System.currentTimeMillis();
			//bw.write(sBuffer.toString() + "\\n");
        bw.write("StringBuffer : " + String.valueOf(end - start) + "\\n");

        bw.flush(); bw.close();
    }
}

실험에 대한 의문….🤔🤔

  • 위 실험이 잘못된건가 싶어서 구글링해보았으나 다른 사람들의 결과도 비슷했다. 똑같이 10만번 반복한 사람의 결과는 내 결과인 약 4~5초와 비슷했다. ( String 의 + 연산 )
    • 이 분은 StringBuilder.append() 연산에서 결과값을 계속 재할당하셨는데 이유는 잘 모르겠다. StringBuilder 실험결과는 꽤 차이가 났는데 재할당이 속도 저하의 원인으로 보인다.
    • str = str.append("world") 로 작성하셨고 약 200밀리초 걸림. 내 실험결과는 1~2 밀리초. str 변수는 StringBuilder의 인스턴스였고 내가 실험했을 때 결과도 추가된 문자열이 잘 출력되어 위 코드는 불필요한 대입인 것으로 보인다.
  • String의 + 연산은 jdk 1.5 미만에서는 매번 String 인스턴스를 생성하여 더하는 방식을 취했고, 그 이후에는 StringBuilder로 컴파일 되도록 변경되었다. 때문에 +를 활용해도 큰 차이가 없다… 는 이야기를 보았다.
  • 하지만, 직접 실험해본 결과 속도에서 너무나도 큰 차이가 난다. 물론 10만번으로 많은 반복을 거쳤지만 그래도 무시할만한 차이는 아니다.
  • oracle 의 java 문서에서 확인한 결과
    • 실제로 문자열 합치는 기능이 append 메소드를 사용한다고 한다. 그런데 왜 이렇게 많은 차이가 발생하는걸까?
  • The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method. String conversions are implemented through the method toString, defined by Object and inherited by all classes in Java. For additional information on string concatenation and conversion, see Gosling, Joy, and Steele, The Java Language Specification.
  • 관련 내용을 찾을 수 없어 chatGPT 에게 질문한 결과는 다음과 같다.

------------------------------------------------------------------------------------------

네, **String**의 + 연산이 내부적으로 **StringBuilder**를 항상 사용하는 것은 아닙니다.

자바 컴파일러는 + 연산자로 문자열을 연결하는 코드를 컴파일할 때, 효율적인 문자열 연결을 위해 내부적으로 **StringBuilder**를 사용하는 방식으로 변환합니다. 이를 "컴파일러가 자동으로 **StringBuilder**를 사용한다"고 생각할 수 있습니다.

따라서 컴파일러는 다음과 같은 코드:

String str = "Hello";
str = str + " World";

다음과 같이 컴파일됩니다:

String str = "Hello";
str = new StringBuilder().append(str).append(" World").toString();

이렇게 **StringBuilder**를 사용하여 문자열 연결이 수행됩니다. 이러한 변환은 컴파일 과정에서 발생하며, 개발자가 직접 **StringBuilder**를 사용하는 것과는 다른 방식입니다.

하지만, + 연산자로 문자열을 반복적으로 연결하는 경우에는 컴파일러가 최적화를 수행하지 못하고 내부적으로 **StringBuilder**를 사용하지 않습니다. 반복적인 + 연산은 매 연산마다 새로운 **StringBuilder**를 생성하고 문자열을 누적하는 비용이 발생하므로, 성능에 영향을 줄 수 있습니다. 이 경우에는 명시적으로 **StringBuilder**를 사용하는 것이 더 효율적입니다.

따라서, + 연산자를 사용하여 문자열을 연결할 때는 개발자가 컴파일러의 동작을 고려할 필요가 없으며, 코드를 가독성 좋게 작성하는 것에 집중하는 것이 좋습니다. 컴파일러는 최적화를 수행하여 효율적인 문자열 연결을 처리할 수 있습니다.

 

------------------------------------------------------------------------------------------

 

  • gpt 답변을 완전히 신뢰할 수 있는지는 모르겠지만 저 말이 맞다면 StringBuilder를 사용하긴 하지만 매 연산마다 새로운 인스턴스를 생성하는 건 여전하다. 이 부분에서 오버헤드가 발생하는지도 모르겠다.
  • 추측 : 아무래도 String 클래스가 불변 속성이다보니 StringBuilder를 사용하더라도 매번 새로운 인스턴스를 만들어 적용할 수 밖에 없는 듯하다. 위 gpt 답변에서 보이듯이 StringBuilder.append().toString()으로 결국 String 클래스로 변환해서 값을 대입해주기 때문이다. 따라서 기존 문자열 대신 새로운 문자열을 넣어주기 위해 매번 새로운 인스턴스를 생성하는 수 밖에 없어보인다.
  • 실험 후 결론 : 속도가 중요한 상황에서 반복적인 문자열 concatenation 이 필요하다면, String의 “+” 보다는 StringBuilder.append() 를 사용하자. 그리고 반복이 끝난 후 toString() 으로 한번에 변환해서 사용하자.
  • 23.09.20 : 시간이 지나서 전에 썼던 글을 다시 보았더니 수정할게 있다. 추측부분에서 새로운 문자열을 할당하기 위해 매번 새로운 인스턴스를 생성한다고 했지만 틀렸다. 내가 for 문에서 hellohello = hellohello + hello; 를 계속 반복하는 바람에 변수에 새로운 String 인스턴스를 10만번 반복해서 할당하는 꼴이되었다. 그러니 오버헤드가 클 수 밖에 없었다. 만약 hellohello = hellohello + hello + bonjour + nihao + annyeong; 처럼 반복문 없이 단순히 + 연산자를 여러번 사용하는 경우라면 내부에서 StringBuilder로 한번에 처리하기때문에 변수에 새 인스턴스를 할당하는 동작은 한 번만 일어난다. 이럴 땐 StringBuilder나 별 차이가 없다. (물론 chatgpt가 한 답변이 옳은 경우라면… 말이다) 하지만 위에서 실험한 것 처럼 for문 같은 반복문안에서 + 연산자 및 인스턴스 할당을 사용할 거라면 StringBuilder를 직접 사용하는게 훨씬 나아보인다.

'학습 기록 > 알고리즘' 카테고리의 다른 글

StringTokenizer VS String.split()  (0) 2023.09.20

레퍼런스로 사용하는 동료의 캡스톤 과제가 ts로 되어있어서 코드 이해를 위해 ts를 배우기로 했다.

 

단기간에 사용할 수 있도록 간단히만 요약함.

 

< 시작 >

- npm install -g typescript로 설치 

- ts 파일이 위치한 위치로 가서 'tsc -w' 실행하면 코드 작성 후 저장할 때마다 자동으로 js로 변환함 

 

< 질문 >

1. tuple 이 필요한 이유가 뭘까 (지정된 위치에 지정된 타입의 원소를 다뤄야 하는 경우가 뭘까) : 

2. TS에서 interface와 type 의 사용방법이 굉장히 유사한데 각각 어느 상황에서 써야 적절한가?

-> object나 class 의 모양을 강제하고 싶다면 interface를 사용하고 그 외 변수타입을 지정하거나 tuple을 사용하는 등 다른 목적이라면 type을 사용한다

3. 

 

// -------------------------- TYPES ---------------------------
// ts 타입 종류: string, number, boolean, null, undefined, bigint, [], {} ... 
let type1: string = "hello";
let type2 = "bye";
type1 = "hi";   type2 = "good bye";

let type3: number = 1;
let type4 = 2;
type3 = 3; type4 = 4;

let type5: boolean = false;
let type6 = false;
type5 = true; type6 = true;

let type7: null = null;
let type8 = null;
let type9;  // undefined

let type10: undefined;  // undefined

let type11: string[] = ["hi", "I'm fine", "and you?"];
let type12 = ["this", "is", "string", "array"];
type11.push("I'm Andrew"); type12.pop();

let type13: any = 123;  // any에는 어떤 타입이든 올 수 있다 -> 제약이 없어지므로 js와 비슷한 상황

let type14: unknown;    // 아직 어떤 타입일지 모름

// void: 아무것도 return 하지 않는 함수
const voidFunction = () => {
    console.log("this is void type function");
}

// never: 1.에러를 발생시키는 함수 2. 절대 반환될 수 없는 
// 1.
const errorFunction = () => {
    throw new Error("error");
}
// 2.
const numOrString = (type15: number|string) => {
    if(typeof type15 === "number"){
        type15 += 1;
    }
    else if(typeof type15 === "string"){
        console.log(type15);
    }
    else{
        type15; // never
    }
}

// unknown: 타입에 따라 다른 결과
let thing: unknown;

if(typeof thing === "string"){
    let sentence = thing;
}

if(typeof thing === "number"){
    let size = thing;
}









// -------------------------- CUSTOM TYPE ---------------------------
// 나만의 타입 만들기 
// 변수명은 대부분 대문자로 시작
type Age = string | number; // union type: 두 타입 중 하나면 ok
const myAge: Age = 27;
const yourAge: Age = "27";

// type 에 concrete type의 특정 값을 지정할 수도 있음
type Color = "red" | "blue" | "yellow";
type Clothes = {
    size: string,
    color: Color
}
const shirt: Clothes = {
    size: "XL",
    color: "red"    // ex: "black" 은 에러
}

// 기존 type에 새로운 property 를 추가할 때
type Phone = {
    kind: string,
    version: string,
    price: number
}
type MyPhone = Phone & {
    // Phone 타입에 추가할 property 작성
    phone_number: string
}
const myPhone: MyPhone = {
    kind: "iPhone",
    version: "XR",
    price: 1000000,
    phone_number: "010-1111-2222"
}

// 선언한 타입의 여러가지 경우의 수에서 한 가지를 선택해서 사용
// 1. 개수는 같고 종류는 다른 인자
type Subtraction = {
    (a: number, b: number): number;
    (a: number, b: string): number;
}

const sub: Subtraction = (a, b) => {
    if(typeof b === "string") return a;
    return a - b;
}

// 2. 개수가 다른 인자
type Menu = {
    (a: string): string;
    (a: string, b: string): string;
}

const menu: Menu = (a: string, b?: string) => {
    if(b) return b;
    return a;
}








// -------------------------- FUNCTION ---------------------------
// 함수에서 타입지정하기: parameter, return value
function plus(x: number, y: number): number{
    return x + y;
}
plus(1, 2);

// 화살표 함수에서 타입 지정하기: parameter, return value
const minus = (x: number, y: number): number => {
    return x - y;
}

// Call Signature: 함수의 파라미터, 반환타입 미리 지정
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => (a + b); // 타입을 미리 지정했기 때문에 a, b 타입을 안써줘도 됨
const result = add(1, 2);   // number








// -------------------------- ARRAY ---------------------------
// array 에서 사용가능한 tuple: 지정한 자리에 지정한 타입 원소가 있어야함
// type으로 변수 선언 후 []에 원하는 타입 지정
type Member = [number, boolean];
let john: Member = [13, false];
// let emily: Member = ["13", true];    // 에러








// -------------------------- OBJECT ---------------------------
// object 속성에 type 지정
type Obj = {
    age: string,
}
let jameson: Obj = {
    age: "hello",
}

// object property의 타입은 알지만 이름은 아직 모를때 
type Member2 = {    // 글자로 된 모든 속성의 타입은 string 으로 지정
    [key: string]: string,  // call signature
}
// obj 속성 key에 ""를 사용하면 식별자규칙을 따르지 않아도됨
// 식별자규칙: 변수 이름을 만드는데 지켜야할 규칙 
// 1. 문자, 숫자, _, & 로 작성 2. 숫자로 시작하지 않기 3. 예약어는 변수명 될 수 없음 ...
let tom: Member2 = {
    "name": "tom",
    "age": "123",
}

// obejct 선언시 타입과 함께 한번에 작성 (비추천)
const playerEx: {
    // 타입 부분을 중괄호 처리해서 사용
    name: string,
    age?: number,    // optional -> age는 존재해도 되고 안해도 됨
}={
    name: "nico"
}








// -------------------------- CLASS ---------------------------
// 추상 클래스 작성
abstract class Pet {
    // JS에는 접근제한자 없음, TS에서만 사용
    constructor(
        protected name: string,
        private age: number,
    ){}
    abstract getName(): string;
    public getAge(){
        return this.age;
    }
}
// 자식 클래스 작성
class Dog extends Pet{
    getName(){
        return this.name;
    }
}
// 클래스 인스턴스 
const pet1 = new Dog("콩이", 7);
pet1.getName();
pet1.getAge();









// -------------------------- INTERFACE ---------------------------
// class, object 의 구성을 나타내기 위해 사용
// 마치 type처럼 사용되지만 type 기능이 좀 더 많음 => argument나 return 타입으로 지정가능
// 구성
interface Gear {
    name: string;
    kind: string;
    getName(): string;
}
// interface 끼리 상속 가능
interface Laptop extends Gear{
    getPrice(): number;
}
// 클래스로 구현
class MyLaptop implements Laptop{
    constructor(
       public name: string,
       public kind: string,
       private price: number    // 인터페이스에서 선언된 변수는 반드시 public 이다
    ){}
    public getName(): string{
        return this.name;
    }
    public getPrice(){
        return this.price;
    }
}
// instance 생성
const myLaptop = new MyLaptop("gram", "ultrabook", 1500000);
// obj 생성
const myLaptopObj: Laptop = {
    name: "gram",
    kind: "utlrabook",
    getName: function(){ return this.name; },
    getPrice: function(){ return 1500000; }
}
const name2 = myLaptopObj.getName();    // Gear, Laptop 인터페이스에서 타입지정해줘도 MyLaptop에서 지정안하면 any 표시됨..

// TS interface는 중첩이 됨(...)
interface Hi{
    Hi: string
}
interface Hi{
    Hello: string
}
const greeting: Hi = {
    Hi: "Hi",
    Hello: "Hello"
}








// -------------------------- ABSTRACT VS INTERFACE ---------------------------
// abstract class 와 interface 사용시 차이점
// class 의 기본적인 골자를 알려주기 위해 abstract, inteface 사용 
// abstract 는 JS 변환시 class로 남아있고 interface 는 type 처럼 코드 남지 않음
// 1. abstrace example
abstract class Food{
    constructor(
        private name: string,
        private price: number
    ){}
    public getName(): string{
        return this.name;
    }
    public getPrice(): number{
        return this.price;
    }
    abstract showInfo(): string;
}
// Food 상속받은 Snack 클래스
class Snack extends Food{
    public showInfo(){
        return `This snack is ${this.getName()}, cost is ${this.getPrice()}`;
    }
}
const myFood = new Snack("Apple", 3000);    // name, price는 getter로만 접근가능, 수정불가

// 2. 위와 같은 내용을 interface로 만들어보기
interface Food2{
    name: string,
    price: number,
    // interface 안에서 함수 구현 불가능 <-> abstract class
    getName(): string,
    getPrice(): number,
    showInfo() : string
}
class Snack2 implements Food2{
    constructor(
        // 인터페이스를 구현한 필드는 반드시 public 접근제한자 <-> abstract class 
        public readonly name: string,   // private, getter 사용 못함 -> readonly로 대체 (뇌피셜)
        public readonly price: number   // private ,getter 사용 못함 -> readonly로 대체 (뇌피셜)
    ){}
    // 외부에서 name, price 접근 가능하므로 getter로 보호 불가 -> readonly 사용
    public getName(){
        return this.name;
    }
    public getPrice(){
        return this.price;
    }
    public showInfo(){
        return `This snack is ${this.name}, cost is ${this.price}`;
    }
}
const myFood2 = new Snack2("신라면", 1000); // name, price 직접 접근가능 but 수정 불가








// -------------------------- READONLY ---------------------------
// Javascript 에는 없는 개념
// 1. object property
type Fruit = {
    readonly color: string,
    name: string
}
const fruit1: Fruit = { color: "red", name: "apple"};
fruit1.name = "banana";
// fuirt1.color = "yellow";     // readonly 는 변경 못함: 에러

// 2. array
const array: readonly string[] = ["1", "2"];
// array.push("3");    // readonly 는 변경 못함: 에러









// -------------------------- GENERIC ---------------------------
// 1. call signature로 너무 많은 타입을 지정해야할 때, 어떤 타입이 올지 모를 때
type MyArray = {
    <T>(array: T): T;
}
const printArray: MyArray = (array) => {
    console.log(array);
    return array;
}
// number, string, boolean 등 다른 타입이와도 처리가능
const arr1 = printArray([1, 2, 3]); // return number[]
const arr2 = printArray(["1", "2", "3"]); // return string[]
const arr3 = printArray([1, 2, "3", true, false]); // return number | string | boolean[]

// 2. 결과적으로 any와 같다고 생각할 수 있지만 다르다 => 같은 코드를 여러 type에서 사용할 수 있게 재활용 
// generic은 각 call signature를 생성
type AnyArray = {
    (array: any): any;
}
const anyArray: AnyArray = (array) => {
    console.log(array);
    return array;
}
const any1 = anyArray([1, 2, 3]);   // return any not number
const any2 = anyArray(["1", "2", "3"]); // return any not string
// const any3 = anyArray(1, 2, "3", true, false);   // 에러

// 3. 함수에서 generic 사용
function printArray2<V> (array: V){
    console.log(array);
    return array;
}
const arr4 = printArray2([1, 2, 3]); // return number[]
const arr5 = printArray2(["1", "2", "3"]); // return string[]
const arr6 = printArray2([1, "2", true]);   // return number | string | boolean

// 4. generic으로 타입 확장
type Person<E> = {
    name: string,
    extraInfo: E
}
type MyExtra = {
    favFood: string
}
type DavidPerson = Person<MyExtra>
const david: DavidPerson = {
    name: "david",
    extraInfo: {
        favFood: "kimchi"
    }
}
const Tim: Person<null> = {
    name: "Tim",
    extraInfo: null 
}









// -------------------------- 응용 ---------------------------
// 1. function 으로 type이 지정된 object 를 반환하는 예
type Player = { // 특정 객체를 위한 타입 선언
    name: string,
    age?: number    // optional (number OR undefined)
}

function playerMaker(name: string): Player{
    return {
        name    // name: name
    }
}

const nico = playerMaker("nico");
nico.age = 30;  // Player type 지정 안하면 Error

// 2. 간단한 Dictionary 만들어보기
type Words = {
    [key: string]: string;
}
class Word{
    constructor(    // 클래스 필드 자동 초기화
    // 접근제한자 꼭 적기
        public name: string,    // this.name = name;
        public def: string  // this.def = def;
    ){}
}
class Dict{
    private words:Words;
    constructor(){
        // 빈 obj 생성
        // this.words = words 로 자동초기화를 원하지 않기때문에 수동초기화
        this.words = {}; 
    };    
    public addWord(word: Word){
        if(this.words[word.name] === undefined){
            this.words[word.name] = word.def;
        }
    }
    public definition(name: string){
        return this.words[name];
    }
}

const whiteboard = new Word("whiteboard", "글쓰는 흰 보드");
const dictionary = new Dict();
dictionary.addWord(whiteboard);
dictionary.definition("whiteboard");

// 3. 다형성 : generic, class, interface ... 
// generic : concrete type이 아닌 placeholder 사용 가능하게 함, 같은 코드를 다른 타입에서 사용할 수 있도록 함
interface SubStorage<G>{
    [key: string]: G
}
// 다양한 type에서 쓸 수 있는 LocalStorage 간단실습
class LocalStorage<G>{
    private storage: SubStorage<G> = {}
    set(key: string, value: G){
        this.storage[key] = value;
    }
    remove(key: string){
        delete this.storage[key];
    }
    get(key: string): G{
        return this.storage[key];
    }
    clear(){
        this.storage = {};
    }
}
const stringStorage = new LocalStorage<string>();
stringStorage.set("greeting", "hello");
stringStorage.get("gretting");
stringStorage.remove("greeting");
stringStorage.clear();

const numberStorage = new LocalStorage<number>();
numberStorage.set("dozen", 12);
numberStorage.get("dozen");
numberStorage.remove("dozen");
numberStorage.clear();

// ...

 

 

 

* 학습내용을 정리한 것으로 아래 내용이 꼭 정답을 의미하진 않습니다. 

 

1. call by refernece 인가 call by value 인가?

자바에 대해 공부를 막 시작할 무렵 " 자바는 call by value 로만 동작한다 " 는 문장을 어렴풋이 듣고만 넘어갔었다. 그래서 그냥 " 아 자바는 C 처럼 call by value 로 동작하는구나 " 라고 생각하고 그냥 그런줄로 알고 있었다. 그런데 최근 자료구조와 자바 문법을 되돌아보며 여러 인터넷 글을 돌아보던 중 Java가 call by reference로 동작하는지 여부에 대해 논하는 글을 보게되었다.그 글을 본 뒤로 정확한 이유를 알지 못하고 넘어갔던 문제를 다시 한번 짚고 넘어가자는 생각으로 관련 정보를 찾아보았다.

 

토비의 스프링 저자이신 토비님께서 이와 관련해 글을 쓰신적이 있었다.

위 글을 요약하자면 결국 "자바는 call by value로 동작한다" 이다. 

위에서 언급된 영문을 해석하면 ...

 

"pass by reference라는 용어는 정확히말하면, argument가 함수로 전달될 때 호출된 함수는 값의 복사본이 아니라 원래 값에 대한 참조를 얻는다. 만약 함수가 parameter를 수정하면, 호출하는 코드의 값은 바뀔것이다. argument(원래 변수) 와 parameter(함수 정의에서의 변수) 는 같은 메모리 공간을 사용하기 때문이다. ... (생략) 자바는 객체를 레퍼런스에 의해 전달하지 않는다. 자바는 객체의 레퍼런스(포인터)를 값으로 전달한다."

 

나는 call by reference가 C에서 포인터(주소 값)을 인자로 넘겨주고 함수 내에서 역참조로 해당 포인터가 가리키는 값을 변경해주는 것이라고 생각했다. 근데 이건 위에서 포인터를 전달한다는 것과 같은 의미아닌가? 그렇다면 내가 알고있던 call by reference가 좀 다른 의미라고 봐야한다. 엄밀히 말하면 call by address 지만 call by reference 와 구분짓지 않고 다들 이야기하길래 그냥 그렇게 같은 것인줄 알았다. 하지만 위 토비님 글에서 "C에도 call by reference가 있다는 식의 이야기와 비슷하다" 는 말의 뉘앙스를 보아도 C 에는 call by reference 가 없다는 말처럼 들린다. 그래서 좀 더 자세히 찾아보기로 했다.

 

참으로 여러가지 이야기가 있고 역시나 call by address 와 call by reference를 같은 의미로 사용하는 글이 많았다. 국내와 국외를 가리지 않고 그랬다. 그래서 뭐가 맞는 말인지 참 어려웠는데 "왠지 이걸 말하는건가?" 싶은 글이 하나 있었다.

 

 

"There is no concept of variable reference in C, as there is in C++. What you show in your small example is simply a use of a pointer (or variable address). But you are passing a value (the address of the variable) not a reference (an alias of the variable name)."

https://stackoverflow.com/questions/59048556/no-call-by-reference-in-c

 

No Call by reference in C?

I know, the title is a little bit odd. Of course I know how to do call-by-reference in C. My problem is simple and the solution could be simple as well. I am currently going through some scripts of...

stackoverflow.com

 

"C에는 C++ 처럼 변수 reference 개념이 없다."

즉 C++ 의 call by reference와 C의 call by reference는 다르다. 하지만 대부분 이를 혼용해서 사용하고 있고 이 부분에서 혼란이 발생하는 것 같았다. C++ 에서 reference 는 원본 변수의 별명(alias)과 같다. 아래는 포인터와 레퍼런스의 차이다.

 

// 포인터와 원본 주소는 같지만 포인터 변수의 주소와는 다르다
ptr == &num
&ptr != &num

// 참조자는 원본과 모든 것이 같다
ref == num
&ref == &num

 

 

위 이야기를 종합해보면 사람들이 call by address 와 call by reference를 혼용해서 사용했고 자바도 같은 이유로 오해했다.. 정도로 보인다. 그럼 왜 자바가 call by address 가 아니라 call by value를 사용한다고 하느냐고 묻는다면 그냥 용어의 차이같다. call by value가 value 복사본을 전달해주듯 call by address 도 포인터(주소값)을 복사해서 넘겨준다. 넘겨주는게 실제 값이냐 주소 값이냐의 차이다. 

 

내 결론 : 

1. Java는 call by value 로 동작한다.

2. call by value : argument 값을 복사해서 parameter로 전달

3. call by address : call by value와 같으나 포인터(address)를 parameter로 전달

4. call by reference : C++ 에서 사용하는 reference(참조자, 원본 변수의 별명)를 전달

 

 

2. 번외 : 포인터와 참조자는 다른가?

이 주제에 대해 길게 쓸건 아니고 위 내용을 찾다가 글을 하나 찾게되었다. 

https://woo-dev.tistory.com/43

 

[C++] 포인터와 레퍼런스(참조)의 차이를 이해해보자

C++에는 포인터(Pointer)와 레퍼런스(Reference)라는 개념이 있다. 포인터는 C 에도 있었던 개념이며 레퍼런스는 C++ 에서 등장한 개념이다. 언뜻 보면 용도가 비슷한데 정확히 어떤 차이점이 있는지,

woo-dev.tistory.com

위 블로그 주인분이 쓰신 글을 보면 내부적으로 동작할 때는 같은 과정을 거치는 것으로 보인다.

인자를 파라미터로 받아올 때 레지스터로 받아온 주소값을 메모리에 저장하는 똑같은 과정을 거친다.

그럼 뭐가 다른건가 생각했을 때, 메모리에 저장한 주소값을 복사해서 사용할 것인가 그대로 사용할 것인가.. 그 차이같다.

 

아래는 내가 확인해본 테스트 결과다.

ptr은 변수 주소가 다르지만 ref와 num 은 완전히 같다. 별명만 다르게 붙여준 같은 변수다. 

 

 

 

3. 번외 : Integer 는 왜 원본 값이 안바뀌지? 

자바는 reference type 변수의 주소값을 매개변수로 전달받는다. 즉 메소드 내에서 원본 값을 변경할 수 있다. 

위 내용을 원래 알고있었다. 그래서 실험을 하나 했는데 int 변수와 Integer 변수 중에 Integer 변수는 기본타입이 아닌 인스턴스니까 값이 변하나? 라는 의문을 풀기 위한 것이었다.

 

결론은 안바뀐다. 알고보니 Integer은 Immutable 이었다. 그럼 새로운 인스턴스를 담아주면 바뀌나?

 

이것도 안바뀐다. 이건 아래 링크에 설명되어있다. 

 

https://stackoverflow.com/a/12429953/5013556

 

Is Java "pass-by-reference" or "pass-by-value"?

I always thought Java uses pass-by-reference. However, I read a blog post which claims that Java uses pass-by-value. I don't think I understand the distinction the author is making. What is the

stackoverflow.com

main 메소드의 b와 test 메소드의 b는 다른 변수다. test가 호출되면 비어있는 변수 b가 새롭게 생기고 포인터로 전달된 main 메소드의 인스턴스 b 를 가리킨다. 이 상태에서 새로운 Integer 인스턴스 c 를 만들고 변수 b에 대입하면 main 메소드의 b 변수가 새로운 c 인스턴스를 가리키는게 아니다. test 메소드의 b 변수가 c 인스턴스를 가리키게된다.

 

 

이 같은 Immutable Class 는 String, Boolean, Integer, Float, Long, Double (Wrapper Class) 등이 있다. 

 

 

 

새벽에 안드로이드 스튜디오를 설치하다가 공식홈페이지에서 다운받은 코드는 

 

자동으로 아이콘이 생성되지 않길래 만드는 과정을 기록한다.

 

--------------------- 설치 --------------------

1. 다운받은 패키지를 원하는 폴더로 옮기기

( 대부분의 apt 외 패키지는 루트의 /opt 에 설치)

2.gzip -d <패키지 이름>.tar.gz

3. tar xvf <패키지 이름>.tar

4. 이후에는 패키지 설치가이드 따르기

 

------------------- 아이콘 만들기 ------------

1. /usr/bin 에 실행파일 심볼릭링크 만들기

sudo ln -s <실행파일 경로> /usr/bin

( studio.sh 를 PATH에 추가했는데도 안된걸보아 /usr/bin 에 추가하는게 강제되나봄

대충 찾아본거라 틀릴 수 있음)

 

2. /usr/share/applications 에 .desktop 파일 만들기

vim <파일이름>.desktop

 

3. .desktop 파일 내용

[Desktop Entry]

Name=<아이콘이름>

Exec=<실행파일>

Type=Application

Terminal=false ( 실행 시 터미널 실행 여부)

Comment=<설명>

Icon=<아이콘파일 경로> 

 

4. desktop 파일 권한 설정 

sudo chmod +x <desktop파일>

----------------------------------------------------------------------------------

 

 

 

출처 : https://www.washingtonpost.com/local/frankenstein-lives-200-years-later/2016/08/23/e6c572c8-64ad-11e6-96c0-37533479f3f5_story.html

 

 

프랑켄슈타인이라는 이야기와의 만남은 순식간에 이루어졌다. 아마존에서 '킨들 페이퍼화이트'라는 전자책을 구매한 후, 내 손에 쥐어진 첫날 무작위로 구매한 책 중의 하나였다. 아마존의 무료 고전을 둘러보다 프랑켄슈타인을 접했고 원서로 조금 읽어보고는 구글에서 번역본을 구매했다. 나는 이 이야기를 그저 차갑고 푸석푸석한 시체를 이어붙인 괴물이 머리에 커다란 나사못을 꽂고 돌아다니는 시시한 호러영화정도로 생각하고있었다. 심지어 프랑켄슈타인을 그 괴물의 이름으로 알고있었다. 그래도 나름 호러장르의 간판 캐릭터인데 이렇게까지 단단히 착각하고 있었다는게 놀라운 지점이었다. 이렇듯 내 잘못된 지식과 관념에 반해, 이야기를 풀어갈 수록 흥미를 돋우는 소설이었다고 말할 수 있다. 

 

이야기는 한 선장의 편지에서 시작된다. 모험심으로 가득차있던 그는 항해를 하며 자신의 여동생에게 편지로 안부를 전했다. 그러던 중 북극 근처에서 개들을 끌고 썰매를 타는 한 거구를 보게된다. 그리고 얼마안가서 똑같이 썰매를 탄 채 개를 끌고있는 한 사람을 발견한 후 그를 배로 거두게된다. 그는 지쳐있었고 곧 생명의 불씨조차 사그라들것 같은 모양새였다. 하지만 선장은 그가 말을 할 때 느껴지는 엄숙한 자태와 고상한 말투에서 이상함을 느낀다. 마침 선장은 인생의 친구를 필요로 하고있었기에 자연스레 대화를 하며 그와 친해질 수 있었고 그가 여지껏 겪어온 기이한 일들을 생생히 듣게된다. 그는 빅터 프랑켄슈타인이었다. 

 

그는 가족과 떨어져 대학에 진학한 후 화학적지식을 통달했고 생명을 되살릴 수 있다는 사실에 도달하게된다. 강박적 광기에 사로잡혀 쉼없이 실험에 몰두한 결과 그는 그의 실험실에서 생명을 창조한다. 허나, 자신이 만든 창조물의 끔찍한 외관에 놀라고 어떠한 조치도 없이 도망쳐 2년이라는 세월을 흘려보낸다. 그런 와중에 창조물은 아무런 준비없이 세상에 나와 갖은 차별과 살해위협을 견뎌가며 고독하게 세상을 배운다. 인적을 피해 숲을 헤메던 중 한 오두막의 가족을 발견해 창고에 숨어 곁눈질로 말과 글을 배웠다. 가난하지만 고상한 그들을 존경하고 사랑하게된 그는 몰래 나무를 베어다 주기도 하며 선행을 베푸는 모습도 보여준다. 자신의 존재를 알리고 그들과 교류하고 싶어했고 마침내 그들에게 모습을 보였으나 다른 사람들과 마찬가지로 그의 외형에 혐오감을 표현하며 폭력을 휘두르고 도망가버린다. 그는 이루말할 수 없는 상실감과 배신감에 깔려 허우적대다가 자신을 이렇게 만들고 끊임없는 고통을 느끼게한 프랑켄슈타인을 증오하게되고 복수를 다짐한다. 결론적으로 프랑켄슈타인은 모든 가족과 친구를 지난 과오의 제물로 바치게되고 창조물의 제거를 숙원으로 삼아 그를 쫓다가 자신을 구해준 배 위에서 생을 마감하게된다. 창조물은 조물주가 죽은 사실을 알고 자신의 삶 또한 끝내기위해 북쪽 끝으로 몸을 숨긴다. 

 

이 이야기를 처음부터 끝까지 주욱 따라가다보면 자연스럽게 창조물의 이야기에 귀를 기울일 수 밖에 없다. 그는 처음에는 고독했고 시간이 지나 비참해지며 마지막에는 증오감에 절여진다. 더욱 절망적인건 그의 지속적인 구애가 있었음에도 조물주인 프랑켄슈타인은 오로지 배타적인 태도로 대응한 것이다. 내가 조금 신선하게 여긴 부분은 괴물로 여겨지는 창조물이 여느 인간보다 신체적으로 뛰어나고 천성이 선하며 지능이 높다는 것이다. 그가 사람들과 함께할 수 없었던 이유는 오직 그의 끔찍한 외모였다. 그리고 프랑켄슈타인이 그를 배척한 이유 또한 외모였다. 

 

+ Recent posts