
Java기반의 Spring Boot를 사용하기때문에 Java 프로그래밍 언어를 사용할 줄은 알지만 이해하고 사용하는가에 대해 물어본다면 명확하게 대답 할 수 없다고 생각 한다.
무지는 죄 다.
알아가보도록 하자.
먼저 String 은 immutable(불변) 하다.
처음 String 형태로 문자열을 생성할때 힙 영역에 해당 문자열 만큼의 메모리 공간이 할당 되는데 이 할당된 공간은 수정되어도 변하지 않는다.
따라서 기존의 String 을 변하게 할 경우 이미 할당된 공간을 GC가 회수 하고 수정된 String 만큼을 재 할당 하여 저장 한다.
코딩 테스트를 준비 하다보면 문자열을 자르고 연결하는 작업이 생각보다 잦은데 이때 String을 사용한다면 성능이 많이 떨어지기때문에 StringBuilder 또는 StringBuffer를 주로 사용하게 된다.
물론 String 을 한번 할당 후 수정사항이 많지 않으면 간단하게 사용이 가능하기 때문에 나쁘지 않으면 동기화에 대해 신경쓰지 않아도 되기때문에(Thread-safe), 내부 데이터를 자유롭게 공유 가능하다.
Thread Safe 란 공유 자원에 동시에 다수의 스레드가 접근 하더라도 값이 변경되지 않는 일관적인 결과를 반환 하는 것을 뜻한다.
StringBuilder 과 StringBuffer 는 String과는 달리 기존에 할당 한 메모리 공간을 필요에 따라 확장하며 가변(mutable)적인 특성을 가진다.
StringBuilder과 StringBuffer의 차이는 Multi Thread에서 안전 여부에 따라 나뉘게 되는데 StringBuilder 는 thread 에서 안전하지 않고 StringBuffer는 thread에서 안전하다.
StringBuilder는 동기화를 지원하지 않는 반면, StringBuffer는 synchronized 키워드를 사용, 동기화를 지원하여 멀티 스레드 환경에서도 안전하게 동작할 수 있다.
java에서 synchronized 키워드는 여러개의 스레드가 한 개의 자원에 접근할려고 할 때, 현재 데이터를 사용하고 있는 스레드를 제외하고 나머지 스레드들이 데이터에 접근할 수 없도록 막는 역할을 수행한다.
public class Sample {
private String secret;
private String getSecret() {
return this.secret;
}
}
private 즉 외부에서는 간섭 할 수 없다.
접근 제어자가 private으로 설정되었다면 private이 붙은 변수나 메서드는 해당 클래스 안에서만 접근이 가능하다.
secret 변수와 getSecret 메서드는 오직 Sample 클래스에서만 접근이 가능하고 다른 클래스에서는 접근이 불가능하다.
private로 선언한 변수는 외부에서 직접적인 변경이 불가능 하기 때문에 getter를 통해서 값을 읽어오고 setter를 통해서 값을 변경 한다.
외부에서 secret변수에 직접 값을 넣어 대입 할 경우 어떤 문제가 발생 할 지 파악하기 난해 하기 때문에 본인은 private로 작성하는 것을 선호한다.
접근 제어자를 별도로 설정하지 않는다면 변수나 메서드는 default 접근 제어자가 자동으로 설정되어 동일한 패키지 안에서만 접근이 가능하다.
package house; // 패키지가 동일하다.
public class HouseKim {
String lastname = "kim"; // lastname은 default 접근제어자로 설정된다.
}
package house; // 패키지가 동일하다.
public class HousePark {
String lastname = "park";
public static void main(String[] args) {
HouseKim kim = new HouseKim();
System.out.println(kim.lastname); // HouseKim 클래스의 lastname 변수를 사용할 수 있다.
}
}
접근 제어자가 protected로 설정되었다면 protected가 붙은 변수나 메서드는 동일 패키지의 클래스 또는 해당 클래스를 상속받은 클래스에서만 접근이 가능하다.
package house; // house 패키지
public class HousePark {
protected String lastname = "park";
}
package house.person; // house.person 패키지
import house.HousePark;
public class EungYongPark extends HousePark { // HousePark을 상속했다.
public static void main(String[] args) {
EungYongPark eyp = new EungYongPark();
System.out.println(eyp.lastname); // 상속한 클래스의 protected 변수는 접근이 가능하다.
}
}
접근 제어자가 public으로 설정되었다면 public 접근 제어자가 붙은 변수나 메서드는 어떤 클래스에서도 접근이 가능하다.
package house;
public class HousePark {
protected String lastname = "park";
public String info = "this is public message.";
}
import house.HousePark;
public class Sample {
public static void main(String[] args) {
HousePark housePark = new HousePark();
System.out.println(housePark.info);
}
}
public 에서 변수를 선언하면 외부에서 직접 값을 대입하여 변경 할 수 있기에 편리하다고 생각 할 수 있다.
하지만 개발자 혹은 다른 실수로 인해 뜻하지 않는 변수 값을 변경 할 경우 예상하지 못하는 오류가 연쇄적으로 발생할 수 있을 것 이다.


예를 들어 감기약을 먹는다고 가정했을때 감기약을 복용하려는 사람은 말 그대로 "감기약"에 집중 할 것이다.
감기라는 병에 걸렸으니 감기약을 먹는 것이고 일반적으로 감기약 알약에 들어있는 수많은 성분들이 어떤 작용을 하는지 생각하고 먹지는 않을 것 이다.
이것을 바꿔서 생각해보면 사용자는 해당 객체(감기약)에서 제공하는 연산들을 통해서 객체를 제어 할 것이고 객체 내부의 연산을 사용하기 때문에 외부의 접근 제한하고 작동 할 수 있다.
외부의 접근을 제한하게 된다면 결합도가 떨어질 것이고 객체 자체의 응집도는 증가 할 것이다.
외부의 잘못된 접근으로 값이 변하는 의도치 않는 동작을 방지하는 보호 효과도 누릴 수 있다.
public class Phill {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
본인은 외부의 접근을 제한 하기 위해서는 Java 기준, 접근 제어자를 통해서 private로 설정후 getter 또는 setter를 통해서 객체를 조작한다.
위에 언급했던 캡슐화와 혼동 할 수 있는데 본인은 은닉화가 캡슐화 보다 더 큰 개념이라고 생각하고 있다.
정보 은닉 이라는 단어를 보자면 무언가 정보를 은닉하여 보안적인 효과를 얻는 것 같다.
그러나 보안적인 측면 뿐만 아니라, 은닉되어 알필요가 없어 덜 알아도 되어 간편하게 사용할 수 있게 해주는 의미도 내포한다.
대표적으로 은닉화는 3가지로 분류할 수 있다고 생각한다.
구체적인 자식객체의 정보를 은닉하는 upcasting 도 정보은닉의 일종이다.
class Car{
public void call(){
System.out.println("car");
}
}
class Myclass {
// 생략
public void method() {
Car car = new Car(); // Car 객체 생성
car.call(); // Myclass 클래스는 Car 클래스에 의존적인 코드
}
}
만약 Car 과 거의 동일하게 작동하는 객체인 Bike로 코드를 수정 한다고 생각해보자.
Bike로 수정하기 위해서는 상당한 코드를 수정 해줘야 한다.
하지만 여러가지 의 타입을 통합하기 위해 추상클래스로 상위 객체를 만들어 다형성 형태를 만들어 준다면 생성 부분만 수정함으로써 원하는 작동을 만들어 낼 것 이다.
abstract class Car{
abstract public void call();
}
class Bike extends Car{
public void call(){
System.out.println("bike");
}
}
class Myclass {
// 생략
public void method() {
Car car = new Bike(); // Bike 객체 생성
car.call();
}
}
객체를 생성 후 call 함수를 호출 하더라도 Bike 의 call 함수를 직접적으로 호출하지 않았기 때문에 일종의 정보 은닉 중 하나라고 볼 수 있을 것이다.
java 에서는 클래스와 유사하게 상속 가능한 타입이면서 구체적인 구현을 배제한 인터페이스를 만들어 메소드 추상화를 통해 상속시킬 공개 메서드를 통합적으로 관리 한다.
interface InterProcess {
public void work(); // 추상 메소드
}
class Process implements InterProcess {
private void init(){} // 은닉 메서드
private void process(){} // 은닉 메서드
private void release(){} // 은닉 메서드
public void work(){ // 공개 메서드 + 메소드 구체화
init();
process();
release();
}
}
public class Main {
public static void main(String[] args) {
InterProcess c = new Process(); // 상위 클래스 타입 처럼 이용될 수 있다
c.work();
}
}
캡슐화를 통해 최소한으로 줄인 공개 메서드를 인터페이스의 추상 메소드와 연동이 되면서, Process 클래스의 기능 동작을 하는데 있어 업캐스팅으로 인한 멤버 제한과 같은 제한 요소는 없어진다.
이밖에 인터페이스를 이용하면 실질적 클래스 간의 의존 관계가 없어지면서 기능 확장에 있어 제약이 줄어들게 된다.
먼저 상위 클래스와 추상 클래스는 똑같이 extends로 확장이 가능 하지만 추상 클래스는 객체 생성이 불가능 하고 하위 클래스에 메서드 작성을 강제 할 수 있다.
추상 메서드는 메서드의 선언부만 존재하고 구현코드가 존재하지 않는다.
구현부가 없는 메서드를 단 하나라도 가진 클래스는 추상 클래스가 되며 추상 클래스가 되면 new 키워드를 사용하여 인스턴스화 할 수 없다.
즉, 클래스를 쓸 수 없게 되는 것 이다.
추상클래스를 사용하려면 다른 클래스가 추상 클래스를 상속받아야 하는데 상속받은 자식 클래스가 부모 클래스에 존재하는 모든 추상메서드를 오버라이딩 하여 구현부를 설계하면 비로소 사용할 수 있는 객체가 된다.
public abstract class Human{
String name;
Human(String name){
this.name = name;
}
void show(){}; // 하위클래스에 메서드 작성(Override)을 강제
}
인터페이스는 추상 클래스의 특수형태 이며 추상클래스 중에서 멤버 변수와 메서드를 제거한 채 추상 메서드만을 남긴 형태이다.
(변하지 않는 고정값인 상수는 선언 할 수 있다.)
인터페이스도 추상 클래스와 마찬가지로 구현한 자식 클래스에서 인터페이스의 추상 메서드를 모두 오버라이딩 해야지만 비로서 객체를 사용할 수 있다.
(추상클래스나 인터페이스를 구현한 자식 객체가 추상 메서드를 전부 구현하지 않았다면 컴파일이 불가)
메서드를 직접 작성하지 않고 정의만 내리기 때문에 다중 상속에서 발생하는 메서드간의 충돌을 걱정 할 필요가 없다.
public interface class Human{
final String name; // 필드값 선언 불가 , 상수만 선언이 가능
void show(); //메소드 이름 선언 , body 내용을 작성 할 수 없다.
}
그렇다면 abstract와 interface에 대해서 굉장히 비슷하게 생각이 될 것이다.
목적에 따른 분류가 가능하며 추상 클래스는 상속받은 하위 클래스들이 exteds 즉 확장의 느낌이 강하고 인터페이스는 implements 즉 기능 구현의 느낌이 강하다.
추상 클래스는 자신의 기능들을 하위로 확장 시키는 것이 주목적이며 인터페이스는 자신에게 정의된 메서드를 각 클래스의 목적에 맞게 동일한 기능으로 구현 하는 것 이다.
상위 클래스의 특성을 하위 클래스가 물려 받는 것을 뜻하며 추상화 와 상속은 상호 보완적인 관계이다.
상위 클래스에서 이미 정의 된 클래스를 재사용해서 만들 수 있기 때문에 효율적이고, 개발 시간을 줄여주게 된다.
상위 클래스에서 private로 접근 제어자가 지정 되어있다면 하위 클래스에는 상속되지 않는다.
다형성이란 여러 개를 의미하는 poly와 형태 또는 실체를 의미하는 morphism의 결합어로, 하나의 객체가 여러 가지 형태를 가질 수 있는 것을 의미한다.
연관된 데이터(변수)와 기능(메소드)을 하나로 묶고, 불필요한 요소를 외부에 노출되지 않도록 설계하는 방식을 뜻한다. 자바에서는 접근 제어자(public, private, default, protected)를 통해 캡슐화를 구현할 수 있다.
위에서 한번 언급 했으므로 크게 짚고 넘어가지는 않겠다.
어떤 객체가 존재 할 때 객체를 추상화 할 수록 객체는 자신만의 특성을 잃어버리고 공통적인 특성만 존재 하게 된다.

예를 들어 비글 이라는 강아지가 존재한다고 생각 해보자.
비글을 추상화 하면 강아지로 추상화 될 것이다.
강아지로 추상화가 된다면 비글의 본연적인 특성은 사라지고 모든 강아지가 가지고 있는 공통적인 특성만 남게 될 것이다.
강아지를 한번 더 추상화 해서 동물로 정의 한다고 가정해보자.
강아지가 가지고 있는 고유한 특성들은 사라질 것 이고 동물이 가지고 있는 공통적인 특성만 가지고 있는 객체로 정의할 수 있을 것이다.
클래스는 하나의 기능만을 위해 작동해야 한다.
확장은 자유롭게 이루어져야 하지만, 기존 코드의 수정은 지양해야 한다는 원칙.
하위 클래스는 상위 클래스를 대체할 수 있어야 한다는 원칙.
하위 클래스는 상위 클래스의 기능을 모두 사용할 수 있어야 하며, 추가적인 기능도 사용 가능해야 한다.
하나의 큰 인터페이스보다는 기능별로 분리된 여러 개의 인터페이스를 사용하는 것이 좋다는 원칙.
객체 간 의존성을 내부에서 주입하는 것보다 외부에서 주입하는 것이 좋다는 원칙.
이를 통해 각 객체의 응집도를 높이고 결합도를 낮출 수 있다.
간단히 말해서 객체를 생성하기 위한 설계 틀 같은 것이다.

예를 들어 붕어빵을 만들어 내려할때 매번 붕어빵을 반죽해서 만드는 것 보다 붕어빵 틀에 재료를 부어서 원할때 마다 찍어내는 것이 훨씬 효율적 이고 간편할 것 이다.
public class FishBread{
int price;
String dough;
String ingredients;
public FishBread(int price, String dough, String ingredients){
//생략
}
//생략
}
위에서 생성한 FishBread를 Class로 정의 했다면 FishBread 를 변수로 선언 하는 것이 Object 즉 객체 라고 한다.
public static void main(String[] args) throws IOException {
FishBread fb;
}
변수를 선언한 FishBread를 실사용 할 수 있도록 실체화 하는 것을 인스턴스 라고 한다.
public static void main(String[] args) throws IOException {
FishBread fb = new FishBread(10000, "밀가루", "팥");
}
https://inpa.tistory.com/entry/JAVA-☕-String-StringBuffer-StringBuilder-차이점-성능-비교