java基本功-入门&语法

Java语言入门

1. Java入门

1.1. Java语言发展历史

版本 年份 特性
1.0 1996 语言本身,约8.3万个网页应用Java技术
1.1 1997 内部类
1.2 1998 strictfp关键字
1.3 2000
1.4 2002 断言
5.0 2004 添加泛型,for each循环,自动装箱,注解
6 2006 改进性能,增强类库
7 2011 2009年Oracle收购Sun公司
switch,钻石操作符,二进制字面量,异常处理改进
8 2014 lambda表达式,包含默认方法接口,流和日期/时间库
9 2017
10 2018.3
11 2018.9
12 2019.3
13 2019.9
14 2020.3
15 2020.9

1.2. Java语言应用领域

  • Java Web开发:后台开发
  • 大数据开发
  • Android应用程序开发

1.3. Java语言特性

1.3.1. 简单性

java语言是c++的“纯净”版本。没有头文件,指针运算,结构,联合,操作符重载,虚基类等。— 《Java核心技术卷I》

java支持开发能够在小型机器上独立运行的软件,基本的解释器以及类支持大约仅为4KB,加上基础的标准类库和对线程的支持大约需要175KB。这是一个了不起的成就。— 《Java核心技术卷I》

1.3.2. 面向对象

java把重点放在数据和对象的接口上。如木匠,“面向对象”木匠关注的是椅子,“面向过程”木匠关注的是使用的工具。

1.3.3. java面向对象特征

  • 封装:高内聚,低耦合

    一般使用private访问权限

    提供相应的get/set方法来访问相关属性,方法的访问权限通常为public,以提供对属性的赋值和读取

    一些只用于本类的辅助性方法可以用private修饰,其他类希望调用的方法用public修饰

  • 继承:实现代码复用

  • 多态:继承,重写方法,父类引用指向子类对象

    父类变量为编译类型

    子类对象为运行时类型

1.3.4. 分布式

java可以处理像HTTP,FTP之类的TCP/IP协议,能够通过URL打开的访问网络上的对象

1.3.5. 安全性

java适用于网络/分布式环境。

使用java可以构建防病毒,防篡改的系统。

防范运行时堆栈溢出。

防范未经授权读写文件。

1.3.6. 平台无关性

java数据类型具有固定的大小,如java的int永远是32位整数。消除了代码移植时数据溢出等问题。

java二进制以固定的格式进行存储和传输,消除了字节顺序的困忧。

java编译器生成与计算机体系结构无关的字节码指令,通过特定的java虚拟机执行字节码文件。

1.3.7. 支持多线程

带来更好的交互响应和实时行为

1.4. 比较其他语言

1.4.1. Java

由美国SUN公司发明于1995年,是目前业界应用最广泛,使用人数最多的语言,连续多年排名世界第一,称之为计算机语言界的英语。被广泛应用于企业级软件开发,安卓移动开发,大数据云计算等领域,几乎涉及IT所有行业。

1
2
3
4
5
public class HelloWorld{
public static void main(){
System.out.println("Hello World!");
}
}

1.4.2. C

诞生于1972年,现代高级语言的鼻祖,由贝尔实验室发明。C语言是人们追求结构化,模块化,高效率的“语言之花”。在底层编程,比如嵌入式,病毒开发等应用,可以替代汇编语言来开发系统程序。在高层应用,也可以开发从操作系统(Unix,Linux,Windows都基于C语言开发)到各种应用软件。

1
2
3
4
5
#include<stdio.h>
int main(){
printf("Hello World\n");
return 0;
}

1.4.3. C++

作为c语言的扩展,是贝尔实验室于80年代推出的,C++是一种混合语言,既可以实现面向对象编程,也可以开发c语言面向过程风格的程序。

C++是编译性语言,使用编译器,针对特定的操作系统,将源代码一次性翻译成可被平台硬件执行的机器码,并包装成该平台所能识别的可执行程序的格式,程序能独立运行。运行效率高,但不能在其他操作系统上运行,又需要重新编译,不易于移植。

c++是面向过程编程的,java是面向对象编程的。

c++中对于内存的分配需要程序显式地管理,用到析构函数。而java提供了垃圾回收机制来实现垃圾的自动回收

c++存在预处理,宏定义等,而java的操作都集中在类中

1
2
3
4
5
6
#include<iostream>
using namespace std;
int main(){
cout<<"Hello World"<<endl;
return 0;
}

1.4.4. C#

c#是微软公司发布的一种面向对象的,运行于.NET Framework之上的高级程序设计语言。

C#在基于windows操作系统的应用开发这一领域在取代C++,占据主导地位。Unity3D开发游戏时,使用C#和javascript

java和c#都摒弃了c++函数及其参数的const修饰,宏替换,全局变量和全局函数等,但c#仅局限于windows平台,windows平台有大量的c#基类。对于java的平台独立性来说,c#不利于移植于其他平台。

1.4.5. Python

发明于1989年,语法结构简单,易学易懂;python具有丰富和强大的库。常被称之为胶水语言,能够把用其他语言制作的各种模块很轻松的联结在一起。广泛应用于图形处理,科学计算,web编程,多媒体应用,引擎开发,尤其是大热的人工智能和机器学习。

是面向对象的解释性脚本语言,拥有大量的类库。

解释性语言是使用解释器,对源代码逐行解释成特定平台的机器码并立即执行。把编译和解释过程混合到一起同时完成。每次执行都需要进行一次编译,不能离开解释器独立运行。

1.4.6. JavaScript

脚本语言,广泛应用于web应用开发,应用范围越来越大,重要性越来越高。流行的H5开发的核心就是JavaScript语言。

1
2
3
<script>
document.write("<h2>Hello World</h2>");
</script>

1.5. Java语言版本

J2ME(Micro Edition) - 控制移动设备和信息家电等有限存储设备,android开发

J2SE(Sandard Edition) - java技术核心,桌面或简单服务器应用的java平台,QQ等

J2EE(Enterpirse Edition) - 企业应用开发相关,服务器端应用

Open JDK - javaSE的免费开源实现

1.6. java包下载

https://www.oracle.com/cn/java/technologies/javase-downloads.html

1.6.1. JDK

java开发工具包,包括java编译器(javac),运行时环境,常用类库,JRE,开发工具(javadoc和jdb)。

它能够创建和编译程序。

1.6.1.1. Oracle JDK

Oracle JDK 是 OpenJDK 的一个实现,并不是完全开源的

Oracle JDK 比 OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择 Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用 OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题

在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能

Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本

Oracle JDK 使用 BCL/OTN 协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可

  • BCL 协议(Oracle Binary Code License Agreement): 可以使用 JDK(支持商用),但是不能进行修改。
  • OTN 协议(Oracle Technology Network License Agreement): 11 及之后新发布的 JDK 用的都是这个协议,可以自己私下用,但是商用需要付费

Oracle JDK 各个版本所用的协议

Oracle JDK版本 BCL协议 OTN协议
6 最后一个公共更新6u45之前
7 最后一个公共更新7u80之前
8 8u201/8u202之前 8u211/8u212之后
9 ☑️
10 ☑️
11 ☑️
12 ☑️

1.6.1.2. OpenJDK

OpenJDK 项目主要基于 Sun 捐赠的 HotSpot 源代码,完全开源。

OpenJDK 被选为 Java 7 的参考实现,由 Oracle 工程师维护

关于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更详细的答案:

问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?

答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些闭源的第三方组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。

1.6.2. JRE

java运行时环境。

包括JVM,类加载器,字节码校验器等。

它是运行已编译java程序所需的所有内容的集合,不能用于创建新程序。

如果只是为了运行一下java程序,只需要安装JRE就可以。需要进行一些java编程工作,则需要安装JDK。

1.6.3. JVM

执行字节码的虚拟计算机,定义了指令集,寄存器集,结构栈,垃圾收集堆,内存区域。负责将java字节码解释运行,边解释边运行。不同操作系统不同虚拟机,从而屏蔽了底层运行平台的差别,实现了“一次编译,随处运行”,jvm是java实现跨平台的核心机制。

JVM并不是只有一种,只要满足JVM规范,每个公司,个人都可以开发自己专属JVM。

除了常用的HotSpot VM外,还有J9 VM,Zing VM,JRockit VM等VM。

常见的JVM对比:https://en.wikipedia.org/wiki/Comparison_of_Java_virtual_machines

官方各个JDK版本对应的JVM规范:https://docs.oracle.com/javase/specs/index.html

1.6.4. JDK文件夹组成

– bin 工具命令

– db 数据库

– include 平台特定头文件

– jre JRE环境

– lib 工具命令实际执行程序

– javafc-src.zip

– README,LICENSE说明文档

1.6.5. 配置环境变量-mac

下载的是.dmg文件

path是操作系统执行命令时,所要搜寻的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ open .bash_profile

//添加内容
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
PATH=$JAVA_HOME/bin:$PATH:.

//导出环境变量
$ export PATH
//使变量值生效
$ source .bash_profile

//检查环境是否生效
$ java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

1.6.6. 配置环境变量-Linux

下载的是.tar.gz文件

解压

1
tar zxvf xxx.tar.gz

放到/usr/local/jdk7

1
cp -r xxx /usr/local/jdk8

配置环境变量

1
nano /etc/profile

变量

1
2
3
4
JAVA_HOME=/usr/local/jdk8
CLASSPATH=.:$JAVA_HOME/lib/:$JRE_HOME/lib
PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
export JAVA_HOME CLASSPATH PATH

使其生效

1
source profile

验证

1
java -v

1.7. 编写第一个Java程序

1
2
3
4
5
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello World");
}
}

class 类是构建所有java应用程序的构建块

main是jvm访问入口标识

void表示方法没有返回值,main方法正常退出,退出代码为0,表示成功运行了程序

static 说明main方法是一个静态方法,即方法中的代码存储在静态存储区,只要类被加载后,就可以使用该方法而不需要通过实例化对象来访问。类还没有实例化,并不能通过对象调用方法,static表示该方法属于类,可以直接通过类.main()直接访问,JVM启动后就是按照这个方法的签名来查找方法的入口地址。

public 说明方法是类可见,包可见的,外部可以调用

String[] 数组用于和程序员进行交互,java HelloWorld a b ==>String[0]=a,String[1]=b;

必须保证main()返回值为void,并有static与public修饰
故main()可以用final,synchronized修饰

文件的命名必须和public class类名相同,一个java文件只能有一个public class

System.out.println("Hello World");表示把文本行输出到控制台上

在java中,每个句子必须用分号结束

.用于调用方法,等价于函数调用

1.8. Java语言运行机制

1
2
$ javac xx.java  //编译
$ java xx //解释

java语言先编译再解释成机器码。

java源码经编译器编译成.class,生成的是与平台无关的字节码,字节码只能通过java虚拟机jvm解释成特定平台的机器码。

1.8.1. 字节码

JVM可以理解的代码就叫做字节码(.class文件),它不面向任何特定的处理器,只面向java虚拟机。java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时保留了解释型语言可移植的特点。所以,java程序运行时相对来说还是高效的。而且,由于字节码并不针对一种特定的机器,因此,java程序无须重新编译便可在多种不同操作系统的计算机上运行。

.class->类装载器->字节码校验器->解释器 需要加载字节码文件,通过解释器逐行解释执行,从而这种方式执行速度会相对比较慢。会有一些方法和代码块经常被调用而被称为热点代码,从而引进了JIT(just in time compilation)编译器,属于运行时编译,在完成第一次编译后,会将字节码对应的机器码保存下来,下次可以直接使用,机器码的运行效率是高于java解释器的。所以常说java是编译与解释共存的语言。

HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。

JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。

JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。

JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。

1.8.2. java是编译与解释共存的语言

编译型 编译型语言 会通过编译器将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较快,开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等

解释型 解释型语言 会通过解释器一句一句的将代码解释(interpret)为机器代码后再执行。解释型语言开发效率比较快,执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。

java则是采用即时编译技术,已经缩小了这两种语言间的差距。这种技术混合了编译语言与解释型语言的优点,它像编译语言一样,先把程序源代码编译成字节码。到执行期时,再将字节码直译,之后执行。

2. Java语法

2.1. Java注释

当项目结构复杂或编写的代码有段时间了,为了方便理解代码意思,可以对代码进行注释。

注释中的内容并不会被执行,在编译期就会被编译器抹掉。

写程序时随手加上注释是一个非常好的习惯。

《clean code》书中指出:

代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。

若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。

注释方式:///**//** */

// 用于单行注释

/**/ 可用于多行注释

/** */可用于自动生成javadoc文档

2.2. Java关键字

关键字是指有专门用途的字符串。属于已经被赋予特殊含义,只能用于特定地方的标识符。

分类 关键字
访问控制 private protected public
类,方法和变量修饰符 abstract class extends final implements interface native
new static strictfp synchronized transient volatile
程序控制 break continue return do while if else
for instanceof switch case default
错误处理 try catch throw throws finally
import package
基本类型 boolean byte char double float int long
short null true false
变量引用 super this void
保留字 goto const

2.3. Java标识符

标识符用来给变量,类,方法以及包进行命名。

  • 标识符必须以字母,下划线_,美元符号$组成

  • 标识符其他部分可以是字母,下划线,美元符和数字的任意组合

  • 标识符大小写敏感,长度无限制

  • 不可以使用关键字和保留字,但能包含关键字和保留字

  • 标识符不能包含空格

  • 不建议使用汉字命名

2.4. Java命名规范

包名:xxxyyyzzz

类名,接口名:XxxYyyZzz

变量名,方法名:xxxYyyZzz

常量名:XXX_YYY_ZZZ

2.5. Java变量

变量 代表可操作的存储空间,空间位置确定,但内容不定。

要素包括变量名,变量类型和作用域,甚至变量值。

变量在使用前必须对其声明,只有在变量声明之后,才能为其分配相应长度的存储空间。

变量的作用域在{}

变量声明 double a = 0.3;每个声明以分号结束。

变量必须是一个以字母开头并由字母,数字组成的序列,字母包括A~Z,a~z,_,$

变量可分为成员变量(实例变量,类变量),局部变量(形参,方法局部变量,代码块局部变量)

2.6. Java常量

在java中关键字final指示常量,表示只能被赋值一次

1
public static final int VALUE = 10;

2.7. Java数据类型

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

java中共有8种基本数据类型和3种引用类型。

基本数据类型包括4种整型(byte,short,int,long),2种浮点型(float,double),1种表示Unicode编码的字符单元的字符类型char,1种用于表示真值的boolean值。

引用类型包括类,接口,数组。

基本数据类型直接存放在java虚拟机栈种的局部变量表中,而基本数据类型对应的包装类属于对象类型,对象实例存在于堆中。

java没有无符号(unsigned)形式

null不是合法的object实例,编译器不会分配内存,它仅仅表示当前引用类型不指向任何对象

2.7.1. 整型

类型 存储需求 取值范围 默认值 包装类
byte 1字节 = 8位 -128~127 0 Byte
short 2字节=16位 -32768~32767 0 Short
int 4字节=32位 -2147483648~2147483647 2x$10^9$ 0 Int
long 8字节=64位 -9x$10^{18}$~9x$10^{18}$ 0L Long

十六进制有前缀0x,0x010=16

八进制有前缀0,010=8

二进制有前缀0b,0b010=2

2.7.2. 浮点型

类型 存储需求 取值范围 默认值 包装类
float 4字节 32位 1 8 11,6~7位精确 0.0f Float
double 8字节 64位 1 11 52,15-16位精确 0.0(浮点数默认类型) Double

浮点数遵循IEEE754规范,浮点数值不适合存在舍入误差的计算

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。

就比如说十进制下的 0.2 就没办法精确转换成二进制小数:

1
2
3
4
5
6
7
8
// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,
// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0(发生循环)
...

2.7.3. 字符型char

类型 存储需求 取值范围 默认值 包装类
char 2字节=16位 0-65536 \u0000 Character

形式:字符常量是单引号引起的一个字符,字符串常量是双引号引起的0个或若干个字符

含义: char类型的值可以表示为十六进制值,取值范围为\u0000-\uffff。字符常量可以参加表达式运算,字符串常量代表一个地址值,表示其在内存中存放位置。

表现形式:

  • char ch = ‘A’;
  • 转义字符:特殊含义的字符,如 \n表示换行
  • 直接使用 Unicode 值来表示字符型常量。\u000a=\n

当计算机保存某个字符时,将该字符的编号转换成二进制码,其中字符集就是所有字符的编号组成的集合。

汉字:默认使用Unicode编码方式,一个字符占2字节

英文占1字符,中文占2字符

判断汉字:String.getByte().length() == String.length()

2.7.4. 布尔型boolean

类型 存储需求 取值范围 默认值 包装类
boolean 4字节,32位
而boolean数组是1字节,8位
false Boolean

boolean有两个值:true和false,用来判定逻辑条件。不同于c++语言,整数值和布尔值不能相互转换。

1
2
boolean flag = true;
//if(flag==1){}

flag==1是不对的

但在c++中是可以的

1
2
bool flag = true;
if(flag==1){}

2.7.5. 数值类型之间的转换

当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型

当byte,char,short三种类型的变量做运算时,结果为int型

实线表示无信息丢失的转换,虚线表示有精度损失的转换

byte/short/char在表示范围内,javac编译时自动会隐含进行类型转换

1
2
3
//字面量5是一个int类型,int->byte 必须进行类型转换
byte b = /*byte*/ 5;
char c = /*char*/ 65;

变量赋值时,右侧表达式中全都是常量,javac直接将若干常量表达式计算得到结果

1
2
3
4
5
6
7
8
short result = 5 + 8;
System.out.println(result); //13

short a = 5;
short b = 8;
//short + short -> int + int -> int
////语法错误,左侧需要为int,因为右边使用的是int的容量计算
//result = a + b;

2.7.6. 强制类型转换

1
2
3
double a = 3.14;
//3
int b = (int)a;

会截断小数部分将浮点值转换为整型

当试图将一个数值从一种类型强制转换为另一种类型,又超出目标类型的表示范围,结果会截断成一个完全不同的值

2.7.7. 包装类

java中对应每个基本数据类型都有包装类,把数据装成对象

Integer,Float,Double,Long,Short,Byte,Character,Void,Boolean 均为不可变类

2.7.7.1. Integer

1
2
3
4
5
6
7
8
9
10
11
public final class Integer extends Number implements Comparable<Integer> {  

private final int value;

public Integer(int value) {
this.value = value;
}
public int intValue() {
return value;
}
}

2.7.7.2. Float

1
2
3
4
5
6
7
public final class Float extends Number implements Comparable<Float> {
private final float value;

public Float(double value) {
this.value = (float)value;
}
}

2.7.7.3. Double

1
2
3
4
5
6
public final class Double extends Number implements Comparable<Double> {  
private final double value;
public Double(double value) {
this.value = value;
}
}

2.7.7.4. Long

1
2
3
4
5
6
public final class Long extends Number implements Comparable<Long> {
private final long value;
public Long(long value) {
this.value = value;
}
}

2.7.7.5. Short

1
2
3
4
5
6
public final class Short extends Number implements Comparable<Short> { 
private final short value;
public Short(short value) {
this.value = value;
}
}

2.7.7.6. Byte

1
2
3
4
5
6
public final class Byte extends Number implements Comparable<Byte> { 
private final byte value;
public Byte(byte value) {
this.value = value;
}
}

2.7.7.7. Character

1
2
3
4
5
6
public final class Character implements java.io.Serializable, Comparable<Character>{ 
private final char value;
public Character(char value) {
this.value = value;
}
}

2.7.7.8. Void

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class Void {

/**
* The {@code Class} object representing the pseudo-type corresponding to
* the keyword {@code void}.
*/
@SuppressWarnings("unchecked")
public static final Class<Void> TYPE = (Class<Void>)Class.getPrimitiveClass("void");

/*
* The Void class cannot be instantiated.
*/
private Void() {}
}

2.7.7.9. Boolean

1
2
3
4
5
6
public final class Boolean implements java.io.Serializable,Comparable<Boolean>{ 
private final boolean value;
public Boolean(boolean value) {
this.value = value;
}
}

2.7.8. 装箱,拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//装箱
Integer a = 10;
//等价于
a = Integer.valueOf(10);

//拆箱
int in = a;
//等价于
in = new Integer(3).intValue();

//String->int
in=Integer.parseInt(str);
//Integer->String
String str = Integer.toString(2);
str = String.valueOf(2);
str = 2+"";

2.7.9. 缓存池问题

1
2
3
4
5
6
7
8
9
Integer x = new Integer(2);
Integer y = new Integer(2);
System.out.println(x == y); // false
x = Integer.valueOf(2);
y = Integer.valueOf(2);
System.out.println(x == y); // true
x = 2;
y = 2;
System.out.println(x == y); // true 采用了自动装箱 Integer.valueOf(2);

【分析】:new Integer(2) 每次创建新对象;Integer.valueOf(2) 使用缓存池取的是同一个对象的引用。

Jdk1.8

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

Integer 缓存池的大小默认为 -128~127。cache[256]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

2.7.10. 上界可调

启动JVM配置

1
-XX:AutoBoxCacheMax=<size>

该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。

1
2
3
4
5
6
7
8
public class Test {

public static void main(String[] args) {
Integer x = Integer.valueOf(180);
Integer y = Integer.valueOf(180);
System.out.println(x == y);
}
}

编译

1
2
3
4
javac Test.java
java Test

false
1
2
3
4
javac Test.java
java -XX:AutoBoxCacheMax=200 Test

true

2.7.11. 其他数据类型的缓存

Short [255]

1
2
3
4
5
6
7
8
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}

Byte [256]

1
static final Byte cache[] = new Byte[-(-128) + 127 + 1];

Character [128]

1
static final Character cache[] = new Character[127 + 1];

Boolean

1
2
3
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。

2.8. 大数值

任意长度数字序列数值

###大整数BigInteger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//BigInteger.valueof(long x)
BigInteger a = BigInteger.valueof(100);
BigInteger b = BigInteger.valueof(10);
//c=a+b;
BigInteger c = a.add(b);

//d=c*(b+2)
BigInteger d = c.multiply(b.add(BigInteger.valueof(2)));

//BigInteger add(BigInteger) +
//BigInteger subtract(BigInteger) -
//BigInteger multiply(BigInteger) *
//BigInteger divide(BigInteger) /
//int compareTo(BigInteger)

2.8.1. 大实数BigDecimal

2.8.1.1. 初始化

1
2
3
BigDecimal bg = BigDecimal.valueof(long val);
//或者 unscaledVal/(10^scale)的实数
bg = BigDecimal.valueof(long unscaledVal, int scale);

2.8.1.2. 加减乘除

  • add 方法用于将两个 BigDecimal 对象相加

  • subtract 方法用于将两个 BigDecimal 对象相减

  • multiply 方法用于将两个 BigDecimal 对象相乘

  • divide 方法用于将两个 BigDecimal 对象相除

1
2
3
4
5
6
7
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.add(b));// 1.9
System.out.println(a.subtract(b));// 0.1
System.out.println(a.multiply(b));// 0.90
System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

其中divide方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
scale 表示要保留几位小数
roundingMode 代表保留规则
*/
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) {
return divide(divisor, scale, roundingMode.oldMode);
}

//保留规则
public enum RoundingMode {
// 5.5 -> 6 , 2.5 -> 3
// -1.1 -> -2 , -2.5 -> -3
UP(BigDecimal.ROUND_UP),
// 5.5 -> 5 , 2.5 -> 2
// -1.1 -> -1 , -2.5 -> -2
DOWN(BigDecimal.ROUND_DOWN),
// 5.5 -> 6 , 2.5 -> 3
// -1.6 -> -1 , -2.5 -> -2
CEILING(BigDecimal.ROUND_CEILING),
// 5.5 -> 5 , 2.5 -> 2
// -1.6 -> -2 , -2.5 -> -3
FLOOR(BigDecimal.ROUND_FLOOR),
// 5.5 -> 6 , 1.6 -> 2
// -1.6 -> -2 , -2.5 -> -3
HALF_UP(BigDecimal.ROUND_HALF_UP),
// 5.5 -> 5 , 1.1 -> 1
// -1.6 -> -2 , -2.5 -> -2
HALF_DOWN(BigDecimal.ROUND_HALF_DOWN),
// 5.5 -> 6 , 1.1 -> 1
// -1.6 -> -2 , -2.5 -> -2
HALF_EVEN(BigDecimal.ROUND_HALF_EVEN),
// 5.5 -> throw ArithmeticException , 1.0 -> 1
// -1.6 -> throw ArithmeticException , -1.0 -> -1
UNNECESSARY(BigDecimal.ROUND_UNNECESSARY);
}

2.8.1.3. 比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

1
2
3
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

2.8.1.4. 保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

1
2
3
BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

2.8.1.5. 返回值

1
2
3
4
public double doubleValue(){}
public float floatValue(){}
public int intValue() {}
public long longValue(){}

2.9. 数组

引用类型。存储同一类型值的有序集合。通过一个整型下标可以访问数组中的每一个值。

  • 长度固定。一旦创建,大小不可变。
  • 元素类型相同。
  • 数组类型可以是任意数据类型,包括基本类型和引用类型。
  • 数组变量属于引用类型,数组也是对象,数组元素就是对象属性。

2.9.1. 初始化

1
2
3
4
5
6
7
8
9
10
11
12
//定义数组长度
int size=4;

//创建长度为size的整型数组
int[] array=new int[size];

//初始化
array={123};
array=new int[]{123};

//多维数组-一维数组的引用
int[][] arr = new int[size][size];

java没有指针运算,即不能通过array+1得到数组下一个元素

java在定义数组时,并不会给数组元素分配存储空间,[]中不需要指定数组的长度

2.9.2. Arrays 工具类

java.util.Arrays

1
2
3
4
5
6
7
8
9
10
11
12
13
//数组所有值拷贝到新数组
int[] newArray = Arrays.copyOf(array,array.length);
//优化的快速排序:从小到大
Arrays.sort(array);
Arrays.asList(array); //转为List
//转为String
Arrays.toString(array);
//赋值
Arrays.fill(array,8);
//二叉查找
Arrays.binarySearch(type[],int start,int end,type v);
//数组内容相等
Arrays.equals(type[] a,type[] b);

输出

1
2
3
4
5
6
7
8
9
10
11
12
int table[n][m];
for(int i=0;i<table.length;i++){
for(int j=0;j<table[i].length;j++){

}
}

//foreach循环
for(int[] rows: table){
for(int value: rows){
}
}

2.10. 运算符

+-*/表示加减乘除

% 结果的符号与被模数的符号相同

2.10.1. strictfp关键字

double w = x * y / z

默认情况下,允许Intel处理器将中间结果存储在80位寄存器,最终结果截断为64位

strictfp关键字则使用严格的浮点计算,可能出现溢出

2.10.2. 结合赋值和运算符 +=,/=,-=,*=

x+=4; == x=x+4;

1
2
3
4
short short1 = 1;
//short1 = short1 + 1; 字面量1是int类型,比short类型精度高,不能隐式将int转为short类型

short1 += 1; //隐式类型转换则是可以的 == (short)(short1+1);

2.10.3. 自增与自减运算符

n++ 表示变量n先参与运算,运算完再+1

++n 表示n先+1,再参与运算

用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。

2.10.4. 关系运算符

<,>,>=,<=,&&,||,!,!=,?:

if(a>1&&a<10) 表示判断a是否大于1,小于10

if(a>1||a>10)表示判断a是否大于1,或者大于10,||短路或,前表达式为true时,不计算后一个表达式

int a = x<y?x:y;表示x小于y的话,a=x,否则的话a=y

2.10.5. 位运算符

& and与:1&1=1,1&0=0,0&0=0,位运算符并,逻辑运算符作用同&&,但不短路,计算前后两个表达式

| or或:1|0=0 , 1|1=1,0|0=0,位运算符或,逻辑运算符作用同||,不短路,计算前后两个表达式

^ xor异或:1^0=1,0^0=0,1^1=0,位运算符异或,逻辑运算符只有当两个表达式结果不同时才返回true

~ not非:0=1,1=0

>> 右移:符号位填充高位,相当于除以2

>>> 右移:0填充高位

<< 左移:0填充低位,相当于乘以2

移位运算符右操作符要完成模32运算,1<<35 = 1<<3 = 8

2.11. 枚举类型

变量取值只在一个有限集合内,可以自定义枚举类型。

1
2
enum Size{SMALL,MEDIUM,LARGE};
Size s = Size.SMALL;

Size类型变量只能存储这个类型声明中国给定的某个枚举值

以下部分需要学习面向对象后理解

2.11.1. 自定义枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Season {
private final String name;
private final String date;

private Season(String name,String date){
this.name = name;
this.date = date;
}

public static final Season SPRING = new Season("Spring","1");
public static final Season SUMMER = new Season("Summer","2");
public static final Season AUTUMN = new Season("Autumn","3");
public static final Season WINNTER = new Season("Winnter","4");

@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", date='" + date + '\'' +
'}';
}
}

运行结果:

1
2
3
4
5
6
7
@Test
void testSystem() {
Season season = Season.SPRING;
//Season{name='Spring', date='1'}
System.out.println(season);

}

2.11.2. enum类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum Size{
//必须在第一句
SAMLL("s"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL");
private String str;

private Size(String str){
this.str=str;
}
public String getStr(){
return this.str;
}
}

//枚举默认类型:class java.lang.Enum
System.out.println(Size.class.getSuperclass());

//Size.SMALL
Size s = Enum.valueof(Size.class,"SMALL");

//获得所有枚举类
Size[] values=Size.values();

//返回枚举常量位置 0
Size.SMALL.ordinal();

//出现位置的比较 <0
s.compareTo(Enum.valueof(Size.class,"LARGE");

2.11.3. 实现接口

不同枚举实现的方法不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface info{
void show();
}
public enum Season implements info{
SPRING("Spring","1"){
@Override
public void show() {
System.out.println("S");
}
},
SUMMER("Summer","2"){
@Override
public void show() {

}
},
AUTUMN("Autumn","3"){
@Override
public void show() {

}
},
WINNTER("Winnter","4"){
@Override
public void show() {

}
};

private final String name;
private final String date;

private Season(String name,String date){
this.name = name;
this.date = date;
}
}

测试:

1
2
3
4
5
@Test
void testSystem() {
Season season = Season.SPRING;
season.show();
}

2.12. 字符串

2.12.1. String不可变字符串

定义为String e = "";

从概念上来说,字符串就是Unicode字符序列

从源码上来说,String内部实现是字符数组

1
2
3
4
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
//...
}

2.12.1.1. String 真正不可变的原因

final关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的基本数据类型变量不能改变值,修饰的引用类型不能再指向其他对象,final关键字修饰的数组保存的字符串其实是可变的。

真正导致String不可变是因为保存字符串的数组被final修饰且为私有的,并且String类没有提供修改这个字符串的方法;String被final修饰导致不能被继承,避免了子类破坏String不可变。

2.12.1.2. 常用方法

查看在线API:https://docs.oracle.com/javase/8/docs/api/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String str.replace("","");

//按符合正则表达式进行分割
String[] str.split("");

//将str2加到str后
String str2 = " World";
str.concat(str2);

//第一次出现字符串的位置,从0位开始
int str.indexOf("wor");

//字符串字符全转为小写
String str.toLowerCase();
//字符串字符全转为大写
String str.toUpperCase();

//charAt(n)返回字符串位置n的代码单元
String str="Hello";
//H
char ch = str.charAt(0);

String与其他数据之间的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//char[] -> string
char[] ch = {'H','E','L','L','O'};
String str = new String(ch);
System.out.println(str);

//string->char[]
char[] cr =str.toCharArray();
System.out.println(cr);

//基本数据类型->string
str = String.valueOf(3);
System.out.println(str);

str = ""+true;
System.out.println(str);

//string->包装类
boolean bo = Boolean.parseBoolean(str);
System.out.println(bo);

//string->byte[]
str = "Hello World";
byte[] bytes = str.getBytes();
System.out.println(bytes.length);//11
//[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]
System.out.println(Arrays.toString(bytes));

//byte[]->string
str = new String(bytes);
System.out.println(str);

//指定字符集
//字符->二进制 编码
//二进制->字符 解码
byte[] bytes = str.getBytes("UTF-8");
str = new String(bytes,"UTF-8");

2.12.1.3. 空串与null串

1
2
3
4
String str = null;
if(str!=null&&str.length()!=0){

}

null表示目前没有任何对象与该变量关联

length()返回采用UTF-16编码表示的给定字符串所需要的代码单元数量

2.12.1.4. 源码分析

Java 8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

//不允许被继承
private final char value[];

//构造器
public String() {
this.value = "".value;
}

//将一个字符串对象作为另一个字符串对象的构造函数参数时
//并不会完全复制 value 数组内容,而是都会指向同一个 value 数组
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}

public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
}

value数组声明为final,初始化后不能被其他数组引用,保证String不可变。

不可变

  • 保证String的hash值不变,在HashMap中当key时保证键值
  • 保证String从常量池中取得字符串常量
  • 作为网络连接参数,确保双方数据一致性
  • 保证多线程的安全性

源码分析

1
2
String str="Hello";
str+="World";

==

1
2
3
StringBuilder su= new StringBuilder("Hello");
su.append("World");
String str=su.toString();

2.12.1.5. 存储机制

1
2
3
4
String s1="abc";
String s2="abc";
String s3="ab"+"c";
String s4=new String("abc");

s1=s2=s3!=s4
常量池中存放abc字符串常量,s1,s2都指向常量池中的abc

s3的ab,c同样在编译期就被解析成一个字符串常量abc,s3指向abc

new String(),堆中生成new出来的对象,对象中value[]指向常量池中的abc,栈中的s4指向堆中的对象

常量池:在编译期就确定的被保存在已编译的.class文件中的一些数据。

1
2
3
4
5
6
7
String a = "abc";
String b = "ab";
String c = b+"c";
String d = c.intern();

System.out.println(a==c);//false
System.out.println(a==d);//true

常量与常量的拼接结果在常量池中,而且常量池中不存在相同的内容常量

只要出现变量,结果就会存入堆中

拼接结果调用intern()方法,返回值放在常量池中

跟输出形式关联

1
String str=String.format("%d",age);

2.12.1.6. 练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
char c = 'a';
int num = 10;
String str = "hello";

//107hello
System.out.println(c + num + str);
//ahello10
System.out.println(c + str + num);
//a10hello
System.out.println(c + (num + str));
//107hello
System.out.println((c + num) + str);
//hello10a
System.out.println(str + num + c);

//* *
System.out.println("* *");
//93
System.out.println('*'+'\t'+'*');
//* *
System.out.println('*'+"\t"+'*');
//51*
System.out.println('*'+'\t'+"*");
//* *
System.out.println('*'+('\t'+"*"));

2.12.1.7. 可变长参数

从java5开始,java支持可变长参数,即允许在调用方法时传入不定长度的参数。

1
public void method(String... args){}

遇到方法重载的情况怎么办呢?会优先匹配固定参数还是可变参数的方法呢?

会优先匹配固定参数的方法,因为固定参数的方法匹配度更高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
void contextLoads() {
method("1");
method("1","2");
method("1","2","3");

}

public void method(String... str){
System.out.println("可变长参数");
for (String s:str) {
System.out.print(" "+s);
}
}

public void method(String str){
System.out.println("一个参数");
System.out.println(str);
}

public void method(String str1,String str2){
System.out.println("两个参数");
System.out.println(str1+" "+str2);
}

结果

1
2
3
4
5
6
一个参数
1
两个参数
1 2
可变长参数
1 2 3

2.12.2. StringBuilder

每次连接字符串都会构建一个新的String对象,既耗时,又浪费空间,使用StringBuilder可以避免这个问题

调用

1
2
3
StringBuilder str = new StringBuilder();
str.append("H").append("I");
String tostr = str.toString();

append()方法返回的是创建的StringBuilder对象,所以可以无限调用

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public final class StringBuilder extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{

char[] value;

/**
* The count is the number of characters used.
*/
int count;

//构造器
public StringBuilder() {
super(16);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}

// 扩容为 int newCapacity = (value.length << 1) + 2;
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}

public int length() {
return count;
}
}

2.12.3. StringBuffer

作为StringBuilder的前身,因采用多线程方式执行添加或删除字符操作故效率低

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
char[] value;

//构造器
public StringBuffer() {
super(16);
}

public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}

//方法都用synchronized 同步代码块,线程安全
@Override
public synchronized int length() {
return count;
}

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
}

2.13. 输入输出

java.util.Scanner类实现输入操作

1
2
3
4
5
6
7
Scanner sc=new Scanner(System.in);//InputStream
//接收控制头输入一行,包括空格
String str = sc.nextLine();
//接收整数
str = sc.nextInt();
//接收浮点数
double d = sc.nextDouble();

System.out输出到控制台

1
2
3
int a = 10;
//输出a
System.out.println(a);

2.14. 控制流程

2.14.1. 条件语句

1
2
3
4
5
6
if(condition)
{
statement1
}else{
statement2
}

switch语句将从选项匹配的case标签处开始执行直到遇到break语句,如果没有相匹配的case标签,如果有default语句,则执行这个语句。

case标签可以是char,byte,short,int,String,枚举常量

1
2
3
4
5
6
7
8
9
int choice = 1;
switch(choice){
case 1:
break;
case 2:
break;
default:
break;
}

2.14.2. 循环语句

1
2
3
while(condition){
statement
}

先判断condition,为true时执行一次语句,再判断条件,持续这个操作,直到条件为false时,退出循环。

如果一开始条件就为false,则语句一次也不会执行

1
2
3
do{
statement
}while(condition);

无论条件是否满足都先执行一次语句,再判断条件,然后持续判断执行操作,直到条件为false时,退出循环。

1
2
3
for(int i=0;i<=10;i++){
System.out.println(i);
}

初始化变量i=0,再判断i值是否满足条件,条件满足情况下执行语句,即输出i值,最后将i值加1,即为2,将i值判断是否满足条件,条件满足情况下执行语句……持续到i值不满足条件后完成for循环

2.14.3. 中断

在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环

break 退出当前整个循环

continue 跳过本次执行,继续下一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//当i=1时退出循环,故输出0
for(int i=0;i<10;i++){
if(i%2!=0){
break;
}
System.out.println(i);
}

//当i为奇数时退出当前循环
//故输出0,2,4,6,8
for(int i=0;i<10;i++){
if(i%2!=0){
continue;
}
System.out.println(i);
}
本文结束  感谢您的阅读
  • 本文作者: Wang Ting
  • 本文链接: /zh-CN/2019/09/12/java语言简史/
  • 发布时间: 2019-09-12 20:31
  • 更新时间: 2022-01-15 19:56
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!