728x90
반응형
클래스와 인스턴스
- 학생에 대한 학번, 이름, 학년, 주소를 모두 통칭할 수 있는 이름인 학생 클래스를 만들어보자.
- 붕어빵과 붕어빵틀을 생각하자
- 붕어빵틀로부터 붕어빵을 만든다.
- 붕어빵틀은 같을지 몰라도, 재료에 따라 다른 붕어빵이 만들어진다.
- 각각의 붕어빵들은 서로 다른 붕어빵이다.
- 하지만 붕어빵틀은 같은 틀이다.
- 클래스로부터 인스턴스를 만든다.
- 붕어빵틀로부터 붕어빵을 만든다.
- 붕어빵 틀 : 학생 클래스
- 붕어빵 :
- 인스턴스 1
- 재료 : 20221001, 홍길동, 1학년, 강남구
- 인스턴스 2
- 재료 : 20221002, 이순신, 1학년, 중구
- 인스턴스 1
- 객체 = 실체 = 인스턴스
- Student 클래스의 studentID, studentName, grade, address 는 아직 메모리에 할당 되지 않은 상태.
- 하지만 객체(=인스턴스) 를 만들고 난 후 20221001, 홍길동, 1학년, 강남구 코엑스 라는 정보는 메모리 할당이 되고 실체(=인스턴스, 객체)가 만들어짐.
- 학생 클래스를 통해서 여러가지 인스턴스를 만들 수 있다.
(정의한 클래스명) (인스턴스 명) = new 생성자();
Student 홍길동 = new Student();
생성자는 클래스 이름과 동일하며, return 타입이 없다.
(void 도 적지 않는다.)
- new 라는 예약어를 쓰면 Heap 영역에 객체를 만들어낸다.
- 참조형 데이터 또한 마찬가지로 Heap 영역에 주소값이 생성(할당)된다.
- 기본형 데이터는 Stack 영역에 저장된다.
생성자
- 리턴 값, 리턴 타입이 없다.
- 메서드 안에서 파라미터로 처리할 수 있게 된다.
- 기본 생성자 vs 명시적 생성자
- 기본 생성자 : 클래스 내부로부터 가져가는 데이터가 없다.
- 밀가루만 있는 붕어빵
- 명시적 생성자 : 클래스 내부로부터 가져가는 데이터가 있다.
- 초코 붕어빵
- 기본 생성자 : 클래스 내부로부터 가져가는 데이터가 없다.
- 생성자를 호출해서 인스턴스를 만들 수 있는데, 생성자가 없다면 인스턴스를 만들 수 없다. 그래서 컴파일러가 클래스가 인스턴스를 만들 수 있도록 기본 생성자를 자동으로 부여한다.
- 즉, 클래스 안에 생성자가 하나도 없으면 컴파일러가 기본 생성자를 자동으로 주고, 생성자가 하나라도 있으면 기본 생성자를 만들어줘야한다.
- MyBatis vs JPA
- SQL을 직접 작성하는 쪽이 MyBatis
- 기본 생성자 추가를 잊으면 안된다.
- SQL 기능을 이미 가지고 있는 쪽이 JPA
- SQL을 직접 작성하는 쪽이 MyBatis
class_basic1 : 클래스, 인스턴스
// Car 클래스에 public 키워드를 사용하려면 파일 이름이 Car이어야 함.
class Car {
// 멤버 변수 (인스턴스 변수)
String name;
boolean gasoline; // run 메서드에서는 이 변수를 전역 변수처럼 사용할 수 있음.
// 멤버 메서드 (인스턴스 메서드)
void run() {
if (gasoline) {
System.out.println("부릉 부릉");
} else {
System.out.println("덜컹 덜컹");
}
}
void stop() {
// 차를 멈추는 메서드
System.out.println("끼익");
}
}
public class class_basic1 {
public static void main(String[] args) {
// new Car()를 호출하면 기본 생성자가 자동으로 호출되어 객체가 생성됨.
// Car 클래스에는 생성자가 정의되어 있지 않기 때문에 자바가 기본 생성자를 자동으로 제공함.
// korando 변수에 생성된 Car 객체를 대입함.
// korando는 Car 클래스 타입의 참조 변수로, '인스턴스 변수' 또는 '인스턴스 객체'라고도 불림.
Car korando = new Car();
korando.name = "코란도C"; // 인스턴스 변수 name에 값을 대입.
korando.gasoline = false; // 인스턴스 변수 gasoline에 값을 대입.
System.out.print(korando.name + " : ");
korando.run(); // korando 객체의 run 메서드를 호출.
Car equus = new Car();
equus.name = "에쿠스";
equus.gasoline = true;
System.out.print(equus.name + " : ");
equus.run();
equus.stop();
}
}
class_basic2 : 클래스, 인스턴스
class Human {
int age; // 나이: 초기화하지 않으면 기본값은 0
String name; // 이름: 초기화하지 않으면 기본값은 null
// Human 객체의 소개 메서드
void intro() {
System.out.println("안녕, " + age + "살 " + name + "이라고 해.");
}
}
public class class_basic2 {
public static void main(String[] args) {
// Human 인스턴스 생성 후 age와 name을 설정
Human kim = new Human();
kim.age = 29; // age 값을 설정
kim.name = "김상형"; // name 값을 설정
kim.intro(); // 인스턴스의 intro() 메서드 호출
// 또 다른 Human 인스턴스 생성
Human ping = new Human();
ping.age = 22;
ping.name = "하츄핑";
ping.intro(); // ping 인스턴스의 intro() 호출
// 인스턴스를 생성하지만 변수에 저장하지 않음
// 이 경우 참조할 이름이 없어 이후 호출 불가
new Human(); // 생성만 하고 참조할 이름이 없음
// 아무 데이터도 설정하지 않은 인스턴스 생성
Human hong = new Human();
hong.intro(); // 기본값으로 설정된 나이(0)와 이름(null)이 출력됨
/* 설명:
- age와 name은 기본적으로 각각 0과 null로 초기화됨.
- 객체를 생성했지만 값을 설정하지 않으면, 자바는 해당 변수에 기본값을 자동으로 할당.
- hong 변수는 이러한 기본값을 가진 인스턴스를 가리키고 있음.
*/
}
}
constructor1 : 생성자 오버로딩
class User{
String name;
int age;
void userInfo() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
}
/*
* 생성자(constructor)
* - 클래스를 통해 생성되는 인스턴스의 초기화를 도와주는 메서드의 일종
* - 클래스 내에 선언되며, 클래스의 이름과 같으며, 리턴 값, 타입이 없다
* - 생성자도 오버로딩이 가능하므로 하나의 클래스에 여러 개의 생성자 존재 가능
*/
// 기본 생성자
// 생성자가 하나라도 존재하면, 기본 생성자를 반드시 추가해야한다.
User(){
System.out.println("기본 생성자 호출");
}
// 명시적 생성자
User(String aName){
System.out.println("명시적 생성자 호출1");
name = aName;
}
User(int aAge){
System.out.println("명시적 생성자 호출2");
age = aAge;
}
User(String aName, int aAge){
System.out.println("명시적 생성자 호출3");
name = aName;
age = aAge;
}
// 이름이 똑같은 생성자인데, 파라미터가 다르다.
// = 생성자 오버로딩
// 생성자는 인스턴스 초기화 기능을 도와준다.
}
public class constructor1 {
public static void main(String[] args) {
// 기본 값 인스턴스
// 기본 생성자를 이용해 인스턴스가 생성됨
User user1 = new User();
user1.userInfo();
// 이름만 넣은 인스턴스
// 명시적 생성자를 잉ㅇ해 인스턴스가 생성됨
User user2 = new User("홍길동");
user2.userInfo();
// 나이만 넣은 인스턴스
User user3 = new User(37);
user3.userInfo();
// 이름과 나이 모두 넣은 인스턴스
User user4 = new User("하츄핑", 45);
user4.userInfo();
}
}
생성자 관련 실습
- 다른 파일에 있는 생성자를 불러와서 사용해보자 !!
Time : 클래스만 정의해 놓음 (초기화 블럭, 생성자 내부에서 생성자 호출 )
import java.time.LocalTime;
// 클래스만 정의해놓은 Time
// 실행은 constructor2 파일에서 진행
public class Time {
boolean am;
int hour;
int minute;
int second;
// 초기화 블럭
// 클래스 내부에서 범위 블럭에 만들어준다.
// 인스턴스를 생성할 때, 생성자보다도 더 먼저 호출이 된다.
// 공통적으로 처리해야할 데이터가 있거나 자동으로 처리해야할 것들을
// 초기화 블럭을 통해 초기화해준다.
{
System.out.println("초기화 블럭 실행");
LocalTime now = LocalTime.now();
hour = now.getHour();
if (hour >= 12) {
hour -= 12;
am = false;
} else {
am = true;
}
minute = now.getMinute();
second = now.getSecond();
}
// 기본 생성자
Time() {
System.out.println("기본 생성자 실행");
}
// 명시적 생성자
// 전역변수와 파라미터의 변수명이 같을 수 있는데, this를 붙여야함.
Time(boolean am, int hour, int minute, int second){
/*
* this
* - 클래스 내부에서는 해당 인스턴스를 부를 이름이 없음.
* - 인스턴스 이름이 각자 다르기 때문에 this를 사용해서
* 지금 다루는 데이터가 그 자신을 가리키도록 사용
* - 생성된 인스턴스 스스로를 가리키는 예약어 (참조 변수)
*/
// 전역변수의 am = 파라미터의 am
// this는 만들어진 인스턴스를 가리기키다.
// this.am = am;
// this.hour = hour;
// this.minute = minute;
// 위의 세 줄의 중복 코드를 계속 작성하지 않고
// 3가지의 파라미터를 받는 생성자를 호출한다.
// 생성자가 다른 생성자를 호출하게 되는 것
// 자기 자신을 가리키는 또다른 생성자, 객체 등을 this로 호출 가능
this(am, hour, minute);
this.second = second;
System.out.println("명시적 생성자4 실행");
}
Time(boolean am, int hour, int minute){
// 생성자를 호출하는 코드는 가장 맨 위에 있어야한다.
this(hour, minute);
this.am = am;
// 중복된 코드
// this.hour = hour;
// this.minute = minute;
this.second = 0;
System.out.println("명시적 생성자3 실행");
}
Time(int hour, int minute){
this.am = hour < 12;
this.hour = hour % 12;
this.minute = minute;
this.second = 0;
System.out.println("명시적 생성자2 실행");
}
void whatTime() {
System.out.println(am ? "오전" : "오후");
System.out.println(hour + "시 " + minute + "분 " + second + "초");
}
}
constructor2 (Time 클래스를 활용)
public class constructor2 {
public static void main(String[] args) {
Time t = new Time();
t.whatTime();
Time t2 = new Time(true, 10, 12, 34);
t2.whatTime();
Time t3 = new Time(true, 12, 34);
t3.whatTime();
Time t4 = new Time(18, 30);
t4.whatTime();
}
}
- 명시적 생성자3, 명시적 생성자4 가 연달아 나온 경우는, 파라미터 네 개를 받는 생성자가 파라미터 세 개를 받는 생성자를 호출했기 때문!
- 생성자 내 다른 생성자를 호출하면서, 공통된 코드들을 줄일 수 있다!!
- 파라미터 4 개를 받는 생성자가 파라미터 3 개를 받는 생성자를 호출하고, 파라미터 3 개를 받는 생성자가 파라미터 2 개를 받는 생성자를 호출한다.
왜 생성자 안에서 생성자를 부르는게 가장 첫 줄에 있어야하나요?
- this(hour, minute);가 밑에 있으면 안 되는 이유는 생성자 내부에서 다른 생성자를 호출하는 구문은 항상 첫 번째 줄에 위치해야 하기 때문입니다. 자바는 하나의 생성자가 다른 생성자를 호출할 수 있지만, 이 호출은 반드시 첫 번째 줄에서 이루어져야 합니다. 그렇지 않으면 컴파일 에러가 발생합니다.
- 생성자 내부에서 다른 생성자를 호출하는 것은 객체 초기화의 중요한 순서를 결정하는 요소이기 때문입니다. 자바는 객체를 생성할 때 객체 초기화 순서를 명확히 정의하고자, 한 생성자가 다른 생성자를 호출할 때 그 호출이 가장 먼저 이루어지도록 강제합니다. 이렇게 해야 객체가 완전히 초기화된 후 다른 작업을 수행할 수 있기 때문입니다.
- 만약 this(hour, minute);가 첫 번째 줄에 있지 않고 그 아래에 위치한다면, 다음과 같은 컴파일 에러가 발생합니다:즉, 다른 생성자를 호출하는 코드가 첫 줄에 있어야만 정상적으로 객체의 초기화 과정을 올바르게 제어할 수 있기 때문입니다.
Static 개념
- JVM의 컴파일러가 .java 확장자 명의 파일을 .class 확장자 명의 파일로 변환시킴.
- Method 영역, Stack 영역, Heap 영역
- static은 Method 영역에 존재한다.
- static 키워드가 붙은 메서드나 변수는 static 영역에 존재하게 된다.
- Static 영역은 프로그램을 실행했을 때, Heap과 Stack보다도 가장 먼저 메모리 할당이 된다. 그리고 프로그램이 끝날 때까지 할당이 유지된다. 그래서 Static 영역은 Heap과 Stack에서 참조(공유)할 수 있게 된다.
- Non-static 영역 : Heap 영역, Stack 영역 등등
- static이라는 키워드가 붙어있다면, 인스턴스를 생성하지 않고도 이미 생성된 인스턴스이므로 사용할 수 있게 된다.
- 원래는 인스턴스를 만들어야만 해당 메서드를 부를 수 있는데, static 영역에 있는 것은 인스턴스를 만들지 않고도, 메서드와 변수를 사용할 수 있게 된다.
constructor3 : static
class Product {
static int count = 0; // 생성된 인스턴스의 수를 저장하기 위한 변수
int serialNo; // 인스턴스 고유 번호
// static이 붙으면 정적 변수, 클래스 변수, 멤버 변수
// static이 안 붙이면 인스턴스 변수, 멤버 변수
static String productName; // 클래스 초기화, 정적 or 클래스 변수
int num = 1; // 명시적 초기화, 인스턴스 변수
/*
* 클래스 변수 : 클래스가 처음 로딩될 때 단 한 번 초기화됨
* 인스턴스 변수 : 인스턴스가 생성될 때마다 각 인스턴스 별로 초기화(Heap 영역)
* 클래스 변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
* 인스턴스 변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자
*
* 클래스 변수는 static 영역이라 이미 생성되어져있어서, 생성자 순서가 없다.
* static을 너무 남발하면 안된다. GC가 처리하지 않기 때문에 메모리 낭비가 될 수 있다.
* Heap 영역에서는 GC(Garbage Collector)가 청소를 해준다.
*/
// 클래스 초기화 블럭 : static 키워드가 있다. (= 정적 초기화 블럭)
static {
productName = "scit_46";
System.out.println("static: " + productName);
System.out.println("클래스 초기화 블럭 실행");
}
// 인스턴스 초기화 블럭 : static 키워드가 없다.
{
count++;
serialNo = count;
System.out.println("인스턴스 초기화 블럭 실행");
}
// 기본 생성자
public Product() {
num++;
System.out.println("기본 생성자 실행");
}
}
public class constructor3 {
public static void main(String[] args) {
// p1이 생성될 때 처음으로 Product 클래스가 로딩되므로
// p1 생성 시 console 창에서 "static: ~"부터 "첫 기본 생성자 실행"까지 출력됨.
Product p1 = new Product();
Product p2 = new Product();
Product p3 = new Product();
// num 변수는 static 키워드가 없는 인스턴스 변수.
// 그래서 변한 값이 저장되지 않고 인스턴스 생성 시
// 계속 1로 초기화된다.
System.out.println(p1.num); // 2
System.out.println(p2.num); // 2
System.out.println(p3.num); // 2
// serialNo 변수는 인스턴스가 생성될 때마다 초기화되지 않고 저장된다.
// static 키워드를 붙인 클래스 변수이기 때문.
// static 키워드가 없으면, 인스턴스 변수라서 계속 초기화되어 변한 값이 저장되지 않는다.
System.out.println("p1의 제품번호: " + p1.serialNo); // 1
System.out.println("p2의 제품번호: " + p2.serialNo); // 2
System.out.println("p3의 제품번호: " + p3.serialNo); // 3
System.out.println("생상된 제품의 수는 모두 " + Product.count + "개");
// non-static 영역에서 static 영역 변수인 count를 호출
System.out.println(p1.count); // 3
// static 영역인 Product 클래스에서 static 영역 변수인 count를 호출
System.out.println(Product.count); // 3
// p1 인스턴스를 만들었으니까 인스턴스 변수를 부르는 건 가능하다.
// non-static 영역에서 non-static 영역 변수인 serialNo를 호출
System.out.println(p1.serialNo);
// static 영역 클래스인 Product에서 non-static 영역 변수인 serialNo을 호출하는 것은 불가능
// non-static 변수는 인스턴스가 생성되기 전에는 존재하지 않는 변수이다.
// 가장 외부에 있는 클래스는 독립적인 논리 단위라서 static을 붙일 수 없다.
// System.out.println(Product.serialNum);
}
}
728x90
반응형