어서와 Java는 처음이지! 제6장 클래스, 메소드 심층연구
접근 제어 클래스 안에 변수나 메소드들을 누구나 사용할 수 있게 하면 어떻게 될까? -> 많은 문제가 발생할 것이다. (예) 국가 기밀 서류를 누구나 보도록 방치하면 어떻게 될까?
접근 제어 접근 제어(access control): 다른 클래스가 특정한 필드나 메소드에 접근하는 것을 제어하는 것
멤버 수준에서의 접근 제어
예제 class A { private int a; // 전용 int b; // 디폴트 public int c; // 공용 } public class Test { public static void main(String args[]) { A obj = new A(); // 객체 생성 obj.a = 10; // 전용 멤버는 다른 클래스에서는 접근 안 됨 obj.b = 20; // 디폴트 멤버는 접근할 수 있음 obj.c = 30; // 공용 멤버는 접근할 수 있음
예제
중간 점검 문제 필드의 경우, private로 만드는 것이 바람직한 이유는 무엇인가? 필드를 정의할 때 아무런 접근 제어 수식자를 붙이지 않으면 어떻게 되는가?
정보은닉 정보 은닉이란 구현의 세부 사항을 클래스 안에 감추는 것이다
설정자와 접근자 설정자(mutator) 접근자(accessor) 필드의 값을 설정하는 메소드 setXXX() 형식 필드의 값을 반환하는 메소드 getXXX() 형식
설정자와 접근자 public class Account { private int regNumber; private String name; private int balance; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } }
예제 public class AccountTest { public static void main(String[] args) { Account obj = new Account(); obj.setName("Tom"); obj.setBalance(100000); System.out.println("이름은 " + obj.getName() + " 통장 잔고는 " + obj.getBalance() + "입니다."); } 이름은 Tom 통장 잔고는 100000입니다.
설정자와 접근자는 왜 사용하는가? 접근자와 설정자를 사용해야만 나중에 클래스를 업그레이드할 때 편하다. 접근자에서 매개 변수를 통하여 잘못된 값이 넘어오는 경우, 이를 사전에 차단할 수 있다. 필요할 때마다 필드값을 계산하여 반환할 수 있다. 접근자만을 제공하면 자동적으로 읽기만 가능한 필드를 만들 수 있다.
접근자와 설정자의 장점 예제 설정자는 변수의 값을 변경하려는 외부의 시도를 주의 깊게 검사할 수 있다. public void setAge(int age) { if( age < 0 ) this.age = 0; else this.age = age; }
LAB: 안전한 배열 만들기 만약 인덱스가 배열의 크기를 벗어나게 되면 실행 오류가 발생한다. 따라서 실행 오류를 발생하지 않는 안전한 배열을 작성하여 보자.
SOLUTION public class SafeArray { private int a[]; public int length; public SafeArray(int size) { a = new int[size]; length = size; } public int get(int index) { if (index >= 0 && index < length) { return a[index]; return -1; public void put(int index, int value) { a[index] = value; } else System.out.println("잘못된 인덱스 " + index);
SOLUTION public class SafeArrayTest { public static void main(String args[]) { SafeArray array = new SafeArray(3); for (int i = 0; i < (array.length + 1); i++) { array.put(i, i * 10); } 잘못된 인덱스 3
생성자 생성자(contructor): 객체가 생성될 때에 필드에게 초기값을 제공하고 필요한 초기화 절차를 실행하는 메소드
생성자 정의
생성자의 예 public class MyCounter { int counter; MyCounter() { }
생성자의 예 public class MyCounterTest { public static void main(String args[]) { MyCounter obj1 = new MyCounter(); MyCounter obj2 = new MyCounter(); System.out.println("객체 1의 counter = " + obj1.counter); System.out.println("객체 2의 counter = " + obj2.counter); } 객체 1의 counter = 1 객체 2의 counter = 1
매개변수를 가지는 생성자 class MyCounter { int counter; MyCounter(int value) { counter = value; }
생성자의 예 public class MyCounterTest { public static void main(String args[]) { MyCounter obj1 = new MyCounter(100); MyCounter obj2 = new MyCounter(200); System.out.println("객체 1의 counter = " + obj1.counter); System.out.println("객체 2의 counter = " + obj2.counter); } 객체 1의 counter = 100 객체 2의 counter = 200
LAB: Television 생성자 생성자를 추가해보자. public class Television { private int channel; // 채널 번호 private int volume; // 볼륨 private boolean onOff; // 전원 상태 void print() { System.out.println("채널은 " + channel + "이고 볼륨은 " + volume + "입니다."); }
SOLUTION public class Television { private int channel; // 채널 번호 private int volume; // 볼륨 private boolean onOff; // 전원 상태 Television(int c, int v, boolean o) { channel = c; volume = v; onOff = o; } void print() { System.out.println("채널은 " + channel + "이고 볼륨은 " + volume + "입니다.");
SOLUTION public class TelevisionTest { public static void main(String[] args) { Television myTv = new Television(7, 10, true); myTv.print(); Television yourTv = new Television(11, 20, true); yourTv.print(); }
LAB: Box 클래스
SOLUTION public class Box { private int width; private int length; private int height; private int volume; public int getVolume() { return volume; } Box(int w, int l, int h) { width = w; length = l; height = h; volume = width * length * height;
SOLUTION public class BoxTest { public static void main(String[] args) { Box b; b = new Box(20, 20, 30); System.out.println("상자의 부피는 " + b.getVolume() + "입니다"); } 상자의 부피는 12000입니다
생성자 오버로딩 메소드처럼 생성자도 오버로딩될 수 있다.
예제 public class Student { private int number; private String name; private int age; Student() { number = 100; name = "New Student"; age = 18; } Student(int number, String name, int age) { this.number = number; this.name = name; this.age = age; @Override public String toString() { return "Student [number=" + number + ", name=" + name + ", age=" + age + "]";
this로 현재 객체 나타내기 메소드나 생성자에서 this는 현재 객체를 나타낸다. public class Point { public int x = 0; public int y = 0; // 생성자 public Point(int x, int y) { this.x = x; this.y = y; }
this() public class Rectangle { private int x, y; private int width, height; Rectangle() { this(0, 0, 1, 1); } Rectangle(int width, int height) { this(0, 0, width, height); Rectangle(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; // ...
LAB: 날짜를 나타내는 Date 클래스
SOLUTION public class Date { private int year; private String month; private int day; public Date() { // 기본 생성자 this(1900, "1월", 1); } public Date(int year) { // 생성자 this(year, "1월", 1); public Date(int year, String month, int day) { // 생성자 this.month = month; // this는 현재 객체를 가리킨다. this.day = day; this.year = year; @Override public String toString() { return "Date [year=" + year + ", month=" + month + ", day=" + day + "]";
SOLUTION Date [year=2015, month=8월, day=10] public class DateTest { public static void main(String[] args) { Date date1 = new Date(2015, "8월", 10); Date date2 = new Date(2020); Date date3 = new Date(); System.out.println(date1); System.out.println(date2); System.out.println(date3); } Date [year=2015, month=8월, day=10] Date [year=2020, month=1월, day=1] Date [year=1900, month=1월, day=1]
LAB: 시간를 나타내는 Time 클래스
SOLUTION class Time { private int hour; // 0 - 23 private int minute; // 0 - 59 private int second; // 0 - 59 // 첫 번째 생성자 public Time() { this(0, 0, 0); } // 두 번째 생성자 public Time(int h, int m, int s) { hour = ((h >= 0 && h < 24) ? h : 0); // 시간 검증 minute = ((m >= 0 && m < 60) ? m : 0); // 분 검증 second = ((s >= 0 && s < 60) ? s : 0); // 초 검증 // “시:분:초”의 형식으로 출력 public String toString() { return String.format("%02d:%02d:%02d", hour, minute, second);
SOLUTION 기본 생성자 호출 후 시간: 00:00:00 두번째 생성자 호출 후 시간: 13:27:06 public class TimeTest { public static void main(String args[]) { // Time 객체를 생성하고 초기화한다. Time time = new Time(); System.out.println("기본 생성자 호출 후 시간: "+time.toString()); // 두 번째 생성자 호출 Time time2 = new Time(13, 27, 6); System.out.print("두번째 생성자 호출 후 시간: "+time2.toString()); // 올바르지 않은 시간으로 설정해본다. Time time3 = new Time(99, 66, 77); System.out.print("올바르지 않은 시간 설정 후 시간: "+time3.toString()); } 기본 생성자 호출 후 시간: 00:00:00 두번째 생성자 호출 후 시간: 13:27:06 올바르지 않은 시간 설정 후 시간: 00:00:00
LAB: 원을 나타내는 Circle 클래스
SOLUTION public class Point { private int x, y; public Point(int a, int b) { x = a; y = b; } @Override public String toString() { return "Point [x=" + x + ", y=" + y + "]";
SOLUTION public class Circle { private int radius; private Point center; public Circle(Point p, int r) { center = p; radius = r; } @Override public String toString() { return "Circle [radius=" + radius + ", center=" + center + "]";
SOLUTION public class CircleTest { public static void main(String args[]) { Point p = new Point(25, 78); Circle c = new Circle(p, 10); System.out.println(c); } Circle [radius=10, center=Point [x=25, y=78]]
필드 초기화 방법 필드 선언시 초기화 public class Hotel { public int capacity = 10; // 10으로 초기화한다. private boolean full = false; // false로 초기화한다. ... }
필드 초기화 방법 인스턴스 초기화 블록(instance initializer block) public class Car { int speed; Car() { System.out.println("속도는 " + speed); } { speed = 100; public static void main(String args[]) { Car c1 = new Car(); Car c2 = new Car();
메소드로 기초형 변수가 전달되는 경우 기초형 변수가 전달되는 경우
예제 public class MyCounter { int value; void inc(int a) { a = a + 1; } public class MyCounterTest1 { public static void main(String args[]) { MyCounter obj = new MyCounter(); int x = 10; obj.inc(x); System.out.println("x = " + x); } x = 10
메소드로 객체가 전달되는 경우 객체를 메소드로 전달하게 되면 객체가 복사되어 전달되는 것이 아니고 참조 변수의 값이 복사되어서 전달된다.
예제 class MyCounter { int value = 0; void inc(MyCounter ctr) { ctr.value = ctr.value + 1; } public class MyCounterTest2 { public static void main(String args[]) { MyCounter obj = new MyCounter(); System.out.println("obj.value = " + obj.value); obj.inc(obj); } obj.value = 0 obj.value = 1
메소드로 배열이 전달되는 경우 배열도 객체이기 때문에 배열을 전달하는 것은 배열 참조 변수를 복사하는 것이다.
사용자로부터 값을 받아서 배열에 채운 후에 배열에 저장된 모든 값의 평균을 구하여 출력하는 프로그램을 작성하여 보자. 성적을 입력하시오:10 성적을 입력하시오:20 성적을 입력하시오:30 성적을 입력하시오:40 성적을 입력하시오:50 평균은 = 30.0
SOLUTION import java.util.Scanner; public class ArrayProc { public void getValues(int[] array) { Scanner scan = new Scanner(System.in); for (int i = 0; i < array.length; i++) { System.out.print("성적을 입력하시오:"); array[i] = scan.nextInt(); } public double getAverage(int[] array) { double total = 0; for (int i = 0; i < array.length; i++) total += array[i]; return total / array.length;
SOLUTION public class ArrayProcTest { final static int STUDENTS = 5; public static void main(String[] args) { int[] scores = new int[STUDENTS]; ArrayProc obj = new ArrayProc(); obj.getValues(scores); System.out.println("평균은 = " + obj.getAverage(scores)); }
메소드에서 객체 반환하기 public class Box { int width, length, height; int volume; Box(int w, int l, int h) { width = w; length = l; height = h; volume = w * l * h; } Box whosLargest(Box box1, Box box2) { if (box1.volume > box2.volume) return box1; else return box2;
메소드에서 객체 반환하기 public class BoxTest { public static void main(String args[]) { Box obj1 = new Box(10, 20, 50); Box obj2 = new Box(10, 30, 30); Box largest = obj1.whosLargest(obj1, obj2); System.out.println("(" + largest.width + "," + largest.length + "," + largest.height + ")"); } (10,20,50)
LAB: 같은 크기의 Box인지 확인하기 2개의 박스가 같은 치수인지를 확인하는 메소드 isSameBox()를 작성하여 보자. 만약 박스의 크기가 같으면 true를 반환하고 크기가 다르면 false를 반환한다. isSameBox()의 매개 변수는 객체 참조 변수가 된다.
SOLUTION public class Box { int width, length, height; Box(int w, int l, int h) { width = w; length = l; height = h; } boolean isSameBox(Box obj) { if ((obj.width == width) & (obj.length == length) & (obj.height == height)) return true; else return false;
SOLUTION public class BoxTest { public static void main(String args[]) { Box obj1 = new Box(10, 20, 50); Box obj2 = new Box(10, 20, 50); System.out.println("obj1 == obj2 : " + obj1.isSameBox(obj2)); } obj1 == obj2 : true
정적 멤버 정적 멤버(static member)는 모든 객체를 통틀어서 하나만 생성되고 모든 객체가 이것을 공유하게 된다.
인스턴스 멤버 vs 정적 멤버 클래스의 멤버는 인스턴스 멤버와 정적 멤버로 나누어진다.
인스턴스 변수 인스턴스마다 별도로 생성되기 때문에 인스턴스 변수(instance variable)라고도 한다.
정적 변수 정적 변수는 모든 객체에 공통인 변수이다. 정적 변수는 하나의 클래스에 하나만 존재한다
정적 변수 예제 public class Car { private String model; private String color; private int speed; // 자동차의 시리얼 번호 private int id; private static int numbers = 0; public Car(String m, String c, int s) { model = m; color = c; speed = s; // 자동차의 개수를 증가하고 id에 대입한다. id = ++numbers; }
SOLUTION public class CarTest { public static void main(String args[]) { Car c1 = new Car("S600", “white”, 80); // 첫 번째 생성자 호출 Car c2 = new Car("E500", “blue”, 20); // 첫 번째 생성자 호출 int n = Car.numbers; // 정적 변수 System.out.println("지금까지 생성된 자동차 수 = " + n); } 지금까지 생성된 자동차 수 = 2
정적 메소드 변수와 마찬가지로 메소드도 정적 메소드로 만들 수 있다. 정적 메소드는 static 수식자를 메소드 앞에 붙이며 클래스 이름을 통하여 호출되어야 한다. (예) double value = Math.sqrt(9.0);
정적 변수 예제 public class Car { private String model; private String color; private int speed; // 자동차의 시리얼 번호 private int id; // 실체화된 Car 객체의 개수를 위한 정적 변수 private static int numbers = 0; public Car(String m, String c, int s) { model = m; color = c; speed = s; // 자동차의 개수를 증가하고 id에 대입한다. id = ++numbers; } // 정적 메소드 public static int getNumberOfCars() { return numbers; // OK!
정적 변수 예제 지금까지 생성된 자동차 수 = 2 public class CarTest { public static void main(String args[]) { Car c1 = new Car("S600", “white”, 80); // 첫 번째 생성자 호출 Car c2 = new Car("E500", “blue”, 20); // 첫 번째 생성자 호출 int n = Car.getNumberOfCars(); // 정적 메소드 호출 System.out.println("지금까지 생성된 자동차 수 = " + n); } 지금까지 생성된 자동차 수 = 2
LAB: 같은 크기의 Box인지 확인하기 직원을 나타내는 클래스에서 직원들의 수를 카운트하는 예를 살펴보자. 직원의 수를 정적 변수로 나타낸다. 객체가 하나씩 생성될 때마다 생성자에서 정적 변수 count를 증가한다.
SOLUTION public class Employee { private String name; private double salary; private static int count = 0; // 정적 변수 // 생성자 public Employee(String n, double s) { name = n; salary = s; count++; // 정적 변수인 count를 증가 } // 객체가 소멸될 때 호출된다. protected void finalize() { count--; // 직원이 하나 줄어드는 것이므로 count를 하나 감소 // 정적 메소드 public static int getCount() { return count;
SOLUTION 현재의 직원수=3 public class EmployeeTest { public static void main(String[] args) { Employee e1, e2, e3; e1 = new Employee("김철수", 35000); e2 = new Employee("최수철", 50000); e3 = new Employee("김철호", 20000); int n = Employee.getCount(); System.out.println("현재의 직원수=" + n); } 현재의 직원수=3
내장 클래스 자바에서는 클래스 안에서 클래스를 정의할 수 있다.
내장 클래스의 분류
내부 클래스 클래스 안에 클래스를 선언하는 경우이다.
정적 변수 예제 public class OuterClass { private int value = 10; class InnerClass { public void myMethod() { System.out.println("외부 클래스의 private 변수 값: " + value); } OuterClass() { InnerClass obj = new InnerClass(); obj.myMethod();
정적 변수 예제 public class InnerClassTest { public static void main(String[] args) { OuterClass outer = new OuterClass(); } 외부 클래스의 private 변수 값: 10
LAB: 내부 클래스 게임에서 캐릭터가 여러 가지 아이템을 가지고 있다. 이것을 코드로 구현하여 보자. 아이템은 캐릭터만 사용한다고 가정하자. 그러면 캐릭터를 나타내는 클래스 안에 아이템을 내부 클래스로 정의할 수 있다.
SOLUTION import java.util.ArrayList; public class GameCharacter { private class GameItem { String name; int type; int price; int getPrice() { return price; } @Override public String toString() { return "GameItem [name=" + name + ", type=" + type + ", price=" + price + "]"; }
SOLUTION private ArrayList<GameItem> list = new ArrayList<>(); public void add(String name, int type, int price) { GameItem item = new GameItem(); item.name = name; item.type = type; item.price = price; list.add(item); } public void print() { int total = 0; for (GameItem item : list) { System.out.println(item); total += item.getPrice(); System.out.println(total);
SOLUTION GameItem [name=Sword, type=1, price=100] public class GameChracterTest { public static void main(String[] args) { GameCharacter charac = new GameCharacter(); charac.add("Sword", 1, 100); charac.add("Gun", 2, 50); charac.print(); } GameItem [name=Sword, type=1, price=100] GameItem [name=Gun, type=2, price=50] 150
내부 클래스를 사용하는 이유
Q & A