Java8新特性

1.接口的默认方法

这一特性允许我们在不破坏现有实现的情况下,向接口中添加新的方法。在Java8之前,一旦接口被定义,任何新的方法添加都会导致实现该接口的类必须实现这些方法,这可能会导致大量的代码需要被修改,特别是当接口被广泛使用时。

在接口中,你可以使用default关键字来定义一个默认方法。这意味着实现该接口的类如果没有显式地实现这个方法,那么就会继承接口的默认实现。

public interface MyInterface {  
    // 接口中的抽象方法  
    void myMethod();  
  
    // 接口中的默认方法  
    default void defaultMethod() {  
        System.out.println("这是接口的默认方法实现");  
    }  
}

当类实现上述接口时,它可以选择性地覆盖默认方法,或者简单地继承接口的默认实现。

public class MyClass implements MyInterface {  
    // 必须实现接口中的抽象方法  
    @Override  
    public void myMethod() {  
        System.out.println("实现了接口中的抽象方法");  
    }  
  
    // 可以选择性地覆盖默认方法  
    @Override  
    public void defaultMethod() {  
        System.out.println("覆盖了接口的默认方法实现");  
    }  
}  
  
// 或者,如果不覆盖默认方法,则自动继承接口的默认实现  
public class AnotherClass implements MyInterface {  
    @Override  
    public void myMethod() {  
        System.out.println("实现了接口中的另一个抽象方法");  
    }  
    // 不覆盖defaultMethod,则使用MyInterface中的默认实现  
}

优势;

  • 向后兼容性:可以在不破坏现有代码的情况下,向接口中添加新的方法。
  • 多继承的类似行为:在Java中,类不支持多继承,但默认方法提供了一种类似于多继承中方法解析的机制。如果一个类实现了多个接口,而这些接口中定义了相同的默认方法,则实现类必须覆盖这个方法,否则会导致编译错误。
  • 灵活性:允许在库或框架的后续版本中添加新的功能,而不需要修改实现这些接口的现有代码。

注意事项:

  • 默认方法不能是静态的,因为静态方法不能被重写。
  • 如果一个类实现了多个接口,而这些接口中定义了相同的默认方法,则该类必须覆盖这个方法,否则会导致编译错误。
  • 默认方法可以通过super关键字来调用接口中的另一个默认方法或抽象方法(如果有默认方法实现了抽象方法)。

2.方法和构造函数的引用

Java 8 引入了方法引用(Method References)和构造函数引用(Constructor References)作为Lambda表达式的一种更简洁、更易于阅读的写法。这些方法引用和构造函数引用使得你可以直接引用已存在的方法或构造函数,而不是去编写完整的Lambda表达式。

方法引用主要有四种形式:

  • 静态方法引用:使用类名来引用静态方法。
    Consumer<String> printString = System.out::println;  
    printString.accept("Hello, World!");
    
  • 特定对象的实例方法引用:使用特定对象来引用实例方法。
    String s = "Hello";  
    Consumer<String> greeting = s::concat;  
    System.out.println(greeting.accept(" World!")); // 输出 Hello World!
    
    注意:这里的greeting.accept(" World!")实际上是不正确的用法,因为concat方法需要一个参数并返回结果,而不是消费(Consumer)类型的操作。正确的方法是使用Function<String, String>而不是Consumer。
  • 特定类型的任意对象的实例方法引用:使用类名来引用任意对象的实例方法,方法的第一个参数是该对象本身。
    List<String> list = Arrays.asList("apple", "banana", "cherry");  
    list.forEach(String::toUpperCase);
    
  • 数组的构造器引用:类似于类型的实例方法引用,但用于数组。
    Function<Integer, String[]> func = String[]::new;  
    String[] array = func.apply(5); // 创建一个长度为5的String数组
    

构造函数引用用于引用构造函数,主要有两种形式:

  • 无参构造函数的引用:使用ClassName::new来引用无参构造函数。
    Supplier<List<String>> listSupplier = ArrayList::new;  
    List<String> list = listSupplier.get();
    
  • 有参构造函数的引用:需要使用Function接口(或它的子接口,如BiFunction、TriFunction等,但Java标准库中并没有这些接口,它们可能来自第三方库)的变种来适配参数。然而,Java标准库中没有直接支持有参构造函数引用的简单方式,但你可以通过Lambda表达式或自定义函数式接口来实现。
    • 不过,对于某些特定情况,如流(Stream)的map操作后接收集操作(如collect),可以使用Collectors.toCollection与ArrayList::new这样的无参构造函数引用来收集元素到ArrayList中,但这并不直接引用有参构造函数。
    • 对于直接引用有参构造函数的需求,你可能需要定义自己的函数式接口或使用现有的Lambda表达式来间接实现。

3.Lambda表达式

  • 介绍:

    • 在Java老版本中实现排列字符串

      		List<String> names = new ArrayList<>();
              names.add("abg");
              names.add("csv");
              names.add("asv");
              names.add("aaa");
      
              Collections.sort(names, new Comparator<String>() {
                  @Override
                  public int compare(String o1, String o2) {
                      return o2.compareTo(o1);
                  }
              });
      
    • 通过Lambda表达式可变为:

      		ollections.sort(names, (String o1, String o2) -> {
                      return o2.compareTo(o1);
              });
      
    • 由于只有一条语句可改为:

      		Collections.sort(names, (o1, o2) -> o2.compareTo(o1));
      
  • 方法局部变量:

    		// 可直接在lambda表达式中访问外部的局部变量
            final int num = 10;
            List<String> names = new ArrayList<>();
            names.add("abg");
            names.add("csv");
            names.add("asv");
            names.add("aaa");
    
            names.forEach(item -> {
                System.out.println(num);
            });
             // 和匿名对象不同的是,这里的变量num可以不用声明为final,该代码仍然正确
            int num = 10;
            List<String> names = new ArrayList<>();
            names.add("abg");
            names.add("csv");
            names.add("asv");
            names.add("aaa");
    
            names.forEach(item -> {
                System.out.println(num);
            });
            // 这里的num不可被后面的代码修改(隐形具有final的语义),下面代码无法被编译
            int num = 10;
            List<String> names = new ArrayList<>();
            names.add("abg");
            names.add("csv");
            names.add("asv");
            names.add("aaa");
    
            names.forEach(item -> {
                System.out.println(num);
            });
            num = 20;
    
  • 访问字段和静态变量

    • 在lambda表达式中对实例字段和静态变量都有读写访问权限,该行为和匿名对象是一致的
  • 访问默认接口方法

    • 无法从lambda表达式中访问默认方法

4.函数式接口

Java语言设计者们投入了大星精力来思考如何使现有的函数友好地支持Lambda,最终采取的方法是:增加函数式接口的概念。

  • 函数式接口:是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口

  • java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子

  • Java8增加了一种特殊的注解@FunctionalInterface,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口

    	@FunctionalInterface
    	public interface Converter<F, T> {
    	    T convert(F from);
    	}
    	
    	Converter<String, Integer> converter = Integer::valueOf;
        Integer num = converter.convert("100");
        System.out.println(num);// 100
    

4.1 Predicate

Predicate接口是只有一个参数的返回布尔类型值的断言型接口,该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑,如与、或、非

@FunctionalInterface
public interface Predicate<T> {
    /**
     * 该方法是接受—个传入类型,返回—个布尔值.此方法应用于判断.
     * @param t 参数
     * @return 是否正确
     */
    boolean test (T t);

    /**
     * and方法与关系型运算符"&&"相似,两边都成立才返回true
     * @param operator 参数
     * @return 本身对象
     */
    default Predicate<T> and(Predicate<? super T> operator) {
        Objects.requireNonNull(operator);
        return (t) -> test(t) && operator.test(t);
    }

    /**
     * 与关系运算符 "|" 相似, 对判断进行取反
     * @return 本身对象
     */
    default Predicate<T> nagate(){
        return (t) -> !test(t);
    }
    /**
     * or方法与关系型运算符”//"相似,两边只要有—个成立就返回true
     * @param operator 参数
     * @return 本身对象
     */
    default Predicate<T> or(Predicate<? super T> operator) {
        Objects.requireNonNull(operator);
        return (t) -> test(t) || operator.test(t);
    }

    /**
     * 该方法接收一个0bject对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal)
     * @param targetRef 目标参数
     * @return 本身对象
     * @param <T> 参数类型
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object);
    }
}

4.2 Function

Function接口接受一个参数并生成结果,默认方法可用于将多个函数链接在一起

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before){
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity(){
        return t -> t;
    }
}

4.3 Supplier

Supplier接口产生给定泛型类型的结果,与Function接口不同,Supplier接口不接受参数

public class TestLambda {
    public static void main(String[] args) {
        Supplier<TestLambda> supplier = TestLambda::new;
        supplier.get();  // new TestLambda()
    }
}

4.4 Consumer

Consumer接口表示要对单个输入参数执行的操作

public class TestLambda {
    public static void main(String[] args) {
        Consumer<TestLambda> consumer = (p) -> System.out.println("Hello, " + p);
        consumer.accept(new TestLambda());
    }
}

4.5 Comparator(比较)

Comparator是老Java中的经典接口,Java8在此之上添加了多种默认方法

5.Optional

Optional不是函数式接口,而是用于防止NullPointerException的漂亮工具,Optional是一个简单的容器,其值可能是null或非null,在Java8之前一般某个函数应该返回非空对象,但有时却什么也没有返回,在Java8中应该返回Optional而不是null

public class TestLambda {
    public static void main(String[] args) {
        // of():为非nuLL的值创建—个optional
        Optional<String> optional = Optional.of("abc");
        // isPresent()∶如果值存在返回true,否则返回faLse
        optional.isPresent();
        // get():如果optional有值则将其返回,否则抛出NoSuchELementException
        optional.get();
        // orElse():如果有值则将其返回,否则返回指定的其它值
        optional.orElse("fall");
        // ifPresent( ):如果Optional实例有值则为其调用consumer,否则不做处理
        optional.ifPresent(s -> System.out.println(s.charAt(0)));
    }
}

6.Streams(流)

java.util.Stream表示能应用在一组元素上一次执行的操作序列。Stream操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样就可以将多个操作依次串起来。Stream的创建需要指定一个数据源,比如java.util.Collection的子类,List或者Set,Map不支持。Stream的操作可以串行或者并行执行

6.1 Filter(过滤)

过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,我们可以在过滤后的结果来应用其他Stream操作(如forEach)。forEach需要一个函数来对过滤后的元素依次执行,forEach是一个最终操作,我们不能在forEach之后来执行其他Stream操作

list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);

6.2 Sorted(排序)

排序是一个中间操作,返回的是排序好的Stream,若不指定一个自定义的Comparator,则会使用默认排序

list.stream().sorted().filter(s -> s.startsWith("a")).forEach(System.out::println);

排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后的源数据stringList是不会被修改的

6.3 Map(映射)

中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象

list.stream().map(String::toUpperCase).sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);

6.4 Mathch(匹配)

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream,所有的匹配操作都是最终操作,并返回一个boolean类型的值

boolean result1 = list.stream().anyMatch(s -> s.startsWith("a"));// true
boolean result2 = list.stream().allMatch(s -> s.startsWith("a"));// false
boolean result3 = list.stream().noneMatch(s -> s.startsWith("z"));// true

6.5 Count(计数)

计数是一个最终操作,返回Stream中元素的个数,返回值类型是long

System.out.println(list.stream().filter(s -> s.startsWith("b")).count());

6.6 Reduce(规约)

规约是一个最终操作,允许通过指定的函数来将stream中的多个元素规约为一个元素,规约后的结果是通过Optional接口表示的

Optional<String> reduce = list.stream().sorted().reduce((s1, s2) -> s1 + "#" + s2);
        reduce.ifPresent(System.out::println);

字符串拼接、数值的sum、min、max、average都是特殊的reduce

7.Paraller Stream(并行流)

Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时给执行
创建一个没有重复元素的的大表,分别进行串行、并排序比较所用时间

		int max = 1000000;
        List<String> values = new ArrayList<>(max);
        for (int i = 0; i < max; i++) {
            UUID uuid = UUID.randomUUID();
            values.add(uuid.toString());
        }

7.1 串行排序

		long t0 = System.nanoTime();
        long count = values.stream().sorted().count();
        long t1 = System.nanoTime();
        long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
        System.out.println(String.format("sequential sort took: %d ms", millis));

7.2 并行排序

		long t0 = System.nanoTime();
        long count = values.parallelStream().sorted().count();
        long t1 = System.nanoTime();
        long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
        System.out.println(String.format("sequential sort took: %d ms", millis));

8.Maps

Map类型不支持streams,另外提供了一些新的有用的方法来处理一些日常任务,可以在键、值上创建专门的流或者通过map.keySet( ) .stream(), map.values().stream()和map.entrySet( ).stream()

Maps支持各种新的和有用的方法来执行常规任务

		Map<Integer, String> map = new HashMap<>();
        for (int i = 0; i < 10; i++) {
            map.putIfAbsent(i, "value" + i);
        }
        map.forEach((id, value) -> System.out.println(value));

putIfAbsent阻止我们在nuill 检查时写入额外的代码; forEach)接受一个consumer 来对 map中的每个元素操作

9.Date API

9.1 Clock

Clock类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代System.currentTimeMillis()来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建旧版本的java.util.Date对象

        Clock clock = Clock.systemDefaultZone();
        long millis = clock.millis();
        System.out.println(millis);// 1721788129236
        Instant instant = clock.instant();
        System.out.println(instant);// 2024-07-24T02:28:49.236Z
        Date from = Date.from(instant);
        System.out.println(from);// Wed Jul 24 10:28:49 CST 2024

9.2 Timezones(时区)

在新 API中时区使用Zoneld 来表示。时区可以很方便的使用静态方法 of 来获取到。抽象类ZoneId (在java.time包中)表示一个区域标识符。它有一个名为getAvailableZoneIds 的静态方法,它返回所有区域标识符

		System.out.println(ZoneId.getAvailableZoneIds());
        ZoneId of1 = ZoneId.of("Europe/Berlin");
        ZoneId of2 = ZoneId.of("Brazil/East");
        System.out.println(of1.getRules());
        System.out.println(of2.getRules());

9.3 LocalTime(本地时间)

LocalTime定义了一个没有时区信息的时间,例如晚上10点或者17:30:15
示例:使用前面代码创建的时区创建两个本地时间,之后比较实际并以小时和分钟为单位计算两个时间的时间差

        LocalTime now1 = LocalTime.now(of1);
        LocalTime now2 = LocalTime.now(of2);
        System.out.println(now1.isBefore(now2));

        long between = ChronoUnit.HOURS.between(now1, now2);
        long minutes = ChronoUnit.MINUTES.between(now1, now2);

        System.out.println(between);
        System.out.println(minutes);

LocalTime提供了多种工厂方法来简化对象的创建,包括解析时间字符串

		LocalTime time = LocalTime.of(23, 59, 59);
        System.out.println(time);
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.GERMAN);
        LocalTime parse = LocalTime.parse("13:37", dateTimeFormatter);
        System.out.println(parse);

9.4 LocalDate(本地日期)

LocalDate表示了一个确切的日期,比如2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致
示例:如何给Date对象加减天/月/年,注意对象不可变,操作返回的是一个新实例

		LocalDate now = LocalDate.now();
        System.out.println("今天的日期:" + now);
        LocalDate tomorrow = now.plus(1, ChronoUnit.DAYS);
        System.out.println("明天的日期:" + tomorrow);
        LocalDate yesterday = tomorrow.minusDays(2);
        System.out.println("昨天的日期:" + yesterday);
        LocalDate of = LocalDate.of(2024, Month.MARCH, 12);
        DayOfWeek dayOfWeek = of.getDayOfWeek();
        System.out.println("今天是周几:" + dayOfWeek);

从字符串解析一个LocalDate类型和解析LocalTime一样简单,下面使用DateTimeFormatter解析字符串

		String str = "2024....12....11 10时46分20秒";
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy....MM....dd HH时mm分ss秒");
        LocalDateTime parse = LocalDateTime.parse(str, dateTimeFormatter);
        System.out.println(parse);

跨年导致日期显示错误

        LocalDateTime rightNow = LocalDateTime.of(2024, 12, 31, 12, 0, 0);
        String date = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow);
        System.out.println(date);
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
        System.out.println(dateTimeFormatter.format(rightNow));

        DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println(dateTimeFormatter1.format(rightNow));

9.5 LocalDateTime (本地日期时间)

LocalDateTime同时表示了时间和日期,相当于前两节内容合并到一个对象上,对象不可变

        LocalDateTime of = LocalDateTime.of(2024, Month.DECEMBER, 31, 23, 59, 59);
        System.out.println(of.getDayOfWeek());
        System.out.println(of.getMonth());
        System.out.println(of.getLong(ChronoField.MINUTE_OF_DAY));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Retrograde-lx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值