아무튼 제네릭은 객체나 메소드의 데이터타입을 코드 작성 단계에서 미리 특정하기 위해서 사용합니다.
이전 포스트에서 알아봤듯이 다형성이 주는 이점을 제네릭도 똑같이 가지고있습니다.
즉, 코드를 여러번 쓸 필요 없어진다는 거죠.
미리 한번 보여드리겠습니다.
//Main.java
public class Main {
public static void main(String[] args) {
//AnimalList 선언 <>이부분에 데이터 타입을 적음
AnimalList<LandAnimal> landAnimal = new AnimalList<>(); // new 뒤 데이터 타입 Java SE 7부터 생략가능함.
//List에 여러 타입의 객체 넣기 (다형성)
landAnimal.add(new LandAnimal());
landAnimal.add(new Cat());
landAnimal.add(new Dog());
// landAnimal.add(new Sparrow()); // LandAnimal을 상속받지 않았기 때문에 오류가 발생함.
//하나씩 출력
for (int i = 0; i < landAnimal.size() ; i++) {
landAnimal.get(i).crying();
}
}
}
//상위 클래스인 LandAnimal 정의
public class LandAnimal {
public void crying() { System.out.println("육지동물");
}
}
import java.util.ArrayList;
//AnimalList 정의
public class AnimalList<T> {
//ArrayList선언
ArrayList<T> al = new ArrayList<T>();
//add함수
void add(T animal) { al.add(animal); }
//T 제너릭 타입 반환
T get(int index) { return al.get(index); }
//배열 내 animal 삭제
boolean remove(T animal) { return al.remove(animal); }
//배열 크기
int size() { return al.size(); }
}
//LandAnimal 상속 및 Cat클래스 정의
public class Cat extends LandAnimal{
@Override
public void crying() {
System.out.println("야옹야옹");
}
}
//LandAnimal 상속 및 Dog클래스 정의
public class Dog extends LandAnimal{
@Override
public void crying() {
System.out.println("멍 멍");
}
}
//육지동물이 아닌 Sparrow 클래스 정의
public class Sparrow {
public void crying() {
System.out.println("짹짹");
}
}
//Car 클래스 정의
public class Car implements Vehicle{
//캡슐화 하기
private String model;
private String color;
//밖에서 사용할 수 있는 set 함수
public Car(String model, String color) {
this.model = model;
this.color = color;
}
//차의 기능은 캡슐화로 안보이게하기 기능: 시동, 앞으로가기, 뒤로가기
private void startEngine() {
System.out.println("시동을 겁니다.");
}
private void moveForward() {
System.out.println("전진을 합니다.");
}
private void moveBackward() {
System.out.println("후진을 합니다.");
}
//다른 함수에서 부를경우 operate 함수를 이용해서 기능이용
@Override
public void operate() {
startEngine();
moveForward();
moveBackward();
}
}
Driver.java
//Driver.java
public class Driver {
//변수 private처리
private String name;
private Vehicle vehicle;
//클래스 변수 초기화
public Driver(String name, Vehicle vehicle) {
this.name = name;
this.vehicle = vehicle;
}
//클래스 변수 이름을 밖에서 볼수있도록 get처리
public String getName() {
return name;
}
//drive메소드사용
public void drive() {
vehicle.operate();
}
}
Main.java에서 Car클래스 선언하기
//Main.java
public class Main {
public static void main(String[] args) {
//Car클래스 선언
Car car = new Car("테슬라 모델x", "레드");
//Driver클래스 선언
Driver driver = new Driver("운전자", car);
driver.drive();
}
}
클래스 선언 같은 경우 바로 변수를 초기화함으로써 편리하게 사용할 수 있도록 했습니다.
이렇게 클래스안의 변수를 초기화해 뒀다면,
새로운 Car함수로 파라미터를 없게 해 두면 파라미터 없이 클래스선언을 할 수 있습니다.
public Car() {}
이런 식으로 Car를 해두면 파라미터 없이 선언이 가능합니다.
선언하는 방법은 알았으니
위에 써져 있는 interface, 캡슐화 이런 거에 대해 알아보겠습니다..
참고로 이 블로그에서 공부를 하려고 한다면 별로 도움이 되지 않을 것입니다. 아래에 제가 본 블로그를 올려두었으니 거기서 공부하시면 편하실 것입니다.
일단 자바 객체에는 4가지 알아둬야 할 특징이 있는데
바로 추상화, 상속, 다형성, 캡슐화입니다.
첫 번째로 추상화를 정리하겠습니다.
제가 이해한 바를 정리하자면 추상화는 클래스에서 만들 메서드(함수)의 틀을 미리 정의해 두는 걸 말합니다.
interface, 추상 클래스가 이에 속합니다.
public interface Vehicle {
void operate();
}
interface안에는 아무 기능 없이 메서드를 적어놓습니다. 그리고 implements를 클래스에 적용시켜서 operate 메소드를 사용하도록 강요합니다. 이런식으로 미리 메소드를 설계하고 class 내부에서 기능을 작성하는 것으로 좀 더 효율적인 개발을 할 수 있다고 합니다.
두 번째로 상속이란 상위 클래스에서 정의된 변수나 메서드를 하위 클래스에서 사용할 수 있게 하는 겁니다. 상위니 하위니 처음 공부할 때는 좀 상상이 안 갑니다.
쉽게 이해하자면 클래스 하나 다 썼는데 다른 데서도 똑같은 메서드를 쓰는 겁니다. 그때 상속을 이용하면 편합니다.
//Car 클래스 정의
public class Car implements Vehicle{
//캡슐화 하기
private String model;
private String color;
public Car() {}
//밖에서 사용할 수 있는 셋 함수
public Car(String model, String color) {
this.model = model;
this.color = color;
}
//차의 기능은 캡슐화로 안보이게하기 기능: 시동, 앞으로가기, 뒤로가기
private void startEngine() {
System.out.println("시동을 겁니다.");
}
private void moveForward() {
System.out.println("전진을 합니다.");
}
private void moveBackward() {
System.out.println("후진을 합니다.");
}
//다른 함수에서 부를경우 operate 함수를 이용해서 기능이용
@Override
public void operate() {
startEngine();
moveForward();
moveBackward();
}
public void scenario() {
moveForward();
moveBackward();
moveForward();
moveBackward();
}
}
//MotorBike클래스 정의
public class MotorBike extends Car implements Vehicle{
//변수 private처리
private String model;
private String color;
//set 함수
public MotorBike(String model, String color) {
this.model = model;
this.color = color;
}
//고유의 기능 stunt
private void stunt() {
System.out.printf("%s %s인 오토바이가 묘기를 부립니다.\n",model,color);
}
//Overide로 새로운 기능 정의
@Override
public void operate() {
stunt();
scenario();
}
}
@Override는 굳이 안 써도 됩니다. 저건 어디 다른데 있는 함수와 이름이 같은데 다른 기능으로 쓸 겁니다 라는 표시입니다.
아무튼 보시면 Car.java에 새로운 함수인 scenario를 만들었습니다.
그리고 extends Car를 MotorBike뒤에 적었습니다. 바로 상속입니다.
상속은 다음과 같이 extends를 이용해서 사용할 수 있습니다.
뭐가 달라진 건가요?
보시면 operate함수 안에 scenario라는 함수가 있죠?
근데 scenario라는 함수가 MotorBike클래스 안에 없습니다.
그럼에도 코드는 잘 실행됩니다. 바로 Car 클래스 안에 scenario가 있기 때문이죠
근데 Car클래스에서는 stunt함수를 사용할 수 없습니다. private로 만든 이유도 있겠지만 public으로 바꿔도 사용 못합니다.
또 Car클래스에서 private로 처리해 둔 변수나 함수는 MotorBike에서 사용할 수 없습니다.
상속 같은 경우에는 비유를 하자면
멤버십을 가입하는 겁니다.
자 저희가 멤버십을 제공하는 업체와 멤버십을 이용하는 고객이 있다고 합시다.
멤버십 제공하는 업체는 일반 고객보다 멤버 고객에게 더 많은 걸 제공하죠?
그래서 원래는 돈 내고 쓰는 걸 멤버 고객은 공짜로 쓸 수 있습니다.
그런데 맴버 고객한테 모든 걸 다 줄 수는 없기도 하고 멤버 고객한테 돈 이외에 업체가 뭘 요구하진 않습니다.
이걸 자바로 풀어내면
상위 클래스 - 멤버십 제공업체
하위 클래스 - 멤버 고객
아무 클래스 - 일반 고객
돈 - new 객체 선언
이 정도 일 것 같습니다.
다형성도 위의 예제에서 찾아볼 수 있습니다. 다형성 같은 경우 하나의 상위 객체로 여러 하위 객체들을 사용할 때 볼 수 있습니다. 예를 들면 위의 Vehicle 함수에서 operate기능이 있죠.
//Driver 클래스 정의
public class Driver {
//캡슐화
private String name;
private Vehicle vehicle;
//캡슐화를 위한 set, get함수
public Driver(String name, Vehicle vehicle) {
this.name = name;
this.vehicle = vehicle;
}
public String getName() {
return name;
}
//고유의 drive함수
public void drive() {
vehicle.operate();
}
}
위 코드를 보시면 set함수에 Vehicle vehicle 파라미터가 보이십니까?
Vehicle은 인터페이스인대 이게 뭐지 싶습니다.
Vehicle인터페이스를 제공하는 class에는 Car 클래스와 MotorBike클래스가 있습니다.
public class Main {
public static void main(String[] args) {
Car car = new Car("테슬라 모델x", "레드");
MotorBike motorBike = new MotorBike("바이크 모델", "블루");
Driver driver = new Driver("운전자", car);
Driver driver2 = new Driver("운전자2", motorBike);
driver.drive();
driver2.drive();
}
}
Main.java에 보시면 운전자가 두 명 있습니다. 근데 두 번째 파라미터로 받는 객체가 각각 다르죠
그런데 코드는 정상적으로 실행됩니다. 이걸 바로 다형성이라고 합니다.
이런 식으로 코드를 짜면 반복되는 코드를 줄일 수 있다고 합니다.
다형성이 없는 코드는 참고사이트에서 "Vehicle 인터페이스 적용"을 Ctrl + F 눌러서 찾아보세요.
마지막으로 캡슐화입니다. 위 코드에서 interface의 메서드는 하나뿐인 거 보이십니까?
원래는 startEngine, moveBackward, moveForward도 저 안에 있었습니다.
근데 캡슐화 챕터 가서 private 걸려고 하니까 자꾸 에러가 뜨더라고요.
그래서 좀 찾아봤더니 interface와 상속할 변수들은 public으로 특정해야 한다고 합니다.
즉 interface에 쓰는 메서드는 모두 public으로 설계해야 한다는 것이죠
private, public, protected 이걸로 변수가 공유될 범위를 특정할 수 있는데
이미지를 좀 찾아보니
이런 게 있었습니다. default는 아무것도 안 쓴 경우입니다.
자 보시면 아까 제가 멤버십 비유를 했습니다.
그 비유를 여기서도 적용시킬 수 있는데
protect부터 멤버 고객에게 제공할 수 있습니다.
그리고 default는 사내에 다른 팀원들에게 까지 공유되고
private부터는 우리 팀만 볼 수 있는 겁니다.
전 이런 식으로 이해하니까 상상이 돼서 쉽더라고요.
자기는 더 좋은 방식이 있다 하시면 그 방식으로 알아두시면 됩니다.
그래서 캡슐화는 제가 이해하기에 private, protected를 어디까지 하냐 그리고 set, get함수를 사용하는 것이다.
위에 코드로 보시면 편할 테니 아래에 한 번도 복붙 해두겠습니다.
//Car 클래스 정의
public class Car implements Vehicle{
//캡슐화 하기
private String model;
private String color;
public Car() {}
//밖에서 사용할 수 있는 셋 함수
public Car(String model, String color) {
this.model = model;
this.color = color;
}
//차의 기능은 캡슐화로 안보이게하기 기능: 시동, 앞으로가기, 뒤로가기
private void startEngine() {
System.out.println("시동을 겁니다.");
}
private void moveForward() {
System.out.println("전진을 합니다.");
}
private void moveBackward() {
System.out.println("후진을 합니다.");
}
//기능을 은닉해서 operate만 보이게함
@Override
public void operate() {
startEngine();
moveForward();
moveBackward();
}
public void scenario() {
moveForward();
moveBackward();
moveForward();
moveBackward();
}
}
//MotorBike클래스 정의
public class MotorBike extends Car implements Vehicle{
//변수 private처리
private String model;
private String color;
//set 함수
public MotorBike(String model, String color) {
this.model = model;
this.color = color;
}
//고유의 기능 stunt
private void stunt() {
System.out.printf("%s %s인 오토바이가 묘기를 부립니다.\n",model,color);
}
//Overide로 새로운 기능 정의
@Override
public void operate() {
stunt();
scenario();
}
}
//Driver 클래스 정의
public class Driver {
//캡슐화
private String name;
private Vehicle vehicle;
//캡슐화를 위한 set, get함수
public Driver(String name, Vehicle vehicle) {
this.name = name;
this.vehicle = vehicle;
}
public String getName() {
return name;
}
//고유의 drive함수
public void drive() {
vehicle.operate();
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car("테슬라 모델x", "레드");
MotorBike motorBike = new MotorBike("바이크 모델", "블루");
Driver driver = new Driver("운전자", car);
Driver driver2 = new Driver("운전자2", motorBike);
driver.drive();
driver2.drive();
}
}
조금 특별한 특징이라면 자바에서는 다른언어(python, JS)등에 비해 좀 더 엄격하게 타입을 관리하기 때문에
int, String, double 등의 타입을 항상 선언해야합니다.
이 부분은 안 읽으셔도 됩니다.
왜 그런가 좀 알아보니까, 엄격하게 타입을 관리할수록 안전하다고 합니다.
1가지 이유에대해 예를 들어보자면, JS에서는 이런 타입선언에 민감하지 않아서 변수 쓰고 아무 타입의 데이터를 저장할 수 있습니다. 그런데 이렇게 되면 유저가 뭘 찾아달라고 하면 한 배열 안에 여러 가지 타입의 변수들이 있어 찾기 어려워집니다. 그래서 요새는 타입을 관리하지 않는 언어에서도 TypeScript와 같은 라이브러리를 사용하는 게 필수라고 합니다.
public class Main {
public static void main(String[] args) {
System.out.println(add({10,20}));
}
public static int add(int[] numbers) {
return numbers[0] + numbers[1];
}
}
보통 for문을 배열과 함께 자주 사용하는데, 요. length 메서드 덕분인 것 같습니다.
array.length를 하면 배열의 인덱스 개수를 자동으로 새 줍니다.
이런 고마운 메서드는 기억해 둡시다,
public class Main {
public static void main(String[] args) {
int[] array = {10,12,11};
for (int i = 0; i < array.length; i++) {
System.out.printf("array[%d] = " + array[i] + "\n",i);
}
}
}