Java快速入门(二)


目录:

Java快速入门(二)

本节我们将介绍Java程序的基础知识,包括:

  • Java程序基本结构
  • 变量和数据类型
  • 整数运算
  • 浮点数运算
  • 布尔运算
  • 字符和字符串
  • 数组类型

Java程序基本结构与类型

/**
 * 多行注释创建文档的注释
 */
// public是访问修饰符,表示该class是公开的。
// 因为Java是面向对象的语言,在Java中一个程序的基本单位就是class
// class是类的关键字,这里定义的class名字就是Hello
public class Hello {
    // 在class内部,可以定义若干方法(method)
    // main 方法是程序的入口方法必须是 public static 
    // static 表示静态方法,可以不创建对象直接通过类名访问,也是必须的
    // void 表示返回值是空的
    // 方法名必须为main,括号内的参数必须是String数组。
    public static void main(String[] args) {
        // 向屏幕输出文本:
        System.out.println("Hello, world!");
        /* 多行注释开始
        注释内容
        注释结束 */
    }
} // class定义结束

类名要求:

  • 类名必须以英文字母开头,后接字母,数字和下划线的组合
  • 习惯以大写字母开头

要注意遵守命名习惯,好的类命名:

  • Hello
  • NoteBook
  • VRPlayer

不好的类命名:

  • hello
  • Good123
  • Note_Book
  • _World

变量

​ 程序在运行过程中,将要使用的数据都要缓存在内存中,为了方便使用数据,我们一般都会给数据起别名,不能修改的数据叫做常量,可以修改叫做变量。

​ 我们根据数据的格式和所占字节空间的大小,将数据分为不同的类型。

  • 定义格式
// 数据类型 变量名 = 初始化值; (声明、赋值)
int num1 = 123;
  • 使用注意

    • 同一作用域内同一变量不可重复声明

    • 变量必须初始化后才能使用(即必须有值才能使用),否则编译报错

    • 变量的值可在同一类型不断变化

    • 变量命名符合标识符规范,使用驼峰命名法,首字母小写

    • 就近原则

      变量分类

    • 成员变量/全局变量/字段:

      • 直接定义在类中方法外的变量
      • 类变量 使用 static 修饰的字段 方法区
      • 实例变量 没有使用 static 修饰的字段
      • 默认是有初始值的,可以先在方法中使用后定义
      • 作用域是整个类
    • 局部变量

      • 1.方法的形参;2. 方法内中的变量;3.代码块中的变量
      • 没有初始值,必须显式初始化后才能使用
      • 定义局部变量后,系统并未分配内存空间,直到程序为这个变量赋值时,系统才会在所在方法的的栈内存中为局部变量分配内存,并将初始值(基本类型的值或者对象的引用)保存在该内存中
      • 定义的位置 开始到所在结束的花括号

基本数据类型

​ 基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:

  • 整数类型:byte,short,int,long
  • 浮点数类型:float,double
  • 字符类型:char
  • 布尔类型:boolean

​ 计算机内存的最小存储单元是字节(byte),一个字节就是一个8位二进制数,即8个bit。它的二进制表示范围从00000000~11111111,换算成十进制是0~255,换算成十六进制是00~ff

       ┌───┐
  byte │   │
       └───┘
       ┌───┬───┐
 short │   │   │
       └───┴───┘
       ┌───┬───┬───┬───┐
   int │   │   │   │   │
       └───┴───┴───┴───┘
       ┌───┬───┬───┬───┬───┬───┬───┬───┐
  long │   │   │   │   │   │   │   │   │
       └───┴───┴───┴───┴───┴───┴───┴───┘
       ┌───┬───┬───┬───┐
 float │   │   │   │   │
       └───┴───┴───┴───┘
       ┌───┬───┬───┬───┬───┬───┬───┬───┐
double │   │   │   │   │   │   │   │   │
       └───┴───┴───┴───┴───┴───┴───┴───┘
       ┌───┬───┐
  char │   │   │
       └───┴───┘
int n = 100; // 声明一个整数类型的变量n,JVM在内存中开辟一个int空间存储 
--------------------------------------------------------------
      n
      
      
┌───┬───┬───┬───┬───┬───┬───┐
│   │100│   │   │   │   │   │
└───┴───┴───┴───┴───┴───┴───┘
--------------------------------------------------------------
n = 200; // 修改变量n的值为200
--------------------------------------------------------------
      n
      
      
┌───┬───┬───┬───┬───┬───┬───┐
│   │200│   │   │   │   │   │
└───┴───┴───┴───┴───┴───┴───┘
--------------------------------------------------------------
int x = n; 
// 定义了一个新的变量x,JVM新分配一个存储单元给变量x
// 并写入和变量n一样的值,结果是变量x的值也变为200:
--------------------------------------------------------------
      n           x
      │           │
      ▼           ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │200│   │   │200│   │   │
└───┴───┴───┴───┴───┴───┴───┘
--------------------------------------------------------------
x = x + 100; // 注意,等号=是赋值语句,不是数学意义上的相等
--------------------------------------------------------------
      n           x
      │           │
      ▼           ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │200│   │   │300│   │   │
└───┴───┴───┴───┴───┴───┴───┘
--------------------------------------------------------------

整型

​ 对于整型类型,Java只定义了带符号的整型,因此,最高位的bit表示符号位(0表示正数,1表示负数)。各种整型能表示的最大范围如下:

  • byte:-128 ~ 127
  • short: -32768 ~ 32767
  • int: -2147483648 ~ 2147483647
  • long: -9223372036854775808 ~ 9223372036854775807

浮点型

​ 浮点类型的数就是小数,因为小数用科学计数法表示的时候,小数点是可以“浮动”的,如1234.5可以表示成12.345x102,也可以表示成1.2345x103,所以称为浮点数。

​ 下面是定义浮点数的例子:

float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324

​ 对于float类型,需要加上f后缀。

​ 浮点数可表示的范围非常大,float类型可最大表示3.4x1038,而double类型可最大表示1.79x10308。

布尔类型

​ 布尔类型boolean只有truefalse两个值,布尔类型总是关系运算的计算结果:

boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // 计算结果为true
int age = 12;
boolean isAdult = age >= 18; // 计算结果为false

字符类型

​ 字符类型char表示一个字符。Java的char类型除了可表示标准的ASCII外,还可以表示一个Unicode字符:

char a = 'A';
char zh = '中';

​ 注意char类型使用单引号',且仅有一个字符,要和双引号"的字符串类型区分开。

基本类型的类型转换

  • boolean 不属于数值类型,不参与转换

自动类型转换 / 隐式类型转换

  • 如果直接将一个较小的整数常量(在 byte 或 short 类型的表数范围内)赋给一个 byte 或 short 变量,系统会自动把这个整型常量当成 byte 或 short 类型来处理

强制类型转换

  • 语法格式:(targetType)value
byte b2 = 65; // 系统会自动把 65 当成 byte 类型处理
int a = 12;
byte b = (byte) a; // 强制类型转换
float a = (float) 5.6; // 强制类型转换
int i = (int) -12.81; // 强制类型转换(小数部分被截掉),i = -12
(char) 65 // 表示 'A' 字符

表达式类型的自动提升

  • 所有的 byteshortchar 类型被自动提升到 int 类型
  • 整个算术表达式最终结果的数据类型被提升到表达式中操作数类型最高的类型
short s = 5; // 自动类型转换(隐式类型转换)
s = s - 2; // 错误: 不兼容的类型: 从int转换到short可能会有损失

常量

​ 定义变量的时候,如果加上final修饰符,这个变量就变成了常量:

final double PI = 3.14; // PI是一个常量
double r = 5.0;
double area = PI * r * r;
PI = 300; // compile error!

​ 常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。

​ 常量的作用是用有意义的变量名来避免魔术数字(Magic number),例如,不要在代码中到处写3.14,而是定义一个常量。如果将来需要提高计算精度,我们只需要在常量的定义处修改,例如,改成3.1416,而不必在所有地方替换3.14

​ 根据习惯,常量名通常全部大写。

var关键字

​ Java中的var是Java10版本新出的特性,用它来定义局部变量

语法:

var 变量名=初始值;

​ 有些时候,类型的名字太长,写起来比较麻烦。例如:

StringBuilder sb = new StringBuilder();

​ 这个时候,如果想省略变量类型,可以使用var关键字:

var sb = new StringBuilder();

变量的作用范围

​ 在Java中,多行语句用{ }括起来。很多控制语句,例如条件判断和循环,都以{ }作为它们自身的范围,例如:

if (...) { // if开始
    ...
    while (...) { // while 开始
        ...
        if (...) { // if开始
            ...
        } // if结束
        ...
    } // while结束
    ...
} // if结束

​ 只要正确地嵌套这些{ },编译器就能识别出语句块的开始和结束。而在语句块中定义的变量,它有一个作用域,就是从定义处开始,到语句块结束。超出了作用域引用这些变量,编译器会报错。举个例子:

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

​ 定义变量时,要遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域,并且,不要重复使用变量名。

小结

​ Java提供了两种变量类型:基本类型和引用类型

​ 基本类型包括整型,浮点型,布尔型,字符型。

​ 变量可重新赋值,等号是赋值语句,不是数学意义的等号。

​ 常量在初始化后不可重新赋值,使用常量便于理解程序意图。

运算符

算术运算符

加号 +

  • 加号在操作数值、字符、字符串时,其结果是不同的
    • 当两个字符相加得到的是 ASCII 码表值
    • 作为字符串连接运算符

除号 /

  • 如果除法运算符的两个操作数都是整数类型,则计算结果也是整数(截断小数部分取整),此时除数不能是 0,否则将引发除以零异常 ArithmeticException: / by zero
  • 如果除法运算符的两个操作数有 1 个是浮点数,或者 2 个都是浮点数,则计算结果也是浮点数,此时允许除数是 0,或者 0.0,得到结果是正无穷大或负无穷大

取模(求余数)%

  • 被模数 % 模数
  • 模数的符号忽略不计,结果的正负取决于被模数
  • 如果求余运算的两个操作数都是整数类型,则求余运算的第二个运算数不能是 0,否则将引发除以零异常
'A' + 'B'  // 131
"A" + "B"  // AB
10 / 3  // 3
System.out.println(10 / 0) // ArithmeticException: / by zero
System.out.println(10.0 / 0) // 输出正无穷大:Infinity System.out.println(-10.0 / 0) // 输出负无穷大:-Infinity System.out.println(0 / 0.0) // 输出非数(Not a Number):NaN

/*注意:无穷大和 NaN 都属于 double 浮点类型但是所有正无穷大数值都是相等的,所有负无穷大数值也是相等的NaN 不与任何数值相等,也不等于自己*/

自增 ++ 自减 —

  • 自增、自减操作都是直接修改变量的值(读、改、写),不经过操作数栈
  • 前置 ( ++i ):局部变量表中的 i 先自增,再把 i 的值压入操作数栈
  • 后置 ( i++ ):先把局部变量表中的 i 压入操作数栈,再自增
int i = 1;
i = ++i;
System.out.println(i); // 2
int j = 1;
j = j++;
System.out.println(j); // 1

赋值运算符

  • += 隐式的将加操作的结果类型强制转换为持有结果的类型
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to 
byteb += a; // ok
short s = 5;
s += 5; // s = (short)(s + 5)

比较运算符

  • 比较运算符的结果是 boolean 类型
  • \== != < > <= >= instanceof

  • 使用 == 判断两个两个变量是否相等

    • 基本类型变量:只要两个变量的值相等( 不一定要求数据类型严格相同),就返回 true
    • 引用类型变量:只有两个变量指向同一个对象时,返回 true(不可用于比较类型上没有继承关系的两个对象,否则编译报错)
97 == 'a'; // true
5.0 == 5; // true
"hello" == new Animal(); // 编译报错

三元运算符 / 三目运算符

  • boolean 表达式 ? 表达式 1 : 表达式 2
// 判断奇数偶数
String ret = number % 2 == 0 ? "偶数" : "奇数";

逻辑运算符

  • 用于操作两个 boolean 类型的变量或常量,结果也是 boolean 类型
  • & :与,都为 true,结果才为 true,否则结果是 false
  • && :短路与,如果左边的操作数是 false,结果一定为 false,且不再计算右边的操作数
  • | :或,都为 false,结果才为 false,只要有一个是 true,结果就是 true
  • || :短路或,如果左边的操作数是 true,结果一定为 true,且不再计算右边的操作数
  • ^ :异或,判断两个操作数是否不同,不同则为 true,相同则为 false
  • ! :取反,!true 结果是 false,!fasle 结果是 true

位运算符

  • 操作的是补码的二进制位
  • 位运算符只能操作整数类型的变量或值
  • & :按位与,当两位同时为 1 时才返回 1
  • | :按位或,只要有一位为 1 即可返回 1
  • ~ :按位非,单目运算符,将操作数的每个位(包括符号位)全部取反
  • ^ :按位异或,当两位相同时返回 0,不同时返回 1
  • << :左移运算符
  • >> :右移运算符
  • >>>:无符号右移运算符
// 左移 n 位相当于乘以 2 的 n 次方
// 右移 n 位相当于除以 2 的 n 次方
4 >> 1; // 效率高的除以 2,等价于4 / 2
1 << 10; // 等于 2 的 10 次方
// 交换两个变量的值
int a = 10;
int b = 12;// 第一种方法,使用临时变量
int temp = a;
a = b;
b = temp;// 第二种方法
// 把 a、b 看做数轴上的点,围绕两点间的距离来进行计算(可能会越界)
a = b - a;
b = b - a;
a = b + a;// 第三种方法
// 任意一个数与任意一个给定的值连续异或两次,值不变
a = a ^ b;
b = a ^ b;
a = a ^ b;

表达式

  • 表达式不是完整的语句!

字符和字符串

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

字符类型

字符类型char是基本数据类型,它是character的缩写。一个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

字符串类型

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

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

因为字符串使用双引号"…"表示开始和结束,那如果字符串本身恰好包含一个"字符怎么表示?例如,"abc"xyz",编译器就无法判断中间的引号究竟是字符串的一部分还是表示字符串结束。这个时候,我们需要借助转义字符\

String s = "abc\"xyz"; // 包含7个字符: a, b, c, ", x, y, z

因为\是转义字符,所以,两个\表示一个\字符:

String s = "abc\\xyz"; // 包含7个字符: a, b, c, \, x, y, z

常见的转义字符包括:

  • \" 表示字符"
  • \' 表示字符'
  • \ 表示字符\
  • \n 表示换行符
  • \r 表示回车符
  • \t 表示Tab
  • \u#### 表示一个Unicode编码的字符

例如:

String s = "ABC\n\u4e2d\u6587"; // 包含6个字符: A, B, C, 换行符, 中, 文

字符串连接

Java的编译器对字符串做了特殊照顾,可以使用+连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。例如:

字符和字符串 - 图1

如果用+连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串,再连接:

字符和字符串 - 图2

多行字符串

如果我们要表示多行字符串,使用+号连接会非常不方便:

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

从Java 13开始,字符串可以用"""…"""表示多行字符串(Text Blocks)了。举个例子:

字符和字符串 - 图3

上述多行字符串实际上是5行,在最后一个DESC后面还有一个\n。如果我们不想在字符串末尾加一个\n,就需要这么写(Java13开始支持):

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

最后,由于多行字符串是作为Java 13的预览特性(Preview Language Features)实现的,编译的时候,我们还需要给编译器加上参数:

javac --source 13 --enable-preview Main.java

不可变特性

Java的字符串除了是一个引用类型外,还有个重要特点,就是字符串不可变。考察以下代码:

字符和字符串 - 图4

观察执行结果,难道字符串s变了吗?其实变的不是字符串,而是变量s的“指向”。

执行String s = "hello";时,JVM虚拟机先创建字符串"hello",然后,把字符串变量s指向它:

      s
      
      
┌───┬───────────┬───┐
│   │  "hello"  │   │
└───┴───────────┴───┘

紧接着,执行s = "world";时,JVM虚拟机先创建字符串"world",然后,把字符串变量s指向它:

      s ──────────────┐
                      
                      
┌───┬───────────┬───┬───────────┬───┐
│   │  "hello"  │   │  "world"  │   │
└───┴───────────┴───┴───────────┴───┘

原来的字符串"hello"还在,只是我们无法通过变量s访问它而已。因此,字符串的不可变是指字符串内容不可变。

理解了引用类型的“指向”后,试解释下面的代码输出:

字符和字符串 - 图5

空值null

引用类型的变量可以指向一个空值null,它表示不存在,即该变量不指向任何对象。例如:

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

注意要区分空值null和空字符串"",空字符串是一个有效的字符串对象,它不等于null

练习

请将一组int值视为字符的Unicode编码,然后将它们拼成一个字符串:

字符和字符串 - 图6

小结

​ Java的字符类型char是基本类型,字符串类型String是引用类型;

​ 基本类型的变量是“持有”某个数值,引用类型的变量是“指向”某个对象;

​ 引用类型的变量可以是空值null

​ 要区分空值null和空字符串""

数组类型

如果我们有一组类型相同的变量,例如,5位同学的成绩,可以这么写:

public class Main {    
    public static void main(String[] args) {        
        // 5位同学的成绩:
        int n1 = 68;        
        int n2 = 79;        
        int n3 = 91;        
        int n4 = 85;        
        int n5 = 62;
    }
}

但其实没有必要定义5个int变量。可以使用数组来表示“一组”int类型。代码如下:

数组类型 - 图1

定义一个数组类型的变量,使用数组类型“类型[]”,例如,int[]。和单个基本类型变量不同,数组变量初始化必须使用new int[5]表示创建一个可容纳5个int元素的数组。

Java的数组有几个特点:

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

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

可以修改数组中的某一个元素,使用赋值语句,例如,ns[1] = 79;

可以用数组变量.length获取数组大小:

数组类型 - 图2

数组是引用类型,在使用索引访问数组元素时,如果索引超出范围,运行时将报错:

数组类型 - 图3

也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小。例如:

数组类型 - 图4

还可以进一步简写为:

int[] ns = { 68, 79, 91, 85, 62 };

注意数组是引用类型,并且数组大小不可变。我们观察下面的代码:

数组类型 - 图5

数组大小变了吗?看上去好像是变了,但其实根本没变。

对于数组ns来说,执行ns = new int[] { 68, 79, 91, 85, 62 };时,它指向一个5个元素的数组:

     ns
      
      
┌───┬───┬───┬───┬───┬───┬───┐
│   │68 │79 │91 │85 │62 │   │
└───┴───┴───┴───┴───┴───┴───┘

执行ns = new int[] { 1, 2, 3 };时,它指向一个_新的_3个元素的数组:

     ns ──────────────────────┐
                              
                              
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│   │68 │79 │91 │85 │62 │   │ 1 │ 2 │ 3 │   │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

但是,原有的5个元素的数组并没有改变,只是无法通过变量ns引用到它们而已。

字符串数组

如果数组元素不是基本类型,而是一个引用类型,那么,修改数组元素会有哪些不同?

字符串是引用类型,因此我们先定义一个字符串数组:

String[] names = { "ABC", "XYZ", "zoo"};

对于String[]类型的数组变量names,它实际上包含3个元素,但每个元素都指向某个字符串对象:

          ┌─────────────────────────┐
    names │   ┌─────────────────────┼───────────┐
      │   │   │                     │           │
      ▼   │   │                     ▼           ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │░░░│░░░│░░░│   │ "ABC" │   │ "XYZ" │   │ "zoo" │   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┘
      │                 ▲
      └─────────────────┘

names[1]进行赋值,例如names[1] = "cat";,效果如下:

          ┌─────────────────────────────────────────────────┐
    names │   ┌─────────────────────────────────┐           │
      │   │   │                                 │           │
      ▼   │   │                                 ▼           ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │░░░│░░░│░░░│   │ "ABC" │   │ "XYZ" │   │ "zoo" │   │ "cat" │   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
      │                 ▲
      └─────────────────┘

这里注意到原来names[1]指向的字符串"XYZ"并没有改变,仅仅是将names[1]的引用从指向"XYZ"改成了指向"cat",其结果是字符串"XYZ"再也无法通过names[1]访问到了。

对“指向”有了更深入的理解后,试解释如下代码:

数组类型 - 图6

小结

数组是同一数据类型的集合,数组一旦创建后,大小就不可变;

可以通过索引访问数组元素,但索引超出范围将报错;

数组元素可以是值类型(如int)或引用类型(如String),但数组本身是引用类型;