rubytomato's “Getting Started”

Webアプリケーション開発の入門的な記事を投稿していきます。

『入門向け』VSCodeでJavaScriptの学習環境を構築する

はじめに

この記事は、JavaScriptの基礎部分は既に入門書籍や他の入門サイトで学習中で、これから手を動かしてコーディングしながら知識を深めたいという人向けに作成しました。 コードエディターにはVSCodeを利用しますので、VSCodeの使い方を知りたいという人にもおすすめです。 なお、スクリーンショットを多用していますが画面やメニューの内容は2020年5月時点のバージョンのものです。今後バージョンアップによって変わることがありますので予めご了承ください。

環境

この記事の内容はWindows 10で作成、動作確認しています。MacOSユーザー方は適宜内容を読み替えてください(特にショートカットキーやフォルダーパス)。

VSCode (Visual Studio Code)のインストール

JavaScriptの学習環境にVSCodeを利用するため、まだインストールされていない方は下記の内容を参考にインストールしてください。すでにインストール済みの方は”拡張機能のインストール”まで読み飛ばしてください。

VSCodeとは

VSCode (Visual Studio Code)とは、マイクロソフトが開発している無料で使える軽量コードエディターです。動作が軽いうえに高機能で細かいカスタマイズが可能な点などが評価されて多くの利用者がいます。 JavaScriptのみならずhtml/cssのコーディングに最適なコードエディターなので、本記事でもこれを使って説明します。

ダウンロード

マイクロソフトダウンロードサイトにアクセスしてインストーラーをダウンロードします。

f:id:rubytomato:20200503230215p:plain
Fig.1

Windows版のインストーラーはUser InstallerSystem Installerzipの3種類あります。

  • User Installerはインストールに管理者権限が不要
  • System Installerはインストールに管理者権限が必要
  • zipはZipアーカイブを自身で展開して手動インストールするタイプ

この記事ではUser Installerの64bit版を選択しました。

f:id:rubytomato:20200503230302p:plain
Fig.2

インストール

2020年5月時点のWindows版のダウンロードファイル名はVSCodeUserSetup-x64-1.44.2.exeです。ダウンロードしたインストーラーを実行してインストールします。 基本的にはインストーラーのデフォルト設定のままインストールすれば問題ありません。追加タスクの選択画面(Fig.6)でいくつか設定ができますが、ここはお好みで設定してください。

f:id:rubytomato:20200503230413p:plain
Fig.3

f:id:rubytomato:20200503230428p:plain
Fig.4

f:id:rubytomato:20200503230444p:plain
Fig.5

f:id:rubytomato:20200503230459p:plain
Fig.6

f:id:rubytomato:20200503230514p:plain
Fig.7

f:id:rubytomato:20200503230531p:plain
Fig.8

インストールが完了するとWelcome画面(Fig.9)が立ち上がります。

f:id:rubytomato:20200503230614p:plain
Fig.9

バージョンの確認

メニューバーのHelpAbout でバージョンを確認できます。バージョンを確認するとFig.10の通りVersion 1.44.2 (user setup)がインストールされたことがわかります。

f:id:rubytomato:20200503230631p:plain
Fig.10

拡張機能のインストール

JavaScriptの学習に入る前にいくつか定番の拡張機能をインストールしておきます。 左側のサイドメニューの一番下のアイコンをクリックします。Fig11は拡張機能を管理するメニューで、マーケットプレイスから拡張機能を検索したりインストールすることや、インストールされている拡張機能やアンインストールすることができます。

f:id:rubytomato:20200503230735p:plain
Fig.11

拡張機能マーケットプレイスとは

拡張機能VSCodeの大きな特徴の1つです。拡張機能をインストールすることでVSCodeに新しい機能を追加することができます。どのような拡張機能があるかはマーケットプレイスというサービスで確認できます。

マーケットプレイスにアクセスすると、定番のものや最近人気が出たもの、新しく追加されたものなどを探せます。

Japanese Language Pack for Visual Studio Code

1つ目はメニューやメッセージを日本語化する拡張機能です。

検索フィールドに"japanese"と入力してEnterを押すと、検索結果が表示されます。この中から"Japanese Language Pack for Visual Studio Code"という拡張機能をインストールします。 インストールはFig.12の"Install"という緑色のラベルをクリックするだけです。

f:id:rubytomato:20200503230756p:plain
Fig.12

インストールすると再起動を促されるので再起動します。Fig.13が再起動後のWelcome画面でメニューが日本語化されています。(この画面下の"起動時にウェルカムページを表示"のチェックを外してください)

f:id:rubytomato:20200503230815p:plain
Fig.13

Visual Studio IntelliCode

次にインストールするのはAIを活用したIntelliSenseの機能が利用できる拡張機能です。コード補完で表示される候補がAIによって最適化されます。

f:id:rubytomato:20200503230836p:plain
Fig.14

Live Server

インストールするとローカルPCで開発用途の簡易HTTPサーバーを起動することができ、コーディング中のhtmlやJavaScriptなどの動作確認が簡単にできるようになります。 またホットリロードというファイルの変更を検知してブラウザ上のページを自動的にリロードする機能もあります。

f:id:rubytomato:20200503230946p:plain
Fig.15

とりあえず現時点ではこの3つだけインストールします。 これでVSCode拡張機能のインストールは完了です。

JavaScript学習用のプロジェクトを作成

学習環境が整ったので、以降はJavaScriptの学習に関する内容になります。 まずはVSCodeソースコードを管理できるようにプロジェクトを作成します。といってもソースコードを保存するフォルダーを作成して、そこにhtmlファイルとjsファイルを作成するだけです。

プロジェクトフォルダーの作成

この記事ではexercise-jsというフォルダーを作成しました。(作成場所やフォルダー名は任意です) 以後はこの場所にプロジェクトフォルダーがあるという前提で説明を行いますので、任意の場所にフォルダーを作成したい場合は適宜読み替えてください。

Fig.16はVSCodeexercise-jsを開いた直後の画面です。

f:id:rubytomato:20200503231006p:plain
Fig.16

htmlファイルの作成

JavaScriptの動作確認はブラウザ(この記事ではChromeを使用します)で行いますので、そのためのhtmlファイルを作成します。 Fig.17の"新しいファイル"を作成するアイコンをクリックし、ファイル名にindex.htmlと入力してhtmlファイルを作成します。

f:id:rubytomato:20200503231836p:plain
Fig.17

空のhtmlファイルがエディタ画面に表示されるのでFig.18のようにhtmlと入力し、候補からhtml:5を選択してEnterを押すとテンプレートのコードが展開されます。

f:id:rubytomato:20200503231905g:plain
Fig.18

titleタグを下記のように修正します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>exercise-js</title>
</head>
<body>

</body>
</html>

次にFig.19のようにbodyタグの中でdiv#appと入力してEnterを押すとコードが展開されます。さらにdivタグの中でh1と入力してEnterを押すとh1タグが展開されます。 このhtmlタグの展開はemmetという機能です(emmetについては後述します)。

f:id:rubytomato:20200503232011g:plain
Fig.19

この状態でLive Serverを立ち上げてブラウザでindex.htmlを表示してみます。 エディタ画面上で右クリックしメニューからOpen with Live Serverをクリックします。

f:id:rubytomato:20200503232034p:plain
Fig.20

ブラウザが起動し、Fig.21の画面が表示されたと思います。

f:id:rubytomato:20200503232049p:plain
Fig.21

続いて下記のようにh1タグの下にdivタグを追加してファイルを保存すると、自動的にページがリロードされて表示が変わります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>exercise-js</title>
</head>
<body>
    <div id="app">
        <h1>exercise-js</h1>
        <div>hello javascript</div>       <!-- 追加する -->
    </div>
</body>
</html>

f:id:rubytomato:20200503232330p:plain
Fig.22

emmetについて

上記でも触れたemmetですが、これはVSCodeにデフォルトで組み込まれているhtml/cssを簡単にコーディングできる機能です。

たとえば div#app と入力してEnterを押すと

<div id="app"></div>

と展開されます。入力したdivの部分がhtmlタグを表し、つぎの#の部分がidを意味し、残りのappがid名となります。

同様に div.contents と入力してEnterを押すと

<div class="contents"></div>

と展開されます。.(ピリオド)の部分がclassを意味し、残りのcontentsがclass名となります。

ちなみにclass名は複数付けることができますが、.でclass名を繋げることで複数指定できます。 例えば div.card.infoと入力してEnterを押すと

<div class="card info"></div>

と展開されます。

ネストしたタグを表現する

ul,liのようにネストしたタグもemmetを使って簡単にコーディングできます。 ul>liと入力してEnterを押すと

<ul>
    <li></li>
</ul>

と展開します。

liタグを複数個展開したい場合は個数を指定することもできます。個数は*3のように表記し ul>li*3と入力してEnterを押すと

<ul>
    <li></li>
    <li></li>
    <li></li>
</ul>

と、ulタグの中にliタグが3つ展開されます。

emmetの機能は他にもありますので、"VSCode emmet"等のキーワードで検索して調べてみてください。

jsファイルの作成

*.jsファイルはjsというフォルダーに作成するようにします。 Fig.23の"新しいフォルダー"を作成するアイコンをクリックし、フォルダー名にjsと入力してjsフォルダーを作成します。

f:id:rubytomato:20200503232355p:plain
Fig.23

そのjsフォルダにmain.jsという名前のjsファイルを作成しFig.24のように1行だけコードを記述します。

f:id:rubytomato:20200503232417p:plain
Fig.24

次にindex.htmlを開き、下のgifのようにbodyタグの直前にscriptタグを追加します。

f:id:rubytomato:20200503232438g:plain
Fig.25

ファイルを保存するとページがリロードされます。 JavaScriptconsole.log(...)の出力を確認するには、ブラウザでF12(またはCtrl + Shift + i)を押して開発者ツールを開きconsoleタブを選択します。 consoleタブに"it works!"という文字列が表示されていれば成功です。

f:id:rubytomato:20200503232459p:plain
Fig.26

Live Serverについて

Live Serverを一度起動すると、ステータスバーにLive Serverのステータスが表示されるようになります。 Fig.27はポート5500でLive Serverが起動中であることを示していて、ここをクリックすると待機中になります。

f:id:rubytomato:20200503232515p:plain
Fig.27

待機中はFig.28のような表示になり、以降はここをクリックするだけで起動と待機を切り替えることができます。

f:id:rubytomato:20200503232532p:plain
Fig.28

設定

Live Serverの設定はユーザー設定から変えることができます。ユーザー設定はCtrl + ,を押すと表示されます(Fig.29)。 Live Serverの設定項目を探すには検索フィールドに"live server"と入力して絞り込みを行います。

f:id:rubytomato:20200503232751p:plain
Fig.29

主な設定項目

Custom Browser

Live Serverで使用するブラウザーを指定します。デフォルトはお気に入りのブラウザです。

Specify custom browser settings for Live Server. By Default it will open your default favorite browser.

File

Live Serverで開くファイルを指定します。デフォルトはindex.htmlです。

When set, serve this file (server root relative) for every 404 (usefull for single-page applications)

Full Reload

CSS変更時に完全なページのリロードを行います。デフォルトでは完全な再読み込みを行わずに変更のあるCSSを挿入します。

By Default Live Server inject CSS changes without full reloading of browser. you can change this behviour by making this setting as 'true'

Host

Live Serverが使用するホストを指定します。デフォルトは"127.0.0.1"です。 "localhost"に代えたい場合は"localhost"と指定します。

To swith between localhost or '127.0.0.1' or anything else. Default is '127.0.0.1'

Port

Live Serverが使用するポートを指定します。デフォルトは5500です。 0を指定するとポートはランダムになります。

Set Custom Port Number of Live Server. Set 0 if you want random port.

Root

ルートディレクトリを指定します。デフォルトはワークスペースです。 ワークスペース内のdistをルートに代えたい場合は、"/dist"と指定します。

Set Custom root of Live Server. To change root the server to sub folder of workspace, use '/' and relative path from workspace.

変更した項目

設定を変えると、項目の左側に青い線が表示されます。Fig.30はポートを変更したときの状態です。

f:id:rubytomato:20200503232907p:plain
Fig.30

ファンクションを作成する

index.htmlを開きFig.31のようにdivタグを追加します。

f:id:rubytomato:20200503232924g:plain
Fig.31

次にmain.jsを開き下記のコードを追記して保存します。

function createElement(message = "ワールド") {
    const template = `<p>
      hello ${message}
    </p>`
    return template
}

const message = createElement("world")

const contents = document.getElementsByClassName("contents")[0]
contents.innerHTML = message

Live Serverを起動していれば、ページがリロードされて"hello world"が表示されたと思います。

f:id:rubytomato:20200503233034p:plain
Fig.32

テンプレートリテラル

createElementファンクションでtemplateという名前の変数の定義していますが、この変数の初期化にテンプレートリテラルという構文を使っています。 テンプレートリテラルはECMAScript2015(ES6)で追加された構文で、文字列リテラルにプレースフォルダ(${ })を含めることができ、そこに変数や式を埋め込むことができます。

デフォルト引数

createElementファンクションのmessage引数にmessage = "ワールド"のようにデフォルト値が与えられていますが、これもECMAScript2015(ES6)で追加されたデフォルト引数(またはデフォルトパラメータ)という構文です。 下記のように呼び出し時にパラメータを渡さなかった場合、デフォルト値が利用されます。

const message = createElement()

jsファイルを分離する

main.jsにコーディングしたfunctionを別のファイルに分けてみます。 jsフォルダにsub.jsというファイルを作成し、main.jsから下記のコードを移動させます。

下記がsub.jsのコードです。

function createElement(message = "ワールド") {
    const template = `<p>
      ${message}
    </p>`
    return template
}

下記がmain.jsのコードです。

console.log("it works!")

const message = createElement("world")

const contents = document.getElementsByClassName("contents")[0]
contents.innerHTML = message

次にindex.htmlを開きmain.jsを読み込んでいるscriptタグの下にsub.jsを読み込むscriptタグを追加します。

<script src="./js/main.js"></script>
<script src="./js/sub.js"></script>   <!-- 追加 -->

ファイル保存後にブラウザで動作確認すると"hello world"というメッセージが表示されなくなっていると思います。 consoleタブを見るとFig.33のようなエラーメッセージが表示されていて、このメッセージから"createElement"が定義されていないことが原因だとわかります。

f:id:rubytomato:20200503233102p:plain
Fig.33

これを厳密に言うと"createElement"は定義されますが、下記のようにsub.jsで"createElement"が定義される前に、main.js内で"createElement"ファンクションが呼び出されているため、ということになります。

<script src="./js/main.js"></script>  <!-- createElementファンクションの呼び出し -->
<script src="./js/sub.js"></script>   <!-- createElementファンクションの定義 -->

この問題を解決するにはjsファイルの読み込み順を下記のように入れ替え、先に"createElement"ファンクションの定義を行うようにします。

<script src="./js/sub.js"></script>   <!-- createElementファンクションの定義 -->
<script src="./js/main.js"></script>  <!-- createElementファンクションの呼び出し -->

ちなみに、行の入れ替えはVSCodeのショートカットキーを使うと簡単にできます。 入れ替えたい行にカーソルを置いた状態で、現在の行を上の行と入れ替えるには上矢印キー、もしくは下の行と入れ替えるには下矢印キーを押します。

Fig.34はsub.jsを読み込んでいる行を上の行と入れ替えている例です。

f:id:rubytomato:20200503233122g:plain
Fig.34

行を入れ替えたらファイルを保存し再度ブラウザで動作確認を行います。これでページに"hello world"というメッセージが表示されていると思います。 このようにJavaScriptは上から下へ順番に解釈されていくので定義順が重要です。

別の解決方法 (document.addEventListener)

上記でjsファイルの読み込み順を変えて解決しましたが、別にdocument.addEventListenerを使って解決する方法があります。この方法ではjsファイルの読み込み順を変える(意識する)必要はありません。

<script src="./js/main.js"></script>  <!-- createElementファンクションの呼び出し -->
<script src="./js/sub.js"></script>   <!-- createElementファンクションの定義 -->

main.jsを下記のように書き換え、コード全体をdocument.addEventListener("DOMContentLoaded", function(event){ ... })で囲みます。

document.addEventListener("DOMContentLoaded", function(event) {

    const message = createElement()
    const contents = document.getElementsByClassName("contents")[0]
    contents.innerHTML = message

})

このコードは大別すると3つの要素に分かれています。

document.addEventListener("DOMContentLoaded", function(event) { ... })
^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^
 |                          |                  |
 |                          |                  +--- (3)
 |                          |
 |                          +---------------------- (2)
 |
 +------------------------------------------------- (1)

1) document.addEventListener

addEventListenerは対象オブジェクトに、何らかのイベントが発生したときに、実行する何らかのファンクションを追加します。 この例での対象オブジェクトはdocumentです。つまりdocumentオブジェクトに(2)任意のイベントが発生した時に、実行する(3)任意のファンクションを追加します。

2) 任意のイベント

イベントの種類を表しています。

このDOMContentLoadedイベントはdocumentオブジェクトのイベントで、HTMLが完全に読み込まれ解釈された時点で発生します。またJavaScriptの読み込みも完了している(例外があります)ので、定義しているファンクションも使えます。ただし、まだスタイルシート、画像、サブフレームの読み込みは終わっていない可能性があります。

3) 任意のファンクション

対象のオブジェクトに指定したイベントが発生したときに実行されるファンクションを指定します。

別の解決方法 (import/export)

さらにもう1つ別の解決方法があります。それはEcmaScript2015(ES6)で追加された、import/export構文を使用する方法です。

sub.jsでexportを使ってファンクションを公開します。

export function createElement(message = "ワールド") {
    const template = `<p>
      hello ${message}
    </p>`
    return template
}

main.jsではimportを使ってsub.jsから公開されているファンクションを読み込みます。 このように前述のdocument.addEventListener("DOMContentLoaded", function(event) { ... })は使わなくても動作しますが、

import { createElement } from "./sub.js"

const message = createElement()
const contents = document.getElementsByClassName("contents")[0]
contents.innerHTML = message

コードの堅牢制(バグの発生のしにくさ)を考えると、イベントを使った方がいいと思いますので下記のようにします。

import { createElement } from "./sub.js"

document.addEventListener("DOMContentLoaded", function(event) {

    const message = createElement()
    const contents = document.getElementsByClassName("contents")[0]
    contents.innerHTML = message

})

最後にhtmlファイルを修正します。htmlファイルで読み込む必要があるのはmain.jsだけなので、sub.jsを読み込むscriptタグは削除します。 なおimport/exportを使う場合はscriptタグにtype=moduleという記述が必要なので追記します。

<script src="./js/main.js" type="module"></script>  <!-- createElementファンクションの呼び出し -->
<!-- <script src="./js/sub.js"></script> -->  <!-- 読み込み不要 -->

classを使う

次にJavaScriptのclassを使ったコードを学習していきます。class構文もECMAScript2015(ES6)で追加された構文です。

jsフォルダーにitem.jsというファイルを作成し、下記のコードを記述します。main.jsで利用するためexportを付けています。

export class Item {
    // コンストラクタ
    constructor(id, name, price) {
        this.id = id
        this.name = name
        this.price = price
    }
    // メソッド
    toString() {
        return `id:${this.id} name:${this.name} price:${this.price}`
    }
}

main.jsを下記のように修正します。先頭の方でitem.jsのItemクラスをインポートします。 Itemクラスのオブジェクトを生成するにはnew Item( ... )としてnew演算子を使用します。

import { createElement, setItemRow } from "./sub.js"
import { Item } from "./item.js"  // ← 追加

document.addEventListener("DOMContentLoaded", function(event) {
    console.log("DOM fully loaded and parsed", event)

    const message = createElement()
    const contents = document.getElementsByClassName("contents")[0]
    contents.innerHTML = message

    // ↓ 追加
    const apple = new Item(1, "apple", 100)
    const orange = new Item(2, "orange", 80)
    const grape = new Item(3, "grape", 120)
    const items = [apple, orange, grape]
    // ↑ 追加

})

itemsという配列に格納したItemクラスのオブジェクトの文字列情報を、ItemクラスのtoStringメソッドで出力してみます。

これまで配列の操作には

forを使う方法

for (let i=0; items.length>i; i++) {
    console.log(items[i].toString())
}

forEachを使う方法

items.forEach(item => {
    console.log(item.toString())
})

がありましたが、ECMAScript2015(ES6)からfor ofという構文も追加されています。

for (const item of items) {
    console.log(item.toString())
}

せっかくなのでItemクラスのオブジェクトをHTMLのtableで表示するように修正を加えてみます。

index.htmlを修正してtableタグを追加します。tbody内にデータを出力するのはJavaScriptで行うので空の状態です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>exercise-js</title>
</head>
<body>
    <div id="app">
        <h1>exercise-js</h1>
        <div>hello javascript</div>
        <div class="contents"></div>
        <!-- ↓ 追加 -->
        <table class="item">
            <thead>
                <tr>
                    <th>id</th>
                    <th>name</th>
                    <th>price</th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
        <!-- ↑ 追加 -->
    </div>
    <script src="./js/main.js" type="module"></script>
</body>
</html>

sub.jsを修正してtbody内にtr、tdタグを出力するファンクションを追加します。trタグを出力するcreateItemRecordファンクションはmain.jsで使用するのでexportを付けているのに対して、tdタグを出力するcreateItemDataファンクションはこのsub.js内でしか使用しないためexportを付けません。このようにexportを付けるかどうかは、そのファンクションの使用範囲(公開範囲)で決めます。

export function createItemRecord(item) {
    const id = createItemData(item.id)
    const name = createItemData(item.name)
    const price = createItemData(item.price)

    const tr = document.createElement("tr")
    tr.appendChild(id)
    tr.appendChild(name)
    tr.appendChild(price)
    return tr
}

function createItemData(value) {
    const td = document.createElement("td")
    const nd = document.createTextNode(value)
    td.appendChild(nd)
    return td
}

main.jsの先頭のimport文をこのように修正してcreateItemRecordファンクションをインポートします。

import { createElement, createItemRecord } from "./sub.js"

下記はitems配列のデータを使ってtbodyタグ内にtr>tdタグを追加するコードです。

const apple = new Item(1, "apple", 100)
const orange = new Item(2, "orange", 80)
const grape = new Item(3, "grape", 120)
const items = [apple, orange, grape]

const itemTable = document.querySelector("table.item > tbody")
for (const item of items) {
    const itemRow = createItemRecord(item)    
    itemTable.appendChild(itemRow)
}

上記のコードを実行すると、ブラウザにFig35のようなテーブルが表示されていると思います。

f:id:rubytomato:20200503233929p:plain
Fig.35

CSSでスタイル

プロジェクトの直下にcssというフォルダーを作成し、そこにmain.cssというファイルを作成します。 とりあえず何も書いていないCSSファイルのままで、index.htmlを修正しheadタグ内にlinkタグを追加します。

<link rel="stylesheet" href="./css/main.css">

次にmain.cssファイルにスタイルを記述していきます。まず最初に下記のスタイルを記述してファイルを保存します。

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

#app {
    margin: 20px;
}

.contents {
    margin: 20px 0;
    font-size: 20px;
}

Live Serverを動かして動作確認をしていれば、main.cssファイルの保存を行ったタイミングでブラウザ上のページにスタイルが当たったことがわかると思います。 次にtableのスタイルを追加します。

table {
    border-collapse: collapse;
    font-size: 18px;
}

th {
    background-color: black;
    color: white;
}

th, td {
    padding: 0.5em 10px 0.5em;
    border-top: 1px solid #666;
}

tr:last-child td {
  border-bottom: 1px solid #666;  
}

td:last-child {
    text-align: right;
}

ファイルを保存するとページがリロードされてFig.36のようにスタイルが適用されたと思います。

f:id:rubytomato:20200503233951p:plain
Fig.36

さいごに

VSCodeを使ったJavaScriptの学習環境の構築とVSCodeの簡単な使い方の説明は以上になります。 これまでに作成したソースコード全体は下記の通りです。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="./css/main.css">
    <title>exercise-js</title>
</head>
<body>
    <div id="app">
        <h1>exercise-js</h1>
        <div>hello javascript</div>
        <div class="contents"></div>
        <table class="item">
            <thead>
                <tr>
                    <th>id</th>
                    <th>name</th>
                    <th>price</th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
    <script src="./js/main.js" type="module"></script>
</body>
</html>

main.css

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

#app {
    margin: 20px;
}

.contents {
    margin: 20px 0;
    font-size: 20px;
}

table {
    border-collapse: collapse;
    font-size: 18px;
}

th {
    background-color: black;
    color: white;
}

th, td {
    padding: 0.5em 10px 0.5em;
    border-top: 1px solid #666;
}

tr:last-child td {
  border-bottom: 1px solid #666;  
}

td:last-child {
    text-align: right;
}

main.js

import { createElement, createItemRecord } from "./sub.js"
import { Item } from "./item.js"

console.log("it works!")

document.addEventListener("DOMContentLoaded", function(event) {

    const message = createElement("world")
    const contents = document.getElementsByClassName("contents")[0]
    contents.innerHTML = message

    const apple = new Item(1, "apple", 100)
    const orange = new Item(2, "orange", 80)
    const grape = new Item(3, "grape", 120)
    const items = [apple, orange, grape,]

    const itemTable = document.querySelector("table.item > tbody")
    for (const item of items) {
        const itemRow = createItemRecord(item)
        itemTable.appendChild(itemRow)
    }

})

sub.js

export function createElement(message = "ワールド") {
    const template = `<p>
      hello ${message}
    </p>`
    return template
}

export function createItemRecord(item) {
    const id = createItemData(item.id)
    const name = createItemData(item.name)
    const price = createItemData(item.price)

    const tr = document.createElement("tr")
    tr.appendChild(id)
    tr.appendChild(name)
    tr.appendChild(price)
    return tr
}

function createItemData(value) {
    const td = document.createElement("td")
    const nd = document.createTextNode(value)
    td.appendChild(nd)
    return td
}

item.js

export class Item {
    // コンストラクタ
    constructor(id, name, price) {
        this.id = id
        this.name = name
        this.price = price
    }
    // メソッド
    toString() {
        return `id:${this.id} name:${this.name} price:${this.price}`
    }
}

『入門向け』Java開発環境の構築とかんたんなコードの書き方と実行方法 (後編)

はじめに

この記事は、 rubytomato.hateblo.jp rubytomato.hateblo.jp に続く記事の後編です。 前編、中編ではJava開発環境の構築とEclipseの簡単な使い方を中心に説明しました。この記事では引き続きJavaプログラムの書き方と実行方法を中心に説明します。

Javaプログラムの書き方

中編のおさらい

中編のレッスン1では、Fig1のようにstudy01projectプロジェクトにDemoクラスとSnackクラスまで作成しました。

f:id:rubytomato:20200404113228p:plain
Fig1.

レッスン2

レッスン2では、レッスン1で作成した「おかし」を表現するSnackクラスに加え、「おかしを売る店」を表現するShopクラスと「おかしを買う客」を表現するPersonクラスを使って、お店とお客がおかしを売買する機能をコーディングしていきます。

おかしを売買できる条件として以下の3つを仕様とします。

  • お店(Shop)は扱っているおかししか売れないこと
  • お客(Person)はおかしを買うのに代金を支払うこと
  • お客(Person)は買ったおかしをおかし袋に仕舞えること(最大5個まで仕舞える)

また、そのほかの仕様として

  • お店(Shop)はおかしをいくつでも販売できる。(おかしの在庫数は管理しない)
  • おかしの売買は『おかしの名前』で行う。(『おかしの名前』が一意で管理できる(重複しない)こと)

とします。

レッスン2用のパッケージを作成する

まずレッスン2用のパッケージcom.example.lesson02を作成し、lesson02パッケージにDemoクラスを作成します。Fig2はここまで作成した状態です。

f:id:rubytomato:20200404113719p:plain
Fig2.

Demoクラス

Demo.javaソースコードは以下の通りです。レッスン1同様にこのクラスのmainメソッドにレッスン2で実行するコードを実装していきます。

package com.example.lesson02;

public class Demo {

    public static void main(String[] args) {
        System.out.println("Lession02 Demo start");

        // ここに動作確認するコードを追加していく

        System.out.println("Lession02 Demo end");
    }

}
Snackクラスを修正する

おかしの売買を『おかしの名前』で行うためには『おかしの名前』が重複なく一意であることが求められます。そのためにSnackクラスのnameフィールドでオブジェクトを一意に識別できるように実装する必要があります。(※ちなみにオブジェクトを一意に識別するフィールドには社員IDや商品IDといったIDフィールドを使うことが一般的です。)

Snackクラスの2つのオブジェクトが同一(おなじおかしを指すか)であるか判別するには、SnackクラスのhashCodeequalsメソッドを正しくオーバーライドする必要があります。 たとえばSnackクラスのhashCodeequalsメソッドをオーバーライドしていない状態で以下のコードを実行すると、同じおかしの名前を持つ2つのオブジェクト(salami_1とsalami_2)なのに”違うおかし”と判断されます。

Snack salami_1 = new Snack("うまいぼう サラミ味", 10);
Snack salami_2 = new Snack("うまいぼう サラミ味", 10);

if (salami_1.equals(salami_2)) {
    System.out.println("同じおかし");
} else {
    System.out.println("違うおかし");
}

この2つのオブジェクトを ”同じおかし”である(おかしの名前が同じなので)と判別するには、hashCodeequalsメソッドを以下のようにオーバーライドします。

  • メニューバーのソース(S)hashCode() および equals() の生成(H)...を選択
  • キーボードのAlt + Shift + Sを押し、メニューのhashCode() および equals() の生成(H)...を選択

"hashCode() および equals() の生成"画面(Fig3)のnameフィールドだけにチェックを入れ、"生成"ボタンをクリックします。

f:id:rubytomato:20200404113257p:plain
Fig3.

すると、Eclipseによって以下のコードが生成されます。これでnameフィールドが同じであれば”同じおかし”であると判別されるようになります。

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Snack other = (Snack) obj;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

先ほどのコードを実行すると2つのオブジェクトは”同じおかし”と判断されます。

ちなみに”おかしの名前”と”価格”の2つのフィールドが同一であれば同じおかしと判別したい場合は、"hashCode() および equals() の生成"画面(Fig3)でnamepriceフィールドにチェックを入れてコードを生成しなおします。

Shopクラスを作成する

まずcom.example.lesson02.modelパッケージを作成し、modelパッケージにShopクラスを作成します。 Fig4はmodelパッケージおよびShopクラスを作成した直後のプロジェクトの状態です。

f:id:rubytomato:20200404113314p:plain
Fig4.

以下がShopクラスを作成した直後のソースコードです。

package com.example.lesson02.model;

public class Shop {

}
フィールドを定義する

Shopクラスに、お店が扱うおかしの種類を持つSet型のsnackVarieties、おかしの売上金を持つint型のsalesの2つのフィールドを定義します。同じ名前のおかしを持たないようにsnackVarietiesSet型にしています。

アクセサメソッドは必要がないので実装しません。

/**
 * お店が扱うおかしの種類
 */
private Set<Snack> snackVarieties;

/**
 * 売上金
 */
private int sales;

ちなみに、下記のようにこのフィールドをList型にすると同じ名前のおかしを複数追加できてしまいます。

private List<Snack> snackVarieties;
toStringメソッドをオーバーライドする

Eclipseの機能を使ってtoStringメソッドをオーバーライドします。

@Override
public String toString() {
    return "Shop [snackVarieties=" + snackVarieties + ", sales=" + sales + "]";
}
コンストラクターを実装する

デフォルトコンストラクタ―と、引数におかしの種類と売上金を取るコンストラクタ―の2つを実装します。

// デフォルトコンストラクタ―
public Shop() {
    this(new HashSet<>(), 0);
}

public Shop(Set<Snack> snackVarieties, int sales) {
    this.snackVarieties = snackVarieties;
    this.sales = sales;
}
おかしの種類を追加するメソッドを実装する

お店が扱うおかしの種類を追加するメソッドをaddSnackVarietyという名前で実装します。上述した通りsnackVarietiesフィールドはSet型なので、オブジェクトを追加する際の重複チェックは不要です。同じ名前のおかしを登録しても無視されます。

/**
 * お店が扱うおかしの種類を追加する
 * @param snack
 */
public void addSnackVariety(Snack snack) {
    this.snackVarieties.add(snack);
}

さらに、お店が扱うおかしを複数まとめて追加するメソッドをaddSnackVarietiesという名前で実装します。このメソッドの(Snack ... snacks)の部分を可変長引数といい、複数のパラメータをまとめて渡すことができます。 その可変長引数snacksを取るSet.ofメソッドは可変長引数をSet型のコレクションにして返します。

/**
 * お店が扱うおかしの種類を複数まとめて追加する
 * @param snacks
 */
public void addSnackVarieties(Snack... snacks) {
    this.snackVarieties.addAll(Set.of(snacks));
}
お店がおかしを扱っているか確認するメソッドを実装する

引数で指定した名前のおかしをお店が扱っているか確認する処理をisAvailableSnackメソッドに実装します。と言っても実際の検索処理はgetSnackメソッドで行い、その結果を真偽値で返すだけの処理になります。

/**
 * 指定した名前のおかしを扱っているか確認する
 * @param snackName おかしの名前
 * @return true:扱っている
 */
public boolean isAvailableSnack(String snackName) {
    return getSnack(snackName).isPresent();
}

Set型のコレクションからおかしの名前で検索する処理はgetSnackメソッドに実装します。このメソッドはShopクラス内からのみ利用するのでメソッドの可視性はprivateにして他のクラスから利用されないようにします。

また、指定した名前のおかしを必ず扱っているとは限らないのでこのメソッドの戻り値はOptionalでラップします。見つかったおかしのオブジェクトはOptional.ofでラップして返し、もし扱っていない(見つからなかった)場合はnullではなくOptional.empty()を返すようにします。

/**
 * 指定した名前のおかしのオブジェクトを返す
 * @param snackName おかしの名前
 * @return おかしのオブジェクト
 */
private Optional<Snack> getSnack(String snackName) {
    Iterator<Snack> ite = snackVarieties.iterator();
    while (ite.hasNext()) {
        Snack snack = ite.next();
        if (snack.getName().equals(snackName)) {
            return Optional.of(snack);
        }
    }
    return Optional.empty();
}
お店が扱っているおかしの種類を表示するメソッドを実装する

扱うおかしを出力する処理はprettyPrintSnackVarietyメソッドに実装します。この記事ではデバッグ用に使用していますが、オブジェクトの状態を出力するメソッドがあると便利なときがあります。

/**
 * お店が扱っているおかしを表示する
 */
public void prettyPrintSnackVariety() {
    if (snackVarieties.isEmpty()) {
        System.out.println("取り扱うおかしはありません");
        return;
    }
    snackVarieties.forEach(snack -> {
        System.out.println(snack.getName() + "は" + snack.getPrice() + "円");
    });
    System.out.println("計" + snackVarieties.size() + "種類のおかしを取り扱い中");
}
Personクラスを作成する

modelパッケージにPersonクラスを作成します。 Fig5はmodelパッケージおよびPersonクラスを作成した直後のプロジェクトの状態です。

f:id:rubytomato:20200404113340p:plain
Fig5.

以下がPersonクラスを作成した直後のソースコードです。

package com.example.lesson02.model;

public class Person {

}
フィールドを定義する

Personクラスに、人の名前を持つString型のname、所持金を持つint型のmoney、持っているおかしを持つList型のsnackBagの3つのフィールドと、おかしの最大所持数を持つ定数を定義します。おかし袋のsnackBagフィールドはList型なのでおなじ名前のおかしを複数持つことができます。

このクラスもアクセサメソッドは必要がないので実装しません。

/**
 * 名前
 */
private String name;

/**
 * 所持金
 */
private int money;

/**
 * おかし袋
 */
private List<Snack> snackBag;

/**
 * おかし袋に入れられるおかしの最大数
 */
private static final int MAX_SNACK_NUM = 5;
toStringメソッドをオーバーライドする

Eclipseの機能を使ってtoStringメソッドをオーバーライドします。

@Override
public String toString() {
    return "Person [name=" + name + ", money=" + money + ", snackBag=" + snackBag + "]";
}
コンストラクターを実装する

名前のname、所持金のmoneyは必須のフィールドなので、コンストラクターの引数で初期化するようにします。 おかし袋のsnackBagの要素数MAX_SNACK_NUM定数で初期化します。(※あくまでも定数の値で要素数を確保するだけで、要素数の最大値を設定しているわけではありません。)

public Person(String name, int money) {
    assert name != null;
    this.name = name;
    this.money = money;
    this.snackBag = new ArrayList<>(MAX_SNACK_NUM);
}
持っているおかしを出力するメソッドを実装する

持っているおかしを出力する処理をprettyPrintHasSnackメソッドに実装します。このメソッドもこの記事ではデバッグ用に使用します。

/**
 * 持っているおかしを表示する
 */
public void prettyPrintHasSnack() {
    StringBuilder sb = new StringBuilder(name);
    sb.append("は 所持金:");
    sb.append(money);
    sb.append("円");
    if (!snackBag.isEmpty()) {
        sb.append("とおかし ");
        String comma = "";
        for (Snack snack : snackBag) {
            sb.append(String.format("%s(name:%s, price=%d)", comma, snack.getName(), snack.getPrice()));
            comma = ", ";
        }
    }
    sb.append(" を持っている");
    System.out.println(sb.toString());
}
おかしを売買する機能を実装する

Personクラスの実装

  • Shopクラスとおかしの売買を行うdealメソッド
  • おかしを買うbuySnackメソッド

Shopクラスの実装

  • Personクラスへおかしを売るsellSnackメソッド
売買が失敗した場合

売買が出来なかった場合に例外をスローするようにします。想定する売買できない状況というのは

  • お店(Shop)が取り扱っていないおかしを購入しようとした場合
  • お客(Person)が購入するおかしの代金を支払えない場合
  • お客(Person)のおかし袋に空きがない場合

があります。このような場合はDealExceptionというRuntimeExceptionを継承した例外クラスをスローするようにします。

f:id:rubytomato:20200404113424p:plain
Fig6.

package com.example.lesson02;

public class DealException extends RuntimeException {

    public DealException(String message) {
        super(message);
    }

}
Shopクラスにおかしを売るsellSnackメソッドを実装する

指定した名前のおかしがお店で扱われているかgetSnackメソッドで確認し、扱われている場合はPersonクラスのbuySnackメソッドでおかしを買う処理を実行します。その処理が正常終了したら代金を売上金に加算します。

なおFig7のようにcustomer.buySnack(snack);の部分にエラーが出るとおもいますが、とりあえずこのままにして次のPersonクラスの実装に移ります。

f:id:rubytomato:20200404113447p:plain
Fig7.

/**
 * おかしを売る
 * @param snackName 売りおかしの名前
 * @param customer おかしを買う人
 * @throws DealException おかしの売買が失敗した場合
 */
public void sellSnack(String snackName, Person customer) {
    Optional<Snack> optionalSnack = getSnack(snackName);
    optionalSnack.ifPresentOrElse(snack -> {
        // おかしが見つかった場合
        // 購入
        int customerPay = customer.buySnack(snack);
        // 売り上げの加算
        sales += customerPay;
    }, () -> {
        // おかしが見つからなかった場合
        throw new DealException(snackName + "は取り扱っていません");
    });
}

getSnackメソッドはOptional型でラップしたSnackクラスのオブジェクトを返します。Optional型でラップするということは、指定した『おかしの名前』でおかしが見つからない場合があるということを表しているので、見つかった場合と見つからなかった場合の両方の処理を忘れずに実装します。

Optional<Snack> optionalSnack = getSnack(snackName);

その次のoptionalSnack.ifPresentOrElse( ... )というコードの部分は、以下のコードの書き方と同じ意味になります。isPresentメソッドがtrueを返す場合はおかしが見つかった場合ということなので、取引処理を実行します。それ以外は見つからなかった場合ということなので例外をスローします。

// 指定した『おかしの名前』でおかしが見つかった場合
if (optionalSnack.isPresent()) {
    Snack snack = optionalSnack.get();
    // 購入
    int customerPay = customer.buySnack(snack);
    // 売り上げの加算
    sales += customerPay;

// おかしが見つからなかった場合
} else {
    throw new DealException(snackName + "は取り扱っていません");
}
Personクラスにおかしの売買をするdealメソッドを実装する

PersonクラスとShopクラス間のおかしを売買する処理はdealメソッドに実装します。メソッドの先頭で、引数がnullであれば不正(バグ)ということを示すためにassert文でチェックを行います。 なお、実際の売買はShopクラスのsellSnackメソッドで行います。

/**
 * お店と取引する
 * @param shop 取引するお店
 * @param snackName 買うおかしの名前
 */
public void deal(Shop shop, String snackName) {
    assert shop != null;
    assert snackName != null;
    try {
        shop.sellSnack(snackName, this);
    } catch (DealException e) {
        System.out.println(snackName + "の購入に失敗しました");
        System.err.println(e.getMessage());
    }
}

buySnackメソッドではおかしを買える所持金を持っているかとおかし袋に空きがあるかのチェックを行い、条件を満たしていない場合はDealExceptionをスローして処理を中断するようにしています。

/**
 * おかしを買う
 * @param snack 買うおかしのオブジェクト
 * @return 支払った金額
 * @throws DealException おかしが買えなかった場合にスロー
 */
int buySnack(Snack snack) throws DealException {
    if (money < snack.getPrice()) {
        throw new DealException("所持金が足りません");
    }
    if (MAX_SNACK_NUM <= snackBag.size()) {
        throw new DealException("おかし袋に空きがありません");
    }
    // 所持金からおかしの価格を引く
    money -= snack.getPrice();
    // おかし袋におかしを追加する
    snackBag.add(snack);
    return snack.getPrice();
}
Demoクラス

上記の実装が終わったら、Demoクラスのmainメソッドに動作確認用のコードを書いて実行してみます。

まず、お店が扱うおかしのオブジェクトを準備します。

// サラミ味のおかしを生成
Snack umaibouSalami = new Snack("うまいぼう サラミ味", 10,
        "コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");
// チーズ味のおかしを生成
Snack umaibouCheese = new Snack("うまいぼう チーズ味", 10,
        "コーン,植物油脂,チーズパウダー,乳糖,クリーミングパウダー,乳製品,パン粉,砂糖,食塩,香辛料,調味料,香料,パプリカ色素,甘味料,ph調整剤,乳化剤,ターメリック色素");
// めんたい味のおかしを生成
Snack umaibouMentai = new Snack("うまいぼう めんたい味", 10,
        "コーン,植物油脂,糖類,パプリカ,香辛料,パン粉,たん白加水分解物,たら調味パウダー,食塩,調味料,香料,パプリカ色素,甘味料");

次にお店のオブジェクトを生成し、扱うおかしを追加します。

// お店の扱うおかしの準備
Shop dagashiya = new Shop();
dagashiya.addSnackVariety(umaibouSalami);
dagashiya.addSnackVariety(umaibouCheese);
dagashiya.addSnackVariety(umaibouMentai);
// 取り扱い中のおかしを出力
dagashiya.prettyPrintSnackVariety();

3つのおかしの登録はaddSnackVarietiesメソッドで1行で書くこともできます。

dagashiya.addSnackVarieties(umaibouSalami, umaibouCheese, umaibouMentai);

おかしを買う人のオブジェクトを生成します。おかしを1つも買っていない場合は以下のようなメッセージを出力します。

Person taro = new Person("たろう", 100);
// 所持金と購入したおかしを出力
taro.prettyPrintHasSnack();
たろうは 所持金:100円 を持っている

dealメソッドでお店からおかしを5つ買い、もう一度所持金と購入したおかしの情報を出力してみます。

taro.deal(dagashiya, "うまいぼう サラミ味");
taro.deal(dagashiya, "うまいぼう サラミ味");
taro.deal(dagashiya, "うまいぼう チーズ味");
taro.deal(dagashiya, "うまいぼう チーズ味");
taro.deal(dagashiya, "うまいぼう めんたい味");
// 所持金と購入したおかしを出力
taro.prettyPrintHasSnack();
たろうは 所持金:50円とおかし (name:うまいぼう サラミ味, price=10), (name:うまいぼう サラミ味, price=10), (name:うまいぼう チーズ味, price=10), (name:うまいぼう チーズ味, price=10), (name:うまいぼう めんたい味, price=10) を持っている

もう1人、おかしを買う人のオブジェクトを生成し、所持金より多いおかしを購入してみます。

Person jiro = new Person("じろう", 30);
// 所持金と購入したおかしを出力
jiro.prettyPrintHasSnack();
jiro.deal(dagashiya, "うまいぼう サラミ味");
jiro.deal(dagashiya, "うまいぼう チーズ味");
jiro.deal(dagashiya, "うまいぼう めんたい味");
jiro.deal(dagashiya, "うまいぼう めんたい味");
// 所持金と購入したおかしを出力
jiro.prettyPrintHasSnack();
じろうは 所持金:30円 を持っている
うまいぼう めんたい味の購入に失敗しました
所持金が足りません
じろうは 所持金:0円とおかし (name:うまいぼう サラミ味, price=10), (name:うまいぼう チーズ味, price=10), (name:うまいぼう めんたい味, price=10) を持っている

レッスン2のさいごに

これでレッスン2は終了です。これまでに書いたJavaソースコードは以下のようになります。

Snack.java

package com.example.lesson01.model;

public class Snack {

    private static final String UNDEFINED_INGREDIENTS = "未定";

    public Snack(String name, int price) {
        this(name, price, UNDEFINED_INGREDIENTS); // 3つの引数を取るコンストラクターを呼ぶ
    }

    public Snack(String name, int price, String ingredients) {
        assert name != null;
        assert price > 0;
        this.name = name;
        this.price = price;
        this.ingredients = ingredients == null ? UNDEFINED_INGREDIENTS : ingredients;
    }

    /**
    * 名前 (必須)
    */
    private String name;

    /**
    * 値段 (必須)
    */
    private int price;

    /**
    * 原材料 (任意)
    */
    private String ingredients;

    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getIngredients() {
        return ingredients;
    }

    public void setIngredients(String ingredients) {
        this.ingredients = ingredients;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Snack other = (Snack) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Snack [name=" + name + ", price=" + price + ", ingredients=" + ingredients + "]";
    }

}

Shop.java

package com.example.lesson02.model;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;

import com.example.lesson01.model.Snack;
import com.example.lesson02.DealException;

public class Shop {

    // デフォルトコンストラクタ―
    public Shop() {
        this(new HashSet<>(), 0);
    }

    public Shop(Set<Snack> snackVarieties, int sales) {
        this.snackVarieties = snackVarieties;
        this.sales = sales;
    }

    /**
    * お店が扱うおかしの種類
    */
    private Set<Snack> snackVarieties;

    /**
    * 売上金
    */
    private int sales;

    /**
    * お店が扱うおかしの種類を追加する
    * @param snack
    */
    public void addSnackVariety(Snack snack) {
        this.snackVarieties.add(snack);
    }

    /**
    * お店が扱うおかしの種類を複数まとめて追加する
    * @param snacks
    */
    public void addSnackVarieties(Snack... snacks) {
        this.snackVarieties.addAll(Set.of(snacks));
    }

    /**
    * 指定した名前のおかしを扱っているか確認する
    * @param snackName おかしの名前
    * @return true:扱っている
    */
    public boolean isAvailableSnack(String snackName) {
        return getSnack(snackName).isPresent();
    }

    /**
    * 指定した名前のおかしのオブジェクトを返す
    * @param snackName おかしの名前
    * @return おかしのオブジェクト
    */
    private Optional<Snack> getSnack(String snackName) {
        Iterator<Snack> ite = snackVarieties.iterator();
        while (ite.hasNext()) {
            Snack snack = ite.next();
            if (snack.getName().equals(snackName)) {
                return Optional.of(snack);
            }
        }
        return Optional.empty();
    }

    /**
    * お店が扱っているおかしを表示する
    */
    public void prettyPrintSnackVariety() {
        if (snackVarieties.isEmpty()) {
            System.out.println("取り扱うおかしはありません");
            return;
        }
        snackVarieties.forEach(snack -> {
            System.out.println(snack.getName() + "は" + snack.getPrice() + "円");
        });
        System.out.println("計" + snackVarieties.size() + "種類のおかしを取り扱い中");
    }

    /**
    * おかしを売る
    * @param snackName 売りおかしの名前
    * @param customer おかしを買う人
    * @throws DealException おかしの売買が失敗した場合
    */
    public void sellSnack(String snackName, Person customer) {
        Optional<Snack> optionalSnack = getSnack(snackName);
        optionalSnack.ifPresentOrElse(snack -> {
            // おかしが見つかった場合
            // 購入
            int customerPay = customer.buySnack(snack);
            // 売り上げの加算
            sales += customerPay;
        }, () -> {
            // おかしが見つからなかった場合
            throw new DealException(snackName + "は取り扱っていません");
        });
    }

    @Override
    public String toString() {
        return "Shop [snackVarieties=" + snackVarieties + ", sales=" + sales + "]";
    }

}

Person.java

package com.example.lesson02.model;

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

import com.example.lesson01.model.Snack;
import com.example.lesson02.DealException;

public class Person {

    public Person(String name, int money) {
        assert name != null;
        this.name = name;
        this.money = money;
        this.snackBag = new ArrayList<>(MAX_SNACK_NUM);
    }

    /**
    * 名前
    */
    private String name;

    /**
    * 所持金
    */
    private int money;

    /**
    * おかし袋
    */
    private List<Snack> snackBag;

    /**
    * おかし袋に入れられるおかしの最大数
    */
    private static final int MAX_SNACK_NUM = 5;

    /**
    * 持っているおかしを表示する
    */
    public void prettyPrintHasSnack() {
        StringBuilder sb = new StringBuilder(name);
        sb.append("は 所持金:");
        sb.append(money);
        sb.append("円");
        if (!snackBag.isEmpty()) {
            sb.append("とおかし ");
            String comma = "";
            for (Snack snack : snackBag) {
                sb.append(String.format("%s(name:%s, price=%d)", comma, snack.getName(), snack.getPrice()));
                comma = ", ";
            }
        }
        sb.append(" を持っている");
        System.out.println(sb.toString());
    }

    /**
    * お店と取引する
    * @param shop 取引するお店
    * @param snackName 買うおかしの名前
    */
    public void deal(Shop shop, String snackName) {
        assert shop != null;
        assert snackName != null;
        try {
            shop.sellSnack(snackName, this);
        } catch (DealException e) {
            System.out.println(snackName + "の購入に失敗しました");
            System.err.println(e.getMessage());
        }
    }

    /**
    * おかしを買う
    * @param snack 買うおかしのオブジェクト
    * @return 支払った金額
    * @throws DealException おかしが買えなかった場合にスロー
    */
    int buySnack(Snack snack) throws DealException {
        if (money < snack.getPrice()) {
            throw new DealException("所持金が足りません");
        }
        if (MAX_SNACK_NUM <= snackBag.size()) {
            throw new DealException("おかし袋に空きがありません");
        }
        money -= snack.getPrice();
        snackBag.add(snack);
        return snack.getPrice();
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", money=" + money + ", snackBag=" + snackBag + "]";
    }

}

以上で、前編・中編・後編と続けたこの記事もひとまず終了です。

『入門向け』Java開発環境の構築とかんたんなコードの書き方と実行方法 (中編)

はじめに

この記事は、 rubytomato.hateblo.jp に続く記事の中編です。 前編ではJava開発環境の構築とEclipseの簡単な使い方を中心に説明しました。この記事ではJavaプログラムの書き方と実行方法を中心に説明します。

Javaプログラムの書き方

前編のおさらい

前編でどこまでおこなったか簡単におさらいをしておきます。

まず、Eclipse(Pleiades All in One Eclipse)のセットアップとFig1のようにstudy01projectプロジェクトとDemoクラスまで作成しました。

f:id:rubytomato:20200330101516p:plain
Fig1.

つぎにレッスン1のはじめで

まで説明しました。

レッスン1(つづき)

レッスン1のつづきでは、Eclipseを使ったJavaプログラムの書き方をもう少し踏み込んで説明したいと思います。 このレッスンでは、おかしの情報を扱うSnackというクラスを題材に、クラス、フィールド、コンストラクタ―の定義方法を説明します。

Snackクラスを作成する

まずcom.example.lesson01.modelパッケージを作成し、modelパッケージにSnackクラスを作成します。 パッケージおよびクラスの作成方法が分からない場合は前編の記事をご覧ください。 Fig2はmodelパッケージおよびSnackクラスを作成した直後のプロジェクトの状態です。

f:id:rubytomato:20200404105736p:plain
Fig2.

以下がSnackクラスを作成した直後のソースコードです。

package com.example.lesson01.model;

public class Snack {

}
フィールドを定義する

Snackクラスに、おかしの名前を持つname、値段を持つprice、原材料を持つingredientsの3つのフィールドを定義します。 namepriceは必須のフィールドで、ingredientsは情報があればセットするというオプショナルなフィールドというルールにします。

package com.example.lesson01.model;

public class Snack {

    /**
    * 名前 (必須)
    */
    private String name;

    /**
    * 値段 (必須)
    */
    private int price;

    /**
    * 原材料 (任意)
    */
    private String ingredients;

}
アクセサメソッドを実装する

定義したフィールドのアクセサメソッドを定義します。アクセサメソッドとはフィールドのゲッターや、セッターメソッドのことです。 たとえばnameフィールドであれば、nameフィールドの値を取得するgetNameメソッドがゲッターメソッド、値をセットするsetNameメソッドがセッターメソッドです。 メソッドの先頭にgetsetが付くことからゲッター(getter)・セッター(setter)メソッドと呼ばれています。 以下のコードはnameフィールドのアクセサメソッドの例です。

// 名前フィールドのゲッター
public String getName() {
    return name;
}
// 名前フィールドのセッター
public void setName(String name) {
    this.name = name;
}

アクセサメソッドは、手動でコーディングするのは非効率なのでEclipseの機能を使って自動的に生成するようにします。生成は下記のメニューバーかキーボードのショートカットから行えます。

  • メニューバーのソース(S)getter および setter の生成(R)...を選択
  • キーボードのShift + Alt + Sを押しメニューのgetter および setter の生成(R)...を選択

f:id:rubytomato:20200404105816p:plain
Fig3.

"getter および setter の生成"画面(Fig4)でフィールドをすべて選択し、"生成"ボタンをクリックします。

f:id:rubytomato:20200330101611p:plain
Fig4.

以下がゲッター・セッターが生成された状態のソースコードです。

package com.example.lesson01.model;

public class Snack {

    /**
    * 名前 (必須)
    */
    private String name;

    /**
    * 値段 (必須)
    */
    private int price;

    /**
    * 原材料 (任意)
    */
    private String ingredients;

    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getIngredients() {
        return ingredients;
    }

    public void setIngredients(String ingredients) {
        this.ingredients = ingredients;
    }

}
Snackクラスのオブジェクトを生成する

Demoクラスを開き、mainメソッドにSnack umaibou = new Snack();というコードを追加します。

public static void main(String[] args) {
    System.out.println("Lession01 Demo start");

    // サラミ味のおかしを生成
    Snack umaibou = new Snack();

    System.out.println("Lession01 Demo end");
}

するとFig5のようにSnackの部分に赤い下線が表示されると思います。これはSnackクラスのインポート文がコーディングされていないために起きているエラーです。

f:id:rubytomato:20200404105859p:plain
Fig5.

インポート文を追加するために、赤い下線の部分にカーソルを置きCtrl + 1を押し、メニューからSnack をインポートします (com.example.lesson01.model)というメニューを選択します。これでインポート文が追加されエラーは解消します。

f:id:rubytomato:20200404105920p:plain
Fig6.

Snack umaibou = new Snack();というコードでは、変数の宣言とコンストラクターの呼び出しを行っています。 このコードによりumaibouというSnack型の変数に、Snackクラスのオブジェクトが代入されます。

Snack umaibou = new Snack();
^^^^^ ^^^^^^^   ^^^ ^^^^^^^
 |     |         |   |
 |     |         |   +--- コンストラクター名
 |     |         |
 |     |         +------- newキーワードでコンストラクタ―を呼び出す
 |     |
 |     +----------------- 変数名
 |
 +----------------------- 変数の型
オブジェクトのフィールドに値をセットする

オブジェクトを生成したらセッターメソッドを使ってオブジェクトのフィールドに値をセットします。 このときにEclipseのコード補完を使うとタイピング量が減り且つ正確にコードがかけます。 umaibou.とまでタイプするとFig7のようにコード補完の候補が表示されます。候補が表示されない場合はCtrl + Spaceを押してください。

f:id:rubytomato:20200404110041p:plain
Fig7.
セッターメソッドの候補だけを表示するには、さらにumaibou.setとまでタイプします。そうするとsetから始まるメソッドやフィールドだけが候補になるので選択が楽になります。この中から選択するとそのコードで補完されます。
f:id:rubytomato:20200404110059p:plain
Fig8.

以下が3つのフィールド(name, price, ingredients)をセットするソースコードです。

// サラミ味のおかしを生成
Snack umaibou = new Snack();
umaibou.setName("うまいぼう サラミ味");
umaibou.setPrice(10);
umaibou.setIngredients("コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");
オブジェクトの情報を出力する

ここまででオブジェクトを生成しフィールドの値をセットするコードがかけましたので、そのオブジェクトの情報をコンソールへ出力してみたいと思います。 コンソールへ出力するにはSystem.out.printlnを使います。以下のコードを追加しファイルを保存します。

System.out.println("name=" + umaibou.getName() + " price=" + umaibou.getPrice() + " ingredients=" + umaibou.getIngredients());

ここまでコーディングしたmainメソッドの全体を示します。

public static void main(String[] args) {
    System.out.println("Lession01 Demo start");

    // サラミ味のおかしを生成
    Snack umaibou = new Snack();
    umaibou.setName("うまいぼう サラミ味");
    umaibou.setPrice(10);
    umaibou.setIngredients("コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");

    System.out.println("name=" + umaibou.getName() + " price=" + umaibou.getPrice() + " ingredients=" + umaibou.getIngredients());

    System.out.println("Lession01 Demo end");
}

Demoクラスのmainメソッドを実行し、コンソールに以下の内容が出力されると成功です。これでオブジェクトの情報を表す文字列をコンソールに出力することができました。

Lession01 Demo start
name=うまいぼう サラミ味 price=10 ingredients=コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤
Lession01 Demo end

次にもう1つ新しいおかしを追加します。これまでと同じようにオブジェクトを生成し、セッターメソッドでフィールドに値をセットします。

// チーズ味のおかしを生成
Snack umaibouCheese = new Snack();
umaibouCheese.setName("うまいぼう チーズ味");
umaibouCheese.setPrice(10);
umaibouCheese.setIngredients("コーン,植物油脂,チーズパウダー,乳糖,クリーミングパウダー,乳製品,パン粉,砂糖,食塩,香辛料,調味料,香料,パプリカ色素,甘味料,ph調整剤,乳化剤,ターメリック色素");

おなじように、このオブジェクトの情報を出力するコードも加えます。

System.out.println("name=" + umaibouCheese.getName() + " price=" + umaibouCheese.getPrice() + " ingredients=" + umaibouCheese.getIngredients());

ここまでコーディングしたmainメソッドの全体を示します。

public static void main(String[] args) {
    System.out.println("Lession01 Demo start");

    // サラミ味のおかしを生成
    Snack umaibou = new Snack();
    umaibou.setName("うまいぼう サラミ味");
    umaibou.setPrice(10);
    umaibou.setIngredients("コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");

    // チーズ味のおかしを生成
    Snack umaibouCheese = new Snack();
    umaibouCheese.setName("うまいぼう チーズ味");
    umaibouCheese.setPrice(10);
    umaibouCheese.setIngredients("コーン,植物油脂,チーズパウダー,乳糖,クリーミングパウダー,乳製品,パン粉,砂糖,食塩,香辛料,調味料,香料,パプリカ色素,甘味料,ph調整剤,乳化剤,ターメリック色素");

    System.out.println("name=" + umaibou.getName() + " price=" + umaibou.getPrice() + " ingredients=" + umaibou.getIngredients());
    System.out.println("name=" + umaibouCheese.getName() + " price=" + umaibouCheese.getPrice() + " ingredients=" + umaibouCheese.getIngredients());

    System.out.println("Lession01 Demo end");
}

Demoクラスのmainメソッドを実行して、以下のようにコンソールに2つのおかしの情報が出力されれば成功です。

Lession01 Demo start
name=うまいぼう サラミ味 price=10 ingredients=コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤
name=うまいぼう チーズ味 price=10 ingredients=コーン,植物油脂,チーズパウダー,乳糖,クリーミングパウダー,乳製品,パン粉,砂糖,食塩,香辛料,調味料,香料,パプリカ色素,甘味料,ph調整剤,乳化剤,ターメリック色素
Lession01 Demo end
オブジェクトの情報を出力するのにtoStringメソッドを利用する

toStringメソッドはオブジェクトの情報を文字列にして返すメソッドです。このメソッドはjava.lang.Objectクラスというすべてのクラスのスーパークラスで定義されているので、どのクラスでも使用できます。 このメソッドを使ってオブジェクトの情報を出力するように修正します。以下の2行を

System.out.println("name=" + umaibou.getName() + " price=" + umaibou.getPrice() + " ingredients=" + umaibou.getIngredients());
System.out.println("name=" + umaibouCheese.getName() + " price=" + umaibouCheese.getPrice() + " ingredients=" + umaibouCheese.getIngredients());

以下のように書き換えます。

System.out.println(umaibou.toString());
System.out.println(umaibouCheese.toString());

修正してファイルを保存したらDemoクラスのmainメソッドを実行してみます。真ん中の2行がumaibouとumaibouCheeseをtoStringメソッドで出力した結果です。修正前に比べると人間にはよくわからない文字列が出力されるようになってしまったと思います。

Lession01 Demo start
com.example.lesson01.model.Snack@1d251891
com.example.lesson01.model.Snack@48140564
Lession01 Demo end
toStringメソッドをオーバーライドする

実はjava.lang.ObjectクラスのtoStringメソッドのデフォルトの実装では、人間にとって意味のある(人間が読める)情報を出力しないため、Snackクラスで改めてtoStringメソッドをオーバーライドする必要があります。 オーバーライドとは、すでに実装されているメソッドより、新しいメソッドを優先させることです。 SnackクラスのtoStringメソッドをオーバーライドするためにSnack.javaをエディタで開きます。

ちなみに、Demoクラスのソースコード上のSnackにカーソルを置いた状態でF3を押すか、Ctrlを押しながらマウスカーソルをSnackにあわせて、メニューの宣言を開くを選択すると、Snack.javaソースコードが開きます。

f:id:rubytomato:20200404110403g:plain
Fig9.

toStringメソッドをオーバーライドするコードは、これまでと同様にEclipseの機能を使って自動的に生成する方が効率がよく正確です。 生成は下記のメニューバーかキーボードのショートカットの操作から行えます。カーソルは生成するコードを出力したい場所へおいてください。

  • メニューバーのソース(S)toString() 生成(R)...を選択
  • キーボードのShift + Alt + Sを押しメニューのtoString() 生成(R)...を選択

"toString() 生成"画面(Fig10)が表示されるので出力するフィールドを選択します。ここでは3つすべて選択し"生成"ボタンをクリックします。

f:id:rubytomato:20200330102110p:plain
Fig10.

すると、以下のコードが追加されたと思います。@Overrideという記述はアノテーションといって、このtoStringメソッドがSnackクラスでオーバーライドされていることを表しています。

@Override
public String toString() {
    return "Snack [name=" + name + ", price=" + price + ", ingredients=" + ingredients + "]";
}

ソースコードを保存したら、もう一度Demoクラスのmainメソッドを実行してみます。コンソールに以下のように出力されれば成功です。

Lession01 Demo start
Snack [name=うまいぼう サラミ味, price=10, ingredients=コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤]
Snack [name=うまいぼう チーズ味, price=10, ingredients=コーン,植物油脂,チーズパウダー,乳糖,クリーミングパウダー,乳製品,パン粉,砂糖,食塩,香辛料,調味料,香料,パプリカ色素,甘味料,ph調整剤,乳化剤,ターメリック色素]
Lession01 Demo end
変数名を変える

これまでのコードを見てみると、サラミ味のおかしの変数の名前はumaibou、チーズ味のおかしの変数の名前はumaibouCheeseとなっています。 サラミ味の変数名が少し抽象的なので、umaibouSalamiに変えたいと思います。 変数名を変えるのも手動で1行ずつ変更していくのではなく、Eclipseの機能を使った方が簡単で正確です。変更する変数名にカーソルをあわせておいた状態で、下記のメニューバーかキーボードのショートカットの操作から行えます。

  • メニューバーのリファクタリング(T)名前の変更(N)... を選択
  • キーボードのShift + Alt + Rを押す

するとエディタ上でカーソルがリファクタリングモードになりますので、変数名を変えてEnterを押して確定させます。

f:id:rubytomato:20200404110453g:plain
Fig11.

コンストラクタを実装する

さらにめんたい味のおかしを追加してみます。

// めんたい味のおかしを生成
Snack umaibouMentai = new Snack();
umaibouMentai.setName("うまいぼう めんたい味");
umaibouMentai.setIngredients("コーン,植物油脂,糖類,パプリカ,香辛料,パン粉,たん白加水分解物,たら調味パウダー,食塩,調味料,香料,パプリカ色素,甘味料");

めんたい味のおかしのオブジェクト情報を出力するコードも書きます。

System.out.println(umaibouMentai.toString());

Demoクラスのmainメソッドを実行するとコンソールにめんたい味のおかしの情報が出力されたと思いますが、priceフィールドは0となっています。これはオブジェクト生成後にセッターでpriceフィールドを初期化しなかったためです。フィールドの値は明示的に初期化しないとデフォルト値で初期化されます。priceフィールドの値が0で出力されたのはint型の初期値が0であるためです。

Snack [name=うまいぼう めんたい味, price=0, ingredients=コーン,植物油脂,糖類,パプリカ,香辛料,パン粉,たん白加水分解物,たら調味パウダー,食塩,調味料,香料,パプリカ色素,甘味料]

例えば、プログラムの仕様(ルール)で、namepriceフィールドは必ずセットしなければならないと決められていた場合、このコードはバグにあたります。このバグは以下のようにpriceフィールドに値をセットするコードを追加すること対応できますが、これよりもっと適切な対応方法があります。

// めんたい味のおかしを生成
Snack umaibouMentai = new Snack();
umaibouMentai.setName("うまいぼう めんたい味");
umaibouMentai.setPrice(10);    // この行を追加してバグ修正する
umaibouMentai.setIngredients("コーン,植物油脂,糖類,パプリカ,香辛料,パン粉,たん白加水分解物,たら調味パウダー,食塩,調味料,香料,パプリカ色素,甘味料");
引数と取るコンストラクターを実装する

オブジェクト生成後に個別にセッターで値をセットするのではなく引数を取るコンストラクターを実装して初期化するようにします。

エディタでSnack.javaを開きコンストラクターを実装しますが、これもEclipseの機能を使って自動的に生成します。生成は下記のメニューバーかキーボードのショートカットの操作から行えます。カーソルは生成するコードを出力したい場所へ置いてください。

  • メニューバーのソース(S)フィールドを使用してコンストラクタ―を生成(A)...を選択
  • キーボードのShift + Alt + Sを押しメニューのフィールドを使用してコンストラクタ―を生成(A)...を選

"フィールドを使用してコンストラクタ―を生成"画面(Fig12)で、初期化するフィールドにnamepriceを選択し"生成"ボタンをクリックします。

f:id:rubytomato:20200330102159p:plain
Fig12.

下記が生成されたコンストラクタ―のコードです。コンストラクターはメソッドのように見えますが戻り値が無いことと、コンストラクタ―名がクラス名と同じでなければならないという特徴があります。

public Snack(String name, int price) {
    super();
    this.name = name;
    this.price = price;
}

このうちのsuper();というコードは、親クラス(Super class)のコンストラクターを呼び出していますが、このコードは無くても良いので削除します。 また、引数のnameがnullだったりpriceが0以下だったり不正な値の場合はエラーになるようにassert文を追加します。

public Snack(String name, int price) {
    assert name != null;
    assert price > 0;
    this.name = name;
    this.price = price;
}

この修正を保存するとDemoクラスに赤い×アイコンが付いたと思います。

f:id:rubytomato:20200404110600p:plain
Fig13.

デフォルトコンストラクター

3カ所に赤い×アイコンが付いていると思いますが、これはデフォルトコンストラクターが見つからないためのエラーです。 画面下の"問題"ビューを見ると(Fig14)、"コンストラクタ― Snack() は未定義です" というエラーが3つ表示されていると思います。

f:id:rubytomato:20200404110617p:plain
Fig14.

クラスに明示的にコンストラクタ―を1つも実装しない場合、デフォルトコンストラクタ―という暗黙のコンストラクターが追加されます。(ソースコード上はみえません) デフォルトコンストラクタ―は引数を取らず、また何の処理もしないコンストラクターです。

public Snack() {
    super();
}

これまでオブジェクトの生成は以下のようにコーディングしていましたが、このときに使用されるコンストラクターはデフォルトコンストラクターです。

Snack umaibouSalami = new Snack();

上記で2つの引数を取るコンストラクタ―を明示的に実装したことによりデフォルトコンストラクタ―が追加されなくなったため、デフォルトコンストラクターが見つからず(未定義)エラーになった、ということです。

Snack umaibouSalami = new Snack();  // この行がエラー
umaibouSalami.setName("うまいぼう サラミ味");
umaibouSalami.setPrice(10);
umaibouSalami.setIngredients("コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");

このエラーを解消するために実装したコンストラクターを使うように修正します。同時にセッターメソッドで値をセットしているコードも不要になるので削除します。

Snack umaibouSalami = new Snack("うまいぼう サラミ味", 10);
//umaibouSalami.setName("うまいぼう サラミ味");     // 不要になった行なので削除
//umaibouSalami.setPrice(10);     // 不要になった行なので削除
umaibouSalami.setIngredients("コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");

原材料のingredientsフィールドはオプション的な扱いなので、セットすべき値がある場合は明示的にセッターで値をセットします。 もし、原材料が不明な場合はingredientsフィールドに"未定”とセットしたい、という仕様があった場合はセッターで以下のようにコーディングするのではなく

umaibouSalami.setIngredients("未定");

以下のようにコンストラクターでセットする方が適切です。

public Snack(String name, int price) {
    assert name != null;
    assert price > 0;
    this.name = name;
    this.price = price;
    this.ingredients = "未定";    // オブジェクト生成直後は未定という値を持つ
}
複数のコンストラクターを実装する

1つのクラスにコンストラクターを複数実装することができるので、Snackクラスに3つの引数を取るコンストラクタ―を追加してみます。これをコンストラクタ―のオーバーロードといいます。 以下がこれまで実装したコンストラクタ―のコードです。

public Snack(String name, int price) {
    assert name != null;
    assert price > 0;
    this.name = name;
    this.price = price;
    this.ingredients = "未定";
}

public Snack(String name, int price, String ingredients) {
    assert name != null;
    assert price > 0;
    this.name = name;
    this.price = price;
    this.ingredients = ingredients;
}

これで、原材料にセットする値があるおかしの場合もコンストラクタ―でオブジェクトを初期化できます。

Snack umaibouSalami = new Snack("うまいぼう サラミ味", 10, "コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");
コンストラクタ―から他のコンストラクタ―を呼ぶ

このコンストラクタ―の実装では、this.namethis.priceをセットするコードがまったく同じことを行っています。なのでこの重複するコードを以下のように最適化します。 まず、2つの引数を取るコンストラクタ―をthis(name, price, "未定");というように修正しました。このコードのthis( ... )という部分が3つの引数を取るコンストラクタ―を呼び出しています。これで重複するコードが取り除けました。

public Snack(String name, int price) {
    this(name, price, "未定");    // 3つの引数を取るコンストラクターを呼ぶ
}

public Snack(String name, int price, String ingredients) {
    assert name != null;
    assert price > 0;
    this.name = name;
    this.price = price;
    this.ingredients = ingredients;
}

このコンストラクターでは"未定"という文字列が2カ所に表れているので、これを定数にします。 また、this.ingredientsは引数でnullが渡された場合は"未定"という文字列をセットするように3項演算子を使うように修正します。

private static final String UNDEFINED_INGREDIENTS = "未定";

public Snack(String name, int price) {
    this(name, price, UNDEFINED_INGREDIENTS);
}

public Snack(String name, int price, String ingredients) {
    assert name != null;
    assert price > 0;
    this.name = name;
    this.price = price;
    this.ingredients = ingredients == null ? UNDEFINED_INGREDIENTS : ingredients;
}

レッスン1のさいごに

レッスン1は以上です。レッスン1ではクラス、フィールド、コンストラクタ―をEclipseの機能を使って書く方法を説明してきました。 Eclipseの機能を使うことでコーディングがより楽になります。

これまでに書いたJavaソースコードは以下のようになります。

Snack.java

package com.example.lesson01.model;

public class Snack {

    private static final String UNDEFINED_INGREDIENTS = "未定";

    public Snack(String name, int price) {
        this(name, price, UNDEFINED_INGREDIENTS);
    }

    public Snack(String name, int price, String ingredients) {
        assert name != null;
        assert price > 0;
        this.name = name;
        this.price = price;
        this.ingredients = ingredients == null ? UNDEFINED_INGREDIENTS : ingredients;
    }

    /**
    * 名前 (必須)
    */
    private String name;

    /**
    * 値段 (必須)
    */
    private int price;

    /**
    * 原材料 (任意)
    */
    private String ingredients;

    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getIngredients() {
        return ingredients;
    }

    public void setIngredients(String ingredients) {
        this.ingredients = ingredients;
    }

    @Override
    public String toString() {
        return "Snack [name=" + name + ", price=" + price + ", ingredients=" + ingredients + "]";
    }

}

Demo.java

package com.example.lesson01;

import com.example.lesson01.model.Snack;

public class Demo {

    public static void main(String[] args) {
        System.out.println("Lession01 Demo start");

        // サラミ味のおかしを生成
        Snack umaibouSalami = new Snack("うまいぼう サラミ味", 10, "コーン,植物油脂,糖類,ポークパウダー,香辛料,パン粉,ビーフパウダー,酵母エキスパウダー,パセリ,調味料,香料,甘味料,カラメル色素,酸化防止剤");

        // チーズ味のおかしを生成
        Snack umaibouCheese = new Snack("うまいぼう チーズ味", 10, "コーン,植物油脂,チーズパウダー,乳糖,クリーミングパウダー,乳製品,パン粉,砂糖,食塩,香辛料,調味料,香料,パプリカ色素,甘味料,ph調整剤,乳化剤,ターメリック色素");

        // めんたい味のおかしを生成
        Snack umaibouMentai = new Snack("うまいぼう めんたい味", 10, "コーン,植物油脂,糖類,パプリカ,香辛料,パン粉,たん白加水分解物,たら調味パウダー,食塩,調味料,香料,パプリカ色素,甘味料");

        System.out.println(umaibouSalami.toString());
        System.out.println(umaibouCheese.toString());
        System.out.println(umaibouMentai.toString());

        System.out.println("Lession01 Demo end");
    }

}

続く後編ではJavaコードの書き方をもう少し踏み込んで説明します。(現在記事作成中です) rubytomato.hateblo.jp

『入門向け』Java開発環境の構築とかんたんなコードの書き方と実行方法 (前編)

はじめに

この記事は、Javaを勉強するための開発環境を構築したい、その開発環境を使ってソースコードを書いて実行する方法を知りたい、といったひと向けの入門記事になります。 前編のこの記事では、Windows 10とJava 11を使った開発環境の構築の手順、Javaプログラムの基本的な書き方と実行方法を説明します。 中編と後編は、Javaプログラムのもう少し踏み込んだコードの書き方について説明します。

開発環境の構築

Javaの開発ツールにはPleiades All in One Eclipseというパッケージを利用します。このパッケージはEclipseをベースにしていてメニューやメッセージが日本語化されている他に、JavaTomcatなどが同梱されているので、このパッケージだけで開発環境を構築できます。

mergedoc.osdn.jp

Pleiades All in One Eclipseのダウンロード

Eclipse 日本語化 | MergeDoc Project にアクセスし、以下の手順でダウンロードします。

最新バージョンを利用したいので、Fig1の赤い枠線で囲った"Eclipse 2020"(2020年3月現在の最新)というボタンをクリックします。

f:id:rubytomato:20200328233051p:plain
Fig1.

Windowsであれば、Fig2の赤い枠線で囲ったWindows 64bit版のFull Editionをダウンロードするボタンをクリックします。

f:id:rubytomato:20200328233110p:plain
Fig2.

pleiades-2020-03-java-win-64bit-jre_20200322.zipという名前(2020年3月現在)のzipファイルのダウンロードが始まります。

Pleiades All in One Eclipseのインストール

ダウンロードしたzipファイルを7-Zipなどのツールで展開します。まだこのツールをインストールしていなければ先にインストールしてください。 zipファイルを展開するとpleiadesというフォルダができるので、これを適当な場所へコピーします。 この記事では以下の場所へコピーしました。インストールはこれで完了です。

C:\dev\pleiades

このフォルダをWindowsエクスプローラで開くと、Fig3のようになっていると思います。

f:id:rubytomato:20200325221812p:plain
Fig3.

この中からさらにeclipseというフォルダを開きます。Fig4のようなフォルダやファイルがあると思いますが、この中のeclipse.exeWindowsの設定によってはeclipseとだけ表示されているかもしれません)を右クリックし、メニューから送るデスクトップ(ショートカットを作成) を選択します。

f:id:rubytomato:20200328233151p:plain
Fig4.

すると、デスクトップにeclipseのフォートカットが出来ていると思います。ショートカットファイルの名前に"ショートカット"と付いていると思いますが、名前は変更できるので任意の名前に変更してください。 このショートカットをダブルクリックするとEclipseが起動します。

ワークスペースの選択

Eclipseを起動するとワークスペースの場所を指定するEclipse IDEランチャー(Fig5)が表示されます。ワークスペースソースコードを管理するフォルダで、基本的には画面の通りの場所で構いませんが、任意のフォルダを指定することもできます。任意のフォルダを指定したい場合は"参照(B)..."ボタンをクリックしてフォルダを選択します。

f:id:rubytomato:20200325221954p:plain
Fig5.
Fig5の通り../workspaceで起動ボタンを押すと、このワークスペースは以下の場所に作成されます。

C:\dev\pleiades\workspace

起動直後の画面

Fig6が起動直後の画面です。この画面の見方を簡単に説明します。 一番上がメニューバーで、その下のアイコンが並ぶバーをツールバーと呼びます。ツールバーによく使われる操作がアイコンで表示されています。 一番下がステータスバーです。ここにはEclipseが使用しているメモリ量やファイルの文字コードなどが表示されています。 一番面積の大きい中央右がエディター・ビューです。ここでファイル(ソースファイルやプロパティファイルなど)を編集します。 その他のビューは役割毎の内容が表示されます。例えば左端の"パッケージ・エクスプローラー"ビューはプロジェクトの構造を表示し、下の"問題"ビューはコンパイルエラーなどの情報を表示します。

f:id:rubytomato:20200329023314p:plain
Fig6.

レイアウトのカスタマイズ

ビューの位置を変更してレイアウトをカスタマイズすることができるので、その方法を簡単に説明します。(※レイアウトはこのままがいいという方は読み飛ばしてください。) ビューの移動は、移動させたいビューのタイトルバーをドラッグ(マウスの左ボタンを押しながら)して移動させたい場所でドロップ(マウスの左ボタンを離す)します。

まず、画面左下の”問題”というビューを画面右下へ移動させてみます。タイトルバーをドラッグしてFig7の黄色い枠線で囲ったあたりへドロップします。

f:id:rubytomato:20200328002838p:plain
Fig7.

Fig8がドロップ後の画面です。

f:id:rubytomato:20200328002435p:plain
Fig8.

次に"ランナー"や"アウトライン"があるビューを画面右端へ移動させてみます。タイトルバーをドラッグしてFig9の黄色い枠線で囲ったあたりへドロップします。

f:id:rubytomato:20200328002821p:plain
Fig9.

Fig10がドロップ後の画面です。

f:id:rubytomato:20200328002155p:plain
Fig10.

あとは各ビューの表示幅を調整します。ビューとビューの間の境界にマウスカーソルをあわせるとポインターが矢印マークになるので、その状態でマウスの左ボタンを押しながら左右、または上下に移動させるとビューの幅が変わります。

最終的にFig11のようなレイアウトに変えました。エディター・ビューは一番見ている時間が長いので中央に大きく表示するようにしました。

f:id:rubytomato:20200328002141p:plain
Fig11.

次によく使うビューをレイアウトに追加します。メニューバーのウィンドウ(W)ビューの表示(V)コンソールを選択します。すると画面下のビューにコンソールが追加されます。

f:id:rubytomato:20200328002124p:plain
Fig12.

以上がレイアウトのカスタマイズ方法です。どのようなレイアウトにするかは個人の好みなので自由に変えても問題ありませんが、パッケージ・エクスプローラーエディタービューは最低限残しておいてください。

使えるJavaのバージョンを確認

何種類かのJavaが利用できるようになっています。どのバージョンのJavaが用意されているかを確認するには、Eclipseのメニューバーのウィンドウ(W)設定(P) をクリックします。

設定画面が表示されるので、左側のメニューのJavaインストール済みのJREをクリックします。Fig13の通りJava6、Java7、Java8、Java11、Java14が利用でき、デフォルトはJava11になっています。この設定はとくに変える必要はないのでこのままにしておきます。

f:id:rubytomato:20200325222048p:plain
Fig13.

使えるTomcatのバージョンを確認

この記事ではTomcatは利用しませんが、Javaで開発したWebアプリケーションを実行するTomcatというアプリケーションサーバーも同梱されています。 確認するには、Eclipseのメニューバーのウィンドウ(W)設定(P)をクリックします。

設定画面が表示されるので、左側のメニューのサーバーランタイム環境をクリックします。Fig14の通りTomat 6、Tomcat 7、Tomcat 8、Tomcat 9が利用できます。

f:id:rubytomato:20200325222116p:plain
Fig14.

Javaプログラムの書き方

ここからはJavaプログラムの書き方の説明になります。まず最初に行うのはJavaプロジェクトの作成です。

Java プロジェクトの作成

プロジェクトが1つも作成されていない状態ではパッケージ・エクスプローラーのメニューからJava プロジェクトの作成を選択するか、またはメニューバーのファイル(F)新規(N)Java プロジェクト を選択します。 Fig15の新規 Java プロジェクトが開きますので、"プロジェクト名(P):"にstudy01projectと入力し、"完了"ボタンをクリックします。

f:id:rubytomato:20200328220056p:plain
Fig15.

Fig16の新規 module-info.javaが開くので、"作成"ボタンをクリックします。

f:id:rubytomato:20200328220112p:plain
Fig16.

Fig17がプロジェクトが作成された状態です。中央のエディタにmodule-info.javaが表示されていますが、いまは編集しないのでタブの×アイコンをクリックして閉じます。

f:id:rubytomato:20200328220155p:plain
Fig17.

パッケージの作成

Javaプロジェクトを作成したら、次はパッケージを作成します。 Fig18のsrcを右クリックしメニューの新規(W)パッケージを選択します。

f:id:rubytomato:20200328220305p:plain
Fig18.

Fig19の"名前(M):"にcom.exampleと入力し、"完了"ボタンをクリックします。

f:id:rubytomato:20200328220321p:plain
Fig19.

Fig20がパッケージが作成された状態です。srcフォルダの下に作成したパッケージが表示されていることがわかります。

f:id:rubytomato:20200328220333p:plain
Fig20.

レッスン1

レッスン1では次のことを行います。

※レッスン1は中編の記事へ続きます。

レッスン1用のパッケージを作る

レッスン1用のパッケージを作成します。Fig21のcom.exampleパッケージを右クリックし、新規(W)パッケージを選択します。

f:id:rubytomato:20200328220350p:plain
Fig21.

Fig22の"名前(M):"にcom.exampleと入力されているところに.lesson01を追加して、"完了"ボタンをクリックします。

f:id:rubytomato:20200328220409p:plain
Fig22.

Fig23がパッケージが作成された状態です。

f:id:rubytomato:20200328220434p:plain
Fig23.

Demoクラスを作る

レッスン1でこれから作るクラスを動かすための実行クラスを作成します。クラス名はDemo(Demonstration / デモンストレーション / 実演)という名前にします。(※クラス名は分かりやすければ何でもいいです。) com.example.lesson01パッケージを右クリックし、新規(W)クラスを選択します。

f:id:rubytomato:20200329021450p:plain
Fig24.

"名前(M):"にDemoと入力し、"どのメソッド・スタブを作成しますか?"には、public static void main(String[] args)(V)にチェックを入れ、"完了"ボタンをクリックします。

f:id:rubytomato:20200329021504p:plain
Fig25.

クラスが作成されると、中央のエディタビューにDemo.javaというソースファイルが表示されます。 ソースファイルにはmainという名前のメソッドがすでに作成されていますが、このmainメソッドはJavaプログラムを実行するために必要なメソッドです。

package com.example.lesson01;

public class Demo {

    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ

    }

}
ソースコードの修正と保存

mainメソッドを以下のように修正します。

public static void main(String[] args) {
    System.out.println("Lession01 Demo start");
    System.out.println("Lession01 Demo end");
}

するとタブのファイル名の先頭にアスタリスク(*)が付いたと思います。このアスタリスクは編集中のファイルであることを示しています。

f:id:rubytomato:20200329033116p:plain
Fig26.
この状態でCtrl + Sを押すとファイルが保存されアスタリスクが消えます。

Eclipseではファイルの保存と同時にソースコードコンパイルが自動的に行われます。 もし、ソースコードに書き間違い (Syntax Error / シンタックスエラー / 構文エラー) があるとファイル名の先頭に赤い×アイコンが付きます。これはコンパイルエラーがおきたことを示しています。 たとえば、以下のようにSystemsystemに書き直して保存すると

public static void main(String[] args) {
    system.out.println("Lession01 Demo start");
    System.out.println("Lession01 Demo end");
}

Fig27のようにエラーがあるクラス、および当該の行に赤い×アイコンが付きます。

f:id:rubytomato:20200329013049p:plain
Fig27.

また、問題ビューにも現時点で起きているコンパイルエラーの内容が表示されます。

f:id:rubytomato:20200329013103p:plain
Fig28.

コンパイルエラーの起きているsystemSystemになおしてファイルを保存すると赤い×アイコンが消えます。

Demoクラスを実行する

試しにDemoクラスを実行してみたいと思います。EclipseからJavaプログラムを実行する方法にはいくつかあります。

1). メニューバー → 実行(R)実行(S)Java アプリケーション

f:id:rubytomato:20200328231516p:plain
Fig29.

2). ツールバー実行アイコンの右の▼ → 実行(R)Java アプリケーション

f:id:rubytomato:20200328231500p:plain
Fig30.

3). エディタ上で右クリックしメニューから実行(R)Java アプリケーション

f:id:rubytomato:20200328231446p:plain
Fig31.

上記の方法のいずれかで実行すると画面下のコンソールビューに、mainメソッドに書いたコードの実行結果が出力されます。

f:id:rubytomato:20200328231205p:plain
Fig32.

起動ヒストリと実行構成の管理

Demoクラスを一度でも実行すると実行構成というプログラム実行の設定と起動ヒストリが自動的に作成されます。そうすると以降はツールバー実行アイコンをクリックするだけでDemoクラスを実行することができます。

f:id:rubytomato:20200329001515p:plain
Fig33.

起動ヒストリとは、今までに実行したことのあるプログラムの一覧です。起動ヒストリは実行アイコンの右の▼をクリックすると確認できます。 Fig33で”実行アイコンをクリックするだけで実行できます”と書きましたが、正確には起動ヒストリの一番上にリストされているプログラムが実行されます。 Fig34の左側がヒストリ無しで、Demoの実行後は右側のようにDemoの起動ヒストリが作成されています。

f:id:rubytomato:20200328233459p:plain
Fig34.

実行構成とは、プログラムをどのような条件で実行するかなどの設定です。Demoの実行構成を確認するには、このメニューの"実行の構成(N)..."をクリックします。 Fig35の左側のメニューのJava アプリケーションの下にDemoというメニューが表示されていると思いますが、これがDemoクラスの実行構成です。 右側にはその実行構成の設定内容が表示されています。"メイン"タブに表示されているプロジェクト名とメイン・クラスの内容の通り、この実行構成で実行するプログラムはstudy01projectプロジェクトのDemoクラスだということがわかります。

f:id:rubytomato:20200329003041p:plain
Fig35.

ソースコードのフォーマット

Demoクラスのソースコードを以下のように修正して実行してみます。コンソールビューに実行結果が出力され、問題なく実行できたと思います。 このようにソースコードはインデント(段落)を付けても、付けなくても実行に支障がない場合がありますが、ソースコードの読みやすさを考えると基本的にインデントは付けるようにします。

package com.example.lesson01;

public class Demo {

public static void main(String[] args) {
System.out.println("Lession01 Demo start");
System.out.println("Lession01 Demo end");
}

}

この例ではたった数行のソースコードなのでタブキーを押して一行一行インデントを付けていくことも大して手間はかかりませんが、ソースコードが数百行に及ぶこともめずらしくありません。そのような場合に手動でインデントを付けると時間がかかるのでEclipseのコードフォーマット機能を利用します。 Ctrl + Shift + Fを押すと、ソースコードが自動的にフォーマットされて以下のようになったと思います。

package com.example.lesson01;

public class Demo {

    public static void main(String[] args) {
        System.out.println("Lession01 Demo start");
        System.out.println("Lession01 Demo end");
    }

}

ソースコードを書いているうちにフォーマットが崩れてきたとおもったら、自動フォーマットを行ってソースコードのフォーマットを整えるようにします。

保存アクションの設定

Eclipseの保存アクションの設定で、ファイル保存時にソースコードの自動フォーマットを行うことができます。この設定を行うと毎回Ctrl + Shift + Fを押して自動フォーマットを行わなくても済むので便利です。 設定はメニューバーのウィンドウ(W)設定(P) を選択し、設定画面の左側メニューのJavaエディター保存アクション を選択します。 右側の設定で"ソース・コードのフォーマット(S)"にチェックを入れ、"適用して閉じる"ボタンをクリックします。

f:id:rubytomato:20200329020939p:plain
Fig36.

この設定を有効にすると、以降はファイル保存時(Ctrl + S)に自動的にソースコードのフォーマットが行われ、ソースコードのインデントが常に保たれます。

以上で、前編は終了です。続く中編ではJavaコードの書き方をもう少し踏み込んで説明します。(現在記事作成中です)

rubytomato.hateblo.jp

Windows 10にPostgreSQL 12をインストーラを使わずにZipファイルからインストールする

はじめに

ローカルPCに開発用途のPostgreSQL 12をインストールする方法を説明します。インストール方法には、インストーラ(ファイルの拡張子がexe)を使う方法と、zipファイルを使う方法があります。通常はインストーラでインストールする方が簡単ですが、この記事ではzipファイルを使って手動でインストールする方法を説明したいと思います。

zipファイルでインストールするメリット

  • レジストリを更新しないので、アンインストールはファイル削除だけで済む。
  • 複数の異なるバージョンのPostgreSQLをインストールすることが簡単。

この記事ではWindows 10 64bitに、2020年3月現在で最新バージョンのPostgreSQL 12.2をインストールします。

PostgreSQLのダウンロード

ダウンロードページ ( PostgreSQL: Windows installers ) からzipファイル (postgresql-12.2-1-windows-x64-binaries.zip) をダウンロードします。Fig1の赤い枠線で囲った"zip archive"というリンクテキストをクリックします。

f:id:rubytomato:20200325010637p:plain
Fig1.

Fig2の赤い枠線で囲った"Win x86-64"というボタンをクリックするとzipファイルのダウンロードが始まります。

f:id:rubytomato:20200325010703p:plain
Fig2.

以上でPostgreSQLのダウンロードは完了です。

PostgreSQLのインストール

インストールと言ってもダウンロードしたzipファイルを7-Zipなどのツールで展開するだけです。 この記事では展開して出来たフォルダを以下のフォルダへコピーしました。以降はこのフォルダをインストールディレクトリと呼びます。

C:\dev\pgsql-12.2

このフォルダをWindowsエクスプローラで表示してFig3のような状態であることを確認します。

f:id:rubytomato:20200325010722p:plain
Fig3.

ユーティリティコマンド

インストールディレクトリ直下のbinディレクトリには、ユーティリティコマンドと呼ばれるコマンドが含まれています。たとえばデータベースを作成するcreatedb.exeコマンド、ユーザーを作成するcreateuser.exeコマンドなどです。 この記事では、データベースおよびユーザーの作成はこれらのユーティリティコマンドで行います。(データベースやユーザーの作成はSQLコマンドでも行えます)

pgAdmin 4

インストールディレクトリ直下のpgAdmin 4ディレクトリにPostgreSQLデータベース用のGUI管理ツールが同梱されています。下記の場所にあるpgAdmin4.exeを実行すると起動することができます。

C:\dev\pgsql-12.2\pgAdmin 4\bin\pgAdmin4.exe

初期設定

フォルダの作成

PostgreSQLのインストールディレクトリ直下にdata、logsフォルダを作成します。 以下のようにフォルダが出来ていればOKです。

C:\dev\pgsql-12.2\data
C:\dev\pgsql-12.2\logs

データベースクラスター(Database Cluster)の作成

PostgreSQLではディスク上のデータベースストレージ領域をデータベースクラスターと呼び、最初に初期化する必要があります。 データベースクラスターを初期化するにはinitdbコマンドまたはpg_ctlコマンドを使用します。この記事ではpg_ctlコマンドでデータベースクラスターを作成します。※以降のコマンドプロンプトからのコマンド実行は、断りが無い限りPostgreSQLのインストールディレクトリから行います。

-Uオプションでスーパーユーザーに任意の名前を付けることができますが、PostgreSQLの慣習ではpostgresという名前を付けるのが一般的なのでそのようにしています。

bin\pg_ctl initdb -D "C:/dev/pgsql-12.2/data" -o "-A password -W -U postgres -E UTF8 --no-locale"

実行結果

途中でスーパーユーザー(postgres)のパスワードを決めるように求められますので入力します。メッセージの最後の方に"成功しました。以下のようにしてデータベースサーバを起動することができます:" と出力されればデータベースクラスターの作成は完了です。

データベースシステム内のファイルの所有者はユーザ"<Windowsのユーザー名>"となります。
このユーザをサーバプロセスの所有者とする必要があります。

データベースクラスタはロケール"C"で初期化されます。
デフォルトのテキスト検索構成は english に設定されます。

データベージのチェックサムは無効です。

新しいスーパユーザのパスワードを入力してください: <postgresのパスワード>
再入力してください: <postgresのパスワード>

ディレクトリC:/dev/pgsql-12.2/dataの権限を設定しています ... ok
サブディレクトリを作成しています ... ok
動的共有メモリの実装を選択しています ... windows
デフォルトのmax_connectionsを選択しています ... 100
デフォルトの shared_buffers を選択しています ... 128MB
selecting default time zone ... Asia/Tokyo
設定ファイルを作成しています ... ok
ブートストラップスクリプトを実行しています ... ok
ブートストラップ後の初期化を実行しています ... ok
データをディスクに同期しています ... ok

成功しました。以下のようにしてデータベースサーバを起動することができます:

    C:/dev/pgsql-12.2/bin/pg_ctl -D C:/dev/pgsql-12.2/data -l ログファイル start

オプションの意味

オプション 意味
-D C:/dev/pgsql-12.2/data データベースクラフターのデータディレクト
-A password ユーザーのデフォルトの認証方法
-W スーパーユーザーのパスワードを要求する
-U postgres スーパーユーザーの名前
-E UTF8 テンプレートデータベースのエンコーディング
--no-locale デフォルトのロケール. --no-locale--locale=Cと同様

データベースの起動

データベースクラスターの作成が完了したらデータベースを起動させることができます。データベースの起動はpg_ctl startコマンドで行います。-Dオプションでデータディレクトリのフルパス、-lオプションでログファイルのフルパスを指定します。

bin\pg_ctl start -D "C:/dev/pgsql-12.2/data" -l "C:/dev/pgsql-12.2/logs/postgresql.log"

実行結果

サーバの起動完了を待っています....完了
サーバ起動完了

postgresユーザーで接続する

データベースが起動したら、postgresユーザーで接続できることを確認します。接続にはクライアントツールのpsqlコマンドを使用します。-hオプションで接続先のホスト名、-Uオプションで接続するユーザー名、-dオプションで接続するデータベース名を指定します。

bin\psql -h localhost -U postgres -d postgres
ユーザ postgres のパスワード: <postgresユーザーのパスワード>
psql (12.2)
"help"でヘルプを表示します。

postgres=#

バージョンの確認

select version();でバージョンを確認できます。

postgres=# select version();
                          version
------------------------------------------------------------
 PostgreSQL 12.2, compiled by Visual C++ build 1914, 64-bit
(1 行)

データベース一覧を表示

\lメタコマンドを実行すると、データベースの一覧を表示します。データベースクラスター作成直後の状態を確認します。(※メタコマンドについては後ほど簡単に触れます。)

postgres=# \l
                                        データベース一覧
   名前    |  所有者  | エンコーディング | 照合順序 | Ctype(変換演算子) |     アクセス権限
-----------+----------+------------------+----------+-------------------+-----------------------
 postgres  | postgres | UTF8             | C        | C                 |
 template0 | postgres | UTF8             | C        | C                 | =c/postgres          +
           |          |                  |          |                   | postgres=CTc/postgres
 template1 | postgres | UTF8             | C        | C                 | =c/postgres          +
           |          |                  |          |                   | postgres=CTc/postgres
(3 行)

終了する場合は\qメタコマンドかexitと入力してenterを押下します。

データベースのステータスの確認

データベースのステータスはpg_ctl statusコマンドで確認できます。

bin\pg_ctl status -D "C:/dev/pgsql-12.2/data"

起動中のステータス

pg_ctl: サーバが動作中です(PID: 5852)
C:/dev/pgsql-12.2/bin/postgres.exe "-D" "C:/dev/pgsql-12.2/data"

停止中のステータス

pg_ctl: サーバが動作していません

データベースの停止

データベースの停止はpg_ctl stopコマンドで行います。

bin\pg_ctl stop -D "C:/dev/pgsql-12.2/data"

実行結果

サーバ停止処理の完了を待っています....完了
サーバは停止しました

メタコマンド

PostgreSQLにはメタコマンドという\から始まるコマンドが用意されています。メタコマンドの種類は\?というメタコマンドで確認できます。 よく使うメタコマンドにテーブル、ビュー、シーケンスを一覧表示する\d、データベースを一覧表示する\lがあります。その他に下記に3つほど使用例をあげました。

別のデータベースへ接続する

接続中のpostgresユーザーから、sample_userユーザーでsample_dbデータベースへ接続します。

\c sample_db sample_user

接続情報

\conninfo
データベース"postgres"にユーザ"postgres"として、ホスト"localhost"(アドレス"::1")上のポート"5432"で接続しています。

クライアントのエンコーディング

\encoding
SJIS

ユーザーの作成

postgresユーザーはスーパーユーザーつまり管理者権限を持つユーザーなので、管理操作以外で使用するための一般ユーザーを作成します。 この例ではcreateuserコマンドでsample_userというユーザーを作成します。-Uオプションにはデータベースに接続するユーザーを指定します。この例ではpostgresユーザーを指定しています。

bin\createuser -U postgres -W -P -e sample_user
新しいロールのためのパスワード: <sample_userのパスワード>
もう一度入力してください:<sample_userのパスワード>
パスワード: <postgresのパスワード>
SELECT pg_catalog.set_config('search_path', '', false);
CREATE ROLE sample_user PASSWORD 'md5c326ab35c9353dd34801ecd7ab7b1d76' NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;

データベースの作成

createdbコマンドでsample_userユーザーがオーナーのsample_dbデータベースを作成します。-Oオプションでデータベースのオーナーになるユーザーを指定、-Uオプションにはデータベースに接続するユーザーを指定します。この例ではpostgresユーザーを指定しています。

bin\createdb -O sample_user -U postgres -W -l Japanese_Japan.932 -T template0 -e sample_db

実行結果

パスワード: <postgresのパスワード>
SELECT pg_catalog.set_config('search_path', '', false);
CREATE DATABASE sample_db OWNER sample_user TEMPLATE template0 LC_COLLATE 'Japanese_Japan.932' LC_CTYPE 'Japanese_Japan.932';

データベースの削除

作成したデータベースを削除はdropdbコマンドで行います。以下はsample_dbデータベースを削除する例です。

bin\dropdb -U postgres -W -e sample_db

実行結果

パスワード: <postgresのパスワード>
SELECT pg_catalog.set_config('search_path', '', false);
DROP DATABASE sample_db;

起動・停止用のバッチファイルの作成

PostgreSQLサーバーの起動や停止を簡単に行うためにバッチファイルを作成します。バッチファイルを作成しておけば、以降の起動や停止はバッチファイルを実行するだけで行えるようになります。

起動用

インストールディレクトリ直下にstartup.batという名前のファイルを作成します。

C:\dev\pgsql-12.2\startup.bat

startup.batをテキストエディタで開き、以下の内容をコピーしてペーストします。

rem 起動用
C:\dev\pgsql-12.2\bin\pg_ctl start -D "C:/dev/pgsql-12.2/data" -l "C:/dev/pgsql-12.2/logs/postgresql.log"

pause

停止用

インストールディレクトリ直下にshutdown.batという名前のファイルを作成します。

C:\dev\pgsql-12.2\shutdown.bat

shutdown.batをテキストエディタで開き、以下の内容をコピーしてペーストします。

rem 停止用
C:\dev\pgsql-12.2\bin\pg_ctl stop -D "C:/dev/pgsql-12.2/data"

pause

postgresユーザー接続用

インストールディレクトリ直下にpostgres_user.batという名前のファイルを作成します。

C:\dev\pgsql-12.2\postgres_user.bat

postgres_user.batをテキストエディタで開き、以下の内容をコピーしてペーストします。

rem postgresユーザーでログイン
C:\dev\pgsql-12.2\bin\psql -U postgres -d postgres

テーブルの作成

作成したsample_userで接続しsample_dbデータベースにtodoテーブルを作成します。

bin\psql -U sample_user -d sample_db
ユーザ sample_user のパスワード: <sample_userのパスワード>
psql (12.2)
"help"でヘルプを表示します。

sample_db=>

以下のSQL文を実行してテーブルとシーケンスを作成します。

CREATE TABLE IF NOT EXISTS todo (
  id BIGSERIAL NOT NULL,
  title VARCHAR(255) NOT NULL,
  description VARCHAR(1024) NULL,
  done BOOLEAN DEFAULT FALSE NOT NULL,
  create_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
  update_at TIMESTAMP WITHOUT TIME ZONE NULL,
  PRIMARY KEY (id)
);

データの登録

INSERT INTO todo (title) VALUES ('買い物に行く')
, ('部屋の掃除')
, ('犬の散歩')
, ('ブログを書く')
, ('JavaScriptの勉強')
;

PostgreSQLの再インストール

なんらかの理由でPostgreSQLの環境をリセットして再作成したい場合は、以下のフォルダをWindowsエクスプローラで削除し、『初期設定』の『フォルダの作成』からやり直します。 ※dataフォルダも削除するので、それまで作成したデータがそのまま消えてしまう点に注意してください。

C:\dev\pgsql-12.2\data
C:\dev\pgsql-12.2\logs

PostgreSQLのアンインストール

PostgreSQLのインストールディレクトリをWindowsエクスプローラで削除します。

C:\dev\pgsql-12.2

補足

参考情報