- 投稿日:2020-06-03T23:42:45+09:00
Eclipseでビルドした.warファイルをEC2上に配置して実行してみるとUnsupportedClassVersionErrorが出力された
環境
実行環境
AWS EC2 t2.micro
Amazon Linux 2
Apache Tomcat/9.0.34
# java -version openjdk version "1.8.0_252-debug" OpenJDK Runtime Environment (build 1.8.0_252-debug-b09) OpenJDK 64-Bit Server VM (build 25.252-b09-debug, mixed mode) # javac -version javac 1.8.0_252-debugビルド環境(ローカル環境)
Windowsコマンドプロンプトで確認したJavaのバージョン
> java -version java version "1.8.0_231" Java(TM) SE Runtime Environment (build 1.8.0_231-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode) > javac -version javac 1.8.0_231経緯
Webアプリ開発の勉強のため、Eclipse上で動的Webプロジェクト「example」を作成し、
サーブレット&JSPでサンプルアプリケーション「HealthCheck」を作成した。
ブラウザから「http://localhost:8080/example/HealthCheck
」で正常動作を確認。
ローカルではちゃんと動く。EC2にデプロイして動くか確認したかったため、Eclipse上でプロジェクトごと
.warファイルにエクスポートしEC2上へ配置。[root@ip-10-0-0-50 webapps]# ls -l /opt/apache-tomcat/webapps total 24 drwxr-x--- 16 tomcat tomcat 4096 Apr 24 15:28 docs drwxr-x--- 5 tomcat tomcat 123 Jun 3 12:55 example drwxr-x--- 6 tomcat tomcat 83 Apr 24 15:28 examples -rwxr-xr-x 1 tomcat tomcat 18910 Jun 3 12:55 example.war ←★ drwxr-x--- 5 tomcat tomcat 87 Apr 24 15:28 host-manager drwxr-x--- 5 tomcat tomcat 103 Apr 24 15:28 manager drwxr-x--- 3 tomcat tomcat 283 Apr 24 15:28 ROOT [root@ip-10-0-0-50 webapps]# ls -l /opt/apache-tomcat/webapps/example total 16 drwxr-x--- 2 tomcat tomcat 31 Jun 3 12:55 css -rw-r----- 1 tomcat tomcat 761 May 27 00:08 Ex5_2.jsp -rw-r----- 1 tomcat tomcat 493 May 26 23:31 formSample.jsp -rw-r----- 1 tomcat tomcat 140 May 25 21:06 hello.html drwxr-x--- 2 tomcat tomcat 44 Jun 3 12:55 META-INF -rw-r----- 1 tomcat tomcat 670 May 26 18:38 sample.jsp drwxr-x--- 5 tomcat tomcat 43 Jun 3 12:55 WEB-INFtomcat.serviceのリスタートを行い、ステータスチェック
[root@ip-10-0-0-50 webapps]# systemctl status tomcat.service ● tomcat.service - Apache Tomcat Web Application Container Loaded: loaded (/usr/lib/systemd/system/tomcat.service; enabled; vendor preset: disabled) Active: active (exited) since Wed 2020-06-03 13:12:27 UTC; 1h 4min ago Process: 4100 ExecStop=/opt/apache-tomcat/bin/shutdown.sh (code=exited, status=0/SUCCESS) Process: 4129 ExecStart=/opt/apache-tomcat/bin/startup.sh (code=exited, status=0/SUCCESS) Main PID: 4129 (code=exited, status=0/SUCCESS) CGroup: /system.slice/tomcat.service mq4144 /usr/bin/java -Djava.util.logging.config.file=/opt/apache-tomcat/conf/loggin... Jun 03 13:12:27 ip-10-0-0-50.ap-northeast-1.compute.internal systemd[1]: Starting Apache Tomca... Jun 03 13:12:27 ip-10-0-0-50.ap-northeast-1.compute.internal systemd[1]: Started Apache Tomcat... Hint: Some lines were ellipsized, use -l to show in full.発生事象
EC2にアタッチしたEIP宛てにブラウザから接続したところ「500 – Internal Server Error」が発生
http://[EIP]:8080/example/HealthCheck
HTTPステータス 500 – Internal Server Error
タイプ: 例外報告
メッセージ: Error instantiating servlet class [servlet.HealthCheck]
説明:サーバーは予期しない条件に遭遇しました。それはリクエストの実行を妨げます。
例外:
javax.servlet.ServletException: Error instantiating servlet class [servlet.HealthCheck]
(中略)
根本原因:
java.lang.UnsupportedClassVersionError: servlet/HealthCheck has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 (unable to load class [servlet.HealthCheck])
(中略)
注意: 原因のすべてのスタックトレースは、サーバのログに記録されていますApache Tomcat/9.0.34
調査
Javaのバージョン差異によるもの?
この「UnsupportedClassVersionError」は、
Java仮想マシンが、クラス・ファイルの読込み中に、そのファイルのメジャー・バージョン番号と
マイナー・バージョン番号がサポートされていないと判定した場合にスローされます。というもの。
つまり、ビルド環境と実行環境のJavaのバージョン差異が原因と考えられる。
冒頭の「環境」に記載した通りビルド環境のjavacのバージョンは
javac 1.8.0_231
実行環境のjavaのバージョンはopenjdk version "1.8.0_252-debug"
となっているため、問題無さそう・・・コンパイルされたclassファイルのバージョンを調べてみる
[root@ip-10-0-0-50 webapps]# javap -v ./example/WEB-INF/classes/servlet/HealthCheck.class Classfile /opt/apache-tomcat-9.0.34/webapps/example/WEB-INF/classes/servlet/HealthCheck.class Last modified Jun 4, 2020; size 1988 bytes MD5 checksum 83f908e6070439cc6281b73baae5306d Compiled from "HealthCheck.java" public class servlet.HealthCheck extends javax.servlet.http.HttpServlet minor version: 0 major version: 55 ←★ flags: ACC_PUBLIC, ACC_SUPERメジャーバージョンが" 55 "になってる!!
一方で、実行環境のJREバージョンは1.8。こちらのサイト様によると、
JREバージョン1.8がサポートするクラスファイルバージョンは"52"とのこと。エラーの内容が把握できました。
で、冒頭の「ビルド環境」のところで確認したJavacのバージョンは、Windows全体として確認した
(スタートメニューから開いたcmdで確認した)結果であって、Eclipse上でプロジェクトの
右クリックメニューに出てくる「コマンドプロンプト」で確認できる内容とは異なることが分かった。>javac -version javac 11.0.5 D:\31_Pleiades\workspace\example>java -version openjdk version "11.0.5" 2019-10-15 OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)これを見ると、javacのバージョンは"11.0.5"となっている。
今使っているEclipseは「Pleiades All in One Eclipse」であり、このプラグインの中には複数バージョンのJDKが入っている。
Windowsには別口で導入したJDK1.8が存在し、パスはそちらに通っているため冒頭の確認では気づけなかった。
Eclipseでビルドする際に使用されるjavacは"11.0.5"だったために、今回の問題が発生したというわけ。
ビルドする際に使用するjavacのバージョンを"1.8"に下げることができれば今回の問題はクリアできそう。対処
ビルド時のJDKバージョンを下げてみる
リビルドしてクラスファイルのバージョンを確認する
>javap -v HealthCheck.class Classfile /D:/31_Pleiades/workspace/example/build/classes/servlet/HealthCheck.class Last modified 2020/06/04; size 1988 bytes MD5 checksum 5deb7a7fe4b855e66be9d06261116bd6 Compiled from "HealthCheck.java" public class servlet.HealthCheck extends javax.servlet.http.HttpServlet minor version: 0 major version: 52 ←★成功 flags: ACC_PUBLIC, ACC_SUPER改めて.warファイルにエクスポート→サーバ配置→アクセスしてみる
・・・
・・・
・・・成功!!
というわけで、Javaのバージョンはしっかり確認しましょう!というお話でした。。。
- 投稿日:2020-06-03T20:32:19+09:00
Javaの参照の仕組み(スタックとヒープ)
自社の後輩にJavaの参照の仕組みをスタック領域とJavaヒープ領域を用いて説明する機会がありました。
自分自身、この領域同士の関係性をまだ知らなかった頃はJavaの参照について色々と混乱していた記憶があります。
良い機会なので記事にまとめてみようと思います。Javaの参照を実現する仕組み
Javaの参照を実現する為にJVMにはスタック領域とJavaヒープ領域が存在します。
この2つの領域によりJavaの参照は実現されます。
領域名 概要 スタック領域 主にヒープ領域への参照情報を保持します。またプリミティブ型の値も保持します。 JAVAヒープ領域 オブジェクトの実際の値はこちらのメモリに保持します。 オブジェクトとプリミティブ型の値の保持方法の違い
Javaのオブジェクト(Classや配列など)はスタック領域にヒープ領域の参照情報を持ちます。
実際の値はヒープ領域に保持します。
プリミティブ型はオブジェクトとは異なるメモリ管理が行われます。
プリミティブ型の変数を作成するとスタック領域に値を保持します。
※ただしプリミティブ型の配列はオブジェクトとして扱われる為ヒープ領域に値を保持します。
メソッドの引数に渡す時の挙動
メソッド引数へオブジェクトやプリミティブ型の変数を渡す際
スタック領域の内容が別のスタック領域にコピーされます。
上記の様な場合、argAの参照は変数aと同じになる為
method1の内部で参照先の値を変更すると変数aにも影響を与えます。
※argBはプリミティブ型なので変数bとは切り離されています。ソースコードサンプル
ここまで記載した内容が正しいか確認するため、ソースコードとその出力結果を照らし合わせて説明していきます。
例1
ソースコードpackage test; class Class1 { String id; int value; } public class Test { public static void main(String[] args) { Class1 a = new Class1(); a.id = "ID1"; a.value = 1; // 操作【1】 Class1 b = a; // 操作【2】 b.id = "ID2"; b.value = 2; // 操作【3】 b = null; // ここで出力される値は何になるか? System.out.println("id:" + a.id); System.out.println("value:" + a.value); } }操作【2】により変数aの参照先の値も書き換えていることになります。
操作【3】により変数bの参照は削除されますがスタックが異なる変数aには影響はありません。
よって出力内容は下記の通りとなります。出力結果id:ID2 value:2例2
ソースコードpackage test; class Class1 { String id; int value; } public class Test { public static void main(String[] args) { Class1 a = new Class1(); a.id = "ID1"; a.value = 1; // 操作【1】 method1(a); // ここで出力される値は何になるか? System.out.println("id:" + a.id); System.out.println("value:" + a.value); } private static void method1(Class1 argA) { // 操作【2】 argA.id = "ID2"; argA.value = 2; } }【操作内容】
操作【2】により変数aの参照先の値も書き換えていることになります。
よって出力内容は下記の通りとなります。出力結果id:ID2 value:2例3
ソースコードpackage test; class Class1 { String id; int value; } public class Test { public static void main(String[] args) { Class1 a = new Class1(); a.id = "ID1"; a.value = 1; // 操作【1】 method1(a); // ここで出力される値は何になるか? System.out.println("1回目========="); System.out.println("id:" + a.id); System.out.println("value:" + a.value); // 操作【2】 method2(a); // ここで出力される値は何になるか? System.out.println("2回目========="); System.out.println("id:" + a.id); System.out.println("value:" + a.value); } private static void method1(Class1 argA) { argA = null; } private static void method2(Class1 argA) { argA = new Class1(); argA.id = "ID2"; argA.value = 2; } }【操作内容】
例2と同じ様にメソッドの引数として変数aを渡しています。
操作【1】のメソッドはnullを代入してargAの参照を削除していますがスタックが異なる変数aに影響はありません。
操作【2】のメソッドは新たにClass1をnewしているため参照先が変数aと変わっています。
参照先が変わった後にidとvalueに値を設定しても変数aに影響はありません。
よって出力内容は下記の通りとなる。出力結果1回目========= id:ID1 value:1 2回目========= id:ID1 value:1例4
ソースコードpackage test; public class Test { public static void main(String[] args) { int a = 1; // 操作【1】 int b = a; // 操作【2】 b = 99; // ここで出力される値は何になるか? System.out.println("1回目========="); System.out.println("a:" + a); System.out.println("b:" + b); // 操作【3】 method1(a); // ここで出力される値は何になるか? System.out.println("2回目========="); System.out.println("a:" + a); } private static void method1(int argA) { // 操作【4】 argA = 99; } }【操作内容】
プリミティブ型の変数はそれぞれ独立してスタック領域に値を保持します。
それぞれの操作が別の変数に影響を及ぼす事はありません。
よって出力内容は下記の通りとなる。出力結果1回目========= a:1 b:99 2回目========= a:1例5
ソースコードpackage test; public class Test { public static void main(String[] args) { int a = 0; // 操作【1】 int[] b = {a}; // 操作【2】 method1(b); // ここで出力される値は何になるか? System.out.println("a:" + a); System.out.println("b[0]:" + b[0]); } private static void method1(int[] argB) { // 操作【3】 argB[0] = 99; } }【操作内容】
プリミティブ型でも配列として保持するとその値はヒープ領域に保存されます。
操作【3】で配列argBに変更を行うと同じ参照先を持つ配列bに影響を与えます。
よって出力内容は下記の通りとなる。出力結果a:0 b[0]:99
- 投稿日:2020-06-03T18:32:15+09:00
GoogleMapsAPIのJavaSDKを使って、逆GeoCodingの結果を日本語で取得する。
はじめに
GoogleMapsAPIを使って、緯度経度から住所を取得する処理を書きました。
GoogleからJavaのSDKが提供されています。
日本語で住所を返す設定のメモ書きです。JavaのSDK
- Java Client for Google Maps Services
https://github.com/googlemaps/google-maps-services-java
ソースコード
public String reverceGeocoding(double lat, double lon) { LatLng latlng = new LatLng(lat, lon); GeoApiContext context = new GeoApiContext.Builder().apiKey(apikey).build(); GeocodingApiRequest request = GeocodingApi.reverseGeocode(context, latlng).language("ja"); GeocodingResult[] results; String address = null; try { results = request.await(); Gson gson = new GsonBuilder().setPrettyPrinting().create(); address = gson.toJson(results[0].formattedAddress); } catch (ApiException | InterruptedException | IOException e) { e.printStackTrace(); } finally { context.shutdown(); } return address; }おわりに
作ってしまえばなんてことないのですが、languageの指定方法は調査しました。覚え書きとして残しておきます。
- 投稿日:2020-06-03T17:10:50+09:00
Gradleでdocker環境を構築する方法 for intelliJ
spring-boot-doma2-sampleリポジトリを参考にしました。
build.gradleを新しい書き方にしました。Spring Initializr
まずは下記のサイトでspringアプリケーションを作成します。
https://start.spring.io/アプリケーションで使用するプラグインをDependenciesに追加します。
- Spring boot DevTools
- Spring Web
- Thymeleaf
- Spring Data JDBC
- Flyway Migration
- MySQL Driver
最後に下部にあるGENERATEをクリックし、ダウンロードされzipファイルを解凍して適当なディレクト名に変更します。
initializrではdocker-composeが出てこないので、build.gradleに追加します。
intelliJでプロジェクトを開く
オープンまたはインポートで、前述の名前を変えたディレクトリを選択します。
build.gradleを変更する
プロジェクト内のbuild.gradleを開きます。
以下のようになっているので、// 追加
の部分を追記してください。build.gradleplugins { id 'java' id 'org.springframework.boot' version '2.3.1.BUILD-SNAPSHOT' id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'com.avast.gradle.docker-compose' version '0.12.1' // 追加 id 'org.flywaydb.flyway' version '6.4.3' } // ...略 dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.flywaydb:flyway-core' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'mysql:mysql-connector-java' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } }gradleのプラグインを調べるサイト→Search Gradle plugins
dockerの設定
dockerディレクトリをプロジェクト内に作成し、docker-compose.ymlを作成します。
docker-compose.ymlversion: '2' services: todo-db: build: ./mysql # Dockerfileがあるディレクトリを指定する environment: - MYSQL_DATABASE=todo - MYSQL_ROOT_USER=root - MYSQL_ROOT_PASSWORD=passw0rd - TZ=Japan image: docker_todo-db container_name: todoDb ports: - "33068:3306" # 33068をすでに使っている場合は別の番号に変更するmysqlディレクトリを作成し、Dockerfileとmy.cnfを作成します。
DockerfileFROM mysql:5.7 RUN /bin/cp -f /etc/localtime /etc/localtime.org RUN /bin/cp -f /usr/share/zoneinfo/Asia/Tokyo /etc/localtime COPY ./my.cnf /etc/mysql/conf.d/ RUN mkdir -p /var/log/mysql RUN chown mysql.mysql /var/log/mysqlmy.cnf[mysqld] default-storage-engine=InnoDB user=mysql character-set-server=utf8mb4 skip-character-set-client-handshake ;general_log=1 ;general_log_file=/var/log/mysql/query.log long_query_time=3 slow_query_log_file=/var/log/mysql/slow-query.log max_allowed_packet=16MB innodb_file_per_table innodb_buffer_pool_size=64M sort_buffer_size=2M read_buffer_size=2M read_rnd_buffer_size=2M group_concat_max_len=2048 [mysqld_safe] log-error=/var/log/mysqld.log [mysql] default-character-set=utf8mb4Dockerを起動
intelliJでターミナルを開き
./gradlew composeUp
を実行します。
下記のようなログが表示されれば完了です。> Task :composeUp Building todo-db ...省略 TCP socket on localhost:33068 of 'todoDb' is ready +--------+----------------+-----------------+ | Name | Container Port | Mapping | +--------+----------------+-----------------+ | todoDb | 3306 | localhost:33068 | +--------+----------------+-----------------+ BUILD SUCCESSFUL in 15sテーブルの確認
dockerコンテナ内のMySQLにテーブルが作成されているか確認します。
IntelliJのターミナルを使用します。
docker ps
を実行します。以下のように起動しているコンテナが表示されていることを確認します。$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 149c67cc70d5 docker_todo-db "docker-entrypoint.s…" 17 seconds ago Up 15 seconds 33060/tcp, 0.0.0.0:33068->3306/tcp todoDb
docker exec -it todoDb /bin/bash
を実行して、コンテナの中に入ります。
root@<CONTAINER ID>:/#
となり、コンテナの中にいることがわかります。$ docker exec -it blogDb /bin/bash root@149c67cc70d5:/#
mysql -u root -p
でMySQLにアクセスします。
パスワードを求められるので、ここではdocker-compose.ymlで指定した「passw0rd」を入力します。MYSQL_ROOT_PASSWORD=passw0rd
$ mysql -u root -p Enter password:※macだとカーソルなどは表示されません
mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todo | +--------------------+ 5 rows in set (0.00 sec)todoというテーブルがちゃんと作成されています。
次の記事ではflyway-coreを使ったmigrationの自動化をしたいと思います。
- 投稿日:2020-06-03T16:45:37+09:00
java 何度も検索してしまうもの【java初心者】
随時追加予定.
1, length の取り方
int 配列
int [] intArray = {1,2,3,4,5}; System.out.println(intArray.length);出力: 5
String 文字列
String text = "text"; System.out.println(text.length());出力: 4
StringBuilder型 文字列
StringBuilder sb = new StringBuilder(); sb.append("text"); System.out.println(sb.length());出力: 4
2, 型変換
String >> int 変換
String StringA = "0"; int intA = Integer.parseInt(StringA);int >> String 変換
int intB = 0; String stringB = String.valueOf(intB);int >> Integer 変換
int intC = 0; Integer integerC = intC;Integer >> int 変換
Integer integerD = 0; int intD = integerD.intValue();
- 投稿日:2020-06-03T12:41:53+09:00
CVE-2019-3799 Spring-Cloud-Config-Server 路径穿越/任意文件读取
复现
idea创建一个spring项目,然后pom.xml中加入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> <version>2.0.2.RELEASE</version> </dependency>配置application.properties
server.port=8888 spring.cloud.config.server.git.uri=https://github.com/SukaraLin/awesome-cve-poc.git然后访问
http://127.0.0.1:8888/aaa/bbb/master/..%252F..%252F..%252F..%252F..%252F..%252Fwindows/win.ini
分析
Spring Cloud Config Server是Spring为了分布式管理的一个组件,在Server端中负责存储配置,Client可以通过http的形式获取配置值。payload对应的路由存在于
org.springframework.cloud.config.server.resource.ResourceController#retrieve()
中,其代码@RequestMapping({"/{name}/{profile}/{label}/**"}) public String retrieve(@PathVariable String name, @PathVariable String profile, @PathVariable String label, HttpServletRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException { String path = this.getFilePath(request, name, profile, label); return this.retrieve(name, profile, label, path, resolvePlaceholders); }
{name}/{profile}/{label}
:name对应仓库名,profile对应配置文件,label为git分支名,一般上都有一个master分支。实际测试中name、profile值无所谓,但是label分支名必须存在。调试跟进getFilePath
这边已经把%25
urldecode为%
了,继续跟retrieve(),这边path传入的是我们的payload继续跟下findOne,位于
org.springframework.cloud.config.server.resource.GenericResourceRepository#findOne
locations目录file:/C:/Users/icu/AppData/Local/Temp/config-repo-6608031716294156148/
是配置文件中git仓库被clone的临时位置,然后拼接../
造成跨目录文件读取。修复
https://github.com/spring-cloud/spring-cloud-config/commit/3632fc6f64e567286c42c5a2f1b8142bfde505c2
通过判断是否存在
%
来urldecode,然后匹配../
、..
、/
等特殊字符。参考
- 投稿日:2020-06-03T10:02:40+09:00
Java Hack
Java Hackとは
Javaのいわゆる案件に入るための準備
Javaはお金になるし、面白い案件に入れると(信じる)
← 作ってからkotlinにすることもできると思われる。
コンテンツ
・Javaそのもの
・Spring
・SQL
・DB周り
・ネットワーク
・シラバス作成Javaそのもの
教材を使う。
https://jflute.hatenadiary.jp/entry/20200301/javatrySpring
APIから作り上げていく。
SQL
Progate道場コースまで完了
https://prog-8.com/languages/sql
・ポートフォリオの事前準備中に強化と情報収集を行う。DB周り その他
シラバス
- 投稿日:2020-06-03T09:46:42+09:00
日付の期間判定・過去未来判定について
結論
ミリ秒にして大小判定するのマジおすすめ
※Javaを例にしていますが、どの言語でも同じ話です※良い子のみんなは、java.util.Dateなんて使わずにJoda-Timeつかうか、Java 1.8以降ならTime APIを使おうネ)
※ただの参考コードです。Dateクラスの非推奨コンストラクタ使うのやめようね
Date nowDate= new Date(); // 2020/4/1 00:00:00 ~2020/4/30 23:59:59 の場合に「キャンペーン期間中だよ」と出力 if (nowDate.getTime() >= new Date(2020, 3, 1, 0, 0, 0).getTime() && nowDate.getTime() < new Date(2020, 4, 1, 0, 0, 0).getTime()) { System.out.println("キャンペーン期間中だよ"); }言い訳
皆さんは、Javaの日付の判定、どうされていますか?
java.util.Dateクラスを利用する場合、afterメソッドやbeforeメソッドを使っていますか?
Joda-TimeやTime APIでも、似たようなメソッドありますよね?でも、正直私はそのメソッドを見ただけでは「引数が未来なんだっけ?過去なんだっけ?指定された日付は含むんだっけ?含まないんだっけ?」というのを即時に判断出来ません。(アホですね)
JavaDocを見るとそれぞれこう書かれています。
(Java 1.8のJavaDocから引用)
- after
この日付が、指定された日付より後にあるかどうかを判定します。
- before
この日付が、指定された日付より前にあるかどうかを判定します。
つまり、どちらも「指定された日そのもの」は含まないわけです。
英語が得意な方は、afterやbeforeが「含まない」という意味だと理解しているかもしれないですが、
僕は知りませんでした。よくやるポカとして、
「2020年4月1日の00:00:00~2020年4月30日の23:59:59までをキャンペーンの申し込み期間としよう!」とした場合にDate nowDate = new Date(); if (nowDate.after(new Date(2020, 3, 1, 0, 0, 0)) && nowDate.before(new Date(2020, 4, 1, 0, 0))) { System.out.println("キャンペーン期間中だよ"); }こんな風にしちゃって、「日付変わった瞬間は申し込み出来ねーじゃん!!!!!」っていうバグを作り込んじゃいます。
(まあ、その1秒を良しとするかどうかはプロジェクト次第ですが)だから、数値型にしちゃって大小比較したほうが間違いが無いよね。っていう話でした。
- 投稿日:2020-06-03T02:10:04+09:00
[Java] 天パの人による適当な入門 Java編 その0 (コードの決まり事)
コードの決まり
その0は、番外編というか、なんとなくわかっていくだろうけどめっちゃ大切なことを書いています。
本編はその1からです。シリーズの説明なども書いています。
https://qiita.com/Shodai-Kurasaki/items/99cf6b0ec1a8e80640b1{ } について
Sample1.javaclass Sample1 { public static void main(String[] args) { System.out.println("Hello World!"); } }このようにコード内には{ }が存在しています。たくさんコードをうつしていくと大体どこにあるかがわかってきますが、覚えてほしいのは、
{と} の数が必ず同じになるようにする
ということです(例外:文字としての { は数えません)。すぐにこのセットをたくさん使うプログラムを書くことになるとおもいますが、その時1つでも足りなければもう動きません。
それの防止のために{ }のセットごとに階段状にしています。
tabキーを一回押すのが普通です。; について
System.out.println("Hello World!");←これ
ひとつの文の終わりには基本的に必ずつけます。例外は直後に{ }がついてるものです。class Sample1 { ←このように{}がうしろについてくるやつ以外は ; をつけます。これがないと動きません。
因みに、これの後ろに改行を入れないと動かないわけではなく、System.out.println("Hello World!"); System.out.println("Hello World! 2");としても問題ありません。ただ、基本的にはみにくくなるので改行しちゃってください。
ファイル名とクラス名
Sample1.javaclass Sample1 ←これがクラス名 { public static void main(String[] args) { System.out.println("Hello World!"); } }クラス名はファイル名と同じにしてください。違うものにすることもあるのですが、最初は何も考えずに同じにしてください。ちなみにファイルの拡張子は.javaで保存するようにします。
コメント文
プログラム内で、だんだんメモを残したいところが出てくるようになります。「この文はHello World!と表示するための文だ」などです。
そういう時は // をかいてそのあとにメモを書きます。// のあとはプログラム実行時には完全に無視されるので好きにかいて構いません。しかし、// はその行の//以降の内容しかメモにしません。
複数行にわたってメモをしたい場合は/* */でメモをはさみます。Sample1.javaclass Sample1 { public static void main(String[] args) { System.out.println("Hello World!"); //ここはメモ /*ここも~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~メモ*/ } }おわり
もし訂正すべき点などありましたらご指摘よろしくお願い致します。