Java编程的逻辑 (63) - 实用序列化: JSON/XML/MessagePack

上节,我们介绍了Java中的标准序列化机制,我们提到,它有一些重要的限制,最重要的是不能跨语言,实践中经常使用一些替代方案,比如XML/JSON/MessagePack。

Java SDK中对这些格式的支持有限,有很多第三方的类库,提供了更为方便的支持,Jackson是其中一种,它支持多种格式,包括XML/JSON/MessagePack等,本文就来介绍如果使用Jackson进行序列化。我们先来简单了解下这些格式以及Jackson。

基本概念

XML/JSON都是文本格式,都容易阅读和理解,格式细节我们就不介绍了,后面我们会看到一些例子,来演示其基本格式。

XML是最早流行的跨语言数据交换标准格式,如果不熟悉,可以查看http://www.w3school.com.cn/xml/快速了解。

JSON是一种更为简单的格式,最近几年来越来越流行,如果不熟悉,可以查看http://json.org/json-zh.html。

MessagePack是一种二进制形式的JSON,编码更为精简高效,官网地址是http://msgpack.org/,JSON有多种二进制形式,MessagePack只是其中一种。

Jackson的Wiki地址是http://wiki.fasterxml.com/JacksonHome,它起初主要是用来支持JSON格式的,但现在也支持很多其他格式,它的各种方式的使用方式是类似的。

要使用Jackson,需要下载相应的库。

  • JSON格式参考:https://github.com/FasterXML/jackson-databind
  • XML格式参考:https://github.com/FasterXML/jackson-dataformat-xml
  • MessagePack格式参考:https://github.com/msgpack/msgpack-java/blob/develop/msgpack-jackson/README.md

对于JSON/XML,本文使用2.8.5版本,对于MessagePack,本文使用0.8.11版本。如果使用Maven管理项目,可引入下面文件中的依赖:

https://github.com/swiftma/program-logic/blob/master/jackson_libs/dependencies.xml

如果非Maven,可从下面地址下载所有的依赖库:

https://github.com/swiftma/program-logic/tree/master/jackson_libs

配置好了依赖库后,下面我们就来介绍如何使用。

基本用法

我们以在57节介绍的Student类来演示Jackson的基本用法。

JSON

序列化一个Student对象的基本代码为:

Student student = new Student("张三", 18, 80.9d);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);

String str = mapper.writeValueAsString(student);
System.out.println(str);

Jackson序列化的主要类是ObjectMapper,它是一个线程安全的类,可以初始化并配置一次,被多个线程共享,SerializationFeature.INDENT_OUTPUT的目的是格式化输出,以便于阅读,ObjectMapper的writeValueAsString方法就可以将对象序列化为字符串,输出为:

{
  "name" : "张三",
  "age" : 18,
  "score" : 80.9
}

ObjectMapper还有其他方法,可以输出字节数组,写出到文件、OutputStream、Writer等,方法声明如下:

public byte[] writeValueAsBytes(Object value)
public void writeValue(OutputStream out, Object value)
public void writeValue(Writer w, Object value)
public void writeValue(File resultFile, Object value)

比如,输出到文件"student.json",代码为:

mapper.writeValue(new File("student.json"), student);

ObjectMapper怎么知道要保存哪些字段呢?与Java标准序列化机制一样,它也使用反射,默认情况下,它会保存所有声明为public的字段,或者有public getter方法的字段。

反序列化的代码如下所示:

ObjectMapper mapper = new ObjectMapper();
Student s = mapper.readValue(new File("student.json"), Student.class);
System.out.println(s.toString());

使用readValue方法反序列化,有两个参数,一个是输入源,这里是文件student.json,另一个是反序列化后的对象类型,这里是Student.class,输出为:

Student [name=张三, age=18, score=80.9]

说明反序列化的结果是正确的,除了接受文件,还可以是字节数组、字符串、InputStream、Reader等,如下所示:

public <T> T readValue(InputStream src, Class<T> valueType)
public <T> T readValue(Reader src, Class<T> valueType)
public <T> T readValue(String content, Class<T> valueType)
public <T> T readValue(byte[] src, Class<T> valueType)

在反序列化时,默认情况下,Jackson假定对象类型有一个无参的构造方法,它会先调用该构造方法创建对象,然后再解析输入源进行反序列化。

XML

使用类似的代码,格式可以为XML,唯一需要改变的是,替换ObjectMapper为XmlMapper,XmlMapper是ObjectMapepr的子类,序列化代码为:

Student student = new Student("张三", 18, 80.9d);
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(student);
mapper.writeValue(new File("student.xml"), student);
System.out.println(str);

输出为:

<Student>
  <name>张三</name>
  <age>18</age>
  <score>80.9</score>
</Student>

反序列化代码为:

ObjectMapper mapper = new XmlMapper();
Student s = mapper.readValue(new File("student.xml"), Student.class);
System.out.println(s.toString()); 

MessagePack

类似的代码,格式可以为MessagePack,同样使用ObjectMapper类,但传递一个MessagePackFactory对象,另外,MessagePack是二进制格式,不能写出为String,可以写出为文件、OutpuStream或字节数组,序列化代码为:

Student student = new Student("张三", 18, 80.9d);
ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
byte[] bytes = mapper.writeValueAsBytes(student);
mapper.writeValue(new File("student.bson"), student);

序列后的字节如下图所示:

反序列化代码为:

ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
Student s = mapper.readValue(new File("student.bson"), Student.class);
System.out.println(s.toString());

容器对象

对于容器对象,Jackson也是可以自动处理的,但用法稍有不同,我们来看下List和Map。

List

序列化一个学生列表的代码为:

List<Student> students = Arrays.asList(new Student[] {
        new Student("张三", 18, 80.9d), new Student("李四", 17, 67.5d) });
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(students);
mapper.writeValue(new File("students.json"), students);
System.out.println(str);

这与序列化一个学生对象的代码是类似的,输出为:

[ {
  "name" : "张三",
  "age" : 18,
  "score" : 80.9
}, {
  "name" : "李四",
  "age" : 17,
  "score" : 67.5
} ]

反序列化代码不同,要新建一个TypeReference匿名内部类对象来指定类型,代码如下所示:

ObjectMapper mapper = new ObjectMapper();
List<Student> list = mapper.readValue(new File("students.json"),
        new TypeReference<List<Student>>() {});
System.out.println(list.toString());

XML/MessagePack的代码是类似的,我们就不赘述了。

Map

Map与List类似,序列化不需要特殊处理,但反序列化需要通过TypeReference指定类型,我们看一个XML的例子。

序列化一个学生Map的代码为:

Map<String, Student> map = new HashMap<String, Student>();
map.put("zhangsan", new Student("张三", 18, 80.9d));
map.put("lisi", new Student("李四", 17, 67.5d));
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(map);
mapper.writeValue(new File("students_map.xml"), map);
System.out.println(str);

输出为:

<HashMap>
  <lisi>
    <name>李四</name>
    <age>17</age>
    <score>67.5</score>
  </lisi>
  <zhangsan>
    <name>张三</name>
    <age>18</age>
    <score>80.9</score>
  </zhangsan>
</HashMap>

反序列化的代码为:

ObjectMapper mapper = new XmlMapper();
Map<String, Student> map = mapper.readValue(new File("students_map.xml"),
        new TypeReference<Map<String, Student>>() {});
System.out.println(map.toString());

复杂对象

对于复杂一些的对象,Jackson也是可以自动处理的,我们让Student类稍微复杂一些,改为如下定义:

public class ComplexStudent {
    String name;
    int age;
    Map<String, Double> scores;
    ContactInfo contactInfo;

    //... 构造方法,和getter/setter方法

} 

分数改为一个Map,键为课程,ContactInfo表示联系信息,是一个单独的类,定义如下:

public class ContactInfo {
    String phone;
    String address;
    String email;

    // ...构造方法,和getter/setter方法

}

构建一个ComplexStudent对象,代码为:

ComplexStudent student = new ComplexStudent("张三", 18);
Map<String, Double> scoreMap = new HashMap<>();
scoreMap.put("语文", 89d);
scoreMap.put("数学", 83d);
student.setScores(scoreMap);
ContactInfo contactInfo = new ContactInfo();
contactInfo.setPhone("18500308990");
contactInfo.setEmail("[email protected]");
contactInfo.setAddress("中关村");
student.setContactInfo(contactInfo);

我们看JSON序列化,代码没有特殊的,如下所示:

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.writeValue(System.out, student);

输出为:

{
  "name" : "张三",
  "age" : 18,
  "scores" : {
    "语文" : 89.0,
    "数学" : 83.0
  },
  "contactInfo" : {
    "phone" : "18500308990",
    "address" : "中关村",
    "email" : "[email protected]"
  }
}

XML格式的代码也是类似的,替换ObjectMapper为XmlMapper即可,输出为:

<ComplexStudent>
  <name>张三</name>
  <age>18</age>
  <scores>
    <语文>89.0</语文>
    <数学>83.0</数学>
  </scores>
  <contactInfo>
    <phone>18500308990</phone>
    <address>中关村</address>
    <email>[email protected]</email>
  </contactInfo>
</ComplexStudent>

反序列化的代码也不需要特殊处理,指定类型为ComplexStudent.class即可。

定制序列化

配置方法和场景

上面的例子中,我们没有做任何定制,默认的配置就是可以的。但很多情况下,我们需要做一些配置,Jackson主要支持两种配置方法:

  • 一种是注解,后续文章会详细介绍注解,这里主要是介绍Jackson一些注解的用法
  • 另外一种是配置ObjectMapper对象,ObjectMapper支持对序列化和反序列化过程做一些配置,前面使用的SerializationFeature.INDENT_OUTPUT是其中一种

哪些情况需要配置呢?我们看一些典型的场景:

  • 如何达到类似标准序列化中transient关键字的效果,忽略一些字段?
  • 在标准序列化中,可以自动处理引用同一个对象、循环引用的情况,反序列化时,可以自动忽略不认识的字段,可以自动处理继承多态,但Jackson都不能自动处理,这些情况都需要进行配置
  • 标准序列化的结果是二进制、不可读的,但XML/JSON格式是可读的,有时我们希望控制这个显示的格式
  • 默认情况下,反序列时,Jackson要求类有一个无参构造方法,但有时类没有无参构造方法,Jackson支持配置其他构造方法

针对这些场景,我们分别来看下。

忽略字段

在Java标准序列化中,如果字段标记为了transient,就会在序列化中被忽略,在Jackson中,可以使用以下两个注解之一:

  • @JsonIgnore:用于字段, getter或setter方法,任一地方的效果都一样
  • @JsonIgnoreProperties:用于类声明,可指定忽略一个或多个字段

比如,上面的Student类,忽略分数字段,可以为:

@JsonIgnore
double score;

也可以修饰getter方法,如:

@JsonIgnore
public double getScore() {
    return score;
}

也可以修饰Student类,如:

@JsonIgnoreProperties("score")
public class Student {

加了以上任一标记后,序列化后的结果中将不再包含score字段,在反序列化时,即使输入源中包含score字段的内容,也不会给score字段赋值。

引用同一个对象

我们看个简单的例子,有两个类Common和A,A中有两个Common对象,为便于演示,我们将所有属性定义为了public,它们的类定义如下:

static class Common {
    public String name;
}

static class A {
    public Common first;
    public Common second;
}

有一个A对象,如下所示:

Common c = new Common();
c.name= "common";
A a = new A();
a.first = a.second = c;

a对象的first和second都指向都一个c对象,不加额外配置,序列化a的代码为:

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(a);
System.out.println(str);

输出为:

{
  "first" : {
    "name" : "abc"
  },
  "second" : {
    "name" : "abc"
  }
}

在反序列化后,first和second将指向不同的对象,如下所示:

A a2 = mapper.readValue(str, A.class);
if(a2.first == a2.second){
    System.out.println("reference same object");
}else{
    System.out.println("reference different objects");
}

输出为:

reference different objects

那怎样才能保持这种对同一个对象的引用关系呢?可以使用注解@JsonIdentityInfo,对Common类做注解,如下所示:

@JsonIdentityInfo(
        generator = ObjectIdGenerators.IntSequenceGenerator.class,
        property="id")
static class Common {
    public String name;
}

@JsonIdentityInfo中指定了两个属性,property="id"表示在序列化输出中新增一个属性"id"以表示对象的唯一标示,generator表示对象唯一ID的产生方法,这里是使用整数顺序数产生器IntSequenceGenerator。

加了这个标记后,序列化输出会变为:

{
  "first" : {
    "id" : 1,
    "name" : "common"
  },
  "second" : 1
}

注意,"first"中加了一个属性"id",而"second"的值只是1,表示引用第一个对象,这个格式反序列化后,first和second会指向同一个对象。

循环引用

我们看个循环引用的例子,有两个类Parent和Child,它们相互引用,为便于演示,我们将所有属性定义为了public,类定义如下:

static class Parent  {
    public String name;
    public Child child;
}

static class Child {
    public String name;
    public Parent parent;
}

有一个对象,如下所示:

Parent parent = new Parent();
parent.name = "老马";
Child child = new Child();
child.name = "小马";
parent.child = child;
child.parent = parent;

如果序列化parent这个对象,Jackson会进入无限循环,最终抛出异常,解决这个问题,可以分别标记Parent类中的child和Child类中的parent字段,将其中一个标记为主引用,而另一个标记为反向引用,主引用使用@JsonManagedReference,反向引用使用@JsonBackReference,如下所示:

static class Parent  {
    public String name;

    @JsonManagedReference
    public Child child;
}

static class Child {
    public String name;

    @JsonBackReference
    public Parent parent;
}

加了这个注解后,序列化就没有问题了,我们看XML格式的序列化代码:

ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(parent);
System.out.println(str); 

输出为:

<Parent>
  <name>老马</name>
  <child>
    <name>小马</name>
  </child>
</Parent>

在输出中,反向引用没有出现。不过,在反序列化时,Jackson会自动设置Child对象中的parent字段的值,比如:

Parent parent2 = mapper.readValue(str, Parent.class);
System.out.println(parent2.child.parent.name);

输出为:

老马

说明标记为反向引用的字段的值也被正确设置了。

反序列化时忽略未知字段

在Java标准序列化中,反序列化时,对于未知字段,会自动忽略,但在Jackson中,默认情况下,会抛异常。比如,还是以Student类为例,如果student.json文件的内容为:

{
  "name" : "张三",
  "age" : 18,
  "score": 333,
  "other": "其他信息"
}

其中,other属性是Student类没有的,如果使用标准的反序列化代码:

ObjectMapper mapper = new ObjectMapper();
Student s = mapper.readValue(new File("student.json"), Student.class);

Jackson会抛出异常:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "other"  ...

怎样才能忽略不认识的字段呢?可以配置ObjectMapper,如下所示:

ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Student s = mapper.readValue(new File("student.json"), Student.class);

这样就没问题了,这个属性是配置在整个ObjectMapper上的,如果只是希望配置Student类,可以在Student类上使用如下注解:

@JsonIgnoreProperties(ignoreUnknown=true)
public class Student {

继承和多态

Jackson也不能自动处理多态的情况,我们看个例子,有四个类,定义如下,我们忽略了构造方法和getter/setter方法:

static class Shape {
}

static class Circle extends Shape {
    private int r;
}

static class Square extends Shape {
    private int l;
}

static class ShapeManager {
    private List<Shape> shapes;
}

ShapeManager中的Shape列表,其中的对象可能是Circle,也可能是Square,比如,有一个ShapeManager对象,如下所示:

ShapeManager sm =  new ShapeManager();
List<Shape> shapes = new ArrayList<Shape>();
shapes.add(new Circle(10));
shapes.add(new Square(5));
sm.setShapes(shapes);

使用JSON格式序列化,输出为:

{
  "shapes" : [ {
    "r" : 10
  }, {
    "l" : 5
  } ]
}

这个输出看上去是没有问题的,但由于输出中没有类型信息,反序列化时,Jackson不知道具体的Shape类型是什么,就会抛出异常。

解决方法是在输出中包含类型信息,在基类Shape前使用如下注解:

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Circle.class, name = "circle"),
    @JsonSubTypes.Type(value = Square.class, name = "square") })
static class Shape {
}

这些注解看上去比较多,含义是指在输出中增加属性"type",表示对象的实际类型,对Circle类,使用"circle"表示其类型,而对于Square类,使用"square",加了注解后,序列化输出会变为:

{
  "shapes" : [ {
    "type" : "circle",
    "r" : 10
  }, {
    "type" : "square",
    "l" : 5
  } ]
}

这样,反序列化时就可以正确解析了。

修改字段名称

对于XML/JSON格式,有时,我们希望修改输出的名称,比如对Student类,我们希望输出的字段名变为对应的中文,可以使用@JsonProperty进行注解,如下所示:

public class Student {
    @JsonProperty("名称")
    String name;

    @JsonProperty("年龄")
    int age;

    @JsonProperty("分数")
    double score;
    //...
}    

加了这个注解后,输出的JSON格式会变为:

{
  "名称" : "张三",
  "年龄" : 18,
  "分数" : 80.9
}

对于XML格式,一个常用的修改是根元素的名称,默认情况下,它是对象的类名,比如对Student对象,它是"Student",如果希望修改呢?比如改为小写"student",可以使用@JsonRootName修饰整个类,如下所示:

@JsonRootName("student")
public class Student {

格式化日期

默认情况下,日期的序列化格式为一个长整数,比如:

static class MyDate {
    public Date date = new Date();
}

序列化代码:

MyDate date = new MyDate();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(System.out, date);

输出如下所示:

{"date":1482758152509}

这个格式是不可读的,怎样才能可读呢?使用@JsonFormat注解,如下所示:

static class MyDate {
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
    public Date date = new Date();
}

加注解后,输出会变为如下所示:

{"date":"2016-12-26 21:26:18"}

配置构造方法

前面的Student类,如果没有定义默认构造方法,只有如下构造方法:

public Student(String name, int age, double score) {
    this.name = name;
    this.age = age;
    this.score = score;
} 

则反序列化时会抛异常,提示找不到合适的构造方法,可以使用@JsonCreator和@JsonProperty标记该构造方法,如下所示:

@JsonCreator
public Student(
        @JsonProperty("name") String name,
        @JsonProperty("age") int age,
        @JsonProperty("score") double score) {
    this.name = name;
    this.age = age;
    this.score = score;
}

这样,反序列化就没有问题了。

Jackson对XML支持的局限性

需要说明的是,对于XML格式,Jackson的支持不是太全面,比如说,对于一个Map<String, List<String>>对象,Jackson可以序列化,但不能反序列化,如下所示:

Map<String, List<String>> map = new HashMap<>();
map.put("hello", Arrays.asList(new String[]{"老马","小马"}));

ObjectMapper mapper = new XmlMapper();

String str = mapper.writeValueAsString(map);
System.out.println(str);

Map<String, List<String>> map2 = mapper.readValue(str,
        new TypeReference<Map<String, List<String>>>() {});
System.out.println(map2);

在反序列化时,代码会抛出异常,如果mapper是一个ObjectMapper对象,反序列化就没有问题。如果Jackson不能满足需求,可以考虑其他库,如XStream (http://x-stream.github.io/)。

小结

本节介绍了如何使用Jackson来实现JSON/XML/MessagePack序列化,使用方法是类似的,主要是创建的ObjectMapper对象不一样,很多情况下,不需要做额外配置,但也有很多情况,需要做额外配置,配置方式主要是注解,我们介绍了Jackson中的很多典型注解,大部分注解适用于所有格式。

Jackson还支持很多其他格式,如YAML, AVRO, Protobuf, Smile等。Jackson中也还有很多其他配置和注解,用的相对较少,限于篇幅,我们就不介绍了。

从注解的用法,我们可以看出,它也是一种神奇的特性,它类似于注释,但却能实实在在改变程序的行为,它是怎么做到的呢?我们暂且搁置这个问题,留待后续章节。

接下来,我们介绍一些常见文件类型的处理,包括属性文件、CSV、Excel、HTML和压缩文件。

原文地址:https://www.cnblogs.com/ivy-xu/p/12556194.html

时间: 2024-10-07 05:28:24

Java编程的逻辑 (63) - 实用序列化: JSON/XML/MessagePack的相关文章

Java编程的逻辑 (87) - 类加载机制

上节,我们探讨了动态代理,在前几节中,我们多次提到了类加载器ClassLoader,本节就来详细讨论Java中的类加载机制与ClassLoader. 类加载器ClassLoader就是加载其他类的类,它负责将字节码文件加载到内存,创建Class对象.与之前介绍的反射.注解.和动态代理一样,在大部分的应用编程中,我们不太需要自己实现ClassLoader. 不过,理解类加载的机制和过程,有助于我们更好的理解之前介绍的内容,更好的理解Java.在反射一节,我们介绍过Class的静态方法Class.f

Java编程的逻辑 (91) - Lambda表达式

在之前的章节中,我们的讨论基本都是基于Java 7的,从本节开始,我们探讨Java 8的一些特性,主要内容包括: 传递行为代码 - Lambda表达式 函数式数据处理 - 流 组合式异步编程 - CompletableFuture 新的日期和时间API 本节,我们先讨论Lambda表达式,它是什么?有什么用呢? Lambda表达式是Java 8新引入的一种语法,是一种紧凑的传递代码的方式,它的名字来源于学术界的λ演算,具体我们就不探讨了. 理解Lambda表达式,我们先回顾一下接口.匿名内部类和

Java编程的逻辑 (58) - 文本文件和字符流

上节我们介绍了如何以字节流的方式处理文件,我们提到,对于文本文件,字节流没有编码的概念,不能按行处理,使用不太方便,更适合的是使用字符流,本节就来介绍字符流. 我们首先简要介绍下文本文件的基本概念.与二进制文件的区别.编码.以及字符流和字节流的区别,然后我们介绍Java中的主要字符流,它们有: Reader/Writer:字符流的基类,它们是抽象类. InputStreamReader/OutputStreamWriter:适配器类,输入是InputStream,输出是OutputStream,

Java编程的逻辑 (26) - 剖析包装类 (上)

包装类 Java有八种基本类型,每种基本类型都有一个对应的包装类. 包装类是什么呢?它是一个类,内部有一个实例变量,保存对应的基本类型的值,这个类一般还有一些静态方法.静态变量和实例方法,以方便对数据进行操作. Java中,基本类型和对应的包装类如下表所示: 基本类型 包装类 boolean Boolean byte Byte short Short int Integer long Long float Float double Double char Character 包装类也都很好记,除

Java编程的逻辑 (29) - 剖析String

上节介绍了单个字符的封装类Character,本节介绍字符串类.字符串操作大概是计算机程序中最常见的操作了,Java中表示字符串的类是String,本节就来详细介绍String. 字符串的基本使用是比较简单直接的,我们来看下. 基本用法 可以通过常量定义String变量 String name = "老马说编程"; 也可以通过new创建String String name = new String("老马说编程"); String可以直接使用+和+=运算符,如: S

Java编程的逻辑 (79) - 方便的CompletionService

上节,我们提到,在异步任务程序中,一种常见的场景是,主线程提交多个异步任务,然后希望有任务完成就处理结果,并且按任务完成顺序逐个处理,对于这种场景,Java并发包提供了一个方便的方法,使用CompletionService,这是一个接口,它的实现类是ExecutorCompletionService,本节我们就来探讨它们. 基本用法 接口和类定义 与77节介绍的ExecutorService一样,CompletionService也可以提交异步任务,它的不同是,它可以按任务完成顺序获取结果,其具

Java编程的逻辑 (94) - 组合式异步编程

前面两节讨论了Java 8中的函数式数据处理,那是对38节到55节介绍的容器类的增强,它可以将对集合数据的多个操作以流水线的方式组合在一起.本节继续讨论Java 8的新功能,主要是一个新的类CompletableFuture,它是对65节到82节介绍的并发编程的增强,它可以方便地将多个有一定依赖关系的异步任务以流水线的方式组合在一起,大大简化多异步任务的开发. 之前介绍了那么多并发编程的内容,还有什么问题不能解决?CompletableFuture到底能解决什么问题?与之前介绍的内容有什么关系?

Java编程的逻辑 (18) - 为什么说继承是把双刃剑

继承是把双刃剑 通过前面几节,我们应该对继承有了一个比较好的理解,但之前我们说继承其实是把双刃剑,为什么这么说呢?一方面是因为继承是非常强大的,另一方面是因为继承的破坏力也是很强的. 继承的强大是比较容易理解的,具体体现在: 子类可以复用父类代码,不写任何代码即可具备父类的属性和功能,而只需要增加特有的属性和行为. 子类可以重写父类行为,还可以通过多态实现统一处理. 给父类增加属性和行为,就可以自动给所有子类增加属性和行为. 继承被广泛应用于各种Java API.框架和类库之中,一方面它们内部大

Java编程的逻辑 (19) - 接口的本质

数据类型的局限 之前我们一直在说,程序主要就是数据以及对数据的操作,而为了方便操作数据,高级语言引入了数据类型的概念,Java定义了八种基本数据类型,而类相当于是自定义数据类型,通过类的组合和继承可以表示和操作各种事物或者说对象. 但,这种只是将对象看做属于某种数据类型,并按该类型进行操作,在一些情况下,并不能反映对象以及对对象操作的本质. 为什么这么说呢?很多时候,我们实际上关心的,并不是对象的类型,而是对象的能力,只要能提供这个能力,类型并不重要.我们来看一些生活中的例子. 要拍个照片,很多