참조 타입, 변수의 생존,배열과 문자열

Tina Jeong·2021년 1월 5일
0

Re-자바

목록 보기
3/16

Contents

  • 프리미티브 타입 vs 레퍼런스 타입
  • 변수의 스코프와 라이프타임
  • 배열과 스트링

Primitive type vs Reference type

8개의 원시 타입을 제외하곤 자바의 모든 타입은 참조 타입이다. 자바가 지원하는 방대한 API들과 사용자가 프로그램에서 정의하는 모든 클래스들은 참조타입이다. 지난 포스트에서 원시 타입을 정리하면서 이야기 했던 몇가지 특징들이 있었다. 원시 타입과 참조 타입을 비교하면서 참조 타입을 정리해본다.

Reference data type

참조타입은 기본적으로 new 키워드를 이용하여 객체를 생성하고, 데이터가 생성된 주소를 참조하는 타입이다. 참조 타입의 종류는 Class, 배열, Interface, Enumeration, Annotation이 있다. String은 참조 타입과 달리 new 없이 리터럴로 생성이 가능하지만 기본 타입이 아닌 참조 타입이다.(Object 리터럴도 있지만 너무 깊게는 가지말자)

public class MyType {
	String name;
    	int age;
    	MyType(String name, int age) {
    		this.name = name;
        	this.age = age;
    	}
}
...
MyType myType = new MyType("Name",28);

원시 타입과의 비교

사용자가 타입을 정의할 수 있나?

8개의 원시 타입은 자바 언어에 귀속된 타입인데다가 reserved word로 정해져있다. 고로 원시 타입은 사용자가 정의할 수 없다. 반면 참조 타입은 사용자가 원하는 만큼 정의할 수 있기 때문에, 참조 타입의 개수는 무한대가 될 수 있다.

single or multiple?

원시 타입은 하나의 값만을 저장한다. 하지만, 클래스(멤버변수 등을 통해)와 배열은 여러개의 값을 한꺼번에 저장하고 다룰 수 있다.

GC의 대상이 되는가?

원시타입은 저장된 값 자제를 저장한다. 지역변수나 메소드로 인자가 넘어갈 때도, 주소가 넘어가는 것이 아니라 복사된 값이 넘어간다. 반면, 참조 타입은 값이 저장된 곳의 주소를 저장하는 공간으로 객체의 주소를 저장한다.

참조 타입은 데이터의 크기가 가변적/동적이고, 확률적으로 원시 타입보다 메모리를 많이 차지하게 된다. 참조 타입들은 동적으로 관리되는 Heap 영역에 저장되고, 더 이상 참조하는 변수가 없을 때 가비지 컬렉션에 의해 파괴된다.

📣C에서는 메모리를 할당하고, 해제하고, 주소값을 참조해서 저장된 값을 불러오는 등 주소값을 적극적으로 다룰 수 있다. 하지만, 자바에서는 지원하지 않는다. pointer가 프로그래머에게 자율성도 주지만, 그만큼 수많은 에러가 발생하기 때문이다. 그대신 객체에 대한 Garbage Collection을 통해 자동 메모리 관리를 해준다.

Scope && Lifetime

제목을 간단히 적기 위해 변수의 생존이라는 말을 썼는데, 엄밀히 말해서 scope는 접근가능여부와 관련된 용어이다. 접근 할수 없는 쪽에서는 변수가 죽은 것처럼 보이니까..흠 쓰고나니 무섭네... lifetime은 메모리에 올라가 있는 시간을 의미하니 생존이라는 말은 lifetime에 좀더 가깝다.

Scope

scope는 공간적 개념, lifetime은 시간적 개념이라고 많이 말하는데, 실제로 scope는 변수/클래스/인터페이스 A에게 접근 가능한 코드의 구역을 의미한다. 즉, A에게 접근 가능한 구역이 scope가 된다.

Method와 Block 단위의 scope

method 단위
메소드의 매개변수로 들어온 num은 foo라는 메소드 안에서만 접근이 가능하다. num 그대로 return 한다해도 위에서 언급했듯이 값의 복사본이지 num 자체를 접근할 순 없다.

void foo(int num) {
	return num;
}

block 단위

int sum =0;
for(int i=1; i<=10 ;i++) { // i는 for문 안에서만 접근이 가능하다.
	sum +=i;
}
switch(a) { //a는 switch문 안에서만 접근이 가능하다.
	case 1:
    ...
}
try (InputStream is = new FileInputStream("/Users/ben/details.txt")) {
   ...
} //is는 try 문 안에서만 접근이 가능하다.

접근 제한자

자바는 high cohesion, low coupling을 지향하는 객체지향적인 언어이다. 그 일환으로 접근 제한자를 지원해 프로그래머가 scope를 자유롭게 조정할 수 있도록 했..지만 멤버변수는 일괄 private 처리하고 public인 getter,setter 등의 메소드를 통해 접근하도록 처리하는 등 정해진 일반적인 규율에 따르는 것이 좋고, 되도록이면 불필요한 정보외부노출 지양하는 방식으로 접근 제한자를 활용한다.

classpackagesubclassglobal
private
(default)
protected
public

private은 해당 클래스 안에서만 접근 가능하다는 의미이다. default는 접근 제한자를 아무것도 쓰지 않았을때 해당 클래스 및 패키지 안에서 접근이 가능하다. protected는 자식 클래스까지도 접근가능하다는 의미이다. public은 scope의 제한을 두지 않아 어디서든 접근이 가능하다.

📣 protected라고 해서 default보다 scope가 좁을 것 같은데, 사실은 그 반대이니 헷갈리지 않도록 주의한다.

Lifetime

Class Variable
class variable은 클래스 멤버 변수를 선언하는 위치에 static keyword와 함께 선언된 변수이다. class variable은 클래스가 메모리가 올라가 있는 동안(클래스 로딩 타임~프로그램 종료 시)까지 살아있다.
Instance Variable
instance variable은 instantiation 될 때 메모리에 올라가고, 프로그램 실행 중 해당 인스턴스가 더이상 참조되지 않을 때, GC로 처리된다.

Foo foo = new Foo(); // instantiation

Local Variable
local variable은 멤버변수도 아니고 클래스변수도 아닌 위에 언급된 method나 특정 블록 내부에 선언된 변수이다. 프로그램 실행이 해당 블록을 지나가면 할당 해제 된다.

Array

배열은 동일한 타입을 가진 여러개의 값을 저장하는 일종의 container이다. 0부터 순차적인 index를 가지며, 초기 생성시 설정한 고정 길이를 유지한다.

배열의 선언

대괄호를 이용해 배열임을 명시한다. 대괄호는 타입 후위에 붙일 수도 있고, 배열이름 후위에 붙여도 된다.

int[] testArr;
int testArr2[];

배열의 초기화는 다음과 같이 할 수 있다. 참조 타입이기 때문에 new 키워드를 사용해 타입과 길이를 적어주어야 한다. 두 번째 예시와 같이 동적으로 길이를 받아서 길이를 할당할 수도 있다. 길이가 정해지지 않았지만 초기화가 필요한 경우에는 빈 배열이라는 의미로 중괄호 {}로 처리할 수 있다. 나중에 코드 아래에서 필요한 시점에 길이를 받아 초기화를 하면 된다. 또는, 중괄호 안에 해당 타입의 원소를 직접 적어 초기화하는 법도 있다. 해당 타입의 원소들을 적었더라도 new int[]처럼 타입을 재명시할 수도 있다.

testArr = new int[5];
testArr2 = new int[Integer.parseInt(args[0])];
int[] testArr3 ={};
int[] testArr4 = {1,2,3};
int[] testArr5 = new int[]{5,6,7,8,9};

앞서 언급한 testArr = new int[5];는 길이가 5인 배열이다. 그런데, 배열만 초기화 했지 원소들은 따로 초기화하지 않았다. 이런 경우에는 int 타입의 기본값인 0이 일괄적으로 채워진다. 0이 아닌 다른 수로 초기화를 하고 싶다면 Arrays.fill(배열이름,초기값) 메소드를 사용하면 된다.

Arrays.fill(testArr,10);

배열의 접근

testArr가 10으로 모두 채워졌는지 확인하고 싶다면, index를 통해 확인하면 된다.

System.out.println(testArr[0]); //10
for(int i=0;i<testArr.length;i++) {
	System.out.printf("%d\t",testArr[i]); //10   10   10   10   10
}
// by enhanced loop
for(int n : testArr) {
	System.out.printf("%d\t",n); //10   10   10   10   10
}

배열은 복사할 때는 for문을 돌릴 수도 있지만, System.arraycopy 메소드를 이용하면 깔끔하게 배열을 복사할 수 있다.

arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

동일한 배열 길이를 가진 testArr과 testArr5로 테스트를 해본다. testArr의 전체 내용을 testArr5에 복사하고 싶다면 다음과 같이 하면 된다. 2번째 인자는 source 배열의 복사 시작 인덱스이고, 4번째 인자는 destination 배열의 붙여넣기 시작 인덱스이다. 5번째 인자는 복사할 원소의 개수이다.

System.arraycopy(testArr,0,testArr5,0,5);
for (int n : testArr5) {
      System.out.printf("%d\t", n); //10   10   10   10   10
}

2차원 배열

2차원 배열은 1차원 배열과 다른게 별로 없어서 다음의 예시를 사용해 정리한다.

col 0col 1col 2col 3
row 01234
row 15678
row 29101112
row 313141516

4행 4열이므로 배열의 선언과 초기값 설정 방법은 다음과 같다. 첫번째 대괄호에 행의 크기가, 두번째 대괄호에 열의 크기가 들어간다. 순회도 동일하게 이중포문 돌린다.

int[][] arr = new int [4][4];
int cnt =1;
for(int i=0;i<arr.length;i++) {
	for(int j=0;j<arr[0].length;j++) {
    		arr[i][j] = cnt++;
    }
}

세번째 줄의 두번째 원소 10에 접근하려면 다음과 같이 접근하면 된다.

System.out.println(arr[2][1]);

2차원 배열은 프로그램을 짜면서 여러번 원소 접근을 해보면 헷갈리지 않을 것이다.

String

자바에서 String이 가장 특이한 점은 원시 타입이 아닌 참조 타입이지만 "리터럴"을 가지고 있다는 점이다. String이 하도 많이 쓰이기 때문에 자바 언어에서도 많은 지원을 해준다.

그리고, String은 상수이다. replace 메소드를 지원해서 mutable하게 보이는 것일뿐, 사실은 immutable한 객체이다. mutable한 String을 사용하려면 StringBuilder나 StringBuffer를 사용해야 한다.

concat 연산

concat 연산할 때는 세가지 방법이 있다. +연산자(or concat 메서드)를 사용하거나 StringBuilder,StringBuffer의 apppend 메서드를 이용한다. 자세한 내용은 concat 3가지 방법 포스트 참고.

자주 사용 되는 메서드

String.charAt(int index)

String str = "re-java";
char ch = str.charAt(4) // a

String.substring(int from, int to)

String substr = str.substring(3,str.length()); // java 

substring 메소드는 [from,to)이므로 1의 여유를 두고 실행해야 한다.

String.compareTo(String anotherString)

System.out.println(str.compareTo(substr)); //8

두 스트링이 동일한 내용이라면 0을, 사전순으로 str이 substr보다 크다면 0 초과의 수를, substr이 더 크다면 0 미만의 수를 반환한다.

자바의 문자열 비교 알고리즘은 서로 다른 캐릭터가 존재하는 첫번째 인덱스 n(다른 char를 가진 가장 작은 n)에서 str.charAt(n) - substr.charAt(n)을 일차적으로 return하고, 문자열이 인자문자열의 부분집합이거나 그 반대인 경우에는 this.length()-anotherString.length()을 반환한다.

String.indexOf(int ch) , String.lastIndexOf(int ch)

System.out.println(str.indexOf('a'));  //4
System.out.println(str.lastIndexOf('a'));  //6

indexOf는 ch가 처음으로 등장하는 index를 반환한다.

String.replace(char oldChar, char newChar), String.replaceAll(String regex, String replacement)

System.out.println(str.replace('j','J')); // re-Java
System.out.println(str.replaceAll("[a-z]","A")); //AA-AAAA

자주 썼던 메소드들을 생각나는 대로 쓰고 있는데 이 포스트에선 too much가 된 것 같기도 하고.. 정규식에 대해서도 할 말이 많고..String 관련 메소드는 아예 다른 포스트로 정리해야겠다.

참고
https://oracle.com/java/technologies/simple-familiar.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/String.html
https://www.geeksforgeeks.org/variable-scope-in-java/
https://www.tutorialspoint.com/scope-and-lifetime-of-variables-in-java
Java in a Nutsell, 7th edition

계속해서 문서를 업데이트하고 있습니다. 언제든지 댓글피드백 남겨주세요. 😉

profile
Keep exploring, 계속 탐색하세요.

0개의 댓글