概述

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种基本数据类型。

  • 整型

    1. 整型用于表示没有小数部分的数值,允许是负数,一共有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,这些下划线只是为了易读性,编译器会去除这些下划线。

  • 浮点型

    1. 浮点型用于有小数部分的数值
类型 存储需求 取值范围
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型

    1. 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型

    1. boolean型只有两个值:falsetrue,占用一个字节,用来判断逻辑条件,整型值与布尔值之间不能相互转换。

    变量与常量

  • 变量

    1. 变量名必须是一个以字母开头并由字母或数字构成的序列,与大多数语言相比,Java中字母和数字的范围更大,字母包括了'A'~'Z''a'~'z''_''$'或在某种语言中表示字母的任何Unicode字符。同样数字也包括'0'~'9'和在某种语言中表示数字的任何Unicode字符。不可使用空格,并且变量名是大小写敏感的,长度基本没有限制。<br /> 声明一个变量之后,必须使用赋值语句对变量进行显式初始化,千万不要使用未初始化的变量。在Java中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的编码风格。
  • 常量

    1. 利用关键字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=13String 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方法
    该方法在垃圾回收器清理对象之前调用,不要使用该方法来回收资源,因为并不能知道它什么时候调用而且它已被废弃。

    类设计技巧

  • 保证数据私有

  • 一定要对数据进行初始化
  • 不要在类中使用过多基本类型
  • 不是所有字段都需要单独的字段访问器和字段更改器
  • 分解有过多职责的类
  • 类名和方法名要能体现它们的职责
  • 优先使用不可变类