생성자는 new 연산자로 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한다.
여기서 객체 초기화란 필드를 초기화하거나 메소드를 호출해서 객체를 사용할 준비를 하는 것을 말한다. 주의할 점은 생성자를 실행하지 않고 객체를 만들 수 없다. new 연산자에 의해 생성자가 성공적으로 실행되면 힙 영역에 객체가 생성되고 객체의 번지가 리턴된다.
tip.생성자가 성공적으로 실행되지 않고 에러가 발생했다면 객체는 당연히 생성되지 않는다.
모든 클래스는 생성자가 반드시 존재하며, 생성자를 하나 이상 가질 수 있다. 우리가 클래스 내부에 생성자 선언을 생략했다면 컴파일러는 다음과 같이 중괄호 {} 블록 내용이 비어 있는 기본 생성자를 바이트 코드에 자동 추가한다.
[public] Test() { } //Test는 클래스 명 클래스()는 생성장
그러나 클래스에 명시적으로 선언한 생성자가 1개라도 있다면 컴파일러는 기본 생성자를 추가하지 않다. 명시적으로 생성자를 선언하는 이유는 객체를 다양한 값으로 초기화하기 위해서 이다.
기본 생성자 대신 우리가 생성자를 명시적으로 선언하려면 다음과 같은 형태로 작성해야 한다.
Test(String s){} // 클래스 명( 매개변수선언, .....)
생성자는 메소드와 비슷한 모양을 가지고 있으나, 리턴 타입이 없고 클래스 이름과 동일하다. 클래스에 생성자가 명시적으로 선언되어 있을 경우에는 반드시 선언된 생성자를 호출해서 객체를 생성해야만 하다.
public class Car {
//생성자
Car(String color, int cc){
}
}
public class CarExample {
public static void main(String[] args){
Car myCar = new Car("검정",3000); //주의:Car myCar = new Car(); <- 기본 생성자 호출 불가
}
}
클래스로부터 객체가 생성될 때 필드는 기본 초기값으로 자동 설정된다. 만약 다른 값으로 초기화 하고 싶다면 필드를 선언할 때 초기값을 주는 방법이고, 또 다른 하나는 생성자에서 초기값을 주는 방법이다.
class Korean {
String nation = "대한민국";
String name;
String ssn;
Korean(String n String s) {
name = n;
ssn = s;
}
}
public class KoreanMain {
public static void main(String[] args) {
Korean k1 = new Korean("변자바", "933233-3344559");
System.out.println("k1.name: " + k1.name);
System.out.println("k1.name: " + k1.ssn);
}
}
Korean 생성자의 매개 변수 이름은 각각 n과 s 를 사용했다. 매개 변수의 이름은 보통 가독성을 위해 동일한 이름 갖거나 비슷한 이름을 갖는다. 그러나 이 경우 필드와 매개 변수 이름이 동일하기 때문에 생성자 내부에서 해당 필드에 접근할 수 없다. 왜냐하면 동일한 이름의 매개 변수가 사용 우선순위가 높기 때문이다. 해결 방법은 앞에 ‘this.’을 붙이면 됩니다. this는 객체 자신의 참조이다. 우리가 우리 자신을 나라고 하듯이 객체가 객체 자신을 this라고 한다.
public Korean(String name, String ssn){
this.name = name; // 순서대로 this.name 은 필드이고 name 매개 변수 입니다.
this.ssn = ssn;
}
외부에서 제공되는 다양한 데이터들을 이용해서 객체를 초기화하려면 생성자도 다양화될 필요가 있다. 그래서 자바는 다양한 방법으로 객체를 생성할 수 있도록 생성자 오버로딩을 제공한다. 생성자 오버로딩이란 매개변수를 달리하는 생성자를 여러개 선언하는 것을 말한다.
tip [생성자의 오버로딩] 매개 변수의 타입, 개수, 순서가 다르게 선언 되는 것을 말한다.
class CarTest {
String company = "현대자동차";
String model;
int maxSpeed;
CarTest(){
}
CarTest(String model){
this.model = model;
}
CarTest(String model, int speed){
this.model = model;
this.maxSpeed = speed;
}
}
public class Car {
public static void main(String[] args) {
CarTest car1 = new CarTest();
CarTest car2 =new CarTest("자가용");
CarTest car3 = new CarTest("자가용",100);
}
}
주의할 점!!
생성자 오버로딩 시 주의할 점은 매개변수의 타입과 개수 그리고 선언된 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 생성자 오버로딩이 아니다.!
Car(String model, String color){}
Car(String color, String model){}
생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다. 매개 변수의 수만 달리하고 필드 초기화 내용이 비슷한 생성자에서 이러한 현상을 많이 볼 수 있다. 이 경우에는 필드 초기화 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선한다.
이럴경우 생성자에서 다른 생성자를 호출할 수 있는 this() 코드를 사용한다
사용시 주의사항 this()는 자신의 다른 생성자를 호출하는 코드로 반드시 생성자의 첫 줄에서만 허용한다.
class CarTest {
String company = "현대자동차";
String model;
int maxSpeed;
CarTest(){
}
CarTest(String model){
this(model,100000); // 생성자에서 model 만적어도 속도까지 출력할 수있음
}
CarTest(String model, int speed){
this.model = model;
this.maxSpeed = speed;
}
}
public class Car {
public static void main(String[] args) {
CarTest car1 = new CarTest();
CarTest car2 =new CarTest("자가용");
CarTest car3 = new CarTest("자가용",100);
}
}
패키지란 서로 관련된 클래스와 인터페이스의 묶음이다. 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 더욱 효율적으로 관리할 수 있다. 자바에서 패키지는 기본적으로 제공하는 내장 패키지와 사용자가 정의하는 사용자 지정 패키지로 나눌 수 있다.
❗️클래스가 물리적으로 하나의 클래스(*.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다.
패키지 이름 짓는 법
클래스나 인터페이스의 소스파일(.java) 맨 위에 한 줄만 적으면 된다.
package 패키지명;
모든 클래스는 반드시 하나의 패키지에 포함되어야하며, 자신이 속할 패키지를 지정하지 않은 모든 클래스는 자동적으로 이름 없는 패키지(default)에 속하게 된다.
사용할 클래스가 속한 패키지를 지정하는데 사용한다. import문을 사용하여 선언하면 클래스를 사용할 때 패키지명을 생략할 수 있다.
💡 java.lang 패키지의 클래스는 import하지 않아도 사용할 수 있다.
모든 소스파일(*.java)에서 import문은 package 문 다음에, 클래스 선언문 이전에 위치해야 한다.
import 패키지명.클래스명; //지정된 패키지 안에 특정 클래스를 선언한다.
import 패키지명.*; //지정된 패키지 안에 속하는 모든 클래스를 선언한다.
💡 import 문은 컴파일시에 처리되기 때문에 프로그램 성능에 아무 영향을 미치지 않는다.
static import문을 사용하면 static 멤버를 호출할 때 클래스 이름을 생략할 수 있다.
import static java.lang.Math.random;
import static java.lang.System.out;
out.println(random());
//System.out.println(Math.random());
컴파일러(javac.exe)나 JVM등이 클래스 위치를 찾는데 사용되는 경로이다. JVM의 동작과정을 보면 javac로 소스코드(.java)을 바이트 코드(.class)로 컴파일 시켜주고, java runtime(java 또는 jre)으로 해당 클래스 파일을 실행시킨다. 이때, 자바는 이 클래스 파일을 찾을 수 있어야하는데 이때 classpath를 사용한다.
.class파일이 포함된 디렉토리와 파일을 콜론으로 구분한 목록이다.
javac <options> <source files>
만약 Test.java파일이 C:\java 디렉토리 안에 존재하고 필요한 클래스 파일들이 C:\java\TestClass에 위치한다면 아래와 같다.javac -classpath C:\java\TestClass C:\java\Test.java
Java에서 정보란 클래스의
인스턴스 변수
를 의미한다. 정보를 은닉한다는 것은 인스턴스 변수를 숨긴다는 것. 즉,정보 은닉
이란 클래스의 상태 정보를 안전하게 보호하기 위한 기능이다.
class Circle {
double rad = 0; // 원의 반지름
final double PI = 3.14;
public Circle(double r){
setRad(r); // 아래에 정의된 setRad 메서드 호출을 통한 초기화
}
public void setRad(double r){
if(r < 0){ // 반지름은 0보다 작을 수 없다
rad = 0;
return; // 여기서 메서드를 빠져 나감
}
rad = r;
}
public double getArea(){
return (rad * rad) * PI; // 원의 넓이 반환
}
}
class WrongCircle {
public static void main(String args[]) {
Circle c = new Circle(1.5);
System.out.println(c.getArea());
c.setRad(2.5);
System.out.println(c.getArea());
c.setRad(-3.3);
System.out.println(c.getArea());
c.rad = -4.5; // **잘못된 접근 방법!** 이 지점에서 문제 발생
System.out.println(c.getArea());
}
}
메서드 호출
을 통해서 변경을 진행해야 한다.c.rad = -4.5; // 큰 문제는 이러한 값을 넣어도 컴파일 오류가 나지 않고 값을 출력한다는것!
인스턴스 변수의 직접적인 접근을 허용하면, 컴파일 과정에서 드러나지 않는 중대한 실수가 발생할 수 있다. 따라서 위와 같은 접근을 허용하지 않도록 클래스를 설계해야 하며, 이러한 클래스의 설계를 가리켜
정보은닉
이라고 한다.
정보 은닉을 하기 위해서 인스턴스 변수 앞에
private
을 선언한다. 은닉된 객체 상태정보에 데이터를 입력하기 위해서는Getter
,Setter
메서드를 이용한다.
class Circle {
private double rad = 0;
final double PI = 3.14;
public Circle(double r){
setRad(r);
}
public void setRad(double r){
if(r < 0){
rad = 0;
return;
}
rad = r;
}
public double getRad(){
return rad;
}
public double getArea(){
return (rad * rad) * PI; // 원의 넓이 반환
}
}
class WrongCircle {
public static void main(String args[]) {
Circle c = new Circle(1.5);
System.out.println("반지름 : " + c.getRad());
System.out.println("넓이: " + c.getArea() + '\n');
c.setRad(3.5);
System.out.println("반지름 : " + c.getRad());
System.out.println("넓이 : " + c.getArea());
}
}
private double rad = 0;
이것이 의미하는 바는 “변수 rad는 클래스 내부에서만 접근을 허용하겠다!” 이다.
- 즉, Circle클래스 내에 정의된 메서드 내부에서의 접근만을 허용하겠다는 것.
- 따라서 클래스 외부에서 private으로 선언된 멤버에 접근할 경우 컴파일 오류가 발생한다.
public static void main(String args[]) {
Circle c = new Circle(1.5);
* * *
c.rad = -4.5; // 컴파일 오류
* * *
}
rad
를 private
으로 선언했으므로 getter
와 setter
메서드를 입력해주어야 한다.public void setRad(double r){ // rad의 값을 저장(수정)
if(r < 0){
rad = 0;
return;
}
rad = r;
public double getRad(){ // rad에 저장된 값을 반환
return rad;
}
setRad
는 ‘값의 설정’을 위한 메서드이고, getRad
는 ‘값의 참조’를 위한 메서드이다.참조
하는 용도로 정의된 메서드설정
하는 용도로 정의된 메서드클래스, 변수, 메서드 선언부에 사용되어 부가적인 의미를 부여한다.
제어자는 크게 접근 제어자와 그 외의 제어자로 나뉜다.
하나의 대상에 여러 개의 제어자를 사용할 수 있으나, 접근제어자는 하나만 사용가능하다.
접근 제어자 | public, protected, default, private |
---|---|
그 외 제어자 | static, fianl, abstract, native, transient, synchronized, volatile, strictfp |
멤버변수, 메서드, 초기화 블럭에서 사용된다.
클래스, 메서드, 멤버변수, 지역변수에서 사용된다.
final이 붙은 변수는 상수이므로 보통은 선언과 초기화를 동시에 하지만, 인스턴스마다 고정값을 갖는 인스턴스 변수의 경우 생성자에서 초기화를 한다.
클래스와 메서드에서 사용된다.
클래스, 멤버변수, 메서드, 생성자에서 사용되며, 외부로부터의 접근을 제한한다.
접근제어자는 외부로부터 데이터를 보호하기 위해, 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서 사용한다. 이를 캡슐화라고 한다.
일반적으로 생성자의 접근제어자는 클래스의 접근제어자와 일치한다. 생성자에 접근제어자를 사용함으로써 인스턴스의 생성을 제한한다.
final class Singleton {
//getIntance()에서 사용될 수 있도록 인스턴스가 미리 생성되어야 하므로 static을 붙인다.
private static Singleton s = new Singleton();
private Singleton(){ //생성자
//...
}
public static Singleton getIntance() {
if (s ==null) {
s = new Singleton();
}
return s;
}
}
class SingletonTest {
public static void main(String args[]){
Singleton s = new Singleton(); //error!
Singleton s1 = Singleton.getInstance();
}
}
[출처]
자바의 정석 - https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp