概述
Java特性
- 简单性
- 面向对象
- 分布式
- 健壮性(鲁棒性)
- 安全性
- 体系结构中立
- 可移植性
- 解释型
- 高性能
- 多线程
- 动态性
发展史
| 版本 | 年份 | 新语言特性 | | —- | —- | —- | | 1.0 | 1996 | 语言本身 | | 1.1 | 1997 | 内部类 | | 1.2 | 1998 | strictfp修饰符 | | 1.3 | 2000 | 无 | | 1.4 | 2002 | 断言 | | 5 | 2004 | 泛型类、for each循环、可变参数、自动装箱、元数据、枚举、静态导入 | | 6 | 2006 | 无 | | 7 | 2011 | 基于字符串的选择语句、菱形运算符、二进制字面量、异常增强处理 | | 8 | 2014 | lambda表达式、包含默认方法的接口、流和日期/时间库 | | 9 | 2017 | 模块、其他的语言和类库增强 |
基本程序设计
数据类型
Java是一种强类型语言,必须为每一个变量声明一个类型,一共有8种基本数据类型。
整型
整型用于表示没有小数部分的数值,允许是负数,一共有4种整型
类型 | 存储需求 | 取值范围 |
---|---|---|
byte | 1字节 | -128~127 |
short | 2字节 | -32767~32767 |
int | 4字节 | -2147483648~2147483647(刚刚超过20亿) |
long | 8字节 | -9223372036854775808~9223372036854775807 |
正常情况下使用 int类型,大数情况下使用long。byte以及short只有在极特殊的情况下才会使用到。
在Java中,整型的取值范围与运行代码的机器无关。各种数据类型的取值范围是固定的。长整型数值有一个后缀L或者l,十六进制数有一个前缀0x或0X,八进制有一个前缀0,很显然八进制数容易混淆所以尽量不要使用八进制常数。从Java7开始,加上前缀0b或0B就可以表示二进制数,还可以为数字字面量加下划线,例如1_000,这些下划线只是为了易读性,编译器会去除这些下划线。
浮点型
浮点型用于有小数部分的数值
类型 | 存储需求 | 取值范围 |
---|---|---|
float | 4字节 | 大约±3.40282347E+38F |
double | 8字节 | 大约±1.79769313486231570E+308 |
double被称为双精度数值,其精度是float的2倍。正常情况下多以使用double,float只有在特定的情况下使用。
float类型的数值有一个后缀F或f,如果没有后缀的浮点数总认为是double类型,当然也可以在浮点数值后面添加后缀D或d。浮点数值不适用于无法接受舍入误差的金融计算,舍入误差的主要原因是浮点数值采用二进制系统表示,在二进制系统中无法精确地表示分数。如果在数值计算中不允许有任何舍入误差就应该使用BigDecimal类。
可移植性是Java语言的设计目标之一,无论在哪个虚拟机上执行,同一运算应该得到同样的结果。对于浮点数而言,实现这样的可移植性是十分困难的。Java虚拟机的最初规范要求所有中间计算必须进行截断,之后改进为默认情况下允许对中间计算采用扩展精度,但是对于使用了strictfp关键字标记的方法必须使用严格的浮点计算。
char型
char型用于表示单个字符,占用两个字节。不过现在情况有所变化。如今,有些Unicode字符可以用一个char值描述,一些Unicode字符则需要两个char值。<br /> char型的字面量值要用单引号括起来,例如** 'A' **是编码值为65所对应的字符常量。**"A" **是包含一个字符A的字符串。char型也可以表示为十六进制值,其范围从\u0000到\Uffff。
转义序列 | 名称 | Unicode值 |
---|---|---|
\b | 退格 | \u0008 |
\t | 制表 | \u0009 |
\n | 换号 | \u000a |
\r | 回车 | \u000d |
\“ | 双引号 | \u0022 |
\‘ | 单引号 | \u0027 |
\\ | 反斜杠 | \u005c |
boolean型
boolean型只有两个值:false和true,占用一个字节,用来判断逻辑条件,整型值与布尔值之间不能相互转换。
变量与常量
变量
变量名必须是一个以字母开头并由字母或数字构成的序列,与大多数语言相比,Java中字母和数字的范围更大,字母包括了'A'~'Z','a'~'z','_','$'或在某种语言中表示字母的任何Unicode字符。同样数字也包括'0'~'9'和在某种语言中表示数字的任何Unicode字符。不可使用空格,并且变量名是大小写敏感的,长度基本没有限制。<br /> 声明一个变量之后,必须使用赋值语句对变量进行显式初始化,千万不要使用未初始化的变量。在Java中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的编码风格。
常量
利用关键字final指示常量,final表示这个变量只能赋值一次,一旦赋值之后就无法再次修改,通常变量名使用大写来表示常量。如果一个常量在一个类的多个方法中使用,则这个常量可以称为类常量,可以使用关键字 static final来设置一个类常量。
运算符
算术运算符
+、-、*、/ 表示加减乘除运算,/在整数中表示整数除法,否则表示浮点除法。整数求余操作(取模)用%表示。
注意:整数被0除将产生一个异常,而浮点数被0除将会得到无穷大或者NaN结果。
- 数学函数
在Math类中,包含了各种各样的数学函数。
计算平方根 :Math.sqrt(double a)
计算幂运算:Math.pow(doube a,double b)
如果计算溢出,数学运算符只是返回一个错误结果而不做任何提醒,但如果是Math中的方法就会生成异常。
数值类型转换
无信息丢失的转换:byte->short、short->int、char->int、int->long、int->double、float->double
可能有精度损失的转换:int->float、long->float、long->double
当用二元运算符连接两个值时,先要将两个操作数转换为同一种类型再进行计算,如果两个操作数有一个
是 double类型,另一个操作数就转为double;否则如果有一个是float类型,另一个操作数就转为float;
否则如果有 一个操作数是long类型,另一个操作数就转为long;否则两个操作数都转为int。强制类型转换
可能丢失信息的转换要通过强制类型转换完成,如果想对浮点数进行舍入运算,以便得到最接近的整数,可以使用Math.round(float/double a)方法。
- 结合赋值和运算符
可以在赋值时使用二元运算符,例如x+=4,等价于x=x+4,如果运算符右边的值类型和左边不一致就会发生强制类型转换,例如x是int类型,x+=3.5等价于x=(int)(x+3.5)。
- 自增与自减运算符
自增和自减运算符改变的是变量的值,不能应用于数值本身,因此1++不是一个合法的语句。
后缀和前缀形式都会使变量值加1或减1,不同的是前缀形式会先完成运算,后缀形式会先使用变量原来
的值。
- 关系和boolean运算符
Java沿用了C++的做法,使用&&表示逻辑“与”运算符,使用||表示逻辑”或”运算符,感叹号!就是逻辑非运算符。&&和||是按照”短路”方式来求值的,如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。
三元操作符 ? :如果条件为true,该表达式condition ? expression1 :expression2
就为第一个表达式的值,否则计算为第二个表达式的值。
- 位运算符
位运算符包括 &(与)、|(或)、^(异或)、~(非)。>> 和 << 可以将位模式左移或右移, >>> 会用0填充高位, >> 会用符号位填充高位,不存在 <<< 运算符。移位运算符的右操作数要完成模32的运算(除非左操作数是long类型,这种情况下需要模64),例如1<<35的值等于1<<3或者8。
- 括号运算符
没有使用圆括号,则按照运算符优先级次序精选计算,同一个级别的运算符按照从左到右的次序进行计算(右结合运算符不按照此规则例子:a += b += c
等价于a +=(b += c)
)
- 枚举类型
字符串
概念上讲Java字符串就是Unicode字符序列,在标准Java类库中提供了一个预定义类,表示字符串 String,每个用双引号括起来的字符串都是String类的一个实例。
- 子串
String类的substring方法可以从一个较大的字符串提取出一个字串。
substring方法的第一个参数是要复制的第一个位置,第二个参数是不想复制的第一个位置,例如
substring(0,3)复制了0,1和2,不包括3。
substring的工作方式有一个优点:容易计算字串的长度,substring(a,b)的长度为b-a。
- 拼接
与绝大多数程序设计语言一样,Java语言允许使用+号连接(拼接)两个字符串。
当将一个字符串与一个非字符串的值拼接起来时,后者会转换成字符串(任何一个Java对象都可以转换成字符串)。例如int age=13
,String rating="PG"+age
,将rating设置为”PG13”。
如果需要把多个字符串放在一起,用一个界定符分隔,可以使用静态方法join,例如String str=String.join("/","S","M","L")
将str设置为”S/M/L”。
在Java11中,还提供了repeart方法,例如String str="Java".repeat(3)
将str的值设为”JavaJavaJava”。
- 不可变字符串
String类没有提供修改字符串中某个字符的方法,由于不能修改Java字符串中的单个字符,所以Java文档中将String类对象称为不可变的,不过可以修改字符串变量,让它引用另一个字符串,就如同可以让原本存3的数值变量改成存放4一样。
通过拼接两个字符串来创建一个新字符串的效率确实不高,但是不可变字符串有一个优点:编译器可以让字符串共享。可以想象将各种字符串存放在公共的存储池中,字符串变量指向存储池中相应的位置,如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。Java认为共享带来的高效率远胜于提取字串、拼接字符串所带来的低效率。
- 检查字符串是否相等
可以使用equals方法比较两个字符串的值是否相等,如果不区分大小写,可以使用equalsIgnoreCase方法。
==运算符只能确定两个字符串是否存放在同一个位置上,如果虚拟机始终将相同的字符串共享,就可以使用==运算符检测是否相等。但实际上只有字符串字面量是共享的,而+或substring等操作得到的字符串并不共享,不要使用==运算符测试字符串的相等性。
- 空串与Null串
空串””是长度为0的字符串,空串是一个Java对象,有自己的串长度(0)和内容(空),不过String变量还可以存放一个特殊的值,名为null,表示目前没有任何对象与该变量关联。
- String类常用API
int compareTo(String other):
按字典顺序比较,在other之前返回负数,之后返回正数,相等返回0。boolean equals(String other):
比较两个字符串值是否相等。boolean startWith(String prefix):
是否以prefix开头。boolean endsWith(String suffix):
是否以suffix结尾。int indexOf(String str[,int fromIndex]):
返回与字符串str匹配的第一个子串的开始位置,从索引0或者fromIndex
开始,不存在返回-1。int indexOf(int cp[,int fromIndex]):
返回与码点cp匹配的第一个子串的开始位置,从索引0或者fromIndex
开始,不存在返回-1。int length():
返回字符串代码单元的长度。String replace(CharSequence old,CharSequence new):
返回新字符串,用new代替所有的old。String substring(int begin[,int end]):
返回新字符串,包含原始字符串从begin到末尾或end-1的所有代码单元。String trim():
返回一个新字符串,删除原始字符串头部和尾部小于等于U+0020的字符。
- 构建字符串(该类中的重要方法可以自行到API中查看)
有时候需要由较短的字符串构建字符串,如果采用字符串拼接的方式来达到目的,效率会比较低。每次拼接字符串时都会构建一个新的String对象,既耗时又浪费空间。可以使用StringBuilder
来解决该问题。StringBuilder
在Java5中引入,这个类的前身是StringBuffer
,它的效率低但是线程安全。如果所有字符串编辑操作都在单个线程中执行(通常都是这样),则应该使用StringBuilder
。
输入与输出
- 读取输入
读取标准输入流首先需要构造一个与标准输入流关联的Scanner对象,nextLine()
方法将读取一整行输入;如果想要读取一个单词(以空白符作为分隔符),可以使用next()
方法;如果想要读取一个整数,可以使用nextInt()
方法;如果想要读取下一个浮点数就是要nextDouble()
方法;hasNext()
可以检测输入中是否还有其他单词,hashNextInt()
和hashNextDouble()
检测是否还有下一个表示整数或浮点数的字符序列。 格式化输出
可以使用printf方法实现格式化输出,例如x的值为10000.0/3.0调用printf(“%8.2f”,x)将输出3333.33
,8代表输出的字符宽度,精度是小数点后2个字符,由于3333.33只有7个字符宽,所以还会打印一个前导的空格。
每一个以%后面的转换符都说明了要格式化的数值类型,除了f表示浮点数,还有s表示字符串,d表示十进制整数,c表示字符,b表示布尔等。除了printf也可以用静态方法String.format
创建一个格式化的字符串。控制流程
与任何程序设计语言一样,Java使用条件语句和循环结构确定控制流程。
块作用域
块(即复合语句)是指由若干条Java语句组成的语句,并用一对大括号括起来。块确定了变量的作用域,一个块可以嵌套在另一个块中。不能在嵌套的两个块中声明同名的变量。- 条件语句
if中的条件为true时会执行对应语句,else是可选的,总是与最近的if构成一组。 - for循环
for循环语句是支持迭代的一种通用结构,由一个计数器或类似的变量控制迭代次数,每次迭代后这个变量将会更新。for语句的第1部分通常是对计数器初始化,第2部分给出每次新一轮循环执行前要检测的循环条件,第3部分指定如何更新计数器。
for语句内部定义的变量,不能在循环体之外使用。因此如果希望在for循环体之外使用循环计数器的最终值,就要确保这个变量在循环之外声明。 - while循环
while语句在最前面检测循环条件,因此循环体中的代码有可能一次都不执行。如果希望循环体至少执行一次可以使用do-while循环,这种循环语句先执行语句再检测循环条件。 - 多重选择:switch语句
在处理多个选项时,使用if/else会显得有些笨拙,Java有一个和C/C++完全相同的switch语句。switch语句将从选项值相匹配的case标签开始执行,直到遇到break语句,或者执行到switch语句的结束处为止。如果没有匹配的case语句而有default语句就执行这个default子句。
case标签可以是char、byte、short、或int的常量表达式,枚举常量,从Java7开始,还可以支持字符串字面量。当使用枚举常量时,不必在每个标签中指明枚举名,可以由switch的表达式值推导得出。 中断控制流程的语句
不带标签的break语句,和用于退出swicth语句的break语句一样,可以用于直接退出循环。
还可以使用带标签的break语句,可以用于跳出多重循环的嵌套语句。标签必须放在最外层循环之前,并且紧跟一个冒号。
continue语句将中断正常的控制流程,将控制转移到最内层循环的首部,如果continue用于for循环,就可以跳到for循环的更新部分,还有一种带标签的continue,将跳到与标签匹配的循环的首部。大数
如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中的两个很有用的类:BigInteger和BigDecimal,这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现任意精度的整数运算,BigDecimal类实现任意精度的浮点数运算。
使用静态的valueOf
方法可以将普通的数值转换为大数,对于更大的数,可以使用一个带字符串参数的构造器。不能使用算术运算符处理大数,例如+和*,而需要大数类的add和multiply方法。除此之外的其他方法:substract求差,divide求商,mod求余,sqrt求平方根,compareTo比较两个数。数组
声明
数组是一种数据结构,用来存储同一类型值的集合。通过一个整形下标(index,或称索引)可以访问数组中的每一个值。在声明数组变量时,需要指出数组类型和数组变量的名字。
一旦创建了数组就不能再改变它的长度,如果程序运行中需要经常扩展数组的大小,就应该使用另一种数据结构:数组列表ArrayList。- 访问元素
创建一个数字数组时,所有元素都初始化为0,boolean数组的元素会初始化为false,对象数组的元素则初始化为一个特殊值null,表示这些元素还未存放任何对象。
Java可以使用增强for循环来依次处理数组(或者其他元素集合)中的每一个元素,而不必考虑指定下标值。可以使用的必须是一个数组或者一个实现了Iterable接口的类对象。如果想打印数组中的所有值可以调用静态的Arrays.toString
方法。 - 数组拷贝
如果希望将一个数组的所有值拷贝到一个新的数组中去,可以使用Arrays.copyOf(int[] arr,int len)
方法,第2个参数是新数组的长度,这个方法通常用于增加数组的大小,如果数组元素是数值型那么额外的元素将被赋值0,如果数组元素是布尔型则赋值false。相反如果长度小于原始数组的长度,则只拷贝前面的值。这个方法实际上调用了System.arraycopy
方法,效率一般。
使用System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
拷贝数组,前2个参数是原数组和拷贝位置,之后2个参数是目标数组和拷贝位置,最后一个参数代表拷贝长度,这是一个本地方法,效率最高,尤其在数组很大时尤为明显。也可以使用for循环和clone方法拷贝数组,for循环适合于小数组的拷贝,数组越大效率越低。 数组排序
可以使用Arrays类中的sort方法,这个方法使用了优化的快速排序,快速排序算法对于大多数数据集合来说都是效率比较高的。对象与类(面向对象)
概述
面向对象程序涉设计(OOP)是当今主流的程序设计泛型,面向对象的程序是由对象组成的,每个对象包含对用户公开的特定部分和隐藏的实现部分。传统的结构化程序通过设计一系列算法求解问题,首先考虑的是如何操作数据,然后再考虑组织数据的结构,而面向对象中数据是第一位的,然后再考虑操作数据的算法。对于规模较小的问题,面向过程开发比较理性,面向对象更加适合解决规模较大的问题。
类
类是构造对象的模板或蓝图,由类构造对象的过程称为创建类的实例。
封装是处理对象的一个重要概念,从形式上看封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。对象中的数据称为实例字段,操作数据的过程称为方法。作为一个类的实例,特定对象都有一组特定的实例字段值,这些值的集合就是这个对象的当前状态。
封装给予了对象黑盒特征,这是提高重用性和可靠性的关键。这意味着一个类完全可以改变数据存储的方式,只要仍使用同样的方法操作数据,其他对象就不会知道也不用关心这个类的变化。
类之间的关系:- 依赖,即uses-a关系,如果一个类的方法使用或操纵另一个类的对象,我们就说一个类依赖于另一个类。
- 聚合,即has-a关系,即一个对象包含另一个对象。
继承,即is-a关系,表示一个更特殊的类与一个更一般的类之间的关系。
构造器
Java语言中要使用构造器用来构造新实例,构造器是一种特殊的方法,用来构造并初始化对象。
构造器的名字与类名相同,想要构造一个新实例需要在构造器前加上new操作符。在Java中,任何对象变量的值都是对存储在另一个地方的某个对象的引用,new操作符返回值也是一个引用。
每个类可以有一个以上的构造器,构造器可以0或多个参数,构造器没有返回值,构造器总是伴随new操作符一起使用。静态字段和方法
如果将一个字段定义为static,每个类只有一个这样的字段,而对于非静态的实例字段,每个对象都有自己的一个副本。
静态方法是用static修饰的方法,静态方法不在对象上指向,可以认为静态方法是没有this参数的方法(在一个非静态方法中,this参数指示这个方法的隐式参数)。可以使用对象调用静态方法,但容易造成混淆,建议使用类名调用。
可以使用静态方法的情况:方法不需要方法对象的状态,因为它所需要的所有参数都通过显示参数提供。
- 方法只需要访问类的静态字段。
方法参数
按值调用表示方法接收的是调用者提供的值,按引用调用表示方法接收的是调用者提供的变量地址。方法可以修改按引用传递的变量的值,而不能修改按值传递的变量的值。Java中总是采用按值调用,也就是说方法得到的是所有参数值的一个副本,方法不能修改传递给它的任何参数变量的内容,实际上对象引用是按值传递的。
对象构造
- 重载
如果多个方法具有相同的名字、不同的参数,便出现了重载。如果编译器找不到匹配的参数就会产生编译时错误,查找匹配的过程叫做重载解析。
Java允许重载任何方法,要完整描述一个方法需要指定方法名和参数类型,这叫做方法的签名。返回类型不是方法签名的一部分,因此不能有两个名字相同、参数类型也相同却有不同返回类型的方法。 - 默认字段初始化
如果在构造器中没有显示地为字段设置初值,那么就会被自动赋为默认值:数值为0、布尔值为false、对象引用为null。
方法中的局部变量必须明确地初始化,不能不设置初始值。 - 无参构造器
很多类都包含无参构造器,由无参构造器创建对象时,对象的状态会设置为适当的默认值。如果没有为一个类编写构造器,就为为你提供一个无参构造器,这个构造器将所有字段设置为默认值。
如果类中至少提供了一个构造器,但是没有提供无参构造器,那么构造对象时就不能使用无参构造器。 - 调用构造器
关键字this指代一个方法的隐式参数,除此之外,还可以使用this来调用同一个类的其他构造器。 - 初始化块
除了在构造器中赋值和在声明中赋值,还可以使用初始化块,即类中的代码块。
构造器调用的具体处理步骤:- 如果构造器的第一行调用了另一个构造器,则基于所提供的参数执行第二个构造器。
- 否则将所有数据字段初始化为默认值,按照在类声明中出现的顺序,执行所有字段初始化方法和初始化块。
- 执行构造器主体代码。
finalize方法
该方法在垃圾回收器清理对象之前调用,不要使用该方法来回收资源,因为并不能知道它什么时候调用而且它已被废弃。类设计技巧
保证数据私有
- 一定要对数据进行初始化
- 不要在类中使用过多基本类型
- 不是所有字段都需要单独的字段访问器和字段更改器
- 分解有过多职责的类
- 类名和方法名要能体现它们的职责
- 优先使用不可变类