見出し画像

Java学習 プリミティブ型と文字列操作について

JavaSilverの試験対策本「黒本」やWeb上の情報でJavaの学び直しをして、基本中の基本であるプリミティブ型について改めて確認した点や新しく気づいた点をまとめていきたいと思います。


プリミティブ型

データ型には大きく分けて「プリミティブ型(基本データ型)」と「オブジェクト型(参照型)」の2種類があります。

プリミティブ型の変数に代入できる値をリテラル値といいます。
末尾にL,l,f,F,d,Dのいずれも付いていない整数はint型のliteral値です。
また、先頭に0xを付けた整数は16進数表記でのリテラル値、先頭0bだと2進数表記でのリテラル値である。

公式ドキュメント(https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)を翻訳すると「リテラルは、固定値のソースコード表現です。リテラルは、計算を必要とせず、コード内で直接表現されます。」とのことであるため、ソースコード上で直接に値を記述するために提供されている記述方法であると考えられます。


8種類のプリミティブ型とリテラル値を紹介していきます

・boolean型
真偽値、リテラル値はtrueかfalse

・byte型
8ビット整数値、リテラル値は-128~127までの整数

・short型
16ビット整数値、リテラル値は-32,768~32,767までの整数

・int型
32ビット整数値、リテラル値は-2,147,483,648~2,147,483,647までの整数
リテラル値の頭に「0x」を付ければ16進数、「0b」を付ければ2進数、「0」を付ければ8進数もリテラル値として扱える。
例)16進数の0x4d⇒10進数の77、2進数の0b110⇒10進数の6を表す

★注意
試験では以下のような引っかけ問題が出るようです。
一応復習のため。

// 0から始まっているので8進数の表記となるが
// 8進数は0~7の数字を使った表記なので、以下のような書き方はコンパイルエラーとなる。
int num = 0827;


・long型
64ビット整数値、リテラル値は-9,223,372,036,854,775,808~9,223,372,036,854,775,807までの整数の末尾にLまたはl(エル)を付けた値。小文字だと紛らわしいのでLを使った方がよさそうです(Javaの公式ドキュメントでも言及あり)

・float型
32ビット、単精度浮動小数点数(有効桁数:7桁
リテラル値は小数点数の末尾にfまたはFを付けた数値。

・double型
64ビット、倍精度浮動小数点数(有効桁数:15桁
リテラル値は小数点数の末尾にdまたはDを付けた数値。

・char型
リテラル値はシングルクオーテーションで囲った文字だが、本質的には文字コードであるため、シングルクオーテーションで囲った16ビットユニコード文字(\u0000)~65,535(\uffff)あるいは0~65535を置けば値に対応した文字と同じ扱いとなる。

リテラルの表記について

前項でint値の16進数、2進数、8進数表記、charのユニコード表記については説明をしましたが、それ以外にもリテラルについては日常生活では見慣れない表記が使われることもあるのでまとめます。

①数値リテラルをアンダースコア「_」で区切る
桁数の多い整数や少数リテラルの見やすさを向上させる目的でJavaSE7から導入されました。以下2つのルールを守れば出現する場所や回数は自由です。
・リテラルの先頭と末尾には記述できない

・記号の前後には記述できない
記号⇒小数点のドット「.」、long型やfloat型を表す「L」や「F」、16進数や2進数を表す「0x」「0b」など

②char型のリテラル表記
char型は単体の文字を表すデータ型で、文字をそのまま入れる場合はシングルクオーテーションで囲います。複数の文字を集めた文字列(ダブルクオーテーションで囲う)とは別物なので注意しましょう。

また、前項でも説明しましたがchar型のリテラルは
・シングルクオーテーションで囲った文字(文字リテラル)

・シングルクオーテーションで囲った「\u」「¥u」から始まるユニコード番号(文字リテラル)

・0~65535の整数(数値リテラル)です。②と③については「\u」「¥u」を付けた16進数か整数かの違いだけでどちらも同じユニコードを表しています。


数値の拡大変換と縮小変換

primitive型の大きさは、byte < short < int < longであり、float < doubleである。

byte→short→int→long、float→double(型の大きさが大きくなる方向)への代入は暗黙に変換される。

また、ある型から別の型に変換することをキャストと言う。
使用する機会は「型は変えたくないけど、一時的に別の型として扱いたい」ときである。

byte型やshort型をint型のような、より大きい値を扱える型に変換することを拡大変換という。
逆にint型からbyte型やshort型のような小さい値しか扱えない型に変換することを縮小変換といいます。

拡大変換の場合はより大きい値を扱える型に数値を入れるので、扱いに困ることはないが、縮小変換の場合は変換する型がその数値を扱えなくなる可能性があるため、常に気を付けなければならなりません。

例え話にはなりますが、500mlのペットボトルに入っている水を1lのボトルに入れる場合(拡大変換)特に心配ないが、1lのペットボトルに入っている水を500mlのボトルに入れる場合(縮小変換)はボトルから溢れてしまう場合があるので注意をする必要がある。と考えればよい。

// まず、それぞれの型を持つ変数を用意してあげる
byte byte1 = 10;
byte byte2 = 11;

// 1.byte型変数にはbyteは代入できるが、short、int、longはcastしない限りできない。
byte2 = byte1;

//byte2 = short1; // compile時エラー「型の不一致: short から byte には変換できません」
byte2 = (byte)short1; // [縮小変換]

//byte2 = int1; // compile時エラー「型の不一致: int から byte には変換できません」
byte2 = (byte)int1; // [縮小変換]

//byte2 = long1; // compile時エラー「型の不一致: long から byte には変換できません」
byte2 = (byte)long1; // [縮小変換]


short short1 = 10;
short short2 = 11;
   
// 2.short型変数にはbyte、shortは代入できるが、int、longはcastしない限りできない。
short2 = byte1; // (拡大変換)
short2 = short1;
//short1 = int1; // compile時エラー「型の不一致: int から short には変換できません」
short2 = (short)int1; // [縮小変換]
//short1 = long1; // compile時エラー「型の不一致: long から short には変換できません」
short2 = (short)long1; // [縮小変換]


int int1 = 10;
int int2 = 11;
    
// 3.int型変数にはbyte、short、intは代入できるが、longはcastしない限りできない。
int2 = byte1; // (拡大変換)
int2 = short1; // (拡大変換)
int2 = int1;
//int2 = long1;
// compile時エラー「型の不一致: long から int には変換できません」
int2 = (int)long1; // [縮小変換]


long long1 = 10;
long long2 = 11;

// 4.long型変数にはbyte、short、int、longすべて代入できる。
long2 = byte1; // (拡大変換)
long2 = short1; // (拡大変換)
long2 = int1; // (拡大変換)
long2 = long1;


float float1 = 10;
float float2 = 11;

// 5.float型変数にはfloatは代入できるが、doubleはcastしない限りできない。
float2 = float1;
//float2 = double1; // compile時エラー「型の不一致: double から float には変換できません」
float2 = (float) double1; // [縮小変換]


double double1 = 10;
double double2 = 11;

// 6.float型変数にはfloat、doubleが代入できる。
double2 = float1; // (拡大変換)
double2 = double1;


文字列とは

複数の文字を集めたものを「文字列」と呼び、リテラルを表す記号はダブルクオーテーションです「"」、1文字だけの「文字」はシングルクオーテーション「'」なので間違えないようにしましょう。
char型で宣言してダブルクオーテーションで囲ってしまうとコンパイルエラーとなります。また、char型で宣言してシングルクオーテーションで囲っても文字列にしてしまうと、これもコンパイルエラーとなってしまいます。

Stringオブジェクト

C言語などのプログラミング言語では文字列を扱うためにchar型の配列を使っていました。プログラム中では文字列を連結したり、途中の数文字だけ抜き出したり操作をよく行いますが配列の操作はとても煩雑な作業です。

そこでJavaでは文字列操作のためのメソッドを提供するクラスとしてjava.lang.Stringクラスが用意されています。

Stringクラスは他のクラスと同じようにインスタンスを生成して利用しますが、Stringクラスのインスタンスを生成する方法はいくつか用意されています。ここでは代表的な方法を2種類ご紹介します。

・ダブルクオーテーションで括った文字列リテラルを記述する。

・newをつかってインスタンス化する。

です、以下サンプル

// ダブルクオーテーションで括った文字列リテラルを記述する。
String b = "fghij";

// newをつかってインスタンス化
String a = new String("abcde");

ダブルクオーテーションを使った記述方法が一般的ですが、ほかのクラスと同様にnewを使ってインスタンス化することもできます。

Stringクラスのstaticメソッド(インスタンスを生成しなくても使える)であるvalueOfメソッドを使って生成する方法などの方法がありますが、興味があれば是非一度調べてみてください。

参照型

紹介した8種類のプリミティブ型というデータ型以外でJavaが扱っているデータ型に参照型があります。

不変(イミュータブル)なオブジェクト

replaceメソッドという指定した文字を置き換えるメソッドがあるが

String a = "abcde";
a.replace("c", "x");
System.out.println(a); // abcde

としても表示されるのは「abcde」です。

replaceでは変数aを変更しているように見えるが、実施には変数aの内容をreplaceメソッドで置き換えた新しいオブジェクトが作成されているので、そのオブジェクトを格納する変数を用意してあげないといけない。
そのため

String a = "abcde";
String b = a.replace("c", "x");
System.out.println(b); // abxde

として、はじめて「abxde」と表示される。
つまり、String型は一度インスタンスを生成したら後から変更することができない、つまり不変(イミュータブル)のオブジェクトであると言えます。

インスタンスへの参照をほかのメソッドの引数に渡すと、そのメソッド内でインスタンス持つデータが不適切に変更されてしまう可能性があります。

例えば、エクリプスの機能を使って作成できるsetterメソッドについて、setterに関しては、それの使用により不変条件(主にフィールド)が変更されてしまい、メソッドが本来得たい結果と違う結果を返してしまう可能性が発生する危険があります。

よく見るsetterとgetterの危険性

例えばsetterメソッドを使ってprice(商品の値段)の値を税込みの価格にしたとして、priceの値そのものが変わってしまうと他に税込みではない、変更される前のpriceを使って計算を行いたいメソッドがあった場合は想定していた結果とは異なる結果となってしまいます。

また、getter(値のコピーを返す)メソッドに関しては、primitive型なら問題ありませんが、参照型の場合は参照値をコピーしてくるだけなので、結局参照先を変更してしまい影響が出ます。

例えば、getterで値を取得して、その値を変更した場合、プリミティブ型であれば値そのものをコピーしてきているので変更したものはコピー元と別物として扱われますが、参照型の場合はコピーしてきているものはあくまで参照値なので、参照先に入っているオブジェクトの値を変更することになってしまい、結局はフィールド(値)自体を変更してしまうことになります。

このような事態を防ぐには、インスタンスのクローン(コピー)を作って、クローンへの参照を渡すという方法がありますが、メソッド呼び出しの度にクローンを作っているのは非効率的であるので、フィールド(値)を変更できないように定義したクラスを作成して、フィールド(値)を不適切に変更されるのを防ぎます。

作り方

このような不変(イミュータブル)なオブジェクトを作成するためには次のような方法があります。
・すべてのフィールドをprivateで修飾する

・オブジェクト内部の状態を変更可能なメソッドを提供しない。
例えばeclipseのデフォルト機能を使って作成できるようなタイプのsetterメソッドは作成しない。

・クラスをfinalで宣言し、メソッドがオーバーライドされないことを保証する(サブクラスからの変更を防ぐ)。

・内部に可変オブジェクトを保持している場合、そのオブジェクトを外部にわたさない。たとえばgetterメソッドを作成しない。

以下サンプル

// 不変(イミュータブル)なクラスの例
public final class Sample {
    private final String name;
    public Sample(String name) {
        this.name = name;
    }
    public void greet() {
        System.out.println("hello," + name);
    }
}

すでにお気づきかと思いますが、Stringクラスは不変クラスです。Stringクラスのインスタンスでは、生成時に与えられた文字列やファイルへの抽象パスを扱いたい場合は新しいインスタンスを作成するしかありません。


エンジニアファーストの会社
株式会社CRE-CO niwatori-program

この記事が気に入ったらサポートをしてみませんか?