本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新)。
一、Java的历史与演变
目的:为完全了解Java,需要理解Java的诞生原因、成型动力以及他继承的思想。
计算机语言的创新与发展的原因:适应环境和用途的变化;实现编程艺术的完善和提高。
1.1、Java的家世
Java大量特性从C和C++继承过来。
C语言的诞生
C语言前,BASIC、COBOL、FORTRAN这几种编程语言没有遵循结构化原则设计,依赖GOTO作为程序控制手段;Pascal虽是结构化语言,但不是针对高效率设计的,无法设计系统级代码。
C语言出现的2个条件:软件需求加快,学术界在努力创建一种新的设计语言;计算机硬件普及,计算机不再被锁起来,程序员可以随意使用计算机进行尝试。
C语言是一种功能强大、高效率、结构化的语言;而且他是程序员设计的,为程序员服务的语言。
C++
出现的原因:更好地管理程序的复杂性。
改进:C++是在C#的基础上,增加了对面向对象(OOP)的支持,使程序员能理解和管理更大的程序。
Java出现的条件
20世纪80年代末90年代初,万维网(World Wide Web)和因特网发展到了临界规模。
1.2、Java诞生
Java于1991年,由Sun公司构想出。
最初开发Java的推动力:对平*立的语言的需求。
推动力
当时需要为一些电子设备(微波炉、遥控器等)的各种CPU开发程序,如果使用C或者C++需要对每一种CPU开发对应的编译器,所以需要一种可移植对平*立的语言。
因特网成就Java:刚好这个时候因特网的飞速发展,也需要可移植的程序,Java的特性也适合于因特网的需求,使Java的关注点从消费电子产品转移到Internet。
Java的特性
1、为了吸引C/C++的程序员,Java继承了很多C/C++的特性,但Java不是为了替代它们,而是为了解决特定领域问题而出现的。
2、Java是专门针对程序员的语言,因为他的设计、测试、改进都是由程序员完成,扎根与程序员的需求,也为程序员开放了足够的权限。
与C#的关系
Java影响了C#语言的发展,两者共享相同语法,支持分布式编程,使用相似的对象模型。
1.3、Java改变Internet的方式
Internet推动了Java,Java也影响了Internet。
Java简化了Web编程,创建了新的网络程序类型applet,解决了一些Internet棘手问题如移植性和安全性。
Java applet
applet是一种可以在Internet上传送的程序,可以在兼容Java的Web浏览器中自动运行。
applet扩展了网络空间*流动的对象的范畴。这是一种动态的,自动运行的代码,由服务器端初始化,然后在客户端自动运行。
虽然这种动态的联网程序符合人们愿望,但是同时带来了严重的安全性和可移植性。
安全性
当下载这个自动执行的程序的时候,里面可能包含各种病毒或者恶意代码。
而Java将applet限定在Java的执行环境内,不允许访问计算机的其它部分来实现安全性。
可移植性
applet会被下载到不同的环境,不同的系统中,如何实现在这些不同的环境下的运行,下面将讲解。
1.4、Java的魔力:字节码
解决安全性和可移植性的关键:字节码。
Java编译器输出的不是可执行代码,而是高度优化的指令集合。
字节码在Java运行时系统中执行,Java运行时系统也称为Java虚拟机(Java Virtual Machine,JVM),原始也称为字节码解释器。
字节码和JVM实现了移植性和安全性
- 移植性:同一个程序,Java编译器输出的是相同的字节码;而这个字节码需要在不同的环境中运行,只需要实现对应环境的JVM即可;这些不同的JVM,运行的是相同的字节码,从而实现了移植性。
- 安全性:因为Java程序依赖JVM来运行,所以在JVM之外的系统其它部分,Java代码是无法修改的,从而实现了一定的安全性。
- 速度:一般来说,这样经过一个中间层再执行代码,理论上会慢一点;但是Java对字节码进行了足够的高度的优化,所以这个速度是足够快的。
- 执行代码:为了提高性能,Java也提供了HotSpot技术,为字节码提供了即时(Just In Time,JIT)编译器,可将字节码实时编译为系统可执行代码。
1.5、servlet:服务器端的Java
Java横跨服务器/客户端:servlet是在服务器中执行的Java程序,像applet扩展了Web浏览器的功能一样,servlet扩展了Web服务器的功能。
servlet用于动态创建发送到客户端的内容,如servlet可以读取服务器数据库,然后整理为客户端想要的内容,发送到客户端;同时,servlet还提供了一些额外的优点,例如性能的提升。
可移植性:因为servlet是Java程序,他们被编译成字节码,由JVM执行,所以servlet可以用于不同服务器环境,只要服务器支持JVM和servlet容器。
1.6、Java关键特性
- 简单性:继承C/C++语法和面向对象特性;本来的设计目标就是让程序员高效学习和使用。
- 安全性:上面已讲述。
- 可移植性:上面已讲述。
- 面向对象:Java中的对象模型既简单又容易扩展,基本类型仍然是高性能的非对象类型。
- 健壮性:编译时检查代码;可预见的方式运行;自动管理内存回收;面向对象的异常处理。
- 多线程:Java提供了易用的多线程方法。
- 体系结构中立:Java提供了Java虚拟机(JVM),解决了程序对不同电脑,或者同一台电脑,不同状态,可能导致程序不能运行的问题。
- 解释执行和高性能:虽然Java使用了字节码和JVM,但是经过Java的优化,使用计时编译器,可使这种模式依然保持了如本机代码一样的高性能。
- 分布式:Java是针对Internet的分布式环境而设计的,所以对分布式有很多强大的支持。
- 动态性:支持运行时类型信息,可在运行时验证和解决对象访问的问题。
二、Java综述
2.1、面向对象编程
面向对象编程(Object-Oriented Programming,OOP)在Java中处于核心地位。
两种范式
所有计算机程序都包含2个元素:代码、数据
- 面向过程模型:将程序描述为一系列步骤,并用代码描述每一个步骤将如何作用于数据。
- 面向对象编程:将数据设计为一系列对象,并为这些对象精心设计接口,使用这些接口来进行组织代码。
抽象
使用抽象管理复杂性:非面向对象的编程方式,往往将对象分解为不同的部分进行处理;而面向对象的编程方式,则是把对象作为一个整体,去使用这个对象。
层次化分类:如一台车,下面又可以分为驾驶系统、制动系统等,每层又可以继续细分。当我们去使用这个车的对象时,则只需要了解各系统如何操作,而不需要知道这个系统里面由什么组成
对变化的支持:面向对象的方式犹如人类理解事物一样;往往事物会不断变化,例如某一部分进行改变;那么面向对象的程序也能支持这些变化,可以优美地废除或者替换旧系统中的一些部分。
2.2、OOP三原则
封装
封装:将代码以及其操作的数据绑定在一齐的机制。可以想象为一个保护性的盒子,只允许外部通过盒子提供的通道进行访问和操作,而不允许随意访问盒子里面的代码和数据。
封装的基础是类:类,是一些对象共享的一种结构和行为(数据和代码)。使用类创建对象,类似使用模具生产零件,这些对象也称为类的实例。
变量和方法:类里面可以定义变量和方法,称为成员变量(或实例变量)和成员方法(或方法)。
公有/私有:类的变量或方法可以设置为公有或私有,公有的表示外部用户可以知道的内容,私有的表示只有类的成员才能访问。
继承
继承:是一个对象获得另一个对象的属性的过程,继承支持了层次化分类的概念。
例如定义了一个哺乳类动物,又定义了下一层的一个类叫犬类,我们定义犬类继承了哺乳类,那么就代表犬类有哺乳类的所有属性和特征(变量和方法)。
多态
多态(多种形态):允许将一个接口用于一类通用动作。
多态是为了降低复杂性。例如一个对象有同一个行为,但是根据数据不同有不同做法的时候,使用相同的接口来指定这个行为,而自动根据对应的参数,会执行对应的实际代码。
封装、继承与多态协同工作
通过这3个原则,可以设计出健壮性强、方便维护、适应变化的系统级代码。
下面例子应在安装并配置好JDK 8下使用
2.3、Java基本语法
源文件
Java中源文件的正确名称叫编译单元(compilation unit),是一个包含一个或多个类定义(及其它内容)的文本文件。
要求源文件使用.java作为扩展名;文件内主类的名称与源文件名必须相同且大小一致。
代码例子Test.java
class Test{ public static void main(String args[]){ System.out.println("测试"); } }
编译成字节码
在命令行中,执行命令,javac编译器会创建名为Test.class文件,这个就是字节码文件
c:\>javac Test.java
运行程序
c:\>java Test
运行后,会显示“测试”,代表程序运行成功
main()方法
Java会找到程序多个类中的main()方法启动程序,注意大小写,没有找到会报错
声明变量
int num;
控制语句
if(num < 100)System.out.println("yes");
代码块
用{}表示逻辑单元
if(num<100){ System.out.println("yes1"); System.out.println("yes2"); }
词汇问题
- 空白符:可以用空格、制表符或换行符随意进行缩进。
- 标识符:用于命名类、变量以及方法等。可由大小写字母、数字、下划线、美元符号等字符组成的任意字符序列。不能以数字开头,不建议使用下划线。
- 字面值:表示常量,如100,85.2,'X',"Long Text"
- 注释:Java有3种注释
- 分隔符:
- 关键字:
三、数据类型、变量和数组
Java是强类型语言
在编译器完成编译前,会检查所有表达式和参数,确保类型是兼容的。
基本类型(也称简单类型)
Java定义了8中基本数据类型
整型:byte、short、int、long,表示有符号整数。
一般使用int,不够长度使用long
如果表达式中有byte、short类型,对表达式求值时他们会被提升为int类型
浮点型:float、double,表示带小数位数字。
float:32位单精度数值,某些处理器上单精度运算速度更快,占用空间是双精度的一半,但是数值很大或很小时会变得不准确;在需要小数部分,但是精度要求不高的时候,建议使用float类型。
double:64位双精度数值,在针对高速数学运算优化的现代处理器中,双精度数值速度更快;在需要多次迭代运算中保持精度,或者操作非常大的数值时,建议使用double类型。
字符型:char,表示字符集中的符号,比如字母和数字。
Java使用Unicode表示字符,Unicode是一个完全国际化的字符集,可以表示全部人类语言中的所有字符;Unicode是16位宽的,对比如ASCII字符集等更宽,效率有一定程度降低,这是Java为了全球化而做出的一点牺牲。
布尔型:boolean,表示true/false值的特殊类型。
深入分析字面值
整型字面值
int x=06;//0开头表示8进制 int x=0x2f;//0x或0X开头表示十六进制 int x=0b1010;//0b或0B开头表示二进制 int x=123_456_789; int x=123___456___789;//可在数值中嵌入一个或多个下划线,方便观看,编译时会去掉
浮点型字面值
float num = 2.44F;//默认浮点数为双精度,后面加F或f标明单精度 double num = 2.44D;//双精度可以这样表示,但是默认就是双精度,所以多余 double num = 3.13148;//标准计数法 double num = 2E-5;//科学计数法,可以用E或e后+正负号(正可省略)+十进制来表示 double num = 3_432_442.0_2_2;//一样可以用下划线分割
布尔型字面值
Java中只能用true和false,不能用1或0
字符型字面值
char t = 'a';//用单引号表示字符 char t = '\141';//八进制表示子母a char t = '\u0061';//十六进制表示Unicode字符a char t = '\n';//转义字符表示换行符
字符串字面值
字符串中同样可以使用转义字符以及八进制/十六进制表示方法
Java中字符串开头和结尾必须同一行,没有分行输入的功能
其它语言中字符串是字符的一个数组,而Java中是把字符串作为一个对象(所以不能用a==b来判断2个字符串,因为2个对象是不同的)
"Hello World"//使用双引号表示
变量
类型开始,可以连续定义多个变量,初始值可选
int a,b,c; int a=1,b,c=1;//连续定义3个int类型变量,其中2个加入初始值 int a=1;//使用常量初始化 doucle c=Math.sqrt(a*a+b*b);//使用公式初始化
变量作用域:
花括号{}表示代码块,变量的作用域在其所在的代码块中,离开代码块,变量会丢失他的值
类型转换和强制类型转换:
自动类型转换:当把一个类型的数据赋值给另外一个类型的变量时,如果满足条件,会发生自动类型转换。
条件:两种类型兼容;目标类型大于源类型。
满足上述条件,会发生扩宽转换。
例如把byte值赋值给int类型变量
强制类型转换
int a; byte b = (byte)a;//缩小转换 double c; int d = (int)c;//浮点数转为整型,会把尾数直接截掉,并且如果整数部分太大,会转换为整数部分除以目标类型范围的余数,例如转换为byte类型,就是除以256
自动类型提升
原则:
1、表达式中的byte、short、char值都会提升为int
2、如果其中一个操作数为long,整个表达式会提升为long
3、如果其中一个操作数为float,整个表达式会提升为float
4、如果其中一个操作数为double,整个表达式会提升为double
byte b=50; b=b*2;//错误,因为进行了自动提升,b在表达式中变成了int类型,所以b*2的结果是int类型,赋值给更小的类型byte会报错 b=(byte)b*2;//正确
数组
int array1[];//创建数组变量,只是变量,未分配内存 array1 = new int[12];//元素自动初始化为0(数值类型)、false、null(引用类型) array1[3]=28;//使用[索引]进行访问 System.out.println(array1[3]); int array1[] = new int[12];//结合起来的写法 int array1[] = {1,2,3,4,5};//初始化式声明 //多维数组 int twoD[][] = new int[4][5];//多维数组 //不同长度的多维数组 int twoD[][] = new int[4][]; twoD[0] = new int[1]; twoD[1] = new int[2]; twoD[2] = new int[3]; twoD[3] = new int[4]; //初始化写法 double m[][] = { { 1, 2 }, { 3, 4 }, }; //另外一种写法 int a1[] = new int[3]; int[] a1 = new int[3]; char twod1[][] = new char[3][4]; char[][] towd1 = new char[3][4]; int[] a1,a2,a3; int a1[],a2[],a3[];
关于字符串的说明
在Java中,字符串类型是String
但是String不是一个基本类型,而是一个对象,为他提供了一些面向对象的特性,所以放在后面探讨
关于指针
因为Java使用了JVM,限定了Java程序的执行边界,Java程序不允许使用指针。
因为指针可以访问内存中任何地址,破坏了JVM和宿主计算机之间的防火墙,和Java的设计理念不同,所以Java不允许使用。
五、运算符
算数运算符
基本算数运算符+ - * /
注意整数类型使用除法,结果会截掉小数部分
求模运算符%
求余数,可以用于整数或小数
算数与赋值符合运算符+= -= *= /=
相当于运算后再赋值
自增与自减运算符++ --
y=x++;//先将x原值赋值给y,然后x加1 y=++x;//x先加1,然后将x加1后的值赋值给y
位运算符
位操作较少用,这里略过
关系运算符
布尔逻辑运算符
赋值运算符
a=1;//使用"="符号赋值 a=b=c=100;//将3个变量都赋值为100
“?”运算符
a=b==0?true:false;//用于简写的判断
优先级
使用圆括号
圆括号可以提升运算的优先级,并且能使阅读更加明确易懂,又不影响性能。
a=(b==0?true:false);
五、控制语句
选择语句
if语句
if(a=1){ b=1; } else if(a=2){ b=2; } else{ b=3; }
switch语句
switch(a){ case 1: b=1; break; case 2: b=2; break; case 3: case 4: b=3.5; break; default: b=100; break; }
迭代语句
while
while(n>0){ System.out.println(n); n--; } i=100; j=200; while(++i<--j);//用;表示空语句
do-while
循环体至少执行一次
do{ System.out.println(n); n--; }while(n>0)
for
int n; for(n=10;n>0;n--){ System.out.println(n); } for(int n=10;n>0;n--){ System.out.println(n); } for(a=1,b=4;a<b;a++,b--){ System.out.println(a); System.out.println(b); }
for-each
for-each风格在不同的编程语言中都有实现,是一种很受程序员喜欢的风格
在Java中,是通过for语句实现的,可以对集合类变量进行循环
int nums[] = {1,2,3,4,5} int sum = 0; for(int x:nums){ sum+=x; }
跳转语句
break
用于终止当前所在的最近一个循环
for(int n=10;n>0;n--){ for(int m=10;m>0;m--){ System.out.println(n); System.out.println(m); if(m==5)break;//跳出m的循环 } }
break label
可以使用标签,终止语句所在的所有嵌套循环中的其中一个
outer:for(int n=10;n>0;n--){ for(int m=10;m>0;m--){ System.out.println(n); System.out.println(m); if(m==5)break outer;//跳出n的循环 } }
continue
提前终止循环的一次迭代
在本次迭代中,在continue后面的代码将不会执行
for(int n=10;n>0;n--){ for(int m=10;m>0;m--){ System.out.println(n); System.out.println(m); if(m>5)continue;//跳出m的一次迭代 } }
continue label
可以使用标签,终止语句所在的所有嵌套迭代中的其中一个
outer:for(int n=10;n>0;n--){ for(int m=10;m>0;m--){ System.out.println(n); System.out.println(m); if(m>5)continue outer;//跳出n的一次迭代 } }
return
显式地从方法返回,把程序的执行控制返回到发放的调用者处
六、类
类的形式
//类的基本形式 class Box{ double width;//实例变量 double height;//实例变量 double depth;//实例变量 //无参构造函数,new Box()实际是调用了类的构造函数 Box(){ width = 10;height= 10;depth= 10; } //带参构造函数 Box(double width,double height,double depth){ this.width = width;//使用this引用实例对象 this.height= height; this.depth= depth; } double volume()//方法,返回值、参数都是可选的 { return width * height * depth; } } //创建类的实例 Box mybox1;//声明一个Box类型变量 mybox1 = new Box();//创建一个实际对象,并将引用到上面的变量中 Box mybox1 = new Box();//简写方式 mybox.width = 100;//为实例变量赋值 //变量引用的概念 Box b1 = new Box(); Box b2 = b1; b1 = null; //此时b2不是null,而是引用第一行创建的对象 //因为第二行代码是把b1引用的对象也给b2引用 //第三行代码,只是将b1的引用去除,并不影响b2和对象的引用
垃圾回收
当对象用完后,应该把对象在内存中清掉,释放内存。
这方面Java会自动管理,大多数情况不需要人为编程。
finalize()方法
为类添加这个方法,可以在类的对象被Java回收的时候执行方法内的代码。
protected void finalize() { //记录回收时间等自定义方法 }
七、方法和类深入分析
重载方法
Java中支持多态性的方式之一
支持多个不同参数的方法共享方法名称,通过传入的参数来判断调用哪个具体方法
class OverloadDemo{ void test(){ System.out.println("test1"); } void test(int a){ System.out.println("test2"); } void test(int a,int b){ System.out.println("test3"); } } class Overload{ public static void main(String args[]){ OverLoadDemo ob = new OverloadDemo(); ob.test();//调用无参的方法 ob.test(10);//调用1个int参数的方法 ob.test(10,20);//调用2个int参数的方法 } }
对象可以作为参数
可以将对象作为参数传递给方法
参数的传递
- 传递的形式分为:值传递,引用传递
- 基本类型的参数,使用的是值传递
- 其它类型的参数,使用的是引用传递
- 参数使用值传递,相当于创建该值的一个副本,修改该参数,不会影响原值
- 参数使用引用传递,相当于把参数指向和原值所指向的同一个对象,所以修改参数值的时候,实际上是在修改其所指向的对象,所以原值也会一齐变化。
对象可以作为返回值
可以讲对象作为参数的返回值返回
递归算法
方法可以调用自己实现递归算法
访问控制
- 为了使类可以更好地封装,我们系统可以把类当作一个黑盒子,只对外暴露可以访问的成员
- Java提供了访问修饰符来实现这种封装
- public:成员被public修饰,可以被任何代码访问,无修饰符时默认为public
- private:成员被private修饰,该成员只能被所属类的其它成员访问
- protected:涉及继承才会使用下章介绍
static
用于声明类本身的成员,而不是对象的成员
限制:
1、只能直接调用其它静态方法
2、只能直接访问静态数据
3、不能引用this或super关键字
class UseStatic ( static int a * 3;//静态变量 static int b;//静态变量 static void meth(int x) {//静态方法 System.out.println("x:" + x); System.out.println("a:" + a); System.out.println("b:" + b); static (//静态代码块,会在加载此类时运行,整个程序生命周期仅运行一次 System.out.println("Static block initialized"); b = a * 4; } public static void main (String args[]) { meth(42);//调用静态方法 } }
静态方法、静态变量也可以看作是Java中的全局方法和全局变量
final
可以用于声明变量或者方法
//声明变量,可以将变量定义成常量 //代表整个程序生命周期,他的值不能改变 //约定写法为全大写 final int FILE_NEW = 1;
final声明方法与声明变量的用法有本质区别,下章讨论final声明方法的用法
重新审视数组
数组的length成员
只反映最初设计时数组所能包含的元素数量
int a1[] = new int[10]; System.out.println("length is " + a1.length);
嵌套类和内部类
嵌套类:在一个类的内部再定义一个类,这个再定义的类称为嵌套类;外部的类称为包含类
限制:
- 1、嵌套类不能独立于包含类存在
- 2、嵌套类可以访问包含类的成员(含私有成员)
- 3、包含类不能访问嵌套类的成员
- 4、嵌套类可以作为包含类的成员声明;也可以在代码块中声明
嵌套类按是否静态可以分为2种:静态的、非静态的
因为静态嵌套类只能访问包含类的静态成员,只能通过对象访问包含类的非静态成员,所以很少这样使用
非静态的嵌套类称为【内部类】
class outer int outer_x = 100; void test(){ Inner inner = new Inner(); inner.display(); } //内部类 class Inner{ void display{ System.out.println("display: outer_x ="+ outer_x); } } //代码块中定义内部类 void test2(){ for(int i=0;i<10;i++){ class Inner2{ void display(){ System.out.println("display:outer_x = " + outer_x); } } Inner inner = new Inner(); inner.display(); } } } class InnerClassDemo{ public static void main(String args[])( Outer outer new Outer(); outer test(); } }
String类介绍
Java中每个字符串,包括变量和常量,都是一个String对象
//多种方式构造字符串 String myString = "this is a test"; //引用 System.out.println(mySting); //Java为String对象定义了“+”运算符 String myString1 = "I" + " Love You"; //常用方法 //equals方法判断相等 if(str1.equals(str2)) System.out.println("equals"); //length方法获取长度 if(str1.length()==0) System.out.println("空字符串"); //charAt方法获取字符 System.out.println("equals".charAt(0)); //常见错误,2个相同字符串是不相等的,因为是2个对象 if("yes"=="yes") System.out.println("Yes");
varargs:可变长度参数
限制:
1、一个方法最多只有一个可变长度参数
2、可变长度参数必须放到最后
class VarArgs{ static void vaTest(int ... v){ for(int x : v){ System.out.println(x); } } }
八、继承
一个类可以继承另外一个类,被继承的类叫超类,继承别人的类叫子类
extends
继承:
- 1、子类包含了超类的所有非私有成员
- 2、一个类只能有一个超类
class A{ int i = 1; void showi(){ System.out.println("i = "+i); } } class B extends A{ int j = 2; void showj(){ System.out.println("j = "+i); } }
超类变量引用子类对象
变量的成员决定于变量类型,而不是对象类型
class test(){ void test(){ A a = new B(); } }
super关键字
用法1:子类构造函数中调用超类构造函数
class B extends A{ B(){ super();//调用超类A的构造函数 } }
用法2:引用超类被子类隐藏的成员
class A{ public string name = "a"; void showA(){ System.out.println(name); } } class B extends A{ public string name = "b";//同名成员,隐藏了超类的name成员 void showB(){ System.out.println(name);//b System.out.println(super.name);//a } }
多级继承层次
类可以无限级继承
构造函数的执行顺序
从最上层超类的构造函数到最下层子类的构造函数执行
重写(隐藏)
当子类的成员与超类成员名称相同且参数类型相同,会把超类成员重写(隐藏),但是可以通过super调用超类的成员
动态方法调度
Java通过对象的类型选择调用的成员,而不是变量的类型
class A{ void call(){ System.out.println("a"); } } class B{ void call(){ System.out.println("b"); } } class C{ void call(){ System.out.println("c"); } } class Test{ public static void main(String args[]){ A a = new A(); A b = new B(); A c = new C(); a.call();//输出a b.call();//输出b c.call();//输出c } }
抽象类
使用abstract类型修饰符修饰的类
- 1、抽象类不存在对象,不能用new实例化。
- 2、不能声明抽象的构造函数。
- 3、不能声明抽象的静态方法。
- 4、子类要么实现所有抽象方法,要么自己声明为抽象的。
- 5、应用动态方法调用,抽象类可以作为变量引用,引用其子类所创建的对象,调用其抽象方法时,实际是调用对应对象所实现的方法。
继承中使用final关键字
1、可使用final关键字阻止重写
2、可使用final关键字修饰类,防止类继承
Object类
Object类是所有其它类的超类
Object类型的引用变量可以应用其它所有类
Object类型可以引用数组,因为数组也是作为类来实现的
所以所有对象都可以使用Object的方法
九、包和接口
包
如果所有类都放一起开发,方便易记的类名很快用完,也很容易出现协作时命名冲突的问题。
包是Java提供的一种命名机制,同时也是一种可见性控制机制。
包定义
Java使用文件系统目录存储包,包名必须和目录路径一致,包括大小写
//定义包名,写在Java源文件中的第一条语句 package pkg; //层次化 package java.awt.img;
包查找与CLASSPATH
Java运行时怎么找到这些包呢?
package MyPack;
3种方式:
- 1、Java程序运行目录下有MyPack目录
- 2、CLASSPATH环境变量下有Mypack目录
- 3、运行程序时通过-classpath选项指明包路径运行
类成员的访问保护
而对【非嵌套类】只有2种访问级别
- 默认级别:只能被同包中的其它类访问。
- 公有级别:任何其它代码都可访问;要求该类是文件中唯一一个公有类,切文件名要和类名完全一致。
导入包
import java.util.Data; import java.io.*;//导入整个包
Java隐式地为所有代码都导入了java.lang包,相当于每个源文件开头都添加了
import java.lang.*;
import语句是可选的,也可以选择使用完全限定名来使用类
class MyDate extends java.util.Date{ }
接口
使用interfere创建接口,可用于指定类必须实现哪些功能
定义接口
interface Callback{ void callback(int param); }
- 1、如果接口没有修饰符,采用默认访问级别,只有声明接口的包中的其它成员才能访问接口。
- 2、如果接口声明为public,那么所有代码都可以使用接口,且接口是当前文件中的唯一公有接口,文件名必须和接口同名。
- 3、JDK 8及以后的版本中,可以为接口创建默认实现。
- 4、接口中可以声明变量,隐式标识为final和static,所有实现接口的类都不能修改他们,还必须初始化他们。
- 5、所有方法和变量都隐式声明为public。
实现接口
class Client implements Callback{ public void callback(int p){ System.out.println(p); } }
- 1、实现接口方法时,必须声明为public
- 2、使用接口调用方法,是在运行时动态查询到方法并执行,和常规方法调用相比占用更多资源,对性能要求苛刻的代码中谨慎使用。
class TestIface{ public static void main(String args[]){ //使用接口调用方法 Callback c = new Client(); //调用的方法是来自对象,而不是类型变量 c.callback(42); } }
- 部分实现:
- 如果类实现一个接口,但是没有实现所有接口方法,那么必须将这个类声明为abstract
abstract class Incomplete implements Callback{ void show(){ System.out.println("Good"); } }
接口中的变量
创建一个包含变量的接口,实现这个接口的类相当于把这些这些变量当作常量导入到类的命名控件下。
interface SharedConstants{ int NO = 0; int YES = 1; int NULL = 2; }
这种用法有争议,仅作介绍
接口的继承
接口可以继承,实现一个子接口,必须实现这个接口的继承链上所有接口的所有方法。
默认接口方法default
【JDK8】才出现
默认方法也称扩展方法
可以为接口的方法提供默认实现
动机:
- 1、当接口需要增加一个方法,那么实现接口的类必须实现这个方法,导致增加方法会破坏原有代码;
- 2、提供可选方法。例如有一个接口有一个方法remove,有2个类都实现了这个接口,其中一个类希望他是不可删除的,原来方法就需要写一个remove的空实现。现在有了默认方法,可以在接口默认空实现,或抛出异常,这样类就不用去做空实现了。
public interface MyIF{ //使用default关键字 default String getString(){ return "Default"; } }
优点:
- 1、优雅地随时间演进接口
- 2、提供可选功能,且类不必在不需要该功能时提供占位符实现。
不同接口的同名方法
一个类实现了2个接口,2个接口都有默认方法reset,那么使用方法的顺序如下:
- 1、类有实现,优先类的实现
- 2、类没有重写实现,则报错
如果是一个接口继承另外一个接口,且有相同默认方法,则继承接口的版本更高优先级
//显式调用被继承接口中的默认实现 SuperInterface.super.reset();
在接口中使用静态方法
【JDK 8】才出现
可以在接口中直接定义静态方法,然后直接调用
public interface MyIF{ static int getDefaultNumber(){ return 0; } } //调用 int a = MyIF.getDefaultNumber();
注意:
1、实现接口的类或子接口不会继承接口中的静态方法。
十、异常处理
异常处理
处理程序运行时的非正常状态,即运行时错误。
Java会在出现异常时,抛出一个异常对象。
异常类型
所有异常对象都是Throwable的子类
Exception:用于用户程序应当捕获的异常,或者自定义的异常
RuntimeException:程序自动定义
Error:一般指运行时环境出现的错误
未捕获的异常
如果出现异常且未捕获,程序会终止,并输出堆栈信息,可以根据堆栈信息帮助查找问题。
使用try和catch
程序处理异常,会终止程序。
我们自己处理异常,一可以修复错误,二可以避免终止程序。
try{ do(); }catch(Exception1 e){ System.out.println(e);//输出异常信息 catchDo(); }catch(Exception2 e){//可以多个catch语句 catchDo(); }
- 1、在try{}的代码中,如果出现异常,则在代码块中异常代码后面的代码会跳过,直接转到catch里面
- 2、可以写多个catch语句
- 3、如果嵌套了try,如果内层try没有处理异常,异常会抛出到外层的try里面去。
throw
显式抛出异常
throw ThrowableInstance;
这个异常必须是Throwable或其子类类型的对象。
throws
除了Error和RuntimeException及其子类类型的异常外,如果方法有其它类型的异常,则需要为方法标明这些异常。
目的是让方法的调用者知道此方法所有异常以做好准备措施。
public void do() throws MyException{ throw new MyException("test"); }
finally
可选加载try{}catch{}后,无论是否抛出异常,都会在try{}catch{}后执行此代码
try{ do(); }catch(Exception1 e){ System.out.println(e);//输出异常信息 catchDo(); }finally{ finallyDo(); }
Java内置异常
未经检查(编译器不检查这些异常是否使用throws标明)的RumtimeException子类
需要检查的Java定义的异常类
创建自己的异常子类
1、定义Exception的子类即可
2、Exception没有为自己定义任何方法,但是继承了Throwable提供的方法
3、Exception有4个公有构造函数,其中2个支持链式异常(下面介绍),另外2个如下
Exception()
Exception(String msg)
链式异常
当一个异常的抛出,是因为另外一个异常导致的,希望能把这个异常链显式出来,所以提出了链式异常。
Throwable中有2个构造函数和2个方法
//2个构造函数 Throwable(Throwable causeExc) Throwable(String msg,Throwable causeExc) //2个方法 Throwable getCuase()//返回引发当前异常的异常 Throwable initCause(Throwable causeExc)//创建异常后将背后异常和创建的异常联系在一起,只能调用一次
通常initCause是用于不支持前面2个构造函数的遗留异常类设置原因
JDK7开始添加的3个异常特性
1、带资源的try语句
当try语句中使用资源时,不再需要时资源能自动释放(如文件),后面解释
2、多重捕获
允许同一个catch子句同时捕获多个异常
catch(Exception1 | Exception2 e)
每个多重捕获参数都被隐式声明为final,不能重新赋值
3、最后重新抛出/更精确地重新抛出(不是很理解,暂时不理)
程序会对重新抛出的异常类型进行限制,只能重新抛出满足以下条件的经检查的异常:由关联的try代码块抛出,没有被前面的catch子句处理过,并且是参数的子类型或超类型。
同样catch参数隐式声明为final,不能重新赋值
十一、多线程编程
多任务:
- 基于进程:运行多个程序
- 基于线程:单个程序同时运行多个任务
Java线程模型:
- 单线程模型:轮询时间循环:轮询一个事件队列,当执行一个事件并返回之前,不能执行其他工作
- 多线程模型:并发执行的线程共享CPU,每个线程得到一篇CPU时钟周期。
线程的状态:
- 运行:只要获得CPU时间就准备运行
- 挂起:临时停止线程活动
- 恢复:让挂起的线程变成运行状态
- 阻塞:等待资源时,线程处于阻塞状态
- 终止:一旦终止不能恢复
线程优先级:
一些整数,用于决定何时从一个运行的线程切换到下一个线程,称为上下文切换
意思应该是这样,根据CPU时钟周期,空余时间可以用来运行线程;当线程过多,而有线程阻塞时,什么时候切换到下一个进程呢:
- 1、运行的线程资源放弃控制:显式放弃控制权、休眠或I/O之前阻塞,则检查所有线程,优先级最高的线程会获得CPU资源
- 2、高优先级线程取代低优先级线程:只要高优先级线程希望运行,就会取代低优先级线程,称为抢占式多任务处理
- 3、相同优先级的2个线程竞争CPU,Windows系统下会循环获取CPU资源,而其他系统除非正在运行的线程放弃控制权,否则其他线程不能运行(由于不同系统不同,可能引起移植性问题)
同步:
- 某个线程位于一个同步方法中,则其他线程不能调用对象的其它同步方法
消息传递:
- Java的消息传递系统允许某个线程进入对象的同步方法,然后进行等待,然后其它线程显式通知这个线程退出为止
Thread类和Runable接口:
Java的多线程系统基于Thread类、Thead类的方法以及伴随接口Runable而构建
主线程:
Java启动就会运行一个线程,称为主线程。
很重要:
- 1、其它子线程都是由主线程产生
- 2、通常主线程必须是最后才结束执行的线程,因为他要执行各种关闭动作。
调用主线程
- Thread t = Thread.currentThread();//获取主线程
- t.setName("");//修改进程名字
创建线程
2种方法:
- 1、实现Runable接口
- 2、扩展Thread类本身
实现Runable接口
- Runnable接口抽象了一个可执行代码单元,可以依托任何实现了Runnable接口的对象来创建线程。
- 要实现Runnable接口,只需要实现run()方法,然后run()方法可以调用其它方法,使用其它类,声明变量,像main线程那样,唯一的区别就是入口点不同,run()方法为并发线程执行建立了入口点,run()方法返回时,这个线程也将结束。
- 然后创建实现了Runnable接口的类后,可以实例化一个Thread类型的对象,然后调用线程start()方法才真正线程。
扩展Thread类
同样的,扩展Thread类,重写run()方法,然后调用start()方法类开始新线程
为什么有2种方式,哪种好?
差别不大,一个是在于实现接口,一个是扩展重写类。
可以使用接口,这样可以使这个类可以扩展其它类。
创建多个线程
isAlive()和join()方法
比如主线程希望等所有子线程执行完成再退出
isAlive()判断线程是否仍在运行
但是一般通过join方法来等待线程结束
再某个线程调用其它线程的join方法,会等待该线程终止。
也可以配置最长等待时间。
线程优先级
1、影响线程得到CPU时间的因素:优先级、系统多任务的方式等
在抢占式多任务环境下,高优先级的线程会替代掉低优先级的线程
在非抢占式多任务环境下,在运行中线程正常运行情况下(未休眠,未放弃控制权等),其它线程只有在运行中线程阻塞状态,如I/O等待等,导致线程挂起,才有机会运行
所以为了平滑多个线程的执行,最好经常释放控制权,使其它线程有机会运行。
调用setPriority方法设置线程优先级,范围1~10,默认值5
使用Java实现可预测,跨平台行为的最安全方法是使用资源放弃CPU控制权的线程。
同步
当多个线程需要访问共享资源时,他们需要用某种方法确保每次只有1个线程使用资源。
实现这个目的的过程称为同步。
同步使用监视器的概念实现,监视器作为互斥锁,一个时刻只有一个线程可以进入监视器,而其他试图进入监视器的线程会被挂起,知道上一个线程退出监视器。
2种方法
使用同步方法:
Java中,所有对象都有和自身关联的隐式监视器。要进入对象的监视器,只需要调用synchronized关键字修饰过的方法。
当调用对象的一个同步方法,线程就会进入对象的监视器;其它线程想要调用这个或其他同步方法,由于进入不了监视器,所以会被挂起等待;知道监视器种的线程退出,把对象的控制权交给下一个等待线程。
使用同步语句:
线程间通信:
十三、I/O、applet以及其他主题
13.1、I/O
I/O:输入输出
流:
- Java程序通过流执行I/O。
- 流是一种抽象,要么产生信息,要么使用信息。
- 流通过Java的I/O系统连接到物理设备。
- 所有流的行为方式都是相同的,即使他们链接的物理设备不同。
- 因此,可以将不同类型的输入(磁盘文件、键盘或网络socket),抽象为输入流;输出流也可以引用控制台、磁盘文件或网络链接。
2种类型的流:
- 字节流:负责处理字节的输入和输出。
- 字符流:使用Unicode编码,为处理字符的输入和输出提供更方便的方法,某些情况下比字节流高效。
一般原则:在合适的情况下,应该更新代码,使用字符流替代字节流。
不过:在底层,所有I/O最终还是面向字节的。
字节流类
2个类层次:
- 顶层:2个抽象类InputStream和OutputStream
- 子类:用于处理不同设备的具体子类
InputStream和OutputStream最重要2个方法是read()和write(),子类需要实现这2个方法
字符流类
2个类层次:
- 顶层:Reader、Writer,这2个抽象类处理Unicode字符流。
- 子类:用于处理不同设备的具体子类
Reader和Writer最重要2个方法是read()和write(),子类需要实现这2个方法
预定义流:
- Java预定义了3个流变量,在java.lang包的System类中,分别为in、out、err,并且声明为pubilc、static、final,所以程序任何地方都可以使用他们。
- System.out引用标准输出流,默认控制台。
- SYstem.in引用标准的输入流,默认键盘
- System.err引用标准的错误留,看默认控制台。
- 但是可以重定向到任何兼容I/O设备
- System.in是InputStream类型对象;out和err是PrintStream类型对象;都是字节流。
import java.io.*; public class test { public static void main(String[] args) throws IOException { // 使用InputStreamReader将字节输入流InputStream转换成字符输入流Reader // 使用BufferedReader读取缓冲的输入流 // 读取字符 { char c; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("输入字符,q退出"); do { c = (char) br.read();// 读取字符 System.out.println(c); } while (c != 'q'); } // 读取字符串 { String str; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("输入字符串,stop退出"); do { str = br.readLine();// 读取字符串 System.out.println(str); } while (!str.equals("stop")); } // 输出 { // System.out类型是PrintStream类,派生自OutputStream,提供了print和println方法,同时也实现了write System.out.print("A"); System.out.print("\n"); System.out.println("B"); System.out.write('C'); System.out.write('\n'); } // 推荐使用字符流的输出方法,更容易国际化 // 使用PrintWriter { PrintWriter pw = new PrintWriter(System.out, true); pw.println("long text"); int i = 1; pw.println(i); double d = 4.5e-7; pw.println(d);// 4.5E-7 double d2 = 0.000000045; pw.println(d2);// 4.5E-8 } } }
文件I/O(暂略)
主要是FileInputStream、FileOutputStream、AutoCloseable接口
13.2、applet(暂略)
一种Java程序,可以在Internet服务器*问,传输,自动安装并作为Web文件的一部分运行的一类小程序。
13.3、几个主题
transient修饰符:
- 用来修饰类的变量,那么该类的对象写入永久存储区域时,不会保存该属性的内容
volatile修饰符:
- 用来修饰类的变量,标明实例变量可以被程序的其它部分随意修改。
- 例如在多线程下,正常一个变量在一个线程中是有一个副本,然后通过同步方法来工作,但是这种有效效率并不高。
- 可以用这个修饰类变量,使其主副本和其它私有版本保持一致,各个线程访问主变量的顺序与所有私有副本相同。
instanceof:
- 判断对象是否该类型或是否可以转换为该类型。
strictfp:
- Java2对浮点计算模型放宽了,不需要截断特定的中间值,可以防止溢出。
- 而使用strictfp修饰类、方法、接口,可以使用旧版本Java的浮点计算方式。
native:
- 本地方法,可以从Java内部调用非Java语言编写的子例程。
assert:
- 断言,通过则不执行,不通过则抛出异常。2种形式。运行时默认禁用断言,可以运行时给予参数启用或禁用断言。
import static:
- 静态导入,导入静态成员,节省代码。
this():
- 调用重载的构造函数,避免在重载的构造函数中代码重复。使用this(),可以减少重复代码,减少了对象代码,提高了对象的加载时间;但是this()的调用和返回又会降低代码执行速度。
- 如果类只创建少量对象,或调用this()构造函数很少调用,那么没有问题;但是如果需要创建数千个对象或经常调用this函数,这个必须衡量加载时间和执行速度。
- 2个限制应该牢记:调用this()时不能使用当前类任何实例变量;同一个构造函数不能同时使用super()和this(),因为她们必须是构造函数中的第一条语句
紧凑API配置文件:
- conpact1、compact2、compact3;2包含1,3包含2;可配置每个配置文件中包含的库,减少库的大小,减小加载时间。
十四、泛型
目的:为了解决不同类型的对象需要运行同一算法的需求
泛型:参数化类型,可以使用参数指定所操作数据的类型
以前:要实现这个需求,则使用Object来引用,因为Object是其它所有类的超类,但是不能以类型安全的方式进行工作。
现在:泛型提供了类型安全性,也避免了显式强制类型转换,更安全、容易地重用代码。
泛型类:
import org.junit.Test; public class test { class Gen<T> { T ob; Gen(T o) { ob = o; } T getob() { return ob; } void showType() { System.out.println("Type of T is" + ob.getClass().getName()); } } @Test public void test() { Gen<Integer> iOb; iOb = new Gen<Integer>(88); iOb.showType(); int v = iOb.getob(); System.out.println("value:" + v); System.out.println(); Gen<String> strOb; strOb = new Gen<String>("Test"); strOb.showType(); String str = strOb.getob(); System.out.println("value:" + str); } }
泛型只能使用引用类型,不能使用基本类型
多个参数的泛型类,差不多,Gen<T,V>
有界类型:
限制参数类型的范围,使用extends和&连接符,可以使用类和接口限定
class Gen<T extends MyClass & MyInterface>{ }
通配符参数:
当其他类需要引用泛型类,可以不写参数,或者使用通配符参数;通配符参数还可以使用有界通配符(包括extends上界和super下界)
public class test { class Gen<T> { T ob; Gen(T o) {ob = o;} T getob() {return ob;} void showType() { System.out.println("Type of T is" + ob.getClass().getName()); } } class Gen1 { public void m1(Gen g){// 直接引用类名 g.showType(); } } class Gen2 { public void m1(Gen<? extends Integer> g){// 要求泛型的类型是Integer或Integer的子类 g.showType(); } } class Gen3 { public void m1(Gen<? super Integer> g){// 要求泛型的类型是Integer或Integer的超类 g.showType(); } } @Test public void test() { Gen<Integer> iOb; iOb = new Gen<Integer>(88); iOb.showType(); int v = iOb.getob(); new Gen1().m1(iOb); new Gen2().m1(iOb); new Gen3().m1(iOb); } }
泛型方法:
public class test { // 泛型方法 static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y) { for (int i = 0; i < y.length; i++) if (x.equals(y[i])) return true; return false; } // 泛型构造函数 public class Gen { <T extends Number> Gen(T num) { System.out.println(num); } } @Test public void test() { Integer nums[] = {1, 2, 3, 4, 5}; if (isIn(2, nums))// 调用泛型方法,不需要指定类型,Java能自动推断 System.out.println("2 is in nums"); new Gen(88); new Gen(12.6); } }
泛型接口:
public class test { interface MinMax<T extends Comparable<T>> {// 泛型接口 T min(); T max(); } class MyClass2 implements MinMax<Interger>{}// 非泛型实现方式 class MyClass<T extends Comparable<T>> implements MinMax<T> {// 泛型实现方式 T[] vals; MyClass(T[] o) { vals = o; } public T min() { T v = vals[0]; for (int i = 1; i < vals.length; i++) if (vals[i].compareTo(v) < 0) v = vals[i]; return v; } public T max() { T v = vals[0]; for (int i = 1; i < vals.length; i++) if (vals[i].compareTo(v) > 0) v = vals[i]; return v; } } @Test public void test() { Integer inums[] = {3, 6, 2, 8, 5}; Character chs[] = {'b', 'r', 'a', 'z'}; MyClass<Integer> iob = new MyClass<Integer>(inums); MyClass<Character> cob = new MyClass<Character>(chs); System.out.println(iob.min());// 2 System.out.println(iob.max());// 8 System.out.println(cob.min());// a System.out.println(cob.max());// z } }
原始类型与遗留代码:
// 可以使用不带参数类型的原始类型,实际上是创建了Gen<Object>,只为了兼容旧代码,新代码不能这样做 Get raw = new Gen(new Double(98.6));
泛型类层次化:
public class test { class Gen<T> { T ob; Gen(T o) { ob = o; } T getob() { return ob; } } class Gen2<T> extends Gen<T> {// 扩展了泛型类Gen,并吧T传递给Gen Gen2(T o) { super(o); } } class Gen3<T, V> extends Gen<T> {// 添加自己另外的参数化类型 V ob2; Gen3(T o, V o2) { super(o); ob2 = o2; } V getob2() { return ob2; } } class Gen4<T> extends Object {// 超类可以不是泛型类 } }
泛型的类型推断:
MyClass<Integer, String> mcOb = new MyClass<Integer, String>(98,"String"); MyClass<Integer, String> mcOb = new MyClass<>(98,"String");// 创建实例时推断 if(mcOb.isSame(new MyClass<>(1,"String"))) System.out.println("same");// 传递参数时推断
擦除:
Java通过擦除这个技术来实现泛型。
擦除工作原理:编译Java代码时,所有泛型信息被擦除;使用它们的界定类型替换类型参数,如果没有界定类型则使用Object,然后使用适当的类型转换。所以泛型只存在于源代码,运行时没有泛型。
详细深入的内容,这里不讨论。
模糊性错误:
class MyClass<T,V>{// 当T和V同类型时,就会引发模糊性错误,2个set重载编程同样类型 T ob1; V ob2; void set(T o){ob1 = o;} void set(V o){ob2 = o;} }
泛型的使用限制:
class Gen<T>{ T ob; T vals[]; Gen(){ ob = new T();// 不能实例化类型参数 vals= new T[10];// 不能实例化类型为参数类型的数组 } static T ob;// 错误,不能使用参数类型创建静态成员 static T getob(){..}// 错误,不能使用参数类型创建静态成员 } Gen<Integer> gens[] = new Gen<Integer>[10];// 错误,不能创建特定类型的泛型引用数组 Gen<?> gens[] = new Gen<?>[10];// 正确 // 另外泛型类不能扩展Throwable,代表不能创建泛型异常类
十五、lambda表达式
JDK8新增功能
增加了新的语法元素
使API库增加了新的功能,包括多核环境的并行处理功能(特别是for-each操作)变得容易,以及支持对数据执行管道操作的新的流API
催生了新的Java功能,包括默认方法以及方法引用
2个关键结构:
lambda表达式:
- 本质上是一个匿名方法。这个方法不是用来独立执行的,而是用于实现函数式接口定义的另一个方法。
- 实现函数式接口,一种方法是平常一样,创建一个类,然后实现方法;而lambda是另一种方法,使用匿名方法的方式实现。
- lambda表达式会导致产生一个匿名类。
- lambda表达式也经常成为闭包。
函数式接口:
- 仅包含一个抽象方法的接口,通常表示单个动作。
- 函数式接口定义了lambda表达式的目标类型。
- 特别注意:lambda表达式只能用于其目标类型已被指定的上下文中。
- 另外,函数式接口有时候被称为SAM类型,意思是单抽象方法(Single Abstract Method)
应用实例:
()->123.45 相当于 double myMeth(){return 123.45;} ()->Math.randow()*100 ()->(n%2)==0 // 函数式接口,只有一个抽象方法的接口 // JDK8之前,所有接口方法隐式都是抽象方法 // 但是JDK8之后,可以接口声明默认方法 // 所有JDK8及以后,没有默认实现的接口方法,才是隐式地是抽象方法 interface MyNumber{ double getValue(); } MyNumber myNum;// 定义一个函数式接口 myNum = ()->123.45;// 赋值lambda表达式,相当于创建了一个匿名类,实现了MyNumber接口,该方法的内容等于等号后面部分。 System.out.println(myNum.getValue())// 有具体实现的接口,可以调用方法 // 带参数的lambda表达式 (n)->(n%2)==0// 参数类型可以根据接口来推线,可以不指定参数类型 (int n)->(n%2)==0// 显式指定参数类型 // 多个参数 NumericTest2 isFactor = (n,d)->(n%d)==0; // 使用代码块的lambda表达式 NumericFunc factorial = (n) -> { int result = 1; for(int i=1; i <= n; i++) result = i * result; return result; }; // 泛型函数式接口 // lambda表达式不能指定类型参数,但是函数式接口可以是泛型 interface SomeFunc<T>{ T func(T t); } SomeFunc<String> func1 = (str) -> {... return str;}; func1.func("func1"); SomeFunc<Integer> func2 = (val) -> {... return val;}; func2.func(1); // 作为参数传递lambda表达式 interface StringFunc{ String func(String n); } public String stringOp(StringFunc,String s) { return StringFunc.func(s); } StringFunc func = (n)->{return n+"!";}; String result = stringOp(func,"no"); // 如果lambda表达式抛出异常,接口的throws子句应该匹配该异常 interface StringFunc{ String func(String n) throws NullException; } StringFunc func = (n)->{ if(n.equals("")) throw new NullException(); return n+"!"; };
lambda表达式和变量捕获:
lambda表达式中,可以访问:
- 可以获取或者是外层类的实例或静态变量的值
- 可以隐式或显式地访问this变量,引用lambda表达式外层类的调用实例
- 变量捕获:
- lambda表达式只能使用实质上是final(第一次赋值后,值就不变化)的局部变量;
- lambda表达式不能修改外层作用域内的局部变量,因为修改会移除其实质final状态
方法引用:
在上面,要传入一个函数式接口的实例,必须使用lambda语句创建一个匿名实现。
方法引用:如果我们在其他类中,已有方法刚好实现了这个接口,那么我们可以直接引用这个方法,传入给这个函数式接口参数
原理:方法引用会创建函数式接口的一个实例。
// 静态方法引用 interface StringFunc{ String func(String n); } class MyStringOps{ static String strReverse(String str){ .... return ...; } } static String stringOp(StringFunc sf,String s){ return sf.func(s); } String outStr = stringOp(MyStringOps::strReverse, "str1"); // 实例方法引用 class MyStringOps{ String strReverse(String str){ .... return ...; } } static String stringOp(StringFunc sf,String s){ return sf.func(s); } MyStringOps ops = new MyStringOps(); String outStr = stringOp(ops::strReverse, "str1"); // 类的实例方法的引用 // 上面引用的是类的静态方法,实例的方法;现在还可以引用类的实例方法 // 此时,函数式接口匹配的方法是,第一个参数匹配调用对象,后面的参数再匹配实例方法的参数 interface MyFunc<T>{ boolean func(T v1,T v2); } class HighTemp{ boolean sameTemp(HighTemp ht2){ ... } } static <T> void do(MyFunc<T> f,T t1,T t2){ f.func(t1,t2);// 执行的是t1.sameTemp(t2) } // HighTemp的sameTemp方法只有一个参数,但是调用类的实例方法,第一个参数需要匹配调用对象 do(HighTemp::sameTemp,new HighTemp(1),new HighTemp(2)); // super::name 可以引用发放的超类版本 // 泛型中的方法引用 // 泛型方法MyArrayOps::<Integer>countMatching // 泛型类MyArrayOps<Integer>::countMatching // 类型也可以不写,通过类型推断 interface MyFunc<T>{ int func(T[] vals, T v); } class MyArrayOps{ static <T> int countMatching(T[] valus,T v){ int count = 0; for(int i=0;i<vals.length;i++) if(vals[i]==v)count++; return count; } } class Demo{ static <T> int myOp(MyFunc<T> f,T[] vals,T v){ return f.func(vals,v); } public static void main(String args[]){ Integer[] vals = {1,2,3,4,2,2,3,4}; String[] strs = {"One","Two","Three","Two"}; int count; count = myOp(MyArrayOps::<Integer>countMatching,vals,4); count = myOp(MyArrayOps::<String>countMatching,strs,"Two"); } } // Collection,如Compare接口,不一定需要实现接口,并创建对应实例 // 可以考虑创建一个静态方法,与Comparator的compare方法兼容,然后使用方法引用 class MyClass{ private int val; MyClass(int v){val = v;} int getVal(){return val;} } class UseMethodRef{ static int compareMC(Myclass a,MyClass b){ reutrn a.getVal() - b.getVal(); } public static void main(String args[]) { ArrayList<MyClass> a1 = new ArrayList<MyClass>(); a1.add.... MyClass maxValObj = Collections.max(a1,UseMethodRef::compareMC); } }
构造函数引用:
暂略,不想看了。
预定义的函数式接口:
为了避免经常自己定义函数式接口,JDK8提供了java.util.function,提供了预定义的函数式接口
十六、字符串处理
String、StringBuffer、StringBuilder
StringBuffer、StringBuilder用于可以修改的字符串
StringBuilder,StringBuilder不是同步的,所以不是线程安全,优势在于性能更高
如果字符串被多个线程修改,没有使用其它同步措施的话,需要使用StringBuffer
十七、java.lang
暂略,大部分是接口和API
public interface Iterable<T> { // 返回迭代器 Iterator<T> iterator(); // 迭代每个元素执行action代码 default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } // 返回被迭代序列的Spliterator default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } }
十八、java.util集合框架
1、集合框架概述
集合框架之前:Java提供了特定的类以存储和管理对象组,如Dictionary、Vector、Stack和Properties。
问题:缺少集中、统一的主体,不易扩展,不易改造。
集合框架的目标:
- 1、高性能。基本集合(动态数组、链表、树和哈希表)的实现是高效率的。
- 2、统一、高互操作性。框架必须允许不同类型的集合以类似的方式进行工作,并且具有高度的互操作性。
- 3、易扩展、易改造。
- 4、必须将标准数组集成到集合框架中的机制。
为了满足这些目标,Java提供了:
- 一套标准接口和实现:整个集合框架基于一套标准接口进行构造,提供了这些接口的一些可以直接使用的标准实现(如LinkedList、HashSet、TreeSet),也可以实现自己的集合。
- 集合算法:算法是集合机制中的另一个重要部分。算法操作集合,并且被定义为Collections类中的静态方法。所以所有集合都可以使用它们,每个集合都不需要实现自己的版本,算法为操作集合提供了标准方法。
- Iterator接口,迭代器:为访问集合中的元素提供了通用的、标准的方式,每次访问一个元素。每个集合都提供了迭代器。
- spliterato:JDK8提供了一种新的迭代器,叫spliterator,用于支持并行迭代。支持spliterator的接口有Spliterator和支持基本类型的几个嵌套接口。JDK8还添加了用于基本类型的迭代器接口,例如PrimitiveIterator和PrimitiveIterator.OfDouble。
- 处理映射:除了集合外,框架还定义了一些映射接口和类。映射存储键值对。虽然映射是集合框架的组成部分,但是严格意义上说,他们不是结婚。不过,可以获得映射的集合视图(collection-view)。这种视图包含来自映射的元素,并存储在集合中。因此,也可以作为一种选择,可以想处理集合一样处理映射内容。
新的JDK为集合框架带来的改变:
- 1、泛型:泛型从根本上改变了集合框架,新增的泛型使整个集合框架对泛型进行了重新设计。所以集合现在都是泛型,许多操作集合的方法现在都是使用泛型参数。
- 2、自动装箱:自动装箱使得使用基本类型更加容易。集合只能存储引用,不能存储基本类型值,以前需要手动装箱,现在就不再需要了。
- 3、for-each风格的for循环:合框架中所有集合类进行了重新设计以实现Iterable接口,所以可以使用for-each风格的for循环遍历集合。以前需要使用迭代器,现在可以使用for循环替代。
2、集合接口
除了集合接口外,集合还使用Comparator、RandomAccess、Iterator以及ListIterator接口,这些后面探讨。JDK8开始,还使用Spliterator接口。
Collection接口
Collection接口是集合框架的基础,所有定义集合的类都必须实现这个接口,是一个泛型接口。
interface Collection<E> //E是存储对象的类型
Collection扩展了Iterable接口,所以所有集合都可以使用for-each风格的for循环
Collection声明了所有集合将拥有的核心方法
异常:
- UnsupportedOperationExceiption:不能被修改集合执行修改集合的方式时抛出
- ClassCastException:一个对象和另一个对象不兼容时抛出,如试图将一个不兼容的对象添加到集合中
- NullPointerException:视图在不允许存储null对象的集合中存储null对象
- IllegalArgumentException:使用参数无效
- IllegalStateException:未长度固定且已满的集合添加元素
核心方法总结:添加、删除、检查是否包含、获取迭代器、转换数组、获取流
List接口
扩展Collection,并声明了用来存储一连串元素的集合的行为。
可以使用从0开始的索引,通过元素位置插入或访问元素。
可以包含重复元素
interface List<E> //E是存储对象的类型
Set接口
Set接口定义了组,扩展了Collection接口,声明了集合中不允许有重复元素的组行为
没有定义自己的其它方法,添加重复元素的时候,add()方法返回false
interface Set<E> //E是存储对象的类型
SortedSet接口
SortedSet扩展了Set接口,并声明以升序进行排序的组行为。
interface SortedSet<E> //E是存储对象的类型
NavigableSet接口
NavigableSet接口扩展了SortedSet接口,并且声明了支持基于最接近匹配原则检索元素的集合行为
interface NavigableSet<E> //E是存储对象的类型
Queue接口
Queue接口扩展了Collection接口,声明了队列的行为,队列通常是先进先出队列,但也有其他准则的队列类型。
interface Queue<E> //E是存储对象的类型
Deque接口
Deque接口扩展了Queue接口,并且声明了双端队列行为,既可以先进先出,也可以后进先出
interface Deque<E> //E是存储对象的类型
3、集合类
集合接口的标准实现
ArrayList
ArrayList扩展了AbstractList类并实现了List接口
class ArrayList<E> //E是存储对象的类型
用于按需增长的动态数组,Java里面的数组长度是固定的,定义后不能增长或缩小。
// 构造函数 ArrayList() ArrayList(Collection<? extends E> c) // 使用一个集合进行初始化 ArrayList(int capacity) // 初始容量
使用示例
public class test { @Test public void test() { ArrayList<String> arrayList = new ArrayList<String>(); arrayList.add("a"); arrayList.add("d"); arrayList.add("c"); arrayList.add("d"); arrayList.add(1, "b"); System.out.println(arrayList);// [a, b, d, c, d] System.out.println(arrayList.size());// 5 arrayList.remove("d");// 从前往后查找,找到第一个删除 System.out.println(arrayList);// [a, b, c, d] System.out.println(arrayList.size());// 4 arrayList.remove("d"); System.out.println(arrayList);// [a, b, c] System.out.println(arrayList.size());// 3 arrayList.ensureCapacity(12);// 一次性增加内存,提升性能;避免每次增加一个元素就分配一次内存 System.out.println(arrayList.size());// 仍为3 arrayList.trimToSize();// 减少内存到刚好容纳现在的元素 System.out.println(arrayList.size());// 仍为3 // 转换为数组 Object[] array1 = arrayList.toArray();// 方法1,转换为Object数组 // 常用方法2,转换为特定类型数组 String[] array2 = new String[arrayList.size()]; arrayList.toArray(array2); for (Object item : array1) System.out.print(item + ",");// a,b,c, System.out.println(); for (Object item : array2) System.out.print(item + ",");// a,b,c, } }
LinkedList类
LinkedList类扩展了AbstractSequentialList类,实现了List、Deque以及Queue接口,并且提供了一种链表数据结构
class LinkedList<E> //E是存储对象的类型
// 构造函数 LinkedList() LinkedList(Collection<? extends E> c)
public class test { @Test public void test() { LinkedList<String> linkedList = new LinkedList<>(); linkedList.add("F"); linkedList.add("B"); linkedList.add("D"); linkedList.add("E"); linkedList.add("C"); linkedList.add("Z"); linkedList.add("A"); linkedList.add(1, "A2"); System.out.println(linkedList);// [F, A2, B, D, E, C, Z, A] linkedList.remove("F"); linkedList.remove(2); System.out.println(linkedList);// [A2, B, E, C, Z, A] linkedList.removeFirst(); linkedList.removeLast(); System.out.println(linkedList);// [B, E, C, Z] String val = linkedList.get(2); linkedList.set(2, val + "Changed"); System.out.println(linkedList);// [B, E, CChanged, Z] } }
HashSet类
HashSet类扩展了AbstractSet类并实现了Set接口,用于创建使用哈希表存储元素的集合。
散列机制优势是及时集合较大,add()、contains()、remove()、size()的方法执行时间保持不变
class HashSet<E> //E是存储对象的类型
// 构造函数 HashSet() HashSet(Collection<? extends E> c)// 使用集合初始化 HashSet(int capacity)// 使用容量初始化,默认16 HashSet(int capacity, float fillRatio)// 容量和填充率初始化,填充率是0.0到1.0之间,集合中数量达到填充率,就会扩展和哈希组,默认0.75
HashSet没有自己的方法,只有超类和接口的方法
public class test { @Test public void test() { HashSet<String> hs = new HashSet<String>(); hs.add("b"); hs.add("c"); hs.add("d"); hs.add("e"); hs.add("a"); hs.add("a"); System.out.println(hs);// [a, b, c, d, e] 不可重复且没有顺序 } }
LinkedHashSet类
LinkedHashSet扩展了HashSet类,没有添加自己的方法
class LinkedHashSet<E> //E是存储对象的类型
LinkedHashSet维护条目中的一个链表,链表中条目顺序就是插入顺序,可以按照插入顺序迭代。
同时toString()方法也是按照插入顺序输出
public class test { @Test public void test() { LinkedHashSet<String> hs = new LinkedHashSet<String>(); hs.add("b"); hs.add("c"); hs.add("d"); hs.add("a"); hs.add("e"); hs.add("a"); System.out.println(hs);// [b, c, d, a, e] 按照插入顺序输出,重复的则不插入 } }
TreeSet
TreeSet扩展了AbstractSet类并实现了NavigableSet接口,用于创建使用树存储的组。
对象以升序存储,访问和检索速度相当快,使得对存储大量的、必须能够快速查找的有序信息来说,TreeSet是极佳选择。
class TreeSet<E> //E是存储对象的类型
// 构造函数 TreeSet()// 创建空树,按照元素的自然顺序以升序进行存储 TreeSet(Collection<? extends E> c)// 构建一个包含集合c中元素的树 TreeSet(Comparator<? super E> comp)// 构建一个空树,按照comp指定的比较器进行存储 TreeSet(SortedSet<E> ss)// 构建一个包含ss中元素的树
public class test { @Test public void test() { TreeSet<String> ts = new TreeSet<String>(); ts.add("C"); ts.add("A"); ts.add("B"); ts.add("E"); ts.add("F"); ts.add("D"); System.out.println(ts);// [A, B, C, D, E, F] 自动排序 System.out.println(ts.subSet("C","F"));// [C, D, E],使用NavagalbeSet接口的方法检索TreeSet元素 } }
PriorityQueue类
PriorityQueue扩展了AbstractQueue类并实现了Queue接口,用于创建根据队列的比较器来判断优先次序的队列。
PriorityQueue是动态的、按需增长的。
class PriorityQueue<E> //E是存储对象的类型
// 构造函数 PriorityQueue()// 起始容量为11 PriorityQueue(int capacity)// 指定初始容量 PriorityQueue(Comparator<? super E> comp)// 指定比较器 PriorityQueue(int capacity,COmparator<? super E> comp)// 指定容量和比较器 PriorityQueue(Collection<? extends E> c)// 使用集合C来初始化 PriorityQueue(PriorityQueue<? extends E> c)// 使用集合C来初始化 PriorityQueue(SortedSet<? extends E> c)// 使用集合C来初始化
如果没有指定比较器,将使用队列中存储的数据类型的默认比较器。
默认比较器以升序对队列进行排序。
使用comparator()方法获取比较器,如果队列使用的是自然顺序,则返回null
虽然可以使用迭代器遍历PriorityQueue,但是迭代顺序是不确定的。
使用PriorityQueue,应该使用offer()和poll()这类由Queue接口定义的方法
ArrayDeque类
ArrayDeque扩展了AbstractCollection类并实现了Deque接口,没有添加自己的方法。
ArrayDeque是动态数组,没有容量限制(Deque接口支持限制容量的实现,但不是必须的)
class ArrayDeque<E> //E是存储对象的类型
// 构造函数 ArrayDeque()// 空的双端队列,初始容量是16 ArrayDeque(int size)// 指定容量 ArrayDeque(COllection<? extends E> c)// 使用集合来初始化
public class test { @Test public void test() { ArrayDeque<String> adq = new ArrayDeque<String>(); adq.push("A"); adq.push("B"); adq.push("D"); adq.push("E"); adq.push("F"); System.out.println(adq);// [F, E, D, B, A] while (adq.peek()!=null) System.out.print(adq.poll()+",");// F,E,D,B,A, } }
EnumSet类
EnumSet扩展了AbstractSet并实现了Set接口,专门用于枚举类型的元素。
class EnumSet<E extends Enum<E>> //E是存储对象的类型
EnumSet类没有定义构造函数,而是使用工厂方法来创建对象
4、通过迭代器访问集合
如果希望遍历集合中的元素,方法之一是使用迭代器。
迭代器是实现了Iterator或ListIterator接口的对象。
Iterator接口允许遍历集合,获取或移除元素。
ListIterator接口扩展了Iterator接口,允许双向遍历列表,并且允许修改元素。
interface Iterator<E> //E是存储对象的类型 interface ListIterator<E> //E是存储对象的类型
使用迭代器
iterator:
获取迭代器:每个集合类都提供了iterator()方法,方法返回一个指向集合开头的迭代器
具体步骤:
- 1、调用集合的iterator()方法,获取指向集合开头的迭代器
- 2、建立一个hasNext()方法调用循环,只要hasNext()返回true,就继续迭代
- 3、循环中,调用next()方法获取每个元素
对于实现List接口的集合,还可以调用listIterator()方法来获取迭代器,这种方式提供了向前和向后2个方向的访问集合的能力,并允许修改元素。除此之外,2个用法类似。
public class test { @Test public void test() { ArrayList<String> a1 = new ArrayList<String>(); a1.add("C"); a1.add("A"); a1.add("E"); a1.add("B"); a1.add("D"); a1.add("F"); System.out.println(a1);// [C, A, E, B, D, F] Iterator<String> itr = a1.iterator(); while (itr.hasNext()) { String element = itr.next(); System.out.print(element + ",");// C,A,E,B,D,F, } System.out.println(); ListIterator<String> litr = a1.listIterator(); while (litr.hasNext()) { String element = litr.next(); litr.set(element + "+"); } System.out.println(a1);// [C+, A+, E+, B+, D+, F+] itr = a1.iterator(); while (itr.hasNext()) { String element = itr.next(); System.out.print(element + ",");// C+,A+,E+,B+,D+,F+, } System.out.println(); while (litr.hasPrevious()) { String element = litr.previous(); System.out.print(element + ",");// F+,D+,B+,E+,A+,C+, } System.out.println(); } }
for-each循环代替迭代器:
如果不修改集合的内容,也不用反向获取元素,那么使用for-each风格的for循环遍历集合通常更加方便。
for循环可以遍历所有实现Iterable接口的集合对象,而所有集合类都实现了这个接口,所以都可以用for循环操作。
public class test { @Test public void test() { ArrayList<Integer> vals = new ArrayList<Integer>(); vals.add(1); vals.add(2); vals.add(3); vals.add(4); vals.add(5); System.out.println(vals);// [1, 2, 3, 4, 5] for(int v :vals) System.out.print(v+",");// 1,2,3,4,5, System.out.println(); int sum = 0; for(int v:vals) sum+=v; System.out.println(sum);// 15 } }
Spliterator:
JDK8新增一种叫splitertor的迭代器,由Spliterator接口定义。
Spliterator用于循环遍历元素序列。
它提供的方法远比Iterator或ListIterator多,最重要的一点是它支持并行迭代序列的一部分。
即使用不到并行编程,也可以使用Spliterator,其中一个原因是它把hasNext和next操作合并到一个方法中,从而提供了效率
interface Spliterator<T> //T是被迭代元素的类型
暂略,需要lambda表达式知识
RandomAccess接口:
RandomAccess接口不包括成员。然而通过实现这个接口,可以表明集合支持高效地随机访问其中的元素。尽管集合可能支持随机访问,但是可能没有这么高效。
通过检查RandomAccess接口,客户端代码可以在运行时确定集合是否适合特定类型的随机访问操作——特别是当将他们应用于大的集合时(可以使用instance of来判断类是否实现了这个接口)。
ArrayList和遗留的Vector类实现了RandomAccess接口。
5、使用映射
映射是存储键和值之间关系(键值对)的对象。
键和值都是对象,键必须唯一,但值可以重复。
某些映射可以接收null键和null值,其它不能。
映射没有实现Iterable接口,所以不能使用for-each风格的for循环,也不能获取迭代器。
但是可以获取映射的集合视图,集合视图允许使用for循环和迭代器。
映射接口:
Map接口:
Map接口将唯一键映射到值。给定键和值就可以在Map对象中存储;存储后可以使用键来检索值。
interface Map<K,V>// K是键的类型,V是值的类型。
映射围绕2个基本方法:get()、put()
使用entrySet()方法,返回一个Set对象,包含映射中元素。
使用keySet()方法,返回一个Set对象,作为键的集合视图。
使用values()方法,返回一个Set对象,作为值的集合视图。
修改一个集合会影响其他集合,因为集合的数据是引用映射内的数据。
集合视图是将映射集成到集合框架中的手段。
SortedMap接口
SortedMap接口扩展了Map接口,确保条目以键的升序保存。
interface SortedMap<K,V>// K为键类型,V为值类型
有序映射支持非常高效的子映射操作,如headMap()、tailMap()或subMap()方法。
NavigableMap接口
NavigableMap接口扩展了SortedMap接口,支持基于最接近匹配原则的条目检索行为,即支持检索与给定的一个或多个键最相匹配的条目。
interface NavigableMap<K,V>
Map.Entry接口
Map.Entry接口提供了操作映射条目的功能。
Map接口声明的entrySet()方法返回一个包含映射条目的Set对象。
组的所有元素都是Map.Entry对象。
interface Map.Entry<K,V>
另外JDK8添加了2个静态方法:comparingByKey()和comparingByValue()。
前者返回根据键比较条目的Comparator对象,后者返回根据值比较条目的Comparator对象。
映射类
有几个类实现了映射接口
AbstractMap是所有具体映射实现的超类。
WeakHasMap实现了使用“弱键”的映射,键不在使用时可以被垃圾回收,这里不讨论。
1、HashMap类
HashMap扩展了AbstractMap类并实现了Map接口,没有添加自己的方法。
他使用哈希表存储映射,使得即使比较大的集合,get()和put()方法执行时间保持不变
class HashMap<K,V>
// 构造函数 HashMap() HashMap(Map<? extends K, ? extends V> m)// 使用m中元素初始化 HashMap(int capacity)// 使用容量初始化 HashMap(int capacity, float fillRatio)// 使用容量和填充率初始化,默认是16,0.75
public class test { @Test public void test() { HashMap<String,Double> hm = new HashMap<String,Double>(); hm.put("a",new Double(11.24)); hm.put("d",new Double(22.24)); hm.put("e",new Double(33.24)); hm.put("b",new Double(44.24)); hm.put("c",new Double(55.24)); Set<Map.Entry<String,Double>> set = hm.entrySet(); for(Map.Entry<String,Double>me:set){ System.out.print(me.getKey()+":"+me.getValue()+",");// a:11.24,b:44.24,c:55.24,d:22.24,e:33.24, } System.out.println(); double balance = hm.get("a"); hm.put("a",balance+100); System.out.println(hm.get("a"));// 111.24 } }
TreeMap类
TreeMap扩展了AbstractMap类并实现了NavigableMap接口,该类用于创建存储在树结构中的映射。
TreeMap提供了有序存储键值对的搞笑手段,并支持快速检索。
与哈希映射不同,树映射确保元素以键的升序存储。
class TreeMap<K,V>
// 构造函数 TreeMap()// 按键的自然顺序存储 TreeMap(Comparator<? super K> comp)// 使用比较器comp进行排序 TreeMap(Map<? extends K, ? extends V> m)// 使用m中条目初始化树映射,使用键的自然顺序进行排序 TreeMap(SortedMap<K, ? extends V> sm)// 使用sm中条目初始化树映射,使用sm相同顺序进行排序
TreeMap类的映射方法没有超出NavagableMap接口和AbstractMap类定义的那些方法。
public class test { @Test public void test() { TreeMap<String,Double> tm = new TreeMap<String,Double>(); tm.put("a",new Double(11.24)); tm.put("d",new Double(22.24)); tm.put("e",new Double(33.24)); tm.put("b",new Double(44.24)); tm.put("c",new Double(55.24)); Set<Map.Entry<String,Double>> set = tm.entrySet(); for(Map.Entry<String,Double>me:set){ System.out.print(me.getKey()+":"+me.getValue()+",");// a:11.24,b:44.24,c:55.24,d:22.24,e:33.24, 按照键升序排列 } System.out.println(); double balance = tm.get("a"); tm.put("a",balance+100); System.out.println(tm.get("a"));// 111.24 } }
LinkedHashMap类
LinkedHashMap扩展了HashMap类,在映射中以插入条目的顺序维护一个条目链表,从而可以按照插入顺序迭代整个映射。
也就是说,当遍历LinkedHashMap的集合视图时,将以元素的插入顺序返回元素。也可以创建按照最后访问的顺序返回元素的LinkedHashMap。
class LinkedHasMap<K,V>
// 构造函数 LinkedHashMap() LinkedHashMap(Map<? extends K, ? extends V> m)// 使用m中元素初始化LinkedHashMap LinkedHashMap(int capacity)// 指定容量 LinkedHashMap(int capacity, float fillRatio)// 指定容量和填充率,默认16,0.75 LinkedHashMap(int capacity, float fillRatio, boolean Order)// Order为true,使用访问顺序;Order为false,使用插入顺序,默认的是插入顺序
LinkedHashMap除了继承自HashMap定义的方法外,只添加了一个方法,removeEldestEntry()
protected bollean removeEldestEntry(Map.Entry<k,V> e)
这个方法由put()和putAll()调用。最旧的条目由e传入。
默认情况下这个方法返回false,并且不执行任何操作;重写这个方法,可以使LinkedHashMap移除映射中最旧的条目。
IdentityHashMap类
IdentityHashMap扩展了AbstractMap类并实现了Map接口。除了当比较元素时使用引用相等性外,其它方面和HashMap类似
class IdentityHasMap<k,V>
API文档明确指出IdentityHashMap不用于通用目的
EnumMap类
EnumMap扩展了AbstractMap类并实现Map接口,是专门为使用枚举类型的值设计的。
class EnumMap<K extends Enum<K>,V>
// 构造函数 EnumMap(Class<K> kType)// 创建类型为kType的空EnumMap EnumMap(Map<K, ? extends V> m)// 创建一个包含m中相同条目的EnumMap EnumMpa(EnumMap<K, ? extends V> em)// 创建一个使用em中的值进行初始化的EnumMap
EnumMap类没有定义自己的方法。
6、比较器
TreeSet和TreeMap使用有序顺序存储元素。
比较器精确定义了有序顺序的含义。
默认情况,Java使用自然循序为这些元素排序(如ABCD,1234等),而比较器可以让我们自定义顺序
interface Comparator<T>
JDK8前,Comparator接口定义了2个方法
int compare(T obj1, T obj2)// obj1大于obj2则返回正数 boolean equals(object obj)// 如果obj和调用对象都是比较器,且使用相同的排序规则,则返回true,否则返回false;一般不需要重写equals方法
JDK8通过默认接口方法和静态接口方法,增加了很多新功能
default Comparator<T> reversed()// 获得一个反转的比较器 static <T extends Comparable<? super T>> Comparator<T> reverseOrder()// 返回一个颠倒元素的自然顺序比较器 static <T extends Comparable<? super T>> Comparator<T> naturalOrder()// 返回一个自然顺序比较器 static <T> Comparator<T> nullsFirst<Comparator<? super T> comp)// 支持处理null值,并认为null比其他值小 static <T> Comparator<T> nullsLast<Comparator<? super T> comp)// 支持处理null值,并认为null比其他值大 // thenComparing:当第一次比较结果相等时,则使用这个比较器执行第二次比较 // 三种形式 default Comparator<T> thenComparing(Comparator<? super T> thenByComp)// 指定第二次使用的比较器 default <U extends Comparable<? super U> Comparator<T> thenComparing(Function<? super T, ? extends U> getKey)// getKey引用的函数用于获取下一个比较键 default <U> Comparator<T> thenComparing(Function<? super T, ? extends U> getKey,Comparator<? super U> keyComp)// keyComp指定了用于比较键的比较器 // Comparator还为基本类型添加了如下所示的专用版本方法 // 这些方法中,getKey引用的函数用于获取下一个比较键 static <T> Comparator<T> ComparingDouble(ToDoubleFunction<? super T> getKey) static <T> Comparator<T> ComparingInt((ToIntFunction<? super T> getKey) static <T> Comparator<T> ComparingLong((ToLongFunction<? super T> getKey)
使用比较器代码(待补充)
7、集合算法
集合框架定义了一些可以用于集合和映射的算法,被定义在Collections类的静态方法中
很多方法,待补充
8、Arrays类
Arrays类提供了对数组操作有用的方法,有助于连接集合和数组。
以下将解释Arrays类定义的每个方法
待补充
9、遗留的类和接口
早期的java.util包没有包含集合框架,而是定义了几个类和一个接口,用来提供存储对象方法的专业方法。
注意现代集合类都不是同步的,但是所有遗留类都是同步的,当然可以使用Collection提供的算法很容易实现同步。
包括
- Enumeration接口
- Vector类
- Dictionary类
- Hashtable类
- Properties类、store()和load()方法
十九、java.util其它工具类(略)
二十、java.io(略)
二十一、探究NIO(略)
二十二、联网(略)
二十三、Applet(略)
二十四、事件处理(略)
二十五、AWT介绍:使用窗口、图形和文本(略)
二十六、使用AWT控件、布局管理器和菜单(略)
二十七、图像(略)
二十八、并发使用工具(略)
二十九、流API
流的概念:流是数据的渠道。
流代表一个对象序列。
流操作数据源,如数组或集合。
流本身不存储数据,只是移动数据,移动过程中可能对数据执行过滤、排序或其他操作。
一般流操作不会修改数据源,而是创建一个新的流,包含过滤、排序或其他操作后的结果。
流接口
终端操作:会消费流,被消费后,流不能重用。
中间操作:会产生另一个流。中间操作不是立即发生,是在新流上执行完终端操作后,中间操作才会发生,称为延迟行为,能让流API更加高效地执行。
另一个关键点:
无状态:独立于其他元素处理每个元素。如过滤,filter()
有状态:某个元素处理以来其他元素。如排序,sorted()
注意,并行处理流时,无状态和有状态区别尤其重要,因为有状态操作可能需要几次处理才能完成。
Stream操作是对象引用,所以增加了以下几个接口支持处理基本类型流,都扩展了BaseStream
DoubleStream
IntStream
LongStream
重点关注Stream,基本类型流处理上基本相同
获取流:
1、COllection接口被扩展,包含2个获取集合流的方法
补充代码
缩减操作
基于任意条件,从流中返回一个值。所有缩减操作都是终端操作。
三十、正则表达式和其它包(略)
三十一、Swing简介(略)
三十二、探究Swing(略)
三十三、Swing菜单简介(略)
三十四、JavaFX GUI编程简介诶(略)
三十五、探究JavaFX控件(略)
三十六、JavaFX菜单简介(略)
三十七、Java Bean
这个是Java的组件架构,通过一定的规范保证组件可以重用和互操作;但是有Spring框架后,Spring框架中的Bean是基于POJO的,所以Java Bean的意义不大了。(个人观点)
软件行业一直在探索,以实现基于组件方式的可重用和互操作性。
Java Bean是一种组件架构,希望开发软件时,可以利用这些软件组件来构建复杂的系统。
例如电子行业的电阻器、电容器、传感器等。
那么设计这可以选择组件,理解他们的功能,并把他们集成到应用程序中。
Java Bean:
Java语言书写,并且遵守JavaBean API规范
软件组件,被设计成可以在不同环境下重用。
功能可以是获取库存值,又或者预测股票走势。
优势:
内省:
- JavaBean的核心是内省,内省是分析Bean的过程,用于确定Bean的功能。
- 这是Java Bean API的本质特征,允许其他应用程序获取关于组建的信息。
- 有了内省机制,Java Bean技术才能起作用。
2种方式指示Bean应当暴露哪些属性、事件和方法:
- 简单的命名约定,这些约定使内省机制能够推断出与Bean相关信息
- 提供一个扩展了BeanInfo的接口的附加类,显式提供这些信息
属性的设计模式:
- 属性是Bean状态的子集。
- 赋给属性的值决定了组件的行为和外观。
- 属性通过setter方法设置,通过getter方法获取
- 2种属性:简单属性、索引属性
简单属性:
补充代码
索引属性:
补充代码
事件的设计模式
补充代码
方法与设计模式:
- 设计模式不能用于命名非属性方法。
- 内省机制能够发现Bean的所有公有方法,但是不能显示保护方法和私有方法。
BeanInfo接口:
- 显式控制哪些信息可得
- 补充代码
绑定属性与约束属性:
具有绑定属性的Bean:
- 属性发生变化时会产生事件。
- 时间的类型是PropertyChangeEvent,并且事件将被发送到之前注册的对接收这种通知感兴趣的对象。
- 处理这种事件的类必须实现PropertyChangeListener接口。
具有约束属性的Bean:
- 当尝试修改约束属性的值会产生事件。
- 事件的类型也是PropertyChangeEvent,并且也将被发送到之前的对接收这种通知感兴趣的对象。
- 但是,其它对象可以抛出PropertyVetoException异常来否决建议的修改。
- 这种能力使得Bean可以根据运行时环境来进行不同的操作。
- 处理这种事件的类必须实现VetoableChangeListener接口。
持久性:
- 持久性是保存Bean当前状态到非易失性存储器的能力,以及之后检索他们的能力,包括Bean的属性值和实例变量的值。
- Java类库提供的对象串行化能力,可以用于为Bean提供持久化。
- 串行化Bean:让Bean实现java.io.Serializable接口,只是一个简单的标记接口。
- 实现后无需其它操作,使得Bean可以自动完成串行化。
- 自动串行化还能进行继承。
- 使用transient关键字可以阻止某个实例变量参与串行化。
- 如果没有实现该接口,可以通过实现java.io.Externalizable等方式来自行提供串行化,否则容器態保存组件的配置信息。
定制器:
- Bean开发者可以提供定制器,用于帮助其他开发者配置Bean。
- 定制器可以提供一步步的指导,以完成再特定上下文中使用组件必须遵循的步骤。
- 还可以提供在线文档。
- 对于开发定制器,Bean开发者拥有很大的灵活性,可以根据市场对产品进行微调。
Java Bean API:
Java Bean功能是由java.beans包的一组类和接口提供的。
三十八、servlet
applet动态扩展Web浏览器功能;而servlet是在Web连接的服务器端执行的小程序,动态扩展了Web服务器的功能。
背景:
Web浏览器如何与服务器之间进行协作并最终为用户提供内容呢?
请求静态Web页面:
- 用户在浏览器输入URL地址
- 浏览器产生HTTP请求,发送到Web服务器
- Web服务器将HTTP请求映射到特定文件
- Web服务器在HTTP响应中,把文件返回到浏览器,响应中HTTP头指明了内容的类型
- MIME(多用途Internet邮件扩展,Multipurpose Internet Mail Extension)用于指明内容类型。如,普通ASCII文本的MIME类型是text/plain,Web页面的HTML源码的MIME类型是text/html
请求动态内容:
- Web早起,通过CGI(公共网关接口,Common Gateway Interface)与Web 服务器通信,CGI允许单独的进程从HTTP请求读取数据,然后将数据写入HTTP响应,可以用各种语言实现CGI程序。
- 但是CGI存在严重性能问题,每个客户端请求都需要创建独立的进程和数据库连接;而且CGI不是平*立的。
- 出现了其它技术,servlet是其中之一。
- servlet在Web服务器地址空间内执行,不需要创建单独进程来处理每个客户端请求;
- servlet是平*立的,由Java编写;
- 服务器上的Java安全管理器强制执行一套限制,以保护服务器上的资源;
- servlet可以使用Java类库的全部功能
servelet的生命周期:
有3个方法控制servlet的生命周期:init()、service()、destroy(),每个servlet都需要实现它们,并且特定的时间由服务器调用它们。
一个典型用户场景:
- 用户在Web浏览器输入URL地址
- 浏览器生成HTTP请求发送到对应的服务器
- Web服务器接收到HTTP请求
- Web服务器将请求映射到特定的servlet
- servlet被动态检索并加载到服务器地址空间中
- Web服务器调用servlet的init()方法,只有servlet第一次被加载到内存中才会调用该方法,向servlet传递初始化参数,以配置自身
- Web服务器调用servlet的service()方法,以处理HTTP请求
- servlet执行service方法时,会读取HTTP请求中的数据,并且根据需求计算出HTTP响应
- servlet会保留在Web服务器的地址空间中,能处理从不同客户端接收到的其它HTTP请求,为每个请求调用service()方法
- 最后服务器可以决定从内存写在servlet,调用destroy()方法,释放资源
servlet开发选项
为了创建servlet,需要访问servlet容器/服务器,常用的有Glassfish和Tomcat
使用Tomcat开发和创建servlet:
1、安装Tomcat
2、启动Tomcat
3、使用Tomcat下的servlet-api.jar包,编译写好的servlet代码java文件,生成class文件
4、把class文件放到Tomcat下并配置Tomcat的web.xml文件,让Tomcat能找到这个servlet
创建并运行一个servlet:
创建并编译servlet源码
将servlet类文件复制到正确的目录下,并添加servlet的名称和映射到正确的web.xml中
启动Tomcat
启动Web浏览器,请求这个servlet
源码编写:
常见问题
JDK、JRE、JVM
JVM:java virtual machine,Java虚拟机,Java代码编译出来的类文件,在Java虚拟机上运行
JRE:java runtime environment,Java运行时环境,包含JVM,以及Java程序运行时需要的库
JDK:java development kit,Java开发工具包,包括JRE,以及Java开发时需要的工具、库等
URI、URL、URN
URI:Uniform Resource Identifier,统一资源标志符,用来唯一的标识一个资源。
URL:Uniform Resource Locator,统一资源定位符。即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
URN:Uniform Resource Name,统一资源命名。即通过名字来表示资源的。
URI指一个网络资源的标识符,有其格式;URL是URI的一种形式,包括协议、主机等信息,有其自己的格式要求;URN也是URI的一种形式,使用名称来表示该资源。
实际应用
下载历史JDK版本的方法
之前一直想下载JDK8的172版本,一直找不到下载地址
登录甲骨文官网:https:/www.oracle.com/index.html,搜索jdk8 archive,点击进去即可下载JDK8所有版本的存档
JDK安装
下载
下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html
选择下载Java 8的JDK
根据系统选择对应安装包
下载后,安装直接下一步就可
安装完成可得2个文件夹
设置环境变量
目的有2个:1、为了在命令行中直接可以使用Java命令,而不需要跳转到Java文件夹里面才能使用;2、其它程序引用Java安装地址时,使用这个变量即可,不需要写绝对地址;而Java地址更改时,只要变量不变,这些程序也不需要更改地址。
1、计算机→属性→高级系统设置→高级→环境变量→系统变量→新建 JAVA_HOME 变量,变量值填写jdk的安装目录
2、系统变量→找到已存在的Path 变量→编辑→在变量值最后输入 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
此步骤相当于为命令行增加了2个搜索java.exe程序的路径,所以当在命令行中执行java命令时,系统就能找对java.exe程序
3、 系统变量→新建 CLASSPATH 变量→变量值填写 .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar(注意最前面有一点)
验证是否安装成功
开始→运行→输入cmd,回车→输入java -version
如果出现一下对应信息,代表安装成功
JDK、JRE、JVM的区别
JDK
JDK:Java Development Kit,Java 语言软件开发工具包。
一个工具包,能支持人类输入编程语言,并将其转化为机器语言,其中包含了JRE和JVM。
JRE
JRE:Java Runtime Environment,Java运行环境。
人类使用JDK开发好程序后,JRE提供程序的运行环境,能把程序针对不同环境编译成可以运行的机器语言。JRE包含了JVM的标准实现。
JVM
JVM:Java Virtual Machine,Java虚拟机。
所有Java程序,都由JRE编译成JVM能认识的一种特定语言,在JVM上运行,JVM再通过与不同操作系统,不同环境的交互,进而在不同的系统下。
25、AWT介绍:使用窗口、图形和文本 |
26、使用AWT控件、布局管理器和菜单 |
27、图像 |
28、并发使用工具 |
29、流API |
30、正则表达式和其它包 |
31、Swing简介 |
32、探究Swing |
33、Swing菜单简介 |
34、JavaFX GUI编程简介诶 |
35、探究JavaFX控件 |
36、JavaFX菜单简介 |