프로그래밍/JAVA

java 기초 문법 정리

p-a-r-k 2018. 5. 10. 10:47
반응형


01. 기본

변수의 선언은 아래와 같다

1 2 int a; String b;

변수a 는 int 자료형 변수이다. 즉 a라는 변수에는 int 자료형 값(1, 10, 25 등의 정수값)만 담을 수 있다.

  1. 변수

    1. 변수명은 숫자로 시작할 수 없다.

    2. _ 와 $문자 이외의 특수문자는 사용불가.

    3. 자바키워드는 사용불가. ex) abstract, continue, for

  2. 자료형(Type)

    1. 변수명앞의 int, String은 변수의 자료형을 의미.

    2. 변수 선언 후 혹은 선언과 동시에 변수에 값을 대입 할 수 있다.

1 2 3 4 int a; a = 1; String b = "hello java";

자주쓰이는 자료형

  • int

  • long

  • double

  • boolean

  • char

  • String

  • StringBuffer

  • List

  • Map

사용자 정의 자료형

사용자가 직접 자료형을 만들 수 있다.

1 2 3 4 class Animal { } Animal dog;

Animal 클래스를 만들면 자료형이 Animal형인 dog변수를 만들 수 있게된다.

main 메서드

프로그램의 시작을 의미한다.

1 2 3 4 5 public class Test { public static void main(String[] ar) { System.out.println("Hello World"); } }

 


02. 자료형

02-1. 숫자 (Number)

정수

  1. 자바의 정수를 표현하기 위한 자료형은 int, long 이다.

  2. int와 long의 차이는 표현할 수 있는 숫자의 범위.

1 2 int age = 10; long countOfStar = 8764827384923849L;

long 변수에 값을 대입할 때는 대입하는 숫자 값이 int 자료형의 최대값인 2147483647 보다 큰 경우 L 접미사를 붙여주어야한다.

실수

  1. 자바의 실수를 표현하기 위한 자료형은 float, double 이다.

  2. float와 double의 차이 역시 표현할 수 있는 숫자의 범위이다.

1 2 float pi = 3.14F; double morePi = 3.14159265358979323846;

8진수와 16진수

  1. 8진수와 16진수는 int 자료형을 사용하여 표기할 수 있다.

  2. 0 으로 시작하면 8진수, 0x 로 시작하면 16진수가 된다.

1 2 int octal = 023; // 십진수: 19 int hex = 0xC; // 십진수: 12

숫자연산

+, -, *, / 기호를 이용하여 두 숫자간 사칙연산을 수행한다.

1 2 3 4 5 6 7 8 9 10 11 12 package jump2java; public class FourArithmetic { public static void main(String[] ar) { int a = 10; int b = 5; System.out.println(a+b); System.out.println(a-b); System.out.println(a*b); System.out.println(a/b); } }

결과

1 2 3 4 15 5 50 2

증감연산 (++, --)

++, -- 기호를 이용하여 값을 증가하거나 감소시킬 수 있다.

1 2 3 4 5 6 7 int i = 0; int j= 10; i++; j--; System.out.println(i); System.out.println(j);

결과

1 2 1 9
  • i++ : 값이 참조된 후에 증가

  • ++i : 값이 참조되기 전에 증가

 

02-2. 부울 (Boolean)

참 또는 거짓의 값을 갖는 자료형. 참(true) 또는 거짓(false)만 대입 가능하다.

부울 연산

1 2 3 4 2 > 1 // 참 1 == 2 // 거짓 3 % 2 == 1 // 참 (3을 2로 나눈 나머지는 1이므로 참이다.) "3".equals("2") // 거짓

조건문

부울 연산은 보통 다음처럼 조건문의 판단 기준으로 많이 사용된다.

1 2 3 4 5 6 7 int base = 180; int height = 185; boolean isTall = height > base; if (isTall) { System.out.println("키가 큽니다."); }
1 2 int i = 3; boolean isOdd = i % 2 == 1;

02-3. 문자 (Char)

첫번째는 문자값, 두번째는 아스키코드값, 세번째는 유니코드값으로 표현

1 2 3 char a1 = 'a'; char a2 = 97; char a3 = '\u0061';

02-4. 문자열 (String)

1 2 String a = "Happy Java"; String b = new String("a");

primitive(원시) 자료형

int, long, double, float, boolean, char 등을 primitive 자료형 이라고 부른다. 이런 primitive 자료형은 new 키워드로 생성할 수 없다.

primitive 자료형은 리터럴(literal)로 값을 세팅할 수 있다.

1 2 3 boolean result = true; char capitalC = 'C'; int i = 100000;

String 은 "Happy Java"와 같이 리터럴로 표기가 가능하지만 primitive 자료형은 아니다. (String은 리터럴 표현식을 사용할 수 있도록 자바에서 특별 대우 해 주는 자료형이다.)

02-5. StringBuffer

문자열을 추가하거나 변경 할 때 주로 사용하는 자료형

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Test { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("hello"); sb.append(" "); sb.append("jump to java"); System.out.println(sb.toString()); String temp = ""; temp += "hello"; temp += " "; temp += "jump to java"; System.out.println(temp); } }

02-6. 배열 (Array)

1 2 3 4 5 6 int[] odds = {1, 3, 5, 7, 9}; String[] weeks = {"월", "화", "수", "목", "금", "토", "일"}; for (int i=0; i<weeks.length; i++) { System.out.println(weeks[i]); }

02-7. 리스트 (List)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.util.ArrayList; public class TestList { public static void main(String[] args) { ArrayList<String> pitches = new ArrayList<String>(); pitches.add("138"); pitches.add("129"); pitches.add("142"); System.out.println(pitches.get(1)); System.out.println(pitches.size()); System.out.println(pitches.contains("142")); System.out.println(pitches.remove("129")); System.out.println(pitches.size()); System.out.println(pitches.remove(0)); } }

02-8. 제네릭스 (Generics)

자바 J2SE 5.0 이후에 도입된 개념. 좀 더 명확한 타입체크가 가능해 지는 것이다.

1 ArrayList<String> aList = new ArrayList<String>();

 <String> 이라는 제네릭스 표현식은 "ArrayList 안에 담을 수 있는 자료형은 String 타입 뿐이다" 라는 것을 의미한다.

02-9. 맵 (Map)

대응관계를 쉽게 표현할 수 있게 해 주는 자료형.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.util.HashMap; public class TestMap { public static void main(String[] args) { HashMap<String, String> map = new HashMap<String, String>(); map.put("people", "사람"); map.put("baseball", "야구"); System.out.println(map.get("people")); System.out.println(map.containsKey("people")); System.out.println(map.remove("people")); System.out.println(map.size()); } }

 


03. 제어문

03-1. if 문

1 2 3 4 5 6 7 8 9 10 11 12 boolean hasCard = true; ArrayList<String> pocket = new ArrayList<String>(); pocket.add("paper"); pocket.add("handphone"); if (pocket.contains("money")) { System.out.println("택시를 타고 가라"); }else if(hasCard) { System.out.println("택시를 타고 가라"); }else { System.out.println("걸어가라"); }

03-2. switch/case 문

switch/case 문은 if 문과 비슷하지만 좀 더 정형화된 모습의 제어문

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class SwitchDemo { public static void main(String[] args) { int month = 8; String monthString = ""; switch (month) { case 1: monthString = "January"; break; case 2: monthString = "February"; break; case 3: monthString = "March"; break; default: monthString = "Invalid month"; break; } System.out.println(monthString); } }

03-3. while 문

“열 번 찍어 안 넘어 가는 나무 없다” 라는 속담을 적용시켜 보면 다음과 같이 될 것이다.

1 2 3 4 5 6 7 8 int treeHit = 0; while (treeHit < 10) { treeHit++; System.out.println("나무를 " + treeHit + "번 찍었습니다."); if (treeHit == 10) { System.out.println("나무 넘어갑니다."); } }

break : while문을 강제로 멈추게 해주는 명령

continue : while문의 맨 처음으로 돌아가게 하는 명령

03-4. for 문

1 2 3 4 5 6 7 // 구구단 for(int i=2; i<10; i++) { for(int j=1; j<10; j++) { System.out.print(i*j+" "); } System.out.println(""); }

03-5. for each 문

for each는 J2SE 5.0 부터 추가되었다. for each 라는 키워드가 따로 있는 것은 아니고 동일한 for를 이용한다. 

1 2 3 4 5 6 7 8 9 10 11 // for String[] numbers1 = {"one", "two", "three"}; for(int i=0; i<numbers1.length; i++) { System.out.println(numbers1[i]); } // for each String[] numbers2 = {"one", "two", "three"}; for(String number: numbers2) { System.out.println(number); }

 


04. 객체지향 프로그래밍

04-1. 클래스

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 동물클래스 만들기 public class Animal { String name; public void setName(String name) { this.name = name; } public static void main(String[] args) { // 객체만들기 Animal cat = new Animal(); Animal dog = new Animal(); Animal horse = new Animal(); System.out.println(cat.name); } }

Animal cat = new Animal() 이렇게 만들어진 cat은 객체이다. 그리고 cat이라는 객체는 Animal의 인스턴스(instance)이다.

즉 인스턴스라는 말은 특정 객체(cat)가 어떤 클래스(Animal)의 객체인지를 관계위주로 설명할 때 사용된다.

"cat은 인스턴스" 보다는 "cat은 객체"라는 표현이 "cat은 Animal의 객체" 보다는 "cat은 Animal의 인스턴스" 라는 표현이 훨씬 잘 어울린다.

객체 변수는 공유되지 않는다

1 2 3 4 5 6 7 8 9 10 public static void main(String[] args) { Animal cat = new Animal(); cat.setName("boby"); Animal dog = new Animal(); dog.setName("happy"); System.out.println(cat.name); System.out.println(dog.name); }

name 객체 변수는 공유되지 않는다는 것을 확인할 수 있다.

 

04-2. 메소드

메소드의 분류

  • 입력과 출력이 모두 있는 메소드

  • 입력과 출력이 모두 없는 메소드

  • 입력은 없고 출력은 있는 메소드

  • 입력은 있고 출력은 없는 메소드

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Test { public int sum(int a, int b) { return a+b; } public static void main(String[] args) { int a = 3; int b = 4; Test myTest = new Test(); int c = myTest.sum(a, b); System.out.println(c); } }

sum이라는 메소드는 입력값으로 두개의 값(int 자료형 a, int 자료형 b)을 받으며 리턴값은 두 개의 입력값을 더한 값(int 자료형)이다

메소드 내에서 선언된 변수의 효력 범위

아래 코드는 a의 출력값이 2가 되지않는다.

vertest 메소드 의 a++는 메소드 내에서만 쓰일뿐 main메서드에서는 아무 변화가 없다.

1 2 3 4 5 6 7 8 9 10 11 12 class Test { public void vartest(int a) { a++; } public static void main(String[] args) { int a = 1; Test myTest = new Test(); myTest.vartest(a); System.out.println(a); } }

아래 코드처럼 return받아서 대입시킨다.

1 2 3 4 5 6 7 8 9 10 11 public int vartest(int a) { a++; return a; } public static void main(String[] args) { int a = 1; Test myTest = new Test(); a = myTest.vartest(a); System.out.println(a); }

혹은 객체변수를 사용한다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Test { int a; // 객체변수 a public void vartest(Test test) { test.a++; } public static void main(String[] args) { Test myTest = new Test(); myTest.a = 1; myTest.vartest(myTest); System.out.println(myTest.a); } }

 

04-3. call by value

메소드에 값(primitive type)을 전달하는 것과 객체(reference type)를 전달하는 것에는 큰 차이가 있다

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Updater { public void update(int count) { count++; } } public class Counter { int count = 0; // 객체변수 public static void main(String[] args) { Counter myCounter = new Counter(); System.out.println("before update:"+myCounter.count); Updater myUpdater = new Updater(); myUpdater.update(myCounter.count); System.out.println("after update:"+myCounter.count); } }

객체 변수 count의 값을 update메소드에 넘겨서 변경시키더라도 값에 변화가 없다. 그 이유는 이전 챕터에서 알아본 것과 같이 update 메소드는 (int 자료형)을 전달받았기 때문이다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Updater { public void update(Counter counter) { counter.count++; } } public class Counter { int count = 0; public static void main(String[] args) { Counter myCounter = new Counter(); System.out.println("before update:"+myCounter.count); Updater myUpdater = new Updater(); myUpdater.update(myCounter); System.out.println("after update:"+myCounter.count); } }

메소드의 입력으로 객체를 전달받는 경우에는 메소드가 입력받은 객체를 그대로 사용하기 때문에 메소드가 객체의 속성값을 변경하면 메소드 수행 후 객체의 변경된 속성값이 유지되게 된다.

 

04-4. 상속

자식이 부모로부터 무언가를 물려받는 것이다. 클래스 상속을 위해서는 extends 라는 키워드를 사용한다.

1 2 3 4 5 6 7 public class Animal { String name; public void setName(String name) { this.name = name; } }
1 2 3 public class Dog extends Animal { }
1 2 3 4 5 6 7 public class Dog extends Animal { public static void main(String[] args) { Dog dog = new Dog(); dog.setName("poppy"); System.out.println(dog.name); } }

Dog 클래스에 name 이라는 객체변수와 setName 이라는 메소드를 만들지 않았지만 Animal클래스를 상속을 받았기 때문에 그대로 사용이 가능하다

자바에서 만드는 모든 클래스는 Object라는 클래스를 상속받게 되어 있다. 사실 우리가 만든 Animal 클래스는 다음과 기능적으로 완전히 동일하다.

하지만 굳이 Object 클래스를 상속하도록 코딩하지 않아도 자바에서 만들어지는 모든 클래스는 Object 클래스를 자동으로 상속받게끔 되어 있다.

 

04-5. 생성자

메소드명이 클래스명과 동일하고 리턴 자료형이 없는 메소드를 생성자(Constructor)라고 말한다.

생성자의 규칙

  1. 클래스명과 메소드명이 동일하다.

  2. 리턴타입을 정의하지 않는다.

생성자는 객체가 생성될 때 호출된다. 객체가 생성될 때는 new라는 키워드로 객체가 만들어질 때이다.

즉, 생성자는 new라는 키워드가 사용될 때 호출된다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class HouseDog extends Dog { public HouseDog(String name) { this.setName(name); } public void sleep() { System.out.println(this.name+" zzz in house"); } public void sleep(int hour) { System.out.println(this.name+" zzz in house for " + hour + " hours"); } public static void main(String[] args) { HouseDog dog = new HouseDog("happy"); System.out.println(dog.name); } }
1 HouseDog dog = new HouseDog("happy"); // 생성자 호출 시 문자열을 전달해야 한다.

default 생성자

생성자의 입력 항목이 없고 생성자 내부에 아무 내용이 없는 생성자를 default 생성자라고 부른다.

1 2 3 4 5 6 7 8 public class Dog extends Animal { public Dog() { } public void sleep() { System.out.println(this.name + " zzz"); } }

위와 같이 디폴트 생성자를 구현하면 new Dog() 로 Dog 객체가 만들어 질 때 위 디폴트 생성자가 실행된다.

만약 클래스에 생성자가 하나도 없다면 컴파일러는 자동으로 위와같은 디폴트 생성자를 추가한다. 하지만 사용자가 작성한 생성자가 하나라도 구현되어 있다면 컴파일러는 디폴트 생성자를 추가하지 않는다.

※ 이러한 이유로 위에서 살펴본 HouseDog 클래스에 name을 입력으로 받는 생성자를 만든 후에 new HouseDog() 는 사용할 수 없게 되는 것이다.

(HouseDog클래스에 이미 생성자를 만들었기 때문에 컴파일러는 디폴트 생성자를 자동으로 추가하지 않는다.)

 

04-6. 인터페이스

인터페이스 구현은 implements 라는 키워드를 사용한다.

  • 상황

난 동물원의 사육사이다.
육식동물이 들어오면 난 먹이를 던져준다.
호랑이가 오면 사과를 던져준다.
사자가 오면 바나나를 던져준다.

1 2 3 4 5 6 7 public class Animal { String name; public void setName(String name) { this.name = name; } }
1 2 3 public class Tiger extends Animal { }
1 2 3 public class Lion extends Animal { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ZooKeeper { public void feed(Tiger tiger) { System.out.println("feed apple"); } public void feed(Lion lion) { System.out.println("feed banana"); } public static void main(String[] args) { ZooKeeper zooKeeper = new ZooKeeper(); Tiger tiger = new Tiger(); Lion lion = new Lion(); zooKeeper.feed(tiger); zooKeeper.feed(lion); } }

 Animal을 상속한 Tiger와 Lion이 등장했다. 그리고 사육사 클래스인 ZooKeeper 클래스가 위와 같이 정의되었다. ZooKeeper 클래스는 호랑이가 왔을 때, 사자가 왔을 때 각각 다른 feed 메소드가 호출된다.

악어, 표범등이 계속 추가된다면 ZooKeeper는 육식동물이 추가될 때마다 매번 다음과 같은 feed 메소드를 추가해야 한다.

1 2 3 4 5 6 7 8 9 10 11 ... public void feed(Crocodile crocodile) { System.out.println("feed strawberry"); } public void feed(Leopard leopard) { System.out.println("feed orange"); } ...

이런 어려움을 극복하기 위해서 육식동물(Predator) 인터페이스를 작성한다.

Tiger, Lion 은 작성한 인터페이스를 구현하도록 변경한다.

1 2 3 4 5 6 7 8 9 10 public interface Predator { } public class Tiger extends Animal implements Predator { } public class Lion extends Animal implements Predator { }

Tiger, Lion이 Predator 인터페이스를 구현하면 ZooKeeper 클래스의 feed 메소드를 다음과 같이 변경 할 수 있다.

하지만 무조건 "feed apple" 이라는 문자열을 출력한다. 사자가 오면 "feed banana" 를 출력해야 한다.

1 2 3 public void feed(Predator predator) { System.out.println("feed apple"); }

인터페이스에 메소드를 추가한다.

1 2 3 public interface Predator { public String getFood(); }

인터페이스의 메소드는 메소드의 이름과 입출력에 대한 정의만 있고 그 내용은 없다. 그 이유는 인터페이스는 규칙이기 때문이다.

위에서 설정한 getFood라는 메소드는 인터페이스를 implements한 클래스들이 구현해야만 하는 것이다.

1 2 3 4 5 6 7 8 9 10 11 public class Tiger extends Animal implements Predator { public String getFood() { return "apple"; } } public class Lion extends Animal implements Predator { public String getFood() { return "banana"; } }

이제 ZooKeeper 클래스도 다음과 같이 변경이 가능하다.

1 2 3 4 5 public class ZooKeeper { public void feed(Predator predator) { System.out.println("feed "+predator.getFood()); } }

여기서 중요한 점은 메소드의 갯수가 줄어들었다는 점이 아니라 ZooKeeper클래스가 동물들의 종류에 의존적인 클래스에서 동물들의 종류와 상관없는 독립적인 클래스가 되었다는 점이다. 바로 이 점이 인터페이스의 핵심이다.

 

04-7. 다형성

1 2 3 public interface Barkable { public void bark(); }
1 2 3 public interface BarkablePredator extends Barkable, Predator { }
1 2 3 4 5 6 7 8 9 public class Tiger extends Animal implements Predator, Barkable { public String getFood() { return "apple"; } public void bark() { System.out.println("어흥"); } }
1 2 3 4 5 6 7 8 9 public class Lion extends Animal implements BarkablePredator { public String getFood() { return "banana"; } public void bark() { System.out.println("으르렁"); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Bouncer { public void barkAnimal(Barkable animal) { animal.bark(); } public static void main(String[] args) { Tiger tiger = new Tiger(); Lion lion = new Lion(); Bouncer bouncer= new Bouncer(); bouncer.barkAnimal(tiger); bouncer.barkAnimal(lion); } }

 

04-8. 추상클래스

인터페이스의 역할도 하면서 뭔가 구현체도 가지고 있는 자바의 돌연변이 같은 클래스이다. 혹자는 추상클래스는 인터페이스로 대체하는것이 좋은 디자인이라고도 얘기한다.

1 2 3 public abstract class Predator extends Animal { public abstract String getFood(); }

추상클래스를 만들기 위해서는 class 앞에 abstract 라고 표기해야 한다. 또한 인터페이스의 메소드와 같은 역할을 하는 메소드(여기서는 getFood 메소드)에도 역시 abstract 를 붙이도록 한다.

abstract 메소드는 인터페이스의 메소드와 마찬가지로 몸통이 없다. 즉 abstract 클래스를 상속하는 클래스에서 해당 abstract 메소드를 구현해야만 하는 것이다.

Predator 인터페이스를 위와 같이 추상클래스로 변경하면 Tiger, Lion 클래스도 다음과 같이 변경되어야 한다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Tiger extends Predator implements Barkable { public String getFood() { return "apple"; } public void bark() { System.out.println("어흥"); } } public class Lion extends Predator implements Barkable { public String getFood() { return "banana"; } public void bark() { System.out.println("으르렁"); } }

 


05. 입/출력

05-1. 콘솔 입/출력

자바의 System.in 을 이용하면 콘솔 입력을 얻을 수 있다. 

1 2 3 4 5 6 7 8 9 10 11 12 import java.io.InputStream; public class StreamTest { public static void main(String[] args) throws Exception { InputStream in = System.in; // System.in은 InputStream의 객체임을 알 수 있다. int a; a = in.read(); System.out.println(a); } }

InputStream은 자바의 내장 클래스이다. 자바의 내장 클래스중에 java.lang 패키지에 속해 있지 않은 클래스는 위 코드처럼 필요할 때 항상 import 해서 사용해야 한다.

우리가 그동안 사용해 왔던 System이나 String등의 클래스는 java.lang 패키지에 속해 있는 클래스이므로 별도의 import 과정이 필요없었다. 

InputStream의 read메소드는 다음처럼 1 byte의 사용자의 입력을 받아들인다.

1 2 int a; a = in.read();

하지만 read메소드로 읽은 1 byte의 데이터는 byte 자료형으로 저장되는 것이 아니라 int 자료형으로 저장된다. 저장되는 int 값은 0-255 사이의 정수값으로 아스키 코드값이다.

"abc" 를 입력했지만 출력은 "a"에 해당되는 아스키 코드값만 출력되었다. 그 이유는 InputStream의 read 메소드는 1 byte만 읽기 때문이다.

즉, 사용자는 "abc"라는 총 3 byte의 데이터를 전달했지만 프로그램에서 1 byte만 읽은 경우라고 할 수 있다.

이렇게 사용자가 전달한 1 byte의 데이터 또는 3 byte의 데이터를 어려운 말로 입력 스트림이라고 한다. 스트림은 이어져 있는 데이터(byte)의 형태라고 이해하면 된다.

그렇다면 사용자가 3 byte를 입력했을 때 3 byte를 전부 읽고 싶다면 ….

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.io.InputStream; public class StreamTest { public static void main(String[] args) throws Exception { InputStream in = System.in; int a; int b; int c; a = in.read(); b = in.read(); c = in.read(); System.out.println(a); System.out.println(b); System.out.println(c); } }

또는 개선된 방법,

1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.io.InputStream; public class StreamTest { public static void main(String[] args) throws Exception { InputStream in = System.in; byte[] a = new byte[3]; in.read(a); System.out.println(a[0]); System.out.println(a[1]); System.out.println(a[2]); } }

하지만 읽어들인 값을 항상 아스키코드 값으로 해석해야 하는 이 방식은 여전히 불편하다. 우리가 입력한 문자값을 그대로 출력하고 싶다.

바이트 대신 문자로 입력 스트림을 읽으려면 InputStreamReader를 사용하면 된다.

1 2 3 4 5 6 7 8 9 10 11 12 13 import java.io.InputStream; import java.io.InputStreamReader; public class StreamTest { public static void main(String[] args) throws Exception { InputStream in = System.in; InputStreamReader reader = new InputStreamReader(in); char[] a = new char[3]; reader.read(a); System.out.println(a); } }

여전히 불편한점은 남아있다. 불편한 점은 고정된 길이로만 스트림을 읽어야 한다는 점이다. 위 예제는 항상 3 byte만 읽도록 고정되어 있다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; public class StreamTest { public static void main(String[] args) throws Exception { InputStream in = System.in; InputStreamReader reader = new InputStreamReader(in); BufferedReader br = new BufferedReader(reader); String a = br.readLine(); System.out.println(a); } }

throws Exception 을 사용한 부분이 있다. InputStream으로 부터 값을 읽어들일 때는 IOException이 발생할 수 있기 때문에 예외처리를 해야 하는데 throws로 그 예외처리를 뒤로 미루게 한 것이다.

  • InputStream - byte

  • InputStreamReader - character

  • BufferedReader - String

 

Scanner

J2SE 5.0 부터 Scanner 라는 java.util.Scanner 클래스가 새로 추가되었다. Scanner 클래스를 이용하면 콘솔입력을 보다 쉽게 처리 할 수 있다.

1 2 3 4 5 6 7 8 import java.util.Scanner; public class Test { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println(sc.next()); } }

Scanner 클래스는 생성자의 입력으로 System.in, 즉 콘솔입력인 InputStream 을 필요로 한다.

Scanner 객체의 next() 메소드는 단어 하나(Token)를 읽어들인다.

  • next - 단어

  • nextLine - 라인

  • nextInt - 정수

 

05-2. 파일 입/출력

파일 쓰기

1 2 3 4 5 6 7 8 9 import java.io.FileOutputStream; import java.io.IOException; public class FileWrite { public static void main(String[] args) throws IOException { FileOutputStream output = new FileOutputStream("c:/out.txt"); output.close(); } }

파일에 내용쓰기

1 2 3 4 5 6 7 8 9 10 11 12 13 import java.io.IOException; import java.io.PrintWriter; public class FileWrite { 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(); } }

파일에 내용 추가하기

프로그램을 만들다 보면 파일에 내용을 쓰고 난 후에 또 새로운 내용을 추가하고 싶을 때가 생긴다. 이럴 경우에는 이미 작성된 파일을 다시 추가모드로 열어야 한다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.io.FileWriter; import java.io.IOException; public class FileWrite { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter("c:/out.txt"); for(int i=1; i<11; i++) { String data = i+" 번째 줄입니다.\r\n"; fw.write(data); } fw.close(); FileWriter fw2 = new FileWriter("c:/out.txt", true); for(int i=11; i<21; i++) { String data = i+" 번째 줄입니다.\r\n"; fw2.write(data); } fw2.close(); } }

파일 읽기

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class FileRead { 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; System.out.println(line); } br.close(); } }

 


06. 자바 날개달기

06-01. 패키지

비슷한 성격의 자바 클래스들을 모아 넣는 자바의 디렉토리이다. package라는 키워드를 사용한다.

ex) WholedocProduct, WholedocCustomer 등의 클래스를 wholedoc 패키지로 분류하면 편할것이다.

1 2 3 4 package com.park.wholedoc; public class WholedocProduct { }
1 2 3 4 package com.park.wholedoc; public class WholedocCustomer { }

파일이 저장되는 디렉토리에 다음과 같은 구조의 파일들이 생성되는 것을 확인할 수 있다.

1 2 3 4 src/com/park/wholeodc/WholedocCustomer.java src/com/park/wholeodc/WholedocProduct.java bin/com/park/wholeodc/WholedocCustomer.class bin/com/park/wholeodc/WholedocProduct.class

서브패키지 (Subpackage)

도트(.)를 이용하여 하위 패키지를 계속해서 만들어 나갈 수 있다. 여기서 com.park.wholedoc.personcom.park.wholedoc 패키지의 서브패키지라고 말한다.

1 2 3 4 5 package com.park.wholedoc.person; public class JungHwanPark { }

 

06-02. 접근제어자 (Access Modifier)

  1. private

  2. default

  3. protected

  4. public

private -> default -> protected -> public 순으로 보다 많은 접근을 허용한다

private

접근제어자가 private으로 설정되었다면 private 이 붙은 변수, 메소드는 해당 클래스에서만 접근이 가능하다.

1 2 3 4 5 6 public class AccessModifier { private String secret; private String getSecret() { return this.secret; } }

secret 변수와 getSecret 메소드는 오직 AccessModifier 클래스에서만 접근이 가능하고 다른 클래스에서는 접근이 불가능하다.

default

접근제어자를 별도로 설정하지 않는다면 접근제어자가 없는 변수, 메소드는 default 접근제어자가 되어 해당 패키지 내에서만 접근이 가능하다.

1 2 3 4 5 6 // HouseKim.java package jump2java.house; public class HouseKim { String lastname = "kim"; }
1 2 3 4 5 6 7 8 9 10 11 // HousePark.java package jump2java.house; public class HousePark { String lastname = "park"; public static void main(String[] args) { HouseKim kim = new HouseKim(); System.out.println(kim.lastname); } }

패키지가 동일하고 Housekim의 lastname 변수는 접근제어자가 default이므로 HousePark 클래스에서 kim.lastname으로 접근이 가능하다.

protected

protected가 붙은 변수, 메소드는 동일 패키지내의 클래스 또는 해당 클래스를 상속받은 외부 패키지의 클래스에서 접근이 가능하다.

1 2 3 4 5 6 // HouserPark.java package jump2java.house; public class HousePark { protected String lastname = "park"; }
1 2 3 4 5 6 7 8 9 10 11 // ParkJungHwan.java package jump2java.house.person; import house.HousePark; public class ParkJungHwan extends HousePark { public static void main(String[] args) { ParkJungHwan pjh = new ParkJungHwan(); System.out.println(pjh.lastname); } }

만약 lastname의 접근제어자가 protected 가 아닌 default 접근제어자였다면 pjh.lastname문장은 컴파일 에러를 유발 할 것이다.

public

public 접근제어자가 붙은 변수, 메소드는 어떤 클래스에서라도 접근이 가능하다.

1 2 3 4 5 6 package jump2java.house; public class HousePark { protected String lastname = "park"; public String info = "this is public message."; }

접근제어자를 이용하면 프로그래머의 코딩 실수를 방지할 수 있고 기타 위험요소를 제거할 수 있는 등의 많은 장점이 있다.

 

06-03. 정적 변수와 메소드 (static)

변수에 static 키워드를 붙이면 자바는 메모리 할당을 딱 한번만 하게 되어 메모리 사용에 이점을 볼 수 있게된다.

static을 사용하는 또 한가지 이유로 공유의 개념을 들 수 있다. static 으로 설정하면 같은 곳의 메모리 주소만을 바라보기 때문에 static 변수의 값을 공유하게 되는 것이다.

static method

스태틱 메소드 안에서는 인스턴스 변수 접근이 불가능 하다. (static 변수는 접근 가능)

싱글톤 패턴 (singleton pattern)

단 하나의 객체만을 생성하게 강제하는 패턴이다. 즉 클래스를 통해 생성할 수 있는 객체는 Only One, 즉 한 개만 되도록 만드는 것이 싱글톤이다.

1 2 3 4 5 6 7 8 9 10 class Singleton { private Singleton() { } } public class SingletonTest { public static void main(String[] args) { Singleton singleton = new Singleton(); } }

생성자를 private 으로 만들어 버리면 외부 클래스에서 Singleton 클래스를 new 를 이용하여 생성할 수 없게 된다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Singleton { private static Singleton one; private Singleton() { } public static Singleton getInstance() { if(one==null) { one = new Singleton(); } return one; } } public class SingletonTest { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton1 == singleton2); } }

getInstance라는 static 메소드를 이용하여 Singleton 객체를 돌려 받을 수 있다. 

위 예제는 Thread Safe 하지는 않다.?

 

06-04. 예외처리 (Exception)

1 2 3 4 5 6 7 8 9 try { ... } catch(예외1) { ... } catch(예외2) { ... } finally { ... }

Exception은 크게 두가지로 구분된다.

  1. RuntimeException

  2. Exception

RuntimeException은 실행 시 발생하는 예외이고 Exception은 컴파일 시 발생하는 예외이다.

즉, Exception은 프로그램 작성 시 이미 예측가능한 예외를 작성할 때 사용하고 RuntimeException은 발생 할수도 발생 안 할수도 있는 경우에 작성한다.

다른 말로 Exception을 Checked Exception, RuntimeException을 Unchecked Exception이라고도 한다.

Exception을 처리하는 위치는 대단히 중요하다. 프로그램의 수행여부를 결정하기도 하고 트랜잭션 처리와도 밀접한 관계가 있기 때문이다.

 

06-05. 쓰레드(Thread)

동작하고 있는 프로그램을 프로세스(Process)라고 한다.

보통 한 개의 프로세스는 한 가지의 일을 하지만, 이 쓰레드를 이용하면 한 프로세스 내에서 두 가지 또는 그 이상의 일을 동시에 할 수 있게 된다.

1 2 3 4 5 6 7 8 9 10 public class Test extends Thread { public void run() { System.out.println("thread run."); } public static void main(String[] args) { Test test = new Test(); test.start(); } }

Thread 클래스를 상속했다. Thread 클래스의 run 메소드를 구현하면 위 예제와 같이 test.start() 실행 시 test객체의 run 메소드가 수행이 된다.

(Thread 클래스를 extends 했기때문에 start 메소드 실행 시 run 메소드가 수행되는 것이다.

Thread 클래스는 start 실행 시 run 메소드가 수행되도록 내부적으로 코딩되어 있다.)

쓰레드의 동작을 확인할 수 있는 예제.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Test extends Thread { int seq; public Test(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) { for(int i=0; i<10; i++) { Thread t = new Test(i); t.start(); } System.out.println("main end."); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 결과 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 메소드가 종료되었다는 사실이다. 

그렇다면 모든 쓰레드가 종료된 후에 main 메소드를 종료시키고 싶은 경우.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main(String[] args) { ArrayList<Thread> threads = new ArrayList<Thread>(); for(int i=0; i<10; i++) { Thread t = new Test(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."); }

main 메소드가 종료되기 전에 threads 에 담긴 각각의 thread에 join 메소드를 호출하여 쓰레드가 종료될 때까지 대기하도록 변경하였다.

join 메소드는 쓰레드가 종료될 때까지 기다리게 하는 메서드이다.

쓰레드 프로그래밍 시 가장 많이 실수하는 부분이 바로 쓰레드가 종료되지 않았는데 쓰레드가 종료된 줄 알고 그 다음 로직을 수행하게 만드는 일이다.

쓰레드가 종료된 후 그 다음 로직을 수행해야 할 때 꼭 필요한 것이 바로 이 join 메소드이다.

 

Runnable

보통 쓰레드 객체를 만들 때 위의 예처럼 Thread를 상속하여 만들기도 하지만 보통 Runnable 인터페이스를 구현하도록 하는 방법을 많이 사용한다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import java.util.ArrayList; public class Test implements Runnable { int seq; public Test(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<Thread>(); for(int i=0; i<10; i++) { Thread t = new Thread(new Test(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."); } }

Thread를 extends 하던 것에서 Runnable을 implements 하도록 변경되었다. (Runnable 인터페이스는 run 메소드를 구현하도록 강제한다.)

그리고 Thread 를 생성하는 부분을 다음과 같이 변경했다.

1 Thread t = new Thread(new Test(i));

Thread의 생성자로 Runnable 인터페이스를 구현한 객체를 넘길 수 있는데 이 방법을 사용한 것이다. 

반응형