介绍
字节码与JVM
Java编译器的输出不是可执行代码,而是字节码(bytecode)。字节码是高度优化的指令集合,这些指令由Java运行时系统执行,Java运行时系统也称为Java虚拟机(Java Virtual Machine,JVM)。本质上,原始的JVM被设计为字节码解释器(interpreter for bytecode)。这可能有点让人吃惊,因为出于性能方面的考虑,许多现代语言被设计为将源代码编译成可执行代码。然而,Java程序是由JVM执行的这一事实,有助于解决与基于Web的程序相关的问题。
将Java程序翻译成字节码,可以使其更容易地在各种环境中运行,因为只需要针对每种平台实现Java虚拟机就可以了。对于给定的系统只要存在运行时包,所有Java程序就可以在该系统中运行。尽管对于不同的平台,Java虚拟机的细节可能有所不同,但是它们都能理解相同的Java字节码。如果Java程序被编译成本机代码,就必须为相同的程序针对连接到Internet的不同类型的CPU提供不同版本,这是不可行的。通过JVM执行字节码是创建真正可以只程序最容易地方法。
JavaSE
Sun公司定制的Java2的另一种说法J2SE(Java 2 Platform Standard Edition, Java2平台标准版)。
Java程序
为了编译Java程序,执行编译器javac,javac编译器会创建于java文件同名的.class文件,该文件包含程序的字节码版本,因此javac的输出不是可以直接执行的代码。
为了实际运行程序,必须使用名为java的java应用程序加载器。
垃圾回收
Java对象是使用new运算符动态创建的,对象内存释放采用自动解除分配的内存的方法。完成该工作的技术被称为垃圾回收(garbage collection)。它的工作原理是:当一个对象的引用不再存在时,就认为该对象不再需要,并且可以回收该对象占用的内存。不存在像c++中那样显示释放对象的需求。在程序运行期间,只会零星地发生垃圾回收(如果确实发生垃圾回收的话),不会简单地因为一个或多个对象不再需要就进行垃圾回收。此外,在不同Java运行时实现也会采用不同的方法进行垃圾回收,但是对于大多数情况,在编写程序的过程不需要考虑这个问题。
基础
关键字
abstract | continue | for | new | switch |
assert | default | goto | package | synchronized |
boolean | do | if | private | this |
break | double | implements | protected | throw |
byte | else | import | public | throws |
case | enum | instanceof | return | transient |
catch | extends | int | short | try |
char | final | interface | static | void |
class | finally | long | strictfp | volatile |
const | float | native | super | while |
true | false | null | … | … |
注释
- 单行注释(single-line comment)
单行注释以//
开头,并在行的末尾结束
1 | // 这是一个单行注释 |
- 多行注释(multiline comment)
多行注释必须以/*
开头,并以*/
结束。编译器会忽略这两个注释符号之间的所有内容。
1 | /* |
算数运算符
运算符 | 结果 |
---|---|
+ |
加法(一元加号) |
- |
减法(一元减号) |
* |
乘法 |
/ |
除法 |
% |
求模 |
++ |
自增 |
+= |
加并赋值 |
-= |
减并赋值 |
*= |
乘并赋值 |
/= |
除并赋值 |
%= |
求模并赋值 |
-- |
自减 |
位运算
运算符 | 结果 |
---|---|
~ |
按位一元取反 |
& |
按位与 |
| |
按位或 |
^ |
按位异或 |
>> |
右移 |
>>> |
右移零填充 |
<< |
左移 |
&= |
按位与并赋值 |
|= |
按位或并赋值 |
^= |
按位异或并赋值 |
>>= |
右移并赋值 |
>>>= |
右移零填充并赋值 |
<<= |
左移并赋值 |
- 使用参考表
A | B | A|B | A&B | A^B | ~A | ~B |
---|---|---|---|---|---|---|
0010 1010 | 0000 1111 | 0010 1111 | 0000 10101 | 0010 0101 | 1101 0101 | 1111 0000 |
- >>与>>>
1 | public static void TestRight() |
关系运算符
运算符 | 结果 |
---|---|
== |
等于 |
!= |
不等于 |
> |
大于 |
< |
小于 |
>= |
大于等于 |
<= |
小于等于 |
布尔逻辑运算符
运算符 | 结果 |
---|---|
& |
逻辑与 |
| |
逻辑或 |
^ |
逻辑异或 |
|| |
短路或 |
&& |
短路与 |
! |
逻辑一元非 |
&= |
逻辑与并赋值 |
|= |
逻辑或并赋值 |
^= |
逻辑异或并赋值 |
== |
等于 |
!= |
不等于 |
?: |
三元 if-then-else |
instanceof运算符
在Java中,无效的类型转换会导致运行时错误。许多无效的类型转换可以在编译时捕获,Java提供了运算符instanceof用于判断某个变量是否可以转换为某个类型。
1 | class ClassA |
命令参数
当运行程序时,有时可能希望程序传递信息,这可以通过main()方法传递命令行参数来完成。命令行参数是执行程序时在命令行上紧跟程序名称之后的信息。在Java程序中访问命令行参数非常容易,它们作为字符串存储在String数组中,并传递给main()方法的args参数。第一个命令行参数存储在args[0]中,第二个存储在args[1]中,以此类推。
所有命令行参数都是作为字符串传递,需要手动将参数转成自己需要的数据形式。
1 | public static void main(String[] args) |
静态导入
Java提供了静态导入特性,静态导入扩展了import关键字的功能。通过在import后面添加static关键字,可以使用import语句导入类或接口的静态成员。当使用静态导入使,可以直接通过名称引用静态成员,而不必使用它们的类名进行限定,从而简化并缩短使用静态成员所需的语法。
除了导入JavaAPI定义的接口和类的静态成员外,也可以使用静态导入导入我们自己创建的接口和类的静态成员。
尽管静态导入很方便,但是注意不要滥用。Java将类库组织到包中的目的是避免名称空间发生冲突。当导入静态成员时,就将这些成员带入全局名称空间。因此,这会增加潜在的名称空间冲突以及无意中隐藏其他名称的风险。静态导入是针对重复使用某个静态成员这类情况设计的,例如执行一系列数学计算,应当使用但不要滥用。
1 | import static java.lang.Math.sqrt; |
数据类型
基本类型
整形
java定义了4种整型类型:byte、short、int和long。所有这些类型都是有符号的、正的或负的整数。java不支持无符号的、只有正值的整数。
名称 | 宽度 | 范围 |
---|---|---|
long | 64 | -9223372036854775808~9223372036854775807 |
int | 32 | -2147483648~2147483647 |
short | 16 | -32768~32767 |
byte | 8 | -128~127 |
1 | public static void TestInteger() |
- 进制
在java中,二进制以0b开头,八进制数以0开头,常规的十进制数不以0开头,十六进制以0x或0X开头。
1 | public static void TestScale() |
- 下划线
从JDK7开始,在整型字面值中还可以嵌入一个或多个下划线。嵌入下划线可以使阅读很大的整数变得更加容易。当编译字面值时,会丢弃下划线。
1 | public static void TestUnderline() |
浮点型
浮点数也称为实数(real number),当计算需要小数精度的表达式时使用。
名称 | 宽度 | 大致范围 |
---|---|---|
double | 64 | 4.9e-324~1.8e+308 |
float | 32 | 1.4e-045~3.4e+038 |
1 | public static void TestFloat() |
- 指数
指数部分用E(或e)后面跟上一个十进制数表示,该十进制数可以是正数,也可以是负数。
1 | public static void TestExponent() |
- 进制
Java也支持十六进制浮点型字面量,使用P或p表示。P后面的数值称为二进制指数,表示2的幂,并且和前面的数值相乘。
1 | public static void TestFloatScale() |
- 下划线
从JDK7开始,在浮点型字面值中可以嵌入一个或多个下划线。目的是使阅读很大的浮点型字面值更加容易。当编译字面值时,会丢弃下划线。
1 | public static void TestFloatUnderline() |
字符型
在java中,用于存储字符的数据类型是char,使用Unicode表示字符,16位的宽度,为了全球语言的移植性设计。
1 | public static void TestChar() |
- 字符转义序列
转移序列 | 描述 |
---|---|
\ddd |
八进制字符(ddd) |
\uxxxx |
十六进制Unicode字符(xxxx) |
\' |
单引号 |
\" |
双引号 |
\\ |
反斜杠 |
\r |
回车符 |
\n |
新行符(也称为换行符) |
\f |
换页符 |
\t |
制表符 |
\b |
回格符 |
八进制
对于八进制表示法,使用反斜杠后面跟三位数表示,例如'\141'
表示的是'a'
。十六进制
对于十六进制,先输入\u
,然后是4位的十六进制数。例如'\u0061'
表示ISO-Latin-1的'a'
。对Unicode代码点的附加支持
从JDK5开始,Character开始支持32位的Unicode字符。在过去,所有Unicode字符都应当使用16位保存,这是char类型的大小(也是在Character中封装的值所占用的空间大小),因为这些值的范围都是从0到FFFF。但是,Unicode字符集已经进行了扩展,需要比16位更多的存储空间。现在,字符的范围可以从0到10FFFF。
代码点是指0到10FFFF范围内的字符;数值大于FFFF的字符被称为补充字符;基本多语言平面(Basic Multilinggual Plane,BMP)字符是指0到FFFF之间的那些字符。
因为附加字符的值大于char能够保存的值,所以需要一些处理补充字符的手段。Java通过两种方法解决这个问题。首先,Java使用两个char表示一个附加字符,第一个char称为高代理(high surrogate),第二个char称为低代理(low surrogate)。Java提供了一些新方法,用于在代码点和补充字符之间进行转换,比如codePointAt()。
对于代码点的附加支持,Java重载了Character类以前存在的一些方法。这些方法的重载形式使用int而不是char型数据。因为对于作为单个值保存任何字符而言,int是足够大的,所以int可以用于保存任何字符。
布尔型
Java有一种称为boolean的基本类型,用于表示逻辑值。它可能是两个可能的值之一:true或false。
1 | public static void TestBool() |
枚举
创建枚举需要使用关键字enum。枚举里面定义的每一个标识符被称为枚举常量,每个枚举常量被隐式声明为公有、静态final成员。此外,枚举常量的类型是声明它们的枚举的类型,在Java语言中,这些常量被称为是“自类型化的”(self-typed),其中的“自”是指枚举常量的枚举。
在case语句中,枚举常量的名称没有使用枚举类型的名称进行限定。
1 | enum Apple |
- 枚举常用方法
方法 | 描述 |
---|---|
values() | 返回一个包含枚举常量列表的数组 |
valueOf() | 返回与传递到参数str的字符串相对应的枚举常量 |
ordinal() | 返回调用常量的序数值,序数值从0开始 |
compareTo() | 调用常量和e进行比较,相同返回0;小于返回负,大于返回正。 |
equals() | 比较两个常量是否相等 |
1 | enum Apple |
枚举是类
在Java枚举是类类型。虽然不能使用new实例化枚举,但是枚举却有许多和其他类相同的功能。例如构造函数、添加实例变量和方法,甚至可以实现接口。
每个枚举常量都是所属枚举类型的对象。因此,如果为枚举定义了构造函数,那么当创建每个枚举常量时都会调用该构造函数。此外,对于枚举定义的实例变量,每个枚举常量都有它自己的副本。
1 | enum Apple |
数组
1 | public static void TestArray() |
语法
if
1 | public static void TestIf() |
switch
对于JDK7以前的Java版本,expression必须是byte、short、int、char或枚举类型。从JDK7开始,expression也可以是String类型。
switch语句只能进行相等性测试,这一点与if语句不同,if语句可以对任何类型的布尔表达式进行求值。也就是说,switch语句只查看表达式的值是否和某个case常量相匹配。
在同一switch语句中,两个case常量不允许具有相同的值。当然,switch语句与包围它的外层switch语句具有相同的case常量。
相当于一系列嵌套的if语句,switch语句通常效率更高。
1 | public static void TestSwitch() |
while
1 | public static void TestWhile() |
do-while
1 | public static void TestDoWhile() |
for
1 | public static void TestFor() |
for-each
1 | public static void TestForeach() |
break
Java的break相比其他语言有一个goto的特性,用于处理嵌套代码块的跳跃和退出嵌套的循环。
1 | public static void TestBreak() |
类型装箱和拆箱
类型封装器
Java使用基本类型来保存语言支持的基本数据类型。出于性能考虑,为这些数据使用基本类型而不是对象。为这些数据使用对象,即使是最简单的计算也会增加不可接受的开销。因此,基本类型不是对象层次的组成部分,它们不继承Object类。
虽然基本类型提供了性能方面的好处,但是有时会需要对象这种表达形式。例如,不能通过引用为方法传递基本类型。此外,Java实现的许多标准数据结构时针对对象进行操作的,这意味着不能使用这些结构存储基本类型。为了处理这些情况,Java提供了类型封装器,用来将基本类型封装到对象。
类型封装器包括Double、Float、Long、Integer、Short、Byte、Character已经Booolean。
将数值封装到对象中的过程称为装箱。从类型封装器中抽取数值的过程称为拆箱。
1 | // 装箱 |
自动装箱和拆箱
JDK开始,Java增加了自动装箱和自动拆箱。自动装箱的过程是:无论何时,只需要基本类型的对象,就自动将基本类型自动封装(装箱)到与之等价的类型封装器中,而不需要显示地构造对象。自动拆箱是当需要时自动抽取(拆箱)已装箱对象的数值的过程,而不需要调用intValue()和doubleValue()这类方法。
有了自动装箱特性,封装基本类型将不必再手动创建对象。只需要将数值赋给类型封装器引用即可,Java会自动创建对象。而拆箱对象,可以简单地将对象引用赋值给基本类型的变量。
1 | // 自动装箱 |
除了赋值这种简单情况之外,无论何时,如果必须将基本类型转换为对象,就会发生自动装箱;无论何时,如果对象必须转换为基本类型,就会发生自动装箱。例如:
- 当向方法传递参数或从方法返回数值时,都可能会发生自动装箱/拆箱。
- 当需要将基本类型转换为对象或将对象转换为基本类型时,就会发生自动装箱/拆箱。
Java也为布尔和字符串类型提供了封装器,他们也会应用自动装箱和拆箱。
通常,因为自动装箱总是会创建正确的对象,并且自动拆箱总是会产生正确的数值,所以不会产生错误类型的对象或数值。在极端情况下,如果期望的类型和自动装箱/拆箱产生的类型不同的话,仍然可以对数值进行手动装箱/拆箱。当前,这样会丢失自动装箱/拆箱代理的好处。
关键字
transient
如果将变量声明为transient,那么当存储对象时,实例变量的值将不需要永久保存。在持久化的时候,被声明的变量将不会被序列化。
1 | class T |
在此,如果将T类型的对象写入永久存储区域,那么虽然不会保存a的内容,但是会保存b的内容。
strictfp
在设计Java2时,浮点计算模型稍微宽松了一些,特别地,在计算期间新模型不需要截断特定的中间值。在某些情况下,这可以防止溢出。通过使用strictfp修饰类、方法或接口,可以确保采用与Java以前版本使用的相同方式执行浮点计算(以及所有截断)。如果使用strictfp对类进行修饰,那么累的所有方法将自动使用strictfp进行修饰。大多数情况下都不需要使用strictfp,因为strictfp只影响很小的一类问题。
1 | strictfp class MyClass |
assert
Java关键字assert,用于在程序开发期间创建断言,断言是在程序执行期间应当为true的条件。在运行时,如果条件为true,就不会发生其他动作。但是,如果条件为false,那么会抛出AssertionError异常。断言经常用于证实在测试期间实际遇到了某些期望的条件。
1 | assert n > 0; |
当执行代码时,可以使用-da选项禁用所有断言。通过在-ea或-da选项后面指定包的名称,并在后面跟上三个点,可以启用或禁用指定包(及其所有子包)中的断言。
1 | -ea:MyPack... |
System类
System类包含一系列静态方法和变量。
- System类定义的方法
方法 | 描述 |
---|---|
static void arraycopy(Object source, int sourceStart, Object target, int targetStart, int size) | 复制数组。被复制的数组是由source传递过来的。在source中,复制开始位置的索引是sourceStart。接收副本的数组是由target传入的。在target中,复制开始位置的索引是由targetStart传入的,size是要复制的元素的数量 |
static String clearProperty(String which) | 删除由which指定的环境变量,返回原来的与which关联的值 |
static Console console() | 返回有JVM关联的控制台。如果JVM当前没有控制台,就返回null |
static long currentTimeMillis() | 以毫秒返回当前时间,从1970年1月1日午夜开始计时 |
static void exit(int exitCode) | 中断执行,并将exitCode的值返回给父进程(通常是操作系统)。根据约定,0表示正常终止。所有其他值表示有某种形式的错误 |
static void gc() | 开始垃圾回收 |
static Map<String, String> getenv() | 返回的Map对象包含当前环境变量以及它们的值 |
static String getenv(String which) | 返回与which传递的环境变量相关联的值 |
static Properties getProperties() | 返回与Java运行时系统关联的属性 |
static String getProperties(String which) | 返回与which关联的属性。如果没有找到期望的属性,就返回null |
static String getProperties(String which, String default) | 返回与which关联的属性。如果没有找到期望的属性,就返回default |
static SecurityManager getSecurityManager() | 返回当前的安全管理器。如果没有安装安全管理器,就返回null对象 |
static int identityHashCode(Object obj) | 返回obj对象的表示散列值 |
static Channel inheritedChannel() | 返回Java虚拟机继承的通道。如果没有通道被继承,就返回null |
static String lineSeparator() | 返回包含行分隔符的字符串 |
static void load(String libraryFileName) | 加载由libraryFileName指定的文件中的动态库,必须指定完整的路径 |
static void loadLibrary(String libraryName) | 加载名为libraryName的动态库 |
static String mapLibraryName(String lib) | 返回lib特定于平台的库名 |
static long nanoTime() | 获得系统中最精确的计时器并返回自某些任意启动点以来以纳秒表示的时间值。计时器的精确度未知 |
static void runFinalization() | 为那些未使用但是还没有被回收的对象调用finalize()方法 |
static void setErr(PrintStream eStream) | 将标准err流设置为eStream |
static void setIn(InputStream iStream) | 将标准in流设置为iStream |
static void setOut(PrintStream oStream) | 将标准out流设置为oStream |
static void setProperties(Properties sysProperties) | 将当前系统属性设置为由sysProperties指定的属性 |
static String setProperty(String which, String v) | 将值v赋给which的属性 |
static void setSecurityManager(SecurityManager secMan) | 将安全管理器设置为由secMan指定的安全管理器 |
环境变量
环境变量在所有情况下都可以使用,通过调用System.getProperty()方法获取。
file.separator | java.specification.version | java.vm.version | java.class.path | java.vendor | line.separator |
java.class.version | java.vendor.url | os.arch | java.compiler | java.version | os.name |
java.ext.dirs | java.vm.name | os.version | java.home | java.vm.specification.name | path.separator |
java.io.tmpdir | java.vm.specification.vendor | user.dir | java.library.path | java.vm.specification.version | user.dir |
java.library.path | java.vm.specification.version | user.home | java.specification.name | java.vm.vendor | user.name |
java.specification.vendor |
1 | public class LearnSystem1 |
Package类
Package封装了与包有关的版本数据。
方法 | 描述 |
---|---|
A getAnnotation(Class annoType) | 返回的Annotation对象包含与调用对象的annoType相关联的注解 |
Annotation[] getAnnotations() | 返回与调用对象关联的所有注解,并将它们存储在元素为Atmotation对象的数组中。返回对这个数组的引用 |
A[] getAnnotatiosByType(Class annoType) | 返回与调用对象关联的annoType注解数组(包括重复注解)(JDK8新增) |
A getDeclaredAnnotation(Class annoType) | 返回Annotation对象包含与annoType关联的非继承注解(JDK8新增) |
Annotation[] getDeclaredAnnotations() | 为调用对象声明的所有注解都返回一个Annotation对象(忽略继承的注解) |
A[] getDeclaredAnnotationsByType(Class annoType) | 返回与调用对象关联的非继承annoType注解数组(包括重复注解)(JDK8新增) |
String getImplementationTitle() | 返回调用包的标题 |
String getImplementataionVendor() | 返回调用包的实现程序的名称 |
String getImplementationVersion() | 返回调用包的版本号 |
String getName() | 返回调用包的名称 |
static Package getPackage(String pkgName) | 返回名为pkgName的Package对象 |
static Package[] getPackages() | 返回调用程序当前知道的所有包 |
String getSpecificationTitle() | 返回调用包规范的标题 |
String getSpecificationVendor() | 返回调用包规范的所有者的名称 |
String getSpecificationVersion() | 返回调用包规范的版本号 |
int hashCode() | 返回调用包的散列码 |
boolean isAnnotationPresent(Class<? extends Annotation> anno) | 如果anno描述的注解与调用对象有关,就返回true;否则返回false |
boolean isCompatibleWith(String verNum) | 如果verNum小于或等于调用包的版本号,就返回true |
boolean isSealed() | 如果调用包被密封,就返回true;否则返回false |
boolean isSealed(URL url) | 如果调用包相对url被密封,就返回true;否则返回false |
String toString() | 返回调用包的等价字符串 |
链接