Java8中的Stream API详解:Stream的背景及使用

摘要:
  
  Stream是Java8的一大亮点,是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的 聚合操作(aggregate operation)或者大批量数据操作。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。所以说,Java8中首次出现的 java.util.stream是一个函数式语言+多核时代综合影响的产物。


版权声明:

  本文转载于陈争云,占宇剑和司磊在developerWorks上发表的《Java8中的Streams API详解》一文。


一. 引子

1、为什么需要Stream ?

  Stream作为Java8的一大亮点,它与java.io包里的InputStream和OutputStream是完全不同的概念。它也不同于StAX对XML解析的Stream,也不是Amazon Kinesis对大数据实时处理的Stream。Java8中的Stream是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的 聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。通常,编写并行代码很难而且容易出错, 但使用Stream API无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java8中首次出现的 java.util.stream是一个函数式语言+多核时代综合影响的产物。


2、什么是聚合操作

  在传统的J2EE应用中,Java代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:

  • 客户每月平均消费金额
  • 最昂贵的在售商品
  • 本周完成的有效订单(排除了无效的)
  • 取十个数据样本作为首页推荐

这类的操作。但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,很多时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。而Java的集合API中,仅仅有极少量的辅助型方法,更多的时候是程序员需要用Iterator来遍历集合,完成相关的聚合应用逻辑,这是一种远不够高效、笨拙的方法。在Java7中,如果要发现type为grocery的所有交易,然后返回以交易值降序排序好的交易ID集合,我们需要这样写:

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
 if(t.getType() == Transaction.GROCERY){
 groceryTransactions.add(t);
 }
}

Collections.sort(groceryTransactions, new Comparator(){
 public int compare(Transaction t1, Transaction t2){
 return t2.getValue().compareTo(t1.getValue());
 }
});

List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
 transactionsIds.add(t.getId());
}

  而在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。

List<Integer> transactionsIds = transactions.parallelStream()
.filter(t -> t.getType() == Transaction.GROCERY)
.sorted(comparing(Transaction::getValue).reversed())
.map(Transaction::getId).collect(toList());

二. Stream总览

1、什么是流?

  Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如,“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

  而和迭代器又不同的是,Stream可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream的并行操作依赖于Java7中引入的Fork/Join框架(JSR166y)来拆分任务和加速处理过程。

  Stream 的另外一大特点是,数据源本身可以是无限的。


2、流的构成

  当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示:

         这里写图片描述


3、Stream的生成方式

(1)从Collection和数组获得

  • Collection.stream()
  • Collection.parallelStream()
  • Arrays.stream(T array) or Stream.of()

(2)从BufferedReader获得

  • java.io.BufferedReader.lines()

(3)静态工厂

  • java.util.stream.IntStream.range()
  • java.nio.file.Files.walk()

(4)自己构建

  • java.util.Spliterator

(5)其他

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

4、流的操作类型

  流的操作类型分为两种:

  • Intermediate:一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。

  • Terminal:一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以,这必定是流的最后一个操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个side effect。

      在对一个Stream进行多次转换操作(Intermediate 操作),每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是N(转换次数)个for循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是lazy的,多个转换操作只会在Terminal操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在Terminal 操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。

      还有一种操作被称为short-circuiting。用以指:对于一个intermediate操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新Stream;对于一个terminal操作,如果它接受的是一个无限大的Stream,但能在有限的时间计算出结果。
    当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个short-circuiting操作是必要非充分条件。


三. 流的使用详解

  简单说,对Stream的使用就是实现一个filter-map-reduce过程,产生一个最终结果,或者导致一个副作用(side effect)。


1).流的构造与转换

  下面提供最常见的几种构造Stream的例子:

// 1. Individual values
Stream stream = Stream.of("a", "b", "c");

// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);

// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();

  需要注意的是,对于基本数值型,目前有三种对应的包装类型Stream:IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long>和Stream<Double>,但是boxing/unboxing会很耗时,所以特别为这三种基本数值型提供了对应的Stream。

  Java8中还没有提供其它数值型Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种Stream进行。

IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);

  流也可以转换为其它数据结构,例如:

// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

2).流的操作

  接下来,当把一个数据结构包装成Stream后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下:

  • Intermediate 操作

     map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal 操作

     forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting 操作

     anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

     我们下面看一下Stream的比较典型用法。


(1).Intermediate 操作

  • map/flatMap

     我们先来看map,它的作用就是把inputStream的每个元素映射成outputStream的另外一个元素,例如:

List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().map(n -> n * n)
.collect(Collectors.toList());

 从上面例子可以看出,map生成的是个1:1映射,每个输入元素都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要flatMap,例如:

Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());

 flatMap把inputStream中的层级结构 扁平化,就是将最底层元素抽出来放到一起,最终output的新Stream里面已经没有List了,都是直接的数字。


  • filter

     filter对原始Stream进行某项测试,通过测试的元素被留下来生成一个新Stream。

// 留下偶数
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens =
Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);

  • forEach

     forEach方法接收一个Lambda表达式,然后在Stream的每一个元素上执行该表达式。

// 对一个人员集合遍历,找出男性并打印姓名。
roster.stream().filter(p -> p.getGender() == Person.Sex.MALE)
.forEach(p -> System.out.println(p.getName()));

 可以看出来,forEach是为Lambda而设计的,保持了最紧凑的风格。当需要为多核系统优化时,可以parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时forEach本身的实现不需要调整,而Java8以前的for循环代码可能需要加入额外的多线程逻辑。但一般认为,forEach和常规for循环的差异不涉及到性能,它们仅仅是函数式风格与传统 Java 风格的差别。

 另外一点需要注意,forEach是terminal操作。因此,它执行后,Stream 的元素就被“消费”掉了,你无法对一个Stream进行两次terminal运算。下面的代码是错误的:

     stream.forEach(element -> doOneThing(element));
     stream.forEach(element -> doAnotherThing(element));

 相反,具有相似功能的intermediate操作peek可以达到上述目的。如下是出现在Stream api javadoc上的一个示例:

// peek 对每个元素执行操作并返回一个新的 Stream
Stream.of("one", "two", "three", "four").filter(e -> e.length() > 3)
 .peek(e -> System.out.println("Filtered value: " + e)).map(String::toUpperCase)
 .peek(e -> System.out.println("Mapped value: " + e)).collect(Collectors.toList());

 forEach 不能修改自己包含的本地变量值,也不能用break/return之类的关键字提前结束循环。


  • findFirst

     这是一个termimal兼short-circuiting操作,它总是返回Stream的第一个元素或者空。这里比较重点的是它的返回值类型Optional:这也是一个模仿 Scala 语言中的概念,作为一个容器,它可能含有某值,或者不包含,使用它的目的是尽可能避免NullPointerException。

// Optional 的两个用例:以下两组示例是等价的

 // Java 8
 Optional.ofNullable(text).ifPresent(System.out::println);

 // Pre-Java 8
 if (text != null) {
 System.out.println(text);
 }

//----------

 // Java 8
return Optional.ofNullable(text).map(String::length).orElse(-1);

// Pre-Java 8
return if (text != null) ? text.length() : -1;
 };

 在更复杂的if (xx != null)的情况中,使用Optional代码的可读性更好,而且它提供的是编译时检查,能极大的降低NPE这种Runtime Exception 对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时再发现和调试。

 Stream中的findAny、max/min、reduce等方法等返回Optional值。还有例如IntStream.average()返回OptionalDouble等等。


  • reduce

     这个方法的主要作用是把Stream元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面Stream的第一个、第二个、第n个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average都是特殊的reduce。例如Stream的sum就相当于:

Integer sum = integers.reduce(0, (a, b) -> a+b);

Integer sum = integers.reduce(0, Integer::sum);

也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

// reduce 的用例

// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);

// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 

// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);

// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();

// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);

 上面代码例如第一个示例的reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。


  • limit/skip

     limit返回Stream的前面n个元素;skip则是扔掉前n个元素(它是由一个叫 subStream的方法改名而来)。

//limit 和 skip 对运行次数的影响
public void testLimitAndSkip() {
 List<Person> persons = new ArrayList();
 for (int i = 1; i <= 10000; i++) {
 Person person = new Person(i, "name" + i);
 persons.add(person);
 }
List<String> personList2 = persons.stream().
map(Person::getName).limit(10).skip(3).collect(Collectors.toList());
 System.out.println(personList2);
}
private class Person {
 public int no;
 private String name;
 public Person (int no, String name) {
 this.no = no;
 this.name = name;
 }
 public String getName() {
 System.out.println(name);
 return name;
 }
}

输出结果为:
name1
name2
name3
name4
name5
name6
name7
name8
name9
name10
[name4, name5, name6, name7, name8, name9, name10]

 这是一个有10,000个元素的Stream,但在short-circuiting操作limit和skip的作用下,管道中map操作指定的getName()方法的执行次数为 limit 所限定的10次,而最终返回结果在跳过前3个元素后只有后面7个返回。

 有一种情况是limit/skip无法达到short-circuiting目的的,就是把它们放在Stream的排序操作后,原因跟sorted这个intermediate操作有关:此时系统并不知道Stream排序后的次序如何,所以sorted中的操作看上去就像完全没有被limit或者skip一样。

// limit 和 skip 对 sorted 后的运行次数无影响
List<Person> persons = new ArrayList();
 for (int i = 1; i <= 5; i++) {
 Person person = new Person(i, "name" + i);
 persons.add(person);
 }
List<Person> personList2 = persons.stream().sorted((p1, p2) -> 
p1.getName().compareTo(p2.getName())).limit(2).collect(Collectors.toList());
System.out.println(personList2);

输出结果为:
name2
name1
name3
name2
name4
name3
name5
name4
[stream.StreamDW$Person@816f27d,stream.StreamDW$Person@87aac27]

 即虽然最后的返回元素数量是 2,但整个管道中的 sorted 表达式执行次数没有像前面例子相应减少。最后有一点需要注意的是,对一个parallel的Stream 管道来说,如果其元素是有序的,那么limit操作的成本会比较大,因为它的返回对象必须是前n个也有一样次序的元素。取而代之的策略是取消元素间的次序,或者不要用parallel Stream。


  • sorted

     对Stream的排序通过sorted进行,它比数组的排序更强之处在于你可以首先对Stream进行各类map、filter、limit、skip甚至distinct来减少元素数量后再排序,这能帮助程序明显缩短执行时间。例如:

// 优化:排序前进行 limit 和 skip
List<Person> persons = new ArrayList();
 for (int i = 1; i <= 5; i++) {
 Person person = new Person(i, "name" + i);
 persons.add(person);
 }

List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);

结果会简单很多:

name2
name1
[stream.StreamDW$Person@6ce253f1,stream.StreamDW$Person@53d8d10a]

当然,这种优化是有business logic上的局限性的:即不要求排序后再取值。


  • min/max/distinct

     min和max的功能也可以通过对Stream元素先排序,再findFirst来实现,但前者的性能会更好为O(n),而sorted的成本是O(nlogn)。同时它们作为特殊的reduce方法被独立出来也是因为求最大最小值是很常见的操作。

// 找出最长一行的长度
BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log"));
int longest = br.lines().mapToInt(String::length).max().getAsInt();
br.close();
System.out.println(longest);

  • distinct

     下面的例子则使用distinct来找出不重复的单词。

// 找出全文的单词,转小写,并排序
List<String> words = br.lines().flatMap(line -> Stream.of(line.split(" "))).
 filter(word -> word.length() > 0).map(String::toLowerCase).distinct().sorted()
 .collect(Collectors.toList());
br.close();
System.out.println(words);

  • Match

     Stream有三个match方法,从语义上说:

     (1).allMatch:Stream 中全部元素符合传入的 predicate,返回 true;
     (2).anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true;
     (3).noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true.

     它们都不是要遍历全部元素才能返回结果。例如allMatch只要一个元素不满足条件,就skip剩下的所有元素,返回false。对清单13中的Person类稍做修改,加入一个age属性和getAge方法。

// 使用 Match
List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));

boolean isAllAdult = persons.stream().allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);

输出结果:
 All are adult? false
 Any child? true

四. 小结

总之,Stream 的特性可以归纳为:

  • 不是数据结构;

  • 它没有内部存储,它只是用操作管道从source(数据结构、数组、generator function、IO channel)抓取数据;

  • 它也绝不修改自己所封装的底层数据结构的数据。例如Stream的filter操作会产生一个不包含被过滤元素的新Stream,而不是从source删除那些元素;

  • 所有Stream的操作必须以lambda表达式为参数;

  • 不支持索引访问;

  • 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个;

  • 很容易生成数组或者List;

  • 惰性化;

  • 很多Stream操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始;

  • Intermediate操作永远是惰性化的;

  • 并行能力;

  • 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的;

  • 可以是无限的。集合有固定大小,Stream 则不必。limit(n)和findFirst()这类的short-circuiting操作可以对无限的Stream进行运算并很快完成。


引用:

Java8中的Streams API详解

  • 100
    点赞
  • 512
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
基本信息 作者: 臧萌 出版社:清华大学出版社 ISBN:9787302217831 上架时间:2010-3-30 出版日期:2010 年3月 开本:16开 其他详细信息查看:http://www.china-pub.com/196571 编辑推荐 Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让知识环环相扣,降低了学习的难度 通过大量的比喻、类比、对比和图示等多种讲解方式,学习效果好 对Java语言的每个语法都提供了一个或多个例程讲解 大量使用流程图表示程序的执行过程,使用结构图表示程序的内部状态 每章最后都给出了典型的练习题,让读者及时练习,巩固提高,并提供了参考答案 目录 第1篇 Java语言基本语法 第1章 让自己的第一个Java程序跑起来 2 教学视频:19分钟 1.1 想要用Java改变这个世界吗? 2 1.1.1 Java有什么优势? 2 1.1.2 Java在哪儿? 3 1.2 准备好开始Java之旅 3 1.2.1 下载JDK 4 1.2.2 安装JDK 5 1.2.3 配置环境变量 6 1.2.4 测试环境是否安装成功 8 1.2.5 如果失败了怎么办? 9 1.3 让自己的第一个程序运行起来 10 1.3.1 编写自己的Hello World源程序 10 1.3.2 编译自己的HelloWorld程序 11 1.3.3 让代码运行起来 13 1.4 初探Hello World 14 1.4.1 类(Class):Java世界一类物体 14 1.4.2 方法(Method):物体的功能 15 1.4.3 main()方法:所有Java程序执行的起点 15 .1.5 名词解释 16 1.5.1 JDK和Java平台 16 1.5.2 Java编译器(Java Compiler) 17 1.5.3 Java类库(Java Class Libraries) 17 1.5.4 Java虚拟机(Java Virtual Machine) 17 1.5.5 HelloWorld的整个流程 17 1.6 小结:我们学会了编译和运行一个Java程序! 18 1.7 习题 19 第2章 搭建自己的集成开发环境 20 教学视频:31分钟 2.1 安装集成开发环境 20 2.1.1 集成开发环境有哪些 20 2.1.2 安装Eclipse 21 2.2 Eclipse界面介绍 23 2.2.1 启动Eclipse 23 2.2.2 Eclipse的Perspective 24 2.2.3 Eclipse的菜单 25 2.2.4 Eclipse的工具条 25 2.2.5 Eclipse辅助视图区 25 2.2.6 EclipsePackage Explorer 26 2.2.7 Eclipse的源代码编辑器 26 2.2.8 Eclipse的设置窗口 26 2.2.9 Eclipse的其他视图 27 2.3 如何使用Eclipse 28 2.3.1 在Eclipse创建自己的第一个项目 28 2.3.2 在Eclipse编写HelloWorld程序 29 2.3.3 通过Eclipse运行Hello World 31 2.4 小结:Eclipse——功能很强大 32 2.5 习题 32 第3章 Java的基本数据类型和运算符 33 教学视频:1小时5分钟 3.1 Java的基本数据类型 33 3.1.1 基本数据类型——编程语言的数据原子 33 3.1.2 Java的基本上数据类型介绍 34 3.1.3 基本数据类型值域 34 3.2 Java运算符 36 3.2.1 变量的概念 36 3.2.2 插曲:Java的语句 37 3.2.3 创建一个变量和变量名的规范 37 3.2.4 Java的基本运算符和表达式 39 3.2.5 Java的布尔运算符 43 3.3 基本数据类型运算的难点 47 3.3.1 强制类型转换——小数哪里去了 48 3.3.2 类型的转换在运算悄悄进行 50 3.3.3 强制类型转换最优先 52 3.3.4 等号其实不简单 52 3.3.5 小心使用浮点数进行比较 53 3.3.6 boolean和char 55 3.3.7 不要使用还没有创建出来的变量 57 3.3.8 String——char串起的项链 58 3.3.9 转义符——看不见写得出 61 3.4 小结:基本数据类型—— Java一切数据和运算的基础 63 3.5 习题 65 第4章 Java的程序执行流程 67 教学视频:1小时57分钟 4.1 顺序执行 67 4.2 使用if-else让程序懂得判断 68 4.2.1 if语句 68 4.2.2 if语句的嵌套 71 4.2.3 if-else语句 73 4.2.4 if-else语句嵌套 75 4.3 使用while进行循环 76 4.3.1 使用while语句 76 4.3.2 使用do-while语句 79 4.4 使用for进行循环 80 4.4.1 自增和自减操作 80 4.4.2 for语句 82 4.4.3 for语句省略形式 84 4.5 语句不能不说的事 84 4.5.1 小心复杂语句创建的变量 85 4.5.2 别让循环次数给弄懵了 86 4.5.3 循环的嵌套 87 4.6 continue关键字与break关键字 88 4.6.1 continue关键字 88 4.6.2 break关键字 89 4.7 使用switch进行跳转 90 4.8 大例子 94 4.8.1 从控制台读取数据 94 4.8.2 结账程序的循环 96 4.9 小结:Java不是一个直肠子 98 4.10 习题 99 第5章 数组 100 教学视频:35分钟 5.1 什么是数组 100 5.1.1 假设:如果需要逐个定义变量 100 5.1.2 数组初探 101 5.1.3 数组——物以类聚 104 5.1.4 数组元素的值内有乾坤 105 5.1.5 创建数组的简洁语法 106 5.2 数组的“名”与“实” 107 5.2.1 “名”与“实”分离的数组 107 5.2.2 一“实”多“名”的数组 109 5.2.3 一“实”多“名”带来的困惑 111 5.3 多维数组 114 5.3.1 什么是多维数组 114 5.3.2 多维数组的实质 115 5.4 数组大练兵 123 5.4.1 轻松查询全班成绩 123 5.4.2 轻松查询全校成绩不在话下 124 5.4.3 杨辉三角 125 5.5 小结:方便快速的数组 129 5.6 习题 129 第2篇 Java语言高级语法 第6章 Java的类(Class)和对象(Object) 132 教学视频:59分钟 6.1 驾驶汽车向类(Class)的世界进发 132 6.1.1 汽车带来的问题 132 6.1.1 类的组成 134 6.1.3 使用自定义的Car类 136 6.1.4 类和对象 139 6.1.5 源文件的存放 141 6.1.5 理解引用 143 6.1.7 null关键字 145 6.2 巧妙使用的属性 147 6.2.1 在类给每个变量一个初始值 147 6.2.2 定义自己的引用 147 6.2.3 使用操作符的技巧 148 6.2.4 类的数组 149 6.3 小结:Java其实是个类和对象的世界 152 6.4 习题 153 第7章 Java的方法——给汽车丰富多彩的功能 154 教学视频:2小时55分钟 7.1 方法:让汽车动开动 154 7.1.1 引出问题:开动汽车 154 7.1.2 那么,方法到底是什么呢? 155 7.1.3 方法调用过程初探 156 7.2 Java普通方法的组成部分 157 7.2.1 访问控制符:public 158 7.2.2 返回值和关键字void 158 7.2.3 方法名(Method Name) 159 7.2.4 参数列表(Parameter List) 159 7.2.5 方法体(Method Body) 160 7.2.6 方法串串烧 160 7.3 方法的参数:让汽车加速 161 7.3.1 方法的参数:让汽车可以加速 161 7.3.2 带参数的方法有何不同? 162 7.3.3 让方法有多个参数 163 7.4 返回值:汽车超速了吗? 164 7.4.1 写一个有返回值的方法 164 7.4.2 调用有返回值的方法 165 7.4.3 发生了什么?如何使用方法的返回值? 166 7.4.4 使用return结束方法 166 7.5 方法重载(overload):给汽车加速添个限制 168 7.5.1 什么是方法的签名 168 7.5.2 什么是重载?为什么要重载? 168 7.5.3 给汽车加个重载的方法 169 7.5.4 测试一下 169 7.5.5 重载容易引发误解的两个地方——返回类型和形参名 170 7.5.6 重载的最难点——参数匹配原则 171 7.6 使用类的实例作为方法参数 172 7.6.1 超车方法:使用类实例做参数 172 7.6.2 调用这个方法 173 7.6.3 发生了什么 174 7.7 加餐:局部变量和实例变量 175 7.7.1 什么是局部变量(Local Variable) 175 7.7.2 什么是实例变量(Instance Variable) 177 7.8 this关键字:指向对象自己的引用 177 7.8.1 发现问题:当实例变量和局部变量重名 177 7.8.2 经常深藏不露的this关键字 178 7.8.3 在方法调用方法 179 7.9 构造方法(Constructor) 181 7.9.1 构造(Constructor)方法初探 181 7.9.2 如何使用构造方法 182 7.9.3 留个无参数的构造方法——给重要属性赋初始值 183 7.9.4 在构造方法调用构造方法 184 7.10 方法大汇总 185 7.10.1 本例用到的类 186 7.10.2 使用例程将本章的知识穿起来 189 7.11 小结:多方位理解Java方法 191 7.12 习题 192 第8章 Java的包(Package)命名习惯和注释 193 教学视频:43分钟 8.1 Java的包(Package) 193 8.1.1 Java的包 193 8.1.2 在Eclipse使用包 194 8.1.3 天上掉下个package 197 8.1.4 包带来了什么? 197 8.2 import语句:化繁为简 200 8.2.1 import语句 200 8.2.2 一网打尽包所有类 201 8.2.3 import语句带来的小问题 202 8.2.4 默认引入的包 204 8.3 命名习惯大回顾 204 8.4 Java的注释 205 8.4.1 使用双斜杠的单行注释 205 8.4.2 多行注释 206 8.4.3 Javadoc注释 206 8.5 小结:包让Java的类更清晰优雅 208 8.6 习题 209 第9章 再看数组、字符串和main()方法 210 教学视频:29分钟 9.1 数组也是类 210 9.1.1 得到数组的长度 210 9.1.2 加餐:不可改变的final变量 211 9.1.3 多维数组的长度 212 9.1.4 一维数组的clone()方法 212 9.1.5 当数组类型不再是基本数据类型 214 9.1.6 多维数组的clone()方法 217 9.2 老朋友String类 220 9.2.1 遍历String的字符 220 9.2.2 获取字符串的一部分 222 9.2.3 判断两个字符串是否相等 223 9.2.4 判断字符串的开头和结尾 225 9.2.5 分割字符串 225 9.2.6 在字符串查找子字符串或字符 226 9.2.7 替换字符串的内容 226 9.2.8 String对象——磐石刻字 227 9.3 String类的最佳拍档——StringBuffer类 227 9.3.1 StringBuffer:专业操纵字符 228 9.3.2 String和StringBuffer一个都不能少 229 9.4 最熟悉的陌生人:main()方法 229 9.4.1 main()方法的参数 229 9.4.2 static关键字 232 9.4.3 当方法遇到static关键字 233 9.5 小结:学会使用的方法 235 9.6 习题 236 第10章 继承和多态 237 教学视频:1小时55分钟 10.1 继承——最优的解决方案 237 10.1.1 饭前水果:实例变量的访问控制符 237 10.1.2 一切还是从汽车开始 238 10.1.3 一类车,一个类 241 10.1.4 分开也有麻烦 244 10.1.5 使用继承——问题迎刃而解 245 10.1.6 使用Bus类 248 10.1.7 Java的单继承 248 10.1.8 Java的类图 249 10.1.9 万类之祖——Object类 250 10.2 子类对象?父类对象? 251 10.2.1 父随子行 251 10.2.2 当构造方法遇到继承 254 10.2.3 记得给类一个无参数的构造方法 255 10.2.4 调用父类的构造方法 256 10.2.5 对象也会“变脸” 258 10.2.6 遵守语法,正确“变脸” 262 10.3 覆盖——与继承如影随形 264 10.3.1 当方法不再通用 264 10.3.2 覆盖——让众口不再难调 265 10.3.3 覆盖——到底调用了哪个方法 270 10.3.4 覆盖的语法不简单 272 10.3.5 更复杂的使用覆盖的情况 274 10.3.6 覆盖——不得不打开的潘多拉魔盒 276 10.3.7 使用super调用父类的方法和属性 278 10.4 多态(Polymorphism)以及其他 279 10.4.1 多态——运行方知结果 280 10.4.2 重载也不简单 280 10.4.3 使用多态构建车队 283 10.5 在多态的环境拨开迷雾 284 10.5.1 神秘的Class类 284 10.5.2 覆盖不再神秘 285 10.5.3 instanceof运算符——让对象告诉你它的类是谁 286 10.6 小结:继承和多态让世界丰富多彩 287 10.7 习题 290 第11章 修饰符(Qualifier) 291 教学视频:26分钟 11.1 插曲:类的组成部分的名字 291 11.2 类的修饰符 292 11.2.1 无修饰符类 292 11.2.2 类的可见性 293 11.2.3 final——让类不可被继承 295 11.2.4 理解final关键字 296 11.2.5 总结:类的修饰符 297 11.3 方法的修饰符 297 11.3.1 方法的访问控制符 298 11.3.2 public:没有限制的修饰符 299 11.3.3 protected:仅对子类和同包的类可见 300 11.3.4 默认控制符:仅在本包可见 301 11.3.5 private:仅对本类可见 303 11.3.6 理解4个访问控制符 304 11.3.7 访问控制符可见性汇总 306 11.3.8 访问控制符带来的覆盖问题 306 11.3.9 final:不允许方法被覆盖 310 11.3.10 重温静态方法 311 11.3.11 静态方法——类范围里的概念 312 11.3.12 静态方法何以为“静态” 314 11.4 变量的修饰符 316 11.4.1 变量方法皆成员 317 11.4.2 变量的访问控制符 317 11.4.3 使用private修饰类的成员变量 318 11.4.4 使用private,然后呢? 320 11.4.5 变量的覆盖 322 11.4.6 使用final修饰成员变量 325 11.4.7 静态成员变量 326 11.4.8 局部变量的修饰符 326 11.4.9 当final遇到引用类型成员变量 327 11.5 小结:修饰符作用大 328 11.6 习题 330 第12章 接口 331 教学视频:29分钟 12.1 自行车带来的问题 331 12.1.1 记录马路上的车辆 331 12.1.2 引发问题的自行车 335 12.1.3 仔细分析recordTransport()方法 338 12.2 初用接口 339 12.2.1 准备好需要用到的类 339 12.2.2 认识接口的代码组成 340 12.2.3 什么是接口 341 12.2.4 使用接口仅需一步——实现接口 342 12.2.5 接口——让类集多重类型于一身 344 12.2.6 简化recordTransport()方法 347 12.3 再探接口 349 12.3.1 重温上节的程序 349 12.3.2 面向接口编程 351 12.3.3 话说“抽象” 353 12.3.4 接口大瘦身 355 12.3.5 实现多个接口 355 12.3.6 接口的变量 357 12.3.7 接口的继承 358 12.3.8 匹配抽象方法的类型 359 12.3.9 空接口 361 12.4 小结:接口的难点在于何时使用 362 12.5 习题 364 第13章 抽象类和内部类 365 教学视频:26分钟 13.1 抽象类(Abstract Class) 365 13.1.1 不知道怎么打招呼的Person类 365 13.1.2 当类有了抽象方法 367 13.1.3 抽象类语法详解 368 13.1.4 理解抽象类的作用 369 13.2 内部类的分类(Inner Class) 370 13.2.1 成员内部类 370 13.2.2 局部内部类 372 13.3 成员内部类 374 13.3.1 使用成员内部类 374 13.3.2 成员内部类的修饰符 375 13.3.3 在类外部使用内部类 376 13.3.4 非静态内部类的特性 378 13.3.5 外部类访问成员内部类的属性 382 13.3.6 静态成员内部类 383 13.4 局部内部类 384 13.4.1 局部内部类之“局部” 385 13.4.2 局部内部类之“内部类” 386 13.4.3 使用局部内部类 388 13.5 匿名内部类(Anonymous inner classes) 389 13.5.1 准备工作 389 13.5.2 匿名内部类的语法 389 13.5.3 通过接口使用匿名类 390 13.5.4 通过抽象类使用匿名类 391 13.6 类,这样一路走来 391 13.7 小结:丰富多彩的类 395 13.8 习题 397 第14章 Java的异常处理机制 398 教学视频:36分钟 14.1 认识异常 398 14.1.1 异常什么时候发生 398 14.1.2 异常是什么 401 14.1.3 Java异常机制的流程 401 14.2 抛出异常 403 14.2.1 异常类的父类——Throwable 403 14.2.2 在代码使用throw抛出一个异常 404 14.2.3 在方法声明使用throws 407 14.2.4 构造自定义异常类 409 14.2.5 使用自定义异常类 410 14.3 异常的传递 411 14.3.1 抛出最确切的异常类型 411 14.3.2 Java异常的传递 412 14.3.3 图说Java异常的传递 414 14.4 异常的处理 418 14.4.1 把异常捉住 418 14.4.2 图说异常处理流程 421 14.4.3 多类异常,一并处理 424 14.4.4 try-catch-finally语句 426 14.4.5 try-finally语句 431 14.4.6 好好利用catch语句 432 14.5 异常的类型 433 14.5.1 3个类的继承关系 433 14.5.2 必须处理的Exception类 434 14.5.3 灵活掌握的RuntimeException类 434 14.5.4 不用处理的Error类 435 14.6 小结:终止错误的蔓延 435 14.7 习题 437 第15章 多线程编程 438 教学视频:1小时14分钟 15.1 线程——执行代码的机器 438 15.1.1 线程——执行代码的基本单位 438 15.1.2 演奏会模型 440 15.2 Java的线程编程 443 15.2.1 线程类Thread 443 15.2.2 覆盖Thread类的run()方法 444 15.2.3 使用Runnable接口 446 15.2.4 两个线程 448 15.3 深入学习Thread类 449 15.3.1 线程的名字 449 15.3.2 得到当前的线程 451 15.3.3 让线程“沉睡” 453 15.4 多个线程的故事 457 15.4.1 一个有多个线程的程序 457 15.4.2 复印社模型 459 15.4.3 一个简单的复印社例程 461 15.5 多个线程的同步 463 15.5.1 线程同步之synchronized关键字 463 15.5.2 深入学习synchronized关键字 468 15.5.3 静态同步方法 469 15.5.4 非静态的同步方法 472 15.5.5 银行的麻烦——账户乱套了 474 15.5.6 多角度理解同步方法 481 15.5.7 闲话同步方法的使用 484 15.5.8 同步代码块 485 15.5.9 锁(Lock) 486 15.5.10 线程同步之wait()和notify()方法 488 15.5.11 wait和notify的顺序 491 15.6 小结:线程——代码执行器 494 15.7 习题 495 第3篇 Java语言编程进阶 第16章 如何学习本篇 498 教学视频:15分钟 16.1 多想多写多练 498 16.2 术业有专攻 498 16.3 拆分问题,逐个击破 500 16.4 阅读Javadoc 500 16.5 小结:大练兵马上开始 506 16.6 习题 507 第17章 编程常用知识 508 教学视频:18分钟 17.1 再谈对象的比较 508 17.1.1 hashcode()方法 508 17.1.2 equals()方法 509 17.1.3 对象的比较equals()方法 509 17.2 Java的集合类框架 510 17.2.1 集合类框架的接口 510 17.2.2 List接口 511 17.2.3 使用ArrayList 512 17.2.4 Set接口 516 17.2.5 使用HashSet类 517 17.2.6 List与Set 518 17.3 泛型简介 518 17.3.1 没有泛型时的程序 519 17.3.2 使用泛型——避免强制类型转 520 17.4 Map接口 522 17.4.1 认识Map 522 17.4.2 使用HashMap 523 17.5 字符集和编码 524 17.5.1 字符集 524 17.5.2 编码 525 17.5.3 关于字符集的小程序 526 17.6 小结:编程需要打好基础 529 17.7 习题 530 第18章 Java文件编程和Java文件I/O 531 教学视频:9分钟 18.1 Java的文件编程 531 18.1.1 File类 531 18.1.2 创建和删除文件 532 18.1.3 列出文件和文件夹 533 18.1.4 重命名文件 535 18.2 Java的I/O编程 536 18.2.1 理解JavaStream 536 18.2.2 向文件写入数据 538 18.2.3 从文件读取数据 539 18.2.4 从控制台读取数据 541 18.2.5 使用输出流写入数据 543 18.2.6 使用输入流读取数据 545 18.3 小结:Java的文件类和输入输出机制 546 18.4 习题 547 第19章 Java Socket编程 548 教学视频:8分钟 19.1 IP地址和端口号 548 19.1.1 IP地址——计算机的标识 548 19.1.2 端口号——通信的窗口 549 19.1.3 网络,IP地址和端口号 551 19.2 Java TCP编程 551 19.2.1 数据传输协议 552 19.2.2 TCP的数据传输模式 552 19.2.3 第一个TCP小程序 553 19.3 Java UDP编程 557 19.3.1 UDP的数据传输模式 557 19.3.2 使用UDP协议收发数据 558 19.3.3 TCP和UDP的区别 560 19.4 小结:让程序伸向整个网络 561 19.5 习题 561 第20章 Java Swing编程 562 教学视频:14分钟 20.1 Java Swing编程简介 562 20.1.1 图形用户界面编程简介 562 20.1.2 组件 563 20.1.3 布局管理器(Layout Manager) 563 20.1.4 事件处理(Event Handling) 564 20.2 Swing基本组件 565 20.2.1 窗口(JFrame) 565 20.2.2 Swing的线程 567 20.2.3 Swing组件的鼻祖——JComponent 567 20.2.4 Swing面板类 568 20.2.5 Swing的标签 568 20.2.6 Swing的文本框 570 20.2.7 Swing的文本域 571 20.2.8 Swing的组合框 572 20.2.9 Swing的按钮 573 20.3 Swing的布局管理器 574 20.3.1 最简单的FlowLayout 574 20.3.2 东南西北之BorderLayout 574 20.3.3 平均分割之——GridLayout 576 20.3.4 最强大的布局管理器——GridBagLayout 577 20.3.5 使用多个布局管理器 579 20.4 Swing的事件处理 581 20.4.1 事件的传递和封装 581 20.4.2 事件监听器——事件的处理者 582 20.4.3 Swing事件处理的机制 584 20.4.4 事件监听器的编写 586 20.4.5 如何学习更多的事件 588 20.5 小结:从此不再依赖控制台 588 20.6 习题 588 第21章 编程,需要的是想象力和恒心 589 教学视频:13分钟 21.1 编程的前奏 589 21.1.1 细数手的积木 589 21.1.2 发挥想象力 590 21.1.3 确定程序的功能 591 21.2 聊天窗口程序 591 21.2.1 聊天程序设计 591 21.2.2 设计程序运行效果 593 21.2.3 UDP消息收发模块 595 21.2.4 图形用户界面模块 598 21.2.5 消息处理模块 600 21.2.6 一个更通用的聊天程序 601 21.3 小结:编程是必不可少的锻炼 602 21.4 习题 602 第22章 JDBC入门 603 教学视频:11分钟 22.1 JDBC的基本API 603 22.1.1 JDBC是什么 603 22.1.2 DriverManager——驱动管理器 605 22.1.3 Connection接口 606 22.1.4 Statement接口 606 22.1.5 PreparedStatement接口 606 22.1.6 ResultSet接口 607 22.1.7 JDBC-ODBC桥 607 22.2 一个操作数据库的简单程序 608 22.2.1 程序的执行结果 608 22.2.2 程序设计与模块划分 609 22.2.3 准备好数据源 610 22.2.4 数据库操作模块的实现 610 22.2.5 图形用户界面模块的实现 611 22.3 小结:强大的JDBC标准 613 22.4 习题 613
YOLO高分设计资源源码,详情请查看资源内容使用说明 YOLO高分设计资源源码,详情请查看资源内容使用说明 YOLO高分设计资源源码,详情请查看资源内容使用说明 YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明YOLO高分设计资源源码,详情请查看资源内容使用说明

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值