- 投稿日:2021-03-29T22:07:01+09:00
SpringBoot2で簡単なアプリを作ってみよう!(2/3) ~画面表示~
この記事は以下の続きです。
SpringBoot2で簡単なアプリを作ってみよう!(1/3) ~環境設定~第2回目は画面表示をしていきます!
画面を表示する
パスについてですが、前回も載せておりますが戻って確認するのも面倒だと思うのでこちらにも載せておきます。
HTMLを作成する
"xmlns"で名前空間を宣言することによりThymeleafタグを使用することができるようになります。
(<th:xxx>)←こんな感じのタグを使います。productManage.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>商品管理</title> </head> <body> <p>商品管理</p> <table> <tr> <th>商品名</th> <th>商品数</th> </tr> </table> </body> </html>コントローラを作成する
次にコントローラのパッケージ、ファイルを作成します。
@Controller
をつけることによってDIコンテナに登録することができます。
(DIについてはまた別の記事を書こうかなと思ってます。)
@GetMapping
でGETリクエストを受け取りHTMLを返しています。AdminController.java@Controller public class AdminController { @Autowired ProductService productService; @GetMapping("/") public String index(Model model) { return "productManage"; } }ここまできたら画面を確認してみましょう。
下記URLで画面が表示されるはずです。
http://localhost:8080/◇画面イメージ
↓第3回はこちら
(第3回目は執筆中)
- 投稿日:2021-03-29T15:24:49+09:00
javacコマンドでコンパイルするソースファイルを指定する方法(※ソースファイル内に、パッケージ宣言のクラスが参照されている場合)
概要
パッケージ宣言のクラスが参照されているソースファイルを
コンパイルする際にエラーが起き、
コンパイルが出来なかったので、その時の健忘録である。フォルダ階層. #① ├── logics #② │ └── CalcLogic.java #③ └── main #④ └── Calc.java #⑤Calc.javapackage calcapp.main; public class Calc{ public static void main(String[] args){ int a = 10; int b = 2; int total = calcapp.logics.CalcLogic.tasu(a, b); int delta = calcapp.logics.CalcLogic.hiku(a, b); System.out.println("足すと" + total + "、引くと" + delta); } }CalcLogic.javapackage calcapp.logics; // ⑥ public class CalcLogic{ public static int tasu(int a, int b){ return (a + b); } public static int hiku(int a, int b){ return (a - b); } }④の階層で下記のコマンドを実行。「NG①」
javac Calc.java
しかし、以下のエラーメッセージが表示され、コンパイルできなかった。
Calc.java:7: エラー: パッケージcalcapp.logicsは存在しません int total = calcapp.logics.CalcLogic.tasu(a, b); ^ Calc.java:8: エラー: パッケージcalcapp.logicsは存在しません int delta = calcapp.logics.CalcLogic.hiku(a, b); ^ エラー2個解決策
javacコマンドを正しいディレクトリの中で実行する。(階層①で実行する)
# ①のディレクトリで実行 javac calcapp/main/Calc.javajavacコマンドは、実行したディレクトリ「直下」にあるパスを検索して、
パッケージ名と同じディレクトリがあれば、それを辿って目的のファイルを見つけるようだ。
もし、なければ、javacコマンドはコンパイルすべきファイルを見つけることができない。「NG①」で、パッケージを見つけることができなかった理由としては、
階層④でjavacコマンドを実行しても、
javacコマンドは、階層④「直下」にあるパスから、
「./calcapp/logics」探そうとしていたからである。備考
ちなみに、今、問題となっているのは、
Calc.javaをコンパイルする際に、パッケージが記載されたクラスが使われていた(パッケージが記載されたクラスの記載があった)場合である。
(Calc.javaのソースファイルに記載されているパッケージ宣言(※⑥の表記)の事ではない。)つまり、パッケージとjavaファイルを探す際に、Javacコマンドを実行するディレクトリとパッケージに対応するディレクトリを正常に配置していないと、Javacコマンドが該当ファイルを見つけることができないのである。)
実際、Calc.javaがたとえ、calcapp.mainパッケージに属していたとしても、そのパッケージ内で、パッケージが参照されていなければ、階層④でjavacコマンドを実施しても、正常にクラスファイルが作成される。
ただし、その場合も、javaコマンドを実行する際には、
同じように、実行ディレクトリ(※Javaコマンドをどのディレクトリで実行するか)と
パッケージに対応するディレクトリから該当classファイルを見つけていくので、
javaコマンドを実行するディレクトリと、パッケージに対応するディレクトリの構成を
正しくしておかなければ、クラスファイルを見つけることができない。(プログラムが起動しない。)参考文献
『スッキリわかるJava入門 第2版 スッキリわかるシリーズ』-第6章
- 投稿日:2021-03-29T13:31:12+09:00
いい感じのランダムの色が欲しいなぁって時にこれ。
例えば10個のいい感じのランダムの色が欲しいって時。
ガチのランダムにすると意味わからない変な色が出来上がったりしてしまうので。
っていうものをメモ程度に残しておきます。
意外と使える!hueの部分の分母を欲しい個数にするだけです。
saturationとbrightnessに関しては自分の好みの色味にしてください。下記コードそのまま使用した際にはこんな感じの色が出力されます。
似てる色があるとか言わないで
#CC8B29
#ABCC29
#49CC29
#29CC6A
#29CCCC
#296ACC
#4929CC
#AB29CC
#CC298B
#CC2929
swift.swiftlet colorList = [ UIColor(hue: 0/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 1/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 2/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 3/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 4/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 5/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 6/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 7/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 8/10, saturation: 0.8, brightness: 0.8, alpha: 1), UIColor(hue: 9/10, saturation: 0.8, brightness: 0.8, alpha: 1) ]java.java/* * いい感じの色のリストを指定数分作成する * colorCount: 色の数 * brightness: 色味 0.0~1.0 * */ private int[] createRandomColorList(int colorCount, float brightness) { float [] f = new float[3]; f[1] = brightness; f[2] = brightness; int[] cList = new int[colorCount]; for (int i = 0; i < colorCount; i++) { f[0] = 360f * (i+1)/colorCount; cList[i] = Color.HSVToColor(f); } return cList; }
- 投稿日:2021-03-29T12:29:26+09:00
JavaのObjectはObjectかとか
クイズとか
またもや突然クイズです。
「Javaにおいて、全ての配列およびクラスは暗黙的にObjectクラスを継承している」
YESかNOか答えとか
これ、自分もよくこういうことを言ってしまっていますし、日本語のJavaDoc読んでみたら
Objectクラスは、クラス階層のルートです。すべてのクラスは、スーパー・クラスとしてObjectを持ちます。配列を含むすべてのオブジェクトは、このクラスのメソッドを実装します。
と書いてありますし。
が、答えはNOです。
Javaにおいて、全ての配列およびクラスは暗黙的にObjectクラスを継承しているわけではありません。
例外があるんです。例外のクラスとか
例外となるクラスはObjectクラスです。
ObjectクラスはObjectを継承しません。
そりゃそーですよね。
Object extends Objectとかやったら
Objcet::newを実施→super();→Object::newを実施→super();→以下略
と、コンストラクタが無限再帰しちゃいます。
"GNU's Not Unix!"みたいになっちゃう!何を継承してるのかとか
そうすると、気になるのはObjectクラスが何を継承しているかですよね。
特にObject::new後。
そういうわけで、コードで確認してみました。
java
System.out.println(new Object().getClass().getSuperclass().toString());Exception in thread "main" java.lang.NullPointerException
ぬるぽ
おまけとか
ついでなのでJavaの言語仕様を見てみましょう。
The class Object is a superclass (§8.1.4) of all other classes.
「Objectクラスは"他の"全てのクラスのスーパークラスである」ちゃんと書いてあった!
- 投稿日:2021-03-29T11:59:27+09:00
【JDBCドライバ】MySQL Connector/J 8.0ダウンロード手順
はじめに
自身がJava学習中にDB接続の環境を整える際に悩んだので、
JDBCドライバのダウンロードから環境のセットアップまでを記載します。使用環境
開発環境:paizacloud
Javaバージョン:13.0.1
Apache Tomcatバージョン:9.0.41
DB:MySQL
JDBCバージョン:8.0.231.JDBCのインストーラーダウンロード
![]()
![]()
![]()
![]()
2.インストーラ起動
![]()
3.connect/Jをダウンロード
C:\Program Files (x86)\MySQL\Connector J 8.0\mysql-connector-java-8.0.23.jar4.jarファイルを配置
ダウンロードしたドライバ(jarファイル)をWEB-INF\libに配置する。
5.Javaには下記のように記載する。
ドライバ名がバージョンで異なるそうです。
ドライバのバージョンが8.0系の場合
Class.forName("com.mysql.cj.jdbc.Driver");※ドライバのバージョンが8.0系よりも前の場合
Class.forName("com.mysql.jdbc.Driver");参考
- 投稿日:2021-03-29T08:26:44+09:00
Java 16 から利用可能な Unix ドメイン ソケット
Java 16 で JEP 380: Unix-Domain Socket Channels が取り入れられました。今回はこれをご紹介します。
名前の通り、Unix で使われてきたものですが、Windows 10 April 2018 Update 以降は Windows でもサポートされています。Windows Subsystem for Linux (WSL) や Docker コンテナのように、ホスト OS 上の Unix と通信したい場面が増えたためだと思われます。
私なりにわかりやすくご紹介しようと思いますが、にわかユーザーに過ぎませんので、正確な情報や参考になる実装コードがほしい方は、ぜひ JEP 380 のオーナーさんによる Inside Java - JEP-380: Unix domain socket channels
(Logico Inside の和訳) もご覧ください。Unix ドメイン ソケットとは
Unix domain sockets are addressed by filesystem pathnames that look much the same as other filenames: eg. /foo/bar or C:\foo\bar.
冒頭でご紹介した Inside Java の記事では、上記のように説明されています。
つまり、Unix ドメイン ソケットでは、アドレスに /foo/bar や C:\foo\bar などのファイル名のようなパス名を使います。ポート番号は使いません。
パイプと比べて
Windows では Unix ドメイン ソケットの役割を、名前付きパイプが果たしてきました。
echo WebSocket | cat
のようにコマンドをつなぐときに使うパイプ|
とちょっと似ています。大きな違いは双方向で通信できることです。この例で cat から echo にデータを送り返すことはできませんが、Unix ドメイン ソケットなら双方向でやり取りできます。
ふつうのソケットと比べて
再び Inside Java の記事を参考に、ローカルのプロセス間通信に Unix ドメイン ソケットを使う利点をご紹介します。
性能
画像は前述の Inside Java の記事からの引用です。
127.0.0.1 や ::1 のようなループバックと比べ、TCP/IP の処理が不要なため、レイテンシや CPU 使用率を削減できます。
『詳解UNIXプログラミング 第3版』1 には具体的に、以下のように書かれていました。
インターネットドメインソケットも同じ目的に使えますが、UNIX ドメインソケットはより効率的です。UNIX ドメインソケットはデータをコピーするだけです。処理すべきプロトコルはありませんし、付加したり削除したりするネットワークヘッダもなく、チェックサム計算も必要なく、連番を振る必要もなく、肯定応答を送る必要もありません。
言われてみれば TCP って色々と面倒なことを肩代わりしてくれているのでしたね。アプリの担当範囲ではないので関係ない感じもしますが、高速化は嬉しいです。
Windows 10 上の PostgreSQL 13 での実測値は TCP と同等
「Windows 10 の PostgreSQL 13 で Unix ドメイン ソケット【TCP? なにそれおいしいの?】」で検証した際には、性能は TCP と同じでした。通信がボトルネックになりがちな処理を選んで検証したつもりでしたが、差が出ませんでした。性能への過剰な期待は禁物です。
安全性
パス名をアドレスにしていれば、うっかり外部に公開してもアクセスされる恐れがありません。「おかしいなー。TCP のポートにつながらないなー。ファイアーウォールを切ってアクセスしてみよう」のように思うことは、Unix ドメイン ソケットを使っていればありません。
2点目は少し意外でした。
Second, because Unix domain sockets are addressed by filesystem objects, this means standard Unix (and Windows) filesystem access controls can be applied to limit access to a service by specific users or groups, as required.
ファイルシステム オブジェクトとしてアクセスされるので、アクセス制御リストにより特定のユーザーやグループにのみアクセスさせることができるそうです。まるでユーザーやグループで制御できるファイアーウォールですね。
利便性
Docker などで TCP/IP で通信しようとしてハマらなくて済むそうです。私はめったに Docker を使わないので、よくわかりません。
互換性
アドレス形式は全く異なりますが、基本的には TCP/IP のソケットのように扱えるので、両方をサポートしてもあまり負担になりません。
Java 16 で何ができるようになったの?
UNIX ドメイン ソケット自体の利点は掴んだところで、今度は解説ブログではなく JEP 380: Unix-Domain Socket Channels 本体から、概要と目標をご紹介します。
概要
Add Unix-domain (AF_UNIX) socket support to the socket channel and server-socket channel APIs in the java.nio.channels package. Extend the inherited channel mechanism to support Unix-domain socket channels and server socket channels.
java.nio.channels パッケージの SocketChannel に Unix ドメイン (AF_UNIX) のソケットが追加され、ServerSocketChannel のAPI が変更されています。
open
メソッドの呼び出し時に StandardProtocolFamily.UNIX を与えると、SelectorProvider が適切な実装を返してくれます。目標
The goal of this JEP is to support all of the features of Unix-domain sockets that are common across the major Unix platforms and Windows.
Windows でサポートされるようになったし、Unix と共通して使える部分は Java で実装してあげようよ、ということです。まさに、みんなが Java に期待していることをしてくれるわけですね。
使ってみた
動くサンプルを書いてみました。
サーバー側
UnixDomainServer.javaimport java.io.IOException; import java.net.StandardProtocolFamily; import java.net.UnixDomainSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; public class UnixDomainServer { public static final Path PATH = Path.of(System.getProperty("java.io.tmpdir"), ".yubaba"); public static final UnixDomainSocketAddress ADDRESS = UnixDomainSocketAddress.of(PATH); public static void main(String[] args) throws IOException { try (ServerSocketChannel ssc = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) { Files.deleteIfExists(PATH); ssc.bind(ADDRESS); try (SocketChannel socketChannel = ssc.accept()) { ByteBuffer buf = ByteBuffer.allocate(1024); socketChannel.read(buf); buf.flip(); String introString = new String(buf.array(), StandardCharsets.UTF_8); System.out.println("She said, \"" + introString + "\""); String[] words = introString.split("\\s"); String lastWord = words[words.length - 1]; String name = lastWord.substring(0, lastWord.length() - 1); String declaration = "From now on, your name is %s!".formatted(yubaba(name)); System.out.println(declaration); socketChannel.write(ByteBuffer.wrap(declaration.getBytes(StandardCharsets.UTF_8))); } } } private static String yubaba(String origName) { String[] graphemeClusters = origName.split("\\b{g}"); return graphemeClusters[0]; } }パスには一時ディレクトリを使っています。サーバーが湯婆婆なので、一時ディレクトリ内の
.yubaba
をアドレスとして使っています。PostgreSQL で設定ファイルに指定したディレクトリ内の.s.PGSQL.5432
(5432 は TCP 用ポート番号) をアドレスとしていたので、真似ました。Unix ドメイン ソケットは使い終わった後も 0 バイトのファイルのようなものが残ります。今回は終了時には削除せず、起動時に残っていたら削除するようにしています。
上記コードで、
buf.flip()
を忘れると、一見正しく動くのですが、実際にはたっぷりヌル文字が入ってしまいますのでご注意ください。私はやりました。Unix ドメイン ソケットとは無関係ですが、今どきの湯婆婆は extended grapheme cluster を意識すると良いと思います。でも細かいことは考えていないので、表示できない文字とか
"
で始まる名前とかは名乗らないであげてください。クライアント側
UnixDomainClient.javapublic class UnixDomainClient { public static void main(String[] args) throws IOException { try (SocketChannel socketChannel = SocketChannel.open(StandardProtocolFamily.UNIX)) { socketChannel.connect(UnixDomainServer.ADDRESS); String introString = "My name is \uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66\uD83D\uDC7B."; System.out.println(introString); socketChannel.write(ByteBuffer.wrap(introString.getBytes(StandardCharsets.UTF_8))); ByteBuffer buf = ByteBuffer.allocate(1024); socketChannel.read(buf); buf.flip(); System.out.println("She said, \"" + new String(buf.array(), StandardCharsets.UTF_8) + "\""); } } }今どきの子は emoji modifier を使った絵文字を名前に含めることにしました。宮崎アニメにこんな奇天烈な名前のキャラクターは出てこないと思いますが...。
動作確認
- UnixDomainServer
- UnixDomainClient
の順で実行します。
サーバー側She said, "My name is ?????." From now on, your name is ????!クライアント側My name is ?????. She said, "From now on, your name is ????!"このサンプルは1回メッセージをやりとりしたらそれで終わりです。
業務で使うなど、より本格的に利用する場合は Inside Java の記事に掲載のコードを参考にしてください。冒頭でご紹介したとおり、和訳もあります。
触れられなかったこと
Inside Java の記事では、Unix で SO_PEERCRED により UnixDomainPrincipal を得ることや、Docker での利用例、
inetd
やlaunchd
で起動された時の inherited channels の仕組みへの言及もあります。興味のある方は、ぜひそちらもご覧ください。まとめ
Java の標準ライブラリを使って同じマシン内で通信する手段が一つ増えました。コンテナとの通信などのプロセス間通信で活用される技術で、セキュリティ上の利点もあります。TCP ソケットのついでに Unix ドメイン ソケットも使えるようにしておくと喜ばれるかもしれません。
W. Richard Stevens ほか (2014).『詳解UNIXプログラミング 第3版』. 翔泳社. ↩
- 投稿日:2021-03-29T05:46:54+09:00
2重登録制御
トーク制御
トークン制御は登録、更新、検索処理をボタンなどに対して、ボタンの二重クリックを防止する。
代表的な制御方法
1.javaScript
更新処理を行うボタンを押下した際に、JavaScriptによるボタン制御を行うことで、2度押しされた際にリクエストが送信されないようにします。
- ボタンやリンクを非活性化することで、ボタンやリンクを押下できないように制御します。
- 処理状態をフラグとして保持しm処理中にボタンやリンクが押された場合に処理中であることを通知するメッセージを表示します。 などがあげられます。
2. PRG(Post-Redirect-Get)パターン
更新処理を行うリクエスト(POSTメソッドによるリクエスト)に対する応答としてリダイレクトを返却し、その後ブラウザから自動的にリクエストされるGETメソッドの応答として遷移先の画面を返却するようにします。
PRGパターンを適用することで、画面表示後にページの再読み込みを行った場合に発生するリクエストがGETメソッドになるため、更新処理の再実行を防ぐことができます。3.トランザクショントークンチェック
画面遷移毎にトークン値を払い出し、ブラウザから送信されたトークン値とサーバ上で保持しているトークン値を比較することで、トランザクション内で不正な画面操作が行われないようにします。
トランザクショントークンチェックを適用することで、ブラウザの戻るボタンを使ってページを移動した後の更新処理の再実行を防ぐことが出来る。
また、トークン値のチェックを行った後にサーバで管理しているトークン値を破棄することで、サーバ側の処理として二重送信を防ぐこともできます、
- サーバは、クライアントからリクエストが来た際に、サーバ上にトランザクションを一意に識別するための値(トークン)を保持します。
- サーバは、クライアントへトークンを送信します。画面を提供するWebアプリケーションの場合は、formのhiddenタグを使用してクライアントにトークンを保持させます。
- クライアントは次のリクエストを送信する際に、サーバから渡されたトークンを送信します。サーバは、クライアントから受け取ったトークンと、サーバ上で管理しているトークンを比較します。
トークの生成例
1.無意味な文字列
import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import javax.xml.bind.DatatypeConverter; public class RandomToken { private static int TOKEN_LENGTH = 16; //16*2=32バイト public static void main(String[] args) { byte token[] = new byte[TOKEN_LENGTH]; StringBuffer buf = new StringBuffer(); SecureRandom random = null; String tokenString = null; try { random = SecureRandom.getInstance("SHA1PRNG"); random.nextBytes(token); for(int i = 0; i < token.length; i++) { buf.append(String.format("%02x", token[i])); } tokenString = buf.toString(); System.out.println("String.format: " + tokenString); System.out.println("DatatypeConverter: " + DatatypeConverter.printHexBinary(token)); } catch(NoSuchAlgorithmException e) { e.printStackTrace(); } } }2.ユーザーIDや有効期限などの情報をエンコード
使用ライブラリ : JJWT
<!-- pom.xml --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>import java.security.Key; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Base64; import java.util.Date; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.impl.crypto.MacProvider; public class JsonWebToken { public static void main(String[] args) { Key key = MacProvider.generateKey(); LocalDateTime now = LocalDateTime.now(); System.out.println("Current Timestamp: " + now); //7日後の日時 ZonedDateTime expirationDate = now.plusDays(7).atZone(ZoneId.systemDefault()); //JWTを生成 System.out.println("*** JWT Create ***"); String compactJws = Jwts.builder() .setSubject("hoge") .setExpiration(Date.from(expirationDate.toInstant())) .signWith(SignatureAlgorithm.HS512, key) .compact(); System.out.println("JWT: " + compactJws); //Base64でデコード System.out.println("*** Base64 decode ***"); String[] jwtSections = compactJws.split("\\."); String header = new String(Base64.getDecoder().decode(jwtSections[0])); String claim = new String(Base64.getDecoder().decode(jwtSections[1])); System.out.println("JWT Header: " + header); System.out.println("JWT Claim: " + claim); //クレームの確認 System.out.println("*** Claim ***"); Date exp = Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody().getExpiration(); String sub = Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody().getSubject(); System.out.println("exp(Expiration Time): " + LocalDateTime.ofInstant(exp.toInstant(), ZoneId.systemDefault())); System.out.println("sub(Subject): " + sub); //署名の検証 System.out.println("*** Signature Validation ***"); try { Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws); System.out.println("Use Correct Key: Validation Success"); } catch(SignatureException e) { System.out.println("Use Correct Key: Validation Fail - " + e.getMessage()); } Key wrongKey = MacProvider.generateKey(); try { //生成時とは別のキーを使用 Jwts.parser().setSigningKey(wrongKey).parseClaimsJws(compactJws); System.out.println("Use Wrong Key: Validation Success"); } catch(SignatureException e) { System.out.println("Use Wrong Key: Validation Fail - " + e.getMessage()); } } }
- 投稿日:2021-03-29T05:24:26+09:00
【Java】ハッシュユーティリティを列挙型とチェーンな感じで書いてみた
0. INDEX
- 概要
- ハッシュユーティリティを書いてみた
- あとがき
1. 概要
列挙型を使ってtypoしないぞ計画!
ってそこまで大層なものじゃないけど、いつものようにあれば使うよなって奴です。2. ハッシュユーティリティを書いてみた
2.1. 動作サンプル
2.2. 使い方
こんな感じでupdate関数を繰り返せるチェーンメソッド仕様です。
String result = MessageDigestUtil.SHA256 .update("hello".getBytes()) .update("everyone".getBytes()) .toHexString(); System.out.println(result);2.3. ソースコード
MessageDigestUtil.javapackage jp.go.mahny_conf.junk_memo; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * SHA-256とか生成するユーティリティ * @author mahny */ public enum MessageDigestUtil { MD2("MD2"), MD5("MD5"), SHA1("SHA-1"), SHA256("SHA-256"), SHA384("SHA-384"), SHA512("SHA-512"), ; private MessageDigest digest; /** * 初期化。使う側は意識しない箇所 * @param algorithm アルゴリズム文字列 */ private MessageDigestUtil(String algorithm) { try { digest = MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { // 引数のnull指定以外では出ないハズ throw new IllegalArgumentException("algorithmの指定が無い", e); } } /** * ハッシュに使用するデータを追加する * @param input 使用するデータ * @return ユーティリティオブジェクト */ public MessageDigestUtil update(byte input) { digest.update(input); return this; } /** * ハッシュに使用するデータを追加する * @param input 使用するデータ * @return ユーティリティオブジェクト */ public MessageDigestUtil update(byte[] input) { digest.update(input); return this; } /** * ハッシュに使用するデータを追加する * @param input 使用するデータ * @return ユーティリティオブジェクト */ public MessageDigestUtil update(ByteBuffer input) { digest.update(input); return this; } /** * ハッシュを生成する * @return ハッシュデータ */ public byte[] toBytes() { return digest.digest(); } /** * ハッシュを生成し、16進数文字列で取得する * @return ハッシュデータ(16進数文字列) */ public String toHexString() { StringBuilder result = new StringBuilder(); for (byte data : toBytes()) { result.append(Integer.toHexString((data >> 4) & 0x0f)); result.append(Integer.toHexString(data & 0x0f)); } return result.toString(); } }3. あとがき
列挙型ネタが続いていますが、今回は過去ネタの3番煎じくらいですが、もうちょっと使い勝手に踏み込んだユーティリティを作ってみました。
ちなみに、過去ネタ執筆時の列挙型の理解が浅かった頃は、文字列で定義して…
アルゴリズム定義public static final String ALG_SHA256 = "SHA-256";想定した使い方byte[] hash = HashUtil.getHash(HashUtil.ALG_SHA256);みたいな使い方を想定するんですが、これだと使い方を浸透させないと↓みたいにチームメイトが好き勝手しちゃうんですよね><
ッ!これは想定外ッ!!byte[] hash = HashUtil.getHash("SHA-256");使いたい時に直感的に使えるようにしておくのは、他人(未来の自分含む)の為にも大切だなと思う今日この頃です。
ではノシ
- 投稿日:2021-03-29T00:52:36+09:00
【メモ】 JSON 概要
JSONを初めて使用したので、概要だけまとめておく。
おおむね、下記の3パターンを階層的に組み合わせていく。
オブジェクト型
json
{ "id": "001", "name": "taro" }java
// マッピング JsonSampleBean bean = mapper.readValue(json, JsonSampleBean.class); // Beanの定義 package com.example.app.bean.json.request; import lombok.Getter; import lombok.Setter; /** * JSONマッピング用のオブジェクト。 * * @author start */ public class JsonSampleBean { /** ID */ @Getter @Setter private String id; /** 名称 */ @Getter @Setter private String name; }リスト(配列)型
json
{ "sampleList": [ { "id": "001", "name": "taro" }, { "id": "002", "name": "jiro" } ] }java
// マッピング JsonSampleListBean list = mapper.readValue(listJson, JsonSampleListBean.class); // Beanの定義 package com.example.app.bean.json.request; import java.util.List; import lombok.Getter; import lombok.Setter; /** * JSONマッピング用のオブジェクト。 * * @author start */ public class JsonSampleListBean { /** リスト */ @Getter @Setter private List<JsonSampleBean> sampleList; }リスト(配列)型_リストのみ
json
[ { "id": "001", "name": "taro" }, { "id": "002", "name": "jiro" } ]java
// マッピング List<JsonSampleBean> list = mapper.readValue(listJson, new TypeReference<List<JsonSampleBean>>(){}); // Beanの定義 // 省略マップ型
json
{ "sampleMap":{ "key1": { "id": "001", "name": "taro" }, "key2": { "id": "002", "name": "jiro" } } }java
// マッピング JsonSampleMapBean map = mapper.readValue(mapJson, JsonSampleMapBean.class); // Beanの定義 package com.example.app.bean.json.request; import java.util.Map; import lombok.Getter; import lombok.Setter; /** * JSONマッピング用のオブジェクト。 * * @author start */ public class JsonSampleMapBean { /** マップ */ @Getter @Setter private Map<String, JsonSampleBean> sampleMap; }マップ型_マップのみ
json
{ "key1": { "id": "001", "name": "taro" }, "key2": { "id": "002", "name": "jiro" } }java
// マッピング Map<String, JsonSampleBean> map = mapper.readValue(mapJson, new TypeReference<LinkedHashMap<String,JsonSampleBean>>(){}); // Beanの定義 // 省略
- 投稿日:2021-03-29T00:19:32+09:00
【解説】AtCoder Beginner Contest 197【A~C問題】
はじめに
オイイイイイイイイイイイイッス!どうも、歌うプログラマのことり兄貴(・8・)です!
「AtCoder Beginner Contest 197」、お疲れさまでした~
昨日はかなりやらかしました...レート-30...
バタバタしてたので動画もないですw
まあ2完でしたが、Cまで書いてみたいと思います。今回はJavaとPythonの両方で書いてみます。
目次
1.A - Rotate
2.B - Visibility
3.C - ORXOR
4.おわりに1. A - Rotate
問題
長さ3の文字列Sが与えられます。
Sの先頭の文字を末尾に移動した文字列を出力してください。制約
・Sは英小文字のみからなる長さ3の文字列
A問題リンク考察
文字列のインデックス番号で考えます。
3文字の文字列の元のインデックス番号は0, 1, 2とする
↓
先頭(0)を末尾に移動
↓
1, 2, 0の順で並ぶことり兄貴(・8・)の解答(Java)
Javaimport java.util.*; class Main { public static void main(String[] ktr) { Scanner sc = new Scanner(System.in); char[] c = sc.next().toCharArray(); System.out.println(new String(c, 1, 2) + c[0]); } }char同士の足し算だと文字コードの和を出力してしまうので、インデックス1から2文字をStringに変換し文字列の足し算にしています。
ことり兄貴(・8・)の解答(Python)
Pythons = input() print(s[1:] + s[0])PythonだとJavaでいうchar配列的な動き初めからができて、1:で1以降という表記ができるのが便利ですね。
2. B - Visibility
問題
縦H行、横W列のマス目があり、いくつかのマスには障害物(#)が置かれています。
障害物がないマスは.で表されます。
H個の文字列(S[1]~s[H])が与えられます。この文字列の上からi行目、左からj行目
をマスを(i, j)とします。現在地は(X, Y)です。
現在地から見えるマスはいくつありますか。
見えるとはXまたはYの座標が一致していて、現在地そのマスの間に障害物が
存在しないことを意味します。制約
・1 <= H <= 100
・1 <= W <= 100
・1 <= X <= H
・1 <= Y <= W
・S[i]は.と#のみで構成される長さWの文字列
・マス(X, Y)に障害物はない
B問題リンクやらかした
えー、筆者ここで盛大にやらかしました。
この問題においてXは縦の座標、Yは横の座標を示します。
問題文をよく読まずに「Yは縦の座標で、Xは横の座標」という前提でコードを書いて、40分以上苦闘しました...ふと問題をよく見て「逆だったのかあああ...」ってなりましたw
問題文はよく読みましょう(自戒)考察
まず、この系統の問題は外側を障害物(#)で囲んでおくのが無難です。
なくても色々範囲とかに気を付ければ解けなくもないと思いますが、楽に行きましょう。
囲んでしまうことで「障害物が見つかるまでループ」という条件がすべてのテストケースに対応できるようになります。
囲んでからは
・縦の座標を固定した状態で現在地から障害物の直前まで左に進む。
-->見つかったら折り返し始める。障害物のないマスを1つ通過するたびに答えを1増やす。
・横の座標を固定した状態で現在地から障害物の直前まで上に進む。
-->見つかったら折り返し始める。障害物のないマスを1つ通過するたびに答えを1増やす。
とすれば答えが出ます。ただし、この数え方では初期のマス(X, Y)を2回数えてしまっているので出力の際は-1を忘れずに。
ことり兄貴(・8・)の解答(Java)
Javaimport java.util.*; class Main { public static void main(String[] ktr) { Scanner sc = new Scanner(System.in); int h, w, x, y, ans, i; h = sc.nextInt(); w = sc.nextInt(); x = sc.nextInt(); y = sc.nextInt(); ans = 0; char[][] map = new char[h + 2][w + 2]; Arrays.fill(map[0], '#'); for (i = 1; i <= h; i++) map[i] = ('#' + sc.next() + '#').toCharArray(); Arrays.fill(map[h + 1], '#'); i = y; while (map[x][i - 1] != '#') i--; while (map[x][i] != '#') { ans++; i++; } i = x; while (map[i - 1][y] != '#') i--; while (map[i][y] != '#') { ans++; i++; } System.out.println(ans - 1); } }mapって名付けてる二次元配列がS[1]~S[H]を含む配列です。
forやwhileで{}省略して横にくっつけてるのはご了承くだされ~ことり兄貴(・8・)の解答(Python)
Pythonh, w, x, y = map(int, input().split()) ans = 0 s = [] s.append('#' * (w + 2)) for _ in range(h): s.append('#' + input() + '#') s.append('#' * (w + 2)) i = y while s[x][i - 1] != '#': i -= 1 while s[x][i] != '#': ans += 1 i += 1 i = x while s[i - 1][y] != '#': i -= 1 while s[i][y] != '#': ans += 1 i += 1 print(ans - 1)横にちょっと短くなるぐらいでJavaとそんなに変わりませんね。
3. C - ORXOR
問題
長さNの数列Aが与えられます。
この数列を、1つ以上の空でない連続した区間に分けます。
その後、分けた各区間で、区間内の数のビット単位ORを計算します。
こうして得られたすべての値のビット単位XORとして考えられる最小値を求めてください。制約
・1 <= N <= 20
・0 <= A[i] <= 2の30乗
・入力に含まれる値は全て整数である考察
制約の1つ目(1 <= N <= 20)は、「あ、ビット全探索っぽいな」ってなるやつで、2つ目(0 <= A[i] <= 2の30乗)は「32ビット型(Javaでいうint)に収まる値が入力されるんだなぁ」ってなるやつです。
「1つ以上の空でない連続した区間に分ける」というのは
1 2 3という数列があるなら
※ここでは | を仕切りとして使います
1 2 3
1 2|3
1|2 3
1|2|3
といったようにグループ分けするイメージですね。
この仕切りをビット全探索(後述)用いることによって再現します。OR ? XOR?
区間に分けることについては考えましたが、そもそも「ORとかXORってなに?」ってことについて軽く説明します。
ORは論理和、XORは排他的論理和と呼ばれるものです。OR
A OR BとはAとBを2進数表記したときにAとBのどちらかに1がある桁は1、どちらも0の桁は0とするものです。
例えば3 OR 5を考えると
3 -> 011
5 -> 101
7 <- 111
といった感じです。XOR
A XOR BとはAとBを2進数表記したときにAとBの一方のみが1である桁は1、AとBに同じ数がある桁は0とするものです。
例えば3 XOR 5を考えると
3 -> 011
5 -> 101
6 <- 110
といった感じです。ただこれは式さえ書けばあとはプログラムが処理してくれるので、軽く「そうなんだ」ぐらいで大丈夫です。
A OR BはA | B
A XOR B は A ^ B
と表現します。ビット全探索?
ビット全探索は、ざっくりいうと「全部の組み合わせを試してみる」方法です。
この問題で言う、「仕切りの有無」をビットを用いて
・その桁が0なら仕切りはない
・その桁が1なら仕切りがある
といった感じで表します。具体的には、
1 2 3
0 01 2|3
0 11|2 3
1 01|2|3
1 1
といった感じです。今挙げた例では仕切りは2つ、組み合わせは4つでした。
このように組み合わせの数は仕切りをM個としたとき、
2のM乗あります。
コード中にある1 << (n - 1)は1を左にn - 1回移動した2進数を表しているのですが、
1 << mは2のm乗に等しいです。2の2乗 = 4
1 << 2
100(2進数) = 4 + 0 + 0 = 4(10進数)ことり兄貴(・8・)の解答(Java)
Javaimport java.util.*; class Main { public static void main(String[] ktr) { Scanner sc = new Scanner(System.in); int n, a[], ans, i, j, xored, ored; n = sc.nextInt(); a = new int[n]; ans = Integer.MAX_VALUE; for (i = 0; i < n; i++) a[i] = sc.nextInt(); for (i = 0; i < 1 << (n - 1); i++) { xored = 0; ored = 0; for (j = 0; j <= n; j++) { if (j < n) ored |= a[j]; if (j == n || (i & 1 << j) > 0) { xored ^= ored; ored = 0; } } ans = Math.min(ans, xored); } System.out.println(ans); } }ことり兄貴(・8・)の解答(PyPy3)
PyPy3n = int(input()) a = list(map(int, input().split())) ans = 2**32 for i in range(1 << (n - 1)): xored = 0 ored = 0 for j in range(n + 1): if j < n: ored |= a[j] if j == n or i & 1 << j: xored ^= ored ored = 0 ans = min(ans, xored) print(ans)Pythonで提出するとTLEになってしまう様です。
この問題はPyPy3で提出しましょう。4. おわりに
いやぁ、やらかしました。XとY逆...改めて問題をじっくり読むことの大切さを実感しました。
この記事を見てる方の参考にもなれば幸いです!!