0 概述
一些比较简单,但是常用的标准库使用经验汇总
1 基础类型
代码在这里
1.1 Box
package java_standard_test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestLong {
@Test
public void test1(){
//两个box类型,不能用指针比较,必须要用equals比较
Long a = new Long(11L);
Long b = 11L;
assertFalse(a == b );
assertTrue(a.equals(b));
}
@Test
public void test2(){
//box类型,无论是用字面值,还是类型,都需要类型相同才是为true
//一个是Long类型,另外一个是Integer类型,用equals肯定为false的
Long a = 11L;
assertFalse(a.equals(11));
Integer b = 11;
assertFalse(a.equals(b));
//这个是显然错误的,但是因为equals的参数是Object类型,所以编译器不会报错
assertFalse(a.equals(true));
}
@Test
public void test3(){
//更好的办法是用longValue转换为基础类型,再去比较,这样能适应不同类型的比较
Long a = 11L;
assertTrue(a.longValue()==11);
Integer b = 11;
assertTrue(a.longValue()==b);
//转换为基础类型以后,这段话会在编译时报错
//assertTrue(a.longValue()==true);
//当Box类型为null的时候,转换为基础类型,会抛出NullPointerException,这个符合预期
assertThrows(NullPointerException.class,()->{
Long c = null;
assertTrue(c.longValue()==3);
});
}
}
注意Box类型的比较,先拆箱再比较是最安全稳当的方法
1.2 String
package java_standard_test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestString {
@Test
public void stringToInt(){
//普通方法
int data = Integer.valueOf("123").intValue();
assertEquals(data,123);
//另外一种的解析方法
int data2 = Integer.parseInt("123");
assertEquals(data2,123);
}
@Test
public void stringToInt_radix(){
//普通方法,带基数的处理
int data = Integer.valueOf("100",16).intValue();
assertEquals(data,256);
//另外一种的解析方法,带基数的处理
int data2 = Integer.parseInt("100",16);
assertEquals(data2,256);
}
@Test
public void stringToInt_invalid(){
//没有字符是不行的
assertThrows(NumberFormatException.class,()-> {
int data4 = Integer.valueOf("").intValue();
assertEquals(data4,123);
});
//前或者后有空格也是不行的
assertThrows(NumberFormatException.class,()-> {
int data3 = Integer.valueOf(" 123 ").intValue();
assertEquals(data3,123);
});
//尾部有其他字符也是不行的
assertThrows(NumberFormatException.class,()-> {
int data2 = Integer.valueOf("123k").intValue();
assertEquals(data2, 123);
});
//前部有其他字符也是不行的
assertThrows(NumberFormatException.class,()->{
int data = Integer.valueOf("k123").intValue();
assertEquals(data,123);
});
}
@Test
public void intToString(){
//有装箱操作,但可能会被优化成没有,这个比较省事
String data = 123+"";
assertEquals(data,"123");
//没有装箱和拆箱操作,效率最好
String data2 = String.valueOf(123);
assertEquals(data2,"123");
}
}
string转int,与int转string,这些方法简直用到烂了,我们要注意的是:
- int转string,用Box类型的parseInt,或者valueOf都可以。格式要求比较严格,空格都不行。
- string转int,直接用加空字符串就可以了
1.3 Map
package java_standard_test;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
public class TestMap {
@Test
public void testMapBasic(){
Map<Integer,String> map = new HashMap();
.put(1,"12");
map
//Map的value是泛型,不需要做类型转换
String data = map.get(1);
assertEquals(data,"12");
}
@Test
public void testMapGetNoGeneric(){
Map<Integer,String> map = new HashMap();
.put(1,"12");
map
//注意,map的Key不是泛型,它总是Object类型,接受任意的参数
assertNull(map.get("35"));
//map的contains操作也不是泛型,总是为Object类型
assertFalse(map.containsKey("35"));
assertFalse(map.containsValue(new BigDecimal("2")));
}
@Test
public void testMapGet(){
//Map的Key是ArrayList类型
Map<ArrayList<Integer>,String> map = new HashMap();
//我们先放入一个数据,Key为ArrayList类型的
ArrayList<Integer> list1 = new ArrayList<>();
.add(1);
list1.add(2);
list1.put(list1,"12");
map
assertEquals(map.get(list1),"12");
//但是,由于map的get是Object类型,我们可以尝试放入一个LinkedList类型,结果数据是可以查询出来的
//Java这样的设计,是因为不同类型也是允许equals的,所以将get接口的参数设置为Object,而不是泛型T
LinkedList<Integer> list2 = new LinkedList<>();
.add(1);
list2.add(2);
list2assertEquals(map.get(list2),"12");
}
}
Map类型考虑到了不同类型的key,执行equals也是可能相同的,所以:
- get,containsKey,containsValue的参数都是Object类型,而不是泛型。传错了类型以后,开发者无法得到编译器的编译时提醒,这点是要注意的。
2 日期与时间
代码在这里
在Java 1.8以前,我们使用Date,Calendar来表达日期和时间的概念,但是这两个类有以下的问题:
- Date和Calendar都是可变类型,运行时可修改的。一旦它们的实例被多个线程共享的时候,就可能产生冲突的问题。
- Date的API设计很糟糕,getYear(),getMonth()都没有做好。Calendar是对Date的补丁,但它的getMonth()是以0开始,不是以1开始的,就是getMonth()返回5的话,代表的是6月,而不是5月。(吐血设计)
- Date的API无法表现以下功能,以当前的Date为基准,获取这个月的最后一个星期五是哪一天。或者,以当前的Date为基准,获取本周的范围。
- Date同时表达了时间和日期的信息,但是业务中会出现只需要日期,或者只需要时间的类型。
- Date没有时区的描述。
- 缺少对时间差的描述,就是Duration。
于是,在Java 1.8以后,提供了LocalDate,LocalTime,LocalDateTime,ZonedDateTime,Duration和Period等类型来表达日期和时间的概念。我们应该尽可能使用这些类型,但是对于Date与Calendar也要必须掌握,毕竟旧代码总是存在的。而且,在所有类型之中,Date的序列化,反序列化,获取当前时间,的操作是最快的。
2.1 Date
package java_standard_test;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.Calendar;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
public class TestDate {
//Date作为时间的容量
@Test
public void testNow(){
//获取当前时间,注意是java.util.Date,不是java.sql.Date
Date now = new Date();
.info("now {}",now);
log
//unix时间戳,以毫秒为单位的
Long unix = now.getTime();
.info("unix time {}",unix);
log
Date now2 = new Date(unix+1000);
.info("now add 1 second {}",now2);
log}
}
Date类,注意,unix时间戳是以毫秒为单位的,不是秒。
2.2 Calendar
package java_standard_test;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.Calendar;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Slf4j
public class TestCalendar {
//Calendar是时间的处理方法,两者是分开的,不同的存储方式
@Test
public void testCalendarGet(){
//2021年6月30日 16:51:48 CST
Date now = new Date(1625043108660L);
//Calendar使用工厂模式生成
Calendar calendar = Calendar.getInstance();
.setTime(now);
calendar
//基础信息的获取
assertEquals(calendar.get(Calendar.YEAR),2021);
//注意month必须加1,才是正确的month
assertEquals(calendar.get(Calendar.MONTH)+1,6);
assertEquals(calendar.get(Calendar.DAY_OF_MONTH),30);
//注意HOUR_OF_DAY是24小时制,HOUR是12小时制,很容易发生上下午偏移的问题
assertEquals(calendar.get(Calendar.HOUR_OF_DAY),16);
assertEquals(calendar.get(Calendar.MINUTE),51);
assertEquals(calendar.get(Calendar.SECOND),48);
assertEquals(calendar.get(Calendar.MILLISECOND),660);
//获取这个月的天数范围,注意用getActualMaximum和getActualMinimum,不要用getMaximum和getMinimum
assertEquals(calendar.getActualMinimum(Calendar.DAY_OF_MONTH),1);
assertEquals(calendar.getActualMaximum(Calendar.DAY_OF_MONTH),30);
}
@Test
public void testCalendarSet(){
//2021年6月30日 16:51:48 CST
Date now = new Date(1625043108660L);
Calendar calendar = Calendar.getInstance();
.setTime(now);
calendar
//设置月份为8月
.set(Calendar.MONTH,8-1);
calendar
//注意month必须加1,才是正确的month
assertEquals(calendar.get(Calendar.YEAR),2021);
assertEquals(calendar.get(Calendar.MONTH)+1,8);
assertEquals(calendar.get(Calendar.DAY_OF_MONTH),30);
assertEquals(calendar.get(Calendar.HOUR_OF_DAY),16);
assertEquals(calendar.get(Calendar.MINUTE),51);
assertEquals(calendar.get(Calendar.SECOND),48);
assertEquals(calendar.get(Calendar.MILLISECOND),660);
//从Calendar转换到了Date
Date now2 = calendar.getTime();
.info("now2 {}",now2);
log}
@Test
public void testCalendarAdd(){
//2021年6月30日 16:51:48 CST
Date now = new Date(1625043108660L);
Calendar calendar = Calendar.getInstance();
.setTime(now);
calendar
//对当前时间加1天
.add(Calendar.DAY_OF_MONTH,1);
calendar
//注意month必须加1,才是正确的month
assertEquals(calendar.get(Calendar.YEAR),2021);
assertEquals(calendar.get(Calendar.MONTH)+1,7);
assertEquals(calendar.get(Calendar.DAY_OF_MONTH),1);
assertEquals(calendar.get(Calendar.HOUR_OF_DAY),16);
assertEquals(calendar.get(Calendar.MINUTE),51);
assertEquals(calendar.get(Calendar.SECOND),48);
assertEquals(calendar.get(Calendar.MILLISECOND),660);
//从Calendar转换到了Date
Date now2 = calendar.getTime();
.info("now2 {}",now2);
log}
}
Calendar的注意点如下:
- 需要用getInstance来拿实例
- 使用setTime与getTime,来触发与Date的相互转换
- 月份要+1,才是正确的值
- 小时要用HOUR_OF_DAY,不要使用HOUR
2.3 Date的format与parse
package java_standard_test;
import org.junit.jupiter.api.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestDateFormat {
@Test
public void testFormat() {
//2021年6月30日 16:51:48 CST
Date now = new Date(1625043108660L);
//24小时制,格式化输出
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
assertEquals(ft.format(now),"2021-06-30 16:51:48");
//12小时制,格式化输出
SimpleDateFormat ft2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
assertEquals(ft2.format(now),"2021-06-30 04:51:48");
}
@Test
public void testParse(){
//2021年6月30日 16:51:48 CST
Date now = new Date(1625043108000L);
//24小时制,读取
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try{
Date now2 = ft.parse("2021-06-30 16:51:48");
assertEquals(now,now2);
}catch(ParseException e){
throw new RuntimeException(e);
}
}
}
使用SimpleDateFormat来做Date与String的转换工作,注意小时要用HH,而不是hh
2.4 Duration
package java_standard_test;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.util.Date;
@Slf4j
public class TestDuration {
@Test
public void go(){
//2021年6月30日 16:51:48 CST
Date begin = new Date(1625043108660L);
//2021年6月30日 19:38:28 CST
Date end = new Date(1625053108660L);
Duration duration = Duration.between(begin.toInstant(),end.toInstant());
//PT2H46M40S,代表2小时,36分钟,40秒。
//getSeconds可以转换为秒级的说明
.info("duration {} {}秒",duration,duration.getSeconds());
log}
@Test
public void go2(){
//从秒级转换为Duration
Duration duration = Duration.ofSeconds(100);
.info("duration {}秒",duration.getSeconds());
log}
}
Duration是java 1.8以后的新类型,用between来创建,输出描述是特别的PT2H46M40S格式。
2.5 LocalDate
package java_standard_test;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Slf4j
public class TestLocalDate {
@Test
public void testLocalDateNew(){
//获取今天日期
= LocalDate.now();
LocalDate date }
@Test
public void testLocalDateGet(){
= LocalDate.of(2021,6,30);
LocalDate date
//没有month的坑
assertEquals(date.getYear(),2021);
assertEquals(date.getMonth().getValue(),6);
assertEquals(date.getDayOfMonth(),30);
assertEquals(date.getDayOfWeek().getValue(),3);
}
@Test
public void testLocalDateSet(){
= LocalDate.of(2021,6,30);
LocalDate date
//LocalDate都是Immutable的,可以在跨线程下使用,每次变化都创建新的Date
= date.withYear(2022);
LocalDate date2 assertEquals(date2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),"2022-06-30");
= date2.withMonth(7);
LocalDate date3 = date3.withDayOfMonth(20);
LocalDate date4 assertEquals(date4.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),"2022-07-20");
}
@Test
public void testLocalDateFormatAndParse(){
= LocalDate.of(2021,6,30);
LocalDate date
//format
String formater = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
assertEquals(formater,"2021-06-30");
//parse
= LocalDate.parse("2022-07-28",DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDate date2 assertEquals(date2.getYear(),2022);
assertEquals(date2.getMonth().getValue(),7);
assertEquals(date2.getDayOfMonth(),28);
}
@Test
public void testLocalDateDuration(){
= LocalDate.of(2021,6,30);
LocalDate date = LocalDate.of(2021,7,30);
LocalDate date2
//LocalDate不能用Duration,Duration是秒级的
//Duration duration = Duration.between(date,date2);
= Period.between(date,date2);
Period period assertEquals(period.toString(),"P1M");
}
}
LocalDate的注意点如下:
- 使用LocalDate.now()来获取当前日期
- 使用LocalDate.of()来自主创建特定日期
- 使用parse与format来做与String的转换
- Immutable实现,每个修改操作都会产生新的LocalDate实例,从而可以在多线程环境中安全使用。
- 日期的间距用Period,不能用Duration
- 获取用get,修改用with,with的功能很强大
2.6 LocalDateTime
package java_standard_test;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Slf4j
public class TestLocalDateTime {
@Test
public void testLocalDateTimeNew(){
//获取今天日期
= LocalDateTime.now();
LocalDateTime date
}
@Test
public void testLocalDateTimeGet(){
//用指定的日期创建LocalDate
= LocalDateTime.of(2021,6,30,12,30,59);
LocalDateTime date
//没有month的坑
assertEquals(date.getYear(),2021);
assertEquals(date.getMonth().getValue(),6);
assertEquals(date.getDayOfMonth(),30);
assertEquals(date.getDayOfWeek().getValue(),3);
assertEquals(date.getHour(),12);
assertEquals(date.getMinute(),30);
assertEquals(date.getSecond(),59);
}
@Test
public void testLocalDateTimeSet(){
= LocalDateTime.of(2021,6,30,18,30,59);
LocalDateTime date
//LocalDateTime都是Immutable的,可以在跨线程下使用,每次变化都创建新的Date
= date.withYear(2022);
LocalDateTime date2 assertEquals(date2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),"2022-06-30 18:30:59");
= date2.withMonth(7);
LocalDateTime date3 = date3.withDayOfMonth(20);
LocalDateTime date4 = date4.withHour(20);
LocalDateTime date5 assertEquals(date5.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),"2022-07-20 20:30:59");
}
@Test
public void testLocalDateTimeFormatAndParse(){
= LocalDateTime.of(2021,6,30,18,30,59);
LocalDateTime date
//format
String formater = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
assertEquals(formater,"2021-06-30 18:30:59");
String formater2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
assertEquals(formater2,"2021-06-30T18:30:59");
//parse
= LocalDateTime.parse("2022-07-28 20:18:39",DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
LocalDateTime date2 assertEquals(date2.getYear(),2022);
assertEquals(date2.getMonth().getValue(),7);
assertEquals(date2.getDayOfMonth(),28);
assertEquals(date2.getHour(),20);
assertEquals(date2.getMinute(),18);
assertEquals(date2.getSecond(),39);
}
@Test
public void testLocalDateDuration(){
= LocalDateTime.of(2021,6,30,18,30,59);
LocalDateTime date = LocalDateTime.of(2021,7,30,19,22,33);
LocalDateTime date2
//LocalDateTime不能用Period,Period是天数级的
//Period period = Period.between(date,date2);
Duration duration = Duration.between(date,date2);
assertEquals(duration.toString(),"PT720H51M34S");
}
@Test
public void testSpecialWith(){
= LocalDateTime.of(2021,6,2,18,30,59);
LocalDateTime date
//该月的最后一个星期一
= date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
LocalDateTime date2 assertEquals(date2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),"2021-06-28 18:30:59");
//该月的最后一天
= date.with(TemporalAdjusters.lastDayOfMonth());
LocalDateTime date3 assertEquals(date3.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),"2021-06-30 18:30:59");
}
}
LocalDateTime的注意点如下:
- 使用LocalDateTime.now()来获取当前日期
- 使用LocalDateTime.of()来自主创建特定日期
- 使用parse与format来做与String的转换,有默认的DateTimeFormatter可以选用
- Immutable实现,每个修改操作都会产生新的LocalDateTime实例,从而可以在多线程环境中安全使用。
- 时间的间距用Duration,不能用Period
- 获取用get,修改用with,with的功能很强大。可以看看date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY)),date.with(TemporalAdjusters.lastDayOfMonth()),这个真的是666.
2.7 ZonedDateTime
@Test
public void testZoned(){
= LocalDateTime.of(2021,6,2,18,30,59);
LocalDateTime date
//带时区信息的时间
= date.atZone(ZoneId.systemDefault());
ZonedDateTime zonedDateTime assertEquals(zonedDateTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME),"2021-06-02T18:30:59+08:00[Asia/Shanghai]");
//切换到其他时区
= ZonedDateTime.ofInstant(zonedDateTime.toInstant(),ZoneId.of("Asia/Yerevan"));
ZonedDateTime zonedDateTime2 assertEquals(zonedDateTime2.format(DateTimeFormatter.ISO_ZONED_DATE_TIME),"2021-06-02T14:30:59+04:00[Asia/Yerevan]");
}
LocalDateTime与ZonedDateTime的互相转换
3 流计算
代码在这里
3.1 创建流
我一直觉得Java的流实现挺糟糕,完全没有C#的强大。不过有时候用流的确能少写点代码。
package java_standard_test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.junit.jupiter.api.Assertions.*;
public class TestStreamCreate {
@Test
public void testCollect(){
List<Integer> lists = Arrays.asList(1,2,3);
List<Integer> lists2 = lists.stream().map((a)->{
return a+1;
}).collect(Collectors.toList());
assertIterableEquals(lists2,Arrays.asList(2,3,4));
}
@Autowired
private UserRepository userRepository;
@Test
public void testIterable(){
Iterable<Integer> iterables = Arrays.asList(1,2,3);
//第二个参数代表不并行
List<Integer> lists = StreamSupport.stream(iterables.spliterator(),false).map((a)->{
return a+1;
}).collect(Collectors.toList());
assertIterableEquals(lists,Arrays.asList(2,3,4));
}
@Test
public void testObjectArray(){
Integer[] intArray = new Integer[]{1,2,3};
//用Arrays.stream来转换为Stream
List<Integer> lists = Arrays.stream(intArray).map((a)->{
return a+1;
}).collect(Collectors.toList());
assertIterableEquals(lists,Arrays.asList(2,3,4));
}
@Test
public void testObjectArray2(){
Integer[] intArray = new Integer[]{1,2,3};
//用Stream.of来转换为Stream
List<Integer> lists = Stream.of(intArray).map((a)->{
return a+1;
}).collect(Collectors.toList());
assertIterableEquals(lists,Arrays.asList(2,3,4));
}
@Test
public void testPrimitiveArray(){
int[] intArray = new int[]{1,2,3};
//需要加入box转换,否则会报错
List<Integer> lists = Arrays.stream(intArray).map((a)->{
return a+1;
}).boxed().collect(Collectors.toList());
assertIterableEquals(lists,Arrays.asList(2,3,4));
}
//使用Stream.of,转换int[]的时候,会报错,会转换为Stream<int[]>,而不是正确的Stream<int>流
/*
@Test
public void testPrimitiveArray(){
int[] intArray = new int[]{1,2,3};
List<Integer> lists = Stream.of(intArray).map((a)->{
return a+1;
}).boxed().collect(Collectors.toList());
assertIterableEquals(lists,Arrays.asList(2,3,4));
}
*/
}
要注意,可以用以下的方式创建流:
- List,直接用stream方法
- spliterator方法创建流
- Arrays.stream,对数组类型创建流,对于基础类型数组,输出的时候要注意用boxed来包装一下元素类型。
- Stream.of,对于基础类型数组,这种方法会失效。
将List与Iterable转换为流的方式,并用map,forEach,filter,sort等流操作符,最后用collect输出。
3.2 流操作
package java_standard_test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
public class TestStreamOperate {
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class User{
private Long id;
private String name;
}
@Test
public void testMap(){
List<User> userList = Arrays.asList(
new User(1L,"fish"),
new User(2L,"dog"),
new User(3L,"sheep")
);
Map<Long,User> userMap = userList.stream().collect(Collectors.toMap(ref->ref.getId(), ref->ref));
.info("{}",userMap);
log}
@Test
public void testGroup(){
List<User> userList = Arrays.asList(
new User(1L,"fish"),
new User(1L,"cat"),
new User(2L,"dog"),
new User(3L,"sheep")
);
Map<Long,List<User>> userMap = userList.stream().collect(Collectors.groupingBy(ref->ref.getId(),Collectors.toList()));
.info("{}",userMap);
log}
}
注意,可以用Collectors.toMap,或者Collectors.groupingBy来创建map映射,groupingBy的方法更为强大一点。
3.3 集合转换
package java_standard_test;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
public class TestListAndArray {
@Test
public void testCreateArray(){
int[] intArray = new int[]{1,2,3};
Integer[] intArray2 = new Integer[10];
char[] charArray = new char[]{'a','b','c','d'};
assertEquals(intArray.length,3);
assertEquals(intArray2.length,10);
assertEquals(charArray.length,4);
}
@Test
public void testPrimitiveArrayToList(){
int[] intArray = new int[]{1,2,3,4};
List<Integer> intList1 = Arrays.stream(intArray)
.boxed()
.collect(Collectors.toList());
assertEquals(intList1.toString(),
Arrays.asList(1,2,3,4).toString());
//这样做不行,只支持boxed Array
//List<Integer> intList1 = Arrays.asList(intArray);
//这样做也不行,只支持boxedArray
//List<Integer> intList2 = new ArrayList<>();
//Collections.addAll(intList2,intArray);
}
@Test
public void testBoxedArrayToList(){
Integer[] intArray = new Integer[]{1,2,3,4};
List<Integer> intList1 = Arrays.asList(intArray);
List<Integer> intList2 = new ArrayList<>(Arrays.asList(intArray));
List<Integer> intList3 = Arrays.stream(intArray).collect(Collectors.toList());
List<Integer> intList4 = Stream.of(intArray).collect(Collectors.toList());
List<Integer> intList5 = new ArrayList<>();
Collections.addAll(intList5,intArray);
assertEquals(intList1.toString(),
Arrays.asList(1,2,3,4).toString());
assertEquals(intList2.toString(),
Arrays.asList(1,2,3,4).toString());
assertEquals(intList3.toString(),
Arrays.asList(1,2,3,4).toString());
assertEquals(intList4.toString(),
Arrays.asList(1,2,3,4).toString());
assertEquals(intList5.toString(),
Arrays.asList(1,2,3,4).toString());
}
@Test
public void testPrimitiveArrayToBoxedArray(){
int[] intArray = new int[]{1,2,3,4};
//stream的方法
Integer[] intArray2 = Arrays.stream(intArray)
.boxed()
.toArray(Integer[]::new);
//手工方法
Integer[] intArray3 = new Integer[intArray.length];
for( int i = 0 ;i != intArray.length;i++){
[i] = intArray[i];
intArray3}
//对比
for(int i = 0 ;i != intArray.length;i++){
assertEquals(intArray[i],intArray2[i]);
assertEquals(intArray[i],intArray3[i]);
}
assertEquals(intArray.length,intArray2.length);
assertEquals(intArray.length,intArray3.length);
}
@Test
public void testBoxedArrayToPrimitiveArray(){
Integer[] intArray = new Integer[]{1,2,3,4};
//stream方法,只支持mapToInt,mapToLong,mapToDouble
int[] intArray2 = Arrays.stream(intArray)
.mapToInt(Integer::valueOf)
.toArray();
//手工方法
int[] intArray3 = new int[intArray.length];
for( int i = 0 ;i != intArray.length;i++){
[i] = intArray[i];
intArray3}
//对比
for(int i = 0 ;i != intArray.length;i++){
assertEquals(intArray[i],intArray2[i]);
assertEquals(intArray[i],intArray3[i]);
}
assertEquals(intArray.length,intArray2.length);
assertEquals(intArray.length,intArray3.length);
}
@Test
public void testListToBoxedArray(){
List<Integer> intList = Arrays.asList(1,2,3,4);
Integer[] intArray = intList.stream().toArray(Integer[]::new);
Integer[] intArray2 = intList.toArray(new Integer[0]);
//对比
for(int i = 0 ;i != intList.size();i++){
assertEquals(intList.get(i),intArray[i]);
assertEquals(intList.get(i),intArray2[i]);
}
assertEquals(intList.size(),intArray.length);
assertEquals(intList.size(),intArray2.length);
}
@Test
public void testListToPrimitiveArray(){
List<Integer> intList = Arrays.asList(1,2,3,4);
int[] intArray = intList.stream().mapToInt(Integer::valueOf).toArray();
//对比
for(int i = 0 ;i != intList.size();i++){
assertEquals(intList.get(i),intArray[i]);
}
assertEquals(intList.size(),intArray.length);
}
}
有三种集合,我们需要相互之间转换
- 原生类型array,例如是int[]
- 包装类型array,例如是Integer[]
- 包装类型list,例如是List<Integer>
以上转换之间均可以使用stream来实现,要学会灵活运用以上方法。
4 IO流
Java里面的IO流相当复杂,需要仔细对比每个类的职责。
代码在这里
4.1 字节流与缓存流
package java_test;
import org.springframework.stereotype.Component;
import java.io.*;
@Component
public class FileTest {
public void go()throws Exception{
FileInputStream inFile = null;
FileOutputStream outFile = null;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try{
//FileInputStream是对文件的字节流
= new FileInputStream("a.jpeg");
inFile = new FileOutputStream("b.jpeg");
outFile //bufferedInputStream是处理流
= new BufferedInputStream(inFile);
bufferedInputStream = new BufferedOutputStream(outFile);
bufferedOutputStream byte[] buffer = new byte[1024];
int length = 0;
while( (length= bufferedInputStream.read(buffer))!=-1){
.write(buffer,0,length);
bufferedOutputStream}
}finally {
if( bufferedOutputStream != null){
.close();
bufferedOutputStream}
if( bufferedInputStream != null){
.close();
bufferedInputStream}
if( outFile != null){
.close();
outFile}
if( inFile != null){
.close();
inFile}
}
}
}
InputStream与OutputStream就是以字节为对象的IO流,BufferedInputStream与BufferedOutputStream是一个装饰器对象,它能将字节流进行封装,高效地预读取数据到内存中,以提高读写效率。
同理地,其他的字节流还有:
- ByteArrayInputStream,从字节数组中读取
- ByteArrayOutputStream,写入到字节数组中
- PipedInputStream,从管道中读取
- PipedOutputStream,写入到管道中
4.2 字符流
package java_test;
import org.springframework.stereotype.Component;
import java.io.*;
@Component
public class FileTest2 {
public void go()throws Exception{
//FileReader是默认按系统的默认编码来读取的,Mac是UTF-8编码,在Windows是GBK编码,读取后的结果统一转换为UTF-8编码
//这个设计显然并不好,在Windows系统中打开UTF-8编码的文件就会乱码,因为Windows系统默认的编码是GBK
FileReader inFile = null;
FileWriter outFile = null;
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try{
//FileInputStream是对文件的字符流
= new FileReader("utf8.txt");
inFile = new FileWriter("b.txt");
outFile //bufferedInputStream是处理字符流
= new BufferedReader(inFile);
bufferedReader = new BufferedWriter(outFile);
bufferedWriter //这是与FileInputStream的区分,处理的是字符,不是字节,所有用char[],而不是byte[]
char[] buffer = new char[1024];
int length = 0;
while( (length= bufferedReader.read(buffer))!=-1){
.write(buffer,0,length);
bufferedWriter}
}finally {
if( bufferedWriter != null){
.close();
bufferedWriter}
if( bufferedReader != null){
.close();
bufferedReader}
if( outFile != null){
.close();
outFile}
if( inFile != null){
.close();
inFile}
}
}
}
Reader与Writer就是字符流,它的处理对象是Char,而不是byte,这就是他们与字节流的区别。同样地,带有缓冲区优化的IO流,就不能使用BufferedInputStream了,要用对应的BufferedReader。注意,FileReader是默认按系统的默认编码来读取的,Mac是UTF-8编码,在Windows是GBK编码,读取后的结果统一转换为UTF-8编码。这个设计显然并不好,在Windows系统中打开UTF-8编码的文件就会乱码,因为Windows系统默认的编码是GBK
同理地,其他的字符流还有:
- CharArrayReader,从字符数组中读取
- CharArrayWriter,写入到字符数组中
- StringReader,从字符串中读取
- StringWriter,写入到字符串中
- PipedReader,从管道中读取
- PipedWriter,写入到管道中
可以看到,CharArray与String是只有字符流,没有字节流的。同理,ByteArray是只有字节流,没有字符流的
4.3 字节流与字符流的转换
package java_test;
import org.springframework.stereotype.Component;
import java.io.*;
@Component
public class FileTest3 {
public void go()throws Exception{
//FileReader是默认按系统的默认编码来读取的,Mac是UTF-8编码,在Windows是GBK编码,读取后的结果统一转换为UTF-8编码
//这个设计显然并不好,在Windows系统中打开UTF-8编码的文件就会乱码,因为Windows系统默认的编码是GBK
FileInputStream inFile = null;
FileOutputStream outFile = null;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
InputStreamReader reader = null;
OutputStreamWriter writer = null;
try{
//FileInputStream是对文件的字节流
= new FileInputStream("utf8.txt");
inFile = new FileOutputStream("gbk.txt");
outFile //bufferedInputStream是处理字节流
= new BufferedInputStream(inFile);
bufferedInputStream = new BufferedOutputStream(outFile);
bufferedOutputStream //Stream转换为Reader,从字节流转换为字符流
= new InputStreamReader(bufferedInputStream,"UTF-8");
reader = new OutputStreamWriter(bufferedOutputStream,"GBK");
writer //这是与FileInputStream的区分,处理的是字符,不是字节,所有用char[],而不是byte[]
char[] buffer = new char[1024];
int length = 0;
while( (length= reader.read(buffer))!=-1){
.write(buffer,0,length);
writer}
}finally {
if( writer != null ){
.close();
writer}
if( reader != null){
.close();
reader}
if( bufferedOutputStream != null){
.close();
bufferedOutputStream}
if( bufferedInputStream != null){
.close();
bufferedInputStream}
if( outFile != null){
.close();
outFile}
if( inFile != null){
.close();
inFile}
}
}
}
字符流与字节流转换的方式就是通过InputStreamReader与OutputStreamWriter,InpuStreamReader能将读取字节流,转换为读取字符流,OutputStreamWriter能写入字符流,转换为写入字节流。与纯粹的FileReader不同,InputStreamReader是能指定文件原来的编码,能显式地避免乱码问题的出现。
4.4 ByteArrayStream
package java_test;
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.*;
@Component
public class ByteArrayStreamTest {
public byte[] readToMemory()throws Exception{
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
FileInputStream inFile = null;
BufferedInputStream bufferedInputStream = null;
FileOutputStream outFile = null;
try{
= new FileInputStream("a.jpeg");
inFile = new BufferedInputStream(inFile);
bufferedInputStream byte[] buffer = new byte[1024];
int length = 0;
while( (length= bufferedInputStream.read(buffer))!=-1){
.write(buffer,0,length);
byteStream}
return byteStream.toByteArray();
}finally {
if( byteStream!= null ){
.close();
byteStream}
if( bufferedInputStream != null){
.close();
bufferedInputStream}
if( inFile != null){
.close();
inFile}
}
}
public void memoryToFile(byte[] memory)throws Exception{
ByteArrayInputStream byteStream = new ByteArrayInputStream(memory);
FileOutputStream outFile = null;
BufferedOutputStream bufferedOutputStream = null;
try{
= new FileOutputStream("b2.jpeg");
outFile = new BufferedOutputStream(outFile);
bufferedOutputStream byte[] buffer = new byte[1024];
int length = 0;
while( (length= byteStream.read(buffer))!=-1){
.write(buffer,0,length);
bufferedOutputStream}
}finally {
if( byteStream != null ){
.close();
byteStream}
if( bufferedOutputStream != null){
.close();
bufferedOutputStream}
if( outFile != null){
.close();
outFile}
}
}
public void go()throws Exception{
byte[] memory = this.readToMemory();
this.memoryToFile(memory);
}
}
ByteArrayOutputStream与ByteArrayInputStream,是将内存看成是流的源和目的地的方法,这个工具经常用到。同理的是,StringReader与StringWriter
4.5 ByteBuffer
package java_test;
import com.oracle.tools.packager.IOUtils;
import org.springframework.stereotype.Component;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
@Component
public class ByteBufferTest {
public void go()throws Exception {
FileChannel readChannel = null;
FileChannel writeChannel = null;
try {
= FileChannel.open(new File("a.jpeg").toPath());
readChannel = FileChannel.open(new File("b3.jpeg").toPath(), StandardOpenOption.WRITE , StandardOpenOption.CREATE);
writeChannel ByteBuffer byteBuffer = ByteBuffer.allocate(64);
int length = 0;
while ((length = readChannel.read(byteBuffer)) != -1) {
//转换到写模式,从byteBuffer读取数据,写入到文件中
.flip();
byteBuffer.write(byteBuffer);
writeChannel
//清空缓冲区,未读未写
.clear();
byteBuffer}
} finally {
if (readChannel != null) {
.close();
readChannel}
if (writeChannel != null) {
.close();
writeChannel}
}
}
}
ByteBuffer是nio的工具,用来代替简单的byte[1024]数组,它需要配合FileChannel或者SocketChannel这些工具来使用。注意,它不是StringBuffer的字节对应版本,它们是完全不同的两个概念。
4.6 关闭流
package java_test;
import org.springframework.stereotype.Component;
import java.io.*;
@Component
public class CloseFileTest {
private void test1(){
FileInputStream inFile = null;
BufferedInputStream bufferedInputStream = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try{
//FileInputStream是对文件的字节流
= new FileInputStream("a.jpeg");
inFile //bufferedInputStream是处理流
= new BufferedInputStream(inFile);
bufferedInputStream byte[] buffer = new byte[1024];
int length = 0;
while( (length= bufferedInputStream.read(buffer))!=-1){
.write(buffer,0,length);
byteArrayOutputStream}
int allLength = byteArrayOutputStream.toByteArray().length;
if( allLength != 256272 ){
throw new RuntimeException("读取错误!");
}
}catch(FileNotFoundException e ){
.printStackTrace();
e}catch(IOException e){
.printStackTrace();
e}finally {
try{
if( bufferedInputStream != null){
.close();
bufferedInputStream}
if( inFile != null){
.close();
inFile}
if( byteArrayOutputStream != null ){
.close();
byteArrayOutputStream}
}catch(IOException e){
.printStackTrace();
e}
}
}
private void test2(){
FileInputStream inFile = null;
BufferedInputStream bufferedInputStream = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try{
//FileInputStream是对文件的字节流
= new FileInputStream("a.jpeg");
inFile //bufferedInputStream是处理流
= new BufferedInputStream(inFile);
bufferedInputStream byte[] buffer = new byte[1024];
int length = 0;
while( (length= bufferedInputStream.read(buffer))!=-1){
.write(buffer,0,length);
byteArrayOutputStream}
if( byteArrayOutputStream.toByteArray().length != 256272){
throw new RuntimeException("读取错误!");
}
}catch(FileNotFoundException e ){
.printStackTrace();
e}catch(IOException e){
.printStackTrace();
e}finally {
try{
//在装饰器模式上,外层流关闭,会触发内层流的关闭,不再需要手动关闭内层流
if( bufferedInputStream != null){
.close();
bufferedInputStream}
if( byteArrayOutputStream != null ){
.close();
byteArrayOutputStream}
}catch(IOException e){
.printStackTrace();
e}
}
}
private void test3(){
try(
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("a.jpeg"));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
){
byte[] buffer = new byte[1024];
int length = 0;
while( (length= inputStream.read(buffer))!=-1){
.write(buffer,0,length);
outputStream}
if( outputStream.toByteArray().length != 256272){
throw new RuntimeException("读取错误!");
}
}catch(FileNotFoundException e ){
.printStackTrace();
e}catch(IOException e){
.printStackTrace();
e}
}
public void go(){
test1();
test2();
test3();
}
}
如何正确关闭IO流,注意几点:
- 装饰器模式的IO流,只需要关闭最外面的流,底层和中间流都会自动关闭
- jdk7的try方式关闭流会省事一点
5 文件操作
看这里
5.1 文件
package java_test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.*;
@Component
@Slf4j
public class FileTest {
public void go()throws Exception{
File a = new File("../file/data/a.jpeg");
.info("exists {}",a.exists());
log.info("name {}",a.getName());
log.info("path {}",a.getPath());
log.info("absolutePath {}",a.getAbsolutePath());
log.info("canonicalPath {}",a.getCanonicalPath());//去掉了...的符号
log.info("isDirectory {}",a.isDirectory());
log.info("isFile {}",a.isFile());
log
File b = new File("data/cc.jpeg");
if( b.exists() == false ){
//创建文件
.createNewFile();
b
//重命名
.renameTo(new File("data/c2.jpeg"));
b}
//删除文件
new File("data/c2.jpeg").delete();
}
}
java的File工具还是相当简单的,也很直观,注意getCanonicalPath与getAbsolutePath的区别。
5.2 文件夹
package java_test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.File;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.stream.Collectors;
@Component
@Slf4j
public class DirectoryTest {
public void go()throws Exception{
File a = new File("../file");
.info("exists {}",a.exists());
log.info("name {}",a.getName());
log.info("path {}",a.getPath());
log.info("absolutePath {}",a.getAbsolutePath());
log.info("canonicalPath {}",a.getCanonicalPath());//去掉了...的符号
log.info("isDirectory {}",a.isDirectory());
log.info("isFile {}",a.isFile());
log
File[] subFiles = a.listFiles();
.info("subFiles {}", Arrays.stream(subFiles).collect(Collectors.toList()));
log
File c = new File("data2");
if( c.exists() == false ){
//创建文件夹
.mkdir();
c//重命名
.renameTo(new File("data3"));
c}
//删除文件夹
new File("data3").delete();
}
}
文件夹的操作也很简单,我们最常用的方法是listFiles。
6 ScriptEngine
代码在这里
Java的ScriptEngine是在JVM上执行javascript代码的工具,性能虽然比不上V8,但也不差。常用在:
- 爬虫里面拉取js代码,然后放在ScriptEngine中执行
- ScriptEngine作为用户输入自定义逻辑的方法,安全可靠
- ScriptEngine作为Java的模板引擎。
遗憾的是,ScriptEngine在Jdk 15废弃了,因为跟不上最新的JS规范。
6.1 Java调用Javascript
package java_test;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
@Component
@Slf4j
public class EvalTest {
public void testEvalExpression(){
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
try {
String script = "10+2";
Object result = nashorn.eval(script);
.info("{} = {},{}",script,result,result.getClass().getTypeName());
log}catch(ScriptException e){
System.out.println("执行脚本错误: "+ e.getMessage());
}
}
public void testEvalObject(){
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
try {
String script = "var a = {\"name\":\"fish\",\"data\":[1,2,3]};JSON.stringify(a)";
String result = (String)nashorn.eval(script);
.info("{} = {},{}",script,result,result.getClass().getTypeName());
log}catch(ScriptException e){
System.out.println("执行脚本错误: "+ e.getMessage());
}
}
public void testEvalFunction(){
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
try {
String script = "function run(){"+
"var a = {\"name\":\"fish\",\"data\":[1,2,3]};" +
"return JSON.stringify(a);"+
"}";
= (ScriptObjectMirror)nashorn.eval(script);
ScriptObjectMirror invocable Object result = invocable.call(null);
.info("{} = {},{}",script,result,result.getClass().getTypeName());
log}catch(ScriptException e){
System.out.println("执行脚本错误: "+ e.getMessage());
}
}
public void go(){
testEvalExpression();
testEvalObject();
testEvalFunction();
}
}
我们主要是使用eval方法来执行对应的脚本。注意点如下:
- 对于返回值为普通类型,eval返回String或者Integer等对应的类型
- 对于返回值为js的Object类型,eval返回ScriptObjectMirror类型
- 对于返回值为function类型,eval也是返回ScriptObjectMirror类型。
7 BigDecimal
代码在这里
7.1 基础操作
package java_standard_test;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Slf4j
public class TestBigDecimal {
@Test
public void testBaisc(){
BigDecimal a = new BigDecimal("11");
BigDecimal b = new BigDecimal("1.2");
assertEquals(a.add(b),new BigDecimal("12.2"));
assertEquals(a.subtract(b),new BigDecimal("9.8"));
assertEquals(a.multiply(b),new BigDecimal("13.2"));
//能够整除的时候才能使用直接的divide,否则会报出ArithmeticException异常
assertThrows(ArithmeticException.class,()->{
assertEquals(a.divide(b),new BigDecimal("12.31"));
});
//直接使用divide模式的时候,结果的scale(小数位数),与a的scale(小数位数)相同,也就是不确定的,我们需要尽量避免这种写法
assertEquals(a.divide(b, BigDecimal.ROUND_HALF_UP),new BigDecimal("9"));
//使用divide,指定scale(小数位数),明确的指定,我们尽量使用这种写法
assertEquals(a.divide(b,4,BigDecimal.ROUND_HALF_UP),new BigDecimal("9.1667"));
}
@Test
public void testEquals(){
BigDecimal a = new BigDecimal("11");
BigDecimal b = new BigDecimal("11.0");
//直接使用equals比较的时候,会比较scale部分,所以结果为false。这种写法我们需要尽量避免
assertFalse(a.equals(b));
//使用compareTo,才能在避免scale不同的时候,只进行value部分的比较
assertEquals(a.compareTo(b),0);
}
@Test
public void testAbs(){
assertEquals(new BigDecimal("11").abs(),new BigDecimal("11"));
assertEquals(new BigDecimal("-11").abs(),new BigDecimal("11"));
}
@Test
public void testFormat(){
BigDecimal veryBigNumber = new BigDecimal("1.23e14");
BigDecimal normal = new BigDecimal("11");
BigDecimal backNumber = new BigDecimal("12.200");
//普通的整数
assertEquals(veryBigNumber.toString(),"1.23E+14");
assertEquals(normal.toString(),"11");
assertEquals(backNumber.toString(),"12.200");
//plainString,避免科学计数法
assertEquals(veryBigNumber.toPlainString(),"123000000000000");
assertEquals(normal.toPlainString(),"11");
assertEquals(backNumber.toPlainString(),"12.200");
//stripTrailingZeros + plainString,先去掉尾部0,再加上避免科学计数法
assertEquals(veryBigNumber.stripTrailingZeros().toPlainString(),"123000000000000");
assertEquals(normal.stripTrailingZeros().toPlainString(),"11");
assertEquals(backNumber.stripTrailingZeros().toPlainString(),"12.2");
}
@Test
public void testRound(){
BigDecimal a = new BigDecimal("2.12");
BigDecimal a_2 = new BigDecimal("2.15");
BigDecimal b = new BigDecimal("-2.12");
BigDecimal b_2 = new BigDecimal("-2.15");
//对于,正数,且不在中间线
assertEquals(a.setScale(1,BigDecimal.ROUND_CEILING).toPlainString(),"2.2");
assertEquals(a.setScale(1,BigDecimal.ROUND_FLOOR).toPlainString(),"2.1");
assertEquals(a.setScale(1,BigDecimal.ROUND_DOWN).toPlainString(),"2.1");
assertEquals(a.setScale(1,BigDecimal.ROUND_UP).toPlainString(),"2.2");
assertEquals(a.setScale(1,BigDecimal.ROUND_HALF_UP).toPlainString(),"2.1");
assertEquals(a.setScale(1,BigDecimal.ROUND_HALF_DOWN).toPlainString(),"2.1");
//对于,正数,且在中间线
assertEquals(a_2.setScale(1,BigDecimal.ROUND_CEILING).toPlainString(),"2.2");
assertEquals(a_2.setScale(1,BigDecimal.ROUND_FLOOR).toPlainString(),"2.1");
assertEquals(a_2.setScale(1,BigDecimal.ROUND_DOWN).toPlainString(),"2.1");
assertEquals(a_2.setScale(1,BigDecimal.ROUND_UP).toPlainString(),"2.2");
assertEquals(a_2.setScale(1,BigDecimal.ROUND_HALF_UP).toPlainString(),"2.2");
assertEquals(a_2.setScale(1,BigDecimal.ROUND_HALF_DOWN).toPlainString(),"2.1");
//对于,负数,且不在中间线
assertEquals(b.setScale(1,BigDecimal.ROUND_CEILING).toPlainString(),"-2.1");
assertEquals(b.setScale(1,BigDecimal.ROUND_FLOOR).toPlainString(),"-2.2");
assertEquals(b.setScale(1,BigDecimal.ROUND_DOWN).toPlainString(),"-2.1");
assertEquals(b.setScale(1,BigDecimal.ROUND_UP).toPlainString(),"-2.2");
assertEquals(b.setScale(1,BigDecimal.ROUND_HALF_UP).toPlainString(),"-2.1");
assertEquals(b.setScale(1,BigDecimal.ROUND_HALF_DOWN).toPlainString(),"-2.1");
//对于,负数,且在中间线
assertEquals(b_2.setScale(1,BigDecimal.ROUND_CEILING).toPlainString(),"-2.1");
assertEquals(b_2.setScale(1,BigDecimal.ROUND_FLOOR).toPlainString(),"-2.2");
assertEquals(b_2.setScale(1,BigDecimal.ROUND_DOWN).toPlainString(),"-2.1");
assertEquals(b_2.setScale(1,BigDecimal.ROUND_UP).toPlainString(),"-2.2");
assertEquals(b_2.setScale(1,BigDecimal.ROUND_HALF_UP).toPlainString(),"-2.2");
assertEquals(b_2.setScale(1,BigDecimal.ROUND_HALF_DOWN).toPlainString(),"-2.1");
//总结如下:
//ROUND_CEILING和ROUND_FLOOR是考虑符号的进位,ROUND_CEILING总是偏大(同时考虑数和符号),ROUND_FLOOR总是偏小(同时考虑数和符号)。
//ROUND_UP和ROUND_DOWN是不考虑符号的进位,ROUND_UP总是偏大(只考虑数,不考虑符号),ROUND_DOWN总是偏小(只考虑数,不考虑符号)。
//ROUND_HALF_UP和ROUND_HALF_DOWN是不考虑符号的进位,ROUND_HALF_UP遇到5进位,ROUND_HALF_DOWN遇到5退位。
}
@Test
public void testToInteger() {
BigDecimal a = new BigDecimal("1.2");
//在没有Round的情况下,直接转换为intValueExtract会报错
assertThrows(ArithmeticException.class,()->{
assertEquals(a.intValueExact(),1);
});
assertEquals(a.setScale(0,BigDecimal.ROUND_FLOOR).intValueExact(),1);
//intValue就没有那么严谨了,没有round的时候也能转换到int,我们尽量避免这样做
assertEquals(a.intValue(),1);
}
}
BigDecimal的基础操作包括有:
- 加减乘除,注意除法运算最好带上scale。
- 比较,用compareTo,不要用equals
- 绝对值,abs,没啥好说的。
- 取整round,注意多种模式之间的区别
- 格式化,一般使用toPlainString,和stripTrailingZeros的组合
- 类型转换,注意全部用xxxExtact的API,以保证安全性
8 Optional
在Java开发中,我们一般返回值都不能返回Null,对于返回结果可能为null的类型,需要显式地用Optional包裹一下
代码在这里
8.1 基础
package java_standard_test;
import org.junit.jupiter.api.Test;
import java.util.NoSuchElementException;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
public class TestOptional {
@Test
public void testEmpty(){
<Integer> result = Optional.empty();
Optional
assertEquals(result.isPresent(),false);
assertThrows(NoSuchElementException.class,()->{
.get();
result});
}
@Test
public void testOf(){
<Integer> result = Optional.of(123);
OptionalassertEquals(result.isPresent(),true);
assertEquals(result.get(),123);
}
}
Optional的写法还是比较简单,要点如下:
- 用Optional.empty和Optional.of来创建实例
- Optional.isPresent是检查是否为null的工具
- Optional.get只有在非null的时候才能执行,在null的时候会抛出异常
一般不需学习Optional的map,orElse,filter等这些操作符,因为它们给与了额外的设计复杂性,但是意义并不是很大。
9 拷贝中的Cloneable与Serializable
代码在这里
9.1 拷贝实现
package java_test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
//Serializable会递归调用每个字段的Serializable接口,如果某个字段没有实现Serializable,就会抛出异常
//这意味着People也必须实现Serializable接口才能正常运行,否则运行时会抛出异常
//Cloneable仅仅是一个浅拷贝的实现
public class Country implements Cloneable,Serializable {
private Long id;
private String name;
private List<People> peopleList = new ArrayList<>();
@Override
@SneakyThrows
public Country clone(){
return (Country)super.clone();
}
}
Cloneable的实现要麻烦一点,加入Cloneable的接口实现,并且将protected的clone转为public。Serializable最为简单,直接加入Serializable的接口就可以了。
package java_test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class People implements Serializable {
private Long id;
private String name;
}
People实体只有Serializable接口
9.2 测试
package java_test;
import com.fasterxml.jackson.databind.ObjectMapper;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@Component
@Slf4j
public class CloneTest {
public Country createCountry(){
= new Country();
Country country .setId(1001L);
country.setName("fish");
country
= new People();
People people1 .setId(201L);
people1.setName("p1");
people1
= new People();
People people2 .setId(202L);
people2.setName("p2");
people2
List<People> peopleList = new ArrayList<>();
.add(people1);
peopleList.add(people2);
peopleList.setPeopleList(peopleList);
countryreturn country;
}
public void testClone(Function<Country,Country> handler){
= createCountry();
Country country .info("{}",country);
log
= handler.apply(country);
Country country2 .setName("cat");
country2.getPeopleList().get(0).setName("p3");
country2
= new People();
People people3 .setId(203L);
people3.setName("p3");
people3.getPeopleList().add(people3);
country2
.info("old {}",country);
log.info("new {}",country2);
log}
@Autowired
private ObjectMapper objectMapper;
@SneakyThrows
public <T> T deepCloneByJackJson(T input){
byte[] arrayBytes = objectMapper.writeValueAsBytes(input);
return (T)objectMapper.readValue(arrayBytes,input.getClass());
}
@SneakyThrows
public <T extends Serializable> T deepClone(T input){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
.writeObject(input);
oosByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream iis = new ObjectInputStream(bais);
return (T)iis.readObject();
}
public void go(){
.info("none clone");
logtestClone(ref->ref);
//Cloneable是浅拷贝实现
.info("shallow clone");
logtestClone(ref->ref.clone());
//Serializable是深拷贝实现
.info("deep clone");
logtestClone(ref->deepClone(ref));
//Jackson复制是深拷贝实现
.info("deep clone by jackjson");
logtestClone(ref->deepCloneByJackJson(ref));
}
}
测试代码也比较简单,没啥好说的,具体就是:
- Cloneable是浅拷贝
- Serializable是深拷贝,只是性能能差一点,而且要注意所有字段都需要实现Serializable。对于忽略的字段,我们需要用transient关键字(不是注解)
- Jackjson的深拷贝方式就经常用了,连注解都不用写。对于忽略的字段,我们用@JsonIgnore的方式
深拷贝与序列化不是同一个问题,深拷贝大多场景是内存中,不需要考虑数据的历史版本,也不需要考虑可读性。序列化则麻烦多了。
10 字符串String
代码在这里
参考资料在这里
10.1 字符串格式化
package java_test;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
//看这里https://www.cnblogs.com/zhongjunbo555/p/11383159.html
public class BasicTest {
@Test
public void stringFormatTest(){
//正数格式%[index$][标识][最小宽度]转换方式
//数字表示占用位数,默认空格占位,前补0表示用0占位,逗号为三位法
String str1 = String.format("%d- %2d-%03d-%,d",1,1,1,99999);
assertEquals(str1,"1- 1-001-99,999");
//十进制,十六进制和八进制
String str2 = String.format("%d %x %o",10,10,10);
assertEquals(str2,"10 a 12");
//xxx$可以指定输出的参数index
String str3 = String.format("%2$d %2$d %1$d",10,20);
assertEquals(str3,"20 20 10");
//浮点数[标识][最小宽度]转换方式,5是指总宽度,包括小数点,整数位,和小数位。
String str4 = String.format("%.2f-%5.2f-%05.2f",1.23,1.4444,1.2);
assertEquals(str4,"1.23- 1.44-01.20");
//字符串和字符
String str5 = String.format("%s,%c","--MM--",65);
assertEquals(str5,"--MM--,A");
}
}
10.2 字符串编码
package java_test;
import org.assertj.core.internal.Bytes;
import org.junit.jupiter.api.Test;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.*;
//https://www.freesion.com/article/58792528/
//java与javascript都是UTF-16的方式存储字符串
//golang是以UTF-8的方式存储字符串
public class CharArrayTest {
private Character[] getChar(String input){
List<Character> input1Array = new ArrayList<>();
for( int i = 0 ;i != input.length();i++){
input1Array.add(input.charAt(i));
}
return input1Array.toArray(new Character[input1Array.size()]);
}
@Test
public void testCharCodeAt() {
//https://www.zhangshilong.cn/work/278178.html
//char类型可以存储一个中文汉字。因为Java中char的编码方式为UTF-16BE。UTF-16编码使用2或者4字节,在65536以内的占两个字节。而基本上所有中文的Unicode编码在19968到40869之间——既Unicode至少包含了20902个汉字,所以一个char类型可以存储一个汉字。
String input1 = "ab";
Character[] input1Array = this.getChar(input1);
assertArrayEquals(
input1Array,
new Character[]{'a','b'});
assertEquals(input1Array.length,2);
String input2 = "a你好";
Character[] input2Array = this.getChar(input2);
assertArrayEquals(
input2Array,
new Character[]{'a','你','好'});
assertEquals(input2Array.length,3);
String input3 = "a\uD83D\uDE00c";
Character[] input3Array = this.getChar(input3);
System.out.println(input3.length());
assertArrayEquals(
input3Array,
new Character[]{'a',0xD83D,0xDE00,'c'});
assertEquals(input3Array.length,4);
}
@Test
public void testToCharArray() {
String input1 = "ab";
char[] input1Array = input1.toCharArray();
assertArrayEquals(
input1Array,
new char[]{'a','b'});
assertEquals(input1Array.length,2);
String input2 = "a你好";
char[] input2Array = input2.toCharArray();
assertArrayEquals(
input2Array,
new char[]{'a','你','好'});
assertEquals(input2Array.length,3);
String input3 = "a\uD83D\uDE00c";
char[] input3Array = input3.toCharArray();
assertArrayEquals(
input3Array,
new char[]{'a',0xD83D,0xDE00,'c'});
assertEquals(input3Array.length,4);
}
@Test
public void testGetBytes(){
String input1 = "ab";
byte[] input1Array = input1.getBytes();
assertArrayEquals(
input1Array,
new byte[]{'a','b'});
assertEquals(input1Array.length,2);
String input2 = "a你好";
byte[] input2Array = input2.getBytes();
assertArrayEquals(
input2Array,
new byte[]{'a',(byte)0xE4,(byte)0xBD,(byte)0xA0,(byte)0xE5,(byte)0xA5,(byte)0xBD});
assertEquals(input2Array.length,7);
String input3 = "a\uD83D\uDE00c";
byte[] input3Array = input3.getBytes();
assertArrayEquals(
input3Array,
new byte[]{'a',(byte)0xF0,(byte)0x9F,(byte)0x98,(byte)0x80,'c'});
assertEquals(input3Array.length,6);
}
@Test
public void testGetBytesUTF16()throws UnsupportedEncodingException {
//https://www.haomeili.net/Code/DetailCodes?wd=%E4%BD%A0%E5%A5%BD
//UTF-16LE,小端模式,无BOM头
String input1 = "ab";
byte[] input1Array = input1.getBytes("UTF-16LE");
assertArrayEquals(
input1Array,
new byte[]{'a',0,'b',0});
assertEquals(input1Array.length,4);
String input2 = "a你好";
byte[] input2Array = input2.getBytes("UTF-16LE");
assertArrayEquals(
input2Array,
new byte[]{'a',0,(byte)0x60,(byte)0x4f,(byte)0x7D,(byte)0x59});
assertEquals(input2Array.length,6);
String input3 = "a\uD83D\uDE00c";
byte[] input3Array = input3.getBytes("UTF-16LE");
assertArrayEquals(
input3Array,
new byte[]{'a',0,(byte)0x3D,(byte)0xD8,(byte)0x00,(byte)0xDE,'c',0});
assertEquals(input3Array.length,8);
}
@Test
public void testToCodePoints() {
String input1 = "ab";
int[] input1Array = input1.codePoints().boxed()
.mapToInt(Integer::valueOf)
.toArray();
assertArrayEquals(
input1Array,
new int[]{
'a',
'b'
});
assertEquals(input1Array.length,2);
String input2 = "a你好";
int[] input2Array = input2.codePoints().boxed()
.mapToInt(Integer::valueOf)
.toArray();
assertArrayEquals(
input2Array,
new int[]{'a','你','好'});
assertEquals(input2Array.length,3);
//Unicode (UTF-32 Big-Endian)
//https://www.haomeili.net/Code/DetailCodes?wd=%F0%9F%98%80
String input3 = "a\uD83D\uDE00c";
int[] input3Array = input3.codePoints().boxed()
.mapToInt(Integer::valueOf)
.toArray();
assertArrayEquals(
input3Array,
new int[]{'a',0x01F600,'c'});
assertEquals(input3Array.length,3);
}
}
要点:
- Java与Javascript的字符串都使用UTF-16LE作为内部编码,所以每个字符都是2个字节,总共可以表达65536字符。而UTF-16LE已经包含了20902个汉字,足够满足几乎所有场景情况下的中文。
- 使用length(), charAt(), toCharArry(),返回的直接就是内部UTF-16编码了
- 使用getBytes(),返回的是字节流编码,默认编码是UTF-8。我们也可以指定为UTF-16LE(无BOM头),或者UTF-16(有BOM头,大端),或者UTF-32等编码。要注意,无论哪个编码,返回的依然是字节流,需要人工切分字符。
- 使用codePoints(),返回的是int双字节流编码,使用的是UTF-32编码,这种编码能表达所有的Unicode字符,而且返回的是int双字节流,不需要人工切分字符,这种方式是对Unicode字符进行准确的length计算,准确的字符数量限制的计算。
参考工具:
10.3 字符串分割
package java_test;
import org.apache.logging.log4j.util.Strings;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class SplitTest {
@Test
public void basic() {
//以单个;分号为分割
String input1 = "a;c";
String[] input1Array = input1.split(";");
assertArrayEquals(
input1Array,
new String[]{"a","c"});
//以<>组合两个字符为分割
String input2 = "a<>c<>ccc<k>e";
String[] input2Array = input2.split("<>");
assertArrayEquals(
input2Array,
new String[]{"a","c","ccc<k>e"});
//以<或者>,任意一个字符为分割
String input3 = "a<>c<>ccc<k>e";
String[] input3Array = input3.split("<|>");
assertArrayEquals(
input3Array,
new String[]{"a","","c","","ccc","k","e"});
}
}
要点:
- String的split方法,传入的不是字符,也不是简单的string,而是正则表达式。所以,我们能轻松实现组合字符分割,或者多分隔符分割等场景。需要注意正则表达式的特殊字符需要用斜杠来转义。
11 系统属性
在Java中定义了一套与环境变量不同的系统属性的方法,它以标准的方式来将参数以命令行方式传递进程序里面,代码看这里
文档在这里
注意,系统属性的特点是:
- 不是环境变量,不依赖于具体的操作系统
- 是JVM的命令行参数,不是程序的命令行参数
11.1 基础
package java_standard_test;
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "my")
@Data
public class MyConfig {
private String name;
}
在SpringBoot中定义了属性
package java_standard_test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
import java.util.Enumeration;
import java.util.Properties;
/**
* Hello world!
*
*/
@SpringBootApplication
@Slf4j
public class App
{
@Autowired
private MyConfig myConfig;
public static void main( String[] args )
{
printProperties();
.run(App.class,args);
SpringApplication}
public static void printProperties(){
Properties properties = System.getProperties();
Enumeration<?> names = properties.propertyNames();
while( names.hasMoreElements() ){
String key = (String)names.nextElement();
String value = properties.getProperty(key);
.info("property: [{}]-> [{}]",key,value);
log}
}
@PostConstruct
public void init(){
.info("myConfig name {}",myConfig.getName());
log}
}
启动代码就是打印系统配置,以及检查SpringBoot是否能获取这个系统配置。
java "-Dmy.name=cc" "-Dlogdir=fish" -jar .\property-1.0-SNAPSHOT.jar
启动jar,注意系统属性是JVM参数,不是程序参数,不能放在-jar后面的
System.getProperty和SpringBoot都可以获取到这个参数了
12 本地IP
代码在这里
12.1 基础
package java_test;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
@Component
public class LocalIPService {
public void getByLocalHost(){
try {
InetAddress localhost = InetAddress.getLocalHost();
System.out.println("Local IP Address: " + localhost.getHostAddress());
} catch (UnknownHostException e) {
.printStackTrace();
e}
}
public void getByNetworkInterface(){
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (!inetAddress.isLoopbackAddress() && inetAddress.isSiteLocalAddress()) {
System.out.println("Local IP Address: " + networkInterface.getName()+" : "+inetAddress.getHostAddress());
}
}
}
} catch (Exception e) {
.printStackTrace();
e}
}
}
代码还是比较简单的
---------- getByLocalHost ----------
Local IP Address: 192.168.1.234
---------- getByNetworkInterface ----------
Local IP Address: eth1 : 192.168.154.1
Local IP Address: eth2 : 192.168.1.234
Local IP Address: eth4 : 192.168.159.1
以上为输出
20 总结
简单,记录下来是为了方便查找而已
参考资料:
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!