자바[7] 클래스
1. 클래스
객체지향 프로그래밍 언어(Object-Oriented Programming Language): Java, C++, C#, Kotlin, ...
절차지향 프로그래밍 언어(Procedural Programming Language): C, ...
객체(Object): 대상. 프로그램에서 사용하려고 하는 대상.
클래스(Class): 객체 설계도. 객체를 만들기 위해서 필요한 코드.
- 객체가 가져야 하는 "데이터"를 변수("필드")로 선언하고,
- 객체가 가져야 하는 "기능"을 "메서드"로 선언하는 코드.
- 변수를 선언할 때 사용할 수 있는 데이터 타입.
인스턴스(Instance): 실제로 생성된 객체.
클래스(class): 객체 설계도. 데이터(필드) + 생성자 + 기능(메서드) => 변수 타입
1. 필드(Filed): 객체의 데이터가 저장되는 곳.
2. 생성자(Constructor)
- 객체를 생성하고, 객체의 필드들을 초기화하는 역할.
- 생성자의 이름은 반드시 클래스의 이름과 같아야 함!
- 생성자를 작성하는 방법은 메서드 작성방법과 비슷하지만, 리턴 타입이 없음!
- 클래스를 선언할 때 생성자를 하나도 정의하지 않으면, 자바 컴파일러가 "기본 생성자"를 자동으로 작성해 줌.
- 클래스를 선언할 때 생성자를 직접 정의한 경우에는, 자바 컴파일러가 기본 생성자를 자동으로 만들어 주지 않음!
- 생성자 오버로딩: 파라미터가 다른 생성자를 여러개 만들 수 있음.
package com.itwill.class02;
public class Score {
// 필드(field). 멤버 변수.
int java;
int sql;
int javascript;
// 기본 생성자(default constructor): 아규먼트를 갖지 않는 생성자.
public Score() {
// 아무런 코드를 작성하지 않아도 됨.(필수가 아님)
// 코드가 없어도 모든 필드들을 그 필드 타입의 기본값으로 초기화를 해 줌.
}
// 아규먼트를 갖는 생성자.
public Score(int java, int sql, int js) {
this.java = java;
this.sql = sql;
javascript = js; // 필드와 파라미터의 이름이 다르다면 this를 안붙여도 된다.
}
// 메서드(method).
public int getTotal() {
return java + sql + javascript;
}
public double getMean() {
return (double) getTotal() / 3.0;
}
}
메서드는 클래스가 가지고 있는 필드들을 가지고 기능들을 만들면 된다.
같은 패키지에 있으면 import를 하지 않아도 된다.
package com.itwill.class02;
public class ClassMain02 {
public static void main(String[] args) {
// Score 객체를 생성:
Score score1 = new Score(); //-> 모든 필드는 기본값으로 생성됨.
System.out.println("java = " + score1.java);
// score1 인스턴스의 필드 값 변경:
score1.java = 100;
score1.sql = 90;
score1.javascript = 85;
// score1 인스턴스의 메서드 호출:
System.out.println("총점: " + score1.getTotal());
System.out.println("평균: " + score1.getMean());
// 아규먼트를 갖는 생성자를 이용한 Score 객체 생성:
Score score2 = new Score(90, 95,80);
System.out.println("score2 java = " + score2.java);
System.out.println("score2 총점: " + score2.getTotal());
System.out.println("score2 평균: " + score2.getMean());
}
}
java = 0
총점: 275
평균: 91.66666666666667
score2 java = 90
score2 총점: 265
score2 평균: 88.33333333333333
생성자는 리턴 타입이 없다!!
연습문제 1)
package com.itwill.class03;
public class Person {
// field
String name;
int age;
// constructor
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//method
public void introduce() {
System.out.println("안녕하세요, 저는 " + name + "입니다.");
System.out.println("제 나이는 " + age + "입니다.");
}
}
package com.itwill.class03;
public class ClassMain03 {
public static void main(String[] args) {
// Person 타입의 객체를 기본 생성자를 호출해서 생성
Person p1 = new Person();
System.out.println(p1.name); //-> null
System.out.println(p1.age); //-> 0
p1.name = "오쌤";
p1.age = 16;
p1.introduce();
// 아규먼트를 갖는 생성자를 호출해서 Person 타입 객체를 생성
Person p2 = new Person("홍길동", 20);
p2.introduce();
}
}
null
0
안녕하세요, 저는 오쌤입니다.
제 나이는 16입니다.
안녕하세요, 저는 홍길동입니다.
제 나이는 20입니다.
연습문제 2)
package com.itwill.class04;
public class Rectangle {
// field
double width; // 직사각형의 가로 길이
double height; // 직사각형의 세로 길이
// 생성자
// (1) 기본 생성자
public Rectangle() {}
// (2) 가로/세로를 아규먼트로 전달받아서 초기화하는 생성자
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
// 메서드
// (1) 넓이를 리턴
public double area() {
return width * height;
}
// (2) 둘레길이를 리턴
public double perimeter() {
return 2 * (width + height);
}
}
package com.itwill.class04;
public class ClassMain04 {
public static void main(String[] args) {
// 기본생성자를 사용해서 Rectangle 객체 생성, 메서드 호출
Rectangle rec1 = new Rectangle();
rec1.width = 5.6;
System.out.println("rect1 가로 = " + rec1.width);
rec1.height = 2.0;
System.out.println("rect1 세로 = " + rec1.height);
rec1.area();
System.out.println("rect1 넓이 = " + rec1.area());
rec1.perimeter();
System.out.println("rect1 둘레 = "+ rec1.perimeter());
System.out.println("=======================");
// 아규먼트를 갖는 생성자를 사용:
Rectangle rec2 = new Rectangle(2.0, 4.8);
System.out.println("rect2 가로 = " + rec2.width);
System.out.println("rect2 세로 = " + rec1.height);
System.out.println("rect2 넓이 = " + rec2.area());
System.out.println("rect2 둘레 = "+ rec2.perimeter());
}
}
rect1 둘레 = 15.2
=======================
rect2 : com.itwill.class04.Rectangle@7dc222ae
rect2 가로 = 2.0
rect2 세로 = 2.0
rect2 넓이 = 8.0
rect2 둘레 = 12.0
rect2 : com.itwill.class04.Rectangle@aecb35a
과제
<Subject.java>
package com.itwill.class05;
public class Subject {
// field
int korean;
int english;
int math;
int science;
// 생성자: (1)기본 생성자. (2)아규먼트를 갖는 생성자.
public Subject() {}
public Subject(int korean, int english, int math, int science) {
this.korean = korean;
this.english = english;
this.math = math;
this.science = science;
}
// 메서드: (1)총점 리턴. (2) 평균 리턴.
public int total() {
// this.은 생략 가능.
return korean + english + math + science;
}
public double ave() {
// this.total() 가능.
return (double) total() / 4;
}
public void info() {
System.out.println("국어: " + this.korean);
System.out.println("영어: " + this.english);
System.out.println("수학: " + this.math);
System.out.println("과학: " + this.science);
System.out.println("총점: " + this.total());
System.out.println("평균: " + this.ave());
}
}
<Student.java>
package com.itwill.class05;
public class Student {
// field
int id; // 학생 번호
String name; // 학생 이름
Subject subject; // 수강 과목. Subject subject = new Subject();도 가능(필드에서도 초기화 가능)
// 생성자:
// (1)기본 생성자.
// 타입 기본값: boolean-false, 정수-0, 실수-0.0, 참조타입-null
public Student() {
}
// (2)아규먼트를 갖는 생성자.
public Student(int id, String name, Subject subject) {
this.id = id;
this.name = name;
this.subject = subject;
}
// 메서드: 학생의 정보(번호, 이름, 각 과목의 점수, 총점, 평균)를 출력.
public void info() {
System.out.println("-----학생 정보-----");
System.out.println("번호: " + id);
System.out.println("이름: " + name);
if (subject != null) {
subject.info();
// System.out.println("국어: " + subject.korean); // this.subject.korean
// System.out.println("영어: " + subject.english);
// System.out.println("수학: " + subject.math);
// System.out.println("과학: " + subject.science);
// System.out.println("총점: " + subject.total());
// System.out.println("평균: " + subject.ave());
} else {
System.out.println("Subject: null");
}
System.out.println("--------------------");
}
}
<ClassMain05.java>
package com.itwill.class05;
public class ClassMain05 {
public static void main(String[] args) {
// TODO: Subject, Student 클래스 객체 생성과 메서드 호출 결과 테스트.
// 기본생성자 사용해서 Subject 타입의 객체 생성
Subject subj1 = new Subject();
// System.out.println("국어: " + subj.korean);
// System.out.println("영어: " + subj.english);
// System.out.println("수학: " + subj.math);
// System.out.println("과학: " + subj.science);
// System.out.println("총점: " + subj.total());
// System.out.println("평균: " + subj.ave());
// 아규먼트를 갖는 생성자를 사용해서 Subject 타입의 객체를 생성
Subject subj2 = new Subject(56, 98, 79, 83);
subj2.info();
// 아규먼트를 갖는 생성자를 사용해서 Student 타입의 객체를 생성
Student stu1 = new Student(01, "히어로", subj2);
stu1.info();
// 기본 생성자를 사용해서 Student 타입의 객체를 생성
Student stu2 = new Student();
stu2.info();
}
}
국어: 56
영어: 98
수학: 79
과학: 83
총점: 316
평균: 79.0
-----학생 정보-----
번호: 1
이름: 히어로
국어: 56
영어: 98
수학: 79
과학: 83
총점: 316
평균: 79.0
--------------------
-----학생 정보-----
번호: 0
이름: null
Subject: null
--------------------
연습 문제
package com.itwill.class06;
public class ClassMain06 {
public static void main(String[] args) {
// Account 타입 객체 생성
Account account1 = new Account(123456, 10000);
account1.info();
Account account2 = new Account(987654, 10000);
account2.deposit(1000);
account2.info();
// account1 계좌에 10,000원 입금
int result = account1.deposit(10_000);
System.out.println("입금 후 잔액: " + result);
// account1 계좌에서 5,000원 출금
account1.withdraw(5_000);
account1.info();
// account1에서 account2으로 2,000원 이체
account1.transfer(account2, 2_000);
account1.info();
account2.info();
// account2에서 account1으로 10,000원 이체
account2.transfer(account1, 10_000);
}
}
package com.itwill.class06;
/**
* 은행 계좌 정보.
* 속성: 계좌번호, 잔고.
* 기능: 입금, 출금, 이체, 정보 출력.
*/
public class Account {
// field
int accountNo; // 계좌번호
int balance; // 잔고
// constructor: 아규먼트 2개를 갖는 생성자
public Account(int accountNo, int balance) {
this.accountNo = accountNo;
this.balance = balance;
}
// method
/**
* deposit(입금). 현재 잔고에 입금액을 더하고, 입금 후의 잔고를 리턴.
* @param amount 입금액
* @return 입금 후 잔고(balance).
*/
public int deposit(int amount) {
this.balance += amount; // balance = balance+amount);
return balance;
}
/**
* withdraw(출금). 현재 잔고에서 출금액을 빼고, 출금 후의 잔고를 리턴.
* @param amount 출금액
* @return 출금 후 잔고.
*/
public int withdraw(int amount) {
this.balance -= amount;
return balance;
}
/**
* transfer(이체). 내 계좌에서의 잔고에서는 이체금액 amount를 빼고,
* 이체할 계좌 to의 잔고에서는 이체금액 amount를 더함.
* @param to 이체할 계좌(Account 타입.)
* @param amount 이체할 금액.
*/
public boolean transfer(Account to, int amount) {
// this.balance -= amount; // 내 계좌에서 출금
this.withdraw(amount);
// to.balance += amount; // 상대방 계좌에서 입금
this.deposit(amount);
return true;
}
/**
* 계좌 정보 출력
*/
public void info() {
System.out.println("--- 계좌 정보 ---");
System.out.println("계좌 번호: " + accountNo);
System.out.println("잔고: " + balance);
System.out.println("----------------------");
}
}
--- 계좌 정보 ---
계좌 번호: 123456
잔고: 10000
----------------------
--- 계좌 정보 ---
계좌 번호: 987654
잔고: 11000
----------------------
입금 후 잔액: 20000
--- 계좌 정보 ---
계좌 번호: 123456
잔고: 15000
----------------------
--- 계좌 정보 ---
계좌 번호: 123456
잔고: 15000
----------------------
--- 계좌 정보 ---
계좌 번호: 987654
잔고: 11000
----------------------
This
2가지 의미로 사용
this. : 생성된 인스턴스
this() : 만들어진 다른 생성자를 호출
<ClassMain>
package com.itwill.class07;
public class ClassMain07 {
public static void main(String[] args) {
// ThisTest 객체를 여러가지 생성자들을 이용해서 생성
ThisTest test1 = new ThisTest();
test1.info();
ThisTest test2 = new ThisTest(1);
test2.info();
ThisTest test3 = new ThisTest(1, 20);
test3.info();
ThisTest test4 = new ThisTest(1, 20, 300);
test4.info();
}
}
<ThisTest>
package com.itwill.class07;
public class ThisTest {
// field
int x;
int y;
int z;
// constructor
public ThisTest() {}
public ThisTest(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
//-> this: ThisTest 타입으로 생성된 객체(인스턴스)
}
public ThisTest(int x) {
//this.x = x;
this(x, 0, 0); //-> this: ThisTest 클래스의 다른 생성자 호출
// (주의) this(...) 생성자를 호출하는 코드는 다른 실행문들보다 먼저 실행되어야 됨.
}
public ThisTest(int x, int y) {
this(); // 현재 클래스의 다른 생성자를 호출하는 문장.
this.x = x;
this.y = y;
}
public void info() {
System.out.printf("x= %d, y= %d, z= %d \n", x, y, z);
}
}
x= 0, y= 0, z= 0
x= 1, y= 0, z= 0
x= 1, y= 20, z= 0
x= 1, y= 20, z= 300
2. Modifier
클래스의 멤버: 필드(field), 생성자(constructor), 메서드(method)
클래스 멤버들의 접근(가시성) 수식어(access modifier):
- 클래스의 멤버들을 접근할 수 있는(보여지는) 범위를 제한하는 수식어
- 종류와 범위: private < (package) < protected < public / package는 선언문에서는 사용, 수식어로는 사용X.
(1) private: 선언된 클래스 안에서만 접근할 수 있는(보이는) 멤버.
(2) (package): 수식어를 사용하지 않은 경우. 같은 패키지에 있는 클래스에서 접근할 수 있는(보이는) 멤버.
(3) protected: 같은 패키지에 있거나 또는 상속하는 클래스에서 접근할 수 있는(보이는) 멤버.
(4) public: 어디서든 접근할 수 있는(보이는) 멤버.
package com.itwill.modifier01;
public class AccessExample {
// field
private int a; // 개인적인. 다른 클래스에선 보이지 않음.
int b;
protected int c;
public int d; // 현재 프로젝트 범위 안.
public AccessExample(int a, int b, int c, int d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
// method
public void info() {
System.out.printf("a=%d, b=%d, c=%d, d=%d\n", a, b, c, d);
}
}
package com.itwill.modifier01;
public class ModifierMain01 {
public static void main(String[] args) {
// AccessExample 타입 객체 생성
AccessExample ex = new AccessExample(1, 2, 3, 4);
// System.out.println("a = " + ex.a); // -> 컴파일 에러: not visible(보이지 않음).
// ex.a = 100;
System.out.println("b = " + ex.b);
ex.b = 200;
System.out.println("b = " + ex.b);
ex.info();
}
}
b = 2
b = 200
a=1, b=200, c=3, d=4
다른 패키지에서 선언된 클래스 이름으로 변수를 선언하려면
(1) import 문장을 작성하고 변수 선언에서 클래스 이름만 사용하거나,
(2) import 문장 없이 패키지 이름을 포함한 전체 클래스 이름으로 변수를 선언함.
java.lang 패키지에 포함된 클래스들은 import 문장 없이 클래스 이름만으로 변수 선언할 수 있음.
(예) java.lang.system, java.lang.String, java.lang.Math, ...
<ModifierMain02.java>
package com.itwill.modifier02;
import com.itwill.modifier01.AccessExample;
public class ModifierMain02 {
public static void main(String[] args) {
// com.itwill.modifier01.AccessExample 타입의 객체를 생성
AccessExample ex = new AccessExample(1, 2, 3, 4);
// com.itwill.modifier01.AccessExample ex1 = new com.itwill.modifier01.AccessExample(1, 2, 3, 4);
// System.out.println(ex.b); //-> 컴파일 에러: not visible(보이지 않음)
ex.info();
}
}
a=1, b=2, c=3, d=4
<Member.java>
package com.itwill.modifier03;
public class Member {
// field
private String memberId; // 읽기 전용 필드
private String memberPassword; // 읽기/쓰기 필드
// constructor
public Member(String memberId, String memberPassword) { // 다른 패키지에 있더라도 생성자는 불러야하기 때문에 public으로 사용
if (memberId != null && memberPassword != null) {
this.memberId = memberId;
this.memberPassword = memberPassword;
} else {
throw new IllegalArgumentException();
}
}
// method
// getter 메서드
public String getMemberId() {
return this.memberId;
}
public String getMemberPassword() {
return this.memberPassword;
}
// setter 메서드
public void setMemberPassword(String password) {
if (password != null) {
this.memberPassword = password;
}
}
}
<Person.java>
package com.itwill.modifier03;
public class Person {
private String name; // 읽기 전용 필드
private int age; // 읽기/쓰기 필드
// 생성자
public Person(String name, int age) {
if (name != null && age != 0) {
this.name = name;
this.age = age;
} else {
throw new IllegalArgumentException();
}
}
// 메소드
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
if (age >= 0) {
this.age = age;
} else {
System.out.println("나이는 1세 이상부터 가능합니다.");
}
}
}
* 데이터 캡슐화(Encapsulation ):
- 필드들을 private으로 선언해서 클래스 외부에서는 보이지 않도록(직접 접근할 수 없도록) 감추고, 대신에 필요한 경우에 한해서 public으로 공개한 메서드를 제공해서 간접적으로 필드의 값을 읽거나 수정하는 것을 허용하는 객체지향 프로그래밍 기법.
- 데이터의 보안과 무결성을 유지하기 위해서 캡슐화를 사용.
- getter 메서드: private 필드의 값을 리턴하는 메서드
- setter 메서드: private 필드의 값을 변경하는 메서드
package com.itwill.modifier03;
public class ModifierMain03 {
public static void main(String[] args) {
// Member 타입 객체 생성
Member member = new Member("guest", "1234");
System.out.println("ID: " + member.getMemberId());
System.out.println("변경전 PW: " + member.getMemberPassword());
member.setMemberPassword(null);
System.out.println("변경후 PW: " + member.getMemberPassword());
System.out.println("--------------------");
Person person = new Person("홍길동", 16);
System.out.println("이름: " + person.getName());
System.out.println("변경전 나이: " + person.getAge());
person.setAge(36);
System.out.println("변경후 나이: " + person.getAge());
}
}
ID: guest
변경전 PW: 1234
변경후 PW: 1234
--------------------
이름: 홍길동
변경전 나이: 16
변경후 나이: 36
클래스의 접근 제한자:
클래스 멤버의 접근 제한자: private < (package) < protect < public
(1) public: 어디서든(모든 패키지)에서 공개된 클래스. import 문장을 사용할 수 있음.
(2) (package): 수식어가 없는 경우. 같은 패키지 안에서만 공개된 클래스.
(주의) public 클래스의 이름은 java 파일의 이름과 같아야 함!
하나의 java 파일 안에 여러개의 클래스를 선언하는 것은 가능.
하나의 java 파일 안에서 2개 이상의 public 클래스는 선언할 수 없음.
하나의 java 파일에 여러개의 클래스를 선언한 경우, 컴파일 후에는 각각의 클래스 파일(바이트 코드)들이 생성됨.
ModifierMain04.java --[compile]--> A.calss, B.class, ModifierMain04.class
<Modifier04.java>
package com.itwill.modifier04;
import com.itwill.modifier05.PublicClassEx;
public class Modifier04 {
public static void main(String[] args) {
// PublicClassEx 타입 객체 생성
PublicClassEx ex = new PublicClassEx();
System.out.println(ex);
// com.itwill.modifier05.PackageClassEx ex2; //-> 컴파일 에러
}
}
// type 이라고 나오면 class라고 보면됨.
// refactor- rename을 이용하면 이름을 바꿀 수 있다.
접근 수식어(access modifier)는 지역 변수에는 사용할 수 없음!
final 수식어
- 클래스, 클래스의 멤버(필드, 메서드), 지역 변수에서 사용 가능.
- final 의미: 바꿀 수 없는
- final 필드/지역변수: 한 번 초기화가 되면 더이상 값을 변경할 수 없는 변수. 상수(constant)
- final 필드는 반드시
(1) 선언과 동시에 초기화하거나
(2) 아규먼트를 갖는 생성자를 선언해서 명시적으로 초기화해야 함.
인스턴스 멤버(필드, 메서드) vs 정적(static) 멤버
1. 인스턴스 멤버
(1) static 수식어가 없는 멤버.
(2) 객체를 생성한 후에 참조 변수를 이용해서 사용하는 멤버.
(3) 인스턴스 필드는 JRE(Java Runtime Environment)가 사용하는 메모리 영역 중에 힙에 생성.
2. 정적 멤버(필드, 메서드)
(1) static 수식어가 사용된 멤버.
(2) 객체를 생성하지 않아도 사용할 수 있는 멤버.
(3) 클래스 이름을 이용해서 사용하는 멤버. (예) Math.PI, Math.random(), System.in, System.out, ...
(4) 정적 필드는 JRE가 사용하는 메모리 영역 중에 메서드 영역에 생성.
(5) 정적 멤버들은 프로그램의 main() 메서드가 호출되기 전에, 프로그램 로딩 시점에 메모리에 생성됨.
(6) static(정적) 메서드는 static 필드와 메서드만 사용 가능. 객체를 생성하고 나면 사용 가능.
package com.itwill.modifier06;
public class ModifierMain06 {
private static final int version = 1; // 선언할 때 초기화되지 않은 final 필드
// final 필드를 명시적으로 초기화하는 생성자
private final String message;
public ModifierMain06(String message) {
this.message = message;
}
public static void main(String[] args) {
// version = 2; // final 필드는 값을 변경할 수 없음.
// 지역 변수 선언 & 초기화.
int n1 = 10;
n1 = 100;
final int n2 = 10; //-> 상수. final 지역 변수
// n2 = 100; // 값을 변경(재할당)할 수 없음.
final int n3;
n3 = 10;
// ModifierMain06 타입의 객체를 생성:
ModifierMain06 app = new ModifierMain06("버스 파업");
// app.message = ""; // final 필드는 개체 생성 이후에 값을 변경할 수 없음.
// Test 클래스의 정적(static) 멤버 사용:
System.out.println("Test.y = " + Test.y);
Test.y = 100; // static 필드는 객체 생성과 관계없이 사용할 수 있음.
System.out.println("Test.y = " + Test.y);
Test.printFields2();
// Test 클래스의 인스턴스(static이 아닌) 멤버 사용: 먼저 객체를 생성해야 됨.
Test test = new Test();
System.out.println("test.x = " + test.x);
test.x = 100;
test.printFields();
}
}
--- 정적(static) 메서드 ---
y = 100
--------------------------=
test.x = 0
--- 인스턴스 메서드 ---
x = 100
y = 100
-----------------------
<Test.java>
Test.y = 0
Test.y = 100
--- 정적(static) 메서드 ---
y = 100
--------------------------=
test.x = 0
--- 인스턴스 메서드 ---
x = 100
y = 100
-----------------------
- 리터럴과 상수 구별할 줄 알기