java一些精干知识点分享
2. java小干货
2.1循环遍历
数组、list、map都需要遍历循环,有下面集中循环方式
1.for ecah
list可以是一个数组、list、set
// list可以是一个数组、list、set
for(bject o :list)
{
}
2.Iterator迭代器
list可以是list、set类的子类
Iterator iter = list.iterator();
while(iter.hasNext()){
Object o = iter.next();
}
3.loop with size
可以是数组、list能得到索引长度的类及子类
for(int i=0;i<list.size();i++){
Object o= list.get(i);
}
4.lambda表达式
list.forEach(
(value)->{
System.out.println(value);
}
);
代码示例
public class GanHuo {
@Test
public void t1(){
//1.for-each遍历,最简单,但无法获得索引
System.out.println("=======for(Object:list)循环=========");
String arrayStr[]=new String[]{"1","2","3"};
//1-1 数组
System.out.println("for-each遍历数组-----------");
for (String s : arrayStr) {
System.out.println("arrayStr:"+s);
}
//1-2 list
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("for-each遍历list-----------");
for (Integer i : list) {
System.out.println("list:"+i);
}
//1-3 遍历map
Map<String,String> map=new HashMap<>();
System.out.println("for-each遍历Map-----------");
map.put("1", "111");
map.put("2", "222");
map.put("3", "222");
//得到key的collecton
Set<String> keySet=map.keySet();
for(String key:keySet){
System.out.println("map["+key+"]="+map.get(key));
}
}
@Test
public void t2(){
System.out.println("=======Iterator循环================");
//1-2 list
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("Iterator 遍历list-----------");
Iterator iter=list.iterator();
while (iter.hasNext()) {
System.out.println("list:"+iter.next());
}
//1-3 遍历map
Map<String,String> map=new HashMap<>();
System.out.println("Iterator 遍历Map-----------");
map.put("1", "111");
map.put("2", "222");
map.put("3", "222");
//得到key的collecton
Set<String> keySet=map.keySet();
iter=keySet.iterator();
while (iter.hasNext()) {
String key= (String) iter.next();
System.out.println("map["+key+"]="+map.get(key));
}
}
@Test
public void t3(){
System.out.println("=======for。size循环================");
//1-1 数组
String arrayStr[]=new String[]{"1","2","3"};
System.out.println("loop size 遍历数组-----------");
for (int i = 0; i < arrayStr.length; i++) {
System.out.println("arrayStr["+i+"]="+arrayStr[i]);
}
//1-2 list
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("loop size 遍历list-----------");
for (int i = 0; i < list.size(); i++) {
System.out.println("list["+i+"]="+list.get(i));
}
}
@Test
public void t4(){
System.out.println("=======lambda表达式================");
//list的lambda表达式
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("lambda表达式 遍历list-----------");
list.forEach(
(value)->{
System.out.println(value);
}
);
//Map的lambda表达式
Map<String,String> map=new HashMap<>();
System.out.println("lambda表达式 遍历Map-----------");
map.put("1", "111");
map.put("2", "222");
map.put("3", "222");
map.forEach((key, value)->{
System.out.print("key = " + key);
System.out.println(", value = " + value);
});
}
}
2.2可变参数
可变参数是指不指定参数的个数
定义:
数据类型… 变量名
可变参数规范
1.可变参数本身是一个数组
2.可变参数也可传递一个数组
3.可变参数个数不受限制,可无限制,也可没有
4.如果可变参数和常规参数混合,则可变参数要放到最后
5.每个方法最多只能有一个可变参数
代码示例
public class ChangeParam {
public void param(Integer... val){
System.out.println("val包含:"+val.length+"个参数");
for (Integer i : val) {
System.out.println(i);
}
}
/***
如果一个方法里包含常规参数和可变参数,则可变参数必须放置到最后一个
*/
public void param1(String name,Integer age,String... likes){
System.out.println("name:"+name);
System.out.println("age:"+age);
System.out.println("likes-----");
String temp="";
for (String like : likes) {
temp+=like+",";
}
System.out.println(temp);
}
@Test
public void t1(){
//可以传递不受限制的个数
param(1,2,3);
//可以不传参数
param();
//也可传递数组
Integer[] array={1,2,3};
param(array);
//常规和可变参数混合
param1("蒋增奎",20,"火锅","串串","烤鸭");
}
}
2.3 list和数组转化
2.3.1 数组转list
1.使用List=Arrays.asList(数组对象),最简单,但不能新增删除
2.List=new ArrayList<>(Arrays.asList(intArray)),可新增删除,但性能差
3. Collections.addAll(list对象, 数组对象);可新增删除,性能好
4.list= Stream.of(数组对象).collect(Collectors.toList());
@Test
public void array2List(){
System.out.println("=========Arrays.asList=======");
//1---直接用Arrays.asList后不能再新增
System.out.println("1---直接用Arrays.asList后不能再新增");
//基本类型数组
Integer[] intArray={1,2,3};
List<Object> list1= Arrays.asList(intArray);
// list1.add(4); //不能再新增,否则要报异常
printList(list1);
//自定义数组
TestVO[] objArray={new TestVO(1,"jzk"),new TestVO(2,"张三")};
list1=Arrays.asList(objArray);
printList(list1);
//2--Arrays.asList作为构造参数传入,可新增,但性能差
System.out.println("2--Arrays.asList作为构造参数传入,可新增,但性能差");
list1=new ArrayList<>(Arrays.asList(intArray));
list1.add(4);
printList(list1);
//3--Collections.addAll(list,数组);能新增删除,性能好
System.out.println("3--Collections.addAll(list,数组);能新增删除,性能好");
list1=new ArrayList<>();
Collections.addAll(list1, intArray);
list1.add(4);
printList(list1);
//4--使用 Stream;能新增删除,性能好
System.out.println("4--使用 Stream;能新增删除,性能好");
list1= Stream.of(intArray).collect(Collectors.toList());
list1.add(4);
printList(list1);
}
private void printList(List<Object> list){
for (Object o : list) {
System.out.println(o);
}
}
2.3.2 list转数组
核心用到list.toArray()方法
1.Object[] arrays=list.toArray(); 只能获得Object数组
2.对象数组=list.toArray(new 对象[list.size] ) ;//可以获得对应的对象数组
测试代码
@Test
public void list2array(){
List<TestVO> list=getTestVO();
System.out.println("方法1:list.toArray()===========");
//注意:只能用转换成Object[]数组,不能强制转化
Object[] arrays=list.toArray();
for (Object array : arrays) {
System.out.println((TestVO)array);
}
System.out.println("方法2:list.toArray(数组对象)===========");
//这样可获得实际的类
TestVO[] vos=new TestVO[list.size()];
list.toArray(vos );
for (TestVO vo : vos) {
System.out.println(vo);
}
//或者这样写
System.out.println("方法3:list.toArray(数组对象)简写===========");
vos=list.toArray(new TestVO[list.size()]);
for (TestVO vo : vos) {
System.out.println(vo);
}
System.out.println("方法4:String[] strings = list.stream().toArray(String[]::new);===");
TestVO[] vos2=list.stream().toArray( TestVO[]::new);
for (TestVO vo : vos2) {
System.out.println(vo);
}
}
private void printList(List<Object> list){
for (Object o : list) {
System.out.println(o);
}
}
private List<TestVO> getTestVO(){
List<TestVO> list=new ArrayList<>();
list.add(new TestVO(1,"jzk"));
list.add(new TestVO(2,"张三"));
return list;
}
2.4 值传递和地址传递
在学习 Java 编程语言的过程中,我们经常会听到“值传递”和“地址传递”这两个概念。它们是用来描述参数传递方式的术语,而理解它们的区别对于编写高效的代码非常重要。在本文中,我们将详细介绍这两种传递方式,并通过代码示例来说明它们的差异。
2.4.1值传递
在 Java 中,基本数据类型(如整数、布尔值等)都是以值传递的方式进行参数传递。这意味着当我们将一个基本数据类型作为参数传递给一个方法时,方法内部会创建一个新的变量来存储这个参数的值,而不会影响原始的变量。
执行过程
- 首先,在调用方法时,将实际参数的值复制一份,并将这份副本传递给方法进行操作。
- 在方法内部,这个副本的值被赋给一个新的局部变量。
- 在方法执行过程中,对该局部变量的任何改动都不会影响原始的变量,因为它们指向的是不同的内存空间。
- 当方法执行完毕后,这个局部变量和方法的栈帧都会被销毁,而原始的变量的值保持不变。
基本数据类型的传递过程中,传入的值被复制到方法内部,并在方法内部进行操作,但不会影响原始变量的值。
@Test
public void valPass(){
String str="a";
double db= Double.parseDouble("34.5");
Double db1=45.2;
Integer zs=Integer.valueOf(100);
int zs1=200;
boolean b=false;
Boolean b1= true;
System.out.println("str="+str);
System.out.println("db="+db);
System.out.println("db1="+db1);
System.out.println("zs="+zs);
System.out.println("zs1="+zs1);
System.out.println("b="+b);
System.out.println("b1="+b1);
change1(str,db,db1,zs,zs1);
System.out.println("str="+str);
System.out.println("db="+db);
System.out.println("db1="+db1);
System.out.println("zs="+zs);
System.out.println("zs1="+zs1);
}
private void change1(String str,double db,Double db1,int zs,Integer zs1){
System.out.println("============change==========");
str="b";
db=-45.2;
db1=-22.4;
zs=-1;
zs1=-2;
}
执行效果,可以看出这些都是值传递,被引用后并没有改变以前值:
str=a
db=34.5
db1=45.2
zs=100
zs1=200
b=false
b1=true
============change==========
str=a
db=34.5
db1=45.2
zs=100
zs1=200
2.4.2 地址传递
与基本数据类型不同,Java 中的对象类型(如数组、集合、自定义类等)则是以地址传递的方式进行参数传递。这意味着当我们将一个对象作为参数传递给一个方法时,方法内部使用的是这个对象的引用,而不是对象本身。
执行过程
-
创建一个对象并将其引用赋值给一个变量。
-
将这个变量作为参数传递给一个方法。
-
在方法内部,参数变量接收到了对原始对象的引用。
-
在方法内部修改参数变量所指向的对象时,原始对象也会受到影响。
-
方法执行完毕后,返回到原始调用处,可以通过原始变量访问到被修改后的对象。
对象的引用传递意味着传递的是对象的引用,通过引用可以访问和修改原始对象的属性。
测试代码:
@Test
public void addressPass(){
StringBuffer sb=new StringBuffer("aa");
TestVO vo=new TestVO(1,"蒋增奎");
String[] array={"1","2","3"};
List<Integer> list=new ArrayList<>();
list.add(1);list.add(2);
Map<String,Integer> map=new HashMap<>();
map.put("1", 11);
map.put("2", 22);
System.out.println("sb:"+sb);
System.out.println("vo:"+vo);
System.out.println("array:"+ Arrays.toString(array));
System.out.println("list:"+Arrays.toString(list.toArray()));
System.out.println("map:"+map.toString());
System.out.println("================");
change2(sb,vo,array,list,map);
System.out.println("sb:"+sb);
System.out.println("vo:"+vo);
System.out.println("array:"+ Arrays.toString(array));
System.out.println("list:"+Arrays.toString(list.toArray()));
System.out.println("map:"+map.toString());
}
private void change2( StringBuffer sb, TestVO vo,String[] array,
List<Integer> list,Map<String,Integer> map){
sb.append("dd");
vo.setName("蒋大爷");
array[0]="改值11";
list.add(3);
map.put("3",333);
}
效果,地址引用值都会被改变
sb:aa
vo:TestVO(id=1, name=蒋增奎)
array:[1, 2, 3]
list:[1, 2]
map:{1=11, 2=22}
================
sb:aadd
vo:TestVO(id=1, name=蒋大爷)
array:[改值11, 2, 3]
list:[1, 2, 3]
map:{1=11, 2=22, 3=333}
代码2:
@Test
public void t2() {
TestVO vo1=new TestVO(1, "jzk");
TestVO vo2=vo1;
vo2.setName("奎哥");
System.out.println(vo1); //打印TestVO(id=1, name=奎哥),不是jzk
System.out.println(vo2);//TestVO(id=1, name=奎哥)
}
首先创建一个对象TestVO vo1,接着申请另一款空间,用来创建TestVO vo2,且vo2=vo1,说明两个数组都指向同一块空间,修改vo2中的字段也就相当于修改了vo1中对应的元素。
代码3
@Test
public void t1(){
TestVO vo=new TestVO(1, "jzk");
System.out.println(vo);
change3(vo);
System.out.println(vo);
change4(vo);
System.out.println(vo);
}
private void change3(TestVO vo){
TestVO vo1=vo; //把vo赋值给vo1,两个对象是指向同一个地址
vo1.setName("奎哥");
}
private void change4(TestVO vo){
vo.setName("鸡哥");
}
打印效果:
TestVO(id=1, name=jzk)
TestVO(id=1, name=奎哥)
TestVO(id=1, name=鸡哥)
代码说明:
change3()和change4()效果一样,change3()虽然通过了赋值,但两个对象指向的同一个地址
2.4.3易错点总结
易错点
java对象:String ,int等对应的封装类Integer,java.util.Date,BigDecimal是值传递,不是地址传递,虽然他们是对象
值传递VS地址传递
2.5 数据类型
2.5.1基础知识
Java基本类型共有八种,基本类型可以分为三类,字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。数值类型又可以分为整数类型byte、short、int、long和浮点数类型float、double。JAVA中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们进行操作
代码说明
//基础数据类型
@Test
public void t1(){
//1.整数类型
byte b=100;
short s=10000;
int i=1000000000;
//注意:长整型long要加L后缀,才能最大化
long l=900000000000000000L;
System.out.println("byte:"+b);
System.out.println("short:"+s);
System.out.println("int:"+i);
System.out.println("long:"+l);
//2.浮点型
//注意:小数默认是double,如果要用float,需要再数字后面加f或者F
float f=34.67f;
double d=34.67;
//char类型,char必须用单引号包围,只能一个字符
char c1='A';
char c2='2';
//boolean
boolean b1=false;
}
注意事项:
1.整数:short int long 就是取值范围不一样,对计算没有影响,当然计算性能存储越低性能越高,long后面需要增加L/l后缀
2.浮点型:小数默认为double类型,float要加l/L后缀,double计算精度高,但性能差些,但高精度计算用BigDecmal(小数存在计算精度问题)
3.基础类型不能赋null,只有对应包装类才行
默认值和初始化
每一种类型都有一个默认值,除基本类型外,其他的类型的默认值都是 null,因为它们都是引用类型。整数默认为 int 类型,浮点数默认为 double 类型。
代码
public float f;
public double d;
public boolean b;
private int i;
private Boolean b3;
@Test
public void t2(){
//静态方法有默认值
System.out.println(f); //0
System.out.println(d); //0
System.out.println(i); //0
System.out.println(b); //false
System.out.println(b3); //null
boolean b1;
System.out.println(b1); //编译不通过
}
注意事项:
1.只有为class的字段属性声明才有默认值
2.在方法内声明的变量,如果不赋初始值,编译不通过
2.5.2 基础数据和包装类
Java 是面向对象的语言,但是为了便于开发者的使用,Java 中却沿用了 C 语言的基本数据类型,在进行基本的数据计算时,开发者可以直接使用基础类。但是基本数据类型是不具备对象的特征的,不能调用方法,而且基本数据类型不能存入集合中,所以就需要将基础数据类型实例封装为 Java 对象,使其具有了对象的属性和方法。
基础类型和包装类的区别
-
存储位置不同:
基本数据类型直接将值放在栈中;
包装类型是把对象放在堆中,然后通过对象的引用来调用他们 ; -
初始值不同:
int的初始值为 0 、 boolean的初始值为false ;
包装类型的初始值为null ; -
使用方式不同:
基本数据类型直接赋值使用就好;
在集合如 coolectionMap 中只能使用包装类型;
在应用场景中MVC模式的form-vo,Dto,PO对象的属性最好都用包装类,因为基础类型都有默认值0或者false,在实际应用中,null和0,false是有实际意义的 -
包装类是java对象,封装有相关方法,而基础类型没有
比如:Integer.valueOf()等
为什么还要保留基础类型?
- 在 Java 中,使用 new 关键字创建的对象存储在堆中,并且通过栈中的引用来使用这些对象,所以对象本身来说是比较消耗资源的。
- 基本类型存储在栈里,因为栈的效率高,所以保留了基本类型。变量的值存储在栈中,方法执行时创建,结束时销毁,因此更加高效。
- 使用基本数据类型参与计算时的性能要比使用包装类的高。
基础类型之间的转换
(1)基础类型自动转化
1.范围小的类型可以自动转换成范围大的类型。
2.整型可以自动转换成浮点型
3.反之,范围大的转化为范围小的必须强制转换
4.范围:short->int->long->float->double ,从小到大
示例代码:
@Test
public void t3(){
System.out.println("1.自动转换===========");
byte i1=2;
short i2=i1;
int i3=i2;
long i4=i3;
float f1=3.45f;
double f2=f1;
float f3=i3;
double f4=i4;
}
(2)强制执行
范围大的转化为范围小的,必须用强制转换
范围大的类型需要强制转换成范围小的类型,否则会精度丢失。
强制类型转换可能会导致数据溢出或者产生负数。
范围小到范围大的的转换也可以用强制转化,和自动转换效果一样
@Test
public void t4(){
System.out.println("2.强制转换===========");
double f1=34.56565;
float f2=(float) f1;
int i=(int)f1;
System.out.println(i);
//范围小的转换也可以用强制转化,和自动转换效果一样
int i1=34;
float f3=(float)i1;
}
基本类型和包装类的转换
基本类型与包装类之间的转换需要使用到自动装箱和拆箱的操作。
@Test
public void t5(){
System.out.println("3.基本类型和封装类转换===========");
int i=10;
Integer i1=i; //自动装箱
Integer i2=30;
int i3=i2;//自动拆箱
}
包装类的常用方法
1.包装类对象=包装类.valueOf(对应的基础类型);
2.基础数据类型/包装类对象=包装类.parseXXX(字符串) ;字符串转化成数据类型
@Test
public void t6() {
System.out.println("4.包装类的常用方法===========");
Integer i = Integer.valueOf(2);
int i1 = Integer.parseInt("344"); //字符串转成整数
Integer i2 = Integer.parseInt("34444"); //实际是转化成int,int在装箱成Integer
System.out.println(i);
System.out.println(i1);
System.out.println(i2);
}
2.6 字符串
2.6.1 char/String区别
1.char和String的区别
char是字符类型,是基础数据类型,长度固定,用单引号表示 如 c=‘谢’;
String是字符串类型,不是基础数据类型,长度无法确定,用双引号表示 str=“傻啊”。
关于String类。
(1)、String类时final类,所以是不可继承的;
( 2)、String类是的本质是字符数组char[];
( 3)、Java运行时会维护一个String Pool(String池),JavaDoc翻译很模糊“字符串缓冲区”。String池用来存放运行时中产 生的各种字符串,并且池中的字符串的内容不重复。而一般对象不存在这个缓冲池,并且创建的对象仅仅存在于方法的堆 栈区。
4.String虽然是对象,但传参也是值传递,不是地址传递
@Test
public void t7() {
String s="abc";
Character c='中';
char[] cs= s.toCharArray();
for (char c1 : cs) {
System.out.println(c1);
}
char[] cs2={'天','地','人'};
String s1=new String(cs2);
System.out.println(s1);
}
2.6.2 .关于String的创建方式
字符串有两种创建方式,String s1=“a”; String s=new String(“a”)其逻辑是不一样的
1.直接创建 String str1 = “123”
直接创建的 String 类的数据存储在公共的常量池中(Java 的常量优化机制),即直接创建的相同值的不同 String 类的引用相同。
2.new 创建 String str1 =new String(“123”)
通过 new 关键字创建的 String 和其他一般的类的创建一样,数据是存储在 String 类的对象的堆上,即通过 new 关键字创建的相同值的不同 String 类的引用不同。
String str1 = "Java";
String str2 = "Java";
String str3 = str2 + "";
String str4=new String("Java");
String str5=new String("Java");
String str6=str1;
String str7=str4
System.out.println(str1 == str2); // Output: true
System.out.println(str2 == str3); // Output: false
System.out.println(str4== str5); // Output: false
System.out.println(str1== str5); // Output: false
System.out.println(str1== str6); // Output: true
System.out.println(str4== str7); // Output: true
代码说明:
-
(str1 == str2)== true
直接创建String,其实就是放到常量池里的对象,直接创建时,先去常量池寻找,如果有,则引用,所以这两个时一个东西 -
(str2 == str3)==false
str3 = str2 + “”;代码实际上是创建了一个StringBuild对象,已经是两个对象了 -
(str4 == str4)==false
两个字符串都是new String(“Java”),是两个对象,所以为false
2.6.3 String StringBuffer StringBuild区别
1.字符串String
字符串类是final修饰,不可继承和修改的,
String s="a";
s=s+"b";
其实是产生了两个字符串,执行s=s+“b”;后,s="a"就被丢弃在公共常量池里,如果进行大量的修改,
会导致性能很差。
2.字符串StringBuffer
在 Java1.0 的时候,若要对字符串进行大量修改,应当使用 StringBuffer,它是可修改的,同时,当时的开发人员考虑到多个线程对一个字符串的修改可能出现线程不安全的问题,于是让 StringBuffer 在拥有可修改字符串的功能的情况下,又给它加上了线程安全的机制。看到这里是不是觉得还挺好,挺正常的?但是要知道一个前提,那就是在 Java5 之前的 Java 在处理字符串的速度上一直被别人诟病,原因出在哪里?原因就在于这个 StringBuffer 上面。
StringBuffer 本来是为了实现大量修改字符串的功能而出现的,但却因为 Java 的开发人员给它加了个线程安全的功能,导致它执行效率极大地下降。这个线程安全的功能的实现并不是像我们现在用的方法,当时只是保证没有异常抛出,程序可以正常运行下去而已。在 Java 中,要实现字符串的相加,用加法运算符将两个字符串相加即可。但在这个过程中,Java5 之前是有 String 自动隐含地转换成 StringBuffer,再进行操作这一个步骤的(毕竟 String 类不可直接修改)。只要有这些步骤,就可以实现字符串的修改,但是呢,StringBuffer 有个线程安全的功能,它会在上面提到的步骤中还额外的执行一些功能,以保证线程的安全,而且,这里实现线程安全的方式和我们现在用锁的方式是不一样的!它这里的实现线程安全的方式极为繁琐且复杂,这就大大降低了 StringBuffer 的执行效率,以至于后来被广大程序员诟病。
3.字符串StringBuilder
我们仔细地想一下,实际上也并没有多少地方需要在修改字符串的同时保证线程安全,就算有,我们给它加个锁就行。基于这种想法,在 StringBuffer 出现 10 年之后,Java 的开发人员回过头看这个问题,才发现 StringBuffer 的实现是多么的愚蠢,于是后来在 Java5 就有了 StringBuilder。StringBuilder 同样可以快速高效地修改字符串,同时不是线程安全的。虽然它不是线程安全的,但是它的执行效率却比 StringBuffer 要高上了不少。在 Java5 之后的版本中,字符串相加隐含的转化过程中,不再将 String 转化为 StringBuffer,而是转化成 StringBuilder。
总结:
在大量操作字符串时,java已经优化了,不推荐使用StringBuffer
2.7数组
2.7.1 数组定义
数组有多种定义方式
@Test
public void t1() {
//第一种定义数组的方法:
int[] array1 = {1, 2, 3};//直接赋值(静态初始化)
//int[]是数组的类型,array为数组名,随意取名字
//第二种定义数组的方法:
int[] array2 = new int[]{1, 2, 3, 4};//数组的动态初始化
//第三种定义数组的方法:
int[] array3 = new int[10];//只是分配了内存,但是没有进行赋值,默认值都是0,
System.out.println(array3[2]); //0
//第四种定义数组的方法:
int[] array4;
array4 = new int[]{1, 2, 3};//一定要为数组符初值,不然编译器会报错,
int[] array5=null;//等价int[] array4
}
4.注意事项
- 静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
- 静态初始化时, {}中数据类型必须与[]前数据类型一致。
- 静态初始化可以简写,省去后面的new T[]。T可以为任意数据类型。
- 如果数组中存储元素类型为引用类型,默认值为null。
- 初始化后,数组的长度是固定的,不能动态扩容,这个是和list最大的区别
- 数组参数是地址引用,不是值引用
地址引用:
int[] array1 = {1,2,3,4};
System.out.print(Arrays.toString(array1));//打印[1,2,3,4]
int[] array2 = array1;
array2[1] = 99;
System.out.print(Arrays.toString(array1));//打印[1,99,3,4],不是[1,2,3,4]
System.out.print(Arrays.toString(array2));//打印[1,99,3,4]
首先创建一个数组array1,并初始化赋值为1,2,3,4,然后打印数组array1,接着申请另一款空间,用来创建array2,且array2=array1,说明两个数组都指向同一块空间,修改array2中的第二个元素也就相当于修改了array1中对应的元素。
2.7.2 数组帮助类Arrays
Arrays提供了几个很有用的数组帮助方法:
方法 | 说明 |
---|---|
toString | 把数组转化为字符串 |
sort | 数组元素排序 |
binarySearch | 查找元素在数组中的位置索引 |
asList | 把数组转化成list |
直接看代码,一目了然
@Test
public void t3(){
System.out.println("Arrays类的常见用法");
int[] array={5,4,2,6,3};
//1.打印
String str=Arrays.toString(array);
System.out.println(str);//[5, 4, 2, 6, 3]
//2.数组大小排序
Arrays.sort(array);
System.out.println(Arrays.toString(array));//[2, 3, 4, 5, 6]
//3.判断两个数组内容是否相等
int[] array1={1,2,3};
int[] array2={1,2,3};
System.out.println(Arrays.equals(array1, array2)); //true
System.out.println(array1.equals(array2));//false,不能采用这个方法判断数组内容
//3.查找某个元素的索引
int[] array3={1,2,3};
int index=Arrays.binarySearch(array3,2);
System.out.println(index); //1
//注意事项,如果没有,则返回小于0,如果多个,则返回第一个
int[] array4={1,2,3,2};
System.out.println(Arrays.binarySearch(array4, 2));//1
System.out.println("ddd:"+Arrays.binarySearch(array4, 4));//小于零
//4.转化成list数据类型
Integer[] array5={1,2,3};
//如果是对象,则直接List<对应的数组元素类型>
List<Integer> list=Arrays.asList(array5);
for (Integer i : list) {
System.out.println(i);
}
// list.add(9); //运行报错,不能新增和删除
// list.remove(0);//运行报错,不能新增和删除
/***
注意如果数组是基本数据类型,用Array.asList是无法转化的。
需要借助第三方先把基本类型数组转化成对应的包装类数组
如:Apache Commons Lang3
*/
int[] array6={1,2,3,5};
Integer[] array7= ArrayUtils.toObject(array6);
//4.copy值
int[] arr1 = {1, 2, 3};
int[] arr2 = Arrays.copyOf(arr1, 5);
System.out.println(Arrays.toString(arr2)); //[1, 2, 3, 0, 0]
//注意:copyOf是值copy,不是地址引用
arr2[3]=20;
System.out.println(Arrays.toString(arr2));//[1, 2, 3, 20, 0]
System.out.println(Arrays.toString(arr1));//[1, 2, 3]
//copyOfRange方法提供了开始索引
int[] arr3 = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] arr4 = new int[5];
arr4 = Arrays.copyOfRange(arr3,3,5);
System.out.println(Arrays.toString(arr4));//[4, 5]
}
2.7.3 Apache Commons Lang3
org.apache.commons.lang3.ArrayUtils这个类也很大扩展了数组的操作,具体查看apache文档
2.8 equals和==、compareTo区别
2.8.1 ==和equals
- == 是一个运算符,用于比较两个对象的引用是否相同,即它们是否指向内存中的相同位置。
- equals 是一个方法,通常在Object类中定义,它用于比较两个对象的内容是否相等。默认情况下,equals方法执行与==相同的引用比较,但它可以被子类重写以提供自定义的相等性逻辑
“== ”在哪些情况下比较的是对象内容而不是引用
- 在Java中,== 运算符通常比较的是对象的引用。但在以下情况下,== 可以比较对象的内容而不是引用:
- 对于基本数据类型(如int、char等),== 比较的是它们的值,而不是引用。
- 字符串常量池:对于字符串字面值,就是String s=“123”,不是String s=new String(“123”);,Java使用常量池来存储它们,因此相同的字符串字面值使用==比较通常会返回true。
代码1
@Test
public void t1(){
//基本类型数据(数值、字符、布尔)的值一样,则==为true
char c1='a';
char c2='a';
float f1=3.5f;
float f2=3.5f;
double f3=3.5;
int i1=1;
int i2=1;
long i3=1l;
System.out.println(c1==c2);//true
System.out.println(f1==f2);//true
System.out.println(f1==f3);//true
System.out.println(i1==i2);//true
System.out.println(i1==i3);//true
}
代码2: 字符串是一个对象又有基础数据的特殊性,可参考2.6.2
@Test
public void t2(){
String s1="123";
String s2="123";
String s3=s1;
String s4=s2+"";
String s5=new String("123");
String s6=new String("123");
String s7=s5;
//s="xx"是直接声明,String是final常量,性质和基础数据类型一样,s1,s2都指向同一个内存地址
System.out.println(s1==s2); //true
//所有=变量赋值的语法,指针都是一样
System.out.println(s1==s3); //true
//因为String是final类型,s2+""实际是创建一个对象
System.out.println(s1==s4); //false
//两个不同对象,内存地址不一样
System.out.println(s5==s6); //false
//所有=变量赋值的语法,指针都是一样
System.out.println(s5==s7); //true
//如果是euqal比较,这几个都返回true
System.out.println( //true
s1.equals(s2) && s1.equals(s3) && s1.equals(s4) && s5.equals(s6)
);
}
}
代码3:标准vo对象
@Test
public void t3(){
TestVO vo1=new TestVO(1,"jzk");
TestVO vo2=vo1;
TestVO vo3=new TestVO(1,"jzk");
//内存指针一样
System.out.println(vo1==vo2);//true
//两个不同对象
System.out.println(vo1==vo3);//false
//三个对象值都是一样的
System.out.println(vo1.equals(vo2) && vo2.equals(vo3));//true
//vo2修改了值,因为vo1指针和他一致,所以vo1也会改变,最后他们都是一致的
vo2.setName("奎哥");
System.out.println(vo1==vo2);//true
//值已经改变了
System.out.println(vo3.equals(vo1));//false
}
equals 和 hashCode 之间有什么关系?
equals 和 hashCode 在Java中通常一起使用,以维护对象在散列集合(如HashMap和HashSet)中的正确行为。
如果两个对象相等(根据equals方法的定义),那么它们的hashCode值应该相同。
也就是说,如果重写了一个类的equals方法,通常也需要重写hashCode方法,以便它们保持一致。
这是因为散列集合使用对象的hashCode值来确定它们在内部存储结构中的位置。
2.8.2 compareTo
obj.compareTo(object otherstr);
仅仅知道两个字符串是否相同是不够的。对于排序应用来说,必须知道一个字符串是大于、等于还是小于另一个。一个字符串小于另一个指的是它在字典中先出现。而一个字符串大于另一个指的是它在字典中后出现。字符串(String)的 compareTo() 方法实现了这种功能。
- compareTo() 方法用于按字典顺序比较两个字符串的大小,该比较是基于字符串各个字符的 Unicode 值。
- 如果两个字符串调用 equals() 方法返回 true,那么调用 compareTo() 方法会返回 0,如果前者大则大于0,否则小于0
- compareTo只能用于对象,不能用于基础数据类型,他实际上是Comparable接口类的方法,实现这个接口的有:String、基础数据类型的包装类、Date、BigDecimal 等
代码:
@Test
public void t4(){
//字符串比较
String s1="a";
String s2="abc";
System.out.println(s1.compareTo(s2));
//基本类型的包装类比较
Integer i1=3;
Integer i2=5;
System.out.println(i1.compareTo(i2));//<0
System.out.println(i1<i2);//true
//日期Date,BigDecimal
Date d1= DateUtil.getDateByStr("2003-10-01", DateUtil.date_gs);
Date d2= DateUtil.getDateByStr("2004-09-01", DateUtil.date_gs);
System.out.println(d1.compareTo(d2));//<0
BigDecimal bg1=new BigDecimal("1.0");
BigDecimal bg2=new BigDecimal("1.00");
System.out.println(bg1.equals(bg2)); //返回的是false,所以比较BigDecimal不要用equals
System.out.println(bg1.compareTo(bg2));//0
}
}
2.9代码块、内部类和匿名类
2.9.1代码块
1.定义:
类里用{}包裹起来的代码称为代码块,代码块有三种类型
1.静态代码块
用static关键字修饰叫静态代码块,
2.同步代码块
使用synchronize关键字修饰,并使用"{}“括起来的代码片段.它表示在同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制.
3.非静态代码块
在类中没与任何的前缀或后缀,并使用”{}"括起来的代码片段叫非静态代码块,也叫构造代码块
//非静态代码块
{
int i=0;
System.out.println("非静态代码块........");
}
//静态代码块
static {
int i1=0;
System.out.println("静态代码块........");
}
静态代码块特点
- 静态代码块使用static关键字进行修饰
- 静态代码块的内部可以有输出语句
- 静态代码块在类加载时执行,并且只会执行一次(因为一个类只会加载一次)
- 静态代码块用于初始化类,但是初始化的是类的静态属性,类的非静态属性是无法在静态代码块中被初始化的
- 若是在一个类中存在多个静态代码块,那么执行时会按照这些静态代码块的定义顺序来执行
- 若是在静态代码块中存在调用,那么只能调用静态的属性和方法,不能调用非静态的属性和方法
非静态代码块特点(又叫构造代码块,有点类似构造函数)
- 非静态代码块不使用static关键字进行修饰
- 非静态代码块的内部可以有输出语句
- 非静态代码块随着对象的创建而执行
- 每创建一个对象便会调用一次非静态代码块(因为非静态代码块是用于初始化对象的,因此,每次使用new关键字创建一个对象都会新增一个对象并使用非静态代码块对其初始化)
- 非静态代码块可以在创建对象时,对对象的属性进行初始化
- 若是在一个类中定义了多个非静态代码块,则这些非静态代码块会按照定义的先后顺序来执行
- 非静态代码块中,可以调用该类的静态属性、静态方法、非静态属性、非静态方法(因为静态的内容是随着类的加载而加载的,而非静态内容是在静态内容之后被加载的,因此非静态代码块调用已经存在的静态内容是完全没有问题的)
在一个对象实例化时的执行顺序
1.首先执行静态代码块,在类初始化执行。静态代码块只执行一次
2.其实执行非静态代码块,在对象实例化new时执行,每次实例化都会执行一次
3.最后执行构造函数
代码:
对象类
public class CodePiece {
public static final String s="dd";
//分静态代码块
{
int i=0;
System.out.println("非静态代码块.......2");
}
//静态代码块
static {
int i1=0;
System.out.println("静态代码块........1");
}
private int i=2;
public CodePiece(){
System.out.println("构造函数.............3");
// System.out.println("i="+i);
}
}
测试
Test
public void t1() throws ClassNotFoundException {
// System.out.println( CodePiece.s);
//第一次要执行静态代码块
Class cls=Class.forName("com.jsoft.ref.CodePiece");
//第二次将不会执行静态代码块
Class cls2=Class.forName("com.jsoft.ref.CodePiece");
CodePiece v1=new CodePiece(); //执行非静态代码代码和构造函数
CodePiece v2=new CodePiece();//执行非静态代码代码和构造函数
}
效果
静态代码块........1
非静态代码块.......2
构造函数.............3
非静态代码块.......2
构造函数.............3
应用场景
1.我们只需要类加载一次执行的时候,我们使用静态代码块(应用场景多)
2.如果有多个构造函数,都要执行共有的代码,我们把这个部分代码放置到非静态代码块里
2.9.2内部类
定义
把一个类定义在令一个类里叫内部类,内部类分为静态内部类和非静态内部类两种,静态内部类用 static关键字修饰;内部类也可以定义成只有外部类能访问的私有类
public class MuCls {
private Integer id;
//非静态内部内类
public class InnerClass1{
}
//静态内部类
public static class InnerClass2{
}
//如果一个内部类不想让外部访问,可以声明一个私有的内部类
privae class InnerClass3{
}
}
内部类语法:内部类和外部类可以非常方便的通信
A{ class B{} }
类型 | 外部访问内部 | 内部访问外部 |
---|---|---|
非静态内部类 | B b=new B(); b.字段 b.方法() | 1.外部类非静态字段或者方法: A.this.字段 A.this.方法() 2.外部类静态字段或方法 : A.字段 A.方法() |
静态内部类 | 1.静态字段和方法: B.字段 B.方法() 2.非静态字段和方法: B b=new B(); b.字段 b.方法() | 1.外部类非静态字段或者方法: A a=new A(); a.字段; a.方法() 2.外部类静态字段或方法 : A.字段 A.方法() |
注意事项:
1.非静态内部类里不能有静态的字段和方法
2.内外部类通信,和 private public无关,因为都在同一个类里
3.一般情况下代码外部类为主,所以非静态内部类使用多一些
public class MuCls {
private Integer id;
public static String type;
public void outM1(){
System.out.println("外部类方法");
}
//操作非静态内部类
public void outM2(){
//访问非静态内部类,必须用new实例化
InnerClass1 innerClass1=new InnerClass1();
//访问内部类字段
Integer i=innerClass1.id; //不管内部类字段是private还是public都可以访问
//调用内部类方法
innerClass1.innerM1();
}
//访问静态内部类
public void outM3(){
//访问静态内部类的静态字段和属性,可以直接访问
Integer sex=InnerClass2.sex;
InnerClass2.help();
//访问非静态字段和属性,还是要new 内部类
InnerClass2 innerClass2=new InnerClass2();
//访问内部类字段
String name=innerClass2.name; //不管内部类字段是private还是public都可以访问
//调用内部类方法
innerClass2.innerM2();
}
//外部静态方法
public static void outM4(){
System.out.println("外部类静态方法");
}
//非静态内部内类
//注意非静态类里不能用static修饰字段和方法
public class InnerClass1{
private Integer id;
public void innerM1(){
System.out.println("非静态类方法innerM1()");
}
//访问外部类
public void innerM2(){
// 外部类非静态字段或者方法:外部类名.this.字段或者方法
Integer outId=MuCls.this.id; //非静态字段
MuCls.this.outM1();//非静态方法
//外部类静态字段或方法:外部名类.字段或者方法
String outType=MuCls.type;//静态字段
MuCls.outM4();//静态方法
}
}
//静态内部类
//静态类里可以用static修饰字段和方法
public static class InnerClass2{
//内部类静态字段
private static Integer sex;
private String name;
//内部类静态方法
public static void help(){
}
public void innerM2(){
System.out.println("非静态类方法innerM2()");
}
//访问外部类
public void innerM3(){
// 静态类不能直接访问外部类的非静态字段和方法,需要new 外部类
MuCls muCls=new MuCls();
Integer outId=muCls.id;
muCls.outM1();
//外部类静态字段或方法:外部名类.字段或者方法
String outType=MuCls.type;//静态字段
MuCls.outM4();//静态方法
}
}
}
第三方类调用含有内部类的类
A{ B{} }
非静态内部内 | 静态内部类 |
---|---|
1.得到内部对象: A.B b=new A().new B(); 2.调用内部类方法: b.方法() | 1.访问静态类里静态方法和字段: A.B.字段 A.B.方法() 2.访问内部类非静态字段和方法: A.B b=new A.B(); b.字段; b.方法(); |
public class InnerTest {
/***
非静态内部类测试
1.外部调用必须先实例化外部类获得外部类的对象
2.获得内部类对象=外部类的对象.new 内部类名
*/
@Test
public void t3(){
MuCls muCls=new MuCls();
//得到内部类对象
MuCls.InnerClass1 innerClass1=muCls.new InnerClass1();
//调用内部类方法
innerClass1.innerM1();
}
/***
静态内部类调用测试
1.内部静态类里面的静态属性和方法可以直接访问
2.内部静态类的实例化和非静态类不一样,可以直接new
*/
@Test
public void t4() {
//1.访问静态内部类的静态字段和方法
MuCls.InnerClass2.help();
//2.访问静态内部类的非静态字段和方法
//静态内部类,实际上是独立的一个类,可直接new生成对象,和非静态内部内不一样
MuCls.InnerClass2 innerClass2=new MuCls.InnerClass2();
innerClass2.innerM2();
}
}
什么场景下使用内部类
有时候一个类太复杂,我们把一个类里面的一些属性和方法抽取成一个内部类,隐藏复杂逻辑
,而且内部方法需要互相方便的访问,比抽取成一个外部类更方便。
2.9.3匿名类
什么是匿名类?
- 使用 Java 编程时,常常需要创建不会被再次使用的对象。
在这种情况下,非常适合使用一种特殊的内部类:匿名内部类。
这种类没有名称,是在同一条语句中声明和创建的。
要使用匿名内部类,可将引用对象的代码替换为关键字 new、对构造函数的调用以及用大括号({和})括起的类定义。
既然是匿名类,所以你无法在别的地方实例化和使用这个类。 - 匿名内部类也可用于接口(interface)的实现
语法: new 类名().方法()
准备类:
public class NoName {
public void play(){
System.out.println("执行NoName.play()");
}
public void eat(){
System.out.println("执行NoName.eat()");
}
}
public interface NoNameInterface {
public void play();
public void eat();
}
声明一个NoName的匿名对象
@Test
public void t1(){
//正常声明对象
NoName noName=new NoName();
noName.play();
//如果我们这个noName不在其他地方使用,而且只调用一个方法,就可以简写如下:
new NoName().play();
}
更常见的一种写法
new 类名或者接口名() {
重写方法;
}
重写匿名类的方法
//在方法体里重写匿名类的方法
@Test
public void t2() {
new NoName() { //重写NoName类的play()方法
@Override
public void play() {
System.out.println("重写方法play()");
}
}.play();
}
//重写参数类里的方法
@Test
public void t3() {
param(new NoName() {
@Override
public void play() {
System.out.println("重写参数类的play()");
}
}
);
/*** 等价于
public class A extends NoName{
public void play() {
System.out.println("重写参数类的play()");
}
}
NoName a=new A();
param(a);
*/
}
private void param(NoName noName) {
noName.play();
}
接口的匿名实现类,这种写法场景更多
代码
//接口实现匿名类的写法
@Test
public void t4(){
new NoNameInterface(){
@Override
public void play(){
System.out.println("接口实现匿名类.play()");
}
@Override
public void eat(){
System.out.println("接口实现匿名类.eat()");
}
}.play();
}
//如果接口里有多个方法被调用,则可以用下面这种方式
@Test
public void t5(){
NoNameInterface service= new NoNameInterface(){
@Override
public void play(){
System.out.println("接口实现匿名类.play()");
}
@Override
public void eat(){
System.out.println("接口实现匿名类.eat()");
}
};
service.play();
service.eat();
}
//方法参数是接口的匿名实现类写法
@Test
public void t6(){
param2(new NoNameInterface(){
@Override
public void play() {
System.out.println("参数接口实现匿名类.play()");
}
@Override
public void eat() {
System.out.println("参数接口实现匿名类.eat()");
}
});
}
private void param2(NoNameInterface service) {
service.play();
}
在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
2.7.4回调函数
2.10枚举
1.枚举定义
枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。
在 JDK 1.5 之前没有枚举类型,那时候一般用接口常量来替代。而使用 Java 枚举类型 enum 可以更贴近地表示这种常量。
public enum SexEnum{
MAN,WOMAN
}
2.枚举命名规则
- 枚举是一个特殊的类,所以应遵循类的命名规则,后缀加Enum,如SexEnum
- 枚举常量对象是一个常量,应遵循常量命名规则,如MAN
2.枚举基础语法
方法 | 说明 |
---|---|
枚举名.常量名 | 获得对象常量 |
values() | 获得枚举的所有常量,一个数组 |
valueOf(name) | 通过常量对象名称获得这个常量 |
对象常量.name() | 获得这个常量对象的name |
对象常量.ordinal() | 获得这个常量对象所在枚举的索引 |
对象常量.compareTo() | 比较两个枚举成员在定义时的顺序 |
//枚举定义
public enum SexEnum{
MAN, //枚举变量
WOMAN//枚举变量
}
@Test
public void t4(){
//直接获得一个枚举的常量对象
SexEnum man=SexEnum.MAN;
SexEnum woman=SexEnum.valueOf("WOMAN");
System.out.println(man.compareTo(woman)); //-1 小于0在前面
int index=woman.ordinal();//常量对象在枚举里的索引
String name=woman.name();//得到常量名
//获得枚举里的所有常量对象
SexEnum[] sexEums= SexEnum.values();
for (SexEnum sexEum : sexEums) {
System.out.println(sexEum);
}
}
3理解枚举类
Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。
理解伪代码:
public enum SexEnum{
MAN, //枚举变量
WOMAN//枚举变量
/**
MAN,WOMAN都是SexEnum 的实例,类似下面定义
class SexEnum{
public final static SexEnum MAN;
public final static SexEnum WOMAN;
public SexEnum[] values(){
return new SexEnum[]{MAN,WOMAN}
}
}
所以应用的时候,才有一下写法
SexEnum man=SexEnum.MAN;
SexEnum[] sexs=SexEnum.values();
*/
}
3.自定义枚举
在程序里我们常常利用编码而不是字符串来表示,枚举本身无法通过new来构造,我们可以Class 类一样覆盖基类的方法。
默认情况下,枚举类是不需要构造方法的,默认的变量就是声明时的字符串。当然,你也可以通过自定义构造方法,来初始化枚举的一些状态信息。通常情况下,我们会在构造参数中传入两个参数,比如,一个编码,一个描述。
package com.jsoft.ref;
import java.util.ArrayList;
import java.util.List;
/**
* @class: com.jsoft.ref.EnumService
* @description:
* @author: jiangzengkui
* @company: 教育家
* @create: 2023-12-28 09:19
*/
public interface EnumService {
public String getCode() ;
public String getLabel() ;
}
enum ColorEnum implements EnumService{
RED("1", "红色"), GREEN("2", "绿色"),
BLANK("3", "白色"), YELLOW("4", "白色");
private String label;
private String code;
private ColorEnum(String code, String label) {
this.label = label;
this.code = code;
}
public String getCode(){
return this.code;
}
public String getLabel(){
return this.label;
}
public static List<EnumVO> getEnums(){
List<EnumVO> list=new ArrayList<>();
EnumVO vo=null;
for (ColorEnum value : ColorEnum.values()) {
vo=new EnumVO(value.getCode(),value.getLabel());
list.add(vo);
}
return list;
}
}
4.枚举集合:EnumSet和EnumMap
在java.util包下引入了两个枚举集合类:EnumSet和EnumMap。
EnumMap 类
EnumMap 是专门为枚举类型量身定做的 Map 实现。虽然使用其他的 Map(如 HashMap)实现也能完成枚举类型实例到值的映射,但是使用 EnumMap 会更加高效。
HashMap 只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值,使得 EnumMap 的效率非常高。
enum DataBaseType{
MYSQL,ORACLE,DB2,SQLSERVER
}
EnumMap<DataBaseType,String> urls=new EnumMap<>(DataBaseType.class);
public void setAllUrls(){
urls.put(DataBaseType.DB2,"jdbc:db2://localhost:5000/sample");
urls.put(DataBaseType.MYSQL,"jdbc:mysql://localhost/mydb");
urls.put(DataBaseType.ORACLE,"jdbc:oracle:thin:@localhost:1521:sample");
urls.put(DataBaseType.SQLSERVER,"jdbc:microsoft:sqlserver://sql:1433;Database=mydb");
}
public String getURL(DataBaseType type)
{
return this.urls.get(type);
}
总结:EnumMap 使用和HashMap没有多大区别
2.11 集合
2.11.1集合基础知识
集合定义
java集合可能是java里面最频繁用到的数据结构
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java 提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。Java 所有的集合类都位于 java.util 包下,提供了一个表示和操作对象集合的统一构架,包含大量集合接口,以及这些接口的实现类和操作它们的算法。
集合和数组的区别
- 数组长度在初始化时已固定,而且数组个数是不能变的,集合可以
- 数组元素可以是对象,也可以是基本数据类型如int,但集合里只能存储对象
集合分类
Java 集合类型分为 Collection 和 Map两大体系,它们是 Java 集合的根接口,这两个接口又包含了一些子接口或实现类
- Collection 接口
单列数据,定义了存取一组对象的方法的集合。 - Map接口
单列数据,定义了存取一组对象的方法的集合。
2.11.2 Collection体系
接口 | 说明 |
---|---|
Iterator 接口 | 集合的输出接口,主要用于遍历输出(即迭代访问)Collection 集合中的元素,Iterator 对象被称之为迭代器。迭代器接口是集合接口的父接口,实现类实现 Collection 时就必须实现 Iterator 接口。 |
Collection 接口 | 是 List、Set 和 Queue 的父接口,是存放一组单值的最大接口。所谓的单值是指集合中的每个元素都是一个对象。一般很少直接使用此接口直接操作。 |
Queue 接口 | Queue 是 Java 提供的队列实现,有点类似于 List。 |
List 接口 | 是最常用的接口。是有序集合,允许有相同的元素。使用 List 能够精确地控制每个元素插入的位置,用户能够使用索引(元素在 List 中的位置,类似于数组下标)来访问 List 中的元素,与数组类似。 |
Set 接口 | 不能包含重复的元素 |
Collection接口
Collection接口继承了Iterable实现了迭代器,并提供了列表的增、删、改、查方法
Iterator接口
Iterator接口核心实现了Collection对象的遍历功能
2.11.3 Collection之List
list特点
list和Set、Queue相比,它的特点是有序、可重复、有索引,有数组特质
List接口底层以数组方式进行对象存储,允许存放null元素
List 常用方法
List的子类区别
-
ArrayList
ArrayList 类实现了可变数组的大小,存储在内的数据称为元素。它还提供了快速基于索引访问元素的方式,对尾部成员的增加和删除支持较好。使用 ArrayList 创建的集合,允许对集合中的元素进行快速的随机访问,不过,向 ArrayList 中插入与删除元素的速度相对较慢。 -
Vector
Vector和ArralyList功能几乎一致,区别点在于:- 最主要的区别是 Vector 是线程安全的,可以用于多线程环境,而 ArrayList 不是线程安全的。Vector 内部实现采用了同步锁,为访问它的方法提供了线程安全保障
- 在内存管理方面,两者的扩容机制也不同。当向 Vector 中添加元素时,如果所需空间大于当前总空间,则会分配一个更大的新数组,并将所有旧元素复制到新数组中,这样做的目的是减少频繁的创建数组的次数,但效率较低,且 Vector 的大小和容量会相同。
总结:ArralyList性能最高,但线程不安全,适合单线程环境,Vector相反
- LinkedList
LinkedList的底层就是用双向链表实现的,因为链表本身是无序的,所以LinkedList 插入或者删除都很快,但是要查找或者遍历的时候就会很慢,因为双向链表每次查找某个元素的时候都会在内部遍历一遍,直到找到那个指定下标的元素为止,另外,LinkedList 的内存占用要比ArrayList大,因为LinkedList除了存储数据之外,还存储了2个指针,一个指向前一个元素,一个指向后一个元素。
总结:
ArrayList底层的实现数组,而LinkedList是双向链表。
ArrayList进行随机访问所消耗的时间是固定的,因此随机访问时效率比较高。
LinkedList是不支持快速的随机访问的,但是在插入删除时效率比较高。
2.11.4 Collection之Set
List和Set区别
List和Set都是Java集合框架中的接口,它们之间的主要区别在于:
-
List是有序的集合,可以存储重复的元素,可以根据索引访问元素;而Set是无序的集合,不允许存储重复的元素,不能根据索引访问元素。(无set.get(i)方法)
-
List的常用实现类有ArrayList、LinkedList和Vector等;而Set的常用实现类有HashSet、TreeSet和LinkedHashSet等。
-
List通常用于存储需要按照顺序访问的元素,如日志记录、历史记录等;而Set通常用于去重和判重操作,如过滤重复的IP地址、手机号码等。
-
List支持添加、删除和修改操作,但这些操作可能会影响到其他元素的位置;而Set不支持修改操作,只能添加和删除元素。
综上所述,List适合存储有序、可重复的元素;而Set适合存储无序、不重复的元素。
set实现类HashSet /TreeSet /Linkset
- HashSet
HashSet底层由HashMap实现,插入的元素被当做是HashMap的key,根据hashCode值来确定集合中的位置,由于Set集合中并没有角标的概念,所以并没有像List一样提供get()方法。当获取HashSet中某个元素时,只能通过遍历集合的方式进行equals()比较来实现;
- TreeSet
从名字上可以看出,此集合的实现和树结构有关。与HashSet集合类似,TreeSet也是基于Map来实现,具体实现TreeMap(后面讲解),其底层结构为红黑树(特殊的二叉查找树);
与HashSet不同的是,TreeSet具有排序功能,分为自然排序(123456)和自定义排序两类,默认是自然排序;在程序中,我们可以按照任意顺序将元素插入到集合中,等到遍历时TreeSet会按照一定顺序输出–倒序或者升序;
TreeSet值不能为null
演示代码
@Test
public void t2(){
TreeSet<Integer> set=new TreeSet();
set.add(7);
set.add(9);
set.add(3);
set.add(2);
//默认是从小到大升序排列
System.out.println(set);//[2, 3, 7, 9]
//倒序排列 获得倒序迭代器
Iterator it=set.descendingIterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
2.11.5 Map体系
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
Map和Collection最大的区别是,它提供key-value键值对,不需要循环就可以定位到一条数据
key值是唯一的。
Map最重要的3个实现类:HashMap TreeMap TableMap
Map集合与Set集合、List集合的关系
1.与Set集合的关系
如果 把Map里的所有key放在一起看,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不能重复),实际上Map确实包含了一个keySet()方法,用户返回Map里所有key组成的Set集合。
2.与List集合的关系
如果把Map里的所有value放在一起来看,它们又非常类似于一个List:元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中索引不再使用整数值,而是以另外一个对象作为索引。
Map常用方法
(1)put(key,value),添加一个键值对,返回null,key和value都是Object,如果已经存在key,则更改value为新的值并将其返回
(2)get(key),返回key对应的value,如果没有,返回null
(3)remove(key),删除相应的键值对,返回删除掉的value,如果没有返回null
(4)containsKey(key),containsValue(value),返回值为boolean类型
(5)size(),返回包含的键值对数量
(6)keySet(),返回包含所有key值的Set集合
(7)values(),返回包含所有value值的Collection集合
@Test
public void t3(){
Map<String,String> map=new HashMap<>();
map.put("1", "1");
map.put("1", "x");//key是唯一的,如果检查key存在,会替换掉以前的值
map.put("2", "2");
map.put("3", "3");
//获得key值的set
Set<String> set=map.keySet();
for (String key : set) {
System.out.println("key:"+key+";value:"+map.get(key));
}
//把k-v封装到一个Map.Entry接口对应set里,Entry提供了getKey,getValue方法
Set<Map.Entry<String,String>> entrySet=map.entrySet();
for (Map.Entry<String, String> o : entrySet) {
System.out.println("key:"+o.getKey()+"-value:"+o.getValue());
}
}
2.11.6 HashMap
- HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
- HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
- HashMap 是无序的,即不会记录插入的顺序。(key是以Set记录的)
- HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口
2.11.7 TreeMap
与HashMap相比,TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。其中,可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序; 不同于HashMap的哈希映射,TreeMap实现了红黑树的结构,形成了一颗二叉树,两者的最大区别就是TreeMap可以排序。不支持插入key为null
排序
@Test
public void t4(){
boolean asc=false;//默认升序
TreeMap<Integer,String> map=null;
if(asc){ //升序
map=new TreeMap<>();
}
else{
map=new TreeMap<>(Collections.reverseOrder());
}
map.put(9, "9");
map.put(12, "12");
map.put(7, "7");
map.put(8, "8");
Set<Integer> set= map.keySet();
for (Integer i : set) {
System.out.println("key:"+i+"-value:"+map.get(i));//7,8,9,12
}
}
2.11.8 HashTable
- 首先父类不同。hashTable的父类是Dictionary<K,V>,HashMap的父类是AbstractMap<K,V>.
- HashMap是支持null键和null值的,而HashTable在遇到null时,会抛出NullPointerException异常。
- 初始化大小不同,扩容机制不同。
- hashTable为线程安全的,方法级别的强制同步。HashMap非线程安全的。所以HashMap效率性能要高
2.11.9 总结
常用集合类总结
2.12 Date理解
2.12.1 基本定义
每个国家、地区展示日期的形式不一样,如果如何来准确定义时间呢?
- 2019-11-20 0:15:00 GMT+00:00
- 2019年11月20日8:15:00
- 11/19/2019 19:15:00 America/New_York
java的日期本质上一个整数,就是是,计算从1970年1月1日零点(格林威治时区/GMT+00:00)到现在所经历的毫秒数,
这个数据就叫时间戳,不同的地区才有不同的展现形式
2.12.2 Date类
直接看代码
static String gs1="yyyy-MM-dd HH:mm:ss";
static String gs2="yyyy-MM-dd";
@Test
public void t0(){
Date d1=new Date();//当前时间
Date d2=getDateByStr("2012-01-01 12:12:12", gs1);
//得到时间戳
long tamp1=d1.getTime();//时间戳,离1970年1月1日零点(格林威治时区/GMT+00:00的毫秒数
long tamp2=d2.getTime();
//通过时间戳设置时间
Date d3=new Date(tamp2);
System.out.println(getStrByDate(d3, gs1));//2012-01-01 12:12:12
//比较时间大小
//1.通过时间戳比较
System.out.println(tamp1>tamp2); //true
//2.before()函数
System.out.println(d1.before(d2));//false
//3.compareTo()函数
System.out.println(d1.compareTo(d2)); //大为1,小为-1 等于为0
}
2.12.3 SimpleDateFormat类
用户转化字符串和日期格式
static String gs1="yyyy-MM-dd HH:mm:ss";
static String gs2="yyyy-MM-dd";
@Test
public void t1() throws ParseException {
//通过SimpleDateFormat类来展示日期
SimpleDateFormat fm1= new SimpleDateFormat(gs1);
//通过format()函数得到日期想要展示的字符串
String str=fm1.format(new Date());
System.out.println(str);
//通过SimpleDateFormat类来把字符串转化成日期
SimpleDateFormat fm2= new SimpleDateFormat(gs2);
Date d2= fm2.parse("2023-12-28");
System.out.println(getStrByDate(d2,gs2));
}
2.12.4 Calendar类
Calendar比Date提供了更为强大的方法
@Test
public void t2(){
//Calendar不能直接new,用getInstance()得到当前时间对应Calendar
Calendar cl=Calendar.getInstance();
//Calendar和Date的转化
Date d3=cl.getTime(); //得到日期
System.out.println(getStrByDate(d3, gs2));
Date dx=getDateByStr("2021-01-101", gs2);
cl.clear();
//设置时间
cl.setTime(dx);
System.out.println(getStrByDate(cl.getTime(),gs2));//2021-01-101
//1.通过Calendar.get(type)得到对应的时间
//get(type)得到对应的字段数值
int y=cl.get(Calendar.YEAR); //得到对应的年份
int m=cl.get(Calendar.MONTH)+1; //得到对应的月份,这里要加1
int d=cl.get(Calendar.DAY_OF_MONTH);//得到对应的月的几号
int d1=cl.get(Calendar.DAY_OF_YEAR);//得到一年对应的第xx天
System.out.println(d1);
int d2=cl.get(Calendar.DAY_OF_WEEK);//星期几,从
System.out.println(d2); //1-7,星期天是1,星期一是2,按照中国习惯可减1,星期天为0
int h=cl.get(Calendar.HOUR_OF_DAY); //24小时制
int h1=cl.get(Calendar.HOUR);//12小时制
int min=cl.get(Calendar.MINUTE);//分钟
int second=cl.get(Calendar.SECOND);//秒
String dateTime=y+"-"+m+"-"+d+" "+h+":"+min+":"+second;
System.out.println(dateTime);
//2.通过set(type,value)设置对应的时间
cl=Calendar.getInstance();
cl.set(2023, 8, 1);//年月日,注意月份要减1
System.out.println(getStrByDate(cl.getTime(), gs2));
cl.clear();
cl.set(2023, 8, 1, 12, 12); //年月日时分
cl.set(2023, 8, 1, 12, 12,23); //年月日时分秒
//3.设置一个字段
cl.set(Calendar.YEAR, 1997); //设置年份
//4.增加或者减少
cl.add(Calendar.YEAR,-1);//减去一年
cl.add(Calendar.MONTH,2);//增加2月
}
2.12.5 java.time包
尽管 Date 类提供了一些方法用于获取、设置和操作日期和时间,但它的使用存在一些问题,例如可变性、线程安全性等。因此,在日常开发中,推荐使用新的日期和时间 API
Java 8 引入了一个全新的日期和时间 API,位于 java.time 包下。这个 API 提供了一系列类和方法,用于处理日期、时间、时区、时间间隔等。下面将介绍一些常用的类和操作。
Date问题
- 粒度不够细: 最小维度为毫秒, 不支持纳秒级别
- 无时区: Date固定采用TimeZone中给定的默认时区
- 不够便捷: 必须通过Calendar类进行时间操作
- 变量歧义: Calendar中, 描述月份不是从112而是从011; 描述星期时第1天是周日而不是周一;
不够抽象: 一个Date为特定的时刻, 无法单纯的描述某一年或某一天
java.time包里3个重要的类 LocalDate、LocalTime、LocalDateTime
2.12.6 LocalDate日期类
LocalDate一个类就能处理所有日期问题
1.创建一个loalDate对象
//1.创建一个loalDate对象
System.out.println("1.获得一个loalDate对象............");
//(1).now()当前日期
LocalDate d11 = LocalDate.now();
//(2)of(年月日)传入年月日
LocalDate d12 = LocalDate.of(2023, 11, 29);
//(3)ofYearDay()传入年,和一年具体第多少天
LocalDate d13 = LocalDate.ofYearDay(2023, 12); //2023-01-12
//(4)解析字符串parse()
//默认是yyyy-MM-dd
LocalDate d14 = LocalDate.parse("2023-11-30");
DateTimeFormatter fm = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate d15 = LocalDate.parse("2023/11/01", fm);
System.out.println("LocalDate.now()=" + d11);
System.out.println("LocalDate.of(y,m.d)=" + d12);
System.out.println("LocalDate.ofYearDay(y,d)=" + d13);
System.out.println("LocalDate.parse(Str)=" + d14);
System.out.println("LocalDate.parse(Str,DateTimeFormatter)=" + d15);
2.格式化展示 String和LocalDate互相转化
//2.格式化展示
System.out.println("2.格式化展示loalDate对象............");
LocalDate ld21 = LocalDate.now();
String dateStr = "";
//(1)toString()默认为yyyy-MM-dd
dateStr = ld21.toString();
System.out.println("LocalDate.toString()=" + dateStr);//2023-12-29
//(2)使用DateTimeFormatter格式化模板
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
dateStr = ld21.format(formatter);
System.out.println("LocalDate.format(DateTimeFormatter)=" + dateStr);//2023/12/29
3.日期大小判断
//3.日期大小判断
System.out.println("3.日期加减判断............");
LocalDate d31 = LocalDate.parse("2023-11-01");
LocalDate d32 = LocalDate.parse("2023-11-01");
LocalDate d33 = LocalDate.parse("2023-11-04");
System.out.println("LocalDate.isBefore/After(ld)=" + d31.isBefore(d33));//true
System.out.println("LocalDate.compareTo(ld)=" + d31.compareTo(d33)); //<0
long timestamp31 = d31.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();
long timestamp33 = d33.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();
System.out.println("timestamp比较=" + (timestamp31 < timestamp33));//true
4.LocalDate和时间戳
//4.时间戳
System.out.println("4.localDate和时间戳转化............");
LocalDate d41 = LocalDate.parse("2023-11-01");
long timestamp = d41.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();
System.out.println("LocalDate转时间戳=" + timestamp);
LocalDate d42 = Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDate();
System.out.println("时间戳转LocalDate=" + d42);
5.日期加减
//5.日期加减
System.out.println("5.localDate日期加减............");
LocalDate d51 = LocalDate.parse("2023-11-01");
LocalDate d52 = LocalDate.parse("2023-11-05");
//(1)日期时间差
Period period = Period.between(d51, d52);
int year5 = period.getYears(); //相差年
int month5 = period.getMonths();//相差月
int day5 = period.getDays();//相差天
long year = ChronoUnit.YEARS.between(d51, d52);//相差年
long month = ChronoUnit.MONTHS.between(d51, d52);//相差月
long days = ChronoUnit.DAYS.between(d51, d52);//相差天
System.out.println(days == day5);//true
6 .日期新增、减少
//6.日期新增、减少
System.out.println("6.日期新增、减少............");
LocalDate d61 = LocalDate.parse("2023-11-01");
//通用d61.plus(num,unit)
/***
ChronoUnit.DAYS ChronoUnit.MONTHS ChronoUnit.YEARS
num>0增加,num<0减少
*/
LocalDate d62 = d61.plus(-1, ChronoUnit.DAYS);
System.out.println(d62);
//plusXxx(num)具体单位已加
LocalDate d63 = d61.plusDays(1);//2023-11-02
d61 = d61.plusMonths(2);
d61 = d61.plusYears(1);
//减少,可以用plus负数代替
d61 = d61.minus(1, ChronoUnit.DAYS);//-1
d61 = d61.minusDays(1);//-1
7.得到特殊的日期
//7.得到特殊的日期
System.out.println("7.得到特殊的日期........................");
/**
LocalDate ld62= ld61.with((TemporalAdjuster adjuster)
*/
LocalDate now = LocalDate.now();
System.out.println("当前时间:" + now); //2021-11-30
//获取当月第一天
System.out.println("当月第一天:" + now.with(TemporalAdjusters.firstDayOfMonth()));// 2021-11-01
System.out.println(now); //now并没有改变
//获取本月第2天:
System.out.println("本月第2天:" + now.withDayOfMonth(2)); //2021-11-02
//获取下月第一天
System.out.println("下月第一天:" + now.with(TemporalAdjusters.firstDayOfNextMonth())); //2021-12-01
//获取明年第一天
System.out.println("明年第一天:" + now.with(TemporalAdjusters.firstDayOfNextYear())); //2022-01-01
//获取本年第一天
System.out.println("本年第一天:" + now.with(TemporalAdjusters.firstDayOfYear()));//2021-01-01
//获取当月最后一天,再也不用计算是28,29,30还是31:
System.out.println("当月最后一天:" + now.with(TemporalAdjusters.lastDayOfMonth())); //2021-11-30
//获取本年最后一天
System.out.println("本年最后一天:" + now.with(TemporalAdjusters.lastDayOfYear())); //2021-12-31
//获取当月的第一个星期一
System.out.println("当月的第一个星期一:" + now.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))); //2021-11-01
//获取当月的最后一个星期一
System.out.println("当月的最后一个星期一:" + now.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY))); //2021-11-29
//获取当月第三周星期五
System.out.println("当月第三周星期五:" + now.with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY))); //2021-11-19
//获取本周一
System.out.println("本周一:" + now.with(DayOfWeek.MONDAY)); //2021-11-29
//获取上周二
System.out.println("上周二:" + now.minusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2)); //2021-11-23
//(往前不包括当天)获取当前日期的上一个周一 如果今天是周一,则返回上周一
System.out.println("上一个周一(不包括当天):" + now.with(TemporalAdjusters.previous(DayOfWeek.MONDAY))); //2021-11-29
//(往前包括当天)最近星期五的日期 如果今天是星期五,则返回今天日期
System.out.println("上一个周一(包括当天):" + now.with(TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY))); //2021-11-26
//获取下周二
System.out.println("下周二:" + now.plusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2)); //2021-12-07
//(往后不包括当天)获取当前日期的下一个周日 如果今天是周日,则返回下周日的时间 如果今天是星期一,则返回本周日的时间
System.out.println("下一个周日(不包括当天):" + now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY))); //2021-12-05
//(往后包括当天)最近星期五的日期 如果今天是星期五,则返回今天日期
System.out.println("下一个周日(包括当天):" + now.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))); //2021-12-03
2.12.7 LocalDateTime类
localDateTime的语法几乎和LocalDate一样
获得LocalDateTime对象
//1.创建LocalDateTime对象
//(1)LDT.now()当前时间
LocalDateTime dt11=LocalDateTime.now();//当前时间
//(2)LDT.of(...)传入每个field字段值
/***
of(年\月\日\时\分\秒) 6个整数可以全部传入,也可以不传秒
*/
LocalDateTime dt12=LocalDateTime.of(
2012, 8,1,
12,12,12);
dt12=LocalDateTime.of(
2012, 8,1,
12,12);
//(3)字符串解析成LocalDateTime
//3-1 parse如果不传格式化类,则必须把字符串的空格用T代替
LocalDateTime dt13=LocalDateTime.parse("2023-02-22T22:22:22");
//3-2使用DateTimeFormatter.of(时间格式)来转化
LocalDateTime dt14=LocalDateTime.parse("2022-02-22 22:22:22",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//注意:ISO 8601规定的日期和时间分隔符是T
System.out.println(dt14);//2023-12-29T15:17:18.018695700
2.LocalDateTime对象和字符串转换
//2.LocalDateTime对象和字符串转换
System.out.println("2.LocalDateTime对象和字符串转换.............");
//(1)LocalDateTime转字符串
LocalDateTime d21=LocalDateTime.now();
String str=d21.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("LocalDateTime转字符串:"+str);
//(2)字符串转LocalDateTime
LocalDateTime d22=LocalDateTime.parse("2023-12-01 23:22:22"
, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
3.日期大小判断,和LocalDate一样
System.out.println(" 3.日期大小判断------------------------");
LocalDateTime dt31=LocalDateTime.parse("2023-12-01T23:22:22");
LocalDateTime dt32=LocalDateTime.parse("2023-12-02T23:22:22");
System.out.println(dt31.isBefore(dt32));//true
System.out.println(dt31.compareTo(dt32));//<0
System.out.println(dt31.equals(dt32));//false
4.时间戳转化
System.out.println(" 4.时间戳转化------------------------");
// (1).LocalDateTime转时间戳
LocalDateTime dt41 = LocalDateTime.now();
System.out.println(dt41);
long stamp = dt41.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
// (2)时间戳转LocalDateTime(LocalDate)
LocalDateTime dt42 = Instant.ofEpochMilli(stamp)
.atZone(ZoneOffset.ofHours(8))
.toLocalDateTime();
System.out.println(dt41.equals(dt42)); //false 有毫秒误差
5.日期加减
不能使用 Period period = Period.between(d51, d52);类
//5.日期加减
System.out.println("5.localDate日期加减............");
LocalDateTime dt51 = LocalDateTime.parse("2023-11-01T11:11:11");
LocalDateTime dt52 = LocalDateTime.parse("2023-11-01T12:11:11");
long year = ChronoUnit.YEARS.between(dt51, dt52);//相差年
long month = ChronoUnit.MONTHS.between(dt51, dt52);//相差月
long days = ChronoUnit.DAYS.between(dt51, dt52);//相差天
long hours=ChronoUnit.HOURS.between(dt51, dt52);//相差小时
long minute=ChronoUnit.MINUTES.between(dt51, dt52);//相差分钟
System.out.println(minute );
6.日期新增、减少 和LocalDate一样
System.out.println("6.日期新增、减少............");
LocalDateTime d61 = LocalDateTime.now();
//通用d61.plus(num,unit)
/***
ChronoUnit.DAYS ChronoUnit.MONTHS ChronoUnit.YEARS
num>0增加,num<0减少
*/
LocalDateTime d62 = d61.plus(-1, ChronoUnit.DAYS);
System.out.println(d62);
//plusXxx(num)具体单位已加
LocalDateTime d63 = d61.plusDays(1);//2023-11-02
d61 = d61.plusMonths(2);
d61 = d61.plusYears(1);
//减少,可以用plus负数代替
d61 = d61.minus(1, ChronoUnit.DAYS);//-1
d61 = d61.minusDays(1);//-1
2.12.8 总结:LocalDate和LocalDateTime
两者语法基本一致,可以非常方便的进行时间和日期的相关计算
类型 | LocalDate | LocalDateTime |
---|---|---|
创建 | LocalDate.now(); LocalDate.of(2023, 11, 29); df=DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”)) LocalDate.parse(str, fm) LocalDate.parse(“2023-11-30”); | LocalDateTime.now() LocalDateTime.of(2012, 8,1,12,12,12); df=DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”)) LocalDateTime.parse(str,df); |
格式化 | ld21.toString(); df=DateTimeFormatter.ofPattern(“yyyy-MM-dd”)) LocalDate.format(DateTimeFormatter) | df=DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”)) str=d21.format(df); |
日期比较 | dt31.isBefore(dt32) dt31.compareTo(dt32) dt31.equals(dt32) | 和LocalDate一样 |
时间戳 | long timestamp= d41.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli(); LocalDate d42 = Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDate(); | long stamp = dt41.toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); LocalDateTime dt42 = Instant.ofEpochMilli(stamp) .atZone(ZoneOffset.ofHours(8)) .toLocalDateTime(); |
日期间隔 | Period period = Period.between(d51, d52); int year5 = period.getYears(); long year = ChronoUnit.YEARS.between(d51, d52); | 不能用Period 类, ChronoUnit一致 |
日期增减 | d61.plus(-1, ChronoUnit.DAYS) d61.plusDays(1); d61.plusMonths(2); | 一致 |
特殊日期 |
2.14流IO
2.14.1 基础定义
java的输入输出流是比较难懂的地方,什么是java.io?
I/O 是指Input/Output,即输入和输出。
Input指从外部读入数据到内存,例如,把文件从磁盘读取到内存,从网络读取数据到内存等等。
Output指把数据从内存输出到外部,例如,把数据从内存写入到文件,把数据从内存输出到网络等等。
Java程序在执行的时候,是在内存进行的,外部的数据需要读写到内存才能处理;而在内存中的数据是随着程序结束就消失的,有时候我们也需要把数据输出到外部文件。Java中,是通过流 处理IO的,这种处理模式称为 IO流,IO流是一种顺序读写数据的模式。
你可以想象它是一根水管,数据就像水一样, 起点—终点 可互相流动。
-
源头
从文件、网络、管道,我们称之为目标源 -
输入/输出
从目标源读取到内容,我们叫输入,用InputStream或者Reader相关子类来处理。
从内存输出到目标源,我们称之为输出,用OutputStream或者Writer相关子类来处理 -
传递介质
我们想要把目标源转化成Byte或者Char才能传输,Byte用InputStream/OutputStream来操作,Char用Reader/Writer来操作。
一般情况,视频、音频、图片等,用byte来传递;文字类的用Char来传递方便一些
2.14.2 IO框架
1.4大框架类
类型 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |