[Java] File 클래스



File 클래스


자바에서는 File 클래스를 통해서 파일과 디렉터리를 다룰 수 있도록 하고 있다. 그래서 File 인스턴스는 파일 일 수도 있고 디렉터리 일 수도 있다.


1. File 클래스 API


- 경로와 관련된 File의 멤버변수

 멤버변수

 설명

 static String pathSeparator

 OS에서 사용하는 경로 구분자. 윈도우";", 유닉스":"

 static char pathSeparatorChar

 OS에서 사용하는 경로 구분자. 윈도우';', 유닉스':'

 static String separator

 OS에서 사용하는 이름 구분자. 윈도우"\", 유닉스"/"

 static char separatorChar

 OS에서 사용하는 이름 구분자. 윈도우'\', 유닉스'/'


import java.io.*;


class FileEx1

{

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

{

File f = new File("c:\\jdk1.5\\work\\ch14\\FileEx1.java");

String fileName = f.getName();

int pos = fileName.lastIndexOf(".");


System.out.println("경로를 제외한 파일이름 - " + f.getName());

System.out.println("확장자를 제외한 파일이름 - " + fileName.substring(0,pos));

System.out.println("확장자 - " + fileName.substring(pos+1));


System.out.println("경로를 포함한 파일이름 - " + f.getPath());

System.out.println("파일의 절대경로        - " + f.getAbsolutePath());

System.out.println("파일이 속해 있는 디렉토리 - " + f.getParent());

System.out.println();

System.out.println("File.pathSeparator - " + File.pathSeparator); // 파일 전체 path 구분자. ;

System.out.println("File.pathSeparatorChar - " + File.pathSeparatorChar);

System.out.println("File.separator - " + File.separator); // 디렉터리 구분다. /

System.out.println("File.separatorChar - " + File.separatorChar);

System.out.println();

System.out.println("user.dir="+System.getProperty("user.dir"));

System.out.println("sun.boot.class.path=" + System.getProperty("sun.boot.class.path"));

}

}

실행결과)

경로를 제외한 파일이름 - FileEx1.java

확장자를 제외한 파일이름 - FileEx1

확장자 - java

경로를 포함한 파일이름 - c:\jdk1.5\work\ch14\FileEx1.java

파일의 절대경로        - c:\jdk1.5\work\ch14\FileEx1.java

파일이 속해 있는 디렉토리 - c:\jdk1.5\work\ch14


File.pathSeparator - ;

File.pathSeparatorChar - ;

File.separator - \

File.separatorChar - \


user.dir=E:\Eclipse_Source\JAVA\My_Lib

sun.boot.class.path=C:\Program Files (x86)\Java\jre6\lib\resources.jar;C:\Program Files (x86)\Java\jre6\lib\rt.jar;C:\Program Files (x86)\Java\jre6\lib\sunrsasign.jar;C:\Program Files (x86)\Java\jre6\lib\jsse.jar;C:\Program Files (x86)\Java\jre6\lib\jce.jar;C:\Program Files (x86)\Java\jre6\lib\charsets.jar;C:\Program Files (x86)\Java\jre6\lib\modules\jdk.boot.jar;C:\Program Files (x86)\Java\jre6\classes


File 인스턴스를 생성하고 메서드를 이용해서 파일의 경로와 구분자 등의 정보를 출력하는 예제이다.

cp.) OS의 시스템 변수로 설정하는 classpath외에 sun.boot.class.path라는 시스템속성에 기본적인 classpath가 있어서 기본적인 경로들은 이미 설정되어 있다. 

그리고 한가지 더 알아 둘 것은 File 인스턴스를 생성했다고 해서 파일이나 디렉터리가 생성되는 것은 아니라는 것이다.

파일명이나 디렉터리명으로 지정된 문자열이 유효하지 않더라도 컴파일 에러나 예외를 발생시키지 않는다.

새로운 파일을 생성하기 위해서는 File 인스턴스를 생성한 다음, 출력스트림을 생성하거나 createNewFile()을 호출해야 한다.


1 이미 존재하는 파일을 참조할 때

File f = new File("c:\\jdk\\work\\ch14", "File1.java");


2. 기존에는 없는 파일을 새로 생성할 때

File f = new File("c:\\jdk\\work\\ch14", "File2.java");

f. createNewFile(); //새로운 파일이 생성된다.


(1) 예제1

1) File 클래스

File(.txt)가 자바 프로그래밍에서 사용되기 위해서는 객체로 되어있어야 사용할수 있다.

--> File클래스가 파일을 자바 프로그램의 객체로 변환시켜준다.

 Exam_01.java 파일 자바에서 사용하기 위한 객체화 작업

import java.io.File;


public class Exam_02 {

public static void main(String[] ar) {

File f = new File("Exam_01.java");// 존재하든 관계없이 파일 객체 생성 가능

File f1 = new File("Exam_03.java");// 존재 안하든 관계없이 파일 객체 생성 가능

File f2 = new File("C:" + File.separator + "test" + File.separator + "lecture\\Exam_02.java");

File dir = new File("E:\\Eclipse_Source\\JAVA\\Java_KSH\\io");

File f3 = new File(dir, "Exam_04.java");

File f4 = new File("E:\\Eclipse_Source\\JAVA\\Java_KSH\\io", "Exam_05.java");

}

}


2) 예제2

import java.io.File;

import java.util.Date;


public class Exam_04 {

public static void main(String[] ar) {

File f = new File("aaa");

// 폴더 만들기

f.mkdir();

File ff = new File("bbb/ccc/ddd/eee");

// 복수의 폴더 만들기

ff.mkdirs();

//File fff = new File("Exam_01");

// 수정일자 변경

f.setLastModified(new Date().getTime());

f.setReadOnly();

//ff.setLastModified(new Date().getTime());

}

}


3) 예제3

import java.io.File;

import java.io.FileDescriptor;

import java.io.FileOutputStream;

import java.io.IOException;


public class Exam_05 {

public static void main(String[] ar) throws IOException {

// 출력 객체 만들기, FileDescriptor.out: 콘솔에 출력하기

FileOutputStream fos1 = new FileOutputStream(FileDescriptor.out);// 파일 객체, network 객체

File f = new File("C:\\test\\aaa.txt");

// 두번째 매개변수에 true면 덮어쓰기 하지 않고 내용이 추가된다.

FileOutputStream fos2 = new FileOutputStream(f, true);

byte[] by = new byte[]{'H', 'e', 'l', 'l', 'o', ' ', 'J', 'a', 'v', 'a'};


fos1.write(by, 6,4); 

// index[6]에서 부터 4번째 문자열 출력

fos2.write(by);

fos1.write(65);

fos1.close();

fos2.close();

}

}


4) 예제4

import java.io.BufferedInputStream;

import java.io.DataInputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;


public class Exam_08 {

public static void main(String[] ar) throws IOException {

File f = new File("C://test/bbb.txt");

FileInputStream fis = new FileInputStream(f);

BufferedInputStream bis = new BufferedInputStream(fis, 1024);

DataInputStream dis = new DataInputStream(bis);

while(true) {

int x = dis.read();

if(x == -1) break;

System.out.print(x);

}

dis.close();

}

}


5) 예제5
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Exam_09 {
public static void main(String[] ar) throws IOException {
//System.out.println("test 안녕하세요...");
//FileOutputStream fos = new FileOutputStream(FileDescriptor.out);
// 출력 대상은 콘솔뿐만 아니라 웹, 서블릿, jsp에서 출력할 수 있는 객체가 OutputStreamWriter이다.
OutputStreamWriter osw = new OutputStreamWriter(System.out);
BufferedWriter bw = new BufferedWriter(osw, 1024);
// write 뿐만 아니라 다양한 메서드 사용할 수 있다.
PrintWriter pw = new PrintWriter(bw);
File f = new File("C://test/ccc.txt");
// 출력대상이 파일이다, 출력은 파일이 자동 생성되어 출력이 된다.
FileWriter fw = new FileWriter(f);
BufferedWriter bw1 = new BufferedWriter(fw, 1024);
PrintWriter pw1 = new PrintWriter(bw1);
pw.println(10);
pw.println("test 안녕하세요!");
pw1.println(10);
pw1.println("test 안녕하세요!");
pw.close();
pw1.close();
}
}


6) ★ 예제6★ 

import java.io.BufferedReader;

import java.io.File;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStreamReader;


public class Exam_10 {

public static void main(String[] ar) throws IOException {

InputStreamReader isr = new InputStreamReader(System.in);

BufferedReader br = new BufferedReader(isr);

// 한줄로 요약

//BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

File f = new File("C://test/ccc.txt");

FileReader fr = new FileReader(f);

BufferedReader br1 = new BufferedReader(fr);

System.out.print("문자열 = ");

String str = br.readLine();

System.out.println("str = " + str);

System.out.print("숫자 = ");

int x = Integer.parseInt(br.readLine());

System.out.println("x = " + x);

System.out.println("==================================");

while(true) {

// 개행하며 한 줄씩 읽어들이기

String s = br1.readLine();

if(s == null) break;

System.out.println("s = " + s);

}

br.close();

br1.close();

}

}



'Programing > Java' 카테고리의 다른 글

[Java] 쓰레드 기본  (0) 2014.12.21
[Java] 직렬화  (0) 2014.12.17
[Java] 문자 기반 스트림  (0) 2014.12.16
[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16

[Java] 문자 기반 스트림


문자 기반 스트림


1. 문자 기반 스트림

문자데이터를 다루는 데 사용된다는 것을 제외하고는 바이트기반 스트림과 문자기반 스트림의 사용방법은 거의 같다.

문자기반 스트림이라는 것이 단순히 2byte로 스트림을 처리하는 것만을 의미하지 않는다는 것이다. 


(1) FileReader와 FileWriter

 FileReader와 FileWriter는 파일로부터 텍스트 데이터를 읽고, 파일에 쓰는데 사용된다. 사용방법은 FileInputStream과 FileOutputStream과 다르지 않다.

import java.io.*;


class FileReaderEx1 {

public static void main(String args[]) {

try {

String fileName = "test.txt";

FileInputStream fis = new FileInputStream(fileName);

FileReader fr = new FileReader(fileName);


int data =0;

// FileInputStream을 이용해서 파일내용을 읽어 화면에 출력한다.

while((data=fis.read())!=-1) {

System.out.print((char)data);

}

System.out.println();

fis.close();


// FileReader를 이용해서 파일내용을 읽어 화면에 출력한다.

while((data=fr.read())!=-1) {

System.out.print((char)data);

}

System.out.println();

fr.close();


} catch (IOException e) {

e.printStackTrace();

}

} // main

}


2. 문자기반 보조 스트림

(1) BuffredReader와 BufferedWriter

BuffredReader와 BufferedWriter는 버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할을 한다. 버퍼를 이용하면 입출력의 효율이 비교할 수 없을 정도로 좋아지기 때문에 사용하는 것이 좋다.

BuffredReader의 readLine()을 사용하면 데이터를 라인단위로 읽어올 수 있다는 장점이 있다. 그리고 BufferedWriter는 newLine()라는 줄바꿈 해주는 메서드를 가지고 있다.


(2) InputStreamReader와 OutputStreamWriter

바이트 기반 스트림을 문자 기반 스트림으로 연결시켜주는 역할을 한다. 그리고 바이트기반 스트림의 데이터를 지정된 인코드의 문자 데이터로 변환하는 작업을 수행한다.

import java.io.*;


class InputStreamReaderEx 

{

public static void main(String[] args) 

{

String line = "";


try {

InputStreamReader isr = new InputStreamReader(System.in);

BufferedReader br = new BufferedReader(isr);


System.out.println("사용중인 OS의 인코딩 :" + isr.getEncoding());


do {

System.out.print("문장을 입력하세요. 마치시려면 q를 입력하세요. ");

line = br.readLine();

System.out.println("입력하신 문장 : "+line);

} while(!line.equalsIgnoreCase("q"));


// br.close();   // System.in과 같은 표준입출력은 닫지 않아도 된다.

System.out.println("프로그램을 종료합니다.");

} catch(IOException e) {}

} // main

}



'Programing > Java' 카테고리의 다른 글

[Java] 직렬화  (0) 2014.12.17
[Java] File 클래스  (0) 2014.12.16
[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14

[Java] 바이트 기반의 스트림


바이트 기반의 스트림


1. 바이트 기반 스트림

(1) InputStream과 OutputStream

InputStream과 OutputStream은 모든 바이트기반의 스트림의 조상이며 같은 메서드가 선언되어 있다.

cp.)

void close(): 스트림을 닫음으로써 사용하고 있던 자원을 반환한다.

abstract int read(): 1byte를 읽어온다.(0~255 사이의 값), 더 이상 읽어 올 데이터가 없으면 -1을 반환한다. abstract메서드라서 InputStream의 자손들은 자신의 상황에 알맞게 구현해야 한다.

int read(byte[] b): 배열 b의 크기만큼 읽어서 배열을 채우고 읽어 온 데이터의 수를 반환한다. 반환하는 값은 항상 배열의 크기보다 작거나 같다.

int read(byte[] b, int off, int len): 최대 len개의 byte를 읽어서, 배열 b의 지정된 위치(off)부터 저장한다. 실제로 읽어올 수 있는 데이터가 len개보다 적을 수 있다.

flush() 버퍼가 있는 출력 스트림의 경우에만 의미가 있으며, OutputSteam에 정의된 flush()는 아무런 일도 하지 않는다.

프로그램이 종료될 때, 사용하고 닫지 않은 스트림을 JVM이 자동적으로 닫아 주기는 하지만, 스트림을 사용해서 모든 작업을 마치고 난 후에는 close()를 호출해서 반드시 닫아 주어야 한다.

cf.) ByteArrayInputStream과 같이 메모리를 사용하는 스트림과 System.in, System.out과 같은 표준입출력은 닫아 주지 않아도 된다.


(2) FileInputStream과 FileOutputStream
FileInputStream과 FileOutputStreamdms 파일에 입출력을 하기 위한 스트림이다.


cp.) FileInputStream(File fileObj): 파일의 이름이 String이 아닌 File 인스턴스로 지정해주어야 하는 점을 제외하고 FileInputStream(String filePath)와 같다.


import java.io.*;


class FileViewer {
public static void main(String args[]) throws IOException{
FileInputStream fis = new FileInputStream(args[0]);
int data =0;
while((data=fis.read())!=-1) {
char c = (char)data;
System.out.print(c);
}
}
}

FileInputStream과 FileOutputStream을 사용해서 FileCopy.java파일의 내용을 그대로 복사하는 내용의 예제이다.
import java.io.*;

class FileCopy {
public static void main(String args[]) {
try {
FileInputStream fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[1]);

int data =0;

while((data=fis.read())!=-1) {
fos.write(data); // void write(int b)
}

fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}


import java.io.*;

class FileViewer {
public static void main(String args[]) throws IOException{
FileInputStream fis = new FileInputStream(args[0]);
int data =0;
while((data=fis.read())!=-1) {
char c = (char)data;
System.out.print(c);
}
}
}
read()의 반환값이 int형(4byte)이긴 하지만, 더 이상 입력값이 없음을 알리는 -1을 제외하고는 0~255(1byte)의 범위의 정수값이기 때문에, char형(2byte)으로 변환한다해도 손실되는 값이 없다.
read()가 한 번에 1byte씩 파일로부터 데이터를 읽어 들이긴 하지만, 데이터의 범위가 십진수로 0~255(16진수로는 0x00~0xff)범위의 정수값이고, 또 읽을 수 있는 입력값이 더 이상 없음을 알릴 수 있는 값도 필요하다.
그래서 다소 크긴 하지만 정수형 중에서는 연산이 가장 효율적이고 빠른 int형 값을 반환하도록 한 것이다.

2. 바이트기반의 보조스트림
(1) FilterInputStream과 FilterOutputStream

FilterInputStream과 FilterOutputStream은 InputStream/OutputStream의 자손이면서 모든 보조 스트림의 조상이다. 
보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반 스트림을 필요로 한다.

- FilterInputStream과 FilterOutputStream의 생성자이다.
Protected FilterInputStream(InputStream in)
public FilterOutputStream(OutputStream out)
FilterInputStream과 FilterOutputStream의 모든 메서드는 단순히 기반 스트림의 메서드를 그대로 호출할 뿐이다. FilterInputStream과 FilterOutputStream 자체로는 아무런 일도 하지 않음을 의미한다.
FilterInputStream과 FilterOutputStream 자체로는 아무런 일도 하지않고 상속을  통해 원하는 작업을 수행하도록 읽고 쓰는 메서드를 오버라이딩 해야 한다.

(2) BufferedInputStream과 BufferedOutputStream
BufferedInputStream과 BufferedOutputStream은 스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조 스트림이다. 한 바이트씩 입출력하는 것 보다 버퍼(바이트배열)를 사용해서 한 번에 여러 바이트를 입출력하는 것이 빠르기 때문에 대부분의 입출력 작업에 사용된다.

BufferedInputStream과 BufferedOutputStream의 버퍼크기는 입력소스로부터 한 번에 가져올 수 있는 데이터의 크기로 지정하면 좋다. 보통 입력소스가 파일인 경우 보통 작게는 1024부터 2048 또는 4096 정도의 크기로 하는 것이 보통이며, 버퍼의 크기를 변경해가면서 테스트하면 최적의 버퍼 크기를 알아낼 수 있다.

프로그램에서 입력 소스로부터 데이터를 읽기 위해 처음으로 read 메서드를 호출하면, BufferedInputStream은 입력 소스로 부터 버퍼 크기만큼의 데이터를 읽어다 자신의 내부 버퍼에 저장한다. 이제 프로그램에서는 
BufferedInputStream의 버퍼에 저장된 데이터를 읽으면 되는 것이다. 외부의 입력 소스로 부터 읽는 것보다 내부의 버퍼로 읽는 것이 훨씬 빠르기 때문에 그만큼 작업 효율이 높아진다.
프로그램에서 버퍼에 저장된 모든 데이터를 다 읽고 그 다음 데이터를 읽기 위해 read 메서드가 호출되면, BufferedInputStream은 입력 소스로부터 다시 버퍼 크기만큼의 데이터를 읽어다 버퍼에 저장해 놓는다.


BufferedOutputStream 역시 버퍼를 이용해서 작업을 하게 되는데, 입력 소스로부터 데이터를 읽을 때와는 반대로, 프로그램에서 write 메서드를 이용한 출력이 BufferedOutputStream의 버퍼에 저장된다. 
버퍼가 가득 차면, 그 때 버퍼의 모든 내용을 출력 소스에 출력한다.
버퍼가 가득 찼을때만 출력소스에 출력을 하기 때문에, 마지막 출력 부분이 출력소스에 쓰여지지 못하고, BufferedOutputStream의 버퍼에 남아있는 채로 프로그램이 종료될 수 있다는 점을 주의해야 한다.
그래서 프로그램에서 모든 출력 작업을 마친 후 BufferedOutputStream에 close()나 flush()를 호출해서 마지막에 버퍼에 있는 모든 내용이 출력소스에 출력되도록 해야 한다.

import java.io.*;

class BufferedOutputStreamEx1 {
public static void main(String args[]) {
try {
    FileOutputStream fos = new FileOutputStream("123.txt");      
             BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
     // BufferedOutputStream의 버퍼 크기를 5로 한다.
   
    for(int i='1'; i <= '9'; i++) {   
    bos.write(i);
     // 파일 123.txt에  1 부터 9까지 출력한다.
    }
    fos.close();
} catch (IOException e) {
    e.printStackTrace();
}
}
}
크기가 5인 BufferedOutputStream을 이용해서 파일 123.txt에 1부터 9까지 출력하는 예제인데 결과를 보면 5까지만 출력된 것을 알 수 있다. 그 이유는 버퍼에 남아있는 데이터가 출력되지 못한 상태로 프로그램이 종료되었기 때문이다.
이 예제에서 fos.close()를 호출해서 스트림을 닫아주기는 했지만, 이렇게 해서는 BufferedOutputStream의 버퍼에 있는 내용이 출력되지 않는다. bos.close();와 같이 해서 BufferedOutputStream의 close()를 호출해 주어야 버퍼에 남아있던 모든 내용이 출력된다. BufferedOutputStream의 close()는 기반 스트림인 FileOutputStream의 close()를 호출하기 때문에 FileOutputStream의 close()는 따로 호출해주지 않아도 된다.
--> 보조스크림을 사용한 경우에는 기반스트림의 close()나 flush()를 호출할 필요없이 단순히 보조스트림의 close()를 호출하기만 하면 된다.

(3) PrintStream
PrintStream은 데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공한다.
PrintStream은 데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행한다.
cf.) PrintStream은 우라기 지금까지 알게 모르게 많이 사용해 왔다. System 클래스의 static 멤버인 out과 err, 다시 말하자면 System.out, System.err이 PrintStream이다.


import java.util.Date;

class PrintStreamEx1 {
public static void main(String[] args) {
int i = 65;
float f = 1234.56789f;

Date d = new Date();

System.out.printf("문자 %c의 코드는 %d\n", i, i);
System.out.printf("%d는 8진수로 %o, 16진수로 %x\n", i ,i, i);
System.out.printf("%3d%3d%3d\n", 100, 90, 80);
System.out.println();
System.out.printf("123456789012345678901234567890\n");
System.out.printf("%s%-5s%5s\n", "123", "123", "123");
System.out.println();
System.out.printf("%-8.1f%8.1f %e\n",f,f,f);
System.out.println();
System.out.printf("오늘은 %tY년 %tm월 %td일 입니다.\n", d,d,d,d );
System.out.printf("지금은 %tH시 %tM분 %tS초 입니다.\n", d,d,d,d );
System.out.printf("지금은 %1$tH시 %1$tM분 %1$tS초 입니다.\n", d );
}
}
실행결과)
문자 A의 코드는 65
65는 8진수로 101, 16진수로 41
100 90 80

123456789012345678901234567890
123123    123

1234.6    1234.6 1.234568e+03

오늘은 2014년 04월 23일 입니다.
지금은 01시 50분 29초 입니다.
지금은 01시 50분 29초 입니다.



'Programing > Java' 카테고리의 다른 글

[Java] File 클래스  (0) 2014.12.16
[Java] 문자 기반 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14
[Java] 컬렉션 프레임워크  (0) 2014.12.14

[Java] 파일I/O 개요


파일I/O 개요


1. 입출력이란?

입출력이란 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말한다.

예를 들면 키보드로부터 데이터를 입력받는다든가 System.out.println()을 이용해서 화면에 출력한다던가 하는 것이 가장 기본적인 입출력의 예이다.


2. 스트림

자바에서 입출력을 수행하려면, 즉 어느 한쪽에서 다른 쪽으로 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이것을 스트림이라 한다.

스트림은 단방향 통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.

스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다. 큐와 같은 FIFO(First In First Out)구조로 되어 있다고 생각하면 쉬울 것이다.

cf.) 스트림은 TV와 VCR을 연결하는 입력선과 출력선과 같은 역할을 한다.

스트림이란 데이터를 운반하는데 사용된느 연결통로이다.


3. 바이트기반 스트림 - InputStream, OutputStream

스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라 다음과 같은 입출력 스트림이 있다.

스트림의 종류가 달라도 읽고 쓰는 방법은 동일하다.



입력스트림 

출력스트림 

입출력 대상의 종류 

 FileInputStream

 FileOutputStream

 파일 

 ByteArrayInputStream

 ByteArrayOutputStream 

 메모리(byte 배열) 

 PipedOnputStream

 PipedOutputStream 

 프로세스(프로세스간의 통신) 

 AudioInputStream

 AudioOutputStream 

 오디오 장치 


어떠한 대상에 대해서 작업을 할 것인지 그리고 입력을 할 것인지 출력을 할 것인지에 따라서 해당 스트림을 선택해서 사용하면 된다.

예를 들어, 어떤 파일의 내용을 읽고자 하는 경우 FileInputStream을 사용하면 될 것이다.

이들은 모두 InputStream 또는 OutputStream의 자손들이며, 각각 읽고 쓰는데 필요한 추상메서드를 자신에 맞게 구현해 놓았다.


※ InputStream과 OutputStream에 정의된 읽기와 쓰기를 수행하는 메서드

 InputStream

 OutputStream

 abstract int read() 

 abstract void write(int c

 int read(byte cbuf[]) 

 void write(byte cbuf[]) 

 int read(byte cbuf[], int offset, int length) 

 void write(byte cbuf[], int offset, int length) 

InputStream의 read()와 OutputStream의 write(int b)는 입출력의 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에 각 상황에 알맞게 구현하라는 의미에서 추상메소드로 정의되어 있다.


cf.) read()의 반환타입이 byte가 아니라 int인 이유는 read()의 반환값의 범위가 0~255와 -1이기 때문이다.


public abstract class InputStream{

...

abstract int read();

// 입력스트림으로부터 1byte를 읽어서 반환한다. 읽을 수 없으면 -1을 반환한다.

int read(byte[] b, int off, int len){

  // 입력스트림으로부터 ien개의 byte를 읽어서 byte배열 b의 off위치부터 저장한다.

...

for(int i-off, i < off+len; i++){

b[i]=(byte)read();

// read()를 호출해서 데이터를 읽어서 배열을 채운다.

}

...

}

int read(byte[] b){

  // 입력스트림으로부터 byte배열 b의 크기만큼 데이터를 읽어서 배열 b에 저장한다.

return read(b, 0, b.length);

}

...


4. 보조 스트림

스트림의 기능을 보완하기 위한 보조 스트림이 제공된다. 보조 스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능을 없지만. 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.그래서 보조 스트림만으로는 입출력 처리를 할 수 없고, 스트림을 먼저 생성한 다음에 이를 이용해서 보조 스트림을 생성해야 한다.

예를 들어 test.txt라는 파일을 읽기 위해 FileInputStream을 사용할 때, 입력 성능을 향상시키기 위해 버퍼를 사용하는 보조 스트림인 BufferedInputStream을 사용하는 코드는 다음과 같다.


// 먼저 기반스트림을 생성한다.FileInputStream은 InputStream의 자손 클래스이다.

FileInputStream fis = new FileInputStream("test.txt");

// 기반스트림을 이용해서 보조스트림을 생성한다.

BufferedInputStream bis = new BufferedInputStream(fis);

// 보조 스트림인 BufferedInputStream으로 부터 데이터를 읽는다.

bis.read();

코드 상으로는 보조스트림인 BufferedinputStream이 입력기능을 수행하는 것처럼 보이지만, 실제 입력기능은 BuffterdinputStream과 연결된 FileInputStream이 수행하고, 

보조스트림인 BufferedinputStream은 버퍼만을 제공한다.


모든 보조 스트림은 InputStream과 OutputStream의 자손들이므로 입출력 방법이 같다.


5. 문자기반 스트림

바이트 기반이라 함은 입출력의 단위가 1byte라는 뜻이다. 문자 데이터를 입출력 할 때는 바이트기반 스트림 대신 문자기반 스트림을 사용하자.

Java에서는 한 문자를 의미하는 char형이 1byte가 아니라 2 byte이기 때문에 바이트기반의 스트림으로 2byte인 문자를 처리하는데 어려움이 있다.

이 점을 보완하기 위해서 문자기반의 스트림이 제공된다. 문자데이터를 입출력할 때는 바이트기반 스트림 대신 문자기반 스트림을 사용하자.




'Programing > Java' 카테고리의 다른 글

[Java] 문자 기반 스트림  (0) 2014.12.16
[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14
[Java] 컬렉션 프레임워크  (0) 2014.12.14
[Java] static  (0) 2014.12.14

[Java] 제네릭

제네릭


제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다. 

즉, 클래스 내부에서 사용할 데이터 타입을 나중에 인스턴스를 생성할 때 확정하는 것을 제네릭이라 한다.

객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.

ArrayList와 같은 컬렉션 클래스는 다양한 종류의 객체를 담을 수 있긴 하지만 보통 한 종류의 객체를 담는 경우가 더 많다. 그런데도 꺼낼 때 마다 타입체크를 하고 형변환을 하는 것은 아무래도 불편할 수 밖에 없다.


위의 그림은 아래의 코드를 간략화한 것이다. 

class Person<T>{

    public T info;// p1 일시 데이터 타입은 String이된다.(인스턴스 생성시 String 제네릭 생성)

// p2 일시 데이터 타입은 StringBuilder이 된다.

}

 

public class GenericDemo {

 

    public static void main(String[] args) {

        Person<String> p1 = new Person<String>();

        Person<StringBuilder> p2 = new Person<StringBuilder>();

    }

}

p1.info와 p2.info의 데이터 타입은 결과적으로 아래와 같다.

- p1.info : String

- p2.info : StringBuilder

그것은 각각의 인스턴스를 생성할 때 사용한 <> 사이에 어떤 데이터 타입을 사용했느냐에 달려있다. 


클래스 선언부를 보자.

public T info;


클래스 Person의 필드 info의 데이터 타입은 T로 되어 있다. 그런데 T라는 데이터 타입은 존재하지 않는다. 이 값은 아래 코드의 T에서 정해진다.

class Person<T>{

위 코드의 T는 아래 코드의 <> 안에 지정된 데이터 타입에 의해서 결정된다. 


Person<String> p1 = new Person<String>();

위의 코드를 나눠보자. 아래 코드는 변수 p1의 데이터 타입을 정의하고 있다.


Person<String> p1

아래 코드는 인스턴스를 생성하고 있다. 


new Person<String>();

즉 클래스를 정의 할 때는 info의 데이터 타입을 확정하지 않고 인스턴스를 생성할 때 데이터 타입을 지정하는 기능의 제네릭이다. 


1. 제네릭의 장점

1) 타입 안정성을 제공한다.(타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다.)

2) 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다. 

간단히 얘기하면, 다룰 객체의 타입을 미리 명시해줌으로써 형변환을 하지 않아도 되게 하는 것이다.

기존에는 다양한 종류의 타입을 다루는 메서드의 매개변수나 리턴타입으로 Object 타입의 참조변수를 많이 사용했고, 그로 인해 형변환이 불가피했지만, 이젠 Object타입 대신 원하는 타입을 지정하기만 하면 되는 것이다.

(타입을 지정하지 않으면 Object 타입으로 간주된다.)


2. 컬렉션 클래스 이름 바로 뒤에 저장할 객체의 타입을 적어주면, 컬렉션에 저장할 수 있는 객체는 지정한 타입의 객체 뿐이다.

컬렉션클래스<저장할 객체의 타입> 변수명 = new 컬렉션클래스<저장할 객체의 타입>();

ArrayList<Tv> tvList = new ArrayList<Tv>();

// Tv객체만 저장할 수 있는 ArrayList를 생성

tvList.add(new Tv());

tvList.add(new Radio());// 컴파일 에러


3. 다형성을 사용해야 하는 경우에는 부모타입을 지정함으로써 여러 종류의 객체를 저장할 수 있다.

class Product{ }

class Tv extends Product{ }

class Audio extends Product{ }


//Product 클래스의 자손객체들을 저장할 수 있는 ArrayList를 생성

ArrayList<Product> list = new ArrayList<Product>();

list.add(new Product());

list.add(new Tv());

list.add(new Audio());


Product p = list.get(0);// 형변환이 필요없다.

Tv t = (Tv)list.get(i);// 형변환을 필요로 한다.

ArrayList가 Product타입의 객체를 저장하도록 지정하면, 이들의 자손인 Tv와 Audio타입의 객체도 저장할 수 있다. 다만 꺼내올 때 원래의 타입으로 형변환해야 한다.


4. Product 클래스가 Tv클래스의 조상이라 할지라도 아래와 같이는 할 수는 없다.

ArrayList<Product> list = new ArrayList<Tv>();//허용안함


List<Tv> tvList = new ArrayList<Tv>();// But, 허용된다.


5. 와일드카드

보통 제네릭에서는 단 하나의 타입을 지정하지만, 와일드 카드'?'를 사용하면 된다. 보통 제네릭에서는 단 하나의 타입을 지정하지만. 와일드 카드는 하나 이상의 타입을 지정하는 것을 가능하게 해준다.

아래와 같이 어떤 타입('?')이 있고 그 타입이 Product의 자손이라고 선언하면, Tv객체를 저장하는 'ArrayList<Tv>' 또는 Audio객체를 저장하는 'ArrayList<Audio>'를 매개변수로 넘겨줄 수 있다.

Tv와 Audio 모두 Product의 자손이기 때문이다.

public static void printAll(ArrayList<? extends Product> list){//Product 또는 그 자손들이 담긴 ArrayList를 매개변수로 받는 메서드

for(Unit u : list){

System.out.println(u);

}

}


6. 복수의 제네릭

클래스 내에서 여러개의 제네릭을 필요로 하는 경우가 있을 것이다. 예제를 보자.

class EmployeeInfo{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

}

class Person<T, S>{//복수의 제네릭을 사용할 시에는 ','를 사용한다.

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info; 

        this.id = id;

    }

}

public class GenericDemo {

    public static void main(String[] args) {

        Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int>(new EmployeeInfo(1), 1);

    }

}

위의 코드는 예외를 발생시키지만 문제는 다음 예제에서 처리하고 형식만 보자. 

즉, 복수의 제네릭을 사용할 때는 <T, S>와 같은 형식을 사용한다. 여기서 T와 S 대신 어떠한 문자를 사용해도 된다. 하지만 묵시적인 약속(convention)이 있기는 하다. 그럼 예제의 오류를 해결하자.


7. 기본 데이터 타입과 제네릭

제네릭은 참조 데이터 타입에 대해서만 사용할 수 있다. 기본 데이터 타입에서는 사용할 수 없다.(wrapper 클래스로 사용할 수 있다.--> 기본타입을 객체타입으로 만드는 것) 따라서 아래와 같이 코드를 변경한다.

class EmployeeInfo{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

}

class Person<T, S>{

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info;

        this.id = id;

    }

}

public class GenericDemo {

    public static void main(String[] args) {

        EmployeeInfo e = new EmployeeInfo(1);

        Integer i = new Integer(10);

        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

        System.out.println(p1.id.intValue());

    }

}

new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 래퍼(wrapper) 클래스라고 한다. 덕분에 기본 데이터 타입을 사용할 수 없는 제네릭에서 int를 사용할 수 있다.


8. 제네릭의 생략

제네릭은 생략 가능하다. 아래 두 개의 코드가 있다. 이 코드들은 정확히 동일하게 동작한다. e와 i의 데이터 타입을 알고 있기 때문이다.

EmployeeInfo e = new EmployeeInfo(1);

Integer i = new Integer(10);

Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

Person p2 = new Person(e, i);// 제네릭 생략함


9. 메소드에 적용

제네릭은 메소드에 적용할 수도 있다. 

class EmployeeInfo{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

}

class Person<T, S>{

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info;

        this.id = id;

    }

    public <U> void printInfo(U info){// U 데이터 타입은 info라는 매개변수의 데이터타입(EmployeeInfo)이 된다.

        System.out.println(info);

    }

}

public class GenericDemo {

    public static void main(String[] args) {

        EmployeeInfo e = new EmployeeInfo(1);

        Integer i = new Integer(10);

        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

        p1.<EmployeeInfo>printInfo(e);//

        p1.printInfo(e);// 제네릭 생략이 가능함

    }

}


10. 제네릭의 제한

(1) extends

제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다.

abstract class Info{//부모 클래스가 반드시 추상클래스일 필요가 없다.

    public abstract int getLevel();

}

class EmployeeInfo extends Info{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

    public int getLevel(){

        return this.rank;

    }

}

class Person<T extends Info>{// info 클래스나 info의 자식클래스만이 제네릭으로 올 수 있는 데이터 타입이 된다.(info의 자식이면 OK, 자식이 아니면 컴파일 에러)

    public T info;

    Person(T info){ this.info = info; }

}

public class GenericDemo {

    public static void main(String[] args) {

        Person p1 = new Person(new EmployeeInfo(1));

        Person<String> p2 = new Person<String>("부장");

    }

}

위의 코드에서 중요한 부분은 다음과 같다.


class Person<T extends Info>{

즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.


extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.

interface Info{

    int getLevel();

}

class EmployeeInfo implements Info{

    public int rank;

    EmployeeInfo(int rank){ this.rank = rank; }

    public int getLevel(){

        return this.rank;

    }

}

class Person<T extends Info>{// extends는 상속이 무엇인가가 아니라, 부모가 누군가를 확인하는 코드이다.(implement가 아니다.)

    public T info;

    Person(T info){ this.info = info; }

}

public class GenericDemo {

    public static void main(String[] args) {

        Person p1 = new Person(new EmployeeInfo(1));

        Person<String> p2 = new Person<String>("부장");

    }

}


'Programing > Java' 카테고리의 다른 글

[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 컬렉션 프레임워크  (0) 2014.12.14
[Java] static  (0) 2014.12.14
[Java] 클래스 메서드와 인스턴스 메서드  (0) 2014.12.14

[Java] 컬렉션 프레임워크


컬렉션 프레임워크



 인터페이스

 특 징

 List

순서가 있는 데이터의 집합, 데이터의 중복을 허용한다.

--> 데이터를 add하면 앞에서 부터 순차적(순서대로)으로 데이터가 들어간다. 

       그래서 각각의 저장되어 있는 공간들은 고유한 index를 갖는다. 

ex.) 대기자 명단

구현 클래스: ArrayList, LinkedList, Stack, Vector등

 

 Set

순서를 유지하지 않는 데이터의 집합, 데이터의 중복을 허용하지 않는다.

--> 집합이다. 데이터가 순서와는 상관없이 add된다. 중복되지 않는다.

ex.) 양의 정수 집합, 소수의 집합

구현 클래스: HashSet, TreeSet등

 Map

키와 값의 쌍으로 이루어진 데이터의 집합. 순서는 유지되지 않으며, 키는 중복을 허용하지 않고, 값을 중복을 허용한다.

ex.) 우편번호, 지역번호

구현 클래스: HashMap, TreeMap, Hashtable, Properties등


1. 컬렉션 프레임워크란

컬렉션즈 프래임워크라는 것은 다른 말로는 컨테이너라고도 부른다. 즉 값을 담는 그릇이라는 의미이다. 그런데 그 값의 성격에 따라서 컨테이너의 성격이 조금씩 달라진다. 자바에서는 다양한 상황에서 사용할 수 있는 다양한 컨테이너를 제공하는데 이것을 컬렉션즈 프래임워크라고 부른다. ArrayList는 그중의 하나다.


2. 배열과 비교

 배열은 연관된 데이터를 관리하기 위한 수단이었다. 그런데 배열에는 몇가지 불편한 점이 있었는데 그 중의 하나가 한번 정해진 배열의 크기를 변경할 수 없다는 점이다.(배열을 만들고 나서 추후에 배열의 크기를 바꿀수 없다.) 이러한 불편함을 컬렉션즈 프래임워크를 사용하면 줄어든다.


import java.util.ArrayList;

public class ArrayListDemo {

 

    public static void main(String[] args) {

        String[] arrayObj = new String[2];

        // 배열의 크기 지정--> 생성후 변경 불가

        arrayObj[0] = "one";

        arrayObj[1] = "two";

        // arrayObj[2] = "three"; 오류가 발생한다.

        // --> 배열의 크기 변경 불가 (배열은 배열의 크기를 알 때만 사용할 수 있다.)

        for(int i=0; i<arrayObj.length; i++){

            System.out.println(arrayObj[i]);

        }

         

        ArrayList<String> al = new ArrayList<String>(); // <Strinf>: al 참조변수에 추가되는 값이 String 데이터 타입이라는 것을 지정한다.

        // 배열과 비슷하나, 객체 생성후 몇개의 값을 사용할 지 지정할 필요가 없다.

        // --> 여기서 al 참조변수는 컨테이너라고 한다.(자료를 담는 그릇이 된다.) --> 아래의 코드는 add()를 이용하여 al에 one, two, Three를 넣은 모습이다.

        al.add("one");

        al.add("two");

        al.add("three");

        // 값을 추가해도 상관없다. outofbound 에러 없음

        for(int i=0; i<al.size(); i++){// 배열은 length

        // String value = al.get(i);// value 변수에 리턴되는 데이터타입은 ArrayList이므로 String으로 데이터타입 지정시 에러가 발생한다. 

        // 그래서 (String)al.get(i) 또는 ArrayList<String>을 객체 선언시 지정해야 한다. 

            System.out.println(al.get(i));//i=1이면 one, i=2이면 two, 제네릭을 통해서 (String)al.get(i)을 통해 형변환할 필요가 없다.

        }

    }

}

실행결과)

one

two

one

two

three

- ArrayList는 배열과는 사용방법이 조금 다르다. 배열의 경우 값의 개수를 구할 때 .length를 사용했지만 ArrayList는 메소드 size를 사용한다. 

또한, 특정한 값을 가져올 때 배열은 [인덱스 번호]를 사용했지만 컬렉션은 .get(인덱스 번호)를 사용한다.


3. 중복을 허용하는 List/ 중복 허용하지 않는 Set

자료를 꺼낼때 순서가 유지되는지 아닌지 주의있는 보자

import java.util.ArrayList;

import java.util.HashSet; 

import java.util.Iterator;

 

public class ListSetDemo {

 

    public static void main(String[] args) {

        ArrayList<String> al = new ArrayList<String>();

        al.add("one");

        al.add("two");

        al.add("two");

        al.add("three");

        al.add("three");

        al.add("five");

        System.out.println("array");

        Iterator<String> ai = al.iterator(); //<String>에서 제네릭을 선언하지 않았다면 (String)로 형변환을 해야 한다.

        while(ai.hasNext()){

            System.out.println(ai.next());

        }

         

        HashSet<String> hs = new HashSet<String>();

        hs.add("one");

        hs.add("two");

        hs.add("two");

        hs.add("three");

        hs.add("three");

        hs.add("five");

        Iterator<String> hi = hs.iterator();

        System.out.println("\nhashset");

        while(hi.hasNext()){

            System.out.println(hi.next());

        }

    }

}

실행결과)

array

one

two

two

three

three

five

 

hashset

two

five

one

three


4. Map

(1) 예제 1

import java.util.*;


public class MapDemo {

 

    public static void main(String[] args) {

        HashMap<String, Integer> a = new HashMap<String, Integer>();

        a.put("one", 1);// key, value

        a.put("two", 2);

        a.put("three", 3);

        a.put("four", 4);

        System.out.println(a.get("one"));

        System.out.println(a.get("two"));

        System.out.println(a.get("three"));

         

        iteratorUsingForEach(a);

        iteratorUsingIterator(a);

    }

    

    // Map 데이터 iterator 기능 없음 --> Map 데이터 가져오는 방법 1

    static void iteratorUsingForEach(HashMap<String, Integer> map){

        Set<Map.Entry<String, Integer>> entries = map.entrySet();

        for (Map.Entry<String, Integer> entry : entries) {

            System.out.println(entry.getKey() + " : " + entry.getValue());

            //Map 데이터를 가져오는 방법 --> getKey()=key값 ,getValue()=value값

        }

    }

    // Map 데이터 iterator 기능 없음 --> Map 데이터 가져오는 방법 2

    static void iteratorUsingIterator(HashMap<String, Integer> map){

        Set<Map.Entry<String, Integer>> entries = map.entrySet();

        Iterator<Map.Entry<String, Integer>> i = entries.iterator();

        while(i.hasNext()){

            Map.Entry<String, Integer> entry = i.next();

            System.out.println(entry.getKey()+" : "+entry.getValue());

        }

    }

}

Map은 iterator 기능이 없기 때문에 Map의 데이터를 가지고 있는 Set을 만들고 Set에 들어가 있는 데이터 타입은 Map.Entry이다.
(Set에 있는 값은 Map에 있는 값이 대응된다.--> Map에 있는 값을 getKey( ), getValue( )로 알아낸다.) 


(2) 예제2

import java.util.*;


class GenericsEx5

{

public static void main(String[] args) 

{

HashMap<String,Student> map = new HashMap<String,Student>();

map.put("1-1", new Student("자바왕",1,1,100,100,100));

map.put("1-2", new Student("자바짱",1,2,90,80,70));

map.put("2-1", new Student("홍길동",2,1,70,70,70));

map.put("2-2", new Student("전우치",2,2,90,90,90));

Student s1 = map.get("1-1");

System.out.println("1-1 :" + s1.name);

Iterator<String> itKey = map.keySet().iterator();

// key값만 가져오는 경우

while(itKey.hasNext()) {

System.out.println(itKey.next());

}


Iterator<Student> itValue = map.values().iterator();

int totalSum = 0;

// value값만 가져오는 경우

while(itValue.hasNext()) {

Student s = itValue.next();

System.out.println(s);

totalSum += s.total;

}


System.out.println("전체 총점:"+totalSum);

} // main

}


class Student extends Person implements Comparable<Person> { 

String name = ""; 

int ban = 0; 

int no = 0; 

int koreanScore = 0; 

int mathScore = 0; 

int englishScore = 0; 


int total = 0; 


Student(String name, int ban, int no, int koreanScore, int mathScore, int englishScore) { 

super(ban+"-"+no, name);

this.name = name; 

this.ban = ban; 

this.no = no; 

this.koreanScore = koreanScore; 

this.mathScore = mathScore; 

this.englishScore = englishScore; 


total = koreanScore + mathScore + englishScore; 


public String toString() { 

return name + "\t" 

+ ban + "\t" 

+ no + "\t" 

+ koreanScore + "\t" 

+ mathScore + "\t" 

+ englishScore + "\t" 

+ total + "\t"; 


   // Comparable<Person>이므로 Person타입의 매개변수를 선언.

public int compareTo(Person o) { 

  return id.compareTo(o.id);    // String클래스의 compareTo()를 호출

}


} // end of class Student 


class Person  {    

     String id;

     String name;


     Person(String id, String name) { 

         this.id = id;

         this.name = name; 

     }

}

실행결과)
1-1 :자바왕
2-2
1-1
2-1
1-2
전우치 2 2 90 90 90 270
자바왕 1 1 100 100 100 300
홍길동 2 1 70 70 70 210
자바짱 1 2 90 80 70 240
전체 총점:1020

5. Iterator

컬렉션 프레임워크에서 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화 하였다. 컬렉션에 저장된 각 요소에 접근하는 기능을 가진 Iterator인터페이스를 정의하고, Collection인터페이스에는 Iterator

(Iterator를 구현한 클래스의 인스턴스)를 반환하는 iterator()를 정의하고 있다.

public interface Iterator{

boolean hasNext();

Object next();

void remove();

}


public interface Collection{

...

public Iterator iterator();

...

}

iterator()는 Collection인터페이스에 정의된 메서드이므로 Collection인터페이스의 자손인 List와 Set에도 포함되어 있다.

그래서 List나 Set인터페이스를 구현하는 컬렉션은 iterator()가 각 컬렉션의 특징에 알맞게 작성되어 있다. 


List list = new ArrayList();


Iterator it = list.iterator();

while(it.hasNext()){

System.out.println(it.next());

}

ArrayList에 저장된 요소들은 출력하기 위한 코드이다.

ArrayList대신 List인터페이스를 구현한 다른 컬렉션 클래스에 대해서도 이와 동일한 코드를 사용할 수 있다. 첫 줄에서 ArrayList 대신 List인터페이스를 구현한 다른 컬렉션 클래스의 객체를 생성하도록 변경하기만 하면 된다.


Map map = new HashMap();

...

Iterator it = map.keySet().iterator();

Map 인터페이스를 구현한 컬렉션 클래스는 키와 값을 쌍을 저장하고 있기 때문에 iterator()를 직접 호출할 수 없고, 그 대신 ketSet()이나 entrySet()과 같은 메서드를 통해서 키와 값을 각각 따로 Set의 형태로 얻어 온 후에 다시 iterator()를 호출해야 Iterator를 얻을 수 있다.


(1) 예제1

import java.util.*;


class IteratorEx1{

public static void main(String[] args) {

ArrayList list = new ArrayList();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

list.add("5");


Iterator it = list.iterator();

while(it.hasNext()) {

Object obj = it.next();

System.out.println(obj);

}

} // main

} // class

실행 결과)

1

2

3

4

5

List 클래스들은 저장순서를 유지하기 때문에 Iterator를 이용해서 읽어 온 결과 역시 저장 순서와 동일하지만 Set클래스들은 각 요소간의 순서가 유지 되지 않기 때문에 Iterator를 이용해서 저장된 요소들은 읽어와도 처음에 저장된 순서와 같지 않다.


6. 정렬

import java.util.*;


class ArrayListEx1

{

    public static void main(String[] args) 

    {

        ArrayList<Integer> list1 = new ArrayList<Integer>(10);

        list1.add(new Integer(5));

        list1.add(new Integer(4));

        list1.add(new Integer(2));

        list1.add(new Integer(0));

        list1.add(new Integer(1));

        list1.add(new Integer(3));

 

        ArrayList list2 = new ArrayList(list1.subList(1,4)); 

        print(list1, list2);

 

        Collections.sort(list1);    // list1과 list2를 정렬한다.

        Collections.sort(list2);    // Collections.sort(List l)

        print(list1, list2);

 

        System.out.println("list1.containsAll(list2):"+ list1.containsAll(list2));

 

        list2.add("B");

        list2.add("C");

        list2.add(3, "A");

        print(list1, list2);

 

        list2.set(3, "AA");

        print(list1, list2);

         

        // list1에서 list2와 겹치는 부분만 남기고 나머지는 삭제한다.

        System.out.println("list1.retainAll(list2):" + list1.retainAll(list2)); 

        print(list1, list2);

         

        //  list2에서 list1에 포함된 객체들을 삭제한다.

        for(int i= list2.size()-1; i >= 0; i--) {

        //--> for문의 카운터를 0부터 증가시킨 것이 아니라, list2.size()-1 부터 감소시키면서 거꾸로 반복시켰다.

       //만약 카운터를 증가시켜가면서 삭제하면 한 요소가 삭제될 때마다 빈 공간을 채우기 위해 나머지 요소들이 자리이동을 하기 때문에 올바른 결과를 얻을 수 없다. 그래서 카운터를 감소시켜가면서

       //삭제를 해야 자리이동이 발생해도 영향을 받지 않고 작업이 가능하다.

            if(list1.contains(list2.get(i)))

                list2.remove(i);

        }

        print(list1, list2);

    } // main

 

    static void print(ArrayList list1, ArrayList list2) {

        System.out.println("list1:"+list1);

        System.out.println("list2:"+list2);

        System.out.println();       

    }

} // class

실행 결과)

list1:[5, 4, 2, 0, 1, 3] 

list2:[4, 2, 0]


list1:[0, 1, 2, 3, 4, 5] // Collections.sort(List1)을 이용해서 정렬하였다.(Collections는 클래스이다.)

list2:[0, 2, 4]


list1.containsAll(list2):true // list1이 list2의 모든 요소를 포함하고 있는 경우에만 true를 얻는다.

list1:[0, 1, 2, 3, 4, 5]

list2:[0, 2, 4, A, B, C]// add(Object obj)를 이용해서 새로운 객체를 저장하였다.


list1:[0, 1, 2, 3, 4, 5]

list2:[0, 2, 4, AA, B, C]// set(int index, Object obj)를 이용해서 저장된 객체를 변경하였다.


list1.retainAll(list2):true// retainAll의 작업결과로 list1에 변화가 있었으므로 true를 반환한다.

list1:[0, 2, 4]// list2와의 공통 요소 이외에는 모두 삭제되었다.(변화가 있었다.)

list2:[0, 2, 4, AA, B, C]


list1:[0, 2, 4]

list2:[AA, B, C]



'Programing > Java' 카테고리의 다른 글

[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14
[Java] static  (0) 2014.12.14
[Java] 클래스 메서드와 인스턴스 메서드  (0) 2014.12.14
[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출  (0) 2014.12.14

[Java] static


static

static이라는 의미는 ‘정적인, 움직이지 않는다.’는 뜻이다. 메모리에서 고정되기 때문에 붙은 이름이지만, 실제 소스에서 static을 사용한다는 의미는 모든 객체가 ‘공유’한다는 의미이다.

cf.) 객체지향이라는 패러다임이란 데이터와 기능(로직, 메소드)을 가진 객체들의 커뮤니케이션으로 어떤 작업을 완료하는 것을 의미한다. 


cf.) 객체마다 데이터를 가져도 불편한 때도 있다

예를 들어 여러분이 어떤 쇼핑몰을 운영한다고 가정해보자. 여러분의 시스템에서 발생하는 매출 현황은 비단 여러분뿐 아니라 여러분의 회사의 모든 직원이 알아야 한다.

이 비유는 객체지향 프로그래밍에서도 마찬가지로 적용할 수 있다.

1 모든 객체가 동일한 데이터를 참고해야 할 필요가 있다.

2 모든 객체는 데이터에 영향을 줄 수 있다.


1. static 중요 법칙

1 static이 붙은 변수들은 객체들이 다 같이 공유하는 데이터를 의미한다.

2 static이 붙은 메소드들은 객체들의 데이터와 관계없는 완벽하게 공통적인 로직을 정의할 때 사용한다.

3 따라서 static 메소드에서는 인스턴스 변수나 객체의 메소드를 사용할 수 없다.


2. static을 사용하는 기준

(1) 객체가 데이터를 보관할 필요가 없다면 static 메소드로 만들어도 된다.

ex.) 

    Random random = new Random( ); ①

    double a = Math.ramdom( ); ②

둘다 마찬가지로 임의의 소수를 발생시킨다.

①은 객체가 데이터를 가지기 때문에 동일한 메소드를 호출해도 다른 결과를 만들어 낼 수 있다는 것을 의미한다.(Random 클래스는 객체를 생성할 때 데이터를 가지게 할 수 있다.)

②Math.random( )은 특정한 데이터를 기준으로 하는 것이 아니라, 완전히 매번 동일한 방법으로 동작하게 된다. 결론적으로 어떤 메소드가 완전히 객체의 데이터와 독립적인 결과를 만들어내야 하는 상황에서는 static 메소드로 만드는 것이 더 나은 선택이다.


(2) 객체들이 공유하는 데이터를 사용할 때에도 static 메소드를 이용한다.

static이라는 키워드가 붙은 존재는 객체와 무관해지기 위해서 사용한다. 그런데 static한 메소드에서 어떤 특정한 객체의 데이터를 활용하게 되면 몇 가지 문제가 생긴다.

만일 static한 공통된 기능의 메소드에서 어떤 하나의 객체의 데이터를 사용하게 된다면 대체 어떤 객체의 데이터를 활용해야 할까?

static이 붙으면 객체와는 무관해진다. 따라서 그 안에서 특정한 하나의 객체의 데이터를 활용하게 되면 문제가 되는데, 가장 근본적인 것은 만들어진 수많은 객체 중에서 어떤 객체의 데이터를 활용해야 할지 알 수 없게 된다는 점이다. 이런 이유 때문에 static이 붙은 메소드 안에서는 인스턴스 변수를 활용할 수 없도록 컴파일러가 확인하게 된다. 


3. static의 의미 

(1) 공유

어떤 변수에 static을 붙이게 되면 이 변수는 모든 객체가 다 같이 공유하는 변수가 된다. (static이 붙은 변수를 사용하는 모든 객체는 공유된 데이터를 사용한다.)

위와 같은 문제에서 어떤 데이터를 모든 객체가 같이 사용하게 하고 싶다면 static이라는 키워드가 바로 방법이다. 

ex.) 은행에 있는 여러개의 번호표 → 각 기계(객체)마다 각각의 번호표를 사용한다면 1번 번호표를 가진 고객들은 서로 자신이 1번이라고 주장하는 웃지 못할 상황이 발생한다.

이 문제의 근본적인 문제는 각 객체가 자신만의 데이터를 갖는다는 것에 있다. 이 문제를 해결하기 위한 가장 손쉬운 해결책 역시 모든 객체가 같은 데이터를 공유할 수 있게 하면 된다.

→ static은 어떤 변수를 모든 객체가 공유하는 값으로 변경해버리기 때문에 아무리 많은 객체가 있더라도 같은 데이터를 사용하게 하는 공유의 개념을 완성할 수 있다.


(2) 객체와 무관한 메소드와 함수라는 개념

static이 붙은 메소드는 모든 객체가 공유하는 메소드이다.(static이 붙은 변수도 모든 객체가 공유)

모든 객체가 자신만의 데이터를 가지고 있으니 발생되는 문제처럼 메소드 역시 객체의 데이터의 의해서 좌우되는 상황을 벗어나기 위해서 Java에서의 static은 메소드에서도 적용할 수 있게 된다.

1) static 메소드는 결과적으로 모든 객체가 완벽하게 똑같이 동작하는 메소드를 위해서 사용

클래스에서 나온 객체의 데이터에 영향받지 않고 완벽히 동일하게 동작하는 메소드를 만들고 싶다면 static을 이용해서 객체의 메소드를 처리하면 된다. 메소드 앞에 static이 붙으면 모든 객체가 클래스에 정의된 방법대로만 사용하게 된다. 이 문제는 좀 더 현실적인 해결책으로 생각해보면 객체가 가진 데이터의 의해서 좌우되고 싶지 않다는 뜻이다.

객체가 어떤 데이터를 가지든 객체의 데이터의 영향 없이 동작하게 만든다면 언제나 완전하게 동일하게 동작하는 것을 보장해줄 수 있다.

2) Integer.parseInt( )는 static 메소드이다.

이 메소드는 언제 어디서나 동일하게 동작한다는 것이다. 이 메소드 자체가 어는 상황에서든 객체의 데이터에 의해서 영향받지 않고 같은 기능만을 제공한다.

→ static 메소드는 마치 C언어의 함수와 유사한다. 완벽하게 언제나 동일하게 동작하는 것이 보장되기 때문에 사용자는 객체의 데이터를 신경 쓰지 않아도 된다. 이런 의미에서 보면 static메소드라는 것은 객체가 가지는 메소드라기 보다는 클래스 자체의 메소드라는 의미로 생각해볼 수 있다.


(3) 단 한번만 동작하게 하는 static

static이 실행되는 시점은 클래스가 메모리상에 올라갈 때이다. 즉 우리가 프로그램을 실행하면 필요한 클래스가 메소리상에 로딩되는 과정을 거치게 된다. 그리고 한번 로딩된 클래스는 특별한 일이 발생하지 않는 이상 메모리상에서 객체를 생성할 수 있도록 메모리에 상주하게 된다. 

static 은 바로 이 시점에 동작한다. 클래스가 메모리상에 올라가자마자 실행되면서 필요한 처리를 하게 된다. 따라서 static 은 객체의 생성과는 관계없이 클래스가 로딩되는 시점에 단 한 번만 실행하고 싶은 처리를 하기 위해서 사용한다.


4. static이 붙은 변수가 공유 되는 원리

cf.) static이 붙은 변수는 객체의 레퍼런스를 이용해서 사용하는 일반적인 객체지향 프로그래밍과는 달리 클래스의 변수이기 때문에 그냥 클래스 ‘이름, 클래스’변수라는 방식으로 사용하게 된다.(new해서 객체생성 불요)

(1) static이 붙은 변수를 클래스 변수라고 부른다.

모든 객체는 자신이 어디에서 생산되었는지 해당 클래스를 알 수 있다. 모든 객체가 자신이 속한 클래스를 알 수 있다는 것을 다른 말로 하면 모든 객체는 자신이 속한 클래스의 정보를 공유한다는 뜻이다.

사실 static의 비밀은 바로 이곳에 있다. static의 가장 중요한 내용은 객체와 묶이는 데이터가 아니라 클래스와 묶이는 데이터라는 것이다.

static을 사용하는 변수는 객체가 아닌 클래스의 변수이기 때문에 별도의 사용할 필요 없이 그냥 사용할 수 있다.

(2) JVM의 구조(java의 메모리 구조)

1) Heap 영역(객체 영역)

객체의 영역이란 객체들이 만들어지고 살다가 죽는 영역이다. 이 영역에서 가장 중요한 존재는 다름 아닌 가비지 컬렉터이다. 

2) Method Area(비객체 영역)

비객체 영역인 Method Area는 클래스가 메모리상에 올라가는 영역이다. 이 영역은 가비지 컬렉터의 영향을 받지 않고 메모리에 상주한다. 간단히 생각해보면 모든 객체는 클래스에서 나오는데 이 클래스가 나도 모르게 메소리상에서 지워지면 난감한 일이 발생할 수 있다. 비객체 영역은 가비지 컬렉터의 영향으로부터 자유롭고, 메모리에 상주하게 되어 있는데 이런 상주의 의미를 ‘static'이라는 뜻으로 사용된다.

(3) static이 붙은 부분은 클래스가 메모리상에 로딩되면서 같이 올라간다.

static 키워드가 붙으면 메소리상에서 다르게 처리된다는 것이다. static이 붙은 변수를 클래스 변수라고 하는 것은 변수가 존재하는 영역이 클래스가 존재하는 영역과 같기 때문이다.

Java에서 모든 객체는 결과적으로는 동일한 클래스에서부터 생성되기 때문에 동일한 클래스에서 나온 모든 객체는 자신이 속한 클래스에 대해서 접근할 수 있다.

static 변수는 이런 방식을 통해서 모든 객체가 동일한 데이터를 사용할 수 있는 공유의 개념을 완성시킨다.


cp.) Tip

- 객체의 영향을 받지 않기 때문에 static은 굳이 객체를 통해서 사용할 이유가 없다.

어떤 변수나 메소드 앞에 static이 나온다는 의미는 결과적으로 객체의 데이터나 메소드의 영향을 받지 않고 완벽하게 동일한 공유의 개념을 완성하는 것이다.

따라서 굳이 객페의 레퍼런스를 이용해야 하는 필요성이 떨어진다. 어차피 객체보다는 클래스와 더 가깝기 때문이다.


→ static 클래스 이름. 함수(메소드)

static은 객체마다 다른 데이터를 가지고 동작하는 것을 막고 완벽하게 동일하게 동작하는 것을 보장하기 위해서 사용하기 때문에 굳이 객체를 사용해야 하는 이유가 없으므로 바로 ‘클래스 이름. 변수’나 ‘클래스 이름. 메소드’를 사용할 수 있다.

굳이 객체를 사용하지 않아도 된다는 의미는 객체를 사용하는 기존의 방식도 가능하기는 하다는 뜻이다.


- static이 붙은 메소드를 보면 무조건 ‘객체와 무관하다.’혹은 ‘객체의 영향 받지 않는 메소드’이다.라고 생각하자.

모든 경우에 완벽하게 동일하게 동작해주어야만 한다. 이런 상황에서는 static을 이용해서 경우에 따라서 다르게 동작할 수 없게 하는 것이 정상적인 static의 사용법이다.

→ 실무에서 static을 이용할 것인지 아닌지를 판단하는 데 있어서 가장 중요한 것은 객체마다 데이터를 활용해야 하는 경우인가. 그렇지 않은가 이다.

Integer.parseInt(),Math.random()을 보면 객체가 가진 데이터와는 완전히 무관하다. 그냥 단순히 로직을 묶어둔 존재에 가깝다.


- static은 속도는 빠르지만, 회수되지 않기 때문에 주의 요한다.

static이라는 키워드가 붙은 변수는 가비지 컬렉션의 대상이 아니라는 데에서 시작한다.

static을 클래스 변수라고 말하듯이 static이 붙은 변수는 클래스와 같은 영역에 생기고, 클래스와 동일하게 메모리에서 항상 상주하게 된다. 이 때문에 static으로 어떤 데이터나 객체를 연결해서 사용하게 되면 메모리 회수가 안된다. 실제로 시스템이 가동되면서 점점 느려진다.


5. 예제

class Exam_02_Sub {

private String name;

private double don = 1000.0;

private static float iyul = 0.05f;

// static 초기화 구문(static 초기화: 객체 발생 이전)

static {

iyul = 0.05f;

 // 보통 생성자를 선언하여 초기화 하는 것과 같은 구문이다.

// iyul이라는 공간을 0.05f로 초기화 및 선언한다.

// 이렇게 static 선언하면 객체 생성을 하지 않고도 iyul을 사용할 수 있다.

}

// static 메서드: static 필드 컨트롤 목적 즉, static 필드 초기화 및 수정하게 할 목적인 것이다.

public static void setIyul(float iyul) {

Exam_02_Sub.iyul = iyul;

// this.iyul=iyul; this를 넣을 수 없다. static에는 0번째 매개변수가 없다. 

// 왜냐하면 static메서드는 모든 객체가 공유하는 메서드이므로 자기자신이라는 것은 있을 수 없다.

// static 메서드는 this.disp();를 할 수 없다. -->this들어가는 것은 static에서 무조건 쓸 수 없다.

// static 메서드 안에서 일반 메서드를 호출하려면 객체 생성를 해야 한다. 바로 다이렉트로 일반 메서드를 사용할 수 없다.

Exam_02_Sub es = new Exam_02_Sub("C", 1000.0, 0.05f);

es.disp();

}

public Exam_02_Sub(String name, double don, float iyul) {

this.name = name;

this.don = don;

Exam_02_Sub.iyul = iyul;

// 같은 클래스 내부에서는 일반 메서드끼리는 호출이 가능하다.

// this.disp();

}

public void disp() {

System.out.println("name : " + this.name);

System.out.println("don : " + this.don);

// 일반 메서드에서 static을 다이렉트로 호출이 가능하다.

System.out.println("iyul : " + Exam_02_Sub.iyul);

}

public class Exam_02 {

// 일반 메서드를 main 메서드 안에서 호출하려면 static 메서드로 만들어야 한다. 그렇지 않으면 객체생성후 메서드 호출 요한다.

// 일반 메서드로 만들면 this 때문에 static 메서드에서 호출할 수 없다.

public static void main(String[] ar) {

Exam_02_Sub es = new Exam_02_Sub("A", 1000.0, 0.07f);

es.disp();

System.out.println();

Exam_02_Sub es1 = new Exam_02_Sub("B", 1000.0, 0.03f);

es.disp();

es1.disp();

System.out.println();

Exam_02_Sub es2 = new Exam_02_Sub("C", 1000.0, 0.05f);

es.disp();

es1.disp();

es2.disp();

}

}


class Exam_02_Sub_01 {

private String name;

private double don = 1000.0;

private static float iyul = 0.05f;

//private float iyul = 0.05f;

public Exam_02_Sub_01(String name, double don, float iyul) {

this.name = name;

this.don = don;

Exam_02_Sub_01.iyul = iyul;

}

public void disp() {

System.out.println("name : " + this.name);

System.out.println("don : " + this.don);

System.out.println("iyul : " + Exam_02_Sub_01.iyul);

}

}

public class Exam_02_01 {

public static void main(String[] ar) {

Exam_02_Sub_01 es = new Exam_02_Sub_01("A", 1000.0, 0.07f);

es.disp();

System.out.println("-------------");

Exam_02_Sub_01 es1 = new Exam_02_Sub_01("B", 1000.0, 0.03f);

es.disp();

es1.disp();

System.out.println("-------------");

Exam_02_Sub_01 es2 = new Exam_02_Sub_01("C", 1000.0, 0.05f);

es.disp();

es1.disp();

es2.disp();

}

}



[Java] 클래스 메서드와 인스턴스 메서드


클래스 메서드와 인스턴스 메서드


클래스메서드도 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능하다.

클래스는 데이터(변수)와 데이터에 관련된 메서드의 집합이라고 할 수 있다. 

같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다.

인스턴스메서드는 인스턴스변수와 관련된 작업을 하는, 즉, 메서드의 작업을 수행하는데 인스턴스변수를 필요로 하는 메서드이다. 그래서 인스턴스변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스변수를 사용하는 인스턴스메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다. 반면에 메서드 중에서 인스턴스와 관계없는(인스턴스변수나 인스턴스메서드를 사용하지 않는) 메서드를 클래스메서드로 정의한다.

cf.) 멤버변수는 인스턴스변수와 static변수를 모두 통칭하는 말이다. -->정확히 인스턴스변수라고 표현하자.


1) 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야하는 것에 static을 붙인다.

생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수는 서로 다른 값을 유지한다. 그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스변수로 정의해야 한다.


2) 클래스변수(static 변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.

static이 붙은 변수는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.


3) 클래스메서드는 인스턴스변수를 사용할 수 없다.

인스턴스변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데,  클래스메서드는 인스턴스 생성 없이 호출가능하므로 클래스메서드가 호출되었을 때 인스턴스가 존재할 수도 있고, 존재하지 않을 수도 있다.

그래서 클래스메서드에서 인스턴스변수의 사용을 금지한다. 그러나, 인스턴스변수나 인스턴스메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다. 인스턴스변수가 존재한다는 것은 static이 붙은 변수가 이미 메모리에 존재한다는 것을 의미하기 때문이다.


4) 메서드 내에서 인스턴스변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.

메서드의 작업내용 중에서 인스턴스변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스변수를 필요로 하지 않는다면 static을 붙이자. 메서드 호출시간이 짧아지기 때문에 효율이 높아진다.

인스턴스 메서드는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.


cf.) random()과 같은 Math클래스의 모든 메서드는 클래스메서드이다. Math 클래스에는 인스턴스변수가 하나도 없거니와 작업을 수행하는데 필요한 값들을 모두 매개변수로 받아서 처리하기 때문이다.


class MyMath2 {

long a, b;

// 인스턴스변수 a, b만을 이용해서 작업하므로 매개변수가 필요없다.

long add() { return a + b; } // a, b는 인스턴스변수

long subtract() { return a - b; }

long multiply() { return a * b; }

double divide() { return a / b; }


// 인스턴스변수와 관계없이 매개변수만으로 작업이 가능하다.

static long add(long a, long b) { return a + b; }     // a, b는 지역변수

static long subtract(long a, long b) { return a - b; }

static long multiply(long a, long b) { return a * b; }

static double divide(double a, double b) { return a / b; }

}


class MyMathTest2 {

public static void main(String args[]) {

// 클래스메서드 호출(객체생성없이 매개변수만으로 작업 수행)

System.out.println(MyMath2.add(200L, 100L));

System.out.println(MyMath2.subtract(200L, 100L));

System.out.println(MyMath2.multiply(200L, 100L));

System.out.println(MyMath2.divide(200.0, 100.0));


MyMath2 mm = new MyMath2();

mm.a = 200L;

mm.b = 100L;

// 인스턴스메서드는 객체생성 후에만 호출이 가능함.

System.out.println(mm.add());

System.out.println(mm.subtract());

System.out.println(mm.multiply());

System.out.println(mm.divide());

}

}

실행결과)

300

100

20000

2.0

30

100

20000

2.0




'Programing > Java' 카테고리의 다른 글

[Java] 컬렉션 프레임워크  (0) 2014.12.14
[Java] static  (0) 2014.12.14
[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출  (0) 2014.12.14
[Java] 변수의 종류  (0) 2014.12.14
[Java] abstract  (0) 2014.12.11

[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출


클래스멤버와 인스턴스멤버간의 참조와 호출


같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스멤버가 인스턴스멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.

그 이유는 인스턴스멤버가 존재하는 시점에 클래스멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스멤버가 존재할 수도 있고 존재하지 않을 수도 있기 때문이다.


class TestClass{

void instanceMethod(){} //인스턴스 메서드

static void staticMethod(){} // static 메서드

void instanceMethod2(){ //인스턴스 메서드

instanceMethod();//다른 인스턴스메서드를 호출한다.

staticMethod();// static 메서드를 호출한다.

}

static void staticMethod2(){//static 메서드

instanceMethod();// 에러!! 인스턴스메서드를 호출할 수 없다. static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.

staticMethod();// static 메서드는 호출할 수 있다.

}

}// end of class;


같은 클래스 내의 메서드는 서로 객체의 생성이나 참조변수 없이 직접 호출이 가능하지만 static 메서드는 인스턴스 메서드를 호출할 수 없다.


class TestClass2{

int iv;// 인스턴스변수

static int cv;// 클래스 변수


void instanceMethod(){// 인스턴스메서드

System.out.println(iv);// 인스턴스 변수를 사용할 수 있다.

System.out.println(cv);// 클래스변수를 사용할 수 있다.

}


static void staticMethod(){// static 메서드

System.out.println(iv);// 에러! 인스턴스 변수를 사용할 수 없다.

System.out.println(cv);//  클래스 변수를 사용할 수 있다.

}

}// end of class

메서드간의 호출과 마찬가지로 인스턴스메서드는 인스턴스변수를 사용할 수 있지만, static 메서드는 인스턴스변수를 사용할 수 없다.


class MemberCall {

int iv = 10;

static int cv = 20;


int iv2 = cv;

        // static int cv2 = iv;// 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음.

static int cv2 = new MemberCall().iv;// 이처럼 객체를 생성해야 사용가능.


static void staticMethod1() {

System.out.println(cv);

    // System.out.println(iv); // 에러. 클래스메서드에서 인스턴스변수를 사용불가.

MemberCall c = new MemberCall();

System.out.println(c.iv);  // 객체를 생성한 후에야 인스턴스변수의 참조가능.

}


void instanceMethod1() {

System.out.println(cv);

System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수를 바로 사용가능.

}


static void staticMethod2() {

staticMethod1();

    // instanceMethod1(); // 에러. 클래스메서드에서는 인스턴스메서드를 호출할 수 없음.

MemberCall c = new MemberCall();

c.instanceMethod1(); // 인스턴스를 생성한 후에야 호출할 수 있음.

  }

void instanceMethod2() {// 인스턴스메서드에서는 인스턴스메서드와 클래스메서드

staticMethod1();//  모두 인스턴스 생성없이 바로 호출이 가능하다.

instanceMethod1();

}

}

클래스 멤버(클래스변수와 클래스메서드)는 언제나 참조 또는 호출이 가능하기 때문에 인스턴스멤버가 클래스멤버를 사용하는 것은 아무런 문제가 안된다.

클래스멤버간의 참조 또는 호출 역시 아무런 문제가 없다.

그러나, 인스턴스멤버(인스턴스변수와 인스턴스메서드)는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에 클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성하여야 한다.

하지만, 인스턴스멤버간의 호출에는 아무런 문제가 없다. 하나의 인스턴스멤버가 존재한다는 것은 인스턴스가 이미 생성되어 있다는 것을 의미하며, 즉 다른 인스턴스멤버들도 모두 존재하기 때문이다.


cf.) MemerCall c = new MemberCall();

      int result = c. instanceMethod(); 


      int result = new MemberCall().instanceMethod(); 

      --> 위의 두 줄을 다음과 같이 한 줄로 할 수 있다.



1) 인스턴스 메소드는 클래스 맴버에 접근 할 수 있다.


2) 클래스 메소드는 인스턴스 맴버에 접근 할 수 없다. 


인스턴스 변수는 인스턴스가 만들어지면서 생성되는데, 클래스 메소드는 인스턴스가 생성되기 전에 만들어지기 때문에 클래스 메소드가 인스턴스 맴버에 접근하는 것은 존재하지 않는 인스턴스 변수에 접근하는 것과 같다.

class C1{

    static int static_variable = 1;

    int instance_variable = 2;

    static void static_static(){

        System.out.println(static_variable);

    }

    static void static_instance(){

        // 클래스 메소드에서는 인스턴스 변수에 접근 할 수 없다. 

        //System.out.println(instance_variable);

    }

    void instance_static(){

        // 인스턴스 메소드에서는 클래스 변수에 접근 할 수 있다.

        System.out.println(static_variable);

    }

    void instance_instance(){        

        System.out.println(instance_variable);

    }

}

public class ClassMemberDemo {  

    public static void main(String[] args) {

        C1 c = new C1();

        // 인스턴스를 이용해서 정적 메소드에 접근 -> 성공

        // 인스턴스 메소드가 정적 변수에 접근 -> 성공

        c.static_static();

        // 인스턴스를 이용해서 정적 메소드에 접근 -> 성공

        // 정적 메소드가 인스턴스 변수에 접근 -> 실패

        c.static_instance();

        // 인스턴스를 이용해서 인스턴스 메소드에 접근 -> 성공

        // 인스턴스 메소드가 클래스 변수에 접근 -> 성공

        c.instance_static();

        // 인스턴스를 이용해서 인스턴스 메소드에 접근 -> 성공 

        // 인스턴스 메소드가 인스턴스 변수에 접근 -> 성공

        c.instance_instance();

        // 클래스를 이용해서 클래스 메소드에 접근 -> 성공

        // 클래스 메소드가 클래스 변수에 접근 -> 성공

        C1.static_static();

        // 클래스를 이용해서 클래스 메소드에 접근 -> 성공

        // 클래스 메소드가 인스턴스 변수에 접근 -> 실패

        C1.static_instance();

        // 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패

        //C1.instance_static();

        // 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패

        //C1.instance_instance();

    } 

}

실행결과)

1

1

2

1



'Programing > Java' 카테고리의 다른 글

[Java] static  (0) 2014.12.14
[Java] 클래스 메서드와 인스턴스 메서드  (0) 2014.12.14
[Java] 변수의 종류  (0) 2014.12.14
[Java] abstract  (0) 2014.12.11
[Java] 인터페이스의 이해  (0) 2014.12.06

[Java] 변수의 종류


변수의 종류


변수의 종류 

선언위치 

생성시기 

클래스 변수 

클래스 영역 

클래스가 메모리에 올라갈 때 

인스턴스 변수 

클래스 영역 

인스턴스가 생성되었을 때 

 지역 변수

클래스 영역 이외의 블럭(메서드, 생성자, 초기화 블럭 내부) 

변수 선언문이 수행되었을 때 


1. 인스턴스 변수

클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다. 그렇기 때문에 인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야 한다.

인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다. 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스변수로 선언한다.


2. 클래스 변수

클래스 변수를 선언하는 방법은 인스턴스 변수 앞에 static을 붙이기만 하면 된다. 인스턴스마다 독립적인 저장공간을 갖는 인스턴스변수와는 달리, 클래스변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다. 그래서 클래스 변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다. 그래서 클래스 변수를 공유 변수라고도 한다. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스변수로 선언해야 한다. 인스턴스 변수는 인스턴스를 생성한 후에야 사용가능하지만, 클래스 변수를 인스턴스를 생성하지 않고도 언제라고 바로 사용할 수 있다.

'클래스이름.클래스변수'와 같은 형식으로 사용한다.

클래스가 로딩될 때 생성되어  프로그램이 종료될 때 까지 유지되며, public을 앞에 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 전역변수의 성격을 갖는다.


3. 지역변수

메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다.

for문 또는 while문의 블러 내에 선언된 지역변수는, 지역변수가 선언된 블럭{} 내에서만 사용 가능하며, 블럭{}을 벗어나면 소멸되어 사용할 수 없게 된다.


4. 예제

클래스 변수와 인스턴스 변수


class Card{

String kind;// 카드의 무늬(인스턴스 변수)

int number;// 카드의 숫자(인스턴스 변수)

static int width;// 카드의 폭(클래스 변수)

static int weight;// 카드의 높이(클래스 변수)

}


각 Card인스턴스의 자신만의 무늬와 숫자를 유지하고 있어야 하므로 이들을 인스턴스 변수로 선언

각 카드의 폭과 높이는 모든 인스턴스가 공통적으로 같은 값을 유지해야하므로 클래스 변수로 선언

카드의 폭을 변경할 필요가 있을 경우, 모든 카드의 width값을 변경하지 않고 한 카드의 width의 값만 변경해도 모든 카드의 width의 값이 변경되는 셈이다.

class CardTest{

public static void main(String argd[]){

System.out.println("Card.width="+Card.width);

System.out.println("Card.height="+Card.height);

// 클래스 변수는 객체 생성 없이 ‘클래스이름.클래스변수’로 직접 사용 가능하다.

Card c1 = new Card();

c1.kind = "Heart";

c1.number = 7;

Card.c2 = new Card();

c2.kind = "Spade";

c2.number = 4;// 인스턴스변수의 값을 변경한다.

System.out.println("c1은“+c1.kind+","+c1.number+"이며, 크기는 ("c1.width+","+c1.height+")");

System.out.println("c2은“+c2.kind+","+c2.number+"이며, 크기는  ("c2.width+","+c2.height+")");

System.out.println("c1의 width와 height를 각각 50,80으로 변경합니다.“);


c1.width = 50;

c1.height = 80;// 클래스 변수의 값을 변경한다.

System.out.println("c1은“+c1.kind+","+c1.number+"이며, 크기는 ("c1.width+","+c1.height+")");

System.out.println("c2은“+c2.kind+","+c2.number+"이며, 크기는  ("c2.width+","+c2.height+")");

}

}


class Card{

String kind;

int number;

static int width =100;

static int height =250;

}

실행결과)

Card.width=100

Card.height=250

c1은Heart,7이며, 크기는 (100,250)

c2은Spade,4이며, 크기는  (100,250)

c1의 width와 height를 각각 50,80으로 변경합니다.

c1은Heart,7이며, 크기는 (50,80)

c2은Spade,4이며, 크기는  (50,80)


Card 클래스의 클래스변수인 width, height를 공유하기 때문에, c1의 width와 height를 변경하면 c2의 width와 height값도 바뀐 것과 같은 결과를 얻는다.

Card.width, c1.width, c2.width는 모두 같은 저장 공간을 참조하므로 항상 같은 값을 갖게 된다.

--> 인스턴스 변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.