아래를 만족하는 클래스를 선언하고 객체를 생성하라.
단 불변 객체가 되도록 하라.
그리고 반복문과 getter를 활용해 각 국가의 특성에 접근하라.
Country
name: String
capitalCity: String
population: int
language: String
isG2: boolean
package more.eg01_country;
import java.util.ArrayList;
import java.util.List;
class Country {
// 캡슐화: 밖에서 접근할 수 없음
private String name;
private String capitalCity;
private int population;
private String language;
private boolean isG2;
public Country(String name, String capitalCity, int population, String language, boolean isG2) {
this.name = name;
this.capitalCity = capitalCity;
this.population = population;
this.language = language;
this.isG2 = isG2;
}
public String getName() {
return name;
}
public String getCapitalCity() {
return capitalCity;
}
public int getPopulation() {
return population;
}
public String getLanguage() {
return language;
}
public boolean getIsG2() {
return isG2;
}
}
public class Main {
public static void main(String[] args) {
// 업케스팅 (부모타입으로 자식의 인스턴스 생성)
List<Country> countries = new ArrayList<>();
// 배열과 리스트의 차이점: 배열은 정적인 자료형, 한 번 생성하면 크기를 바꿀 수 없음
// 리스트는 동적인 자료형, 크기 조절 가능
// 리스트에 국가 추가
countries.add(new Country("한국", "서울", 53_000_000, "한국어", false));
countries.add(new Country("미국", "워싱턴 D.C", 350_000_000, "영어", true));
countries.add(new Country("일본", "도쿄", 130_000_000, "일본어", false));
// getter를 통해 속성에 접근
for (Country country : countries) {
System.out.println(country.getName());
System.out.println(country.getCapitalCity());
System.out.println(country.getPopulation());
System.out.println(country.getLanguage());
System.out.println(country.getIsG2());
System.out.println();
}
}
}
이번 예제에서는 국가 정보를 객체로 관리하고 여러 국가를 List에 저장하여 반복문으로 출력하는 방법을 학습하였다.
Country 클래스는 하나의 국가 정보를 표현하는 객체이다.
각 변수는 국가의 이름, 수도, 인구수, 언어, G2 국가 여부를 저장한다.
모든 필드는 praivate으로 선언하여 외부에서 직접 접근할 수 없도록 캡슐화를 적용했다.
객체 생성시 생성자를 통해 값을 초기화한다.
캡슐화된 데이터는 Getter 메서드를 통해 접근한다.
이를 통해 객체 내부 데이터를 안전하게 조회할 수 있다.
여러 국가를 저장하기 위해 List를 사용하였다.
배열과 달리 List는 크기를 자유롭게 늘리거나 줄일 수 있는 동적 자료구조이다.
저장된 국가 정보를 출려갛기 위해 향상된 for문을 사용하였다.
이는 List 안의 Country 객체를 하나씩 꺼내어 country 변수에 저장한 뒤 반복 수행하는 문법이다.
다음을 만족하는 클래스를 선언하고 인스턴스를 생성하라.
그리고 일반 반복문과 향상된 반복문을 통해 각 객체를 출력하라.
Person
firstName: String
lastName: String
age: int
toString(): String
package more.eg02_loopbjects;
import java.util.ArrayList;
import java.util.List;
class Person {
private String firstName;
private String lastName;
private int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
@Override
public String toString() {
return String.format("Person{firstName=%s, lastName=%s, age=%s}", firstName, lastName, age);
}
}
public class Main {
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();
persons.add(new Person("John", "Doe", 30));
persons.add(new Person("Jane", "Doe", 25));
persons.add(new Person("Mary", "Doe", 40));
// 순회용 + 반복적인 작업, 산술 계산 등 여러가지 용도
System.out.println("일반 for문으로 출력:");
for (int i = 0; i < persons.size(); i++) {
System.out.println(persons.get(i));
}
// 순회에 특화된 반복문
System.out.println("향상된 for문으로 출력:");
for (Person person : persons) { // for (각 요소: 반복 가능한 객체)
// 반복 가능한 객체(Iterable): 배열 + 컬렉션 + 기타 반복할 수 있는 객체들...
System.out.println(person);
}
}
}
이번 예제에서는 Person 객체를 생성한 뒤 List에 저장하고 일반 for문과 향상된 for문을 이용해 객체를 순회하는 방법을 학습하였다.
Person 클래스는 사람 한 명의 정보를 저장하는 객체이다.
각 필드는 이름, 성, 나이를 저장한다.
생성자를 이용해 객체를 생성할 때 필요한 값을 초기화한다.
그 다음 객체를 출력할 때 사람이 읽기 쉬운 형태로 표시하기 위해 toString() 메서드를 재정의하였다.
만약 오버라이딩하지 않으면 Person@6d06d69c 이런 형태로 출력된다.
하지만 오버라이딩 후에는 객체의 실제 정보를 확인할 수 있다.
Person 객체를 저장할 수 있는 리스트를 생성하고 여러개의 Person 객체를 리스트에 추가하였다.
일반 for문으로는 인덱스를 이용해 리스트의 요소를 하나씩 가져온다.
인덱스를 사용할 수 있고 특정 위치의 데이터를 조회할 수 있다.
향상된 for문은 반복 가능한 객체의 요소를 하나씩 꺼내어 사용할 때 편리하다.
코드가 간결하고 인덱스를 직접 사용할 필요가 없다.
컬렉션 순회에 특화되어있다.
다음을 참고해 Person이 가솔린차와 전기차 모두에 의존할 수 있도록 클래스를 선언하라.

package more.eg03_dip;
// 인터페이스
interface Car {
void drive();
}
// 구현 클래스 1
class GasolineCar implements Car {
public void drive() {}
}
// 구현 클래스 2
class Ev implements Car {
public void drive() {}
}
class Person {
String name;
// Car에 의존한다: 가솔린차, 전기차 모두 탈 수 있다
Car car; // DIP: 클래스는 구체적인 클래스보다 인터페이스에 의존해야 함
// DIP(Dependency Inversion Principle) 의존 역전 원칙
// 추상화, 다형성이 가능하도록 환경을 만들어준다
Person(String name, Car car) {
this.name = name;
this.car = car;
}
}
public class Main {
public static void main(String[] args) {
Car avante = new GasolineCar();
// 존은 가솔린차를 타고 싶고
Person jonh = new Person("John Doe", avante);
Car ev9 = new Ev();
// 제인은 전기차를 타고 싶고
Person jane = new Person("Jand Doe", ev9);
}
}
이번 예제에서는 인터페이스와 DIP를 활용해 객체 간의 결합도를 낮추는 방법을 학습했다.
Car 인터페이스는 자동차가 반드시 가져야하는 drive() 기능을 정의한다.
실제 자동차가 가솔린 차량인지 전기차인지는 중요하지 않고 운전할 수 있는 자동차라는 공통 규칙만 정의한다.
두 클래스 모두 Car 인터페이스를 구현한다.
즉 가솔린차와 전기차는 서로 다른 객체이지만 모두 Car 타입으로 사용할 수 있다.
예를들어 CasolineCar car; 처럼 작성했다면 가솔린차만 사용할 수 있었을 것이다.
하지만 Car car; 로 선언함으로써 가솔린차와 전기차를 모두 사용할 수 있게 되었다.
생성자 역시 Car 인터페이스를 받도록 설계하엿다.
John은 가솔린 차량을 사용하고 Jane은 전기차를 사용한다.
하지만 Person 클래스는 어떤 자동차가 전달되는지 신경쓰지 않는다.
단지 Car 인터페이스만 알고 있을 뿐이다.
만약 새로운 자동차가 추가된다면 class HybridCar implements Car만 만들면 된다.
Person 클래스는 수정할 필요가 없다.
Photo 클래스가 다음과 같을 때 사용자가 게시물에 '좋아요'를 누른 경우를 처리하는 로직을 단순화해 표현하라.
Photo
id: int
caption: String
likeCount: int
package more.eg03_likebutton;
import java.util.ArrayList;
import java.util.List;
class Photo {
private int id;
private String caption;
private int likeCount;
public Photo(int id, String caption, int likeCount) {
this.id = id;
this.caption = caption;
this.likeCount = likeCount;
}
public int getId() {
return id;
}
public String getCaption() {
return caption;
}
public int getLikeCount() {
return likeCount;
}
// 좋아요 수를 1 증가시키는 메서드
// setter 대신에 목적에 맞는 메서드를 만드는 것을 권장
public void addLikeCount() { // 메서드의 이름이 무엇을 하는지 표현하는게 좋음
likeCount++;
}
}
public class Main {
public static void main(String[] args) {
// 사용자가 새롭게 좋아요를 누른 게시물의 id
int newLikePhtoId = 2;
// 데이터베이스에 있는 전체 Photo 데이터들
List<Photo> photos = new ArrayList<>();
photos.add(new Photo(1, "photo1", 4));
photos.add(new Photo(2, "photo2", 8));
photos.add(new Photo(3, "photo3", 99));
// 사진 데이터들을 순회
for (Photo photo : photos) {
// 사용자가 좋아요를 누른 게시물을 찾은 다음
if (newLikePhtoId == photo.getId()) { // id 비교
photo.addLikeCount(); // 좋아요를 1 증가시킨다
}
}
System.out.println(photos.get(1).getLikeCount());
}
}
이번 예제에서는 게시물에 좋아요를 누르면 해당 게시물의 좋아요 수가 증가하는 기능을 객체 지향 방식으로 구현했다.
Photo 클래스는 하나의 게시물을 표현하는 객체이다.
각각 게시물 번호, 설명, 좋아요 수를 저장한다.
생성자를 통해 게시물을 생성할 수 있으며 Getter 메서드를 이용해 데이터를 조회할 수 있다.
좋아요 수를 1증가 시키는 메서드를 만들었다.
일반적으로 Setter를 사용해 값을 직접 변경하기보다 photo.addLikeCount();처럼 객체가 스스로 자신의 상태를 변경하도록 만드는 것이 객체지향적인 설계 방법이다.
여러개의 게시물을 저장하기 위해 List를 사용하였다.
총 3개의 게시물 데이터를 생성하였다.
사용자가 좋아요를 누른 게시물의 번호를 변수에 저장한다.
이후 반복문을 사용해 모든 게시물을 순회한다.
현재 게시물의 id와 사용자가 좋아요를 누른 게시물 id를 비교한다.
일치하는 게시물을 찾으면 photo.addLikeCount();를 실행해 좋아요 수를 증가시킨다.
2번 게시물의 좋아요 수를 출력한다.
초기값은 8이었지만 좋아요가 한 번 증가했기 때문에 9가 출력된다.
사용자와 게시물 간의 연관 관계를 코드로 표현해보세요.

package more.eg05_userpost;
class User { // 사용자
private int id;
private String username; // 사용자이름
public User(int id, String username) {
this.id = id;
this.username = username;
}
public int getId() {
return id;
}
public String getUsername() {
return username;
}
}
class Post { // 게시물
private int id;
private String content; // 내용
private User author; // 작성자
public Post(int id, String content, User author) {
this.id = id;
this.content = content;
this.author = author;
}
public int getId() {
return id;
}
public String getContent() {
return content;
}
public User getAuthor() {
return author;
}
}
public class Main {
public static void main(String[] args) {
User user = new User(1, "johndoe");
Post post = new Post(100, "자연사진", user);
// 100번 게시물을 작성자
System.out.println(post.getAuthor().getUsername());
}
}
이번 예제에서는 사용자(User)와 게시물 (Post)의 관계를 객체지향 방식으로 표현하였다.
User 클래스는 사용자 정보를 저장하는 객체이다.
각각 사용자 번호와 사용자 이름을 저장한다.
생성자를 통해 사용자 객체를 생성할 수 있다.
Post 클래스는 게시물 정보를 저장하는 객체이다.
특히 작성자를 문자열로 저장하지 않고 User 객체 자체로 저장한다.
이를 통해 게시물은 작성사에 대한 모든 정보를 참조할 수 있다.
게시물을 생성할 때 작성자 객체를 함께 전달한다.
즉 Post 객체 안에 User 객체를 참조하는 형태이다.
getAuthor() -> 작성자(User 객체)를 가져온다.
getUsername() -> User 객체의 이름을 가져온다.
그 결과 johndoe 가 출력된다.
소셜 미디어 앱에서 사용자의 프로필 페이지를 처리하는 DTO를 생성하라.
단 ProfilePost는 UserProfileDto의 내부 클래스이다
클래스: UserProfileDto 설명: 사용자의 프로필 정보
클래스: ProfilePost 설명: 프로필 페이지에 표시될 사용자의 게시물
package more.eg06_innerclassdive;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class Post {
private String photoUrl;
private String content;
private int likeCount;
private int authorId;
public Post(String photoUrl, String content, int likeCount, int authorId) {
this.photoUrl = photoUrl;
this.content = content;
this.likeCount = likeCount;
this.authorId = authorId;
}
public String getPhotoUrl() {
return photoUrl;
}
public String getContent() {
return content;
}
public int getLikeCount() {
return likeCount;
}
public int getAuthorId() {
return authorId;
}
}
// 사용자 프로필 DTO (사용자 정보, 사용자의 게시물 정보)
// 사용자의 게시물 정보는 이 DTO 안에 있는 형태
class UserProfileDto{
private String username;
private String bio;
private List<ProfilePost> posts;
public UserProfileDto(String username, String bio, List<ProfilePost> posts) {
this.username = username;
this.bio = bio;
this.posts = posts;
}
public static class ProfilePost{
private String photoUrl;
private int likeCount;
// 내부 클래스 (사용자 프로필에 표시할 게시물들)
public ProfilePost(String photoUrl, int likeCount) {
this.photoUrl = photoUrl;
this.likeCount = likeCount;
}
@Override
public String toString() {
return String.format("ProfilePost{photoUrl=%s, likeCount=%d}", photoUrl, likeCount);
}
}
public String getUsername() {
return username;
}
public String getBio() {
return bio;
}
public List<ProfilePost> getPosts() {
return posts;
}
}
public class Main {
public static void main(String[] args) {
// 사용자 john의 아이디가 1이라고 가정한다
// 데이터베이스로부터 존의 게시물을 가져온다
List<Post> johnPosts = new ArrayList<>();
johnPosts.add(new Post("/1.jpg", "content1", 99, 1));
johnPosts.add(new Post("/2.jpg", "content2", 99, 1));
johnPosts.add(new Post("/3.jpg", "content3", 99, 1));
// 존의 게시물로부터 존의 ProfilePost를 추출한다
List<UserProfileDto.ProfilePost> johnProfilePosts = johnPosts.stream()
// static 내부 클래스를 쓰는 이유: 외부 클래스의 인스턴스를 생성할 필요가 없다
// 즉 외부클래스와 독립적으로 인스턴스를 생성할 수 있음
.map(post -> new UserProfileDto.ProfilePost(post.getPhotoUrl(), post.getLikeCount()))
.collect(Collectors.toList());
// UserProfileDto 만드는 부분
UserProfileDto johnProfileDto = new UserProfileDto("johndoe", "hello", johnProfilePosts);
// 존의 프로필 페이지를 확인하기
System.out.println(johnProfileDto.getUsername());
System.out.println(johnProfileDto.getBio());
for (UserProfileDto.ProfilePost post: johnProfileDto.getPosts()) {
System.out.println(post);
}
}
}
이번 예제에서는 사용자 프로필 화면에 필요한 데이터를 DTO로 구성하고 내부 클래스를 활용해 프로필 게시물 정보를 관리하는 방법을 학습하였다.
Post 클래스는 데이터베이스에 저장된 게시물 정보를 표현한다.
각 게시물은 사진 경로, 내용, 좋아요 수, 작성자 정보를 가지고 있다.
예제에서는 데이터베이스 대신 List를 사용해 게시물 데이터를 생성하였다.
UserProfileDto는 사용자 프로필 화면에 필요한 데이터를 담기 위한 DTO 클래스이다.
프로팔 화면에는 사용자 이름, 소개글, 게시물 목록만 필요하므로 필요한 정보만 모아 관리한다.
DTO는 화면에 전달할 데이터를 묶어서 관리할 때 자주 사용된다.
ProfilePost는 프로필 화면에 표시할 게시물 정보를 담는 클래스이다.
프로필 화면에서는 게시물의 내용이나 작성자 정보가 필요하지 않기 때문에 사진 경로와 좋아요 수만 저장한다.
static 내부 클래스를 사용하는 이유는 외부 클래스의 객체를 생성하지 않아도 사용할 수 있기 때문이다.
즉 UserProfileDto와 관련은 있지만 독립적으로 생성 가능한 데이터 객체이다.
Stream을 통해 데이터 변환을 시켰다.
기존 Post 객체를 프로필 화면용 ProfilePost 객체로 변환한 것이다.
Post 객체들 -> ProfilePost 객체로 변환 -> List< ProfilePost > 생성
즉 데이터베이스의 원본 데이터를 화면에 필요한 형태로 가공하는 작업이다.
프로필 화면에 필요한 모든 정보를 하나의 객체로 묶는다.
UserProfileDto를 생성한 것이다.
그 다음 사용자 정보를 출력하고 for문을 이용해 게시물 목록을 순회한다.
이번 연습문제를 통해 객체지향 프로그래밍의 기본 개념들을 직접 코드로 구현해보며 학습할 수 있었다.
처음에는 클래스와 객체의 차이도 헷갈렸지만 Country, Person, User, Post 등의 객체를 직접 만들어보면서 객체가 데이터를 묶어서 관리하는 역할을 한다는 것을 이해할 수 있었다.
또한 List와 반복문을 사용하여 여러 객체를 관리하는 방법을 배웠고, Getter를 이용한 캡슐화와 toString() 오버라이딩을 통해 객체를 다루는 방법도 익힐 수 있었다.
DIP 예제를 통해 인터페이스와 다형성의 개념을 처음 접했으며, 클래스가 구체적인 구현보다 추상화에 의존해야 한다는 이유도 이해할 수 있었다. 좋아요 기능과 게시물 작성자 예제를 구현하면서는 객체 간의 관계를 표현하는 방법을 배웠다.
마지막으로 DTO와 내부 클래스를 활용한 예제를 통해 실제 서비스에서 데이터를 화면에 맞게 가공하는 과정도 경험할 수 있었다. 특히 Stream API를 이용한 객체 변환은 처음 접한 개념이었지만 객체를 다른 형태의 객체로 변환하는 흐름을 이해하는 데 도움이 되었다.
아직 부족한 부분도 많지만 이번 연습을 통해 객체지향 프로그래밍의 기초를 조금씩 이해하게 되었고, 앞으로 더 복잡한 프로젝트를 만들 수 있는 기반을 쌓을 수 있었다.