1、对HashMap集合的数据结构优化
原来的HashMap采用的数据结构是哈希表(数组+链表),HashMap默认大小是16,一个下标0-15的数组,往里面存储元素时,首先调用元素的hashcode()方法,计算出哈希值,经过哈希算法找到对应的数组的索引,如果对应索引没有存放元素则直接存放,如果已经有元素了,(发生碰撞,equals方法就是碰撞时才会执行的方法)–那么调用它们的equals方法比较内容(key),如果内容一样,则后面的value值代替前者的value值;如果内容不同,则后加的放在前面,(挂在下面),形成一个链表。
这种情况下,如果链表无限下去,效率极低,碰撞避免不了
1.8之后,在数组+链表+红黑树来实现hashMap,当碰撞的元素个数大于8时或总容量大于64,会有红黑树的引入。
2、Lambda表达式
- lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码
- 最直接的优点:简洁代码
- 只有函数式接口的匿名内部类 才可以使用Lambda表达式来进行简化
函数式接口不同于普通接口,接口中只有一个抽象方法是需要我们去实现的,Lambda表达式正好是针对这个唯一的抽象方法使用
public class TreeSetTest {
public static void main(String[] args) {
//创建实现Comparator接口的匿名类对象,对象方法的参数和返回值类型均为Integer类型
Comparator<Integer> cpt1 = new Comparator<Integer>() {
//接口里有唯一方法,可以用匿名内部类来创建实现接口的类
@Override
public int compare(Integer o1, Integer o2) {
//Integer中封装了比较大小的方法compare
return Integer.compare(o1, o2);
}
};
/**使用lambda表达式
*创建实现Comparator接口的类的对象,重写接口的方法
*(x,y)实现方法参数,Integer.compare(x,y)方法实现
* 只需要写参数名和方法实现。因为方法名唯一已经知道了
*/
Comparator<Integer> cpt2 = (x, y) -> Integer.compare(x, y);
//每插入一条数据,就用比较器来比较一次
//TreeSet内部元素有序、不可重复
TreeSet<Integer> set = new TreeSet<>(cpt1);
set.add(1);
set.add(3);
set.add(2);
set.add(6);
System.out.println(set);
System.out.println("=========================");
}
}
- 可以自定义一个比较器,通过重写比较器的方法,来自定义比较的字段,如,自定义一个来比较学生的成绩的比较器
//自己重写Comparator中的compare方法,变成比较学生成绩的比较器
Comparator<Student>cs = (s1,s2)-> ((s1.grade < s2.grade) ? -1 : (s1.grade == s2.grade) ? 0 : 1);
TreeSet<Student>treeSet = new TreeSet<>((Comparator) cs);
treeSet.add(new Student(78));
treeSet.add(new Student(68));
treeSet.add(new Student(58));
treeSet.add(new Student(88));
for (Student student:treeSet
) {
System.out.println(student);
}
3、Default关键字
- 在JDK1.8之前,default一直是switch用于条件分值中默认项的一个关键字。但是在JDK1.8之后,它被赋予了新的功能,它可以被用在接口中,定义非抽象方法。
- JDK1.8之后,接口中声明的方法就并不一定是抽象方法了 ,现在经过default修饰的方法可以有了方法体了。
interface TestInterface {
//此处默认会是public static final修饰的,如果你修改,会有错误提示
String test= "test";
static void test1(){//此处有方法体
System.out.println("我是interface中的static修饰的方法体");
}
default void test2(){ //此处有方法体
System.out.println("我是interface中的default修饰的方法体");
}
}
public class TestInterface8 implements TestInterface{//一个类实现接口,相当于继承这个方法里的default方法
public static void main(String[] args)
{
//接口中的方法不一定都是abstract
TestInterface8 a=new TestInterface8();
//继承接口中的default方法test2
a.test2();
//接口里的static方法可以直接类名.方法来调用
TestInterface.test1();
}
}
-
如上代码,jdk1.8之后,接口中的方法不再都是抽象方法,接口中的方法可以通过 static或者defalut修饰,并且可以拥有方法体。通过static修饰的方法,可以有方法体,实现接口的类,可以直接通过接口类.方法来调用 ;通default修饰的方法,可以有方法体,实现接口的类,可通过创建接口类的对象,通过对象调用default方法
-
这是JDK8中为了加强接口的能力,使得接口可以存在具体的方法,前提是方法需要被default或static 关键字所修饰。这样做的好处是接口的每个实现类如果都想要相同的功能,就不需要重复代码,而是在接口进行定义即可 。默认方法在子类也可以被重写。
-
interface 的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface 修改的时候,实现它的类也必须跟着改。于是1.8之后,interface中修改方法,可以加static或default修饰,这样子类不用一定要实现,并且可以直接调用
4、Stream
- Stream是JDK 8 中最优秀的设计,集合的操作中提供了更加便捷的方式,这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。
- Stream是对 Java 集合运算和表达的高阶抽象。将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
- 使用场景:适用于集合操作超过两个步骤 ;任务较重,注重效能,希望并发执行;函数式编程的编码等场景中。Stream 存在于 java.util.stream 包中,
Stream的创建
//通过集合创建
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
//通过数组创建
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
//通过Stream的静态方法: of(),iterate(),generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
遍历/匹配
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
// 遍历输出符合条件的元素。 遍历输出x>6的元素
list.stream().filter(x -> x > 6).forEach(System.out::println);
// 匹配第一个
Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
// 是否包含符合特定条件的元素
boolean anyMatch = list.stream().anyMatch(x -> x > 6);
筛选filter
List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
//筛选大于6的元素并打印出来
list.stream()
.filter(x->x>6)
.forEach(System.out::println);
5、函数式接口
- 函数式接口是仅且只能包含一个抽象的方法 的接口,每一个该类型的lambda的表达式都会匹配到这个抽象方法上,jdk1.8提供**@FunctionalInterface**注解来定义函数式接口 ,如:
/**
* 函数式接口:只包含一个抽象方法的接口,称为函数式接口
* 通过 Lambda 表达式来创建该接口的对象
* 可以通过@FunctionalInterface注解检查它是否是一个函数式接口,也可以省略
*/
@FunctionalInterface
interface Haha {
public int test(int a, int b);
}
public class FunInter {
public static void main(String[] args) {
/**
1.调用Haha接口的test方法
2.该方法用lambda表达式实现test方法
3.在lambda表达式中(a,b)表示方法的参数,a+b为方法的实现
*/
Haha haha = (a, b) -> a + b;
System.out.println(haha.test(2, 5));
//System.out.println(((Haha)(a, b) -> a + b).test(2, 5));
}
}
6、方法与构造方法引用
- jdk1.8提供一种方式,通过::关键字来传递方法或者引用,方法引用分为三种:
- 静态方法引用,通过类名::静态方法名,如 Integer :: parseInt
- 实例方法引用,通过实例对象::实例方法,如 str::substring
- 构造方法引用,通过类名::new ,如User::new
//Integer类
public final class Integer {
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
}
//User类
public class User {
private String username;
private Integer age;
public User() {
}
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
// Getter&Setter
}
/*
三种::引用方式
*/
public static void main(String[] args) {
// 使用双冒号::来构造静态函数引用 。创建实现Function接口的对象
//通过类名::静态方法名
Function<String, Integer> fun = Integer::parseInt;
Integer value = fun.apply("123");
System.out.println(value);
// 使用双冒号::来构造非静态函数引用
//通过 实例对象名::实例方法名 创建实现Function接口的对象
String content = "Hello JDK8";
Function<Integer, String> func = content::substring;
String result = func.apply(1);
System.out.println(result);
// 构造函数引用
//通过 类名::new 创建实现 BiFunction接口的对象
BiFunction<String, Integer, User> biFunction = User::new;
User user = biFunction.apply("mengday", 28);
System.out.println(user.toString());
// 函数引用也是一种函数式接口,所以也可以将函数引用作为方法的参数
sayHello(String::toUpperCase, "hello");
}
// 方法有两个参数,一个是
private static void sayHello(Function<String, String> func, String parameter){
String result = func.apply(parameter);
System.out.println(result);
}
- 目前为止,有三种创建函数式接口对象的方式,匿名内部类创建,lambda表达式创建,方法的引用
package day01;
class Person {
String firstName;
String lastName;
Person() {
System.out.println("Person()被调用");
}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
System.out.println("Person(String firstName, String lastName)被调用");
System.out.println("firstName =" + firstName);
System.out.println("lastName =" + lastName);
}
}
/**
* PersonFactory接口的方法形参类型为String类型,返回值为Person类型
*/
interface PersonFactory<String, Person> {
public Person create(String firstName, String lastName);
}
public class PerFa {
public static void main(String[] args) {
System.out.println("使用匿名类创建接口的对象");
PersonFactory<String, Person> personFactory1 = new PersonFactory<String, Person>() {
@Override
public Person create(String firstName, String lastName) {
return new Person(firstName, lastName);
}
};
Person person1 = personFactory1.create("li", "sa");
System.out.println(person1);
System.out.println("*****************************");
System.out.println("使用lambda表达式创建接口的对象");
PersonFactory<String, Person> personFactory2 =
(firstName, lastName) -> new Person(firstName, lastName);
Person person2 = personFactory2.create("zhang", "san");
System.out.println(person2);
System.out.println("*****************************");
/**
使用构造方法引用创建接口的对象
使用 Person::new 来获取Person类构造函数的引用,(new Person)
Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数
*/
System.out.println("使用构造方法引用创建接口的对象");
//根据接口的方法的参数类型来自动选择合适的构造函数
PersonFactory<String, Person> personFactory3 = Person::new;
Person person3 = personFactory3.create("Peter", "Parker");
System.out.println(person3);
}
}
7、Date-Time API
- 这是对java.util.Date强有力的补充,解决了Date类的大部分痛点
非线程安全
时区处理麻烦
各种格式化、和时间计算繁琐
设计有缺陷,Date类同时包含日期和时间;还有一个java.sql.Date,容易混淆
- java.util.Date 既包含日期又包含时间,而java.time把它们进行了分离。java.time主要类
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //时间 format: HH:mm:ss
- 1.8之后的格式化
public void newFormat(){
//format yyyy-MM-dd 日期
LocalDate date = LocalDate.now();
System.out.println(String.format("date format : %s", date));
//format HH:mm:ss 时间
LocalTime time = LocalTime.now().withNano(0);
System.out.println(String.format("time format : %s", time));
//format yyyy-MM-dd HH:mm:ss 时间+日期
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateTimeStr = dateTime.format(dateTimeFormatter);
System.out.println(String.format("dateTime format : %s", dateTimeStr));
}
- 1.8之后的字符串转日期格式
LocalDate date = LocalDate.of(2021, 1, 26);
LocalDate.parse("2021-01-26");
LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);
LocalDateTime.parse("2021-01-26 12:12:22");
LocalTime time = LocalTime.of(12, 12, 22);
LocalTime.parse("12:12:22");
- 1.8之后获取指定日期
public void getDayNew() {
LocalDate today = LocalDate.now();
//获取当前月第一天:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
// 取本月最后一天
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
//取下一天:
LocalDate nextDay = lastDayOfThisMonth.plusDays(1);
//当年最后一天
LocalDate lastday = today.with(TemporalAdjusters.lastDayOfYear());
//2021年最后一个周日
LocalDate lastMondayOf2021 = LocalDate.parse("2021-12-31").with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
}
- JDBC和java8
Date----> LocalDate
Time----> LocalTime
Timestamp---->LocalDateTime