【编程学习】Java 教程 02
2023-12-24 09:39:33 Author: 利刃信安攻防实验室(查看原文) 阅读量:2 收藏

Java 快速入门

Java 简介

Java 介于编译型语言和解释型语言之间。编译型语言如 C、C++,代码是直接编译成机器码执行,但是不同的平台(x86、ARM 等)CPU 的指令集不同,因此,需要编译出每一种平台的对应机器码。解释型语言如 Python、Ruby 没有这个问题,可以由解释器直接加载源码然后运行,代价是运行效率太低。而 Java 是将代码编译成一种“字节码”,它类似于抽象的 CPU 指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果。当然,这是针对 Java 开发者而言。对于虚拟机,需要为每个平台分别开发。为了保证不同平台、不同公司开发的虚拟机都能正确执行 Java 字节码,SUN 公司制定了一系列的 Java 虚拟机规范。从实践的角度看,JVM 的兼容性做得非常好,低版本的 Java 字节码完全可以正常运行在高版本的 JVM 上。

  • Java SE:Standard Edition

  • Java EE:Enterprise Edition

  • Java ME:Micro Edition

Java SE 就是标准版,包含标准的 JVM 和标准库,而 Java EE 是企业版,它只是在 Java SE 的基础上加上了大量的 API 和库,以便方便开发 Web 应用、数据库、消息服务等,Java EE 的应用使用的虚拟机和 Java SE 完全相同。Java ME 就和 Java SE 不同,它是一个针对嵌入式设备的“瘦身版”,Java SE 的标准库无法在 Java ME 上使用,Java ME 的虚拟机也是“瘦身版”。

Java SE 是整个 Java 平台的核心,而 Java EE 是进一步学习 Web 应用所必须的。我们熟悉的 Spring 等框架都是 Java EE 开源生态系统的一部分。不幸的是,Java ME 从来没有真正流行起来,反而是 Android 开发成为了移动平台的标准之一,因此,没有特殊需求,不建议学习 Java ME。

首先要学习 Java SE,掌握 Java 语言本身、Java 核心开发技术以及 Java 标准库的使用;

如果继续学习 Java EE,那么 Spring 框架、数据库开发、分布式架构就是需要学习的;

如果要学习大数据开发,那么 Hadoop、Spark、Flink 这些大数据平台就是需要学习的,他们都基于 Java 或 Scala 开发;

如果想要学习移动开发,那么就深入 Android 平台,掌握 Android App 开发。

  • JDK:Java Development Kit

  • JRE:Java Runtime Environment

JRE 就是运行 Java 字节码的虚拟机。但是,如果只有 Java 源码,要编译成 Java 字节码,就需要 JDK,因为 JDK 除了包含 JRE,还提供了编译器、调试器等开发工具。

  • JSR 规范:Java Specification Request

  • JCP 组织:Java Community Process

JSR 是一系列的规范,从 JVM 的内存模型到 Web 程序接口,全部都标准化了。而负责审核 JSR 的组织就是 JCP。

  • RI:Reference Implementation

  • TCK:Technology Compatibility Kit

RI 只是一个“能跑”的正确的代码,它不追求速度,所以,如果真正要选择一个 Java 的消息服务器,一般是没人用 RI 的,大家都会选择一个有竞争力的商用或开源产品。

安装 JDK

Windows 优先选x64 MSI Installer,Linux 和 macOS 要根据自己电脑的 CPU 是 ARM 还是 x86 选择合适的安装包。

设置环境变量

安装完 JDK 后,需要设置一个JAVA_HOME的环境变量,它指向 JDK 的安装目录。在 Windows 下,它是安装目录,类似:

C:\Program Files\Java\jdk-21

在 Mac 下,它在~/.bash_profile~/.zprofile里,它是:

export JAVA_HOME=`/usr/libexec/java_home -v 21`

然后,把JAVA_HOMEbin目录附加到系统环境变量PATH上。在 Windows 下,它长这样:

Path=%JAVA_HOME%\bin;<现有的其他路径>

在 Mac 下,它在~/.bash_profile~/.zprofile里,长这样:

export PATH=$JAVA_HOME/bin:$PATH

JAVA_HOMEbin目录添加到PATH中是为了在任意文件夹下都可以运行java。打开命令提示符窗口,输入命令java -version,如果一切正常,你会看到如下输出:

D:\VMware\VMware Workstation\bin>java -version
java version "17.0.9" 2023-10-17 LTS
Java(TM) SE Runtime Environment (build 17.0.9+11-LTS-201)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.9+11-LTS-201, mixed mode, sharing)

如果你看到的版本号不是21,而是171.8之类,说明系统存在多个 JDK,且默认JDK不是JDK 21,需要把JDK 21提到PATH前面。

JDK
  • java:这个可执行程序其实就是 JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码;

  • javac:这是 Java 的编译器,它用于把Java源码文件(以.java后缀结尾)编译为Java字节码文件(以.class后缀结尾);

  • jar:用于把一组.class文件打包成一个.jar文件,便于发布;

  • javadoc:用于从Java源码中自动提取注释并生成文档;

  • jdbJava调试器,用于开发阶段的运行调试。

第一个 Java 程序

public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}

定义被称为class(类),这里的类名是Hello,大小写敏感,class用来定义一个类,public表示这个类是公开的,publicclass都是Java的关键字,必须小写,Hello是类的名字,按照习惯,首字母H要大写。而花括号{}中间则是类的定义。

方法是可执行的代码块,一个方法除了方法名main,还有用()括起来的方法参数,这里的main方法有一个参数,参数类型是String[],参数名是argspublicstatic用来修饰方法,这里表示它是一个公开的静态方法,void是方法的返回类型,而花括号{}中间的就是方法的代码,方法的代码每一行用;结束。

Java 规定,某个类定义的public static void main(String[] args)Java程序的固定入口方法,因此,Java程序总是从main方法开始执行。

Java源码的缩进不是必须的,但是用缩进后,格式好看,很容易看出代码块的开始和结束,缩进一般是 4 个空格或者一个tab

当我们把代码保存为文件时,文件名必须是Hello.java,而且文件名也要注意大小写,因为要和我们定义的类名Hello完全保持一致。

如何运行 Java 程序

Java源码本质上是一个文本文件,我们需要先用javacHello.java编译成字节码文件Hello.class,然后,用java命令执行这个字节码文件。

可执行文件javac是编译器,而可执行文件java就是虚拟机。

第一步,在保存Hello.java的目录下执行命令javac Hello.java

第二步,执行Hello.class,使用命令java Hello

给虚拟机传递的参数Hello是我们定义的类名,虚拟机自动查找对应的class文件并执行。

直接运行java Hello.java也是可以的;这是Java 11 新增的一个功能,它可以直接运行一个单文件源码!

在实际项目中,单个不依赖第三方库的Java源码是非常罕见的,所以,绝大多数情况下,我们无法直接运行一个Java源码文件,原因是它需要依赖其他的库。

一个Java源码只能定义一个public类型的class,并且class名称和文件名要完全一致;

使用javac可以将.java源码编译成.class字节码;

使用java可以运行一个已编译的Java程序,参数是类名。

使用 IDE

IDE是集成开发环境:Integrated Development Environment的缩写。

Eclipse

Eclipse 是由 IBM 开发并捐赠给开源社区的一个 IDE,也是目前应用最广泛的 IDE。Eclipse 的特点是它本身是 Java 开发的,并且基于插件结构,即使是对 Java 开发的支持也是通过插件 JDT 实现的。除了用于 Java 开发,Eclipse 配合插件也可以作为 C/C++开发环境、PHP 开发环境、Rust 开发环境等。

IntelliJ Idea

IntelliJ Idea 是由 JetBrains 公司开发的一个功能强大的 IDE,分为免费版和商用付费版。JetBrains 公司的 IDE 平台也是基于 IDE 平台+语言插件的模式,支持 Python 开发环境、Ruby 开发环境、PHP 开发环境等,这些开发环境也分为免费版和付费版。

NetBeans

NetBeans 是最早由 SUN 开发的开源 IDE,由于使用人数较少,目前已不再流行。

使用 Eclipse
安装 Eclipse

Eclipse 的发行版提供了预打包的开发环境,包括 JavaJavaEEC++PHPRust 等。

我们需要下载的版本是 Eclipse IDE for Java Developers

根据操作系统是 Windows、Mac 还是 Linux,从右边选择对应的下载链接。

对于“Text file encoding”,如果Default不是UTF-8,一定要改为“Other:UTF-8”,所有文本文件均使用UTF-8编码;

对于“New text file line delimiter”,建议使用Unix,即换行符使用\n而不是Windows\r\n

新建 Java 项目
新建 Java 文件并运行

Java 程序基础

Java 程序基本结构

/**
* 可以用来自动创建文档的注释
*/
public class Hello {
public static void main(String[] args) {
// 向屏幕输出文本:
System.out.println("Hello, world!");
/* 多行注释开始
注释内容
注释结束 */
}
} // class定义结束

因为Java是面向对象的语言,一个程序的基本单位就是classclass是关键字,这里定义的class名字就是Hello

public class Hello { // 类名是Hello
// ...
} // class定义结束

类名要求:

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

  • 习惯以大写字母开头

public是访问修饰符,表示该class是公开的。

不写public,也能正确编译,但是这个类将无法从命令行执行。

class内部,可以定义若干方法(method):

public class Hello {
public static void main(String[] args) { // 方法名是main
// 方法代码...
} // 方法定义结束
}

方法定义了一组执行语句,方法内部的代码将会被依次顺序执行。

这里的方法名是main,返回值是void,表示没有任何返回值。

public除了可以修饰class外,也可以修饰方法。而关键字static是另一个修饰符,它表示静态方法,Java入口程序规定的方法必须是静态方法,方法名必须为main,括号内的参数必须是String数组。

方法名也有命名规则,命名和class一样,但是首字母小写。

在方法内部,语句才是真正的执行代码。Java的每一行语句必须以分号结束:

public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!"); // 语句
}
}

Java程序中,注释是一种给人阅读的文本,不是程序的一部分,所以编译器会自动忽略注释。

Java有 3 种注释,第一种是单行注释,以双斜线开头,直到这一行的结尾结束:

而多行注释以/*星号开头,以*/结束,可以有多行:

还有一种特殊的多行注释,以/**开头,以*/结束,如果有多行,每行通常以星号开头:

// 这是注释...
/*
这是注释
blablabla...
这也是注释
*/

/**
* 可以用来自动创建文档的注释
*
* @auther liaoxuefeng
*/
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}

这种特殊的多行注释需要写在类和方法的定义处,可以用于自动创建文档。

变量和数据类型

变量

在 Java 中,变量分为两种:基本类型的变量和引用类型的变量。

在 Java 中,变量必须先定义后使用,在定义变量的时候,可以给它一个初始值。

变量的一个重要特点是可以重新赋值。

注意到第一次定义变量x的时候,需要指定变量类型int,因此使用语句int x = 100;。而第二次重新赋值的时候,变量x已经存在了,不能再重复定义,因此不能指定变量类型int,必须使用语句x = 200;

public class Main {
public static void main(String[] args) {
int x = 100; // 定义int类型变量x,并赋予初始值100
System.out.println(x); // 打印该变量的值,观察是否为100
x = 200; // 重新赋值为200
System.out.println(x); // 打印该变量的值,观察是否为200
}
}

变量不但可以重新赋值,还可以赋值给其他变量。

基本数据类型
  • 整数类型:byte,short,int,long

  • 浮点数类型:float,double

  • 字符类型:char

  • 布尔类型:boolean

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

内存单元从 0 开始编号,称为内存地址。每个内存单元可以看作一间房间,内存地址就是门牌号。

一个字节是 1byte,1024 字节是 1K,1024K 是 1M,1024M 是 1G,1024G 是 1T。

image

byte恰好就是一个字节,而longdouble需要 8 个字节。

整型

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

  • byte:-128 ~ 127

  • short: -32768 ~ 32767

  • int: -2147483648 ~ 2147483647

  • long: -9223372036854775808 ~ 9223372036854775807

public class Main {
public static void main(String[] args) {
int i = 2147483647;
int i2 = -2147483648;
int i3 = 2_000_000_000; // 加下划线更容易识别
int i4 = 0xff0000; // 十六进制表示的16711680
int i5 = 0b1000000000; // 二进制表示的512

long n1 = 9000000000000000000L; // long型的结尾需要加L
long n2 = 900; // 没有加L,此处900为int,但int类型可以赋值给long
int i6 = 900L; // 错误:不能把long型赋值给int
}
}

同一个数的不同进制的表示是完全相同的,例如15=0xf0b1111

浮点型

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

float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
float f3 = 1.0; // 错误:不带f结尾的是double类型,不能赋值给float

double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324

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

浮点数可表示的范围非常大,float类型可最大表示3.4x10^38,而double类型可最大表示 1.79x10^308

布尔类型

布尔类型boolean只有truefalse两个值。

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

Java语言对布尔类型的存储并没有做规定,因为理论上存储布尔类型只需要 1 bit,但是通常JVM内部会把boolean表示为 4 字节整数。

字符类型

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

public class Main {
public static void main(String[] args) {
char a = 'A';
char zh = '中';
System.out.println(a);
System.out.println(zh);
}
}

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

引用类型

引用类型最常用的就是String字符串:

String s = "hello";

引用类型的变量类似于 C 语言的指针,它内部存储一个“地址”,指向某个对象在内存的位置。

常量

定义变量的时候,如果加上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 关键字

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

StringBuilder sb = new StringBuilder();

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

var sb = new StringBuilder();

编译器会根据赋值语句自动推断出变量 sb 的类型是StringBuilder

变量的作用范围

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

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

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

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

整数运算

Java 的整数运算遵循四则运算规则,可以使用任意嵌套的小括号。四则运算规则和初等数学一致。

整数的数值表示不但是精确的,而且整数运算永远是精确的,即使是除法也是精确的,因为两个整数相除只能得到结果的整数部分:

int x = 12345 / 67; // 184

求余运算使用%

int y = 12345 % 67; // 12345÷67的余数是17

整数的除法对于除数为 0 时运行时将报错,但编译不会报错。

溢出

整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果:

public class Main {
public static void main(String[] args) {
int x = 2147483640;
int y = 15;
int sum = x + y;
System.out.println(sum); // -2147483641
}
}

有一种简写的运算符,即+=-=*=/=

自增/自减

Java 还提供了++运算和--运算,它们可以对一个整数进行加 1 和减 1 的操作:

public class Main {
public static void main(String[] args) {
int n = 3300;
n++; // 3301, 相当于 n = n + 1;
n--; // 3300, 相当于 n = n - 1;
int y = 100 + (++n); // 不要这么写
System.out.println(y);
}
}

注意++写在前面和后面计算结果是不同的,++n表示先加 1 再引用 n,n++表示先引用 n 再加 1。不建议把++运算混入到常规运算中,容易自己把自己搞懵了。

移位运算

在计算机中,整数总是以二进制的形式表示。例如,int类型的整数7使用4字节表示的二进制如下:

00000000 0000000 0000000 00000111

可以对整数进行移位运算。对整数7左移1位将得到整数14,左移两位将得到整数28

int n = 7;       // 00000000 00000000 00000000 00000111 = 7
int a = n << 1; // 00000000 00000000 00000000 00001110 = 14
int b = n << 2; // 00000000 00000000 00000000 00011100 = 28
int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192
int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912

左移29位时,由于最高位变成1,因此结果变成了负数。

类似的,对整数 28 进行右移,结果如下:

int n = 7;       // 00000000 00000000 00000000 00000111 = 7
int a = n >> 1; // 00000000 00000000 00000000 00000011 = 3
int b = n >> 2; // 00000000 00000000 00000000 00000001 = 1
int c = n >> 3; // 00000000 00000000 00000000 00000000 = 0

如果对一个负数进行右移,最高位的 1 不动,结果仍然是一个负数:

int n = -536870912;
int a = n >> 1; // 11110000 00000000 00000000 00000000 = -268435456
int b = n >> 2; // 11111000 00000000 00000000 00000000 = -134217728
int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2
int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1

还有一种无符号的右移运算,使用>>>,它的特点是不管符号位,右移后高位总是补 0,因此,对一个负数进行>>>右移,它会变成正数,原因是最高位的 1 变成了 0:

int n = -536870912;
int a = n >>> 1; // 01110000 00000000 00000000 00000000 = 1879048192
int b = n >>> 2; // 00111000 00000000 00000000 00000000 = 939524096
int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7
int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1

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

左移实际上就是不断地×2,右移实际上就是不断地÷2

位运算

位运算是按位进行与、或、非和异或的运算。

与运算的规则是,必须两个数同时为 1,结果才为 1:

或运算的规则是,只要任意一个为 1,结果就为 1:

非运算的规则是,0 和 1 互换:

异或运算的规则是,如果两个数不同,结果为 1,否则为 0:

n = 0 ^ 0; // 0
n = 0 ^ 1; // 1
n = 1 ^ 0; // 1
n = 1 ^ 1; // 0
运算优先级

在 Java 的计算表达式中,运算优先级从高到低依次是:

  • ()

  • ! ~ ++ --

  • * / %

  • + -

  • << >> >>>

  • &

  • |

  • += -= *= /=

类型自动提升与强制转型

在运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。例如,shortint计算,结果总是int,原因是 short 首先自动被转型为int

也可以将结果强制转型,即将大范围的整数转型为小范围的整数。强制转型使用(类型),例如,将int强制转型为short

public class Main {
public static void main(String[] args) {
int i1 = 1234567;
short s1 = (short) i1; // -10617
System.out.println(s1);
int i2 = 12345678;
short s2 = (short) i2; // 24910
System.out.println(s2);
}
}

因此,强制转型的结果很可能是错的。

浮点数运算

浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算。

在计算机中,浮点数虽然表示的范围大,但是,浮点数有个非常重要的特点,就是浮点数常常无法精确表示。

浮点数0.1在计算机中就无法精确表示,因为十进制的0.1换算成二进制是一个无限循环小数,很显然,无论使用float还是double,都只能存储一个0.1的近似值。但是,0.5这个浮点数又可以精确地表示。

因为浮点数常常无法精确表示,因此,浮点数运算会产生误差。

public class Main {
public static void main(String[] args) {
double x = 1.0 / 10;
double y = 1 - 9.0 / 10;
// 观察x和y是否相等:
System.out.println(x); //0.1
System.out.println(y); //0.09999999999999998
}
}

由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数:

// 比较x和y是否相等,先计算其差的绝对值:
double r = Math.abs(x - y);
// 再判断绝对值是否足够小:
if (r < 0.00001) {
// 可以认为相等
} else {
// 不相等
}
类型提升

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

public class Main {
public static void main(String[] args) {
int n = 5;
double d = 1.2 + 24.0 / n; // 6.0
System.out.println(d);
}
}

在一个复杂的四则运算中,两个整数的运算不会出现自动提升的情况。

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

计算结果为5.2,原因是编译器计算24 / 5这个子表达式时,按两个整数进行运算,结果仍为整数4

溢出

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

  • NaN表示Not a Number

  • Infinity表示无穷大

  • -Infinity表示负无穷大

double d1 = 0.0 / 0; // NaN
double d2 = 1.0 / 0; // Infinity
double d3 = -1.0 / 0; // -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

如果要进行四舍五入,可以对浮点数加上 0.5 再强制转型:

public class Main {
public static void main(String[] args) {
double d = 2.6;
int n = (int) (d + 0.5);
System.out.println(n);
}
}
image
public class Main {
public static void main(String[] args) {
double a = 1.0;
double b = 3.0;
double c = -4.0;
double r1 = 0;
double r2 = 0;
r1 = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
r2 = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
System.out.println(r1);
System.out.println(r2);
System.out.println(r1 == 1 && r2 == -4 ? "测试通过" : "测试失败");
}
}

布尔运算

对于布尔类型boolean,永远只有truefalse两个值。

布尔运算是一种关系运算,包括以下几类:

  • 比较运算符:>>=<<===!=

  • 与运算 &&

  • 或运算 ||

  • 非运算 !

关系运算符的优先级从高到低依次是:

  • !

  • >>=<<=

  • ==!=

  • &&

  • ||

短路运算

布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。

因为false && x的结果总是false,无论xtrue还是false,因此,与运算在确定第一个值为false后,不再继续计算,而是直接返回false

类似的,对于||运算,只要能确定第一个值为true,后续计算也不再进行,而是直接返回true

三元运算符

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

三元运算b ? x : y会首先计算b,如果btrue,则只计算x,否则,只计算y。此外,xy的类型必须相同,因为返回值不是boolean,而是xy之一。

字符和字符串

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

字符类型

字符类型char是基本数据类型,它是character的缩写。一个char保存一个Unicode字符。

因为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是引用类型,我们用双引号"..."表示字符串。

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 的编译器对字符串做了特殊照顾,可以使用+连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。

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

多行字符串

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

public class Main {
public static void main(String[] args) {
String s = """
SELECT * FROM
users
WHERE id > 100
ORDER BY name DESC
""";
System.out.println(s);
}
}

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

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

多行字符串前面共同的空格会被去掉,即:

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

.标注的空格都会被去掉。

不可变特性

Java 的字符串除了是一个引用类型外,还有个重要特点,就是字符串不可变。

空值 null

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

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

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

/**
* char and String
*/
public class Main {

public static void main(String[] args) {
// 请将下面一组int值视为字符的Unicode码,把它们拼成一个字符串:
int a = 72;
int b = 105;
int c = 65281;
String s = "" + (char) a + (char) b + (char) c;
System.out.println(s);
}

}

数组类型

public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
ns[0] = 68;
ns[1] = 79;
ns[2] = 91;
ns[3] = 85;
ns[4] = 62;
}
}

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

Java 的数组有几个特点:

  • 数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false

  • 数组一旦创建后,大小就不可改变。

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

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

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

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

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

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

还可以进一步简写为:

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

注意数组是引用类型,并且数组大小不可变。

字符串数组

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

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

数组是同一数据类型的集合,数组一旦创建后,大小就不可变;可以通过索引访问数组元素,但索引超出范围将报错;数组元素可以是值类型(如 int)或引用类型(如 String),但数组本身是引用类型。


文章来源: http://mp.weixin.qq.com/s?__biz=MzU1Mjk3MDY1OA==&mid=2247508992&idx=1&sn=0fa59bca1ebd35a7ce36957323cacdbf&chksm=faa4d5c74925c9fa8a2eee50c25e7114cf805c61a6e1425e4a4e50edbb3b76142222da8107cc&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh