20200223のJavaに関する記事は7件です。

Minecraft Forge 1.15.2 で Mod にオリジナル武器を追加する

概要

  • Minecraft Forge を使用して Mod にオリジナルの武器を追加する
  • 今回は剣タイプ (SwordItem クラスのインスタンス) の武器を3つ追加した

環境

  • Minecraft Forge 1.15.2-31.1.0
  • ビルド環境: macOS Catalina + Java 8 (AdoptOpenJDK) + Gradle

ソースコード

ファイル一覧

├── build.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── bukimod
        │               ├── BukiItemTier.java
        │               ├── BukiMod.java
        │               └── BukiModRegister.java
        └── resources
            ├── META-INF
            │   └── mods.toml
            ├── assets
            │   └── bukimod
            │       ├── lang
            │       │   ├── en_us.json
            │       │   └── ja_jp.json
            │       ├── models
            │       │   └── item
            │       │       ├── maigo_ita.json
            │       │       └── murasama_blade.json
            │       └── textures
            │           └── items
            │               ├── maigo_icon.png
            │               └── murasama_blade.png
            └── pack.mcmeta

今回は build.gradle, pack.mcmeta, mods.toml には必要最低限の記述程度のみしている。
参考: Minecraft Mod 開発環境構築 (IntelliJ IDEA + Minecraft Forge 1.15.2) + Hello World Mod 作成 - Qiita

src/main/java/com/example/bukimod/BukiMod.java

MOD メインクラス。@Mod アノテーションを付けて MOD であることを示す。
今回は特に処理をせず MOD 内で使用する値を定義している。

package com.example.bukimod;

import net.minecraftforge.fml.common.Mod;

/**
 * Buki Mod.
 */
@Mod(BukiMod.MOD_ID)
public class BukiMod {

  /** MODのID */
  public static final String MOD_ID = "bukimod";

  /** ムラサマ・ブレードのID (アイテムを一意に識別するために必要) */
  public static final String ITEM_ID_MURASAMA_BLADE = "murasama_blade";

  /** 迷子板のID (アイテムを一意に識別するために必要) */
  public static final String ITEM_ID_MAIGO_ITA = "maigo_ita";

  /** ひのきのぼうのID (アイテムを一意に識別するために必要) */
  public static final String ITEM_ID_HINOKINO_BOU = "hinokino_bou";
}

src/main/java/com/example/bukimod/BukiItemTier.java

IItemTier インターフェースを実装したクラスまたは列挙型を用意する。今回は列挙型で実装した。

IItemTier は以前のバージョンでツール・マテリアル (Item.ToolMaterial クラス) と呼ばれていたものの後継であるようだ。

IItemTier インターフェースの各メソッドでは耐久度や攻撃力を返すようにしている。

IItemTier#getAttackDamage で返す値がそのまま攻撃力になるのではなく、武器のオブジェクトを生成する際にも別途攻撃力を指定しており、最終的な攻撃力はそれらの複数の値から算出されている。

package com.example.bukimod;

import net.minecraft.item.IItemTier;
import net.minecraft.item.Items;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.tags.ItemTags;
import net.minecraft.util.LazyValue;

import java.util.function.Supplier;

/**
 * IItemTier インターフェースの実装列挙型。
 */
public enum BukiItemTier implements IItemTier {

  /**
   * 鋼。
   * harvestLevel: 2
   * maxUses: 9999
   * efficiency: 6.0F
   * attackDamage: 2.0F
   * enchantability: 14
   * repairMaterial: Ingredient.fromItems(Items.IRON_INGOT)
   */
  HAGENE(2, 9999, 6.0F, 2.0F, 14, () -> {
    return Ingredient.fromItems(Items.IRON_INGOT);
  }),

  /**
   * ベニヤ。
   */
  VENEER(1, 1, 1.0F, 1.0F, 1, () -> {
    return Ingredient.fromTag(ItemTags.PLANKS);
  });

  private final int harvestLevel;
  private final int maxUses;
  private final float efficiency;
  private final float attackDamage;
  private final int enchantability;
  private final LazyValue<Ingredient> repairMaterial;

  BukiItemTier(int harvestLevel, int maxUses, float efficiency, float attackDamage, int enchantability, Supplier<Ingredient> repairMaterial) {
    this.harvestLevel = harvestLevel;
    this.maxUses = maxUses;
    this.efficiency = efficiency;
    this.attackDamage = attackDamage;
    this.enchantability = enchantability;
    this.repairMaterial = new LazyValue<>(repairMaterial);
  }

  @Override
  public int getHarvestLevel() {
    return harvestLevel;
  }

  @Override
  public int getMaxUses() {
    return maxUses;
  }

  @Override
  public float getEfficiency() {
    return efficiency;
  }

  @Override
  public float getAttackDamage() {
    return attackDamage;
  }

  @Override
  public int getEnchantability() {
    return enchantability;
  }

  @Override
  public Ingredient getRepairMaterial() {
    return repairMaterial.getValue();
  }
}

src/main/java/com/example/bukimod/BukiModRegister.java

アイテム登録クラス。Minecraft Forge からアイテム登録のイベント発行時に呼び出される。

今回は3つのオリジナル武器を登録している。
3つとも剣タイプの武器で SwordItem クラスのインスタンスを使用している。
SwordItem クラスは以前のバージョンで存在していた ItemSword クラスの後継であるようだ。

package com.example.bukimod;

import net.minecraft.item.IItemTier;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemTier;
import net.minecraft.item.SwordItem;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.IForgeRegistry;

/**
 * アイテム登録クラス。
 */
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) // システム内部で MOD を導入する際のイベントを受け取るためのアノテーション
public class BukiModRegister {

  /**
   * システム内部でアイテム登録イベントが発生した時に呼び出されるメソッド。
   */
  @SubscribeEvent
  public static void onItemsRegistry(final RegistryEvent.Register<Item> event) {
    IForgeRegistry<Item> registry = event.getRegistry();
    registry.registerAll(
      createMurasamaBlade(), // ムラサマ・ブレードを登録する
      createMaigoIta(), // 迷子板を登録する
      createHinokinoBou()); // ひのきのぼうを登録する
  }

  /**
   * ムラサマ・ブレードを生成する。
   */
  private static Item createMurasamaBlade() {

    // ムラサマ・ブレードのオブジェクトを生成
    IItemTier tier = BukiItemTier.HAGENE; // 鋼
    int attackDamage = 9999; // 攻撃力
    float attackSpeed = 99.0f; // 攻撃速度
    Item.Properties builder = new Item.Properties().group(ItemGroup.COMBAT); // Combat 戦闘タブに置く
    Item murasamaBlade = new SwordItem(tier, attackDamage, attackSpeed, builder);

    // 一意になる名称をアイテムに設定
    String namespace = BukiMod.MOD_ID;
    String path = BukiMod.ITEM_ID_MURASAMA_BLADE;
    murasamaBlade.setRegistryName(new ResourceLocation(namespace, path));

    return murasamaBlade;
  }

  /**
   * 迷子板を生成する。
   */
  private static Item createMaigoIta() {

    // 迷子板のオブジェクトを生成
    IItemTier tier = BukiItemTier.VENEER; // ベニヤ
    int attackDamage = 1; // 攻撃力
    float attackSpeed = 1.0f; // 攻撃速度
    Item.Properties builder = new Item.Properties().group(ItemGroup.MISC); // Miscellaneous その他タブに置く
    Item maigoIta = new SwordItem(tier, attackDamage, attackSpeed, builder);

    // 一意になる名称をアイテムに設定
    String namespace = BukiMod.MOD_ID;
    String path = BukiMod.ITEM_ID_MAIGO_ITA;
    maigoIta.setRegistryName(new ResourceLocation(namespace, path));

    return maigoIta;
  }

  /**
   * ひのきのぼうを生成する。
   */
  private static Item createHinokinoBou() {

    // ひのきのぼうのオブジェクトを生成
    IItemTier tier = ItemTier.WOOD; // 木
    int attackDamage = 1; // 攻撃力
    float attackSpeed = 1.0f; // 攻撃速度
    Item.Properties builder = new Item.Properties().group(ItemGroup.MISC); // Miscellaneous その他タブに置く
    Item hinokinoBou = new SwordItem(tier, attackDamage, attackSpeed, builder);

    // 一意になる名称をアイテムに設定
    String namespace = BukiMod.MOD_ID;
    String path = BukiMod.ITEM_ID_HINOKINO_BOU;
    hinokinoBou.setRegistryName(new ResourceLocation(namespace, path));

    return hinokinoBou;
  }
}

武器の名前は有名RPGなどから借りてきた。

「村正」、「手裏剣」、「聖なる鎧」 全部手に入れた? 『ウィザードリィ』の楽しさはアイテム収集にもアリ! - AKIBA PC Hotline!

実際の村正は徳川家に祟る妖刀として知られているが、最初のAppleII版では「MURASAMA BLADE」となっていたのは、ファンの間では有名なトリビアである。

「ひのきのぼう」に込めたドラクエ精神 堀井雄二が語ったこだわり

今とは考えられない開発条件の中、最弱のアイテムだと「一目でわかる」名前が「ひのきのぼう」だったのです。

src/main/resources/assets/bukimod/lang/*.json

en_us.json にはアイテムの名称を記述する (英語環境用であり、言語用ファイルを定義していない環境用でもある)。
「item.MODのID.アイテムID: アイテム名称」のフォーマットとなる。

{
  "item.bukimod.murasama_blade":  "Murasama Blade",
  "item.bukimod.maigo_ita":  "Maigo Ita"
}

ja_jp.json にはアイテムの名称を記述する(日本語環境用)。

{
  "item.bukimod.murasama_blade":  "ムラサマ・ブレード",
  "item.bukimod.maigo_ita":  "迷子板"
}

src/main/resources/assets/bukimod/models/item/*.json

アイテムのモデル情報ファイル。アイテムの描画情報などを記述する。

murasama_blade.json で murasama_blade.png を使うように指定。

{
  "parent": "item/generated",
  "textures": {
    "layer0": "bukimod:items/murasama_blade"
  }
}

maigo_ita.json で maigo_icon.png を使うように指定。

{
  "parent": "item/generated",
  "textures": {
    "layer0": "bukimod:items/maigo_icon"
  }
}

モデル - Minecraft Wiki

layer#: アイテムのパスで指定する。parentで "item/generated" としたときのみ有効で、インベントリに表示するアイコンを指定する。通常は1つだけだがスポーンエッグのように複数のlayerを持つものもある。この数はアイテムごとに決まっている。

src/main/resources/assets/bukimod/textures/items/*.png

アイテムのテクスチャ画像。

murasama_blade.png 背景透過 PNG 画像。
2100 x 2100 ピクセル。こんなに大きい画像を使う必要はないはず。

murasama_blade.png

maigo_icon.png 背景透過していない PNG 画像。
500 x 500 ピクセル。こんなに大きい画像を使う必要はないはず。

maigo_icon.png

Minecraft Forge で動かす

クリエイティブモードのワールドで確認する。

ムラサマ・ブレード

ムラサマ・ブレードは背景透過PNG画像を使ったのでちゃんと剣らしいものになっている。

クリエイティブ・インベントリの戦闘(Combat)タブに追加されている。

mod_murasama_blade_1.png

mod_murasama_blade_2.png

mod_murasama_blade_3.png

迷子板

迷子板は背景透過していないPNG画像を使ったので板のような形状で描画されている。

mod_maigo_ita_1.png

mod_maigo_ita_2.png

mod_maigo_ita_3.png

ひのきのぼう

「ひのきのぼう」はモデルもテクスチャも用意しなかったため、名称や画像が自動で設定されたようだ。

mod_hinokino_bou_1.png

mod_hinokino_bou_2.png

参考資料

Minecraft の武器について

Minecraft Forge での開発について

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Swing, SWT, JavaFX

Gui環境ではどれがおすすめかな?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

階層構造をもつTODOリストの作り方

はじめに

以前、階層構造をもつTODOリストをWebアプリケーションとして実装したので、その知見を共有します。

実際に作成したTODOリスト(OSSとして公開)


機能一覧

  • メモの新規作成
  • メモの編集(編集前の内容がテキストボックスに入力されている)
  • メモの削除
  • メモの追加(メモAの下にメモBを紐付け)
  • メモを一つ上の階層に移動
  • メモの一括削除
  • 新規作成や編集時のテキストボックスを、ボックス外の任意の場所をクリックすることで非表示(UX)
  • MemoモードとTaskモードの切り替え
  • (Taskモードのみ) メモ横のチェックボックスにチェックを入れると、非同期で打ち消し線を表示

解説

技術スタックは、Docker Compose、Java、PostgreSQLがメインです。

内部では基本的にCRUDしか行っておらず、DB側で再帰的なデータを保存しています。

Docker Compose

portsを定義して、ホストとゲストのポートをマッピングしています。

サービス名tomcatpostgresでお互い接続は可能なはずですが、ipv4も固定していました(理由は失念してしまいました)。

version: "3.7"
services:
  tomcat:
# When building from source code, uncomment following build
# and remove local image. e.g.) docker rmi resotto/tomcat:1.0
#    build: ap/
    image: resotto/tomcat:1.0
    container_name: tomcat
    tty: true
    ports:
     - "8888:8080"
    networks:
      app_net:
        ipv4_address: 172.16.1.3

  postgres:
# When building from source code, uncomment following build
# and remove local image. e.g.) docker rmi resotto/postgres:1.0
#    build: db/
    image: resotto/postgres:1.0
    container_name: postgres
    tty: true
    networks:
      app_net:
        ipv4_address: 172.16.1.2

networks:
  app_net:
    ipam:
      driver: default
      config:
       - subnet: "172.16.1.0/24"

DB

使い捨てアプリのため、postgresサービスのDockerfileから呼び出すstartup.sh内にスキーマを定義していました。

要素テーブルとその関連テーブルの2つを定義しました。

要素テーブルは(恥ずかしいのですが)テーブル名が残念なのと、typeカラムは数字で種類の情報を保持しているため、アンチパターンです。

element_typesテーブルを定義し、そちらへの外部キー制約をつけるのが良いです。

関連テーブルは、要素テーブルの主キーidの親と子で複合主キーになっています。

#!/bin/bash
service postgresql-9.6 start
psql -U postgres -c "create role uranus superuser login"
createdb -U postgres -O uranus uranusdb
psql -U uranus uranusdb -c \
"create table mst_element ( \
  id serial PRIMARY KEY, \
  type integer, \
  title text, \
  is_checked boolean, \
  is_root boolean, \
  create_date date, \
  update_date date \
);"

psql -U uranus uranusdb -c \
"create table mst_relation ( \
  parent_id integer, \
  child_id integer, \
  PRIMARY KEY (parent_id, child_id) \
);"

/bin/bash

フロントエンド

(画面の実装は採用する技術次第ですが)再帰的に表示する部分を独立して実装しました。

<!-- index.jsp -->
<!DOCTYPE html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Uranus</title>
        <link rel="stylesheet" href="${f:url('/css/index.css')}">
        <noscript>
            <link rel="stylesheet" href="${f:url('/css/noscript.css')}">
        </noscript>
    </head>
    <body>
        <!-- header -->
        <h1>
            <c:choose>
                <c:when test="${mode eq 0}">
                    [ Memo ] /
                    <s:link href="task">Task</s:link>
                </c:when>
                <c:otherwise>
                    <s:link href="memo">Memo</s:link>
                    / [ Task ]
                </c:otherwise>
            </c:choose>
        </h1>
        <p>
            <!-- create button -->
            <input type="button" onclick="toggleCreateMode()" value="new" class="btn createButton">
            <!-- clear section -->
            <div id="clearButton">
                <s:form>
                    <input type="submit" name="clear" value="clear" class="btn">
                    <input type="hidden" name="mode" value="${mode}">
                </s:form>
            </div>
        </p>
        <!-- create block -->
        <div id="createBlock">
            <s:form>
                <textarea name="inputText" rows="3" cols="30"></textarea>
                <input type="submit" name="create" value="create" class="btn">
                <input type="hidden" name="mode" value="${mode}">
            </s:form>
        </div>
        <!-- main contents -->
        <ul>
            <c:forEach var="elm" items="${list}" varStatus="parentStatus">
                <c:if test="${mode eq 1}">
                    <c:if test="${elm.updateDate.toString() != date}">
                        <p>
                            <h2>
                                ${elm.updateDate}
                            </h2>
                            <c:set var="date" value="${elm.updateDate.toString()}" scope="request"></c:set>
                        </p>
                    </c:if>
                </c:if>
                <c:set var="child" value="${elm}" scope="request"></c:set>
                <c:import url="element.jsp"></c:import>
                <br>
            </c:forEach>
        </ul>
        <script type="text/javascript" src="${f:url('/js/index.js')}"></script>
    </body>
</html>
<!-- element.jsp -->
<li>
    <c:if test="${mode eq 1}">
        <input type="checkbox" id="checkbox_${child.id}" onclick="toggleCheckbox(this)" ${child.isChecked ? "checked" : ""}>
    </c:if>
    <p>
        <c:if test="${child.isChecked}"><del></c:if>
            ${child.title}
        <c:if test="${child.isChecked}"></del></c:if>
        &nbsp;
    </p>
    <p>
        <input type="button" onclick="toggleAddMode(this)" value="+" class="btn addButton">
    </p>
    <p>
        <input type="button" onclick="toggleEditMode(this)" value="edit" class="btn editButton">
    </p>
    <c:if test="${child.isRoot == false}">
        <s:form>
            <input type="submit" name="up" value="↑" class="btn">
            <input type="hidden" name="targetId" value="${child.id}">
            <input type="hidden" name="mode" value="${mode}">
        </s:form>
    </c:if>
    <s:form>
        <input type="submit" name="remove" value="-" class="btn">
        <input type="hidden" name="targetId" value="${child.id}">
        <input type="hidden" name="mode" value="${mode}">
    </s:form>
    <div class="addBlock">
        <s:form>
            <textarea name="addText" rows="3" cols="30"></textarea>
            <input type="submit" name="add" value="add" class="btn">
            <input type="hidden" name="targetId" value="${child.id}">
            <input type="hidden" name="mode" value="${mode}">
        </s:form>
    </div>
    <div class="editBlock">
        <s:form>
            <textarea name="editText" rows="3" cols="30">${child.title}</textarea>
            <input type="submit" name="update" value="update" class="btn">
            <input type="hidden" name="targetId" value="${child.id}">
            <input type="hidden" name="mode" value="${mode}">
        </s:form>
    </div>
    <ul>
        <c:if test="${child.children != null}">
            <c:forEach var="child" items="${child.children}" varStatus="childStatus">
                <c:set var="child" value="${child}" scope="request"></c:set>
                <c:import url="element.jsp"></c:import>
            </c:forEach>
        </c:if>
    </ul>
</li>

UIUX

(変数を定数っぽく書いたりと迷走してますが)非同期で打ち消し線を入れたり、他の領域をクリックすることによる非表示を実装しています。

// index.js
let createMode = false;
let addMode = false;
let editMode = false;

const toggleCreateMode = () => {
    createMode = !createMode;
    const CREATE_BLOCK = document.getElementById("createBlock");
    if (createMode) {
        CREATE_BLOCK.style.display = "block";
    } else {
        CREATE_BLOCK.style.display = "none";
    }
}

const toggleAddMode = (elm) => {
    const ID = elm.id.split("_")[1];
    addMode = !addMode;
    const ADD_BLOCK = document.getElementById("addBlock_" + ID);
    if (addMode) {
        ADD_BLOCK.style.display = "block";
    } else {
        ADD_BLOCK.style.display = "none";
    }
};

const toggleEditMode = (elm) => {
    const ID = elm.id.split("_")[1];
    editMode = !editMode;
    const EDIT_BLOCK = document.getElementById("editBlock_" + ID);
    if (editMode) {
        EDIT_BLOCK.style.display = "block";
    } else {
        EDIT_BLOCK.style.display = "none";
    }
};

const toggleCheckbox = (elm) => {
    const XHR = new XMLHttpRequest();
    const FD = new FormData();
    const ID = elm.id.split("_")[1];
    const URL = "http://" + location.host
        + location.pathname.match(/\/.+\//) + "toggleCheck";
    FD.append("targetId", ID);
    XHR.open("POST", URL);
    XHR.send(FD);
}

const setEventListener = (selector) => {
    const ELM = document.getElementById(selector);
    if (selector == "createBlock") {
        document.addEventListener('click', function(e) {
            if (!e.target.closest(".createButton")
                    && !e.target.closest("#createBlock")) {
                createMode = false;
                ELM.style.display = "none";
            }
        }, false)
        return;
    }
    const CLASSNAME = selector.split("_")[0];
    if (CLASSNAME == "addBlock") {
        document.addEventListener('click', function(e) {
            if (!e.target.closest(".addButton")
                    && !e.target.closest("#" + selector)) {
                addMode = false;
                ELM.style.display = "none";
            }
        }, false)
    } else {
        document.addEventListener('click', function(e) {
            if (!e.target.closest(".editButton")
                    && !e.target.closest("#" + selector)) {
                editMode = false;
                ELM.style.display = "none";
            }
        }, false)
    }
}

const setId = (str, settingListener) => {
    const LIST = document.getElementsByClassName(str);
    for (let i = 0; i < LIST.length; i++) {
        const ID = str + "_" + i;
        LIST[i].setAttribute("id", ID);
        if (settingListener) {
            setEventListener(ID);
        }
    }
}

const setIndex = () => {
    setEventListener("createBlock");
    setId("addButton", false);
    setId("addBlock", true);
    setId("editButton", false);
    setId("editBlock", true);
}

window.onload = function() {
    setIndex();
}

バックエンド

(Webフレームワークにもよりますが)リクエストを受け取って必要な(ロジック)サービスを呼び出して処理を行っていました。

要素の入れ替えロジックの実装が大変でした。

package com.uranus.service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.dbflute.cbean.result.ListResultBean;
import org.dbflute.exception.EntityAlreadyDeletedException;
import org.dbflute.optional.OptionalEntity;

import com.uranus.dbflute.exbhv.MstElementBhv;
import com.uranus.dbflute.exbhv.MstRelationBhv;
import com.uranus.dbflute.exentity.MstElement;
import com.uranus.dbflute.exentity.MstRelation;
import com.uranus.dto.ElementDto;
import com.uranus.dxo.ElementDxo;
import com.uranus.util.MstElementDateComparator;
import com.uranus.util.MstElementIdComparator;
import com.uranus.util.StringUtil;
import com.uranus.util.Type;

public class IndexService {

    @Resource
    protected MstElementBhv mstElementBhv;

    @Resource
    protected MstRelationBhv mstRelationBhv;

    /**
     * Parent ElementDto
     */
    private static ElementDto parent;

    /**
     * Log4j logger
     */
    public Logger logger = Logger.getLogger(IndexService.class);

    // -------------------------- public methods --------------------------

    /**
     * Setup ElementDto and return it.
     * @param  text title
     * @return      assembled ElementDto
     */
    public ElementDto assembleElementDto(String text, String mode) {
        ElementDto dto = new ElementDto();
        dto.type = Integer.parseInt(mode);
        dto.title = StringUtil.sanitize(text);
        LocalDate now = LocalDate.now();
        dto.createDate = now;
        dto.updateDate = now;
        return dto;
    }

    /**
     * Get root elements and their children (recursively) from database.
     * @param  type element type
     * @return      contents list if exists, otherwise {@code null}
     */
    public List<ElementDto> getList(int type) {
        List<Integer> rootIdList = getRootElementsId(type);
        if (rootIdList.size() == 0) return null;
        Map<Integer, ElementDto> elmMap = createElementMap(rootIdList);
        ListResultBean<MstRelation> rootRels = getRootRelation(rootIdList);
        rootRels.forEach(rel -> {
            ElementDto parentDto = getParent(elmMap.get(rel.getParentId()));
            setChildren(parentDto, rel.getChildId());
        });
        return createSortedList(elmMap, type);
    }

    /**
     * Create new element.
     * @param dto target ElementDto
     */
    public void createElement(ElementDto dto) {
        insertElement(dto, true);
    }

    /**
     * Add element.
     * @param parentId parent element id
     * @param childDto child ElementDto
     */
    public void addElement(int parentId, ElementDto childDto) {
        MstElement element = insertElement(childDto, false);
        insertRelation(parentId, element.getId());
    }

    /**
     * Update element.
     * @param targetId target element id
     * @param text     element text
     */
    public void updateElementText(int targetId, String text) {
        ElementDto dto = getElementDtoById(targetId);
        dto.title = text;
        dto.updateDate = LocalDate.now();
        MstElement element = ElementDxo.toElementEntity(dto);
        updateElement(element);
    }

    /**
     * Remove element and its relation (recursively) from database.
     * @param id target element id
     */
    public void removeElement(int id) {
        MstElement element = getElementEntityById(id);
        if (element == null) return;
        MstRelation upwardRel = getUpwardRelation(element.getId());
        if (upwardRel != null) deleteRelation(upwardRel);
        removeDownwardContents(element.getId());
    }

    /**
     * Toggle element's isChecked property.
     * @param id target element id
     */
    public void toggleElementCheck(int id) {
        MstElement element = getElementEntityById(id);
        if (element == null) return;
        boolean checked = element.getIsChecked();
        element.setIsChecked(!checked);
        updateElement(element);
    }

    /**
     * Exchange elements relation.
     * @param childId target element id to be promoted
     */
    public void exchangeElements(int childId) {
        if (getElementDtoById(childId).isRoot) return;
        int parentId = getUpwardRelation(childId).getParentId();
        Integer ancestorId = getAncestorId(parentId);
        rearrangeAncestorRelation(ancestorId, parentId, childId);
        ListResultBean<MstRelation> childRels = getRelationByParentId(childId);
        ListResultBean<MstRelation> parentRels = getRelationByParentId(parentId);
        rearrangeChildRelation(parentId, childId, childRels);
        rearrangeParentRelation(parentId, childId, parentRels);
        toggleIsRootProperty(childId, parentId);
    }

    /**
     * Delete all elements and relations with type.
     * @param type contents type
     */
    public void clear(int type) {
        List<Integer> rootElmsIdList = getRootElementsId(type);
        rootElmsIdList.forEach(rootElm -> {
            removeDownwardContents(rootElm);
        });
    }

    // -------------------------- private methods --------------------------

    /**
     * Get ancestor element id.
     * @param  parentId parent element id
     * @return          ancestor element id if exists, otherwise {@code null}
     */
    private Integer getAncestorId(int parentId) {
        MstRelation rel = getUpwardRelation(parentId);
        if (rel != null) return rel.getParentId();
        return null;
    }

    /**
     * Rearrange relation between ancestor element, parent element, and target
     * element.
     * @param ancestorId ancestor element id
     * @param parentId   parent element id
     * @param targetId   target element id to be promoted
     */
    private void rearrangeAncestorRelation(Integer ancestorId, int parentId, int targetId) {
        if (ancestorId != null) {
            deleteRelation(ancestorId, parentId);
            insertRelation(ancestorId, targetId);
        }
    }

    /**
     * Rearrange relation between parent element and target element.
     * @param parentId   parent element id
     * @param targetId   target element id to be promoted
     * @param parentRels parent element's downward relation
     */
    private void rearrangeParentRelation(int parentId, int targetId, ListResultBean<MstRelation> parentRels) {
        removeRelations(parentId, parentRels);
        createRelations(targetId, parentRels);
        insertRelation(targetId, parentId);
    }

    /**
     * Rearrange target element downward relation.
     * @param parentId  parent element id
     * @param targetId  target element id to be promoted
     * @param childRels target element's downward relation
     */
    private void rearrangeChildRelation(int parentId, int targetId, ListResultBean<MstRelation> childRels) {
        if (childRels.size() > 0) {
            removeRelations(targetId, childRels);
            createRelations(parentId, childRels);
        }
    }

    /**
     * Get downward relation by parent id.
     * @param  parentId parent element id
     * @return          relation list
     */
    private ListResultBean<MstRelation> getRelationByParentId(int parentId) {
        ListResultBean<MstRelation> rels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_Equal(parentId);
        });
        return rels;
    }

    /**
     * Toggle element is_root property.
     * @param newParentId element id to be promoted
     * @param oldParentId element id to be demoted
     */
    private void toggleIsRootProperty(int newParentId, int oldParentId) {
        MstElement oldParent = getElementEntityById(oldParentId);
        if (oldParent.getIsRoot()) {
            oldParent.setIsRoot(false);
            updateElement(oldParent);
            MstElement newParent = getElementEntityById(newParentId);
            newParent.setIsRoot(true);
            updateElement(newParent);
        }
    }

    /**
     * Create relation with parentId and relation list's childId.
     * @param parentId parent element id
     * @param rels     relation list
     */
    private void createRelations(int parentId, ListResultBean<MstRelation> rels) {
        rels.forEach(rel -> {
            if (parentId != rel.getChildId()) {
                insertRelation(parentId, rel.getChildId());
            }
        });
    }

    /**
     * Remove relation with parentId and relation list's childId.
     * @param parentId
     * @param rels
     */
    private void removeRelations(int parentId, ListResultBean<MstRelation> rels) {
        rels.forEach(rel -> {
            deleteRelation(parentId, rel.getChildId());
        });
    }

    /**
     * Create map from root elements id.
     * @param  rootIdList root elements id
     * @return            map key:element id, value:ElementDto
     */
    private Map<Integer, ElementDto> createElementMap(List<Integer> rootIdList) {
        Map<Integer, ElementDto> elmMap = new HashMap<>();
        ListResultBean<MstElement> rootElms = getRootElement(rootIdList);
        rootElms.forEach(rootElm -> {
            elmMap.put(rootElm.getId(), ElementDxo.toElementDto(rootElm));
        });
        return elmMap;
    }

    /**
     * Create list from map.
     * @param  map  key:id, value:ElementDto
     * @param  type contents type
     * @return      ElementDto list
     */
    private List<ElementDto> createSortedList(Map<Integer, ElementDto> map, int type) {
        List<ElementDto> list = new ArrayList<>(map.values());
        if (type == Type.MEMO.getType()) {
            list.sort(new MstElementIdComparator());
        } else {
            list.sort(new MstElementDateComparator());
        }
        return list;
    }

    /**
     * Get root elements by root elements id.
     * @param  rootIdList root elements id list
     * @return            root elements list
     */
    private ListResultBean<MstElement> getRootElement(List<Integer> rootIdList) {
        ListResultBean<MstElement> rootElms = mstElementBhv.selectList(cb -> {
            cb.query().setId_InScope(rootIdList);
            cb.query().addOrderBy_Id_Asc();
        });
        return rootElms;
    }

    /**
     * Get root relation by root elements id.
     * @param  rootIdList root elements id list
     * @return            root relation list
     */
    private ListResultBean<MstRelation> getRootRelation(List<Integer> rootIdList) {
        ListResultBean<MstRelation> rootRels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_InScope(rootIdList);
            cb.query().addOrderBy_ParentId_Asc();
        });
        logger.info("    Root relation list: " + rootRels);
        return rootRels;
    }

    /**
     * Delete downward relation and its element recursively from database.
     * @param parentId parent element id
     */
    private void removeDownwardContents(int parentId) {
        List<MstRelation> downwardRels = getAllRelation(parentId);
        downwardRels.forEach(rel -> {
            removeDownwardContents(rel.getChildId());
            deleteRelation(parentId, rel.getChildId());
        });
        MstElement elm = getElementEntityById(parentId);
        deleteElement(elm);
    }

    /**
     * Update element.
     * @param elm element entity
     */
    private void updateElement(MstElement elm) {
        mstElementBhv.update(elm);
        logger.info("    Element updated: " + elm);
    }

    /**
     * Delete element from database by element entity.
     * @param  elm element entity
     * @return     element entity after deletion.
     */
    private MstElement deleteElement(MstElement elm) {
        mstElementBhv.delete(elm);
        logger.info("    Element deleted: " + elm);
        return elm;
    }

    /**
     * Delete relation from database by parentId and childId.
     * @param  parentId parent element id.
     * @param  childId  child element id.
     * @return          relation entity after deletion.
     */
    private MstRelation deleteRelation(int parentId, int childId) {
        MstRelation rel = new MstRelation();
        rel.setParentId(parentId);
        rel.setChildId(childId);
        return deleteRelation(rel);
    }

    /**
     * Delete relation from database by relation entity.
     * @param  rel relation entity
     * @return     relation entity after deletion.
     */
    private MstRelation deleteRelation(MstRelation rel) {
        try {
            mstRelationBhv.delete(rel);
        } catch (EntityAlreadyDeletedException e) {}
        logger.info("    Relation deleted: " + rel);
        return rel;
    }

    /**
     * Get all relation by id.
     * @param  parentId parent element id
     * @return          relation entity list
     */
    private ListResultBean<MstRelation> getAllRelation(int parentId) {
        ListResultBean<MstRelation> rels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_Equal(parentId);
            cb.query().addOrderBy_ChildId_Asc();
        });
        return rels;
    }

    /**
     * Insert Element into database.
     * @param  dto    target element DTO
     * @param  isRoot whether argument DTO is root or not
     * @return        element entity after insertion
     */
    private MstElement insertElement(ElementDto dto, boolean isRoot) {
        MstElement element = ElementDxo.toElementEntity(dto);
        element.setId(null);
        element.setIsRoot(isRoot);
        mstElementBhv.insert(element);
        logger.info("    Element created: " + element);
        return element;
    }

    /**
     * Insert Relation into database.
     * @param  parentId parent element id
     * @param  childId  child element id
     * @return          relation entity after insertion.
     */
    private MstRelation insertRelation(int parentId, int childId) {
        MstRelation relation = new MstRelation();
        relation.setParentId(parentId);
        relation.setChildId(childId);
        mstRelationBhv.insert(relation);
        logger.info("    Relation created: " + relation);
        return relation;
    }

    /**
     * Get element from database by id.
     * @param  id element id
     * @return    ElementDto if entity exists, {@code null} otherwise
     */
    private ElementDto getElementDtoById(int id) {
        OptionalEntity<MstElement> op = mstElementBhv.selectEntity(cb -> {
            cb.query().setId_Equal(id);
        });
        if (op.isPresent()) return ElementDxo.toElementDto(op.get());
        return null;
    }

    /**
     * Get element from database by id.
     * @param  id element id
     * @return    element entity if it exists, otherwise {@code null}
     */
    private MstElement getElementEntityById(int id) {
        OptionalEntity<MstElement> op = mstElementBhv.selectEntity(cb -> {
            cb.query().setId_Equal(id);
        });
        if (op.isPresent()) return op.get();
        return null;
    }

    /**
     * Get relation from database by id.
     * @param  childId child element id
     * @return         relation entity if it exists, {@code null} otherwise
     */
    private MstRelation getUpwardRelation(int childId) {
        OptionalEntity<MstRelation> op = mstRelationBhv.selectEntity(cb -> {
            cb.query().setChildId_Equal(childId);
        });
        if (op.isPresent()) return op.get();
        return null;
    }

    /**
     * If parent has children, set it recursively by id.
     * @param parentDto parent ElementDto
     * @param childId   child element id
     */
    private void setChildren(ElementDto parentDto, int childId) {
        if (parentDto == null) return;
        ElementDto childDto = getElementDtoById(childId);
        if (childDto == null) return;
        // If child also has children, set it recursively
        setChildrenRecursively(childDto, childId);
        parentDto.children.add(childDto);
    }

    /**
     * Get relation with childId and call setChildren if they exist
     * @param childDto child ElementDto
     * @param childId  child element id
     */
    private void setChildrenRecursively(ElementDto childDto, int childId) {
        ListResultBean<MstRelation> rels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_Equal(childId);
            cb.query().addOrderBy_ChildId_Asc();
        });
        rels.forEach(rel -> {
            setChildren(childDto, rel.getChildId());
        });
    }

    /**
     * Get root elements from database, and return their id.
     * @param  mode contents mode
     * @return      root elements id list
     */
    private List<Integer> getRootElementsId(int type) {
        ListResultBean<MstElement> elms = getRootElements(type);
        List<Integer> idList = new ArrayList<>();
        elms.forEach(elm -> {
            idList.add(elm.getId());
        });
        logger.info("    Root id list: " + idList);
        return idList;
    }

    /**
     * Get root elements with element type.
     * @param  type element type
     * @return      Element entity list
     */
    private ListResultBean<MstElement> getRootElements(int type) {
        return mstElementBhv.selectList(cb -> {
            cb.query().setIsRoot_Equal(true);
            cb.query().setType_Equal(type);
            cb.query().addOrderBy_Id_Asc();
        });
    }

    /**
     * Return the same ElementDto until argument DTO has different id.
     * @param  dto ElementDto
     * @return     parent if argument DTO has the same id as that of it,
     *                 argument DTO otherwise.
     */
    private ElementDto getParent(ElementDto dto) {
        if (parent != null && dto.id == parent.id.intValue()) return parent;
        parent = dto;
        return parent;
    }
}

所感

使用するテーブルはたったの二つだけですが、一方でアプリケーションロジックの実装がかなり大変でした。

もし同じ仕様を実装するなら

  • ドメイン駆動設計(注:完全な好み)
  • Compositeパターン
  • 閉包テーブル

等を使ってみるといいかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「だからなに」で学ぶ、Javaの初歩【初心者向け】

プログラミングの学習でよくあることですが、こういう長い文章を読んでると途中で飽きちゃいます。
だから最初に書いておくのですが、この記事では女性タレントさんのおっぱいについて扱います。わかったら頑張って読んでください。

はじめに

あなたは全くのプログラミング未経験者あるいは初心者で、Javaを書けるようになるために初心者向けの講座を受けているとしましょう。そしてこう習います。

System.out.println("Hello, World!")と書いてコンパイルして実行すれば、コンソールにHello, World!という文字列を出力できるよ!」
「Javaはオブジェクト指向だから、extendsでクラスの継承ができるよ!」

だからなにって思いますよね。
その気持ち、ごもっともだと思うんです。

そういうことが出来るのはわかりますよ。でもそれの何が良いのかわからないし、どういう使い方をすれば良いのかもわからない。

もちろん教える側にしたって他に言いようが無いからそう言ってるのでしょうが、なんとかならないでしょうか。

そういうわけでこの記事では、実際に何かプログラムを作ってみることを通じて、その「だからなに」をなんとか回避しながらJavaの初歩を学んでみることを目指します。

長い文章ですから、途中で疲れてきたら適当に好みのおっぱいでも想像してやる気を繋ぐようにしてください。

目標

「だからなに」を回避しながらプログラミングを学ぶにあたっては、どんなプログラムを作るかの目標が必要かと思います。

それは「超面白いゲームを作りたい」でも「ロボットを動かしたい」でも何でもいいんですが、今回のところは現実的な実装の難易度や皆の興味などを総合的に考えて、「好きなおっぱいのカップサイズからおすすめの女性タレントを教えてくれる便利なプログラムを作りたい」で行きましょう。

  • 動作デモ

oppai-finder_src_OppaiFinder.java - D__Users_fukuchan_Desktop_pleiades_workspace - Eclipse IDE 2020-02-23 16-03-43.gif

もちろん現実に好みのおっぱいのカップサイズからタレントを調べる人なんているのかは疑問ですが、おっぱいのことなら何を勉強するにもやる気を維持しやすいでしょう?それに実装も簡単なので、今回のところはそれでお願いします。

一度プログラミングを覚えてしまえば、AIなんか使ったもっとすごいプログラムだって作れるようになるかもしれません。でも何事も最初は簡単な目標から始めましょう。

実装

用意するもの

  • Eclipse (JDKが適切に設定されたもの)

Eclipseのインストール方法

eclipseと言っても色々あるのですが、ここではPleiades All in One Eclipseという、諸々の設定が元々済ませてあるEclipseのインストール方法を解説します。

image.png

https://mergedoc.osdn.jp/
まずはこちらのサイトにアクセスし、一番新しいPleiades All in One Eclipseのダウンロードページを開きます。(記事執筆時点では2019-12)

image.png

色んなパッケージが選べますが、今回はJavaのFull Editionを使います。
お使いのOSがWindowsならWindows版を、MacならMac版をダウンロードして保存しましょう。

image.png

zipで圧縮されたPleiades All in One Eclipseがダウンロードできました。ただ圧縮されたままでは使えませんから、必ず解凍しましょう。解凍先は好きな場所で大丈夫です。

なおWindowsの場合、解凍にあたってはWindowsエクスプローラではなく専用の解凍ソフトを使うべきです。
Pleiades All in One Eclipseの公式は7-zipを使うべきだとしています。他の解凍ソフトでも大抵は大丈夫と思いますが、自信がないなら7-zipを使いましょう。

image.png

そうして解凍が済んだら、解凍先のeclipseフォルダ以下にあるeclipse.exeを開いてみましょう。

image.png

ワークスペースがどうとか言われたらそのまま起動(L)を押して…

image.png

↑このような画面が開いたらインストールは無事に完了しています。お疲れさまでした。

Q. なぜEclipseを使うの?

A. そのほうが楽だから。

Eclipseは、Javaなどのプログラミング言語を楽に書くためのツール(いわゆる統合開発環境、IDE)です。
Javaのコードというのは実は何を使っても、例えばWindowsのメモ帳なんかでも書けるのですが、メモをするために作られてるメモ帳と違ってEclipseはプログラミングのために作られてるのでこれを使ったほうが色々と楽です。
今回はEclipseを使う前提で話を進めます。

ちなみに筆者は本当は違う宗教の人なので普段Eclipseはあまり使わないのですが、Eclipseは日本語の情報が充実していて初心者向けとされていますから今回のところは我慢して使っています。IDEにこだわりがある人で、以降の解説を適切に読み替えられるならIntelliJやVSCodeを使っても良いでしょう。

先に覚えておいたほうが良いこと

初心者がEclipseを使う時にありがちなトラブルが、画面に必要なパーツを間違って消してしまい、復活させる方法がわからなくなるというものです。画面を自由に組み替えられるのがEclipseの良いところなんですけど、初心者のうちは困りますよね。

image.png

そういうときは[ウィンドウ(W)]->[パースペクティブ(R)]->[パースペクティブのリセット(R)]で画面を初期状態に戻せます。先に覚えておくと良いでしょう。

1. プロジェクトを作る

image.png

さて、まずはEclipseを起動し、上部のツールバーから[ファイル]->[新規(N)]->[Javaプロジェクト]を作りましょう。

プロジェクトというのはEclipseに固有の仕組みなのですが、その名の通りあるプロジェクトに関わるファイルや設定をひとまとめにするものだと思ってください。

image.png

プロジェクトの名前を決めなければなりませんが、以降に出てくる変数名やクラス名と違って、プロジェクト名は自由に決めることができます。
ただ色々な面倒を避けるため、次のような決まり事に従って決めるほうが無難です。こういう決まり事のことを命名規則と言います。

プロジェクト名の命名規則

  • 以下の種類の文字だけを使って名付ける。
    • アルファベットの半角文字(a,b,c…)
    • 半角数字(0,1,2…)
    • 半角ハイフン(-)またはアンダーバー(_)
  • 「sample」「test」「project-01」などの行き当たりばったりな名前は避け、一目見て何のことかわかるような名前にする。

加えて、アルファベットの中でも小文字のみを用いるという決まりを作ることもあります。

Q. なんでプロジェクト名の付け方にそんな決まり事を作るの?

A. そのほうが楽だから。

まず使える文字の種類を制限すると楽な理由ですが、おおまかには2つあります。

image.png

  • 日本語やスペース(空白)がバグの元だから。特に日本語は文字化けを起こすから。

image.png

  • githubやgitlabのようなサービスでソースコードを共有するとき、日本語やスペースが使えなかったり、大文字小文字の区別がつかなかったりすることがあるから。

次に一目見て何のことか分かるような名前をつける理由ですが、これはもしそうしなかった場合の半年後を想像すればわかりやすいでしょう。
その頃にはプログラミングも上達し、次々に新しいプロジェクトを作るようになります。そして…
image.png
どれが何だっけ。
と、こうなります。そうなる前に最初から、一目でどれが何のことかわかるような名前を付けておくべきというわけです。

今回のおっぱい検索プログラムを作るというプロジェクトに適した名前を考えましょう。

好みのおっぱいを持つタレントさんを見つける…好みのおっぱい…見つける…おっぱい…発見器…ファインダー…
うん、oppai-finderでいいかな。

image.png

今回のところはプロジェクト名以外に設定をいじる必要はありません。
プロジェクト名だけ入力したら[完了(E)]を押しましょう。

image.png

module-info.javaがどうとか言われますがこれも今回のところは扱わないので[作成しない(D)]を押しましょう。

これでプロジェクトの作成は完了です。

2. 実行用クラス・メインメソッドを作る

実行用クラスを作る

クラスについての詳しい解説は後に回すのですが、とりあえずの実行用にクラスというのを作らないと話が進みませんからとりあえず作ってもらいます。

image.png

image.png

画面左にある「パッケージエクスプローラ」でoppai-finderプロジェクトを選択した状態で、[ファイル(F)]->[新規(N)]->[クラス(C)]をクリックしてください。
クラス作成用のウィンドウが開いたら、名前をOppaiFinderとして完了(E)をクリックします。

image.png

クラスが正常に作成できたら、OppaiFinder.javaというファイルが作成されます。

OppaiFinder.java
public class OppaiFinder {

}

メインメソッドを作る

OppaiFinder.javaの1行目public class OppaiFinder {の中括弧と、最後の行}の閉じ中括弧の間に

public static void main(String[] args) {
}

と書き足し、メインメソッドというものを作ります。

OppaiFinder.java
public class OppaiFinder {
    public static void main(String[] args) {

    }
}

また中括弧が増えました。中括弧で囲われた区間一つ一つのことをコードブロックといいますが、最初はメインメソッドのコードブロックの中にコード(コンピュータへの命令文)を書いていくことになります。

ちなみにここでコードブロックに関して決まり事があります。

  • コードブロックの始まりから終わりまで、行頭に決まった長さの空白を挿入すること。
    • インデントという。
    • キーボードの[Tab]キーで入力できる。

コードブロックの中にコードブロックがあるような場合には、それに従ってインデントの長さも増えていきます。以下のQAのコード例を見ると分かりやすいでしょう。

Q.どうしてインデントを入れないといけないの?

A. そのほうが楽だから。

今度の楽だからは「読みやすいから」という意味です。

なんのこっちゃという感じだと思うので例で示すと、

import java.util.ArrayList;
import java.util.List;

public class OppaiFinder {
public static void main(String[] args) {
List<FemaleTalent> talents = new ArrayList<>();

for (int i = 0; i < talents.size(); i++) {
FemaleTalent talent = talents.get(i);
String cupSize = talent.getCupSize();
if (cupSize.equals("A")) {
System.out.println("[" + i + "] " + talent.getName());
}
}
}
}

こうではなく

import java.util.ArrayList;
import java.util.List;

public class OppaiFinder {
  public static void main(String[] args) {
      List<FemaleTalent> talents = new ArrayList<>();

      for (int i = 0; i < talents.size(); i++) {
          FemaleTalent talent = talents.get(i);
          String cupSize = talent.getCupSize();
          if (cupSize.equals("A")) {
              System.out.println("[" + i + "] " + talent.getName());
          }
      }
  }
}

こうのほうが、それぞれのコードブロックがどこから始まってどこで終わっているか見やすいと思いませんか?

最初のうちはそんなの無くても十分読めるって思うかもしれませんが、コードが長くなってくるとそうも言ってられません。コードブロックごとにインデント。必ず守るようにしましょう。

それとEclipseの場合、
image.png
コード全体を選択した状態で、[ソース(S)]->[フォーマット(F) Ctrl+Shift+F]をクリックすることでインデント(を含めたいろいろ)を自動調整してくれます。

実行してみる

まだメインメソッドに何のコードも書いていないので何の処理も行われませんが、試しにプログラムを実行してみましょう。

image.png

まずはOppaiFinder.javaを開いた状態で、[ファイル(F)]->[保存(S) Ctrl+S]をクリックし、編集内容を保存。(クリックできないような場合では既に保存済みなのでこの操作は不要です。)

image.png

次に[実行(R)]->[実行(S)]->[Javaアプリケーション Alt+Shift+X,J]をクリックすることで、OppaiFinderクラスのメインメソッドが実行できます。

image.png

何も起こらなければ、それで成功です。

3. 文字を出力してみる

入出力とコンソール

ここで今回のプログラムの動作の流れを簡単に見てみましょう。

  1. 好きなカップサイズ(A,B,C…)の入力を受け付ける
  2. そのカップサイズに当てはまる女性タレントの情報を出力する
  3. 出力された女性タレントの中で気になるタレントの番号(0,1,2…)の入力を受け付ける
  4. その番号の女性タレントの詳細な情報を出力する

入力・出力という言葉が出てきました。

image.png

プログラミングで入出力というと、こういうオシャレな画面のテキストボックスに入力して、ボタンを押すと結果が画面に出力される…みたいなのが想像されるかもしれません。

実際そういうのもJavaで作れはするのですが、入出力の基本というのは違います。

入出力の基本はそういう見た目華やかなところではなく、

image.png

こういう文字だけの世界で行われます。

image.png

eclipse的にはここ、コンソールがその世界です。(デフォルトではコンソールは表示されていませんが、コンソールに何か文字列を出力すれば勝手に出現します)

そのコンソールを使った入出力を試してみましょう。
入力は難しいので先に出力からです。

コンソールへの出力

コンソールに何か出力するには、System.out.println(<出力したい内容>);というコードを書きます。

例えばコンソールに「1」って出力したければSystem.out.println(1);、「-123.4」って出力したければSystem.out.println(-123.4);です。
末尾の;(セミコロン)を忘れないようにしましょう。

ただ一つ注意しておかないといけないのが、文字列を直接書くときはそれを"(ダブルクオーテーション)で囲まなければならないということ。
つまり「abc」って出力したければSystem.out.println(abc);でなくSystem.out.println("abc");です。

その上で、好きなカップサイズとして「A」という入力があったと仮定し、Aカップの女性タレントとして「園田海未」「矢澤にこ」「松井結」「熊澤枝里子」「中村明花」という順番で5人の名前を出力することを考えます。
(※3次元の女性タレントについてはWikipediaから無作為に抽出していますのでチョイスに意図はありません。)

image.png

書くところは先のメインメソッドの2つの中括弧の間。上の行から順番に処理されますから、書くべきコードは次のようになります。

OppaiFinder.java
public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        System.out.println("園田海未");
        System.out.println("矢澤にこ");
        System.out.println("松井結");
        System.out.println("熊澤枝里子");
        System.out.println("中村明花");
    }
}

実際に実行してみて、コンソールに5人の名前が順番通り出力されることを確かめてみましょう!

image.png

4. 変数を使って出力してみる

前項では最も単純で簡単な出力を試しましたが、今回のプログラムでは「好きなカップサイズ」の入力の後に「詳しくプロフィールを見たい女性タレントの番号」の入力を受け付ける予定でいます。
そのために、出力される女性タレントに番号を振っておきたいのです。

image.png

なので前回の出力の形式を少し変えてみることにします。
名前の先頭に連番を振って、「[0] 園田海未」「[1] 矢澤にこ」「[2] 松井結」「[3] 熊澤枝里子」「[4] 中村明花」という感じで出力することにしましょう。

一番単純な実装方法としては、先ほどのコードを書き替えて、こうでしょうか。

OppaiFinder.java
public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        System.out.println("[0] 園田海未");
        System.out.println("[1] 矢澤にこ");
        System.out.println("[2] 松井結");
        System.out.println("[3] 熊澤枝里子");
        System.out.println("[4] 中村明花");
    }
}

名前を出力する行を全て書き換えなければいけませんでしたがまあ実現できました。
しかしこのやり方は、5人程度までなら良いのですが、50人とかに番号を振るとなるとすごく面倒です。途中で番号振り間違えそうですし、後で人を減らしたり増やしたりしようものならそのたびに番号を振りなおさなきゃいけません。

そこで変数というものを使ってみましょう。
Javaで整数を表すデータ型はint。int型の変数を作るにはint i = 0;のようにします。

データ型・変数について

image.png

intとか変数とか新しい言葉が出てきましたが、まずintというのはJavaのデータ型の一種です。

データ型は単純に、整数とか文字列とかデータの種類を表すものだと思ってください。下表に代表的なデータ型をいくつか示します。

分類 データ型 用途 変数の作り方の例
プリミティブ型 boolean true(正)かfalse(偽)を表す。条件文などで使う boolean bool = true;
プリミティブ型 int 整数を表す。 int i = 123;
プリミティブ型 double 浮動小数点数(要するに小数点付きの数)を表す double d = 3.14;
参照型 String 文字列を表す String str = new String(new char[] {'あ', 'い', 'う', 'え', 'お'});(本来の書き方)、またはString str = "あいうえお";(String型でだけ使える特別な書き方)

データの種類に応じて正しいデータ型を選択するのはとても大切です。覚えておきましょう。

次に、変数というのはデータを収納するための箱のようなものだと思ってください。

変数を定義する(作る)には次のようなコードを書きます。

<データ型> <変数名>;

image.png

例えばint i;のように書けばiという名前でint型の変数を作ることができます。

その変数に値を代入するには、次のようなコードを書きます。

<変数名> = <代入したい値>;

image.png

例えばi= 0;と書けば変数iに0という値を代入することができます。(定義と合わせて、int i = 0;というふうに省略して書くことが多いです)

一度変数を作れば、そのコードブロックの中で読み出したり書き換えたり自由に使うことができます。

変数を読み出すには単純にiと書けばOKです。System.out.println(i);と書けば、コンソールにiに代入されている値を出力できます。

image.png
image.png

また変数を書き換えるには単純に代入をやり直せば良く、i = i + 1;と書けばiに1を足した数(1)を代入し直すことができます。

OppaiFinder.java
public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        int i = 0;

        System.out.print("[");
        System.out.print(i);
        System.out.print("] ");
        System.out.println("園田海未");
        i = i + 1;

        System.out.print("[");
        System.out.print(i);
        System.out.print("] ");
        System.out.println("矢澤にこ");
        i = i + 1;

        System.out.print("[");
        System.out.print(i);
        System.out.print("] ");
        System.out.println("松井結");
        i = i + 1;

        System.out.print("[");
        System.out.print(i);
        System.out.print("] ");
        System.out.println("熊澤枝里子");
        i = i + 1;

        System.out.print("[");
        System.out.print(i);
        System.out.print("] ");
        System.out.println("中村明花");
        i = i + 1;
    }
}

※System.out.print(<出力したい内容>)で末尾の改行なしにコンソールに出力できる

image.png

ところで変数にも命名規則があります。

  • 以下の種類の文字だけを使って名付ける。
    • アルファベットの半角文字(a,b,cまたはA,B,C…)
    • 半角数字(0,1,2…)
  • 最初の1文字は小文字で始め、以降各単語の先頭文字を大文字、それ以外を小文字にする。
  • 配列やリスト以外には必ず単数形の名前をつける。
  • 「test」「sample」「variable1」などの行き当たりばったりな名前は避け、できるだけ一目見て何のことかわかるような名前にする。
    • 但しインデックス(番号)を表すint型の変数は便宜上i,j,kなど1文字で済ませることも多い。

Q. なぜ変数名の決め方にそんな決まりを作るの?

A. そのほうが楽だから。

使える文字の種類を制限し、特に日本語は使わない理由ですが、例によって日本語が文字化け等々、色んなトラブルの元になるからです。
近頃は「日本語使ってもいいんじゃね」という話も一応あるようですが、一般的には使いません。

次に各単語の先頭文字を大文字、それ以外を小文字にする理由は、単語区切りを明らかにして読みやすくするためです。
例えば(おっぱいの)カップサイズ=cup sizeを表す変数を作りたいとします。
ただ変数名にスペースは使えません。
だから単に半角スペースを削除してcupsizeという名前にするもよしですが、これだと「cup」と「size」の区切りがなくなって読みにくいと感じませんか?
対処方法としてはcup_sizeのように単語区切りにアンダーバーを挿入するか、cupSizeのように各単語の先頭を大文字にするかですが、Javaでは一般的に後者の方法が採られます。

一番最初の文字を小文字で始める理由は、クラス(後述)との区別のためです。
クラスの名前は大文字で始めるという決まりがあるので、変数名は小文字で始めたほうがわかりやすいよね、ということになっています。

それとできるだけ一目見て何のことかわかるような名前にする理由は、そうしないと変数の数が増えてきたときにどれが何のことかわからなくなってくるためです。

算術演算子・文字列連結演算子について

前項でさりげに+記号を使って足し算をやりましたが、そういうint型やdouble型の計算に使う記号のことを算術演算子といいます。代表的な算術演算子は下表の通りです。

算術演算子 意味
+ 足す
- 引く
/ 割る(int型同士の場合、小数点以下までは求めないことに注意)
* 掛ける
% 割った時の余りを求める

image.png

image.png

普通の計算式のように書いて使います。
普通の計算式と同様、掛け算や割り算は足し算・引き算より優先されます。(40-32/2は4ではなく24)

ちなみに「変数に1足した値を変数に代入する」、要は普通に1足すことを示すi = i + 1;という代入文は短く
i += 1;
とも書けます。

また、1を足すときだけは特別に
i++;
と書くこともできます。(インクリメントという)

逆に1を引くときだけも特別に
i--;
と書くことができます。(デクリメントという)

それと算術演算子とは違った使い方で、+記号は「文字列同士を繋げる」という用途にも使われます。例えば
String str = "あいうえお" + "かきくけこ";
と書けば変数strの値はあいうえおかきくけことなります。

OppaiFinder.java
public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        int i = 0;

        System.out.println("[" + i + "] 園田海未");
        i++;

        System.out.println("[" + i + "] 矢澤にこ");
        i++;

        System.out.println("[" + i + "] 松井結");
        i++;

        System.out.println("[" + i + "] 熊澤枝里子");
        i++;

        System.out.println("[" + i + "] 中村明花");
        i++;
    }
}

これらを踏まえた上で、先のコードを書き直すと上のようになります。
System.out.println("[" + i + "] <女性タレントの名前>");i++;を繰り返し順番で書くことさえ守れば番号が自動で振られるようになりました。

5. リストとループを使って出力してみる

前項では変数を使うことで女性タレントに自動で番号が振られるようにしましたが、あの方法にもまだ問題があります。
例えば番号のフォーマットを[0],[1],[2]…ではなく(0),(1),(2)…に変えたくなったらどうでしょうか。また出力の行全てを書き直さなければなりません。

OppaiFinder.java
public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        int i = 0;

        System.out.println("(" + i + ") 園田海未");
        i++;

        System.out.println("(" + i + ") 矢澤にこ");
        i++;

        System.out.println("(" + i + ") 松井結");
        i++;

        System.out.println("(" + i + ") 熊澤枝里子");
        i++;

        System.out.println("(" + i + ") 中村明花");
        i++;
    }
}

ていうかこのコードは「女性タレントの番号と名前を出力する」処理と「番号を表す変数に1足す」処理の2行を5回も繰り返しています。
何度も同じことを書くコードというのは読みにくいし、何をするにも面倒です。
「この処理を何度も繰り返す」というのを簡潔に書くためにはどうしたら良いでしょうか?

そういう流れからリストとループというものが出てきます。

条件式について

ループで必要になるので、先に条件式について見ておきましょう。

条件式というのは、何らかの演算の結果をboolean型で表してくれる式です。

image.png

image.png

簡単な例が比較演算で、例えば10 < 20と書けば10は20より小さいのでtrueとなります。逆に20 < 10と書けば20は10より小さくないのでfalseとなります。
また、これを変数と組み合わせて、例えばi < 20と書けば、変数iが20より小さいときはtrue、小さくないときはfalseとなります。

<(小なり)のようなプリミティブ型の比較演算をする時に使う記号を比較演算子といいます。下表に代表的な比較演算子をいくつか示します。

比較演算子 意味
== 等しい
!= 等しくない
< 小なり
> 大なり
<= 小なりイコール(≦)
>= 大なりイコール(≧)

リストについて

話を戻して、リストについてです。
リストというのはその名の通り、たくさんの値を順序つきの一覧のようにして収納できる仕組みのことです。

例として、女性タレントの名前のリストを作ることを考えましょう。

名前は文字列なのでString型。

String型のリストを作り、namesという名前の変数に代入するには、以下のどちらかの書き方をします。

  1. java.util.List<String> names = new java.util.ArrayList<>();のように書く。
  2. OppaiFinder.javaの冒頭にimport java.util.ArrayList;import java.util.List;を書いて「このファイルでArrayListって言ったらjava.util.ArrayListのことな」って示してからList<String> names = new ArrayList<>();と書く。

image.png

後々のことを考えると、1行あたりのコードの長さが短くて済む2のやり方のほうが楽です。import文を書くのは面倒に思えるかもしれませんが…

image.png

eclipseでは単にList<String> names = new ArrayList<>();と書けばListとArrayListの下に赤波線が引かれます。
ここにポインタを合わせると開くメニューで['List'をインポートします(java.util)]をクリックすることでimport文を自動で挿入させることが可能です。

image.png

image.png

そうして作られたリストに新しい値を追加するには、names.add(<追加したい値>);というふうにします。
今回はnames.add("園田海未");,names.add("矢澤にこ");,names.add("松井結");…という感じ。

リストに入れた値を後から取得するときには、names.get(<インデックス>);というようにします。インデックスというのはリストに値を追加した順に振られる数字で、0から始まって1ずつ増えていきます。
先ほどの例ならnames.get(0);で園田海未、names.get(1);で矢澤にこ、names.get(2);で松井結が取得できます。

それとまだ出番はありませんが、names.size();で今リストに入っている値の数をint型で取得することもできます。

それでは、リストを使って先のコードを書きなおしてみましょう。

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;

public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        List<String> names = new ArrayList<>();

        names.add("園田海未");
        names.add("矢澤にこ");
        names.add("松井結");
        names.add("熊澤枝里子");
        names.add("中村明花");

        int i = 0;

        System.out.println("[" + i + "] " + names.get(i));
        i++;

        System.out.println("[" + i + "] " + names.get(i));
        i++;

        System.out.println("[" + i + "] " + names.get(i));
        i++;

        System.out.println("[" + i + "] " + names.get(i));
        i++;

        System.out.println("[" + i + "] " + names.get(i));
        i++;
    }
}

リストを使うことでコードはむしろ長くなってしまいましたが、ここでのキモは繰り返し部分です。
以前は各行違った名前が書かれていた最後の数行が、完全に同じ内容の繰り返しになったことに注目してください。
このようにリストを書けば繰り返し処理が書きやすくなるし、後のループを使えば書く量だって減らせます。

ループについて

ループというのは、繰り返し処理をするための仕組みです。
いくつか方法がありますが、まずはwhile文というのを使ってみます。

while (<条件式>) {
  <条件式の結果がtrueの間繰り返されるコードブロック>
}

image.png

前項のサンプルコードでは、リストのi番目に入っている名前を取得する処理を、iの値が0から4までの範囲で繰り返していました。
while文を使って書き直すと次のようになります。

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;

public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        List<String> names = new ArrayList<>();

        names.add("園田海未");
        names.add("矢澤にこ");
        names.add("松井結");
        names.add("熊澤枝里子");
        names.add("中村明花");

        int i = 0;
        while (i <= 4) {
            System.out.println("[" + i + "] " + names.get(i));
            i++;
        }
    }
}

while文の中でこんな処理が流れていくのが想像できるでしょうか。

  1. iの値は0(初期値)。0 <= 4はtrueなのでコードブロックに入る
    1. リストの0番目のインデックスにある名前(園田海未)がコンソールに出力される
    2. iの値に1が足される。
  2. iの値は1。1 <= 4はtrueなのでコードブロックに入る
    1. リストの1番目のインデックスにある名前(矢澤にこ)がコンソールに出力される
    2. iの値に1が足される。
  3. iの値は2。2 <= 4はtrueなのでコードブロックに入る
    1. リストの2番目のインデックスにある名前(松井結)がコンソールに出力される
    2. iの値に1が足される。
  4. iの値は3。3 <= 4はtrueなのでコードブロックに入る
    1. リストの3番目のインデックスにある名前(熊澤枝里子)がコンソールに出力される
    2. iの値に1が足される。
  5. iの値は4。4 <= 4はtrueなのでコードブロックに入る
    1. リストの4番目のインデックスにある名前(中村明花)がコンソールに出力される
    2. iの値に1が足される。
  6. iの値は5。5 <= 4はfalseなのでwhile文を抜ける

これだけの繰り返し処理を、短くまとめて綺麗に書くことができました。
またこのやり方なら番号のフォーマットを変えたくなったときでもコードの変更は1行だけで済むこともわかるでしょう。

ただ一つ惜しいのが、これだと今後リストに入れる名前の数が増えたとき、その度に条件式のi <= 4i <= 5とかi <= 10に書き直さないといけないことです。

そういうわけで今こそnames.size();の出番です。
iの値は0から、リストの長さよりも1小さい数字までの範囲で変化させれば良いわけですから…

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;

public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        List<String> names = new ArrayList<>();

        names.add("園田海未");
        names.add("矢澤にこ");
        names.add("松井結");
        names.add("熊澤枝里子");
        names.add("中村明花");

        int i = 0;
        while (i < names.size()) {
            System.out.println("[" + i + "] " + names.get(i));
            i++;
        }
    }
}

条件式に注目してください。
これで、リストの長さが変わってもコードの変更は必要なくなりました。

このようにしてできるだけ値を決め打ちするようなコードは無くし、後に事情が変わってもコードの変更は最小限で済むようなコードに書くのがプログラマーの美徳です。覚えておきましょう。

それとループでは大抵の場合、ループ用の変数をあらかじめ初期化する処理(今回はint i = 0;)と、ループ1回ごとにその変数を書き換える処理(今回はi++;)が書かれます。

そういう処理をもっと簡単に1行で書くためにfor文というのもあって、これは次のようにして使います。

for (<ループの開始前に実行されるコード>; <条件式>; <ループ1回ごとに実行されるコード>) {
  <条件式の結果がtrueの間繰り返されるコードブロック>
}

image.png

先ほどのコードをfor文で書き換えるとこのようになります。

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;

public class OppaiFinder {
    public static void main(String[] args) {
        System.out.println("Aカップの女性タレント一覧:");

        // 名前の一覧を収納するリストを初期化
        List<String> names = new ArrayList<>();

        // 名前をリストに追加
        names.add("園田海未");
        names.add("矢澤にこ");
        names.add("松井結");
        names.add("熊澤枝里子");
        names.add("中村明花");

        // リスト中の名前をインデックスと共にコンソールに出力する
        for (int i = 0; i < names.size(); i++) {
            System.out.println("[" + i + "] " + names.get(i));
        }
    }
}

while文より短く、さらに綺麗に書くことができました。

コメントについて

少し話は逸れますが、変数やループなどコードの内容が徐々に複雑になってきました。ここらでコメントについて覚えましょう。

// コメントの内容

あるいは

/*
 * コメント
 * の
 * 内容
 */

このように書くことでコード中に注釈やメモなどを書くことができます。
コードと言うのは長くなるにつけ「どこで何をしてるか」を忘れがちになるので、一目で何してるかわかるような部分以外にはできるだけコメントをつけるようにしておいたほうが良いです。

image.png
image.png

それと今まで、何につけてもなるべく日本語は使わないようにと書いてきました。
ただコメントに関しては、どうせコードとしては実行されないことからトラブルの元にはなりにくく、そこまで英語で書くのもさすがに大変という事情から、日本語で書くことも多いです。

6. 入力を受け付けてみる

出力の次は入力です。

好みのカップサイズとして「A」「B」「C」のような文字列の入力を受け付けることを考えます。

コンソールからの入力を受け付ける方法にはいくつかあるのですが、Scannerクラスを使うことが多いように思います。

クラスについて

新しくクラスという言葉が出てきました。
最初にOppaiFinderクラスを作って以来ですが、今まで出てきたStringやArrayListだって実はJavaが元々用意してくれているクラスです。

クラスというのは、よく設計図に例えられます。

前項に出てきたデータ型の一つにString型というのがありましたが、そのString型というのがどんなものか定義してる設計図がStringクラスだと思ってください。

(ただややこしい話ですがデータ型の全てがクラスによって定義されているわけではありません。データ型の項に書いた表でString型のことを参照型と書きましたが、String型のようにクラスによって定義されている型を参照型、int型のようにクラスとかそういうの無しに存在する型のことをプリミティブ型と言っています。)

クラスは、その設計図で表したいもの(Stringなら文字列)を表すのにどんな変数を保持すれば良いかを示しています。そういう変数1つ1つのことをメンバ変数、あるいはフィールドといいます。

さらに、その設計図で表されるものに対してよく行う処理(Stringなら文字列を分割したり、一部を置換したりする処理)に必要なコードがあらかじめまとめてあります。そういうコードのまとまり1つ1つのことをメソッドといいます。

メンバ変数とメソッドは後にも出てきますから覚えておきましょう。

それともう一つ、間違えがちですが覚えておかなければならないのが、クラスはあくまで設計図であること。
Stringクラスは文字列の設計図であって、文字列そのものではありません。

実体としての文字列というか、文字列そのものを作る時には
new String(new char[] {'あ', 'い', 'う', 'え', 'お'});
のようにnew演算子を使ってコンストラクタという専用のメソッドを呼び出し、「新しく文字列を作るよー」ということを示します。

image.png

今まではnewなんて使わず単に"あいうえお";って書いてたじゃねえかと思われるかもしれません。その辺もちょっとややこしい話なのですが、newを使うのが本来の書き方で、Stringクラスだけしょっちゅう使うので特別に省略した書き方が許されてるんだと思ってください。

そうやってクラスから作られる実体のことをインスタンスと言います。

インスタンスは大抵の場合、
String str = new String(new char[] {'あ', 'い', 'う', 'え', 'お'});
のようにしてそのクラスが定義する参照型の変数に代入されます。

Scannerクラスを使って入力を受け付けてみる

そんなクラスの一つに「どっかからの入力を受け付けてくれるやつ」の設計図ことScanner(java.util.Scanner)クラスというのがあります。これはコンソールからの入力を受け付けるときに便利です。

Scannerクラスのコンストラクタからインスタンスを作り、scannerという名前の変数に代入するには、
Scanner scanner = new Scanner(System.in);
のように書きます。

System.inというのはコンソールからの入力を表します。今までよく出てきたSystem.outの逆と思えばわかりやすいでしょう。

Scannerクラスは文字を読みとるときに便利なように、入力を1行分読み込んでその内容をString型で返す、nextLineというメソッドを定義してくれています。

image.png

メソッドを呼び出すには<インスタンス>.<メソッド名>(<引数1>, <引数2>…);のように書きます。引数というのはメソッドの呼び出し元からメソッドへ何かの値を渡せる仕組みですが、nextLineメソッドは引数をとらないので今回は
scanner.nextLine();
と書きます。

このメソッドの実行結果(戻り値といいます)はString型で返ってくるので、
String input = scanner.nextLine();
のようにしてString型の変数に代入すれば良いでしょう。

それとScannerのような入出力系クラスのインスタンスは大抵の場合、必要な読み出しが終わったら
scanner.close();
というふうにcloseメソッドを呼び出して、もうこのインスタンスいらないよということを明示的に示す必要があります。

そういうわけで好みのカップサイズの入力を受け付けるコードを追加してみましょう。

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class OppaiFinder {
    public static void main(String[] args) {
        // Scannerを初期化
        Scanner scanner = new Scanner(System.in);

        // 好みのカップサイズの入力を受け付ける
        System.out.println("好みのカップサイズ(A,B,C…)を入力してください");
        String inputCupSize = scanner.nextLine();
        System.out.println(inputCupSize + "カップの女性タレント一覧:");

        // 名前の一覧を収納するリストを初期化
        List<String> names = new ArrayList<>();

        // 名前をリストに追加
        names.add("園田海未");
        names.add("矢澤にこ");
        names.add("松井結");
        names.add("熊澤枝里子");
        names.add("中村明花");

        // リスト中の名前をインデックスと共にコンソールに出力する
        for (int i = 0; i < names.size(); i++) {
            System.out.println("[" + i + "] " + names.get(i));
        }

        // Scannerを閉じる
        scanner.close();
    }
}

image.png

まだ入力の内容によって出力を分岐するようなコードは実装していないのでどんなカップサイズを入力しても同じ出力ですが、とりあえずのところカップサイズの入力を受け付ける部分は実装できました。

7. 独自のクラスを作ってみる

ここで、今回扱う女性タレントのプロフィール一覧を表で示します。

  • プロフィール一覧
age birthDayOfMonth birthMonth birthYear bustSize class cupSize height hipSize name voiceBy waistSize weight
33 10 12 1986 81.0 class FemaleRealTalent C 153.0 88.0 新田恵海 60.0 40.0
16 3 8 78.0 class FemaleAnimeTalent B 157.0 82.0 高坂穂乃果 新田恵海 58.0 47.2
33 28 6 1986 80.0 class FemaleRealTalent C 160.0 86.0 三森すずこ 58.0 45.0
16 15 3 76.0 class FemaleAnimeTalent A 159.0 80.0 園田海未 三森すずこ 58.0 46.7
31 2 5 1988 86.0 class FemaleRealTalent F 158.0 87.0 Pile 60.0 40.0
15 19 4 78.0 class FemaleAnimeTalent B 161.0 83.0 西木野真姫 Pile 56.0 47.7
30 26 12 1989 82.0 class FemaleRealTalent C 159.0 82.0 徳井青空 56.0 40.0
17 22 7 74.0 class FemaleAnimeTalent A 154.0 79.0 矢澤にこ 徳井青空 57.0 44.5
45 27 5 1974 91.0 class FemaleRealTalent FF 160.0 97.0 ヴァネッサ・ブルー 71.0 55.0
52 26 8 1967 102.0 class FemaleRealTalent FF 175.0 86.0 ケリー・マディソン 61.0 61.0
41 7 8 1978 107.0 class FemaleRealTalent HH 160.0 86.0 リンゼイ・ドーン・マッケンジー 56.0 51.0
24 12 10 1995 75.0 class FemaleRealTalent AA 166.0 84.0 岩瀬唯奈 54.0 44.0
34 8 3 1985 102.0 class FemaleRealTalent EE 164.0 88.0 エヴァ・ソネット 62.0 52.0
57 24 2 1962 91.0 class FemaleRealTalent DD 170.0 89.0 テリー・ウィーゲル 58.0 54.0
45 14 1 1975 86.0 class FemaleRealTalent DD 165.0 86.0 テイラー・ヘイズ 56.0 48.0
52 28 11 1967 97.0 class FemaleRealTalent DD 180.0 97.0 アンナ・ニコル・スミス 66.0 64.0
35 6 4 1984 77.0 class FemaleRealTalent A 169.0 83.0 松井結 58.0 48.0
34 15 11 1985 83.0 class FemaleRealTalent A 170.0 84.0 熊澤枝里子 58.0 49.0
33 17 4 1986 82.0 class FemaleRealTalent A 175.0 87.0 中村明花 58.0 53.0
48 7 2 1972 86.0 class FemaleRealTalent B 163.0 86.0 ステファニー・スウィフト 64.0 55.0
44 29 9 1975 91.0 class FemaleRealTalent B 162.0 91.0 アヴァ・ヴィンセント 66.0 54.0
39 22 10 1980 84.0 class FemaleRealTalent B 172.0 86.0 ソニア・スイ 58.0 48.0
51 21 7 1968 91.0 class FemaleRealTalent C 168.0 81.0 ラクエル・デーリアン 61.0 53.0
35 21 6 1984 91.0 class FemaleRealTalent C 174.0 91.0 アリシア・アリガッティ 66.0 54.0
32 3 10 1987 83.0 class FemaleRealTalent C 158.0 91.0 坪木菜果 60.0 45.0
37 16 3 1982 86.0 class FemaleRealTalent D 167.0 87.0 土性愛 60.0 48.0
49 26 5 1970 91.0 class FemaleRealTalent D 165.0 91.0 カイリー・アイルランド 66.0 59.0
35 28 7 1984 88.0 class FemaleRealTalent D 166.0 87.0 伊藤かな 59.0 40.0
70 26 1 1950 99.0 class FemaleRealTalent DDD 168.0 91.0 ジャネット・ルポー 61.0 57.0
42 10 7 1977 88.0 class FemaleRealTalent E 163.0 86.0 西宮七海 58.0 46.0
38 19 12 1981 88.0 class FemaleRealTalent E 173.0 88.0 佐藤江梨子 58.0 53.0
26 31 1 1994 84.0 class FemaleRealTalent E 156.0 85.0 秋田知里 58.0 42.8
37 11 3 1982 93.0 class FemaleRealTalent F 160.0 86.0 郷司利也子 60.0 50.0
33 10 10 1986 88.0 class FemaleRealTalent F 160.0 86.0 秋山優 60.0 47.0
36 4 11 1983 90.0 class FemaleRealTalent G 157.0 90.0 いとうあこ 60.0 45.0
29 12 9 1990 90.0 class FemaleRealTalent G 157.0 86.0 黒田万結花 58.0 43.0
24 27 3 1995 89.0 class FemaleRealTalent G 163.0 86.0 相原美咲 56.0 42.0
26 16 1 1994 95.0 class FemaleRealTalent H 163.0 93.0 夏来唯 70.0 58.0
29 1 10 1990 95.0 class FemaleRealTalent H 160.0 89.0 青井鈴音 60.0 50.0
36 14 6 1983 85.0 class FemaleRealTalent H 160.0 83.0 三林千夏 58.0 43.0
39 28 7 1980 103.0 class FemaleRealTalent I 166.0 88.0 根本はるみ 60.0 58.0
34 30 1 1986 102.0 class FemaleRealTalent I 168.0 89.0 ヨルダン・カーヴァー 61.0 54.9
39 25 6 1980 98.0 class FemaleRealTalent I 158.0 86.0 竹内希実 60.0 45.0
35 15 10 1984 95.0 class FemaleRealTalent J 147.0 85.0 花井美理 58.0 39.0
26 20 3 1993 112.0 class FemaleRealTalent J 162.0 99.0 うさまりあ 99.0 176.0
37 2 2 1983 100.0 class FemaleRealTalent J 166.0 85.0 田辺はるか 58.0 45.0
51 18 9 1968 97.0 class FemaleRealTalent K 160.0 91.0 クロエ・ヴェヴリエ 66.0 55.0
36 7 2 1984 115.0 class FemaleRealTalent L 165.0 85.0 松坂南 58.0 47.0
32 20 7 1987 108.0 class FemaleRealTalent L 158.0 88.0 伊藤杏奈 58.0 42.0
  • 各カラム(列)の意味
カラム名 意味
age 年齢
birthDayOfMonth 誕生日(0~31)
birthMonth 誕生月(1~12)
birthYear 誕生年
bustSize スリーサイズのうちのバストサイズ。単位はcm
class (後述)
cupSize カップサイズ
height 身長。単位はcm
hipSize スリーサイズのうちのヒップサイズ。単位はcm
name 名前
voiceBy 中の人
waistSize スリーサイズのうちのウエストサイズ。単位はcm
weight 体重。単位はkg

今までは女性タレントの名前だけを扱ってきましたが、こうして見るとプロフィールの情報というのは多岐にわたります。特にカップサイズは名前と並んで今回のプログラムに絶対必要です。

今後の実装のためにはこれらの値をリストに収納しなければならないわけですが、ここでやってしまいがちなあまり良くない方法がこちら。表の各カラムでそれぞれ別のリストを作ってしまうという方法です。

悪い例1
OppaiFinder.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class OppaiFinder {
    public static void main(String[] args) {
        // Scannerを初期化
        Scanner scanner = new Scanner(System.in);

        // 好みのカップサイズの入力を受け付ける
        System.out.println("好みのカップサイズ(A,B,C…)を入力してください");
        String inputCupSize = scanner.nextLine();
        System.out.println(inputCupSize + "カップの女性タレント一覧:");

        // 各リストを初期化
        List<String> names = new ArrayList<>(); // 名前
        List<Integer> birthMonths = new ArrayList<>(); // 誕生月
        List<Integer> birthDayOfMonths = new ArrayList<>(); // 誕生日
        List<Double> heights = new ArrayList<>(); // 身長
        List<Double> weights = new ArrayList<>(); // 体重
        List<Double> bustSizes = new ArrayList<>(); // バストサイズ
        List<Double> waistSizes = new ArrayList<>(); // ウエストサイズ
        List<Double> hipSizes = new ArrayList<>(); // ヒップサイズ
        List<String> cupSizes = new ArrayList<>(); // カップサイズ

        names.add("新田恵海");
        birthMonths.add(12);
        birthDayOfMonths.add(10);
        heights.add(153.0);
        weights.add(40.0);
        bustSizes.add(81.0);
        waistSizes.add(60.0);
        hipSizes.add(88.0);
        cupSizes.add("C");

        names.add("高坂穂乃果");
        birthMonths.add(8);
        birthDayOfMonths.add(3);
        heights.add(157.0);
        weights.add(47.2);
        bustSizes.add(78.0);
        waistSizes.add(58.0);
        hipSizes.add(82.0);
        cupSizes.add("B");

        names.add("三森すずこ");
        birthMonths.add(6);
        birthDayOfMonths.add(28);
        heights.add(160.0);
        weights.add(45.0);
        bustSizes.add(80.0);
        waistSizes.add(58.0);
        hipSizes.add(86.0);
        cupSizes.add("C");

        names.add("園田海未");
        birthMonths.add(3);
        birthDayOfMonths.add(15);
        heights.add(159.0);
        weights.add(46.7);
        bustSizes.add(76.0);
        waistSizes.add(58.0);
        hipSizes.add(80.0);
        cupSizes.add("A");

        names.add("Pile");
        birthMonths.add(5);
        birthDayOfMonths.add(2);
        heights.add(158.0);
        weights.add(40.0);
        bustSizes.add(86.0);
        waistSizes.add(60.0);
        hipSizes.add(87.0);
        cupSizes.add("F");

        names.add("西木野真姫");
        birthMonths.add(4);
        birthDayOfMonths.add(19);
        heights.add(161.0);
        weights.add(47.7);
        bustSizes.add(78.0);
        waistSizes.add(56.0);
        hipSizes.add(83.0);
        cupSizes.add("B");

        names.add("徳井青空");
        birthMonths.add(12);
        birthDayOfMonths.add(26);
        heights.add(159.0);
        weights.add(40.0);
        bustSizes.add(82.0);
        waistSizes.add(56.0);
        hipSizes.add(82.0);
        cupSizes.add("C");

        names.add("矢澤にこ");
        birthMonths.add(7);
        birthDayOfMonths.add(22);
        heights.add(154.0);
        weights.add(44.5);
        bustSizes.add(74.0);
        waistSizes.add(57.0);
        hipSizes.add(79.0);
        cupSizes.add("A");

        names.add("ヴァネッサ・ブルー");
        birthMonths.add(5);
        birthDayOfMonths.add(27);
        heights.add(160.0);
        weights.add(55.0);
        bustSizes.add(91.0);
        waistSizes.add(71.0);
        hipSizes.add(97.0);
        cupSizes.add("FF");

        names.add("ケリー・マディソン");
        birthMonths.add(8);
        birthDayOfMonths.add(26);
        heights.add(175.0);
        weights.add(61.0);
        bustSizes.add(102.0);
        waistSizes.add(61.0);
        hipSizes.add(86.0);
        cupSizes.add("FF");

        names.add("リンゼイ・ドーン・マッケンジー");
        birthMonths.add(8);
        birthDayOfMonths.add(7);
        heights.add(160.0);
        weights.add(51.0);
        bustSizes.add(107.0);
        waistSizes.add(56.0);
        hipSizes.add(86.0);
        cupSizes.add("HH");

        names.add("岩瀬唯奈");
        birthMonths.add(10);
        birthDayOfMonths.add(12);
        heights.add(166.0);
        weights.add(44.0);
        bustSizes.add(75.0);
        waistSizes.add(54.0);
        hipSizes.add(84.0);
        cupSizes.add("AA");

        names.add("エヴァ・ソネット");
        birthMonths.add(3);
        birthDayOfMonths.add(8);
        heights.add(164.0);
        weights.add(52.0);
        bustSizes.add(102.0);
        waistSizes.add(62.0);
        hipSizes.add(88.0);
        cupSizes.add("EE");

        names.add("テリー・ウィーゲル");
        birthMonths.add(2);
        birthDayOfMonths.add(24);
        heights.add(170.0);
        weights.add(54.0);
        bustSizes.add(91.0);
        waistSizes.add(58.0);
        hipSizes.add(89.0);
        cupSizes.add("DD");

        names.add("テイラー・ヘイズ");
        birthMonths.add(1);
        birthDayOfMonths.add(14);
        heights.add(165.0);
        weights.add(48.0);
        bustSizes.add(86.0);
        waistSizes.add(56.0);
        hipSizes.add(86.0);
        cupSizes.add("DD");

        names.add("アンナ・ニコル・スミス");
        birthMonths.add(11);
        birthDayOfMonths.add(28);
        heights.add(180.0);
        weights.add(64.0);
        bustSizes.add(97.0);
        waistSizes.add(66.0);
        hipSizes.add(97.0);
        cupSizes.add("DD");

        names.add("松井結");
        birthMonths.add(4);
        birthDayOfMonths.add(6);
        heights.add(169.0);
        weights.add(48.0);
        bustSizes.add(77.0);
        waistSizes.add(58.0);
        hipSizes.add(83.0);
        cupSizes.add("A");

        names.add("熊澤枝里子");
        birthMonths.add(11);
        birthDayOfMonths.add(15);
        heights.add(170.0);
        weights.add(49.0);
        bustSizes.add(83.0);
        waistSizes.add(58.0);
        hipSizes.add(84.0);
        cupSizes.add("A");

        names.add("中村明花");
        birthMonths.add(4);
        birthDayOfMonths.add(17);
        heights.add(175.0);
        weights.add(53.0);
        bustSizes.add(82.0);
        waistSizes.add(58.0);
        hipSizes.add(87.0);
        cupSizes.add("A");

        names.add("ステファニー・スウィフト");
        birthMonths.add(2);
        birthDayOfMonths.add(7);
        heights.add(163.0);
        weights.add(55.0);
        bustSizes.add(86.0);
        waistSizes.add(64.0);
        hipSizes.add(86.0);
        cupSizes.add("B");

        names.add("アヴァ・ヴィンセント");
        birthMonths.add(9);
        birthDayOfMonths.add(29);
        heights.add(162.0);
        weights.add(54.0);
        bustSizes.add(91.0);
        waistSizes.add(66.0);
        hipSizes.add(91.0);
        cupSizes.add("B");

        names.add("ソニア・スイ");
        birthMonths.add(10);
        birthDayOfMonths.add(22);
        heights.add(172.0);
        weights.add(48.0);
        bustSizes.add(84.0);
        waistSizes.add(58.0);
        hipSizes.add(86.0);
        cupSizes.add("B");

        names.add("ラクエル・デーリアン");
        birthMonths.add(7);
        birthDayOfMonths.add(21);
        heights.add(168.0);
        weights.add(53.0);
        bustSizes.add(91.0);
        waistSizes.add(61.0);
        hipSizes.add(81.0);
        cupSizes.add("C");

        names.add("アリシア・アリガッティ");
        birthMonths.add(6);
        birthDayOfMonths.add(21);
        heights.add(174.0);
        weights.add(54.0);
        bustSizes.add(91.0);
        waistSizes.add(66.0);
        hipSizes.add(91.0);
        cupSizes.add("C");

        names.add("坪木菜果");
        birthMonths.add(10);
        birthDayOfMonths.add(3);
        heights.add(158.0);
        weights.add(45.0);
        bustSizes.add(83.0);
        waistSizes.add(60.0);
        hipSizes.add(91.0);
        cupSizes.add("C");

        names.add("土性愛");
        birthMonths.add(3);
        birthDayOfMonths.add(16);
        heights.add(167.0);
        weights.add(48.0);
        bustSizes.add(86.0);
        waistSizes.add(60.0);
        hipSizes.add(87.0);
        cupSizes.add("D");

        names.add("カイリー・アイルランド");
        birthMonths.add(5);
        birthDayOfMonths.add(26);
        heights.add(165.0);
        weights.add(59.0);
        bustSizes.add(91.0);
        waistSizes.add(66.0);
        hipSizes.add(91.0);
        cupSizes.add("D");

        names.add("伊藤かな");
        birthMonths.add(7);
        birthDayOfMonths.add(28);
        heights.add(166.0);
        weights.add(40.0);
        bustSizes.add(88.0);
        waistSizes.add(59.0);
        hipSizes.add(87.0);
        cupSizes.add("D");

        names.add("ジャネット・ルポー");
        birthMonths.add(1);
        birthDayOfMonths.add(26);
        heights.add(168.0);
        weights.add(57.0);
        bustSizes.add(99.0);
        waistSizes.add(61.0);
        hipSizes.add(91.0);
        cupSizes.add("DDD");

        names.add("西宮七海");
        birthMonths.add(7);
        birthDayOfMonths.add(10);
        heights.add(163.0);
        weights.add(46.0);
        bustSizes.add(88.0);
        waistSizes.add(58.0);
        hipSizes.add(86.0);
        cupSizes.add("E");

        names.add("佐藤江梨子");
        birthMonths.add(12);
        birthDayOfMonths.add(19);
        heights.add(173.0);
        weights.add(53.0);
        bustSizes.add(88.0);
        waistSizes.add(58.0);
        hipSizes.add(88.0);
        cupSizes.add("E");

        names.add("秋田知里");
        birthMonths.add(1);
        birthDayOfMonths.add(31);
        heights.add(156.0);
        weights.add(42.8);
        bustSizes.add(84.0);
        waistSizes.add(58.0);
        hipSizes.add(85.0);
        cupSizes.add("E");

        names.add("郷司利也子");
        birthMonths.add(3);
        birthDayOfMonths.add(11);
        heights.add(160.0);
        weights.add(50.0);
        bustSizes.add(93.0);
        waistSizes.add(60.0);
        hipSizes.add(86.0);
        cupSizes.add("F");

        names.add("秋山優");
        birthMonths.add(10);
        birthDayOfMonths.add(10);
        heights.add(160.0);
        weights.add(47.0);
        bustSizes.add(88.0);
        waistSizes.add(60.0);
        hipSizes.add(86.0);
        cupSizes.add("F");

        names.add("いとうあこ");
        birthMonths.add(11);
        birthDayOfMonths.add(4);
        heights.add(157.0);
        weights.add(45.0);
        bustSizes.add(90.0);
        waistSizes.add(60.0);
        hipSizes.add(90.0);
        cupSizes.add("G");

        names.add("黒田万結花");
        birthMonths.add(9);
        birthDayOfMonths.add(12);
        heights.add(157.0);
        weights.add(43.0);
        bustSizes.add(90.0);
        waistSizes.add(58.0);
        hipSizes.add(86.0);
        cupSizes.add("G");

        names.add("相原美咲");
        birthMonths.add(3);
        birthDayOfMonths.add(27);
        heights.add(163.0);
        weights.add(42.0);
        bustSizes.add(89.0);
        waistSizes.add(56.0);
        hipSizes.add(86.0);
        cupSizes.add("G");

        names.add("夏来唯");
        birthMonths.add(1);
        birthDayOfMonths.add(16);
        heights.add(163.0);
        weights.add(58.0);
        bustSizes.add(95.0);
        waistSizes.add(70.0);
        hipSizes.add(93.0);
        cupSizes.add("H");

        names.add("青井鈴音");
        birthMonths.add(10);
        birthDayOfMonths.add(1);
        heights.add(160.0);
        weights.add(50.0);
        bustSizes.add(95.0);
        waistSizes.add(60.0);
        hipSizes.add(89.0);
        cupSizes.add("H");

        names.add("三林千夏");
        birthMonths.add(6);
        birthDayOfMonths.add(14);
        heights.add(160.0);
        weights.add(43.0);
        bustSizes.add(85.0);
        waistSizes.add(58.0);
        hipSizes.add(83.0);
        cupSizes.add("H");

        names.add("根本はるみ");
        birthMonths.add(7);
        birthDayOfMonths.add(28);
        heights.add(166.0);
        weights.add(58.0);
        bustSizes.add(103.0);
        waistSizes.add(60.0);
        hipSizes.add(88.0);
        cupSizes.add("I");

        names.add("ヨルダン・カーヴァー");
        birthMonths.add(1);
        birthDayOfMonths.add(30);
        heights.add(168.0);
        weights.add(54.9);
        bustSizes.add(102.0);
        waistSizes.add(61.0);
        hipSizes.add(89.0);
        cupSizes.add("I");

        names.add("竹内希実");
        birthMonths.add(6);
        birthDayOfMonths.add(25);
        heights.add(158.0);
        weights.add(45.0);
        bustSizes.add(98.0);
        waistSizes.add(60.0);
        hipSizes.add(86.0);
        cupSizes.add("I");

        names.add("花井美理");
        birthMonths.add(10);
        birthDayOfMonths.add(15);
        heights.add(147.0);
        weights.add(39.0);
        bustSizes.add(95.0);
        waistSizes.add(58.0);
        hipSizes.add(85.0);
        cupSizes.add("J");

        names.add("うさまりあ");
        birthMonths.add(3);
        birthDayOfMonths.add(20);
        heights.add(162.0);
        weights.add(176.0);
        bustSizes.add(112.0);
        waistSizes.add(99.0);
        hipSizes.add(99.0);
        cupSizes.add("J");

        names.add("田辺はるか");
        birthMonths.add(2);
        birthDayOfMonths.add(2);
        heights.add(166.0);
        weights.add(45.0);
        bustSizes.add(100.0);
        waistSizes.add(58.0);
        hipSizes.add(85.0);
        cupSizes.add("J");

        names.add("クロエ・ヴェヴリエ");
        birthMonths.add(9);
        birthDayOfMonths.add(18);
        heights.add(160.0);
        weights.add(55.0);
        bustSizes.add(97.0);
        waistSizes.add(66.0);
        hipSizes.add(91.0);
        cupSizes.add("K");

        names.add("松坂南");
        birthMonths.add(2);
        birthDayOfMonths.add(7);
        heights.add(165.0);
        weights.add(47.0);
        bustSizes.add(115.0);
        waistSizes.add(58.0);
        hipSizes.add(85.0);
        cupSizes.add("L");

        names.add("伊藤杏奈");
        birthMonths.add(7);
        birthDayOfMonths.add(20);
        heights.add(158.0);
        weights.add(42.0);
        bustSizes.add(108.0);
        waistSizes.add(58.0);
        hipSizes.add(88.0);
        cupSizes.add("L");

        // リスト中の名前をインデックスと共にコンソールに出力する
        for (int i = 0; i < names.size(); i++) {
            System.out.println("[" + i + "] " + names.get(i));
        }

        // Scannerを閉じる
        scanner.close();
    }
}

あるいはこちら。配列(長さの決まったリスト的なもの)型のリストを作って収納する方法です。

悪い例2
OppaiFinder.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class OppaiFinder {
    public static void main(String[] args) {
        // Scannerを初期化
        Scanner scanner = new Scanner(System.in);

        // 好みのカップサイズの入力を受け付ける
        System.out.println("好みのカップサイズ(A,B,C…)を入力してください");
        String inputCupSize = scanner.nextLine();
        System.out.println(inputCupSize + "カップの女性タレント一覧:");

        // リストを初期化
        List<String[]> talents = new ArrayList();
        talents.add(new String[] { "新田恵海", "12", "10", "153.0", "40.0", "81.0", "60.0", "88.0", "C" });
        talents.add(new String[] { "高坂穂乃果", "8", "3", "157.0", "47.2", "78.0", "58.0", "82.0", "B" });
        talents.add(new String[] { "三森すずこ", "6", "28", "160.0", "45.0", "80.0", "58.0", "86.0", "C" });
        talents.add(new String[] { "園田海未", "3", "15", "159.0", "46.7", "76.0", "58.0", "80.0", "A" });
        talents.add(new String[] { "Pile", "5", "2", "158.0", "40.0", "86.0", "60.0", "87.0", "F" });
        talents.add(new String[] { "西木野真姫", "4", "19", "161.0", "47.7", "78.0", "56.0", "83.0", "B" });
        talents.add(new String[] { "徳井青空", "12", "26", "159.0", "40.0", "82.0", "56.0", "82.0", "C" });
        talents.add(new String[] { "矢澤にこ", "7", "22", "154.0", "44.5", "74.0", "57.0", "79.0", "A" });
        talents.add(new String[] { "ヴァネッサ・ブルー", "5", "27", "160.0", "55.0", "91.0", "71.0", "97.0", "FF" });
        talents.add(new String[] { "ケリー・マディソン", "8", "26", "175.0", "61.0", "102.0", "61.0", "86.0", "FF" });
        talents.add(new String[] { "リンゼイ・ドーン・マッケンジー", "8", "7", "160.0", "51.0", "107.0", "56.0", "86.0", "HH" });
        talents.add(new String[] { "岩瀬唯奈", "10", "12", "166.0", "44.0", "75.0", "54.0", "84.0", "AA" });
        talents.add(new String[] { "エヴァ・ソネット", "3", "8", "164.0", "52.0", "102.0", "62.0", "88.0", "EE" });
        talents.add(new String[] { "テリー・ウィーゲル", "2", "24", "170.0", "54.0", "91.0", "58.0", "89.0", "DD" });
        talents.add(new String[] { "テイラー・ヘイズ", "1", "14", "165.0", "48.0", "86.0", "56.0", "86.0", "DD" });
        talents.add(new String[] { "アンナ・ニコル・スミス", "11", "28", "180.0", "64.0", "97.0", "66.0", "97.0", "DD" });
        talents.add(new String[] { "松井結", "4", "6", "169.0", "48.0", "77.0", "58.0", "83.0", "A" });
        talents.add(new String[] { "熊澤枝里子", "11", "15", "170.0", "49.0", "83.0", "58.0", "84.0", "A" });
        talents.add(new String[] { "中村明花", "4", "17", "175.0", "53.0", "82.0", "58.0", "87.0", "A" });
        talents.add(new String[] { "ステファニー・スウィフト", "2", "7", "163.0", "55.0", "86.0", "64.0", "86.0", "B" });
        talents.add(new String[] { "アヴァ・ヴィンセント", "9", "29", "162.0", "54.0", "91.0", "66.0", "91.0", "B" });
        talents.add(new String[] { "ソニア・スイ", "10", "22", "172.0", "48.0", "84.0", "58.0", "86.0", "B" });
        talents.add(new String[] { "ラクエル・デーリアン", "7", "21", "168.0", "53.0", "91.0", "61.0", "81.0", "C" });
        talents.add(new String[] { "アリシア・アリガッティ", "6", "21", "174.0", "54.0", "91.0", "66.0", "91.0", "C" });
        talents.add(new String[] { "坪木菜果", "10", "3", "158.0", "45.0", "83.0", "60.0", "91.0", "C" });
        talents.add(new String[] { "土性愛", "3", "16", "167.0", "48.0", "86.0", "60.0", "87.0", "D" });
        talents.add(new String[] { "カイリー・アイルランド", "5", "26", "165.0", "59.0", "91.0", "66.0", "91.0", "D" });
        talents.add(new String[] { "伊藤かな", "7", "28", "166.0", "40.0", "88.0", "59.0", "87.0", "D" });
        talents.add(new String[] { "ジャネット・ルポー", "1", "26", "168.0", "57.0", "99.0", "61.0", "91.0", "DDD" });
        talents.add(new String[] { "西宮七海", "7", "10", "163.0", "46.0", "88.0", "58.0", "86.0", "E" });
        talents.add(new String[] { "佐藤江梨子", "12", "19", "173.0", "53.0", "88.0", "58.0", "88.0", "E" });
        talents.add(new String[] { "秋田知里", "1", "31", "156.0", "42.8", "84.0", "58.0", "85.0", "E" });
        talents.add(new String[] { "郷司利也子", "3", "11", "160.0", "50.0", "93.0", "60.0", "86.0", "F" });
        talents.add(new String[] { "秋山優", "10", "10", "160.0", "47.0", "88.0", "60.0", "86.0", "F" });
        talents.add(new String[] { "いとうあこ", "11", "4", "157.0", "45.0", "90.0", "60.0", "90.0", "G" });
        talents.add(new String[] { "黒田万結花", "9", "12", "157.0", "43.0", "90.0", "58.0", "86.0", "G" });
        talents.add(new String[] { "相原美咲", "3", "27", "163.0", "42.0", "89.0", "56.0", "86.0", "G" });
        talents.add(new String[] { "夏来唯", "1", "16", "163.0", "58.0", "95.0", "70.0", "93.0", "H" });
        talents.add(new String[] { "青井鈴音", "10", "1", "160.0", "50.0", "95.0", "60.0", "89.0", "H" });
        talents.add(new String[] { "三林千夏", "6", "14", "160.0", "43.0", "85.0", "58.0", "83.0", "H" });
        talents.add(new String[] { "根本はるみ", "7", "28", "166.0", "58.0", "103.0", "60.0", "88.0", "I" });
        talents.add(new String[] { "ヨルダン・カーヴァー", "1", "30", "168.0", "54.9", "102.0", "61.0", "89.0", "I" });
        talents.add(new String[] { "竹内希実", "6", "25", "158.0", "45.0", "98.0", "60.0", "86.0", "I" });
        talents.add(new String[] { "花井美理", "10", "15", "147.0", "39.0", "95.0", "58.0", "85.0", "J" });
        talents.add(new String[] { "うさまりあ", "3", "20", "162.0", "176.0", "112.0", "99.0", "99.0", "J" });
        talents.add(new String[] { "田辺はるか", "2", "2", "166.0", "45.0", "100.0", "58.0", "85.0", "J" });
        talents.add(new String[] { "クロエ・ヴェヴリエ", "9", "18", "160.0", "55.0", "97.0", "66.0", "91.0", "K" });
        talents.add(new String[] { "松坂南", "2", "7", "165.0", "47.0", "115.0", "58.0", "85.0", "L" });
        talents.add(new String[] { "伊藤杏奈", "7", "20", "158.0", "42.0", "108.0", "58.0", "88.0", "L" });

        // リスト中の名前をインデックスと共にコンソールに出力する
        for (int i = 0; i < talents.size(); i++) {
            System.out.println("[" + i + "] " + talents.get(i)[0]);
        }

        // Scannerを閉じる
        scanner.close();
    }
}

どちらもまあ確かにJavaに元々ある型だけで実装しようとすればそうなるのですが、前者の方法ではリストの数が著しく増える上に、一回でもリストへの追加忘れでもすればインデックスがずれて大変なことになります。後者の方法では配列のどこに何の値が入ってるかが解り難く、また本来int型やdouble型で表すべき数字までString型にしてしまっています。

もっと良いやり方は、クラスを使うという方法です。
前項で解説した通り、クラスは設計図です。何かを表すために必要な変数ことメンバ変数を複数保持できます。

つまり「女性タレントを表すクラス」を自分で作って、そのメンバ変数として年齢、誕生日、スリーサイズなどを保持させれば良いのです。
そしてそのクラスのインスタンスをリストに収めるようにすれば、作るリストは1つで良く、配列のどこに何の値が…の問題も起きません。
コードはとても分かりやすくなるでしょう。

独自クラスを作る

というわけで新しいクラスを作りましょう。

image.png

画面左にある「パッケージエクスプローラ」でoppai-finderプロジェクトを選択した状態で、[ファイル(F)]->[新規(N)]->[クラス(C)]をクリックしてください。

image.png

クラスの名前を決めます。
前回OppaiFinderクラスを作った時はとりあえずそういう名前にしてもらいましたが、クラスにも命名規則があります。

  • 以下の種類の文字だけを使って名付ける。
    • アルファベットの半角文字(a,b,cまたはA,B,C…)
    • 半角数字(0,1,2…)
  • 最初の1文字は大文字で始め、以降各単語の先頭文字を大文字、それ以外を小文字にする。
  • 必ず単数形の名前にする。
  • 「Test」「Sample」「Class1」などの行き当たりばったりな名前は避け、一目見て何のことかわかるような名前にする。

Q. なぜクラス名の決め方にそんな決まり事を作るの?

A. そのほうが楽だから。

変数名の決め方とほとんど全く同じ理由です。
バグ避けのために日本語の使用は避け、読みやすさのために各単語の先頭文字を大文字にし、さらに変数との区別のために一番最初の文字も大文字にし、ごちゃごちゃ防止のために一目見て何のことかわかるような名前にします。

また必ず単数形の名前をつける理由としては、クラスが設計図であるからです。ArrayListのように「中身に複数の値が入る」ものの設計図はあれど、設計図それ自体は常に単数のものです。単数のものには単数形の名前をつけないと意味不明なことになります。

女性タレントを表すクラスなので、名前はそのままFemaleTalentにしましょう。

クラスが正常に作成できたら、FemaleTalent.javaというファイルが作成され、その中身はこうなっていると思います。

FemaleTalent.java
public class FemaleTalent {

}

次はこのクラスにメンバ変数を定義してみましょう。

メンバ変数を定義する

image.png

クラスの持つメンバ変数を定義するには、class FemaleTalent {の次の行から始まるコードブロックにprivate <メンバ変数の型> <メンバ変数の名前>;という文を書きます。

何てことなく、普通の変数(ローカル変数)を定義する文の先頭にprivateがついただけです。
これはアクセス修飾子といって、メンバ変数やメソッドを他のクラスから直接読み書き(アクセス)可能にするかどうかを示します。

  • メンバ変数やメソッドに対して使う場合の各アクセス修飾子の意味
アクセス修飾子 アクセス可能なクラス
private 自クラスの中からだけアクセス可能
protected 自クラスと同じパッケージ(クラスをまとめるもの)にあるクラスか、子クラスからだけアクセス可能
public どこからでもアクセス可能
(何も書かない) 自クラスと同じパッケージにあるクラスからだけアクセス可能

4種類ありますが、メンバ変数に対しては基本的にprivateを使い、他のクラスからアクセス可能にしたいときでもprotectedやpublicにはせず、getter・setterというメソッドを作ることで対応します。
getter・setterについての解説は後に回すので、とりあえずメンバ変数を定義してみましょう。

女性タイトルの一覧表に記した各カラムの値をそのままメンバ変数にしていきます。なおローカル変数と同じく、メンバ変数についても適切なデータ型を選ばなければなりません。
女性タレントの一覧表にあった各カラムについて、height(身長)・weight(体重)は数字で小数点以下の値もあり得るのでdouble型、name(名前)やcupSize(カップサイズ)は文字列なのでString型…というふうに考えていきます。
なお事情によりage・class・birthYear・voiceByの4つのカラムについては無視してください。理由は後で解説します。

FemaleTalent.java
public class FemaleTalent {
    /**
     * 名前
     */
    private String name;

    /**
     * 誕生月
     */
    private int birthMonth;

    /**
     * 誕生日
     */
    private int birthDayOfMonth;

    /**
     * 身長(cm)
     */
    private double height;

    /**
     * 体重(kg)
     */
    private double weight;

    /**
     * バストサイズ(cm)
     */
    private double bustSize;

    /**
     * ウエストサイズ(cm)
     */
    private double waistSize;

    /**
     * ヒップサイズ(cm)
     */
    private double hipSize;

    /**
     * カップサイズ(A,B,C...)
     */
    private String cupSize;
}

そのメンバ変数を定義したクラスの中でメンバ変数を取得するには、this.<メンバ変数名>というふうにします。

image.png

thisとつけるのはローカル変数との区別のためですが、thisとかなくても区別できるとき(同名のローカル変数が無いとき)には単に<メンバ変数名>と書いて省略可能です。

getter・setterメソッドを作る

image.png

クラスの持つメソッドを定義するには、メンバ変数の定義後、同じコードブロックに次のように書きます。

<アクセス修飾子> <戻り値の型> <メソッド名>(<引数1の型> <引数1の名前>, <引数2の型> <引数2の名前>…) {
  <メソッドが呼び出されたときに実行されるコードブロック>
  return <戻り値>;
}

メソッドの命名規則は次の通りです。

  • 以下の種類の文字だけを使って名付ける。
    • アルファベットの半角文字(a,b,cまたはA,B,C…)
    • 半角数字(0,1,2…)
  • 最初の1文字は小文字で始め、以降各単語の先頭文字を大文字、それ以外を小文字にする。
    • 但しコンストラクタについてはクラス名と同じにする決まりなので大文字から始まる
  • 「test」「sample」「method1」などの行き当たりばったりな名前は避け、一目見て何のことかわかるような名前にする。
    • 特にgetterの名前はget<メンバ変数名>とする
    • 特にsetterの名前はset<メンバ変数名>とする

なんのこっちゃだと思うので実例として、前項に出てきたgetter・setterメソッドを作ってみましょう。

まずgetterというのは「get」の名の通り、メンバ変数の値を取得するためのメソッドです。
メンバ変数birthDayOfMonthのgetterは次のように書きます。

public int getBirthDayOfMonth() {
  return birthDayOfMonth;
}

メソッド名は命名規則に従ってgetBirthDayOfMonth、このメソッドでこれといった処理は必要なく、戻り値としてメンバ変数birthDayOfMonthの値さえそのまま返せば良いのでreturn birthDayOfMonth;とだけ書けばよし。戻り値の型はそのままメンバ変数の型なのでint。それと基本的に言ってgetterのアクセス修飾子はpublicにします。

次にsetterというのはメンバ変数の値を設定するためのメソッドです。
メンバ変数birthDayOfMonthのsetterは次のようになります。

public void setBirthDayOfMonth(int birthDayOfMonth) {
  this.birthDayOfMonth = birthDayOfMonth;
}

戻り値の型がvoid型になっていますが、これは特殊な型で戻り値を返さないことを示します。戻り値を返さないのでreturn文も省略できます。
それとこのメソッドではメンバ変数と同名の引数を受け取っていますが、引数を使えばメソッドはその呼び出し元から何らかの値を受け取ることができ、その値はローカル変数のように使うことができます(引数と言いますがint型やdouble型に限りません)。

他のメンバ変数についてもgetter・setterを定義して、コードはこのようになります。

FemaleTalent.java
public class FemaleTalent {
    /**
     * 名前
     */
    private String name;

    /**
     * 誕生月
     */
    private int birthMonth;

    /**
     * 誕生日
     */
    private int birthDayOfMonth;

    /**
     * 身長(cm)
     */
    private double height;

    /**
     * 体重(kg)
     */
    private double weight;

    /**
     * バストサイズ(cm)
     */
    private double bustSize;

    /**
     * ウエストサイズ(cm)
     */
    private double waistSize;

    /**
     * ヒップサイズ(cm)
     */
    private double hipSize;

    /**
     * カップサイズ(A,B,C...)
     */
    private String cupSize;

    /**
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name セットする name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return birthMonth
     */
    public int getBirthMonth() {
        return birthMonth;
    }

    /**
     * @param birthMonth セットする birthMonth
     */
    public void setBirthMonth(int birthMonth) {
        this.birthMonth = birthMonth;
    }

    /**
     * @return birthDayOfMonth
     */
    public int getBirthDayOfMonth() {
        return birthDayOfMonth;
    }

    /**
     * @param birthDayOfMonth セットする birthDayOfMonth
     */
    public void setBirthDayOfMonth(int birthDayOfMonth) {
        this.birthDayOfMonth = birthDayOfMonth;
    }

    /**
     * @return height
     */
    public double getHeight() {
        return height;
    }

    /**
     * @param height セットする height
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * @return weight
     */
    public double getWeight() {
        return weight;
    }

    /**
     * @param weight セットする weight
     */
    public void setWeight(double weight) {
        this.weight = weight;
    }

    /**
     * @return bustSize
     */
    public double getBustSize() {
        return bustSize;
    }

    /**
     * @param bustSize セットする bustSize
     */
    public void setBustSize(double bustSize) {
        this.bustSize = bustSize;
    }

    /**
     * @return waistSize
     */
    public double getWaistSize() {
        return waistSize;
    }

    /**
     * @param waistSize セットする waistSize
     */
    public void setWaistSize(double waistSize) {
        this.waistSize = waistSize;
    }

    /**
     * @return hipSize
     */
    public double getHipSize() {
        return hipSize;
    }

    /**
     * @param hipSize セットする hipSize
     */
    public void setHipSize(double hipSize) {
        this.hipSize = hipSize;
    }

    /**
     * @return cupSize
     */
    public String getCupSize() {
        return cupSize;
    }

    /**
     * @param cupSize セットする cupSize
     */
    public void setCupSize(String cupSize) {
        this.cupSize = cupSize;
    }

}

image.png

getter・setterはよく作るメソッドなので、eclipseではgetter・setterを作りたい位置にカーソルを置いた状態で[ソース(S)]->[getterおよびsetterの生成(R)]をクリックし、

image.png

getter・setterを作りたいメンバ変数を選択して[作成(R)]をクリックすることでgetter・setterを自動生成することができます。

年齢を取得するメソッド・詳細情報を文字列で取得するメソッドを作る

前項で作成したgetter・setterとは別に、

  • 年齢を取得するgetAgeメソッドと、それと対になるsetAgeメソッド
  • 女性タレントの詳細情報を文字列で取得するtoDetailStringメソッド

を実装しておきます。

image.png

どちらも後に女性タレントの詳細情報をコンソールに出力するときに使います。

例外について

年齢を取得するメソッドを作ると言っといてなんですが、FemaleTalentクラスには年齢を表すageも誕生年を表すbirthYearもメンバ変数として含めなかったので年齢は計算不能です。

じゃあ何でそんなメソッド作るのかという話は後回しにして、そんなときどうしたら良いでしょうか。

「そんな操作(年齢の計算)はサポートされてないよ」などといったことを示す都合の良い仕組みが存在します。例外というものです。

image.png

今回の場合、getAgesetAgeメソッドに
throw new UnsupportedOperationException();
と書くことでUnsupportedOperationException(そんな操作はサポートされてないよ)例外を発生させることができます。

image.png

そして、このメソッドを無理に呼び出そうとすれば…

image.png

エラーが発生し、プログラムの実行は中止されます。

もし、エラーが発生してもプログラムの実行を中止したくない場合は、呼び出し側でtry-catch文というのを書いてエラーハンドリングを行います。

image.png

try {
  <例外が発生しそうなコードブロック>
} catch (<エラーハンドリングの対象にしたい例外の型> <例外のインスタンスが代入される変数>) {
  <例外が発生したときに実行されるコードブロック>
}

ただ、いくつもある例外のうちUnsupportedOperationExceptionは非検査例外といって、「きちんとしたコードを書けば避けられる」例外です。(今回の場合単にgetAgesetAgeを呼び出さなければ例外を回避できる)
非検査例外についてはエラーハンドリング以前にきちんとしたコードを書いて例外の発生を避けるべきとされています。できるだけ使わないようにしましょう。

ただ今回の例では出てきませんが、エラーハンドリングが必須の検査例外というのもあります。try-catch文自体は覚えておくべきでしょう。

そういうわけで年齢を取得するメソッド・詳細情報を文字列で取得するメソッドの実装は次のようになります。

FemaleTalent.java
public class FemaleTalent {
    /**
     * 名前
     */
    private String name;

    /**
     * 誕生月
     */
    private int birthMonth;

    /**
     * 誕生日
     */
    private int birthDayOfMonth;

    /**
     * 身長(cm)
     */
    private double height;

    /**
     * 体重(kg)
     */
    private double weight;

    /**
     * バストサイズ(cm)
     */
    private double bustSize;

    /**
     * ウエストサイズ(cm)
     */
    private double waistSize;

    /**
     * ヒップサイズ(cm)
     */
    private double hipSize;

    /**
     * カップサイズ(A,B,C...)
     */
    private String cupSize;

    /**
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name セットする name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return birthMonth
     */
    public int getBirthMonth() {
        return birthMonth;
    }

    /**
     * @param birthMonth セットする birthMonth
     */
    public void setBirthMonth(int birthMonth) {
        this.birthMonth = birthMonth;
    }

    /**
     * @return birthDayOfMonth
     */
    public int getBirthDayOfMonth() {
        return birthDayOfMonth;
    }

    /**
     * @param birthDayOfMonth セットする birthDayOfMonth
     */
    public void setBirthDayOfMonth(int birthDayOfMonth) {
        this.birthDayOfMonth = birthDayOfMonth;
    }

    /**
     * @return height
     */
    public double getHeight() {
        return height;
    }

    /**
     * @param height セットする height
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * @return weight
     */
    public double getWeight() {
        return weight;
    }

    /**
     * @param weight セットする weight
     */
    public void setWeight(double weight) {
        this.weight = weight;
    }

    /**
     * @return bustSize
     */
    public double getBustSize() {
        return bustSize;
    }

    /**
     * @param bustSize セットする bustSize
     */
    public void setBustSize(double bustSize) {
        this.bustSize = bustSize;
    }

    /**
     * @return waistSize
     */
    public double getWaistSize() {
        return waistSize;
    }

    /**
     * @param waistSize セットする waistSize
     */
    public void setWaistSize(double waistSize) {
        this.waistSize = waistSize;
    }

    /**
     * @return hipSize
     */
    public double getHipSize() {
        return hipSize;
    }

    /**
     * @param hipSize セットする hipSize
     */
    public void setHipSize(double hipSize) {
        this.hipSize = hipSize;
    }

    /**
     * @return cupSize
     */
    public String getCupSize() {
        return cupSize;
    }

    /**
     * @param cupSize セットする cupSize
     */
    public void setCupSize(String cupSize) {
        this.cupSize = cupSize;
    }

    /**
     * @return age
     */
    public int getAge() {
        throw new UnsupportedOperationException();
    }

    /**
     * @param age セットする age
     */
    public void setAge(int age) {
        throw new UnsupportedOperationException();
    }

    /**
     * @return 文字列でフォーマットされた詳細情報
     */
    public String toDetailString() {
        return getName() + "\n" +
                "身長: " + getHeight() + "cm\n" +
                "体重: " + getWeight() + "kg\n" +
                "生年月日: " + getBirthMonth() + "月" + getBirthDayOfMonth() + "日\n" +
                "スリーサイズ: " + getBustSize() + "cm - " + getWaistSize() + "cm - " + getHipSize() + "cm\n" +
                "カップサイズ: " + getCupSize();
    }
}

toDetailStringメソッドで\nという文字を使っていますが、これは改行という意味です。(はフォントによってはと表記されます。日本語キーボードではを押すことで入力できます。)

コンストラクタを作る

image.png

コンストラクタというのはnew演算子を使ってインスタンスを作る時に呼び出される特別なメソッドです。

FemaleTalent talent = new FemaleTalent();
talent.setName("伊藤杏奈");
talent.setBirthMonth(7);
talent.setBirthDayOfMonth(20);
talent.setHeight(158.0);
talent.setWeight(42.0);
talent.setBustSize(108.0);
talent.setWaistSize(58.0);
talent.setHipSize(88.0);
talent.setCupSize("L");

getter・setterを定義しただけでは、呼び出し側でFemaleTalentのインスタンスを作ってメンバ変数に値を代入するのにこんな長い処理を書かないといけません。
これだと面倒な上に代入忘れが心配です。

そこで大抵の場合コンストラクタに、全てのメンバ変数に値を代入する処理を書いておきます。
コンストラクタはインスタンスを作る時に必ず呼び出されますから、そうしておけば代入忘れの心配もなくなって安心です。

コンストラクタは大抵の場合、メソッドの中で一番上に書かれます。
コンストラクタの名前はクラス名と同じにしなければなりません。

FemaleTalent.java
public class FemaleTalent {
    /**
     * 名前
     */
    private String name;

    /**
     * 誕生月
     */
    private int birthMonth;

    /**
     * 誕生日
     */
    private int birthDayOfMonth;

    /**
     * 身長(cm)
     */
    private double height;

    /**
     * 体重(kg)
     */
    private double weight;

    /**
     * バストサイズ(cm)
     */
    private double bustSize;

    /**
     * ウエストサイズ(cm)
     */
    private double waistSize;

    /**
     * ヒップサイズ(cm)
     */
    private double hipSize;

    /**
     * カップサイズ(A,B,C...)
     */
    private String cupSize;

    /**
     * @param name
     * @param birthMonth
     * @param birthDayOfMonth
     * @param height
     * @param weight
     * @param bustSize
     * @param waistSize
     * @param hipSize
     * @param cupSize
     */
    public FemaleTalent(String name, int birthMonth, int birthDayOfMonth, double height, double weight, double bustSize,
            double waistSize, double hipSize, String cupSize) {
        this.name = name;
        this.birthMonth = birthMonth;
        this.birthDayOfMonth = birthDayOfMonth;
        this.height = height;
        this.weight = weight;
        this.bustSize = bustSize;
        this.waistSize = waistSize;
        this.hipSize = hipSize;
        this.cupSize = cupSize;
    }

    /**
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name セットする name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return birthMonth
     */
    public int getBirthMonth() {
        return birthMonth;
    }

    /**
     * @param birthMonth セットする birthMonth
     */
    public void setBirthMonth(int birthMonth) {
        this.birthMonth = birthMonth;
    }

    /**
     * @return birthDayOfMonth
     */
    public int getBirthDayOfMonth() {
        return birthDayOfMonth;
    }

    /**
     * @param birthDayOfMonth セットする birthDayOfMonth
     */
    public void setBirthDayOfMonth(int birthDayOfMonth) {
        this.birthDayOfMonth = birthDayOfMonth;
    }

    /**
     * @return height
     */
    public double getHeight() {
        return height;
    }

    /**
     * @param height セットする height
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * @return weight
     */
    public double getWeight() {
        return weight;
    }

    /**
     * @param weight セットする weight
     */
    public void setWeight(double weight) {
        this.weight = weight;
    }

    /**
     * @return bustSize
     */
    public double getBustSize() {
        return bustSize;
    }

    /**
     * @param bustSize セットする bustSize
     */
    public void setBustSize(double bustSize) {
        this.bustSize = bustSize;
    }

    /**
     * @return waistSize
     */
    public double getWaistSize() {
        return waistSize;
    }

    /**
     * @param waistSize セットする waistSize
     */
    public void setWaistSize(double waistSize) {
        this.waistSize = waistSize;
    }

    /**
     * @return hipSize
     */
    public double getHipSize() {
        return hipSize;
    }

    /**
     * @param hipSize セットする hipSize
     */
    public void setHipSize(double hipSize) {
        this.hipSize = hipSize;
    }

    /**
     * @return cupSize
     */
    public String getCupSize() {
        return cupSize;
    }

    /**
     * @param cupSize セットする cupSize
     */
    public void setCupSize(String cupSize) {
        this.cupSize = cupSize;
    }

    /**
     * @return age
     */
    public int getAge() {
        throw new UnsupportedOperationException();
    }

    /**
     * @param age セットする age
     */
    public void setAge(int age) {
        throw new UnsupportedOperationException();
    }

    /**
     * @return 文字列でフォーマットされた詳細情報
     */
    public String toDetailString() {
        return getName() + "\n" +
                "身長: " + getHeight() + "cm\n" +
                "体重: " + getWeight() + "kg\n" +
                "生年月日: " + getBirthMonth() + "月" + getBirthDayOfMonth() + "日\n" +
                "スリーサイズ: " + getBustSize() + "cm - " + getWaistSize() + "cm - " + getHipSize() + "cm\n" +
                "カップサイズ: " + getCupSize();
    }
}

このようにコンストラクタを定義すれば、呼び出し側のコードも1行でシンプルになります。

FemaleTalent talent = new FemaleTalent("伊藤杏奈", 7, 20, 158.0, 42.0, 108.0, 58.0, 88.0, "L");

FemaleTalentクラスはこれで完成です。

image.png

image.png

それとこういうコンストラクタはよく作るので、eclipseではコンストラクタを作りたい位置にカーソルを置いて[ソース(S)]->[フィールドを使用してコンストラクタを生成(A)]することで自動生成することができます。

8. 入力の内容によって出力を分岐させる

女性タレントを表すクラスは完成したので、話をOppaiFinder.javaのメインメソッドに戻します。

次は入力が「B」なら上表のうちBカップの女性タレントだけを出力する、「C」ならCカップの女性タレントだけを出力する、というように、入力の内容によって出力を切り替えるようにしてみましょう。

独自型のリストを作る

まずは前項で作ったクラスを使ってリストを作り直してみましょう。

FemaleTalentクラスが定義するFemaleTalent型の値を収納するリストの作り方は、
List<FemaleTalent> talents = new ArrayList<>();
です。String型のリストを作る時と基本は変わりません。

このリストに値を追加するには
talents.add(new FemaleTalent("伊藤杏奈", 7, 20, 158.0, 42.0, 108.0, 58.0, 88.0, "L"));
のようにします。

ループでこのリストの値を1つずつ取り出し、getterを使って名前を取得、順番にコンソールに出力するには次のようにします。

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class OppaiFinder {
    public static void main(String[] args) {
        // Scannerを初期化
        Scanner scanner = new Scanner(System.in);

        // 好みのカップサイズの入力を受け付ける
        System.out.println("好みのカップサイズ(A,B,C…)を入力してください");
        String inputCupSize = scanner.nextLine();
        System.out.println(inputCupSize + "カップの女性タレント一覧:");

        // 女性タレントの一覧を収納するリストを初期化
        List<FemaleTalent> talents = new ArrayList<>();

        // リストにタレントを追加
        talents.add(new FemaleTalent("新田恵海", 12, 10, 153.0, 40.0, 81.0, 60.0, 88.0, "C"));
        talents.add(new FemaleTalent("高坂穂乃果", 8, 3, 157.0, 47.2, 78.0, 58.0, 82.0, "B"));
        talents.add(new FemaleTalent("三森すずこ", 6, 28, 160.0, 45.0, 80.0, 58.0, 86.0, "C"));
        talents.add(new FemaleTalent("園田海未", 3, 15, 159.0, 46.7, 76.0, 58.0, 80.0, "A"));
        talents.add(new FemaleTalent("Pile", 5, 2, 158.0, 40.0, 86.0, 60.0, 87.0, "F"));
        talents.add(new FemaleTalent("西木野真姫", 4, 19, 161.0, 47.7, 78.0, 56.0, 83.0, "B"));
        talents.add(new FemaleTalent("徳井青空", 12, 26, 159.0, 40.0, 82.0, 56.0, 82.0, "C"));
        talents.add(new FemaleTalent("矢澤にこ", 7, 22, 154.0, 44.5, 74.0, 57.0, 79.0, "A"));
        talents.add(new FemaleTalent("ヴァネッサ・ブルー", 5, 27, 160.0, 55.0, 91.0, 71.0, 97.0, "FF"));
        talents.add(new FemaleTalent("ケリー・マディソン", 8, 26, 175.0, 61.0, 102.0, 61.0, 86.0, "FF"));
        talents.add(new FemaleTalent("リンゼイ・ドーン・マッケンジー", 8, 7, 160.0, 51.0, 107.0, 56.0, 86.0, "HH"));
        talents.add(new FemaleTalent("岩瀬唯奈", 10, 12, 166.0, 44.0, 75.0, 54.0, 84.0, "AA"));
        talents.add(new FemaleTalent("エヴァ・ソネット", 3, 8, 164.0, 52.0, 102.0, 62.0, 88.0, "EE"));
        talents.add(new FemaleTalent("テリー・ウィーゲル", 2, 24, 170.0, 54.0, 91.0, 58.0, 89.0, "DD"));
        talents.add(new FemaleTalent("テイラー・ヘイズ", 1, 14, 165.0, 48.0, 86.0, 56.0, 86.0, "DD"));
        talents.add(new FemaleTalent("アンナ・ニコル・スミス", 11, 28, 180.0, 64.0, 97.0, 66.0, 97.0, "DD"));
        talents.add(new FemaleTalent("松井結", 4, 6, 169.0, 48.0, 77.0, 58.0, 83.0, "A"));
        talents.add(new FemaleTalent("熊澤枝里子", 11, 15, 170.0, 49.0, 83.0, 58.0, 84.0, "A"));
        talents.add(new FemaleTalent("中村明花", 4, 17, 175.0, 53.0, 82.0, 58.0, 87.0, "A"));
        talents.add(new FemaleTalent("ステファニー・スウィフト", 2, 7, 163.0, 55.0, 86.0, 64.0, 86.0, "B"));
        talents.add(new FemaleTalent("アヴァ・ヴィンセント", 9, 29, 162.0, 54.0, 91.0, 66.0, 91.0, "B"));
        talents.add(new FemaleTalent("ソニア・スイ", 10, 22, 172.0, 48.0, 84.0, 58.0, 86.0, "B"));
        talents.add(new FemaleTalent("ラクエル・デーリアン", 7, 21, 168.0, 53.0, 91.0, 61.0, 81.0, "C"));
        talents.add(new FemaleTalent("アリシア・アリガッティ", 6, 21, 174.0, 54.0, 91.0, 66.0, 91.0, "C"));
        talents.add(new FemaleTalent("坪木菜果", 10, 3, 158.0, 45.0, 83.0, 60.0, 91.0, "C"));
        talents.add(new FemaleTalent("土性愛", 3, 16, 167.0, 48.0, 86.0, 60.0, 87.0, "D"));
        talents.add(new FemaleTalent("カイリー・アイルランド", 5, 26, 165.0, 59.0, 91.0, 66.0, 91.0, "D"));
        talents.add(new FemaleTalent("伊藤かな", 7, 28, 166.0, 40.0, 88.0, 59.0, 87.0, "D"));
        talents.add(new FemaleTalent("ジャネット・ルポー", 1, 26, 168.0, 57.0, 99.0, 61.0, 91.0, "DDD"));
        talents.add(new FemaleTalent("西宮七海", 7, 10, 163.0, 46.0, 88.0, 58.0, 86.0, "E"));
        talents.add(new FemaleTalent("佐藤江梨子", 12, 19, 173.0, 53.0, 88.0, 58.0, 88.0, "E"));
        talents.add(new FemaleTalent("秋田知里", 1, 31, 156.0, 42.8, 84.0, 58.0, 85.0, "E"));
        talents.add(new FemaleTalent("郷司利也子", 3, 11, 160.0, 50.0, 93.0, 60.0, 86.0, "F"));
        talents.add(new FemaleTalent("秋山優", 10, 10, 160.0, 47.0, 88.0, 60.0, 86.0, "F"));
        talents.add(new FemaleTalent("いとうあこ", 11, 4, 157.0, 45.0, 90.0, 60.0, 90.0, "G"));
        talents.add(new FemaleTalent("黒田万結花", 9, 12, 157.0, 43.0, 90.0, 58.0, 86.0, "G"));
        talents.add(new FemaleTalent("相原美咲", 3, 27, 163.0, 42.0, 89.0, 56.0, 86.0, "G"));
        talents.add(new FemaleTalent("夏来唯", 1, 16, 163.0, 58.0, 95.0, 70.0, 93.0, "H"));
        talents.add(new FemaleTalent("青井鈴音", 10, 1, 160.0, 50.0, 95.0, 60.0, 89.0, "H"));
        talents.add(new FemaleTalent("三林千夏", 6, 14, 160.0, 43.0, 85.0, 58.0, 83.0, "H"));
        talents.add(new FemaleTalent("根本はるみ", 7, 28, 166.0, 58.0, 103.0, 60.0, 88.0, "I"));
        talents.add(new FemaleTalent("ヨルダン・カーヴァー", 1, 30, 168.0, 54.9, 102.0, 61.0, 89.0, "I"));
        talents.add(new FemaleTalent("竹内希実", 6, 25, 158.0, 45.0, 98.0, 60.0, 86.0, "I"));
        talents.add(new FemaleTalent("花井美理", 10, 15, 147.0, 39.0, 95.0, 58.0, 85.0, "J"));
        talents.add(new FemaleTalent("うさまりあ", 3, 20, 162.0, 176.0, 112.0, 99.0, 99.0, "J"));
        talents.add(new FemaleTalent("田辺はるか", 2, 2, 166.0, 45.0, 100.0, 58.0, 85.0, "J"));
        talents.add(new FemaleTalent("クロエ・ヴェヴリエ", 9, 18, 160.0, 55.0, 97.0, 66.0, 91.0, "K"));
        talents.add(new FemaleTalent("松坂南", 2, 7, 165.0, 47.0, 115.0, 58.0, 85.0, "L"));
        talents.add(new FemaleTalent("伊藤杏奈", 7, 20, 158.0, 42.0, 108.0, 58.0, 88.0, "L"));

        // リスト中の名前をインデックスと共にコンソールに出力する
        for (int i = 0; i < talents.size(); i++) {
            FemaleTalent talent = talents.get(i);
            System.out.println("[" + i + "] " + talent.getName());
        }

        // Scannerを閉じる
        scanner.close();
    }
}

条件分岐を実装する

何かの条件によって処理を分岐させることを条件分岐といいます。条件分岐の方法にはいくつかありますが、一番よく使われるのはif文を使った方法です。

image.png

if (<条件式>) {
  <条件式の結果がtrueだった場合に実行されるコードブロック>
}

今回実装する条件分岐は、ループで処理される女性タレント1人1人について、そのカップサイズが入力のカップサイズと同じなら、その女性タレントの名前をコンソールに出力するというものです。
そうすることで入力のカップサイズと同じカップサイズの女性タレントの名前だけがコンソールに出力され、違うカップサイズの女性タレントの名前は出力されないようにできます。

前にint型のようなプリミティブ型同士で等しいかどうかを調べる比較演算子は==だと書きましたが、String型のような参照型同士を比較するときにはequals(<比較対象>)メソッドを呼び出します。

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class OppaiFinder {
    public static void main(String[] args) {
        // Scannerを初期化
        Scanner scanner = new Scanner(System.in);

        // 好みのカップサイズの入力を受け付ける
        System.out.println("好みのカップサイズ(A,B,C…)を入力してください");
        String inputCupSize = scanner.nextLine();
        System.out.println(inputCupSize + "カップの女性タレント一覧:");

        // 女性タレントの一覧を収納するリストを初期化
        List<FemaleTalent> talents = new ArrayList<>();

        // リストにタレントを追加
        talents.add(new FemaleTalent("新田恵海", 12, 10, 153.0, 40.0, 81.0, 60.0, 88.0, "C"));
        talents.add(new FemaleTalent("高坂穂乃果", 8, 3, 157.0, 47.2, 78.0, 58.0, 82.0, "B"));
        talents.add(new FemaleTalent("三森すずこ", 6, 28, 160.0, 45.0, 80.0, 58.0, 86.0, "C"));
        talents.add(new FemaleTalent("園田海未", 3, 15, 159.0, 46.7, 76.0, 58.0, 80.0, "A"));
        talents.add(new FemaleTalent("Pile", 5, 2, 158.0, 40.0, 86.0, 60.0, 87.0, "F"));
        talents.add(new FemaleTalent("西木野真姫", 4, 19, 161.0, 47.7, 78.0, 56.0, 83.0, "B"));
        talents.add(new FemaleTalent("徳井青空", 12, 26, 159.0, 40.0, 82.0, 56.0, 82.0, "C"));
        talents.add(new FemaleTalent("矢澤にこ", 7, 22, 154.0, 44.5, 74.0, 57.0, 79.0, "A"));
        talents.add(new FemaleTalent("ヴァネッサ・ブルー", 5, 27, 160.0, 55.0, 91.0, 71.0, 97.0, "FF"));
        talents.add(new FemaleTalent("ケリー・マディソン", 8, 26, 175.0, 61.0, 102.0, 61.0, 86.0, "FF"));
        talents.add(new FemaleTalent("リンゼイ・ドーン・マッケンジー", 8, 7, 160.0, 51.0, 107.0, 56.0, 86.0, "HH"));
        talents.add(new FemaleTalent("岩瀬唯奈", 10, 12, 166.0, 44.0, 75.0, 54.0, 84.0, "AA"));
        talents.add(new FemaleTalent("エヴァ・ソネット", 3, 8, 164.0, 52.0, 102.0, 62.0, 88.0, "EE"));
        talents.add(new FemaleTalent("テリー・ウィーゲル", 2, 24, 170.0, 54.0, 91.0, 58.0, 89.0, "DD"));
        talents.add(new FemaleTalent("テイラー・ヘイズ", 1, 14, 165.0, 48.0, 86.0, 56.0, 86.0, "DD"));
        talents.add(new FemaleTalent("アンナ・ニコル・スミス", 11, 28, 180.0, 64.0, 97.0, 66.0, 97.0, "DD"));
        talents.add(new FemaleTalent("松井結", 4, 6, 169.0, 48.0, 77.0, 58.0, 83.0, "A"));
        talents.add(new FemaleTalent("熊澤枝里子", 11, 15, 170.0, 49.0, 83.0, 58.0, 84.0, "A"));
        talents.add(new FemaleTalent("中村明花", 4, 17, 175.0, 53.0, 82.0, 58.0, 87.0, "A"));
        talents.add(new FemaleTalent("ステファニー・スウィフト", 2, 7, 163.0, 55.0, 86.0, 64.0, 86.0, "B"));
        talents.add(new FemaleTalent("アヴァ・ヴィンセント", 9, 29, 162.0, 54.0, 91.0, 66.0, 91.0, "B"));
        talents.add(new FemaleTalent("ソニア・スイ", 10, 22, 172.0, 48.0, 84.0, 58.0, 86.0, "B"));
        talents.add(new FemaleTalent("ラクエル・デーリアン", 7, 21, 168.0, 53.0, 91.0, 61.0, 81.0, "C"));
        talents.add(new FemaleTalent("アリシア・アリガッティ", 6, 21, 174.0, 54.0, 91.0, 66.0, 91.0, "C"));
        talents.add(new FemaleTalent("坪木菜果", 10, 3, 158.0, 45.0, 83.0, 60.0, 91.0, "C"));
        talents.add(new FemaleTalent("土性愛", 3, 16, 167.0, 48.0, 86.0, 60.0, 87.0, "D"));
        talents.add(new FemaleTalent("カイリー・アイルランド", 5, 26, 165.0, 59.0, 91.0, 66.0, 91.0, "D"));
        talents.add(new FemaleTalent("伊藤かな", 7, 28, 166.0, 40.0, 88.0, 59.0, 87.0, "D"));
        talents.add(new FemaleTalent("ジャネット・ルポー", 1, 26, 168.0, 57.0, 99.0, 61.0, 91.0, "DDD"));
        talents.add(new FemaleTalent("西宮七海", 7, 10, 163.0, 46.0, 88.0, 58.0, 86.0, "E"));
        talents.add(new FemaleTalent("佐藤江梨子", 12, 19, 173.0, 53.0, 88.0, 58.0, 88.0, "E"));
        talents.add(new FemaleTalent("秋田知里", 1, 31, 156.0, 42.8, 84.0, 58.0, 85.0, "E"));
        talents.add(new FemaleTalent("郷司利也子", 3, 11, 160.0, 50.0, 93.0, 60.0, 86.0, "F"));
        talents.add(new FemaleTalent("秋山優", 10, 10, 160.0, 47.0, 88.0, 60.0, 86.0, "F"));
        talents.add(new FemaleTalent("いとうあこ", 11, 4, 157.0, 45.0, 90.0, 60.0, 90.0, "G"));
        talents.add(new FemaleTalent("黒田万結花", 9, 12, 157.0, 43.0, 90.0, 58.0, 86.0, "G"));
        talents.add(new FemaleTalent("相原美咲", 3, 27, 163.0, 42.0, 89.0, 56.0, 86.0, "G"));
        talents.add(new FemaleTalent("夏来唯", 1, 16, 163.0, 58.0, 95.0, 70.0, 93.0, "H"));
        talents.add(new FemaleTalent("青井鈴音", 10, 1, 160.0, 50.0, 95.0, 60.0, 89.0, "H"));
        talents.add(new FemaleTalent("三林千夏", 6, 14, 160.0, 43.0, 85.0, 58.0, 83.0, "H"));
        talents.add(new FemaleTalent("根本はるみ", 7, 28, 166.0, 58.0, 103.0, 60.0, 88.0, "I"));
        talents.add(new FemaleTalent("ヨルダン・カーヴァー", 1, 30, 168.0, 54.9, 102.0, 61.0, 89.0, "I"));
        talents.add(new FemaleTalent("竹内希実", 6, 25, 158.0, 45.0, 98.0, 60.0, 86.0, "I"));
        talents.add(new FemaleTalent("花井美理", 10, 15, 147.0, 39.0, 95.0, 58.0, 85.0, "J"));
        talents.add(new FemaleTalent("うさまりあ", 3, 20, 162.0, 176.0, 112.0, 99.0, 99.0, "J"));
        talents.add(new FemaleTalent("田辺はるか", 2, 2, 166.0, 45.0, 100.0, 58.0, 85.0, "J"));
        talents.add(new FemaleTalent("クロエ・ヴェヴリエ", 9, 18, 160.0, 55.0, 97.0, 66.0, 91.0, "K"));
        talents.add(new FemaleTalent("松坂南", 2, 7, 165.0, 47.0, 115.0, 58.0, 85.0, "L"));
        talents.add(new FemaleTalent("伊藤杏奈", 7, 20, 158.0, 42.0, 108.0, 58.0, 88.0, "L"));

        // 各女性タレントについてループを回す
        for (int i = 0; i < talents.size(); i++) {
            FemaleTalent talent = talents.get(i);

            // 女性タレントのカップサイズが入力のカップサイズと同じ場合
            String cupSize = talent.getCupSize();
            if (cupSize.equals(inputCupSize)) {
                // 番号と名前をコンソールに出力する
                System.out.println("[" + i + "] " + talent.getName());
            }
        }

        // Scannerを閉じる
        scanner.close();
    }
}

image.png

image.png

これで入力のカップサイズと一致する女性の一覧を出力する処理は実装できました。

9. 2次元女性タレントと3次元女性タレントのクラスを作ってみる

ところでタレントの一覧表を見た時に、「birthYear(誕生年)」と「voiceBy(中の人)」が空欄の人がいたりいなかったりすること気付きましたでしょうか。

これは3次元のタレントには誕生年の設定がありますが2次元のタレントには無かったり、2次元のタレントには中の人がいますが3次元のタレントにはいなかったりするためです。あの一覧表には2次元の女性タレントと3次元の女性タレントが混在していました。

前項でFemaleTalentクラスを作ったとき、そういう「あったりなかったりする」情報についてはメンバ変数に含めませんでしたが、本当はそれらの情報についてもうまく扱いたいのです。どうすれば良いでしょうか?

最も単純な方法は、2次元の女性タレントを表すFemaleAnimeTalentクラスと3次元の女性タレントを表すFemaleRealTalentクラスをそれぞれ個別に作ってしまうというものです。

継承を使わない実装例
FemaleAnimeTalent.java
public class FemaleAnimeTalent {
    /**
     * 名前
     */
    private String name;

    /**
     * 誕生月
     */
    private int birthMonth;

    /**
     * 誕生日
     */
    private int birthDayOfMonth;

    /**
     * 身長(cm)
     */
    private double height;

    /**
     * 体重(kg)
     */
    private double weight;

    /**
     * バストサイズ(cm)
     */
    private double bustSize;

    /**
     * ウエストサイズ(cm)
     */
    private double waistSize;

    /**
     * ヒップサイズ(cm)
     */
    private double hipSize;

    /**
     * カップサイズ(A,B,C...)
     */
    private String cupSize;

    /**
     * 年齢
     */
    private int age;

    /**
     * 中の人
     */
    private FemaleRealTalent voiceBy;

    /**
     * @param name
     * @param birthMonth
     * @param birthDayOfMonth
     * @param height
     * @param weight
     * @param bustSize
     * @param waistSize
     * @param hipSize
     * @param cupSize
     * @param age
     * @param voiceBy
     */
    public FemaleAnimeTalent(String name, int birthMonth, int birthDayOfMonth, double height, double weight,
            double bustSize, double waistSize, double hipSize, String cupSize, int age, FemaleRealTalent voiceBy) {
        this.name = name;
        this.birthMonth = birthMonth;
        this.birthDayOfMonth = birthDayOfMonth;
        this.height = height;
        this.weight = weight;
        this.bustSize = bustSize;
        this.waistSize = waistSize;
        this.hipSize = hipSize;
        this.cupSize = cupSize;
        this.age = age;
        this.voiceBy = voiceBy;
    }

    /**
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name セットする name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return birthMonth
     */
    public int getBirthMonth() {
        return birthMonth;
    }

    /**
     * @param birthMonth セットする birthMonth
     */
    public void setBirthMonth(int birthMonth) {
        this.birthMonth = birthMonth;
    }

    /**
     * @return birthDayOfMonth
     */
    public int getBirthDayOfMonth() {
        return birthDayOfMonth;
    }

    /**
     * @param birthDayOfMonth セットする birthDayOfMonth
     */
    public void setBirthDayOfMonth(int birthDayOfMonth) {
        this.birthDayOfMonth = birthDayOfMonth;
    }

    /**
     * @return height
     */
    public double getHeight() {
        return height;
    }

    /**
     * @param height セットする height
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * @return weight
     */
    public double getWeight() {
        return weight;
    }

    /**
     * @param weight セットする weight
     */
    public void setWeight(double weight) {
        this.weight = weight;
    }

    /**
     * @return bustSize
     */
    public double getBustSize() {
        return bustSize;
    }

    /**
     * @param bustSize セットする bustSize
     */
    public void setBustSize(double bustSize) {
        this.bustSize = bustSize;
    }

    /**
     * @return waistSize
     */
    public double getWaistSize() {
        return waistSize;
    }

    /**
     * @param waistSize セットする waistSize
     */
    public void setWaistSize(double waistSize) {
        this.waistSize = waistSize;
    }

    /**
     * @return hipSize
     */
    public double getHipSize() {
        return hipSize;
    }

    /**
     * @param hipSize セットする hipSize
     */
    public void setHipSize(double hipSize) {
        this.hipSize = hipSize;
    }

    /**
     * @return cupSize
     */
    public String getCupSize() {
        return cupSize;
    }

    /**
     * @param cupSize セットする cupSize
     */
    public void setCupSize(String cupSize) {
        this.cupSize = cupSize;
    }

    /**
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * @param age セットする age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * @return voiceBy
     */
    public FemaleRealTalent getVoiceBy() {
        return voiceBy;
    }

    /**
     * @param voiceBy セットする voiceBy
     */
    public void setVoiceBy(FemaleRealTalent voiceBy) {
        this.voiceBy = voiceBy;
    }

    /**
     * @return 文字列でフォーマットされた詳細情報
     */
    public String toDetailString() {
        return getName() + "\n" +
                "身長: " + getHeight() + "cm\n" +
                "体重: " + getWeight() + "kg\n" +
                "生年月日: " + getBirthMonth() + "月" + getBirthDayOfMonth() + "日(" + getAge() + "歳)\n" +
                "スリーサイズ: " + getBustSize() + "cm - " + getWaistSize() + "cm - " + getHipSize() + "cm\n" +
                "カップサイズ: " + getCupSize() + "\n" +
                "中の人: " + voiceBy.getName();
    }
}
FemaleRealTalent.java
import java.time.LocalDate;
import java.time.Period;

public class FemaleRealTalent {
    /**
     * 名前
     */
    private String name;

    /**
     * 誕生年
     */
    private int birthYear;

    /**
     * 誕生月
     */
    private int birthMonth;

    /**
     * 誕生日
     */
    private int birthDayOfMonth;

    /**
     * 身長(cm)
     */
    private double height;

    /**
     * 体重(kg)
     */
    private double weight;

    /**
     * バストサイズ(cm)
     */
    private double bustSize;

    /**
     * ウエストサイズ(cm)
     */
    private double waistSize;

    /**
     * ヒップサイズ(cm)
     */
    private double hipSize;

    /**
     * カップサイズ(A,B,C...)
     */
    private String cupSize;

    /**
     * @param name
     * @param birthYear
     * @param birthMonth
     * @param birthDayOfMonth
     * @param height
     * @param weight
     * @param bustSize
     * @param waistSize
     * @param hipSize
     * @param cupSize
     */
    public FemaleRealTalent(String name, int birthYear, int birthMonth, int birthDayOfMonth, double height,
            double weight, double bustSize, double waistSize, double hipSize, String cupSize) {
        this.name = name;
        this.birthYear = birthYear;
        this.birthMonth = birthMonth;
        this.birthDayOfMonth = birthDayOfMonth;
        this.height = height;
        this.weight = weight;
        this.bustSize = bustSize;
        this.waistSize = waistSize;
        this.hipSize = hipSize;
        this.cupSize = cupSize;
    }

    /**
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name セットする name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return birthYear
     */
    public int getBirthYear() {
        return birthYear;
    }

    /**
     * @param birthYear セットする birthYear
     */
    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }

    /**
     * @return birthMonth
     */
    public int getBirthMonth() {
        return birthMonth;
    }

    /**
     * @param birthMonth セットする birthMonth
     */
    public void setBirthMonth(int birthMonth) {
        this.birthMonth = birthMonth;
    }

    /**
     * @return birthDayOfMonth
     */
    public int getBirthDayOfMonth() {
        return birthDayOfMonth;
    }

    /**
     * @param birthDayOfMonth セットする birthDayOfMonth
     */
    public void setBirthDayOfMonth(int birthDayOfMonth) {
        this.birthDayOfMonth = birthDayOfMonth;
    }

    /**
     * @return height
     */
    public double getHeight() {
        return height;
    }

    /**
     * @param height セットする height
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * @return weight
     */
    public double getWeight() {
        return weight;
    }

    /**
     * @param weight セットする weight
     */
    public void setWeight(double weight) {
        this.weight = weight;
    }

    /**
     * @return bustSize
     */
    public double getBustSize() {
        return bustSize;
    }

    /**
     * @param bustSize セットする bustSize
     */
    public void setBustSize(double bustSize) {
        this.bustSize = bustSize;
    }

    /**
     * @return waistSize
     */
    public double getWaistSize() {
        return waistSize;
    }

    /**
     * @param waistSize セットする waistSize
     */
    public void setWaistSize(double waistSize) {
        this.waistSize = waistSize;
    }

    /**
     * @return hipSize
     */
    public double getHipSize() {
        return hipSize;
    }

    /**
     * @param hipSize セットする hipSize
     */
    public void setHipSize(double hipSize) {
        this.hipSize = hipSize;
    }

    /**
     * @return cupSize
     */
    public String getCupSize() {
        return cupSize;
    }

    /**
     * @param cupSize セットする cupSize
     */
    public void setCupSize(String cupSize) {
        this.cupSize = cupSize;
    }

    /**
     * @return age
     */
    public int getAge() {
        LocalDate birthDate = LocalDate.of(birthYear, getBirthMonth(), getBirthDayOfMonth());
        LocalDate currentDate = LocalDate.now();
        return Period.between(birthDate, currentDate).getYears();
    }

    /**
     * @return 文字列でフォーマットされた詳細情報
     */
    @Override
    public String toDetailString() {
        return getName() + "\n" +
                "身長: " + getHeight() + "cm\n" +
                "体重: " + getWeight() + "kg\n" +
                "生年月日: " + getBirthYear() + "年" + getBirthMonth() + "月" + getBirthDayOfMonth() + "日(" + getAge() + "歳)\n" +
                "スリーサイズ: " + getBustSize() + "cm - " + getWaistSize() + "cm - " + getHipSize() + "cm\n" +
                "カップサイズ: " + getCupSize();
    }

}

上にそういう実装例を示しましたが、この実装はちょっと冗長です。
大半のメンバ変数はどちらのクラスにも共通ですから、getter・setterなど大半の部分のコードを2度書いてしまっています。

またこのやり方では、2次元・3次元タレントに共通の情報…例えばアンダーバストのサイズなんかを新しく追加したくなったときに、両方のクラスにそれぞれメンバ変数を追加しなくてはなりません。面倒です。

そこでクラスの継承の出番となります。

クラスの継承について

継承というのは、クラスに親子関係をつけることでメンバ変数・メソッドなどの共通部分をまとめるための仕組みです。

image.png

共通部分をまとめた親クラスをあらかじめ用意しておき、クラス名の後にextends <親クラス名>と書くだけで継承は実現できます。継承元の親クラスのことをスーパークラスとも言います。

image.png

子クラスは親クラスの持っていたメンバ変数やメソッドを全て引継ぎます。
したがって複数の子クラスを作るときには、まずそれらの共通部分をまとめた親クラスを作って、子クラスではそれを継承してからそれぞれの子クラスでだけ必要なメンバ変数・メソッドを別途定義するようにすれば、色んなクラスへ同じ処理を書く手間を省くことができるのです。

今回、FemaleAnimeTalentクラス(2次元の女性タレント)とFemaleRealTalentクラス(3次元の女性タレント)の共通部分は既にFemaleTalentクラス(女性タレント)にまとめてありますから、それを親クラスとすれば実装は簡単です。

  • FemaleAnimeTalentクラスだけに含めたいメンバ変数
    • age(年齢)
    • voiceBy(中の人)
  • FemaleRealTalentクラスにだけ含めたいメンバ変数
    • birthYear(誕生年)(年齢は変化するものなので含めない)
FemaleAnimeTalent.java
public class FemaleAnimeTalent extends FemaleTalent {
    /**
     * 年齢
     */
    private int age;

    /**
     * 中の人
     */
    private FemaleRealTalent voiceBy;

    /**
     * @param name
     * @param birthMonth
     * @param birthDayOfMonth
     * @param height
     * @param weight
     * @param bustSize
     * @param waistSize
     * @param hipSize
     * @param cupSize
     * @param age
     * @param voiceBy
     */
    public FemaleAnimeTalent(String name, int birthMonth, int birthDayOfMonth, double height, double weight,
            double bustSize, double waistSize, double hipSize, String cupSize, int age, FemaleRealTalent voiceBy) {
        super(name, birthMonth, birthDayOfMonth, height, weight, bustSize, waistSize, hipSize, cupSize);
        this.age = age;
        this.voiceBy = voiceBy;
    }

    /**
     * @return age
     */
    @Override
    public int getAge() {
        return age;
    }

    /**
     * @param age セットする age
     */
    @Override
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * @return voiceBy
     */
    public FemaleRealTalent getVoiceBy() {
        return voiceBy;
    }

    /**
     * @param voiceBy セットする voiceBy
     */
    public void setVoiceBy(FemaleRealTalent voiceBy) {
        this.voiceBy = voiceBy;
    }
FemaleRealTalent.java
public class FemaleRealTalent extends FemaleTalent {
    /**
     * 誕生年
     */
    private int birthYear;

    /**
     * @param name
     * @param birthMonth
     * @param birthDayOfMonth
     * @param height
     * @param weight
     * @param bustSize
     * @param waistSize
     * @param hipSize
     * @param cupSize
     * @param birthYear
     */
    public FemaleRealTalent(String name, int birthYear, int birthMonth, int birthDayOfMonth, double height,
            double weight, double bustSize, double waistSize, double hipSize, String cupSize) {
        super(name, birthMonth, birthDayOfMonth, height, weight, bustSize, waistSize, hipSize, cupSize);
        this.birthYear = birthYear;
    }

    /**
     * @return birthYear
     */
    public int getBirthYear() {
        return birthYear;
    }

    /**
     * @param birthYear セットする birthYear
     */
    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }

}

「継承を使わない実装例」に比べて非常に短くシンプルに書けました。

なおコード中にあるsuperは親クラスを意味していますから、super.<メンバ変数名>で親クラスのメンバ変数にアクセスしたり、super.<メソッド名>(<引数1>,<引数2>…)で親クラスのメソッドを呼び出したりすることができます。(アクセス修飾子がprotected/publicのもののみ。またthisと同じく省略可能)
またsuper(<引数1>,<引数2>…)で親クラスのコンストラクタを呼び出せます。

それとFemaleAnimeTalentのメンバ変数にFemaleRealTalent型のものが含まれていることにも注目してください。
独自クラスが表す参照型の変数もメンバ変数に含められるよというのは、考えてみれば当然なのですが忘れがちです。覚えておきましょう。

オーバーライドについて

継承を行えば親クラスのメソッドもそのまま子クラスに引き継がれますが、親クラスと子クラスでメソッドの挙動を変えたいことがあります。

今回の場合変えたいのは、年齢を取得・設定するgetAgesetAgeメソッドの挙動と、女性タレントの詳細情報を文字列で取得するtoDetailStringメソッドの挙動です。

  • 年齢の取得方法が変わる理由
    • 2次元タレントは歳を取らないから年齢は固定だが、3次元タレントは歳を取るから年齢は生年月日を元に計算したい。
  • 詳細情報の取得方法が変わる理由
    • 2次元タレントなら中の人、3次元タレントなら誕生年など、子クラスだけが持つ情報も余すことなく表示したい。
クラス getAgeメソッドの挙動 setAgeメソッドの挙動 toDetailStringメソッドの挙動
FemaleTalent 計算不能なので例外を起こす 設定不能なので例外を起こす 名前・誕生月・誕生日・身長・体重・バストサイズ・ウエストサイズ・ヒップサイズ・カップサイズを返す
FemaleAnimeTalent 単にメンバ変数ageの値を返す 単にメンバ変数ageを設定する 追加で年齢・中の人も返す
FemaleRealTalent 誕生年月日から計算する 設定不能なので例外を起こす 追加で誕生年・年齢も返す

親クラスと子クラスでメソッドの挙動を変えたいときには、オーバーライドという仕組みを使います。

オーバーライドというのは、子クラスで親クラスのメソッドを上書きすることができる仕組みです。

やり方は単純で、単に子クラスで親クラスにあるのと同名のメソッドを作ればオーバーライドできます。

FemaleAnimeTalent.java
public class FemaleAnimeTalent extends FemaleTalent {
    /**
     * 年齢
     */
    private int age;

    /**
     * 中の人
     */
    private FemaleRealTalent voiceBy;

    /**
     * @param name
     * @param birthMonth
     * @param birthDayOfMonth
     * @param height
     * @param weight
     * @param bustSize
     * @param waistSize
     * @param hipSize
     * @param cupSize
     * @param age
     * @param voiceBy
     */
    public FemaleAnimeTalent(String name, int birthMonth, int birthDayOfMonth, double height, double weight,
            double bustSize, double waistSize, double hipSize, String cupSize, int age, FemaleRealTalent voiceBy) {
        super(name, birthMonth, birthDayOfMonth, height, weight, bustSize, waistSize, hipSize, cupSize);
        this.age = age;
        this.voiceBy = voiceBy;
    }

    /**
     * @return age
     */
    @Override
    public int getAge() {
        return age;
    }

    /**
     * @param age セットする age
     */
    @Override
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * @return voiceBy
     */
    public FemaleRealTalent getVoiceBy() {
        return voiceBy;
    }

    /**
     * @param voiceBy セットする voiceBy
     */
    public void setVoiceBy(FemaleRealTalent voiceBy) {
        this.voiceBy = voiceBy;
    }

    /**
     * @return 文字列でフォーマットされた詳細情報
     */
    @Override
    public String toDetailString() {
        return getName() + "\n" +
                "身長: " + getHeight() + "cm\n" +
                "体重: " + getWeight() + "kg\n" +
                "生年月日: " + getBirthMonth() + "月" + getBirthDayOfMonth() + "日(" + getAge() + "歳)\n" +
                "スリーサイズ: " + getBustSize() + "cm - " + getWaistSize() + "cm - " + getHipSize() + "cm\n" +
                "カップサイズ: " + getCupSize() + "\n" +
                "中の人: " + voiceBy.getName();
    }

}
FemaleRealTalent.java
import java.time.LocalDate;
import java.time.Period;

public class FemaleRealTalent extends FemaleTalent {
    /**
     * 誕生年
     */
    private int birthYear;

    /**
     * @param name
     * @param birthMonth
     * @param birthDayOfMonth
     * @param height
     * @param weight
     * @param bustSize
     * @param waistSize
     * @param hipSize
     * @param cupSize
     * @param birthYear
     */
    public FemaleRealTalent(String name, int birthYear, int birthMonth, int birthDayOfMonth, double height,
            double weight, double bustSize, double waistSize, double hipSize, String cupSize) {
        super(name, birthMonth, birthDayOfMonth, height, weight, bustSize, waistSize, hipSize, cupSize);
        this.birthYear = birthYear;
    }

    /**
     * @return birthYear
     */
    public int getBirthYear() {
        return birthYear;
    }

    /**
     * @param birthYear セットする birthYear
     */
    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }

    /**
     * @return age
     */
    @Override
    public int getAge() {
        LocalDate birthDate = LocalDate.of(birthYear, getBirthMonth(), getBirthDayOfMonth());
        LocalDate currentDate = LocalDate.now();
        return Period.between(birthDate, currentDate).getYears();
    }

    /**
     * @return 文字列でフォーマットされた詳細情報
     */
    @Override
    public String toDetailString() {
        return getName() + "\n" +
                "身長: " + getHeight() + "cm\n" +
                "体重: " + getWeight() + "kg\n" +
                "生年月日: " + getBirthYear() + "年" + getBirthMonth() + "月" + getBirthDayOfMonth() + "日(" + getAge() + "歳)\n" +
                "スリーサイズ: " + getBustSize() + "cm - " + getWaistSize() + "cm - " + getHipSize() + "cm\n" +
                "カップサイズ: " + getCupSize();
    }
}

オーバーライドを行うメソッドを定義する前の行に@Overrideと書いていますがこれはアノテーションといって、「オーバーライドしてるよ」ということを示す注釈です。書いたほうがオーバーライドしてると分かりやすいですが、別に書かなくても動作はします。

ところで、FemaleRealTalentクラスのgetAge()でさりげにLocalDatePeriodという新たなクラスが出てきましたが、これらはそれぞれ「日付」を表す設計図と、「期間」を表す設計図です。
しかし変なメソッドの呼び出し方をしていますね。

LocalDate.now();は今日の日付のインスタンスを生成するメソッドを、Period.between(<日付1>, <日付2>);は2つの日付から期間のインスタンスを生成するメソッドをそれぞれ呼び出しているのですが、インスタンスから呼び出していた今までのメソッドと違って、
<クラス名>.<メソッド名>(<引数1>, <引数2>…);
というように、クラスから直接メソッドを呼び出しているような書き方をしています。クラスはあくまで設計図という話はどこに行ったのでしょうか?

staticメソッドについて

初心者のうちにこれを覚えると何でもかんでもstaticにしてしまいがちなので説明をかなり後のほうに持ってきたのですが、メソッドの中にはstaticメソッドというのもあります。

これはインスタンスとか関係なしに呼び出せるメソッドです。今まで出てきた中では、最初に作ったメインメソッドもそうです。

staticメソッドを定義するには、このようにします。普通のメソッドを定義する方法にstaticとつけただけです。

<アクセス修飾子> static <戻り値の型> <メソッド名>(<引数1の型> <引数1の名前>, <引数2の型> <引数2の名前>…) {
  <メソッドが呼び出されたときに実行されるコードブロック>
  return <戻り値>;
}

image.png

これは例えば、インスタンスを生成するメソッドをコンストラクタとは別に作りたいときなんかに使われます。
new Period(<日付1>, <日付2>);とかよりPeriod.between(<日付1>, <日付2>);のほうが「2つの日付の間」ってことが明確ですよね。

ただ今までの内容をちゃんと学んでいれば、そういうとき以外でインスタンスとか関係なしに呼び出せるメソッドを作りたいことなんてそう多くは無いことに気づくでしょう。
今回のコードでもメインメソッド以外には使っていませんね。

10. 残りを実装する

いよいよ完成も目前です。
それに向けてまずは、OppaiFinderクラスのメインメソッドで今までFemaleTalentクラスを使っていた部分を、前項で作ったFemaleAnimeTalentクラスとFemaleRealTalentクラスで置き換えてみましょう。

キャストについて

クラスを置き換えると書きましたが、Javaでは、子クラスのインスタンスは親クラス型としても扱うことができます。

言葉で聞いてもなんのこっちゃだと思いますが、要するに

FemaleTalent talent = new FemaleRealTalent("郷司利也子", 1982, 3, 11, 160.0, 50.0, 93.0, 60.0, 86.0, "F");

とか書いて子クラスのインスタンスを親クラス型の変数に代入しちゃったりしても問題ないし、

List<FemaleTalent> talents = new ArrayList<>();
talents.add(new FemaleRealTalent("新田恵海", 1986, 12, 10, 153.0, 40.0, 81.0, 60.0, 88.0, "C"));

とか書いて子クラスのインスタンスを親クラス型のリストに追加しちゃったりしても問題ないということです。

Javaが空気読んで勝手に、子クラス型を親クラス型に変換してくれているものと考えれば良いでしょう。
この変換のことをキャストといい、特にJavaが勝手にやってくれるキャストのことを暗黙的なキャスト、子クラスから親クラスへのキャストのことをアップキャストといいます。

親クラス型へのアップキャスト後に呼び出せるメンバ変数・メソッドは親クラスのものだけになります。(但し子クラスでオーバーライドしたメソッドの挙動はそのまま)

暗黙的なキャストが行われるのは、やっても大丈夫そうなキャストだけです。
例えば子クラスは親クラスのメンバ変数・メソッドを全て引き継いでいるのでアップキャストはやっても大丈夫。

  • 暗黙的なキャストが行われる例
変換元 変換先 備考
int double double d = 1; 整数(1)から浮動小数点数(1.0)へ
FemaleRealTalent FemaleTalent 上の例を参照 子クラスから親クラスへ

一方、暗黙的なキャストに対して、明示的なキャストというのもあります。

(<変換先のデータ型>) <変換元>;

変換先のデータ型を書くことで、キャストすることを明示的に示す方法です。
やっても大丈夫そうなキャストのときはわざわざこうする必要はないですが、ヤバそうなキャストのときは必ずこの方法を採らなければなりません。

例えば親クラスから子クラスへの変換のことをダウンキャストといいますが、そのダウンキャストでは明示的なキャストが必要です。

但しダウンキャストができるのは、一度アップキャストで親クラスにしたインスタンスをまた元に戻すような場合だけということに注意してください。

// ダメ
FemaleTalent talent = new FemaleTalent("伊藤かな", 7, 28, 166.0, 40.0, 88.0, 59.0, 87.0, "D");
FemaleRealTalent realTalent = (FemaleRealTalent) talent;

// イイヨ!
FemaleTalent talent = new FemaleRealTalent("伊藤かな", 1984, 7, 28, 166.0, 40.0, 88.0, 59.0, 87.0, "D");
FemaleRealTalent realTalent = (FemaleRealTalent) talent;

親クラスは子クラスのメンバ変数・メソッドを全て持っているわけではないので、そういう場合以外でダウンキャストはできません。
要は危ないからJavaも勝手にはキャストしてくれないってわけですね。

  • 明示的なキャストが必要な例
変換元 変換先 備考
double int int i = (int) 1.7; 浮動小数点数(1.7)の小数点以下(.7)を切り捨てて整数(1)へ
FemaleTalent FemaleRealTalent 上の例を参照 親クラスから子クラスへ。あぶない

あとはアップキャストとかダウンキャストとか関係なくどんな場合でもキャストが無理な例もあります。
見るからに無理とわかるかと思いますが一応。

  • キャストが無理な例
変換元 変換先 備考
String int int i = (int) "1"; 文字列と整数では全く別物なので無理
FemaleTalent String String str = (String) new FemaleTalent("伊藤かな", 7, 28, 166.0, 40.0, 88.0, 59.0, 87.0, "D"); 女性タレントと文字列では全く別物なので無理

ただキャストでは無理でも、こういう無理な変換をなんとかやってくれるメソッドが用意されていることはあります。
例えばInteger.parseInt(<変換元の文字列>);でString型からint型への変換は可能です。

さて、そんなキャストを駆使してコードを書きなおしてみましょう。

image.png

女性タレントの一覧表では、classというカラムにFemaleAnimeTalentクラスかFemaleRealTalentクラスのどちらを使うべきか示してありますのでそれを参考にして、クラスの置き換え後のコードは次のようになります。

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class OppaiFinder {
    public static void main(String[] args) {
        // Scannerを初期化
        Scanner scanner = new Scanner(System.in);

        // 好みのカップサイズの入力を受け付ける
        System.out.println("好みのカップサイズ(A,B,C…)を入力してください");
        String inputCupSize  scanner.nextLine();
        System.out.println(inputCupSize + "カップの女性タレント一覧:");

        // 女性タレントの一覧を収納するリストを初期化
        List<FemaleTalent> talents = new ArrayList<>();

        // リストにタレントを追加
        talents.add(new FemaleRealTalent("新田恵海", 1986, 12, 10, 153.0, 40.0, 81.0, 60.0, 88.0, "C"));
        talents.add(new FemaleAnimeTalent("高坂穂乃果", 8, 3, 157.0, 47.2, 78.0, 58.0, 82.0, "B", 16, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("三森すずこ", 1986, 6, 28, 160.0, 45.0, 80.0, 58.0, 86.0, "C"));
        talents.add(new FemaleAnimeTalent("園田海未", 3, 15, 159.0, 46.7, 76.0, 58.0, 80.0, "A", 16, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("Pile", 1988, 5, 2, 158.0, 40.0, 86.0, 60.0, 87.0, "F"));
        talents.add(new FemaleAnimeTalent("西木野真姫", 4, 19, 161.0, 47.7, 78.0, 56.0, 83.0, "B", 15, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("徳井青空", 1989, 12, 26, 159.0, 40.0, 82.0, 56.0, 82.0, "C"));
        talents.add(new FemaleAnimeTalent("矢澤にこ", 7, 22, 154.0, 44.5, 74.0, 57.0, 79.0, "A", 17, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("ヴァネッサ・ブルー", 1974, 5, 27, 160.0, 55.0, 91.0, 71.0, 97.0, "FF"));
        talents.add(new FemaleRealTalent("ケリー・マディソン", 1967, 8, 26, 175.0, 61.0, 102.0, 61.0, 86.0, "FF"));
        talents.add(new FemaleRealTalent("リンゼイ・ドーン・マッケンジー", 1978, 8, 7, 160.0, 51.0, 107.0, 56.0, 86.0, "HH"));
        talents.add(new FemaleRealTalent("岩瀬唯奈", 1995, 10, 12, 166.0, 44.0, 75.0, 54.0, 84.0, "AA"));
        talents.add(new FemaleRealTalent("エヴァ・ソネット", 1985, 3, 8, 164.0, 52.0, 102.0, 62.0, 88.0, "EE"));
        talents.add(new FemaleRealTalent("テリー・ウィーゲル", 1962, 2, 24, 170.0, 54.0, 91.0, 58.0, 89.0, "DD"));
        talents.add(new FemaleRealTalent("テイラー・ヘイズ", 1975, 1, 14, 165.0, 48.0, 86.0, 56.0, 86.0, "DD"));
        talents.add(new FemaleRealTalent("アンナ・ニコル・スミス", 1967, 11, 28, 180.0, 64.0, 97.0, 66.0, 97.0, "DD"));
        talents.add(new FemaleRealTalent("松井結", 1984, 4, 6, 169.0, 48.0, 77.0, 58.0, 83.0, "A"));
        talents.add(new FemaleRealTalent("熊澤枝里子", 1985, 11, 15, 170.0, 49.0, 83.0, 58.0, 84.0, "A"));
        talents.add(new FemaleRealTalent("中村明花", 1986, 4, 17, 175.0, 53.0, 82.0, 58.0, 87.0, "A"));
        talents.add(new FemaleRealTalent("ステファニー・スウィフト", 1972, 2, 7, 163.0, 55.0, 86.0, 64.0, 86.0, "B"));
        talents.add(new FemaleRealTalent("アヴァ・ヴィンセント", 1975, 9, 29, 162.0, 54.0, 91.0, 66.0, 91.0, "B"));
        talents.add(new FemaleRealTalent("ソニア・スイ", 1980, 10, 22, 172.0, 48.0, 84.0, 58.0, 86.0, "B"));
        talents.add(new FemaleRealTalent("ラクエル・デーリアン", 1968, 7, 21, 168.0, 53.0, 91.0, 61.0, 81.0, "C"));
        talents.add(new FemaleRealTalent("アリシア・アリガッティ", 1984, 6, 21, 174.0, 54.0, 91.0, 66.0, 91.0, "C"));
        talents.add(new FemaleRealTalent("坪木菜果", 1987, 10, 3, 158.0, 45.0, 83.0, 60.0, 91.0, "C"));
        talents.add(new FemaleRealTalent("土性愛", 1982, 3, 16, 167.0, 48.0, 86.0, 60.0, 87.0, "D"));
        talents.add(new FemaleRealTalent("カイリー・アイルランド", 1970, 5, 26, 165.0, 59.0, 91.0, 66.0, 91.0, "D"));
        talents.add(new FemaleRealTalent("伊藤かな", 1984, 7, 28, 166.0, 40.0, 88.0, 59.0, 87.0, "D"));
        talents.add(new FemaleRealTalent("ジャネット・ルポー", 1950, 1, 26, 168.0, 57.0, 99.0, 61.0, 91.0, "DDD"));
        talents.add(new FemaleRealTalent("西宮七海", 1977, 7, 10, 163.0, 46.0, 88.0, 58.0, 86.0, "E"));
        talents.add(new FemaleRealTalent("佐藤江梨子", 1981, 12, 19, 173.0, 53.0, 88.0, 58.0, 88.0, "E"));
        talents.add(new FemaleRealTalent("秋田知里", 1994, 1, 31, 156.0, 42.8, 84.0, 58.0, 85.0, "E"));
        talents.add(new FemaleRealTalent("郷司利也子", 1982, 3, 11, 160.0, 50.0, 93.0, 60.0, 86.0, "F"));
        talents.add(new FemaleRealTalent("秋山優", 1986, 10, 10, 160.0, 47.0, 88.0, 60.0, 86.0, "F"));
        talents.add(new FemaleRealTalent("いとうあこ", 1983, 11, 4, 157.0, 45.0, 90.0, 60.0, 90.0, "G"));
        talents.add(new FemaleRealTalent("黒田万結花", 1990, 9, 12, 157.0, 43.0, 90.0, 58.0, 86.0, "G"));
        talents.add(new FemaleRealTalent("相原美咲", 1995, 3, 27, 163.0, 42.0, 89.0, 56.0, 86.0, "G"));
        talents.add(new FemaleRealTalent("夏来唯", 1994, 1, 16, 163.0, 58.0, 95.0, 70.0, 93.0, "H"));
        talents.add(new FemaleRealTalent("青井鈴音", 1990, 10, 1, 160.0, 50.0, 95.0, 60.0, 89.0, "H"));
        talents.add(new FemaleRealTalent("三林千夏", 1983, 6, 14, 160.0, 43.0, 85.0, 58.0, 83.0, "H"));
        talents.add(new FemaleRealTalent("根本はるみ", 1980, 7, 28, 166.0, 58.0, 103.0, 60.0, 88.0, "I"));
        talents.add(new FemaleRealTalent("ヨルダン・カーヴァー", 1986, 1, 30, 168.0, 54.9, 102.0, 61.0, 89.0, "I"));
        talents.add(new FemaleRealTalent("竹内希実", 1980, 6, 25, 158.0, 45.0, 98.0, 60.0, 86.0, "I"));
        talents.add(new FemaleRealTalent("花井美理", 1984, 10, 15, 147.0, 39.0, 95.0, 58.0, 85.0, "J"));
        talents.add(new FemaleRealTalent("うさまりあ", 1993, 3, 20, 162.0, 176.0, 112.0, 99.0, 99.0, "J"));
        talents.add(new FemaleRealTalent("田辺はるか", 1983, 2, 2, 166.0, 45.0, 100.0, 58.0, 85.0, "J"));
        talents.add(new FemaleRealTalent("クロエ・ヴェヴリエ", 1968, 9, 18, 160.0, 55.0, 97.0, 66.0, 91.0, "K"));
        talents.add(new FemaleRealTalent("松坂南", 1984, 2, 7, 165.0, 47.0, 115.0, 58.0, 85.0, "L"));
        talents.add(new FemaleRealTalent("伊藤杏奈", 1987, 7, 20, 158.0, 42.0, 108.0, 58.0, 88.0, "L"));

        // 各女性タレントについてループを回す
        for (int i = 0; i < talents.size(); i++) {
            FemaleTalent talent = talents.get(i);

            // 女性タレントのカップサイズが入力のカップサイズと同じ場合
            String cupSize = talent.getCupSize();
            if (cupSize.equals(inputCupSize)) {
                // 番号と名前をコンソールに出力する
                System.out.println("[" + i + "] " + talent.getName());
            }
        }

        // Scannerを閉じる
        scanner.close();
    }
}

詳細情報の出力を実装する

image.png

残る実装は3と4を残すのみです。
タレントの番号の入力を受け付けるには、カップサイズの入力を受け付けるときと同様Scannerクラスを使います。

ただ今回はString型でなくint型で入力を受け付けたいので、scanner.nextInt();として前回とは別のメソッドを使います。(Integer.parseInt(scanner.nextLine());としてString型で入力を受け付けた後、int型に変換しても良いでしょう。)

今回の場合、タレントの番号はそのままリストのインデックスに対応しているので、番号に対応する女性タレントのインスタンスを取り出すには単にtalents.get(<番号>);とすればよく簡単です。

それと詳細な情報の出力については、既に各クラスにtoDetailStringメソッドが実装済みです。
このメソッドを呼び出し、戻り値をコンソールに出力するようにすれば…

OppaiFinder.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class OppaiFinder {
    public static void main(String[] args) {
        // Scannerを初期化
        Scanner scanner = new Scanner(System.in);

        // 好みのカップサイズの入力を受け付ける
        System.out.println("好みのカップサイズ(A,B,C…)を入力してください");
        String inputCupSize = scanner.nextLine();
        System.out.println(inputCupSize + "カップの女性タレント一覧:");

        // 女性タレントの一覧を収納するリストを初期化
        List<FemaleTalent> talents = new ArrayList<>();

        // リストにタレントを追加
        talents.add(new FemaleRealTalent("新田恵海", 1986, 12, 10, 153.0, 40.0, 81.0, 60.0, 88.0, "C"));
        talents.add(new FemaleAnimeTalent("高坂穂乃果", 8, 3, 157.0, 47.2, 78.0, 58.0, 82.0, "B", 16, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("三森すずこ", 1986, 6, 28, 160.0, 45.0, 80.0, 58.0, 86.0, "C"));
        talents.add(new FemaleAnimeTalent("園田海未", 3, 15, 159.0, 46.7, 76.0, 58.0, 80.0, "A", 16, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("Pile", 1988, 5, 2, 158.0, 40.0, 86.0, 60.0, 87.0, "F"));
        talents.add(new FemaleAnimeTalent("西木野真姫", 4, 19, 161.0, 47.7, 78.0, 56.0, 83.0, "B", 15, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("徳井青空", 1989, 12, 26, 159.0, 40.0, 82.0, 56.0, 82.0, "C"));
        talents.add(new FemaleAnimeTalent("矢澤にこ", 7, 22, 154.0, 44.5, 74.0, 57.0, 79.0, "A", 17, (FemaleRealTalent) talents.get(talents.size() - 1)));
        talents.add(new FemaleRealTalent("ヴァネッサ・ブルー", 1974, 5, 27, 160.0, 55.0, 91.0, 71.0, 97.0, "FF"));
        talents.add(new FemaleRealTalent("ケリー・マディソン", 1967, 8, 26, 175.0, 61.0, 102.0, 61.0, 86.0, "FF"));
        talents.add(new FemaleRealTalent("リンゼイ・ドーン・マッケンジー", 1978, 8, 7, 160.0, 51.0, 107.0, 56.0, 86.0, "HH"));
        talents.add(new FemaleRealTalent("岩瀬唯奈", 1995, 10, 12, 166.0, 44.0, 75.0, 54.0, 84.0, "AA"));
        talents.add(new FemaleRealTalent("エヴァ・ソネット", 1985, 3, 8, 164.0, 52.0, 102.0, 62.0, 88.0, "EE"));
        talents.add(new FemaleRealTalent("テリー・ウィーゲル", 1962, 2, 24, 170.0, 54.0, 91.0, 58.0, 89.0, "DD"));
        talents.add(new FemaleRealTalent("テイラー・ヘイズ", 1975, 1, 14, 165.0, 48.0, 86.0, 56.0, 86.0, "DD"));
        talents.add(new FemaleRealTalent("アンナ・ニコル・スミス", 1967, 11, 28, 180.0, 64.0, 97.0, 66.0, 97.0, "DD"));
        talents.add(new FemaleRealTalent("松井結", 1984, 4, 6, 169.0, 48.0, 77.0, 58.0, 83.0, "A"));
        talents.add(new FemaleRealTalent("熊澤枝里子", 1985, 11, 15, 170.0, 49.0, 83.0, 58.0, 84.0, "A"));
        talents.add(new FemaleRealTalent("中村明花", 1986, 4, 17, 175.0, 53.0, 82.0, 58.0, 87.0, "A"));
        talents.add(new FemaleRealTalent("ステファニー・スウィフト", 1972, 2, 7, 163.0, 55.0, 86.0, 64.0, 86.0, "B"));
        talents.add(new FemaleRealTalent("アヴァ・ヴィンセント", 1975, 9, 29, 162.0, 54.0, 91.0, 66.0, 91.0, "B"));
        talents.add(new FemaleRealTalent("ソニア・スイ", 1980, 10, 22, 172.0, 48.0, 84.0, 58.0, 86.0, "B"));
        talents.add(new FemaleRealTalent("ラクエル・デーリアン", 1968, 7, 21, 168.0, 53.0, 91.0, 61.0, 81.0, "C"));
        talents.add(new FemaleRealTalent("アリシア・アリガッティ", 1984, 6, 21, 174.0, 54.0, 91.0, 66.0, 91.0, "C"));
        talents.add(new FemaleRealTalent("坪木菜果", 1987, 10, 3, 158.0, 45.0, 83.0, 60.0, 91.0, "C"));
        talents.add(new FemaleRealTalent("土性愛", 1982, 3, 16, 167.0, 48.0, 86.0, 60.0, 87.0, "D"));
        talents.add(new FemaleRealTalent("カイリー・アイルランド", 1970, 5, 26, 165.0, 59.0, 91.0, 66.0, 91.0, "D"));
        talents.add(new FemaleRealTalent("伊藤かな", 1984, 7, 28, 166.0, 40.0, 88.0, 59.0, 87.0, "D"));
        talents.add(new FemaleRealTalent("ジャネット・ルポー", 1950, 1, 26, 168.0, 57.0, 99.0, 61.0, 91.0, "DDD"));
        talents.add(new FemaleRealTalent("西宮七海", 1977, 7, 10, 163.0, 46.0, 88.0, 58.0, 86.0, "E"));
        talents.add(new FemaleRealTalent("佐藤江梨子", 1981, 12, 19, 173.0, 53.0, 88.0, 58.0, 88.0, "E"));
        talents.add(new FemaleRealTalent("秋田知里", 1994, 1, 31, 156.0, 42.8, 84.0, 58.0, 85.0, "E"));
        talents.add(new FemaleRealTalent("郷司利也子", 1982, 3, 11, 160.0, 50.0, 93.0, 60.0, 86.0, "F"));
        talents.add(new FemaleRealTalent("秋山優", 1986, 10, 10, 160.0, 47.0, 88.0, 60.0, 86.0, "F"));
        talents.add(new FemaleRealTalent("いとうあこ", 1983, 11, 4, 157.0, 45.0, 90.0, 60.0, 90.0, "G"));
        talents.add(new FemaleRealTalent("黒田万結花", 1990, 9, 12, 157.0, 43.0, 90.0, 58.0, 86.0, "G"));
        talents.add(new FemaleRealTalent("相原美咲", 1995, 3, 27, 163.0, 42.0, 89.0, 56.0, 86.0, "G"));
        talents.add(new FemaleRealTalent("夏来唯", 1994, 1, 16, 163.0, 58.0, 95.0, 70.0, 93.0, "H"));
        talents.add(new FemaleRealTalent("青井鈴音", 1990, 10, 1, 160.0, 50.0, 95.0, 60.0, 89.0, "H"));
        talents.add(new FemaleRealTalent("三林千夏", 1983, 6, 14, 160.0, 43.0, 85.0, 58.0, 83.0, "H"));
        talents.add(new FemaleRealTalent("根本はるみ", 1980, 7, 28, 166.0, 58.0, 103.0, 60.0, 88.0, "I"));
        talents.add(new FemaleRealTalent("ヨルダン・カーヴァー", 1986, 1, 30, 168.0, 54.9, 102.0, 61.0, 89.0, "I"));
        talents.add(new FemaleRealTalent("竹内希実", 1980, 6, 25, 158.0, 45.0, 98.0, 60.0, 86.0, "I"));
        talents.add(new FemaleRealTalent("花井美理", 1984, 10, 15, 147.0, 39.0, 95.0, 58.0, 85.0, "J"));
        talents.add(new FemaleRealTalent("うさまりあ", 1993, 3, 20, 162.0, 176.0, 112.0, 99.0, 99.0, "J"));
        talents.add(new FemaleRealTalent("田辺はるか", 1983, 2, 2, 166.0, 45.0, 100.0, 58.0, 85.0, "J"));
        talents.add(new FemaleRealTalent("クロエ・ヴェヴリエ", 1968, 9, 18, 160.0, 55.0, 97.0, 66.0, 91.0, "K"));
        talents.add(new FemaleRealTalent("松坂南", 1984, 2, 7, 165.0, 47.0, 115.0, 58.0, 85.0, "L"));
        talents.add(new FemaleRealTalent("伊藤杏奈", 1987, 7, 20, 158.0, 42.0, 108.0, 58.0, 88.0, "L"));

        // 各女性タレントについてループを回す
        for (int i = 0; i < talents.size(); i++) {
            FemaleTalent talent = talents.get(i);

            // 女性タレントのカップサイズが入力のカップサイズと同じ場合
            String cupSize = talent.getCupSize();
            if (cupSize.equals(inputCupSize)) {
                // 番号と名前をコンソールに出力する
                System.out.println("[" + i + "] " + talent.getName());
            }
        }
        System.out.println();

        // 気になる女性タレントの番号の入力を受け付ける
        System.out.println("気になる女性タレントの番号(0,1,2…)を入力してください");
        int inputIndex = scanner.nextInt();

        // 番号に当てはまる女性タレントの詳細情報を出力する
        FemaleTalent talent = talents.get(inputIndex);
        System.out.println(talent.toDetailString());

        // Scannerを閉じる
        scanner.close();
    }
}

image.png

このプログラムは完成です!

試しに色んな入力を試してみて、ちゃんと動くことを確かめてみましょう。

image.png

image.png

自分が作ったプログラムが思った通りに動く瞬間というのはいつだって楽しいものです。
この楽しさを忘れずに、またこの記事で学んだことを参考に、次は自分の作りたいものを作ってみましょう!

あとがき

先に謝っておきます。

いやつまり、おっぱい!とかそういうノリが嫌いな人もいるでしょう。不快に感じた方がいたらごめんなさい。

ただ、誰もがプログラミングを始めたくて始めるわけではありません。
そういう人たちがプログラミングの初歩を学ぼうとして途中で飽きてしまい、説明は読み飛ばしてソースコードだけコピペ、結果何も理解できずひどいことになる、みたいなのを何とかできないかと私が本気で考えた結果がおっぱいでした。
この記事の制作にあたっては知り合いの女性数名の監修を受けることでなるべくキモくならないように努めており、ふざけたテーマなりにできるだけ誠実であろうと努めましたから、何卒ご容赦頂ければと思います。

そういうわけですからこの記事をより良くするための意見、すなわち初心者としてこの辺解りにくかったとか、上級者としてこの辺おかしいぞとかそういうのあればぜひコメントにお寄せください。

ソースコードはgithubで公開中です。

https://github.com/ia15076/oppai-finder

公開しているコードでは記事中のコードを一部を少し応用的な書き方で書き直している他、この記事の作成に利用したクラス(Wikipediaをスクレイピングして女性タレントのプロフィールを収集するクラス、プロフィールの一覧をMarkdown記法の表で出力するクラスなど)もソースコードに含めてあります。

備考

というよりは文章の長さの都合で書きたかったけど書けなかった内容。

  • コンストラクタに渡す引数の数がかなり多くなってしまっていますが、引数の数を6個以上にすると順番がこんがらがってバグの元になりやすいので本来望ましくありません。
    • 誕生月日はMonthDay型で表すとか、ThreeSize型を新しく作ってスリーサイズはそれで表すとかすれば、メンバ変数の数は減らせる→コンストラクタに渡す引数の数も減らせるでしょう。
  • +演算子で文字列を結合するのは時間がかかるので、そういうの専用にStringBuilderというクラスもあります。
    • このくらいのプログラムで処理時間が問題になることは無いでしょうが、そういうのに配慮する気持ちは大事です
  • リストに女性タレントを追加する処理が長くなってますが、これも外部のCSVファイルなど読み込むようにすればループで書けます。

出典・参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Scannerを用いた入力内容の取得(Java)

Scannerとは

コンソールに値を「入力」し、その値をプログラム内で使用するための外部ライブラリです。
この記事では、文字列を受け取る方法と、数値を受け取る方法について記述します。

文字列で受け取る方法について

文字列で受け取る手順は以下になります。

  1. ライブラリの読み込み
  2. Scannerの初期化
  3. 入力内容を受け取るための処理

ライブラリの読み込み

Scannerを利用するためには外部ライブラリを読み込むための記述が必要になります。
まず、class定義より上にimport java.util.Scanner;の記述を追加します。

Main.java
import java.util.Scanner;

class Main {
  public static void main (String[] args) {
    System.out.print("名前: ");
  }
}

Scannerの初期化

次に、Scannerを初期化するために、Scanner scanner = new Scanner(System.in);の記述を追加します。
new Scanner(System.in)でScannerを初期化し、変数scannerに代入しています。

Main.java
import java.util.Scanner;

class Main {
  public static void main (String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.print("名前: ");
  }
}

入力内容を受け取るための処理

最後に、String name = scanner.next();の記述を追加します。
scanner.next()と記述することで、変数nameに入力された文字列を代入できます。

Main.java
import java.util.Scanner;

class Main {
  public static void main (String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.print("名前: ");
    String name = scanner.next();
    System.out.println("こんにちは" + name + "さん");
  }
}

数値で受け取る方法について

数値で受け取る手順についても以下の手順になりますが、文字列で受け取る手順と共通部分があるため、1と2は省略します。

  1. ライブラリの読み込み
  2. Scannerの初期化
  3. 入力内容を受け取るための処理

入力内容を受け取るための処理

以下のファイルに数値で受け取るための記述を追加していきます。
(手順1、2を終えた状態です)

Main.java
import java.util.Scanner;

class Main {
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    System.out.print("年齢:");

    System.out.print("身長(m):");

  }
}

年齢の値を受け取るために、
int age = scanner.nextInt();
の記述を追加します。
文字列で受け取る手順との相違点としてnextInt()と記述することで、データ型を指定しています。
なお、文字列で受け取るときに、nextString()などと記述するとエラーになるので注意です。

Main.java
import java.util.Scanner;

class Main {
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    System.out.print("年齢:");
    int age = scanner.nextInt();

    System.out.print("身長(m):");
    double height = scanner.nextDouble();

  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

classの切り分けについて(Java)

classの切り分けの必要性

以下のような冗長なコードを記述していると可読性が悪化します。
実行部分とロジック部分を同じファイルに記述しているためです。
この記事では実行部分をMainクラス、ロジック部分をpersonクラスに分割する手順について以下の順で記述しております。

  1. Personクラスの定義
  2. Personクラスにロジック部分のメソッドを移動
  3. Personクラスのメソッドを呼び出すように変更
Main.java
public class Main {
  public static void main(String[] args) {
    printData(fullName("Kate", "Jones"), 27, 1.6, 50.0);
    printData(fullName("John", "Christopher", "Smith"), 65, 1.75, 80.0);
  }

  public static void printData(String name, int age, double height, double weight) {
    System.out.println("私の名前は" + name + "です");
    System.out.println("年齢は" + age + "歳です");
    System.out.println("身長は" + height + "mです");
    System.out.println("体重は" + weight + "kgです");

    double bmi = bmi(height, weight);
    System.out.println("BMIは" + bmi + "です");

    if (isHealthy(bmi)) {
      System.out.println("標準値です");
    } else {
      System.out.println("標準値の範囲外です");
    }
  }

  public static String fullName(String firstName, String lastName) {
    return firstName + " " + lastName;
  }

  public static String fullName(String firstName, String middleName, String lastName) {
    return firstName + " " + middleName + " " + lastName;
  }

  public static double bmi(double height, double weight) {
    return weight / height / height;
  }

  public static boolean isHealthy(double bmi) {
    return bmi >= 18.5 && bmi < 25.0;
  }
}

Personクラスの定義

上記のMain.javaは以下の構成で記述しています。

  • 実行部分
    • mainメソッド
  • ロジック部分
    • printDataメソッド
    • fullNameメソッド
    • bmiメソッド
    • isHealthyメソッド

まず、Person.javaを作成しPersonクラスを定義します

Person.java
 class Person {
}

Personクラスにロジック部分のメソッドを移動

次に、mainクラスのロジック部分をPersonクラスに移動させます。

  • ロジック部分
    • printDataメソッド
    • fullNameメソッド
    • bmiメソッド
    • isHealthyメソッド
Person.java
 class Person {
   public static void printData(String name, int age, double height, double weight) {
    System.out.println("私の名前は" + name + "です");
    System.out.println("年齢は" + age + "歳です");
    System.out.println("身長は" + height + "mです");
    System.out.println("体重は" + weight + "kgです");

    double bmi = bmi(height, weight);
    System.out.println("BMIは" + bmi + "です");

    if (isHealthy(bmi)) {
      System.out.println("標準値です");
    } else {
      System.out.println("標準値の範囲外です");
    }
  }

  public static String fullName(String firstName, String lastName) {
    return firstName + " " + lastName;
  }

  public static String fullName(String firstName, String middleName, String lastName) {
    return firstName + " " + middleName + " " + lastName;
  }

  public static double bmi(double height, double weight) {
    return weight / height / height;
  }

  public static boolean isHealthy(double bmi) {
    return bmi >= 18.5 && bmi < 25.0;
  }
}
Main.java
public class Main {
  public static void main(String[] args) {
    printData(fullName("Kate", "Jones"), 27, 1.6, 50.0);
    printData(fullName("John", "Christopher", "Smith"), 65, 1.75, 80.0);
  }
}

Personクラスのメソッドを呼び出すように変更

切り分けはできましたが、このままMainクラスを実行するとエラーになります。
最後に、MainクラスにPersonクラスのメソッドを呼び出すための記述を以下のように追加します。

Main.java
public class Main {
  public static void main(String[] args) {
    Person.printData(Person.fullName("Kate", "Jones"), 27, 1.6, 50.0);
    Person.printData(Person.fullName("John", "Christopher", "Smith"), 65, 1.75, 80.0);
  }
}

これで問題なく実行できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

classの分割について(Java)

classの分割の必要性

以下のような冗長なコードを記述していると可読性が悪化します。
実行部分とロジック部分を同じファイルに記述しているためです。
この記事では実行部分をMainクラス、ロジック部分をpersonクラスに分割する手順について以下の順で記述しております。

  1. Personクラスの定義
  2. Personクラスにロジック部分のメソッドを移動
  3. Personクラスのメソッドを呼び出すように変更
Main.java
public class Main {
  public static void main(String[] args) {
    printData(fullName("Kate", "Jones"), 27, 1.6, 50.0);
    printData(fullName("John", "Christopher", "Smith"), 65, 1.75, 80.0);
  }

  public static void printData(String name, int age, double height, double weight) {
    System.out.println("私の名前は" + name + "です");
    System.out.println("年齢は" + age + "歳です");
    System.out.println("身長は" + height + "mです");
    System.out.println("体重は" + weight + "kgです");

    double bmi = bmi(height, weight);
    System.out.println("BMIは" + bmi + "です");

    if (isHealthy(bmi)) {
      System.out.println("標準値です");
    } else {
      System.out.println("標準値の範囲外です");
    }
  }

  public static String fullName(String firstName, String lastName) {
    return firstName + " " + lastName;
  }

  public static String fullName(String firstName, String middleName, String lastName) {
    return firstName + " " + middleName + " " + lastName;
  }

  public static double bmi(double height, double weight) {
    return weight / height / height;
  }

  public static boolean isHealthy(double bmi) {
    return bmi >= 18.5 && bmi < 25.0;
  }
}

Personクラスの定義

上記のMain.javaは以下の構成で記述しています。

  • 実行部分
    • mainメソッド
  • ロジック部分
    • printDataメソッド
    • fullNameメソッド
    • bmiメソッド
    • isHealthyメソッド

まず、Person.javaを作成しPersonクラスを定義します

Person.java
 class Person {
}

Personクラスにロジック部分のメソッドを移動

次に、mainクラスのロジック部分をPersonクラスに移動させます。

  • ロジック部分
    • printDataメソッド
    • fullNameメソッド
    • bmiメソッド
    • isHealthyメソッド
Person.java
 class Person {
   public static void printData(String name, int age, double height, double weight) {
    System.out.println("私の名前は" + name + "です");
    System.out.println("年齢は" + age + "歳です");
    System.out.println("身長は" + height + "mです");
    System.out.println("体重は" + weight + "kgです");

    double bmi = bmi(height, weight);
    System.out.println("BMIは" + bmi + "です");

    if (isHealthy(bmi)) {
      System.out.println("標準値です");
    } else {
      System.out.println("標準値の範囲外です");
    }
  }

  public static String fullName(String firstName, String lastName) {
    return firstName + " " + lastName;
  }

  public static String fullName(String firstName, String middleName, String lastName) {
    return firstName + " " + middleName + " " + lastName;
  }

  public static double bmi(double height, double weight) {
    return weight / height / height;
  }

  public static boolean isHealthy(double bmi) {
    return bmi >= 18.5 && bmi < 25.0;
  }
}
Main.java
public class Main {
  public static void main(String[] args) {
    printData(fullName("Kate", "Jones"), 27, 1.6, 50.0);
    printData(fullName("John", "Christopher", "Smith"), 65, 1.75, 80.0);
  }
}

Personクラスのメソッドを呼び出すように変更

分割できましたが、このままMainクラスを実行するとエラーになります。
最後に、MainクラスにPersonクラスのメソッドを呼び出すための記述を以下のように追加します。

Main.java
public class Main {
  public static void main(String[] args) {
    Person.printData(Person.fullName("Kate", "Jones"), 27, 1.6, 50.0);
    Person.printData(Person.fullName("John", "Christopher", "Smith"), 65, 1.75, 80.0);
  }
}

これで問題なく実行できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む