Java?中如何使用?stream?流
前言
如果你了解過(guò) Liunx ,了解過(guò) Liunx 的中管道命令 | ,那么你會(huì)發(fā)現(xiàn),其實(shí) Java 8 的 stream 和 Liunx 非常類(lèi)似的。
Liunx 中的管道命令也是將上一個(gè)命令的輸出流作為下一條命令的輸入流。
今天主要聊起的是如何使用 stream 流,關(guān)于它為什么被引入,有什么樣的優(yōu)勢(shì),還有一些平時(shí)未曾注意到的知識(shí)點(diǎn)的話(huà),就在下一次再講吧~
能基礎(chǔ)的使用,是深入了解它的一個(gè)基礎(chǔ)吧,我覺(jué)得~
在本文中,你將會(huì)看到Stream API支持的許多操作。這些操作能讓你快速完成復(fù)雜的數(shù)據(jù)查詢(xún),如篩選、切片、映射、查找、匹配和歸約。
一、篩選和切片
1.1、篩選 filter
filter 會(huì)接受一個(gè) Predicate 接口的參數(shù),其本質(zhì)就是一個(gè)布爾值函數(shù)(官方稱(chēng)為謂詞,說(shuō)成白話(huà),即為一個(gè)布爾值函數(shù))
準(zhǔn)備好的數(shù)據(jù)~
? ? ? static ? List<Student> students = new ArrayList<>();
??
? ? ?static {
? ? ? ? ? ? ? ? students.add(new Student("學(xué)生A", "大學(xué)1", 18));
? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18));
? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18));
? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18));
? ? ? ? ?students.add(new Student("學(xué)生B", "大學(xué)1", 18));
? ? ? ? ?students.add(new Student("學(xué)生C", "大學(xué)1", 19));
? ? ? ? ?students.add(new Student("學(xué)生D", "大學(xué)2", 20));
? ? ? ? ?students.add(new Student("學(xué)生E", "大學(xué)2", 21));
? ? ? ? ?students.add(new Student("學(xué)生F", "大學(xué)2", 20));
? ? ? ? ?students.add(new Student("學(xué)生G", "大學(xué)3", 22));
? ? ? ? ?students.add(new Student("學(xué)生H", "大學(xué)3", 23));
? ? ? ? ?students.add(new Student("學(xué)生I", "大學(xué)3", 19));
? ? ? ? ?students.add(new Student("學(xué)生J", "大學(xué)4", 20));
? ? }1、從中篩選出小于20的學(xué)生們組成一個(gè)新的集合
jdk 8 之前的寫(xiě)法:
?List<Student> result = new ArrayList<>();
?for (Student student : students) {
? ? ?if (student.getAge() < 20) {
? ? ? ? ?result.add(student);
? ? }
?}Jdk 8 及之后的寫(xiě)法:使用stream流操作
?/**
? ? ? * 選出小于20的學(xué)生組成一個(gè)集合
? ? ? *
? ? ? * @param students
? ? ? */
?private static List<Student> selectAgeLt18(List<Student> students) {
? ? ?// 最基礎(chǔ)的寫(xiě)法, filter的參數(shù)是一個(gè) Predicate,而它是一個(gè)FunctionalInterface 式的接口, 唯一的接口就是表示一個(gè)參數(shù)的謂詞(布爾值函數(shù))。
? ? ?// ? ? ? List<Student> list = students.stream().filter(new Predicate<Student>() {
? ? ?// ? ? ? ? ? @Override
? ? ?// ? ? ? ? ? public boolean test(Student student) {
? ? ?// ? ? ? ? ? ? ? return student.getAge()<20;
? ? ?// ? ? ? ? ? }
? ? ?// ? ? ? }).collect(Collectors.toList());
? ? ?// 因此可以簡(jiǎn)化寫(xiě)成 以下這種寫(xiě)法
? ? ?// ? ? ? List<Student> list = students.stream().filter(student -> {
? ? ?// ? ? ? ? ? return student.getAge() < 20;
? ? ?// ? ? ? }).collect(Collectors.toList());
? ? ?//又因?yàn)閒ilter 的參數(shù)實(shí)際上是一個(gè)lambda表達(dá)式,當(dāng)只有一條返回語(yǔ)句時(shí),又可以省略大括號(hào)和return
? ? ?List<Student> list = students.stream().filter(student -> student.getAge() < 20).collect(Collectors.toList());
? ? ?return list;
?}1.2、去重 distinct
distinct()它會(huì)返回一個(gè)元素各異(根據(jù)流所生成元素的 hashCode和equals方法實(shí)現(xiàn))的流。
jdk 8之前對(duì)集合的一些去重方式
?/**
? ? ? * 去重操作,去除掉數(shù)據(jù)集合中重復(fù)的數(shù)據(jù)
? ? ? */
?private static void selectSchoolRepresent(List<Student> students) {
? ? ?// ? ? ? ? jdk 8之前的一些方式,
? ? ?// ? ? ? ? 1、set集合去重
? ? ?HashSet<Student> set = new HashSet<>();
? ? ?for (Student student : students) {
? ? ? ? ?set.add(student);
? ? }
? ? ?// ? ? ? ? 還可以簡(jiǎn)寫(xiě)成
? ? ?List<Student> newList = new ArrayList<>(new HashSet<>(students));
??
? ? ?// ? ? ? ? 2、 利用 list的contains() 方法
? ? ?List<Student> list = new ArrayList<>();
? ? ?for (Student student : students) {
? ? ? ? ?if(!list.contains(student)){
? ? ? ? ? ? ?list.add(student);
? ? ? ? }
? ? }
?}Java 8 及之后使用stream中的 distinct()方法,其實(shí)咋說(shuō)勒,就是方便,其他的也木有
?/**
? ? ? * 去重操作,去除掉數(shù)據(jù)集合中重復(fù)的數(shù)據(jù)
? ? ? */
?private static void selectSchoolRepresent(List<Student> students) {
? ? ?List<Student> collect = students.stream().distinct().collect(Collectors.toList());
? ? ?collect.forEach(System.out::println);
?}1.3、切片 limit
流支持limit(n)方法,該方法會(huì)返回一個(gè)不超過(guò)給定長(zhǎng)度的流。
如果流是有序的,則最多會(huì)返回前n個(gè)元素。無(wú)序的則不會(huì)以任何方式排序。
Jdk 8 之前的寫(xiě)法
?/**
? ? ? * 選出集合中前五位同學(xué) 組成一個(gè)新的集合
? ? ? *
? ? ? * @param students
? ? ? */
?private static void selectLimit(List<Student> students) {
? ? ?List<Student> list = new ArrayList<>();
? ? ?for (int i = 0; i < students.size(); i++) {
? ? ? ? ?if (i < 5) {
? ? ? ? ? ? ?list.add(students.get(i));
? ? ? ? }
? ? }
? ? ?list.forEach(System.out::println);
?}Jdk 8的 stream 流中的 limit 的寫(xiě)法
?/**
? ? ? * 選出集合中前五位同學(xué) 組成一個(gè)新的集合
? ? ? *
? ? ? * @param students
? ? ? */
?private static void selectLimit(List<Student> students) {
??
? ? ?List<Student> collect = students.stream().limit(5).collect(Collectors.toList());
??
? ? ?collect.forEach(System.out::println);
?}1.4、跳過(guò)元素 skip
流還支持skip(n)方法,返回一個(gè)扔掉了前n個(gè)元素的流。如果流中元素不足n個(gè),則返回一 個(gè)空流。
?/**
? ? ? * 從第二個(gè)同學(xué)開(kāi)始組成新的集合
? ? ? *
? ? ? * @param students
? ? ? */
?private static void selectSkip(List<Student> students) {
? ? ?List<Student> collect = students.stream().skip(2).collect(Collectors.toList());
? ? ?collect.forEach(System.out::println);
? ? ?/**
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=90.0)
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=76.0)
? ? ? ? ? * Student(name=學(xué)生B, school=大學(xué)1, age=18, score=91.0)
? ? ? ? ? * Student(name=學(xué)生C, school=大學(xué)1, age=19, score=65.0)
? ? ? ? ? * Student(name=學(xué)生D, school=大學(xué)2, age=20, score=80.0)
? ? ? ? ? * Student(name=學(xué)生E, school=大學(xué)2, age=21, score=78.0)
? ? ? ? ? * Student(name=學(xué)生F, school=大學(xué)2, age=20, score=67.0)
? ? ? ? ? * Student(name=學(xué)生G, school=大學(xué)3, age=22, score=87.0)
? ? ? ? ? * Student(name=學(xué)生H, school=大學(xué)3, age=23, score=79.0)
? ? ? ? ? * Student(name=學(xué)生I, school=大學(xué)3, age=19, score=92.0)
? ? ? ? ? * Student(name=學(xué)生J, school=大學(xué)4, age=20, score=84.0)
? ? ? ? ? */
?}1.5、排序 sorted
這個(gè)就是排序啦,沒(méi)啥能說(shuō)的啦吧~偷個(gè)懶哈
? ? ?/**
? ? ? * 給這群學(xué)生按年齡排序
? ? ? *
? ? ? *
? ? ? * @param students
? ? ? */
? ? ?private static void sortedDemo(List<Student> students) {
? ? ? ? ?List<Student> collect = students.stream()
? ? ? ? ? ? ? ? .sorted((student1, student2) -> student1.getAge() - student2.getAge())
? ? ? ? ? ? ? ? .collect(Collectors.toList());
? ? ? ? ?collect.forEach(System.out::println);
??
? ? ? ? ?/**
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=98.0)
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=91.0)
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=90.0)
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=76.0)
? ? ? ? ? * Student(name=學(xué)生B, school=大學(xué)1, age=18, score=91.0)
? ? ? ? ? * Student(name=學(xué)生C, school=大學(xué)1, age=19, score=65.0)
? ? ? ? ? * Student(name=學(xué)生I, school=大學(xué)3, age=19, score=92.0)
? ? ? ? ? * Student(name=學(xué)生D, school=大學(xué)2, age=20, score=80.0)
? ? ? ? ? * Student(name=學(xué)生F, school=大學(xué)2, age=20, score=67.0)
? ? ? ? ? * Student(name=學(xué)生J, school=大學(xué)4, age=20, score=84.0)
? ? ? ? ? * Student(name=學(xué)生E, school=大學(xué)2, age=21, score=78.0)
? ? ? ? ? * Student(name=學(xué)生G, school=大學(xué)3, age=22, score=87.0)
? ? ? ? ? * Student(name=學(xué)生H, school=大學(xué)3, age=23, score=79.0)
? ? ? ? ? */
? ? }1.6、小結(jié)與綜合應(yīng)用
filter 、distinct、limit、skip、sorted 對(duì)比起 Java 8 之前的一些實(shí)現(xiàn),從我個(gè)人看來(lái)是方便了許多的。
如果是看起來(lái)不習(xí)慣,我覺(jué)得可以試著多用上幾次,會(huì)慢慢愛(ài)上它的。
綜合應(yīng)用
filter 、distinct、limit、skip、sorted 這些操作,他們的執(zhí)行結(jié)果的返回值仍然是 stream,所以在使用中,他們完全可以無(wú)縫鏈接.
如: 我要去這一群學(xué)生中找到 年齡在 20 歲以下,分?jǐn)?shù)在90分以上的前3名學(xué)生。
? ? ?/**
? ? ? * 如: 我要去這一群學(xué)生中找到 年齡在 20 歲以下,分?jǐn)?shù)在90分以上的前3名學(xué)生。
? ? ? *
? ? ? * @param students
? ? ? */
? ? ?private static void select(List<Student> students) {
? ? ? ? ?List<Student> collect = students.stream()
? ? ? ? ? ? ? ? .filter(student -> student.getAge() < 20)
? ? ? ? ? ? ? ? .filter(student -> student.getScore() > 90.0)
? ? ? ? ? ? ? ? .limit(3)
? ? ? ? ? ? ? ? .collect(Collectors.toList());
? ? ? ? ?collect.forEach(System.out::println);
? ? ? ? ?/**
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=98.0)
? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=91.0)
? ? ? ? ? * Student(name=學(xué)生B, school=大學(xué)1, age=18, score=91.0)
? ? ? ? ? */
? ? }二、映射 map
這個(gè)map的映射其實(shí)不光Java 有,JavaScript 也是有的,用法我感覺(jué)是一樣的~
一個(gè)非常常見(jiàn)的數(shù)據(jù)處理套路就是從某些對(duì)象中選擇信息。比如在SQL里,你可以從表中選 擇一列。
用我個(gè)人的話(huà)來(lái)說(shuō),filter 是用來(lái)過(guò)濾元素的,而這一小節(jié)的 map 是用來(lái)創(chuàng)建一個(gè)新的元素。(在官方中的使用的映射一詞,是因?yàn)閙ap 會(huì)接受一個(gè)函數(shù)作為參數(shù),并且將其映射成一個(gè)新的元素。)
可能說(shuō)起來(lái)還是不如實(shí)踐來(lái)的實(shí)在。
數(shù)據(jù)還是上一節(jié)造的那些數(shù)據(jù)。
如:找出集合中所有學(xué)生的姓名,去除掉重復(fù)的名稱(chēng),組成一個(gè) List 集合
?/**
? ? ? * 找出集合中所有學(xué)生的姓名,去除掉重復(fù)的名稱(chēng),組成一個(gè) List<String> 集合
? ? ? *
? ? ? * @param students
? ? ? */
?private static void selectAllStudentName(List<Student> students) {
??
? ? ?List<String> collect = students.stream().map(new Function<Student, String>() {
? ? ? ? ?@Override
? ? ? ? ?public String apply(Student student) {
? ? ? ? ? ? ?return student.getName();
? ? ? ? }
? ? }).distinct().collect(Collectors.toList());
??
? ? ?List<String> list = students.stream().map(student -> {
? ? ? ? ?return student.getName();
? ? }).distinct().collect(Collectors.toList());
??
? ? ?List<String> collect1 = students.stream()
? ? ? ? .map(student -> student.getName())
? ? ? ? .distinct()
? ? ? ? .collect(Collectors.toList());
? ? ?collect1.forEach(System.out::println);
??
? ? ?/**
? ? ? ? ? * 學(xué)生A
? ? ? ? ? * 學(xué)生B
? ? ? ? ? * 學(xué)生C
? ? ? ? ? * 學(xué)生D
? ? ? ? ? * 學(xué)生E
? ? ? ? ? * 學(xué)生F
? ? ? ? ? * 學(xué)生G
? ? ? ? ? * 學(xué)生H
? ? ? ? ? * 學(xué)生I
? ? ? ? ? * 學(xué)生J
? ? ? ? ? */
?}三、查找和匹配
3.1、匹配 anyMatch、allMatch和noneMatch 方法
anyMatch方法可以回答“流中是否有一個(gè)元素能匹配給定的謂詞”
這里的謂詞也就是filter那部分所說(shuō)的一個(gè) 布爾值函數(shù)。
其實(shí)看到 any 的第一眼,大家也明白,任一,只有集合中含有你需要的,那就是返回 true。
? ? ?/**
? ? ? * 判斷這群學(xué)生中有木有年齡大于20歲的學(xué)生
? ? ? *
? ? ? * @param students
? ? ? */
? ? ?private static void anyMatchDemo(List<Student> students) {
? ? ? ? ?boolean anyMatch = students.stream().anyMatch(student -> student.getAge() > 20);
? ? ? ? ?System.out.println(anyMatch);
? ? ? ? ?/**
? ? ? ? ? * true
? ? ? ? ? */
? ? }還有 allMatch 和 noneMatch 他們都和 anyMatch 類(lèi)似。
allMatch 要求全部元素都滿(mǎn)足要求,
noneMatch 則是要求全部元素都不滿(mǎn)足要求時(shí)返回true。
3.2、查找 findAny 與 findFirst
findAny 方法將返回當(dāng)前流中的任意元素。
它的搭檔一般是 filter,和 filter 使用可以實(shí)現(xiàn)很多操作。
如我想要當(dāng)確定這群學(xué)生中有20歲以上的學(xué)生時(shí)立馬返回結(jié)果。
?/**
? ? ? * 當(dāng)確定這群學(xué)生中有20歲以上的學(xué)生時(shí)即返回。
? ? ? *
? ? ? * @param students
? ? ? */
?private static void findAnyDemo(List<Student> students) {
? ? ?Optional<Student> student1 = students.stream().filter(student -> student.getAge() > 20).findAny();
? ? ?Student student = student1.get();
? ? ?System.out.println(student);
? ? ?/**
? ? ? ? ? * Student(name=學(xué)生E, school=大學(xué)2, age=21, score=78.0)
? ? ? ? ? */
?}這里的 Optional 是 Java 8 新增的一個(gè) 容器類(lèi),作用就是用來(lái)判斷存在和不存在。也就是大家常談到的更優(yōu)雅的判空操作。
Optional 幾個(gè)常見(jiàn)的Api
isPresent()將在Optional包含值的時(shí)候返回true, 否則返回falseifPresent(Consumer<T> block)會(huì)在值存在的時(shí)候執(zhí)行給定的代碼塊。T get()會(huì)在值存在時(shí)返回值,否則拋出一個(gè)NoSuchElement異常。T orElse(T other)會(huì)在值存在時(shí)返回值,否則返回一個(gè)默認(rèn)值。
詳細(xì)的用法,大家也可以去了解了解,這也是非常好用的一個(gè)東東。
findFirst 其實(shí)就是確定返回第一個(gè)元素。它也和 filter 一起搭配使用。
咋一看, findany 和 findFirst 不是一樣嗎,其實(shí)在你對(duì)于返回的第一個(gè)元素沒(méi)有明確要求時(shí),你可以理解成他們確實(shí)就是一樣的。
但其實(shí)他們真實(shí)區(qū)別并非體現(xiàn)如此,而是在 stream 中的并行流中。
今天沒(méi)談這個(gè),大家可以去了解了解,了解并行流就會(huì)和常常聊到的性能相關(guān)啦,到底那種好一些啥的~
3.3、小結(jié)
anyMatch、allMatch和noneMatch這三個(gè)操作都用到了我們所謂的短路。
就是我們剛學(xué)語(yǔ)法時(shí)的 && 和 || 運(yùn)算符,這也算是他們?cè)?stream 的實(shí)現(xiàn)。
最簡(jiǎn)單的理解方式,就是他們通過(guò)遍歷,組成了一個(gè)很長(zhǎng)很長(zhǎng)的布爾表達(dá)式。
除去他們能實(shí)現(xiàn)短路操作, findAny 與 findFirst 也是同樣如此,并非都需要遍歷結(jié)束才會(huì)得到最終的結(jié)果。只要在其中某一次中達(dá)成條件,即可返回結(jié)果。
四、歸約
官方的說(shuō)法,成為歸約,如果用簡(jiǎn)單的話(huà)語(yǔ)來(lái)說(shuō)的話(huà),可以理解為將多個(gè)東西歸為一堆。
4.1、元素求和 reduce
? ? ?private static void reduceDemo() {
? ? ? ? ?List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
??
? ? ? ? ?Integer reduce = list.stream().reduce(0, (a, b) -> a + b);
? ? ? ? ?System.out.println("list集合的總和:==>" + reduce);
??
? ? ? ? ?Integer reduce1 = list.stream().reduce(1, (a, b) -> a * b);
? ? ? ? ?System.out.println("list集合中的元素相乘結(jié)果==>" + reduce1);
??
? ? ? ? ?Optional<Integer> reduce2 = list.stream().reduce((a, b) -> a + b);
? ? ? ? ?Integer integer = reduce2.get();
? ? ? ? ?System.out.println("list 集合的總和==>"+integer);
? ? ? ? ?/**
? ? ? ? ? * list集合的總和:==>55
? ? ? ? ? * list集合中的元素相乘結(jié)果==>3628800
? ? ? ? ? * list 集合的總和==>55
? ? ? ? ? */
? ? }
reduce接受兩個(gè)參數(shù):
一個(gè)初始值,這里是0;
一個(gè) BinaryOperator 來(lái)將兩個(gè)元素結(jié)合起來(lái)產(chǎn)生一個(gè)新值,BinaryOperator 也是funcational 接口,所以也可以使用lambda 表達(dá)式 lambda (a, b) -> a + b 來(lái)表示。
?Integer reduce = list.stream().reduce(0, (a, b) -> a + b);
另外還有一個(gè)重載函數(shù),就是沒(méi)有初始值版本的,它的返回值是Optional<Integer> 的容器類(lèi)。
? ?Optional<Integer> reduce2 = list.stream().reduce((a, b) -> a + b);
最大值與最小值:
?private static void reduceDemo2(){
? ? ?List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
? ? ?Optional<Integer> max = list.stream().reduce(Integer::max);
? ? ?Optional<Integer> min = list.stream().reduce(Integer::min);
? ? ?System.out.println("max==>"+max.get());
? ? ?System.out.println("min==>"+min.get());
? ? ?/**
? ? ? ? ? * max==>10
? ? ? ? ? * min==>1
? ? ? ? ? */
?}后記
到此這篇關(guān)于Java 中如何使用 stream 流的文章就介紹到這了,更多相關(guān)Java stream 流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC中的ResourceUrlProviderExposingInterceptor詳解
這篇文章主要介紹了SpringMVC中的ResourceUrlProviderExposingInterceptor詳解,ResourceUrlProviderExposingInterceptor是Spring MVC的一個(gè)HandlerInterceptor,用于向請(qǐng)求添加一個(gè)屬性,需要的朋友可以參考下2023-12-12
SpringBoot整合RestTemplate用法的實(shí)現(xiàn)
本篇主要介紹了RestTemplate中的GET,POST,PUT,DELETE、文件上傳和文件下載6大常用的功能,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08
Java 利用binarySearch實(shí)現(xiàn)抽獎(jiǎng)計(jì)算邏輯
這篇文章主要介紹了Java 利用binarySearch實(shí)現(xiàn)抽獎(jiǎng)計(jì)算邏輯,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12
SpringBoot利用ThreadPoolTaskExecutor批量插入百萬(wàn)級(jí)數(shù)據(jù)
在處理大量數(shù)據(jù)時(shí),為了提高效率和性能,通常需要采用批量插入的方式,本文主要介紹了SpringBoot利用ThreadPoolTaskExecutor批量插入百萬(wàn)級(jí)數(shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
Springboot?jpa使用sum()函數(shù)返回結(jié)果如何被接收
這篇文章主要介紹了Springboot?jpa使用sum()函數(shù)返回結(jié)果如何接收,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
SpringBoot自動(dòng)配置原理,你真的懂嗎?(簡(jiǎn)單易懂)
這篇文章主要介紹了SpringBoot自動(dòng)配置原理,你真的懂嗎?本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
java實(shí)現(xiàn)簡(jiǎn)單銀行管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單銀行管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
從搭建Struts2 開(kāi)發(fā)環(huán)境說(shuō)起
本篇文章,小編為大家介紹從搭建Struts2 開(kāi)發(fā)環(huán)境說(shuō)起,有需要的朋友可以參考一下2013-04-04

