반응형

추상 팩토리 패턴(Abstract Factory Pattern)

  - 추상 팩토리 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

  - 생성 패턴을 이용하면 무엇이 생성되고, 누가 이것을 생성하며, 이것이 어떻게 생성되는지, 언제 생성할 것인지 결정하는 데 유연성을 화      보할 수 있습니다.

 

장점

  - 추상 팩토리 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 한다.

  - 추상 팩토리 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공한다.

  - 추상 팩토리 패턴에서는 팩토리 클래스에서 서브 클래스를 생성하는 데에 있어서 필요한 if-else, switch 문을 걷어냅니다.

 

팩토리 메소드 패턴 vs 추상 팩토리 패턴

- 둘 다 구체적인 객체 생성 과정을 추상화한 인터페이스를 제공한다.

 

팩토리 메소드 패턴 

추상 팩토리 패턴

관점 "팩토리를 구현하는 방법"에 초점 "팩토리를 사용하는 방법"에 초점
목적 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적

 

코드

  * 부동산 중개 수수료 계산 프로그램 가정(매매/임대차)

 

  - 중개 정책 interface 생성 :

     interface를 구현하는 하위 클래스에서 수수료 계산 로직을 구현한다.

     java8 이후로 default 메소드를 사용하면 interface 내부에서 로직 작성이 가능하다. (default 명시)

public interface BrokeragePolicy {

    BrokerageRule createBrokerageRule(Long price);

     default Long calculate(Long price) {
        BrokerageRule rule = createBrokerageRule(price);
        return rule.calcMaxBrokerage(price);
    }
}

 

  - 매매 중개 수수료 class :

    java7 이후부터 _는 숫자 리너털 어디에도 사용할 수 있다. 가독성 향상

 

public class PurchaseBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.6, 250_000L);
        } else if(price < 200_000_000) {
            rule = new BrokerageRule(0.5, 800_000L);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else if(price < 900_000_000) {
            rule = new BrokerageRule(0.5, null);
        }else{
            rule = new BrokerageRule(0.9, null);
        }
        return rule;
    }


}

 

  - 임대차 중개 수수료 class

 

public class RentBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.5, 200_000L);
        } else if(price < 100_000_000) {
            rule = new BrokerageRule(0.4, 300_000L);
        }else if(price < 300_000_000) {
            rule = new BrokerageRule(0.3, null);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else{
            rule = new BrokerageRule(0.8, null);
        }
        return rule;
    }


}

 

  - 추상 팩토리 Interface Class :

     추상 팩토리 역할을 하는 인터페이스 생성

 

public interface BrokeragePolicyAbstractFactory {

    public BrokeragePolicy createBrokerageRule();

}

 

- 팩토리 Interface를 구현하는 클래스(매매 중개 수수료) :

  작성한 팩토리 인터페이스의 createBrokerageRule() 메소드의 리턴 타입이 super class인 BrokeragePolicy이다.

  이는 자바의 다형성을 잘 활용한 방식이다.

public class PurchaseBrokerageFactory implements BrokeragePolicyAbstractFactory{

    @Override
    public BrokeragePolicy createBrokerageRule() {
        return new PurchaseBrokeragePolicy();
    }
}

 

- 팩토리 Interface를 구현하는 클래스(임대차 중개 수수료)

 

public class RentBrokerageFactory implements BrokeragePolicyAbstractFactory{

    @Override
    public BrokeragePolicy createBrokerageRule() {
        return new RentBrokeragePolicy();
    }
}

 

- 서브 클래스들을 생성하기 위해서 if-else 없이 분개 처리해주는 클래스

 

public class BrokeragePolicyFactory {


    public static BrokeragePolicy getBrokeragePolicy(BrokeragePolicyAbstractFactory policyAbstractFactory) {
        return policyAbstractFactory.createBrokerageRule();
    }

}

 

- 중개 수수료 계산 api

 

  // 매매 중계수수료 계산 메소드
    @GetMapping("/api/calc/brokeragepur")
    public Long calcBrokeragePurchase(@RequestParam ActionType actionType,
                              @RequestParam Long price) {
        BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new PurchaseBrokerageFactory());
        return policy.calculate(price);
    }
  // 임대차 중계수수료 계산 메소드
    @GetMapping("/api/calc/brokeragerent")
    public Long calcBrokerageRent(@RequestParam ActionType actionType,
                              @RequestParam Long price) {
        BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new RentBrokerageFactory());
        return policy.calculate(price);
    }

 

 

실무 사용 사례

Spring에서 FactoryBean과 그 구현체

 

정리

 - 객체 생성을 담당 및 처리하는 팩토리 클래스를 생성하여서 객체 생성에 관한 확장도 쉽게 구성할 수 있다. 

    팩토리 패턴과 다르게 분개 처리하는 부분이 사라진다.

 

728x90
반응형

'JAVA > Design Pettern' 카테고리의 다른 글

[Design Pattern] 템플릿 메서드 패턴  (0) 2022.03.23
[Design Pattern] 프록시 패턴  (0) 2022.03.22
[Design Pattern] 어댑터 패턴  (0) 2022.03.16
[Design Pattern] 싱글톤 패턴  (0) 2022.03.15
[Design Pattern] 빌더 패턴  (0) 2022.03.14
반응형

어댑터 패턴(Adapter Pattern)

  - 어댑터 패턴은 구조패턴(Structural Pattern) 중 하나이다.
  - 구조 패턴은 클래스를 상속과 합성을 이용해서 더 큰 클래스를 생성하는 방법을 제공하는 패턴이다.

  - 구조 패턴을 사용하면 서로 독립적으로 개발한 클래스를 하나인 양 사용할 수 있다.

  - 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴

 

장점

  - 어댑터 패턴은 연결할 수 없는 두 개의 호환되지 않는 인터페이스 사이의 커넥터 역할을 수행한다.

  - 클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 변환시킨다.

  - 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어 재사용할 수 있다.

  - 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.

단점

  - 새 클래스가 생겨 복잡도가 증가할 수 있다. 경우에 따라서는 기존 코드가 해당 인터페이스를 구현하도록 수정하는 것이 좋은 선택이 될 수도 있다.

 

코드

  • 최종 변경해 줘야 하는 객체 타켓 (Target)
  • 변환의 대상이 되는 객체 어댑티 (Adaptee)
  • 변경을 도와주는 객체 어댑터 (Adapter)

 * 예시) BugattiVeyron의 속도를 시속 마일(MPH) 단위에서 킬로미터(km/h) 단위로 반환해야 하는 예제

 

BugattiVeyron.java

 

public class BugattiVeyron implements Movable{

    @Override
    public double getSpeed() {
        return 268;
    }
}

 

Movable.java

 

public interface Movable {

    double getSpeed();
}

 

MovableAdapter.java

  - Adapter interface

 

public interface MovableAdapter {

    double getSpeed();
}

 

MovableAdapterImpl.java

  - Adapter 실제 구현체 convertMPHtoKMPH 마일단위를 킬로미터로 변환한다.

 

public class MovableAdapterImpl implements MovableAdapter{

    private Movable luxuryCars;

    // standard constructors
    MovableAdapterImpl(Movable luxuryCars) {
        this.luxuryCars = luxuryCars;
    }

    @Override
    public double getSpeed() {
        return convertMPHtoKMPH(luxuryCars.getSpeed());
    }

    private double convertMPHtoKMPH(double mph) {
        return mph * 1.60934;
    }
}

 

AdapterPatternTest.java

 

@SpringBootTest
class AdapterPatternTest {

    @Test
    void 어댑터_테스트() {
        Movable bugattiVeyron = new BugattiVeyron();
        MovableAdapter bugattiVeyronAdapter = new MovableAdapterImpl(bugattiVeyron);

        assertEquals(bugattiVeyronAdapter.getSpeed(), 431.30312, 0.00001);

    }


}

 

정리

 - 어댑터 패턴은 호환되지 않는 두 개의 객체를 호환되게 하기 위해서 하나의 Adapter라는 인터페이스를 추가해서 변환하는 로직을 추가한다.

기존 클래스의 소스코드를 수정해서 인터페이스에 맞추는 작업보다 기존 클래스의 소스코드의 수정을 하지 않고 타겟 인터페이스에 맞춰서 동작을 가능하게 한다.

 

실무 사용 사례

- java에서 collections(Arrays.asList, Collections.enumeration(), Collections.list())

- java에서 IO code

- Spring에서 Spring Security에 UserDetails

- Spring에 HandlerAdapter

728x90
반응형
반응형

싱글톤 패턴(Singleton Pattern)

  - 싱글톤 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 싱글톤 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 싱글톤 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

  - 하나의 객체만을 생성해 이후에 호출된 곳에서는 생성된 객체를 반환하여 프로그램 전반에서 하나의 인스턴스만을 사용하게 하는 패턴이다.

 

장점

  - 한번의 객체 생성으로 재사용이 가능하여 메모리 낭비를 방지한다.

  - 시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 생기는 문제를 미연에 방지할 수 있다.

 

문제점

  - 멀티 스레드 환경에서 안전하지 않다. (대처가 가능하다.)

  - 객체간의 결함도가 높아지고 변경에 유연하게 대처할 수 없다. 싱글톤 객체가 변경되면 이를 참조하고 있는 모든 값들이 변경되어야 한다.

  - 객체 상태를 유지하면 큰 문제가 발생한다.(stateful)

코드

  * 여러가지 싱글턴 구현 방법이있다.

  1. 기본 Singleton
  2. 이른 초기화(Eager Initialization)
  3. synchronized 사용
  4. static inner SIngleton(권장하는 방법)
  5. enum SIngleton(권장하는 방법)

 

1. 기본 Singleton

 - 단점이 Thread Safe 하지 못하다.

 

public class SingletonStatic {

    private static SingletonStatic instance;

    private SingletonStatic() {}

    public static SingletonStatic getInstance() {
        if(instance == null) {
            instance = new SingletonStatic();
        }
        
        return instance;
    }

}

 

 

2. Eager Initialization

 - Thread Safe

 - 단점이 인스턴스를 미리 만들어 놓는다는 단점이 있다.

 

public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }

}

 

3. Thread Safe Singleton(synchronized 사용)

  - synchronized를 통해서 여러 쓰레드에서 동시에 접근하는 것을 막을 수 있다.(Thread Safe)

  - 동기화 처리하는 작업 때문에 성능에 불이익이 생길 수 있다.

 

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton() {}

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) { instance = new ThreadSafeSingleton();}
        return instance;
    }
    
}

 

4. static inner SIngleton(권장하는 방법)

  - SingletonHelper 클래스는 SIngleton 클래스가 Load 될 때에도 Load 되지 않다가 getInstance()를 호출할 때 jvm 메모리에 로드되어 인스턴스를 생성한다. synchronized를 사용하지 않기 때문에 synchronized 키워드 자체에 대한 성능 저하 문제를 해결할 수 있다.

  - 단점으로 자바 리플렉션을 이용하면 싱글톤 패턴이 깨진다.

 

public class BillPughSIngleton {

    private BillPughSIngleton(){}

    private static class SingletonHelper{
        private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
    }

    public static BillPughSIngleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
    
}

 

객체 상태를 유지했을 경우 주의 (stateful)

BillPughSIngleton.java

 

public class BillPughSIngleton {

    private String apartment;

    private BillPughSIngleton(){}

    private static class SingletonHelper{
        private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
    }

    public static BillPughSIngleton getInstance(){
        return SingletonHelper.INSTANCE;
    }

    public String getApartment() {
        return apartment;
    }

    public void setApartment(String apartment) {
        this.apartment = apartment;
    }

}

 

SIngletonTest.java

 

    @Test
    void singleTonStatefulTest() {

        BillPughSIngleton instance1 = BillPughSIngleton.getInstance();
        BillPughSIngleton instance2 = BillPughSIngleton.getInstance();

        instance1.setApartment("아크로빌 아파트");
        instance2.setApartment("한남더힐 아파트");

        System.out.println(instance1.getApartment());
        System.out.println(instance2.getApartment());


    }

  - stateful하게 코드를 작성하게 되면 위와 같이 객체의 상태가 바뀌는 문제가 발생하게 된다.

무결성하게(stateless) 코드를 작성하여야 한다.

 

BillPughSIngleton.java

  - setApartment 값을 바로 return 한다.

 

public class BillPughSIngleton {

    private String apartment;

    private BillPughSIngleton(){}

    private static class SingletonHelper{
        private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
    }

    public static BillPughSIngleton getInstance(){
        return SingletonHelper.INSTANCE;
    }

    public String getApartment() {
        return apartment;
    }

    public String setApartment(String apartment) {
        this.apartment = apartment;
        return apartment;
    }

}

 

SIngletonTest.java

 

   @Test
    void singleTonStatefulTest() {

        BillPughSIngleton instance1 = BillPughSIngleton.getInstance();
        BillPughSIngleton instance2 = BillPughSIngleton.getInstance();

        String apartment1 = instance1.setApartment("아크로빌 아파트");
        String apartment2 = instance2.setApartment("한남더힐 아파트");

        System.out.println(apartment1);
        System.out.println(apartment2);
        
    }

 

5. enum Singleton(권장하는 방법)

 - 4번(static inner Singleton)과 같이 권장하는 방법으로 enum 클래스로 사용하는 방법이다.

 - 자바 리플랙션을 이용해도 enum 클래스는 newInstance를 이용해서 새로운 인스턴스를 생성할 수 없어서 더 안전하다.

 - 단점으로는 상속을 사용하지 못하고, 사용하지 않아도 미리 만들어 놓는다는 단점이 있다.  

 

public enum ThreadSafeV5 {

    INSTANCE;

    private String name;

    public String getName() {
        return name;
    }

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

 

실무 사용 사례

1. 자바 java.lang.Runtime 클래스가 싱글톤 패턴으로 구현되어 있다.

2. java Application 개발에서 많이 사용되는 스프링 프레임워크는 스프링 빈 컨테이너의 도움을 받아 싱글톤 스코프로 관리된다.

스프링 프레임워크를 사용하면 싱글톤 패턴의 문제점들을 보완하면서 장점을 누릴수 있다.  

3. 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.

 

728x90
반응형
반응형

빌더패턴(Builder Pattern)

  - 빌더 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

  - 별도의 Builder 클래스를 생성해서 필수 값에 대해서는 생성자를 통해, 선택적인 값들에 대해서는 메소드를 통해 값을 입력받은 후에 build() 메서드를 통해 최종적으로 하나의 인스턴스를 리턴하는 방식이다.

 

장점

  - 필요한 데이터만 설정할 수 있다.

  - 유연성을 확보할 수 있다.

  - 가독성을 높일 수 있다.

  - 변경 가능성을 최소화할 수 있다.

 

코드

House.java

 

public class House {

    private Long houseId;
    private String address;
    private String type;
    private Long price;
    private String phone;

    public Long getHouseId() {
        return houseId;
    }
    public String getAddress() {
        return address;
    }
    public String getType() {
        return type;
    }
    public Long getPrice() {
        return price;
    }
    public String getPhone() {
        return phone;
    }

    private House(HouseBuilder builder) {
        this.houseId = builder.houseId;
        this.address = builder.address;
        this.type = builder.type;
        this.price = builder.price;
        this.phone = builder.phone;
    }

    public static class HouseBuilder{

        private Long houseId;
        private String address;
        private String type;
        private Long price;
        private String phone;

	// 필수값 생성자
        public HouseBuilder(Long houseId, String address, String type) {
            this.houseId = houseId;
            this.address = address;
            this.type = type;
        }

        public HouseBuilder price(Long price) {
            this.price = price;
            return this;
        }

        public HouseBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public House build() {
            return new House(this);
        }

    }


}

HouseTest.java

@SpringBootTest
class HouseTest {

    @Test
    void HouseBuilderTest() {

        String houseAddress = "서울특별시 관악구";
        String houseType = "매매";
        Long housePrice = 10000000L;

        House house =  new House.HouseBuilder(1L, houseAddress, houseType)
                                .price(housePrice)
                                .build();

        assertThat(house.getAddress(), is(houseAddress));
        assertThat(house.getType(), is(houseType));
        assertThat(house.getPrice(), is(housePrice));

    }


}

 

실무 사용 사례

- java에 StringBuilder

- java에 Stream API

- Spring Lombok

- Spring UriComponents

 

정리

 - 인스턴스를 생성할 때 생성자만을 통해서 생성하면 칼럼이 변경될 때마다 많은 소스코드에 영향이 가는 고통스러운 상황이 발생할 것이다. 빌더 패턴을 이용해서 유연성을 확보할 수 있다.

 

참조

 - https://readystory.tistory.com/121

 

 

728x90
반응형
반응형

팩토리 메소드 패턴(Factory Method Pattern)

  - 팩토리 메소드 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

 

장점

  - 확장에 열려있고 변경에 닫혀있는 객체 지향 원칙을 적용해서 기존에 인스턴스를 만드는 과정의 로직을 건드리지 않고, 새로운 인스턴스를 다은 방법으로 확장이 가능하다.

  - 팩토리 메소드 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 한다.

  - 팩토리 메소드 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공한다.

 

단점

  - 각자의 역할을 나누어서 생성하다 보니 클래스 개수가 늘어난다.

코드

  * 부동산 중개 수수료 계산 프로그램 가정(매매/임대차)

 

  - 중개 정책 interface 생성 :

     interface를 구현하는 하위 클래스에서 수수료 계산 로직을 구현한다.

     java8 이후로 default 메소드를 사용하면 interface 내부에서 로직 작성이 가능하다. (default 명시)

     java9 이후로는 interface에 priave 메소드도 사용이 가능하다.

public interface BrokeragePolicy {

    BrokerageRule createBrokerageRule(Long price);

     default Long calculate(Long price) {
        BrokerageRule rule = createBrokerageRule(price);
        return rule.calcMaxBrokerage(price);
    }
}

 

  - 매매 중개 수수료 class :

    java7 이후부터 _는 숫자 리너털 어디에도 사용할 수 있다. 가독성 향상

 

public class PurchaseBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.6, 250_000L);
        } else if(price < 200_000_000) {
            rule = new BrokerageRule(0.5, 800_000L);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else if(price < 900_000_000) {
            rule = new BrokerageRule(0.5, null);
        }else{
            rule = new BrokerageRule(0.9, null);
        }
        return rule;
    }


}

 

  - 임대차 중개 수수료 class

 

public class RentBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.5, 200_000L);
        } else if(price < 100_000_000) {
            rule = new BrokerageRule(0.4, 300_000L);
        }else if(price < 300_000_000) {
            rule = new BrokerageRule(0.3, null);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else{
            rule = new BrokerageRule(0.8, null);
        }
        return rule;
    }


}

 

  - Factory Class (핵심 클래스)

 

public class BrokeragePolicyFactory {

    public static BrokeragePolicy of(ActionType actionType) {
        switch (actionType) {
            case RENT:
                return new RentBrokeragePolicy();
            case PURCHASE:
                return new PurchaseBrokeragePolicy();
            default:
                throw new IllegalArgumentException("해당 actionType에 대한 정책이 존재하지 않습니다.");
        }
    }

}

 

- 중개 수수료 계산 api

 

public Long calcBrokerage(@RequestParam ActionType actionType,
                          @RequestParam Long price) {
        // 타입 정의 - 매매 / 임대차
        BrokeragePolicy policy = BrokeragePolicyFactory.of(actionType);
        return policy.calculate(price);
}

 

실무 사용 사례

- Spring에서 BeanFactory

- Java Calendar 

728x90
반응형

'JAVA > Design Pettern' 카테고리의 다른 글

[Design Pattern] 프록시 패턴  (0) 2022.03.22
[Design Pattern] 추상 팩토리 패턴  (0) 2022.03.19
[Design Pattern] 어댑터 패턴  (0) 2022.03.16
[Design Pattern] 싱글톤 패턴  (0) 2022.03.15
[Design Pattern] 빌더 패턴  (0) 2022.03.14
반응형

스트림(Stream)

스트림(Stream)은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

 

스트림은 자바8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다. Iterator와 비슷한 역할을 하지만 람다식으로 요소 처리 코드를 제공하여 코드가 좀 더 간결하게 할 수 있다는 점과 내부 반복자를 사용하므로 병렬처리가 쉽다는 점에서 차이점이 있습니다. 

 

스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.

  1. 생성하기 : 스트림 인스턴스 생성.
  2. 가공하기 : 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations).
  3. 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).

1. 생성하기

배열 스트림

 

String[] arr = new String[]{"Bumblebee", "b", "c"};
Stream<String> stream = Arrays.stream(arr); // return Bumblebee, b, c
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3); // return b, c

 

컬렉션 스트림

 

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> colStream = list.stream(); // return a, b, c
Stream<String> parallelStream = list.parallelStream();  // 병렬 처리 스트림

 

Stream.builder()

 

Stream<String> builderStream =
Stream.<String>builder()
                        .add("kakao").add("naver").add("google")
                        .build(); // return kakao, naver, google

 

Stream.generate()

생성되는 스트림은 크기가 정해져있지 않고 무한(infinite)하기 때문에 특정 사이즈로 최대 크기를 제한해야 합니다.

 

 

Stream<String> generatedStream =
Stream.generate(() -> "Bumblebee").limit(3); // return Bumblebee, Bumblebee, Bumblebee

 

Stream.iterate()

초기값 설정 후 해당 값을 람다를 통해서 스트림에 들어갈 요소를 만들 수 있다.

 

Stream<Integer> iteratedStream = Stream.iterate(10, n -> n + 2).limit(3); // return 10, 12, 14

 

기본 타입형 스트림

리스트나 배열을 이용해서 기본 타입(int, long, double) 스트림을 생성할 수 있습니다.

 

IntStream intStream = IntStream.range(1, 5); // return 1,2,3,4
LongStream longStream = LongStream.rangeClosed(1, 5); // return 1,2,3,4,5

 

Java 8 의 Random 클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream, LongStream, DoubleStream)을 만들어낼 수 있습니다.

DoubleStream doubles = new Random().doubles(3);

 

병렬 스트림 Parallel Stream

스트림 생성 시 사용하는 stream 대신 parallelStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다.

내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join framework 를 사용합니다.

각 작업을 쓰레드를 이용해 병렬 처리됩니다.

 

Stream<Product> parallelStream = productList.parallelStream(); // 병렬 스트림 생성
boolean isParallel = parallelStream.isParallel(); // 병렬 여부 확인

 

다시 시퀀셜(sequential) 모드로 돌리고 싶다면 다음처럼 sequential 메소드를 사용합니다.

 

parallelStream.sequential(); // sequential
boolean isParallel = parallelStream .isParallel();

 

스트림 연결하기

 

Stream<String> stream1 = Stream.of("kakao", "naver");
Stream<String> stream2 = Stream.of("google");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(a -> System.out.print(a+", ")); // kakao, naver, google

 

2. 가공하기

전체 요소 중에서 다음과 같은 API 를 이용해서 내가 원하는 것만 뽑아낼 수 있습니다.

이러한 가공 단계를 중간 작업(intermediate operations)이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙여서(chaining) 작성할 수 있습니다.

 

Filtering

필터(filter)은 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업입니다. 

 

List<String> itCompany = Arrays.asList("kakao", "naver", "google"); // 테스트 dataset

Stream<String> filterStream = itCompany.stream().filter(company -> company.contains("a")); // return kakao, naver

 

Mapping

맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환해줍니다. 이 때 값을 변환하기 위한 람다를 인자로 받습니다.

 

Stream<String> mapStream = itCompany.stream().map(String::toUpperCase); // return KAKAO, NAVER, GOOGLE

 

Sorting

 

List<String> sortStream = itCompany.stream()
                                            .sorted()
                                            .collect(Collectors.toList()); //return google, kakao, naver,


List<String> sortReverseStream = itCompany.stream()
                                                  .sorted(Comparator.reverseOrder()) //역순
                                                  .collect(Collectors.toList()); // return naver, kakao, google

 

Comparator 의 compare 메소드는 두 인자를 비교해서 값을 리턴합니다.

 

List<String> compareSortStream = itCompany.stream()
                                                   .sorted(Comparator.comparingInt(String::length))
                                                   .collect(Collectors.toList()); // return kakao, naver, google


List<String> collect = itCompany.stream()
                                        .sorted((s1, s2) -> s2.length() - s1.length())
                                        .collect(Collectors.toList()); // reutrn google, kakao, naver,

 

peek

확인해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 인터페이스 Consumer 를 인자로 받습니다.

 

IntStream.of(1, 3, 5, 7, 9)
                            .peek(System.out::println)
                            .sum(); //return 1, 3, 5, 7, 9

 

 

3. 결과 만들기

가공한 스트림을 가지고 내가 사용할 결과값으로 만들어내는 단계입니다. 따라서 스트림을 끝내는 최종 작업(terminal operations)입니다.

 

Calculating

스트림 API 는 다양한 종료 작업을 제공합니다. 최소, 최대, 합, 평균 등 기본형 타입으로 결과를 만들어낼 수 있습니다.

 

long count = IntStream.of(1, 2, 3, 4, 5).count(); //return 5
long sum = LongStream.of(1, 2, 3, 4, 5).sum();  //return 15

 

만약 스트림이 비어 있는 경우 count  sum 은 0을 출력하면 됩니다. 하지만 평균, 최소, 최대의 경우에는 표현할 수가 없기 때문에  Optional을 이용해 리턴합니다.

 

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min(); //return OptionalInt[1]
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max(); //return OptionalInt[9]

 

Reduction

스트림은 reduce라는 메소드를 이용해서 결과를 만들어냅니다.

 

reduce 메소드는 총 세 가지의 파라미터를 받을 수 있습니다.

  1. accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
  2. identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
  3. combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.

1. accumulator 

 

OptionalInt reduced =
IntStream.range(1, 4) //[1, 2, 3]
.reduce((a, b) -> {
    return Integer.sum(a, b);
});  // return OptionalInt[6]

 

2. identity

10은 초기값이고, 스트림 내 값을 더해서 결과는 16이 됩니다.

여기서 람다는 메소드 참조(method reference)를 이용해서 넘길 수 있습니다.

 

int reducedTwoParams =
IntStream.range(1, 4) // [1, 2, 3]
.reduce(10, Integer::sum); // return 16

 

3.combiner

Combiner 는 병렬 처리 시 각자 다른 쓰레드에서 실행한 결과를 마지막에 합치는 단계입니다.

따라서 병렬 스트림에서만 동작합니다.

 

Integer reducedParams = Stream.of(1, 2, 3)
.reduce(10, // identity
Integer::sum, // accumulator
(a, b) -> {
   System.out.println("combiner was called"); // 병렬 스트림이 아니어서 호출 안됨
   return a + b;
}); // return 16

 

결과는 다음과 같이 36이 나옵니다. 먼저 accumulator 는 총 세 번 동작합니다. 초기값 10에 각 스트림 값을 더한 세 개의 값(10 + 1 = 11, 10 + 2 = 12, 10 + 3 = 13)을 계산합니다. Combiner 는 identity 와 accumulator 를 가지고 여러 쓰레드에서 나눠 계산한 결과를 합치는 역할입니다. 12 + 13 = 25, 25 + 11 = 36 이렇게 두 번 호출됩니다.

 

Integer reducedParallel = Arrays.asList(1, 2, 3)
.parallelStream() // 병렬 스트림
.reduce(10,
Integer::sum,
(a, b) -> {
   System.out.println("combiner was called"); // 두번 호출 됨
   return a + b;
}); // return 36

 

Collecting

- Product.java

public class Product {
int count;
String name;

public Product(int count, String name) {
this.count = count;
this.name = name;
}

public String getName() {
return this.name;
}

public int getAmount() {
return this.count;
}
}

// 테스트 dataset

List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
                                                      new Product(14, "orange"),
                                                      new Product(13, "lemon"),
                                                      new Product(23, "bread"),
                                                      new Product(13, "sugar"));

 

Collectors.toList()

 

List<String> collectorCollection = productList.stream()
                                                      .map(Product::getName)
                                                   	.collect(Collectors.toList());

// return potatoes, orange, lemon, bread, sugar

 

Collectors.joining()

 

String listToString = productList.stream()
                                         .map(Product::getName)
                                         .collect(Collectors.joining()); // reutrn potatoesorangelemonbreadsugar

 

 

  • delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
  • prefix : 결과 맨 앞에 붙는 문자
  • suffix : 결과 맨 뒤에 붙는 문자
String listToString2 = productList.stream()
                                         .map(Product::getName)
                                         .collect(Collectors.joining(", ", "<", ">"));

// reutrn <potatoes, orange, lemon, bread, sugar>

 

Collectors.averageingInt()

 

Double averageAmount = productList.stream()
                                         .collect(Collectors.averagingInt(Product::getAmount)); // return 17.2

 

Collectors.summingInt()

 

Integer summingAmount = productList.stream()
                                          .collect(Collectors.summingInt(Product::getAmount)); // return 86

 

IntStream 으로 바꿔주는 mapToInt 메소드를 사용해서 좀 더 간단하게 표현할 수 있습니다.

 

Integer summingAmount = productList.stream()
                                           .mapToInt(Product::getAmount)
                                           .sum(); // return 86

 

Collectors.summarizingInt()

 

IntSummaryStatistics statistics =productList.stream()
                                                   .collect(Collectors.summarizingInt(Product::getAmount));

//return IntSummaryStatistics{count=5, sum=86, min=13, average=17.200000, max=23}

 

 

참고

- Java 스트림 Stream (1) 총정리(https://futurecreator.github.io/2018/08/26/java-8-streams/)

- [Java] 자바 스트림(Stream) 사용법 & 예제(https://coding-factory.tistory.com/574)

728x90
반응형

'JAVA' 카테고리의 다른 글

JAVA int, Integer 비교  (0) 2023.03.16
반응형

목표

자바의 제네릭에 대해 학습하세요.

학습할 것

 

  • 제네릭 사용법
  • 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기
  • Erasure

제네릭이란

  • 데이터 타입을 일반화하는 것을 의미한다.

제네릭을 사용하는 이유

  • 컴파일 시 강한 타입 체크를 할 수 있다.
  • 타입 변환을 제거한다.
  • 자바 5부터 제네릭 타입이 새롭게 추가되었다. 제네릭 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다. 제네릭은 클래스와 인터페이스, 그리고 메소드를 정의할 때 타입 파라미터로 사용할 수 있도록 한다.

제네릭 사용법

제네릭 클래스 선언 및 생성

GenericSample.class

타입 변수

- 임의의 참조형 타입을 의미한다.

- 아무런 이름을 사용해도 컴파일에 문제가 없다.

- 여러 개의 타입 변수는 쉼표(,)로 구분하여 사용한다.

 

제네릭 타입의 권장 네이밍

- E : 요소

- K : 키

- N : 숫자

- T : 타입

- V : 값

- S, U, V : 두 번째, 세 번째, 네 번째에 선언된 타입

 

 

제네릭 클래스 사용법

GenericSample<String>  // 꺽세안에 사용할 타입을 넣어준다.

GenericSample class는 String 타입으로 정의된 것과 같으므로 다른 타입을 넣으면 컴파일 에러가 발생한다.

 

GenericMain.class
실행 결과

 

genericSample 객체에 int 타입을 넣으면 컴파일 에러가 발생한다.

추가적으로 genericSample2 객체와 같이 제네릭 클래스도 기존의 객체 생성 방식으로 꺽세 없이 사용 가능하다.

 

GenericMain.class

 

참조변수와 생성자에 대입된 타입은 일치해야 한다.

JDK 1.7부터 추정이 가능한 경우 생성자 타입 지정 생략 가능하다.

 

GenericMain.class

 

제네릭의 제한

- 제네릭 클래스의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다. 제네릭은 인스턴스별로 다르게 동작하려고 만들었기 때문이다.

- 모든 객체에 대해 동일하게 동작해야하는 static맴버에(class 변수) 타입 변수 T를 사용할 수 없다. 

- 제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.(new 연산자 때문)

 

 

제한된 제네릭 클래스 생성법

 

아래와 같이 Box를 상속받는 FruitBox 제네릭 클래스를 생성하였다.

 

Box.class

 

FruitBox.class

 

그런데 여기서 문제는 현재 과일 박스에 아무런 물건을 받을 수 있다. 장난감이라든지 과일이라든지 

GenericMain.class

이러한 문제를 상속을 이용해서 해결할 수 있다.

더 이상 과일 박스에 과일 외에 상품을 담을 수 없다.

FruitBox.class

 

GenericMain.class

 

제네릭 주요 개념 (바운디드 타입, 와일드 카드)

바운디드 타입

- 바운디드 타입은 특정 타입의 서브 타입으로 제한한다.

 

와일드 카드

제네릭으로 구현된 메소드의 경우 선언된 타입으로만 매개변수를 입력해야 한다.

이를 상속받은 클래스 혹은 부모클래스를 사용하고 싶어도 불가능하고 어떤 타입이 와도 상관없는 경우에 대응하기 좋지 않다.

이러한 문제를 해결하기 위해서 와일드 카드를 사용한다.

 

와일드 카드 종류

Unbounded WildCard

- Unbounded WildCard는 List<?> 와 같은 형태로 물음표만 가지고 정의되어지게 된다.

- Object 클래스에서 제공되는 기능을 사용하여 구현할 수 있는 메소드를 작성하는 경우

- 타입 파라미터에 의존적이지 않는 일반 클래스의 메소드를 사용하는 경우

 

Upper Bounded WildCard

- Upper Bounded WildCard는 List<? extends Foo>의 형태로 사용한다.

- 특정 클래스의 자식 클래스만 인자로 받는다는 의미이다.

- 임의 Foo 클래스를 상속받는 어느 클래스가 와도 되지만 사용할 수 있는 기능은 Foo 클래스에 정의된 기능만 사용할 수 있다.

 

Lower Bounded WildCard

- Lower Bounded WildCard는 List<? super Foo>의 형태로 사용한다.

- 특정 클래스의 부모 클래스만 인자로 받는다는 의미이다.

 

 

제네릭 메서드

메서드의 선언부에 제네릭 타입이 선언된 메서드를 제네릭 메서드라 한다.

타입 변수의 선언은 메서드 선언부에서 반환 타입 바로 앞에 위치한다.

클래스에서 지정한 제네릭유형과 별도로 메소드에서 독립적으로 제네릭 유형을 선언하여 쓸 수 있다.

public static <T> T getObject(Class<T> classType) {
}

위와 같은 방식은 정적 메소드로 선언할 때 필요하기 때문이다.

정적 메소드는 객체를 인스턴스로 생성해서 접근할 필요없이 메모리에 올라가 있기 때문에 인스턴스 생성없이 바로 사용할 수 있다.

이런 정적 메소드가 타입을 얻어 오기 위해서는 제네릭 클래스와 별도로 독립적인 제네릭이 사용되어야 하는 이유이다. 

 

Erasure

제네릭의 타입 소거(Generics Type Erasure)

- Erasure란 원소 타입을 컴파일 타임에서만 검사를하고 런타임에는 해당 타입 정보를 알 수가 없다. 즉 컴파일 상태에만 제약 조건을 적용하고, 런타임에는 타입에 대한 정보를 소거하는 프로세스이다.

즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없는 것이다.

  • 이렇게 처리되는 이유는 지네릭이 도입되기 이전의 소스코드와의 호환성을 유지하기 위해서다.
  • JDK1.5 부터 지네릭스가 도입되었지만, 아직도 원시 타입을 사용해서 코드를 작성하는 것을 허용한다.
    • 언젠가 새로운 기능을 위해 하위 호환성을 포기하기 될 때가 올 것이다.

이렇게 하위호환성을 유지해야 함으로 인해 raw type 지원 + 지네릭을 구현할 때 소거(erasure) 방식을 이용하였다.

 

제네릭 primitive 타입 사용못하는 이유

제네릭에 대해 알아보면 다양한 코드를 접하고 작성해봤겠지만 특이한? 점이 있다.

바로 타입 파라미터에 primitive 타입을 사용하지 않는다는 것이다.

  • 왜일까?- 결론은 타입 소거(type Erasure) 때문이다.
  • 이해를 위한 List<Integer>를 정의해본다.List<Integer> list = new ArrayList<>();
    • 위 코드의 바이트코드를 보면 아래와 같다.... INVOKESPECIAL java/util/ArrayList. <init> ()V ...
    • 주목해야 할 점은 ArrayList가 생성될 때 타입정보가 없다는 것이다.
    • 제네릭을 사용하지 않고 Raw Type으로 ArrayList를 생성해도 똑같은 바이트코드를 볼 수 있다.
    • 그리고 내부에서 타입 파라미터를 사용할 경우 Object 타입으로 취급되어 처리한다.
    • 이것이 타입 소거 (type Erasure) 라고 한다.
    • 타입 소거는 제네릭 타입이 특정 타입으로 제한 되어 있을 경우 해당 타입에 맞춰 컴파일시 타입 변경이 발생하고 타입 제한이 없을 경우 Object 타입으로 변경된다.

→ 바로 하위 호환성을 지키기 위해 위와 같은 개념이 등장한 것이다.

→ 제네릭을 사용하더라도 하위 버전에서도 동일하게 동작해야 하기 때문이다.

자 그렇다면 알 수 있다 primitive 타입을 사용하지 못하는 것도 바로 이 기본 타입은 Object 클래스를 상속받고 있지 않기 떄문이다.

  • 그래서 기본 타입 자료형을 사용하기 위해서는 Wrapper 클래스를 사용해야 한다.
  • Wrapper 클래스를 사용할 경우 Boxing, Unboxing을 명시적으로 사용할 수도 있지만 암묵적으로 사용할 수 있으니 구현 자체에는 크게 신경쓸 부분이 없다.

 

 

 

참조

www.notion.so/4735e9a564e64bceb26a1e5d1c261a3d

 

제네릭

WhiteShip Java Study 시즌 1

www.notion.so

 

728x90
반응형
반응형

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기

스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

스트림

  • FIFO(First In First Out)
  • 단방향 이기 때문에 입력 스트림과 출력 스트림을 별도로 사용해야 한다.
  • 연속된 데이터의 흐름으로 입출력 진행시 다른 작업을 할 수 없는 블로킹(Blocking) 상태가 된다.
  • 입출력 대상을 변경하기 편하며 동일한 프로그램 구조를 유지할 수 있다.

스트림 종류

  • 바이트 스트림(Byte Stream)
    • 데이터를 Byte 단위로 주고 받는 것을 의미
    • binary 데이터를 입출력하는 스트림
    • 이미지, 동영상등 모든 종류의 데이터들을 송수신할 떄 주로 사용
    • 대표적인 바이트 스트림에는 데이터 입력의 InputStream과 데이터 출력의 OutputStream이 있고 이 두 추상 클래스는 byte기반 stream의 최고 조상이다
  • 문자 스트림 (Character Stream)
    • 문자 단위로 인코딩 처리를 하는 스트림
    • 텍스트 파일등을 송수신할 떄 주로 사용

버퍼 

  • byte, char, int 등 기본 데이터 타입을 저장할 수 있는 저장소로서, 배열과 마찬가지로 제한된 크기(capacity) 에 순서대로 데이터를 저장한다.
  • 버퍼는 데이터를 저장하기 위한 것이지만, 실제로 버퍼가 사용되는 것는 채널을 통해서 데이터를 주고 받을 때 쓰인다.
  • 채널을 통해서 소켓, 파일 등에 데이터를 전송할 때나 읽어올 때 버퍼를 사용하게 됨으로써 가비지량을 최소화 시킬 수 있게 되며, 이는 가바지 콜렉션 회수를 줄임으로써 서버의 전체 처리량을 증가시켜준다.

채널

  • 데이터가 통과하는 쌍방향 통로이며, 채널에서 데이터를 주고 받을 때 사용 되는 것이 버퍼이다.
  • 채널에는 소켓과 연결된 SocketChannel, 파일과 연결된 FileChannel, 파이프와 연결된 Pipe.SinkChannel 과 Pipe.SourceChannel 등이 존재하며, 서버소켓과 연결된 ServerSocketChannel 도 존재한다.

자바 NIO(New I/O)

  • 자바 1.4 버전부터 추가된 API 로 넌블로킹(Non-blocking) 처리가 가능하며, 스트림이 아닌 채널(Channel)을 사용한다.

IO, NIO 비교

  • IO 의 방식으로 각각의 스트림에서 read() 와 write() 가 호출이 되면 데이터가 입력 되고, 데이터가 출력되기전까지, 스레드는 블로킹(멈춤) 상태가 된다. 이렇게 되면 작업이 끝날때까지 기다려야 하며, 그 이전에는 해당 IO 스레드는 사용할 수 없게 되고, 인터럽트도 할 수 없다. 블로킹을 빠져나오려면 스트림을 닫는 방법 밖에 없다.
  • NIO 의 블로킹 상태에서는 Interrupt 를 이용하여 빠져나올 수 있다.
    구분 IO NIO
    입출력 방식 스트림 채널
    버퍼 방식 Non-Buffer Buffer
    비동기 방식 지원 X O
    Blocking/Non-Blocking 방식 Blocking Only Both
    사용 케이스 연결 클라이언트가 적고, IO 가 큰 경우(대용량) 연결 클라이언트가 많고, IO 처리가 작은 경우(저용량)

InputStream과 OutputStream

InputStream

  • 바이트 기반 입력 스트림 최상위 클래스로 추상 클래스이다.
  • 모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어진다.
  • InputStream 클래스에는 바이트 기반 입력 스트림이 기본적으로 가져야 할 메소드들이 정의 되어 있다.
메소드 설명
int available() 현재 읽을 수 있는 바이트 수를 반환한다
void close() 현재 열려있는 InputStream을 닫는다
void mark(int readlimit) InputStream에서 현재의 위치를 표시해준다
boolean markSupported() 해당 InputStream에서 mark()로 지정된 지점이 있는지에 대한 여부를 확인한다
abstract int read() InputStream에서 한 바이트를 읽어서 int값으로 반환한다
int read(byte[] b) byte[] b 만큼의 데이터를 읽어서 b에 저장하고 읽은 바이트 수를 반환한다
int read(byte[] b, int off, int len) len만큼 읽어서 byte[] b의 off위치에 저장하고 읽은 바이트 수를 반환한다
void reset() mark()를 마지막으로 호출한 위치로 이동
long skip(long n) InputStream에서 n바이트만큼 데이터를 스킵하고 바이트 수를 반환한다

OutputStream

  • 바이트 기반 출력 스트림 최상위 클래스로 추상 클래스이다.
  • 모든 바이트 기반 출력 스트림은 이 클래스를 상속받아서 만들어진다.
  • OnputStream 클래스에는 바이트 기반 입력 스트림이 기본적으로 가져야 할 메소드들이 정의 되어 있다.
메소드 설명
void close() OutputStream을 닫는다
void flush() 버퍼에 남아있는 출력 스트림을 출력한다
void write(byte[] b) 버퍼의 내용을 출력한다
void write(byte[] b, int off, int len) b배열 안에 있는 시작 off부터 len만큼 출력한다
abstract void write(int b) 정수 b의 하위 1바이트를 출력한다

Byte 와 Character 스트림

Byte Stream

  • binary 데이터를 입출력하는 스트림
  • 데이터는 1바이트 단위로 처리
  • 이미지, 동영상 등을 송수신 할 때 주로 사용
  • 예) FileInputStream, FileOutputStream, ByteArrayInputStream, ByteArrayOutputStream

Character Stream

  • text 데이터를 입출력하는 스트림
  • 데이터는 2바이트 단위로 처리
  • 일반적인 텍스트 및 JSON, HTML 등을 송수신할 때 주로 사용
  • 예) FileReader, FileWriter, CharArrayReader, CharArrayWriter

보조 스트림

  • FilterInputStream 과 FilterOutputStream 을 상속받는 클래스들로 기본 스트림과 결합하여 특정 상황에서 보다 편리하게 사용할 수 있다.
  • BufferedInputStream/BufferedOutputStream: 버퍼를 사용해 입출력 효율과 편의를 위해 사용
  • BufferedReader/BufferedWriter: 라인단위의 입출력이 편리함
  • InputStreamReader/OutputStreamReader: 바이트 스트림을 문자 스트림처럼 쓸 수 있도록하며 문자 인코딩 변환을 지원
  • DataInputStream/DataOutputStream: 자바 원시자료형 데이터 처리에 적합

표준 스트림(System.in, System,out, System.err)

  • System.out 은 콘솔 화면에 문자열을 출력하기 위한 용도로 사용되는 출력 스트림이다.
  • System.in 은 키보드의 입력을 받아들이기 위해서 사용되는 입력 스트림이다.
  • System.err 는 버퍼링을 지원하지 않는다. 이것은 err 이 보다 정확하고 빠르게 출력되어야 하기 때문이라고 한다. 버퍼링을 하던 도중 프로그램이 멈추면 버퍼링된 내용은 출력되지 않기 때문이다.

파일 읽기

텍스트 파일 생성

testfile.txt 

 

보조 스트림을 이용하여 파일 읽기

 

 

파일 쓰기

보조 스트림을 이용하여 파일 쓰기

새로 생성된 testwrite 파일

 

텍스트 파일 내용

 

 

 

 

 

 

 

 

 

참조

bingbingpa.github.io/java/whiteship-live-study-week13/

 

자바 I/O

자바 I/O Feb 11, 2021  Java  whiteship/live-study  java  java I/O whiteship/live-study 13주차 정리 목표 학습할 것 (필수) 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O InputStream 과 OutputStream Byte 와 Character

bingbingpa.github.io

www.notion.so/I-O-af9b3036338c43a8bf9fa6a521cda242

 

I/O

목표

www.notion.so

 

728x90
반응형

+ Recent posts