Skip to content
Go back

Java 基础语法

Edit page

Java 基础语法

简单说说 JAVA

Java 最初由 Sun 公司开发,后来 Sun 公司被 Oracle 收购了,由于 Java 的优良特性,很快便流行起来。

Java 有以下 11 点好处:

  1. 简单性:阉割了 C++ 许多容易带来麻烦的特性,包括指针运算、头文件、结构、联合、操作符重载、虚基类等;
  2. 面向对象
  3. 分布式:丰富的网络对象处理例程库;
  4. 健壮性:强大的编译器能够检测出很多其他编译器检测不出来的错误;
  5. 安全性:基于虚拟机运行的沙箱环境拥有良好的安全模型;
  6. 体系结构中立:为虚拟机设计的字节码,拥有相对中立的文件格式;
  7. 可移植性:精心设计的字节码搭配 JVM 虚拟机,数据类型边界唯一格式统一,拥有优秀的可移植性;
  8. 解释性:即探索性开发,这一点在 Python 等纯解释型语言上可以充分体验到,而 JAVA 的开发环境重点不在这种体验上;
  9. 高性能:具备热点代码自优化能力的即时编译器,具备高性能;
  10. 多线程
  11. 动态性:将代码添加到运行中的程序中,而不影响系统运行;

关于 OpenJDK 和 OracleJDK

OpenJDK 基于 Sun 公司捐赠的 HotSpot 源代码构建,是一个参考模型,是完全开源的。 OracleJDK 的构建实现过程就是基于 OpenJDK 的,并不是完全开源的。

先验知识

Java 语言规范、API、JDK、IDE

Java 的三个版本:

创建、编译和执行 Java 程序

picture 15

一个简单的例子

// Welcome.java
public class Welcome {
 public static void main(String[] args) {
  System.out.println("Welcome to Java!");
 }
}

通过命令行调用 jdk 编译器生成字节码文件(可执行文件、.class 文件),通过 JVM 执行。

#cmd
javac Welcome.java
java Welcome
# Welcome to Java!

在执行一个 Java 程序时, JVM 首先使用 类加载器(class loader) 将字节码载入到内存中,每个类在使用之前都需要被动态地载入到内存中。类被加载进内存之后, JVM 会使用 字节码验证器(bytecode verifier) 对字节码进行验证,以保证程序符合安全规范不会篡改或者危害计算机。随后通过解释器逐行执行。后来引进了运行时编译器—— JIT 编译器,将热点代码的机器码在第一次编译时就保存下来,重复执行。改善了解释器在解释热点代码时效率低的问题。

这种解释与编译共存的方式是 JAVA 的特点。 JVM 的存在也是 JAVA 语言“一次编译到处运行”的关键。

jdk 9 引入了 AOT(Ahead of Time Compilation) ,直接将字节码编译成机器码,避免了 JIT 的预热等开销。

代码规范

规范的注释:

// 行注释风格
/**
块注释风格
**/

两种块代码风格:

// 次行风格 next-line style
public static void main(String[] args) {
 ...
}
// 行尾风格 end-of-line style
public static void main(String[] args)
{
 ...
}

两种风格选哪种都可以,就是要统一,不要造成代码混乱。

三种程序错误

基本程序设计

合法的标识符、命名习惯

标识符的命名规范:

定义一个常量:

final double PI = 3.14159; // Declare a constant

定义一个变量:

double height = 1.7;
int age = 18;
String name = "John";

int i, j;
i = 0;
j = 1;

int row = 0, column = 1;

数据类型和运算操作

Java 是一种强类型语言,每一个变量必须声明一种类型。

Java 拥有 4 种整型(int(4 字节) short(2 字节) long(8 字节) byte(1 字节))、 2 种浮点型(double(8 字节) float(4 字节))、 1 种 Unicode 编码的字符类型(char(2 字节))和一种表示真值的布尔型(boolean)。

Java 中主要的数值变量类型:

picture 16

float

double :规则同上,位数如下

+-*/% 五种种运算以外还有布尔运算(逻辑运算)&&&|||!

注意:

输入/输出的简单代码:

// 初始化键盘输入扫描类
Scanner input = new Scanner(System.in);
// 输出
System.out.print("Enter a byte value: ");

// 读取一个 byte 类型的值
byte byteValue = input.nextByte();
// 读取一个 short 类型的值
short shortValue = input.nextShort();
// 读取一个 int 类型的值
int intValue = input.nextInt();
// 读取一个 long 类型的值
long longValue = input.nextLong();
// 读取一个 float 类型的值
float floatValue = input.nextFloat();

整型直接量

直接量就是单纯的数字,不带任何标识符的;

System.out.println(425342432143214L) // long int
System.out.println(0B111111) // binary int
System.out.println(0777777) // Octal int
System.out.println(0XFFFFFF) // hex int

浮点数

科学计数法:5.52604E+1

遵循 IEEE 754 规范,

不同位长处理器的兼容问题,例如 80 位处理机在进行浮点运算时不会对中间结果采取截断措施,只对最终结果截断为 64 位以保证程序的可移植性。但在 64 位处理机中,会产生精度更低的结果,有可能导致程序运行结果的不一致,降低可移植性。通过在相应位置添加 strictfp 关键字可以使方法或类中的的所有方法计算的中间结果都截断,即严格的浮点运算模式。

程序实例:显示当前时间
public class ShowCurrentTime {
 public static void main(String[] args) {
        long totalMilliseconds = System.currentTimeMillis();

        long totalSeconds = totalMilliseconds / 1000;
        long currentSecond = totalSeconds % 60;

        long totalMinutes = totalSeconds / 60;
        long currentMinute = totalMinutes % 60;

        long totalHours = totalMinutes / 60;
        long currentHour = totalHours % 24;

        System.out.println("Current time is " + currentHour + ":" + currentMinute + ":" + currentSecond + " GMT");
 }
}

这个时间戳是从现在到格林威治时间 1970-01-01 的毫秒数,是跟时区有关的。

浮点计算会发生舍入误差,比如命令 System.out.println(2.0-1.1) 会打印出 0.8999999999999999 这是由于二进制导致的;在不容许舍入误差的场景,应该使用 BigDecimal 类。

字符型

Java 中用 char 类型代表 UTF-16 中的一个代码单元(code unit)( 16 个二进制位, 4 个十六进制位),其长度占两个字节,即 `。

在早期的 Unicode 标准中,一个字符使用两个字节表示,如 U+0041 代表了 A 。但随着越来越多的语种加入 Unicode 标准,要完全表示那么多字符所需的二进制位数已经超过了原来的 16 位了。于是 Java 的设计者将 UTF-16 的 U+0000 ~ U+FFFF 划分为基本语言级别,将 U+10000 ~ U+10FFFF 的代码段根据超出 4 位的部分划分为 16 个语言级别。基本语言级别的码点(一个代码对应的编号)只需要一个代码单元即可表示,后面的则需要两个代码单元表示。比如一个码点为 U+1D546 的字符,编码为 U+D835U+DD46 。第一个代码单元的范围为 U+D800 ~ U+DBFF , 第二个代码单元的范围为 U+DC00 ~ U+DFFF

具体的映射算法见: http://en.wikipedia.org/wiki/UTF-l6

除非一定要处理 UTF-16 ,否则最好不要使用 char

字符串

裁剪

String.substring(start, destination) 方法用于裁剪字符串,生成一个子串。

拼接

Java 使用 + 拼接字符串,不会改变原有的字符串,而是产生新的字符串。

判空与判 Null

一般判 Null 先,因为 Null 对象不能调用方法,而判断字符串的长度(或者说判空)都需要调用方法。为了保证程序的顺利执行,一般先判 Null 。

if (s1 != null && s1.length() == 0) {
 // 字符串非 Null 且非空
}

使用 s1.equals("") 或者 "".equals(s1) 也能判空。

字符串不可变

Java 的 String 类对象是 不可变字符串 ,我们可以修改字符串的引用,指向不同对象,但不可以修改字符串本身。这样看起来是低效的,但由于编译器的 共享 机制,使得程序无需因为重复的字符串对象浪费资源。引用可以有很多个,本源只能有一个。

通常是字符串常量才适用共享机制,字符串变量还是有可能会在堆中存储多个等值的字符串。因此比较两个字符串变量是否相等一般使用 s1.equals(s2) 或者 if(s1.compareTo(s2) == 0) 。字面量也可以直接使用这两个方法。

String API
char charAt(int index)
// 返回指定位置的代码单元,因为有的 UTF-16 的字符码点是超过两个代码单元的。
// 除非底层的代码单元感兴趣,否则一般不用这个方法。

int codePointAt(int index)
// 返回指定位置的码点。

IntStream codePoints()
// 将字符串的码点作为流返回。
// s1.codePoints().toArray() 可以得到 int[]

new String(int[] codePoints, int offset, int count)
// 从 offset 开始的 count 个码点转换成 String

boolean equalsIgnoreCase(String other)
// 比较两个字符串,忽略大小写

int indexOf(String str)
int indexOf(String str, int fromIndex)
int indexOf(int cp)
int indexOf(int cp, int fromIndex)
// 从 fromIndex 或者 0 开始扫描字符串,
// 查找子串 str 或者码点 cp 并返回第一个匹配的位置

String replace(CharSequence oldString, CharSequence newString)
// 用 newString 代替字符串中的 oldString,返回一个新字符串。
// String 或者 StringBuilder 都能当作 CharSequence 来传

String toLowerCase()
String toUpperCase()

String trim()
// 删除头尾空格,返回新字符串。

String join(CharSequence delimeter, CharSequence... elements)
// 是静态方法,将多个 CharSequence 使用定界符 delimeter 连接起来。
构建字符串

字符串处理有三个相关的类:StringStringBuilderStringBuffer

StringBuilderStringBuilder 的 API 都大致相同,下面以 StringBuilder 为例。

int length()
// 返回的是代码单元的数量,区别于 String 的字符长度,现实中的字符由于编码不同,导致 code unit 不同

// 插入类的 API 都是返回 this 对象,可以连续 append/insert/deleted
StringBuilder append(String str)
StringBuilder append(char c)
StringBuilder appendCodePoint(int cp)
StringBuilder insert(int offset, String str)
StringBuilder insert(int offset, char c)

StringBuilder delete(int startIndex, int endIndex)
// 注意包头不包尾

String toString()
// 构建字符串并返回

枚举类型

枚举类 enum 用来将变量限制在一个范围内,除了范围内的类型和 null 以外,其他取值都是非法的,这种方式用来写一些状态列表可以避免很多错误。

// 声明枚举类
enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE};

// 使用枚举类
Size s = Size.MEDIUM;

更详细的内容,在讲完面向对象之后会介绍到。

变量

没什么特别的,就是变量未声明以及未初始化禁止使用。

常量

Java 利用 final 指示常量。常量只能被赋值一次。一旦被赋值之后, 就不能够再更改了,习惯上使用全大写字母声明,使用下划线连接不同的单词。

范围限定符

类限定符:从类的层面上进行限定。

代码块

数学函数与常量

常用三角函数:

指数函数和对数函数:

数学常量:

显式类型转换和隐式类型转换

隐式类型转换无需声明,在编写表达式或者赋值时就会自动转换。

合法的隐式数值类型转换:

picture 17

实线代表不会丢失精度,虚线代表会丢失精度。

这里的精度是从二进制的角度进行理解的,由于 double 的整数位数比 long 要短,所以会使得整数位数中的低位变成零,从而产生精度丢失的问题。

显式类型转换需要在语句中声明,这种方式也可能丢失精度,比如从 double 转换成 int

double x = 9.997;
int y = (int)x; // 9,精度丢失方式是直接截断小数位数

Math 库提供了更多的方式进行小数转换,比如 Math.round() 方法可以返回最接近的整数。(但返回值为 longint 来接收还是会产生精度丢失的问题。)

每次进行类型转换不得不考虑的问题就是转换双方的数据范围差别,以及程序可能出现的值,做好边界检查。

位运算符

&(and) |(or) ^(xor) ~(not) >> << >>>

注意:

运算符优先级:方法调用 > 一元运算符(右结合) > 乘除取余 > 加减 > 位运算符 >

picture 1

picture 2

输入输出

控制台输入

使用 Scanner 类进行输入;

// 创建输入对象
Scanner input = new Scanner(System.in);
// 接受一个整数
int a = input.nextInt();
// 接受一行输入作为字符串
String s = input.nextLine();
// 读取一个单词,空格作为分隔符
String str = input.next();
// 读取什么类型的数据,就 next 什么
...

// 另外还有判断有无下一个输入的方法
boolean isNextInt = input.hasNextInt();
...

为了提供更好的交互性,Java 提供了 Console 对象进行更多的交互;

Console cons = System.console();
String username = cons.readLine("User name: ");
// 这样使用,用户输入密码时不会显示出来
char[] password = cons.readPassword("Password: ");
// 用 char[] 接收密码之后,处理完了要用别的数据填充 char[] 覆盖用户输入的密码
格式化输出

主要使用 System.out.printf(String patternString, String... params) 进行格式化输出

patternString 由固定部分和要插入的部分以及格式控制标志构成。

picture 1

picture 4

picture 5

picture 3

example:

System.out.printf("Hello, %s. Next year, you'll be %d.", name, age);
// 百分号表示这部分由后面对应位置的变量替换
System.out.printf("%,.2f", 10000.0 / 3.0);
// 3,333.33

picture 6


Edit page
Share this post on:

Previous Post
Linux踩坑合集
Next Post
Java Stream API