Java Development Kit (JDK) 发展历程 及新特性

   SE(J2SE),standard edition,标准版,是我们通常用的一个版本,从JDK 5.0开始,改名为Java SE。

      EE(J2EE),enterprise edition,企业版,使用这种JDK开发J2EE应用程序,从JDK 5.0开始,改名为Java EE。

      ME(J2ME),micro edition,主要用于移动设备、嵌入式设备上的java应用程序,从JDK 5.0开始,改名为Java ME。

      没有JDK的话,无法编译Java程序,如果想只运行Java程序,要确保已安装相应的JRE

      以下是各版本的名称及发布日期:

版本

名称

发行日期

JDK 1.1.4

Sparkler(宝石)

1997-09-12

JDK 1.1.5

Pumpkin(南瓜)

1997-12-13

JDK 1.1.6

Abigail(阿比盖尔–女子名)

1998-04-24

JDK 1.1.7

Brutus(布鲁图–古罗马政治家和将军)

1998-09-28

JDK 1.1.8

Chelsea(切尔西–城市名)

1999-04-08

J2SE 1.2

Playground(运动场)

1998-12-04

J2SE 1.2.1

none(无)

1999-03-30

J2SE 1.2.2

Cricket(蟋蟀

1999-07-08

J2SE 1.3

Kestrel(美洲红隼)

2000-05-08

J2SE 1.3.1

Ladybird(瓢虫)

2001-05-17

J2SE 1.4.0

Merlin(灰背隼)

2002-02-13

J2SE 1.4.1

grasshopper(蚱蜢)

2002-09-16

J2SE 1.4.2

Mantis(螳螂

2003-06-26

Java SE 5.0 (1.5.0)

Tiger(老虎)

2004-09-30

Java SE 6.0 (1.6.0)

Mustang(野马)

2006-04

Java SE 7.0 (1.7.0)

Dolphin(海豚)

2011-07-28

Java SE 8.0 (1.8.0)

未知

2013-09(预定)

 

 
 
参考:
 

 
 
  
“JDK1.5”(开发代号猛虎)的新特性
 
1.泛型(Generic)

  C++通过模板技术 可以指定集合的元素类型,而Java在1.5之前一直没有相对应的功能。一个集合可以放任何类型的对象,相应地从集合里面拿对象的时候我们也不得不对他们 进行强制得类型转换。猛虎引入了泛型,它允许指定集合里元素的类型,这样你可以得到强类型在编译时刻进行类型检查的好处。

 
Collection<String> c = new ArrayList();
c.add(new Date());


  编译器会给出一个错误:

add(java.lang.String) in java.util.Collection<java.lang.String> cannot be applied to (java.util.Date)


  2.For-Each循环

  For-Each循环得加入简化了集合的遍历。假设我们要遍历一个集合对其中的元素进行一些处理。典型的代码为:

void processAll(Collection c){
    for(Iterator i=c.iterator(); i.hasNext();){
        MyClass myObject = (MyClass)i.next();
        myObject.process();
    }
}


  使用For-Each循环,我们可以把代码改写成:

void processAll(Collection<MyClass> c){
    for (MyClass  myObject :c)
        myObject.process();
}


  这段代码要比上面清晰许多,并且避免了强制类型转换。

  3.自动装包/拆包(Autoboxing/unboxing)

  自动装包/拆包大大方便了基本类型数据和它们包装类地使用。

  自动装包:基本类型自动转为包装类.(int >> Integer)

  自动拆包:包装类自动转为基本类型.(Integer >> int)

  在JDK1.5之前,我们总是对集合不能存放基本类型而耿耿于怀,现在自动转换机制解决了我们的问题。

int a = 3;
Collection c = new ArrayList();
c.add(a);//自动转换成Integer.

Integer b = new Integer(2);
c.add(b + 2);

  这里Integer先自动转换为int进行加法运算,然后int再次转换为Integer.

   4.枚举(Enums)

  JDK1.5加入了一个全新类型的“类”-枚举类型。为此JDK1.5引入了一个新关键字enmu. 我们可以这样来定义一个枚举类型。
 
public enum Color
{
   Red,
   White,
   Blue
}

  然后可以这样来使用Color myColor = Color.Red.

  枚举类型还提供了两个有用的静态方法values()和valueOf(). 我们可以很方便地使用它们,例如

for (Color c : Color.values())
            System.out.println(c);

   5.可变参数(Varargs)

  可变参数使程序员可以声明一个接受可变数目参数的方法。注意,可变参数必须是函数声明中的最后一个参数。假设我们要写一个简单的方法打印一些对象,

util.write(obj1);
util.write(obj1,obj2);
util.write(obj1,obj2,obj3);

  在JDK1.5之前,我们可以用重载来实现,但是这样就需要写很多的重载函数,显得不是很有效。如果使用可变参数的话我们只需要一个函数就行了

public void write(Object... objs) {
   for (Object obj: objs)
      System.out.println(obj);
}

   在引入可变参数以后,Java的反射包也更加方便使用了。对于c.getMethod("test", new  Object[0]).invoke(c.newInstance(), new Object[0])),现在我们可以这样写了 c.getMethod("test").invoke(c.newInstance()),这样的代码比原来清楚了很多。 

   6.静态导入(Static Imports)

  要使用用静态成员(方法和变量)我们必须给出提供这个方法的类。使用静态导入可以使被导入类的所有静态变量和静态方法在当前类直接可见,使用这些静态成员无需再给出他们的类名。

import static java.lang.Math.*;
…….
r = sin(PI * 2); //无需再写r = Math.sin(Math.PI);

  不过,过度使用这个特性也会一定程度上降低代码地可读性。
Collection<String> c = new ArrayList();
c.add(new Date());


  编译器会给出一个错误:

add(java.lang.String) in java.util.Collection<java.lang.String> cannot be applied to (java.util.Date)





Java SE 6.0 (1.6.0)

Mustang(野马)的新特性

简化Web Services
Mustang 将 简化Web services 的开发和发布. XML和Web服务一直都是Mustang的关注重点.. Mustang为此引入了JAX-WS(Java Architecture for XML-Web Services) 2.0 以及JAXB(Java Architecture for XML Binding) 2.0.. 同时还有Streaming API for XML (STaX), 它提供了一个双向API,这个API可以通过一个事件流来读取或者写入XML,其中包括跳过某个部分,然后直接关注与文档中的另外一个小部分的能力。


Scripting,整合脚本语言
目前来讲,Java 开发者们必须在Java之外独立地额外编码来使用non-Java 脚本语言。这个头痛的问题将被Mustang 消灭,开发者将更加轻松的使用Perl、PHP、Python、JavaScript 和Ruby等脚本语言。新的框架将允许人们操作任意的脚本语言,和使用Java 对象。

Java SE6中实现了JSR223。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本。这个脚本API允许你为脚本语言提供Java支持。另外,Web Scripting Framework允许脚本代码在任何的Servlet容器(例如Tomcat)中生成Web内容。

Database,绑定Derby
开源嵌入式数据库 Derby(JavaDB) 绑定在JDK 1.6中.具体可以参考:JDK 1.6 将绑定开源数据库 Derby

更丰富的Desktop APIs
Mustang中拥有更多强的桌面API提供给开发者, 开发者可以更简单地开发更强大的桌面应用, 比如启动界面的支持,系统托盘的支持,JTable排序等等

监视和管理
Java SE 6中对内存泄漏增强了分析以及诊断能力。当遇到java.lang.OutOfMemory异常的时候,可以得到一个完整的堆栈信息,并且当堆已经满了的时候,会产生一个Log文件来记录这个致命错误。另外,JVM还添加了一个选项,允许你在堆满的时候运行脚本。(这也就是提供了另外一种方法来诊断错误)

增强的JMX 监视API在MBean的属性值传入了一个特定的参数的时候,允许这个应用程序发送一个事件通告。(这里的属性值可以在很复杂的类型中)

对于Solaris 10的用户,为Solaris提供的Hotspot JVM中,提供了一种通过Solaris DTrace(这是个系统的调试工具)来追踪显示JVM内部的活动情况,包括垃圾收集,类装载,线程,锁等等。

Pluggable Annotations
从Java SE 5   带来得新特性Annotations,将在Mustang继续扮演重要角色..

Compiler API:访问编译器
对于Java开发工具, 或者Web框架 等的开发者来说, 利用编译器编译动态生成的代码, 是一个普遍的需求.

Mustang实现了JSR 199,   提供了Java编译器API(应用程序接口),允许你从一个Java应用程序中去编译其他的Java源程序--比如在应用程序中动态生成的一些源代码..

Security:安全性
Java SE 6的安全部分,增加了 XML-Digital Signature (XML-DSIG) APIs, 整合了GSS/Kerberos的操作API,LDAP上的JAAS认证。

Instrumentation
      利用 Java 代码,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。在 Java SE 5 当中,开发者只能在 premain 当中施展想象力,所作的 Instrumentation 也仅限与 main 函数执行前,这样的方式存在一定的局限性。在 Java SE 6 的 Instrumentation 当中,有一个跟 premain“并驾齐驱”的“agentmain”方法,可以在 main 函数开始运行之后再运行。

Http
    在 Java SE 6 当中,围绕着 HTTP 协议出现了很多实用的新特性:NTLM 认证提供了一种 Window 平台下较为安全的认证机制;JDK 当中提供了一个轻量级的 HTTP 服务器;提供了较为完善的 HTTP Cookie 管理功能;更为实用的 NetworkInterface;DNS 域名的国际化支持等等。
    HTTP Cookie管理可以应用客户操作临时变量的保存,如查询条件,当前状态等


JMX与系统管理

Agent / SubAgent 起到的就是翻译的作用:把 IT 资源报告的消息以管理系统能理解的方式传送出去。

也许读者有会问,为什么需要 Agent 和 SubAgent 两层体系呢?这里有两个现实的原因:

管理系统一般是一个中央控制的控制软件,而 SubAgent 直接监控一些资源,往往和这些资源分布在同一物理位置。当这些 SubAgent 把状态信息传输到管理系统或者传达管理系统的控制指令的时候,需要提供一些网络传输的功能。

  1. 管理系统的消息是有一定规范的,消息的翻译本身是件复杂而枯燥的事情。

一般来说,管理系统会将同一物理分布或者功能类似的 SubAgent 分组成一组,由一个共用的 Agent 加以管理。在这个 Agent 里封装了 1 和 2 的功能。

JMX 和管理系统

JMX 既是 Java 管理系统的一个标准,一个规范,也是一个接口,一个框架

JMX 是管理系统和资源之间的一个接口,它定义了管理系统和资源之间交互的标准。javax.management.MBeanServer 实现了 Agent 的功能,以标准的方式给出了管理系统访问 JMX 框架的接口。而 javax.management.MBeans 实现了 SubAgent 的功能,以标准的方式给出了 JMX 框架访问资源的接口。而从类库的层次上看,JMX 包括了核心类库 java.lang.management 和 javax.management 包。java.lang.management 包提供了基本的 VM 监控功能,而 javax.management 包则向用户提供了扩展功能。 JMX帮助开发者监控JVM的信息。

编辑器API

     JDK 6 提供了在运行时调用编译器的 API。在传统的 JSP 技术中,服务器处理 JSP 通常需要进行下面 6 个步骤:

  1. 分析 JSP 代码;
  2. 生成 Java 代码;
  3. 将 Java 代码写入存储器;
  4. 启动另外一个进程并运行编译器编译 Java 代码;
  5. 将类文件写入存储器;
  6. 服务器读入类文件并运行;

     但如果采用运行时编译,可以同时简化步骤 4 和 5,节约新进程的开销和写入存储器的输出开销,提高系统效率。实际上,在 JDK 5 中,Sun 也提供了调用编译器的编程接口。然而不同的是,老版本的编程接口并不是标准 API 的一部分,而是作为 Sun 的专有实现提供的,而新版则带来了标准化的优点。
     新 API 的第二个新特性是可以编译抽象文件,理论上是任何形式的对象 —— 只要该对象实现了特定的接口。有了这个特性,上述例子中的步骤 3 也可以省略。整个 JSP 的编译运行在一个进程中完成,同时消除额外的输入输出操作。
     第三个新特性是可以收集编译时的诊断信息。作为对前两个新特性的补充,它可以使开发人员轻松的输出必要的编译错误或者是警告信息,从而省去了很多重定向的麻烦

一些有趣的新特性:

1 本地行为 java.awt.Desktop
比如用默认程序打开文件,用默认浏览器打开url,再也不用那个browserlauncher那么费劲

Desktop desk=Desktop.getDesktop();
desk.browse(new URI(" http://www.google.com/"));
desk.open(file)
desk.print(file)

2 console下密码输入 java.io.Console
再也不用自己写线程去删echo字符了
Console console = System.console();
char password[] = console.readPassword("Enter password: ");

3 获取磁盘空间大小 java.io.File的新方法
File roots[] = File.listRoots();
for (File root : roots) {
System.out.println(root.getPath()+":"+root.getUsableSpace()
+"/"+root.getTotalSpace());
}

4 专利过期,可以提供合法的lzw的gif encoder了
ImageIO.write(input, "GIF", outputFile);

5 JAXB2.0 增加了java-to-xml schema,完成java bean,xml间转换非常容易

6 xml数字签名 javax.xml.crypto,记得以前似乎只有ibm有个类库实现了

7 编译器,以前是com.sun.tools.javac,现在是javax.tools.JavaCompiler
有人写了完全在内存里的生成源文件,编译,反射运行的过程,比较好玩。

8 脚本引擎,javax.script,内嵌的是Mozilla Rhino1.6r2 支持ECMAScript1.6
 

Java SE 7.0 (1.7.0)

Dolphin(海豚)的新特性

1、Switch中可以使用String了

  在之前的版本中是不支持在Switch语句块中用String类型的数据的,这个功能在C#语言中早已被支持,好在JDK1.7中加入了。

String s = "test";   
switch (s) {   
  case "test" :   
     System.out.println("test");  
  case "test1" :   
    System.out.println("test1"); 
    break ;   
  default :   
    System.out.println("break"); 
    break ;   
 }

2、泛型实例化类型自动推断

List<String> tempList = new ArrayList<>();

3、对Java 集合( Collections )的增强支持

  在JDK1.7之前的版本中,Java集合容器中存取元素的形式如下。以List、Set、Map集合容器为例:

//创建List接口对象  
List<String> list=new ArrayList<String>();  
list.add("item"); //用add()方法获取对象  
String Item=list.get(0); //用get()方法获取对象  

//创建Set接口对象  
Set<String> set=new HashSet<String>();  
set.add("item"); //用add()方法添加对象  

//创建Map接口对象  
Map<String,Integer> map=new HashMap<String,Integer>();  
map.put("key",1); //用put()方法添加对象  int value=map.get("key")  

  在JDK1.7 中,摒弃了 Java 集合接口的实现类,如: ArrayList 、 HashSet 和 HashMap 。而是直接采用 [] 、{} 的形式存入对象,采用 [] 的形式按照索引、键值来获取集合中的对象,如下: 

List<String> list=["item"]; //向List集合中添加元素  
String item=list[0]; //从List集合中获取元素  
Set<String> set={"item"}; //向Set集合对象中添加元素  
Map<String,Integer> map={name:"xxx",age:18}; //向Map集合中添加对象  int value=map["age"]; //从Map集合中获取对象  

4、新增一些取环境信息的工具方法

File System.getJavaIoTempDir() // IO临时文件夹
File System.getJavaHomeDir() // JRE的安装目录
File System.getUserHomeDir() // 当前用户目录
File System.getUserDir() // 启动java进程时所在的目录
……

5、Boolean类型反转,空指针安全,参与位运算

//类型反转,空指针安全
Boolean Booleans.negate(Boolean booleanObj) //True => False , False => True, Null => Null
//参与位运算boolean Booleans.and(boolean[] array) 
boolean Booleans.or(boolean[] array) 
boolean Booleans.xor(boolean[] array) 
boolean Booleans.and(Boolean[] array) 
boolean Booleans.or(Boolean[] array) 
boolean Booleans.xor(Boolean[] array)

6、两个char间的equals

boolean Character.equalsIgnoreCase(char ch1, char ch2)

7、安全的加减乘除

int Math.safeToInt(long value)
int Math.safeNegate(int value)
long Math.safeSubtract(long value1, int value2)
long Math.safeSubtract(long value1, long value2)
int Math.safeMultiply(int value1, int value2)
long Math.safeMultiply(long value1, int value2)
long Math.safeMultiply(long value1, long value2)
long Math.safeNegate(long value)
int Math.safeAdd(int value1, int value2)
long Math.safeAdd(long value1, int value2)
long Math.safeAdd(long value1, long value2)
int  Math.safeSubtract(int value1, int value2)

8、数值可加下划线

int one_million = 1_000_000;

9、支持二进制文字

int binary = 0b1001_1001;
 
    

1. 可以用二进制表达数字

可以用二进制表达数字(加前缀0b/0B),包括:byte, short, int, long

复制代码
    // 可以用二进制表达数字(加前缀0b/0B),包括:byte, short, int, long    @Test
    public void testLiterals() {
        // An 8-bit 'byte' value:
        byte aByte = (byte)0b00100001;
        
        // A 16-bit 'short' value:
        short aShort = (short)0b1010000101000101;

        // Some 32-bit 'int' values:
        int anInt1 = 0b10100001010001011010000101000101;
        int anInt2 = 0b101;
        int anInt3 = 0B101; // The B can be upper or lower case.

        // A 64-bit 'long' value. Note the "L" suffix:
        long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;
        
        // 来个简单版本的
        byte b = 0b10;
        short s = 0B100;
        int i = 0b1000;
        long l = 0B10000;
        
        System.out.println(b + "|" + s + "|" + i + "|" + l);
        // ->输出将会是2|4|8|16
    }
复制代码

2. 可以对数字加下划线

可以对数字加下划线以让变量表达得更清楚些;注意:符号“.”左右不可以用下划线、还包括“L/F/0x"等等。

复制代码
    // 可以对数字加下划线以让变量表达得更清楚些
    // 注意:符号“.”左右不可以用下划线、还包括“L/F/0x"等等。    @Test
    public void testUnderscores() {
        long creditCardNumber = 1234_5678_9012_3456L;
        long socialSecurityNumber = 999_99_9999L;
        float pi =     3.14_15F;
        long hexBytes = 0xFF_EC_DE_5E;
        long hexWords = 0xCAFE_BABE;
        long maxLong = 0x7fff_ffff_ffff_ffffL;
        byte nybbles = 0b0010_0101;
        long bytes = 0b11010010_01101001_10010100_10010010;
        
        System.out.println(creditCardNumber + "|" + socialSecurityNumber);
        // ->下划线仅供代码中直观查看,输出时自动去掉了;输出将会是:1234567890123456|999999999
    }
复制代码

3. switch中可以使用字符串了

复制代码
    // switch中可以使用字符串了 
    public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
         String typeOfDay;
         switch (dayOfWeekArg) {
             case "Monday":
                 typeOfDay = "Start of work week";
                 break;
             case "Tuesday":
             case "Wednesday":
             case "Thursday":
                 typeOfDay = "Midweek";
                 break;
             case "Friday":
                 typeOfDay = "End of work week";
                 break;
             case "Saturday":
             case "Sunday":
                 typeOfDay = "Weekend";
                 break;
             default:
                 throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
         }
         return typeOfDay;
    }
复制代码

4. 泛型实例化类型自动推断

复制代码
    // 泛型实例化类型自动推断    @Test
    public void testGeneric() {
        // 旧版本
        Map<String, List<String>> myMap1 = new HashMap<String, List<String>>();
        
        // 新版本
        Map<String, List<String>> myMap2 = new HashMap<>();
        
        List<String> list = new ArrayList<>();
        list.add("A");
        
        // 下面这条语句编译不过;如果改成:new ArrayList<String>()则可以。
//        list.addAll(new ArrayList<>());
    }
复制代码

5. “非安全操作”的警告

 当使用一个不可具体化的参数(Non-Reifiable Formal Parameters)调用一个可变参数方法(Varargs Methods )编辑器会生成一个“非安全操作”的警告。

复制代码
package com.clzhang.sample.thinking;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

/**
 * 当使用一个不可具体化的参数(Non-Reifiable Formal Parameters)调用一个可变参数方法(Varargs Methods )
 * 编辑器会生成一个“非安全操作”的警告。
 * @author acer
 *
 */public class ArrayBuilder {
    //Type safety: Potential heap pollution via varargs parameter elements
    public static <T> void addToList(List<T> listArg, T... elements) {
        for (T x : elements) {
            listArg.add(x);
        }
    }

    //Type safety: Potential heap pollution via varargs parameter l    @SafeVarargs
    public static void faultyMethod(List<String>... l) {
        Object[] objectArray = l; // Valid
        
        // 这一行代码把列表中的数据类型给改变了!
        objectArray[0] = Arrays.asList(new Integer(42));
        
        // 下面再取值,会报错;因为里面已经不再是String类型的数据,而是Integer类型的数据。
        String s = l[0].get(0); // ClassCastException thrown here
        
        // 如果注释掉本方法中的第2行代码,则此条语句可以执行;否则,执行不到这里。
        System.out.println("first param is:" + s);
    }

    @Test
    public void testHeapPollution() {
        List<String> stringListA = new ArrayList<String>();
        List<String> stringListB = new ArrayList<String>();

        ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
        ArrayBuilder.addToList(stringListA, "Ten", "Eleven", "Twelve");
        List<List<String>> listOfStringLists = new ArrayList<List<String>>();
        ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);

        ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
    }
}
复制代码

6. 对资源的自动回收管理

复制代码
    // JDK1.7之前的做法,需要在finally块中关闭相关资源
    String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
            if (br != null)
                br.close();
        }
    }

    // JDK1.7中已经不需要手工关闭这些资源了,JRE自动关闭这些资源。
    // 一个对象实现了java.lang.AutoCloseable接口,或者是包含的所有对象实现了java.io.Closeable接口,即可以作为一个资源来使用。
    String readFirstLineFromFile(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }

    @Test
    public void testAutoClose() throws Exception {
        String path = "D:\\TDDOWNLOAD\\readme.txt";
        
        System.out.println(readFirstLineFromFileWithFinallyBlock(path));
        System.out.println(readFirstLineFromFile(path));
    }
复制代码

7. 多个异常的合并与重抛异常的检查

7.1 捕捉多个异常

看下面这段代码:

复制代码
catch (IOException ex) {
     logger.log(ex);
     throw ex;
catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}
复制代码

在JDK1.7中,上述代码可以改写为:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

7.2 重抛异常

看下面这段代码:

复制代码
  static class FirstException extends Exception { }
  static class SecondException extends Exception { }

  public void rethrowException(String exceptionName) throws Exception {
    try {
      if (exceptionName.equals("First")) {
        throw new FirstException();
      } else {
        throw new SecondException();
      }
    } catch (Exception e) {
      throw e;
    }
  }
复制代码

在之前 JDK版本中,它不可以:throws FirstException, SecondException。而在JDK1.7中,它可以了,如下:

复制代码
  public void rethrowException(String exceptionName)
  throws FirstException, SecondException {
    try {
      // ...    }
    catch (Exception e) {
      throw e;
    }
  }
复制代码
 
注意:try/catch中throw的是Exception,但在函数头中,却是:throws  FirstException, SecondException。这在之前的版本中编译是通不过的。
 
 
 

转载于:https://www.cnblogs.com/YcYYcY/p/3584891.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值