🐔

Javaの基本文法(基礎固め)

に公開

まえがき

chat-gptで作った学習ロードマップをプログラミング初学者が勉強する試みです
ロードマップはchat-gptを使用してますが、学習は公式のチュートリアルや技術系ブログなどを参考にしています
Goの復習も兼ねているのでGoとの違いについても言及します
今回はJavaの基本文法について内容をまとめていきます

目標

Javaの基本的な構文を理解する

Javaの特徴・Goとの違い(静的型付け、JVMなど)

JavaもGoも静的型付けの言語である
両者ともに型推論に対応しているのでこれと言って違いはなさそう?

JVMとはJava Virtual Machineの略でJavaを実行するのに必要なソフトウェア
主な役割はコンパイルされたclassファイルを変換してJavaで実行できるようにしたり、メモリ領域を管理したりする

JavaはJVMを経由する事によってプラットフォーム(OS)を選ばずに実行出来る
Goはクロスコンパイルが出来るので各プラットフォーム毎に対応する実行ファイルをコンパイル出来る

メモリ領域も管理しているらしいのでガベージコレクションについても触れておくと
ローカル変数の非ポインターは事前に解放されるタイミングが決定されている(スタック領域は処理が終わり次第解放される)ので、それ以外の値がヒープ領域に格納される

Java

ヒープ領域を世代別コレクションによってYoung/Oldでそれぞれ効率的にメモリ解放している

Go

ヒープ領域の解放はマーク&スイープGCとGo routineによって並行的に行われる

変数・データ型・リテラル

Java

  1. 基本形
    以下のように基本的に初期化して使う
main.java
int a; // int型の変数aを宣言
a = 10; // 変数aに整数10を代入
int b = 10; // 変数bを初期化(初期値10)基本的にはこれを使う
  1. 型推論
    右辺が明確な場合には有効だがこの辺はプロジェクト毎にルールを決めるのがベター?
main.java
var c = "hoge"; // リテラル
var d = new word(); // newでインスタンス化

Go

  1. 基本形
main.go
var a int // int型の変数aを宣言、Goは基本的に[変数名 型]となる
a = 10 // 変数aに整数10を代入
var b int = 20 // 変数bを初期化(初期値10)
var c = 30 // 初期化する時は型推論が使える
  1. 省略代入文(:=)
c := "hoge" //リテラル
d := newStruct() //構造体のポインタを返すコンストラクタ

関数の中でしか使えないが基本的には2の省略代入文が推奨される、関数の外で省略代入文を使うとコンパイルエラー

データ型

キャストも仕様が違う

Java

int a = 1;
double b = a;
// 暗黙的キャストが可能

int a = (int)1.1; // aにint型の1を代入
// double型の値も整数部分をint型にキャスト出来る

Go

var a int = 1
var b float64 = a // コンパイルエラー
// [var b float64 = int(a)]のように明示的にキャストする必要がある
// Goは型に対して厳格で暗黙的キャストを許さない
a := int(1.1) // コンパイルエラー
// 整数以外のfloat64型の値はint型にキャスト出来ない

var b float64 = 1.0
c := int(b) // cにint型の1を代入
// float64型でも1.0のようにint型と等しい値なら型を明示すればキャスト出来る

数値であれば簡単にキャスト出来るのは便利だと思うけど個人的にはGoの厳格さの方が好み
Javaの場合、変数は使う直前に初期化するのが慣習らしいのでヒューマンエラーはそこで対策してるのかな
chat-gptによると継承を伴うキャストが多いとのこと。だからこういう仕様なのだろうか
いずれにせよクラスは未勉強のため後日書き直します

演算子・制御構文(if, switch, for, while)

演算子はほぼ同じだが三項演算子の書き方が違う

Java

var 変数名 = 条件式 ? trueの値 : falseの値;

Go

三項演算子が無いので素直にif文を書く

result := falseの値
if <条件式> {
	result = trueの値
}
// 初期値をtrueとするならfalseと逆にする

if文

違いは条件式に()があるかないかだけ
elseもelse ifも使える

Java

if (条件式) {
	trueの処理;
} else {
	falseの処理;
}

Go

if 条件式 {
	trueの処理
} else {
	falseの処理
}

switch文

Java

条件式に()が必要で条件に当てはまるcaseから順に実行される
条件に当てはまるcaseが無ければdefaultから実行される

switch (条件式) {
	case:	// case1
		処理;
		break;
	case:	// case2
		処理;
		break;
	default:
		処理;
        break;
}

後に記述されているcaseも実行されるのでbreakで抜ける必要がある
defaultが無いとコンパイルエラー

Go

条件に当てはまるcaseのみ実行される
条件に当てはまるcaseが無ければdefaultが実行されるが、defaultが無い場合は何もしない

switch 条件式 {
	case:	// case1
		処理
	case:	// case2
		処理
	default:
		処理
}

breakは必要ない、defaultが無くてもコンパイル出来る

for文

条件式がtrueの間ループする

Java

for (初期化; 条件式; 値変化) {
	処理;
}

初期化でint i = 0などして、条件式がtrueの間ループする。値変化はi++など
<初期化><条件式><値変化>はそれぞれ省略可能だがセミコロンは必要

Go

基本形
for 初期化; 条件式; 値変化 {
	処理
}

条件の()が要らない。省略も同じ。全て省略すると無限ループになる

goのwhile
for 条件式 {
	処理
}

条件式のみ、Goにはwhileが無いのでforで記述する

無限ループ
for {
	処理
    if 条件 {break}
}

途中でbreakが必要

拡張for文

要素を繰り返す

Java

基本形
for (データ型 変数名: 配列) {
	処理;
}

データ型の配列が持つ各要素をループする
変数名は各要素が代入される。インデックスの取得には一工夫必要

forEachメソッド
リスト.forEach(引数 -> 処理);

可長変配列(リスト)はforEachメソッドとラムダ式というのが便利らしい。固定長の配列だと使えないのが難点

Go

基本形
for インデックス名, 変数名 := range 配列 {
	処理
}

rangeは繰り返しのインデックスと、各要素のコピーの二つの変数を返す

インデックスの廃棄
for _, 変数名 := range 配列 {
	処理
}

インデックスが必要なければ_(アンダーバー)で捨てることが出来る

変数の省略
for インデックス名 := range 配列 {
	処理
}

インデックスだけが必要なら二つ目の変数名を省略できる

while文

条件式がtrueの間ループする

Java

基本形
while (条件式) {
	処理;
}

forでも書けるけどwhileなら一目で目的がわかる

do-while
do {
    処理;
} while (条件式);

条件式の真偽に関わらず1回は必ず実行される

Go

goのwhile
for 条件式 {
	処理
}

さっき見たな……
Goのシンプルさが顕著に出てて好き

goのdo-while
for {
	処理
	if 条件式 {break}
}

do-whileも無い。forをこねくり回して記述する

メソッドとスコープ(値渡し・参照渡しの違い)

メソッド(関数)

JavaのメソッドとはCやGoで言うところの関数にあたる
Goのメソッドは型が持つ関数の事を指すのでごっちゃにならないように注意

Java

基本形
修飾子 返り値のデータ型 メソッド名(引数のリスト) {
	処理;
	return 返り値;
}
要素 説明
修飾子 キーワードによってメンバーの性質を変えることが出来る、スコープも修飾子で変更できる
返り値のデータ型 returnで返す値の型を指定する。複数返す場合は工夫が必要
メソッド名 任意
引数のリスト 複数あれば,(カンマ)で区切る
メソッドの例
public static void myMethod(String s) {
	処理;
}
キーワード 説明
public アクセス修飾子。全てのクラスからアクセス出来る
static クラス変数修飾子。全てのインスタンスで共有される
void 返り値が無い場合

Go(関数)

基本形
func 関数名(引数名 引数の型) (返り値の型) {
	処理
	return 返り値
}
要素 説明
アクセス範囲 関数名の最初の文字で決まる。大文字ならpublic、小文字ならprivate
引数 複数の引数は,(カンマ)で区切る、型が同じなら(int a, b)のようにまとめて記述できる
返り値 同上。返り値が単一なら()は省略できる
関数の例
func myFunc(int num1, num2) (sum int) {
	sum = num1 + num2
	return	// 返り値に名前がついているのでsumを返す
}
要素 説明
関数名 小文字なのでprivate
引数 二つともint型なのでまとめて記述
返り値 名前を付けると関数の最初に変数を宣言したことになる(sum int)

スコープ(値渡し・参照渡しの違い)

1. スコープ

Java

種類 説明
ローカル変数 宣言されたブロック内だけでアクセス出来る
インスタンス変数 クラスのインスタンス毎に保持される変数
クラス変数(static) クラス全体で共有される変数

Javaにグローバル変数は無い
クラス変数が実質グローバル変数

Go

種類 説明
ローカル変数 宣言されたブロック内だけでアクセス出来る
グローバル変数 関数の外で宣言されどこからでもアクセス出来る

ちなみに宣言したグローバル変数をローカルで宣言し直す事も出来るが
これはerror型を扱う際にerr :=で統一出来たりする利点がある

2. 値渡し

Java

変数の種類 説明
プリミティブ型 基本的に値をコピーして渡す
参照型(配列やStringなど) アドレスの値を渡して、アドレス先の値を参照する

Javaではポインタを意識して操作することはない
よくある「参照の値を渡す」という解説はポインタの事だと思うんだけど……詳しい人教えてください

Go

変数の種類 説明
プリミティブ型 基本的に値をコピーして渡す
参照型(sliceやStringなど) アドレスの値を渡して、アドレス先の値を参照する
ポインタ変数 アドレスの値を渡して、アドレス先の値を参照する

GoはC言語と同じくポインタ変数を使えるが、ポインタ演算は使えない

ポインタ変数
str := "hoge"
p := &str
fmt.Print(p) // 0xc000012060
fmt.Print(*p) // hoge

&(アンパサンド)をつけると変数のポインタを参照する
*(アスタリスク)をつけるとポインタ変数になる。ポインタ変数はアドレス先の値を参照する

3. 参照渡しの違いについて

参照渡しはJavaとGoでは使わないっぽいので割愛します

配列・コレクション(ArrayList, HashMap)

1. 配列(Array)

  • 固定長
  • 同じ型のデータをまとめて扱える
  • インデックスで要素にアクセス出来る

Java

型名[] 変数名 = new 型名[要素数];
配列
int[] empty = new int[10]; // 配列の宣言
int[] numbers = {0, 1, 2, 3, 4}; // 配列の初期化

System.out.print(numbers[2]); // 2
System.out.print(numbers); // [I@2eafffde

配列には先頭要素のメモリアドレスが格納されているのでそのままprintすると要素が出力されない
要素を取得するにはインデックスで指定するか、for文などを使う
文字列で出力したい場合はtoStringメソッド等を活用する

Go

変数名 := [要素数]型名{要素1, 要素2...}
配列
var empty [10]int // 配列の宣言
numbers := [5]int{0, 1, 2, 3, 4}

fmt.Print(numbers[2]) // 2
fmt.Print(numbers) // [0, 1, 2, 3, 4]

Goの配列は要素とサイズを持ったデータ構造の為そのまま出力出来る
各要素を取得するにはインデックスで指定するか、for文などを使う

2. 可変長配列

  • 可変長。サイズを変更出来る
  • 同じ型のデータをまとめて扱える
  • インデックスで要素にアクセス出来る

Java

ArrayList<型名> 変数名 = new ArrayList<>();
可変長配列
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(1); // 要素の追加
numbers.add(2);
System.out.print(numbers); // [1, 2]
System.out.print(numbers.get(1); // 2

ArrayListは要素の配列やサイズなどの情報を内部に持つデータ構造
キャパシティを超える要素を追加しようとするとサイズの大きい新しい配列にコピーする
要素の追加、削除、取得などはメソッドを使う
配列と違って直接printすると要素がもろっと出る

Go

numbers := []int{0, 1, 2, 3}
numbers = append(numbers, 4) // 要素の追加
fmt.Print(numbers) // [0 1 2 3 4]
fmt.Print(numbers[2]) // 2

......。Go君さぁ......(呆れ)
配列の要素数を打ち忘れると可変長配列(Goではスライスと呼ぶ)になる
要素の追加はappend関数、削除は出来ない、取得はインデックスで指定して使う
スライスは配列の参照をしているのでJavaと同じくキャパシティを超える要素を追加しようとするとサイズの大きい新しい配列にコピーする

3. HashMap

  • キーと値のペアでデータを保存
  • キーは一意(上書きされる)
  • 値は重複OK
  • キーを指定して値を取得出来る
  • 検索が高速

Java

HashMap<キーの型, 値の型> 変数名 = new HashMap<>();
HashMap
HashMap<String, Integer> myMap = new HashMap<>();
myMap.put("Apple", 150); // 要素の追加
myMap.put("Orange", 100);
System.out.print(myMap); // {Apple=150, Orange=100}

int price = myMap.get("Apple"); // 要素の取得
System.out.print(price); // 150

インデックスで管理する配列に対してHashMapはキーと値の連想配列
要素の追加、検索、削除、取得などはメソッドを使う
HashMapはキーをハッシュ値で管理するので検索が高速

Go

変数名 := map[キーの型]値の型){}
m := map[string]int{}

m["Apple"] = 150 // 要素の追加
m["Orange"] = 100
m["Banana"] = 200

delete(m, "Banana") // 要素の削除
v, ok := m["Banana"] // 存在確認。
    // vに値を代入(なければゼロ値)
    // okにキーの真偽(あればtrue、なければfalse)が代入される
fmt.Print(v, ok) // 0 false

price := m["Apple"] + m["Orange"] // 合計金額
fmt.Print(price) // 250

インデックスで管理する配列に対してmapはキーと値の連想配列
要素の追加、検索、削除、取得などは組み込みの関数を使う
mapはキーをハッシュ値で管理するので検索が高速

まとめ

思ったより長くなってしまいました
仕様などにフォーカスして比べてみましたが一長一短といった感じですね
合理性の為にそうなってると思うので触ってるうちに理解を深めて行きたいです

初学者なので間違ってる部分があればご指摘いただけると嬉しいです

Discussion