集合类再探

注:本文使用的pom依赖见文末。

java语言层面支持对实现了Iterable接口的对象使用for-each语句。Iterator可以实现有限流和无限流。

Collection类定义了基本的增删改查操作,转向基本数组类型(toArray),1.8引入了stream操作。

可变与不可变

不可变集合看似是限制,但是其会极大简化了编程的心理负担。

心理负担举例:

我们使用一个List对象,对其修改的操作必须小心翼翼,因为宽接口的问题,add之类的操作很可能不支持。

stream 操作在其他类库上不一定有效,因为default方法不一定适用于所有子类。

一个集合对象作为方法的入参,有可能被方法修改,而这种修改我们很难轻易地理解,需要阅读代码或者注释。一个方法不能复用常常是因为添加了过多的副作用,而这种副作用暗含其中,为我们的项目添加了一颗颗隐形炸弹。注释的产生只能说明代码设计存在一定的缺陷,优秀的代码应该减少不必要的注释,显然对于副作用,我们必须要显著说明,比如可能抛出的异常。

  1. ImmutableList<String> list = ...
  2. foo(list)
  3. boo(list)
  4. zoo(list)
  5. doSomethingWith(list)
  6. // 如上的几个方法互不影响,可以继续放心地使用 list
  7. // 如果list的类型是List,这几个方法的入参很可能都不一样

guava 和很多其他工具类都是按照这种思想设计的:

  1. // Guava
  2. // builder 模式
  3. ImmutableList<Integer> list = ImmutableList.<Integer>builder()
  4. .add(1)
  5. .add(2)
  6. .addAll(otherList)
  7. .build();
  8. // 静态工厂
  9. ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);
  10. // shallow copy
  11. ImmutableList<Integer> list = ImmutableList.copyOf(new Integer[]{1, 2, 3});

协变的意思是对象的继承会在集合的维度上传递,不可变类型由于不支持修改,对于协变的支持理所当然。

Java不支持类定义时定义协变,只支持使用集合对象时使用通配符,所以我们能在许多方法上看到泛型通配符。

  1. / # Guava.ImmutableList
  2. public static <E> ImmutableList<E> copyOf(Collection<? extends E> elements) {
  3. if (elements instanceof ImmutableCollection) {
  4. @SuppressWarnings("unchecked") // all supported methods are covariant
  5. ImmutableList<E> list = ((ImmutableCollection<E>) elements).asList();
  6. return list.isPartialView() ? ImmutableList.<E>asImmutableList(list.toArray()) : list;
  7. }
  8. return construct(elements.toArray());
  9. }
  10. // elements 入参后,如果不进行修改,可以@SuppressWarnings("unchecked"),直接转换类型为不变,方便后续使用。
  1. // code1
  2. // 请思考这段代码的运行结果
  3. Random random = new Random();
  4. List<Integer> list = random.ints(6L).boxed().collect(Collectors.toList());
  5. System.out.println("list = " + list);
  6. List<Integer> subList = list.subList(0, 3);
  7. System.out.println("subList = " + subList);
  8. Collections.sort(list);
  9. System.out.println("list = " + list);
  10. System.out.println("subList = " + subList);
  11. // 以上代码的运行结果
  12. /**
  13. list = [40, 60, 28, 4, 83, 90]
  14. subList = [40, 60, 28]
  15. list = [4, 28, 40, 60, 83, 90]
  16. Exception in thread "main" java.util.ConcurrentModificationException
  17. **/
  18. // 我们发现:subList这个变量在sort操作之后,不能使用了
  19. // code2
  20. Random random = new Random();
  21. List<Integer> _list = random.ints(6L, 0, 100).boxed().collect(Collectors.toList());
  22. ImmutableList<Integer> list = ImmutableList.copyOf(_list);
  23. System.out.println("list = " + list);
  24. List<Integer> subList = list.subList(0, 3);
  25. System.out.println("subList = " + subList);
  26. Collections.sort(list);
  27. System.out.println("list = " + list);
  28. System.out.println("subList = " + subList);
  29. // 以上代码的运行结果
  30. /**
  31. list = [22, 34, 50, 49, 93, 49]
  32. subList = [22, 34, 50]
  33. Exception in thread "main" java.lang.UnsupportedOperationException
  34. at com.google.common.collect.ImmutableList.sort(ImmutableList.java:581)
  35. at java.util.Collections.sort(Collections.java:141)
  36. **/
  37. // 虽然编译通过了,但是 list 禁止了修改,同时由于没有直接调用list.sort()方法,在运行前我们无法获取编译的提示。
  38. // 使用 list.~~sort~~(null); 会得到 IDEA inspection 提示,因为Immutable类的sort标注为了@Deprecate
  39. // code3
  40. Random random = new Random();
  41. List<Integer> _list = random.ints(6L, 0, 100).boxed().collect(Collectors.toList());
  42. ImmutableList<Integer> list = ImmutableList.copyOf(_list);
  43. System.out.println("list = " + list);
  44. List<Integer> subList = list.subList(0, 3);
  45. System.out.println("subList = " + subList);
  46. ImmutableList<Integer> sortedList = list.stream().sorted().collect(ImmutableList.toImmutableList());
  47. System.out.println("list = " + list);
  48. System.out.println("subList = " + subList);
  49. System.out.println("sortedList = " + sortedList);
  50. ImmutableList<Integer> sortedSubList = sortedList.subList(0, 3);
  51. System.out.println("sortedSubList = " + sortedSubList);
  52. // 以上代码的运行结果
  53. /**
  54. list = [53, 7, 69, 5, 23, 7]
  55. subList = [53, 7, 69]
  56. list = [53, 7, 69, 5, 23, 7]
  57. sortedList = [5, 7, 7, 23, 53, 69]
  58. subList = [53, 7, 69]
  59. sortedSubList = [5, 7, 7]
  60. **/
  61. // 可以看出一旦确定list, subList,不管后续进行如何复杂的操作,其值都不变。
  62. // 使用安全的方法,stream(), sorted(), collect()等,可以保证方法无副作用。
  63. // Collections.sort(List<T> list) 方法有副作用

其实 IDEA 已经为我们提供了相关的提示:

我们可以在@Contract注解中看到,入参list被修改了。同时注释里表明了入参、出参、以及可能的异常。Implementation Note 给出了提示。

Collector 接口

  1. // A: 容器, T: 源类型, R: 最终类型(一般为T)
  2. public interface Collector<T, A, R> {
  3. Supplier<A> supplier();
  4. BiConsumer<A, T> accumulator();
  5. BinaryOperator<A> combiner();
  6. Function<A, R> finisher();
  7. Set<Characteristics> characteristics();
  8. }

简单来说,Collectors 对集合类型进行了reduce运算,supplier提供容器,accmulater 添加元素到容器,combiner 联结多个容器,也就是说,reduce

可以分组进行运算,每个组为一个容器,然后合并各个容器,·finisher进行最终运算,一般为不可变类型的再封装,比如将 List 封装为 ImmutableList。characteristics 指定了Collector的特性,包括

CONCURRENT, UNORDERED, IDENTITY_FINISH,我们忽略CONCURRENT,因为

  1. 多线性编程的复杂性,不推荐使用 Stream 做多线程处理。Stream流处理进行多线程需要调优,默认使用的commonPool,不好控制,commonPool适用于计算密集型任务。
  2. Stream 不适合做精细控制,不好调试。
  3. 不要过早调优。绝大部分情况下不要使用多线程。
  4. 就算需要使用多线程,还不如直接使用线程安全类,对集合进行迭代处理。
  5. 加个parallel 不一定增加性能,最好会编写 Spliterator

Collectors 工具类提供了collector, 常用的有以下一些:

  • toMap 转换为map
  • groupingBy 分组,返回结果为Map<K, Collection>
  • partition 分成两组,返回结果为Map<Boolean, Collection>
  • toCollection
  • toList
  • toSet PS: 如果一个容器可以是集合,那么就应该使用 Set,而不是所有的集合类都用 List 表示。
  • joining 字符串拼接

一些常用的工具方法如下,通常用来当做中间步骤:

  • collectingAndThen 添加 finisher,常用来创建ImmutableCollection
  • mapping(Function mapper, Collector downstream) 实现多层收集,如注解中的示例:
  1. public class MultiLayerStreamDemo {
  2. public static void main(String[] args) {
  3. Map<City, Set<String>> lastNamesByCity
  4. = people.stream().collect(groupingBy(Person::getCity,
  5. mapping(Person::getLastName, toSet())));
  6. }
  7. }

其他的方法几乎不用,甚至可以用其他的方法代替:

  • summarizingInt/Long/Double() 返回统计数据,包括sum,average, max, min;很多工具类可以直接计算,比如Ints
  • averagingInt 返回平均值,很多工具类就可以完成
  • maxBy 返回最大值,Stream自己就带有max,min方法
  • counting 计数,因为 Stream 流只能用一次,所以不常用;不如直接转换为集合再调用size方法。
  • reduce Stream自己就带有reduce 方法

BUT,标准库的缺陷

虽然我们可以创建List, Map<K, Collection<V>>, Optional<T>(reduce创建)等容器类,但是标准库提供的能力有限。 对于不可变类型,我们一般创建为 ImmutableCollection

;对于一些容器,我们可以用更精确的容器类来描述; collect 可以作为不同容器的转换方法:

SpringData

  • Stream => Streamable(支持Iterator接口)

Guava 类库

  • List => ImmutableList
  • Map<K, Collection> => Multimap<K, V>
  • Map<K, Integer> => Multiset

vavr 类库

  • List => io.vavr.collection.List(不可变链表)
  • Map => io.vavr.collection.Map(不可变Map)
  1. public class CountDemo {
  2. public static void main(String[] args) {
  3. String[] words = Stream.generate(new Faker().food()::vegetable)
  4. .limit(100)
  5. .toArray(String[]::new);
  6. String s = "Carrot";
  7. Map<String, Integer> counts = map1(words);
  8. System.out.println("counts = " + counts);
  9. System.out.println("counts.get(s) = " + counts.get(s));
  10. ImmutableMultiset<String> counts2 = map7(Arrays.asList(words));
  11. System.out.println("counts2 = " + counts2);
  12. System.out.println("counts2.count(s) = " + counts2.count(s));
  13. }
  14. // 1. 使用map基本方法迭代
  15. @NotNull
  16. public static Map<String, Integer> map1(String[] words) {
  17. Map<String, Integer> counts = new HashMap<>();
  18. for (String word : words) {
  19. Integer count = counts.get(word);
  20. if (count == null) {
  21. counts.put(word, 1);
  22. } else {
  23. counts.put(word, count + 1);
  24. }
  25. }
  26. return counts;
  27. }
  28. // 2. 使用 merge 方法
  29. @NotNull
  30. public static Map<String, Integer> map2(String[] words) {
  31. Map<String, Integer> counts = new HashMap<>();
  32. for (String word : words) {
  33. counts.merge(word, 1, Integer::sum);
  34. }
  35. return counts;
  36. }
  37. // 3. forEach 迭代,不推荐
  38. @NotNull
  39. public static Map<String, Integer> map3(Iterable<String> words) {
  40. Map<String, Integer> counts = new HashMap<>();
  41. words.forEach(word ->
  42. counts.merge(word, 1, Integer::sum)
  43. );
  44. return counts;
  45. }
  46. // 4. Stream + Collector
  47. @NotNull
  48. public static Map<String, Long> map4(Iterable<String> words) {
  49. return Streamable.of(words).stream()
  50. .collect(groupingBy(it -> it, counting()));
  51. }
  52. // 5. Stream + 自定义 Collector
  53. @NotNull
  54. public static ImmutableMap<String, Integer> map5(Iterable<String> words) {
  55. return Streamable.of(words).stream()
  56. .collect(toCountMap());
  57. }
  58. @NotNull
  59. public static <T> Collector<T, ?, ImmutableMap<T, Integer>> toCountMap() {
  60. Collector<T, ?, Map<T, Integer>> countCollector = groupingBy(it -> it, countInt());
  61. return collectingAndThen(countCollector, ImmutableMap::copyOf);
  62. }
  63. @NotNull
  64. public static <T> Collector<T, ?, Integer> countInt() {
  65. return Collectors.reducing(0, e -> 1, Integer::sum);
  66. }
  67. // 6. Stream + ImmutableMultiset
  68. @NotNull
  69. public static ImmutableMultiset<String> map6(Iterable<String> words) {
  70. return Streamable.of(words).stream()
  71. .collect(toImmutableMultiset());
  72. }
  73. // 7. ImmutableMultiset 直接创建
  74. @NotNull
  75. public static ImmutableMultiset<String> map7(Iterable<String> words) {
  76. return ImmutableMultiset.copyOf(words);
  77. }
  78. }

由以上实现可以看出,方法1为一般实现,可能出错,推荐使用内部迭代(不自己控制迭代过程),如果有工具类或方法,则不建议自己写(虽然这个例子很简单)

方法2使用了Map::merge方法,这个方法适用于计数和map合并,ConcurrentMap::merge为原子操作

方法3使用了forEach方法,只在生产者-消费者模型、日志打印时推荐使用,遍历Map对象时也可以用

方法4使用标准库的工具方法,缺点是计数类型为Long,不是我们想要的

方法5为自己编写的 Collector,基本思路是分组计数,然后用ImmutableMap包装

7最简单,若在Stream流中进行filter、map、flatMap等运算,可使用方法6

总之,实际应用时建议使用Immutable类型,对于实际问题,应用对应具体的模型,我们使用counts时,面向的是接口Multiset或抽象类ImmutableMultiset, 封装了我们需要使用的方法,不易出错。

以下是一个利用collector机制编写的排行榜的简单实现。

  1. public class TopKCollectorDemo {
  2. public static void main(String[] args) {
  3. List<Integer> list = new Random().ints(100, 0, 100)
  4. .boxed().collect(Collectors.toList());
  5. System.out.println("list = " + list);
  6. System.out.println("topK(list, 5) = " + topK(list, 5));
  7. }
  8. private static class FixSizePQ<E extends Comparable<E>> extends PriorityQueue<E> {
  9. private final int sz;
  10. public FixSizePQ(int sz) {
  11. super(sz);
  12. assert sz > 0;
  13. this.sz = sz;
  14. }
  15. @Override
  16. public boolean add(E e) {
  17. if (size() == sz)
  18. if (e.compareTo(peek()) > 0) {
  19. poll();
  20. } else {
  21. return true;
  22. }
  23. return super.add(e);
  24. }
  25. }
  26. @Contract(pure = true)
  27. public static <T extends Comparable<T>> ImmutableList<T> topK(Iterable<? extends T> iterable, int k) {
  28. Collector<T, ?, FixSizePQ<T>> tpqCollector = Collector.of(() -> new FixSizePQ<T>(k),
  29. Collection::add,
  30. (r1, r2) -> {
  31. r1.addAll(r2);
  32. return r1;
  33. },
  34. Characteristics.UNORDERED);
  35. return Streams.stream(iterable).collect(
  36. collectingAndThen(tpqCollector, TopKCollectorDemo::toImmutableList));
  37. }
  38. @NotNull
  39. @Contract(pure = true)
  40. private static <T extends Comparable<T>> ImmutableList<T> toImmutableList(PriorityQueue<T> pq) {
  41. List<T> list = new ArrayList<>(pq.size());
  42. while (!pq.isEmpty()) {
  43. list.add(pq.poll());
  44. }
  45. return ImmutableList.copyOf(list).reverse();
  46. }
  47. }

外部迭代与内部迭代可以相互转换

同一个任务可能有多种实现,有时A方法好,有时B方法好,有时两者有差不多,多种实现之间可以相互转换。

  1. public class ToMapDemo {
  2. // 外部迭代
  3. @NotNull
  4. public static Map<String, Integer> map2(String[] words) {
  5. Map<String, Integer> counts = new HashMap<>();
  6. for (String word : words) {
  7. counts.merge(word, 1, Integer::sum);
  8. }
  9. return counts;
  10. }
  11. // IDEA 基于以上方法自动转换成 Stream 运算
  12. @NotNull
  13. public static Map<String, Integer> map2_(String[] words) {
  14. return Arrays.stream(words).collect(toMap(word -> word, word -> 1, Integer::sum));
  15. }
  16. }

如上例,对于words的迭代有外部迭代和内部迭代两种,外部迭代即我们自己控制迭代过程,这里使用的是 for each 形式,还可以使用 with index 形式; 内部迭代由程序自己实现,其迭代过程不受我们直接控制,优点是不易出错。

如果你发现一个Stream流过于复杂,不妨利用IDEA 自动转换为外部迭代方式。

  1. public class ComplicateStreamDemo {
  2. @Value
  3. static class User {
  4. String id;
  5. String name;
  6. String mobile;
  7. public static User generateRandom() {
  8. Faker faker = new Faker();
  9. return new User(faker.idNumber().valid(), faker.name().name(), faker.phoneNumber().cellPhone());
  10. }
  11. }
  12. @Value
  13. static class Pair {
  14. User a, b;
  15. }
  16. public static void main(String[] args) {
  17. User[] users = Stream.generate(User::generateRandom)
  18. .limit(5)
  19. .toArray(User[]::new);
  20. List<Pair> pairs = f1(users);
  21. pairs.forEach(System.out::println);
  22. System.out.println("pairs.size() = " + pairs.size());
  23. }
  24. @NotNull
  25. private static List<Pair> f1(User[] users) {
  26. return Arrays.stream(users)
  27. .flatMap(user1 ->
  28. Arrays.stream(users)
  29. .filter(user2 -> user1 != user2)
  30. .map(user2 -> new Pair(user1, user2))
  31. ).collect(toList());
  32. }
  33. @NotNull
  34. private static List<Pair> getPairs2(User[] users) {
  35. List<Pair> list = new ArrayList<>();
  36. for (User user1 : users) {
  37. for (User user2 : users) {
  38. if (user1 != user2) {
  39. Pair pair = new Pair(user1, user2);
  40. list.add(pair);
  41. }
  42. }
  43. }
  44. return list;
  45. }
  46. private static List<Pair> getPairs3(Iterable<User> users1, Iterable<User> users2) {
  47. return API.For(
  48. users1,
  49. users2
  50. ).yield((a, b) -> a == b ? Option.<Pair>none() : Option.of(new Pair(a, b)))
  51. .flatMap(it -> it)
  52. .toJavaList();
  53. }
  54. }

最开始接触Stream的人会发现f1的可读性没有那么强,其实flatMap可以实现多层for循环以及不同层级的控制(如本例中的filter)。

若将f1转换为f2的话,就一目了然了:方法生成了不同用户间的配对。f1和f2两者属于不同的编程风格,实现了相同的效果。

for comprehension

flatMap 还可以实现将普通方法应用在容器类上实现拆包、枚举、过滤和生成结果序列。 若有函数f,其参数均为普通类型,而 for comprehension 可以将包装类的结果取出,应用到函数上。

如 subtract(int a, int b):

  • a = Optional(1), b = Optional(2) => result = Optional(-1)
  • a = Optional(3), b = Optional.empty => result = Optional.empty

上例中的 getPairs3 函数, For comprehension 生成了用户间的组合枚举:

  • users1: [u1, u2], users2: [u3, u4] => result = [(u1, u3), (u1, u4), (u2, u3), (u2, u4)]
  • users1: [], users2: [u1, u2] => result = []

java 中不提供 for comprehension 语法糖,我们可以自己实现,不过需要对于每种 monad 单独编写;或者使用现有的集合类vavr。

有时,对于复杂的 flatMap, 不妨直接回归到原来的方法:外部迭代。

  1. public class ForComprehensionDemo {
  2. @Value
  3. static class User {
  4. String id;
  5. String name;
  6. String mobile;
  7. Age age;
  8. Gender gender;
  9. public static User generateRandom() {
  10. Faker faker = new Faker();
  11. return new User(
  12. faker.idNumber().valid(),
  13. faker.name().name(),
  14. faker.phoneNumber().cellPhone(),
  15. rand(Age.class),
  16. rand(Gender.class)
  17. );
  18. }
  19. }
  20. enum Gender {
  21. MALE, FEMALE;
  22. }
  23. enum Age {
  24. MIDDLE_AGE, YOUNG_ADULT;
  25. }
  26. @Value
  27. static class FindFriendRequest {
  28. Option<Gender> gender;
  29. Option<Age> age;
  30. }
  31. public static void main(String[] args) {
  32. FindFriendRequest request = new FindFriendRequest(Option.of(Gender.FEMALE), Option.of(Age.MIDDLE_AGE));
  33. Option<List<User>> friends = getFriends1(request);
  34. friends.forEach(list -> list.forEach(System.out::println));
  35. }
  36. // vavr 集合库实现
  37. private static Option<List<User>> getFriends1(FindFriendRequest request) {
  38. return API.For(
  39. request.getGender(),
  40. request.getAge()
  41. ).yield(ForComprehensionDemo::searchInDb);
  42. }
  43. // 使用flatMap实现
  44. private static Option<List<User>> getFriends2(FindFriendRequest request) {
  45. Option<Gender> ts1 = request.getGender();
  46. Option<Age> ts2 = request.getAge();
  47. BiFunction<Gender, Age, List<User>> f = ForComprehensionDemo::searchInDb;
  48. return ts1.flatMap(t1 ->
  49. ts2.map(t2 ->
  50. f.apply(t1, t2)
  51. )
  52. );
  53. }
  54. public static List<User> searchInDb(Gender gender, Age age) {
  55. return Stream.generate(User::generateRandom)
  56. .filter(user -> user.gender == gender && user.age == age)
  57. .limit(3)
  58. .collect(toList());
  59. }
  60. public static <T extends Enum<T>> T rand(Class<T> clazz) {
  61. T[] values = clazz.getEnumConstants();
  62. return values[new Random().nextInt(values.length)];
  63. }
  64. }

jdk8 之后标准库的补充

点评:能新增这些方法,基本上说明这些方法挺有用。可以通过编写工具类或者使用Guava类库等实现相同功能。

  • List::of 创建不可变集合
  • List::copyOf 浅拷贝,生成不可变集合
  • Stream::toList 创建不可变集合
  • Stream::takeWhile, dropWhile, iterate(seed, predicate, mapper) 更精确的流控制,便于对无限流的过滤
  • Stream::ofNullable 帮助避免判空。null在代码中就应该少用
  • Optional::stream 终于提供了 Optional 和 Stream之间的转换,但是 Optional 和 Stream 还是没有实现 Iterable 接口
  • Optional::or 很有用的方法,可以实现短路运算
  1. <dependency>
  2. <groupId>org.projectlombok</groupId>
  3. <artifactId>lombok</artifactId>
  4. <version>1.18.24</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.github.javafaker</groupId>
  8. <artifactId>javafaker</artifactId>
  9. <version>1.0.2</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>io.vavr</groupId>
  13. <artifactId>vavr</artifactId>
  14. <version>0.10.4</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>com.google.guava</groupId>
  18. <artifactId>guava</artifactId>
  19. <version>31.1-jre</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.data</groupId>
  23. <artifactId>spring-data-commons</artifactId>
  24. <version>2.6.10</version>
  25. </dependency>

集合类再探:不可变类的好处,Collector接口详解,使用内部迭代的更多相关文章

  1. CSS中伪类及伪元素用法详解

    CSS中伪类及伪元素用法详解   伪类的分类及作用: 注:该表引自W3School教程 伪元素的分类及作用: 接下来让博主通过一些生动的实例(之前的作业或小作品)来说明几种常用伪类的用法和效果,其他的 ...

  2. StringUtils类API及使用方法详解

    StringUtils类API及使用方法详解 StringUtils方法概览 判空函数 1)StringUtils.isEmpty(String str) 2)StringUtils.isNotEmp ...

  3. 第15.10节 PyQt(Python+Qt)入门学习:Qt Designer可视化设计界面组件与QWidget类相关的组件属性详解

    PyQt学习有阵子了,对章节的骨架基本考虑好了,准备本节就写组件的属性的,结果一是日常工作繁忙,经常晚上还要加班,二是Qt的组件属性很多,只能逐一学习.研究和整理,花的时间有点长,不过终于将可视化设计 ...

  4. Java基础进阶:多态与接口重点摘要,类和接口,接口特点,接口详解,多态详解,多态中的成员访问特点,多态的好处和弊端,多态的转型,多态存在的问题,附重难点,代码实现源码,课堂笔记,课后扩展及答案

    多态与接口重点摘要 接口特点: 接口用interface修饰 interface 接口名{} 类实现接口用implements表示 class 类名 implements接口名{} 接口不能实例化,可 ...

  5. java中带继承类的加载顺序详解及实战

    一.背景: 在面试中,在java基础方面,类的加载顺序经常被问及,很多时候我们是搞不清楚到底类的加载顺序是怎么样的,那么今天我们就来看看带有继承的类的加载顺序到底是怎么一回事?在此记下也方便以后复习巩 ...

  6. Object类、包装类、内部类详解

    1.Object类 1.概念: 1.1 所有类在创建时都默认继承了java.lang.Object 1.2 所有类对象都可以声明为类对象的引用 Object ob1=new String(); Obj ...

  7. Kotlin——从无到有系列之中级篇(四):面向对象的特征与类(class)继承详解

    如果您对Kotlin很有兴趣,或者很想学好这门语言,可以关注我的掘金,或者进入我的QQ群大家一起学习.进步. 欢迎各位大佬进群共同研究.探索 QQ群号:497071402 进入正题 在前面的章节中,详 ...

  8. 不可变字符串String与可变字符串StringBuilder、StringBuffer使用详解

    String字符串 char类型只能表示一个字符,而String可以表示字符串,也就是一个字符序列.但String不是基本类型,而是一个定义好的类,是一个引用类型.在Java中,可以将字符串直接量赋给 ...

  9. 类的命名空间与卸载详解及jvisualvm使用

    类的命名空间详解: 在上一次[https://www.cnblogs.com/webor2006/p/9108301.html]最后实验中有一个违背咱们理解的,这里回顾一下: 也就是说,"某 ...

  10. java中ReentrantLock类的详细介绍(详解)

    博主如果看到请联系小白,小白记不清地址了 简介 ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字 ...

随机推荐

  1. 新版的Eureka已经移除了基于Ribbon的客户端的负载均衡

    启用一个EurekaServer和一个服务调用方,两个copy的服务提供方. 本次测试用Springcloud 2021.0.1版本 客户端使用RestTemplate 的负载均衡 @LoadBala ...

  2. Linux下用rm误删除文件的三种恢复方法

    Linux下用rm误删除文件的三种恢复方法 对于rm,很多人都有惨痛的教训.我也遇到一次,一下午写的程序就被rm掉了,幸好只是一个文件,第二天很快又重新写了一遍.但是很多人可能就不像我这么幸运了.本文 ...

  3. 4.1:简单python爬虫

    简单python爬虫 在创建的python文件中输入下列代码: # coding:utf-8 import requests from bs4 import BeautifulSoup def spi ...

  4. 【FAQ】在华为鸿蒙车机上集成华为帐号的常见问题总结

    随着新一代信息技术与汽车产业的深度融合,智能网联汽车正逐渐成为汽车产业发展的战略制高点,无论是传统车企还是新势力都瞄准了"智能座舱"这种新一代人机交互方式.面对竞争如此激烈的车机市 ...

  5. 搭建IIS网站后,点击浏览地址,报403错误

    点击左侧的浏览地址,报右侧的错误,可将目录浏览进行启用 双击进去,进行启用即可

  6. [OpenCV实战]12 使用深度学习和OpenCV进行手部关键点检测

    目录 1 背景 2 实现 3 结果和代码 4 参考 手部关键点检测是在手指上找到关节以及在给定图像中找到指尖的过程.它类似于在脸部(面部关键点检测)或身体(人体姿势估计)上找到关键点.但是手部检测不同 ...

  7. 痞子衡嵌入式:MCUBootUtility v4.0发布,开始支持MCX啦

    -- 痞子衡维护的 NXP-MCUBootUtility 工具距离上一个大版本(v3.5.0)发布过去 9 个月了,这一次痞子衡为大家带来了版本升级 v4.0.0,这个版本主要有两个重要更新需要跟大家 ...

  8. CLISP学习(二)

    它是一门函数式语言,你要用函数的思维来思考. 只不过与数学的表达不同的是,数学里的函数是在括号外  f(x) ,而lisp是在括号内,以列表的形式(f x), cos(x) --> (cos x ...

  9. 震网(Stuxnet)病毒深度解析:首个攻击真实世界基础设施的病毒

    摘要:震网病毒主要是通过改变离心机的转速,来破坏离心机,并影响生产的浓缩铀质量. 本文分享自华为云社区<[安全技术]震网(Stuxnet)病毒深度解析:首个攻击真实世界基础设施的病毒(1)[原创 ...

  10. 浅谈LCA问题(最近公共祖先)(四种做法)

    [模板]最近公共祖先(LCA) \(update \ 2023.1.3\) 新增了树链剖分 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入格式 第一行包含三个正整数 \ ...