5주차
클래스 정의하는 방법
클래스란 객체를 정의해 놓은것 또는 클래스는 객체의 설계도 또는 틀이라고 정의할 수 있다. 클래스는 객체를 생성하는데 사용되며, 객체는 클래스에 정의된 대로 생성된다.
클래스의 정의 : 클래스란 객체를 정의해 놓은것이다.
클래스의 용도 : 클래스는 객체를 생성하는데 사용된다.
클래스 이름은 다른 클래스와 식별할 목적으로 사용되므로 자바의 식별자 작성 규칙에 따라서 만들어야 한다.
- 하나이상의 문자로 이루어져야 한다. 예) Car,SportsCar
- 첫 번째 글자는 숫자가 올 수 없다. 예) Car, 3Car(x)
- '$','_' 외의 특수문자는 사용 할 수 없다. 예)$Car, _Car , @Car(x), #Car(x)
- 자바 키워드는 사용할 수 없다. 예)int(x), for(x)
public class 클래스이름{}
//클래스 뒤에는 반드시 중괄호{}를 붙여주는데,
//중괄호 시작{은 클래스 선언의 시작을 알려주고 중괄호 끝}은 클래스 선언의 끝을알려준다.
public class Car{}
class Tire{}
//일반적으로 소스 파일당 하나의 클래스를
//선언하지만 두 개 이상의 클래스 선언도 가능하다.
두개 이상의 클래스가 선언된 소스 파일을 컴파일하면 바이트 코드 파일은(.class) 클래스를 선언한 개수만큼 생긴다.
객체 만드는 방법(new 키워드 이해하기)
클래스를 선언한 다음, 컴파일을 했다면 객체를 생성할 설계도가 만들어진 셈이다. 클래스로부터 객체를 생성하는 방법은 다음과 같이 new 연산자를 사용하면 된다.
new 클래스();
new 는 클래스로부터 객체를 생성시키는 연산자이다. new 연산자 뒤에는 생성자가 오는데, 생성자는 클래스() 형태를 가지고 있다. new 연산자로 생성된 객체는 메모리 힙(heap) 영역에 생성된다.
new 연산자는 힙 영역에 객체를 생성시킨 후, 객체의 주소를 리턴하도록 되어 있다.
이 주소를 참조 타입인 클래스 변수에 저장해 두면, 변수를 통해 객체를 사용할 수 있다.
다음은 클래스 타입으로 선언된 변수에 new 연산자가 리턴한 객체의 주소를 저장하는 코드이다.
클래스 변수; 변수 = new 클래스();
클래스 변수 선언과 객체 생성을 한 개의 실행문으로 작성할 수도 있다.
클래스 변수 = new 클래스();
이렇게 new 연산자로 객체를 생성하고 리턴된 객체의 주소를 변수에 저장하면 다음 그림과 같이 변수가 객체를 참조하게 된다.
@SpringBootApplication
public class StudyApplication {
public static void main(String[] args) {
SpringApplication.run(StudyApplication.class, args);
Student s1 = new Student();
System.out.println("s1 변수가 Student 객체를 참조합니다.");
Student s2 = new Student();
System.out.println("s2 변수가 또다른 Student 객체를 참조합니다.");
}
}
//결과
s1 변수가 Student 객체를 참조합니다.
s2 변수가 또다른 Student 객체를 참조합니다.
Student 클래스는 하나지만 new 연산자를 사용한 만큼 객체가 메모리에 생성된다.
이러한 객체들은 Student 클래스의 인스턴스들이다. 비록 같은 클래스로부터 생성되었지만 각각의 Student 객체는 자신만의 고유 데이터를 가지면서 메모리에서 활동하게 된다.
System.out.println("s1의 주소값 : "+s1);
System.out.println("s2의 주소값 : "+s2);
//결과값
s1의 주소값 : com.example.study.Student@27abb83e
s2의 주소값 : com.example.study.Student@69e308c6
서로 다른 주소를 참조하고 있어서 완전히 독립된 서로 다른 객체임을 알 수 있다.
메소드 정의하는 방법
메소드는 객체의 동작에 해당하는 중괄호{} 블록을 말한다. 중괄호 블록은 이름을 가지고 있는데, 이것이 메소드 이름이다.
메소드를 호출하게 되면 중괄호 블록에 있는 모든 코드들이 일괄적으로 일행된다.
메소드는 필드를 읽고 수정하는 역할도 하지만, 다른 객체를 생성해서 다양한 기능을 수행하기도 한다.
메소드는 객체 간의 데이터 전달의 수단으로 사용된다. 외부로부터 매개값을 받을 수 도 있고, 실행 후 어떤 값을 리턴할 수도 있다.
메소드 선언은 선언부(리턴타입, 메소드이름, 매개변수선언) 와 실행 블록으로 구성된다.
리턴타입 메소드이름([매개변수선언, ...]){
실행할 코드를 작성하는곳
}
//여기의 리턴타입 메소드이름 등 자세하게 알아보자
- 리턴 타입은 메소드가 실행 후 리턴하는 값의 타입을 말한다.
메소드는 리턴값이 있을 수도 있고 없을 수도 있다. 메소드가 실행 후 결과를 호출한 곳에 넘겨줄 경우에는 리턴값이 있어야 한다.
예를들어 전자계산기 객체에서 전원을 켜는 powerOn() 메소드와 두 수를 나누는 기능인 divide() 메소드가 있다고 가정해보자. divide()메소드는 나눗셈의 결과를 리턴해야 하지만 powerOn() 메소드는 전원만 켜면 그만이다. 따라서 powerOn() 메소드는 리턴값이 없고, divide() 메소드는 리턴값이 있다고 봐야한다.
리턴값이 없는 메소드는 리턴 타입에 void가 와야하며 리턴값이 있는 메소드는 리턴값의 타입이 와야한다.
void powerOn() {...}
divide() 메소드의 결과가 double 값이라면 double을 리턴 타입으로 사용해야 한다.
double divide(int x, int y) {...}
리턴값이 있으냐 없느냐에 따라 메소드를 호출하는 방법이 조금 다르다.
powerOn(); //powerOn 메소드 호출
double result = divide(10,20); //divide 메소드 호출
int result = divide(10,20); // 컴파일에러
리턴 타입이 있다고 해서 반드시 리턴값을 변수에 저장할 필요는 없다. 리턴값이 중요하지 않고, 메소드 실행이 중요할 경우에는 다음과 같이 변수 선언 없이 메소드를 호출할 수도 있다.
divide(10,20);
- 메소드 이름
메소드 이름은 자바 식별자 규칙에 맞게 작성하면 되는데, 다음 사항에 주의하면 된다.
- 숫자로 시작하면 안 되고, $와 _ 를 제외한 특수 문자를 사용하지 말아야 한다.
- 관례적으로 메소드명은 소문자로 작성한다.
- 서로 다른단어가 혼합된 이름이라면 뒤이어 오는 단어의 첫머리 글자는 대문자로 작성한다.
void run(){...}
void startEngine(){...}
String getName(){...}
int[] getScore(){...}
메소드 이름은 이 메소드가 어떤 기능을 수행하는지 쉽게 알 수 있도록 기능 이름으로 지어주는 것이 좋다.
2. 매개 변수 선언
매개 변수는 메소드가 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용된다. 매개 변수도 필요한 경우가 있고 필요 없는 경우가 있다.
예를들어 powerOn() 메소드는 그냥 전원만 켜면 그만이지만, divide() 메소드는 나눗셈할 두 수가 필요하다. divide(10,20) 따라서 powerOn() 메소드는 매개변수가 필요없고, divide() 메소드는 매개 변수가 두 개 필요하다.
double divide(int x, int y){ ... }
이렇게 선언된 divide() 메소드를 호출할 때에는 반드시 두 개의 int 값을 주어야한다.
double result = divide(10,20);
호출 시 넘겨준 매개값인 10과 20은 해당 위치의 매개 변수인 x와y에 각각 저장되고, 이 매개 변수들을 이용해서 메소드 블록을 실행한다.
divide() 메소드가 int 타입 매개 변수를 가지고 있다면 호출 시 매개값으로 int 값이나 int 타입으로 변환될 수 있는 값을 넘겨주어야 한다.
double result = divide(10.5, 20.5) //컴파일 에러
3. 리턴(return) 문
메소드 선언에 리턴 타입이 있는 메소드는 반드시 retrun문을 사용해서 리턴값을 지정해야한다.
retrun문의 리턴값은 리턴 타입이거나 리턴 타입으로 변환될 수 있어야 한다. 예를 들어 리턴 타입으로 int인 plus() 메소드에서 byte, short, int 타입의 값이 리턴되어도 상관없다. byte와 short은 int로 자동 타입 변환되어 리턴되기 때문이다.
int plus(int x, int y){
int result = x + y;
return result;
}
int plus(int x, int y){
byte result = (byte) (x+y);
return result;
}
return문 뒤에 실행문이 오면 "Unreachable code"라는 컴파일 오류가 발생한다. 왜냐하면 return문 이후의 실행문은 결고 실행되지 않기 때문이다.
하지만 다음과 같은 경우, 컴파일 에러가 발생하지 않는다.
boolean isLeftGas(){
if(gas==0){
System.out.println("gas가 없습니다.");
return false;
}
System.out.println("gas가 있습니다.");
return true;
}
생성자 정의하는 방법
생성자(Constructor)는 new 연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한다.
객체 초기화란 필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 하는 것을 말한다.
new 연산자에 의해 생성자가 성공적으로 실행되면 힙(heap) 영역에 객체가 생성되고 객체의 주소가 리턴된다. 리턴된 객체의 주소는 클래스 타입 변수에 저장되어 객체에 접근할 때 이용된다. 만약 생성자가 성공적으로 실행되지 않고 예외(에러)가 발생했다면 객체는 생성되지 않는다.
- 기본 생성자
모든 클래스는 생성자가 반드시 존재하며, 하나 이상을 가질 수 있다.
우리가 클래스 내부에 생성자 선언을 생략했다면 컴파일러는 다음과 같이 중괄호{} 블록 내용이 비어있는 기본 생성자(Default Constructor)를 바이트 코드에 자동 추가시킨다.
//소스파일(Car.java)
public class Car{ } //바이트 코드 파일(Car.class)
public class Car{
public Car(){} //자동추가
}
그렇기 때문에 클래스에 생성자를 선언하지 않아도 다음과 같이 new 연산자 뒤에 기본 생성자를 호출해서 객체를 생성시킬 수 있다.
Car myCar = new Car();
- 생성자 선언
기본 생성자 대신 우리가 생성자를 명시적으로 선언하려면 다음과 같은 형태로 작성하면 된다.
클래스(매개변수선언, ...){ //객체의 초기화 코드 }
생성자는 메소드와 비슷한 모양을 가지고 있으나, 리턴 타입이 없고 클래스 이름과 동일하다.
생성자 블록 내부에는 객체 초기화 코드가 작성되는데, 일반적으로 필드에 초기값을 저장하거나 메소드를 호출하여 객체 사용 전에 필요한 준비를 한다.
예를들어 다음과 같이 Car 생성자를 호출할 때 세 개의 값을 제공한다고 보자
Car myCar = new Car("그랜져","검정",300);
두 개의 매개값은 String 타입이고 마지막 매개값은 int 타입인 것을 볼 수 있다. 세 매개값을 생성자가 받기 위해서는 다음과 같이 생성자를 선언해야한다.
public class Car{
//생성자
Car(String model, String color, int maxSpeed){...}
}
클래스에 생성자가 명시적으로 선언되어 있을 경우에는 반드시 선언된 생성자를 호출해서 객체를 생성해야만 한다.
public class Car{
//생성자
Car(String color, int cc){
}
}
public class CarEx{
public static void main(String[] args){
Car myCar = new Car("검정",3000);
//Car myCar = new Car(); x기본생성자를 호출할수없다.
}
}
this 키워드 이해하기
먼저 코드를 살펴보자
public Class Korean{
//필드
String nation = "대한민국";
String name;
String birthDay;
//생성자
public Korean(String n, String b){
name = n;
birthDay = b;
}
}
public class koreanEx{
public static void main(String[] args){
Korean k1 = new Korean("홍성필", "930806");
System.out.println("k1.name : " + k1.name);
System.out.println("k1.birthDay : " + k1.birthDay);
}
}
//출력
k1.name : 홍성필
k1.birthDay : 930806
위의 코드가 이해는 되지만 Korean 생성자의 매개 변수 이름은 각각 n 과 b를 사용했다.
매개 변수의 이름이 너무 짧으면 코드의 가독성이 좋지 않기 때문에 가능하면 초기화시킬 필드 이름과 비슷하거나 동일 이름을 사용할 것을 권한다.
관례적으로 필드와 동일한 이름을 갖는 매개 변수를 사용한다.
이 경우 필드와 매개 변수 이름이 동일하기 때문에 생성자 내부에서 해당 필드에 접근할 수 없다. 왜냐하면 동일한 이름의 매개 변수가 사용 우선순위가 높기 때문이다.
해결 방법은 필드 앞에 "this."를 붙이면 된다. this는 객체 자신의 참조인데, 우리가 자신을 "나"라고 하듯이 객체가 객체 자신을 this라고 한다.
자 다시 수정해보자
public Class Korean{
//필드
String nation = "대한민국";
String name;
String birthDay;
//생성자
public Korean(String name, String birthDay){
this.name = name; //this.name이 필드, name이 매개변수이다.
this.birthDay = birthDay; //this.birthDay이 필드, birthDay이 매개변수이다.
}
}
참고 문헌
이것이 자바다