《Java核心技术卷一》读书笔记(二)

第六章——接口、lambda表达式与内部类

1.接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义,初衷是将接口作为抽象规范。

2.(1)接口中的所有方法自动地属于public abstract,所以在接口中声明方法时不必提供关键字public和abstract,但在实现接口的类中,应该把方法设为public。一般将静态方法放在接口的伴随类中,而不是接口中。在标准库中,会成对出现接口和实用工具类,如 Collection/Collections或Path/Paths。之前接口不能有静态方法,但Java SE 8中,允许在接口中增加静态方法。

(2)接口不能有实例域(不是static的域),但可以包含常量,可以将接口看成没有实例域的抽象类。与接口中的方法都自动地被设置为 public abstract一样,接口中的域将被自动设为 public static final。可以将接口方法标记为public abstract,将域标记为public static final,但Java语言规范建议不要书写这些多余的关键字

3.如果想使用Arrays.sort()方法对对象数组排序,需要对象对应的类实现Comparable<类名>接口,并实现方法int compareTo()。

4.注意在继承过程中compareTo方法可能会出现错误,有两种解决方法,见P234。(待学习)

5.不能使用new运算符实例化一个接口,即不能构造接口的对象(new),但能声明接口的变量,而且应该引用实现了接口的类对象。

6.可以使用 instanceof检查一个对象是否属于某个特定类或其子类,也可以使用 instanceof 检查一个对象是否实现了某个特定的接口。

7.注意学习默认方法解决冲突,待学习P239。如果一个类扩展了一个超类, 同时实现了一个接口,并从超类和接口继承了相同的方法,这种情况下只会考虑超类方法。注意不要让一个默认方法重新定义 Object 类中的某个方法,如toString或equals方法。

8.比较器是实现了Comparator 接口的类的实例,Arrays.sort(数组名, 比较器);可以用于对数组排序。

9.克隆通常是浅拷贝(两个对象引用引用同一个对象),在子对象不会改变的时候是可行的,但一般子对象都会改变,因此需要深拷贝。

10.Cloneable 接口是Java提供的一组标记接口之一,标记接口不包含任何方法,它唯一的作用就是允许在类型查询中使用 instanceof,建议不要在程序中使用标记接口:

if (obj instanceof Cloneable) . . .

即使 clone 的默认(浅拷贝)实现能够满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone()。

11.所有数组类型都有一个 public 的 clone 方法,而不是 protected;可以用这个方法建立一个新数组,包含原数组所有元素的副本。

12.带参数变量的表达式被称为 lambda 表达式。Java 中的一种 lambda 表达式形式:(参数)+箭头(->) 以及一个表达式,如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在括号{}中,可能会包含显式的return语句。就算没有参数,也要有圆括号()。如果可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型。lambda 表达式的返回类型总是会由上下文推导得出。

13.对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口,在Java中,对lambda表达式所能做的也只是能转换为函数式接口。想要用 lambda 表达式做某些处理,还是要谨记表达式的用途,为它建立一个特定的函数式接口。

14.了解一下方法引用(待学习)。类似于lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例。

15.在Java中,lambda表达式就是闭包。在 lambda 表达式中,只能引用值不会改变的变量,不管是在外部还是内部都不能改变。在 lambda 表达式中声明与一个局部变量同名的参数或局部变量是不合法的。在方法中,不能有两个同名的局部变量,因此lambda表达式中同样也不能有同名的局部变量。

16.注意学习通过调用Comparator接口的一些静态方法来创建比较器,便于用于Arrays.sort方法。待学习

17.内部类中声明的所有静态域都必须是final的,内部类最好没有静态方法,但也可以有静态方法,只能访问外围类的静态域和方法。

18.P266注释部分,先要编译v1ch06/innerClass目录下的InnerClassTest.java文件,得到InnerClassTest.class、TalkingClock.class、TalkingClock$TimePrinter.class文件,然后cd到v1ch06目录执行命令:

javap -private innerClass.TalkingClock\$TimePrinter

出现:

Compiled from "InnerClassTest.java"
public class innerClass.TalkingClock$TimePrinter implements java.awt.event.ActionListener {
  final innerClass.TalkingClock this$0;
  public innerClass.TalkingClock$TimePrinter(innerClass.TalkingClock);
  public void actionPerformed(java.awt.event.ActionEvent);
}

注:因为使用UNIX系统,所以要将$字符进行转义,符号前加“\”。

18.局部内部类(一般声明在方法中)不能用public或private访问说明符进行声明,其优势是对外部世界可以完全地隐藏起来,即除了外围类中声明该局部内部类的方法,其他的代码都不能访问它。在java SE 8之前,局部类的方法只可以引用定义为 final 的局部变量。

19.匿名内部类没有类名,因此不能有构造器。以前用匿名内部类实现事件监听器和其他回调,现在最好使用lambda表达式。

20.在内部类不需要访问外围类对象的时候,应该使用静态内部类。与常规内部类不同,静态内部类可以有静态域和方法。声明在接口中的内部类自动成为 static 和 public 类。

第八章——泛型程序设计

1.泛型对于集合类尤其有用,例如ArrayList就是一个无处不在的集合类。

2.一个泛型类就是具有一个或多个类型变量的类。用具体的类型替换类型变量就可以实例化泛型类,泛型类可以看作普通类的工厂。

3.类型变量使用大写形式且比较短,在Java库中使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T(需要时还可以用临近的字母U和S)表示“任意类型”。

4.泛型方法可以定义在普通类中,也可以定义在泛型类中。

5.泛型方法的名字前返回类型前的类型变量,如果要限制的话,应该是,其中T和OtherType可以是类也可以是接口,限定类型可以有多个,用符号&分隔,逗号用来分隔类型变量。限定类型中至多有一个类,如果用一个类作为限定类型,它必须是限定列表中的第一个,而且应该将标签(tagging)接口(没有方法的接口)放在边界列表的末尾。记住有关 Java 泛型转换的事实P337。桥方法覆盖了超类同签名的方法,具体出现在翻译泛型方法时。

6.@SuppressWarnings(“unchecked”)注解会关闭对方法中所有代码的检査。?待实践

7.不允许创建参数化类型的数组,如Pair[] table = new Pair[10];但声明类型为Pair[] 的变量是合法的,只是不能用 new Pair[10] 初始化这个变量。如果需要收集参数化类型对象(的数组?),只有一种安全而有效的方法:使用ArrayList:ArrayList<Pair>。

8.注意P340的Varargs警告部分,注释@SafeVarargs直接标注方法(Java SE 7之后)或者为包含出现错误的方法的方法增加注解@SuppressWarnings(“unchecked”)。

9.Class类本身是泛型,例如,String.class 是Class唯一的实例。

10.禁止使用带有类型变量(T等)的静态域和方法。

11.不能抛出或捕获泛型类的实例,泛型类继承/扩展Throwable是不合法的,并且catch子句中不能有类型变量(T等)

12.注意第8.6.9小节,和异常有关(待学习)。

13.pair类之间没有任何关系:无论S与T有什么联系,通常Pair<S>Pair<T>没有什么联系。但永远可以将参数化类型转换为一个原始类型,如:Pair<Employee>是原始类型Pair的一个子类型。泛型类可以扩展或实现其他的泛型类。

14.引入有限定的通配符可以调用安全的访问器方法,不能调用不安全的更改器方法(会出现编译错误);带有超类型限定的通配符可以为方法提供参数,但不能使用返回值。总之,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

15.ArrayList类有一个removelf方法,它的参数就是一个接口Predicate<? super E>,这个接口专门用来传递lambda表达式。即可以用以下表达式:list.removelf(e -> e == null);代替list.removeIf(Predicate<? super E>)。或者

Predicate<Object> some= e -> e == null;
list.remove(some);

16.通配符不是类型变量,所以不能在编写代码中使用“ ?” 作为一种类型。

17.要想了解有关 Java 泛型更加详尽的信息,可以到该网站上求助。

第九章——集合

1.如果需要一个循环数组队列,可以使用ArrayDeque类。如果需要一个链表队列,使用LinkedList类,都实现了Queue接口。

2.如果想要实现自己的队列类,扩展AbstractQueue类要比实现Queue接口中的所有方法轻松得多。

3.在Java 类库中,集合类的基本接口是Collection接口。“ for each” 循环可以与任何实现了 Iterable 接口的对象一起工作,Collection 接口扩展了Iterable接口。因此,对于标准类库中的任何集合都可以使用“ for each” 循环。

4.应该将Java迭代器认为是位于两个元素之间,当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。

5.Iterator接口的remove方法将会删除上次调用next方法时返回的元素。对next方法和remove方法的调用具有互相依赖性。如果调用remove之前没有调用next将是不合法的。如果这样做,将会抛出一个IllegalStateException异常。

6.为了能够让实现者更容易地实现Collection接口,Java类库提供了一个类AbstractCollection,它将基础方法size和iterator抽象化了,有些方法被实现了,这样一个具体的集合类可以扩展AbstractCollection类了。

7.集合框架的接口:
在这里插入图片描述

集合有两个基本接口:Collection和Map。注意map和collection不同之处。

8.list是有序集合,可以采用两种方式访问元素:使用迭代器访问(顺序地访问元素),或者使用一个整数索引(随机访问,可以按任意顺序访问元素)来访问。

9.Listlterator接口是Iterator的一个子接口,它定义了一个方法用于在迭代器位置前面增加一个元素:void add(E element);

10.标记接口RandomAccess不包含任何方法,但可以用它来测试一个特定的集合是否支持高效的随机访问。形式:

if(c instanceof RandomAccess){...

}else{...

}

11.Set接口相当于Collection接口,不过更严格,比如其add方法不允许添加重复的元素。

12.SortedSet和SortedMap接口会提供用于排序的比较器对象,这两个接口定义了可以得到集合子集视图的方法。(?9.4节详细了解)

13.Java SE 6 引入了接口NavigableSet和NavigableMap,其中包含一些用于搜索和遍历有序集和映射的方法。(理想情况下,这些方法本应当直接包含在SortedSet和SortedMap接口中)TreeSet和TreeMap类实现了这些接口。

14.集合框架中的类:

在这里插入图片描述

注:ArrayQueue应该是ArrayDeque,实现了Queue接口和Deque接口(继承了Queue接口)。

15.在Java中,所有链表实际上都是双向链接的 (doubly linked) ——即每个结点还存放着指向前驱结点的引用。LinkedList实现了List接口,是一个有序集合。

(1)对有序的集合使用迭代器添加元素,Listlterator接口添加了List接口中没有的、依赖于位置的add方法,即这个接口中的add方法可以在next或previous方法后使用,而不是默认的添加在列表尾部。

(2)LinkedList 类的 listlterator 方法返回一个实现了 Listlterator 接口的迭代器对象,该接口有两个方法可以反向遍历链表。

(3)迭代器处于两个元素之间,add方法只依赖于迭代器的位置,在迭代器当前位置前插入元素;而remove方法依赖于迭代器的状态,调用next方法后,调用remove方法是删除迭代器左边的元素,而调用previous方法后,调用remove方法是删除迭代器右边的元素,注意不能连续调用两次remove方法。

(4)为了避免发生并发修改的异常,需要遵循下述规则:可以根据需要给容器附加许多迭代器,但是这些迭代器只能读取列表,另外再单独附加一个既能读又能写的迭代器。

(5)注意链表只负责跟踪对列表的结构性修改,不能有多个迭代器同时为列表添加元素、删除元素,但set方法不被视为结构性修改,可以为一个链表生成许多迭代器,所有的迭代器都调用set方法对现有结点的内容进行修改。

(6)虽然LinkedList类提供了一个用来访问某个索引对应元素的get方法(做了微小的优化:如果索引大于 size() / 2 就从列表尾端开始搜索元素),但效率很低,每次査找一个元素都要从列表的头部重新开始搜索,所以在程序需要采用整数索引访问元素时,通常不用链表。

(7)如果有一个整数索引n,,list.listlterator(n) 将返回一个指向索引为n的元素前面位置的迭代器,效率很低。

(8)如果列表只有少数几个元素,可以使用ArrayList;如果有很多元素,并且增加删除的操作也比较频繁,可以用LinkedList。

(9)避免使用以整数索引表示链表中位置的所有方法,如果需要对集合进行随机访问,应该使用数组或 ArrayList,而不是使用链表。

16.ArrayList类实现了List接口,并且封装了一个动态再分配的对象数组(有序集合)。建议在不需要同步时使用ArrayList,需要同步时使用Vector。

17.自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)为true,a与b必须具有相同的散列码。

18.java中的散列表用链表数组实现,数组中的每个可以存储元素的位置称为桶(bucket)。在Java SE 8 中, 桶满时会从链表变为平衡二叉树,桶数是指用于收集具 有相同散列值的桶的数目。注意装填因子默认值是0.75,如果超过,这个表就会用双倍的桶数自动地进行再散列。散列表可以用于实现几个重要的数据结构。 其中最简单的是 set 类型,只有不关心集合中元素的顺序时才应该使用 HashSet。

19.TreeSet实现SortedTree接口,其中元素的排序是用树结构完成的,当前使用的是红黑树(待学习),迭代器总是以排好序的顺序访问每个元素。将元素添加到树集中比添加到散列集中更慢。使用树集时元素一定要能够比较,即实现了Comparable接口。从Java SE 6 起,TreeSet类实现了NavigableSet接口,这个接口增加了几个便于定位元素以及反向遍历的方法。

20.优先级队列(priority queue)没有对所有的元素进行排序,而是使用了一种数据结构——堆(一个可以自我调整的二叉树,对树执行添加(add)和删除(remove)操作可以让最小的元素移动到根,而不用花费时间对元素进行排序)。

(1)无论何时调用remove方法,总会获得当前优先级队列中最小的元素。

(2)与TreeSet中的迭代不同,优先级队列的迭代器并不是按照元素的排列顺序访问的(随机访问?),而删除却总是删掉剩余元素中优先级数最小(按照排序顺序来)的那个元素。

21.两个常见的映射(map)类:HashMap和TreeMap,都实现了Map接口,HashMap对键进行散列, TreeMap用键的整体顺序对元素进行排序,并将其组织成搜索树,散列或比较函数只能作用于键,与键关联的值不能进行散列或比较。map的键值可以是null,注意getOrDefault方法的调用。

22.要迭代处理map对象的键和值,最容易的方法是使用forEach方法,提供一个接收键和值的lambda表达式。映射中的每一项会依序调用这个表达式,如:

scores,forEach((k, v) -> System.out.println("key=" + k + ", value:" + v));

23.对map对象连续调用两次put方法,如果键已经存在,第二次的值会覆盖第一次的值,并且put方法将返回键对应的旧值,如果这个键以前没有出现过则返回值是null。

24.注意putlfAbsent(k, defaultValue)方法的使用,预防get方法查询键对应的值时,键不存在的情况。merge方法更有效,如:

counts.merge(word, 1, Integer::sum);

如果键word是第一次出现,merge方法会把word与1关联,否则使用Integer::sum函数组合原值和1(也就是将原值与1求和),如果结果是null,则删除键word,否则返回get(word)。

25.集合框架不认为映射本身是一个集合,但可以得到映射的视图(View):实现了Collection接口或某个子接口的对象。有3种视图:键集(Set keySet(); 其返回的键集不是HashSet或TreeSet,而是实现了Set接口的另外某个类的对象)、 值集合(不是一个集)(Collection values();)、键/值对集(Set<Map.Entry<K, V>> entrySet();)。以前通过访问键/值对集中的entry,是访问所有map条目(entry)的最高效的方法,但现在只需要用forEach方法:

counts.forEach((k,v) -> { 
    do somethingwith k, v 
})

如果在键集、值集合、键/值对集调用迭代器的remove方法,实际上会从map中删除这个键和与它关联的值,但不能对这三个视图添加元素。

26.对于WeakHashMap(使用弱引用(weak references)保存键(散列键?)),当对键的唯一引用来自散列条目时,弱散列映射会配合垃圾回收器一起删除键/值对。(不太理解,待学习)

27.LinkedHashSet类用于记住插入元素项的顺序,输出其中元素时(如使用迭代器)会按照插入顺序输出。

28.LinkedHashMap类将用访问顺序(?),而不是插入顺序,对映射条目进行迭代(不知道是不是翻译有误还是我的理解能力不够,和API对照着看有点奇怪,待研究)。最常用的元素被放到双向链表的尾部,但对应的“桶”不会变,因为散列值没有变化,最终结果是最近最少使用的元素在链表前面?如果LinkedHashMap代表缓存,方法removeEldestEntry可以让map通过删除旧条目来减少内存消耗。

29.在对两个对象进行比较时,IdentityHashMap类使用==,而不使用equals方法,即对于不同的键对象,即使内容相同,也被视为是不同的对象,在实现对象遍历算法(如对象串行化)时,这个类可以用于跟踪每个对象的遍历状况(待学习)。

30.map的keySet方法返回一个实现Set接口的类对象,这个类的方法对原映射进行操作,这种类称为视图。

31.Arrays类的静态方法asList将返回一个包装了普通Java数组的List包装器(wrapper),不是ArrayList对象,而是一个视图对象,带有访问底层数组元素的get和set方法,但改变数组大小的所有方法,如迭代器的add和remove方法都不能使用(?)。

32.注意Collections.nCopies方法的使用(返回一个实现了list接口的不可修改对象),Collections类包含对集合进行操作或返回集合的静态方法,不要混淆Collections类和Collection接口。

33.构建子范围视图时两个索引参数中,包含第一个索引,不包含第二个索引,可以将任何操作应用于子范围,并且能够自动地反映整个列表的情况(可修改视图)。

34.Collections类有一些静态方法,用于产生集合的不可修改视图,这些视图对现有集合增加了一个运行时的检查,如果发现视图对集合进行修改,就抛出一个异常并且同时这个集合将保持未修改的状态。

35.Collections.unmodifiableList方法将返回一个实现List接口的类对象(不可修改视图),不可修改视图只是包装了接口而不是实际的集合对象,所以只能访问接口中定义的方法(不能访问更改器方法),如,LinkedList类有一些方法:addFirst和addLast,都不是List接口的方法,不能通过不可修改视图进行访问。

36.Conllections类的unmodifiableCollection方法(以及synchronizedCollection方法和checkedCollection方法)返回的集合的equals方法继承了Object类的equals方法,就是判断两个对象引用是否是同一个地址(==),而不是判断内容是否相同,hashCode方法也是同样的方法处理,但unmodifiableSet方法和unmodifiableList方法返回的对象使用的是Set或List的equals方法和hashCode方法。

37.Collections类通过调用静态方法(如:checkedList等)产生的“受査”视图(可修改视图)用来对泛型类型发生问题时提供调试支持,如避免出现将错误类型的元素混入泛型集合中的问题。如果插入一个错误类型的元素,视图的方法拋出错误:ClassCastException。

38.注意:集合视图存在一些可选方法和不可选方法,细节查看API。

39.对于实现了list接口的集合进行排序,可以用list接口的sort方法并传入一个Comparator对象,或者使用Collections类的sort方法(列表元素必须实现Comparable接口)。如果想对列表进行降序,可以如下(staff是列表),这个方法将根据元素类型的compareTo方法(实现了Comparable接口的CompareTo方法)给定排序顺序,按照逆序对列表staff进行排序:

staff.sort(Comparator.reverseOrder());

40.java对列表的(归并)排序是将所有元素转入一个数组,对数组进行排序,再将排序后的序列复制回列表。归并排序的优点——稳定, 不需要交换相同的元素。

41.将列表(集合?)对象(实现了list接口的子类对象)作为参数传递给算法时,列表必须是可修改的(支持set方法),但不必是可以改变大小的(不能支持add和remove方法)。

42.Collections类的算法(静态方法)shuffle的功能与sort相反,是随机地混排列表中元素的顺序。如果提供的列表参数没有实现RandomAccess接口(可以随机访问元素),shuffle方法将元素复制到数组中,然后打乱数组元素的顺序,最后再将打乱顺序后的元素复制回列表。

修改原书代码v1ch09/shuffle/ShuffieTest.java,证明子视图的操作会改变原视图的数据:

package shuffle;

import java.util.*;

/**
 * This program demonstrates the random shuffle and sort algorithms.
 * @version 1.11 2012-01-26
 * @author Cay Horstmann
 */
public class ShuffleTest
{
   public static void main(String[] args)
   {
      List<Integer> numbers = new ArrayList<>();
      for (int i = 1; i <= 49; i++)
         numbers.add(i);
      Collections.shuffle(numbers);
      System.out.println("old" + numbers);
      List<Integer> winningCombination = numbers.subList(0, 6);
      Collections.sort(winningCombination);
      System.out.println(winningCombination);
      System.out.println("new" + numbers);
   }
}

结果:
在这里插入图片描述

43.Collections类的binarySearch方法实现了二分查找算法,作为参数的集合必须已经排好序、并且实现了list接口,如果集合没有采用Comparable接口的compareTo方法进行排序,就还要提供一个比较器对象(Comparator)。

(1)只有采用随机访问,二分査找才有意义,所以如果为binarySearch方法提供一个链表,链表将自动地变为线性查找。

(2)binarySearch方法的时间复杂度为O(a(n) logn), n 是列表的长度,a(n)是访问一个元素的平均时间。这个方法将返回要查找的元素在列表中的索引,如果在列表中不存在这个键将返回负值i,在这种情况下,应该将这个键插入到列表索引(-i-1)的位置上,以保持列表的有序性。

44.Collections类的replaceAll方法,第一个参数是要替换的对象,第二个参数是替换的对象。注意Collection接口的removeIf方法和List接口的replaceAll方法,是java SE 8加入的,方法的参数是一个lamda表达式,replaceAll方法的参数是对列表中的每个元素执行一个操作。

45.Collections类中有很多针对实现list接口的类的简单算法,待学习,多查API。

46.coll1.retainAll(coll2);会从coll1(一个集合对象)中删除所有未在coll2(一个集合)中出现的元素。这种方法可以用于生成交集。

47.Arrays.asList包装器可以将数组转换成列表,hashSet类初始化时可以将任意实现Collection接口的类作为构造器的参数进行传递,如实现list接口的对象引用。

48.toArray方法可以将集合转换成数组,不过只能是Object数组,不能用强制类型转换成对应的数组,验证代码:

package set;

import java.util.Arrays;
import java.util.HashSet;

public class Test413 {
    public static void main(String[] args){
        String[] values = new String[]{"one", "two", "three"};
        HashSet<String> staff = new HashSet<>(Arrays.asList(values));
        //String[] valueOne = (String[]) staff.toArray(); // 错误
        // 正确做法
        String[] value = staff.toArray(new String[0]);
        for(String v: value)
            System.out.println(v);
        // 或构造指定大小的数组
        String[] test = staff.toArray(new String[staff.size()]);
        for(String t: test)
            System.out.println(t);
    }
}

如果staff.toArray(new String[staff.size()]);没有将返回值赋给字符串数组,将不会产生新的数组,结果:

test413

49.注意编写算法时,应尽量使用接口,而不是具体实现。

50.集合框架中的遗留类:
在这里插入图片描述

51.与Vector类的方法一样,Hashtable类(实现了Map接口)的方法也是同步的,如果对同步性或与遗留代码的兼容性没有任何要求,就应该使用HashMap,如果需要并发访问,则要使用ConcurrentHashMap。

52.遗留的Enumeration接口是以前对泛型集合遍历的唯一机制,以前java版本写的代码可能会出现。

53.实现属性映射(property map)的Java平台类称为Properties,属性映射通常用于程序的特殊配置选项(常用?)。

54.将Vector类扩展成为Stack类,加入了不属于栈操作的方法:insert和move方法,即可以在任何地方进行插入或删除操作,而不仅仅是在栈顶。

55.BitSet类用于存放一个位序列,如果需要高效地存储位序列(如标志)就可以使用位集(BitSet),由于位集将位包装在字节里,所以使用位集要比使用Boolean对象的ArrayList更加高效(重点,待学习)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值