欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Java8 Collect收集Stream的方法

 更新時(shí)間:2018年04月20日 08:28:55   作者:Ryan.Miao  
這篇文章主要介紹了Java8-Collect收集Stream的方法,提到了收集器的作用,連接收集器的方法,需要的朋友可以參考下

Collection, Collections, collect, Collector, Collectos

Collection是Java集合的祖先接口。
Collections是java.util包下的一個(gè)工具類(lèi),內(nèi)涵各種處理集合的靜態(tài)方法。
java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一個(gè)函數(shù),負(fù)責(zé)收集流。
java.util.stream.Collector 是一個(gè)收集函數(shù)的接口, 聲明了一個(gè)收集器的功能。
java.util.Comparators則是一個(gè)收集器的工具類(lèi),內(nèi)置了一系列收集器實(shí)現(xiàn)。

收集器的作用

你可以把Java8的流看做花哨又懶惰的數(shù)據(jù)集迭代器。他們支持兩種類(lèi)型的操作:中間操作(e.g. filter, map)和終端操作(如count, findFirst, forEach, reduce). 中間操作可以連接起來(lái),將一個(gè)流轉(zhuǎn)換為另一個(gè)流。這些操作不會(huì)消耗流,其目的是建立一個(gè)流水線。與此相反,終端操作會(huì)消耗類(lèi),產(chǎn)生一個(gè)最終結(jié)果。collect就是一個(gè)歸約操作,就像reduce一樣可以接受各種做法作為參數(shù),將流中的元素累積成一個(gè)匯總結(jié)果。具體的做法是通過(guò)定義新的Collector接口來(lái)定義的。

預(yù)定義的收集器

下面簡(jiǎn)單演示基本的內(nèi)置收集器。模擬數(shù)據(jù)源如下:

final ArrayList<Dish> dishes = Lists.newArrayList(
    new Dish("pork", false, 800, Type.MEAT),
    new Dish("beef", false, 700, Type.MEAT),
    new Dish("chicken", false, 400, Type.MEAT),
    new Dish("french fries", true, 530, Type.OTHER),
    new Dish("rice", true, 350, Type.OTHER),
    new Dish("season fruit", true, 120, Type.OTHER),
    new Dish("pizza", true, 550, Type.OTHER),
    new Dish("prawns", false, 300, Type.FISH),
    new Dish("salmon", false, 450, Type.FISH)
);

最大值,最小值,平均值

// 為啥返回Optional? 如果stream為null怎么辦, 這時(shí)候Optinal就很有意義了
Optional<Dish> mostCalorieDish = dishes.stream().max(Comparator.comparingInt(Dish::getCalories));
Optional<Dish> minCalorieDish = dishes.stream().min(Comparator.comparingInt(Dish::getCalories));
Double avgCalories = dishes.stream().collect(Collectors.averagingInt(Dish::getCalories));
IntSummaryStatistics summaryStatistics = dishes.stream().collect(Collectors.summarizingInt(Dish::getCalories));
double average = summaryStatistics.getAverage();
long count = summaryStatistics.getCount();
int max = summaryStatistics.getMax();
int min = summaryStatistics.getMin();
long sum = summaryStatistics.getSum();

這幾個(gè)簡(jiǎn)單的統(tǒng)計(jì)指標(biāo)都有Collectors內(nèi)置的收集器函數(shù),尤其是針對(duì)數(shù)字類(lèi)型拆箱函數(shù),將會(huì)比直接操作包裝類(lèi)型開(kāi)銷(xiāo)小很多。

連接收集器

想要把Stream的元素拼起來(lái)?

//直接連接
String join1 = dishes.stream().map(Dish::getName).collect(Collectors.joining());
//逗號(hào)
String join2 = dishes.stream().map(Dish::getName).collect(Collectors.joining(", "));

toList

List<String> names = dishes.stream().map(Dish::getName).collect(toList());

將原來(lái)的Stream映射為一個(gè)單元素流,然后收集為L(zhǎng)ist。

toSet

Set<Type> types = dishes.stream().map(Dish::getType).collect(Collectors.toSet());

將Type收集為一個(gè)set,可以去重復(fù)。

toMap

Map<Type, Dish> byType = dishes.stream().collect(toMap(Dish::getType, d -> d));

有時(shí)候可能需要將一個(gè)數(shù)組轉(zhuǎn)為map,做緩存,方便多次計(jì)算獲取。toMap提供的方法k和v的生成函數(shù)。(注意,上述demo是一個(gè)坑,不可以這樣用?。?!, 請(qǐng)使用toMap(Function, Function, BinaryOperator))

上面幾個(gè)幾乎是最常用的收集器了,也基本夠用了。但作為初學(xué)者來(lái)說(shuō),理解需要時(shí)間。想要真正明白為什么這樣可以做到收集,就必須查看內(nèi)部實(shí)現(xiàn),可以看到,這幾個(gè)收集器都是基于java.util.stream.Collectors.CollectorImpl,也就是開(kāi)頭提到過(guò)了Collector的一個(gè)實(shí)現(xiàn)類(lèi)。后面自定義收集器會(huì)學(xué)習(xí)具體用法。

自定義歸約reducing

前面幾個(gè)都是reducing工廠方法定義的歸約過(guò)程的特殊情況,其實(shí)可以用Collectors.reducing創(chuàng)建收集器。比如,求和

Integer totalCalories = dishes.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
//使用內(nèi)置函數(shù)代替箭頭函數(shù)
Integer totalCalories2 = dishes.stream().collect(reducing(0, Dish::getCalories, Integer::sum));

當(dāng)然也可以直接使用reduce

Optional<Integer> totalCalories3 = dishes.stream().map(Dish::getCalories).reduce(Integer::sum);

雖然都可以,但考量效率的話,還是要選擇下面這種

int sum = dishes.stream().mapToInt(Dish::getCalories).sum();

根據(jù)情況選擇最佳方案

上面的demo說(shuō)明,函數(shù)式編程通常提供了多種方法來(lái)執(zhí)行同一個(gè)操作,使用收集器collect比直接使用stream的api用起來(lái)更加復(fù)雜,好處是collect能提供更高水平的抽象和概括,也更容易重用和自定義。

我們的建議是,盡可能為手頭的問(wèn)題探索不同的解決方案,始終選擇最專(zhuān)業(yè)的一個(gè),無(wú)論從可讀性還是性能來(lái)看,這一般都是最好的決定。

reducing除了接收一個(gè)初始值,還可以把第一項(xiàng)當(dāng)作初始值

Optional<Dish> mostCalorieDish = dishes.stream()
        .collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

reducing

關(guān)于reducing的用法比較復(fù)雜,目標(biāo)在于把兩個(gè)值合并成一個(gè)值。

public static <T, U>
  Collector<T, ?, U> reducing(U identity,
                Function<? super T, ? extends U> mapper,
                BinaryOperator<U> op) 

首先看到3個(gè)泛型,

U是返回值的類(lèi)型,比如上述demo中計(jì)算熱量的,U就是Integer。

關(guān)于T,T是Stream里的元素類(lèi)型。由Function的函數(shù)可以知道,mapper的作用就是接收一個(gè)參數(shù)T,然后返回一個(gè)結(jié)果U。對(duì)應(yīng)demo中Dish。

?在返回值Collector的泛型列表的中間,這個(gè)表示容器類(lèi)型,一個(gè)收集器當(dāng)然需要一個(gè)容器來(lái)存放數(shù)據(jù)。這里的?則表示容器類(lèi)型不確定。事實(shí)上,在這里的容器就是U[]。

關(guān)于參數(shù):

identity是返回值類(lèi)型的初始值,可以理解為累加器的起點(diǎn)。

mapper則是map的作用,意義在于將Stream流轉(zhuǎn)換成你想要的類(lèi)型流。

op則是核心函數(shù),作用是如何處理兩個(gè)變量。其中,第一個(gè)變量是累積值,可以理解為sum,第二個(gè)變量則是下一個(gè)要計(jì)算的元素。從而實(shí)現(xiàn)了累加。

reducing還有一個(gè)重載的方法,可以省略第一個(gè)參數(shù),意義在于把Stream里的第一個(gè)參數(shù)當(dāng)做初始值。

public static <T> Collector<T, ?, Optional<T>>
  reducing(BinaryOperator<T> op) 

先看返回值的區(qū)別,T表示輸入值和返回值類(lèi)型,即輸入值類(lèi)型和輸出值類(lèi)型相同。還有不同的就是Optional了。這是因?yàn)闆](méi)有初始值,而第一個(gè)參數(shù)有可能是null,當(dāng)Stream的元素是null的時(shí)候,返回Optional就很意義了。

再看參數(shù)列表,只剩下BinaryOperator。BinaryOperator是一個(gè)三元組函數(shù)接口,目標(biāo)是將兩個(gè)同類(lèi)型參數(shù)做計(jì)算后返回同類(lèi)型的值??梢园凑?>2? 1:2來(lái)理解,即求兩個(gè)數(shù)的最大值。求最大值是比較好理解的一種說(shuō)法,你可以自定義lambda表達(dá)式來(lái)選擇返回值。那么,在這里,就是接收兩個(gè)Stream的元素類(lèi)型T,返回T類(lèi)型的返回值。用sum累加來(lái)理解也可以。

上述的demo中發(fā)現(xiàn)reduce和collect的作用幾乎一樣,都是返回一個(gè)最終的結(jié)果,比如,我們可以使用reduce實(shí)現(xiàn)toList效果:

//手動(dòng)實(shí)現(xiàn)toListCollector --- 濫用reduce, 不可變的規(guī)約---不可以并行
List<Integer> calories = dishes.stream().map(Dish::getCalories)
    .reduce(new ArrayList<Integer>(),
        (List<Integer> l, Integer e) -> {
          l.add(e);
          return l;
        },
        (List<Integer> l1, List<Integer> l2) -> {
          l1.addAll(l2);
          return l1;
        }
    );

關(guān)于上述做法解釋一下。

<U> U reduce(U identity,
         BiFunction<U, ? super T, U> accumulator,
         BinaryOperator<U> combiner);

U是返回值類(lèi)型,這里就是List

BiFunction<U, ? super T, U> accumulator是是累加器,目標(biāo)在于累加值和單個(gè)元素的計(jì)算規(guī)則。這里就是List和元素做運(yùn)算,最終返回List。即,添加一個(gè)元素到list。

BinaryOperator<U> combiner是組合器,目標(biāo)在于把兩個(gè)返回值類(lèi)型的變量合并成一個(gè)。這里就是兩個(gè)list合并。
這個(gè)解決方案有兩個(gè)問(wèn)題:一個(gè)是語(yǔ)義問(wèn)題,一個(gè)是實(shí)際問(wèn)題。語(yǔ)義問(wèn)題在于,reduce方法旨在把兩個(gè)值結(jié)合起來(lái)生成一個(gè)新值,它是一個(gè)不可變歸約。相反,collect方法的設(shè)計(jì)就是要改變?nèi)萜鳎瑥亩鄯e要輸出的結(jié)果。這意味著,上面的代碼片段是在濫用reduce方法,因?yàn)樗谠馗淖兞俗鳛槔奂悠鞯腖ist。錯(cuò)誤的語(yǔ)義來(lái)使用reduce方法還會(huì)造成一個(gè)實(shí)際問(wèn)題:這個(gè)歸約不能并行工作,因?yàn)橛啥鄠€(gè)線程并發(fā)修改同一個(gè)數(shù)據(jù)結(jié)構(gòu)可能會(huì)破壞List本身。在這種情況下,如果你想要線程安全,就需要每次分配一個(gè)新的List,而對(duì)象分配又會(huì)影響性能。這就是collect適合表達(dá)可變?nèi)萜魃系臍w約的原因,更關(guān)鍵的是它適合并行操作。

總結(jié):reduce適合不可變?nèi)萜鳉w約,collect適合可變?nèi)萜鳉w約。collect適合并行。

分組

數(shù)據(jù)庫(kù)中經(jīng)常遇到分組求和的需求,提供了group by原語(yǔ)。在Java里, 如果按照指令式風(fēng)格(手動(dòng)寫(xiě)循環(huán))的方式,將會(huì)非常繁瑣,容易出錯(cuò)。而Java8則提供了函數(shù)式解法。

比如,將dish按照type分組。和前面的toMap類(lèi)似,但分組的value卻不是一個(gè)dish,而是一個(gè)List。

Map<Type, List<Dish>> dishesByType = dishes.stream().collect(groupingBy(Dish::getType));

這里

public static <T, K> Collector<T, ?, Map<K, List<T>>>
  groupingBy(Function<? super T, ? extends K> classifier)

參數(shù)分類(lèi)器為Function,旨在接收一個(gè)參數(shù),轉(zhuǎn)換為另一個(gè)類(lèi)型。上面的demo就是把stream的元素dish轉(zhuǎn)成類(lèi)型Type,然后根據(jù)Type將stream分組。其內(nèi)部是通過(guò)HashMap來(lái)實(shí)現(xiàn)分組的。groupingBy(classifier, HashMap::new, downstream);

除了按照stream元素自身的屬性函數(shù)去分組,還可以自定義分組依據(jù),比如根據(jù)熱量范圍分組。

既然已經(jīng)知道groupingBy的參數(shù)為Function, 并且Function的參數(shù)類(lèi)型為Dish,那么可以自定義分類(lèi)器為:

private CaloricLevel getCaloricLevel(Dish d) {
  if (d.getCalories() <= 400) {
   return CaloricLevel.DIET;
  } else if (d.getCalories() <= 700) {
   return CaloricLevel.NORMAL;
  } else {
   return CaloricLevel.FAT;
  }
}

再傳入?yún)?shù)即可

Map<CaloricLevel, List<Dish>> dishesByLevel = dishes.stream()
    .collect(groupingBy(this::getCaloricLevel));

多級(jí)分組

groupingBy還重載了其他幾個(gè)方法,比如

public static <T, K, A, D>
  Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                     Collector<? super T, A, D> downstream)

泛型多的恐怖。簡(jiǎn)單的認(rèn)識(shí)一下。classifier還是分類(lèi)器,就是接收stream的元素類(lèi)型,返回一個(gè)你想要分組的依據(jù),也就是提供分組依據(jù)的基數(shù)的。所以T表示stream當(dāng)前的元素類(lèi)型,K表示分組依據(jù)的元素類(lèi)型。第二個(gè)參數(shù)downstream,下游是一個(gè)收集器Collector. 這個(gè)收集器元素類(lèi)型是T的子類(lèi),容器類(lèi)型container為A,reduction返回值類(lèi)型為D。也就是說(shuō)分組的K通過(guò)分類(lèi)器提供,分組的value則通過(guò)第二個(gè)參數(shù)的收集器reduce出來(lái)。正好,上個(gè)demo的源碼為:

public static <T, K> Collector<T, ?, Map<K, List<T>>>
  groupingBy(Function<? super T, ? extends K> classifier) {
    return groupingBy(classifier, toList());
  }

將toList當(dāng)作reduce收集器,最終收集的結(jié)果是一個(gè)List<Dish>, 所以分組結(jié)束的value類(lèi)型是List<Dish>。那么,可以類(lèi)推value類(lèi)型取決于reduce收集器,而reduce收集器則有千千萬(wàn)。比如,我想對(duì)value再次分組,分組也是一種reduce。

//多級(jí)分組
Map<Type, Map<CaloricLevel, List<Dish>>> byTypeAndCalory = dishes.stream().collect(
  groupingBy(Dish::getType, groupingBy(this::getCaloricLevel)));
byTypeAndCalory.forEach((type, byCalory) -> {
 System.out.println("----------------------------------");
 System.out.println(type);
 byCalory.forEach((level, dishList) -> {
  System.out.println("\t" + level);
  System.out.println("\t\t" + dishList);
 });
});

驗(yàn)證結(jié)果為:

----------------------------------
FISH
  DIET
    [Dish(name=prawns, vegetarian=false, calories=300, type=FISH)]
  NORMAL
    [Dish(name=salmon, vegetarian=false, calories=450, type=FISH)]
----------------------------------
MEAT
  FAT
    [Dish(name=pork, vegetarian=false, calories=800, type=MEAT)]
  DIET
    [Dish(name=chicken, vegetarian=false, calories=400, type=MEAT)]
  NORMAL
    [Dish(name=beef, vegetarian=false, calories=700, type=MEAT)]
----------------------------------
OTHER
  DIET
    [Dish(name=rice, vegetarian=true, calories=350, type=OTHER), Dish(name=season fruit, vegetarian=true, calories=120, type=OTHER)]
  NORMAL
    [Dish(name=french fries, vegetarian=true, calories=530, type=OTHER), Dish(name=pizza, vegetarian=true, calories=550, type=OTHER)]

總結(jié):groupingBy的核心參數(shù)為K生成器,V生成器。V生成器可以是任意類(lèi)型的收集器Collector。

比如,V生成器可以是計(jì)算數(shù)目的, 從而實(shí)現(xiàn)了sql語(yǔ)句中的select count(*) from table A group by Type

Map<Type, Long> typesCount = dishes.stream().collect(groupingBy(Dish::getType, counting()));
System.out.println(typesCount);
-----------
{FISH=2, MEAT=3, OTHER=4}

sql查找分組最高分select MAX(id) from table A group by Type

Map<Type, Optional<Dish>> mostCaloricByType = dishes.stream()
    .collect(groupingBy(Dish::getType, maxBy(Comparator.comparingInt(Dish::getCalories))));

這里的Optional沒(méi)有意義,因?yàn)榭隙ú皇莕ull。那么只好取出來(lái)了。使用collectingAndThen

Map<Type, Dish> mostCaloricByType = dishes.stream()
  .collect(groupingBy(Dish::getType,
    collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));

到這里似乎結(jié)果出來(lái)了,但I(xiàn)DEA不同意,編譯黃色報(bào)警,按提示修改后變?yōu)椋?/p>

Map<Type, Dish> mostCaloricByType = dishes.stream()
  .collect(toMap(Dish::getType, Function.identity(),
    BinaryOperator.maxBy(comparingInt(Dish::getCalories))));

是的,groupingBy就變成toMap了,key還是Type,value還是Dish,但多了一個(gè)參數(shù)?。∵@里回應(yīng)開(kāi)頭的坑,開(kāi)頭的toMap演示是為了容易理解,真那么用則會(huì)被搞死。我們知道把一個(gè)List重組為Map必然會(huì)面臨k相同的問(wèn)題。當(dāng)K相同時(shí),v是覆蓋還是不管呢?前面的demo的做法是當(dāng)k存在時(shí),再次插入k則直接拋出異常:

java.lang.IllegalStateException: Duplicate key Dish(name=pork, vegetarian=false, calories=800, type=MEAT)
  at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)

正確的做法是提供處理沖突的函數(shù),在本demo中,處理沖突的原則就是找出最大的,正好符合我們分組求最大的要求。(真的不想搞Java8函數(shù)式學(xué)習(xí)了,感覺(jué)到處都是性能問(wèn)題的坑)

繼續(xù)數(shù)據(jù)庫(kù)sql映射,分組求和select sum(score) from table a group by Type

Map<Type, Integer> totalCaloriesByType = dishes.stream()
  .collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));

然而常常和groupingBy聯(lián)合使用的另一個(gè)收集器是mapping方法生成的。這個(gè)方法接收兩個(gè)參數(shù):一個(gè)函數(shù)對(duì)流中的元素做變換,另一個(gè)則將變換的結(jié)果對(duì)象收集起來(lái)。其目的是在累加之前對(duì)每個(gè)輸入元素應(yīng)用一個(gè)映射函數(shù),這樣就可以讓接收特定類(lèi)型元素的收集器適應(yīng)不同類(lèi)型的對(duì)象。我么來(lái)看一個(gè)使用這個(gè)收集器的實(shí)際例子。比如你想得到,對(duì)于每種類(lèi)型的Dish,菜單中都有哪些CaloricLevel。我們可以把groupingBy和mapping收集器結(jié)合起來(lái),如下所示:

Map<Type, Set<CaloricLevel>> caloricLevelsByType = dishes.stream()
  .collect(groupingBy(Dish::getType, mapping(this::getCaloricLevel, toSet())));

這里的toSet默認(rèn)采用的HashSet,也可以手動(dòng)指定具體實(shí)現(xiàn)toCollection(HashSet::new)

分區(qū)

分區(qū)是分組的特殊情況:由一個(gè)謂詞(返回一個(gè)布爾值的函數(shù))作為分類(lèi)函數(shù),它稱為分區(qū)函數(shù)。分區(qū)函數(shù)返回一個(gè)布爾值,這意味著得到的分組Map的鍵類(lèi)型是Boolean,于是它最多可以分為兩組:true or false. 例如,如果你是素食者,你可能想要把菜單按照素食和非素食分開(kāi):

Map<Boolean, List<Dish>> partitionedMenu = dishes.stream().collect(partitioningBy(Dish::isVegetarian));

當(dāng)然,使用filter可以達(dá)到同樣的效果:

List<Dish> vegetarianDishes = dishes.stream().filter(Dish::isVegetarian).collect(Collectors.toList());

分區(qū)相對(duì)來(lái)說(shuō),優(yōu)勢(shì)就是保存了兩個(gè)副本,當(dāng)你想要對(duì)一個(gè)list分類(lèi)時(shí)挺有用的。同時(shí),和groupingBy一樣,partitioningBy一樣有重載方法,可以指定分組value的類(lèi)型。

Map<Boolean, Map<Type, List<Dish>>> vegetarianDishesByType = dishes.stream()
  .collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
Map<Boolean, Integer> vegetarianDishesTotalCalories = dishes.stream()
  .collect(partitioningBy(Dish::isVegetarian, summingInt(Dish::getCalories)));
Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = dishes.stream()
  .collect(partitioningBy(Dish::isVegetarian,
    collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));

作為使用partitioningBy收集器的最后一個(gè)例子,我們把菜單數(shù)據(jù)模型放在一邊,來(lái)看一個(gè)更加復(fù)雜也更為有趣的例子:將數(shù)組分為質(zhì)數(shù)和非質(zhì)數(shù)。

首先,定義個(gè)質(zhì)數(shù)分區(qū)函數(shù):

private boolean isPrime(int candidate) {
  int candidateRoot = (int) Math.sqrt((double) candidate);
  return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
}

然后找出1到100的質(zhì)數(shù)和非質(zhì)數(shù)

Map<Boolean, List<Integer>> partitionPrimes = IntStream.rangeClosed(2, 100).boxed()
  .collect(partitioningBy(this::isPrime));


相關(guān)文章

  • java中應(yīng)用Stack進(jìn)行算術(shù)運(yùn)算的操作

    java中應(yīng)用Stack進(jìn)行算術(shù)運(yùn)算的操作

    這篇文章主要介紹了java中應(yīng)用Stack進(jìn)行算術(shù)運(yùn)算的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-03-03
  • Java讀取網(wǎng)絡(luò)文件的實(shí)例代碼

    Java讀取網(wǎng)絡(luò)文件的實(shí)例代碼

    這篇文章主要介紹了Java讀取網(wǎng)絡(luò)文件的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • 詳解idea中web.xml默認(rèn)版本問(wèn)題解決

    詳解idea中web.xml默認(rèn)版本問(wèn)題解決

    這篇文章主要介紹了詳解idea中web.xml默認(rèn)版本問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Java設(shè)計(jì)模式之淺談外觀模式

    Java設(shè)計(jì)模式之淺談外觀模式

    這篇文章主要介紹了Java設(shè)計(jì)模式之外觀模式的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • Mybatis中SqlSession接口中selectList方法詳解

    Mybatis中SqlSession接口中selectList方法詳解

    這篇文章主要給大家介紹了關(guān)于Mybatis中SqlSession接口中selectList方法的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Spring整合消息隊(duì)列RabbitMQ流程

    Spring整合消息隊(duì)列RabbitMQ流程

    Spring整合RabbitMQ很容易,但是整合的目的是為了使用,那要使用RabbitMQ就要對(duì)其有一定的了解,不然容易整成一團(tuán)漿糊。因?yàn)檎f(shuō)到底,Spring只是在封裝RabbitMQ的API,讓其更容易使用而已,廢話不多說(shuō),讓我們一起整它
    2023-03-03
  • Java線性結(jié)構(gòu)中棧、隊(duì)列和串的基本概念和特點(diǎn)詳解

    Java線性結(jié)構(gòu)中棧、隊(duì)列和串的基本概念和特點(diǎn)詳解

    前幾天小編給大家介紹了Java線性結(jié)構(gòu)中的鏈表,除了鏈表這種結(jié)構(gòu)之外,實(shí)際上還有棧、隊(duì)列、串等結(jié)構(gòu),那么這些結(jié)構(gòu)又有哪些特點(diǎn)呢,本文就給大家詳細(xì)的介紹一下,感興趣的小伙伴跟著小編一起來(lái)看看吧
    2023-07-07
  • mybatis多個(gè)plugins的執(zhí)行順序解析

    mybatis多個(gè)plugins的執(zhí)行順序解析

    這篇文章主要介紹了mybatis多個(gè)plugins的執(zhí)行順序解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 徹底搞懂Java多線程(五)

    徹底搞懂Java多線程(五)

    這篇文章主要給大家介紹了關(guān)于Java面試題之多線程和高并發(fā)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • java使用Jsoup連接網(wǎng)站超時(shí)的解決方法

    java使用Jsoup連接網(wǎng)站超時(shí)的解決方法

    jsoup是一個(gè)非常好的解析網(wǎng)頁(yè)的包,用java開(kāi)發(fā)的,提供了類(lèi)似DOM,CSS選擇器的方式來(lái)查找和提取文檔中的內(nèi)容,提取文檔內(nèi)容時(shí)會(huì)出現(xiàn)超時(shí)的情況,解決方法可看下文
    2013-11-11

最新評(píng)論