접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용, 제한할 수 있다.
package access;
public class Speaker {
int volume;
public Speaker(int volume) {
this.volume = volume;
}
void volumeUp() {
if (volume >= 100) {
System.out.println("음량을 증가할 수 없습니다. 최대 음량 입니다.");
} else {
volume += 10;
System.out.println("음량을 10 증가 합니다.");
}
}
void volumeDown() {
volume -= 10;
System.out.println("volumeDown 호출");
}
void showVolume() {
System.out.println("현재 음량 : " + volume);
}
}
// 이런 코드에서 필드에 직접 접근을 막고싶을때 접근 제어자를 사용하여 막을 수 있다.
Private
모든 외부 호출은 막고, 내부 호출만 사용 가능
해당 접근제어자가 붙은 필드, 메서드 는 외부에서 접근 할 수 없다.
private int volume;
외부에서 사용하려 하면 java: private access 이런 오류를 내보낸다.
접근 제어자 종류
4가지 종류의 접근 제어자가 있다.
private → 모든 외부 호출을 막는다
default(package - private) → 같은 패키지 안에서 만 호출 가능 사용법: 접근 제어자를 작성하지 않으면 사용 된다.
protected → 같은 패키지 안에서 호출 가능, 그리고 상속 관계의 호출은 허용.
public → 모든 외부 호출 가능
제한 순위
private > default > protected > public 순으로 제한을 한다.
접근 제어자 위치
필드, 메서드 , 생성자에 사용 되고
클래스 레벨에서도 일부 접근 제어자를 사용 할 수 있다.
접근제어자의 목적
속성(맴버변수), 기능(메서드)를 외부로부터 숨기고, 제한해서 열어주는것
접근 제어자 사용 - 클래스 레벨
클래스 레밸의 접근 제어자는 public, default 만 사용할 수 있다.
private, protected 는 사용할 수 없다.
public 클래스는 반드시 파일 명과 동일해야 한다.
public 클래스는 파일 당 1개만 가능하다.
default 클래스는 파일에 무한정 만들 수 있다.
package access.a;
public class PublicClass {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
class DefaultClass1 {
}
class DefaultClass2 {
}
// accss.a 패키지 내부에 생성된 public 클래스는 1개 default 클래스는 2개 이고
// public class 에서 자기 자신을 호출하고 default 클래스를 호출하고 있는 것 이다.
→ 파일 형식은 이렇게 표현된다
접근제어자와 캡슐화
캡슐화
객체 지향 프로그래밍의 중요한 개념 중 하나.
데이터와 데이터 처리 메서드를 하나로 묶어 외부의 접근을 제한 하는 것을 말한다.
쉽게 말해 속성과 기능을 하나로 묶고, 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것 이다.
기능이 적은 프로젝트라면 문제가 되지 않을 테지만 기능이 점점 커지게 되면 여러 클래스들을 테마별로 묶어 관리 해줄 필요가 생기게 될 것이다.
이때에 서로 관련 있는 기능들을 분류하여 관리 할 때에 사용 한다.
컴퓨터의 폴더 개념과 동일하게 자바에서는 패키지라고 이해하면 될 꺼 같다.
package pack; // <- 패키지 위치
public class Data {
public Data() {
System.out.println("패키지 pack Data 생성");
}
}
→ 항상 모든 코드들은 자기가 위치해있는 패키지를 제일 상단에 표시해 주어야 한다.
package pack.a; // <- 점으로 위치 들어감
public class User {
public User() {
System.out.println("패키지 pack.a 회원 생성");
}
}
→ 패키지의 하위 폴더는 . (점, Dot)으로 구분
패키지 - Import
다른 패키지에 만들어진 class 를 호출 할 때에 import 를 사용한다.
package pack;
import pack.a.User; // <- import 를 하고
public class PackageMain2 {
public static void main(String[] args) {
Data data = new Data();
User user = new User(); // <- 외부 클래스를 사용할 수 있다.
}
}
import 를 사용한 덕분에 패키지 명을 생략 하고 클래스 이름 만으로 호출이 가능하게 되고 코드가 간소화 된다.
*(별)표를 사용하면 하위 클래스까지 호출한다는 뜻을 지닌다.
*(별)표가 하위 패키지는 호출하지 않는다.
package pack;
import pack.a.*; // <- a의 패키기 하위의 모든것을 사용한다는 뜻
public class PackageMain2 {
public static void main(String[] args) {
Data data = new Data();
User user = new User();
User2 user2 = new User2();
}
}
클래스 이름 중복
패키지 덕분에 클래스의 이름이 같아도 패키지로 구분하여 사용 할 수 있다.
pack.a.User;
pack.b.USer;
// 이런식으로 구분 가능하다
package pack;
import pack.a.User;
public class PackageMain3 {
public static void main(String[] args) {
User user = new User();
pack.b.User userB = new pack.b.User();
}
}
// 단하나는 import 하고 동일한 이름의 클래스를 호출한다면
// 하나는 fullName 으로 작성해주어야 한다.
객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(Construct)를 이용하면 된다.
제약을 걸어준다.
THIS → 나 자신의 객체 주소를 나타낸다.
public class MemberInit {
String name;
int age;
int grade;
// 추가
void initMember(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}
this 는 매개변수로 받는 name 인지 멤버 변수의 name 인지 구분해주기 위해 사용하고,
this 가 붙은 변수는 멤버 변수임을 나타낸다.
this 를 생략해도 되는 경우🔽
public class MemberThis {
String nameField; // 멤버변수
void initMember(String nameParameter) { // 매개변수
nameField = nameParameter;
}
}
// 멤버 변수와 매개 변수의 변수명이 다를때는 this 가 생략 가능하다.
생성자 - 도입
대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 편리하게 수행할 수 잇도록 생성자 기능을 제공한다.
객체를 생성하자마자 그 즉시 필요한 기능 수행
참조 타입 클래스의 객체를 생성시켜주는 것 → 내가 이해한 이론
생성자와 메서드의 차이점 및 생성자 특징
생성자 이름은 클래스 이름과 같아야 한다.
생성자는 반환 타입이 없다.
나머지는 메서드와 같다.
생성자가 있는 클래스는 객체 호출을 할 때에 바로 생성자가 호출 된다.
생성자를 사용한 기능은 무조건 초기값을 세팅하는 “제약” 조건이 성립 하게 된다.
생성자 장점
생성자가 없던 시전에는 어떤 작업을 수행하기 위해서 생성자 생성후 메서드를 한번더 호출 해야 했다. → 하지만 생성자 덕분에 객체 생성하면서 바로 필요한 작업을 하게 되어 코드를 간소화 시키고, 간결하게 작업처리가 가능해졌다.
// 생성자 등장 전
MemberInit member = new MEmberInit(); // 객체 생성
member.intitMemeber("user1", 15, 90); // 메서드 호출 (작업 실행)
// 생성자 등장 후
MemberConstruct member = new MemberConstruct("user1", 15, 90); // 객체 생성과 동시에 작업 실행
→ 이러한 장점이 있다.
생성자를 호출한다면 무조건 초기 값을 설정 해 주어야 하는 “제약” 조건이 생성되게 되어 → 개발자의 실수를 막아줄 수 있다.
기본 생성자
매개 변수가 없는 생성자
해당 클래스에 생성자가 하나도 없으면 자바 컴파일러가 자동으로 기본 생성자를 만들어 준다.
하지만 생성자가 하나라도 있으면 기본 생성자를 만들지 않는다.
기본 생성자를 자동으로 만들어주는 이유?
생성자의 속성 값 만 쓰고 기능이 필요 없을 때 개발자가 직접 생성자를 정의해주어야 하는 불편함이 있기 때문에 자동으로 생성해주고 사용 한다.
생성자 - 오버로딩과 THIS
오버로딩 - 생성자도 메서드 오버로딩 처럼 매개변수만 다르게 해서 여러 생성자를 제공할 수 있다.
public class MemberConstruct {
String name;
int age;
int grade;
MemberConstruct(String name, int age) {
this.name = name;
this.age = age;
this.grade = 50;
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name = " + name + ", age = " + age + ", grade = " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
→ 이렇게 작성하면 MemberConstruct 의 생성자가 2개가 된다.
MemberConstruct(String name, int age)
MemberConstruct(String name, int age, int grade)
→ 이렇게 오버로딩이 된다.
→ 오버로딩된 생성자를 각각 매개변수 타입에 맞게 인자를 전달 하면 알맞은 생성자를 호출 한다
public class ConstructMain2 {
public static void main(String[] args) {
MemberConstruct member1 = new MemberConstruct("user1", 15, 90);
// System.out.println 이 있는 생성자를 호출 할테고
MemberConstruct member2 = new MemberConstruct("user2", 16);
// sout 이 없는 생성자를 호출 할 것이다.
MemberConstruct[] members = {member1, member2};
for (MemberConstruct s : members) {
System.out.println("이름 : " + s.name + ", 나이 : " + s.age + ", 성적 : " + s.grade);
}
}
}
→ 출력 해보면 다음과 같이 출력 된다
생성자 호출 name = user1, age = 15, grade = 90
이름 : user1, 나이 : 15, 성적 : 90
이름 : user2, 나이 : 16, 성적 : 50
오버로딩과 THIS 같이 사용하여 중복 제거
this → 자신의 참조 값을 나타낸다. → 결국 나 자신을 호출 하는 것
this( ) 의 규칙으로는 생성자 블럭 안에서 첫 줄에만 작성이 가능하다.
public class MemberConstruct {
String name;
int age;
int grade;
MemberConstruct(String name, int age) {
this.name = name;
this.age = age;
this.grade = 50;
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name = " + name + ", age = " + age + ", grade = " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
// 위의 코드를 보면 중복 되는 부분이 보인다.
// 중복된는 부분은 this 를 사용 하여 줄일 수 있는데
public class MemberConstruct {
String name;
int age;
int grade;
MemberConstruct(String name, int age) {
this(name, age, 50);
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name = " + name + ", age = " + age + ", grade = " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
// 이렇게 사용하면 this 가 다시한번 생성자를 호출 하고 인자를 넘기는데
// 이때에 인자 타입이 맞는 생성자를 호출 하게 된다.
이런 식으로 자기 자신을 this 로 호출 하여 간소화 시킬 수 있다.
생성자
객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(Construct)를 이용하면 된다.
제약을 걸어준다.
THIS → 나 자신의 객체 주소를 나타낸다.
public class MemberInit {
String name;
int age;
int grade;
// 추가
void initMember(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}
this 는 매개변수로 받는 name 인지 멤버 변수의 name 인지 구분해주기 위해 사용하고,
this 가 붙은 변수는 멤버 변수임을 나타낸다.
this 를 생략해도 되는 경우🔽
public class MemberThis {
String nameField; // 멤버변수
void initMember(String nameParameter) { // 매개변수
nameField = nameParameter;
}
}
// 멤버 변수와 매개 변수의 변수명이 다를때는 this 가 생략 가능하다.
생성자 - 도입
대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 편리하게 수행할 수 잇도록 생성자 기능을 제공한다.
객체를 생성하자마자 그 즉시 필요한 기능 수행
참조 타입 클래스의 객체를 생성시켜주는 것 → 내가 이해한 이론
생성자와 메서드의 차이점 및 생성자 특징
생성자 이름은 클래스 이름과 같아야 한다.
생성자는 반환 타입이 없다.
나머지는 메서드와 같다.
생성자가 있는 클래스는 객체 호출을 할 때에 바로 생성자가 호출 된다.
생성자를 사용한 기능은 무조건 초기값을 세팅하는 “제약” 조건이 성립 하게 된다.
생성자 장점
생성자가 없던 시전에는 어떤 작업을 수행하기 위해서 생성자 생성후 메서드를 한번더 호출 해야 했다. → 하지만 생성자 덕분에 객체 생성하면서 바로 필요한 작업을 하게 되어 코드를 간소화 시키고, 간결하게 작업처리가 가능해졌다.
// 생성자 등장 전
MemberInit member = new MEmberInit(); // 객체 생성
member.intitMemeber("user1", 15, 90); // 메서드 호출 (작업 실행)
// 생성자 등장 후
MemberConstruct member = new MemberConstruct("user1", 15, 90); // 객체 생성과 동시에 작업 실행
→ 이러한 장점이 있다.
생성자를 호출한다면 무조건 초기 값을 설정 해 주어야 하는 “제약” 조건이 생성되게 되어 → 개발자의 실수를 막아줄 수 있다.
기본 생성자
매개 변수가 없는 생성자
해당 클래스에 생성자가 하나도 없으면 자바 컴파일러가 자동으로 기본 생성자를 만들어 준다.
하지만 생성자가 하나라도 있으면 기본 생성자를 만들지 않는다.
기본 생성자를 자동으로 만들어주는 이유?
생성자의 속성 값 만 쓰고 기능이 필요 없을 때 개발자가 직접 생성자를 정의해주어야 하는 불편함이 있기 때문에 자동으로 생성해주고 사용 한다.
생성자 - 오버로딩과 THIS
오버로딩 - 생성자도 메서드 오버로딩 처럼 매개변수만 다르게 해서 여러 생성자를 제공할 수 있다.
public class MemberConstruct {
String name;
int age;
int grade;
MemberConstruct(String name, int age) {
this.name = name;
this.age = age;
this.grade = 50;
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name = " + name + ", age = " + age + ", grade = " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
→ 이렇게 작성하면 MemberConstruct 의 생성자가 2개가 된다.
MemberConstruct(String name, int age)
MemberConstruct(String name, int age, int grade)
→ 이렇게 오버로딩이 된다.
→ 오버로딩된 생성자를 각각 매개변수 타입에 맞게 인자를 전달 하면 알맞은 생성자를 호출 한다
public class ConstructMain2 {
public static void main(String[] args) {
MemberConstruct member1 = new MemberConstruct("user1", 15, 90);
// System.out.println 이 있는 생성자를 호출 할테고
MemberConstruct member2 = new MemberConstruct("user2", 16);
// sout 이 없는 생성자를 호출 할 것이다.
MemberConstruct[] members = {member1, member2};
for (MemberConstruct s : members) {
System.out.println("이름 : " + s.name + ", 나이 : " + s.age + ", 성적 : " + s.grade);
}
}
}
→ 출력 해보면 다음과 같이 출력 된다
생성자 호출 name = user1, age = 15, grade = 90
이름 : user1, 나이 : 15, 성적 : 90
이름 : user2, 나이 : 16, 성적 : 50
오버로딩과 THIS 같이 사용하여 중복 제거
this → 자신의 참조 값을 나타낸다. → 결국 나 자신을 호출 하는 것
this( ) 의 규칙으로는 생성자 블럭 안에서 첫 줄에만 작성이 가능하다.
public class MemberConstruct {
String name;
int age;
int grade;
MemberConstruct(String name, int age) {
this.name = name;
this.age = age;
this.grade = 50;
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name = " + name + ", age = " + age + ", grade = " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
// 위의 코드를 보면 중복 되는 부분이 보인다.
// 중복된는 부분은 this 를 사용 하여 줄일 수 있는데
public class MemberConstruct {
String name;
int age;
int grade;
MemberConstruct(String name, int age) {
this(name, age, 50);
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name = " + name + ", age = " + age + ", grade = " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
// 이렇게 사용하면 this 가 다시한번 생성자를 호출 하고 인자를 넘기는데
// 이때에 인자 타입이 맞는 생성자를 호출 하게 된다.
실제 세계의 사물이나 사건을 객체로 보고, 객체들 간의 상호 작용을 중심으로 프로그래밍 하는 방식 → 즉 “무엇” 을 중심으로 프로그래밍 한다.
차이점 → 절차 지향 방식은 데이터에 대한 처리 방식이 분리 되어 있지만 객체 지향 방식에서는 데이터와 그 데이터의 처리 방식이 하나의 객체 안에 포함되어 있다.
절차 지향 프로그램을 → 순차적으로 개선 하기
다음은 절차 지향 프로그래밍으로 코드를 짠 것이다. 🔽
public class MusicPlayerMain1 {
public static void main(String[] args) {
int volume = 0;
boolean isOn = false;
// 음악 플레이어 켜기
isOn = true;
System.out.println("음악 플레이어를 시작합니다.");
// 볼륨 증가
volume++;
System.out.println("음악 플레이어 볼륨 : " + volume);
// 볼륨 증가
volume++;
System.out.println("음악 플레이어 볼륨 : " + volume);
// 볼륨 감소
volume--;
System.out.println("음악 플레이어 볼륨 : " + volume);
// 음악 플레이어 상태
System.out.println("음악 플레이어 상태 확인");
if (isOn) {
System.out.println("음악 플레이어 ON, 볼륨 : " + volume);
} else {
System.out.println("음악 플레이어 OFF");
}
// 음악 플레이어 끄기
isOn = false;
System.out.println("음악 플레이어를 종료 합니다.");
}
}
순서대로 코드가 실행 되었다.
음악 플레이어를 시작합니다.
음악 플레이어 볼륨 : 1
음악 플레이어 볼륨 : 2
음악 플레이어 볼륨 : 1
음악 플레이어 상태 확인
음악 플레이어 ON, 볼륨 : 1
음악 플레이어를 종료 합니다.
// 코드를 짠 대로 위에서부터 순서대로 코드를 실행 한 것이다.
절차 지향 프로그램 → 클래스 묶기
public class MusicPlayerData {
int volume = 0;
boolean isOn = false;
}
public class MusicPlayerMain2 {
public static void main(String[] args) {
MusicPlayerData data = new MusicPlayerData();
// 음악 플레이어 켜기
data.isOn = true;
System.out.println("음악 플레이어를 시작합니다.");
// 볼륨 증가
data.volume++;
System.out.println("음악 플레이어 볼륨 : " + data.volume);
// 볼륨 증가
data.volume++;
System.out.println("음악 플레이어 볼륨 : " + data.volume);
// 볼륨 감소
data.volume--;
System.out.println("음악 플레이어 볼륨 : " + data.volume);
// 음악 플레이어 상태
System.out.println("음악 플레이어 상태 확인");
if (data.isOn) {
System.out.println("음악 플레이어 ON, 볼륨 : " + data.volume);
} else {
System.out.println("음악 플레이어 OFF");
}
// 음악 플레이어 끄기
data.isOn = false;
System.out.println("음악 플레이어를 종료 합니다.");
}
}
이런식으로 볼륨과 상태를 하나의 클래스로 묶어 관리하기때문에 관리가 쉬워진다.
절차 지향 프로그램 → 메서드 추출
// 뮤직플레이어2 에서 선언한 클래스를 동일하게 사용한다.
public class MusicPlayerMain3 {
public static void main(String[] args) {
MusicPlayerData data = new MusicPlayerData();
// 음악 플레이어 켜기
on(data);
// 볼륨 증가
volumeUp(data);
// 볼륨 증가
volumeUp(data);
// 볼륨 감소
volumeDown(data);
// 음악 플레이어 상태
showStatus(data);
// 음악 플레이어 끄기
off(data);
}
static void on(MusicPlayerData data) {
data.isOn = true;
System.out.println("음악 플레이어를 시작합니다.");
}
static void off(MusicPlayerData data) {
data.isOn = false;
System.out.println("음악 플레이어를 종료 합니다.");
}
static void volumeUp(MusicPlayerData data) {
data.volume++;
System.out.println("음악 플레이어 볼륨 : " + data.volume);
}
static void volumeDown(MusicPlayerData data) {
data.volume--;
System.out.println("음악 플레이어 볼륨 : " + data.volume);
}
static void showStatus(MusicPlayerData data) {
System.out.println("음악 플레이어 상태 확인");
if (data.isOn) {
System.out.println("음악 플레이어 ON, 볼륨 : " + data.volume);
} else {
System.out.println("음악 플레이어 OFF");
}
}
}
메서드를 추출해서 사용하니 각각의 기능들이 모듈화가 되었다.
메서드 추출의 장점
중복제거
변경 영항 범위 → 메서드 내부에서만 내부 변경이 된다.
메서드 이름 추가 → 기능을 이름으로 구분해서 코드를 이해하기 쉬워진다.
이 코드 역시 절차 지향 프로그램이다.
절차 지향 프로그래밍의 한계
데이터와 기능이 분리가 되어있다.
음악 플레이어의 데이터는 MusicPlayerData 에 담겨있고
음악 플레이어의 기능은 MusicPlayerMain3 에 나뉘어 담겨있다.
MusicPlayerData 의 맴버 변수의 변수 명이나 타입이 변경되면 MusicPlayerMain3 내부의 메서드도 함께 변경이 되어야 한다.
이런식으로 데이터와 기능이 분리되어있으면 관리포인트가 2곳으로 나뉘어 있게 된다.
객체 지향 프로그래밍
→ 속성과 기능이 한 곳에 정의됨
클래스와 메서드
해당 코드는 데이터인 value 와 데이터를 사용하는 기능인 add() 메서드를 함께 정의 했다.
public class ValueData {
int value;
void add() {
value++;
System.out.println("숫자 증가 value = " + value);
}
}
// 이런 코드를 객체 지향 프로그래밍이라고 할 수 있다.
// 속성과 기능이 한 곳에 정의 된것
참고 → 메서드는 원래 객체를 생성해야 호출 할 수 있다. 하지만 static 이 붙으면 객체 생성 없이 메서드 호출이 가능하다. (나중에 설명 예정)
public class ValueObjectMain {
public static void main(String[] args) {
ValueData valueData = new ValueData();
valueData.add();
valueData.add();
valueData.add();
System.out.println("최종 숫자 = " + valueData.value);
}
}
이렇게 클래스 내부의 메서드도 호출 할 수 있다.
정리
클래스 속성 (데이터, 멤버 변수)과 기능(메서드)을 정의할 수 있다.
객체는 자신의 메서드를 통해 멤버 변수에 접근할 수 있다.
객체 지향 프로그래밍
위에서 만든 음악 플레이어를 활용할 것 인데
지금은 음악 플레이어 라는 개념을 중요하게 생각하고 객체 지향적으로 프로그램을 작성할 예정이다.
public class MusicPlayer {
int volume = 0;
boolean isOn = false;
void on( ) {
isOn = true;
System.out.println("음악 플레이어를 시작합니다.");
}
void off( ) {
isOn = false;
System.out.println("음악 플레이어를 종료 합니다.");
}
void volumeUp( ) {
volume++;
System.out.println("음악 플레이어 볼륨 : " + volume);
}
void volumeDown() {
volume--;
System.out.println("음악 플레이어 볼륨 : " + volume);
}
void showStatus() {
System.out.println("음악 플레이어 상태 확인");
if (isOn) {
System.out.println("음악 플레이어 ON, 볼륨 : " + volume);
} else {
System.out.println("음악 플레이어 OFF");
}
}
}
// 온전한 음악 플레이어에 필요한 속성과 기능을 작성 하였다.
public class MusicPlayerMain4 {
public static void main(String[] args) {
MusicPlayer player = new MusicPlayer();
// 음악 플레이어 켜기
player.on();
// 볼륨 증가
player.volumeUp();
// 볼륨 증가
player.volumeUp();
// 볼륨 감소
player.volumeDown();
// 음악 플레이어 상태
player.showStatus();
// 음악 플레이어 끄기
player.off();
}
}
// 플레이어 클래스를 호출하여 온전하게 사용 할 수 있다.
MusicPlayer를 사용하는 입장에서는 MusicPlayer의 데이터 (변수) 가 무었이 들어있는지 신경 쓰지 않고 단순하게 기능만 호출해서 사용할 수 있게 된다.
String 도 class 인 참조형 데이터 타입이다. → 그런데 기본형처럼 문자 값을 바로 대입할 수 있다.
기본형, 참조형 - 변수 대입
대원칙 - 기본형이던 참조형이던 자바는 항상 변수의 값을 복사해서 대입한다.
단 참조형은 실제 값을 복사하는게 아니라 값의 위치를 저장한 주소값만 복사하는것 이다.
기본형, 참조형 - 메서드 호출
메서드 호출 역시 대원칙인 값 복사를 따른다.
메서드의 매개변수 (파라미터) 도 결국 변수일 뿐, 값 전달 역시 값을 복사해서 전달 한다.
기본형 - 메서드 호출 예 ⬇️
public class MethodChange1 {
public static void main(String[] args) {
int a = 10;
System.out.println("호출 전 a = " + a); // 10
changePrimitive(a); // <- a 의 값인 10 이 들어간다
System.out.println("호출 후 a = " + a); // 10
// x 에 20 이 저장 되었을뿐 a 에는 10이 여전히 존재하기 때문
}
public static void changePrimitive(int x) { // <- x 에 10 저장
x = 20; // <- x 에 20 저장
}
}
참조형 - 메서드 호출 예 ⬇️
public class MethodChange2 {
public static void main(String[] args) {
Data dataA = new Data();
dataA.value = 10;
System.out.println("호출 전 dataA.value = " + dataA.value); // 10
changeReference(dataA); // <- 주소 위치 정보 전달
System.out.println("호출 후 dataA.value = " + dataA.value); // 20
// 주소정보에 value값을 불러보니 변경이 되어 있어 20 을 호출 하게 된다.
}
public static void changeReference(Data dataX) { // <- 주소 위치정보 받음
dataX.value = 20; // <- 주소위치에 value에 저장된 값을 직접 변경
}
}
참조형과 메서드 호출 - 활용
참조형 class에 메서드 활용하기
public class Method1 {
public static void main(String[] args) {
Student student1 = new Student();
initStudent(student1, "학생1", 15, 90);
Student student2 = new Student();
initStudent(student2, "학생2", 16, 80);
printStudent(student1);
printStudent(student2);
}
// 입력 부위
static void initStudent(Student student, String name, int age, int grade) {
// Student 타입 참조데이터를 받고, name,age,grade각각 타입의 기본형 데이터를 받아서
student.name = name; // <- 복제한 참조값 객체(인스턴스) 에 접근 맴버변수의 값을 받아온 값으로 복사한다.
student.age = age;
student.grade = grade;
}
// 사용 부위
static void printStudent (Student student) {
// 각 복제한 student 객체 주소 값을 읽고
System.out.println("이름 : " + student.name + ", 나이 : " + student.age + ", 성적 : " + student.grade);
// 각 참조값의 매개변수에 접근하여 값을 읽는다.
}
}
객체의 생성과 객체 내부의 맴버변수도 초기화 하기 ⬇️
public class Method2 {
public static void main(String[] args) {
Student student1 = createStudent("학생1", 15, 90);
// 리턴 받은 객체값(주소)을 studnet1 참조타입 변수에 복제한다.
Student student2 = createStudent("학생2", 16, 80);
// studnet1과 다른 새로 생성된 리턴 받은 객체값(주소)을
// studnet2 참조타입 변수에 복제한다.
printStudent(student1);
printStudent(student2);
}
// 기존에 입력과 새로 만든 객체 생성기능을 합친 기능
// 리턴값으로 Student 타입의 참조 값을 던지는데
// 생성 할때에 매개변수 값 들도 동시에 전달 해준다.
// 해당 메소드를 호출할때마다 새로운 객체(인스턴스)를 생성하며 호출 한다.
static Student createStudent(String name, int age, int grade) {
Student student = new Student();
student.name = name;
student.age = age;
student.grade = grade;
return student;
}
static void printStudent (Student student) {
System.out.println("이름 : " + student.name + ", 나이 : " + student.age + ", 성적 : " + student.grade);
}
}
변수와 초기화
변수의 종류
맴버 변수 (필드) : 클래스에 선언한 것
지역 변수 : 메서드에 선언한 것 이나 매개변수
특정 지역에서만 사용되는 변수라는 뜻 → 변수가 지정된 코드블럭이 끝나면 사라진다.
변수의 초기화
맴버변수 : 맴버 변수는 선언과 동시에 자동으로 초기화가 된다
int = 0; boolean = false, 참조형 = null
개발자가 직접 초기화 값을 직접 지정해줄 수 있다.
지역변수
항상 개발자가 직접 초기화 시켜주어야 한다.
null
참조 타입 변수에는 위치 주소 값이 들어가는데 아직 가리키는 대상이 없다면 null 이라는 값이 들어가게 된다.
Java 에서는 참조형 타입에만 null이 허용된다.
null 인 객체(인스턴스) 는 다른 곳에 참조값 (주소) 를 복사해 두지 않은 이상 찾을 수 없다.
null 인 (객체)인스턴스 는 → Java 의 JVM 에서 GC (가비지 컬렉션)가 더 이상 사용하지 않는 객체(인스턴스) 라고 판단하여 자동으로 메모리에서 삭제 해준다.
NullPointerException
Null Pointer Exception 은 말 그대로 Null을 가리키다(Pointer), 그래서 발생하는 예외(Exception) 다, → 이 말 뜻은 찾아갈 참조 값의 주소 값이 없어서 발생했다는 뜻 이다.
NullPointerException 이 발생하면 그 즉시 코드를 중단하고 나간다.
참조형 데이터가 다른 클래스(참조형 데이터)에 맴버변수로 할당 되어 사용할때는 주의 해서 사용 해야 한다. (NullPointerException)발생할 위험이 있다.
사용자가 직접 정의하는 사용자 정의 타입 설계도가 필요한데 이 설계도가 바로 class 이다.
참조형 데이터 타입이다.
클래스가 필요한 이유
예를 들어 다음과 같은 환경에서
public class ClassStart2 {
public static void main(String[] args) {
String[] studentNames = {"학생1", "학생2", "학생3", "학생4"};
int[] studentAges = {15, 16, 17, 20};
int[] studentGrade = {90, 80, 70, 60};
for (int i = 0; i < studentNames.length; i++) {
System.out.println("이름 : " + studentNames[i] +
" 나이 : " + studentAges[i] + " 성적 : " +
studentGrade[i]);
}
}
}
한 학생의 데이터가 다른 배열에 담겨 있다 따라서 학생 데이터를 제거하거나 변경할 때에 주의를 기울여야 한다. 이런환경에서 실수할 확률이 높다. 각각 학생의 이름, 나이, 성적을 관리하는게 좋을 꺼 같다. 이럴때 클래스를 도입한다.
클래스 도입
클래스를 사용하여 학생이라는 개념을 만들고 학생 별 이름, 나이, 성적을 관리하는 코드를 작성할 것이다.
public class Student {
String name;
// <- 클래스 내부에 선언한 변수들을 **맴버 변수**, **필드** 라고 한다.
int age;
int grade;
}
이런식으로 이름, 나이, 성적 변수를 갖는 클래스를 생성 해준다.
클래스는 관례상 대문자로 시작, 카멜타입으로 생성해준다.
클래스 사용
사용
public class ClassStart3 { public static void main(String[] args) { Student student1; student1 = new Student(); student1.name = "학생1"; student1.age = 15; student1.grade = 90; Student student2 = new Student(); student2.name = "학생2"; student2.age = 16; student2.grade = 80; System.out.println("이름 : " + student1.name + " 나이 : " + student1.age + " 성적 : " + student1.grade); System.out.println("이름 : " + student2.name + " 나이 : " + student2.age + " 성적 : " + student2.grade); } }
출력
이름 : 학생1 나이 : 15 성적 : 90
이름 : 학생2 나이 : 16 성적 : 80
클래스와 사용자 정의 타입 (클래스 타입)
타입은 데이터 종류나 형태를 나타낸다
int (정수) , String (문자) 타입을 지정했듯이
class 를 사용하면 생성한 class 타입을 만들어 낼 수 있다.
사용자가 직접 정의하는 사용자 정의 타입 설계도가 필요한데 이 설계도가 바로 class 이다.
객체화 (인스턴스)
객체화 (인스턴스) → 설계도인 class 를 기반으로 메모리에 공간을 확보해서 사용하는것
Student student1 = new Student();
//클래스타입 변수 = 객체생성 (인스턴스생성, 사용공간생성)
// Student 타입을 받는 student1 변수 선언
// new Student() : new 새로 메모공간을 생성해라 Studnet 클래스를 기반으로
// 이렇게 선언하면 Studnet 라는 class를 보고 메모리 공간을 생성한다.
// 생성한 메모리 공간의 주소값(참조값)을 저장한다.
// 그럼 student1 변수에 주소(참조) 값이 저장되고 변수를 통해 실제 객체에
// 접근이 가능해진다.
실제 메모리에 만들어진 실체를 객체 또는 인스턴스 라 한다.
참조값을 변수에 보관하는 이유
new Student() // 코드 자체에는 아무런 이름이 없다.
// 그저 Student class 기반으로 메모리에 객체만 만들어둔것이다.
// 따라서 생성한 객체에 접근할 방법이 필요한데
// 이런 이유로 참조값을 저장할 Student class 타입의 변수에
// 참조 값을 저장해 두고
// 변수에 저장한 참조값을 통해 실제 메모리에 존재하는 객체에 접근하게 되는것이다.
new 할때마다 새로운 메모리가 생성된다.