page4(update:2009/10/19)[例題4a例題4b例題4c例題4d]


4.データ型


[ prev | next | index ]

C言語との類似点は多いのですが、以下の点に注意してください。

1)基本データ型 文字、整数、実数などのデータを直接格納する型。同じプログラムが何処でも動くように表現方法が一意に決められています。基本データ型の変数は直接参照のみ可能です。

2)参照データ型 オブジェクトを参照するためのデータを格納する型。C言語のポインターに相当する変数型で、オブジェクトは参照データ型変数からの間接参照でのみ利用可能。java言語では配列や文字列の様な構造をもつデータは全てオブジェクト です。

目次

[目次]


4.1 基本データ型

整数、浮動小数点数、真偽値、文字などのデータを格納するデータ型。Pascal,C/C++等のデータ型と同様だが、Javaの特徴は従来のC言語にみられた様な計算機のCPUやOSに依存してint型が16ビットや32ビットになったりはしないことである。これは、Javaがどの計算機でも同じプログラムがそのまま動くことを重視しているからで、それぞれのCPUの性能を極限まで引き出すことが期待されたC言語とは対照的です。

4.1.1 基本データ型の種類と値の範囲

以下javaの基本データ型を示す。

型名 bit長  データの種類 値の範囲
boolean 1ビット 真偽値 false、true
char 16ビット 文字
Unicode 2.0
\u0000〜\uFFFF
U+FFFF よりも大きいコードポイントを持つ「補助文字」は
2個のcharで表現するのでchar型の値として格納できない。
    符号付き整数

 

byte 8ビット -128〜127
short 16ビット -32768〜32767
int 32ビット -2147483648〜2147483647
long 64ビット -9223372036854775808〜9223372036854775807
    浮動小数点数
IEEE754
 
float 32ビット ±3.40282347E+38 〜 ±1.4E-45と
0, ±無限大Infinity、非数NaN
double 64ビット ±1.79769313486231570E+308 〜 ±4.9E-324と
0, ±無限大Infinity、非数NaN

文字はUnicodeで表現され、整数のデータは2の補数表示で、実数はIEEE754に準じて表現される。

Unicode

ACSIIコードの0x00〜0x7FはそのままUnicodeの\u0000〜\u007Fに対応付けされる
(注: ASCIIコード中の半角バックスラッシュ( \ )とチルダ(〜)の場所にはJISコードで半角の 円記号「\」とオーバースコア「 ̄」 が割り当てられているが、円記号は\u00A5にオーバースコアは\u203Eと別の場所に対応付けされている)

javaのライブラリーで日本語のJIS、SJIS、、EUC等の文字コードをUnicodeに変換する機能が提供されている

ユニコードUTF-16の範囲はU+0000 〜 U+10FFFFだが2バイトで格納できるのはU+FFFFまでである。これを超えるコードポイントを持つ 文字は「補助文字」と言う。補助文字はchar方には格納できないが、Stringや文字配列に格納する場合は「上位サロゲート」範囲 (\uD800-\uDBFF) からの最初の値と、「下位サロゲート」範囲 (\uDC00-\uDFFF) からの第 2 の値から構成されるchar2個で1文字を表現する。 intに格納する場合は上位11ビットを0とし下位21ビットにコードを格納する。

2の補数表示

byte型は8bit、8bitでは256通りのものを区別できる。これを下記の様に正負の値に割り当てる

IEEE754

符号:s bitが0なら正s=1 1なら負s=-1を示す

指数部 eは8桁の2進数だがバイアス-127を加えて正負の領域を表現する
     ただし全bitが0と1の場合には特別の意味を持つ

仮数部 23桁の2進数
      指数部が 全て0 or 全て1 の場合を除いて
       m=1.mmmm...m23の正規化された2進小数値
      指数部が 全て0 なら
       m=0.mmmm...m23の2進小数値

指数部
8bit
仮数部
23bit
全て0 ±0
全て0 0以外 ±2emin×0.m1m2.......m23
-126<=e<127 ±2e×1.m1m2......m23
全て1 ±Infinity:無限大
全て1 0以外 NaN:非数

有効桁数

float型の有効数字は仮数部の23ビットから10進6-7桁であるが、指数部が全て0となる場合には有効桁はもっと少なくなる。最小値では10進1桁分もない。

double型の場合 符号1bit 指数部11bit(emin=-1022、emax=1023) 仮数部52bit 10進での有効数字15桁程度


値の範囲を書き出すjavaプログラムを示す。実行してみて下さい

例題4a
/*データ型の値の範囲を表示するアプリケーション*/
/*このプログラムのファイル名はType.java*/
public class Type{
        
        /**main関数*/
        static public void main(String arg[]){
                //
                //基本データ型のラッパークラスに定義された最大値、最小値の定数をプリント
                //
                System.out.println("-------------------------------------");
                System.out.println("byte の最大値"+Byte.MAX_VALUE);
                System.out.println("byte の最小値"+Byte.MIN_VALUE);
                System.out.println("-------------------------------------");
                System.out.println("short の最大値"+Short.MAX_VALUE);
                System.out.println("short の最小値"+Short.MIN_VALUE);
                System.out.println("-------------------------------------");
                System.out.println("int の最大値"+Integer.MAX_VALUE);
                System.out.println("int の最小値"+Integer.MIN_VALUE);
                System.out.println("-------------------------------------");
                System.out.println("long の最大値"+Long.MAX_VALUE);
                System.out.println("long の最小値"+Long.MIN_VALUE);
                System.out.println("-------------------------------------");
                System.out.println("float の正の最大値"+Float.MAX_VALUE);
                System.out.println("float の正の最小値"+Float.MIN_VALUE);
                System.out.println("負の場合はこれに−が付くだけです");
                System.out.println("float  1.0/0.0は"+(float)( 1.0/0.0));
                System.out.println("float -1.0/0.0は"+(float)(-1.0/0.0));
                System.out.println("float  0.0/0.0は"+(float)( 0.0/0.0));
                System.out.println("-------------------------------------");
                System.out.println("double の正の最大値"+Double.MAX_VALUE);
                System.out.println("double の正の最小値"+Double.MIN_VALUE);
                System.out.println("負の場合はこれに−が付くだけです");
                System.out.println("double  1.0/0.0は"+(double)( 1.0/0.0));
                System.out.println("double -1.0/0.0は"+(double)(-1.0/0.0));
                System.out.println("double  0.0/0.0は"+(double)( 0.0/0.0));
                System.out.println("-------------------------------------");
        }
}

プログラム中に太字で示した部分は定数です。基本データ型と良く似た名前のクラスが用意されています。これらを基本データ型を包み込んだクラスの意味でラッパークラスと言います。この対応関係を下記に示します。これらのクラスには文字列から基本データ型への変換など重要な関数が用意されています。使うときはクラス名.変数名クラス名.関数名の様にクラスとその中身を指定して使います。

基本データ型

ラッパークラス

boolean

Boolean

char

Character

byte

Byte

short

Short

int

Integer

long

Long

float

Float

double

Double

CharacterとIntegerだけは少し違いますが、後は先頭の文字が大文字に変えてあるだけで同じ名前です。

4.1.2 基本データ型の演算子

 演算記号を表の形で示します。論理演算の結果がbooleanになる以外はC言語とそれほど違わないと考えて良いでしょう。

優先順位
演算子
注釈
1 ++ -- + - ++と--は前置と後置の区別がある
2 * / % 乗算 除算 剰余
3

+ -

加算 減算

4 << >> >>> int型のビットシフト
5 < > <= >= instanceof 比較とオブジェクト生成元の確認
6 == != 等号と不等号
7 & ビット単位と論理のAND
8 ^ ビット単位と論理のXOR
9 ビット単位と論理のOR
10 && 論理のAND(短絡的評価を行う)
11

||

論理のOR(短絡的評価を行う)

12

?:

三項演算子

13

= 演算付き代入(*= += ......)

代入演算


※++は前置なら増やした後の値を、後置なら増やす前の値を返します。
   a=10;System.out.println(++a);は11を書き出す
   a=10;System.out.println(a++);は10を書き出す
※ <<と>>>はシフトで空いた部分を0で埋めますが、>>は符号ビットの値で埋めます。
※ instanceofは基本データ型の演算子ではありませんオブジェクトの型をチェックする演算子です
※ 論理値に対する&と&&の違いは&&が短絡的評価を行うことです
※ ?:は 条件式 ? 真の場合の値 : 偽の場合の値 の様な書式で使います
   例 System.out.println( a%2==0 ? "aは偶数" : "aは奇数" );

4.1.3 基本データ型の型変換

型が異なるデータの演算では自動的に精度の高い方の型に揃えて演算が行われる。
ただし、boolean型は他の型と変換できない。

基本データ型の拡大変換(widening primitive conversion)

byte>short>int>long>float>double および char>int のデータ変換は必要に応じて自動的に行われる。
byte>short>int>longの拡大変換では符号を保って(0または1で上位ビットを埋める)変換する。
charからintへの変換は0で上位ビットを埋めて変換する。
long型からfloat, double あるいは intからfloatへの変換では精度が落ちる場合がある

/*このプログラムのファイル名はCast.java*/
public class Cast{
        static public void main(String argv[]){
                byte b=65;
                short s=b;
                //char c=b;はコンパイルエラー
                //char c=s;はコンパイルエラー
                int i=s;
                long l=i;
                float f=l;
                double d=f;
                System.out.println("f="+f);
                char c='B';
                i=c;
            System.out.println("i="+i);
        }
}
/* 実行結果
f=65.0
i=66
*/

基本データ型の縮小変換(narrowing primitive conversion)

拡大変換の逆の変換はキャストによる変換が必要
long>int>short>byteの向きに縮小変換を行う場合は上位ビットを切り捨てるため、値の符号が変ることがある
整数の範囲を超えた実数型から整数型への変換では、例えば整数の上限以上の値は最大値に変換される。

/*このプログラムのファイル名はCast2.java*/
public class Cast2{
        static public void main(String argv[]){
                int i=-129;//負の数
                byte b=(byte)i;//この型変換で正の数に化ける
                System.out.println("b="+b);
        }
}
/* 実行結果
b=127
*/

 

[目次]


4.2 参照データ型

javaでは文字列や配列などの構造を持つデータは全てオブジェクトに格納されます。オブジェクトはデータや関数を要素として持ち、色々な型のものが定義できます。(具体的な説明は後回しにします)

ところがjavaではオブジェクトを直接格納する変数は作れません。 代わりに参照データ型の変数を使います。 これはオブジェクトの場所を示す値「参照reference)」を格納する変数です。 ここで、オブジェクトを格納するメモリは次の様にして確保します。

オブジェクトを格納するメモリの確保と初期化は演算子newを用いて行います。new演算子はオブジェクトを格納するメモリを確保しメモリの内容を コンストラクタで初期化し、さらにオブジェクトの参照を戻します。この値を参照データ型の変数に代入すれば、この変数を使ってオブジェクトを参照しデータの読み書きが可能になります。

※C言語で、動的に確保したメモリ番地をポインタ変数に代入し、メモリをポインタの間接参照で読み書きするのと同様です。

次に参照データ型の変数 s(文字列型Stringの参照を格納する変数)に文字列「Hello」の参照を代入する文を示します。

String s;  //String型のオブジェクトを参照する変数sの定義
s=new String("Hello");  // sへ「Hello」で初期化されたString型のオブジェクトへの参照を代入

変数の値が直接データとなる場合を直接参照、Cのポインターやjavaの参照データ型の様に、変数の値が「データの場所を示す情報」で、この情報を辿って目的のデータを参照することを間接参照といいます。

javaでは変数に格納できるデータは基本データ型とオブジェクトへの参照のみです
これ以外のデータ(配列や文字列など)は全てオブジェクトに格納します。

次の表はデータの種類と参照方法を比較したものです。

変数 変数の値 参照
java言語の場合
基本データ型
変数
基本データ型の値を格納
直接参照のみ
 
参照データ型
変数

 
オブジェクトの参照を格納 直接参照で参照の代入や比較が可能
間接参照でオブジェクトを参照
オブジェクトの要素を参照する演算子は
C/C++言語の場合
変数 データの値を格納
配列、構造体、オブジェクトなども格納可
オブジェクトの要素を参照する
メンバアクセス演算子は
直接参照だけでなく、変数のアドレスを
ポインタ型の変数に格納すれば下記のように
間接参照も可能
ポインター型
変数
メモリーアドレスを格納 直接参照でアドレスを数値として参照可能
間接参照は間接参照演算子は*による
間接参照でオブジェクトの要素を参照する
間接メンバアクセス演算子は->
もある

4.2.1 参照型変数

C/C++言語には、配列や構造体などの構造を持つデータ型が有ります。次のプログラム例の様にデータは変数で直接扱える他に間接的にポインタ変数を用いて利用することもできます。この為にC/C++では 要素を参照する演算子、メンバアクセス演算子「.」と間接メンバアクセス演算子「−>」の2つが用意されています。

/*C言語の構造体のサンプル*/
#include<stdio.h>
#include<stdlib.h>

struct Complex /*複素数の構造体*/
{
        double re;/*実部*/
        double im;/*虚部*/
}complex;

int main(void){
    {
        /*直接参照の変数を使う場合*/
        complex z;/*自動変数なのでスタックにメモリ割り当てされる*/
        z.re=1; z.im=0;/*直接に成分へ代入*/
        printf("z=(%3.1lf,%3.1lf)\n",z.re,z.im);/*直接に成分を読む*/
        /*ブロックから出るとスタック上の変数zのメモリ領域は解放される*/
    }
    {
        /*間接参照のポインタ変数を使う場合*/
        complex *p;/*ポインタ変数*/
        p=(complex*)malloc(sizeof(complex));/*動的メモリ確保*/
        p->re=1; p->im=0;/*間接的に成分へ代入*/
        printf("*p=(%3.1lf,%3.1lf)\n",p->re,p->im);/*間接に成分を読む*/
        free(p);/*構造体を格納したヒープ上のメモリを解放する*/
    }
    return 0;
}

これに対してjavaのデータは基本のデータ型かオブジェクトです。変数に格納できるのは基本データ型の値とオブジェクトの参照のみです。

 変数のデータ型が基本のデータ型以外の場合、それはオブジェクトの参照を格納する変数です。従って、変数の宣言にC言語のようなポインタ子「*」は使いません。

オブジェクトのメンバ(要素)の参照は全て間接参照で行われるのでC言語のようにメンバアクセス演算子を区別する必要はありません。従って、javaでは ドット演算子「.」のみを使います。

java言語
C言語

ない

ポインタ演算子「*」
間接参照演算子「*」

ない

メンバアクセス演算子「.」

ドット演算子「.」

間接メンバアクセス演算子「−>」

上の例をjavaで書き直してみました。javaでは上の変数に構造体を代入する形は不可能なので、「ポインタを用いた場合」に対応するプログラムを示しています。

例題4b

Cの構造体はjavaではクラス(class)で書けます

Complex
<<変数;フィールド>>
+re:double
+im:double
<<関数;メソッド>>
+Complex( r: double, i: double)

このクラスのオブジェクトを使う形でプログラムを書くと次のようになります

/*このプログラムのファイル名はComplexTest.java*/
/*java言語のオブジェクトのサンプル*/
class Complex
{
        double re;//実部
        double im;//虚部
        
        //初期化の為の関数。コンストラクタと言う
        public Complex(double r,double i)
        {
                re=r;im=i;
        }
}

public class ComplexTest
{
        static public void main(String arg[])
        {
                Complex p;//参照データ型の変数pの宣言
                  //Cプログラムでの対応する記述は
                  //complex *p;
                p=new Complex(1,0);//メモリ確保+コンストラクタで初期化+参照のpへの代入
                  //Cプログラムでの対応する記述は
                  //p=(complex*)malloc(sizeof(complex));
                  //p->re=1;p->im=0;  
                System.out.println("p=("+p.re+","+p.im+")");
                  //Cプログラムでの対応する記述は
                  //p.re,p.imはp->re,p->imに対応
                  
		//ブロックから出るとスタック上の変数pの領域は解放される
                //ヒープ上にあるオブジェクトのメモリ領域は、
		//いずれ自動的に解放されるのでfree(p)に対応する文は無い
        }
}

※javaの参照データ型の変数はC言語の構造体型の変数の様に使えます。しかし、実際はCのポインタ変数に対応するものであることを忘れないでください。

4.2.2 メモリーの確保と開放

普通、オブジェクトを入れる場所として、OSから空いているメモリー領域を分けてもらいます。これをメモリの動的確保といい、OSは要求された大きさのメモリー領域を見つけて、その先頭番地を教えてくれます。確保したメモリが不要になった場合はOSに空いたことを知らせます。これをメモリ開放といいます。メモリが足りなくなると新たなメモリ確保ができなくなったり、ディスクスワップが多発しプログラムの実行が極端に遅くなることがあるので、用済みのメモリは必ず開放するのが常識です。

現在のOSはマルチタスクの機能を持ち多数のプログラムを時分割で同時に実行しています。この為、、実行中のプログラムの中にメモリーを多量に食いつぶすモノがあると、他のプログラムの動作に悪い影響を与えてしまいます。プログラムで確保したメモリは必ずそのプログラム内で細めに開放してください。

Javaが用意する参照データ型は「ガベージコレクション」と呼ばれる自動的なメモリ開放の仕組みをもっています。

参照型の変数を全て調べて何処からも参照されていないメモリ領域を不要領域とし て開放します。使用メモリ領域を移動して隙間をつめることで、連続した空きメモリを作ります。同時に参照も移動後の新しい場所を示すように書き換えます。

メモリー・リーク(memory leak)

メモリーを使っていない筈なのに、空きメモリーが次第に減少していく状況。空きメモリーが少なくなると新たなメモリー割り当てができなくなりシステムの動作が遅くなったり停止したりする。

0)OSに問題が在ってメモリ解放が不完全な場合。
これは最近は無いはず。多くのOSはプログラムが終了すれば、そのプログラムが使っていたメモリーを自動的に解放します 。これに期待して、メモリー解放を行わないプログラムを作ることで、メモリー解放の煩わしさや間違いを避けようとする人もいます。 しかし、これは短時間で終了する一時的なプログラムでしか許されない手法です。

1)プログラムが確保したメモリーを解放せず、新しいメモリー割り当てを要求し続ける場合。
これはプログラムが本当に必要とするメモリーなら仕方がないのですが、不要な物なら解放しなければいけません。特に長時間動作するようなプログラムの場合は計算機のメモリーを使い切る恐れがあります。

※ほとんどの場合にメモリー解放を行っていても、一つだけ解放を忘れているということもあります。長い時間にわたってメモリーを少しづつ食いつぶすことになってしまいます。しかも長時間動かさないと異常に気がつかないかもしれません。

※プログラム実行中に確保したが、そのメモリ領域を示すポインタが失われ、プログラム実行中に解放できなくなる場合もあります。確保したメモリ領域の番地をポインタ変数に代入し記憶しますが、うっかりポインタ変数を上書きしてしまうとこの状態になります。番地情報がないとメモリ解放ができません。

(注)解放忘れを避けようとして2重3重に同じメモリーを解放してしまう間違いもあります。さらに解放したメモリーにポインターで書きこんでしまうこともあり得ます。実際、ポインタを使ったPascalやC/C++のプログラミングでは メモリー確保と解放に関係した問題がよく起きます。

4.2.3 javaの参照とCのポインターの違い

C言語のポインターは番地を格納するデータ型で数値としても代入や演算が可能なため、その値を変更すれば、どんな番地のメモリーでも参照できます。ただ、できることが増えれば間違いも多くなります。さらに、他人の書いたプログラムの場合にはなにをされるか分からないといった危険性ももたらします。

Javaが用意する参照データ型はC言語のポインターの様に数値として値を取り出したり、数値の代入や四則演算を行うことは許されません。 さらにオブジェクトを参照するためのデータを格納していますが、それがメモリ番地であると決められている訳でもありません。

※例えば2段のポインター。全オブジェクトの場所を指すポインターのリストを作り。このリスト上の位置を参照の値とすることも可能です。 

4.2.4 参照データ型の例

配列と文字列についてプログラム例を示します。自分の環境で実行してみて下さい。

1)配列

配列
<<フィールド>>
+length:int
−要素データ列
<<メソッド>>
+配列の初期化
+添え字を用いたデータの参照
   ※ここで添え字はint型
 

配列のデータが格納されたフィールド(メンバ変数)は非公開です。その代わりに[添え字]を用いたデータ参照のための公開メソッドが用意されています。ここで[ ]は一見メソッドには見えませんが、添え字として範囲外の値を与えると、チェックされて実行時エラーとなるので添え字の範囲を確認する処理が行われているのがわかります。また、配列の公開フィールドとして配列の要素数が用意されています。これは便利です。詳細は下記で
http://java.sun.com/docs/books/jls/second_edition/html/arrays.doc.html

(注)フィールドlengthは値を取り出せますが変更はできません。コンパイルエラーとなります。

例題4c
/*
このファイルはArrayTest.java
n[0]=1,n[1]=1でn[i]=n[i-1]+n[i-2]をi=10まで計算するプログラム
*/
public class ArrayTest{
        static public void main(String arg[]){
                int n[];//一次元配列を参照する変数n 要素数は未定
		//int n[11];のような書き方はできません
                int i;
                n=new int[11];//11個のint型データの配列をメモリに確保し参照をnに代入。0〜10で11個に注意

                for(i=0;i<n.length;i++){//配列nの要素数は成分lengthに入っている
                        if(i==0){
                                n[i]=1;
                        }else if(i==1){
                                n[i]=1;
                        }else{
                                n[i]=n[i-1]+n[i-2];
                        }
                        System.out.println("n["+i+"]="+n[i]);
                }
                //メモリー開放は自動的に行われる
        }
}

Cの配列とよく似ていますが作り方が異なります。さらにJavaの配列では要素数を配列自身が成分として保持しているので便利です。

2)文字列

String
<<フィールド>>
−文字列データ
....
<<メソッド>>
+String() 空の文字列を生成
+String(s:String) 文字列sをコピーして初期化
....
+charAt(index:int):char index番目の文字を戻す
+length():int 文字数を返す
+indexOf(s:String):int 文字列sが最初に現れる位置を戻す
.....
....

Stringの公開メソッドは非常に多い。文字列検索のメソッドも多数ある。詳細は下記を参照
http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/String.html

(注)Stringは生成後に内容を変更することはできない。変更できる文字列はStringBufferを用いること

例題4d
/*このファイルはStringTest.java*/
public class StringTest{
        static public void main(String arg[]){
                String one=new String("One ");//String one="One ";と簡略化できる
                String two=new String("Two ");
                String three=new String("Tree");
                String s,t;
                //
                System.out.println("one="+one);
                System.out.println("two="+two);
                System.out.println("three="+three);
                //
                s=one+two+three;//文字列の足し算は文字列の結合
                System.out.println("s=one+two+three;//文字列の足し算は文字列の結合");
                System.out.println("s="+s);
                System.out.println("文字数s.length()は"+s.length());//文字列の長さはlength()関数でget
                //
                t=s;//tにもsと同じ場所を代入
                System.out.println("t=s;を実行するとt,sは同じ実体を参照する");
                System.out.println("s="+s);
                System.out.println("t="+t);
                //
                t=t+" Four";//文字列の足し算はその文字を長くするのではなく別に作る
                System.out.println("t=t+\" Four\";//文字列の足し算では別に実体が作られる");//「"」は「\"」
                System.out.println("s="+s);
                System.out.println("t="+t);
                System.out.println("t,sは異なる実体を参照している");
        }
}

文字列に対しては足し算の演算子で文字列の結合ができます。ここで注意してほしいのは、結合された文字列は別の場所に作られることです。プログラムの中で文字列の足し算を繰り返し行うとメモリが足りなくなってガベージコレクションが頻繁に行われます。これはJavaのプログラムが遅くなる原因となります。

[目次]


[ prev | next | index ]