Java 基础-程序结构与数据类型





    2. 变量与数据类型概述

    基本数据类型(值类型):

    • 整数类型:byteshortintlong
    • 浮点数类型:floatdouble
    • 字符类型:char
    • 布尔类型:boolean

    各种类型内存大小及可表示数据大小

    计算机中最小表示单位byte(字节B),1字节可表示8位二进制,即8bit(位,b)。可表示范围:为28,即00000000~11111111,也即十进制的0~255,也即十六进制的00~FF

    可表示数据大小

    • byte1字节,-128 ~ 127
    • short2字节,-32768 ~ 32767
    • int4字节,-2147483648 ~ 2147483647
    • long8字节,-9223372036854775808 ~ 9223372036854775807
    • float4字节,3.4x1038
    • double8字节,1.79x10308
    • char2字节

    浮点数

       float f1 = 3.4f;
        float f2 = 3.4e38f; // 科学计数法表示的3.14x10^38
        double d1 = 1.79;
        double d2 = 1.79e308;

    float类型变量需要加上f后缀,最大可表示3.4x1038

    double最大可表示3.4x1038

    floatdouble均支持科学计数法表示

    字符类型

    char用于表示一个,在Java中除可表示ASCII外,还可表示Unicode字符。char类型使用'(单引号)区分。

    布尔型

    boolean只有两个值truefalse

    boolean b1 = true;
    boolean b2 = false;
    boolean isGreater = 5 > 3; // 计算结果为true


    常量

    在变量前增加final关键字。按语言习惯,常量定义一般使用大写字母。


    var关键字与类型推断

    可以用var关键字来定义变量,编译器会根据赋值语句自动推断变量类型:

    var sb = new StringBuilder();


    变量作用域

    在语句块({}内)中定义变量时应该注意,每个变量都有其作用范围。在内层括号内定义的变量,不能在外层括号(括号外)访问;但外层定义的,可以在内层括号内访问。要遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域,并且,不要重复使用变量名。

    {
        ...
        int i = 0; // 变量i从这里开始定义
        ...
        {
            ...
            int x = 1; // 变量x从这里开始定义
            ...
            {
                ...
                String s = "hello"; // 变量s从这里开始定义
                ...
            } // 变量s作用域到此结束
            ...
            // 注意,这是一个新的变量s,它和上面的变量同名,
            // 但是因为作用域不同,它们是两个不同的变量:
            String s = "hi";
            ...
        } // 变量x和s作用域到此结束
        ...
    } // 变量i作用域到此结束


    3. 整数

    整数运算

    整数运算与数学中的四则运算规则一致,包括:加(+)、减(-)、乘(*)、除(/)、括号(())等:

    public class Main {
      public static void main(String[] arg) {
        int i = (100+99)*(88+99);
        int j = 10*(5+8)/5;
        System.out.println(i);  // 37213
        System.out.println(j);  // 26
      }
    }
    


    自增/自减

    Java中使用++表示自增,使用--表示自减,分别表示对整数进行加1和减1操作:

    int n = 1;
    n++;
    System.out.println(n);  // 2
    int y= 1+(++n);     // ++ 写在前面,表示先对执行自增,再进行引用。也即:n会变成3
    System.out.println(y);    // 4
    System.out.println(n);    // 3
    System.out.println(n++);  // 3, ++ 写在后台,表示先引用再自增

    ++(或--)写在变量前面和后台是有区别的,写变量前面(++n)表示先对变量自增再引用;而写在后面(n++)表示先对变量引用,再自增。在实际使用中应该注意。


    位移

    位移是对二进制形式的整数,进行左移(<<)或右移>>。如:

    int n = 3;    // 二进制形式为:00000000 00000000 00000000 00000011
    int a = n<<1;
    int b = n>>1;
    System.out.println(a);  // 6,即:00000000 00000000 00000000 00000110
    System.out.println(b);  // 1,即:00000000 00000000 00000000 00000001

    位运算的符号位

    整数最高位为符号位,将3左移30位,其值会变成负数;

    int n = 3;
    System.out.println(n<<29);  // 1610612736,即:01100000 00000000 00000000 00000000
    System.out.println(n<<30);  // -1073741824,即:11000000 00000000 00000000 00000000

    类似的,对正数进行右移超出其二进制长度,将变成0。而对负数进行位移时,其符号位保持不动,其结果仍是一个负数:

    int n = 3;
    System.out.println(n>>2); // 0, 即: 00000000 00000000 00000000 00000000
    System.out.println(n>>3); // 0, 即: 00000000 00000000 00000000 00000000
    
    n = -3;
    System.out.println(n>>2); // -1, 即: 11111111 11111111 11111111 11111111
    System.out.println(n>>3); // -1, 即: 11111111 11111111 11111111 11111111

    对于右移来说,可以使用>>>,右移时符号位会跟随移动,会使负数变成正数:

    int n = -3;
    System.out.println(n>>>30); // -1, 即: 00000000 00000000 00000000 00000011

    byteshort类型进行移位时,会首先转换为int再进行位移。


    位运算

    位运算是按位(二进制)进行(&)、(|)、(~)和异或(^)运算。规则是:

    • 运算,两个操作数,必须都是1,结果才是1
    • 运算,两个操作数,其中一个1,结果就是1
    • 运算,一个操作数,结果是其相反值,即:1的结果是00的结果是1
    • 异或运算,两个操作数,两者不同才是1,否则是0
    // 与运算
    n = 0 & 0; // 0
    n = 0 & 1; // 0
    n = 1 & 0; // 0
    n = 1 & 1; // 1
    
    // 或运算
    n = 0 | 0; // 0
    n = 0 | 1; // 1
    n = 1 | 0; // 1
    n = 1 | 1; // 1
    
    // 非运算
    n = ~0; // 1
    n = ~1; // 0
    
    // 异或运算
    n = 0 ^ 0; // 0
    n = 0 ^ 1; // 1
    n = 1 ^ 0; // 1
    n = 1 ^ 1; // 0

    基于以上规则,对两个整数进行位运算时:

    int n = 3;     // 二进制形式为:00000000 00000000 00000000 00000011
    int i = 2;    // 二进制形式为:00000000 00000000 00000000 00000010
    System.out.println(n&i);  // 2, 即: 00000000 00000000 00000000 00000010
    System.out.println(n|i);  // 3, 即: 00000000 00000000 00000000 00000011
    System.out.println(n^i);  // 1, 即: 00000000 00000000 00000000 00000001


    运算符优先级

    • ()
    • ! ~ ++ --
    • * / %
    • + -
    • << >> >>>
    • &
    • |
    • += -= *= /=


    类型自动提升与强制转型

    如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。如果,shortint运算,short会自动转为int类型

    还可以使用(类型)强制类型转换,强制类型转换可能会造成精度丢失,超出范围时,高位字段会被直接丢掉:

    int i1 = 1234567;
    short s1 = (short) i1; // -10617


    4. 浮点数运算

    浮点数只能进行四则运算(加减乘除),不能进行位运算和移位运算。

    在计算机中,浮点数虽然表示的范围大,但是,浮点数有个非常重要的特点,就是浮点数常常无法精确表示。所以,浮点数运算也会产生误差:

    double x = 1.0 / 10;
    double y = 1 - 9.0 / 10;
    
    System.out.println(x);    // 0.1
    System.out.println(y);    // 0.09999999999999998


    类型提升

    如果参与运算的两个数其中一个是整型,那么整型可以自动提升到浮点型:

    但需要注意,在一个复杂的四则运算中,两个整数的运算不会出现自动提升的情况。例如:

    double d = 1.2 + 24 / 5; // 5.2

    结果为5.2,是因为在对子表达式24 / 5进行计算时,会按整数计算,结果为4


    溢出

    整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:

    • NaN表示Not a Number
    • Infinity表示无穷大
    • -Infinity表示负无穷大

    强制转型

    可以将浮点数强制转型为整数。在转型时,浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值:

    int n1 = (int) 12.3; // 12
    int n2 = (int) 12.7; // 12
    int n2 = (int) -12.7; // -12
    int n3 = (int) (12.7 + 0.5); // 13
    int n4 = (int) 1.2e20; // 2147483647


    5. 布尔运算

    布尔(boolean)只有truefalse两个值。布尔运算是一种关系运算,包括以下几类:

    • 比较运算符:>>=<<===!=
    • 与运算 &&
    • 或运算 ||
    • 非运算 !
    boolean isGreater = 5 > 3; // true
    int age = 12;
    boolean isZero = age == 0; // false
    boolean isNonZero = !isZero; // true
    boolean isAdult = age >= 18; // false
    boolean isTeenager = age >6 && age <18; // true

    关系运算符的优先级:

    • !
    • >>=<<=
    • ==!=
    • &&
    • ||


    强制转型

    Java还提供一个三元运算符b ? x : y,它根据第一个布尔表达式的结果,分别返回后续两个表达式之一的计算结果。


    6. 字符和字符串

    在Java中,字符和字符串是两个不同的类型。

    字符

    在Java中,字符类型(char)是基本类型,一个char类型可以保存一个Unicode字符:

    char c1 = 'A';
    char c2 = '中';

    因为Java在内存中总是使用Unicode表示字符,所以,一个英文字符和一个中文字符都用一个char类型表示,它们都占用两个字节。要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可:

    int n1 = 'A'; // 字母“A”的Unicodde编码是65
    int n2 = '中'; // 汉字“中”的Unicode编码是20013

    还可以直接用转义字符\u+Unicode编码来表示一个字符:

    // 注意是十六进制:
    char c3 = '\u0041'; // 'A',因为十六进制0041 = 十进制65
    char c4 = '\u4e2d'; // '中',因为十六进制4e2d = 十进制20013


    字符串

    字符串类型String是引用类型,用双引号""表示字符串。一个字符串可以存储0个到任意个字符:

    String s = ""; // 空字符串,包含0个字符
    String s1 = "A"; // 包含一个字符
    String s2 = "ABC"; // 包含3个字符
    String s3 = "中文 ABC"; // 包含6个字符,其中有一个空格


    连接字符串

    可以用+来连接两个字符串,或者将一个字符串与其它任意类型连接:

    String s1 = "Hello";
    String s2 = "world";
    String s = s1 + " " + s2

    与其它类型连接时,会首先将其转为字符串:

    int age = 25;
    String s = "age is " + age;
    System.out.println(s);

    连接字符串

    +来表示多行字符串:

    String s = "first line \n"
              + "second line \n"
              + "end";

    从Java 13开始,可以用...来表示多行字符串(Text Blocks):

    String s = """
               SELECT * FROM
                 users
               WHERE id > 100
               ORDER BY name DESC
               """;

    不可变特性

    对于以下代码来说:

    public class Main {
        public static void main(String[] args) {
            String s = "hello";
            System.out.println(s); // 显示 hello
            s = "world";
            System.out.println(s); // 显示 world
        }
    }

    JVM工作过程是:

    1. 创建一个字符串hello
    2. 将变量s指定hello
    3. 创建一个字符串world
    4. 再将变量s指定world

    也就是说,其是引用类型,不能改变原内存的内容,而只能将其指定新的内存地址。

    空值null与空字符串""

    引用类型的变量可以指向一个空值null,它表示不存在,即该变量不指向任何对象;而指向""时表示指向一个空字符串。

    String s1 = null; // s1是null
    String s2; // 没有赋初值值,s2也是null
    String s3 = s1; // s3也是null
    String s4 = ""; // s4指向空字符串,不是null


    数组类型

    Java中的数组是一组相同类型变量的列表,定义形式为:类型[],数组必须使用new来初始化,如:new int[5]表示创建一个可容纳5个int元素的数组。

    Java数组有以下特点为:

    • 数组所有元素初始化为默认值,整型是0,浮点型是0.0,布尔型是false
    • 数组创建后大小不可变

    要访问数组中元素使用索引。数组索引从0开始,如:5个元素的数组,其索引范围是0~4

    可以通过赋值语句,对数组元素进行修改,如:ns[1] = 79;

    可以通过.length属性获取数组长度:

    int[] ns = new int[5];
    System.out.println(ns.length); // 5

    数组是引用类型,使用索引访问数组元素时,如果索引超过数组范围,将报错。

    创建数组时可以指定数组元素,这时可以不指定数组长度:

    int[] ns = new int[] {1, 2, 3, 4, 5};
    System.out.println(ns.length); // 5

    还可以进一度简写为字面量形式:

    int[] ns = {1, 2, 3, 4, 5};

    数组是引用类型,并且数组大小不可变,但我们可以将变量指向一个新数组:

    int[] ns;
    ns = new int[] { 68, 79, 91, 85, 62 };
    System.out.println(ns.length); // 5
    ns = new int[] { 1, 2, 3 };
    System.out.println(ns.length); // 3


    字符串数组

    如果数组元素除了可以是基本类型来,还可以是一个引用类型,如:字符串。

    String[] names = {"ABC", "XYZ", "zoo"};
    String s = names[1];[
    s = "cat";
    System.out.println(names[1]);   // XYZ]