赞
踩
学习了有一段时间的java了,感谢我的恩师——李钦坤。那么今天来梳理下java的相关知识吧。
注意:手机app访问不支持目录跳转
主要是自己用来复习,有需要的可以收藏。
正文开始
a.人机交互
dos命令 | 功能 |
---|---|
cd | 在同一盘符下进行目录的任意切换 |
. | 代表当前目录 当在linux中执行一些当前目录的脚本时需要使用 ./xxx |
… | 代表上一级目录 |
md | 创建一个文件夹 |
rd | 删除对应的文件夹 |
echo | 将一段脚本或者文字输出到当前DOS窗口的命令提示行中 |
> | 代表将管道进行重定向 |
cls | 清屏 |
exit | 退出DOS命令 |
help | 可以单独使用也可以和其他命令结合使用 表示查看当前命令或者默认命令的使用方式 |
ping | 测试网络通畅 |
ipconfig | 查看当前电脑的所有网卡的状态及内容 |
path | 查看当前环境变量的主配置 |
其他命令 | 具体看使用的软件决定 |
b.java的代码结构
代码如下(示例):
public class 类名{
public static void main(String[] args){
//main方法当前程序的入口
}
}
用法如下:
// 单行注释
/*
多行注释 写在此处的注释可以换行
*/
/**
文本注释 主要用来生成javadoc文档 (API)
@param
@return
*/
符号 | 翻译 |
---|---|
\n | 换行 |
\r | 将光标移动至当前行的第一个字符位置 |
\t | 制表符Tab |
\" | 将“变成普通字符 |
\’ | 将’变成普通字符 |
\ | 将\变成普通字符 |
数据类型 变量名;//变量的声明
变量名 = 数值;//变量的赋值
//在初始化时 赋值
数据类型 变量名 = 数值;
在Java中的数据类型分为两大类:
- 基础数据类型
- 整型:
- byte(1字节)
- short(2字节)
- int(4字节,默认整型是int类型)
- long(8字节,使用long时,需要在数字后面添加L(推荐)或者l)
- 浮点类型:
- float( 4字节 单精度 使用float类型 需要在数字结尾添加 f 或者 F)
- double(8字节 双精度 默认所有的浮点数类型都是double类)
- 字符型:char( 2个字节 底层其实还是使用数字来进行存储的(编码) 使用一对 ‘’ 包裹起来的一个字符)
- 布尔类型:boolean 布尔类型 只有两个值 true真 false假
- 引用数据类型
- 所有的非基本数据类型都是引用数据类型
byte num1 = 12;
int num2 = num1;//自动类型转换
int num1 = 127;
byte num2 = (byte)num1;//强制类型转换 但是有数据溢出的风险 谨慎使用
//语法
小类型 变量 = (小类型) 大类型值;
注意:
1.byte,short,int --> long --> float --> double 按照从小到大的返回 会出现自动类型转换 都是数字类型
2.byte,short,int不会互相转换,它们三者在计算时会转换成int类型
算术运算符
+ - * / % ++ –
加减乘除都是正常计算,但除法/ 当符号两边都是整型时 结果是整数类型 ,如果任意一边是浮点类型 ,则结果是小数类型
++、-- 使用时要注意
int a = 10;
a++;//a++ ---> a=a+1;
System.out.println(a++);//11 先执行后运算
System.out.println(++a);//12 先运算后执行
赋值运算符
= += -= *= /= %=
= 指的是普通的赋值运算 会先计算= 右侧的内容 之后再将值赋值给左侧的变量 不能进行类型的自动提升
复合型赋值运算符
+= -= *= /= %= 在计算时 就是将左侧变量与右侧值进行运算符相关运算操作后 将值再赋值给左侧变量 计算过程中会出现自动类型提升,小心使用
关系运算符
> < >= <= == !=
所有的关系运算符的结果都是boolean类型
逻辑运算符
& | ! ^ && ||
符号 | 描述 |
---|---|
& | 当运算符两侧的值都为真 结果才为真 一个为假即为假 |
| | 当运算符两侧都为假 结果才是假 一个为真 结果为真 |
! | 放置在变量前面 用于改变当前变量的值 真的变假的 假的变真的 |
^ | 运算符两侧值相同时为假 不同时为真 |
&& | 短路与 判断依据和&相同 只是当第一个表达式能够确定整个运算结果时 则不再执行后面的表达式 可以提升运算效率 建议使用 |
|| | 短路或 判断依据和|相同 只是当第一个表达式能够确定整个运算结果时 则不再执行后面的表达式 可以提升运算效率 建议使用 |
- 任何类型的数据 和字符串进行拼接后 全部会转型成字符串类型 之后进行字符串拼接
- 可以使用该语法来快速转化字符串类型 eg. a+"";
//三目运算符 语法
//(条件表达式)?表达式1:表达式2 当条件表达式为真时 执行表达式1 反之执行表达式2
int age = 25;
System.out.println((age>=18)?"已成年":"未成年");
表达式:
在Java中 满足语法的前提下 将运算符和变量按照一定顺序排列的序列 可以称之为表达式
在表达式中 存在两个概念 表达式的值 表达式类型
优先级:
所有的运算符都有优先级问题, 无需特殊记忆 ,如果需要优先级 则可以使用()来自定义优先级顺序
Separator | . ( ) { } ; , |
---|---|
L to R | * / % |
L to R | + - |
L to R | << >> >>> |
L to R | < > <= >= instanceof |
L to R | == != |
L to R | & |
L to R | ^ |
L to R | | |
L to R | && |
L to R | || |
R to L | ++ – ~ ! (data type) |
R to L | ? : |
R to L | = *= /= %= += -= <<= >>= >>>= &= ^= |= |
/*语法 当满足条件时 执行 不满足时跳过: if(条件表达式){ 条件执行代码 } */ if(3<5){System.out.println("这是真的");} /* 只有两种结果的分支选择: if(条件表达式){ 条件符合时执行的代码 }else{ 条件不符合时执行的diamante } */ int age = 18; if(age >= 18){ System.out.println("已成年"); }else{ System.out.println("未成年"); } /* 在同一逻辑内部有多种结果分支 则使用一下方式: if(条件表达式1){ 条件符合1时执行的代码 }else if(条件表达式2){ 条件符合2时执行的diamante }else if(条件表达式3){ 条件符合3时执行的diamante }... else{ 以上条件都不满足 执行该区域代码 } */ int score = 70; if(score >= 0 && score < 60){ System.out.println("不及格,需要继续努力哦~~加油~~我看好你!!!"); }else if(score >= 60 && score <70){ System.out.println("刚及格,打了个擦边球,一不小心就挂了"); }else if(score >= 70 && score <80){ System.out.println("良好,还可以,莫骄傲,继续前进吧"); }else if(score >= 80 && score <90){ System.out.println("优秀,秀儿,你回来了"); }else if(score >= 90 && score <100){ System.out.println("666,打6就完事了"); }else if(score == 100){ System.out.println("完美,你本来就很美"); }else{ System.out.println("成绩都输不对,你个憨憨,赶紧核对成绩");
/* switch 语句 适用于 条件比较固定 且范围较小 而且是等值匹配的情况 优点 : 效率比if高 缺点 : 逻辑不灵活 比较死板 语法: switch(n){ case v1 : 执行语句1;[break;] case v2 : 执行语句2;[break;] case v3 : 执行语句3;[break;] ... default: 执行语句n;[break;] } 执行过程 当n 和下属case后面的某个值匹配时 会直接执行该case后面的执行语句 如果有break 则执行完跳出 如果没有break 则继续向下执行 直到遇到break或者程序块执行完毕 switch中 case以及default都没有先后顺序 但是建议按照一定的逻辑 排列case以及default n变量 在java中只能是 byte short char int 在JDK7之后 提供了 String 枚举类型 */ int num = 7; switch(num){ case 1:System.out.println("今天是星期一,一周的开始,不高兴");break; case 2:System.out.println("今天是星期二,才过了一天,好慢呀");break; case 3:System.out.println("今天是星期三,好不容易到中间了,在坚持一下");break; case 4:System.out.println("今天是星期四,还有一天,小激动");break; case 5:System.out.println("今天是星期五,明天就放假了,好高兴");break; case 6:System.out.println("今天是星期六,终于放假了,想干啥干啥");break; case 7:System.out.println("今天是星期日,明天又上班了,太快了");break; default: System.out.println("快醒醒,你要活在当下");break; }
语法:
while(条件表达式){
循环内容
}
//循环输出1-100
int i = 1;
while(i<=100){
System.out.println("当前输出的数字为:"+i++);
}
语法:
do{循环内容
}while(条件表达式);
//计算5-70 的和
int i = 5;
int sum = 0;
do{
sum += i++;
}while(i<=70);
System.out.println("5-70的和为:"+sum);
在条件满足的情况下 while和 do-while 执行方式和内容完全一致 只有当第> 一次条件不满足时 才会有执行区分 根据业务需求进行选择
一定注意 在使用while和do-while的语句时 不要写成死循环 一定要进行出> 口达式的描述
除非遇到死循环需求
语法:
for(初始化表达式 1; 条件表达式2 ; 出口表达式3 ){循环内容4
}
for循环一共可分为4个部分 执行顺序为
1 —》 2 ----》 4 —》 3 ----》 2 —》4 —》3 …… —》2 不满足条件 退出程序
/*
循环输出 1-100 的和
*/
int sum = 0 ;
for(int i = 1; i<=100 ;i++){
sum += i;
}
System.out.println("1-100的和为:"+sum);
//输入一个1-10 之内的数 之后从0开始循环至当前数字
int n = 6;
for(int i = 0 ; i <=10; i++){
System.out.println("当前数字为:"+i);
if( i == n){
System.out.println("到"+n+"了");
break;
}
}
//当循环执行到 n 时 忽略该次循环
for(int i = 0 ; i<= 10 ; i++){
System.out.println("当前数字为:"+i);
if( i == n){
System.out.println("到"+n+"了");
continue;
}
}
break 和 continue的使用 尤其是在嵌套循环中 出现的循环层级不同 结果
不同 每个关键字 只控制当前所在的那个循环层次
例如:
for(int i = 0 ; i < 5;i++){
if(i==3){
continue;
}
for(int j = 0 ; j < 5 ; j++ ){
if(j == 2){
continue;
}
System.out.println("当前是第"+(i+1)+"次循环,目前是该循环的第"+(j+1)+"次");
}
}
在其他语言中叫做函数,在Java中称之为方法。
在程序的开发过程中,希望将重复性的功能进行封装,打包成一个个的方法,每次可以重复使用。
[访问修饰符1 访问修饰符2] 返回值类型 方法名称(形参列表){
方法主体
[return 返回值;]
}
考虑封装一个方法时,主要考虑两个方面:
当前方法需要返回什么内容 ----- 返回值类型
如果一个方法有返回值,则必须声明当前返回值的类型,否则必须在返回值类型处声明void关键字
当前方法在执行时需要哪些未知数(原料) ---- 形参列表
参数:形式参数:方法声明时 用于同时调用者 当前需要哪种类型的参数 也称之为形参
实际参数:在使用者调用方法时 实际传入的参数 称之为实参 真正参与方法执行的参数
注意 方法内部不能再定义任何的方法 方法不能嵌套定义
定义:在当前方法内部调用方法自身
用法:
//典型的递归函数
public void test(){
test();
}
概念:将具有相同类型的一组数据放置在一个组里,这种数据结构,叫做数组
注意:
1. 是引用数据类型。
2. 数组中可以放置任何类型的数据 但是一但确定数据类型 就要保证内部的数据类型完全一致
//数组的声明
数据类型[] 变量名;//在Java中建议使用此种方式
数据类型 变量名[];
int[] a;
/*
数组的创建 需要使用关键字 new
*/
数据类型[] 变量名 = new 数据类型[数组长度];
数据类型[] 变量名 = {v1,v2,v3,…,vn};
//静态初始化
int[] x = {2,4,6,8,9};//--> 完整形式 int[] x = new x[]{2,4,6,8,9};
数据类型[] 变量名 = new 数据类型[长度];
根据下标(索引) 去依次初始化
可变参数:
在JDK5(含)之后,出现了可变参数,用来接收未知个数的参数
数据类型… 变量名
eg:(可变参数必须要放置在当前参数列表的最后一个参数位置)
public void add(String name,int... nums){
System.out.println("操作员:"+name);
//可变参数在操作时 按照数组方式操作
System.out.println("当前接受到的可变参数个数:"+nums.length);
int sum = 0;
for(int i = 0 ; i < nums.length;i++){
sum += nums[i];
}
System.out.println("所有可变参数的和为:"+sum);
}
for(元素类型 变量名 : 待遍历数组/集合){
> 操作变量名
}
public void forEach(int[] x ){
for(int n : x){
System.out.println(n);
}
}
注意: | forEach只能遍历 不能改变原数组内容 |
---|
概述:
在Java中并没有在底层实现二维数组的存储结构,只不过是在一维数组内部的元素仍是数组 二维数组
创建数组方式:
eg:
//创建二维数组
数据类型[][] 变量名 = new 数据类型[l1][l2];//l1是必须要写的长度 l2 可写可不写
//动态初始化
int[][] x = new int[3][3];
int[0][0] = 1;
int[0][2] = 3;
int[2][2] = 6;
//静态初始化
int[][] x = {{1,2,3},{3,4},{3}}
注意:
二维数组中 可以改变一维数组中的元素数组 但必须使用new关键字创建的数组才可以进行替换
int[][] x = {{2,3,4},{0}}
//error
x[1] = {1,2,3}
//正常
x[1] = new int[]{1,2,3}
//或者
x[1] = new int[3];
核心思想:求最值都需要将当前所有要比较的元素全部过滤一遍 只需要一个循环将所有数据进行比较即可
示例:
publc int max(int... x){
int temp = 0 ;
int i = 1;
for(int n : x){
if(i == 1){
temp = n;
i++;
continue;
}
if(temp < x){//最小值 就是改变判断符号 将小于改成大于即可
temp = x;
}
}
return temp;
}
冒泡排序
核心思想:每次比较相邻两个元素的值,之后将较大的放置在右侧(位置交换)
如果有n个待排序的元素 则一共需要比较n-1轮 每一轮需要比较 n-当前的轮数
示例:
public void BubbleSort(int... args ){
for(int i = 0 ;i < args.length-1;i++){
//一共比较多少次
for(int k = 0 ; k< args.length-1-i;k++){
//每轮比较的次数
//如果右侧的小于左侧的 就交换两个元素的位置
if(args[k]<args[k+1]){
int t = args[k];
args[k] = args[k+1];
args[k+1] = t;
}
}
}
}
示例代码:
//选择排序 public void selectSort(int[] x){ for(int i = 0 ; i < x.length -1;i++){ //控制比较轮数 int temp = i;//每轮先将最左侧元素下标当做最小下标 for(int j = i+1 ; j < x.length;j++){ //每轮比较多少次 if(x[temp] > x[j]){ //当前元素小于temp中的元素 需要更新temp中的最小下标值 temp = j; } } //如果当前temp大于i的值 表示最小小标更新 需要交换两个元素的位置 if(temp > i){ int t = x[temp]; x[temp] = x[i]; x[i] = t; } } }
核心算法:
//快速排序 /** 采用分治思想实现最终排序 */ public void fastSort(int[] x, int l, int h){ //使用迭代来进行分治 if(l < h){ int index = getIndex(x,l,h); fastSort(x,l,index-1); fastSort(x,index+1,h); } } /** 获取当前基准数据的最终位置 */ pubic int getIndex(int[] x, int l , int h){ int base = x[l];//每次获取最左侧数据当做基准数据 int low = l;//最低位置 int height = h;//最高位置 while(low < height){ //未找到基准数据的最终位置 //先从右至左开始查找小于基准数据的值 如果没有则height-- while(low < height && base < x[height]){ height--; } //找到小于基准数据的值后 进行赋值操作 if(low < height){ x[low] = x[height]; low++; } //再从左至右开始查找大于基准数据的值 如果没有则low++ while(low < height && base > x[low]){ low++; } if(low < height){ x[height] = x[low]; height--; } } //循环执行完毕后 产生low=height的情况 此时基准位置确定 进行赋值操作 x[low] = base; return low;//返回最终基准位置 }
两种查询算法:
1.遍历查询
2.二分查找
遍历查询
当前数组从左至右依次查找该元素 如果找到返回该元素下标,否则返回-1
缺点:数据量大时 查找成本高
示例代码:
//遍历查询
public int find(int[] x , int n){
for(int i = 0 ; i < x.length;i++){
if(x[i] == n){
//表示找到了
return i;
}
}
return -1;
}
//二分查找 public int halfFind(int[] x, int n ){ int low = 0;//最低位下标 int height = x.length-1;//最高位下标 while(low < height){ int middle = (low+height)/2; //判断当前查询数据和中间数据的关系 if(x[middle] < n){ //在右侧继续查找 low = middle + 1; }else if(x[middle] > n){ //在左侧继续查找 height = middle - 1; }else { return middle; } } return -1; }
考量一个算法的优劣主要通过两个维度来评判:
面向对象编程过程中,最底层,最核心的概念:类和对象。
类和对象相辅相成:
可以将所有的事物抽象成一个类,在使用时再实例化成一个对象。
在抽象成类的过程中,也需要参考很多对象,之后再根据已有对象,按经验进行封装。
生活中描述事物无非就是描述事物的属性和行为
在一个类中只需要考虑两个方面:
局部变量
范围:在当前代码块或者语句中
特点:
成员变量
范围::在当前所在类的范围内部,可以任意使用。
特点:
当在成员变量和局部变量名称冲突时 局部变量会在代码块内部范围内覆盖成员变量
在引用数据类型中,所有的成员属性都可以被默认初始化,具体初始化的值和数据类型有关
成员变量类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
char | ‘\u0000’ |
float | 0.0F |
double | 0.0D |
boolean | false |
所有的引用类型 | null |
概念
就是隐藏对象的属性和实现细节,仅对外提供公共访问方式。
优点
将变化隔离,便于使用,提高重用性,提高安全性。
封装原则:
将不需要对外提供的内容都隐藏起来。
把属性都隐藏,提供公共方法对其访问。
将客观事物的特征用java的语言描述出来,抽象只关注与当前主题有关的方面,忽略与目标无关的方面
一般类里面包含两类,成员属性和成员方法
访问修饰符 class 类名{
成员属性
成员方法
}
概念:在Java中 所有的类都需要通过new关键字抵用构造方法来创建出一个个的对象
构造方法是在类里面创建的
构造器就是用来创建对象的 帮助初始化成员属性
语法
访问修饰符 方法名(){
初始化成员属性
}
要求
一般要访问修饰符为public,并且方法名与类名完全相同。
构造方法分类
无参构造
a.在java中,如果没有手动声明任何构造器,则JVM在编译过程中会自动添加一个无参构造,以便程序员快速新建对象。
b.如果程序员手动创建了一个构造器,则JVN不再管理任何构造器,后期只能根据已有的构造器进行对象的创建。
含参构造
需要根据需求 在构造器中对成员属性进行初始化定义 需要在构造方法的参数列表中 添加所有要初始化属性的参数 之后在构造器中去进行初始化过程
建议:如果手动创建了含参构造 一定要在去手动添加一个无参构造
当我们写好一个方法之后,该如何去调用呢?
如果对象是
成员方法:可以使用对象名(变量名).方法 使用. 成员方法一般是为了解决某个功能而封装的方法
构造方法:必须使用new 关键字来进行调用 构造方法只是用来创建对象使用
什么是成员方法和构造方法?
成员方法指的是本类新建的方法,而构造方法是在另一个类新建的,另一个
类可以是写在同一个文件里面,也可以写在不同文件里面。
引用数据类型的变量指针索引储存在栈中,而真实的值储存在堆中。
而基本数据类型的值直接储存在栈中。
栈与队的区别
栈结构:后进先出(弹夹模型),执行完毕后自动回收垃圾。
堆结构:先进先出(手枪模型),很大的可共享空间,执行完毕后会等待JVM的垃圾回收。
举例
public class Student{
……
public static void main(String[] args){
Student stu = new Student(“张三”, 18, ‘男’, “精英班”, 10);
stu.displayInfo();
Student stu2 = new Student(“李四”, 23, ‘女’, “预热班”, 18);
stu2.displayInfo();
}
}
数据在内存的位置如图:
概念:指同一个类中可以有方法名相同名字的多个方法,调用时会根据参数不同选择相应的方法。
好处:方便使用者调用,优化了程序的设计。
构成语法重载的条件:
在一个类中 普通方法和构造方法均可以重载
• 每个类的每个非静态方法(没有被static修饰)都会隐含一个this引用名称,它指向调用这个方法的对象
• 当在方法中使用本类的属性时,都会隐含地使用this名称,当然也可以明确指定。
• this可以看作是一个变量,它的值就是当前对象的引用
举例
//Persion类的构造方法
public Person(String n, boolean s, int a){
this.name = n;
this.sex = s;
this.age = a;
}
public class Employee {
private String name; //姓名
private int age; //年龄
private double salary; //薪水
public Employee(String name, int age, double salary){ //构造方法
this.name = name;
this.age = age;
this.salary = salary;
}
}
public class Employee {
private String name; //姓名
private int age; //年龄
private double salary; //薪水
public Employee(String name, int age, double salary){ //构造方法1
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(){ //构造方法2
this(“无名”, 18, 800.0); //调用到了构造方法1
}
}
作用:
为了便于管理大型软件系统中数目众多的类,解决类命名冲突的问题
使用
在package语句中,用"."来指明包(目录)的层次。包对应着文件系统的目录层次结构。
如:package com.liqk; -->编译后对应的类文件位于com\liqk目录下。
导入某个包中的所有类使用:包名.*
注意
同一包中的类之间直接引用,无需import语句
无名包中的类,无法在其它带包的类中使用
建议:自定义类都要放置在包中。
JDK常用包介绍
java三大特性之一 继承 在逻辑上具有 is-a 关系的类 可以声明成继承关系
继承好处:代码的复用性 扩展性
使用关键字 extends 来表示当前父类是谁
在Java中只有单一继承 :一个子类只能有一个父类 而一个父类 可以派生出多个子类
但继承关系可以向下传递 父 —> 儿 -->孙 -->…–>n
示例:
class Employee { private String name; //姓名 private double salary = 2000.0; //薪水 public Employee(String name, double salary){ this.name = name; this.salary = salary; } public Employee(){} public double getSalary(){ return salary; } } class Manager extends Employee{ private double bonus; //奖金 public void setBonus(double bonus){ this.bonus = bonus; } } public class InheritanceTest { public static void main(String [] args){ Manager manager = new Manager(); manager.getSalary(); } }
解释
父类中除了构造器之外其他的所有信息都能被子类继承
子类在继承父类的信息后 可以在原有基础上进行扩展 扩展的内容只能子类自己使用 父类无法访问
在创建子类的过程中 一定会先创建父类 如果没有显示声明 则会默认掉用父类的无参构造来进行创建
如果父类没有无参构造 则必须手动显示声明调用父类的已有构造器
在Java中 除了this关键字 之外 还有一个super关键字
super 只能出现在继承关系的子类中 表示某个参数或者某个方法 是父类的属性或方法
super关键字 在一般情况下可写可不写 一般不写
必须要声明super关键字的两种情况
class Employee { … public void displayInfo(){ System.out.println(“name=” + name); } } class Manager extends Employee{ private double bonus; //奖金 private String position; //职位 public Manager(String name, double salary, String position){ super(name, salary); this.position = position; super.displayInfo(); } public void setBonus(double bonus){ this.bonus = bonus; } }
class Employee { private String name; //姓名 private double salary = 2000.0; //薪水 public Employee(String name, double salary){ this.name = name; this.salary = salary; } public Employee(){} public double getSalary(){ return salary; } public void displayInfo(){ System.out.println(“name=” + name); } } class Manager extends Employee{ private double bonus; //奖金 private String position; //职位 public Manager(String name, double salary, String position){ super(name, salary); this.position = position; } public void setBonus(double bonus){ this.bonus = bonus; } }
所有的类、属性、方法都是有使用范围的 由访问修饰符类决定
范围
位置 | private | 默认 | protected | public |
---|---|---|---|---|
同一个类 | 是 | 是 | 是 | 是 |
同一个包内的类 | 是 | 是 | 是 | |
不同包内的子类 | 是 | 是 | ||
不同包并且不是子类 | 是 |
当子类不满足父类提供的方法时 需要重写父类方法 该过程叫做方法的重写 override
在重写中 Java专门定义了一个注解 @Override 用来在编译过程中校验当前方法是否构成重写
构成重写的语法条件
示例
class Employee { private String name; //姓名 private double salary = 2000.0; //薪水 public Employee(String name, double salary){ this.name = name; this.salary = salary; } public double getSalary(){ return salary; } } class Manager extends Employee{ private double bonus; //奖金 private String position; //职位 public Manager(String name, double salary, String position){ super(name, salary); this.position = position; } public void setBonus(double bonus){ this.bonus = bonus; } public double getSalary(){ return super.getSalary() + bonus; } }
对于希望数据共享的属性或方法 可以使用static关键字进行修饰 其内容会随着类的加载而初始化 整个程序只有一份static数据,
static既可以修饰属性 该属性可被共享
static也可修饰方法 该方法只能调用static修饰的成员 而不能调用非static修饰的内容
static修饰的内容 可以称之为类下属的属性和方法
两种调用方式:
对于static修饰的属性 提供了静态代码块 可以进行静态属性初始化
public class Dog{ private static int count = 100; private int id; private String name; public Dog(String name){ this.name = name; id = count++; } public void displayInfo(){ System.out.print(“名字是:” + name + 编号是" + id); } public static void currentCount(){ System.out.println("当前编号是" + count); } public static void main(String[] args){ Dog.currentCount(); Dog dog = new Dog("小黑"); dog.displayInfo(); Dog dog2 = new Dog(“旺财”); dog2.displayInfo(); Dog.currentCount(); } }
注意:
static 修饰的方法中 不能使用this和super关键字
static代码块 每次类加载时 只执行一次 而普通代码块 随着对象的创建 每次都会执行
执行顺序:
静态代码块 --> 普通代码块 -->构造方法 --> 普通代码块 --> 构造方法 …
示例
public class Student{ private static String country; private String name; static { country = "中国"; System.out.println("Student类已经加载"); } public Student(String name){ this.name = name; } public void displayInfo(){ System.out.println("我的名字是:" + name + ",国家是:" + country); } public static void main(String[] args){ Student stu = new Student("张三"); stu.displayInfo(); } }
final修饰的变量(成员变量或局部变量)的值不能改变,即等同于常量,在定义时就必须要进行初始化,之后就再也不改变它的值了。
在后期版本中可以将声明和初始化分开执行 但是每个步骤都不能省略 且一旦赋值完成后 值不能修改
final修饰方法的参数叫最终参数。调用这个方法时给最终参数赋值之后,在方法体内再也不能改变它的值。
注意:
示例:
public class TestFinal {
public static void main(String[] args) {
T t = new T();
//t.i = 100;
}
}
final class T {
final int i = 10;
public final void m() { //j = 11; }
}
class TT extends T {}
在项目中经常会遇到一些功能性的类,那么可以将该类制作成一个单例模式的类,全项目只适用这一个类来提供服务,节省内存开销。
常见的写法
示例 :
饿汉式:
//饿汉式 --- 简单易用 建议使用
public class Singleton01{
//私有化构造器
private Singleton01(){}
//提供私有静态变量 用来接收单例对象
private static Singleton01 s1 = new Singleton01();
//提供静态公共访问方式
public static Singleton01 getInstance(){
return s1;
}
}
//懒汉式 对于饿汉式的加载时创建对象 懒汉式提出使用时创建对象 但存在线程安全问题
public class Singleton02{
private Singleton02(){}
private static Singleton02 s2 ;
public static Singleton02 getInstance(){
if(null == s2){
s2 = new Singleton02();
}
return s2;
}
}
Java三大特性之一
多态: 同一个事物具有不同形态
对象的多态性:
判定一个父类是否指向某个具体的子类 需要使用关键字 instanceof 返回值为boolean
语法:对象变量名 instanceof 类名(或接口名)
class Animal { private String name; Animal(String name) { this.name = name; } public String getName(){ return name; } } class Cat extends Animal { private String eyesColor; Cat(String n,String c) { super(n); eyesColor = c; } public String getEyesColor(){ return eyesColor; } } class Dog extends Animal { private String furColor; Dog(String n,String c) { super(n); furColor = c; } public String getFurColor() { return furColor; } } public class CastingTest{ public static void main(String args[]){ Animal a = new Animal("动物"); Cat c = new Cat("猫","black"); Dog d = new Dog("狗","yellow"); System.out.println(a instanceof Animal); System.out.println(c instanceof Animal); System.out.println(d instanceof Animal); System.out.println(a instanceof Cat); //向上转型 Animal an = new Dog("旺财","yellow"); System.out.println(an.getName()); System.out.println(an.getFurColor()); //error! System.out.println(an instanceof Animal); //true System.out.println(an instanceof Dog); //true //向下转型,要加强制转换符 //-- 为了安全起见,要加 instanceof 判断 Dog d2 = (Dog)an; //Cat c2 = (Cat)an; System.out.println(d2.getFurColor()); } }
示例
class Animal { private String name; public Animal(String name) {this.name = name; } public void enjoy(){ System.out.println("叫声......"); } } class Cat extends Animal { private String eyesColor; public Cat(String n) {super(n);} public void enjoy(){ System.out.println("喵~喵~"); } } class Dog extends Animal { private String furColor; public Dog(String n) {super(n);} public void enjoy(){ System.out.println("汪~汪~"); } } class Lady{ private String name; private Animal pet; public Lady(String name, Animal pet) { this.name = name; this.pet = pet; } public void myPetEnjoy(){ pet.enjoy(); } } public class DynamicBindingTest { public static void main(String args[]){ Cat c = new Cat("catname","blue"); Lady l = new Lady(“张女士”, c); l.myPetEnjoy(); } }
当一个类中有某些方法不知道如何实现时,可以将该方法定义成抽象方法,只需使用abstract修饰符进行修饰
抽象方法形式:(没有方法体)
[访问修饰符] abstract 返回类型 方法名(参数列表);
在一个类中如果出现了抽象方法 则该类 必须是抽象类
声明抽象类语法:
[访问修饰符] abstract class 类名{…… }
抽象类特征:
用到抽象类其实就是面向父类编程的思想
抽象类的使用:
无法直接通过new来创建对象 需要定义一个类
继承自抽象类 之后重写抽象类中所有未实现方法
如果有任意一个 抽象方法未实现 则当前类也必须定义为抽象类
示例:
abstract class Shape { //形状类 protected double length; //长 protected double width; //宽 public Shape(double length, double width){ this.length = length; this.width = width; } public abstract double area(); //计算面积 } class Rectangle extends Shape { //矩形 Rectangle(final double num, final double num1) { super(num, num1); } public double area() { return length * width; } } class Triangle extends Shape{ //三角形 Triangle(final double num, final double num1) { super(num, num1); } public double area() { return length * width/2; } } public class TestAbstract{ public static void main(String[] args){ … } }
思考:
抽象类中可不可以没有抽象方法?
有抽象方法的类可以可以不是抽象类?
接口就是某个事物对外提供的一些功能的申明 。相当于一个说明书
可以利用接口实现多态,同时也弥补Java单一继承的弱点
使用interface关键字定义接口。
JDK8之前,在接口中所有的方法必须都是抽象方法 且修饰符必须是 public abstract
所有的属性 都必须是静态常量 修饰符为 public static final
如果不定义修饰符 则JVM会在编译过程中自动添加 但是不能出现和语法修饰符相悖的修饰符
接口没有构造方法,所以不能被实例化(不能用来创建对象)。
//方法接口
public interface Runner{
public void run();
}
//定义常量的接口
public interface Constants{
public static final int COLOR_RED = 1;
public static final int COLOR_GREEN = 2;
public static final int COLOR_BLUE = 3;
}
每个类只能有一个父类,但可以实现多个接口。如果实现多个接口,则用逗号隔开接口名称,如下所示:
class Car implements Runner, Constants
一个类实现了一个接口,它必须实现接口中定义的所有方法,否则该类必须声明为抽象类。
接口可以继承自其它的接口,并添加新的常量和方法。接口支持多重继承。
示例:
class Car implements Runner,Constants{ //实现两个接口 public void run(){ System.out.println("车颜色是:" + COLOR_RED); System.out.println("用四个轮子跑..."); } } interface Animal extends Runner{ //接口的继承 void breathe(); //呼吸 } class Fish implements Animal{ public void run(){ System.out.println("颜色是:" + COLOR_BLUE); System.out.println(“游啊游..."); } public void breathe(){ System.out.println("冒气泡来呼吸"); } }
声明在类的内部的类称之为嵌套类(nested class)
//语法定义
[public] class OuterClass{
...
[public|proteceted|private] [static] class NestedClass{
...
}
}
嵌套类在编译后会生成OuterClass$NestedClass.class类文件
内部类:
内部类作为外部类的一个成员存在,与外部类的成员变量、成员方法并列
示例:
class Outer { private int outer_i = 100; private int j = 123; public void test() { System.out.println("Outer:test()"); } public void accessInner(){ Inner inner = new Inner();//外部类中使用内部类也需要创建出它的对象 inner.display(); } public class Inner { private int inner_i = 100; private int j = 789; //与外部类某个属性同名 public void display() { //内部类中可直接访问外部类的属性 System.out.println("Inner:outer_i=" + outer_i); test(); //内部类中可直接访问外部类的方法 //内部类可以用this来访问自己的成员 System.out.println("Inner:inner_i=" + this.inner_i); System.out.println(j); //访问的是内部类的同名成员 //通过“外部类.this.成员名”来访问外部类的同名成员 System.out.println(Outer.this.j); } } } //调用测试 public class MemberInnerClassTest { public static void main(String[] args) { Outer outer = new Outer(); outer.test(); outer.accessInner(); //在外部类以外的地方创建内部类的对象 Outer.Inner inner = outer.new Inner(); inner.display(); } }
[public ] class OuterClass{
...
static class StaticNestedClass { //静态嵌套类
...
}
}
方法类
可访问它所在方法中的final参数和final局部变量
由于线程与并发的原因,局部内部类仅能用方法中的final参数和final局部变量。
public class Outer{
public void test(final int y){
final int x = 9;//可以访问final修饰的变量
class FunctionInner{//方法类(局部内部类)
public void show()
{
System.out.println(x+" "+y);
}
}
FunctionInner f = new FunctionInner();//创建方法类对象
f.show();//调用方法类中的方法
}
public class AnnonymoseInnerClassTest { public static void main(String[] args) { (new AClass("redhacker") { public void print() { //对父类的print方法进行覆盖 System.out.println("the anonymose class print"); super.print(); //调用父类中的print方法 } }).print(); //调用覆盖后的print方法 } } class AClass { private String name; AClass(String name) { this.name = name;} public void print() { System.out.println("SuperClass:The name = " + name); } }
简介:异常是程序在运行期发生的不正常的事件,它会打断指令的正常执行流程。
设计良好的程序应该在异常发生时提供处理这些不正常事件的方法,使程序不会因为异常的发生而阻断或产生不可预见的结果。
在程序中顶级的异常类型 叫做Throwable 所有的异常都是通过该类衍生而来 所以也称之为 万恶之源
分类如图
按照严重性来分
JVM只能处理Exception,而对Error无能为力。
从编程角度分
最直观判定受检非受检 看程序是否强制进行异常捕获
异常的抛出默认情况下是遇到错误时自动抛出给JVM
捕获操作需要程序员手动处理,语法构成:
try{ ...... //可能产生异常的代码 }catch(ExceptionName1 e ){ ...... //异常的处理代码 }catch(ExceptionName2 e ){ ...... //异常的处理代码 }finally{ ......//无论如何都会执行的语句 }
try块中的内容越少越好 因为try中的代码执行开销较大
对于一个try块 可以同时捕获多个异常类型 即 使用多个catch进行捕获
示例:
try{
String a = null;
a.split(",");//空指针异常
int[] x = {2,4,6,7}
x[4] = 10;//数组下标越界
}catch(NullPointException e){
//获取异常的信息
System.out.println(e.getMessage());
//打印异常的堆栈信息
e.printStackTrace();
//输出自定义信息
System.out.println("不能为空");
}catch(ArrayIndexOutOfBoundException e){
e.printStackTrace();
}
public class TestException2{ public int calculate(int num1, int num2) { int result = num1 / num2; return result; } public static void main(String[] args){ TestException2 test = new TestException2(); try{ int i = test.calculate(100, 10); System.out.println(i); }catch(Exception e){ System.out.println("父类异常.."); }catch(ArithmeticException e){ // 错误–不能到达的代码 Sysetm.out.println("出异常啦"); e.printStackTrace(); }finally{ System.out.println(“finally语句块是始终要执行的"); } } }
throw关键字用在方法代码中主动抛出一个异常
如果方法代码中自行抛出的异常是受检异常,则这个方法要用throws关键字声明这个异常。
throws用来声明一个方法可能会抛出的所有异常。跟在方法签名的后面。
如果一个方法声明的是受检异常,则在调用这个方法之处必须处置这个异常(谁调用谁处理),继续用throws向上声明。
注:重写一个方法时,它所声明的异常范围不能被扩大
import java.io.*; import java.sql.*; class E { public String[] createArray(int length) { //error!! if (length < 0) { throw new Exception("数组长度小于0,不合法"); } else { return new String[length]; } } public void test() { createArray(10); } public void readFile() throws IOException,SQLException { } } /*class EE extends E { public void readFile() throws Exception { } }*/ public class TestException3 { public static void main(String[] args) { … } }
创建自定义异常
public class MyException extends Exception {
public MyException() { super(); }
public MyException(String msg) { super(msg); }
public MyException(Throwable cause) { super(cause); }
public MyException(String msg, Throwable cause) { super(msg, cause); }
}
public String[] createArray(int length) throws MyException {
if (length < 0) {
throw new MyException("数组长度小于0,不合法");
}
return new String[length];
}
观察抛出的异常的名字和行号很重要。
调用内置类库中某个类的方法前,阅读其API文档了解它可能会抛出的异常。然后再据此决定自己是应该处理这些异常还是将其加入throws列表。
应捕获和处理那些已知如何处理的异常,而传递那些不知如何处理的异常。
尽量减小try语句块的体积。
在处理异常时,应该打印出该异常的堆栈信息以方便调试。
进程(Process):每个独立执行的程序称为进程。
多进程: 在操作系统中能同时运行多个任务(程序)
线程(Thread):程序内部的一条执行路径。
多线程: 在同一应用程序中多条执行路径同时执行
线程和进程的区别
在Java中有三种线程类型
继承Thread类方式、并重写run方法
示例:
public class MyThread extends Thread{
//重写run方法
@Override
public void run(){
//多线程执行代码
}
}
public class MyRunnable implements Runnable{
@Override
public void run(){
//多线程执行代码
}
}
public class Mycallable<T> implements Callable<T>{
@Override
public T call() throws Exception{
//执行多线程内容
return T;//返回最终值
}
}
线程的启动
线程可以通过Thread类来进行启动,如果线程较多可以交给线程池来管理(后面再说)。
通过Thread来开启线程
public class StartThread{ public void static main(String[] args){ //参照上面描述的三种线程来分别启动 //1、继承Thread MyThread mth = new MyThread(); mth.start(); //2、实现Runnable接口 MyRunnable mrun = new MyRunnable(); Thread rth = new Thread(mrun); rth.start(); //3、实现Callable接口 Mycallable<Integer> mcall = new Mycallable<>(); FutureTask<Integer> ft = new FutureTask<>(mcall); Thread cth = new Thread(ft); cth.start(); //3中的取值问题 Integer num = ft.get();//Future的get方法会阻塞线程 直到接收到值 } }
用户线程:默认创建的都是用户线程 ,用于完成指定任务的一个线程。
守护线程:在没有其它用户线程在运行时会自动退出
对于线程来说,有6中状态,在Java中专门对6中状态定义了一个枚举类型 Thread.State
从生命周期来说
从执行或阻塞的角度来说
示意图
程调度器,根据系统分为两种:
抢占式模型:根据多个线程的优先级、饥饿时间(已经多长时间没有被执行了),计算出每个线程的总优先级,线程调度器就会让总优先级最高的这个线程来执行。
分时间片模型:所有线程排成一个队列。线程调度器按照它们的顺序,给每个线程分配一段时间(毫秒级)。
如果在时间片结束时线程还没有执行完,则它的执行权会被调度器剥夺并分配给另一个线程;
如果线程在时间片结束前阻塞或结束,则调度器立即进行切换。
为了表示不同线程对操作系统和用户的重要性,Java定义了10个等级的优先级。
•用1-10之间的数字表示,数字越大表明线程的级别越高
•三个静态成员变量:MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY(默认值)
优先级的操作
void setPriority(int newPriority); //要在start()前调用
注意:不要依赖优先级来控制线程的执行顺序。
线程睡眠:
线程让步:
示例:
public class ThreadYieldTest { public static void main(String[] args) { System.out.println("主线程:" + Thread.currentThread().getName()); Thread thread1 = new Thread(new MyThread2()); thread1.start(); Thread thread2 = new Thread(new MyThread2()); thread2.start(); } } class MyThread2 implements Runnable{ public void run() { for(int i = 0; i < 100; i++){ System.out.println(Thread.currentThread().getName()+ ":" + i); if(i % 10 == 0){ Thread.yield(); //线程让步 } } } }
线程加入:
示例:
public class ThreadJoinTest { public static void main(String[] args) { Thread thread1 = new Thread(new MyThread3()); thread1.start(); for (int i = 1; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); if (i == 50) { try { thread1.join(); //线程合并 } catch (InterruptedException e) { e.printStackTrace(); } } } } } class MyThread3 implements Runnable{ public void run() { for (int i = 1; i <= 50; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程停止指定是让长睡眠或者死循环的线程终止退出
通过改变逻辑标识可以结束死循环的线程
public class ThreadEndTest { public static void main(String[] args) { SomeThread some = new SomeThread(); Thread thread1 = new Thread(some); thread1.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } some.terimate(); } } class SomeThread implements Runnable { private boolean flag = true; public void terimate() { this.flag = false; } public void run() { while (flag) { System.out.println("...run..."); } } }
public class ThreadEnd2Test { public static void main(String[] args) { Thread thread1 = new Thread(new SomeThread2()); thread1.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); } } class SomeThread2 implements Runnable{ public void run(){ System.out.println("sleep........."); try { Thread.sleep(9999); } catch (InterruptedException e) { System.out.println(" interrupted..."); e.printStackTrace(); } } }
如果程序因为输入/输出的等待而阻塞,基本上必须等待输入/输出的动作完成才能离开阻塞状态。无法用interrupt()方法来使得线程离开run()方法,要想离开,只能通过引发一个异常。
原理:
• 多个线程需要共享对同一个数据的访问。如果每个线程都会调用一个修改共享数据状态的方法,那么,这些线程将会互相影响对方的运行。
•在Java语言中,引入对象互斥锁的概念,保证共享数据操作的完整性。
•每个对象都对应一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问对象。
多线程同步的实现
synchronized同步代码块
当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
synchronize可以用来同步方法 :
public synchronized void sale(){
…
}
synchronized(obj){
需要同步的代码;
}
synchronized在同步时需要指定一个唯一的对象,此时才能实现当前的线程同步操作,如果锁的对象不唯一,则不构成互斥锁。建议使用当前类的class对象
说明: synchronized修饰方法时 其锁的对象时 this 当锁this时一定要注意当前是否只有一个Runnable对象
//使用synchronized加锁 class MyThread implements Runnable{ private int num = 1000; @Override public void run() { while(true){ synchronized (MyThread.class){ if(num > 0){ System.out.println(Thread.currentThread().getName()+"--当前数字减一,结果为:"+(--num)); } } try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
使用Lock来同步代码
显式加锁:JDK5.0。锁的粒度越细越好
Lock是JDK5推出的一个新锁 使用灵活 轻量级锁 常用其实现类ReentrantLock来获取实例
示例:
private static final Lock lock = new ReentrantLock(); //创建Lock实例
lock.lock(); //获取锁
...
lock.unlock(); //释放锁
//使用Lock加锁 class MyThread implements Runnable{ private int num = 1000; private Lock lock = new ReentrantLock();//创建出一个锁对象 @Override public void run() { while(true){ lock.lock(); if(num > 0){ System.out.println(Thread.currentThread().getName()+"--当前数字减一,结果为:"+(--num)); }else{ break; } lock.unlock(); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
编程技巧:在方法中尽量少操作成员变量,多使用局部变量
线程等待
Object类中的wait() throws InterruptedException 方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()唤醒方法。
线程唤醒
Object 类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。
Object类中的notifyAll()方法,唤醒在此对象监视器上等待的所有线程。
这三个方法只能在被同步化(synchronized)的方法或代码块中调用
示例:生产者-消费者模式
java.util.Timer类提供了基本的调度功能。这个类允许你调度一个任务(通过java.util.TimerTask子类定义)按任意周期运行。
java.util.Timer类:代表一个计时器
java.util.TimerTask抽象类:表示一个计时器任务
示例
public class TimmerTest { public static void main(String[] args) { Timer t = new Timer();//将Timer比作Thread 需要在Timer中运行TimeTask(Runnable) //当前任务延时多久后第一次执行 第二个参数代表当前任务每隔多久执行一次 Date d = new Date(); long l = d.getTime() + 5000; Date dd = new Date(l); // t.schedule(new MyTime(),dd,2000); t.scheduleAtFixedRate(new MyTime(),dd,2000); System.out.println("主线程执行至此"); } } class MyTime extends TimerTask{ @Override public void run() { System.out.println("我被打印了"); } }
Thread创建线程的缺点
一些重要的类
ExecutorService: | 真正的线程池接口。 |
---|---|
ScheduledExecutorService | 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。 |
ThreadPoolExecutor | ExecutorService的默认实现。 |
ScheduledThreadPoolExecutor | 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 |
线程工厂 | 描述 |
---|---|
newSingleThreadExecutor | 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照指定顺序(FIFO, LIFO,优先级)执行。 |
newFixedThreadPool | 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 |
newCachedThreadPool | 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程, 当任务数增加时,此线程池又可以智能的添加新线程来处理任务。 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 |
newScheduledThreadPool | 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求 |
方法名 | 描述 |
---|---|
execute() | 这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行 |
submit() | 这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,最终利用了Future来获取任务执行结果 |
shutdown() | 不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务 |
shutdownNow() | 立即尝试终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务 – 最终目的打断线程的长睡眠 |
泛型:就是在定义类、接口、方法、方法参数或成员变量时,指定它的操作对象的数据类型为一个参数。
在具体使用类、接口、方法、方法参数或成员变量时,将这个参数用具体的某一数据类型来代替。
泛型的好处:它在编译时进行类型安全检查,并且在运行时所有的转换都是强制的、隐式的。提高了代码的重用率。
类(接口)泛型
方法泛型
类(接口)泛型定义语法
class 类名<类型参数的名称> { …}
interface 接口名<类型参数的名称> { …}
说明:类型参数的名称建议使用单个大写字母。如常用的名有:
当某个类的父类是泛型类时:这个子类要把类型参数传递给父类;也可以把子类定义成特定于指定类型的,这个子类就不再是泛型类了。
class SuperClass<T>{
private T o;
public SuperClass(T o){ this.o = o; }
public String toString(){ return "T:" + o; }
}
/** 泛型类的继承 */
class SubClass <T> extends SuperClass<T>{
public SubClass(T o){ super(o); }
}
class SpecialSubClass extends SuperClass<String>{
public SpecialSubClass(String o){ super(o); }
}
interface MyGenericTypeInterface<T>{ }
class MyImplement<T> implements MyGenericTypeInterface<T>{}
class IntegerImplement implements MyGenericTypeInterface<Integer>{ }
访问控制符 [修饰符] <类型参数列表> 返回值类型 方法名(参数列表)
示例
public static <T extends Number> void max(T... args){
T temp = (T)Integer.valueOf(0);
for(T t : args){
if(t.doubleValue() > temp.doubleValue()){
temp = t;
}
}
System.out.println(temp);
}
普通泛型(默认):无界泛型
有界泛型(手动设置):泛型只支持上界泛型 使用关键字 extends 表示当前的类型只能是某个类或器子类类型,示例:
class Statistics<T extends Number> {
private T[] arrs;
public Statistics(T[] arrs) {
this.arrs = arrs;
}
public double count() { //计算数组中元素数值的总和
double sum = 0.0;
for (int i = 0; i < arrs.length; ++i) {
sum += arrs[i].doubleValue();
}
return sum;
}
}
当使用泛型类或接口声明属性、局部变量、参数类型或返回值类型时,可使用通配符来代替类型参数
通配符和泛型的区别
泛型:通常只在某个是个的固定类型
通配符: 在当前不能确定该类型是什么
通配符语法:<?>
通配符的界限
默认通配符为无界通配符
可手动设置边界:
JDK1.5以前的版本中没有泛型,为了保证对以前版本的兼容,Java采用了被称为擦除的方式来处理泛型。
当Java代码被编译成字节码时,泛型类型的定义信息会被删除(擦除),而使用界限类型或Object来代替泛型参数。
在使用泛型类型时,也会用相应的强制转换(由类型参数来决定)以维持与类型参数的类型兼容。
因为,在擦除时基本类型无法用Object类来代替。可以使用基本类型的包装类来代替它们。
因为静态成员独立于任何对象,是在对象创建之前就已经存在了,此时,编译器根本还无法知道它使用的是哪一个具体的类型。
Java代码中不能抛出也不能捕获泛型类的异常。
Generic arr[] = new Generic[10];
public class A{ T a = new T(); //编译报错}
集合:能够动态存放多个数据的存储结构,Java提供的一个公共API,存在于java.util包中
整个集合的架构如图:
主要有两个分支:
Collection 每次只能存入一个值 具体的存储性质和不同的子接口有关
Set 存入的元素无序且不重复 常用实现类有
List 存入的元素有序且可以重复 常用实现类有
Map 每次需要存入一个键值对
Collection是所有单个存储元素的一个顶级类 常用方法:
方法名 | 解释 |
---|---|
int size(); | 返回此collection中的元素数。 |
boolean isEmpty() | 判断此collection中是否包含元素。 |
boolean contains(Object obj); | 判断此collection是否包含指定的元素。 |
boolean contains(Collection<?> c); | 判断此collection是否包含指定collection中的所有元素。 |
boolean add(E element); | 向此collection中添加元素 |
boolean addAll(Collection<? extends E> c); | 将指定collection中的所有元素添加到此collection中 |
boolean remove(Object element); | 从此collection中移除指定的元素。 |
boolean removeAll(Collection<?> c); | 移除此collection中那些也包含在指定collection中的所有元素。 |
void clear(); | 移除些collection中所有的元素。 |
boolean retainAll(Collection<?> c); | 仅保留此collection中那些也包含在指定collection的元素。 |
Iterator iterator(); | 返回在此collection的元素上进行迭代的迭代器。 |
T[] toArray(T[] arr); | 把此collection转成数组。 |
自从JDK5开始,所有Iterator方式实现的变量都可以使用 forEach方式来进行遍历
根据对象的哈希码值计算出它的存储索引,在散列表的相应位置(表元)上的元素间进行少量的比较操作就可以找出它。
Set要注意的事项
- Set系的集合存、取、删对象都有很高的效率。
- 对于要存放到Set集合中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法以实现对象相等规
Set常用子接口
实现List接口的集合类中的元素是有序的,且允许重复。
List集合中的元素都对应一个整数型的序号记载其在集合中的位置,可以根据序号存取集合中的元素。
JDK API所提供的List集合类常用的有:
ArrayList
LinkedList
常用方法
public Object get(int index) 返回列表中的元素数
public Object add(int index, Object element); 在列表的指定位置插入指定元素.将当前处于该位置的元素(如果有的话)和所有后续元素向右移动
public Object set(int index, Object element) ; 用指定元素替换列表中指定位置的元素
public Object remove(int index) 移除列表中指定位置的元素
public ListIterator listIterator() 返回此列表元素的列表迭代器
remove(Object obj) 如果obj存在则直接移除
remove(int index) 移除指定位置处的元素 并将元素返回给调用者
ArrayList
使用数组结构实现的List集合
优点:
对于使用索引取出元素有较好的效率
它使用索引来快速定位对象
缺点:
因为使用了数组,需要移动后面的元素以调整索引顺序
如图:
优点:
对频繁的插入或删除元素有较高的效率
适合实现栈(Stack)和队列(Queue)
缺点:
在集合中,Map和Collection是一个并列的关系,它内部只能存储键值对(key-value)。且key的值不能重复且是无序的,value无要求。
常用实现类
HashMap 内部使用哈希表对 “键-值”映射对 进行散列存放。
LinkedHashMap 是HashMap的子类
使用哈希表存放映射对
用链表记录映射对的插入顺序。
Map实现类中存储的“键-值”映射对是通过键来唯一标识,Map底层的“键”是用Set来存放的。
所以,存入Map中的映射对的“键”对应的类必须重写hashcode()和equals()方法。常用String作为Map的“键”。
Vector
旧版的ArrayList,它大多数操作跟ArrayList相同,区别之处在于Vector是线程同步的。
它有一枚举方式可以类似Iterator进行遍历访问,也可以使用forEach或者Enumcation(JDK1.0用来遍历集合,后面不推荐使用可以用Iterator替代)
Stack
Stack类中增加了5个方法对Vector类进行了扩展
方法名 | 方法介绍 |
---|---|
public E push(E item) | 把元素压入堆栈顶部 |
public E pop() | 移除堆栈顶部的对象,并作为此方法的值返回该对象 |
public E peek() | 查看堆栈顶部的对象,但不从堆栈中移除它。 |
public boolean empty() | 测试堆栈是否为空。 |
public int search(Object o) | 返回对象在堆栈中的位置,以1为基数。 |
Hashtable
旧版的HashMap,JDK1.0中创建的,在JDK2.0中使用HashMap替换Hashtable,本身具有线程同步的功能。
使用方式和HashMap大致一样。
Properties
Hashtable的一个子类,用于获取当前项目中的配置文件中的信息,该类没有泛型 所有的key-value均为String类型
Properties类表示了一个持久的属性集。Properties可保存在流中或从流中加载。属性集中每个键及其对应值都是一个字符串。
不建议使用 put 和 putAll 这类存放元素方法,应该使用 setProperty(String key, String value)方法,因为存放的“键-值”对都是字符串。类似取值也应该使用getProperty(String key)。
示例:
import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class PropertiesTest { public static void main(String[] args) { InputStream is = Thread.currentThread() .getContextClassLoader() .getResourceAsStream("config.properties"); Properties prop = new Properties(); try { prop.load(is); } catch (IOException e) { e.printStackTrace(); } String name = prop.getProperty("name"); String pwd = prop.getProperty("pwd"); System.out.println(name + ", " + pwd); } }
TreeSet & TreeMap
在集合中存在一个自动排序的存储集合,底层使用红黑树进行数据存储。
只要存入该集合中的数据都会按照排序规则进行默认排列
对于数据类型的包装类 以及 String类型 默认都实现了排序接口 会按照字典顺序进行数据排列
如果希望元素放置在TreeSet或者TreeMap中 则该对象必须实现比较接口Comparable 否则在存放时会抛出异常
按照降序排列则只需将正整数和负整数的位置交换即可
优缺点
优点:无序另外设置排序规则,在可排序的方法中可以直接进行排
缺点:在一个项目中如果实现Comparable 则该对象默认只能有一种排序方式
public class Student implements Comparable{ private int id; //编号 private String name; //姓名 private double score; //考试得分 public Student(){} public Student(int id, String name, double score) { this.id = id; this.name = name; this.score = score; } //省略所有属性的getter和setter方法 //实现compareTo方法,按成绩升序排序 public int compareTo(Object o) { Student other = (Student)o; if(this.score > other.score){ return 1; }else if(this.score < other.score){ return -1; }else{ return 0; } } public boolean equals(Object obj) {…} public int hashCode() { …} }
比较规则和Comparable中的规则一致。
当一个对象即实现了Comparable接口 用有Comparator类型的排序类 则如果显示调用Comparator的排序类 会覆盖Comparable的实现规则 否则直接使用Comparable的默认排序
示例:
/** 学生考试得分比较器 */ class StudentScoreComparator implements Comparator<Student>{ public int compare(Student o1, Student o2) { if(o1.getScore() > o2.getScore()){ return 1; }else if(o1.getScore() < o2.getScore()){ return -1; }else{ return 0; } } } /** 学生姓名比较器 */ class StudentNameComparator implements Comparator<Student>{ public int compare(Student o1, Student o2) { return o1.getName().compareTo(o2.getName()); } } //使用不同比较器的排序集合 Set<Student> set = new TreeSet<Student>(new StudentScoreComparator()); Set<Student> set2 = new TreeSet<Student>(new StudentNameComparator());
针对于集合专门设计的工具类,提供了集合常用的功能,方便开发者提升开发效率。
常用方法
void sort(List list) 根据元素的自然顺序 对指定List列表按升序进行排序。List列表中的所有元素都必须实现 Comparable 接口。
void shuffle(List list) 对List列表内的元素进行随机排列
void reverse(List list) 对List列表内的元素进行反转
void copy(List dest, List src) 将src列表内的元素复制到dest列表中
List synchronizedList(List list) 返回指定列表支持的同步(线程安全的)列表
集合类大多数默认都没有考虑线程安全问题,程序必须自行实现同步以确保共享数据在多线程下存取不会出错:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
Iterator i = list.iterator(); while (i.hasNext()) {
foo(i.next());
}
}
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet
以上的对象在同步性和效率性上进行了平衡,只在修改元素时加锁,在读取元素时不上锁,提高读取效率,适合使用于并发性强且不长修改的数据。
使用字符流读取不同编码类型的字符时,会出现乱码问题
JVM默认的字符集:跟你的源文件的编码有关。
字符集(charset):是一套文字符号及其编码的集合。
Unicode字符集包括全世界所有语言的文字和符号。
它为每种语言中的每个字符设定了统一并且唯一的二进制编码
Unicode的常用编码实现方案:
File类代表文件(文件和目录)
存储介质中的文件和目录在Java程序中都是用File类的实例来表示
常用构造方法:
public File(String pathname):以pathname为路径创建File对象
pathname为一个具体路径:
建议:
常用方法
1.访问File对象的属性:
•public boolean canRead()
•public boolean canWrite()
•public boolean exists()
•public boolean isDirectory()
•public boolean isFile()
•public boolean isHidden()
•public long lastModified() //毫秒值
•public long length() //以字节为单位
•public String getName() //获取文件名
•public String getPath() //路径名
•public String getAbsolutePath() //返回此File对象的绝对路径名
•public File getAbsoluteFile()
•public String getCanonicalPath() //返回此File对象的规范路径名字符串
•public File getCanonicalFile() //返回此File对象的规范形式
•public String getParent() //返回父目录的路径名字符串
•public URI toURI() //返回此文件的统一资源标识符名
示例
import java.io.File;
public class FileTest{
public static void main(String[] args){
File file = new File(args[0]);
System.out.println("文件或目录是否存在:" + file.exists());
System.out.println("是文件吗:" + file.isFile());
System.out.println("是目录吗:" + file.isDirectory());
System.out.println("名称:" + file .getName());
System.out.println("路径: " + file.getPath());
System.out.println("绝对路径: " + file.getAbsolutePath());
System.out.println("最后修改时间:" + file.lastModified());
System.out.println(“文件大小:” + file.length()+ “ 字节”);
……
}
}
•public String[] list() //返回此目录下的文件名和目录名的数组
•public File[] listFiles()//返回此目录下的文件和目录File实例数组
•public File[] listFiles(FilenameFilter filter) //返回此目录中满足指定过滤器的文件和目录
•java.io.FilenameFilter接口:实现此接口的类实例可用于过滤文件名
•public boolean createNewFile() //不存在时创建此文件对象所代表的空文件
•public boolean delete() //删除文件或目录。目录必须是空才能删除
•public boolean mkdir() //创建此抽象路径名指定的目录
•public boolean mkdirs() //创建此抽象路径名指定的目录,包括所有必需但不存在的父目录
•public boolean renameTo(File dest) //重命名
示例
用递归算法列出指定目录下的所有子孙文件和目录
package file; /* * 将某个目录所有下属目录或文件 全部遍历出来 * */ import java.io.File; public class LookDir { public static final String SYMBOL = "|--"; public static void main(String[] args) { String path = "D:/iotest"; lookSome(new File(path),1); } public static void lookSome(File file,int level){ StringBuilder sb = new StringBuilder(); for (int i = 0; i < level ; i++) { sb.append(SYMBOL); } System.out.println(sb.toString()+file.getName()); //判断当前是否是目录 if(file.isDirectory()){ //如果是目录 再进行遍历 File[] fs = file.listFiles();//获取当前目录的所有子元素 for(File f : fs){ //遍历当前子元素 if(f.isFile()){ System.out.println(sb.toString()+f.getName());//出口 }else{ lookSome(f,++level);//使用递归来进行文件结构的查看 } } }else{ //如果是文件 则直接输出文件名 System.out.println(sb.toString()+file.getName());//出口 } } }
列出指定目录下的jpg类型图片
import java.io.File; import java.io.FileFilter; public class FileFilterTest { public static void main(String[] args) { /* File 对象可以获取当前目录下的指定文件 --- 可以进行文件过滤 */ File f = new File("D:/iotest"); File[] fs = f.listFiles(); for(File f1 : fs){ System.out.println(f1.getName()); } //需求只希望看到当前目录下的所有.jpg文件 File[] fs1 = f.listFiles(new FileFilter() { @Override public boolean accept(File ff) { if (ff.getName().endsWith(".jpg")) { return true; } else { return false; } } }); System.out.println("~~~~~~~~~~~~~~~~~"); for(File f1 : fs1){ System.out.println(f1.getName()); } } }
删除一个目录的过程是如何进行的?–用递归删除一个非空目录
package file; import java.io.File; /* 删除指定目录/文件 核心思想 文件直接删除 如果是目录 必须进行判定 是空目录才能删除 否则需要先删除其内部的所有文件 之后再删除该目录 */ public class DeleteDir { public static void main(String[] args) { String path = "D:/iotest"; deleteDir(new File(path)); } public static void deleteDir(File file){ if(null == file ){ System.out.println("目标文件不存在,请检查"); }else{ if(file.isDirectory()){ //清空当前目录下的所有元素 File[] fs = file.listFiles(); for(File f : fs){ if(f.isFile()){ //下属元素是文件 直接删除 f.delete(); }else{ //如果是目录 使用递归方式将该目录当做参数 传递给删除方法 deleteDir(f); } } } file.delete();//删除当前目标元素 } } }
数据流(Stream)是指数据通信的通道。
java程序中对数据的输入、输出操作是以“流”方式进行的。JDK中提供了各式的“流”类来获取不同种类的数据。
示意图:
按照方向区分
输入和输出方向的参照物是内存
按照传输单位区分
按照功能区分
在Java中按照方向和传输单位两个维度 提供了4大抽象类
字节 | 字符 | |
---|---|---|
输入 | InputStream | Reader |
输出 | OutPutStream | Writer |
示意图
InputStream的常用方法
示意图:
OutPutStream基础方法
Reader抽象类
继承自Reader的流都是用于向程序中输入数据的,且数据的单位为字符(16位)。
示意图:
Reader常用方法
Writer抽象类
示意图:
Writer的基础方法
主要用来操控文件的读写,可以用来复制文件或者剪切。
专门用于操作文件的流。Java SE API中提供了4种:
二进制文件(字节文件):图片、音频、视频等。 需要使用字节流来操作
文本文件(字符文件):.txt、.properties、.xml、.html 字符或字节都可以操作 但建议适应字符操作 很简便
注:文件续写问题 在文件写出时 如果该输出流的构造器第二个参数为true 则会自动续写 如果为false或者不写第二个参数 则直接覆盖原文件所有内容
示意 :
/* 使用字节流完成文件复制的功能 */ int b = 0; FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("d:/IOTest/soruce.jpg"); out = new FileOutputStream("d:/IOTest/dest.jpg"); while ((b = in.read()) != -1) { //有没有效率更高的方式? out.write(b); } System.out.println("文件复制成功"); } catch (FileNotFoundException e) { System.out.println("找不到指定文件"); e.printStackTrace(); } catch (IOException e) { System.out.println("文件复制错误"); e.printStackTrace(); }finally{ …}
缓冲流是建立在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,还增加了一些新的方法。
Java SE API提供四种缓冲流:
对于缓冲输出流,写出的数据会先缓存在内存缓冲区中,关闭此流前要用flush()方法将缓存区的数据立刻写出。
关闭过滤流时,会自动关闭过滤流所包装(套接)的所有底层流。
示例:
BufferedInputStream bis = null; BufferedOutputStream bos = null; byte[] buf = new byte[1024]; try{ bis = new BufferedInputStream(new FileInputStream(src)); bos = new BufferedOutputStream(new FileOutputStream(dest)); for(int len = 0; (len = bis.read(buf)) != -1;){ bos.write(buf, 0, len); } bos.flush(); }catch(IOException e){ e.printStackTrace(); }finally{ if(bos != null){ try {bos.close();} catch (IOException e) {e.printStackTrace();} } if(bis != null){ try {bis.close();} catch (IOException e) {e.printStackTrace();} } }
转换流用于在字节流和字符流之间转换。
JaveSE API提供了两种转换流:
InputStreamReader需要和InputStream“套接”,它可以将字节流中读入的字节解码成字符
OutputStreamWriter需要和OutputStream“套接”,它可以将要写入字节流的字符编码成字节
常用于:
解决乱码问题
网络编程中,只提供字节流。
示例 :
System.out.println("请输入信息(退出输入e):"); InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); String s = null; try { while ((s = br.readLine()) != null) { //阻塞程序 if (s.equals("e")) { System.out.println("安全退出!!"); break; } System.out.println("-->:"+s.toUpperCase()); System.out.println("继续输入信息"); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (null != br) { br.close(); } } catch (IOException e) { e.printStackTrace(); } }
用于存储和读取基本类型数据或对象的过滤流
能被序列化的对象所对应的类必须实现java.io.Serializable这个标识性接口
示例:
package serialized; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SeriaillzedTest { public static void main(String[] args) { /* 在java中有一个流 ObjectStream 对象流 能够将对象序列化 序列化 就是将程序中的对象或者信息 持久化(本地存储) 可以通过序列化 和反序列化将一个对象 转换成一个文件 之后再从文件中读取还原成一个对象 底层就可以通过文件来传输该对象的信息 示例:将一个学生类 序列化成一个文件 */ Student stu = new Student("王源", 20); xlh(stu); fxlh(); } public static void xlh(Student stu) { File f = new File("D:/iotest/stu.dat"); ObjectOutputStream oo = null; try { oo = new ObjectOutputStream(new FileOutputStream(f)); oo.writeObject(stu); oo.writeBoolean(true); oo.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (oo != null) { try { oo.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void fxlh() { File f = new File("D:/iotest/stu.dat"); ObjectInput oi = null; try { oi = new ObjectInputStream(new FileInputStream(f)); Student stu = (Student) oi.readObject(); System.out.println(stu); boolean b = oi.readBoolean(); System.out.println("booelan=" + b); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
数据流
打印流
随机访问文件
void seek(long pos); //文件指针移动到指定的指针偏移量
long getFilePointer(); //获取当前文件指针偏移量
int read(byte[] b); //读数据
void write(byte[] b, int off, int len); //写数据
JDK7中提供的,它可以自动关闭相关的资源的语法:
try (资源声明1; 资源声明2; …) {
…
}catch(…){
}
它会确保在本语句结束时关闭try语句中声明的所有资源。资源被关闭的顺序与它们被创建的顺序相反。
示例
public static void copy(File src, File dest){
byte[] buf = new byte[1024];
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(dest))){
for(int len = 0; (len = bis.read(buf)) != -1;){
bos.write(buf, 0, len);
}
bos.flush();
}catch (Exception e) {
e.printStackTrace();
}
}
计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件、软件、数据信息等资源。
网络通信协议
要使计算机连成的网络能够互通信息,需要对数据传输速率、传输代码、代码结构、传输控制步骤、出错控制等制定一组标准,这一组共同遵守的通信标准就是网络通信协议,不同的计算机之间必须使用相同的通讯协议才能进行通信。
在Internet中TCP/IP协议是使用最为广泛的通讯协议。TCP/IP是英文Transmission Control Protocol/Internet Protocol的缩写,意思是“传输控制协议/网际协议”。
网络参考模型
OSI参考模型:开放系统互连参考模型。由国际标准化组织ISO提出的一个网络系统互连模型。
OSI模型目前主要用于教学理解。实际使用中,网络硬件设备基本都是参考TCP/IP模型
示意图:
•针对TCP/IP模型的各层,都有对应数据传输格式,这个数据传输的格式就是常说的协议
•TCP/IP包括上百个各种功能的协议,如:超文本传输协议(HTTP)、远程登录(TeLnet)、文件传输(FTP)和电子邮件(SMTP、POP3、IMAP4)等。
IP地址:网络中每台设备的标识号
是一个逻辑地址:IPV4、IPV6 区别在于可用范围
IP地址不便记忆,可用主机名
本地回环地址:127.0.0.1 主机名:localhost
端口号:用于标识具有网络功能的进程的逻辑地址(标识号)
有效端口:0~65535,其中0~1024系统使用或保留端口。
端口与协议有关:TCP和UDP的端口互不相干,TCP和UPD的端口号可以重复
传输协议:通讯的规则 常用协议:TCP、UDP
表示互联网地址(IP地址),它封装了IP地址和域名相关的操作方法
//使用域名创建InetAddress对象
InetAddress inet1 = InetAddress.getByName("www.baidu.com");
System.out.println(inet1);
//使用IP创建InetAddress对象
InetAddress inet2 = InetAddress.getByName("117.79.93.222");
System.out.println(inet2);
InetAddress inet3 = InetAddress.getLocalHost(); //获得本机InetAddress对象
System.out.println(inet3);
String host = inet3.getHostName();//获得InetAddress对象中存储的域名
System.out.println("本机名:" + host);
String ip = inet3.getHostAddress(); //获得InetAddress对象中存储的IP
System.out.println("本机IP:" + ip);
两个应用程序可以通过一个双向的网络通信连接实现数据交换,这个双向链路的一端称为一个Socket。
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
创建Socket的四个基本步骤
java.net包中定义了两个类:Socket和ServerSocket,分别用来实现TCP的client和server端
示意图:
基于TCP 方式的Socket数据传输示例
package socket.demo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Service { /* Socket使用场景 一般用在两个程序之间进行数据通信 ServerSocket 和Socket 两个对象 一个是服务端用来接收客户请求并进行制定响应 Socket就是一个客户端 用来和服务端之间进行数据传递 */ public static void main(String[] args) { int port = 8888; startServer(port); } public static void startServer(int port){ System.out.println("服务端开启...."); //服务端 只用执行端口号来监听客户端访问 ServerSocket ss = null; BufferedReader br = null; try { ss = new ServerSocket(port); Socket s = ss.accept(); //使用Socket的流来读取数据即可 br = new BufferedReader(new InputStreamReader(s.getInputStream())); String str = br.readLine(); System.out.println(str); } catch (IOException e) { e.printStackTrace(); }finally { if(br != null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(ss != null){ try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
package socket.demo; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; public class Client { /* 模拟客户端访问服务端Socket并发送数据 */ public static void main(String[] args) throws UnknownHostException { System.out.println("客户端准备中....."); String ip = "127.0.0.1"; // String host = "localhost";//hostname int port = 8888; String msg = "你好,我是SocketClient,How do you do!"; sendMsg(InetAddress.getByName(ip),port,msg); } public static void sendMsg(InetAddress ip ,int prot,String msg){ Socket s = null; PrintWriter pw = null; try { s = new Socket(ip,prot); pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream())); pw.println(msg); pw.flush(); } catch (IOException e) { e.printStackTrace(); }finally { if(null != s){ try { s.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
基于TCP方式的聊天程序
//Socket服务端 先等待接收 之后再想客户端发送信息 /** 模拟小明和小红聊天 小红为服务端 小明为客户端 */ public class XiaoHong { public static void main(String[] args) { ServerSocket ss = null; PrintWriter pw = null; BufferedReader br = null; BufferedReader sc = null; Socket xm = null; System.out.println("欢迎小红登录,请开始聊天..."); try { ss = new ServerSocket(6666); xm = ss.accept(); pw = new PrintWriter( new OutputStreamWriter(xm.getOutputStream())); br = new BufferedReader(new InputStreamReader(xm.getInputStream())); sc = new BufferedReader(new InputStreamReader(System.in)); String xmMsg = null; while(!(xmMsg = br.readLine()).equalsIgnoreCase("exit")){ System.out.println("小明:"+xmMsg); String xhMsg = sc.readLine(); pw.println(xhMsg); pw.flush(); } } catch (IOException e) { e.printStackTrace(); }finally { if(null != sc){ try { sc.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != br){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != xm){ try { xm.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != ss){ try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
/* 客户端先向服务端发起链接 之后发送数据 再等待服务端返回的消息 */ public class XiaoMing { public static void main(String[] args) { Socket xiaoming = null; PrintWriter pw = null; BufferedReader br = null; BufferedReader sc = null; System.out.println("欢迎小明登录,请开始聊天..."); try { xiaoming = new Socket("localhost", 6666); /* 先向服务端发起信息 之后再接收服务端的信息 */ OutputStream os = xiaoming.getOutputStream();//输出流 发送信息 InputStream is = xiaoming.getInputStream();//输入流 接收信息 pw = new PrintWriter(new OutputStreamWriter(os)); br = new BufferedReader(new InputStreamReader(is)); sc = new BufferedReader(new InputStreamReader(System.in));//从标准输入流获取传输信息 while(true){ String str = sc.readLine(); if(str.equalsIgnoreCase("exit")){ break; }else{ pw.println(str); pw.flush(); String xhMsg = br.readLine(); System.out.println("小红:"+xhMsg); } } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(null != sc){ try { sc.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != br){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != xiaoming){ try { xiaoming.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
UDP编程所需要的类DatagramSocket
用来发送和接收数据报包的套接字
DatagramPacket 数据报包
•buf - 包数据
•length - 包长度
•address - 目的地址
•port - 目的端口号
•int getLength() 返回将要发送或接收到的数据的长度。
package udp.socket; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; /* 基于UDP协议的 数据报包的发送 发送至接收端 */ public class SendMsg { public static void main(String[] args) { String msg = "重大消息,xxxxxx开业了,所有商品一律6折,请各位奔走相告!"; byte[] msgb = msg.getBytes(); DatagramSocket ds = null; try { ds = new DatagramSocket("9999");//定义发包的端口号,该数据从此端口向外发送,如不写则系统默认选取一个闲置端口使用 DatagramPacket dp = new DatagramPacket(msgb,msgb.length, InetAddress.getByName("localhost"),6666);//数据报包拼接完毕 需指定发送至何处(接收端的ip和端口号) //开始发送 ds.send(dp); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(null != ds){ ds.close(); } } } }
package udp.socket; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /* 接收数据报包 */ public class ReciveMsg { public static void main(String[] args) { DatagramSocket ds = null; byte[] b = new byte[64*1024]; try { ds = new DatagramSocket(6666);//该接口为接听端的端口 发送时需和该端口一致 DatagramPacket dp = new DatagramPacket(b,b.length); //接收方法会产生阻塞 拿到包后继续执行 ds.receive(dp); //接收到数据报包之后 开始解析 byte[] data = dp.getData(); String str = new String(data,0,dp.getLength()); System.out.println("接收到的信息为:"+str); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
URL代表一个统一资源定位符,它是指向互联网“资源”的指针。
网络中的资源都有唯一的URL地址。
//获取百度的HTML内容 import java.io.*; import java.net.URL; public class TestURL { public static void main(String[] args) { String strUrl = "http://www.baidu.com"; BufferedReader br = null; try { URL url = new URL(strUrl); br = new BufferedReader(new InputStreamReader(url.openStream())); String str = ""; while((str = br.readLine()) != null){ System.out.println(str); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ if(null != br){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
代表应用程序和URL之间的通信链接。此类的实例可用于读取和写入此URL引用的资源。
常用它的子类HttpURLConnection,用于支持HTTP的通信链接
创建一个到URL的连接的步骤:
//使用Get方式请求数据 public static String sendGet(String url) { String result = ""; BufferedReader ir; try { URL u = new URL(url); HttpURLConnection connection = (HttpURLConnection)u.openConnection();//如确认是HTTP协议传输,则此处可强转成HTTPURLConnection connection.connect(); ir = new BufferedReader(new InputStreamReader(connection.getInputStream())); for(String line = null; (line = ir.readLine()) != null;) { result += "\n" + line; } } catch (Exception e) { System.out.println("没有结果!" + e); } finally{ ir.close(); } return result; }
元数据(metadata):就是关于数据的数据。
示例:
在JDK5版本开始 ,提出了注解的概念,Java核心语法。
注解是可以添加到代码中的修饰符,对程序代码做出一些说明和解释。可以用于包声明、类声明、构造方法、方法、字段、参数和变量
注解语法
注解类型和注解的区别:注解类型类似于类,注解类似于该类的实例
Override:只能放在方法上 用来在编译时检查 当前的方法是否构成重写语法 如果不符合会直接在编译过程中提示错误
Deprecated:可以放在类中的任意地方 其对应的内容 会被提示为已过时的内容 在编程中不建议时间器修饰的对象来进行编程开发 在IDE工具中如果一个对象被标为过时 则会出现中划线
SuppressWarnings :可以放在任何位置 用来进行告警抑制功能 可以对当前IDE工具中提示的告警信息进行抑制 但其在使用时必须要提供参数。
它有一个必需属性:value 是String[]类型的,指定取消显示的警告集。警告类型有
•unused 未被使用的警告
•deprecation 使用了不赞成使用的类或方法时的警告
•unchecked 执行了未检查的转换时的警告
•rawtypes 没有用泛型 (Generics) 的警告
•fallthrough 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
•path 在类路径、源文件路径等中有不存在的路径时的警告。
•serial 当在可序列化的类上缺少 serialVersionUID 定义时的警告。
•finally 任何 finally 子句不能正常完成时的警告。
•all 关于以上所有情况的警告。
注意:注解如果仅接收一个参数且名称是“value”,则可省略”value=“,否则必须显示声明所有的属性值
语法:
[访问修饰符] @interface 注解类型名称{
//如果有参数
变量类型 变量名() [default 默认值];
…
}
@Target 指定此注解的适用时机 使用ElementType枚举类定义当前注解的使用范围
@Retention 告知编译器如何处理此注解 使用Retention枚举来描述声明周期
@Documented 要求此注解成为API文件的一部分
@Inherited 指定子类是否继承父类的注解
反射是Java语言的特征之一。它允许在运行时动态加载类、获取类信息、生成对象、操作对象的属性或方法等。主要提供了以下功能:
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法。甚至是private方法。
生成动态代理
Class类是Java反射的起源。
运行中的类或接口在JVM中都会有一个对应的Class对象存在,它保存了对应类或接口的类型信息。
包括:基本数据类型、void、数组、enum、annotation
JVM为每种类型管理着一个独一无二的Class对象
获取Class对象
有三种方式可以获取每一个对象对应的Class对象:
使用Object类中的getClass()方法。//返回的Class<? exstends S>
使用Class类的静态方法forName(String className);//会抛出ClassNotFoundException 最终返回Class<?>
使用class常量。格式: 类名.class;// 返回一个Class
示例
public class ClassDemo {
public static void main(String[] args) {
String str = "java技术";
Class stringClass = str.getClass();
//Class stringClass = Class.forName("java.lang.String");
//Class stringClass = String.class;
System.out.println("类名:" + stringClass.getName());
System.out.println("是否为接口:" + stringClass.isInterface());
System.out.println("是否为基本类型:" + stringClass.isPrimitive());
System.out.println("父类名称:" + stringClass.getSuperclass().getName());
}
}
关于Class其内部可以认为就是对于一个类的完全描述 所以一个类中所有的构成都可以看做一个个单独的类型:
使用Class类可以获取以上每个类型的对象 但是在使用时 如果是私有类型的 则必须要开启可访问模式 setAccessible(true)
使用反射来创建对象
Class.newInstance();//默认调用当前类型的无参公共构造来创建对象 所以一般声明含参构造时 一定要声明无参构造 以方便框架的反射机制来创建对象
正则表达式(regular expression)描述了一种字符串匹配的模式。
正则表达式作为一个模板,与所搜索的字符串进行匹配。
常用于:字符串的匹配、查找、替换、分割等
java.util.regex包中提供了相关类
Pattern:模式类。正则表达式的编译表示形式
Matcher:匹配器。用于匹配字符序列与正则表达式模式的类
PatternSyntaxException:正则表达式模式中的语法错误
String类中与正则相关的方法
public boolean matches(String regex); 告知此字符串是否匹配给定的正则表达式
String[] split(String regex); 根据给定正则表达式的匹配拆分此字符串
String replaceAll(String regex, String replacement);使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串
public static Pattern compile(String regex) 将给定的正则表达式编译到模式中
public static Pattern compile(String regex, int flags) 将给定的正则表达式编译到具有给定标志的模式中
public Matcher matcher(CharSequence input) 生成一个给定命名的Matcher对象
static boolean matches(String regex, CharSequence input) 直接判断字符序列input是否匹配正则表达式regex。该方法适合于该正则表达式只会使用一次的情况
public boolean matches() 尝试将整个输入序列与该模式匹配。 (开头到结尾)
public boolean find() 扫描输入序列以查找与该模式匹配的下一个子序列
public String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列
public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列
public String group() 返回由以前匹配操作所匹配的输入子序列
public String group(int group) 返回在以前匹配操作期间由给定组捕获的输入子序列
正则表达式的模式串:是由普通字符(如字符a到z)以及一些特殊字符(称为元字符)组成
元字符从功能上分为:限定符、选择匹配符、特殊字符、字符匹配符、定位符、分组组合符、反向引用符。
在正则中使用限定符来限制其修饰的前一个/一组元素的出现次数
字符 | 说明 |
---|---|
***** | 匹配前面的字符或子表达式零次或多次。 例如:"ja*“匹配"j"和"jaaaaaaaa”。 |
+ | 匹配前面的字符或子表达式一次或多次。 例如:"ja+"与"ja"和"jaaaaa"匹配,但与"j"不匹配。 |
? | 匹配前面的字符或子表达式零次或一次。 例如:“core?”匹配“cor”或“core”。不能匹配”coreeeee” |
{n} | 正好匹配n次。n是一个非负整数。 例如:"o{2}"与"Bob"中的"o"不匹配,与"food"中的两个"o"匹配。 |
{n,} | 至少匹配n次。 例如:"o{2,}"与"Bob"中的"o"不匹配,与"foooood"中的所有"o"匹配。 |
{n,m} | 最少匹配n次,且最多匹配m次。m和n均为非负整数,其中n<=m。 例如:"o{1,3}"匹配"fooooood"中的头三个 o。注意:不能将空格插入逗号和数字之间。 |
限定符默认都是贪婪匹配,即尽可能多的去匹配字符。
当“?”紧跟在任何一个其他限定符(*,+,?,{n},{n,},{n,m})后面时,就是非贪婪匹配模式。
String str = "abccccc";
Pattern p = Pattern.complie("abc?");//默认贪婪匹配 能够匹配到abccccc
Pattern p = Pattern.complie("abc*?");//使用?后变为非贪婪 能够匹配到ab
例如:“j|qava” 匹配“j”或“qava” ,"(j|q)ava"匹配"java"或"qava"。
普通字符可以直接用来表示它们本身,也可以用它们的ASCII码或Unicode代码来代表。
ASCII码:两位的十六进制值,前面加"\x"
字符b的ASCII码为98(十六进制是62)。所以表示b字符可用"\x62"
Unicode码:四位十六进制值,前面加"\u"
匹配字符b,可以用它的Unicode代码"\u0062"
最常用情况是用来表示中文字符的范围:"\u4E00-\u9FA5"
元字符中用到的特别字符,当作普通字符使用时需要用""进行转义:
使用[]来进行一位内容的匹配
注意 使用[]包裹的所有字符都会变为普通字符
字符 | 说明 |
---|---|
[…] | 字符集。匹配指定字符集合包含的任意一个字符。 例如:"[abc]“可以与"a”、“b”、"c"三个字符中的任何一个匹配。 |
[ ^… ] | 反向字符集。匹配指定字符集合未包含的任意一个字符。 例如:"[ ^abc]“匹配"a”、“b”、"c"三个字符以外的任意一个字符。 |
[a-z] | 字符范围。匹配指定范围内的任意一个字符。 例如:"[a-z]“匹配"a"到"z"范围内的任何一个小写字母。”[0-9]"匹配"0"到"9"范围内容的任何一个数字。 |
[ ^a-z ] | 反向字符范围。匹配不在指定范围内的任何一个字符。 例如:"[ ^a-z]“匹配任何不在"a"到"z"范围内的任何一个字符。”[ ^0-9]"匹配任何不在"0"到"9"范围内容的任何一个字符。 |
. | 匹配除"\n"之外的任何单个字符。若要匹配包括"\n"在内的任意字符,使用如"[\s\S]"之类的模式。 |
\d | 数字字符匹配。等效于[0-9]。 |
\D | 非数字字符匹配。等效于[ ^0-9]。 |
\w | 匹配任何单词字符,包括下划线。与"[A-Za-z0-9_]"等效。 |
\W | 与任何非单词字符匹配。与"[ ^A-Za-z0-9_]"等效。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等。与"[\f\n\r\t\v]"等效。 |
\S | 匹配任何非空白字符。与"[ ^\f\n\r\t\v]"等效。 |
用于匹配字符出现的位置
字符 | 说明 |
---|---|
^ | 匹配输入字符串的开始位置。^必须出现在正则模式文本的最前才起定位作用。 |
$ | 匹配输入字符串的结尾位置。$必须出现在正则模式文本的最面才起定位作用。 |
\b | 匹配一个单词边界。 例如:“er\b"匹配"never love"中的"er”,但不匹配"verb"中的"er"。 |
\B | 非单词边界匹配。 例如:“er\B"匹配"verb"中的"er”,但不匹配"never"中的"er"。 |
用()将正则表达式中的某一部分定义为"组",并且将匹配这个组的字符保存到一个临时区域,该方式叫做捕获性分组。
字符 | 说明 |
---|---|
(pattern) | 捕获性分组。将圆括号中的pattern部分组合成一个组合项当作子匹配,每个捕获的子匹配项按照它们在正则模式中从左到右出现的顺序存储在缓冲区中,编号从1开始,可供以后使用。 例如:“(dog)\1"可以匹配"dogdogdog"中的"dogdog"。 |
(?:pattern) | 非捕获性分组。即把pattern部分组合成一个组合项,但不能捕获供以后使用。 |
(?=pattern) | 正向预测匹配分组。 例如:“Windows(?=95|98|NT|2000)“能匹配"Windows2000"中的"Windows”,但不能匹配"Windows3.1"中的"Windows”。 |
(?!pattern) | 正向否定预测匹配分组 |
(?<=pattern) | 负向预测匹配分组。例如:"(?<=95|98|NT|2000)Windows" |
(?<!pattern) | 负向否定预测匹配分组 |
反向引用符:用于对捕获分组后的组进行引用的符号。格式为"\组编号" 能够在模式串中根据组编号来引用当前某个组的内容
例如:要匹配"goodgood study, dayday up!“中所有连续重复的单词部分,可使用”\b([a-z]+)\1\b"来匹配。
"$分组编号"可用来引用分组匹配到的结果字符串,在匹配器执行过程中可以通过该方式来获取对应分组内容
String str = "我我我要要学java";
Matcher m = Pattern.compile("(.)\\1+").matcher(str);
str = m.replaceAll("$1");
System.out.println(str);
API(Application Programming Interface)应用程序编程接口。
Java SE API:JDK中提供的各种功能的java类和接口
JDK6.0——API文档百度网盘下载,密码: 6666
java.lang包中提供的是利用Java编程语言进行程序设计的基础类。
java.lang包中的类由编译器自动导入
所有Java类的根类。如果在类的声明中未明确使用extends关键字指定父类,则默认为继承自Object类
查询Java SE API 中Object类的方法说明
String toString():返回代表该对象值的字符串。
Object类中返回的串是 “类的全限定名@对象哈希码的十六进制整数值"
建议在自定义类中重写 重写成可识别的对信息描述
boolean equals(Object obj):测试其它某个对象是否与此对象"相等"
equals()方法内部默认使用 == 来进行比较 比较的是地址是否相同
建议在自定义类中重写 重写成比较两个对象的属性是否相等
int hashCode():返回该对象的哈希码值
在重写equals()方法时,建议同时重写hashCode()方法,因为在某些场合需要比较两个对象是否为相同的对象时,会调用到这两个方法来判断
public class TestObject{
public static void main(String[] args){
TestObject obj = new TestObject();
System.out.println(obj.toString());
System.out.println(obj.hashCode());
TestObject obj2 = new TestObject();
System.out.println(obj.equals(obj2));
}
}
所有基本数据类型都提供了对应的包装类
基本数据类型 | 包装类 |
---|---|
byte(字节) | java.lang.Byte |
char(字符) | java.lang.Character |
short(短整型) | java.lang.Short |
int(整型) | java.lang.Integer |
long(长整型) | java.lang.Long |
float(浮点型) | java.lang.Float |
double(双精度) | java.lang.Double |
boolean(布尔) | java.lang.Boolean |
所有的包装类可以和基本数据类型之间无缝转换
public class NumberWrap { public static void main(String[] args) { String str = args[0]; //------------- String转换成Ingeter Integer integer = new Integer(str); // 方式一 // Integer integer = Integer.valueOf(str); //方式二 //------------- Ingeter转换成String String str2 = integer.toString(); //------------- 把Ingeter转换成int int i = integer.intValue(); //------------- 把int转换成Integer Integer integer2 = new Integer(i);// //Integer integer2 = Integer.valueOf(i); //------------- String转换成int int i2 = Integer.parseInt(str); //------------- 把int转换成String String str3 = String.valueOf(i2); // 方式一 String str4 = i2 + ""; // 方式二 } }
java编译器在编译时期会根据源代码的语法来决定是否进行装箱或拆箱
Java SE 5.0以后版本中引入的一个新的引用类型。
枚举类型是指由一组固定的常量组成合法值的类型,例如:一年的四季、一个星期的七天、红绿蓝三基色、状态的可用和不可用等。
f
使用enum关键字来定义,如:
public enum Year{ SPRING, SUMMER, AUTUMN, WINTER}
所有枚举类型对应的类是java.lang.Enum类的子类。在枚举类型定义中可以添加属性、构造器和方法,并可以实现任意的接口。
每个枚举类型 其实底层会编译为一个final修饰的类 且该类需要继承自Enum
枚举中的每个选项都是 public static final修饰的当前类类型
enum Status{ ACTIVE("可用"),INACTIVE("不可用"); //枚举值列表 private final String name;//final成员 Status(String name){//构造器。只能在内部定义枚举值时使用 this.value = value; } public String getName(){ //提供get方法访问Name属性的值 return name; } } public class StatusEnumTest { public static void main(String[] args) { Status status = Status.ACTIVE; System.out.println("文章状态:" + status); System.out.println("此状态对应的名称:" + status.getName()); } }
提供了方便数学计算的方法,Math类中所有的属性和方法都是static,可以直接使用
静态常量
静态方法
double sin(double a) | 计算角 a的正弦值 |
---|---|
double cos(double a) | 计算角 a的余弦值 |
double pow(double a, double b) | 计算 a 的 b 次方 |
double sqrt(double a) | 计算给定值的平方根 |
int abs(int a) | 计算int类型值a的绝对值.也提供long/float/double的 |
double ceil(double a) | 返回大于等于 a的最小整数的double值 |
double floor(double a) | 返回小于等于 a的最大整数的double值 |
int max(int a, int b) | 返回int型值a和b中的较大值.也提供long/float/double的 |
int min(int a, int b) | 返回 a 和 b 中的较小值。也提供long/float/double的 |
int round(float a); | 四舍五入返回整数 |
double random() | 返回带正号的double值,该值大于等于0.0且小于1.0 |
public class MathTest {
public static void main(String[] args) {
int num = 38;
float num1 = -65.7f;
System.out.println(Math.ceil(num));
System.out.println(Math.ceil(num1));
System.out.println(Math.floor(num));
System.out.println(Math.floor(num1));
System.out.println(Math.round(num));
System.out.println(Math.round(num1));
System.out.println((int)(Math.random()*10 + 1));
}
}
使用小技巧:
Math.random() 可以返回一个随机的[0,1)范围的数
如果希望返回的是[min,max]范围的数据,使用以下方式:
M a t h . f l o o r ( M a t h . r a n d o m ( ) ∗ ( m a x − m i n + 1 ) ) + m i n Math.floor(Math.random()*(max-min+1))+min Math.floor(Math.random()∗(max−min+1))+min
在JDK5.0以上版本 当一个类中频繁的使用某个静态类的属性和方法时 ,可以采用静态导入的方式来进行简化
import static 包名.类名.*;
不建议使用:
import static java.lang.Math.*;
public class StaticImportTest {
public static void main(String[] args) {
int num = 38;
System.out.println(PI);
System.out.println(ceil(num));
System.out.println(floor(num));
System.out.println(round(num));
}
}
System类代表与操作系统平台进行沟通的类。
它和Math类有类似之处,也定义成final的,构造器也定义成了私有的,所有的属性和方法都是static的。
常用方式
集合总是包含以下键的值:
键 | 相关值的描述 |
---|---|
java.version Java | 运行时环境版本 |
java.vendor Java | 运行时环境供应商 |
java.vendor.url Java | 供应商的 URL |
java.home Java | 安装目录 |
java.vm.specification.version Java | 虚拟机规范版本 |
java.vm.specification.vendor Java | 虚拟机规范供应商 |
java.vm.specification.name Java | 虚拟机规范名称 |
java.vm.version Java | 虚拟机实现版本 |
java.vm.vendor Java | 虚拟机实现供应商 |
java.vm.name Java | 虚拟机实现名称 |
java.specification.version Java | 运行时环境规范版本 |
java.specification.vendor Java | 运行时环境规范供应商 |
java.specification.name Java | 运行时环境规范名称 |
java.class.version Java | 类格式版本号 |
java.class.path Java | 类路径 |
java.library.path | 加载库时搜索的路径列表 |
java.io.tmpdir | 默认的临时文件路径 |
java.compiler | 要使用的 JIT 编译器的名称 |
java.ext.dirs | 一个或多个扩展目录的路径 |
os.name | 操作系统的名称 |
os.arch | 操作系统的架构 |
os.version | 操作系统的版本 |
file.separator | 文件分隔符(在 UNIX 系统中是“/”) |
path.separator | 路径分隔符(在 UNIX 系统中是“:”) |
line.separator | 行分隔符(在 UNIX 系统中是“/n”) |
user.name | 用户的账户名称 |
user.home | 用户的主目录 |
user.dir | 用户的当前工作目录 |
Runtime类提供的方法用于本应用程序与其运行的环境进行信息的交互。
这个类使用单例模式构建,即应用程序只能通过它提供的**getRuntime()**静态方法来获取唯一的实例。
常用
执行指定的命令
Process proc = Runtime.getRuntime().exec(cmd); //执行系统的一些命令
if (proc.waitFor() != 0) { //等待子进程结束,判断出口值
}
在Java中所有创建出来的对象都可以自动的由JVM进行垃圾回收
判定一个对象是否为垃圾 主要的思想:当前对象是否有引用关系 如果没有则判断为垃圾
堆中的空间 主要分为两个部分
新生代
老年代
在JDK1.7版本中还有一个区域 永久代(方法区)
在JDK1.8版本及以后新推出元空间替代原有永久代(整个内存空间,可设置大小)。
java.lang.String代表不可变的字符序列。是final类
创建字符串实例的方式:(分析内存)
String a = “hello”;
String b = new String(“hello”);
Java中允许使用符号"+"把两个字符串连接起来组合成一个新字符串:String str = “你好” + “世界”;
"+"号也能将字符串与其它的数据类型相连成一个新的字符串。String str = “abc” + 12;
String类中有length()方法可以获得此字符串的长度。
注意跟数组的length属性区分开来。
String str = "abc你好吗"; str.length();
==号用来比较两个字符串是否存储在同一内存位置
String类的equals()方法用来比较两字符串的内容是否相等
public class TestStringSign { public static void main(String[] args) { String s1 = "abc中文"; String s2 = "abc中文"; String s3 = "abc"; System.out.println(s1 == s2); // true System.out.println(s1 == s3); // false System.out.println(s1.equals(s2)); // true System.out.println(s1.equals(s3)); // false } } public class TestStringEquals { public static void main(String[] args) { String ss1 = new String("abc中文"); String ss2 = new String("abc中文"); String ss3 = new String("abc"); System.out.println(ss1 == ss2); // false System.out.println(ss1 == ss3); // false System.out.println(ss1.equals(ss2)); // true System.out.println(ss1.equals(ss3)); // false } }
常用方法
方法 | 说明 |
---|---|
boolean equalsIgnoreCase(String val) | 此方法比较两个字符串,忽略大小写形式 |
int compareTo(String value) | 按字典顺序比较两个字符串。 如果两个字符串相等,则返回 0; 如果字符串在参数值之前,则返回值小于 0; 如果字符串在参数值之后,则返回值大于 0 |
int compareToIgnoreCase(String val) | 按字典顺序比较两个字符串,不考虑大小写 |
boolean startsWith(String value) | 检查一个字符串是否以参数字符串开始。 |
boolean endsWith(String value) | 检查一个字符串是否以参数个字符串结束。 |
public int indexOf(int ch) | 返回指定字符ch在此字符串中第一次出现处的索引值;如果未出现该字符,则返回 -1。 |
public int indexOf(String str) | 返回第一次出现的指定子字符串str在此字符串中的索引值;如果未出现该字符串,则返回 -1。 |
public int lastIndexOf(int ch) | 返回最后一次出现的指定字符在此字符串中的索引值;如果未出现该字符,则返回 -1。 |
public int lastIndexOf(String str) | 返回最后一次出现的指定子字符串str在此字符串中的索引值;如果未出现该字符串,则返回 -1。 |
public char charAt(int index) | 从指定索引index处提取单个字符,索引中的值必须为非负 |
public class SearchString {
public static void main(String[] args) {
String name = "test@126.com";
System.out.println("我的Email是: " + name);
System.out.println("@ 的索引是:" + name.indexOf('@'));
System.out.println(".com 的索引是:" + name.indexOf(".com"));
if (name.indexOf('@') < name.indexOf(".com")) {
System.out.println("该电子邮件地址有效");
} else {
System.out.println("该电子邮件地址无效");
}
}
}
方法 | 说明 |
---|---|
public String substring(int index) | 提取从位置索引index开始直到此字符串末尾的这部分子字符串 |
public String substring(int beginIndex, int endIndex) | 提取从 beginindex开始直到 endindex(不包括此位置)之间的这部分字符串 |
public String concat(String str) | 将指定字符str串联接到此字符串的结尾成为一个新字符串返回。 |
public String replace(char oc, char nc) | 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 而生成的。 |
public String replace(CharSequence target, CharSequence replacement) | 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串 |
public String trim() | 返回字符串的副本,忽略前导空白和尾部空白 |
public String toUpperCase(); | 将此字符串中的所有字符都转换为大写 |
public String toLowerCase(); | 将此字符串中的所有字符都转换为小写 |
public class StringMethods { public static void main(String [] args) { String s1 = "Hello world"; String s2 = "Hello"; String s3 = " Hello world "; System.out.println(s1.charAt(7)); System.out.println(s1.substring(3, 8)); System.out.println(s2.concat("World")); System.out.println(s2.replace('l', 'w')); System.out.println(s3.trim()); } } public class StringCase { public static void main(String [] args) { String str = "Hello World"; System.out.println(str.toUpperCase()); System.out.println(str.toLowerCase()); } }
在String类中定义了一些静态的重载方法
public static String **valueOf(…)**可以将基本类型数据、Object类型转换为字符串。如:
public static String valueOf(double d) 把double类型数据转成字符串
public static String valueOf(Object obj) 调用obj的toString()方法得到它的字符串表示形式。
int I = 100;
**String s = I + “”;**
String中和正则相关的方法
方法 | 说明 |
---|---|
public boolean matches(String regex) | 此字符串是否匹配给定的正则表达式 |
public String[] split(String regex) | 根据给定正则表达式的匹配拆分此字符串 |
public String replaceAll(String regex, String replacement) | 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串 |
String s = "6667";
String s1 = "666"+"7";//s == s1
String t = "7";
String s2 = "666"+t;//s2 != s
/*字符串拼接符两端都是常量 则JVM会自动帮你转换成一个拼接好的字符串 最终放置在字符串常量池中
如果字符串中有一个是变量 则会创建n个字符串对象 放置在常量池中
故在程序中不建议使用+ 用来拼接大量的含变量的数据 eg."你好"+nane+"欢迎您第"+n+“次访问”+osName+"系统,当前时间为:"+date+",请开心使用,如遇问题请致电"+phone
*/
如果程序对这种附加字符串的需求很频繁,并不建议使用+来进行字符串的串联。应该使用java.lang.StringBuilder 类或者java.lang.StringBuffer类。
java.lang.StringBuilder/StringBuffer代表可变的字符序列。它们提供了相同的操作方法。
StringBuilder类的常用构造方法:
StringBuilder()
构造一个其中不带字符的字符串缓冲区,初始容量为 16 个字符
StringBuilder(String str)
构造一个字符串缓冲区,并将其内容初始化为指定字符串内容
方法 | 说明 |
---|---|
StringBuilder append(String str); | 将指定的字符串追加到此字符序列 |
StringBuilder insert(int offset, String str) | 将字符串str插入此字符序列指定位置中 |
int length( ) | 确定 StringBuffer 对象的长度 |
void setCharAt(int pos, char ch) | 使用 ch 指定的新值设置 pos 指定的位置上的字符 |
String toString( ) | 转换为字符串形式 |
StringBuilder reverse() | 反转字符串 |
StringBuilder delete(int start, int end) | 此方法将删除调用对象中从 start 位置开始直到 end 指定的索引 – 1 位置的字符序列 |
StringBuilder deleteCharAt(int pos) | 此方法将删除 pos 指定的索引处的字符 |
StringBuilder replace(int start, int end, String s) | 此方法使用一组字符替换另一组字符。将用替换字符串从 start 指定的位置开始替换,直到 end 指定的位置结束 |
StringBuild和String之间的互相转换
public class StringBuilderTest {
public static void main(String []args) {
StringBuilder sb = new StringBuilder("Java");
sb.append(" action ");
sb.append(1.0);
sb.insert(5, "in ");
String s = sb.toString(); //转换为字符串
System.out.println(s);
}
}
包含collection框架、事件模型、日期和时间设施、国际化和各种实用工具类。
常用类
此类用于生成随机数(伪随机)。
两种构造方法
如果用相同的种子创建两个 Random 实例,则对每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列
Random类的方法:
import java.util.Random;
public class TestRandom {
public static void main(String[] args) {
Random random = new Random();
for(int i = 0; i < 5; i++){
System.out.println(random.nextInt(11));
}
Random random2 = new Random(123);
System.out.println(random2.nextDouble());
Random random3 = new Random(123);
System.out.println(random3.nextDouble());
}
}
注:如果需要获取[min,max]之间的一个随机数 则使用公式
n
e
x
t
I
n
t
(
m
a
x
−
m
i
n
+
1
)
+
m
i
n
nextInt(max-min+1)+min
nextInt(max−min+1)+min
常用方法:
static String toString(int[] a); 返回数组内容的字符串表示形式
static int[] sort(int[] a); 使用快速排序法对指定的int型数组按数字升序进行排序
static int binarySearch(int[] a, int key); 使用二分搜索法搜索指定的int型数组,获得指定值所在的索引值
static int[] copyOf(int[] original, int newLength);复制指定的数组
严格来讲,UTC比GMT更加精确,不过它们的差值不会超过0.9秒
java.util.Date类表示特定的瞬间,精确到毫秒。
常用构造方法
Date()
使用系统当前的时间创建 一个Date实例
内部就是使用System. currentTimeMillis()获取系统当前时间的毫秒数来创建Date对象
Date(long dt)
使用自1970年1月1日00:00:00 GMT以来的指定毫秒数创建 一个Date实例
常用方法
Date类中的API方法不易于实现国际化,大部分被废弃了*。
Calendar类(日历)是一个抽象基类,主要用于完成日历字段之间相互操作的功能。即可以设置和获取日历数据的特定部分。
使用Calendar.getInstance(); 调用它的子类GregorianCalendar的构造方法 来获取Calendar类的实例
常用方法:
获取指定日历字段值
public int get(int field)
更改指定日历字段值
void set(int field, int value) //设置指定时间单位的值 不会重新计算日历的时间值
void add(int field, int amount) //强迫日历系统立即按照增/减时间 重新计算日历的毫秒数和所有字段
//Calendar的使用方式 import java.util.Calendar; import java.util.GregorianCalendar; public class CalendarTest { public static void main(String[] args) { // Calendar cal = Calendar.getInstance(); Calendar cal = new GregorianCalendar(); System.out.println("Date 和 Time 的各个组成部分: "); System.out.println("年: " + cal.get(Calendar.YEAR)); // 一年中的第一个月是JANUARY,值为0 System.out.println("月: " + (cal.get(Calendar.MONTH))); System.out.println("日: " + cal.get(Calendar.DATE)); // Calendar的星期常数从星期日Calendar.SUNDAY是1,星期六Calendar.SATURDAY是7 System.out.println("星期: " + (cal.get(Calendar.DAY_OF_WEEK))); System.out.println("小时: " + cal.get(Calendar.HOUR_OF_DAY)); System.out.println("分钟: " + cal.get(Calendar.MINUTE)); System.out.println("秒: " + cal.get(Calendar.SECOND)); } }
//add 和 set方法示例 import java.util.Calendar; import java.util.Date; public class CalendarChangeFieldTest { public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); // 从一个 Calendar 对象中获取 Date 对象 calendar.setTime(date); //使用给定的 Date 设置此 Calendar 的时间 calendar.set(Calendar.DAY_OF_MONTH, 8); System.out.println("当前时间日设置为8后,时间是:" + calendar.getTime()); calendar.add(Calendar.HOUR, 2); System.out.println("当前时间加2小时后,时间是:" + calendar.getTime()); calendar.add(Calendar.MONTH, -2); System.out.println("当前日期减2个月后,时间是:" + calendar.getTime()); } }
//Calendar与Date的转换
public class Calendar2DateTest {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
//Calendar转换成Date
Date date = cal.getTime();
//Date转换成Calendar
Date date2 = new Date();
Calendar cal2 = Calendar.getInstance();
cal2.setTime(date2);
}
}
国际化(I18N):是指某个软件应用程序运行时,在不改变它们程序逻辑的前提下支持各种语言和区域。
本地化(简称L10N):是指某个软件应用程序在运行时,能支持特定地区。
格式化是指对一些语言和区域敏感的数据按照特定的要求进行特定输出。
代表特定的地理、政治和文化地区
国际标准语言代码是小写的两个字母。中文zh、英文en
区域代码是大写的两个字母。大陆CN、台湾TW、美国US。
用于加载一个资源包
常用方法:
static ResourceBundle getBundle(String baseName, Locale lo)
String getString(String key)
提供了与语言无关的生成连接消息的方式。格式化字符串
v常用方法:
/* classpath下的资源文件 msgs.properties msgs_zh_CN.properties 需要通过native2ascii.exe把本地字符转换成Unicode码 */ public class I18NHelloWorldTest { public static void main(String[] args) { Locale myLocale = Locale.getDefault(); //取得系统默认的国家/语言环境 //根据指定的国家/语言环境加载资源包 ResourceBundle bundle = ResourceBundle.getBundle("msgs", myLocale); //从资源包中取得key所对应的消息 String msg = bundle.getString("hello"); //为带占位符的字符串传入参数 String s = MessageFormat.format(msg, new Object[]{"张三", new Date()}); System.out.println(s); } }
DateFormat用来格式化(日期->文本)、解析(文本->日期)日期/时间。
public class TestDateFormat { public static void main(String[] args) { Date date = new Date(); DateFormat formater = new SimpleDateFormat(); System.out.println(formater.format(date)); DateFormat f2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); System.out.println(f2.format(date)); try { Date date2 = f2.parse("2008年08月08日 08:08:08"); System.out.println(date2.toString()); } catch (ParseException e) { e.printStackTrace(); } } }
NumberFormat类用来格式化和解析数值。
获取实例:static getInstance()
也可通过DecimalFormat类的构造方法指定模式字符串来创建
格式化:
String format(long n)
String format(double d)
解析:
Number parse(String s);
public class DecimalFormatTest {
public static void main(String[] args) {
System.out.println(formatDecimal("#,###.##", 12345.6));
System.out.println(formatDecimal("0,000.00", 12345.678));
System.out.println(formatDecimal("#0.00%", 0.123456789));
System.out.println(formatDecimal("¤#,##0.00", 12345.6789));
}
public static String formatDecimal(String pattern, double num){
DecimalFormat df = new DecimalFormat(pattern);
return df.format(num);
}
}
当程序中出现大数字类型并需要进一步计算时:
666666666666666666666666666 * 88888888888888 = ?
Java语言支持大数字的操作。在java.math包中提供了两个专门的类:
BigInteger类代表任意精度的整数
BigDecimal类代表任意精度的浮点数。
public class BigDecimalTest {
public static void main(String[] args) {
BigDecimal bd = new BigDecimal("66666666666.666666666");
BigDecimal bd2 = new BigDecimal("1234567890.123456789");
System.out.println("和:"+ bd.add(bd2));
System.out.println("差:"+ bd.subtract(bd2));
System.out.println("积:"+ bd.multiply(bd2));
System.out.println("商:"+bd.divide(bd2, 10, BigDecimal.ROUND_HALF_UP));
}
}
JDK8版本开始较之前的版本变化较大、核心添加了流式编程、并增加了G1垃圾回收机制
新特性:
在JDK1.8中允许接口中有已实现的方法,但该方法必须使用static或者default来修饰。
interface Formula {
double calculate(int a);
default double sqrt(int a) {return Math.sqrt(a); }//接口中定义默认方法
}
Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100);//实现接口方法中调用接口中的默认方法 }};
formula.calculate(100); // 100.0formula.sqrt(16); // 4.0
在JD1.8中允许方法/匿名内部类中使用非常量的值,即可以使用非final修饰的变量,但是该变量只能赋值一次,如果多次赋值则也不能在其内部使用。
public class Outer{
public void test(){
final int x = 9;//可以访问final修饰的变量
int y = 10;//jdk8.0后 可以访问只赋值过一次的变量
//y = 12;//非final修饰的变量,如果改变它的值,则不能被方法类访问
class FunctionInner{//方法类(局部内部类)
public void show(){ System.out.println(x+" "+y); }
}
FunctionInner f = new FunctionInner();//创建方法类对象
f.show();//调用方法类中的方法
}
在JDK1.8中专门提供了一个java.time包,该包内部提供了操作日期和时间的类,首先这些类大部分是不可变的,类似于String的不可变性。
所以在使用时线程安全,针对于之前的Date或者Calender类来说,当前的时间日期类更加符合日常规则,使用灵活方便。
常用的类:
关于时间的相关名词 | 含义 |
---|---|
GMT | Greenwich Mean Time 格林尼治标准时间,十七世纪,格林威治皇家天文台为了海上霸权的扩张计画而进行天体观测。到了1884年决定以通过格林威治的子午线作为划分地球东西两半球的经度零度,并以地球由西向东每24小时自转一周360°,订定每隔经度15°,时差1小时。而每15°的经线则称为该时区的中央经线,将全球划分为24个时区,其中包含23个整时区及180°经线左右两侧的2个半时区,东区的时间要早于西区,例如北京是东八区,东京在东九区,北京现在是下午14:00,那东京就是下午15:00 |
UTC | Coordinated Universal Time 世界协调时间,比GMT更加精准,但功能和精度基本差不多 |
ISO | ISO 8601是日期和时间的表示方法,即用字母和符号表示日期和时间 |
时间戳 | 从1970-01-01 00:00:00与现在所差的秒数,这里的差值表示的伦敦时间的差值,换算到本地时,会加上本地的偏移量的秒数 |
在JDK8中 常用的三个时间相关类
以上的类全部都实现了Temporal接口 而其父接口为TemporalAccess
这三个类分别表示使用 ISO-8601日历系统的 日期、时间、日期和时间
/* 通过静态方法 now() 返回该类的实例 */
LocalDateTime now = LocalDateTime.now(); //获取当前的日期时分秒
System.out.println(now);
LocalDate now1 = LocalDate.now(); //获取当前的日期
System.out.println(now1);
LocalTime now2 = LocalTime.now(); //获取当前的时分秒
System.out.println(now2);
/* 静态方法 of() 返回该类的实例 */
LocalDateTime localDateTime = LocalDateTime.of(2048, 11, 25, 12, 00, 30); //指定日期时分秒
System.out.println(localDateTime);
LocalDate date = LocalDate.of(2020, 12, 12); //指定日期
System.out.println(date);
LocalTime time = LocalTime.of(14, 20, 30); //指定时分秒
System.out.println(time);
常用get方法
•getYear():获取年
•getHour():获取小时
•getMinute():获取分钟
•getSecond():获取秒值
•getDayOfMonth():获得月份天数(1-31)
•getDayOfYear():获得年份天数(1-366)
•getDayOfWeek():获得星期几(返回一个 DayOfWeek枚举值)
•getMonth():获得月份(返回一个 Month 枚举值)
•getMonthValue():获得月份(1-12)
LocalDateTime now = LocalDateTime.now(); //获取年份 int year = now.getYear(); System.out.println(year); //获取月份枚举 //Month 枚举类,定义了十二个月份 Month month = now.getMonth(); System.out.println(month); //获取月份的数值 int monthValue = now.getMonthValue(); System.out.println(monthValue); //获取当天在本月的第几天 int dayOfMonth = now.getDayOfMonth(); System.out.println(dayOfMonth); //获取小时 int hour = now.getHour(); System.out.println(hour); //获取分钟 int minute = now.getMinute(); System.out.println(minute); //获取秒值 int second = now.getSecond(); System.out.println(second);
LocalDateTime可以通过toLocalDate()转换成LocalDate
通过toLocalTime()转成LocalTime
反之也可以
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//将目标LocalDateTime转换为相应的LocalDate对象
LocalDate localDate = now.toLocalDate(); System.out.println(localDate);
//将目标LocalDateTime转换为相应的LocalTime对象
LocalTime localTime = now.toLocalTime(); System.out.println(localTime);
//将LocalDate和LocalTime转换成LocalDateTime
LocalDateTime.0f(localDate,localTime);
时间判定方法
注意:以上方法只能在相同类型之间进行判定
//获取当前的日期
LocalDate now = LocalDate.now();
//指定的日期
LocalDate of = LocalDate.of(2015, 12, 12);
//判断一个日期是否在另一个日期之前
boolean before = of.isBefore(now); System.out.println(before);
//判断一个日期是否在另一个日期之后
boolean after = of.isAfter(now); System.out.println(after);
//判断这两个日期是否相等
boolean after1 = now.equals(of); System.out.println(after1);
//判断闰年
boolean leapYear = of.isLeapYear(); System.out.println(leapYear);
按时间单位增减方法(plus/minus系列的方法)如果传入的值为负数 则其功能反转
//增加时间量的方法 plusXXX系类的方法 返回的是一个新的日期对象
LocalDateTime now = LocalDateTime.now();
//可以给当前的日期增加时间量
LocalDateTime newDate= now.plusYears(1);
int year = newDate.getYear();
System.out.println(year);
//减去时间量的方法minusXXX 系列的方法 返回的是一个新的日期对象
LocalDate now1 = LocalDate.now();
LocalDate newDate2 = now1.minusDays(10);
int dayOfMonth = newDate2.getDayOfMonth();
System.out.println(dayOfMonth);
指定年月日时分秒的方法(均不改变原有时间而是返回一个新的时间副本)
LocalDate方法
LocalTime方法
以上方法LocalDateTime 均可用
//设置日期
LocalDate now2 = LocalDate.now();
LocalDate localDate = now2.withYear(2014);
System.out.println(localDate);
//设置时间
LocalTime now3 = LocalTime.now();
LocalTime lt = now3.withMinute(33);
System.out.println(lt);
TemporalAdjuster接口—— 时间校正器
提供了校正时间的具体执行方案,一般来说常用一个TemporalAdjusters类中的静态方法来获取一些指定好的常用规则,具体参照API文档
我们主要运用它的几个静态方法next()/previous()方法来指定日期
或者采用自定义的方式来指定日期with(TemporalAdjuster adjuster):指定特殊时间
LocalDate now = LocalDate.now();
//指定日期
//对于一些特殊的日期,可以通过一个工具类TemporalAdjusters 来指定
TemporalAdjuster temporalAdjuster = TemporalAdjusters.firstDayOfMonth(); //见名知意,本月第一天 LocalDate with = now.with(temporalAdjuster);
System.out.println(with);
TemporalAdjuster next = TemporalAdjusters.next(DayOfWeek.SUNDAY); //下周周末
LocalDate with1 = now.with(next);
System.out.println(with1);
当TemporalAdjusters中的方法不能满足需求是,可以自定义TemporalAdjuster实现方法,例如获取下一个工作日
LocalDate now1 = LocalDate.now(); //自定义日期 —— 下一个工作日 LocalDate with2 = now1.with(new TemporalAdjuster() { @Override //参数 nowDate 当前的日期对象 public Temporal adjustInto(Temporal nowDate) { //向下转型 LocalDate date= (LocalDate) nowDate; if(date.getDayOfWeek().equals(DayOfWeek.FRIDAY)){ LocalDate localDate = date.plusDays(3); return localDate; } else if(date.getDayOfWeek().equals(DayOfWeek.SATURDAY)){ LocalDate localDate = date.plusDays(2); return localDate; }else{ LocalDate localDate = date.plusDays(1); return localDate; } } }); System.out.println("下一个工作日是:" + with2);
主要用until方法来获取两个时间按照指定单位计算的相隔时间
Period until(ChronoLocalDate endDateExclusive)
将此日期和其他日期之间的期间计算为 Period 。 此方法只有LocalDate可用
long until(Temporal endExclusive, TemporalUnit unit)
根据指定的单位计算直到另一个日期的时间量。 一般使用此方法
//设置日期
LocalDateTime now1 = LocalDateTime .of(2020,11,12,10,20,23);
LocalDateTime now2 = LocalDateTime . of(2020,12,30,12,22,25);
//ChronoUnit是TemporalUntil的实现枚举,具体参考API文档
long days = now1.until(now2, ChronoUnit.DAYS);
long weeks = now1.until(now2, ChronoUnit. WEEKS );
long hours= now1.until(now2, ChronoUnit. HOURS );
System.out.println(“相差天数:”+days);
System.out.println(“相差星期:”+ weeks );
System.out.println(“相差小时:”+hours);
DateTimeFormatter类 解析和格式化日期或时间的类
常用API :
//指定格式 静态方法 ofPattern()
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // DateTimeFormatter 自带的格式方法
LocalDateTime now = LocalDateTime.now();
//把日期对象,格式化成字符串
String format = formatter.format(now);
//刚才的方式是使用的日期自带的格式化方法
String format1 = now.format(formatter);
System.out.println(format); System.out.println(format1);
//把字符串转日期对象
LocalDateTime lt1 = formatter. parse(“2020-12-25 00:05:36”,LocalDateTime::from);//不常用
LocalDateTime lt2= LocalDateTime.parse (“2020-12-25 00:05:36”, formatter);//常用
System.out.println(lt1 ); System.out.println(lt2);
在时间线上的瞬间点。 该类在时间线上建立单个瞬时点。 这可能用于在应用程序中记录事件时间戳。
Instant.now():获取当前时刻,注意默认获取的是美国时间,而中国是东8区,所以要设置时区
Instant now = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));//设置为东8区时间
//互相转化
Date date = Date.from(localDateTime2.atZone(ZoneId.systemDefault()).toInstant());
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(),ZoneId.systemDefault());
用于计算两个“时间”间隔的类
常用API:
注意:不适合LocalDate使用
Instant start = Instant.now();
for (int i = 0; i < 15; i++) { System.out.println(i); }
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
long l = duration.toNanos(); //间隔的时间
System.out.println("循环耗时:"+l+"纳秒");
用于计算两个“日期”间隔的类
常用API:
注意:不适合LocalTime使用
//计算两个日期的间隔
LocalDate birthday = LocalDate.of(2012, 12, 12);
LocalDate now = LocalDate.now();
//模拟从出生到现在,有多少岁,零几个月,几天 计算两个日期的间隔
Period between = Period.between(birthday, now);
int years = between.getYears();
int months = between.getMonths();
int days = between.getDays();
System.out.println(“出生了"+years+"年"+months+"月"+days+"天了...");
JDK8.0新增注解类型@FunctionalInterface
在一个接口上方添加@FunctionalInterface,代表这个接口为函数式接口,函数式接口只允许有一个抽象方法。编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
@FunctionalInterface
//这个接口专门用于类型转换
interface Converter<F, T> {
T convert(F from);//只有一个抽象方法
}
:: 符号
Java 8 可以使用 ::关键字来传递方法或者构造函数引用,只适用于函数式接口,且两个::中间不能加任何符号。
使用匿名内部类方式实现接口:
//正常情况使用匿名内部类来实现接口中的抽象方法,功能说明:将字符串类型转化为int类型
Convert<Integer, String> convert =
new Convert<Integer, String>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
};
int temp = convert1.convert("123");
System.out.println(temp);//输出123
使用Lambda表达式实现接口
//由于上面功能的实现核心在于Intege.valueOf(from)方法,所以可以直接将该方法的引用交给Convert接口,来实现Convert接口中的抽象方法
//使用::关键字,将Integer的静态方法引用赋值给convert
//注意,引用的方法不能带()
Convert<Integer, String> convert = Integer::valueOf;
int temp = convert1.convert("123");
System.out.println(temp);//输出123
::关键字还可以用来引用对象的方法。
public class TestA { //获取字符串的第一个字符 public String startWith(String a){ return a.substring(0, 1); } } public class Test{ public static void main(String[] args) { TestA ta = new TestA();//创建TestA的对象ta //将ta的startWith方法的引用赋值给converter 接口, //相当于用satrtWith方法中的内容来实现了Convert接口中的抽象方法 Convert<String, String> converter = ta::startWith; String converted = converter.convert("Java"); System.out.println(converted); //输出”J” } }
::关键字可以用来引用构造函数
//首先我们定义一个包含多个构造函数的简单类 class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } //接下来我们指定一个用来创建Person对象的对象工厂接口 interface PersonFactory<P extends Person> { P create(String firstName, String lastName); } //这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂 PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker"); /* 说明: 只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会 自动根据PersonFactory.create方法的签名来选择合适的构造函数。 */
简单介绍 λ(Lambda) 表达式
在Java8之前,想实现一个只用一次的接口可以使用匿名内部类
但在Java 8 中你就没必要使用这种传统的匿名内部类的方式了,Java 8提供了更简洁的语法,lambda表达式
适用范围:只能适用于函数(型)接口(functional interface),接口中只有一个抽象方法。
语法:
(形参列表) -> { 要实现对应抽象方法的代码块}
示例:
//定义一个函数接口 @FunctionalInterface public interface MathUtils{ int add(int a,int b);//返回两个数字相加的和 } //匿名内部类方法实现 MathUtils mUtils = new MathUtils(){ @Override public int add(int a,int b) { return a+b; } }; System.out.println(mUtils.add(5,3));//输出8 //用λ(Lambda) 表达式方式实现 MathUtils mUtils = (int a, int b) -> {return a+b;}; System.out.println(mUtils.add(5,3));//输出8
对于完整的语法,可以根据不同的情况简化该结构
参数列表部分:
//以上面的MathUtils接口为例,可简化参数列表
MathUtils mUtils = (a, b) -> {return a+b;};
System.out.println(mUtils.add(5,3));//输出8
@FunctionalInterface
public interface StringUtils {
//获取str的第一个字符
String getFirstChar(String str);
}
//可简化参数列表
StringUtils strUtil = (s)->{ return s.substring(0, 1);};
System.out.println(strUtil.getFirstChar(“Java”));//输出“J”
@FunctionalInterface
public interface StringUtils {
//任意返回一个字符串
String getString();
}
//可简化参数列表为
StringUtils strUtil = ()->{ return “Hello World”;};
System.out.println(strUtil.getString());//输出“Hello World”
{代码块}部分
//示例1
@FunctionalInterface
public interface StringUtils {
//任意返回一个字符串
String getString();
}
//可将代码块部分简化为
StringUtils strUtil = ()-> “Hello World”;
System.out.println(strUtil.getString());//输出“Hello World”
//示例2
@FunctionalInterface
public interface MathUtils{
int add(int a,int b);//返回两个数字相加的和
}
//可将代码块部分简化为
MathUtils mUtils = (a,b) -> a+b;
System.out.println(mUtils.add(5,3));//输出8
JDK8.0针对于流式编程中频繁出现的NullPointException,提供了一个解决方案。Optional是一个容器对象,将非空检测标准化。
Optional的常用方法:
方法 | 描述 |
---|---|
empty | 放回一个值为空的Optional实例 |
filter | 如果值存在并且满足提供的谓词,就返回包含该Optional对象;否则返回一个空的Optional对象 |
flatMap | 如果值存在,就对该值执行提供的mapping函数,将mapping函数返回值直接返回,但要求其返回值类型必须为Optional,如果不是,需要手动将返回值封装成Optional对象,否则就返回一个空的Optional对象 一般用在返回值类型本身就是Optional类型 |
get | 如果值存在就返回该Optional对象,否则就抛出一个 NoSuchElementException异常 |
ifPresent | 如果值存在就对该值执行传入的方法,否则就什么也不做 |
isPresent | 如果值存在就返回true,否则就返回false |
map | 如果值存在,就对该值执行提供的mapping函数调用,并自动将mapping函数返回值用Optional封装并返回,所有返回值都会自动被Optional进行封装 常用在返回值类型是非Optional类型的情况 |
of | 如果传入的值存在,就返回包含该值的Optional对象,否则就抛出NullPointerException异常 |
ofNullable | 如果传入的值存在,就返回包含该值的Optional对象,否则返回一个空的Optional对象 |
orElse | 如果值存在就将其值返回,否则返回传入的默认值 注:orElse中的内容无论如何都会执行 |
orElseGet | 如果值存在就将其值返回,否则返回一个由指定的Supplier接口生成的值 注:只有当值为空时才调用 |
orElseThrow | 如果值存在就将其值返回,否则返回一个由指定的Supplier接口生成的异常 |
Optional的使用示例:
//当前有3个类 有嵌套关系 注:为了节省空间 省略所有的get/set方法 class Person{ private Address addr; private String name; } class Address{ private Content content; private String no; } class Content{ private String street; private String doorNo; }
使用普通范式来避免NPE问题,并通过person获取street的值
boolean f = false; if(null != person){ Address addr = person.getAddr(); if(null != addr){ Content content = addr.getContent(); if(null != content){ String street = content.getStreet(); System.out.println(street); }else{ f = true; } }else{ f = true; } }else{ f = true; } if(f){ System.out.println("获取值的过程出现NPE问题"); }
使用Optional来皮面NPE问题,并通过person获取street的值
String street = Optional.ofNullable(person)
.map(p->p.getAddr())
.map(Address::getContent)
.map(Content::getStreet).orElse("出现NPE问题");
System.out.println(street);
笔者:娄黔子
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。