- 클래스란?
클래스는 객체지향 프로그래밍에서 특정 개체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀이다.
public class Animal{
}
다음과 같이 클래스 틀을 만들었다.
Animal cat = new Animal();
new 를 이용해 객체를 생성하였다. cat은 Animal클래스의 인스턴스이자 하나의 객체이다.
이제 하나하나의 객체에 대한 정보를 객체 변수를 이용해 만들어보자. (멤버 변수, 속성이라고도 한다.)
public class Animal{
String name;
public static void main(String[] args){
Animal cat = new Animal();
System.out.println(cat.name);
}
다음과 같이 객체이름.객체변수 처럼 .연산자를 통해 객체변수에 접근이 가능하다.
- 메소드
클래스 내에 함수가 구현되면 이를 메소드라고 표현한다.
public class Animal {
String name;
public void setName(String name) {
this.name = name;
} // 생성자
public static void main(String[] args){
Animal cat = new Animal();
cat.setName("gyeomi"); // 메소드 호출
System.out.println(cat.name);
}
}
}
setName 메소드를 생성해 cat.name에 값을 대입해준다.
객체 변수의 값은 공유되지 않는다.
public class Animal {
String name;
public void setName(String name) {
this.name = name;
} // 생성자
public static void main(String[] args){
Animal cat = new Animal();
cat.setName("gyeomi"); // 메소드 호출
Animal dog = new Animal();
dog.setName("happy");
System.out.println(cat.name); //gyeomi
System.out.println(dog.name); //happy
}
}
이 코드가 의미하는 바는 name 객체 변수는 공유되지 않는다는 점이다. 이것이 객체지향 프로그래밍의 의미라고 할 수 있다. 객체변수의 값이 독립적으로 유지되기 때문이다.
자바에서 클래스 안에 선언된 함수를 말한다.
메소드는 입력값과 리턴값을 다루는 블랙박스와 같다. 다음 코드를 보자
public class Test {
public int sum(int a, int b) {
return a + b;
}
public static void main(String[] args) {
int a = 3;
int b = 4;
Test myTest = new Test(); // Test 클래스의 객체 myTest 생성
int c = myTest.sum(a, b); //변수 a,b를 sum 메소드의 매개변수로 대입
System.out.println(c);
}
}
sum함수를 보면 두가지의 매개변수가 주어져 있고, return값은 int형이므로 메소드도 int로 정의되어있다.
main 안에서 메소드를 호출하기 위해 (리턴 값을 받을 변수) = (객체).(메소드이름) 와 같이 사용한다.
public class Test {
String nick;
public void say_nick(String nick){
if ("fool".equals(nick)) {
return ;
}
System.out.println("나의 별명은 " + nick + "입니다.");
}
public static void main(String[] args) {
Test person= new Test();
String nick = "gyeomi1";
person.say_nick(nick);
}
}
이렇게 nick이란 변수를 받아서 메소드를 이용해 별명을 출력할 수 있다. 이때 say_nick메소드에서 if문을 설정하고 nick 변수의 값이 fool인경우 return을 해주어 메소드를 바로 빠져나가 아무것도 출력하지 않도록 설정할 수 있다.
메소드 내에서 선언된 변수의 효력 범위는 무엇일까?
public class Test {
public void Plus(int a) {
a++;
}
public static void main(String[] args) {
int a = 1;
Test myTest = new Test();
myTest.Plus(a); // a는 지역변수 이므로
System.out.println(a); // 1이 출력될 것이다.
}
}
메소드 내의 변수는 메소드안에서만 쓰여진다. 이것을 로컬 변수라고 하며, 반대로 메소드 내의 변수를 다른 메소드에서도 동일하게 사용하고 싶으면 객체로 Plus메소드의 입력값을 정의해주어야 한다.
public class Test {
int a; // 객체 변수 a
public void Plus(Test myTest) {
myTest.a++;
}
public static void main(String[] args) {
Test myTest = new Test();
myTest.a = 1;
myTest.Plus(myTest);
System.out.println(myTest.a);
}
}
public class Test {
int a; // 객체 변수 a
public void Plus()
{
this.a++;
}//this는 Plus의 객체를 표시
public static void main(String[] args) {
Test myTest = new Test();
myTest.a = 1;
myTest.Plus();
// myTest.Plus(myTest); //this 키워드를 사용하면 굳이 객체를 매개변수에 전달할 필요가 없다.
System.out.println(myTest.a);
}
}
객체로 매개변수를 넘기게 되면 객체 자체의 객체변수인 a가 증가하는 것이므로 main 메소드에서도 증가한 값이 그대로 유지된다.
혹은, 다음과 같이 Plus 메소드를 정의해주어야 한다.
public class Test {
public int Plus(int a) {
a++;
return a;
}
public static void main(String[] args) {
int a = 1;
Test myTest = new Test();
a = myTest.Plus(a); // a는 지역변수 이므로
System.out.println(a); // 1이 출력될 것이다.
}
}
달라진 점은 Plus메소드를 int형 메소드로 변환하여 a를 반환해줬다는 점, 그리고 메소드의 반환값을 다시 a 변수에 대입하였다는 점이다.
- Call by Value
class Updator{
public void update(int count) {
count++;
}
}
public class Test{
int count = 0;
public static void main(String[] args){
Test myCounter = new Test(); // Test 객체 생성
System.out.println("before update : " + myCounter.count); // 0
Updator myUpdator = new Updator(); // Updator 객체 생성
myUpdator.update(myCounter.count); // count는 지역변수이기 때문에 실제 증가 x
System.out.println("after update : " + myCounter.count); // 1
}
}
update 메소드의 입력 항목을 int count로 정의했다. 객체를 메소드의 입력으로 받으면 객체의 멤버 변수의 변경된 값이 그대로 유지되는 것이다.
- 상속
상속이란 부모 클래스의 메소드와 멤버 변수를 자식 클래스가 그대로 물려받는 것이다.
자식 클래스 extends 부모 클래스
다음과 같이 extends 키워드를 통해 클래스를 상속한다.
Animal.java는 동물 객체의 이름 변수인 name과 이름을 지정하는 setName 메소드로 구성되어있다.
Dog.java를 만들어 extends 키워드를 통해 상속해준다. Dog 클래스에 sleep 메소드를 정의해준다.
//Animal.java
public class Animal {
String name;
public void setName(String name) {
this.name = name;
}
}
//Dog.java
public class Dog extends Animal{
public void sleep()
{
System.out.println(this.name + "zzz");
}
public static void main(String args[]) {
Dog dog = new Dog();
dog.setName("poppy");
System.out.println(dog.name);
dog.sleep();
}
}
이렇게 Dog가 Animal에 포함된 관계를 IS-A 관계라고 한다. 이때 자식(Dog) 객체는(dog) 부모 클래스(Animal)의 자료형인 것처럼 사용할 수 있다.
Animal dog = new Dog();
Dog 클래스를 상속하는 클래스, HouseDog를 만들어보자.
public class HouseDog extends Dog {
public void sleep(){
System.out.println(this.name + "zzz in house");
}
public static void main(String[] args) {
HouseDog houseDog = new HouseDog(); //
houseDog.setName("happy");
houseDog.sleep();
}
}
해당 코드를 통해 C++의 함수 오버로딩 처럼 메소드 오버로딩이 가능함을 알 수 있다. HouseDog는 상속에 의해 Dog의 메소드, sleep이 사용 가능하지만 HouseDog 클래스 안의 main에서 sleep을 호출하면 해당 클래스 안의 메소드가 우선 호출 되는 것을 알 수 있다. 물론 매개변수의 차이에 의해 오버로딩도 가능하다.
public class HouseDog extends Dog {
public void sleep(){
System.out.println(this.name + "zzz in house");
}
public void sleep(int hour) {
System.out.println(this.name + "zzz in house for " + hour + "hours");
}
public static void main(String[] args) {
HouseDog houseDog = new HouseDog(); //
houseDog.setName("happy");
houseDog.sleep();
houseDog.sleep(3);
}
}
이렇게 말이다!
- 생성자
위 예제에서 HouseDog 클래스의 main메소드를 다음과 같이 정의해보자.
public static void main(String[] args)
{
HouseDog dog = new HouseDog();
System.out.println(dog.name);
}
dog.name은 아무런 값도 설정하지 않았기 때문에 null이 출력될 것이다. name 객체 변수에 값을 무조건 설정해야만 객체가 생성될 수 있도록 강제할 수 있는 방법은 무엇일까?
바로 생성자를 이용하는 것인데, 생성자는 메소드 명이 클래스명과 동일하고 리턴자료형이 없는 메소드를 말한다.
public HouseDog(String name) {
this.setName(name);
}
new라는 키워드로 객체가 생성될 때 생성자가 호출된다.
main 메서드에서 매개변수에 name값을 입력해주지 않으면 에러가 뜨기 때문에 객체 변수를 객체 생성과 동시에 초기화한다.
public static void main(String[] args) {
HouseDog houseDog = new HouseDog("happy"); //
houseDog.sleep();
houseDog.sleep(3);
}
defualt 생성자라는 개념도 있다. 생성자를 굳이 구현하지 않아도 컴파일러가 자동으로 디폴트 생성자를 추가해줘, 객체가 만들어 질때 이 생성자를 실행하는 것이다.
public class Dog()
{
}
public class Dog()
{
public Dog()
{
}
}
위의 두 클래스는 같은 형태의 클래스이다.
메소드 오버로딩과 같은 개념으로 생성자 오버로딩도 가능하다. 예제를 보자
public class HouseDog extends Dog {
public HouseDog(String name) {
this.setName(name);
}
public HouseDog(int type) {
switch (type) {
case 1:
this.setName("yok");
case 2:
this.setName("bulldog");
}
}
public void sleep() {
System.out.println(this.name + "zzz in house");
}
public void sleep(int hour) {
System.out.println(this.name + "zzz in house for " + hour + "hours");
}
public static void main(String[] args) {
HouseDog houseDog = new HouseDog("happy");
HouseDog houseDog2 = new HouseDog(1);
System.out.println(houseDog.name); // happy
System.out.println(houseDog2.name); //bulldog
}
}
- 인터페이스
인터페이스는 반복되는 코드를 줄이기 위해 사용하는 키워드이다.
다음과 같이 Predator 인터페이스를 생성하자
public interface Predator {
}
public class Lion extends Animal implements Predator {
}
public class Tiger extends Animal implements Predator {
}
implements 키워드를 이용해 인터페이스를 구현한다.
public class ZooKeeper {
public void feed(Predator predator) {
System.out.println("feed apple");
}
}
feed메소드에 Lion, Tiger 클래스를 일일이 입력하지 않아도, Predator 인터페이스의 구현체로서 Tiger과 Lion등의 역할을 할 수 있다.
그럼 어떻게 동물에 따라 출력을 다르게 할 것인가?
public interface Predator {
public String getFood();
}
인터페이스에 getFood() 메소드를 추가한다. 인터페이스에는 메소드의 이름과 입력값만 나타나있고 내용은 없다.
이 인터페이스를 사용하는 클래스들은 모두 이 메소드에 대한 내용을 포함하여야 한다.
//Tiger.java
public class Tiger extends Animal implements Predator {
public String getFood(){
return "banana";
}
}
//Lion.java
public class Lion extends Animal implements Predator {
public String getFood(){
return "apple";
}
}
ZooKeeper 클래스도 변경해준다.
public class ZooKeeper {
public void feed(Predator predator) {
System.out.println("feed " + predator.getFood());
}
}
predator.getFood()를 호출하면 매개변수에 해당하는 구현체(Lion, Tiger)의 getFood 메소드가 호출된다
public static void main(String args[]) {
ZooKeeper person = new ZooKeeper();
Tiger tiger = new Tiger();
Lion lion = new Lion();
person.feed(tiger); //feed apple
person.feed(lion); //feed banana
}
구현체 하나하나마다 feed메소드를 정의해줄 필요없이 인터페이스 하나에 구현체 Lion, Tiger을 묶어서 하나의 메소드로만 출력을 달리 하는 것에 성공했다. 이때 Predator 인터페이스 안에 getFood 메소드를 정의해주고, 구현체 파일에서 (implements를 하고) getFood메소드의 리턴값을 정의해주어 구현체마다 반환값이 다르게 하였다.
여기서 핵심은 단순히 ZooKeeper에서 구현해야할 메소드가 줄었다는 것이 아닌, 동물의 종류와 상관없는 독립적인 클래스가 되었다는 점이다.
- 다형성
다형성. Polymorphism
public class Bouncer { // 경비원 클래스
public void barkAnimal(Animal animal) { //동물을 짖게 하여 경비한다
if (animal instanceof Tiger) {
// animal이 Tiger의 객체라면
System.out.println("어흥");
} else if (animal instanceof Lion) {
System.out.println("으르렁");
}
}
public static void main(String[] args) {
Tiger tiger = new Tiger();
Lion lion = new Lion();
Bouncer bouncer = new Bouncer();
bouncer.barkAnimal(tiger); // 어흥
bouncer.barkAnimal(lion); // 으르렁
}
}
barkAnimal의 입력값에는 Tiger, Lion 등의 자료형이 들어갈 수도 있다. (Animal의 상속 클래스이기 때문)
이를 인터페이스로 구현하면
1. Bark 인터페이스를 구현한다.
public interface Bark {
public String getSound();
}
2. Lion과 Tiger 클래스에 getSound 메소드를 각각 정의한다.
public class Lion extends Animal implements Bark {
public String getSound(){
return "으르렁";
}
}
public class Tiger extends Animal implements Bark{
public String getSound(){
return "어흥";
}
}
3. Bouncer 클래스에서 barkAnimal 클래스의 입력 값을 Bark 인터페이스로 넣은 후 sout문에서 Bark 인터페이스 객체의 getSound 메소드를 호출하도록 한다. 그리고 main에서 출력한다.
public class Bouncer { // 경비원 클래스
public void barkAnimal(Bark animal) {
System.out.println(animal.getSound());
}
public static void main(String[] args) {
Tiger tiger = new Tiger();
Lion lion = new Lion();
Bouncer bouncer = new Bouncer();
bouncer.barkAnimal(tiger); // 어흥
bouncer.barkAnimal(lion); // 으르렁
}
}
tiger 객체는 Tiger, Animal 클래스의 객체이기도 하며 Bark 인터페이스의 객체이기도 하다.
이렇게 하나의 객체가 여러 자료형 타입을 가지는 것을 다형성이라고 한다. (폴리모피즘)
Bark, Predator 두 인터페이스를 하나의 인터페이스로 정의하는 방법은 두가지이다.
1. Bark, Predator 인터페이스의 모든 메소드를 생성한 하나의 인터페이스에 때려넣기
public interface BarkablePredator {
public void bark();
public String getFood();
}
2. extends를 이용해 인터페이스에 인터페이스를 다중 상속하기
public interface BarkablePredator extends Predator, Barkable {
}
- 추상 클래스
Predator 인터페이스를 추상클래스로 만들어보자!
public abstract class Predator extends Animal {
public abstract String getFood();
}
추상 클래스를 만들기 위해서 class 앞에 abstract라고 표기해야 한다. 이렇게 만든 추상 클래스의 구현체 Tiger, Lion도 다음과 같이 변경한다.
// Tiger.java
public class Tiger extends Predator implements Barkable {
public String getFood() {
return "apple";
}
public void bark() {
System.out.println("어흥");
}
}
// Lion.java
public class Lion extends Predator implements Barkable {
public String getFood() {
return "banana";
}
public void bark() {
System.out.println("으르렁");
}
}
Predator을 추상클래스로 변경하니 implements가 아닌 extends를 사용한다.
추상 클래스에서는 실제 메소드도 추가 가능하다.
public abstract class Predator extends Animal {
public abstract String getFood();
public boolean isPredator()
{
return true;
}
}
다음과 같이 몸통을 가지는 메소드도 말이다.
객체 긑
'Langauge > Java' 카테고리의 다른 글
Java의 정석 Chapter2. 변수 (0) | 2022.03.07 |
---|---|
Java의 정석 Chapter1. 자바를 시작하기 전에 (0) | 2022.03.07 |
점프 투 자바 요약 #제어문 (0) | 2021.02.23 |
점프 투 자바 요약 #자료형 (0) | 2021.02.16 |
Java1 생활코딩 강의 Write-up (0) | 2021.01.29 |