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

SpringのFilterでHttpResponseのBody部を取得する

いい感じの凡例が見つからなかったので作ってみました。
STSのデバック環境下で動作確認済みです。

LoggingFilter.java
@Component
public class LoggingFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class.getName());

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponseWrapperEx httpResponseEx = new HttpServletResponseWrapperEx(httpResponse);   
        chain.doFilter(request, httpResponseEx);

        String responseBody = httpResponseEx.getBody();     
        log.debug(responseBody);
    }
}

HttpServletResponseWrapperEx.java
public class HttpServletResponseWrapperEx extends HttpServletResponseWrapper {
    private ServletResponse response;
    private PrintWriterEx writerEx;
    private ServletOutputStreamEx outputStreamEx;

    public HttpServletResponseWrapperEx(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {

        ServletOutputStream stream = this.response.getOutputStream();

        if (null == this.outputStreamEx) {
            this.outputStreamEx = new ServletOutputStreamEx(stream);
        }

        return this.outputStreamEx;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        PrintWriter writer = this.response.getWriter();
        this.writerEx = new PrintWriterEx(writer);
        return this.writerEx;
    }

    public String getBody() {
        String result = "";

        if (null != this.outputStreamEx) {
            result = this.outputStreamEx.getBody();
        }
        else if (null != this.writerEx) {
            result = this.writerEx.getBody();
        }

        return result;
    }
}
ServletOutputStreamEx.java
public class ServletOutputStreamEx extends ServletOutputStream {
    private StringBuilder body = new StringBuilder();
    private ServletOutputStream target;

    public ServletOutputStreamEx(ServletOutputStream target) {
        this.target = target;
    }

    @Override
    public boolean isReady() {
        return this.target.isReady();
    }

    @Override
    public void setWriteListener(WriteListener listener) {
        this.target.setWriteListener(listener);     
    }

    @Override
    public void write(int b) throws IOException {
        this.target.write(b);
        byte [] value = new byte[] {(byte)b};
        String temp = new String(value);
        body.append(temp);      
    }

    public String getBody() {
        return this.body.toString();
    }
}
PrintWriterEx.java
public class PrintWriterEx extends PrintWriter {

    private StringBuilder body = new StringBuilder();

    public PrintWriterEx(Writer out) {
        super(out);
    }

    @Override
    public void write(int c) {
        super.write(c);
        this.body.append((char)c);
    }

    @Override
    public void write(char[] chars, int offset, int length) {
        super.write(chars, offset, length);
        this.body.append(chars, offset, length);
    }

    public void write(String string, int offset, int length) {
        super.write(string, offset, length);
        this.body.append(string, offset, length);
    }

    public String getBody() {
        return this.body.toString();
    }
}

適当にサンプルを作って動作を確認。
下記サンプルの場合、PrintWriterExが動作した。

HelloController.java
@Controller
public class HeloController{
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(Model model) {
        model.addAttribute("message", "Hello Springboot");
        return "index";
    }
}

下記サンプルの場合、GET/POSTのどちらの場合もServletOutputStreamExが動作した。

GoodbyeController.java
@RestController
public class GoodbyeController {

    @RequestMapping(path = "/goodbye", method = RequestMethod.GET)
    public String goodbye() {
        return "good bye";
    }

    @RequestMapping(path = "/goodbyebye", method = RequestMethod.POST)
    public String goodbyebye() {
        return "{ message: \"goodbye\" }";
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Cloud FunctionでAmazon API Gatewayのプロキシ統合なLambda関数をサクッと書いてみる

はじめに

Lambda関数をJavaで実装するのは面倒くさい。
Spring Cloud Functionというのを使うと、その辺の煩雑さを良い感じにフレームワークが吸収してくれるらしい。
ということで、本当に良い感じに吸収されてサクッと書けるのかを試してみた。

ちなみに、Spring Cloud Functionはプロキシ統合していないサンプルは多くあれど、統合版はなかなか見つからなかったので、その検討の一助にもなれば。

構成

以下のようなMavenプロジェクトを作成する。

SpringCloudFunctionTest
├── pom.xml
├── serverless.yml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── springcloudfunctiontest
    │   │           ├── AWSLambdaHandler.java
    │   │           ├── Hello.java
    │   │           └── SpringCloudFunctionExampleApplication.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── springcloudfunctiontest
                    └── SpringCloudFunctionExampleApplicationTests.java

pom.xmlは↓こんな感じにしておく。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.techprimers.serverless</groupId>
    <artifactId>spring-cloud-function-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-function-test</name>
    <description>Demo project for Spring Boot with Spring Cloud Function</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-adapter-aws</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
                </dependency>
            </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.boot.experimental</groupId>
                        <artifactId>spring-boot-thin-layout</artifactId>
                        <version>1.0.10.RELEASE</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <shadedArtifactAttached>true</shadedArtifactAttached>
                    <shadedClassifierName>aws</shadedClassifierName>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

ハンドラのコード

通常のSpring Bootなアプリ同様、SpringApplication.runするクラスを作る。

SpringCloudFunctionExampleApplication.java
package com.springcloudfunctiontest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCloudFunctionExampleApplication {

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

}

で、Lambdaのイベントハンドラを作る。
正直、このクラスが空っぽで良い理由がよく分からない……。
API Gatewayのバックエンドに置くので、InputとOutputにはそれぞれAPIGatewayProxyRequestEventAPIGatewayProxyResponseEventを設定する。

AWSLambdaHandler.java
package com.springcloudfunctiontest;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler;

public class AWSLambdaHandler extends SpringBootRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
}

最後に、ビジネスロジックのクラスを作成する。
APIGatewayProxyRequestEventAPIGatewayProxyResponseEvent`の仕様はjavadocを確認しよう。

しかし、Spring Cloud Functionはマルチクラウドでの互換性がウリなのに、こんなインタフェースにしたら完全にAWS特化な実装になっちゃうよなぁ……。

最後のsetBodyのJSONの扱いが雑なのは気にしないように。

Hello.java
package com.springcloudfunctiontest;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import org.springframework.stereotype.Component;

import java.util.function.Function;
import java.util.Map;

@Component
public class Hello implements Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
    @Override
    public APIGatewayProxyResponseEvent apply(APIGatewayProxyRequestEvent input) {
        Map<String, String> queryStringParameter = input.getQueryStringParameters();

        String id = queryStringParameter.get("id");
        String name = null;

        if( id.equals("11111") ) {
          name = "\"Taro\"";
        } else {
          name = "\"Nanashi\"";
        }

        APIGatewayProxyResponseEvent responseEvent = new APIGatewayProxyResponseEvent();
        responseEvent.setStatusCode(200);
        responseEvent.setBody("{\"name\":" + name + "}");
        return responseEvent;
    }
}

テストコード

テストコードも普通に書ける。

SpringCloudFunctionExampleApplicationTests.java
package com.springcloudfunctiontest;

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringCloudFunctionExampleApplicationTests {

    @Test
    public void HelloTest1() {
        Hello hello = new Hello();
        APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent();

        Map<String,String> queryStringParameter = new HashMap<>();
        queryStringParameter.put("id", "11111");
        request.setQueryStringParameters(queryStringParameter);

        APIGatewayProxyResponseEvent response = hello.apply(request);

        assertThat(response.getBody()).isEqualTo("{\"name\":\"Taro\"}");
    }

    @Test
    public void HelloTest2() {
        Hello hello = new Hello();
        APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent();

        Map<String,String> queryStringParameter = new HashMap<>();
        queryStringParameter.put("id", "22222");
        request.setQueryStringParameters(queryStringParameter);

        APIGatewayProxyResponseEvent response = hello.apply(request);

        assertThat(response.getBody()).isEqualTo("{\"name\":\"Nanashi\"}");
    }
}

デプロイ

手でいちいちS3にアップロードしたり、パイプラインのビルド待ちしたりするのも面倒臭いので、正式版のアプリでないからServerless Frameworkでテキトーにデプロイしよう。

serverless.yml
service: test

provider:
  name: aws
  runtime: java8
  timeout: 30

  region: ap-northeast-1

package:
  artifact: target/spring-cloud-function-test-0.0.1-SNAPSHOT-aws.jar

functions:
  hello:
    handler: org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler
    events:
      - http:
          path: testapi
          method: get
          integration: lambda-proxy
          request:
            template:
              application/json: '$input.json("$")'

これでデプロイすると、こんな感じでちゃんとRESTAPIが返されるぞ!

$ curl -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/testapi?id=11111; echo
{"name":"Taro"}

結論

うーん、Springフレームワークの恩恵を全然預かっていないので、普通のLambdaのイベントハンドラを作った時と手間としては変わらなかった気分。もっとフレームワークの機能をふんだんに使うようになったらありがたみがわかるのだろうか。

今回は、JavaでLambda関数を書くならやっぱりAPIGatewayProxyRequestEventを使うと実装が楽だね、ということと、Serverless Frameworkのデプロイがお手軽だね、と言うことは理解できた。

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

JAVAオブジェクトマッピングライブラリ

はじめに

Beanの変換に詰め替え作業は手動で実施するとコード量は増えてしまい、メンテナンス性は良くないです。
BeanUtils.copyPropertiesのような便利メソッドがありますが、ほかのマッピングライブラリをまとめてみます。

FasterXML ObjectMapper

https://github.com/FasterXML/jackson-databind

JSON関連する処理はこれを使うと便利です。

使い方

build.gradleに追加

build.gradle
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'

Beanクラス作成

package com.test.lombok;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class User {
    private String userId;

    private String userName;

    private String age;

    private String gender;
}

user.jsonサンプル作成

user.json
{
"userId": "user001",
"userName": "テスト 太郎",
"age": 20,
"gender": "famale"
}

テストコード

package com.test.lombok;

import java.io.File;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

public class TestMain {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        // ファイルパスで指定
        User user1 = mapper.readValue(
                new File("C:\\workspace_sts\\lombok\\src\\main\\java\\com\\test\\lombok\\user.json"), User.class);
        System.out.println(user1);

        // URL形式で指定
        User user2 = mapper.readValue(TestMain.class.getResource("user.json"), User.class);
        System.out.println(user2);

        // 文字列から変換
        User user3 = mapper.readValue(
                "{\"userId\":\"user001\", \"userName\": \"テスト 太郎2\", \"age\": 22, \"gender\": \"male\"}", User.class);
        System.out.println(user3);

        // オブジェクトから文字列に変換
        String jsonString = mapper.writeValueAsString(user3);
        System.out.println(jsonString);

        // 文字列からMapに変換
        Map<String, String> userMap = mapper.readValue(jsonString, Map.class);
        System.out.println(userMap);

    }

}

結果:

User(userId=user001, userName=テスト 太郎, age=20, gender=famale)
User(userId=user001, userName=テスト 太郎, age=20, gender=famale)
User(userId=user001, userName=テスト 太郎2, age=22, gender=male)
{"userId":"user001","userName":"テスト 太郎2","age":"22","gender":"male"}
{userId=user001, userName=テスト 太郎2, age=22, gender=male}

ModelMapper

http://modelmapper.org/

使い方

build.gradleに追加

build.gradle
    // https://mvnrepository.com/artifact/org.modelmapper/modelmapper
    compile group: 'org.modelmapper', name: 'modelmapper', version: '2.3.5'

元Beanクラス

package com.test.lombok;

import lombok.Data;

@Data
public class User {
    private String userId;

    private String userName;

    private String age;

    private String gender;

    private Address addr;
}

@Data
class Address {
    private String zipCode;
    private String address1;
    private String address2;
    private String address3;
}

DTOクラス

package com.test.lombok;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UserDTO {
    private String userId;

    private String userName;

    private String age;

    private String gender;

    private String addrZipCode;

    private String addrAddress1;

    private String addrAddress2;

    private String addrAddress3;
}

テストコード

package com.test.lombok;

import org.modelmapper.ModelMapper;

public class TestMain {

    public static void main(String[] args) throws Exception {
        ModelMapper modelMapper = new ModelMapper();

        User user = new User();
        user.setUserId("user001");
        user.setUserName("テスト");
        user.setAge("18");
        user.setGender("male");
        Address addr = new Address();
        addr.setZipCode("984-0031");
        addr.setAddress1("○○県");
        user.setAddr(addr );

        UserDTO userDTO = modelMapper.map(user, UserDTO.class);
        System.out.println(userDTO);

    }

}

結果:

UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=984-0031, addrAddress1=○○県, addrAddress2=null, addrAddress3=null)

明示的にマッピング(Explicit Mapping)

package com.test.lombok;

import org.modelmapper.ModelMapper;
import org.modelmapper.TypeMap;

public class TestMain {

    public static void main(String[] args) throws Exception {
        ModelMapper modelMapper = new ModelMapper();

        User user = new User();
        user.setUserId("user001");
        user.setUserName("テスト");
        user.setAge("18");
        user.setGender("male");
        Address addr = new Address();
        addr.setZipCode("984-0031");
        addr.setAddress1("○○県");
        user.setAddr(addr);

        TypeMap<User, UserDTO> typeMap = modelMapper.typeMap(User.class, UserDTO.class).addMappings(mapper -> {
            mapper.map(src -> src.getAddr().getZipCode(), UserDTO::setAddrZipCode);
            mapper.map(src -> src.getAddr().getAddress1(), UserDTO::setAddrAddress1);
        });

        System.out.println(typeMap.map(user));

    }

}

結果:

UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=984-0031, addrAddress1=○○県, addrAddress2=null, addrAddress3=null)

SpringBootのControllerにてFormのBeanから自分のDTOに変換するに使うと便利です。

MapStruct

https://mapstruct.org/

使い方

build.gradleに追加

build.gradle
plugins {
    id 'java'
    id 'net.ltgt.apt' version '0.20'
}

repositories {
    jcenter()
}

dependencies {
    // lombok
    compileOnly "org.projectlombok:lombok:1.18.12"
    annotationProcessor "org.projectlombok:lombok:1.18.12"

    // https://mvnrepository.com/artifact/org.mapstruct/mapstruct
    implementation  group: 'org.mapstruct', name: 'mapstruct', version: '1.3.1.Final'

    // https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor
    annotationProcessor group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.3.1.Final'

}

[compileJava, compileTestJava]*.options*.encoding= "UTF-8"

sourceSets {
    main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main"
}

Mapperインタフェース

package com.test.lombok;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "addr.zipCode", target = "addrZipCode")
    @Mapping(source = "addr.address1", target = "addrAddress1")
    @Mapping(source = "addr.address2", target = "addrAddress2")
    UserDTO userToUserDTO(User user);
}

上記定義し、ビルドするとbuild\generated\sources\annotationProcessor\java\main\com\test\lombokにUserMapperImpl.javaクラスが生成されます。

UserMapperImpl.java
package com.test.lombok;

import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-06-28T21:47:47+0900",
    comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDTO userToUserDTO(User user) {
        if ( user == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setAddrAddress2( userAddrAddress2( user ) );
        userDTO.setAddrAddress1( userAddrAddress1( user ) );
        userDTO.setAddrZipCode( userAddrZipCode( user ) );
        userDTO.setUserId( user.getUserId() );
        userDTO.setUserName( user.getUserName() );
        userDTO.setAge( user.getAge() );
        userDTO.setGender( user.getGender() );

        return userDTO;
    }

    private String userAddrAddress2(User user) {
        if ( user == null ) {
            return null;
        }
        Address addr = user.getAddr();
        if ( addr == null ) {
            return null;
        }
        String address2 = addr.getAddress2();
        if ( address2 == null ) {
            return null;
        }
        return address2;
    }

    private String userAddrAddress1(User user) {
        if ( user == null ) {
            return null;
        }
        Address addr = user.getAddr();
        if ( addr == null ) {
            return null;
        }
        String address1 = addr.getAddress1();
        if ( address1 == null ) {
            return null;
        }
        return address1;
    }

    private String userAddrZipCode(User user) {
        if ( user == null ) {
            return null;
        }
        Address addr = user.getAddr();
        if ( addr == null ) {
            return null;
        }
        String zipCode = addr.getZipCode();
        if ( zipCode == null ) {
            return null;
        }
        return zipCode;
    }
}

テストコード

package com.test.lombok;

public class TestMain {

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

        User user = new User();
        user.setUserId("user001");
        user.setUserName("テスト");
        user.setAge("18");
        user.setGender("male");
        Address addr = new Address();
        addr.setZipCode("984-0031");
        addr.setAddress1("○○県");
        user.setAddr(addr);


        UserDTO userDTO = UserMapper.INSTANCE.userToUserDTO(user);

        System.out.println(userDTO);

    }
}

結果:

UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=984-0031, addrAddress1=○○県, addrAddress2=null, addrAddress3=null)

ClassNotFoundエラー

Exception in thread "main" java.lang.ExceptionInInitializerError
    at com.test.lombok.TestMain.main(TestMain.java:18)
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot find implementation for com.test.lombok.UserMapper
    at org.mapstruct.factory.Mappers.getMapper(Mappers.java:61)
    at com.test.lombok.UserMapper.<clinit>(UserMapper.java:9)
    ... 1 more
Caused by: java.lang.ClassNotFoundException: Cannot find implementation for com.test.lombok.UserMapper
    at org.mapstruct.factory.Mappers.getMapper(Mappers.java:75)
    at org.mapstruct.factory.Mappers.getMapper(Mappers.java:58)

build.gradleにsourceSetsを追加

build.gradle
sourceSets {
    main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main"
}

詳細Doc:https://mapstruct.org/documentation/stable/reference/html/

Dozer

https://github.com/DozerMapper/dozer

使い方

build.gradleに追加

build.gradle
// https://mvnrepository.com/artifact/com.github.dozermapper/dozer-core
compile group: 'com.github.dozermapper', name: 'dozer-core', version: '6.5.0'

テストコード

package com.test.lombok;

import org.modelmapper.ModelMapper;
import org.modelmapper.TypeMap;

import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;

public class TestMain {

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

        User user = new User();
        user.setUserId("user001");
        user.setUserName("テスト");
        user.setAge("18");
        user.setGender("male");
        Address addr = new Address();
        addr.setZipCode("984-0031");
        addr.setAddress1("○○県");
        user.setAddr(addr);

        Mapper mapper = DozerBeanMapperBuilder.buildDefault();
        UserDTO userDTO = mapper.map(user, UserDTO.class);

        System.out.println(userDTO);

    }
}

結果:

UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=null, addrAddress1=null, addrAddress2=null, addrAddress3=null)

同じ項目名はコピーされましたが、それ以外nullでした。

違う項目の場合はXML、API、アノテーションで定義して対応可能

違う項目名の場合は @Mapping("binaryData")のように設定で対応可能ですが、複雑のタイプの場合はXMLで設定する必要があります。

好きなライブラリを選択すればと思います。

以上

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

【Java】enumを使ってDBのステータスコードをコード値変換する

DBに登録されたステータスコードをJava側でコード値に変換するサンプルです。
これにより、ステータスコードの意味を定義するマスタを作成する必要がなくなります。
フレームワークはSpring Boot。

環境

  • OS:Windows10
  • IDE:Eclipse2020-03
  • Java:8
  • MySQL:5.7
  • Hibernate ORM:5.4
  • Spring Boot:2.3.1

概要

生徒テーブルに登録された生徒ステータスコード(整数値)をJava側でenumを用いて対応するコード値に変換します。

ER図

image.png

ステータスコードとコード値

ステータスコード コード値
1 在学中
2 休学中
3 卒業

パッケージ構成図

enum-sample
     |
    src/main/java
     |----jp.co.high_school
     |               |---- app
     |               |     |---- controller
     |               |     |---- response
     |               |
     |               |---- domain
     |               |      |---- service
     |               |      |---- repository
     |               |      |---- model
     |               |  
     |               |---- util
     |
    src/main/resources
     |----application.yml

ソースコード

1. DDL

生徒テーブル
CREATE TABLE `students` (
  `student_id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '生徒ID',
  `student_name` varchar(300) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '生徒名',
  `sex` tinyint(4) DEFAULT NULL COMMENT '性別',
  `school_year` tinyint(4) DEFAULT NULL COMMENT '学年',
  `class` tinyint(4) DEFAULT NULL COMMENT 'クラス',
  `student_status` tinyint(4) DEFAULT NULL COMMENT '生徒ステータス',
  PRIMARY KEY (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='生徒';

2. 定数クラス

定数クラスにenumを定義して使用します。
クラスとしてenumを作成してもよいのですが、数が多くなることを想定してクラス内に定義しました。
こうすることで1クラスファイルで複数のenumを管理することができます。

ConstUtil.java
package jp.co.test_shop.util;

import java.util.stream.Stream;

import lombok.Getter;

/**
 * 定数クラス
 * @author CHI-3
 * @Version 1.0
 */
public class ConstUtil {

        /**
         * StudentStatus
         * 生徒ステータス
         */
        @Getter
        public static enum StudentStatus {

            In(1, "在学中"),
            Rest(2, "休学中"),
            Out(3, "卒業");

            private StudentStatus(int key, String value) {
                this.key = key;
                this.value = value;
            }


            private int key;

            private String value;

            public static Stream<StudentStatus> stream(){
                return Stream.of(StudentStatus.values());
            }

        }


        // 今回は使用しない:1クラス内に複数enum定義できることの証明として記述
        /**
         * Grade
         * 成績
         */
        @Getter
        public static enum Grade{

            // 優、良、可は最低条件-sam、不可は最高条件
            Excellent(1, "優", 90),
            Good(2, "良", 75),
            Passing(3, "可", 60),
            Failing(4, "不可", 59);


            private Grade(int key, String value, int order) {
                this.key = key;
                this.value = value;
                this.order = order;
            }

            private int key;

            private String value;

            private int order;

        }

}

3. Entityクラス

今回は、EclipseのHibernate Toolsを用いて、DBより自動生成しました。

Student.java
package jp.co.test_shop.domain.model;

import java.io.Serializable;
import javax.persistence.*;


/**
 * The persistent class for the students database table.
 *
 */
@Entity
@Table(name="students")
@NamedQuery(name="Student.findAll", query="SELECT s FROM Student s")
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="student_id")
    private Integer studentId;

    @Column(name="class")
    private Byte class_;

    @Column(name="school_year")
    private Byte schoolYear;

    private Byte sex;

    @Column(name="student_name")
    private String studentName;

    @Column(name="student_status")
    private Byte studentStatus;

    //bi-directional one-to-one association to Grade
    @OneToOne(mappedBy="student")
    private Grade grade;

    public Student() {
    }

    public Integer getStudentId() {
        return this.studentId;
    }

    public void setStudentId(Integer studentId) {
        this.studentId = studentId;
    }

    public Byte getClass_() {
        return this.class_;
    }

    public void setClass_(Byte class_) {
        this.class_ = class_;
    }

    public Byte getSchoolYear() {
        return this.schoolYear;
    }

    public void setSchoolYear(Byte schoolYear) {
        this.schoolYear = schoolYear;
    }

    public Byte getSex() {
        return this.sex;
    }

    public void setSex(Byte sex) {
        this.sex = sex;
    }

    public String getStudentName() {
        return this.studentName;
    }

    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }

    public Byte getStudentStatus() {
        return this.studentStatus;
    }

    public void setStudentStatus(Byte studentStatus) {
        this.studentStatus = studentStatus;
    }

    public Grade getGrade() {
        return this.grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

}

4. Repositoryクラス

生徒IDの指定により、該当する生徒の情報を取得します。

StudentRepository.java
package jp.co.test_shop.domain.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import jp.co.test_shop.domain.model.Student;

/**
 * 生徒(students)テーブル操作用リポジトリクラス
 * @author CHI-3
 * @Version 1.0
 */
@Repository
public interface StudentRepository extends JpaRepository<Student, Integer>{

    /**
     * findByStudentIdメソッド
     * 生徒IDに対応する生徒を取得する
     * @param studentId
     * @return 生徒
     */
    public Student findByStudentId(Integer studentId);

}

5. Serviceクラス

取得した生徒の、生徒ステータスのコードを対応するステータスコードに変換して返します。

StudentInformationService.java
package jp.co.high_school.domain.service;

import org.springframework.stereotype.Service;

import jp.co.test_shop.domain.model.Student;
import jp.co.test_shop.domain.repository.StudentRepository;
import jp.co.test_shop.util.ConstUtil.StudentStatus;
import lombok.RequiredArgsConstructor;

/**
 * 生徒情報取得用サービスクラス
 * @author CHI-3
 * @version 1.0
 */
@Service
@RequiredArgsConstructor
public class StudentInformationService {

    private final StudentRepository studentRepository;

    /**
     * getStatusメソッド
     * 対象の生徒の生徒ステータス(コード値)を取得する
     * @param studentId 生徒ID
     * @return 生徒ステータス(コード値)
     */
    public String getStatus(Integer studentId) {

        Student student = studentRepository.findByStudentId(studentId);
        String status = null;

        // ステータスコード → 対応するコード値
        for(StudentStatus ss:StudentStatus.values()){
            if(ss.getKey() == student.getStudentStatus()) {
                status = ss.getValue();
                break;
            }
        }

        return status;

    }

}

6. Controllerクラス

Serviceクラスで取得したコード値を整形して返します。

StudentInformationController.java
package jp.co.high_school.app.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import jp.co.test_shop.app.response.StudentStatusResponse;
import jp.co.test_shop.domain.service.StudentInformationService;
import lombok.RequiredArgsConstructor;

/**
 * 生徒情報取得用コントローラークラス
 * @author CHI-3
 * @Version 1.0
 */
@RestController
@RequiredArgsConstructor
public class StudentInformationController {

    private final StudentInformationService studenInformationService;

    /**
     * getStatusメソッド
     * 生徒ステータスを取得する
     * @param studentId 生徒ID
     * @return レスポンスエンティティ(ステータスのコード値+HTTPステータス)
     */
    @GetMapping("/student-information/{studentId}")
    public ResponseEntity<StudentStatusResponse> getStatus(@PathVariable("studentId") Integer studentId){
        String status = studenInformationService.getStatus(studentId);
        StudentStatusResponse studentStatusReponse = StudentStatusResponse.builder().status(status).build();
        return new ResponseEntity<>(studentStatusReponse, HttpStatus.OK);
    }

}

7. Responseクラス

StudentStatusResponse.java
package jp.co.test_shop.app.response;

import lombok.Builder;
import lombok.Getter;

/**
 * 生徒ステータスレスポンスクラス
 * 生徒ステータスの成形に使用
 * @author CHI-3
 * @Version 1.0
 */
@Getter
@Builder
public class StudentStatusResponse {

    private String status;

}

動作確認

今回はローカル環境で動作確認を行います。

生徒(students)テーブル

student_id student_name sex school_year class student_status
1 在学中 1 1 2 1

生徒テーブルが上記の内容の場合、API(Advanced Rest Clientを使用)を叩くと以下のような結果が返ります。
きちんと、ステータスコード(1)に対応するコード値(在学中)がとれていますね。

メソッド GET
URL http://localhost:8080/student-information/1

image.png

終わりに

enumを用いれば、マスタ(DB)や定数(Java)の管理がかなり楽になりそう。
うまく使っていきたいところです。

参考

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

今更Spring framework+JettyでHelloWorld Webアプリを作る

タイトル通りですが、10年ぶりにJavaを仕事で触る機会ができたので、今のうちに自習します。
稼働中の既存システムの勉強なので、最新の技術スタックではなく、ちょっと古めの組み合わせです。

Webアプリはデプロイせず、ローカルで実行するだけ。

使うツールと筆者の知識レベル。

  • Mac book Pro
    • 使い始めて半年くらい。私生活はほぼWindows。
  • Java
    • 約10年ぶりに触る。3年くらい触ってた。当時はちょうどJava8が発表されたくらいか。
  • Maven
    • 大昔にちょっとだけ触ったことがある。当時のメインはAntだった。今はGradleの方が主流?
  • Spring framework
    • 名前は知ってる。当時はStrutsも多かったが、今はSpring bootなのだろう。
  • IntelliJ IDEA Community
    • 名前は知ってる。当時はEclipseしか使っていなかった。
  • Jetty Server
    • JavaはTomcat以外で使ったことがなかった

環境準備

Javaは以前に入れてあったので、そのまま使う。大人の事情で14でも11でもなく、Java8。

$ java -version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

とりあえずmacなので、brewでMavenを入れる。

$ brew install maven
()
$ mvn -v
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
()
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.14.6", arch: "x86_64", family: "mac"

IntelliJのインストール

IntelliJ IDEA Communityもインストール。GUIで入れたので方法は省略。

好みに応じて設定を変える。
ここが参考になった。 https://www.karakaram.com/intellij-idea-always-do-things/

自分は基本はそのままで使うスタイルなのだけれど、精神衛生上よろしくないので、Keymapだけ少しいじった(特にC-h,C-d)。

プロジェクト作成

とりあえずGitHubに空のプロジェクトを作って、それをcloneしておく。ここが作業ディレクトリ。

$ git clone https://github.com/sekikatsu36/spring-sample.git
$ cd spring-sample
$ git config --local user.name "sekikatsu36"
$ git config --local user.email "(略)"

IntelliJでMavenの新規プロジェクトを作成。特に設定は変更せず、デフォルトのまま。

スクリーンショット 2020-06-27 13.29.07.png

スクリーンショット 2020-06-27 13.36.29.png

これで初期状態が完成。

$ ls -a
.           .DS_Store       .gitignore      pom.xml         src
..          .git            .idea           spring-sample.iml

spring frameworkの設定

Webにはspring bootの記事が多く、spring frameworkは少なめ。
とはいえ、今はspring frameworkが必要なので、使えるようにするためにpom.xmlを修正。

これが完成形。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.api</groupId>
    <artifactId>spring-sample</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!--  pom.xml内でバージョンを変数的に扱えるようにする -->
        <spring.version>5.2.5.RELEASE</spring.version>
        <jetty.version>9.4.11.v20180605</jetty.version>
    </properties>

    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- jetty -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.4.14.v20181114</version>
            </plugin>
        </plugins>
    </build>
</project>

最初の段階では、propertiesdependenciesだけ書いてた。それ以外の点については後で追加説明する。

参照でエラーが出た場合

自分の環境の場合、org.springframework.spring-webmvc:5.2.5.RELEASEが見つからないというエラーが出た。
リポジトリには存在している。

以下を実行

mvn clean install

結果

  • ログに Downloaded from central: https://repo.maven.apache.org/maven2/org/springframework/spring-webmvc/5.2.5.RELEASE/spring-webmvc-5.2.5.RELEASE.jar が表示された
  • /Users/(ユーザ名)/.m2/repository/org/springframework/spring-webmvc/5.2.5.RELEASE が追加された
  • IntelliJ ではエラーが表示されたままだったが、Mavenのリロードをしたらエラーが消えた

ソースを書く

基本的に https://qiita.com/kasa_le/items/6aaf17823db67c951fb0 の通りにやればいい。
ただし、こちらの記事はIntelliJ IDEA Ultimateなので、 IDEA CommunityではViewの作成以降はこのまま動かない。

とりあえず、コントローラの作成とviewフォルダの作成まではこの通りに終わらせる。
以下ができている状態。

/src/main
  /java/org/example/controllers
    IndexController.java
  /webapp/WEB-INF
    /views
    web.xml
    dispatcher-servlet.xml

JSPファイルを作る

Communityエディションの場合、Springサポートがないため、ファイルの新規作成でJSP/JSPXが出てこない様子。 https://www.jetbrains.com/ja-jp/idea/features/editions_comparison_matrix.html

なので、普通にFileを選んで、全部自作する。

index.jsp
<%@ page import="org.example.controllers.IndexController" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Spring Test Page</title>
    </head>
    <body>
        <p>${someAttribute}</p>
    </body>
</html>

Jettyで起動する

大人の都合でTomcatではなくJettyを使う。
IntelliJ IDEAのUltimate(有償版)であれば、Tomcatがデフォルトで組み込まれているらしい(?)が、Communityなのでどのみち使えない。
Jettyは事前にインストールしておく必要がないので楽。

http://zetcode.com/spring/jetty/ を参考に、pom.xmlに以下を足した。

   <packaging>war</packaging>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.4.14.v20181114</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

この状態で

mvn jetty:run

を実行し、http://0.0.0.0:8080にアクセスすると、無事にHelloWorldが表示される。

スクリーンショット 2020-06-28 19.37.48.png

maven-compiler-pluginでソースとターゲットのバージョンを指定しない場合5になるらしく、エラーが起きてしまう。 http://orionb312.hatenablog.com/entry/2018/03/06/233734

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project spring-sample: Compilation failure: Compilation failure: 
[ERROR] ソース・オプション5は現在サポートされていません。7以降を使用してください。
[ERROR] ターゲット・オプション5は現在サポートされていません。7以降を使用してください。

おわり

出来上がったものは以下。
https://github.com/sekikatsu36/spring-sample

とりあえず、Springが動くところは確認できた。
もうちょっとやりたいところ。

  • WebPageではないAPIにしたい(Jerseyを使う?)
  • デバッグできるようにしたい(今はブレークポイントで止まらない)
  • PaaSやSaaSを使いたい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HerokuにBasic認証の導入【Spring Framework】

成果物

スクリーンショット 2020-06-28 17.22.18.png

準備

Heroku公式ドキュメント

↑のHeroku公式ドキュメントにしたがって、Herokuデプロイの準備をすること。

pom.xml
・・・
<packaging>war</packaging>
・・・
<dependencies>
・・・
</dependencies>
<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals><goal>copy</goal></goals>
                    <configuration>
                        <artifactItems>
                            <artifactItem>
                                <groupId>com.heroku</groupId>
                                <artifactId>webapp-runner</artifactId>
                                <version>9.0.30.0</version>
                                <destFileName>webapp-runner.jar</destFileName>
                            </artifactItem>
                        </artifactItems>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

※プロジェクトがwarでコンパイルされていること

Procfile
web: java $JAVA_OPTS -jar target/dependency/webapp-runner.jar --port $PORT --enable-basic-auth --basic-auth-user [ユーザー名] --basic-auth-pw [パスワード] target/*.war
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerをインストールしてJava実行環境をつくる

新しいパソコンでJava実行環境を構築するにあたり、せっかくならDockerでやってみようと思った備忘録

環境

  • Windows 10 Home (64bit)

Dockerのインストール

  1. VirtualBoxとかKitematicとか全部入りの Docker Toolbox [link] をダウンロード
  2. Setup画面はデフォルト設定でOK、最後の画面で確認し[Install]をクリック
  3. デスクトップに作成されたDocker Quickstart Terminalのショートカット(>?みたいなやつ)を起動させると色々表示されるので待つ
  4. 画像のようにクジラの絵が出たら完了2020-06-27 (1).png
  5. Dockerターミナルにdocker run hello-worldと入力しEnterを押すと、"hello-world" という名前のDockerイメージをダウンロードしてくれる2020-06-27 (2).png

Dockerイメージのダウンロード

そのままターミナルでコマンド入力してもできるけど、今回は視覚的にわかりやすいKitematicを使ってDockerイメージをダウンロードしてみる

  1. デスクトップのショートカットから Kitematic を起動
  2. "Setup Initialization" の画面で [USE VIRTUALBOX] を選択、DockerHubアカウントは作成しなくてもOK
  3. セットアップが完了して表示された画面の検索ボックスにキーワードを入力(今回はjava)
  4. 出てきた候補から今回は "openjdk" を選び、[CREATE] ボタンを押す2020-06-27 (4).png
  5. 左側のContainers欄に "openjdk" が表示され、(先程ターミナルからダウンロードした)"hello-world" と同じように灰色アイコンの表示になったらダウンロード完了

"Connecting to Docker Hub" と表示されダウンロードが進まない場合
  • 上の画像左下の DOCKER CLI をクリックしてPowerShellを起動
  • docker pull scrapinghub/splashと入力してEnter
  • しばらく待つと諸々のダウンロードが完了しているので、この状態でKitematicに戻るとダウンロードが完了してた
  • ダメなら一度Dockerイメージのダウンロードを中止して、再度 [CREATE] ボタンを押してダウンロードを試みる

Dockerイメージからコンテナをつくる

これも Kitematic でやろうと思ったら、何回 [START] をクリックしてもすぐストップしてしまったので Docker のターミナルに戻って作業

  1. docker run -it --name testcont openjdk:latestを実行してコンテナを起動状態で作成("testcont" はコンテナの名前)
  2. 完成!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】DataBindingのvariableはcamelCaseで書くのが無難

<variable name="hoge_text" type="String">

より、

<variable name="hogeText" type="String">

のが困らないと思うっすという話。プチハマったので。

具体例

「被<include>ファイルでsnake_caseのvariableを使おうとしたらアクセスできなかった」

書いてみるxml

child.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="value_text"
            type="String" />
    </data>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{value_text}"/>
</layout>
parent.xml
<親要素省略>
    <include
        layout="@layout/child"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:value_text="@{someVariable}"/>
</親要素省略>

生成されるコード

ChildBindingImpl.java
    public void setValueText(@Nullable java.lang.String ValueText) {
        this.mValueText = ValueText;
        synchronized(this) {
            mDirtyFlags |= 0x2L;
        }
        notifyPropertyChanged(BR.value_text);
        super.requestRebind();
    }
ParentBindingImpl.java
    @Nullable
    private final app.package.name.databinding.ChildBinding mboundView;
// ~~~ 略 ~~~
            this.mboundView.setValue_text(someVariableGetValue); // <- お前ー!!

エラー内容

エラー: シンボルを見つけられません
            this.mboundView3.setValue_text(someVariableGetValue);
                            ^
  シンボル:   メソッド setValue_text(String)

というわけで、app:snake_case属性により生成される関数名が、
<variable name="snake_case">により生成される関数名と異なるためビルドに失敗します。

「じゃあapp:camelCase&<variable name="snake_case">ならどうよ」という気もしますが、
その場合はxmlファイル側で名前の解決が出来ず、BindingImplの生成前にエラーになるようです。書く側としてもxml内でその切り替えはしんどいっす。(自分の場合)

対処

冒頭に書いた通りです。

child.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="valueText"
            type="String" />
    </data>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{valueText}"/>
</layout>
parent.xml
<親要素省略>
    <include
        layout="@layout/child"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:valueText="@{someVariable}"/>
</親要素省略>

定義からcamelCaseにしましょう。

おまけ

あんまり明言はされてないと思うんですが、
公式のサンプルでも2単語の変数名をさがしました。camelCaseでした。(userList

    <data>
        <import type="com.example.User"/>
        <import type="java.util.List"/>
        <variable name="user" type="User"/>
        <variable name="userList" type="List&lt;User>"/>
    </data>

このあたり(じゃあresourceのidもcamelCaseにしとく? とか)はGoogle提供のコードでも一貫されていない感があるので、昔から小さく話題になることみたいですね。

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

Java HTTP Client API のタイムアウト設定

概要

  • Java 11 から正式導入された HTTP Client API (java.net.http パッケージ) のタイムアウト設定について接続時の動作を検証する
  • HTTP Client API では設定できるタイムアウト値が2種類ある
    • 接続タイムアウト: HttpClient.Builder.connectTimeout
    • リクエストタイムアウト: HttpRequest.Builder.timeout

検証結果からの考察

  • 接続タイムアウト HttpClient.Builder.connectTimeout には接続タイムアウトの許容時間を指定する
  • リクエストタイムアウト HttpRequest.Builder.timeout にはリクエスト全体 (接続タイムアウトや読み取りタイムアウトを含めたもの) の許容時間を指定する

公式ドキュメントによる情報

HttpClient.Builder.connectTimeout

HttpClient.Builder (Java SE 14 & JDK 14)

HttpClient.Builder connectTimeout​(Duration duration)

このクライアントの接続タイムアウト期間を設定します。
新しい接続を確立する必要がある場合に、指定された duration内に接続を確立できないと、HttpClient::sendはHttpConnectTimeoutExceptionをスローし、HttpClient::sendAsyncはHttpConnectTimeoutExceptionを使用して例外的に完了します。 たとえば、接続を前のリクエストから再利用できる場合など、新しい接続を確立する必要がない場合、このタイムアウト期間は影響しません。

パラメータ:
duration - 基礎となる接続の確立を許可する期間

戻り値:
このビルダー

例外:
IllegalArgumentException - 期間が非正の場合

HttpClient.Builder (Java SE 14 & JDK 14)

HttpClient.Builder connectTimeout​(Duration duration)

Sets the connect timeout duration for this client.
In the case where a new connection needs to be established, if the connection cannot be established within the given duration, then HttpClient::send throws an HttpConnectTimeoutException, or HttpClient::sendAsync completes exceptionally with an HttpConnectTimeoutException. If a new connection does not need to be established, for example if a connection can be reused from a previous request, then this timeout duration has no effect.

Parameters:
duration - the duration to allow the underlying connection to be established

Returns:
this builder

Throws:
IllegalArgumentException - if the duration is non-positive

HttpRequest.Builder.timeout

HttpRequest.Builder (Java SE 14 & JDK 14)

HttpRequest.Builder timeout​(Duration duration)

このリクエストのタイムアウトを設定します。 指定されたタイムアウト内にレスポンスを受信しなかった場合、HttpClient::sendまたはHttpClient::sendAsyncからHttpTimeoutExceptionが例外的にHttpTimeoutExceptionを使ってスローされます。 タイムアウトを設定しない場合の影響は、無限期間(永続的ブロック)の設定と同じです。

パラメータ:
duration - タイムアウト期間

戻り値:
このビルダー

例外:
IllegalArgumentException - 期間が非正の場合

HttpRequest.Builder (Java SE 14 & JDK 14)

HttpRequest.Builder timeout​(Duration duration)

Sets a timeout for this request. If the response is not received within the specified timeout then an HttpTimeoutException is thrown from HttpClient::send or HttpClient::sendAsync completes exceptionally with an HttpTimeoutException. The effect of not setting a timeout is the same as setting an infinite Duration, i.e. block forever.

Parameters:
duration - the timeout duration

Returns:
this builder

Throws:
IllegalArgumentException - if the duration is non-positive

挙動を検証するソースコード

HttpClientTimeoutTest.java というファイル名で以下の内容を保存する。

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.LocalDateTime;

public class HttpClientTimeoutTest {

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

    // 接続タイムアウトのテスト (存在しないサーバ、接続タイムアウト3秒)
    testTimeout("http://127.0.0.2:8000/", Duration.ofSeconds(3), null);
    Thread.sleep(1000L);

    // 読み取りタイムアウトのテスト (レスポンスが遅いサーバ、リクエストタイムアウト3秒)
    testTimeout("http://127.0.0.1:8000/", null, Duration.ofSeconds(3));
    Thread.sleep(1000L);

    // リクエストタイムアウトの影響範囲のテスト (存在しないサーバ、接続タイムアウト5秒、リクエストタイムアウト3秒)
    testTimeout("http://127.0.0.2:8000/", Duration.ofSeconds(5), Duration.ofSeconds(3));
    Thread.sleep(1000L);
  }

  /**
   * タイムアウトのテスト。
   * @param url URL
   * @param connectTimeout HttpClient.Builder.connectTimeout にセットする値
   * @param requestTimeout HttpRequest.Builder.timeout にセットする値
   */
  private static void testTimeout(String url, Duration connectTimeout, Duration requestTimeout) {

    try {
      // パラメータを確認
      System.out.println("URL: " + url);
      System.out.println("ConnectTimeout: " + connectTimeout);
      System.out.println("RequestTimeout: " + requestTimeout);

      // HttpClient オブジェクトを構築
      HttpClient.Builder hcb = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1);
      if (connectTimeout != null) {
        hcb.connectTimeout(connectTimeout); // 接続タイムアウトを設定
      }
      HttpClient client = hcb.build();

      // HttpRequest オブジェクトを構築
      HttpRequest.Builder hrb = HttpRequest.newBuilder(new URI(url)).GET();
      if (requestTimeout != null) {
        hrb.timeout(requestTimeout); // リクエストタイムアウトを設定
      }
      HttpRequest request = hrb.build();

      // タイムアウト設定を確認
      System.out.println("HttpClient.connectTimeout: " + client.connectTimeout());
      System.out.println("HttpRequest.timeout: " + request.timeout());

      // HTTP リクエストを投げる
      System.out.println("HttpClient.send begins: " + LocalDateTime.now());
      HttpResponse<String> res = client.send(request, HttpResponse.BodyHandlers.ofString());

    } catch (Exception e) {
      System.out.println("HttpClient.send failed: " + LocalDateTime.now());
      e.printStackTrace();
    }
  }
}

レスポンスが遅い HTTP サーバを用意

リクエストが来てから10秒後にレスポンスを返すような HTTP サーバを Ruby で書く。

server.rb というファイル名で以下の内容を保存する。

require 'webrick'

server = WEBrick::HTTPServer.new({
  :Port => 8000,
  :HTTPVersion => WEBrick::HTTPVersion.new('1.1')
})

server.mount_proc('/') do |req, res|
  sleep(10) # 10秒スリープ
  res.status = 200
  res.body = ''
end

Signal.trap('INT'){server.shutdown}
server.start

ruby コマンドで HTTP サーバのプログラムを実行する。

$ ruby server.rb
[2020-06-28 11:03:15] INFO  WEBrick 1.6.0
[2020-06-28 11:03:15] INFO  ruby 2.7.1 (2020-03-31) [x86_64-darwin19]
[2020-06-28 11:03:15] INFO  WEBrick::HTTPServer#start: pid=47723 port=8000

実行結果

java コマンドで HttpClientTimeoutTest.java を実行する。

Java 11 移行であれば javac によるコンパイル無しで実行できる。
参考: JEP 330: Launch Single-File Source-Code Programs

今回の検証環境: macOS Catalina + Java 14 (AdoptOpenJDK 14.0.1+7)

$ java HttpClientTimeoutTest.java
URL: http://127.0.0.2:8000/
ConnectTimeout: PT3S
RequestTimeout: null
HttpClient.connectTimeout: Optional[PT3S]
HttpRequest.timeout: Optional.empty
HttpClient.send begins: 2020-06-28T11:07:13.619689
HttpClient.send failed: 2020-06-28T11:07:16.703287
java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:557)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
    at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59)
    at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:13)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.MultiExchange.toTimeoutException(MultiExchange.java:508)
    at java.net.http/jdk.internal.net.http.MultiExchange.getExceptionalCF(MultiExchange.java:455)
    at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:382)
    at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:930)
    at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:907)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2152)
    at java.net.http/jdk.internal.net.http.Http1Exchange.lambda$cancelImpl$9(Http1Exchange.java:482)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
    at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: java.net.ConnectException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.MultiExchange.toTimeoutException(MultiExchange.java:509)
    ... 10 more
URL: http://127.0.0.1:8000/
ConnectTimeout: null
RequestTimeout: PT3S
HttpClient.connectTimeout: Optional.empty
HttpRequest.timeout: Optional[PT3S]
HttpClient.send begins: 2020-06-28T11:07:17.713737
HttpClient.send failed: 2020-06-28T11:07:20.720473
java.net.http.HttpTimeoutException: request timed out
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:561)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
    at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59)
    at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:17)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
URL: http://127.0.0.2:8000/
ConnectTimeout: PT5S
RequestTimeout: PT3S
HttpClient.connectTimeout: Optional[PT5S]
HttpRequest.timeout: Optional[PT3S]
HttpClient.send begins: 2020-06-28T11:07:21.729119
HttpClient.send failed: 2020-06-28T11:07:24.735766
java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:557)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
    at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59)
    at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:21)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68)
    at java.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1259)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:888)
Caused by: java.net.ConnectException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69)
    ... 2 more

実行結果の検証

接続タイムアウトのテスト (存在しないサーバ、接続タイムアウト3秒)

  • 接続タイムアウト: HttpClient.Builder.connectTimeout に3秒を指定
  • リクエストタイムアウト: HttpRequest.Builder.timeout は無指定
  • 3秒で接続タイムアウトが発生
  • 例外 java.net.http.HttpConnectTimeoutException が発生

  • 例外チェーン

    • java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    • Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    • Caused by: java.net.ConnectException: HTTP connect timed out

読み取りタイムアウトのテスト (レスポンスが遅いサーバ、リクエストタイムアウト3秒)

  • 接続タイムアウト: HttpClient.Builder.connectTimeout は無指定
  • リクエストタイムアウト: HttpRequest.Builder.timeout に3秒を指定
  • 3秒でリクエストタイムアウトが発生
  • 例外 java.net.http.HttpTimeoutException が発生

  • 例外チェーン (原因例外は無し)

    • java.net.http.HttpTimeoutException: request timed out

リクエストタイムアウトの影響範囲のテスト (存在しないサーバ、接続タイムアウト5秒、リクエストタイムアウト3秒)

  • 接続タイムアウト: HttpClient.Builder.connectTimeout に5秒を指定
  • リクエストタイムアウト: HttpRequest.Builder.timeout に3秒を指定
  • 3秒で接続タイムアウトが発生
    • 接続タイムアウトに5秒を指定しているのにもかかわらず3秒で接続タイムアウトが発生している
    • リクエストタイムアウトに設定した3秒で接続タイムアウトが発生していると思われる
    • リクエストタイムアウトは接続タイムアウトの時間込みの値だと考えられる
  • 例外 java.net.http.HttpTimeoutException が発生

  • 例外チェーン

    • java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    • Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    • Caused by: java.net.ConnectException: HTTP connect timed out

参考資料

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

インスタンスメソッドの呼び出しについて

インスタンスメソッドの呼び出しの書式についての備忘録です。

メソッド呼び出しの書式について

  • 同じインスタンスに定義されているメソッドの場合は[メソッド名(引数)]
  • インスタンスに定義されているメソッドの場合は[変数.メソッド名(引数)]
  • staticなメソッドの場合は[クラス名.メソッド名(引数)]

同じインスタンスに定義されているメソッド

Test.java
public class Test {
  public void SayHello() {
    System.out.println("Hello World");
  }
  public void Display() {
    SayHello();
  }
}

異なるインスタンスに定義されているメソッド

新しくインスタンスを作成して、変数が格納されている参照を使う(hello.SayHello();)。

Hello.java
public class Hello{
  public void SayHello() {
    System.out.println("Hello World");
  }
}
Test.java
public class Test {
  public static void main(final String[] args) {
    Hello hello = new Hello();
    hello.SayHello();
  }
}

staticなメソッド

クラス名の参照を使う(Hello.SayHello();)。

Hello.java
public class Hello{
  public static void SayHello() {
    System.out.println("Hello World");
  }
}
Test.java
public class Test {
  public static void main(final String[] args) {
    Hello.SayHello();
  }
}

おまけ

後ろにカッコがつかないのはインスタンスのフィールドへのアクセスです。

参考文献

徹底攻略Java SE11 Silver問題集

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

【Java】parallelStreamでプログラムを10倍速にする

結論

parallelStream() によってJavaプログラムは10倍速くなる可能性がある

環境

  • CPU:Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz、2208 Mhz、6 個のコア、12 個のロジカル プロセッサ
  • メモリ:16GB
  • Java:Amazon Corretto-11.0.3.7.1

検証内容

検証用コード

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

public class Main {
    public static void main(String[] args) {
        List<Integer> n1 = new ArrayList<>();
        List<Integer> n2 = new ArrayList<>();

        for(int i = 0; i < 500; i++) {
            n1.add(i);
            n2.add(i);
        }

        long s1 = System.currentTimeMillis();
        n1.stream().forEach(i -> { try { Thread.sleep(10); } catch(Exception e) { e.printStackTrace(); } });
        long t1 = System.currentTimeMillis();

        long s2 = System.currentTimeMillis();
        n2.parallelStream().forEach(i -> { try { Thread.sleep(10); } catch(Exception e) { e.printStackTrace(); } });
        long t2 = System.currentTimeMillis();

        System.out.printf("n1 exec time: %d [ms]\n", t1 - s1);
        System.out.printf("n2 exec time: %d [ms]\n", t2 - s2);
    }
}

取り立てて解説する点はあまりないです。n1 は順次ストリームで、 n2 は並列ストリームで500回 Thread.sleep(10) を呼び出してい ます。try-catchしているのは Thread.sleep() の検査例外に対応するためです。forEach の中のiは虚空に消えています。

実行結果

n1 exec time: 5298 [ms]
n2 exec time: 505 [ms]

n2n1 に比べて10倍以上速くなってます!
n1 は10ms × 500回の結果が如実に現れていますね。

補足

公式ドキュメントによると、デフォルトの並列スレッド数は、コンピュータで利用できるプロセッサの数と等しいそうです。

For applications that require separate or custom pools, a ForkJoinPool may be constructed with a given target parallelism level; by default, equal to the number of available processors.

並列ストリームでの実行時間が順次ストリームのものの1/10以下になっているところを見るに、物理プロセッサ数ではなく論理プロセッサ数がデフォルト値として適用されているようですね。
「10倍速くなる」...っておま環じゃねーか!! という大変もっともなツッコミはあると思いますが、最近のPCなら大抵8論理プロセッサくらいはあると思うので許してください。

注意

各スレッドで共通して参照しているオブジェクトを更新したり削除したりする操作は、基本的にしないほうがいいです(Listに対するaddなど)。

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

Javaの5つのstaticの違いをさらっと説明する

概要

Javaの「static」には5つの種類がある。しかし、それぞれは似て非なるものである。
本記事では、それぞれのstaticについて、落とし穴にハマらないための要点を説明する。
もっと詳しく知りたい場合は、記事最後の参考資料などを参照されたい。

1. staticメソッド

メソッドにstatic修飾子が付与されていると、staticメソッドとなる。

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

staticメソッドは、インスタンスをnewしなくても呼び出すことができるため、一見便利に思える(使いやすそうに見える)。
しかし、単体テストを書きやすくするためには、「決まりきった処理」のみに利用すべきである。

2. staticフィールド

フィールドにstatic修飾子が付与されていると、staticフィールドとなる。

public static final double PI = 3.14159265358979323846;

上記のように、final修飾子と一緒に利用することで、定数として利用することができる。
final修飾子と一緒に利用することで、プログラムのどこかで値を書き換えられ、誤った計算結果を出力してしまうなどのバグを防ぐことができる。

なお、システムの設計によっては、キャッシュの保持などにも利用することができる。

3. 【中級者向け】staticインポート

外部のクラスのstaticフィールドや staticメソッドを、クラス名を指定せずに呼び出すことができるようになる。

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class SampleTest {
    @Test
    public void Test() {
        assertThat("foo", is("foo"));
    }
}

上記のサンプルでは、Assert.assertThatMatchers.isについて、クラス名を省略して記述することができている。

4. 【中級者向け】staticインナークラス

インナークラスにstatic修飾子が付与されていると、staticインナークラスとなる。

public class Outer {
  public static class Inner {
    // staticインナークラス
  }
}

非staticなインナークラスは親クラスのフィールドやインスタンスメソッドにもアクセスできる。
このため、親クラスへの参照を保持している。

しかし、不必要な親クラスへの参照は、メモリやGC1への悪影響が懸念される。
インナークラスをstaticにできる状況であれば、staticにすべきである。2

5. 【中級者向け】staticイニシャライザ

下記のブロック内に記載した処理は、そのクラスに初めてアクセスされる時に呼び出される。

static {
  // 処理
}

具体的には、staticフィールドのコレクションの初期化などに利用する3

public static final List<String> LIST;
static {
    List<String> tmp = new ArrayList<>();
    tmp.add("foo");
    tmp.add("bar");
    LIST = Collections.unmodifiableList(tmp);
}

参考資料

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

Javaの5種類のstaticをさらっと説明する

概要

Javaの「static」には5つの種類がある。しかし、それぞれは似て非なるものである。
本記事では、それぞれのstaticについて、落とし穴にハマらないための要点を説明する。
もっと詳しく知りたい場合は、記事最後の参考資料などを参照されたい。

1. staticメソッド

メソッドにstatic修飾子が付与されていると、staticメソッドとなる。

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

staticメソッドは、インスタンスをnewしなくても呼び出すことができるため、一見便利に思える(使いやすそうに見える)。
しかし、単体テストを書きやすくするためには、「決まりきった処理」のみに利用すべきである。

2. staticフィールド

フィールドにstatic修飾子が付与されていると、staticフィールドとなる。

public static final double PI = 3.14159265358979323846;

上記のように、final修飾子と一緒に利用することで、定数として利用することができる。
final修飾子と一緒に利用することで、プログラムのどこかで値を書き換えられ、誤った計算結果を出力してしまうなどのバグを防ぐことができる。

なお、システムの設計によっては、キャッシュの保持などにも利用することができる。

3. 【中級者向け】staticインポート

外部のクラスのstaticフィールドや staticメソッドを、クラス名を指定せずに呼び出すことができるようになる。

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class SampleTest {
    @Test
    public void Test() {
        assertThat("foo", is("foo"));
    }
}

上記のサンプルでは、Assert.assertThatMatchers.isについて、クラス名を省略して記述することができている。

4. 【中級者向け】staticインナークラス

インナークラスにstatic修飾子が付与されていると、staticインナークラスとなる。

public class Outer {
  public static class Inner {
    // staticインナークラス
  }
}

非staticなインナークラスは親クラスのフィールドやインスタンスメソッドにもアクセスできる。
このため、親クラスへの参照を保持している。

しかし、不必要な親クラスへの参照は、メモリやGCへの悪影響1が懸念される。
インナークラスをstaticにできる状況であれば、staticにすべき2である。

5. 【中級者向け】staticイニシャライザ

下記のブロック内に記載した処理は、そのクラスに初めてアクセスされる時に呼び出される。

static {
  // 処理
}

具体的には、staticフィールドのコレクションの初期化などに利用する3

public static final List<String> LIST;
static {
    List<String> tmp = new ArrayList<>();
    tmp.add("foo");
    tmp.add("bar");
    LIST = Collections.unmodifiableList(tmp);
}

参考資料

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

モンテカルロ法による各言語の速度比較

はじめに

モンテカルロ法
モンテカルロ法を使用して各言語で円周率を求めることにより各言語の速度比較を行います。
モンテカルロ法とは、1.0>=x>=0, 1.0>=y>=0に任意の(乱数の)点を落とし、その落とされた点が原点(0,0)からの円内に落ちたものと、円外に落ちたものの比を求め、その比が円周率になるというものです。
もちろん、計算で求められた円周率に近似するものの、正確な値はでませんが、プログラミング言語で実装することには意味があると思っています。
理由としては、
・浮動小数点計算の速度が比較できる。
・Loop速度が比較できる。
・メモリの使用は少量であり、ページングは(おそらく)発生しない。
・DiskIOが発生しないため、外部記憶装置の影響を受けにくい。
・正解の円周率は既知の情報であり、デバッグが楽。
などがあります。

2020年6月28日 rubyを追加しました。
2020年6月28日 pythonの計算方法を変更しました。
2020年6月28日 rustのループ回数指定に誤りがあったので修正しました。
2020年6月28日 rustを@tatsuya6502 さんご提供のXoroshiro版のソースを追加測定

以下、各言語ソース

perl

perlは今まで試した中で非常に高速のイメージがあったため、ベンチマーク(≒基準)として、用意しました。
ソースもシンプルですので、オブジェクト指向等によるオーバーヘッドもなく、高速に動作するはずです。

montecarlo.pl
use Time::HiRes qw( gettimeofday tv_interval);

local $ave = 0;

local $time_start, $time_end, $time_split = 0;

local $max_times    = 10000000;
local $test_times   = 100;

local $cnt = 0;

for ( $i = 1 ; $i <= $test_times ; $i++ ) {

    print($i."\n");

    $time_start = [gettimeofday];
    $cnt = 0;
    for ( $j = 0 ; $j < $max_times ; $j++) {
        $x = rand();
        $y = rand();

        if ( $x * $x + $y * $y <= 1) {
            $cnt++;
        }
    }

    $time_split = tv_interval($time_start);
    print("pi ".(4 * $cnt / $max_times)."\n");
    print("split:".$time_split."\n");

    $ave += $time_split;
}

print("AVE:".($ave / $test_times)."\n");
~                                                                                                                    ~                                                                                                                    

c言語

とりあえず、「Cなら速いんじゃね?」ということで用意しました。
厳密にチューニングすればもっと早くなるのかもしれませんが、
コーディングのしやすさでこのくらいで抑えてみました。

montecarlo.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

float montecarlo();

void main() {

    float ave = 0;
    int times = 100;

    for (int i = 1 ; i <= times ; i++){
        printf("%03d\n", i );
        ave += montecarlo();
    }

    printf("AVE %f\n\n", ave / times);

}

float montecarlo() {

    clock_t time_start, time_end;
    double  time_split;
    int times = 10000000;
    float x, y;

    time_start = clock();

    int cnt = 0;
    for ( int i = 0; i < times ; i++) {
        x = (float)rand() / (float)RAND_MAX;
        y = (float)rand() / (float)RAND_MAX;

        if ( (x * x + y * y) <= 1) {
            cnt++;
        }
    }

    time_end = clock();

    time_split = (double)(time_end - time_start) / 1000000;
    printf("%f\n", 4.0 * cnt / times);
    printf("time_split : %lf\n", time_split);

    return(time_split);

}

go言語

goは前評判も良かったことから、perlと異なるベンチマークとして用意しました。
コンパイル言語ですので、c言語にどれだけ肉薄できるか?という意味でも興味があります。

montecarlo.go
package main

import (
        "fmt"
        "time"
        "math/rand"
)

var i, j, cnt int
var x, y float64
var time_start, time_end int64
var ave, time_split float64

const MAX_TIMES = 10000000
const TEST_TIMES = 100

func main() {
        //fmt.Printf("%03d\n", 10)

        //fmt.Printf("%d\n", time.Now())
        //fmt.Printf("%f\n", float64(time.Now().UnixNano()) / 1000000000.0)
        //fmt.Printf("%f\n", float64(time.Now().UnixNano()) / 1000000000.0)
        //fmt.Printf("%f\n", float64(time.Now().UnixNano()) / 1000000000.0)

        rand.Seed(time.Now().UnixNano())

        ave := 0.0
        for i := 1 ; i <= TEST_TIMES ; i++ {

                fmt.Printf("%03d times\n", i)

                time_start      := time.Now().UnixNano()
                cnt := 0
                for j := 0 ; j <  MAX_TIMES ; j++ {

                        x := rand.Float64()
                        y := rand.Float64()

                        if x * x + y * y <= 1 {
                                cnt++
                        }

                }
                fmt.Printf("pi : %f\n", 4 * float64(cnt) / float64(MAX_TIMES))

                time_end        := time.Now().UnixNano()
                time_split      := float64(time_end - time_start) / 1000000000
                fmt.Printf("time : %f\n", time_split)
                ave += time_split
                //ave := time_split

        }

        fmt.Printf("AVE : %f\n", ave / TEST_TIMES)

}

Java

コンパイル言語としては現状で鉄板に位置づけられると思います。(各々ご意見はあるでしょうが・・・。)
VMが間に入ることによりC言語よりは遅いことが予想されますが、過去の経験では致命的なほど遅いわけではないという印象です。

montecarlo.java
import java.util.Random;
import java.util.*;


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

        //System.out.println( getNowTime() );

                // 円の半径を指定する
                double iR = 1.0;

                // 描画回数を指定する
                int MAX_POINT = 10000000;

        // テスト回数を指定する
        int TEST_TIMES = 100;

                // Random オブジェクトの定義
                Random rand = new Random();

        // 開始、終了時刻
        long time_start, time_end = 0;

        double time_split;
        double ave = 0.0;

                // Random値の待避用変数の定義
                double ranValue = 0.0;
                double POS_X = 0.0;
                double POS_Y = 0.0;

        for ( int j = 1 ; j <= TEST_TIMES ; j ++) {

            //System.out.println("%03d", j);
            System.out.printf("%03d\n", j);

            // 開始時刻
            time_start = System.nanoTime();
                //System.out.println( "start : " + time_start );

            // 円弧状に落ちた件数をカウントする
            int HIT_COUNT = 0;

            // 出力用ワーク変数の初期化
            for ( int i = 0 ; i < MAX_POINT ; i++ ) {

                POS_X = rand.nextDouble();
                POS_Y = rand.nextDouble();

                if ( iR >= POS_X * POS_X + POS_Y * POS_Y )  {
                    HIT_COUNT++;
                }

            }
            // System.out.println(HIT_COUNT );
            System.out.println( (  4 * HIT_COUNT  / (double)MAX_POINT ) );

            // 終了時刻
            time_end = System.nanoTime();

                //System.out.println( "end   : " + time_end );

            time_split = (double)(time_end - time_start) / 1000000000.0;

                //System.out.println( "split : " + (time_end - time_start) );
                System.out.println( "split : " + time_split );

            ave += time_split;

        }

        //System.out.println( getNowTime() );
        System.out.println( "AVE : " + ave / TEST_TIMES );

    }

        /// 現在時刻をyyyy/mm/dd hh:mm:ss(jpn) 形式で取得する
        public static String getNowTime() {

                return(String.format("%1$tF %1$tT" ,Calendar.getInstance()));

        }

}

lua

script言語としては軽いと評判ですので加えてみました。
言語仕様としてはOSに近しい命令を使用するため早いというイメージです。

montecarlo.lua
local socket = require("socket")

math.randomseed(os.time())

MAX_LOOP = 10000000
TEST_TIMES = 100

ave = 0.0
for i = 1, TEST_TIMES do
    print(string.format("%03d times", i))
    time_start  = socket.gettime()

    cnt = 0
    for j = 1 , MAX_LOOP do
        x = math.random()
        y = math.random()

        if x * x + y * y <= 1 then
            cnt = cnt + 1
        end

    end

    time_end    = socket.gettime()

    time_split  = time_end - time_start

    print(string.format("pi    : %f", 4 * cnt / MAX_LOOP))
    print(string.format("split : %f", time_split))

    ave = ave + time_split

end

print(string.format("ave : %f", ave / TEST_TIMES))

python

言わずと知れたpythonです。
今回は2系、3系で比較するとともに、pypyでの比較を行います。

montecarlo.py
import random
import time
import math

_times = 10000000

def pi():
    _time_start = time.time()

    _cnt = 0
    for i in range(_times):

        _x = random.random()
        _y = random.random()

        #if  _x ** 2 + _y **2 <= 1:
        if  _x * _x + _y * _y <= 1:

            _cnt += 1

    print( 4.0 * _cnt / _times )

    _time_end = time.time()

    _time_split = _time_end - _time_start
    print(_time_split)

    return _time_split

_test = 100

_ave = 0.0
for _i in range(_test):
    print('{:03d} times'.format(_i+1))
    _ave += pi()

print('ave: {} s'.format(_ave/_test))

Rust

RustはAI,機械学習で注目されている言語とのことです。
pythonよりは早く、Cよりは開発しやすい言語とのことです。

Cargo.toml
[package]
name = "montecarlo"
version = "0.1.0"
authors = ["ubuntu"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.6"
floating-duration="0.1.2"
src/main.rs
fn main() {

    use rand::Rng;                  // 乱数用
    use std::time::Instant;         // 時刻取得用
    use floating_duration::TimeAsFloat; // 時刻→floatへの変換用

    println!("Hello, world!");

    let loop_max = 10000000;        // 打点の回数
    let test_max = 100;             // 平均を出すためのループ

    let mut x: f32;
    let mut y: f32;

    let mut rng = rand::thread_rng();//randseedの設定

    let mut split_time: f32 = 0.0;
    //for _test_count in 1..test_max {
    for _test_count in 1..=test_max {
        println!("times:{:?}", _test_count );
        let start_time = Instant::now();

        let mut count = 0;
        //for _loot_count in 1..loop_max {
        for _loot_count in 1..=loop_max {
            x = rng.gen_range(0., 1.);  //乱数0.0~1.0
            y = rng.gen_range(0., 1.);

            if x * x + y * y <= 1.0 {
                count = count + 1;
            }

        }
        println!("pi:{}", 4.0 * count as f32 / loop_max as f32); // as f32は型キャスト
        let end_time = start_time.elapsed().as_fractional_secs();// 時間を秒に変換
        println!("time:{:?}", end_time );

        split_time = split_time + end_time as f32;

    }

    println!("AVE:{}", split_time / test_max as f32);

}

@tatsuya6502 さんご提供のXoroshiro版ソース

Cargo.toml
[package]
name = "montecarlo"
version = "0.1.0"
authors = ["ubuntu"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.7"
rand_xoshiro = "0.4"
src/main.rs
// 乱数用
use rand::Rng;
use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128Plus};

// 時刻取得用
use std::time::{Instant, SystemTime};

const LOOP_MAX: usize = 10_000_000; // 打点の回数
const TEST_MAX: usize = 100; // 平均を出すためのループ

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // random seedを設定
    let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
    let mut rng = Xoroshiro128Plus::seed_from_u64(now.as_millis() as u64);

    let mut split_time: f32 = 0.0;
    for _test_count in 1..=TEST_MAX {
        println!("times:{}", _test_count);
        let start_time = Instant::now();

        let mut count = 0;
        for _loot_count in 0..LOOP_MAX {
            // 乱数を生成 [0.0, 1.0) ・・ 0.0以上 1.0未満
            let x: f32 = rng.gen_range(0.0, 1.0);
            let y: f32 = rng.gen_range(0.0, 1.0);

            if x * x + y * y <= 1.0 {
                count += 1;
            }
        }
        println!("pi:{}", 4.0 * count as f32 / LOOP_MAX as f32); // as f32は型キャスト
        let end_time = start_time.elapsed().as_secs_f32(); // 時間を秒に変換
        println!("time:{}", end_time);

        split_time += end_time as f32;
    }

    println!("AVE:{}", split_time / TEST_MAX as f32);
    Ok(())
}

Nim

比較用に・・・

montecarlo.nim
import random
import times

randomize()

const TEST_MAX = 10000000
const LOOP_MAX = 100

var x, y = 0.0

var ave = 0.0

for loop in 1..LOOP_MAX:
    echo $loop & " times"

    var time_start = cpuTime()
    var count = 0
    for test in 1..TEST_MAX:

        x = rand(0.0..1.0)
        y = rand(0.0..1.0)

        # echo $x
        # echo $y

        if ( x * x + y * y <= 1):
            count += 1

    echo "pi:" &  repr(float(4.0 * float(count) / float(TEST_MAX)))

    var time_split = cpuTime() - time_start

    echo "split" & repr(time_split)
    ave += time_split

echo "AVE:" & repr(ave / float(LOOP_MAX))
                                                                                                                    ~                                                                                                                    

Ruby

@scivola さんにソースを提供していただきましたのでrubyを追加しました。

montecarlo.rb
TIMES = 10000000

def pi
  time_start = Time.now

  cnt = 0
  TIMES.times do
    x = rand
    y = rand

    if  x * x + y * y <= 1
      cnt += 1
    end
  end

  puts 4.0 * cnt / TIMES

  time_end = Time.now

  time_split = time_end - time_start
  puts time_split

   time_split
end

test = 100

ave = 0.0

test.times do |i|
    puts "%03d times" % (i + 1)
    ave += pi
end

puts "ave: #{ ave / test } s"

結果

perl 5.30
    AVE:8.93247789s

java 14.0.1
    AVE: 1.4161036380199996s

c
    gcc 9.3.0

    gcc montecarlo.c -o pi

    gcc 最適化なし
    AVE:1.501472s

    gcc 実行速度の最適化 -O3
    AVE:1.425400s

python 2.7.18
    AVE:14.216013s

pypy 7.3.1 with GCC 9.3.0
    ave: 0.983056025505

python 3.8.2
    AVE:12.485190s
pypy3 7.3.1 with GCC 9.3.0
    ave: 1.1475282835960388 s

go 1.13.8
    AVE:2.132493

lua 5.3
    AVE:6.715329

rust (cargo 1.44.0)
    cargo run --release
    AVE 0.507196783

    cargo run
    AVE 16.449156

Nim 1.0.6
    nim c -r montecarlo.nim
    AVE 7.2063023393

    nim c -r -d:release montecarlo.nim
    AVE:0.3236947073200001

2020年6月28日 追記

ruby を追加しました。
 Ruby 2.7.0dev (2019-12-25 master e1e1d92277) [aarch64-linux]
    ave: 7.5495061204099985 s

pythonのソースを修正しました。
 if  _x ** 2 + _y **2 <= 1:    ①
 ↓
 if  _x * _x  + _y * _y <= 1:  ②

 ※python 3.8.2の計測
 ①再取得 
    ave: 11.132939925193787 s
 ②
    ave: 9.153075976371765 s
 ※pypy3の計測
 ①再取得
    ave: 1.0760090994834899 s
 ②
    ave: 1.3655683732032775 s
 あれ?

rustのループを修正しました。
    for _test_count in 1..test_max {
        for _loot_count in 1..loop_max { ①
     ↓
    for _test_count in 1..=test_max {
        for _loot_count in 1..=loop_max { ②

 cargo run --release の計測
 ①再取得 
    AVE:0.5022585
 ②
    AVE:0.51400733

rustで@tatsuya6502 さんご提供のXoroshiro版ソースについて計測しました。

 AVE:0.21163946

結論

このレベルであれは Nim rustが最速との評価になりました。
速度的には、

Nim > rust > pypy > C = java > pypy3 > go > lua > ruby > perl > python2系 > python3系
rust(Xoroshiro版) > Nim > rust > pypy > C = java >= pypy3 > go > lua > ruby > perl > python2系 > python3系

となりました。
順当にコンパイル言語>スクリプト言語となっています。
もちろん、私のコーディングが悪く、「もっとこうすれば早くなる」というのはあるかもしれません。
しかし、多少工夫をしたところで1段階変わるだけで、さほど順位の入れ替わりがあるとは考えにくいです。

特筆すべきはpythonで、pypyを使用することによりC言語に近しい速度で動作する(可能性)が確認できました(もちろん、そのままのソースがpypyで動かない場合もありますし、ライブラリがpypyに対応していない場合もあります)。
しかしながらpythonの柔軟性のある記述で高速化できるのであれば試してみる価値があるとおもいます。

2020年6月28日追記
rubyが予想以上に速くなっており驚きを隠せません。以前試したときはc:ruby=1:20くらいでしたので、今回もそのくらいと予想していましたが見事に裏切られました。
@scivola さんに提案をいただかなければ無視していました。ありがとうございます。

pythonは今回のソース修正でpython3 で起動した場合は早くなるものの、pypy3で起動した場合は遅くなるという謎の結果になりました。(識者のコメント希望)
ちなみに本稿のソースではコメントして違いを表していますが、実測のソースでは置き換えていますので、コメントの評価で遅くなったということはありません。(昔のBasicとかでそういうのがあった気がしますが・・・)

rustは疑似乱数生成器の違いが如実に現れました。
@tatsuya6502 さんご提供のXoroshiro版ソースにより疑似乱数生成器も適材適所であることが分かりました。
ご指摘ありがとうございました。

本稿はたまーに更新します。
よさそうな言語がありましたら追加していこうと思います。

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