本篇文章是 Stack Overflow 周报的第二周,共收集了 4 道高关注的问题和对应的高赞回答。公众号「渡码」为日更,欢迎关注。


DAY1.  serialVersionUID 的重要性

关注: 2820,最高赞: 2152

这篇文章介绍一下 Java 中 serialVersionUID 属性的含义以及重要性。从属性可以看出它与序列化有关系,所以在  java.io.Serializable 接口的注释中对它有详细的介绍,下面我们对照文档注释来学习一下。Java 中每个可序列化的类都有一个版本号与之关联,这个版本号就是 serialVersionUID。它在对象反序列化时使用, 用于判断该类的发送方和接收方的 serialVersionUID 是否一致,如果接收方装载的类的 serialVersionUID 与发送方不一致,则抛出 InvalidClassException 异常。一个可序列化的类可以显示地声明 serialVersionUID 属性,但必须是 static,final,long 修饰的,如:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

下面结合实际的例子来看看 serialVersionUID 的用法以及作用,上面说了 serialVersionUID 属性是定义在可序列化的类中,所以我们的类需要实现 java.io.Serializable 接口。因此,我们定义的 Person 类如下:

package com.cnblogs.duma.week2;

import java.io.Serializable;

public class Person implements Serializable {
private static final long serialVersionUID = 42L;
public int age;
public String name; public Person(int age, String name){
this.age = age;
this.name = name;
} @Override
public String toString() {
return age + "," + name;
}
}

接下来,我们在定义两个方法分别用来序列化和反序列化,序列化方法如下:

// 将 Person 对象序列化后存到文件
public static void ser() {
Person p = new Person(28, "duma");
System.out.println("person Seria:" + p);
try {
FileOutputStream fos = new FileOutputStream("person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p);
oos.flush();
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}

序列化方法如下:

// 从文件中反序列化出 Person 对象
public static void deser() {
Person p;
try {
FileInputStream fis = new FileInputStream("person.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
p = (Person) ois.readObject();
ois.close();
System.out.println(p.toString());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}

先调用 ser 方法,成功后可以看到项目根目录下生成了 person.txt 文件,然后在调用 deser 方法可以成功反序列化 Person 对象并输出结果。这波操作就是正常的序列化和反序列化操作。回到今天的主题,为了验证 serialVersionUID 属性的作用,我们可以在调用完 ser 方法后,先修改 serialVersionUID 值,然后再调用 deser 方法,这时就会抛出 java.io.InvalidClassException 异常。

明白了 serialVersionUID 属性的含义和作用,接下来我们再来看看它的重要性。在我们的例子中我们显式地定义了 serialVersionUID 属性,如果没有显式地指定 serialVersionUID,序列化运行时会根据类的信息计算一个默认值,《Effective Java》一书中提到这些信息包括类名、实现的接口、public和protected的成员。虽然有默认值,但 Java 官方文档强烈建议我们显式地定义 serialVersionUID 属性,因为默认的 serialVersionUID 依赖类的信息,而类的信息可能在不同编译器下会不同。因此,如果发送方和接收方使用的编译器不同,有可能导致默认的 serialVersionUID 不一致从而导致接收方无法正常反序列化,同时 Java 官方也建议使用 private 修饰 serialVersionUID,这样可以防止子类继承这个属性。

对于上面提到的《Effective Java》一书中的内容,我们可以做个简单的验证。因为生成默认的 serialVersionUID 会用到 public 成员信息,那我们改变成员变量就会导致 serialVersionUID 值改变。首先我们将 Person 类中的 serialVersionUID 属性删掉,调用 ser 方法序列化。然后在 Person 中加一个成员,比如:public String nickname = "zhangsan"; ,然后调用 deser 方法,可以看到程序抛出 java.io.InvalidClassException 异常。这同时也警示我们尽量显示地定义 serialVersionUID 属性。

查看原文


DAY2.  创建线程到底用哪种方式

关注: 1972,最高赞: 1583

我们知道 Java 实现线程的方式有两种, 一种是继承 Thread 类,另一种是实现 Runnable 接口。那么问题来了,这两种方式有什么区别呢?我们应该用哪种方式更好呢?下面先简单看下这两种方式的代码。

1. 继承 Thread 类

static class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}

2. 实现 Runnable 接口

static class Workder implements Runnable {
@Override
public void run() { }
}

对于这两种方式的选择,Stack Overflow 的回答者普遍认为优先选择实现 Runnable 接口的方式,理由如下:

  1. 在面向对象中,继承意味着添加新功能、修改或者改进父类的行为,如果我们没有这方面的改动,那就尽量避免使用继承,因为我们的代码只是单纯需要执行一些任务,而不需要改造 Thread 的行为,所以继承 Runnable 接口更合理,所以上面代码中继承的方式类名是 Thread1 是一种(is a)Thread,而实现 Runnable 接口的类名是 Worker,一个 Worker 对象(工人)的“工作”逻辑可以放在 run 方法中,然而这并不意味这这个工人 7*24 小时一直工作。
  2. 由于 Java 中不支持多继承,因此继承 Thread 类意味着无法在继承其他的类,影响代码的扩展性。
  3. 继承 Thread 意味着每个线程都有一个唯一的 Thread 的对象与之对应,而实现 Runnable 接口可以让多个线程共享同一个对象。

总之,如果我们的类定位在单纯地执行任务,并不需要改造 Thread 类,那我们就应该实现 Runnable 接口。反之,如果我们需要改造 Thread 类,或者它是一种(is a)线程,那我们就继承 Thread 类。我目前正在写的一本关于 RPC 的书中,创建线程就是以继承 Thread 为主。

查看原文


DAY3. 反射非用不可吗

关注: 1960,最高赞: 1611

相信有 Java 基础的朋友都知道反射的概念。然而如果你仅仅了解概念,而在工程实践中没有应用的话,那可能总是感觉有层窗户纸模模糊糊的。我之前学习反射就有这种感觉。那么今天这篇文章我就完整地梳理一下反射的概念和作用,结合 Hadoop RPC 框架聊聊反射为什么非用不可或者说用了反射是不是给程序带来了非常大的便利性。

首先,我们看定义:反射是语言提供了一种在运行时检查和动态调用类、方法和属性的能力。基于这个能力,反射一般大量应用在框架中,如:Spring,Hadoop。从反射的定义我们可能会问一个问题,为什么要在运行时动态地调用?既然 Java 是静态语言,任何需要调用的东西为什么不在编译时就确定好呢?这个问题也就是在问反射的作用是什么以及是不是非用不可。

我们可以简单猜想一下,以类的反射为例,当我们使用第三方框架时,框架并不知道用户定义了什么类,因此框架想要使用用户的类,只能在运行时动态地检查类是否存在,再进行调用。下面以 Hadoop 的 MapReduce 框架为例,看一下它使用反射的一个例子。

用户自定义的类如下:

// 需要继承 Mapper 基类,Hadoop 框架才能正常使用它
public class WordCountMapper
extends Mapper<Object, Text, Text, IntWritable> { // Hadoop 框架会在创建 WordCountMapper 对象后调用 map 方法
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
// 数据处理逻辑
}
}

Hadoop 框架通过反射调用 WordCountMapper 的代码如下:

// taskContext.getMapperClass() 为运行时用户传入的类,WordCountMapper
org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE> mapper =
(org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE>)
ReflectionUtils.newInstance(taskContext.getMapperClass(), job);
// run 方法中循环调用 map 方法处理数据
mapper.run(mapperContext);

上面是 Hadoop 代码的一部分,它将类的反射代码封装在 ReflectionUtils.newInstance 方法中,该方法用类的默认构造方法动态地创建一个对象。上述代码中该方法创建的对象被强转为 Mapper 类,这就是为什么因为我们的 WordCounterMapper 类要继承 Mapper。

因此,可以看到 Hadoop 框架在编译的时候并不知道用户定义了WordCountMapper 类,只能在运行时根据配置动态地检查、调用。当然为了框架能够正常使用我们定义的类,就需要定义类时符合框架定义的规范,在我们的例子中需要遵循的规范是实现一个 Mapper 基类,并且需要有默认构造函数。如果我们在代码中修改 WordCountMapper 的构造函数,那就不符合框架的规范,反射就会报错,如下:

public class WordCountMapper
extends Mapper<Object, Text, Text, IntWritable> { // 有参构造函数覆盖默认构造函数
public WordCountMapper(int a) {
a = 100;
}
}

再次运行,当 Hadoop 调用 ReflectionUtils.newInstance 时找不默认构造函数便会以下报错:

Error: java.lang.RuntimeException: java.lang.NoSuchMethodException: com.cnblogs.duma.mapreduce.WordCountMapper.<init>()
at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:135)
at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:751)

接下来,再举一个动态调用方法的例子,假设我们要在运行时才能检查并调用某方法,写法如下:

Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);

这种场景也是框架比较喜欢用的,比如:Java 的单元测试框架 Junit4,通过反射检查类中带有 @Test 注解的方法,然后调用他们运行单元测试。

从上面两个例子可以看到为什么框架对反射如此钟情。框架不需要关心用户定义了什么类,只要用户的代码符合框架定义的规范,框架就会在运行时进行检查,并按照自己定义的规范调用代码即可。因此反射可以让框架和用户的应用解耦,使得开发更方便。

查看原文


DAY4. 一行代码搞定数组的初始化、搜索、打印

我们平时遇到的好多问题可能一行代码就搞定了。平时遇到问题可以多想想是不是已经有工具已经实现了, 如果有的话可以直接拿来用,避免重复造轮子。这篇文章今天发在公众号上,算是关注公众号读者的一个福利吧。后续再发博客。

以上便是 Stack Overflow 的第二周周报,希望对你有用,后续会继续更新,如果想看日更内容欢迎关注公众号。

公众号「渡码」,分享更多高质量内容

StackOverflow 周报 - 与高关注的问题过过招(Java)的更多相关文章

  1. StackOverflow 周报 - 这些高关注的问题你是否都会

    我从 Stack Overflow 上找的了一些高关注度且高赞的问题.这些问题可能平时我们遇不到,但既然是高关注的问题和高点赞的回答说明是被大家普遍认可的,如果我们提前学到了以后不管工作中还是面试中处 ...

  2. Java高并发--CPU多级缓存与Java内存模型

    Java高并发--CPU多级缓存与Java内存模型 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 CPU多级缓存 为什么需要CPU缓存:CPU的频率太快,以至于主存跟 ...

  3. StackOverflow 周报 - 第四周高质量问题的问答(Java、Python)

    这是 Stack Overflow 第三周周报,由于本周周四外出,所以只有三篇内容.两篇 Java.一篇 Python.公众号「渡码」为日更,欢迎关注. DAY1. 枚举对象 == 和 equals ...

  4. StackOverflow 周报 - 高质量问题的问答(Java、Python)

    这是 Stack Overflow 第三周周报,本周加入了 Python 的内容,原计划两篇 Java.两篇 Python.但明天过节所以今天就先把周报发了,两篇 Java.一篇 Python.公众号 ...

  5. 【高并发】面试官:Java中提供了synchronized,为什么还要提供Lock呢?

    写在前面 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块.既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是 ...

  6. 高端面试必备:一个Java对象占用多大内存

    这个问题一般会出现在稍微高端一点的 Java 面试环节.要求面试者不仅对 Java 基础知识熟悉,更重要的是要了解内存模型. Java 对象模型 HotSpot JVM 使用名为 oops (Ordi ...

  7. 针对Properties中实时性要求不高的配置参数,用Java缓存起来

    Properties常用于项目中参数的配置,当项目中某段程序需要获取动态参数时,就从Properties中读取该参数,使程序是可配置的.灵活的. 有些配置参数要求立即生效,有些则未必: 一.实时性要求 ...

  8. atitit.thumb生成高质量缩略图 php .net c++ java

    atitit.java thumb生成高质量缩略图 php .net c++ 1. 图像缩放(image scaling)---平滑度(smoothness)和清晰度(sharpness) 1 2.  ...

  9. 188W+程序员关注过的问题:Java到底是值传递还是引用传递?

    在逛 Stack Overflow 的时候,发现了一些访问量像阿尔卑斯山一样高的问题,比如说这个:Java 到底是值传递还是引用传递?访问量足足有 188万+,这不得了啊!说明有很多很多的程序员被这个 ...

随机推荐

  1. css常用知识与用法

    1 类选择器就是再 某一个标签后面加上class  =“”     然后再到前面去定义这个class   一定要记住前面加. 2 id选择器和类选择器是差不多的   不过id选择器前面不加.而加#   ...

  2. “$Bitmap 有标记已使用的未用簇”

    前几天在电脑上用 DiskGenius 给移动硬盘分区的时候出现了这个错误,如下图所示: 解决方法: 在 cmd 命令行窗口中输入如下代码: chkdsk /f /x c: PS: 其中 " ...

  3. Android开发进阶——自定义View的使用及其原理探索

    在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了.下 ...

  4. 彻底理解kubernetes CNI

    kubernetes各版本离线安装包 CNI接口很简单,特别一些新手一定要克服恐惧心里,和我一探究竟,本文结合原理与实践,认真读下来一定会对原理理解非常透彻. 环境介绍 我们安装kubernetes时 ...

  5. linux杂货铺

    vmware虚拟机克隆后网卡不能使用 解决方法如下 cat /etc/udev/rules.d/70-persistent-net.rules1.将eth0这行注释掉或者删除,这里记载的还是克隆系统时 ...

  6. 二、Markdown基本语法

    目录 2.1 标题 一级标题 二级标题 三级标题 2.2 加粗 2.3倾斜 2.4 高亮 2.5 上标 2.6 下标 2.7 代码引用(>式) 2.8 代码引用(```式) 2.9 代码引入(` ...

  7. 从原理层面掌握@SessionAttribute的使用【一起学Spring MVC】

    每篇一句 不是你当上了火影大家就认可你,而是大家都认可你才能当上火影 前言 该注解顾名思义,作用是将Model中的属性同步到session会话当中,方便在下一次请求中使用(比如重定向场景~). 虽然说 ...

  8. EnjoyingSoft之Mule ESB开发教程第六篇:Data Transform - 数据转换

    目录 1. 数据转换概念 2. 数据智能感知 - DataSense 3. 简单数据转换组件 3.1 Object to JSON 3.2 JSON to XML 3.3 JSON to Object ...

  9. 【Java例题】7.3 线程题3-素数线程

    3.素数线程.设计一个线程子类,依次随机产生10000个随机整数(100-999):再设计另一个线程子类,依次对每一个随机整数判断是不是素数,是则显示:然后编写主类,在主函数中定义这两个线程类的线程对 ...

  10. mvnjar包冲突解决方法

    命令 mvn dependency:tree -Dverbose 结果: [INFO] +- com.esotericsoftware:kryo:jar:4.0.2:test [INFO] | +- ...