콘솔이란 콘솔은 사용자의 입력을 받거나 사용자에게 문자열을 출력해주는 역할을 하는 것을 말한다.
- 터미널
- 텍스트 입력 및 출력의 환경이며, 명령을 처리하고 출력을 뱉는 것은 쉘이 하는 역할이다.
- 터미널은 데이터를 입력하거나 처리 결과를 출력하는 단말기다.
- 터미널은 쉘을 실행하고 명령을 입력하게 해주는 포장(Wrapper) 프로그램이다.
- 터미널은 그래픽 인터페이스를 표시하고 쉘과 상호작용할 수 있는 프로그램이며, 용어 자체는 일반적으로 키보드와 디스플레이를 통해 사용자가 컴퓨터와 상호작용할 수 있도록 하는 장치를 말한다.
- 콘솔
- 물리적 터미널을 콘솔이라고 한다.(하드웨어 형태의 터미널)
- 콘솔은 물리적 장치라면, 터미널은 원격제어 환경까지 포함한 더 넓은 개념이라 볼 수 있다.
- 콘솔은 일종의 터미널이며, 텍스트 모드 프로그램이 활성화된 창이다.
- 콘솔은 운영체제와의 저수준의 직접적 통신을 위해 컴퓨터 전용직렬 콘솔 포트에 연결된 단일 키보드와 모니터로 구성된다.
- 콘솔은 시스템에 직접 연결된 기본적인 터미널이자 물리적 터미널이다.
- 문자열을 얻기 위해서는 자바의
System.in을 사용한다.
import java.io.IOException;
import java.io.InputStream;
public class Sample {
public static void main(String[] args) throws IOException {
InputStream in = System.in;//InputStream 객체
int a;
a = in.read(); // 1바이트 사용자 입력을 정수로 받게되며, 이 때 system.out은 그대로 정수로 출력해준다.
System.out.println(a);
}
}InputStream자바 내장 클래스, 자바 내장 클래스 중java.lang패키지에 속하지 않은 경우, 지정 내용을 import 시켜주어야 한다. 반대로java.lang에 속한 클래스는 별도의import를 요구하지 않는다.- IOException :
InputStream의 경우 예외 처리가 발생할 수 있어서 해당 라이브러리와 예외처리로throw키워드가 존재하게 된다.
- 위의 예시 코드는 1바이트씩 읽어들이다보니, 한 글자씩 입력을 받을 수 밖에 없다. 따라서 이러한 경우 스트림으로 된 데이터의 형태를 읽지 못하고, 다른 방식을 사용해야 한다.
import java.io.IOException;
import java.io.InputStream;
public class Sample {
public static void main(String[] args) throws IOException {
InputStream in = System.in;//InputStream 객체
bytep[] a = new byte[3];
in.read(a);
for (int i = 0; idx < 3; i++)
System.out.println(a[i]);
}
}abc (입력)
97 (출력)
98 (출력)
99 (출력)- 그러나 위의 방식은 읽어들인 값을 아스키 코드로 해석해야 하고, 형변환이 필요하다. 바이트 단위로 읽는 것도 제약이 크다. 이에 문자로 입력스트림을 받는 방법에
InputStreamReader를 사용하면된다.
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Console {
public static void main(String[] args) throws IOException {
InputStream in = System.in;
InputStreamReader reader = new InputStreamReader(in);
char[] a = new char[3];
reader.read(a);
for (int i = 0; idx < 3; i++)
System.out.println(a[i]);
}
}- 그러나 여전히 3바이트로 길이를 유한하게 해야만 받아들이는 구조는 제약이 있다. 온전히 들어오는 내용을 전체다 받아 들이는 예제는 다음과 같이 가능하다.
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.ImputStreamReader;
public class Console {
public static void main(String[] args) throws IOException {
InputStream in = System.in;
InputStreamReader reader = new InputStreamReader(in);
BufferedReader br = new BufferedReader(reader);
String a = br.readLine();
System.out.println(a);
}
}- J2SE(Java Special Edition) 5.0 부터
java.util.Scanner클래스가 새로 추가되어 콘솔 입력이 훨씬 쉬워졌다.
import java.util.Scanner;
public class Sample {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(sc.next());
}
}System.out.println메서드는 PrintStream 클래스의 객체로 콘솔에 값을 출력할 때 사용하고, 디버깅시 많이 사용한다.System.err의 경우 에러 출력을 위한 역할을 한다.
import java.io.FileOutputStream;
import java.io.IOException;
public class FileInOut {
public static void main(String[] args) throws IOException {
FileOutputStream output = new FileOutputStream("./testfile");
output.close();
}
}-
파일을 생성하기 위한 FileOutputStream 클래스, close() 메소드는 기존의 다른 언어들의 close와 유사한 역할을 하며, 자동으로 닫아주긴 하나 직접 사용한 파일의 경우 추가적인 오류를 막고자 미사용시 닫기를 권장한다.
-
아래와 같이 write 메소드를 활용해 내용을 넣어줄수 있다.
import java.io.FileOutputStream;
import java.io.IOException;
public class FileInOut {
public static void main(String[] args) throws IOException {
FileOutputStream output = new FileOutputStream("./testfile");
for(int i = 1; i < 11; i++) {
String data = i + "번째 줄입니다.\n";
output.write(data.getBytes());
}
output.close();
}
}- InputStream 과 마찬가지로 OutputStream 은 바이트 단위로 데이터를 처리하는 클래스다. FileOutputStream은 이를 상속받아 만들었고, 바이트 단위로 처리하게 되어 있다.
- 그러다보니 한글이 깨지는 현상이 발생하는 경우가 있다고 한다. 왜 깨지는 걸까?
- 문자열을 쓸 때, 위의 예시는 다소 불편하여 이를 해소한 방식이다.
import java.io.FileWriter;
import java.io.IOException;
public class FileInOut {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("./test2.md");
for(int i = 1; i < 11; i++) {
String data = i + "번째 줄입니다.";
fw.write(data);
}
fw.close();
}
}- 이 방식의 특징은 byte 배열이 아닌 문자열로 사용할 수 있어 편리한 면을 가지고 있다.
- 그러나 여기서 FileWriter 도 개행문자를 넣어야 한다는 등의 불편한 부분이 존재한다.
- 따라서 PrintWriter 를 사용하는 형태가 되면, 메소드가 개행을 지원하는 경우
import java.io.PrintWriter;
import java.io.IOException;
public class FileInOut {
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter("./test2.md");
for(int i = 1; i < 11; i++) {
String data = i + "번째 줄입니다.";
pw.println(data);
}
pw.close();
}
}- 프로그래맴을 만들고, 쓴 내용에 추가로 내용을 적고 싶을 때 추가모드로 열어서 추가 내용을 작성해야 한다.
import java.io.FileWriter;
import java.io.IOException;
public class FileInOut {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("./test3.md");
for(int i = 1; i < 11; i++) {
String data = i + "번째 줄입니다.";
fw.write(data);
}
fw.close();
FileWriter fw2 = new FileWriter("./test3.md", true);
for(int i = 11; i < 21; i++) {
String data = i + "번째 줄입니다.";
fw2.write(data);
}
fw2.close();
}
}- PrintWriter 를 사용할 경우에도 생성자 파라미터 파일명 대신, 추가모드로 열린 FileWriter의 객체를 전달하면 된다.
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class Sample {
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter("c:/out.txt");
for(int i=1; i<11; i++) {
String data = i+" 번째 줄입니다.";
pw.println(data);
}
pw.close();
PrintWriter pw2 = new PrintWriter(new FileWriter("c:/out.txt", true));
for(int i=11; i<21; i++) { // 다형성을 이용한 트릭
String data = i+" 번째 줄입니다.";
pw2.println(data);
}
pw2.close();
}
}- 바이트 단위로 읽고 처리하기
import java.io.FileInputStream;
import java.io.IOException;
public class Sample {
public static void main(String[] args) throws IOException {
byte[] b = new byte[1024];
FileInputStream input = new FileInputStream("c:/out.txt");
input.read(b);
System.out.println(new String(b)); // byte 배열을 문자열로 변경하여 출력
input.close();
}
}- 라인 단위로 읽고 처리하기
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Sample {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("c:/out.txt"));
while(true) {
String line = br.readLine();
if (line==null) break; // 더 이상 읽을 라인이 없을 경우 while 문을 빠져나간다.
System.out.println(line);
}
br.close();
}
}- 패키지 : 비슷한 성격의 자바 클래스들을 모아놓은 자바 디렉토리이다.
package house; // 이렇게 지정만 해주면 된다.
public class HouseKim {
(... 생략 ...)
}- 패키지 안에 또 다른 하위 패키지를 생성하는 방식이다.
package house.person;
public class EungYoungPark {
}- 외부에서는
import키워드를 활용해서 패키지를 인클루드할 수 있고, 모든 클래스를 사용할 수 있다. - 패키지 내부에 포함된
- 패키지를 사용하면 클래스의 분류가 용이하다. 클래스명이 동일한 경우가 생기거나 하면, 문제가 생길수 있는데, 이러한 상황을 구분하기 위해 패키지로 묶어서 사용이 가능하다.
- 변수나 메소드의 사용권한을 제어하는 역할을 한다.
- 다음과 같은 제어자가, 변수나 메소드 사용권한으로 제공된다.
- private
- default
- protected
- public
- 위에서부터 아래로 갈 수록 보다 많은 접근성이 생긴다.
- 접근 제어자로 사용되면 해당 클래스에서만 사용이 가능하다.
public class Sample {
private String secret;
private String getSecret() {return this.secret;}
}- 접근 제어자를 별도로 설정하지 않았다면, 접근 제어자가 없는 변수나 메소드는 기본적으로 해당 접근 제어자가 연결되어, 해당 '패키지' 내에서만 접근이 가능하다.
house/HouseKim.java
package house; // 패키지가 동일하다.
public class HouseKim {
String lastname = "kim"; // lastname은 default 접근제어자로 설정된다.
}house/HousePark.java
package house; // 패키지가 동일하다.
public class HousePark {
String lastname = "park";
public static void main(String[] args) {
HouseKim kim = new HouseKim();
System.out.println(kim.lastname); // HouseKim 클래스의 lastname 변수를 사용할 수 있다.
}
}- 동일 패키지 안에서의 서로 다른 클래스는 접근이 가능하다.
- 해당 클래스를 상속 받은 다른 패키지의 클래스에서만 접근이 가능하다.
house/HousePark.java
package house; // 패키지가 서로 다르다.
public class HousePark {
protected String lastname = "park";
}house/person/EungYongPark.java
package house.person; // 패키지가 서로 다르다. 즉, 기본적으로 접근이 불가하다.
import house.HousePark; // 패키지 안의 클래스를 임포트했다.
public class EungYongPark extends HousePark { // HousePark을 상속했다.
public static void main(String[] args) {
EungYongPark eyp = new EungYongPark();
System.out.println(eyp.lastname); // 상속한 클래스의 protected 변수는 접근이 가능하다.
}
}- 상속이 되었으므로, 사용이 가능해졌다.
- 어떤 클래스나 메소드라도 접근이 가능하다.
package house;
public class HousePark {
protected String lastname = "park";
public String info = "this is public message.";
}- 접근제어자를 사용하면 위험요소를 제거하고, 코딩의 실수를 줄이는 역할을 한다는 것을 명심할 것.
- C++ 과 동일하게 메모리 상에서 한번만 할당하는 형태가 되고
공유가 된다. - 따라서 클래스 전체에서 하나를 공유하는 경우, 전체를 관리하는 등의 용도로 사용하기 편리하다.
- 해당 키워드를 사용하는 메소드는, 클래스를 이용하여 호출하는 것이 가능하다. 이는 기능적으로 객체가 필요 없는 메소드들을 위함이라고 생각하면 된다.
class Counter {
static int count = 0;
Counter() {
count++;
System.out.println(count);
}
public static int getCount() {
return count;
}
}
public class Sample {
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
System.out.println(Counter.getCount()); // 스태틱 메서드는 클래스를 이용하여 호출
}
}import java.text.SimpleDateFormat;
import java.util.Date;
class Util {
public static String getCurrentDate(String fmt) {
SimpleDateFormat sdf = new SimpleDateFormat(fmt);
return sdf.format(new Date());
}
}
public class Sample {
public static void main(String[] args) {
System.out.println(Util.getCurrentDate("yyyyMMdd")); // 오늘 날짜 출력
}
}- 특정 클래스에서 단 하나의 객체만을 생성하도록 강제하는 패턴이다.
Sample.java
class Singleton {
private Singleton() {
}
}
public class Sample {
public static void main(String[] args) {
Singleton singleton = new Singleton(); // 컴파일 오류가 발생한다.
}
}- 기본적으로 위 코드로 작성시 컴파일의 에러가 발생한다. 이는 private 키워드로 생성자 접근을 막아기 때문이다.
new키워드로 생성이 불가능하다.
class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return new Singleton(); // 같은 클래스이므로 생성자 호출이 가능하다.
}
}
public class Sample {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
}
}- 생성자가
private키워드로 되어 있으면, 해당 클래스 생성자에 접근하는 것이 불가능하다. 하지만 거기서 get함수를 만들고, 여기서 생성자를 호출하는 방식으로 한다면, 클래스 생성자 없이 생성이 가능해진다. - 하지만 보면 알다시피,
getInstance메소드를 호출할 때마다 문제가 생기는데 이는 계속 새로운 인스턴스를 생성한다는 점이다. - 따라서 이러한 상황을 개선한 singleton 기법은 다음 처럼 짜여진 경우를 의미한다.
class Singleton {
private static Singleton one;
private Singleton() {
}
public static Singleton getInstance() {
if(one==null) {
one = new Singleton(); // 클래스 객체 자체를 이미 갖고 있는 방식으로 하고, null 여부를 판단하여 생성을 한다. 즉 한번 생성 이후에는 더이상 new 키워드를 쓰지 않고 해당 객체를 그대로 호출한다.
}
return one;
}
}
public class Sample {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1 == singleton2); // true 출력
}
}- 오류가 발생하고 이에 대해 무시하거나 적절한 처리를 할 수 있는데, 이때 try ... catch 와 throw 구문을 통한 오류의 적절한 처리를 해볼 수 있다.
- 없는 대상에 접근하는 경우
- 연산이 불가능한 연산(4 /0 등...)
- 접근 불가능한 배열 위치
- ... 등
- 기본적으로 try... catch 을 통해 처리된다.
- 기본적인 방법과 예외의 표준은 자바 내부 처리된 클래스 객체를 확인하면 될 것으로 보이며 C++ 과 동일한 구조를 가진다.
try {
... // 특정 조건문 등 실행
} catch(예외 조건 1) {
... // 에러 처리
} catch(예외 조건 2) {
...
} ...- 프로그램 수행 중 예외 발생시 예외 처리하여 catch 구문이 실행되지만, 어떤 예외처리가 발생한다고 해도 반드시 실행되도록 만드는 것을 원할 수 있습니다. 이 경우, fianlly 키워드를 활용해서 예외처리 후 공통적으로 할 작업들을 정리해 둘 수 있다.
public class Sample {
public void shouldBeRun() {
System.out.println("ok thanks.");
}
public static void main(String[] args) {
Sample sample = new Sample();
int c;
try {
c = 4 / 0;
} catch (ArithmeticException e) {
c = -1;
} finally {
sample.shouldBeRun(); // 예외에 상관없이 무조건 수행된다.
}
}
}- 직접 만드는 예외를 보며 참고하자!
public class Sample {
public void sayNick(String nick) {
if ("fool".equals(nick)) { return; }
System.out.println("당신의 별명은 "+ nick +" 입니다.")
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("fool");
smaple.sayNick("genious");
}
}- 적극적으로 예외를 발생시켜버리는 방식으로 할 수 있다.
Sample .java
class FoolException extends RuntimeException {}
public class Sample {
public void sayNick(String nick) {
if ("fool".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 "+ nick +" 입니다.")
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("fool");
smaple.sayNick("genious");
}
}- 위의 예시 프로그램을 실행해보면, sayNick 메소드 실행과 함께 예외가 발생하게 된다.
- 이때 Excetpion은 두가지 종류로 구분할 수 있다.
- RuntimeException : 실행시 발생하는 예외, 발생할 가능성이 있는 경우에 사용하는 형태이다.
- Exception : 컴파일 발생시 사용하는 예외, 따라서 이는 컴파일 과정에서 예외를 발견할 수 있다.
- 따라서 이미 예측이 되어서 막아버리기 때문에
Checked Exception,RuntimeException,Unchecked Exception이라고도 부른다.
- 따라서 이미 예측이 되어서 막아버리기 때문에
- FoolException 의 예시를 Runtime이 아닌 Exception을 상속받게 되면, 예외 처리가 컴파일러가 강제하므로 컴파일 자체가 안된다. 따라서 이를 수정해줘야 한다.
- 즉, 에러가 발생했을 때의 대처가 없이는 Exception 을 상속받은 걸로, 예외처리를 만들게 되면, 정상적인 컴파일이 안될 수 있다는 것이다.
class FoolException extends Exception {
}
public class Sample {
public void sayNick(String nick) {
try {
if("fool".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
}catch(FoolException e) {
System.err.println("FoolException이 발생했습니다.");
}
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("fool");
sample.sayNick("genious");
}
}- 예제를 수정하여 sayNick 메서드에서 했는데 이렇게 하지 않고, sayNick을 호출한 지점에서 FoolException을 처리하도록 만들수도 있다. 즉, 특정 메소드가 호출된 순간에 그 내부에서 수행되는게 아니라, 해당 메소드를 호출한 지점에서 이를 대처하도록 만드는 것이다.
```java
public class Sample {
public void sayNick(String nick) throws FoolException {
try {
if("fool".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
/**}catch(FoolException e) {
System.err.println("FoolException이 발생했습니다.");
}*/
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("fool");
sample.sayNick("genious");
}
}- 이렇게
throws라는 구문을 이용하여 위로 보낼 수 가 있다. (이를 예외를 뒤로 미룬다 라고도 표현한다.) 그러나 이경우 main 메소드에서 컴파일 에러가 발생한다. 왜냐면 throws 구문 때문에 예외처리의 대상이 main메소드로 바뀌었기 때문이다. - 따라서 이를 해결하기 위해서는 try catch 구문이 main 메소드로 나가야 한다.
class FoolException extends Exception {
}
public class Sample {
public void sayNick(String nick) throws FoolException {
if("fool".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
}
public static void main(String[] args) {
Sample sample = new Sample();
try {
sample.sayNick("fool");
sample.sayNick("genious");
} catch (FoolException e) {
System.err.println("FoolException이 발생했습니다.");
}
}
}- 그러나 이러한 방식에서는 큰 차이가 있다.
- 만약
sayNick이라는 메소드 내부에서 실행되게 된다면, 아마도 sample.sayNick("genious") 호출에 대해서는 정상적으로 수행될 것이다. - 하지만 위에 있는 예시의 경우 sayNick이 겹쳐져서 있고, fool 이 매개변수로 넘어간 경우이므로 아래의 "genious"는 수행되지 않고 catch 문으로 넘어가 실행되게 된다.
- 만약
- 결론적으로 이러한 이유로 프로그래밍 시 Exception은 매우 중요하며, 위치가 특히나 중요하다. 트랜잭션 처리와도 매우 밀접한 관계가 있다.
- 트랜잭션이란 하나의 작업 단위를 말한다. 어떤 작업을 진행하다가 예외가 발생한다고 할 때, 신경써야 하는 것을 말한다.
- 어떤 값들의 상호 전환에서, 정상적으로 처리되어야 하지만, 예외처리가 정상적으로 되지 않아 처리가 엉망으로 되어 있는 경우가 트랜잭션이 정상적이지 않은 것으로 볼 수 있다.
수도코드(슈도코드, pseudocode)는 특정 프로그래밍 언어의 문법을 따라 씌여진 것이 아니라, 일반적인 언어로 코드를 흉내내어 알고리즘을 써놓은 코드를 말한다. 수도코드는 말그대로 흉내만 내는 코드이기 때문에, 실제적인 프로그래밍 언어로 작성된 코드처럼 컴퓨터에서 실행할 수 없으며, 특정 언어로 프로그램을 작성하기 전에 알고리즘의 모델을 대략적으로 모델링하는 데에 쓰인다.
상품발송() {
try {
포장();
영수증발행();
발송();
} catch(예외) {
모두취소(); // 하나라도 실패하면 모두 취소한다.
}
}
포장() throws 예외 {
...
}
영수증발행() throws 예외 {
...
}
발송() throws 예외 {
...
}상품발송() {
포장();
영수증발행();
발송();
}
포장(){
try {
...
} catch(예외) {
포장취소(); // 각각의 조건을 따로하다보니 정상적인 처리가 안될 수 있다.
}
}
영수증발행() {
try {
...
} catch(예외) {
영수증발행취소();
}
}
발송() {
try {
...
} catch(예외) {
발송취소();
}
}- 쓰레드를 이용하면 한 프로세스 내에서 두가지 또는 그 이상의 일을 동시에 할 수 있다.
- 가장 간단한 쓰레드 예시는 다음과 같다.
Sample.java
public class Sample extends Thread {
public void run() { // Thread 를 상속하면 run 메서드를 구현해야 한다.
System.out.println("thread run.");
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.start(); // start()로 쓰레드를 실행한다.
}
}- 더 많은 쓰레드를 돌리는 예시는 다음과 같다.
public class Sample extends Thread {
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq + " thread start."); // 쓰레드 시작
try {
Thread.sleep(1000); // 1초 대기한다.
} catch (Exception e) {
}
System.out.println(this.seq + " thread end."); // 쓰레드 종료
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { // 총 10개의 쓰레드를 생성하여 실행한다.
Thread t = new Sample(i);
t.start();
}
System.out.println("main end."); // main 메소드 종료
}
}0 thread start.
4 thread start.
6 thread start.
2 thread start.
main end.
3 thread start.
7 thread start.
8 thread start.
1 thread start.
9 thread start.
5 thread start.
0 thread end.
4 thread end.
2 thread end.
6 thread end.
7 thread end.
3 thread end.
8 thread end.
9 thread end.
1 thread end.
5 thread end.
- 쓰레드가 모두 수행되고 종료되기 전에 main 메소드가 종료되지 않도록 만들었다.
import java.util.ArrayList;
public class Sample extends Thread {
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq+" thread start.");
try {
Thread.sleep(1000);
}catch(Exception e) {
}
System.out.println(this.seq+" thread end.");
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<>();
for(int i=0; i<10; i++) {
Thread t = new Sample(i);
t.start();
threads.add(t);
}
for(int i=0; i<threads.size(); i++) {
Thread t = threads.get(i);
try {
t.join(); // t 쓰레드가 종료할 때까지 기다린다.
}catch(Exception e) {
}
}
System.out.println("main end.");
}
}0 thread start.
5 thread start.
2 thread start.
6 thread start.
9 thread start.
1 thread start.
7 thread start.
3 thread start.
8 thread start.
4 thread start.
0 thread end.
5 thread end.
2 thread end.
9 thread end.
6 thread end.
1 thread end.
7 thread end.
4 thread end.
8 thread end.
3 thread end.
main end.
- 보통 쓰레드 객체를 만들 때는 위의 방식으로 만든다. 하지만 Runnable 인터페이스를 구현하도록 하는 방법으로 보통 사용한다. 왜냐면, Thread클래스를 상속해버려야 하고, 이러면 다른 클래스 상속이 불가능하기 때문이다.
- Runnable 인터페이스를 만드는 방식으로 수정하면 다음처럼 구현이 가능하다.
import java.util.ArrayList;
public class Sample implements Runnable {
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq+" thread start.");
try {
Thread.sleep(1000);
}catch(Exception e) {
}
System.out.println(this.seq+" thread end.");
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<>();
for(int i=0; i<10; i++) {
Thread t = new Thread(new Sample(i));
t.start();
threads.add(t);
}
for(int i=0; i<threads.size(); i++) {
Thread t = threads.get(i);
try {
t.join();
}catch(Exception e) {
}
}
System.out.println("main end.");
}
}- 참고로 Runnable 인터페이스를 사용하면 run 메소드를 반드시 구현해야 한다. (인터페이스 필수조건)
- 더불어 쓰레드를 생선하는 부분이 다음처럼 변경된다.
Thread t = new Sample(i); // before
Thread t = new Thread(new Sample(i)); // after- 함수형 프로그래밍을 지원하기 위해, 람다와 스트림이 도입되었다. 람다와 스트림을 사용하여 작성한 코드를 일반 스타일의 자바 코드로 바꾸는 것은 불가능하지 않고, 없어도 된다. 하지만 그럼에도 이를 사용함은 코드 양을 줄이기에 적절한다.
- 익명함수(Anonymous functions)
interface Caclcuator {
int sum(int a, int b); // 인터페이스 구현
}
class MyCalculator implements Calculator { // 이를 활용한 My Calculator 생성
public int sum(int a, int b) {
return a + b;
}
}
public class Sample {
public static void main(String[] args) {
MyCalculator mc = new MyCalculator();
int result = mc.sum(3, 4);
System.out.println(result);
}
}interface Calculator {
int sum(int a, int b);
}
public class Sample {
public static void main(String[] args){
Calculator mc = (int a, int b) -> a + b; // 람다 방식의
int result = mc.sum(3, 4);
System.out.println(result);
}
}- 람다를 사용시 인터페이스의 메서드가 1개 이상이면 람다 함수를 사용할 수 없다.
interface Calculator {
int sum(int a, int b);
int mul(int a, int b); // mul 메서드를 추가하면 컴파일 에러가 발생한다.
}- 따라서 여기엔
@FunctionalInterface어노테이션을 사용하는 것이 좋다. 해당 어노테이션을 넣으면 2개 이상의 메서드를 가진 인터페이스를 작성하는 것이 불가능해진다.
@FunctionalInterface
interface Calculator {
int sum(int a, int b);
int mul(int a, int b); // @FunctionalInterface 는 두번째 메서드를 허용하지 않는다.
} - 기본적인 형태에서 더 축약하면 다음처럼 수정이 가능하다.
(a, b) -> a + b // 자료타입을 생략하는 것이 가능하다. - 위의 예시는
Integer.sum(int a, int b)와 동일하기 때문에 더 축약이 가능하다.
@FunctionalInterface
interface Calculator {
int sum(int a, int b)
}
public class Sample {
public static void main(String[] args) {
Calculator mc = Integer::sum;
int result = mc.sum(3, 4);
System.out.println(result);
}
}- 이번엔 인터페이스 생성조차 더 축약이 가능하다. 자바가 제공하는 BiFunction 인터페이스를 사용하면 더 축약이 가능하다.
import java.util.function.BiFunction;
public class Sample {
public static void main(String[] args) {
BitFunction<Integer, Integer, Integer> mc = (a, b) -> a + b;
int result = mc.apply(3, 4);
System.out.println(reuslt);
}
}상당히 많은 인터페이스 라이브러리 패키지가 있으나, 이해 목적으로 BiFunction, BinaryOperator 에 대해서만 알아보았다.
- 데이터가 물결처럼 흘러가면서 필터링 과정으로 변경되어 반환되는 구조라, 이러한 이름으로 불린다.
- 문제
- 다음 정수 배열이 있다.
int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8}
- 다음 정수 배열이 있다.
- 이 배열에서 짝수만 찾아 중복을 제거한 후 역순으로 정렬하는 프로그램을 작성하시오.
```java
int[] result = {8, 6, 4, 2};
```
```java
import java.util.*;
public class Sample {
public static void main(String[] args) {
int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
// 짝수만 포함하는 ArrayList 생성
ArrayList<Integer> dataList = new ArrayList<>();
for(int i=0; i<data.length; i++) {
if(data[i] % 2 == 0) {
dataList.add(data[i]);
}
}
// Set을 사용하여 중복을 제거
HashSet<Integer> dataSet = new HashSet<>(dataList);
// Set을 다시 List로 변경
ArrayList<Integer> distinctList = new ArrayList<>(dataSet);
// 역순으로 정렬
distinctList.sort(Comparator.reverseOrder());
// Integer 리스트를 정수 배열로 변환
int[] result = new int[distinctList.size()];
for(int i=0; i< distinctList.size(); i++) {
result[i] = distinctList.get(i);
}
}
}
```
- 위의 일반적인 코드를 수정하면 다음처럼 정리 된다.
```java
import java.util.Arrays;
import java.util.Comparator;
public class Sample {
public static void main(String[] args) {
int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
int[] result = Arrays.stream(data) // IntStream을 생성한다.
.boxed() // IntStream을 Stream<Integer>로 변경한다.
.filter((a) -> a % 2 == 0) // 짝수만 걸러낸다.
.distinct() // 중복을 제거한다.
.sorted(Comparator.reverseOrder()) // 역순으로 정렬한다.
.mapToInt(Integer::intValue) // Stream<Integer>를 IntStream으로 변경한다.
.toArray() // int[] 배열로 반환한다.
;
}
}
```
- 제공되는 스트림 방식은 일반적인 코드보다 확실히 간결하고 가독성이 좋다. 이를 위한 기능들을 더 찾으면서 사용하는것이 중요해 보인다.