20200715のJavaに関する記事は14件です。

TDD勉強#3(July 15th, 2020)

新しい知識

static

staticが含まれるクラスをインスタンス化せずにその変数にアクセスすることができる.
この変数をstatic変数,または,クラス変数と呼ばれる.
クラス変数はグローバル変数のような使い分けをすることができる.

アクセス修飾子 static 型名 変数名    //宣言
クラス名.変数名    //呼び出し

抽象クラス

どういう処理を行うかが決まっていないときに使う.
抽象メソッドは処理内容を書かない.
抽象クラスはインスタンス化できない.

abstract class Sample {    //抽象クラスの宣言
    abstract void test();    //抽象メソッドの宣言.{}がない.
}

抽象クラスは親クラスとなることが前提のクラス.
抽象メソッドはサブクラスでオーバーライドされることで処理内容が確定する.

super

親クラスの変数やメソッドに子クラスからアクセスする.

super.メソッド名    //親クラスメソッドを呼び出す.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DjangoでAPIを作る

はじめに

やあみんな!こんにちは、こんばんは、あるいはおやすみなさい!

どうも僕です。Takurintonです。

今回はDjangoでAPI作ってそれを叩くみたいなことします。
背景としては、弊学のとある科目でそんな感じのことしないと実装できない問題があったのでちょっくらやってやるかって感じです。
まあ無知の状態からだったのでだいぶキツかったです。自由課題なので完全に自爆なんですけどね( ;∀;)

前提条件

所要時間

全体の所要時間の合計としてはググる時間も含めて6時間くらいでした。目安までにどうぞ。

背景

もともと業務委託で作らせてもらっていたECサイトをまんま改造して作りました。でも更新してる最中のサイトをぶち壊してはいけないので、別で環境をDockerに作って、docker-composeで仮想環境をエイヤ!ってしてやろうかなとか思って簡単にですが作りました。
Dockerって使っても使わなくてもデータベースくらいしか変更点ないと思うので、そこら辺は省略します(細かいことは個人ブログでそのうち書きます)

環境

OS macOS Chatalina version 10.15.4
言語 Python, Java
フレームワーク Django REST framework

使用技術

Django REST framework全体のリンクはこちら

作る!!!

構成

もともとデータベースやらロジックはしっかりしてあったので、そこらへんの整備は必要ありませんでした。

今回作成するAPIでは注文情報を取得します。
イメージとして

  • ユーザーのメアド
  • 注文した店舗
  • 注文した商品の値段と写真のリスト
  • 合計金額
  • 希望お届け時間

あたりを取得したいのでそれに合わせて実装しました。

これに関連したデータベースは以下のようになっています。

models.py
class CustomUserManager(UserManager):
    use_in_migrations = True
    def _create_user(self, email, password=None, zip_code=None, address1=None, address2=None, address3=None,  **extra_fields):
        if not email:
            raise ValueError('メールは必須で被りなし')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        phone_number_regex = RegexValidator(regex=r'^[0-9]+$', message = ("Tel Number must be entered in the format: '09012345678'. Up to 15 digits allowed."))
        phone_number = models.CharField(validators=[phone_number_regex], max_length=15, verbose_name='電話番号')
        zip_code = models.CharField(max_length=8)
        address1 = models.CharField(max_length=40)
        address2 = models.CharField(max_length=40)
        address3 = models.CharField(max_length=40, blank=True)
        user.save(using=self._db)
        return email



    def create_user(self, request_data, **kwargs):
        if not request_data['email']:
            raise ValueError('Users must have an email address.')

        user = self.model(
            email=request_data['email'],
            first_name=request_data['first_name'], 
            last_name=request_data['last_name'], 
            # password=request_data['password'], 
            zip_code=request_data['zip_code'],
            address1=request_data['address1'], 
            address2=request_data['address2'], 
            address3=request_data['address3'], 
        )

        user.set_password(request_data['password'])
        user.save(using=self._db)
        return user

    def create_superuser(self, email, phone_number=None, password=None, zip_code=None, address1=None, address2=None, address3=None,  **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')
        return self._create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    #username = models.CharField(_('username'), max_length=20, unique=True)
    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30)
    last_name = models.CharField(_('last name'), max_length=150)
    zip_code = models.CharField(max_length=8)
    address1 = models.CharField(max_length=40)
    address2 = models.CharField(max_length=40)
    address3 = models.CharField(max_length=40, blank=True)
    phone_number_regex = RegexValidator(regex=r'^[0-9]+$', message = ("Tel Number must be entered in the format: '09012345678'. Up to 15 digits allowed."))
    phone_number = models.CharField(validators=[phone_number_regex], max_length=15, verbose_name='電話番号', null=True, blank=True)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = CustomUserManager()
    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []


    def user_has_perm(self, user, perm, obj):
        return _user_has_perm(user, perm, obj)

    def has_perm(self, perm ,obj=None):
        return _user_has_perm(self, perm, obj=obj)

    def has_module_perms(self, app_label):
        return self.is_staff

    def get_short_name(self):
        return self.first_name

    class Meta:
        # db_table = 'api_user'
        swappable = 'AUTH_USER_MODEL'

class Company(models.Model):
    name = models.CharField(max_length=255)
    introduction = models.TextField(max_length=65536)
    postal_code = models.CharField(max_length=8)
    company_image = models.ImageField()
    homepage = models.CharField(max_length=255, null=True, blank=True)
    images = models.BooleanField(verbose_name='', default=False)
    place = models.CharField(max_length=255)

    def __str__(self):
        return str(self.name)


class Product(models.Model):
    company = models.ForeignKey(Company, on_delete=models.CASCADE)
    name = models.CharField(max_length=255)
    contents = models.CharField(max_length=255)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    product_image = models.ImageField()
    option = models.CharField(max_length=255, null=True, blank=True, default=None)
    price = models.IntegerField()
    def __str__(self):
        return str(self.name)

class Cart(models.Model):
    cart_id = models.IntegerField(null=True, blank=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
    is_active = models.BooleanField(default=True)
    pub_date = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return str(self.cart_id)



class UserInfomation(models.Model):
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE, blank=True, null=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
    day = models.CharField(max_length=255, default=None)
    time = models.CharField(null=True, blank=True, max_length=255,  default=None)
    status = models.BooleanField(default=False, null=True, blank=True)
    total = models.IntegerField(null=True)
    remark = models.TextField(max_length=65535, null=True, blank=True)
    pub_date = models.DateTimeField(default=now)

    def __str__(self):
        return str(self.user)


class OrderItems(models.Model):
    user = models.ForeignKey(UserInfomation, on_delete=models.CASCADE)
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE, blank=True, null=True)
    item = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True)
    number = models.IntegerField(null=True)
    price = models.IntegerField(null=True)
    total = models.IntegerField(null=True)

  • Userテーブルはカスタムユーザーモデルを拡張しています
  • それぞれのパーミションを取得するための関数をいくつか定義しています
  • companyはproductと1対多になっています
  • userはcartと1対多になっていますが、アクティブなカートは1つだけになります
  • UserInfomationには確定した注文が入っています
  • OrderItemsにはUserの情報が入っています

他にもテーブルはあるのですが、今回はこれだけあればできるので省略します。

作っていく

準備

まずrestframeworkをインストールしていない方はインストールします。

pip install djangorestframework

次にsettings.pyに以下の設定を追加します。

settings.py
...  

INSTALLED_APPS = [
    ...
    'rest_framework',
]

...



JWT_AUTH = {
    'JWT_VERIFY_EXPIRATION': False, # tokenの永続化
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}


REST_FRAMEWORK = { 
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),  
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),  
    'NON_FIELD_ERRORS_KEY': 'detail',
    'TEST_REQUEST_DEFAULT_FORMAT': 'json', 
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend', 
    ), 
}



ログイン機能

ここでは既に存在するユーザーにログインする機能を作成します。
今回は全てのリクエストに認証をかけます。ログイン中のユーザーのみ操作を可能にするのでここは必須です。
まずはエンドポイントを作成します。

project_name/urls.py
from django.conf.urls import url

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    url(r'^login_/', obtain_jwt_token),
    ..., 
]

このエンドポイントを追加することでログイン機能が完成します。
あくまでデータベースを構築している前提です。obtain_jwt_tokenというのは、このエンドポイントにアクセスした時に認証用のトークンを返してくれるようにします。

既存のユーザーでログインしてみたいと思います。
実験段階ではipythonを使っていきます(楽だから)

In [1]: import requests
In [2]: import json
In [3]: data = {'email': 'hogehoge@gmail.com', 'password': 'hogehoge'}
In [4]: r = requests.post('localhost:8000/login_/', data=data)
In [5]: print(r.json())
Out[5]: {'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo5LCJ1c2VybmFtZSI6ImIxODA2NDI5QGdtYWlsLmNvbSIsImV4cCI6MTU5NDc5OTMwNCwiZW1haWwiOiJiMTgwNjQyOUBnbWFpbC5jb20ifQ.Torhy69ZyKMOOxQUUv3Ebn9V6wqSwUlsQUD5IPUaDJA'}

こんな感じで長めのJSONが返ってきます。
この時の注意点としては、Pythonには辞書をJSONに変換してくれるjson.dumps(dict)というようなものがありますが、それをやるとエラーになります。ここで渡すのはあくまでもdict型のデータになります。また、この返ってきたトークンは後々使うので大切にしておいてください。
ログイン時に使うデータとしては、主キーであるメールアドレスとパスワードになっています。なんだかわからない人はmodels.pyのコードを読んでみてください。

余談ですが、Pythonのrequestsではdataにbody、headersにヘッダーを入れることができます。
r.json()にすることでレスポンスをdict型に変換し、使いやすい形に変換することができます。
requestsとjsonはだいぶ密です。密です。密です。

カスタムJSON

次は自分用にカスタマイズしたJSONを返していきます。
個人的にはここでつまづきました。

最初はSerializerGeneric viewsを使って実装する方がいいと思っていたのですが、どうやらこれではカスタムのJSON(これらを使うと任意の単一テーブルに存在するフィールドのものしか返せない)を返すことができないようなので、やり方を変更しました。
今のやり方にたどり着くまでにだいぶ時間がかかってしまったのでそこが反省です。

カスタムのJSONを返したい場合にはシリアライザーなどはいらないらしいので、直接views.pyに記述していきます。
エンドポイントはurls.pyに記述します。

urls.py
from django.urls import path
from . import views


urlpatterns = [
...
path('get_shop_view', views.ShopView.as_view()), #これを追加
]
views.py
from django.http import HttpResponse
from rest_framework import generics

class ShopView(generics.ListCreateAPIView):
    # list(self, request, *args, **kwargs) を使うことでカスタムJSONを生成することができるようになる
    def list(self, request, *args, **kwargs):
        # 最終的に返すJSONのリスト
        return_list = list()
        try:
            user_info = UserInfomation.objects.all() # 購入経験のある全てのユーザーを取得
            for i in user_info:
                order = OrderItems.objects.filter(user=i) # ユーザーを指定
                shop = order[0].item.company.name # 会社を指定

                # 商品一覧を取得
                order_items = dict()
                for i in order:
                    order_items[i.item.name] = {
                                                'price': i.item.price, # 値段
                                                'images': str(i.item.product_image) # 画像のURL
                                                } 


                total = sum([i.total for i in order]) #合計金額。リスト内包で生成したリストの合計を出す

                date_time_ = UserInfomation.objects.get(cart=i.cart)
                date_time = date_time_.day + date_time_.time # 希望お届け時間を取得

                # 全体のリストにこれらのデータを入れる
                return_list.append(
                    {
                        'user': str(i.user),   # ユーザーのメアド
                        'shop': shop,          # 注文した店舗
                        'order': order_items,  # 注文した商品一覧
                        'total': total,        # 合計金額
                        'datetime': date_time, # 希望お届け時間
                    }
                )    
        # エラーが出たら空のリストを返す        
        except:
            pass

        return Response(
            return_list
        )

ここまで作成してから先ほど作成したエンドポイントに今度はgetリクエストを投げてみます

In [1]: {'Content-Type': 'application/json',
 'Authorization': 'JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo5LCJ1c2VybmFtZSI6ImIxODA2NDI5QGdtYWlsLmNvbSIsImV4cCI6MTU5NDc5OTMwNCwiZW1haWwiOiJiMTgwNjQyOUBnbWFpbC5jb20ifQ.Torhy69ZyKMOOxQUUv3Ebn9V6wqSwUlsQUD5IPUaDJA'}
In[2]: order_list = requests.get('http://localhost:8000/get_shop_view', headers=headers)
In [3]: order_list.json()
Out[3]:
[{'user': 'hogehoge@gmail.com',
  'shop': '店舗B',
  'order': {'ハンバーグ': {'price': 1000,
    'images': 'IMG_4145_ykxb9h'}},
  'total': 1000,
  'datetime': '2020年7月14日今すぐ'},
 {'user': 'hogehoge@gmail.com',
  'shop': '店舗B',
  'order': {'ハンバーグ': {'price': 1000,
    'images': 'IMG_4145_ykxb9h'},
   'カレー': {'price': 2,
    'images': '11967451898714_y6tgch'}},
  'total': 2002,
  'datetime': '2020年7月14日今すぐ'},
 {'user': 'fugafuga@gmail.com',
  'shop': '店舗B',
  'order': {'シチュー': {'price': 11111,
    'images': 'IMG_4900_oyb5ny'},
   'ハンバーグ': {'price': 1000,
    'images': 'IMG_4145_ykxb9h'},
   'コーヒー': {'price': 199,
    'images': '54490_jawqyl'},
   'カレー': {'price': 2,
    'images': '11967451898714_y6tgch'},
   'パンケーキ': {'price': 100,
    'images': 'tweet_p7chgi'},
   'Takurinton': {'price': 100,
    'images': 'npyl13'}},
  'total': 24220,
  'datetime': '2020年7月16日今すぐ'}]

これは僕が事前にカートにホイホイ突っ込んでおいたやつですが、無事に返ってきました!!!
嬉しい〜!!!ここまで5時間45分くらいかかった。。。

Javaで叩いてみる

レポートはJavaなのでJavaでも叩けるようになりたいなと思ってググってみたら意外と簡単でした。気難しい言語だと思ってたのでちょっと嬉しい。。。笑笑

Test.java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import java.nio.charset.StandardCharsets;

public class Test{
    public static void main(String[] args){
        try {
            HttpRequest request = HttpRequest
                    .newBuilder(URI.create("http://localhost:8000/get_shop_view"))
                    .header("Content-Type", "application/json")
                    .header("Authorization", "JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo5LCJ1c2VybmFtZSI6ImIxODA2NDI5QGdtYWlsLmNvbSIsImV4cCI6MTU5NDc5OTMwNCwiZW1haWwiOiJiMTgwNjQyOUBnbWFpbC5jb20ifQ.Torhy69ZyKMOOxQUUv3Ebn9V6wqSwUlsQUD5IPUaDJA")
                    .GET()
                    .build();

            BodyHandler<String> bodyHandler = HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8);
            HttpResponse<String> response = HttpClient.newBuilder().build().send(request, bodyHandler);

            String body = response.body();
            System.out.println(body);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

相変わらずコードは長いですが、許容範囲内です。
HttpRequestにじゃんじゃか書いていくスタンスみたいです。ヘッダーのトークンは先ほど取得したものを同じものを利用することができます。
なんかあまり深く理解できてないけど、、、。

コンパイルして実行します。

(base) Hogehoge:working takurinton$ javac Test.java
(base) Hogehoge:working takurinton$ java Test
[{'user': 'hogehoge@gmail.com',
  'shop': '店舗B',
  'order': {'ハンバーグ': {'price': 1000,
    'images': 'IMG_4145_ykxb9h'}},
  'total': 1000,
  'datetime': '2020年7月14日今すぐ'},
 {'user': 'hogehoge@gmail.com',
  'shop': '店舗B',
  'order': {'ハンバーグ': {'price': 1000,
    'images': 'IMG_4145_ykxb9h'},
   'カレー': {'price': 2,
    'images': '11967451898714_y6tgch'}},
  'total': 2002,
  'datetime': '2020年7月14日今すぐ'},
 {'user': 'fugafuga@gmail.com',
  'shop': '店舗B',
  'order': {'シチュー': {'price': 11111,
    'images': 'IMG_4900_oyb5ny'},
   'ハンバーグ': {'price': 1000,
    'images': 'IMG_4145_ykxb9h'},
   'コーヒー': {'price': 199,
    'images': '54490_jawqyl'},
   'カレー': {'price': 2,
    'images': '11967451898714_y6tgch'},
   'パンケーキ': {'price': 100,
    'images': 'tweet_p7chgi'},
   'Takurinton': {'price': 100,
    'images': 'npyl13'}},
  'total': 24220,
  'datetime': '2020年7月16日今すぐ'}]

無事返ってきます。ReactとかVueだとブラウザの関係でもっとめんどくさくなるのですが、Javaならコマンドラインで完結するので簡単でした。

まとめ

実はここに書いたもの意外にユーザー作成、ユーザーの情報取得、お店の情報取得、お店が持ってる商品一覧などなど、、、たくさん作ってあります。
ここら辺は1つのテーブルに依存する部分が多いので、カスタムよりもフィールドを指定して作る方が速かったりするので使い分けが重要ってことですね。
Java触ると疲れちゃうので今日はここら辺にしておきます。笑笑

また、6時間くらいで実装することができましたが、これをまだどんどんいじって行かないといけないので長い旅になりそうです。

コロナウイルスによるオンライン授業で課題ばっかり出て大変!テストはやるんかい!とか思ってる大学生の皆さん!一緒にコロナウイルスに負けないくらい勉強してこの状況を乗り越えましょう!あと2週間頑張るぞ〜!!

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

Docker × Spring Boot環境構築

Docker × Spring Boot環境構築

概要

Dockerを使って、Spring Bootの環境を構築します。
動作確認として、Hello Worldをブラウザに表示させます。

環境

  • macOS Catalina バージョン10.15.5
  • Docker version 19.03.8
  • docker-compose version 1.25.5
  • openjdk 14.0.1 2020-04-14
  • Spring Boot 2.3.1

構成

最終的には以下のような構成になります。

├── docker-compose.yml
└── server
    ├── HELP.md
    ├── build
    │   ├── classes
    │   │   └── java
    │   │       ├── main
    │   │       │   └── com
    │   │       │       └── example
    │   │       │           └── api
    │   │       │               └── ApiApplication.class
    │   │       └── test
    │   │           └── com
    │   │               └── example
    │   │                   └── api
    │   │                       └── ApiApplicationTests.class
    │   ├── generated
    │   │   └── sources
    │   │       ├── annotationProcessor
    │   │       │   └── java
    │   │       │       ├── main
    │   │       │       └── test
    │   │       └── headers
    │   │           └── java
    │   │               ├── main
    │   │               └── test
    │   ├── libs
    │   │   └── api-0.0.1-SNAPSHOT.jar
    │   ├── reports
    │   │   └── tests
    │   │       └── test
    │   │           ├── classes
    │   │           │   └── com.example.api.ApiApplicationTests.html
    │   │           ├── css
    │   │           │   ├── base-style.css
    │   │           │   └── style.css
    │   │           ├── index.html
    │   │           ├── js
    │   │           │   └── report.js
    │   │           └── packages
    │   │               └── com.example.api.html
    │   ├── resources
    │   │   └── main
    │   │       ├── application.properties
    │   │       ├── static
    │   │       └── templates
    │   ├── test-results
    │   │   └── test
    │   │       ├── TEST-com.example.api.ApiApplicationTests.xml
    │   │       └── binary
    │   │           ├── output.bin
    │   │           ├── output.bin.idx
    │   │           └── results.bin
    │   └── tmp
    │       ├── bootJar
    │       │   └── MANIFEST.MF
    │       ├── compileJava
    │       └── compileTestJava
    ├── build.gradle
    ├── gradle
    │   └── wrapper
    │       ├── gradle-wrapper.jar
    │       └── gradle-wrapper.properties
    ├── gradlew
    ├── gradlew.bat
    ├── settings.gradle
    └── src
        ├── main
        │   ├── java
        │   │   └── com
        │   │       └── example
        │   │           └── api
        │   │               └── ApiApplication.java
        │   └── resources
        │       ├── application.properties
        │       ├── static
        │       └── templates
        └── test
            └── java
                └── com
                    └── example
                        └── api
                            └── ApiApplicationTests.java

手順

1. docker-compose.yml作成

今回はjavaコンテナ1つのみのシンプルな構成です。

docker-compose.yml
version: '3.6'
services:
  java:
    image: openjdk:14-slim
    ports:
      - 8080:8080
    tty: true
    volumes:
      - ./server:/srv:cached
    working_dir: /srv

2. Gradleプロジェクト作成

2.1 spring initializrにてGradleプロジェクトの雛形を作成
スクリーンショット 2020-07-10 11.37.29.png

2.2 Gradleプロジェクトの雛形をダウンロード
スクリーンショット 2020-07-10 11.43.22.png

2.3 プロジェクトに展開
プロジェクトディレクトリ直下にserverディレクトリを作成します。
2.2でダウンロードした api.zipを展開し、中身をserver直下に移します。

3. アプリケーションファイルを編集

server/src/main/java/com/example/api/ApiApplication.javaを以下のように編集します。

server/src/main/java/com/example/api/ApiApplication.java
package com.example.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class ApiApplication {

    @RequestMapping("/")
    public String home() {
        return "Hello World";
    }

    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
    }

}

4. Docker起動

// docker環境ビルド
% docker-compose build

// dockerをバックグラウンドで起動
% docker-compose up -d

5. Gradleのビルド

// javaコンテナにインスペクション
% docker-compose exec java bash

// gradleビルド
root@62acca270468:/srv# sh gradlew build
...
BUILD SUCCESSFUL in 1m 38s
5 actionable tasks: 3 executed, 2 up-to-date

server直下にbuildディレクトリができていることを確認する。

6. アプリケーションの実行

root@62acca270468:/srv# java -jar build/libs/api-0.0.1-SNAPSHOT.jar

7. 動作確認

http://localhost:8080/ にアクセスしてみましょう。
Hello Worldが表示されていたら成功です。

参考

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

駅データ.jpのCSVファイルから東京の地下鉄路線図を作る

はじめに

ちょっと古い記事ですが、「駅とか路線のマスターデータの入手方法 - Qiita」を読んで、路線のCSVデータがあることを知りました。この記事で紹介されている『駅データ.jp』にあるCSVファイルを使って東京の地下鉄路線図を作成してみます。使用するJavaのバージョンは14です。グラフの表示にはyEd - Graph Editorを使います。
すべてのソースコードはここにあります。

ダウンロード

マイページ | 駅データ 無料ダウンロード 『駅データ.jp』から路線データ、駅データ、接続駅データをダウンロードします。

マイページ _ 駅データ 無料ダウンロード 『駅データ.jp』 - Google Chrome 2020_07_15 8_04_35.png

ダウンロードするためには無料のアカウントを作成しておく必要があります。

CSVファイルの読み込み

CSVファイルを読み込んでList<List<String>>に変換する共通のプログラムを作成します。

static final Charset CHARSET = StandardCharsets.UTF_8;
static final Path DIRECTORY = Paths.get("data", "eki");
static final Path LINE_CSV = DIRECTORY.resolve("line20200619free.csv");
static final Path STATION_CSV = DIRECTORY.resolve("station20200619free.csv");
static final Path JOIN_CSV = DIRECTORY.resolve("join20200619.csv");
static final Path GML = DIRECTORY.resolve("metro.gml");

static List<List<String>> readCSV(Path file) throws IOException {
    return Files.readAllLines(file, CHARSET).stream()
        .map(line -> List.of(line.split(",")))
        .collect(toList());
}

各項目は引用符などで囲まれていないので簡単に読み込むことができます。

路線データの読み込み

最初に路線データを読み込みます。東京の地下鉄路線図を作成するので、路線名が「東京メトロ」または「都営」で始まるものだけを抽出します。

// 東京の地下鉄の路線
List<List<String>> lines = readCSV(LINE_CSV).stream()
    .filter(line -> line.get(2).startsWith("東京メトロ") || line.get(2).startsWith("都営"))
    .collect(toList());

駅データの読み込み

次に駅データを読み込みますが、先に読み込んだ路線データの路線コードに該当するものだけを抽出するために、路線コードのリストを作成します。(ListではなくSetにすべきでした)

// 東京の地下鉄の路線コードのリスト
List<String> lineCodes = lines.stream()
    .map(line -> line.get(0))
    .collect(toList());

このリストにある路線コードを持つ駅だけを抽出します。

// 東京の地下鉄の駅
List<List<String>> stations = readCSV(STATION_CSV).stream()
    .filter(station -> lineCodes.contains(station.get(5)))
    .collect(toList());

接続駅データの読み込み

最後に接続駅データを読み込みます。ここでも東京の地下鉄の路線コードに関連するものだけを抽出します。

// 東京の地下鉄の駅の接続
List<List<String>> joins = readCSV(JOIN_CSV).stream()
    .filter(line -> lineCodes.contains(line.get(0)))
    .collect(toList());

グラフの作成

読み込んだデータからグラフを作成します。グラフはyEd - Graph Editorを使って読み込むので、GML (Graphic Modeling Language)という形式のテキストデータを作成します。

// グラフの作成
try (PrintWriter w = new PrintWriter(Files.newBufferedWriter(GML))) {
    w.println("graph [");
    for (List<String> s : stations) {
        w.println("  node [");
        w.println("    id " + s.get(0));     // 駅コード
        w.println("    label \"" + s.get(2) + "\"");  // 駅名
        w.println("  ]");
    }
    for (List<String> j : joins) {
        w.println("  edge [");
        w.println("    source " + j.get(1));    // 接続駅コード1
        w.println("    target " + j.get(2));    // 接続駅コード2
        w.println("  ]");
    }
    w.println("]");
}

駅をnode、接続駅をedgeとして作成します。

最初のグラフ

yEdで読み込んで以下の整形をします。

  • Tools -> Fit Node to Label
    各ノードは固定の大きさの正方形として表示されますが、ノードの中に表示されるラベルテキストの大きさに合わせてノードの幅を可変にします。
  • Layout -> Organic
    各ノードは位置情報をもっていないので、すべてのノードが画面中央に表示されます。Layoutメニューはこれを見やすくレイアウトしてくれます。ここではOrganicレイアウトを使います。

そうするとこんなグラフになりました。13ある路線がバラバラに作成されています。左上は環状線を含むので大江戸線、その隣はY字型なので方南町支線を含む丸の内線であることがわかります。

metro-最初のプログラム.png

しかし、これでは路線図とは言えません。
こうなった原因は同じ駅が路線ごとに別々に登録されているためです。

同じ駅名で集約

駅コードを同一の駅名を持つもので集約します。結果はリストのマップで、キーが駅名、値がその駅名を持つ駅コードのリストです。例えば新宿=[2800218, 9930128, 9930401]といった感じです。

// 駅名でグループ化した駅コード (ex. 新宿=[2800218, 9930128, 9930401])
Map<String, List<String>> stationNameMap = stations.stream()
    .collect(groupingBy(e -> e.get(2),
        mapping(e -> e.get(0), toList())));

新宿を表すコードをひとつにまとめるため、駅コードを代表の駅コードに変換するためのマップを作成します。駅コードリストの先頭に登場する駅コードを代表駅コードとします。新宿の場合、2800218=2800218, 9930128=2800218, 9930401=2800218となります。

// 駅コードから代表駅コードへのマップ (ex. 2800218=2800218, 9930128=2800218, 9930401=2800218)
Map<String, String> stationCodeMap = stationNameMap.values().stream()
    .flatMap(codes -> codes.stream().map(code -> Map.entry(code, codes.get(0))))
    .collect(toMap(Entry::getKey, Entry::getValue));

同じ駅名を集約したグラフの作成

同じ駅名をひとつにまとめたグラフを作成します。

// グラフの作成
try (PrintWriter w = new PrintWriter(Files.newBufferedWriter(GML))) {
    w.println("graph [");
    for (Entry<String, List<String>> e : stationNameMap.entrySet()) {   // 駅名で集約したデータを使用
        w.println("  node [");
        w.println("    id " + e.getValue().get(0));     // 代表駅コード
        w.println("    label \"" + e.getKey() + "\"");  // 駅名
        w.println("  ]");
    }
    for (List<String> j : joins) {
        w.println("  edge [");
        w.println("    source " + stationCodeMap.get(j.get(1)));  // 代表駅コードに変換
        w.println("    target " + stationCodeMap.get(j.get(2)));  // 代表駅コードに変換
        w.println("  ]");
    }
    w.println("]");
}

yEdで読み込ませて、整形するとこうなりました。

metro-市ケ谷問題あり.png

市ケ谷と市ヶ谷の統合

路線図らしくなりましたが市ケ谷が2つあります。

metro-市ケ谷問題あり-拡大.png

よく見ると「ケ」と「ヶ」の違いで、2つの市ケ谷駅ができてしまっています。
Wikipediaで市ケ谷駅を調べてみると、こんなことが書いてあります。

JR東日本と東京メトロの駅は「市ケ谷」、都営地下鉄の駅は「市ヶ谷」と表記する。

グラフでは

  • 曙橋→市ヶ谷→九段下 (都営新宿線)
  • 飯田橋→市ケ谷→四ツ谷 (東京メトロ南北線)

となっているので、駅データ.jpはこの違いを正確に表現していることがわかります。
この違いを吸収するためには、先に記述した駅データの読み込みを以下のように変更します。

// 東京の地下鉄の駅
List<List<String>> stations = readCSV(STATION_CSV).stream()
    .filter(station -> lineCodes.contains(station.get(5)))
    .map(station -> station.stream()
        .map(item -> item.replace('ヶ', 'ケ')).collect(toList())) // 市ヶ谷と市ケ谷を統一
    .collect(toList());

ここでは東京メトロ流に統一することにしました。

完成したグラフ

グラフの最終形はこのようになりました。

metro.png

まとめ

「大手町」が中央付近にあってそれらしいのですが、よく見ると左端に「西船橋」、右端に「荻窪」、上端に「西馬込」、下端に「西高島平」があります。東西と南北がそれぞれ逆転している感じです。グラフには位相幾何学的な情報しか与えていないので仕方がないのですが、駅データには駅の緯度・経度の情報も含まれているので、地理的に正確に近い路線図も作ることができるでしょう。GMLのnodeにはcolor属性もあるので、路線ごとに色分けすることもできます。yEdには様々なレイアウトがあるので、いろいろ試してみると面白いです。
今回作成したプログラムのほとんどがStream APIを使用しています。ほとんどが「ワンライナー」で簡潔に記述できるので非常に便利だと感じました。

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

Androidアプリ開発準備 7/15

本日はJDKインストール、Androidstudioダウンロードをした。

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

IT未経験が新卒で常駐型インフラエンジニアとして働きます

初めまして、マイクさんと申します。

本日からのんびりと投稿をしていくので、まずは自己紹介をします。

20卒男

小中高と平凡な人生を歩み、大学では建築を学びにいきました。

しかし、建築つまらん。

サークルのためだけに大学に通いました。

そして就活もサボりました。

結果入社した会社は俗に言う人売りIT。

研修でJavaとLinuxに触れ、Javaの楽しさに気づくも、配属先は常駐型インフラ。

7月にして転職を考えています。

希望はWEB・スマホアプリの自社開発。

今のままでは到底不可能なので、コツコツと勉強をします。

そしてその記録を残す。

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

Problem During Content Assistとなったときの対応方法

  • 環境
    • Windows 10 Pro 64bit バージョン1909
    • Payara Server 5.194
    • Eclipse Version 2020-03
    • openjdk version 11

事象 : Javaコードを書き始めたらダイアログが表示された

ある日、突然ダイアログが現れた。
image.png
image (1).png

エラーメッセージ
The 'org.eclipse.jdt.ui.org.eclipse.jdt.ui.javaCompletionProposalComputer.chain' proposal computer from the 'org.eclipse.jdt.ui' plug-in did not complete normally. The extension has thrown a runtime exception.
To avoid this message, disable the 'org.eclipse.jdt.ui' plug in or disable the 'Chain Template Proposals' category on the content assist preference page.

原因 : 不明

この辺が原因かもしれないけれど古いバグだから違うかもしれない

We currently flag a processor that takes longer than 5s as unacceptable for a user since content assist is a typing assistant. Note that we do not disable the processor, we leave that up to the user i.e. if it happens often he can disable it.
141457 – Time out error message when doing content assist

対応 : Javaの入力補完のタイムアウト時間を伸ばすようにeclipse.iniに設定する

eclipse.ini
; ...省略...
-vmargs
-Dosgi.requiredJavaVersion=1.11
-Xms4096m
-Xmx4096m
; ↓↓↓↓↓↓-vmargsにここを追記する↓↓↓↓↓↓↓↓↓
-Dorg.eclipse.jdt.ui.codeAssistTimeout=60000

他の対応

今回はやらなかったけどいつかのために・・・。
eclipse.iniの-Xmxの値を大きくして使えるメモリを増やす対応もあるらしい。

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

文字列を検索して,検索文字列が見つかったら,行頭から,検索文字列直前までの文字列を表示するプログラム

Javaで文字列を検索して,検索文字列が見つかったら,行頭から,検索文字列直前までの文字列を表示するプログラムを作っています。
※太文字斜文字でユーザ入力
文字列をコピー&ペーストで入力

このような実行結果にしたい。

スクリーンショット 2020-07-15 19.55.09.png

現在の実行結果
スクリーンショット 2020-07-15 19.56.33.png

現在のソースコード
スクリーンショット 2020-07-15 19.57.30.png

補足情報
PC: MAC
エディタ: Atom

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

結果の理由が分かるAIで計画最適化

著者: 高久 隆史, 株式会社日立製作所

はじめに

本稿では、結果の理由が分かるAIによる計画最適化を紹介します。

計画最適化の概要

計画最適化とは、限られたリソースの中で、守るべき制約や効率を考慮しながら、最も良い計画(組合せ計画)を立案することです。
対象となる業務には、人員のシフト計画、機器の検査計画、配送計画、セミナの時間割、在庫管理、積荷配置、金融ポートフォリオ最適化などがあります。

AIによる計画最適化のメリット

計画最適化には様々な分野がありますが、大量の組合せから限られたリソースや個別の制約の中で最適な解を導出する難しい処理となり、人手で実現するには限界があります。
AIによる計画最適化では、次のような点を解決できます。
・計画立案の作業時間が大幅短縮できる
・多数の制約を考慮した、質の高い計画が実現できる
・属人化や不公平感を排除できる

AIによる計画最適化の課題

熟練の計画立案者が時間をかけて実施している計画立案業務をAIで自動化する場合、次のような課題があります。
・多数の制約条件/評価指標に対する仕様の整理や実装に時間がかかる。
・ブラックボックスで結果の確認や説明が難しい。
・熟練の計画立案者が実施したとき以上の計画になっているかの確認が難しい。

Red Hat Decision Managerによる計画最適化

Red Hat Decision Managerを活用すると、上記のような課題も解決できます。
BRMS/ルールエンジンで有名な製品ですが、計画最適化の機能も提供されています。
本製品を活用した計画最適化の特長を次に示します。

特長1 ルールベースで楽々実装できる

計画最適化の仕様となる、様々な制約条件/評価指標を一つずつルールとして独立定義できるため、楽々実装できます。
制約条件/評価指標の全てを最初から正確に定義することは難しいですが、仕様が明確になった制約条件/評価指標から実装し、残りはTRY&ERRORで追加/調整する形で、仕様の明確化と実装を平行して短期間で開発できます。
実装方法や特長を次に示します。

・計画最適化の制約条件/評価指標は、それぞれ独立したルールとしてExcelで実装します。
 計画最適化の仕様を、絶対制約、考慮制約、評価指標に分類して定義し、絶対制約は必ず守りつつ、考慮制約の遵守度合いと評価指標が良くなるように最適化できます。
 絶対制約:必ず守りたい制約。
 考慮制約:可能な限り守りたい制約。
 評価指標:より良い組合せを算出するための指標。

Excelルールの例

ルール名 レベル 重み係数
絶対制約#1_同一勤務時間帯の複数のシフト枠に同一従業員を割り当てない。 Hard 1
考慮制約#1_勤務できる業務に従業員を割り当てる。 Soft 1000
考慮制約#2_研修員と指導員は同一シフト枠に割り当てる。 Soft 500
考慮制約#3_同時勤務除外メンバは、同一シフト枠に割り当てない。 Soft 50
評価指標#1_前後の勤務時間帯の勤務場所を近くする。 Soft 10
評価指標#2_作業平準化_担当回数を平均に近づける。 Soft 1

・ルール間の優先度はレベル+重み係数で定義し、結果はレベルごとのスコア値として評価します。
 高いレベルのスコア値(上の例だとHard)から順に評価され、高いレベルのスコア値が同じ場合は次のレベルのスコア値で評価される仕組みで、最もスコア値の良い組合せを最適解とします。

・ルールは外出しされたExcelファイルに定義し、いつでも変更できます。

・制約条件/評価指標の洗い出しが完了しなくても、明確になったものから先に実装し、後から残りを追加で実装できます。

・制約条件/評価指標の優先度が明確でなくても、調整しながら開発できます。(上の表のレベル、重み係数を調整していきます。)

・各ルールの動作は、ルールのExcelファイル内にDRLという専用言語のコードで定義します。
 定義後、DRL部分は、業務ユーザが必要な部分だけ参照/編集しやすいよう非表示にします(上の表は非表示後の例です)。
 DRLはJavaが分かる方ならすぐ習得できます。

・簡単なルール追加はExcel編集だけで対応できます。

考慮制約#1のExcelルールのコード例
ShiftAssignment(employee != null, shift.skill != employee.skill)

・複雑な処理はJava側で記載し、Excel上のコードからJavaメソッドを呼び出します。

絶対制約#1のExcelルールのコード例
ShiftAssignment(employee != null, $name : shift.name, $employee : employee, $shiftDateTime : shift.shiftDateTime)
ShiftAssignment(shift.name > $name, employee == $employee, shift.shiftDateTime.overlaps($shiftDateTime))

特長2 スコア情報出力で結果の理由が分かる

・最適化結果となる制約条件/評価指標の遵守度合いをスコア情報として出力できます。

スコア情報の出力例
score_example.png

 ・トータルスコア
  全体でどのくらい制約条件/評価指標を遵守できているか(目標値に達しているか)。
 ・ルール単位のスコア
  どのルール(制約条件/評価指標)を違反しているか。各ルールをどのくらい遵守しているか。
 ・各ルールの詳細スコア
  詳細スコアの値は正しいか(期待通り動作しているか)。どのデータが違反しているか。
  スコア=-(平均担当回数-担当回数)^2
  A001:-(7-6)^2=-1 ⇒ 正しい
  A002:-(7-9)^2=-4 ⇒ 正しい

・スコア情報により、期待通り動作しているか確認でき、品質も保証できます。

・守れなかった制約条件が分かるため、入力データや制約条件の見直しができます。

特長3 過去計画のスコアも出力して比較できる

・人手or他製品で計画した計画済データのスコア情報を出力して、旧計画と新計画のスコアを比較し、旧計画以上となっているか確認できます。

・計画済データのスコア情報は、通常の最適化計算用に作成した業務アプリケーション、Excelルールをほぼそのまま利用して簡単に出力できます。

通常の最適化計算
normal_flow.png
計画済データのスコア情報出力
planned_data_flow.png

おわりに

本稿では、結果の理由が分かるAIによる計画最適化を紹介しました。
AIはまだ難しい、信用できない、という方も活用を検討してみてはどうでしょうか。

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

【Java入門】ラムダ式について

目的

Java言語を含めたプログラミングの学習を始めたばかりの方、既学習者の方は復習用に、
今回はラムダ式について学ぶために書いています。

【Java入門目次】
変数と型
型変換
変数のスコープ
・文字列の操作(準備中)
配列の操作
・演算子(準備中)
・条件分岐(準備中)
・繰り返し処理(準備中)
・クラスについて(準備中)
・抽象クラス(準備中)
・インターフェース(準備中)
・カプセル化(準備中)
・モジュールについて(準備中)
例外処理について
・ラムダ式について ←今ここ
Stream APIについて

ラムダ式とは

関数型インターフェースの実装は、無名クラス(匿名クラス)を利用する事で行っていたが、
ソースコードの可読性が良くなく、冗長になりがちであった。

そのような悩みを解決するのがラムダ式
Java8から導入された構文であり、無名クラス(匿名クラス)を利用する事と同様の実装が、シンプルかつ可読性の高い状態で可能になった。

ラムダ式の構文
( 実装するメソッドの引数 ) -> { 処理 };

ラムダ式の例

実際にどの様にシンプルかつ可読性が高くなったのか、簡単な例で比較してみます。

まずは、無名クラス(匿名クラス)での実装。

無名クラス(匿名クラス)
// 自作関数型インターフェース
interface Greet {
  String sayHello(String name);
}

public class Lambda {
  public static void main(String[] args) {

    // Greetインターフェースの実装を行った、無名クラスのインスタンスを生成
    Greet case1 = new Greet(){

      // 抽象メソッドsayHelloをオーバーライド
      public String sayHello(String name) {
        return name + " さん、こんにちは!";
      }

    };

    // 実装したsayHelloメソッドに文字列を渡す
    String tanaka = case1.sayHello("tanaka");
    System.out.println(tanaka); // tanaka さん、こんにちは! と出力される

  }
}

次に、ラムダ式で同様の処理を実装します。

ラムダ式
// 自作関数型インターフェース
interface Greet {
  String sayHello(String name);
}

public class Lambda {
  public static void main(String[] args) {

    // ラムダ式でGreetインターフェースを実装し、インスタンスを生成
    Greet case2 = (String name) -> { return name + " さん、こんにちは!"; };

    // 実装したsayHelloメソッドに文字列を渡す
    String suzuki = case2.sayHello("suzuki");
    System.out.println(suzuki); // suzuki さん、こんにちは! と出力される
  }
}

new Greet() と記述、その中の抽象メソッドのメソッド名を再度記述する事なくオーバーライドする事ができています。
これは、関数型インターフェースは抽象メソッドが1つしかない為ラムダ式ではどのメソッドを実装するのか判断できる様になっているからです。

ラムダ式の省略した記述方法

また特定の場合に限り、更に省略して記述する事も可能です。

1.型推論による型の省略

before
Greet case2 = (String name) -> { return name + " さん、こんにちは!"; };
after
Greet case2 = (name) -> { return name + " さん、こんにちは!"; };

関数型インターフェースの宣言時にメソッドの引数の型は決定している為、実装時に引数の型を明記せずに省略が可能です。

2.()の省略

before
Greet case2 = (String name) -> { return name + " さん、こんにちは!"; };
after
Greet case2 = name -> { return name + " さん、こんにちは!"; };

引数が1つの場合、()の省略が可能です。ただし、引数がない場合、複数ある場合、引数が1つであっても型を明記した場合は省略できません。

3.{}の省略

before
Greet case2 = (String name) -> { return name + " さん、こんにちは!"; };
after
Greet case2 = name -> name + " さん、こんにちは!";

処理が1文の場合、{}の省略が可能です。また、{}を省略している場合、returnも省略可能です。

シンプルに書けますね!

関数型インターフェースの紹介

先程は自作関数型インターフェースを用いた例でしたが、
java.util.functionパッケージとして提供されている関数型インターフェースの例もいくつか紹介していきます。

1.Function<T, R>

Tの型の引数を受け取って、Rの型の値を返します。
メソッドはR apply(T t)です。

Functionの例
Function<Integer, String> funcTest = number -> number + "です";
String resutlt = funcTest.apply(19);
System.out.println(resutlt); // 19です

2.Consumer<T>

Tの型の引数を受け取ります。
メソッドは、void accept(T t)です。

Consumerの例
Consumer<String> weather = str -> System.out.println("今日の天気は" + str);
weather.accept("雨です"); // 今日の天気は雨です

3.Predicate<T>

Tの型の引数を受け取ります。
メソッドは、boolean test(T t)です。

Predicateの例
Predicate<String> check = str -> 5 < str.length();
boolean result = check.test("みたらし団子");
System.out.println(result); // true

boolean result2 = check.test("豆大福");
System.out.println(result2); // false

4.Supplier<T>

引数では何も受け取りません。
メソッドは、T get()です。

Supplierの例
Supplier<String> name = () -> "鈴木一郎";
System.out.println(name.get()); // 鈴木一郎

5.UnaryOperator<T>

Functionを拡張したインターフェースです。引数で受け取る型と返す型の両方が一緒です。
メソッドは、T apply(T t)です。

UnaryOperatorの例
UnaryOperator<String> breakfast = food -> "朝はやっぱり" + food;
System.out.println(breakfast.apply("カレー")); // 朝はやっぱりカレー

その他のメソッドでラムダ式を使う

コレクションフレームワークを中心として提供されているメソッドをラムダ式を用いて実装する事も可能です。(複数のオブジェクトの格納、取り出し、削除が行える)
なぜなら、メソッドの引数に関数型インターフェースを受け取っているからです。こちらもいくつか紹介します。

1.default boolean removeIf(Predicate<? super E> filter)

指定された処理を満たすコレクション要素を全て削除するメソッドです。

removeIfメソッド
List<Integer> numbers = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));
numbers.removeIf(number -> number % 2 == 0); // 2で割って、0になるものを削除する
System.out.println(numbers); // [1, 3, 5]

2.default void replaceAll(UnaryOperator<E> operator)

指定された処理を行い、リストの要素を置き換えるメソッドです。

replaceAllメソッド
List<String> names = Arrays.asList("tanaka", "suzuki", "yamada");
names.replaceAll(name -> name.toUpperCase()); // 全て大文字に置き換える
System.out.println(names); // [TANAKA, SUZUKI, YAMADA]

3.default void sort(Comparator<? super E> c)

指定した順序にしたがってリストをソートするメソッドです。

sortメソッド
List<Integer> randomNum = Arrays.asList(30, 50, 10, 20, 40);
randomNum.sort((a, b) -> b.compareTo(a)); // 降順に並び替え
System.out.println(randomNum); // [50, 40, 30, 20, 10]

randomNum.sort((Integer a, Integer b) -> { return a.compareTo(b); } ); // 昇順に並び替え
System.out.println(randomNum); // [10, 20, 30, 40, 50]

4.void forEach(Consumer<? super T> action)

全ての要素に対して指定されたアクションを実行するメソッドです。

forEachメソッド
List<String> names = Arrays.asList("tanaka", "suzuki", "yamada");

// 名前+半角スペース区切り で1件ずつ出力する様にしている
names.forEach(name -> System.out.print(name + " ")); // tanaka suzuki yamada

終わりに

ラムダ式という構文について触れてみました。
ソースコードが短くなり、実装部分だけの記述で済むのは便利ですね。

Stream APIについては、別記事にまとめようと思います。

参考サイト

java.util.function
【初心者向け】今更聞けない?Java8のラムダ式について知ろう!

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

JavaはPPTドキュメントを最終状態としてマークします

PPTドキュメントを設計および作成した後、ドキュメントが最終的な設計状態に達したことを示し、内部のコンテンツを誰も編集および変更したくない場合は、PPTドキュメントを最終状態としてマークできます。この記事では、Free Spire.Presentation for Javaを使用してPPTプレゼンテーションを最終版としてマークする方法について説明します。

JARパッケージのインポート
方法1:Free Spire.Presentation for Javaをダウンロードして解凍し、jarパッケージをlibフォルダーに依存関係としてJavaアプリケーションに直接インポートします。

方法2:Mavenリポジトリからjarパッケージをインストールし、pom.xmlファイルのコードを次のように構成します。

<repositories>
        <repository>
            <id>com.e-iceblue</id>
            <name>e-iceblue</name>
            <url>http://repo.e-iceblue.com/nexus/content/groups/public/</url>
        </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>e-iceblue</groupId>
        <artifactId>spire.presentation.free</artifactId>
        <version>2.6.1</version>
    </dependency>
</dependencies>

Javaコード

import com.spire.presentation.FileFormat;
import com.spire.presentation.Presentation;

public class MarkAsFinal {
    public static void main(String[] args) throws Exception {

        //サンプルドキュメントをロード
        Presentation presentation = new Presentation();
        presentation.loadFromFile("test.pptx");

        //ドキュメントプロパティMarkAsFinalをtrueに設定します
        presentation.getDocumentProperty().set("_MarkAsFinal", true);

        //文書を保存します
        presentation.saveToFile("MarkasFinal.pptx", FileFormat.PPTX_2010);
    }
}

ja.png

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

AWS SDK for Java 1.11.x と 2.x

WindowsローカルのAWS SDKでJavaプログラムを動かすまで の続き

Amazon AWS SDK 1.x と 2.x は共存できるようだ、というメモ。

pom.xml
        <aws-java-sdk.version>1.11.699</aws-java-sdk.version>
        <aws-java-sdk2.version>2.13.53</aws-java-sdk2.version>

        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-bom</artifactId>
            <version>${aws-java-sdk.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>${aws-java-sdk2.version}</version>
             <type>pom</type>
            <scope>import</scope>
        </dependency>

AWS SDK for Java のドキュメント

SDK for Java 1.11.x と 2.x の相違点

Using the SDK for Java 1.x and 2.x Side by Side (SDK for Java 1.x and 2.xを共用する)

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

<JavaScript>特定の文字列で区切られたファイル名をそのファイル名の一部で一括変換するクイズ

背景

環境

  • Macでやってます
$ java -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.20.0, JRE 14 Mac OS X amd64-64-Bit Compressed References 20200416_40 (JIT enabled, AOT enabled)
OpenJ9   - 05fa2d361
OMR      - d4365f371
JCL      - 5757187cae based on jdk-14.0.1+7)

事前準備

  • 同じように元記事はabc_test_ok_1_this.csv・・・abc_test_ok_100_this.csvのファイルを用意するところからワンライナーでやってるのでそこから
echo 'IntStream.range(1,101).forEach(i -> {try {Files.createFile(Paths.get("abc_test_ok_"+String.valueOf(i) + "_this.csv"),java.nio.file.attribute.PosixFilePermissions.asFileAttribute(java.nio.file.attribute.PosixFilePermissions.fromString("r--r--r--")));} catch (Exception e) {throw new RuntimeException(e);}})' | jshell -
  • Javaはコンパイルが必要だからワンライナーは向かないと思われがちですが、最近のJavaではJShellというREPLツールが使えるのでできます。
  • echoで書きつつ、パイプでjshellコマンドに渡しています。最後の - は余計な表示をしないようにしています。
  • import文はものによって省略できます。
  • パーミッションの書き方などがUnixベースなので、Windowsだと動かないと思います。
  • ループはFor文で書いたら負けかなと思いましたので、特に意味なくStremからのforEachつかいました。
  • 改行込みで書くと以下になります。例外処理とかが面倒な感じですね。
        IntStream.range(1,101).forEach(i -> {
            try {
                Files.createFile(Paths.get("abc_test_ok_"+String.valueOf(i) + "_this.csv"),
                    PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("r--r--r--")));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

ファイルのリネーム

  • ここからが本題です。
  • といいつつ、いきなりで以下で動きました。
echo 'Path path1 = Paths.get(".");try{Files.list(path1).forEach(new Consumer<Path>() {public void accept(Path from) {String intStr = from.toString().replaceAll("[^0-9]", "");try {Files.move(from, Paths.get(".",intStr + ".csv"));} catch (IOException e) {e.printStackTrace();}}});}catch (IOException e) {e.printStackTrace();}'| jshell -
  • 正規表現で数字だけ取ってファイル名にリネームしているので、フォルダ内に余計なファイルがあると動かない可能性があります。
  • 現在のディレクトリのPathを取った後に、listで直下のものをがさっと取ってforEachで回しつつ、moveでファイルリネームしています。
  • 改行込みで書くと以下になります。やはり例外処理とかが面倒な感じですね。(例外処理の後処理が上と違うのは特に意味がないです)
    Path path1 = Paths.get(".");
        try{
            Files.list(path1).forEach(new Consumer<Path>() {
            public void accept(Path from) {
                String intStr = from.toString().replaceAll("[^0-9]", "");
                try {
                    Files.move(from, Paths.get(".",intStr + ".csv"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

おわりに

  • 久しぶりにJshell触りました。さくっと動くのは良いですね。
  • ファイル処理は前回の人と同じく、普段あまりやらないので調べながらやったので、よりよいやり方あればご指摘ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<Java>特定の文字列で区切られたファイル名をそのファイル名の一部で一括変換するクイズ

背景

環境

  • Macでやってます
$ java -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.20.0, JRE 14 Mac OS X amd64-64-Bit Compressed References 20200416_40 (JIT enabled, AOT enabled)
OpenJ9   - 05fa2d361
OMR      - d4365f371
JCL      - 5757187cae based on jdk-14.0.1+7)

事前準備

  • 同じように元記事はabc_test_ok_1_this.csv・・・abc_test_ok_100_this.csvのファイルを用意するところからワンライナーでやってるのでそこから
echo 'IntStream.range(1,101).forEach(i -> {try {Files.createFile(Paths.get("abc_test_ok_"+String.valueOf(i) + "_this.csv"),java.nio.file.attribute.PosixFilePermissions.asFileAttribute(java.nio.file.attribute.PosixFilePermissions.fromString("r--r--r--")));} catch (Exception e) {throw new RuntimeException(e);}})' | jshell -
  • Javaはコンパイルが必要だからワンライナーは向かないと思われがちですが、最近のJavaではJShellというREPLツールが使えるのでできます。
  • echoで書きつつ、パイプでjshellコマンドに渡しています。最後の - は余計な表示をしないようにしています。
  • import文はものによって省略できます。
  • パーミッションの書き方などがUnixベースなので、Windowsだと動かないと思います。
  • ループはFor文で書いたら負けかなと思いましたので、特に意味なくStreamからのforEachつかいました。
  • 改行込みで書くと以下になります。例外処理とかが面倒な感じですね。
        IntStream.range(1,101).forEach(i -> {
            try {
                Files.createFile(Paths.get("abc_test_ok_"+String.valueOf(i) + "_this.csv"),
                    PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("r--r--r--")));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

ファイルのリネーム

  • ここからが本題です。
  • といいつつ、いきなりで以下で動きました。
echo 'Path path1 = Paths.get(".");try{Files.list(path1).forEach(from -> {String intStr = from.toString().replaceAll("[^0-9]", "");try {Files.move(from, Paths.get(".",intStr + ".csv"));} catch (IOException e) {e.printStackTrace();}});} catch(IOException e) {e.printStackTrace();}' | jshell -
  • 正規表現で数字だけ取ってファイル名にリネームしているので、フォルダ内に余計なファイルがあると動かない可能性があります。
  • 現在のディレクトリのPathを取った後に、listで直下のものをがさっと取ってforEachで回しつつ、moveでファイルリネームしています。
  • 改行込みで書くと以下になります。やはり例外処理とかが面倒な感じですね。(例外処理の後処理が上と違うのは特に意味がないです)
  • (7/15追記)ラムダ式を使う形にリファクタリングしました。
        Path path1 = Paths.get(".");
        try{
            Files.list(path1).forEach(from -> {
                String intStr = from.toString().replaceAll("[^0-9]", "");
                try {
                    Files.move(from, Paths.get(".",intStr + ".csv"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } catch(IOException e) {e.printStackTrace();}
    }

おわりに

  • 久しぶりにJShell触りました。さくっと動くのは良いですね。
  • ファイル処理は前回の人と同じく、普段あまりやらないので調べながらやったので、よりよいやり方あればご指摘ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む