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中类型:

  1. 构造方法引用,语法为Class::new,调用的为无参构造器
  2. 静态方法引用,语法为Class::method
  3. 实例方法引用,语法为instance::method
  4. 对象方法引用,语法为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编码

  1. 基本编码,编码表为52个英文字母加上+,/两个字符,结果输出为一行
  2. url安全编码,编码表为52个英文字母加上-,_两个字符,结果输出为一行
  3. 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为例,来分析一下是如何编码的

  1. 转成字节数组 [97]
  2. 将每一个字节转成二进制表示,前面补足8位,01100001
  3. 按每6位分组,最后一组后面补足6位,011000 010000
  4. 每一组转成int类型的十进制表示 24 16
  5. 根据每一个索引查找编码表,转换 YQ
  6. 第3步补了4个0,表示最后要填充4/2个=号,YQ==就是最终结果

解码过程就是反向的过程。

posted @ 2020-07-26 13:10  strongmore  阅读(358)  评论(0编辑  收藏  举报