딸기말차

[Java] 4. ArrayList, 접근 제어자, 다중 클래스 본문

Bootcamp/Java

[Java] 4. ArrayList, 접근 제어자, 다중 클래스

딸기말차 2023. 6. 28. 00:10

엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)

 

백엔드 개발 부트캠프

백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.

playdata.io


1. ArrayList

ArrayList와 배열은 거의 동일한 기능을 가지고 있지만, 가장 큰 차이라고 볼 수있는 점은 ArrayList는 리스트의 길이를 자유자재로 늘리거나 줄일 수 있다는 점이다.

배열은 선언 시에 배열의 크기를 정해줘야 하는 점 때문에 용량이 수시로 변하는 데이터를 다루기엔 부적합한데, 그러한 단점을 보완, 사용할 수있다는 점이 정말 큰 장점인 것 같다.

 * Java Collection Framework의 종류 : List, Map, Set
 * 1. List : index가 존재하기 때문에 순서가 존재한다.
 * 2. Map : index가 없기 때문에 순서가 없다. (key : value)
 * 3. Set : 데이터만 존재한다.
 * 
 * ArrayList : java.util 패키지 안에 ArrayList.class로 존재
 * 현재 패키지에 없는 다른 클래스를 사용할 경우, import를 이용하여 프로젝트 내부로 읽어들여야한다.
 * import : ctrl + shift + o -> import 구문은 package 아래에 위치한다.
 * 
 * ArrayList 사용방법
 * 1. ArrayList<데이터타입> 변수명 = new ArrayList<데이터타입>();
 * 2. 데이터 추가 방법 : 변수명.add(데이터);
 * 3. 데이터 추출 방법 : 변수명.get(index);
 * 4. 데이터 삭제 방법 : 변수명.remove(index); -> 해당 데이터를 지우면서 반환
 * 5. 모든 데이터 삭제 : 변수명.clear();
 * 6. 데이터 개수 확인 : 변수명.size(); -> java 1.8버전에선 size(), length() 둘다 사용 가능
 * 
 * ArrayList는 특정 데이터타입을 지정하지 않으면 추가되는 데이터들을 Object로 감싸서 추가한다.
 * 따라서 다양한 데이터타입들을 포함할 수도 있다.
 * ArrayList는 주로 객체를 저장할 때 사용한다.

ArrayList는 Java의 Collection에 구현되어있는 클래스이다. 때문에 사용할 수 있는 메서드가 존재하고, 그에 대한 실습을 진행하였다.

ArrayList<NewsClass> al = new ArrayList<NewsClass>();

NewsClass a = new NewsClass();
al.add(a);
al.add(a);	
al.add(a);	
al.add(a);	
al.add(a);
System.out.println(al.size());

NewsClass b = al.remove(0);
System.out.println(al.size());

al.set(1, b); // 변경 : 특정 위치(index)의 데이터를 새로운 데이터로 변경

NewsClass c = al.get(3); // 특정 index의 데이터를 가져옴

al.clear();
System.out.println(al.size());

TestClass testClass = new TestClass();
System.out.println(testClass.a); // default
System.out.println(testClass.b); // public
System.out.println(testClass.getName()); // private

2.  접근 제어자

Java에서 제공하는 접근 제어자에 관한 실습을 진행하였다. 접근 제어자는 언뜻보기에 이게 그렇게 중요한가? 싶지만 정보를 은닉하거나, 캡슐화를 하거나, 추후에 배울 싱글톤 패턴을 구현하는데 굉장히 중요한 역할을 하기 때문에 반드시 개념을 알고 있어야 한다.

 * 접근제어자
 * 1. public : 언제 어디서든 접근 가능
 * 2. protected : 상속까지만 허용
 * 3. default : 아무것도 없는 경우 (동일 패키지 내에서만 접근 가능)
 * 4. private : 해당 클래스 내에서만 접근 가능, 정보에 대한 은닉성을 주입하기 위해 사용
 * 
 * 접근제어자 사용 위치
 * 1. 클래스 선언 시 (단, private는 사용 x)
 * 2. 생성자 선언 시
 * 3. 메서드 선언 시
 * 4. 변수 선언 시
 * 
 * 클래스 및 생성자 쪽에는 웬만하면 private를 붙이지 않는다.
 * private를 붙이면 다른 클래스에서 접근할 수 없기 때문에, 클래스를 만드는 의미가 없어지기 때문이다.
 * 하지만 생성자에 private를 붙이는 경우가 있는데, 디자인 패턴 중 싱글톤 패턴을 구현할 때 사용한다.
 * 변수에 private를 사용하는 경우, 외부에서 해당 변수의 값을 함부로 변경 못하도록 은닉 시킬 때 사용한다.
 * 
 * 디자인 패턴 : 기존 개발자들이 코딩할 때 최적화 될 수 있도록 명시해 놓은 코딩 구조

은닉을 위해 private을 사용하는 경우, Java에서 지원하는 getter와 setter 메서드를 사용하여 접근해야한다. 굳이 이런 방식을 사용하는 이유는, 타 사용자 혹은 개발자가 클래스 내부 변수에 바로 접근하여 데이터 값을 바꿔버리는 일이 생기면 프로그램에 큰 문제가 발생할 수 있기 때문이다.

public class TestClass {
	int a = 10; // default 이기 때문에 동일한 패키지에서만 접근 가능
	private String name = "sss"; // private 이기 때문에 외부에서 접근 불가능
	public boolean b = true; // public 이기 때문에 누구나 접근 가능

	public TestClass() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
TestClass testClass = new TestClass();
System.out.println(testClass.a); // default
System.out.println(testClass.b); // public
System.out.println(testClass.getName()); // private

3.  다중 클래스 실습_3) 뉴스 데이터로 URL 추출 및 중복제거

우선 ArrayList를 통해 데이터를 저장하고, Scanner를 통해 신문사 이름을 입력해 해당 신문사에 해당하는 뉴스의 URL을 추출하는 실습을 진행하였다.

실습에 앞서 어제와 마찬가지로 NewsClass, DataClass, MethodClass, MainClass, PrintClass를 구현하고 진행하였다.

public class NewsClass {
	String press = null; // 신문사
	String image = null; // 대표이미지
	String url = null; // 링크주소
	String category = null; // 종류
	
	public NewsClass() {
		
	}
	
	public NewsClass(String press, String image, String url, String category) {
		this.press = press;
		this.image = image;
		this.url = url;
		this.category = category;
	}
}

만들어 둔 NewsClass를 토대로, NewsDataClass에 ArrayList를 만들어 데이터를 저장하였다.

public class NewsDataClass {
	ArrayList<NewsClass> news = new ArrayList<NewsClass>();
	
	public NewsDataClass() { // 기본 생성자
		this.initNews();
	}

	private void initNews() {
		news.add(new NewsClass("경향신문", "nsd16500796.gif", "media.naver.com/press/032",  "종합지"));
		news.add(new NewsClass("국민일보", "nsd113224489.gif", "media.naver.com/press/005",  "종합지"));
		news.add(new NewsClass("동아일보", "nsd162737318.gif", "media.naver.com/press/020",  "종합지"));
		news.add(new NewsClass("서울신문", "nsd165611664.gif", "media.naver.com/press/081",  "종합지"));	
...

이 후 저장한 데이터에서 뉴스의 url을 추출하기 위해, NewsMethodClass에 메서드를 만들었다. 이 때 Main에서 Scanner를 통해 입력받은 신문사의 url을 추출해야 하기 때문에 메서드의 인자를 데이터들과 입력데이터 총 2개로 설정해 구현하였다.

public static ArrayList<String> searchNewsURL(ArrayList<NewsClass> news, String press) {
    ArrayList<String> urls = new ArrayList<String>();
    for (int i = 0; i < news.size(); i++) {
        if (news.get(i).press.contains(press))
            urls.add(news.get(i).url);
    }
    return urls;
}

구현한 메서드를 Main에서 호출하고, PrintClass에 구현한 출력문까지 같이 호출하여, url을 추출하였다.

NewsDataClass newsDataClass = new NewsDataClass();

// 콘솔에서 입력 받기
Scanner scan = new Scanner(System.in);
System.out.print("검색할 신문사 입력: ");
String press = scan.next();
NewsPrintClass.printURL(press, NewsMethodClass.searchNewsURL(newsDataClass.news, press));
public static void printURL(String press, ArrayList<String> url) {
    for (String s : url)
        System.out.println(press + "의 링크주소: https://" + s);
}

두번째로, DataClass 내 데이터들의 중복 된 category를 제거하는 실습을 진행하였다.

이 문제를 해결하기 위해 MethodClass에 2가지 방법을 사용해 구현해 보았는데, 우선 list를 활용해 중복을 제거해보았고, 다음으로 중복을 허용하지 않는 hash를 사용하여 해결해 보았다.

// 1. list를 활용한 중복제거
public static ArrayList<String> removeDuplicateCategory(ArrayList<NewsClass> news) {
    ArrayList<String> categories = new ArrayList<String>();
    for (NewsClass n : news) {
        if (!categories.contains(n.category))
            categories.add(n.category);
    }
    return categories;
}

hash는 중복을 허용하지 않을 뿐더러, 시간복잡도가 O^n에 해당하는 굉장히 빠른 자료구조이다. 이 때문에 검색 등에 특화되어있고, 기능을 구현하는데 자주 사용하기 때문에 반드시 알아둬야한다.

// 2. hash를 활용한 중복제거
public static ArrayList<String> removeDuplicateCategory2(ArrayList<NewsClass> news) {
    HashSet<String> hashSet = new HashSet<String>();
    for (NewsClass n : news)
        hashSet.add(n.category);
    return new ArrayList<String>(hashSet);
}

이렇게 구현한 메서드를 Main에서 실행하고, PrintClass를 통해 중복이 제거 된 category가 출력되는 것을 확인하였다.

NewsDataClass newsDataClass = new NewsDataClass();
NewsPrintClass.printCategory(NewsMethodClass.removeDuplicateCategory(newsDataClass.news));
public static void printCategory(ArrayList<String> categories) {
    System.out.println("-----중복 제거 된 카테고리-----");
    for (String s : categories)
        System.out.println(s);
}

4.  다중 클래스 실습_4) 뉴스 데이터로 URL 추출 및 중복제거

두번째 실습으로 cafe 데이터를 받아, 해당 날짜에 가장 많이 팔린 상품명을 추출하는 실습을 진행하였다.

이 실습을 진행하다보니 가장 많이 팔린 상품을 구하기 위해선 각 상품별로 얼마나 팔렸는지를 알아야 했고, 이 부분을 따로 구현해 이용하면 좋을 것 같다는 생각이 들었다.

때문에 메서드를 분리하여 각 상품별로 얼마나 팔렸는지를 먼저 구현하고, 그 결과를 토대로 가장 많이 상품명을 구하는 로직으로 MethodClass를 구현해 보았다.

public class CafeSalesClass {
	private String order_id = null;
	private String order_date = null;
	private String category = null;
	private String item = null;
	private int price = 0;
	
	public CafeSalesClass() {

	}
	
	public CafeSalesClass(String order_id, String order_date, String category, String item, int price) {
		this.setOrder_id(order_id);
		this.setOrder_date(order_date);
		this.setCategory(category);
		this.setItem(item);
		this.setPrice(price);
	}

	public String getOrder_id() {
		return order_id;
	}

	public String getOrder_date() {
		return order_date;
	}

	public String getCategory() {
		return category;
	}

	public String getItem() {
		return item;
	}

	public int getPrice() {
		return price;
	}
}

해당 클래스를 통해 DataClass에 cafe 데이터를 저장하였다.

public class CafeSalesDataClass {
	ArrayList<CafeSalesClass> cafeSales = new ArrayList<CafeSalesClass>();
	
	public CafeSalesDataClass() {
		this.cafeInit();
	}
	
	private void cafeInit() {
		cafeSales.add(new CafeSalesClass("C38167668", "2017-09-13 10:15", "Coffee", "카라멜마끼아또", 5000));
		cafeSales.add(new CafeSalesClass("C89217297", "2017-09-13 10:20", "Latte", "홍차라떼", 5000));
		cafeSales.add(new CafeSalesClass("C39178816", "2017-09-13 10:40", "Latte", "초코라떼", 5000));
...

구현한 데이터들을 토대로 MethodClass에 로직을 구현하였다. 위 실습과 마찬가지로 중복을 제거하기 위해 hash를 사용했는데, 차이점이 있다면 위에선 저장할 데이터가 한 종류였기 때문에 set을 사용했지만 현재 실습에선 중복 된 상품명을 제거하며 해당 상품이 몇개나 팔렸는지 까지 count하여 저장해야 하기 때문에, map을 사용해 구현하였다.

 

우선, 첫 번째 메서드인 todaySales에서는 hash를 통해 각 상품들의 중복을 제거하고, 중복 된 상품이 있다면 팔린 개수를 하나씩 늘려 저장하였다.

hash.putIfAbsent : hash 내에 해당 데이터가 없으면 key값과 초기값을 넣어주는 메서드이다.

hash.computeIfPresent : hash 내에 해당 데이터가 존재하면 해당 key값의 value를 변화시킬 수 있는 메서드이다.

위 코드에선 람다식을 사용해 이미 존재하는 데이터라면 value의 값을 1씩 증가시키게 구현하였다.

public static Map<String, Integer> todaySales(ArrayList<CafeSalesClass> cafeSales) {
    Map<String, Integer> hash = new HashMap<String, Integer>();

    for (CafeSalesClass cafe : cafeSales) {
        hash.putIfAbsent(cafe.getItem(), 1);
        hash.computeIfPresent(cafe.getItem(), (k, v) -> v + 1);
    }
    return hash;
}

두 번째 메서드인 bestSales에서는 가장 많이 팔린 상품이 여러개 일 수 있기 때문에 return 값은 list로 설정한 뒤 Collections에 존재하는 최대값을 구하는 메서드인 max를 이용해 value들 중 가장 큰 값을 구하고, 그에 해당하는 key값을 검색해 list에 추가해 return하는 방식으로 구현하였다.

public static ArrayList<String> bestSales(ArrayList<CafeSalesClass> cafeSales) {
    ArrayList<String> bestitems = new ArrayList<String>();
    Map<String, Integer> hash = todaySales(cafeSales);

    int max = Collections.max(hash.values());
    for (String s : hash.keySet()) {
        if (hash.get(s).equals(max))
            bestitems.add(s);
    }
    return bestitems;
}

이렇게 구현한 메서드를 Main에서 호출하여, PrintClass에 구현한 출력 메서드를 통해 결과를 확인하였다.

CafeSalesDataClass cafeSalesData = new CafeSalesDataClass();
CafeSalesPrintClass.prnTodaySales(CafeSalesMethodClass.todaySales(cafeSalesData.cafeSales));
CafeSalesPrintClass.prnBestSales(CafeSalesMethodClass.bestSales(cafeSalesData.cafeSales));
CafeSalesPrintClass.prnBestSales(CafeSalesMethodClass.bestSales2(cafeSalesData.cafeSales));
public static void prnTodaySales(Map<String, Integer> items) {
    System.out.println("----- 오늘 판매 된 상품들 -----");
    for (Map.Entry<String, Integer> entry : items.entrySet())
        System.out.println("상품명: " + entry.getKey() + ", 갯수: " + entry.getValue());
}

public static void prnBestSales(ArrayList<String> bestitems) {
    System.out.println("----- 2017-09-13에 가장 많이 판매된 상품명 -----");
    for (String s : bestitems)
        System.out.println(s);
}

위 Main 의 코드를 보면 bestSales2 라 되어있는 메서드도 존재한다. 해당 메서드는 hash를 사용하지 않고, list만 사용해서 중복제거 및 가장 많이 팔린 상품을 추출하는 메서드이다. 이를 통해 hash를 사용한 메서드와 비교해보며, list만 사용해서는 다양한 로직을 구현하는데 불편함이 많이 따른다는 것을 깨닫게 되었다.

public static ArrayList<String> bestSales2(ArrayList<CafeSalesClass> cafeSales) {
    ArrayList<String> menu = new ArrayList<String>(); // 중복 제거한 메뉴
    ArrayList<Integer> sales = new ArrayList<Integer>(); // 메뉴 당 팔린 갯수
    ArrayList<String> result = new ArrayList<String>(); // 가장 많이 팔린 메뉴들

    for (CafeSalesClass cafe: cafeSales) {
        if (!menu.contains(cafe.getItem()))
            menu.add(cafe.getItem());
    }

    for (String s: menu) {
        int cnt = 0;
        for (CafeSalesClass cafe: cafeSales) {
            if (cafe.getItem().equals(s)) {
                cnt++;
            }
        }
        sales.add(cnt);
    }

    int max = Collections.max(sales);
//		int max = 0;
//		for (int i = 0; i < sales.size(); i++) {
//			if (sales.get(i) > max) {
//				max = sales.get(i);
//			}
//		}

    for (int i = 0; i < sales.size(); i++) {
        if (sales.get(i) == max)
            result.add(menu.get(i));
    }
    return result;
}

5. 4일차 후기

다중 클래스를 계속 실습하며, 확실히 여러 클래스 사이의 데이터 이동에 익숙해지는 것을 느꼈다. 그리고 여러 메서드를 구현해보면서 Collections에 있는 클래스를 적절히 사용해 구현을 하면 로직도 간단해지고, 코드도 간결해지고, 프로그램의 속도 또한 올라간다는 것을 확인하였다.

 

이를 통해 앞으로 실습을 할 때는 Collections에 구현되어있는 다양한 자료구조 클래스를 적극적으로 사용하며 구현하는 연습을 하여 개발에 익숙해지고, 나아가 좋은 코드를 짤 수있는 개발자가 되고싶다는 생각이 들었다.