Javaは同じ処理を何通りにも書ける言語です。動くかどうかではなく、半年後に読み返して安全に直せるかどうかで書き方が決まります。この記事では、実務でレビュー指摘が集中する場面をJavaのコード例で並べ、なぜその書き方を選ぶのかまで示しました。バージョンによって使える構文が変わる箇所には、導入されたJavaのバージョンを明記しています。Java 8で止まっている現場でも、そのまま判断材料にできる構成です。
まとめ
- 文字列の比較は
==ではなくequalsを使い、null対策はObjects.equalsかリテラル側を左に置く書き方で行います。 Arrays.asListが返すのは不変リストではなく固定長リストです。変更不可を保証したいならList.of(Java 9以降)を使います。- Mapの反復は
entrySetが既定です。keySetとキーによる取得を組み合わせると探索が2回走ります。 - 拡張forの途中で要素を削除すると
ConcurrentModificationExceptionになります。ただし末尾から2番目を消したときだけ例外が出ず、要素が静かに読み飛ばされます。削除条件があるならremoveIfを使います。 - 命名とインデントは自分で決めずGoogle Java Style Guideに寄せると、レビューの争点が減ります。
- 速さの主張は
System.nanoTimeのループ計測ではなくJMHで測ってから書きます。
以下では、この6点それぞれについて動かせるコード例と判断基準を示します。
読みやすいJavaコードを決める3つの観点
「きれいなコード」は感覚語なので、レビューで揉めます。可読性・保守性・拡張性を別々の性質として分けて扱うと、指摘が具体的になります。
可読性・保守性・拡張性の優先順位
可読性は、初見の読者がメソッドを上から下に読んで処理を追えることです。保守性は、仕様変更のときに影響範囲がコードから読み取れるかどうかを指します。拡張性は、既存のクラスを触らずに機能を足せる度合いです。3つは別々に評価できます。
優先順位は可読性、保守性、拡張性の順で固定して構いません。読めないコードは安全に直せませんし、直せないコードに機能を足すと壊れるからです。逆に、拡張性を先に置いて抽象クラスやインターフェースを先回りで用意すると、使われない拡張点だけが残ります。将来の要件が確定していない段階で拡張点を作るのは避けてください。
インデントと桁数はスタイルガイドで固定
Google Java Style Guideは4.2で「ブロックまたはブロック相当の構造が開くたびに、インデントを2スペース増やす」と定め、4.4で「Javaコードの桁数上限は100文字」と定めています。2スペースか4スペースかという値そのものより、リポジトリ全体で統一されていることに意味があります。
public class ShipmentService {
public void cancel(Shipment shipment) {
if (shipment.isShipped()) {
throw new IllegalStateException("shipped order cannot be cancelled");
}
shipment.markCancelled();
}
}
手で揃えるとレビューで指摘する手間が増えます。フォーマッタの設定ファイルをリポジトリに置き、ビルドで検証まで通して、インデントの議論を設定ファイルに一度だけ集約してください。
「なぜ」を書くコメントと公開APIのJavadoc
何をしているかはコードが表しています。コメントに書く価値があるのは、その書き方を選んだ理由、外部仕様に由来する制約、あえて残しているトレードオフです。
// 決済APIが同一注文IDの二重送信を拒否するため、リトライ前に必ず状態を再取得する
Order latest = orderRepository.findById(order.getId());
他チームが呼ぶ公開APIには、引数と戻り値と例外をJavadocで書きます。タグの使い分けと生成手順はJavadocの書き方|コメント・タグの記述例と生成方法を解説で整理しています。privateメソッドまで機械的にJavadocを付けるのは、更新されない説明が増えるだけなので不要です。
命名規則のコード例:パッケージ・クラス・変数・定数
命名は好みの問題に見えて、実際は言語仕様と広く使われるスタイルガイドで大半が決まっています。
パッケージ名は逆ドメインの小文字
Java言語仕様6.1は、一意なパッケージ名の作り方を「インターネットドメイン名を要素ごとに逆順にし、それを接頭辞として使う」と定め、さらに「一意なパッケージ名の第1要素は常に全小文字のASCII文字で書く」としています。Google Java Style Guide 5.2.1も「パッケージ名とモジュール名には小文字と数字だけを使い、アンダースコアは使わない」と規定しています。
package com.example.billing.invoice;
複数語をつなぐ場合もアンダースコアやキャメルケースにはせず、そのまま連結します。ドメインを持たない社内システムでも、社名や事業部名から逆ドメイン形式を決めておくと、後でライブラリとして切り出すときに衝突を避けられます。
クラス・メソッド・変数・定数の書き分け
| 対象 | 記法 | 例 |
|---|---|---|
| クラス・インターフェース | UpperCamelCase | OrderService |
| メソッド・変数 | lowerCamelCase | calculateTotal |
| 定数(static final) | 大文字とアンダースコア | MAX_RETRY_COUNT |
| パッケージ | 小文字と数字のみ | com.example.billing |
| 型変数 | 1文字または末尾T | T、E、RequestT |
変数名では、booleanを isShipped や hasChildren のように述語にすること、単位を名前に含めること(timeout ではなく timeoutMillis)の2点で、読み違いによる取り違えを防げます。社内でしか通じない略語は、命名規則の適用範囲外として避けてください。
文字列比較のコード例:==とequalsの違い
Javaで最も再発する不具合が、文字列を == で比較して条件が成立しないパターンです。原因は比較演算子の意味にあります。
参照比較になる理由と文字列リテラルのプール
== は参照が同一かどうかを判定します。ソースに直接書いた文字列リテラルは定数プールで共有されるため同じ参照になりますが、new String や入力から組み立てた文字列は別インスタンスです。
String a = "example";
String b = "example";
String c = new String("example");
System.out.println(a == b); // true(同じリテラルなので同一参照)
System.out.println(a == c); // false(別インスタンス)
System.out.println(a.equals(c)); // true(内容の比較)
ローカルで動いたのに本番で条件が通らない、という不具合はこの取り違えが原因です。リテラル同士の比較でたまたま true になっていただけで、値が外部入力に変わった瞬間に壊れます。文字列の内容を比べるなら例外なく equals を使ってください。
NullPointerExceptionを避ける2つの書き方
変数がnullになりうる場合、input.equals("example") は例外になります。定数側を左に置くか、Objects.equals(Java 7以降)を使えば回避できます。
String input = request.getParameter("mode");
String expected = "example";
// 1: 定数を左に置く。input が null でも false を返す
if ("example".equals(input)) {
System.out.println("match");
}
// 2: どちらが null でもよい場合。両方 null なら true
if (Objects.equals(input, expected)) {
System.out.println("equal");
}
2つの違いは、nullどうしをどう扱うかです。定数を左に置く書き方はnull入力を「不一致」として落としますが、Objects.equals はnullどうしを一致と判定します。未設定を一致扱いしてよいかは仕様次第なので、選ぶ前に確認してください。大文字小文字を無視するなら equalsIgnoreCase、順序が必要なら compareTo を使います。
longとLongで結果が変わる境界
数値の比較でも同じ落とし穴があります。プリミティブの long は == で値を比較しますが、ラッパーの Long は参照比較です。やっかいなのは、小さい値では期待どおり動いてしまうため、テストをすり抜ける点です。
Java言語仕様5.1.7は、boolean・byte・char・short・int・longの定数式をボクシングした結果について「その結果がtrue、false、U+0000からU+007Fの範囲の文字、あるいは-128から127の範囲の整数であれば、2回のボクシング変換の結果 a と b は常に a == b である」と定めています。long の定数式も対象です。したがって127Lまでは == がtrueになることが仕様で保証されます。一方128以上については、同じ節が「それ以外の値については、ボクシングされた値の同一性に関する一切の仮定を許さない」とし、long値を-32Kから+32Kの範囲までキャッシュする実装もありうると述べています。128以降がfalseになるかどうかは実装依存です。
Long x = 127L;
Long y = 127L;
System.out.println(x == y); // true(仕様上 a == b が保証される範囲)
Long p = 128L;
Long q = 128L;
System.out.println(p == q); // false(OpenJDKの既定。仕様は同一性を規定していない)
System.out.println(p.equals(q)); // true
System.out.println(p.longValue() == q.longValue()); // true
実装依存の値に条件分岐を委ねないのが要点です。ラッパー型どうしを比べるときは equals か longValue() による値の取り出しを使ってください。IDや金額をLongで持ち回る設計では、この境界がそのまま本番障害になります。
ループのコード例:拡張for・インデックスfor・Streamの使い分け
Javaのループは3系統あり、それぞれ使うべき場面が違います。全部を拡張forに寄せると、添字が必要な処理で書きにくくなります。
拡張forの書き方とコロンの意味
拡張for文はJava 5で追加された構文で、コロンは「右辺の各要素を左辺の変数に取り出す」ことを表します。日本語なら「listの中のelementごとに」と読むと意味が通るはずです。配列にも、Iterableを実装したコレクションにも同じ形で使えます。
List<String> names = List.of("sato", "suzuki", "takahashi");
for (String name : names) {
System.out.println(name);
}
String[] codes = {"A", "B", "C"};
for (String code : codes) {
System.out.println(code);
}
添字を使わないので、範囲外アクセスと添字の書き間違いが構文レベルで起きなくなります。要素を順に見るだけならこれが既定です。
配列・List・Mapそれぞれの反復と検索
配列の最大値、Listからの検索、Mapの反復は書き方が固まっているので、コード例をそのまま覚えて構いません。
import java.util.*;
// 配列の最大値
int[] scores = {58, 91, 74};
int max = Arrays.stream(scores).max().getAsInt(); // 引数なしの orElseThrow() はJava 10以降
// List から条件に合う最初の要素を探す
Optional<String> found = names.stream()
.filter(n -> n.startsWith("s"))
.findFirst();
// Map の反復は entrySet
Map<String, Integer> stock = new HashMap<>();
stock.put("apple", 12);
stock.put("banana", 5);
for (Map.Entry<String, Integer> entry : stock.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
Mapで keySet を回して get を呼ぶ書き方は、キーのハッシュ探索が反復と取得の2回走ります。値が必要なら entrySet、キーだけで足りるなら keySet、値だけなら values と、必要なものを直接取るのが基本です。Setの選択で迷う場合は【Java】TreeSetとHashSetの違いと使い分け|特徴・速度・null・使い方を解説を参照してください。
ループ中の削除が例外になる条件
拡張forで回している最中にコレクション自体を変更すると、次の反復で ConcurrentModificationException が発生します。マルチスレッドでなくても起きます。
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
// 例外になる書き方
for (String s : list) {
if ("a".equals(s)) {
list.remove(s);
}
}
// 条件で削除するなら removeIf(Java 8以降)
list.removeIf(s -> "a".equals(s));
やっかいなのは、削除対象が末尾から2番目の要素だと例外が出ない点です。ArrayList のイテレータは hasNext() を「カーソル位置と要素数が違うか」で判定するため、削除でサイズが1つ減った時点で反復が終わり、変更検知が走りません。上の例で消す対象を “b” に変えると、例外は出ないまま “c” が読み飛ばされます。例外にならないぶん、発見が遅れる壊れ方です。
条件に合う要素を消すだけなら removeIf で1行になります。削除しながら他の処理も必要な場合は、Iterator を明示的に取得して Iterator.remove を呼んでください。
インデックスforを残すべき4つの場面
拡張forに寄せきれない場面もあります。添字そのものを使う場合、隣接する前後の要素を同時に参照する場合、末尾から逆順に走査する場合、2つのコレクションを同じ添字で突き合わせる場合の4つです。ここで無理に拡張forやStreamを使うと、外部変数のカウンタを持ち出すことになり、かえって読みにくくなります。
コレクション初期化のコード例:Arrays.asList・List.of・Stream.toList
「不変リストを作る」つもりで Arrays.asList を使うのは誤りです。javadocの記述と挙動を確認します。
Arrays.asListは不変ではなく固定長
JDK 25のjavadocは Arrays.asList を「指定された配列に連動する固定長のリストを返す」と説明しています。固定長であって不変ではないため、要素の追加と削除は例外になりますが、既存要素の置き換えは通ってしまいます。
String[] source = {"a", "b", "c"};
List<String> fixed = Arrays.asList(source);
fixed.set(0, "z"); // 通る
System.out.println(source[0]); // z(元の配列にも書き戻る)
fixed.add("d"); // UnsupportedOperationException
fixed.remove("b"); // UnsupportedOperationException
// 変更可能なリストが欲しい場合
List<String> mutable = new ArrayList<>(Arrays.asList("a", "b", "c"));
削除は、存在しない要素を渡したときだけ例外にならず false が返る点に注意してください。上の例で fixed.remove("a") と書くと、直前の set で “a” が消えているため何も起きません。
置き換えが通る以上、定数として公開したコレクションを Arrays.asList で作ると、呼び出し側から中身を書き換えられます。不変を意図しているなら次のList.ofを使ってください。
List.of(Java 9以降)とcopyOfの保証
Listのjavadocは、List.of と List.copyOf が返すインスタンスについて「変更不可である。要素の追加、削除、置き換えはできない。Listに対していずれかのmutatorメソッドを呼ぶと、常にUnsupportedOperationExceptionがスローされる」「null要素を許可しない。null要素を含めて生成しようとするとNullPointerExceptionになる」と明記しています。
List<String> immutable = List.of("a", "b", "c");
Map<String, Integer> config = Map.of("retry", 3, "timeout", 5000);
Set<String> roles = Set.of("admin", "editor");
immutable.set(0, "z"); // UnsupportedOperationException
List.of("a", null); // NullPointerException
null要素を弾く仕様は、設定値の取りこぼしを生成時点で落としてくれるので実務では利点になります。null混入がありうるデータをそのまま List.of に渡すと生成で落ちるため、その場合は先にフィルタしてください。なお保証されるのはリスト自体の変更不可までで、javadocも「格納された要素自体が可変であれば、Listの内容が変化したように見えることがある」と断っています。要素の中身までは守られません。
Stream.toListとCollectors.toUnmodifiableListの違い
Streamから変更不可のリストを得る方法は2つあり、null要素の扱いが違います。Stream.toList() はjavadocの@sinceが16で、説明は「返されるListは変更不可であり、mutatorメソッドの呼び出しは常にUnsupportedOperationExceptionをスローする」だけです。一方 Collectors.toUnmodifiableList() は@sinceが10で「返されるCollectorはnull値を許可せず、null値が渡されるとNullPointerExceptionをスローする」と明記されています。
import java.util.stream.Collectors;
// Java 16以降。null要素の禁止は明記されていない
List<String> a = names.stream().filter(n -> n.length() > 4).toList();
// Java 10以降。null要素があれば NullPointerException
List<String> b = names.stream()
.collect(Collectors.toUnmodifiableList());
正確には、toList() がnullを許可すると約束しているわけではありません。javadocに禁止の記載がなく、実装要件が Collections.unmodifiableList(new ArrayList<>(...)) 相当と示されているため、結果としてnullを含められます。Java 16以降を使えるなら toList() が短く、null混入を生成時に検出したいなら toUnmodifiableList() を選びます。従来の Collectors.toList() は変更可能かどうかが実装依存なので、変更不可を前提にしないでください。
Stream APIのコード例:フィルタ・変換・集計
Stream APIはJava 8で追加されました。ループを短く書く道具ではなく、絞り込みと変換と集計を宣言的に並べるための仕組みです。
filter・mapと終端操作の基本形
絞り込み、変換、収集の3段を1本につなぐのが基本形です。
List<String> fruits = List.of("apple", "banana", "cherry", "date");
List<String> longNames = fruits.stream()
.filter(s -> s.length() > 5)
.map(String::toUpperCase)
.toList(); // Java 16以降。Java 8〜15は collect(Collectors.toList())
System.out.println(longNames); // [BANANA, CHERRY]
Map<Integer, List<String>> byLength = fruits.stream()
.collect(Collectors.groupingBy(String::length));
途中の操作は終端操作が呼ばれるまで実行されません。filter や map だけ書いて結果を受け取らないコードは何も動かないので、レビューで見つけたら終端操作の付け忘れです。
平均値・合計・最大値の1行集計
平均値を求める専用メソッドは、プリミティブ特化のストリームにあります。要素が空のときに値が存在しないため、戻り値は OptionalDouble です。
List<Integer> scores = List.of(58, 91, 74);
double average = scores.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
IntSummaryStatistics stats = scores.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println(stats.getMax() + " " + stats.getSum() + " " + stats.getAverage());
合計と最大値と平均をまとめて欲しいなら summaryStatistics() で1回の走査に収まります。orElse(0.0) で空のときの値を決めていますが、件数0の平均を0点として扱ってよいかは仕様の判断です。集計対象が空になりうる画面では、0を返すか未表示にするかを先に決めてください。
parallelStreamを使ってよい条件
既定では使わない、という立場を取ります。parallelStream が効くのは、要素数が十分に多く、処理がCPUバウンドで、ソースが分割しやすい構造(ArrayListや配列)で、各要素の処理に副作用がない場合だけです。条件を外せば、分割と結合のコストで逐次処理より遅くなります。
加えて、明示的に指定しない限り共通のForkJoinPoolを使うため、アプリ全体の並列処理が同じプールを取り合います。データベースアクセスやHTTP呼び出しのような待ち時間主体の処理を並列化したい場合は、Java Virtual Thread(仮想スレッド)とProject Loomとは?作り方・仕組み・落とし穴を実務目線で解説で扱う仮想スレッドのほうが目的に合います。
設計に効くコード例:依存性注入と委譲
クラスの中で new して依存を作ると、テストで差し替えられなくなります。依存を外から渡す形にするのが依存性注入です。
コンストラクタインジェクションを既定にする理由
フィールドを final にできること、生成が終わった時点で依存が揃っていることが保証されること、フレームワークなしでも new でテストできることの3点で、コンストラクタ経由が既定です。セッター経由は、後から差し替える必要が本当にある場合に限ります。
public class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repository) {
this.repository = repository;
}
public void cancel(long orderId) {
repository.markCancelled(orderId);
}
}
Spring Frameworkのリファレンスは、コンストラクタへの @Autowired について「対象のBeanがコンストラクタを1つしか定義していない場合、そのコンストラクタへの@Autowired注釈は不要である」と記しています。この省略はSpring Framework 4.3以降で有効なので、コンストラクタが1本ならアノテーションは書かなくて構いません。Spring側の世代差についてはSpring Boot 4とは?最新バージョン4.1の変更点・新機能とSpring Boot 3との違いを解説【2026年最新】で整理しています。
JUnit 5とMockitoでの差し替え
サンプルには @RunWith(MockitoJUnitRunner.class) を使うものがありますが、これはJUnit 4用です。JUnit 5では拡張機構が変わり、mockito-junit-jupiter に含まれる MockitoExtension を使います。Mockito本体のソースにも、JUnit Jupiter版としてこの書き方が例示されています。
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository repository;
@InjectMocks
private OrderService service;
@Test
void cancelMarksOrderCancelled() {
service.cancel(1L);
Mockito.verify(repository).markCancelled(1L);
}
}
依存をコンストラクタで受け取っていれば、Mockitoを使わずに手書きのスタブを渡すだけでもテストは書けます。モックライブラリは検証したい呼び出しが多いときの道具であって、必須ではありません。
継承ではなく委譲を選ぶ判断基準
委譲は、処理を自分で持たずに別のオブジェクトへ渡す書き方です。継承と迷ったときは、is-aの関係が成り立つかで判断します。「注文サービスはリポジトリの一種である」とは言えないので、この関係は委譲です。
class AuditLogger {
void log(String message) {
System.out.println("audit: " + message);
}
}
class PaymentService {
private final AuditLogger logger;
PaymentService(AuditLogger logger) {
this.logger = logger;
}
void pay(long amount) {
logger.log("pay " + amount);
}
}
継承は親の実装変更に子が引きずられます。振る舞いを組み合わせたい、あとから差し替えたいという動機なら委譲を選んでください。型で振る舞いを切り替える設計そのものはポリモフィズム(多態性)とは?読み方・種類・Javaの実装をわかりやすく解説で扱っています。
バージョンで書き方が変わるJava構文の対応表
Javaのコード例を検索すると、Java 8前提のものとJava 21前提のものが混在して出てきます。現場のJavaバージョンで使えるかどうかを判断するために、書き方が変わった主な構文を整理します。
導入バージョン早見表
| 機能 | 使えるバージョン | 根拠 |
|---|---|---|
| 拡張for文・ジェネリクス | Java 5 | 言語仕様 |
| try-with-resources・Objects.equals | Java 7 | 言語仕様・javadoc |
| ラムダ式・Stream API・removeIf | Java 8 | 言語仕様・javadoc |
| List.of・Map.of・Set.of | Java 9 | javadoc |
| var・Collectors.toUnmodifiableList | Java 10 | JEP 286・javadoc |
| switch式 | Java 14 | JEP 361 |
| テキストブロック | Java 15 | JEP 378 |
| record・Stream.toList | Java 16 | JEP 395・javadoc |
| sealedクラス | Java 17 | JEP 409 |
| switchのパターンマッチ・仮想スレッド | Java 21 | JEP 441・JEP 444 |
LTSとサポート期限の選び方
長期サポート版はJava 8、11、17、21、25で、2026年8月時点の最新LTSは25、最新の機能リリースは26です。Java 8で止まっている現場では、上の表の3行目までがそのまま使える範囲になります。4行目の List.of はJava 9以降なので、Java 8では Collections.unmodifiableList で代替してください。
値だけを持つクラスをgetterとequalsごと自動生成できるrecordは、Java 16以降で書き方が最も大きく変わる機能です。詳細はレコードクラスとは?Javaのrecordの使い方・自動生成・制限を実装者向けに解説と、最新版の変更点をまとめた2026年3月リリースのJava 26で開発者が押さえるべき10個のJEPと全体像で扱っています。ディストリビューションとサポート期限の選定はOpenJDKは商用利用できる?無料の範囲とOracle JDKとの違い・ライセンス・サポート期限【2026年最新】を参照してください。
避けたいコード例と、その理由
最後に、レビューで差し戻す基準を4つ挙げます。どれも「動くから問題ない」と言われがちなものです。
ループ計測で速さを主張するコード
System.nanoTime でループを挟んで速度差を測る書き方は、Javaでは結果が信用できません。JITコンパイラのウォームアップが終わる前の実行が混ざり、結果を使っていない処理は最適化で消えることがあるためです。
// 空ループはJITに消されるため、そもそも何も測れていない
long start = System.nanoTime();
for (Map.Entry<String, Integer> e : stock.entrySet()) { }
System.out.println(System.nanoTime() - start);
性能を根拠に書き方を決めるなら、ウォームアップと最適化除去に対処したJMH(Java Microbenchmark Harness)で測ってください。測っていないなら、性能ではなく可読性を理由に選んだと書くほうが誠実です。
例外を握りつぶすcatch
catch (Exception e) { } と書いて何もしない実装は、障害の原因を消します。想定内の例外なら業務的な意味に翻訳して投げ直し、想定外ならログに残したうえで上位へ伝播させてください。ログにスタックトレースを出さずメッセージだけ出す実装も、調査時にほぼ役に立ちません。
可変フィールドと一律のgetter・setter
全フィールドにgetterとsetterを機械的に付けるのは、カプセル化を名乗った公開フィールドです。値を持ち回るだけのクラスなら、Java 16以降はrecordで置き換えられます。それ以前のバージョンでも、フィールドを final にしてコンストラクタで受け取る形にすれば、setterは不要になります。
よくある質問
Javaで文字列を比較するときは==とequalsのどちらを使いますか
内容が同じかどうかを判定したいなら equals です。== は参照が同一かどうかの判定なので、ソースに書いたリテラル同士では偶然一致しますが、外部入力や new String で作った文字列では一致しません。変数がnullになりうる場合は "example".equals(input) のように定数を左に置くか、Objects.equals(Java 7以降)を使ってください。ただし後者はnullどうしを一致と判定するため、未設定を一致扱いしてよいかは仕様の確認が必要です。
拡張for文のコロンはどういう意味ですか
コロンは「右辺のコレクションまたは配列から要素を1つずつ取り出して、左辺の変数に入れる」ことを表します。for (String name : names) なら「namesの中のnameごとに」という読み方です。Java 5で追加された構文で、配列とIterableを実装したコレクションのどちらにも同じ形で使えます。添字を持たないため、範囲外アクセスと添字の書き間違いが起きません。
Javaのパッケージ名の命名規則は決まっていますか
Java言語仕様6.1が、ドメイン名を要素ごとに逆順にした形を接頭辞として使うこと、第1要素を全小文字のASCII文字で書くことを定めています。Google Java Style Guide 5.2.1はさらに「小文字と数字だけを使い、アンダースコアは使わない」と規定しており、複数語も区切らずそのまま連結する形です。実際の例は com.example.billing.invoice になります。
Arrays.asListで作ったリストは変更できますか
要素の置き換えはできますが、追加と削除はできません。javadocの説明は「指定された配列に連動する固定長のリストを返す」であり、不変リストではありません。set は成功して元の配列にも反映され、add と remove は UnsupportedOperationException になります。変更を一切許さないリストが必要ならJava 9以降の List.of、自由に変更したいなら new ArrayList<>(Arrays.asList(...)) を使ってください。
今から書くならJava 8と最新版のどちらの書き方に合わせますか
実行環境のJavaバージョンに合わせます。Java 8で動かす限り、List.of(Java 9以降)もvar(Java 10以降)もrecord(Java 16以降)もコンパイルが通りません。移行を検討する場合は、この記事の導入バージョン早見表で使いたい構文の必要バージョンを確認したうえで、サポート期限とディストリビューションの条件から移行先を決めてください。