【JAVA基础——JAVA8版本特性】

news2024/11/20 11:46:56

JAVA基础

版本特性


文章目录

  • JAVA基础
    • Java8
      • Lambda表达式
        • lambda表达式类型
      • 函数式接口
      • 接口默认方法与静态方法
        • 接口默认方法
        • 接口静态方法
      • 方法引用
      • Optional
      • Stream流
        • 生产Stream Source的方式
        • 流的方法
        • parallelStream流
      • Date/Time API
      • 其他特性
        • 重复注解
        • 扩展注解
        • 参数名字保留在字节码中
        • ReentrantLock
        • ReentrantReadWriteLock
        • StampedLock
        • 并行数组
        • CompletableFuture
        • 虚拟机


Java8

Lambda表达式

JDK8之前,一个方法能接受的参数都是变量.如果需要传入一个动作如何做?

@FunctionalInterface
public interface PersonCallback {
    void callback(Person person);

  /**
   * 声明覆盖java.lang.Object公共方法的抽象方法.
   * 不计入接口的抽象方法计数,(接口的任何实现都将具有来自java.lang.Object的实现)
   */
  boolean equals(Object obj);
}

public class Person {
    private String id;
    private String name;

    public Person(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // 创建一个Person之后,进行回调
    public static void create(String id, String name, PersonCallback personCallback) {
        Person person = new Person(id, name);
        personCallback.callback(person);
    }
}

public class Demo {
    public static void main(String[] args) {
        Person.create("1", "name", new PersonCallBack() {
            @Override
            public void callback(Person person) {
                log.debug("person:{}", person);
            }
        });

        Person.create("2", "name", (Person person) -> {
            log.debug("person:{}", person);
        });
        //Java8的类型推导机制
        Person.create("3", "name", (person) -> {
            log.debug("person:{}", person);
        });

    }
}

Lambda允许把函数作为一个方法的参数,一个lambda由用逗号分隔的 参数列表、–>符号、函数体三部分表示.

当实现接口时,需要实现接口里面的方法,Lambda表达式也遵循基本准则.
一个Lambda表达式实现了接口里的有且仅有的唯一一个抽象方法.这种接口叫做接函数式接口.
可以认为Lambda表达式 代表一种动作,可以直接把这种特殊的动作动作进行传递.

lambda表达式类型

一个Lambda表达式 可以理解为一个函数式接口的实现者,作为表达式,写法是多种多样的.

  • () -> {return 0;},无参数,有返回值
  • (int i) -> {return 0;},一个参数,有返回值
  • (int i) -> {System.out.println(i)},一个参数,无返回值
  • (int i, int j) -> {System.out.println(i)},多参数,无返回值
  • (int i, int j) -> {return i+j;},多参数,有int返回值
  • (int i, int j) -> {return i>j;},多参数,有boolean返回值
  • 其他

函数式接口

函数式接口是新增的一种接口定义.
用 @FunctionalInterface 修饰的接口叫做函数式接口,或者只具有一个抽象方法的普通接口,(@FunctionalInterface起到校验的作用)
有一个抽象方法能编译正确,具有多个抽象方法则无法编译.(除了覆盖java.lang.Object的方法)

@FunctionalInterface
public interface PersonCallback {
    void callback(Person person);

    /**
     * 声明覆盖java.lang.Object公共方法的抽象方法.
     * 不计入接口的抽象方法计数,(接口的任何实现都将具有来自java.lang.Object的实现)
     */
    boolean equals(Object obj);
}

JDK7已有一些函数式接口,如Runnable、Callable、FileFilter等.
JDK8中也增加了很多函数式接口

接口描述
Supplier无参数,返回一个结果
Function接受一个输入参数,返回一个结果
Consumer接受一个输入参数,无返回结果
Predicate接受一个输入参数,返回一个布尔值结果

多函数式接口有什么作用?

一个Lambda表达式 可以理解为一个函数式接口的实现者,作为表达式,写法其实是多种多样的.
每种表达式的写法其实都应该是某个函数式接口的实现类,需要特定函数式接口进行对应函数式接口进行对应,
如lambda表达式类型对应如下函数式接口:

  • Supplier
  • Function<T,R>
  • Consumer
  • BiConsumer<T, U>
  • BiFunction<T, U, R>
  • BiPredicate<T, U>

为了写Lambda表达式更加方便.特殊情况还是需要自定义函数式接口.

接口默认方法与静态方法

JDK8前,想对接口新增一个方法,需要修改所有的实现类源码.
JDK8前,借助抽象类实现接口来解决: 当接口新增抽象方法时,只需要在抽象类中实现默认方法.
JDK8后,支持接口默认方法与静态方法.
使用默认方法和静态方法,不用修改实现类了,可以进行直接调用.

接口默认方法

在接口中用default修饰的方法称为默认方法默认方法.
接口中的默认方法必须有默认实现(方法体),接口实现者可以继承,也可以覆盖.

public interface TestInterface {
    default void testDefault(){
         System.out.println("default");
    }
}

接口静态方法

在接口中用static修饰的方法称为静态方法静态方法.

public interface TestInterface {
    static void testStatic() {
        System.out.println("static");
    }
}
//调用
TestInterface.testStatic();

方法引用

//lambda
Consumer<String> consumer = s -> System.out.println(s); 
consumer.accept("test");
//方法引用
//PrintStream类(System.out的类型)的println方法已经实现
Consumer<String> consumer = System.out::println;
consumer.accept("test");

方法引用方法的参数列表必须与函数式接口的抽象方法的参数列表保持一致,返回值不作要求!
使用方式:

  • 实例对象::实例方法名
        //Consumer<String> consumer = s -> System.out.println(s);
        Consumer<String> consumer = System.out::println;
        consumer.accept("test");
    
  • 类名::静态方法名
        //Function<Long, Long> f = x -> Math.abs(x);
        Function<Long, Long> f = Math::abs;
        Long result = f.apply(-3L);
    
  • 类名::实例方法名
        //BiPredicate<String, String> b = (x,y) -> x.equals(y);
        BiPredicate<String, String> b = String::equals;
        b.test("a", "b");
    
  • 引用构造器
        //Function<Integer, StringBuffer> fun = n -> new StringBuffer(n); 
        Function<Integer, StringBuffer> fun = StringBuffer::new;
        StringBuffer buffer = fun.apply(10);
    
  • 引用数组
        // Function<Integer, int[]> fun = n -> new int[n];
        Function<Integer, int[]> fun = int[]::new;
        int[] arr = fun.apply(10);
        Function<Integer, Integer[]> fun2 = Integer[]::new;
        Integer[] arr2 = fun2.apply(10);
    

Optional

空指针异常是导致Java应用程序失败的最常见原因,为了解决空指针异常,
Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染.
受到Google Guava的启发,Optional类已经成为Java 8类库的一部分.

Optional实际上是个容器:保存类型T的值,或者null.Optional 提供很多方法,不用显式进行空值检测.
创建Optional对象的方法:

Optional.of(T value), 返回一个Optional对象,value不能为空,否则会出空指针异常
Optional.ofNullable(T value), 返回一个Optional对象,value可以为空
Optional.empty(),代表空

其他API:

//是否存在值
optional.isPresent()
//如果存在值则执行consumer
optional.ifPresent(Consumer<? super T> consumer)
//获取value
optional.get()
//如果value为null则返回other
optional.orElse(T other)
//如果value为null则执行other并返回
optional.orElseGet(Supplier<? extends T> other)
//如果value为null执行exceptionSupplier,并抛出异常
optional.orElseThrow(Supplier<? extends X> exceptionSupplier);

//映射,映射规则由function指定,返回映射值的Optional,所以可以继续使用Optional的API.
optional.map(Function<? super T, ? extends U> mapper);
//类似map,区别在于map中获取的返回值自动被Optional包装,flatMap中返回值保持不变,入参必须是Optional类型.
optional.flatMap(Function<? super T, Optional< U > > mapper);
//过滤,按predicate指定的规则进行过滤,不符合规则则返回empty,可以继续使用Optional的API.
optional.filter(Predicate<? super T> predicate);

Stream流

Java 8 中的 Stream 是对集合(Collection)对象功能的增强,
专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation).
提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程
有关算法和计算的,像一个高级版本的Iterator.

中间操作(Intermediate Operation):
一个流可以后面跟随零个或多个 intermediate 操作.其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流, 交给下一个操作使用.
这类操作都是惰性化的(lazy),调用到这类方法,并没有真正开始流的遍历.

终止操作(Terminal Operation):
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作.必定是流的最后一个操作.
Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果.

Intermediate Operation可以分为两种类型:

  • 无状态操作(Stateless Operation):操作是无状态的,不需要知道集合中其他元素的状态,每个元素之间是相互独立的,比如map()、filter()等操作.
  • 有状态操作(Stateful Operation):有状态操作,操作是需要知道集合中其他元素的状态才能进行的,比如sort()、distinct().

Terminal Operation从逻辑上可以分为两种:

  • 短路操作(short-circuiting):不需要处理完所有元素即可结束整个过程.
  • 非短路操作(non-short-circuiting):需要处理完所有元素之后才能结束整个过程.

流不是数据结构,没有内部存储,用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据.
不修改所封装的底层数据结构的数据.例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素.

Stream 的操作必须以 lambda 表达式为参数.
惰性化,很多 Stream 操作是向后延迟的,Intermediate 操作永远是惰性化的.
当一个 Stream 是并行化的,所有对它的操作会自动并行进行的.

生产Stream Source的方式

从Collection 和数组生成:

  • Collection.stream()
  • Collection.parallelStream()
  • Arrays.stream(T array)
  • Stream.of(T t)

BufferReader:

  • java.io.BufferedReader.lines()

静态工厂:

  • java.util.stream.IntStream.range()
  • java.nio.file.Files.walk()

其他:

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

自定义:

  • java.util.Spliterator

流的方法

  • map/flatMap
  • filter
  • forEach
  • peek
  • reduce
  • Limit/skip
  • sorted/min/max/distinct
  • allMatch
  • anyMatch
  • noneMatch

parallelStream流

通过默认的 ForkJoinPool,提高多线程任务的速度.
ForkJoinPool 默认线程数量等于运行计算机上的处理器数量,
java进程使用parallelStream的地方公用的同一个ForkJoinPool.
parallelStream提供了更简单的并发执行的实现,但并不意味着更高的性能.
如果cpu资源紧张 parallelStream不会带来性能提升;如果存在频繁的线程切换反而会降低性能.

stream.parallel()

Date/Time API

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理.
LocaleDate只持有ISO-8601格式且无时区信息的日期部分:

LocalDate date = LocalDate.now();  // 当前日期         
date = date.plusDays(1);           // 增加一天         
date = date.plusMonths(1);         // 增加一月         
date = date.minusDays(1);          // 减少一天         
date = date.minusMonths(1);        // 减少一月

LocalTime类只持有ISO-8601格式且无时区信息的时间部分:

LocalTime time = LocalTime.now();    // 当前时间         
time = time.plusMinutes(1);          // 增加一分钟         
time = time.plusSeconds(1);          // 增加一秒钟         
time = time.minusMinutes(1);         // 减少一分钟         
time = time.minusSeconds(1);         // 减少一秒钟

LocaleDateTime类和格式化:
LocaleDateTime把LocaleDate与LocaleTime的功能合并起来,持有ISO-8601格式无时区信息的日期与时间:

LocalDateTime localDateTime = LocalDateTime.now();
// 2023-06-21T08:57:07.465 UTC格式
localDateTime;
// 2023-06-21 08:57:07 自定义格式
localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));  

ZonedDateTime类:
需要特定时区的日期/时间,持有ISO-8601格式具具有时区信息的日期与时间:

//2023-06-21T08:57:07.465-08:00[America/Los_Angeles]
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now(ZoneId.of( "America/Los_Angeles" ));

Clock类:
通过指定一个时区,就可以获取到当前的时刻,日期与时间。
Clock可以替换System.currentTimeMillis()与TimeZone.getDefault().

final Clock utc = Clock.systemUTC();  // 协调世界时,又称为世界统一时间、世界标准时间、国际协调时间
final Clock shanghai = Clock.system(ZoneId.of("Asia/Shanghai"));  // 上海
LocalDateTime.now(utc);         // 2023-06-21T08:57:07.176
LocalDateTime.now(shanghai);    // 22023-06-21T08:57:07.177

Duration类:
计算两个日期间的不同.

final LocalDateTime from = LocalDateTime.parse("2018-12-17 18:50:50", DateTimeFormatter.ofPattern("yyyyMM-dd HH:mm:ss")); 
final LocalDateTime to = LocalDateTime.parse("2018-12-18 19:50:50", DateTimeFormatter.ofPattern("yyyyMM-dd HH:mm:ss"));
final Duration duration = Duration.between(from, to);
System.out.println("Duration in days: " + duration.toDays()); // 1
System.out.println("Duration in hours: " + duration.toHours()); // 25

其他特性

重复注解

java 8前不允许重复注解出现在同一个地方.

@Repeatable元注解
指定重复注解的存储注解(需要数组来存储重复注解).

反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型.

扩展注解

JDK8之前的注解只能加在:

public enum ElementType {
    TYPE,        // 类、枚举、接口
    FIELD,       // 类型变量
    METHOD,      // 方法
    PARAMETER,   // 方法参数
    CONSTRUCTOR,  // 构造方法
    LOCAL_VARIABLE, // 局部变量
    ANNOTATION_TYPE, // 注解类型
    PACKAGE,           // 包
  
    //JDK8中新增了两种:
    //类型变量的声明语句中。
    TYPE_PARAMETER//能写在使用类型的任何语句中
    TYPE_USE}

比如: @Nullable, @NonNull

参数名字保留在字节码中

Java8前没有能够获取到方法的参数名字列表!
Method.getParameterAnnotations() 获取方法参数上的注解
Method.getParameterTypes() 获取方法的参数类型列表
在JDK8中增加了两个方法:
Method.getParameters() 获取参数名字列表
Method.getParameterCount() 获取参数名字个数

ReentrantLock

ReentrantLock类,实现了Lock接口,是一种可重入的独占锁,它具有与使用 synchronized 相同的一些基本行为和语义,但功能更强大。ReentrantLock内部通过内部类实现了AQS框架(AbstractQueuedSynchronizer)的API来实现独占锁的功能。

ReentrantReadWriteLock

和ReentrantLock不同,ReentrantReadWriteLock实现的是ReadWriteLock接口。
在ReentrantReadWriteLock中,当读锁被使用时,如果有线程尝试获取写锁,写线程会阻塞。
在读线程非常多,写线程很少的情况下,很容易导致写线程“饥饿”.
虽然使用“公平”策略可以一定程度上缓解这个问题,但“公平”策略是以牺牲系统吞吐量为代价的。

StampedLock

对读写锁ReentrantReadWriteLock的增强,提供了一些功能,优化了读锁、写锁的访问,同时使读写锁之间可以互相转换,更细粒度控制并发.

并行数组

大量的新方法来对数组进行并行处理.
parallelSort()方法可以在多核机器上极大提高数组排序的速度。

long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll( arrayOfLong, index -> ThreadLocalRandom.current().nextInt( 1000000 ) );     
Arrays.parallelSort( arrayOfLong );         

CompletableFuture

异步编排.

虚拟机

Metaspace元空间取代PermGen, 永久代空间被移除.
JVM选项-XX:PermSize与-XX:MaxPermSize 分别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替.

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/945676.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

绿色能源迎来跨越式增长新时代

当今世界&#xff0c;百年未有之大变局加速演进&#xff0c;新一轮科技革命和产业变革深入发展&#xff0c;全球气候治理呈现新局面&#xff0c;新能源和信息技术紧密融合&#xff0c;生产生活方式加快转向低碳化、智能化&#xff0c;能源体系和发展模式正在进入非化石能源主导…

ROS中使用Navigation报错信息

在ROS中使用遇到了几个Navigation报错信息&#xff0c;在这里进行下记录&#xff1a; [ WARN] [1688134727.429227824]: The origin for the sensor at (7.35, 13.12) is out of map bounds. So, the costmap cannot raytrace for it. 解决办法&#xff1a; [ WARN] [16881…

flink on yarn with kerberos 边缘提交

flink on yarn 带kerberos 远程提交 实现 flink kerberos 配置 先使用ugi进行一次认证正常提交 import com.google.common.io.Files; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.flink.client.cli.CliFrontend; import o…

Gorilla LLM:连接海量 API 的大型语言模型

如果你对这篇文章感兴趣&#xff0c;而且你想要了解更多关于AI领域的实战技巧&#xff0c;可以关注「技术狂潮AI」公众号。在这里&#xff0c;你可以看到最新最热的AIGC领域的干货文章和案例实战教程。 一、前言 在当今这个数字化时代&#xff0c;大型语言模型&#xff08;LLM…

史上最全AP、mAP详解与代码实现

文章目录 前言一、mAP原理1、mAP概念2、准确率3、精确率4、召回率5、AP: Average Precision 二、mAP0.5与mAP0.5:0.951、mAP0.52、mAP0.5:0.95 三、mAP代码实现1、真实标签json文件格式2、模型预测标签json文件格式3、mAP代码实现4、mAP结果显示 四、模型集成mAP代码1、模型mai…

项目:点餐系统3mysql知识回顾MySQL客户端

连接数据库 mysql -uroot -p 密码&#xff1a;空 一、第三方库&#xff1a;MySQL 数据库-存储并管理数据的仓库&#xff0c;是一个C/S架构 MySQL客户端通过sql来告诉MySQL服务器&#xff0c;自己需要做什么操作 1.sql语句 sql&#xff1a;structure query language结构化查询…

LeetCode 44题:通配符匹配

题目 给你一个输入字符串 (s) 和一个字符模式 (p) &#xff0c;请你实现一个支持 ? 和 * 匹配规则的通配符匹配&#xff1a; ? 可以匹配任何单个字符。* 可以匹配任意字符序列&#xff08;包括空字符序列&#xff09;。 判定匹配成功的充要条件是&#xff1a;字符模式必须…

AI识别工人安全绳佩戴检测算法

AI识别工人安全绳佩戴检测算法通过yolov5智能图像识别算法对现场图像进行处理和分析&#xff0c;AI识别工人安全绳佩戴检测算法识别出工人是否佩戴安全绳&#xff0c;一旦发现工人未佩戴安全绳&#xff0c;AI识别工人安全绳佩戴检测算法将立即进行告警&#xff0c;并将事件记录…

SOLIDWORKS中多实体文件到装配体的转换技巧

我们在做机械等工程设计中&#xff0c;有时为了节省时间&#xff0c;需要把多实体的“零件”&#xff0c;直接转换为装配体&#xff0c;不再另外装配&#xff0c;这样能大大简化设计的操作时间&#xff0c;复杂程度。 在这里&#xff0c;我们首先要了解&#xff0c;SOLIDWORKS文…

2023常见前端面试题

以下是一些2023年秋招常见的前端面试题及其答案&#xff1a; 1. 请解释一下什么是前端开发&#xff1f; 前端开发是指使用HTML、CSS和JavaScript等技术来构建网页和用户界面的过程。前端开发人员负责将设计师提供的视觉设计转化为可交互的网页&#xff0c;并确保网页在不同设备…

【活体检测模型】活体检测思路推演

ref:https://arxiv.org/pdf/1611.05431.pdf https://github.com/miraclewkf/ResNeXt-PyTorch 用分类的思想做活体检测&#xff0c;要求准确的分出正负样本&#xff0c;否则&#xff0c;支付宝被别人用了&#xff0c;问题就很严重。 大部分的商用场景还是 摇摇头、张张口&#x…

Android——基本控件(下)(二十)

1. 树型组件&#xff1a;ExpandableListView 1.1 知识点 &#xff08;1&#xff09;掌握树型组件的定义&#xff1b; &#xff08;2&#xff09;可以使用事件对树操作进行监听。 2. 具体内容 既然这个组件可以完成列表的功能&#xff0c;肯定就需要一个可以操作的数据&…

Java集合sort排序报错UnsupportedOperationException处理

文章目录 报错场景排查解决UnmodifiableList类介绍 报错场景 我们使用的是PostgreSQL数据库&#xff0c;存储业务数据&#xff0c;业务代码使用的是Spring JPA我们做的是智慧交通信控平台&#xff0c;有个功能是查询展示区域的交通态势&#xff0c;需要按照不同维度排序展示区…

pngPacker与TexturePacker打包对比,手把手教你使用pngPacker

pngPacker与TexturePacker打包对比&#xff0c;手把手教你使用pngPacker打包出媲美texturepacker的效果 pngPacker是一款免费的图片打包工具&#xff0c;软件小巧易用&#xff0c;主流游戏图片格式&#xff0c;如 bmp,jpg,png 可以打包为 png 大图&#xff0c;采用命令行格,前一…

数字乡镇综合解决方案PPT

导读&#xff1a;原文《数字乡镇综合解决方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 部分内容&#xff1a; 喜欢文章&#xff0c;您可以关注评论转发本…

网络编程 http 相关基础概念

文章目录 表单是什么http请求是什么http请求的结构和说明关于http方法 GET和POST区别http常见状态码http响应http 请求是无状态的含义html是什么 &#xff08;前端内容&#xff0c;了解即可&#xff09;html 常见标签 &#xff08;前端内容&#xff0c;了解即可&#xff09;关于…

哇塞不!赛博时代云上自动化辅导孩子学习。

背景 孩子天天上网看动画片&#xff0c;都幼儿园大班了还不会100以内的加减法&#xff0c;因为我平时还需要忙着工作不能天天陪着孩子。 这次我们整个活&#xff0c;让孩子每天心甘情愿的做100道题。而且电脑还要给孩子一些提醒&#xff0c;即使做错了也会不厌其烦的给孩子提示…

图像颜色空间转换

目录 1.图像颜色空间介绍 RGB 颜色空间 2.HSV 颜色空间 3.RGBA 颜色空间 2.图像数据类型间的互相转换convertTo() 3.不同颜色空间互相转换cvtColor() 4.Android JNI demo 1.图像颜色空间介绍 RGB 颜色空间 RGB 颜色空间是最常见的颜色表示方式之一&#xff0c;其中 R、…

【C++】—— 异常处理

前言&#xff1a; 本期&#xff0c;我将给大家讲解的是有关 异常处理 的相关知识&#xff01; 目录 &#xff08;一&#xff09;C语言传统的处理错误的方式 &#xff08;二&#xff09;C异常概念 &#xff08;三&#xff09;异常的使用 1、异常的抛出和捕获 1️⃣ 异常的…

Windows Qt 5.12.10下载与安装

Qt 入门实战教程&#xff08;目录&#xff09; C自学精简实践教程 目录(必读) 1 Qt5.12.10下载 qt-opensource-windows-x86-5.12.10.exe 官方离线安装包 Download Source Package Offline Installers | Qt 下载巨慢&#xff08;也可能很快&#xff09; 只能下载到最新的&…