java8新特性简单介绍
lambda表达式
public class Client {
public static void main(String[] args) {
Arrays.asList("hello", "world").forEach(a -> {
System.out.println(a);
System.out.println(a.length());
});
}
}
lambda可以看做java的函数式编程,主要有3部分组成,参数列表、符号->、函数体,java本身提供来了一些函数式接口,Function,Consumer,Predicate,Supplier等,我们也可以定义自己的函数式接口。
接口默认方法和静态方法
public class Client {
public static void main(String[] args) {
MyList myList = new MyArrayList();
myList.addAll("hello", "world");
System.out.println(myList);
MyList newList = MyList.of("java", "python");
System.out.println(newList);
}
// 定义一个接口包含默认方法和静态方法
interface MyList {
void add(Object obj);
// 默认方法
default void addAll(Object... objs) {
for (Object obj : objs) {
add(obj);
}
}
// 静态方法
static MyList of(Object... objs) {
MyList myList = new MyArrayList();
myList.addAll(objs);
return myList;
}
}
// 接口实现类
static class MyArrayList implements MyList {
private List<Object> data = new ArrayList<>();
@Override
public void add(Object obj) {
data.add(obj);
}
@Override
public String toString() {
return data.toString();
}
}
}
默认方法类似于抽象类中的非抽象方法,默认被所有实现类继承,java中的List接口就定义了很多默认方法,如stream方法,静态方法和类中的静态方法类似。
方法引用
public class Client {
public static void main(String[] args) {
testConstructorReference();
testStaticMethodReference();
testInstanceMethodReference();
testObjectMethodReference();
}
// 构造方法引用
private static void testConstructorReference() {
Supplier<Date> supplier = Date::new;
Date date = supplier.get();
System.out.println(date);
}
// 静态方法引用
private static void testStaticMethodReference() {
List<Integer> list = Arrays.asList(82, 22, 34, 50, 9);
list.sort(Integer::compare);
System.out.println(list);
}
// 实例方法引用
private static void testInstanceMethodReference() {
List<Integer> list = Arrays.asList(82, 22, 34, 50, 9);
Supplier<Integer> supplier = list::size;
System.out.println(supplier.get());
}
//对象方法引用
private static void testObjectMethodReference() {
BiFunction<List, String, Boolean> function = List::contains;
Boolean result = function.apply(Arrays.asList("a", "b", "c"), "a");
System.out.println(result);
}
}
方法引用一般和lambda表达式一起使用,共有4中类型:
- 构造方法引用,语法为Class::new,调用的为无参构造器
- 静态方法引用,语法为Class::method
- 实例方法引用,语法为instance::method
- 对象方法引用,语法为Class::method
重复注解
public class Client {
public static void main(String[] args) {
Logs logs = User.class.getAnnotation(Logs.class);
for (Log log : logs.value()) {
System.out.println(log.value());
}
}
// 使用重复注解
@Log("hello")
@Log("world")
static class User {
}
// 定义一个注解 Repeatable注解标识重复注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Logs.class)
@interface Log {
String value();
}
// 定义一个注解容器,存放重复注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Logs {
Log[] value();
}
}
java8新增了一个Repeatable注解来支持重复注解,如果Log注解没有重复使用,只使用了一次,那么Class中只能得到Log注解信息,如果使用多次,那么只能得到Logs注解信息,这是编译器帮我们做的。
Optional
ublic class Client {
public static void main(String[] args) {
findUserById(1).ifPresent(System.out::println);
findUserById(2).ifPresent(System.out::println);
}
private static Optional<User> findUserById(int id) {
if (id == 1) {
return Optional.of(new User("lisi"));
} else {
return Optional.empty();
}
}
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
static class User {
private String userName;
}
}
Optional可以看做一个单容量的容器,可以用来防止空指针异常。
StreamApi
public class Client {
public static void main(String[] args) {
// 创建一个流
String lan = Stream.of("java", "javascript", "python", "go", "kotlin")
// 过滤
.filter(language -> language.length() > 3)
// 排序
.sorted(Comparator.comparingInt(String::length))
// 收集
.collect(Collectors.joining("-"));
System.out.println(lan);
}
}
streamApi可以看做一连串支持映射、过滤、排序、聚集等操作的集合,通过它我们可以很方便的操作集合,数组等容器。
Date/Time Api
public class Client {
public static void main(String[] args) {
// 不带时区的时间点
Instant instant = Instant.ofEpochMilli(System.currentTimeMillis());
instant = instant.plus(2, ChronoUnit.DAYS);
// 格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 带时区的日期加时间
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
System.out.println(localDateTime);
System.out.println(dateTimeFormatter.format(localDateTime));
}
}
java8新增了一些关于日期时间的API,来替代之前的Date,Calendar等,新的API都是线程安全的,在易用性上有很大提高。
Base64
public class Client {
private static String BLOWFISH = "Blowfish";
private static String SECRET = "test";
public static void main(String[] args) throws Exception {
// 待处理字符串 数据多才可以看出3种编码的区别
String source = "abcdefghijklmnopqrstuvwxyz -+*/_= 1234567890 abcdefghijklmnopqrstuvwxyz";
byte[] encrypt = encrypt(source);
// 基本编码
System.out.println(Base64.getEncoder().encodeToString(encrypt));
System.out.println("===============");
// url安全的编码
System.out.println(Base64.getUrlEncoder().encodeToString(encrypt));
System.out.println("===============");
// 支持MIME类型的编码
System.out.println(Base64.getMimeEncoder().encodeToString(encrypt));
}
// 对数据使用Blowfish算法进行加密
private static byte[] encrypt(String data) throws Exception {
Cipher cipher = Cipher.getInstance(BLOWFISH);
SecretKeySpec myskeys = new SecretKeySpec(SECRET.getBytes(), BLOWFISH);
cipher.init(Cipher.ENCRYPT_MODE, myskeys);
return cipher.doFinal(data.getBytes());
}
}
输出结果为
FJV8zIvqmM4xP1GwLJoRATXWE2tCzicKEp8MQtIZE5RalzKc+O4DyccpbGhanbsM+tQ3TG+TIYCMipoUGRcShP09Od+f/h1GBPyLq7fDo6Q=
===============
FJV8zIvqmM4xP1GwLJoRATXWE2tCzicKEp8MQtIZE5RalzKc-O4DyccpbGhanbsM-tQ3TG-TIYCMipoUGRcShP09Od-f_h1GBPyLq7fDo6Q=
===============
FJV8zIvqmM4xP1GwLJoRATXWE2tCzicKEp8MQtIZE5RalzKc+O4DyccpbGhanbsM+tQ3TG+TIYCM
ipoUGRcShP09Od+f/h1GBPyLq7fDo6Q=
java共提供了3中Base64编码
- 基本编码,编码表为52个英文字母加上+,/两个字符,结果输出为一行
- url安全编码,编码表为52个英文字母加上-,_两个字符,结果输出为一行
- MIME类型编码,编码表为52个英文字母加上+,/两个字符,结果输出为多行
Base64原理及实现
自己实现如下
public class Base64 {
private Base64() {
}
private static final String toBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
public static String encode(byte[] src) {
StringBuilder res = new StringBuilder();
//得到二进制字符串
String binaryString = toEncodeBinary(src);
//按长度6分组,最后一组补足6 以01100001为例 011000 010000
int length = binaryString.length();
int pageSize = 6;
int lack = 0;
if (length % pageSize != 0) {
lack = pageSize - length % pageSize;
}
binaryString += "0".repeat(lack);
length = binaryString.length();
int totalPage = length / pageSize;
for (int i = 0; i < totalPage; i++) {
int startPos = i * pageSize;
//将每一组的二进制字符串转为int型数字
int index = Integer.parseInt(binaryString.substring(startPos, startPos + pageSize), 2);
res.append(toBase64.charAt(index));
}
//最后一组缺少的个数用=填充
res.append("=".repeat(lack / 2));
return res.toString();
}
public static byte[] decode(String src) {
String buffer = toDecodeBinary(src);
//按长度8分组
int pageSize = 8;
int totalPage = buffer.length() / pageSize;
byte[] res = new byte[totalPage];
for (int i = 0; i < totalPage; i++) {
int startPos = i * pageSize;
res[i] = (byte) Integer.parseInt(buffer.substring(startPos, startPos + pageSize), 2);
}
return res;
}
private static String toDecodeBinary(String src) {
int paddingLen = 0;
//去除=填充的字符串
for (int i = src.length() - 1; i >= 0; i--) {
if (src.charAt(i) != '=') {
break;
}
paddingLen++;
}
StringBuilder buffer = new StringBuilder();
src = src.substring(0, src.length() - paddingLen);
for (int i = 0; i < src.length(); i++) {
String sub = Integer.toBinaryString(toBase64.indexOf(src.charAt(i)));
//补足6位
buffer.append("0".repeat(6 - sub.length()));
buffer.append(sub);
}
//删除填充的0,个数为=的2倍
buffer.delete(buffer.length() - paddingLen * 2, buffer.length());
return buffer.toString();
}
/**
* 将字节数组的二进制表示转换成字符串 以[97]为例 结果为01100001
*/
private static String toEncodeBinary(byte[] src) {
StringBuilder buffer = new StringBuilder();
for (byte b : src) {
//转成二进制
String binary = Integer.toBinaryString(b);
//补足8位
buffer.append("0".repeat(8 - binary.length()));
buffer.append(binary);
}
return buffer.toString();
}
public static void main(String[] args) {
System.out
.println(new String(Base64.decode("YXNkZmdoamtscXdlcnR5dWlvcDEyMzQ1Njc4OTB7OicvLix9")));
System.out.println(new String(
java.util.Base64.getDecoder().decode("YXNkZmdoamtscXdlcnR5dWlvcDEyMzQ1Njc4OTB7OicvLix9")));
System.out.println(Base64.encode("asdfghjklqwertyuiop1234567890{:'/.,}".getBytes()));
System.out.println(java.util.Base64.getEncoder()
.encodeToString("asdfghjklqwertyuiop1234567890{:'/.,}".getBytes()));
}
}
输出为
asdfghjklqwertyuiop1234567890{:'/.,}
asdfghjklqwertyuiop1234567890{:'/.,}
YXNkZmdoamtscXdlcnR5dWlvcDEyMzQ1Njc4OTB7OicvLix9
YXNkZmdoamtscXdlcnR5dWlvcDEyMzQ1Njc4OTB7OicvLix9
可以看到和java内置的Base64的结果一致,以字符串a为例,来分析一下是如何编码的
- 转成字节数组 [97]
- 将每一个字节转成二进制表示,前面补足8位,01100001
- 按每6位分组,最后一组后面补足6位,011000 010000
- 每一组转成int类型的十进制表示 24 16
- 根据每一个索引查找编码表,转换 YQ
- 第3步补了4个0,表示最后要填充4/2个=号,YQ==就是最终结果
解码过程就是反向的过程。