관리 메뉴

LC Studio

JAVA & Spring 강의 (자바 인강 5주차) 객체 지향 핵심, 자바의 유용한 클래스들, 자바와 자료구조 본문

Java/Java&Spring 기초 강의

JAVA & Spring 강의 (자바 인강 5주차) 객체 지향 핵심, 자바의 유용한 클래스들, 자바와 자료구조

Leopard Cat 2022. 4. 20. 01:00

인터페이스(interface)

모든 메서드가 추상 메서드로 선언된 상태

모든 변수 또한 상수로 선언됨,

 

생성방법, class 자리에 interface를 붙여준다.

 

public interface Calc {

  double PI = 3.14; -> 상수로 선언됨

  int add(int num1, int num2); -> 추상 메서드
  int substract(int num1, int num2); -> 추상 메서드
}

 

package ch11;

public interface Calc {

	double PI = 3.14;
	int ERROR = -999999999;
	
	int add(int num1, int num2);
	int substract(int num1, int num2);
	int times(int num1, int num2);
	int divide(int num1, int num2);
}

Calc interface 생성

package ch11;

public abstract class Calculator implements Calc{

	@Override
	public int add(int num1, int num2) {
		return num1+num2;
	}

	@Override
	public int substract(int num1, int num2) {
		return num1 - num2;
	}
}

Calc interface implements, 상속 extends와는 다른개념

package ch11;

public class CompleteCalc extends Calculator{

	@Override
	public int times(int num1, int num2) {
		return num1*num2;
	}

	@Override
	public int divide(int num1, int num2) {
		if(num2 == 0)
			return ERROR;
		return num1/num2;
	}
	
	public void showInfo() {
		System.out.println("모두 구현했습니다.");
	}

}

Calculator을 상속받는 CompleteCalc

 

Calc calc = new CompleteCalc();

직접 상속받은 관계는 아니지만,

인터페이스를 구현한 클래스는 인터페이스 형으로 선언한 변수로 형 변환 할 수 있다.

 

상속에서의 형 변환과 동일하게, Calc에 선언된 메서드만 사용할 수 있다.


인터페이스(interface) 가 하는 일

인터페이스의 뜻은 연결점 이라는 뜻이다.

Java에서도 비슷한 맥락을 가지고 있다.

어떤 프로그램, 어떤 객체는 이런것을 제공한다. 라고 명시적으로 선언하는 역할을 한다.

 

ex) 이 프로그램, 이 객체는 이런것을 제공한다,,, 명시적으로 선언하는 역할

예로, Client 와 Server사이에 interface가 존재하여,

Client는 굳이 Server를 들여다보지 않고, 명시적으로 작성된 interface를 보고 무언갈 작성한다.

 

위의 예제에서도 

int add(int num1, int num2);  interface안에 이와같은 메서드가 있었다.

굳이 implements받은 클래스를 보지 않더라도,

아 이거는 더해주는 거구나 라는 것을 알고 사용할 수 있지 않은가.


인터페이스와 다형성

하나의 인터페이스를 여러 객체가 구현하게 되면

클라이언트 프로그램은 인터페이스의 메서드를 활용하여 여러 객체의 구현을 사용할 수 있다.

ex) 하나의 UserInfoDao에 3종류의 DB를 사용할 경우 사용할 수 있다.

 

위와같은 구조의 Package가 있다.

UserInfoDao 인터페이스를 활용하여 상황에 따라 다른 DB를 사용하도록 구현할 수 있다. 

package ch13.domain.userinfo.dao;

import ch13.domain.userinfo.UserInfo;

public interface UserInfoDao {

	void insertUserInfo(UserInfo userInfo);
	void updateUserInfo(UserInfo userInfo);
	void deleteUserInfo(UserInfo userInfo);
}

위의 코드는 interface 이다. 

UserInfoDao userInfoDao = null;
		if(dbType.equals("ORACLE")) {
			userInfoDao = new UserInfoOracleDao();
		}		
		else if(dbType.equals("MYSQL")) {
			userInfoDao = new UserInfoMySqlDao();
		}
		else {
			System.out.println("db error");
			return;
		}

interface를 활용하여 DB Type에 따라 다른 class 인터페이스를 생성한다.

 

만약 다른 DB를 추가한다면, 새로운 것을 만들필요 없이, if else문에 추가해주면 된다.

유지보수에 중요한 역할을 한다.


인터페이스의 여러가지 요소

1. 디폴트 메서드

인터페이스를 구현하는 클래스들에서 공통으로 사용할 수 있는 기본 메서드

ex)

public interface Calc {
	
	default void description() {
		System.out.println("정수의 사칙연산을 제공합니다.");
	}

interface에서 default void description() 메서드 정의

package ch14;

public abstract class Calculator implements Calc{
	
	//default 재정의
		@Override
		public void description() {
			System.out.println("CompleteCalc overriding");
		}
}

interface의 implements class에서 defalut 재정의 가능!

 

2. 정적 메서드

인스턴스 생성과 상관 없이 인터페이스 타입으로 사용할 수 있는 메서드

ex)

package ch14;

public interface Calc {

	//인스턴스 생성과 상관 없이 인터페이스 타입으로 사용할 수 있는 메서드 static
	static int total(int[] arr) {
		int total =0;
		for(int num : arr) {
			total += num;
		}
		return total;
	}
}

interface에서

인스턴스 생성과 상관 없이 인터페이스 타입으로 사용할 수 있는 static 메서드 정의

package ch14;

public class CalculatorTest {

	public static void main(String[] args) {

		int[] arr = {1, 2, 3, 4, 5};
		System.out.println(Calc.total(arr));
		
	}

}

main class에서 별도의 인터페이스 생성없이 바로 호출 가능

Clac.total(arr)

 

3. private 메서드

package ch14;

public interface Calc {
	
	private static void myMethod() {
		System.out.println("myMethod");
	}
	
	private static void myStaticMethod() {
		System.out.println("myStaticMethod");
	}
}

일반적인 private와 비슷하게 다른 클래스에서 참조할 수 없고,(재정의도 불가)

오직 인터페이스 내부에서만 사용하기 위해 구현하는 메서드이다.


여러 인터페이스 구현

Java에서 하나의 클래스가 여러 인터페이스를 구현할 수 있다. implements할 수 있다.

package ch15;

public class Customer implements Buy, Sell{
	
}

Buy 와 Sell implements

다만, 디폴트 메서드가 중복 되는 경우는 구현 하는 클래스에서 재정의 하여야 한다.

다음과 같이 Buy와 Sell에서 중복으로 order() 추상메서드를 정의한 경우

package ch15;

public interface Buy {

	void buy();
	
	default void order() {
		System.out.println("buy order");
	}
}
package ch15;

public interface Sell {

	void sell();
	
	default void order() {
		System.out.println("sell order");
	}
}
package ch15;

public class Customer implements Buy, Sell{

	@Override
	public void order() {
		System.out.println("customer order");
	}
}

위와같이 order을 재정의해주어야 한다.

 

그 외,

 

-구현 코드를 가지고 인스턴스 생성된 경우만 호출되는 디폴트 메서드의 경우가 있다.

두 개의 인터페이스에서 중복되면 구현하는 클래스에서 반드시 재정의를 해야한다. 

 

-인터페이스 사이에도 상속을 사용할 수 있어, extends 키워드를 사용할 수 있다.

interface 상속 extends의 의미는 상속받는 클래스가 위의 interface 메서드를 모두 선언하겠다라는 의미

 

클래스 상속과 인터페이스 구현 함께 쓰기

클래스와 인터페이스 구현을 함께 쓰는 경우가 있다. 기본적인 경우이다.

 

//이미 잘 구현된 클래스에서 상속을 받고
//자신이 구현해야 할 기능이 선언되어 있는 인터페이스를 구현한다

위와같은 구조가 있다면,

Shelf에서 잘 구현된 메서드를 상속받아, Queue에 정의된 추상 메서드 기준에 따라 BookShelf에 메서드를 작성한다.

package ch15;

import java.util.ArrayList;

public class Shelf {
	
	protected ArrayList<String> shelf; //shelf 생성자
	
	public Shelf() {
		shelf = new ArrayList<String>();
	}
	
	public ArrayList<String> getShelf(){
		return shelf;
	}
	
	public int getCount() {
		return shelf.size();
	}
}
package ch15;

public interface Queue {

	void enQueue(String title);
	String deQueue();
	
	int getSize();
}
package ch15;

public class BookShelf extends Shelf implements Queue{
	//이미 잘 구현된 클래스에서 상속
	//자신이 구현해야 할 기능이 선언되어 있는 인터페이스 implements

	@Override
	public void enQueue(String title) {
		shelf.add(title);
	}

	@Override
	public String deQueue() {
		return shelf.remove(0); //return하고 삭제
	}

	@Override
	public int getSize() {
		return getCount();
	}

}

Object 클래스

Object 클래스는 모든 클래스의 최상위 클래스이다.

java.lang 패키지에 속해있고, 프로그래밍시 import 하지 않아도 자동으로 import된다.

모든 클래스는 Object 클래스를 상속받는다. 그래서 최상위 클래스라 불린다.

String, Integer, System... 등이 속해있다.

 

toString() 메서드

객체의 정보를 String으로 바꾸어서 사용할 때 쓰인다.

String이나, Integer 등은 이미 재정의가 되어있다.

 

toString()을 재정의하는 예를 들어보자

@Override
	public String toString() {
		return title + "," + author;
	}

위와같이 toString()을 재정의하였고,

package ch01;

class Book {

	private String title;
	private String author;
	
	public Book(String title, String author) {
		this.title = title;
		this.author = author;
	}
	
	@Override
	public String toString() {
		return title + "," + author;
	}
}

public class BookTest {
	public static void main(String[] args) {
		Book book = new Book("데미안", "헤르만 헤세");
		System.out.println(book);
	}
}

원래대로라면, book이 가르키는 주소가 출력되어야하지만, (book.toString() 뒤에 toString()이 붙어있는데 안보임)

toString()을 Override해준 후에는 데미안, 헤르만 헤세가 출력된다.


Object 클래스의 메서드 활용

equals() 메서드

두 인스턴스의 주소 값을 비교하여 true/false를 반환한다.

hashCode() 메서드

인스턴스의 저장 주소를 반환한다.

 

*두 인스턴스가 같다는 것은?

두 인스턴스에 대한 equals()의 반환 값이 true이고,

동일한 hashCode() 값을 반환한다는 것이다.

 

clone() 메서드

객체의 원본을 복제하는데 사용하는 메서드이다.

 

예제

package ch02;

public class Student implements Cloneable{

	private int studentNum;
	private String studentName;
	
	public Student(int studentNum, String studentName) {
		this.studentNum = studentNum;
		this.studentName = studentName;
	}
	
	public String toString() {
		return studentNum +","+studentName;
	}

	@Override
	public int hashCode() {
		return studentNum;
	}

	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Student) {
			Student std = (Student)obj;
			if(this.studentNum == std.studentNum) {
				return true;
			}
			else {
				return false;
			}
		}
		else{
			return false;
		}
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
	
	
	
	
}
package ch02;

public class EqualsTest {
	public static void main(String[] args) throws CloneNotSupportedException {
		
		Student std1 = new Student(100, "Lee");
		Student std2 = new Student(100, "Lee");
		
		System.out.println(std1 == std2);
		System.out.println(std1.equals(std2));
		
		System.out.println(std1.hashCode());
		System.out.println(std2.hashCode());
		//실제 주솟값은 다르지만, 논리적으로 같기때문에 같은 hashCode값 반환
		
		System.out.println(System.identityHashCode(std1));
		System.out.println(System.identityHashCode(std2));
		//실제 hashCode값
		
		
		/*
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		System.out.println(str1.equals(str2));
		
		System.out.println(str1.hashCode());
		System.out.println(str1.hashCode());
		
		Integer i = 100;
		System.out.println(i.hashCode());
		*/
		
		Student copyStudent = (Student)std1.clone();
		System.out.println(copyStudent);
	}
}

String 클래스

    String str1 = new String("abc"); //힙 메모리에 생성
    String str2 = "abc"; //상수 풀로 생성

위와같이 new, 일반상수 선언에 따라 String이 생성되는 공간이 달라진다.

public class StringTest {

	public static void main(String[] args) {
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		System.out.println(str1 == str2); //다른값, False 반환
		
		String str3 = "abc";
		String str4 = "abc";
		
		System.out.println(str3 == str4); //같은값, True 반환
	}
}

위와같이,

힙 메모리는 생성될때마다 다른 주소 값을 가지지만,

상수 풀의 문자열은 모두 같은 주소 값을 가진다.

 

*한번 생성된 String은 불변(immutable)이기 때문에 메모리 낭비를 초래하지 않도록 잘 만들어야 한다.

ex)

package ch03;

public class StringTest {
	public static void main(String[] args) {
		String java = new String("java");
		String android = new String("android");
		
		System.out.println(System.identityHashCode(java)); //hash 코드주소
		java = java.concat(android);
		
		System.out.println(System.identityHashCode(java)); //hash 코드주소
	}
}

"java", "android" 두 String을 합치면, 두 주소가 연결되는것이 아닌 새로운 주소가 할당되고,

기존의 주소는 그대로 존대한다.

 

StringBuilder, StringBuffer

-StringBuilder = 단일 쓰레드 상황

-StringBuffer = 멀티 쓰레드 상황

위의 String new 같이 새로운 인스턴스를 생성하지 않고 cahr[]을 변경

 

ex)

package ch03;

public class StringBuilderTest {
	public static void main(String[] args) {
		
		String java = new String("java");
		String android = new String("android");
		
		StringBuilder buffer = new StringBuilder(java);
		
		System.out.println(System.identityHashCode(buffer)); //연결 전 메모리값
		buffer.append(android);
		System.out.println(System.identityHashCode(buffer)); //연결 후 메모리값
		
		String test = buffer.toString();
		System.out.println(test);
	}
}

append를 활용하여 문자를 붙였고, 전 후 의 주소값이 동일하다.

text block

문자열을 """ """사이에 이어서 만들 수 있다.

//html, json 문자열을 만드는데 유용하다.

package ch03;

public class TextBlockTest {
	public static void main(String[] args) {
		
		String textBlocks = """
					Hello,
					hi,
					how are you""";
		
		System.out.println(textBlocks);
	}
}

Class 클래스

 

Class 클래스는 컴파일 된 class 파일을 로드하여 객체를 동적 로드하고, 정보를 가져오는 메서드가 제공된다.

다음과 같이 forName을 활용하여 클래스를 동적으로 로드 한다.

Class c = Class.forName("java.lang.String");

 

동적 로딩

실행(runtime) 중에 데이터 타입을 binding 하는 방법

 

public class ClassTest {

	public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
						ClassNotFoundException, NoSuchMethodException, SecurityException {
		Person person = new Person("James");
		System.out.println(person);
		
		Class c1 = Class.forName("ch04.Person");
		Person person1 = (Person)c1.newInstance();
		System.out.println(person1);
		
		Class[] parameterTypes = {String.class};
		Constructor cons = c1.getConstructor(parameterTypes);
		
		Object[] initargs = {"김유신"};
		Person personLee = (Person)cons.newInstance(initargs);
		System.out.println(personLee);
	}
}

- newInstance()메서드로 인스턴스 생성

public class StringTest {

	public static void main(String[] args) throws ClassNotFoundException {
		Class c3 =  Class.forName("java.lang.String");
		
		Constructor<String>[] cons =  c3.getConstructors();
		for(Constructor con: cons) {
			System.out.println(con);
		}
		
		System.out.println();
		
		Method[] methods = c3.getMethods();
		for(Method  method : methods) {
			System.out.println(method);
		}
	}

}
public class Person {
	private String name;
	private int age;
	
	public Person() {};
	
	public Person(String name) {
		this.name = name;
	}
	
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	public String toString() {
		return name;
	}
}

여러가지 자료구조

자료구조

프로그램에서 사용할 많은 데이타를 메모리 상에서 관리하는 여러 구현방법들이다.

효율적으로 구성하면 성능좋은 프로그램을 구성할 수 있다.

 

1. 배열

Array //선형

논리적 위치와 물리적 위치가 동일! 그래서 자료의 입출력이 빠름.

중간에 빈 공간이 있으면 안됨. 사이즈 미리 할당.

 

2. 연결리스트

LinkedList //선형

자료가 추가될 때마다 메모리 할당.

다음 리스트 객체의 참조 변수를 갖고있음, 즉 자기 다음에 올 것을 가르키고 있다.

자료의 물리적위치와 논리적위치가 다를 수 있다. 추가&제거의 속도가 빠름.

 

3. 스택

Stack //선형

자료의 추가 삭제가 맨 위에서만 일어남. 방바닥에 책을 쌓아놓은 구조.

last in first out 후입선출 구조

함수의 메모리도 Stack구조로 되어있다.

 

4. 큐

Queue //선형

선착순, first in first out 선입선출.

추가는 무조건 맨 뒤에서부터 할 수 있고, 출력은 무조건 맨 앞에서부터 한다.

 

5. 트리

Tree //비선형

부모노드와 자식노드간의 연결로 이루어짐.

-힙(heap) :

     Max heap 부모노드가 자식노드보다 항상 크거나 같은 트리

     Min heap 부모노드가 자식노드보다 항상 작거나 같은 트리

-이진 검색 트리(bianry search tree) :

    왼쪽 자식 노드는 부모 노드보다 작은 값, 오른쪽 자식 노드는 부모 노드보다 큰 값을 가진다.

    자료 검색을 위한 트리, 자료가 오름차순 or 내림차순으로 정렬됨

     

6. 그래프

Graph

정점과 간선들의 유한 집합이다. 

-정점(vertex) :

   여러 특성을 가지는 객체, 노드(node)

-간선(edge) :

   이 객체들을 연결 관계를 나타냄. 링크(link)

구현방법 : 인접행렬, 인접 리스트

탐색방법: BFS, DFS

 

7. 해싱

Hashing

key에 대한 자료를 검색하기 위한 dictionary 개념의 자료구조이다.

dictionary라고 하기도 한다.

index = h(key) : 해시 함수가 key에 대한 인덱스를 반환해준다.

 

반응형