2017년 7월 3일 월요일

[Java] 메소드 파라미터(Paratmeter) 전달, call by Reference vs Value

메소드를 호출할 때 우리는 파라미터를 전달 할 수 있다. 예를들어, 우리가 자주 쓰는 메소드인 "System.out.println(파라미터)" 도 파라미터를 받아 실행된다. 이런 파라미터를 전달하는 박식은 "call by reference", "call by value"라는 2가지 방식으로 나뉜다.

Call by Value 방식은 간단하다. 단순히 메소드를 호출하는 호출문(caller)이 전달하는 값(value)의 복사값이 메소드에 전달된다. 그러므로 호출된 메소드는 전달받은 변수 값을 변경해도 원래 변수에는 적용되지 않는다. 그에비해 Call by Reference 방식에서는, 메소드가 호출문에서 제공된 변수가 저장된 메모리 참조(Reference)값을 전달받는다. 그러므로 메소드에서 파라미터값을 변경한다면 해당 변수에 모두 적용된다.

이런 Call by ... 단어는 단순이 자바에서만이 아니라 프로그래밍 이론적으로도 메소드 파라미터의 습성을 다룰 때 쓰이는 단어이니 잘 알아두자. 옛 프로그래밍 언어인 Algol 언어에서는 Call by name이라는 개념도 존재했다.

자바(Java)에서는 언제나 Call by Value 방식으로 메소드를 호출한다. 그래서 메소드 내부에서 전달받은 파라미터를 수정하거나 해도 실제 변수에는 적용되지 않는다.


public static void main(String[] args) {
 int test = 0;
 raiseIntBy(test, 3);
 System.out.println(test); // 0
}

public static void raiseIntBy(int x, int y){
 x+=y;
}

test 변수의 값은 변하지 않는다. 우선 raiseIntBy 메소드의 변수 x는 test 변수의 복사값인 0으로 초기화된다. 그리고 x에 y가 더해져서 x는 3이 되었지만, 변수 test의 값은 그대로 0이게 된다. 그리고 메소드가 종료되고, 변수 x의 스코프에서 벗어나게 된다. 원시자료형(Primitive Type)의 경우에는 이런 시나리오로 진행이 되게 된다. 하지만 자바에는 2가지 자료형이 있고, 객체 자료형에서는 다른 결과를 내게 된다.
이는 객체 자료형이 저장하는 값이 원시자료형처럼 자료값이 아닌 자료가 저장된 메모리 참조값(Memory Reference)을 저장하기 때문이다.


// Person 객체를 설계해보자
public class Person {
 private String name;
 private int age;
 public Person(String name, int age) {
  this.name = name;
  this.age = age;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public int getAge() {
  return age;
 }
 public void setAge(int age) {
  this.age = age;
 } 
}

// Test 클래스에서 객체를 사용해보자
public class Test {

 public static void main(String[] args) {
  Person p = new Person("Jason", 22);
  System.out.printf("Name: %s, Age: %d \n", p.getName(), p.getAge());
  // Name: Jason, Age: 22

  changeName(p, "Eva");
  changeAge(p, 33);

  System.out.printf("Name: %s, Age: %d \n", p.getName(), p.getAge());
  // Name: Eva, Age: 33

  Person q = new Person("David", 45);
  System.out.printf("Name: %s, Age: %d \n", q.getName(), q.getAge());
  // Name: David, Age: 45

  swap(p, q);

  System.out.printf("Name: %s, Age: %d \n", p.getName(), p.getAge());
  // Name: Eva, Age: 33

  System.out.printf("Name: %s, Age: %d \n", q.getName(), q.getAge());
  // Name: David, Age: 45
 }

 public static void changeName(Person s, String newName) {
  s.setName(newName);
 }

 public static void changeAge(Person s, int a) {
  s.setAge(a);
 }

 private static void swap(Person a, Person b) {
  Person temp = a;
  a = b;
  b = a;
 }
}

위 코드를 실행하면 객체의 name 변수와, age 변수의 값이 바뀌게 된다. 메소드가 호출될 대, Person sp의 값의 복사값을 받게 된다. 그리고 그 복사값은 p가 가진 객체 참조값과 동일한 값이다. 결국 s와 p는 둘다 같은 참조값을 가지게되고 같은 인스턴스를 참조하게된다. 그리고 둘이 같은 값을 참조하게 되니 둘중 한 객체에 적용되는 변경사항은 둘 모두에게 적용된다.

하지만 swap 메소드를 실행새서 나온 결과를 알다시피 객체를 서로 교체하는 메소드에서는 변경사항이 적용되지 않는것처럼 보인다. 약간 헷갈릴수도 있겠지만, Call by Reference 방식이라면 두 인스턴스가 서로 뒤바뀌는게 맞다. 하지만 Call by Value 방식에서는 앞서 말했듯이 파라미터가 변수의 복사값을 가진다. swap 메소드를 자세히 보자.
  1. 메소드 호출시에 Person 인스턴스 a 와 b는 또다른 Person 인스턴스 p 와 q의 참조값을 복사해 초기화 된다.
  2. 다른 Person temp라는 새로운 인스턴스가 생성되고 a의 참조값을 가지게 된다.
  3. Person 인스턴스 a는 b의 참조값을 가지게 된다.
  4. b는 a의 참조값을 가지게 된다.
  5. 그렇다면 서로 참조값이 바뀌었으니, 서로 바뀌어야하는게 아닌가 싶은데, 실질적으로 인스턴스 a, b는 p, q와는 서로가 참조하는 메모리 주소만 같지 완전 다른 인스턴스이다. 그러니 그 참조값에 저장하는 정보에 적용되는 변경사항이 아니고 인스턴스의 참조값이 변경되는건 적용되지 않는다. 그러면 swap 메소드를 마지막단계까지 진행해보자.

  6. 메소드가 끝나고 인스턴스 a와 b의 스코프(Scope)가 종료되어 a와 b에 접근할 수 없게 된다.
  7. a와 b가 저장하는 참조값은 변했지만 실질적으로 원래 인스턴스인 p와 q는 달라진게 없다.

Call by Value방식을 요약하게되면
  • 메소드는 원시자료형(Primitive Data Type) 파라미터를 변경하는게 불가능하다.
  • 메소드는 객체의 상태(State)를 변경할수있다.
  • 메소드는 객체가 또다른 객체를 참조하게 하는것은 불가능하다.

[Java] static 키워드

자바는 메소드나 데이터를 static 키워드르 붙여 static으로 정의할 수 있다. static은 기본적으로 변수나 메소드가 인스턴스가 아닌 클래스에 속하게 한다.
즉, 인스턴스를 통해 변수에 접근하는게 아니라, 클래스를 통해 접근하고, 이때문에 여러개의 인스턴스에 서로 달리 존재하는 일반적인 변수,메소드와 달리 한개의 클래스에 존재하기 때문에 한개밖에 존재하지 않는다.
위 한문장만 읽으면 조금 헷갈리는데, static 변수와 static 메소드를 각각보면 좀 이해하기 쉽다.


private static int staticVariable;

public static void staticMethod(){
}

일반적인 변수가 객체(인스턴스)당 1개씩 존재하는 반면 static으로 정의된 변수는 클래스당 1개씩 존재한다. 예를들어 vehicle 객체 중 4륜객체가 1이라는 코드를 가지고 이 코드를 저장하는 정수형 변수가 id라고 할때 vehicle 객체는 아래와 같을거다.


public class Vehicle {

   private static int id = 1;
   public static String brand = "Hyundai";
   private String model;

//생성자함수와 다른 mutator, accessor 메소드는 생략
...
}

위의 객체를 생성하고 id 변수와, model 변수에 접근하고, 수정할때 결과를 본다면 아래와 같은 결과가 나온다.


Vehicle a = new Vehicle("Avantte");
Vehicle b = new Vehicle("Prius");

System.out.printf("ID: %d || Model: %s\n",a.getId(),a.getModel());
//ID: 1 || Model: Avantte

System.out.printf("ID: %d || Model: %s\n",b.getId(),b.getModel());
//ID: 1 || Model: Prius

//id 및 model 값 변경
a.setId(2);
a.setModel("Sonata");
 
System.out.printf("ID: %d || Model: %s\n",a.getId(),a.getModel());
//ID: 2 || Model: Avantte

System.out.printf("ID: %d || Model: %s\n",b.getId(),b.getModel());
//ID: 2 || Model: Prius

System.out.printf("브랜드: %s", Vehicle.brand);
//브랜드: Hyundai

위 코드를 실행하게되면, a 객체의 id 변수를 변경했는데, b 객체의 id 변수도 변경된것을 알 수 있다. 이는 id 변수가 static이기 때문이고, static 변수가 아닌 model 변수는 a 객체가 변경되도 b 객체에서 변하지 않는다. 그러니 한 클래스에서 인스턴스화된 모든 객체가 static 변수를 공유한다고 생각하면 된다.
또한 static 변수가 public 으로 선언된다면, 객체생성을 하지 않고, Vehicle.brand처럼 그냥 클래스를 통해 바로 호출이 가능하다.
이런 static 변수들은 보통 final 키워드와 함께 선언되어 Math.PI 변수 처럼 불변(immutable)하는 상수로 이용된다.
우리가 자주쓰는 System.out객체도 static과 final 속성을 가지고 있다. 또한 public 속성과 final 속성을 함께가지는 변수는 객체의 캡슐화를 훼손하지 않는다.

데이터 또는 메소드를 static으로 정의한다면 해당 메소드는 객체에서 실행되지 않는다. 대표적인 예로 Math.pow같은 Math 클래스의 메소드들이 있다. x의 제곱을 계산하는데 우리는 아무 객체도 생성않고 바로 pow 메소드를 호출해 결과값을 반환받는다. 그러니 결과적으로 implicit 파라미터가 필요하지 않다.


public class Vehicle {

   private static int id = 1;
   private static String brand = "Hyundai";
   private String model;

   public static String getBrand(){
   return brand;
   }
//생성자함수와 다른 mutator, accessor 메소드는 생략
...
}

위의 Vehicle 객체에 brand 변수가 private 속성을 가지고 brand 변수에 대한 static accessor 메소드가 존재할 때 우리는 아무런 객체를 생성하지 않고 brand 변수에 접근이 가능하다. 만약 brand 변수의 accessor 메소드가 static 속성을 가지고 있지 않다면 아래와 같이 객체 생성없이 brand 값에 접근할수는 없다.


System.out.println("브랜드: " + Vehicle.brand);
// 브랜드: Hyundai

static 메소드들은 다음과 같은 상황에서 사용하면 된다


  • 메소드가 필요한 모든 파라미터들이 explicit 파라미터로 제공되어 객체의 상태(State)에 접근할 필요가 없을 때
  • 메소드가 클래스의 static 필드에만 접근하면 될때


static 메소드의 대표적인 예는 main 메소드가 있다. main 메소드는 어떤 객체에서도 실행되지 않는다. 다만 프로그램이 실행될때, 아무런 객체도 생성되지 않았기 때문에 main 메소드가 실행되고 프로그램에 필요한 객체들을 생성한다. main 메소드는 어느 클래스든 사용될수 있으니, 객체 설계 후 테스트를 할때도 사용할수있다.

2017년 7월 2일 일요일

[Java] 객체 생성과 캡슐화(Encapsulation)

앞서 말했듯이 객체는 클래스에서 생성된다. 기본적으로 클래스는 아래 형식으로 이루어져있다.

class 클래스이름
{
field1,field2,field3...
constructor1
constructor2
...
method1
method2
...
}

field는 객체가 저장하는 데이터
constructor는 객체를 생성하는 생성자 함수이다.

위의 형식에 따라서 간단하게 중고 자동차 판매 관리 프로그램에 사용될 자동차(vehicle) 객체의 클래스를 만들어보자
간단하게 설계할때, vehicle 객체는 자동차의 브랜드, 모델, 엔진, 사고횟수, 가격등을 저장하게 된다.
생성될때 위 값을 가지고 생성되고, 그 값에 접근하기 위한 accessor 메소드들이 존재한다.
그리고 추후 사고기록을 추가할 경우를 위해 사고기록을 +1하는 메소드를 생성했다.
그외에 다른 정보를 수정할 필요는 없기에, 다른 필드에대한 mutator 메소드들은 존재하지 않는다.
그리고 객체정보를 간략하게 String으로 표현해주는 toString 메소드가 있다.
이 기준에 맞춰 객체를 설계하게되면 아래와 같은 클래스를 적게된다.


public class Vehicle {
 //field
 private String brand, model, engineModel;
 private int price, accidentHistory;

 //constructor
 public Vehicle(String brand, String model, String engineModel, int price) {
  this.brand = brand;
  this.model = model;
  this.engineModel = engineModel;
  this.price = price;
  this.accidentHistory = 0;
 }

 public Vehicle(String brand, String model, String engineModel, int price, int accidentHistory) {
  this.brand = brand;
  this.model = model;
  this.engineModel = engineModel;
  this.price = price;
  this.accidentHistory = accidentHistory;
 }
 
 public String toString(){
  return String.format("브랜드: %s, 모델: %s, 엔진명: %s, 가격: %d," + 
                                      " 사고기록: %d", brand, model, engineModel, price, accidentHistory);
 }
 
 public void addAccidentHistory(int x){
  this.accidentHistory+=Math.abs(x);
 }

 public String getBrand() {
  return brand;
 }

 public String getModel() {
  return model;
 }

 public String getEngineModel() {
  return engineModel;
 }

 public int getPrice() {
  return price;
 }

 public int getAccidentHistory() {
  return accidentHistory;
 }
}

모든 메소드들은 public 키워드를 가진다. public 키워드는 접근 제한자의 한가지로 어떤 클래스의 어떤 메소드든 public 키워드를 가진 메소드를 호출할수 있다는 뜻이다. 하지만 모든 데이터필드를은 private 키워드를 가진다. public과 동일하게 접근제한자이고 private 키워드를 가진 변수는 같은 클래스 안에서만 접근이 가능하다.

생성자 함수는 클래스 이름과 동일한 메소드 이름을 가진다.


public Vehicle(String brand, String model, String engineModel, int price) {
 this.brand = brand;
 this.model = model;
 this.engineModel = engineModel;
 this.price = price;
 this.accidentHistory = 0;
}

public Vehicle(String brand, String model, String engineModel, int price, int accidentHistory) {
 this.brand = brand;
 this.model = model;
 this.engineModel = engineModel;
 this.price = price;
 this.accidentHistory = accidentHistory;
}

이 생성자 함수들은 객체가 생성될때 호출되며, 파라미터로 데이터를 받아 객체에 저장한다.
클래스는 파라미터만 여러개의 다르다면 생성자 함수를 가질 수 있다. 다른 메소드와의 차이점이 있다면, 생성자(Constructor) 함수는 반환(return) 값을 가질 수 없으며, 언제나 new 연산자와 함께 호출된다. 그러므로 이미 생성된 객체의 생성자 함수를 호출할수없다. 그리고 키워드 this는 implicit parameter를 가리킨다.
parameter는 explicit, implicit parameter로 나뉘는데, 예를들어 이 객체에서 addAccidentHistory메소드를 호출할 때에


car.addAccidentHistory(3);

implicit parameter란 Vehicle 형을 가진 객체 car을 뜻하고, explicit parameter란 메소드 이름 뒤 괄호 내부에 있는 값을 뜻한다. implicit 파라미터와 달리 explicit 파라미터는 메소드 선언시, 데이터 타입과 이름을 같이 선언하게된다(public void addAccidentHistory(int x)).
인스턴스의 this 키워드는 모두 implicit parameter를 가리킨다.

인스턴스의 모든 데이터필드는 private 키워드를 가지고 있는데, 이는 자동적으로 인스턴스를 캡슐화한다. 앞서 말했듯이 private 키워드를 가진 변수들은 오직 같은 클래스에서만 접근이 가능하다. 객체가 생성될때 어싸인된 값들은 get 메소드를을 이용해 불러올수는 있지만, private 키워드를 가지고 있기때문에 직접적인 수정이 불가능하다. 만약 필요하다면 addAccidentHistory 메소드 처럼 메소드를 통해서 수정할 수 있다. 만약 개발자가 위의 경우와 같이 사고기록 횟수를 줄이는것을 불가능하게 설계했다면, addAccidentHistory메소드를 통해 사고기록을 늘리는것만 가능하고 줄이는것은 불가능하다.

캡슐화(Encapsulation)을 얻기위해서는 3가지 기준만 충족하면 된다.

  • private 데이터 필드
  • public 접근자(Accessor) 메소드
  • public 수정자(Mutator) 메소드

이로써 얻어지는 이점들은, 우선 클래스 설계에 수정이 필요할 때 해당 접근자(accessor), 수정자(mutator) 메소드의 이름을 제외하고는 모두 다 변경이 가능하다. accessor 메소드란 getBrand처럼 객체의 데이터 필드를 가져오는것이고 mutator 메소드란 객체의 데이터 필드를 변경하는 메소드이다.
예를들어 위 객체에서 price 데이터 필드를 int 에서 double로 변경하고 싶다면, accessor와 mutator 메소드 내용만 변경하면 된다. 객체 사용자의 입장에서는 이 변경을 체감할 수 없다. 그리고 addAccidentHistory에서 주어진 정수의 절대값을 이용해 사고기록 줄이기가 불가능하게 한것처럼, 설계자가 원하는대로 에러를 예방할 수도 있다. 다만 accessor 메소드에서 값을 반환할때 객체는 clone()메소드를 이용해서 복사한 클론을 반환해야한다. 그냥 객체를 반환한다면, 예를들어 Date 객체는 자체에 mutator 메소드를 가지기 때문에 캡슐화(Encapsulation)를 부숴버린다.

또 한가지 알아둬야될것은, 같은 클래스를 이용하는 모든객체들은 서로의 private 데이터 필드에 접근이 가능하다. 그렇기 때문에 아래와 같은 메소드를 위의 객체에 추가해도 제대로 작동하게된다.


public boolean equalPrice(Vehicle v){
   return this.price==v.price;
}


이제 위의 객체를 생성하고 사용해 사용해보자.


Vehicle[] list = new Vehicle[3];
list[0] = new Vehicle("Hyundai", "Sonata", "GDI2.0", 170000000);
list[1] = new Vehicle("Toyota", "Prius", "TOI1.2", 150000000);
list[2] = new Vehicle("Hyundai", "Avantte", "GDI2.0", 80000000, 2);

// 알고보니 현대 소나타 모델이 사고기록이 2번 있었다.
// addAccidentHistory() 메소드를 이용해 기록을 추가해주자
list[0].addAccidentHistory(2);

System.out.println("소나타와 프리우스 가격 일치 여부 : " + list[0].equalPrice(list[1]));
//소나타와 프리우스 가격 일치 여부 : false

// 차량 정보들을 한번보자
for (Vehicle i : list) {
 System.out.println(i.toString());
}

// 브랜드: Hyundai, 모델: Sonata, 엔진명: GDI2.0, 가격: 170000000, 사고기록: 1
// 브랜드: Toyota, 모델: Prius, 엔진명: TOI1.2, 가격: 150000000, 사고기록: 0
// 브랜드: Hyundai, 모델: Avantte, 엔진명: GDI2.0, 가격: 80000000, 사고기록: 2

2017년 6월 27일 화요일

Object Oriented Programming(객체지향프로그래밍) 소개

객체지향프로그래밍(Object Oriented Programming), 줄여서 OOP는  전세대(1970년)의 절차지향프로그래밍(Procedural Programming)과 구조적프로그래밍(Structured Programming)을 갈아치우고 현세대를 지배하는 프로그래밍 패러다임이다. 대부분의 프로그래밍 언어들은 객체 지향적(Object Oriented)이므로 Java, C#, Python 등을 사용한다면 이 컨셉에 익숙해질 필요가 있다.

흔히 객체 지향적이라 말해지는 프로그램들은 객체(Object)들로 이루어진다. 각 객체들은 목적에 맞는 특별한 기능들을 가지고 있고, 사용자들은 그런 기능들을 사용할수 있다. 물론 객체 디자이너가 몇몇 기능들을 사용자들에게서 숨길수도 있다. 과거의 절차지향적, 구조적 프로그래밍들은 문제를 풀기위한 절차(알고리즘)을 설계하는것이였고, 그런 절차들이 결정되면 절차에 알맞게 자료를 저장할수있는 방법을 찾았다. 프로그래밍 언어인 Pascal을 개발한 Nikalus Writh는 "Algorithms + Data Structures = Programs" 라는 책을 1975년 발행하기도 했다. 책 이름을 보면 알고리즘이 앞에오고 자료구조가 뒤에오는데 이는 당시 프로그래밍 컨셉이 "알고리즘(절차) 설계가 데이터 관리보다 선행한다" 였다는 것을 알려준다. 하지만 OOP의 등장은 이 순서를 서로 뒤바꿔 놓아서, "데이터 관리가 알고리즘(절차)에 선행한다" 라는 패러다임을 만들었다.

간단한 프로그램의 경우 절차적 프로그래밍이 훨씬 쉽게 접근할 수 있다. 객체지향 프로그래밍은 더 큰 프로젝트에 적용된다. 간단하게 이 포스트를 보고있는 웹브라우져를 생각해보자. 일반 사용자가 사용하는 기능만 수백게에 달하며, 백그라운드에서 실행되는 기능들을 포함한다면 몇천개가 존재할텐데 만약 객체의 존재 없이 이 코드를 관리한다면 머리가 빠질거다. 객체지향 프로그래밍이 적용된다면 각기 목적에 맞는 클래스들과, 그 클래스들에 맞는 기능들이 잘 분류되기 때문에 훨씬 쉽게 관리가 가능하다.

그렇다면 클래스(Class)란 무었일까?


우리가 자바 프로그래밍에서 javac 라는 명령어로 .java 파일을 컴파일했을때 출력되는 그 클래스(Class)파일이다 클래스(Class)란 객체 생성을 위한 설계도와 같다. 클래스라는 설계도에 맞춰 객체가 생성(Construct)된다. 이때 생성된 객체를 클래스(Class)의 인스턴스(Instance)라 한다. 당연하게도 java파일에 적힌 모든 코드들이 class파일에 들어있다. Java API는 다양한 목적들을 위한 라이브러리를 제공하고, 라이브러리에는 Date, Math, Arrays등 수천개의 클래스들이 존재한다. 그리고 사용자는 본인만의 객체 생성을 위해 직접 클래스를 생성할 수 있다.

여기서 Encapsulation(캡슐화)이라는 컨셉이 등장하는데, 정보 은닉이라고도 알려진 이 컨셉은 말그대로 사용자에게서 객체가 저장하는 정보(Instance
Fields)들에대한 직접적인 접근을 막고, 메소드(method)를 통한 간접적인 접근만 가능케하여 실제 구현내용을 숨기는것이다. 그리고 객체의 현재 상태(State) 그런 Instance Fields들의 상태이며, method들이 호출하여 state를 변경할수있다. 이 캡슐화라는 속성은 클래스가 데이터를 관리하는법을 변경할 수는 있지만, 사용자 입장에서는 언제나 같은 메소드를 호출하므로 아무런 영향을 받지 않는다.

객체(Object)

앞서 말한 class가 인스턴스화된게 객체(Object)이다. 객체의 사용을 위해서는 3가지 특성을 파악해야한다.

  • 객체의 습성(Behavior) - 객체를 어떻게 활용하고, 어떤 메소드를 적용 가능한지
  • 객체의 상태(State) - 특정 메소드를 호출했을때 어떻게 상태가 변하는지
  • 객체의 정체성(Identity) - 동일한 동작과 상태를 가진 다른 객체와 어떻게 구별되는지

같은 클래스에서 인스턴스화된 객체들은 모두 같은 습성을 가지고 있다. 객체의 습성(Behavior)이란 사용자가 어떤 메소드를 호출할수 있느냐에 따라 결정된다. 그리고 각 객체는 데이터들을 저장하는데, 이게 객체의 상태(State)이다. 객체의 상태 변경은 메소드 호출의 결과여야 하고, 그 때문에 객체가 저장하는 데이터들이 모두 동시에 바뀌지는 않는다. 만약 두 객체의 State가 같다해도, 두 객체가 동일하다는것은 아니다. 이는 모든 객체의 정체성(Identity)이 서로 다르기 때문이다

[Java] 배열(Array)

자바에서 배열(Array)는 같은 형(type)의 값들을 저장하는 자료구조이다. index 값을 통해 각 값에 접근이 가능하다. 예를들어, 배열 a 가 존재할 때 a[i]는 a 배열의 i번째 값이다. 배열의 선언은 어떤 형의 배열인지를 표기하고, 초기화시는 배열의 길이를 정해주면 된다. 그리고 바로 값을 어싸인하여 배열을 생성할수도 있다

double[] a = new double[3]; //double 3개를 저장할 수 있는 배열
int b[] = new int[10]; //인터져 10개를 저장할 수 있는 배열
b[0] = 113; //배열 b의 첫번째 값에 113을 저장
System.out.println(a[0]); //0.0
System.out.println(b.length); //10

int[] c = { 1, 2, 3, 4, 5 };
int[] d = new int[] { 6, 7, 8, 9, 10 };

배열의 인덱스는 0부터 시작한다. 길이가 10인 배열은 0 ~ 9까지의 인덱스를 가지고, 100이라면 0 ~ 99까지의 인덱스를 가진다.
배열 초기 생성시, 정수나 소수형 배열은 모든 값에 0이 저장되고, 객체형 배열은 null 값이 저장된다. array.length 프로퍼티를 이용해 배열의 길이를 알 수 있다.
배열은 한번 생성되게되면 그 길이를 수정할 수 없다. 만약 배열의 길이가 수정되어야할 필요가 있다면 ArrayList라는 객체를 이용해야한다.

어레이의 모든 값을 대상으로 하는 for-each 루핑이 존재한다. for-each loop를 이용하면 굳이 인덱스를 통하지 않고 배열의 모든 값에 접근이 가능하다.


int[] a = new int[10];
for(int i = 0; i <a.length; i++){
 System.out.print(a[i] + " ");
}
//0 0 0 0 0 0 0 0 0 0 
System.out.println();


for(int i : a){
 System.out.print(i + " ");
}
//0 0 0 0 0 0 0 0 0 0

//둘다 같은 결과값을 낸다.

배열은 객체이기 때문에 한 배열을 다른 배열에 어싸인하는 것은 2개의 배열이 같은 메모리값를 참조하게 만든다. 그렇기 때문에 배열의 값이 수정되면 2 배열이 같은 메모리 위치를 참조하기 때문에 모두 수정된 값을 가지게 된다. 그렇기 때문에 배열을 복사하거나 배열의 길이를 늘리고 싶다면 Arrays.copyOf 메소드를 이용해야한다.


int[] a = { 1, 2, 3, 4, 5 };
int[] b = a;
for (int i = 0; i < a.length; i++) {
 a[i]++;
}
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));

a = Arrays.copyOf(a, a.length * 2);
// 배열 a와 같은 값을 가지고 크기가 2배인 배열 생성후 어싸인
b = Arrays.copyOf(a, a.length);
// 배열 a 복사 후 b에 어싸인

for (int i = 0; i < a.length; i++) {
 a[i]++;
}

System.out.println(Arrays.toString(b));
// [2, 3, 4, 5, 6, 0, 0, 0, 0, 0]

System.out.println(Arrays.toString(a));
// [3, 4, 5, 6, 7, 1, 1, 1, 1, 1]

배열 정렬


손쉽게 배열을 정렬하고 싶다면 Arrays.sort() 메소드를 이용하면 된다. 기본적인 Quick Sort 알고리즘을 적용한 메소드이다.


int[] a = new int[5];
for (int i = 0; i < a.length; i++) {
 a[i] = (int)(Math.random()*10);
}
System.out.println(Arrays.toString(a));
//[4, 9, 1, 2, 2]
Arrays.sort(a);
System.out.println(Arrays.toString(a));
//[1, 2, 2, 4, 9]

다차원 배열(Multidimensional Array)


다차원 배열은 2개 이상의 인덱스를 사용해 값에 접근해야하는 배열이다. 1차원 배열이 부족할 때 사용된다. 예를들어 3*3 매트릭스를 만들고 싶다고 하면
원리는 단순히 1차원 배열의 각 값에 또다른 배열이 들어있다 생각하면 된다. 그렇기 때문에 접근방법은 동일하다. 그렇기 때문에 각 2차원 배열의 크기가 틀리게 생성할수도 있다.


int[][] a = new int[3][3];//3*3 이차원 배열
a[1][1] = 10;
for(int[] i : a)
{
 System.out.println(Arrays.toString(i));
}
// [0, 0, 0]
// [0, 10, 0]
// [0, 0, 0]

// 2차원 배열이 다른 크기를 가진 배열 생성
int[][] b = new int[6][];
for(int i = 0;i