Keaper's Blog

  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

JAVA 日志简介

发表于 2018-10-28 | 更新于 2020-09-15

当我们开发应用程序时,它可能不会按照我们预期的运行,这个时候通常我们会DEBUG,来观察程序中的代码分支(if,else),观察一些变量在运行中的值等等以便确定程序的运行过程。但是,在生产环境,我们通常没有办法,或者不能够很容易去进行DEBUG。因此,更好的方法是使用日志工具。
在生产环境中,日志是查找问题来源的重要依据。应用程序运行时的产生的各种信息,包括记录程序运行时产生的错误信息、状态信息、调试信息和执行时间信息等等,都可以通过日志来进行记录。

在JAVA中,有哪些记录日志的方法呢?
介绍之前我们对下文做如下约定:

  1. 依赖方式均以maven denpendency方式给出。
  2. 下文所说的系统属性指的是java中的System Property,即通过System.setProperty设置或者通过java -D设置的系统属性。
  3. 本文只会简单介绍各个日志库的配置方式及基本用法,详细的配置项及更高级的用法请移步官方文档。

最原始的方式

你一定记得在刚开始学习Java时,下面这种写法:

1
System.out.println("variable is :" + variable);

或者这样,

1
2
3
4
5
try{
…………
}catch (Exception e){
e.printStackTrace();
}

在程序中打印变量的值,在异常发生时打印异常栈,这都有助于分析程序的运行过程,但是这种方式缺乏灵活性,一般也只会在初学时用到。

严格来说,这根本算不上记录日志。。。
我们需要的日志工具库至少应该满足:

  1. 输出目标(也就是日志库中appender或者handler)
    上述方式会将日志打印到控制台,但是在现实的应用场景中,我们可能需要将输出到文件系统,数据库,数据总线等系统中。
  2. 环境(可以灵活的设置日志级别level)
    我们需要根据环境不同来决定是否输出某些日志信息,生产环境只输出必要的信息,而在非生产环境,更多的调试信息会更有助于我们解决问题。

JDKLog(JUL)

JDK在java.util.logging包下提供了日志相关的API,不过在JDK1.4才加入,在此之前,JDK中并不包含日志记录相关的API和实现。

依赖

不需要任何外部依赖,JDK自身提供的。

配置

JDK通过java.util.logging.config.file系统属性来读取自定义配置文件的位置,如果不配置,JDK中有默认的配置文件,位置在[path/to/jre]/lib/logging.properties。

配置文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 全局的日志handler
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 全局的日志级别
.level= INFO

# ConsoleHandler配置level以及formater
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# SimpleFormatter的格式
java.util.logging.SimpleFormatter.format = %1$tc [%4$s]:%5$s %n

# FileHandler配置
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

SimpleFormatter.format如何自定义格式?参见:
https://docs.oracle.com/javase/7/docs/api/java/util/logging/SimpleFormatter.html#method_detail

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.logging.Logger;

public class App {
//Logger.getLogger(String name),参数为logger的name,通常为类的全限定名
private static Logger logger = Logger.getLogger(App.class.getName());
public static void main( String[] args )
{
try{
logger.info("This is test log.");
throw new IllegalArgumentException("test Exception");
}catch (Exception e){
logger.severe("This is test Exception:" + e);
}
}
}

Log4j

Log4j 是Apache的一个用于JAVA的日志库。是出现较早的日志工具,目前已经发展到Log4j2.X,但是这里我们讨论的是Log4j1.X,Log4j2.X下文会单独讨论。

依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

配置

Log4j没有任何默认的日志配置,如果没有配置文件。你将会遇到:

1
2
3
log4j:WARN No appenders could be found for logger (xx.xx.xx).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

我们看下Log4j默认的初始化过程(具体过程在org.apache.log4j.LogManager类中):

  1. 设置log4j.defaultInitOverride为除了false之外的其他值都会导致Log4j跳过初始化过程。(但是一般我们不会这么做)
  2. 试图将log4j.configuration系统属性转换为一个URL(java.net.URL),从该URL加载配置,如果转换失败,则从classpath中寻找log4j.configuration系统属性配置的文件,从该文件加载配置
  3. 如果没有设置log4j.configuration属性,则在classpath目录下寻找log4j.xml文件,从该文件加载配置。
  4. 如果仍然没有,则在classpath目录下寻找log4j.properties文件,从该文件加载配置。
  5. 如果以上均失败了,中止初始化。

从以上流程可以看出,最简单的配置方式就是提供log4j.xml或者log4j.properties文件。

log4j.xml文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<!--配置Console Appender-->
<appender name="stdout" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] %l [%m]%n"/>
</layout>
</appender>

<!--配置File Appender-->
<appender name="file" class="org.apache.log4j.FileAppender">
<!--文件位置-->
<param name="File" value=" out.log" />
<param name="Append" value="false" />
<!--日志格式-->
<layout class="org.apache.log4j.PatternLayout" >
<param name="ConversionPattern" value="[%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] %l [%m]%n" />
</layout>
</appender>

<!--配置Logger-->
<logger name="cn.keaper" additivity="false">
<level value="debug"/>
<appender-ref ref="file"/>
</logger>

<root>
<level value="debug"/>
<appender-ref ref="stdout"/>
</root>
</log4j:configuration>

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.log4j.Logger;

public class App {
//Logger.getLogger()可以传递string作为 logger name
//也可以传递Class,此时则用类的全限定名作为 logger name
private static final Logger logger = Logger.getLogger(App.class);

public static void main( String[] args )
{
try{
logger.info("This is test log.");
throw new IllegalArgumentException("test Exception");
}catch (Exception e){
logger.error("This is test Exception:",e);
}
}
}

休息一下

除了 JUL 和 Log4J这样的日志记录库之外,还有一类库用来封装不同的日志记录库。这样的封装库中一开始以 Apache Commons Logging框架最为流行,现在比较流行的是SLF4J。这样封装库的API都比较简单,只是在日志记录库的API基础上做了一层简单的封装,屏蔽不同实现之间的区别。

这样做典型的好处是可以在一个公共的项目中,可以通过这类公共的API去操作日志,但是具体的实现由调用方去指定。这样可以避免在依赖多个不同的日志库时需要对多种不同的日志库提供实现及配置。

commoning-log(JCL)

JCL provides thin-wrapper Log implementations for other logging tools, including Log4J, Avalon LogKit (the Avalon Framework’s logging infrastructure), JDK 1.4, and an implementation of JDK 1.4 logging APIs (JSR-47) for pre-1.4 systems.

JCL内置支持的日志实现工具包括Log4J、Avalon LogKit(项目目前已经关闭)、JDK 1.4和为JDK1.4之前系统提供的JDK log API的实现(JSR-47)。这几个中使用最多的还是Log4j。

依赖

1
2
3
4
5
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

为了实现具体的日志实现,则需要引入具体的依赖库,如果用Log4j,那么需要引入Log4j的依赖:

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

配置

JCL的配置文件是classpath路径中的commons-logging.properties。JCL中使用LogFactory来获取具体的Log,LogFactory可以通过org.apache.commons.logging.LogFactory属性指定,当然JCL提供了默认的LogFactory,默认的Logfactory通过如下流程决定使用哪种具体的日志实现:

  1. 寻找LogFactory中名为org.apache.commons.logging.Log(同时兼容1.0版本之前的org.apache.commons.logging.log)的属性指定的Log。

    这里的属性指的是通过LogFactory类的getAttribute方法获得的,注意定义在commons-logging.properties文件中的每个属性都会成为这个默认的LogFactory的Attribte.

  2. 如果没有找到,寻找系统属性中名为org.apache.commons.logging.Log(同时兼容1.0版本之前的org.apache.commons.logging.log)的属性指定的Log。
  3. 如果依然没有找到,Log4j是可用的(可以找到Log4j相关的类),使用Log4j。
  4. 如果没有找到Log4j,JDK版本在1.4以上,那么使用JDKLog。
  5. 使用JCL内置的SimpleLog。

如果我们使用Log4j,JDKLog,我们可以不用配置commons-logging,注意着并不等于不用配置Log4j或者JDKLog,在使用了commons-logging之后,不管是Log4j还是JDKLog,配置方式都是与单独使用时是相同的,commoons-logging和具体的日志实现的配置是分开的。

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class App {
private static final Log logger = LogFactory.getLog(App.class);

public static void main( String[] args )
{
try{
logger.info("This is test log.");
throw new IllegalArgumentException("test Exception");
}catch (Exception e){
logger.error("This is test Exception:",e);
}
}
}

slf4j

slf4j与commons-logging相似,是有一个日志门面框架。
slf4j支持log4j,JDKLog,logback同时支持commons-logging(相当于两层门面)。

依赖

1
2
3
4
5
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>

slf4j-api提供Log的接口,而具体使用哪种实现则需要提供另一类包:绑定包(SLF4J bindings),每一种绑定包对应一种实现:

binding jar 支持日志实现
slf4j-log4j12.jar log4j 1.2
slf4j-jdk14.jar JDKLog
slf4j-jcl.jar commons-logging

同时logback内置slf4j实现,所以使用logback并不需要binding jar。

引入了对应的binding jar之后,我们还需要引入日志实现库所需要的依赖。
例如如果我们用Log4j作为日志实现,除了slf4j-api需要引入,还需要引入

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

配置

Slf4j并不需要单独配置,只要引入相应日志的binding jar以及其对应依赖并配置即可。

这里的绑定实际上是加载org.slf4j.impl.StaticLoggerBinder类,每个binding jar中都有这样一个类。如果找到多个绑定,则取决于加载的先后顺序。

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);

public static void main( String[] args )
{
try{
logger.info("This is test log.");
throw new IllegalArgumentException("test Exception");
}catch (Exception e){
logger.error("This is test Exception:",e);
}
}
}

slf4j bridging

如果在你依赖的模块中使用了别的日志库,而在你的项目中你使用的是slf4j,这个时候slf4j 提供了一些桥接模块可以避免你同时对多个日志库进行配置,这些桥接模块可以重定向对其他日志库的调用到slf4j.
官方图表达的很清晰:

Logback

Logback是由Log4j创始人设计的又一个开源日日志组件。Logback当前分成三个模块:logback-core,logback-classic和logback-access。
logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本。此外logback-classic完整实现了SLF4J API,使你可以很方便地更换成其它日记系统如Log4J或JDK14 Logging。logback-access模块可以与Servlet容器集成以提供HTTP日志相关的功能。

依赖

1
2
3
4
5
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>

logback-classic这个包内部依赖logback-coer和slf4j-api。所以不需要再额外引入。另外我们在上一节说到,logback提供了对slf4j接口的原生实现,所以不需要提供bingding jar。

配置

logback通过以下步骤完成初始化:

  1. logback尝试在classpath目录下寻找logback-test.xml文件。
  2. 如果不存在,logback尝试在classpath目录下寻找logback.groovy文件。
  3. 如果不存在,logback尝试在classpath目录下寻找logback.xml文件。
  4. 如果都不存在,在classpath目录下寻找META-INF\services\ch.qos.logback.classic.spi.Configurator,这个文件的内容是com.qos.logback.classic.spi.Configurator接口的实现类的全限定名,logback将使用该类作为配置类。
  5. 如果仍然没有找到,那么logback使用默认的BasicConfigurator类作为配置类,这个Configurator将日志输出至控制台。

logback.xml示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0"  encoding="UTF-8" ?>
<configuration>
<!--Console Appender配置-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--File Appender配置-->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>

<logger name="cn.keaper" level="INFO">
<appender-ref ref="FILE" />
</logger>

<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>

用法

既然logback内置了对slf4j的实现,那么他的使用方式就和slf4j的使用方式一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);

public static void main( String[] args )
{
try{
logger.info("This is test log.");
throw new IllegalArgumentException("test Exception");
}catch (Exception e){
logger.error("This is test Exception:",e);
}
}
}

Log4j 2

以上我们说的全部都是Log4j 1,而Log4j现在已经升级到了2.X版本。Log4j 2已经不仅仅是一个日志实现框架了,他也能够作为一个门面框架来使用,并提供对其他框架丰富的支持。我们只介绍他单纯作为一个日志实现框架的用法,更多的用法请移步官网自行查阅。

依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>

配置

Log4j 2通过初始化实现自动配置的流程如下:

  1. 检查log4j.configurationFile系统属性,如果有设置,将使用
  2. 如果没有设置,依次从classpath中寻找配置文件,顺序依次为:
    • log4j2-test.properties
    • log4j2-test.yaml或者log4j2-test.yml
    • log4j2-test.json或者log4j2-test.jsn
    • log4j2-test.xml
    • log4j2.properties
    • log4j2.yaml或者log4j2.yml
    • log4j2.json或者log4j2.jsn
    • log4j2.xml
  3. 如果这么多文件都没有没有找到,则会有一个默认的配置,这个配置会将日志输出至控制台。

Log4J2.X的xml配置方式上有一些不同,主要的不同可以看这里:Migrating from Log4j 1.x

log4j2.xml配置文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<!--配置Console Appender-->
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
<!--配置File Appender-->
<File name="FILE" filename="out.log" append="false">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</File>
</Appenders>
<Loggers>
<Logger name="cn.keaper" level="info">
<AppenderRef ref="FILE"/>
</Logger>
<Root level="debug">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>

用法

在使用方式上,也有了变化,主要变化是在Log4J1.X中Logger是具体的类,而在Log4J2.X中,Logger变成了接口(log4j-api包中),在log4j-core中提供具体实现。这样做的好处是使用log4j的api同时可以使用其他不同日志库的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//注意类的位置不同了
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class App {
//Log4j 1.x中可以使用Logger.gerLogger()来获取Logger
//但是在Log4j 2.X中需要使用LogManager.getLogger()来获取
private static final Logger logger = LogManager.getLogger(App.class);

public static void main( String[] args )
{
try{
logger.info("This is test log.");
throw new IllegalArgumentException("test Exception");
}catch (Exception e){
logger.error("This is test Exception:",e);
}
}
}

几种框架的关系

以上介绍的这几种框架可以分为两类(log4j 2 暂时不做区分):

  1. JUL, Log4j 1 和 Logback 是真正的日志实现。
  2. commons-logging和sl4j是在真正日志实现之上的门面框架。每个框架都有自己一个简单实现,也可以使用一个另外的日志实现。

commons-logging和slf4j都可以作为JDKLog和Log4j 1的日志门面,此外slf4j还能够作为commons-logging本身和logback的门面框架。
他们之间的关系可以用下图表示:

参考

  1. JDKLog文档
  2. Log4J1.X文档
  3. Log4j2.X文档
  4. commons-loggging
  5. logback文档
  6. Java日志管理最佳实践
  7. The Java Logging Mess
  8. The Java logging quagmire

JSONObject.toBean不能正确反序列化thrift生成的java类

发表于 2018-09-16 | 更新于 2020-09-15

现象

在使用json-lib中JSONObject.toBean将JsonObject转化为由thrift生成的Java类时,发现得到的javabean中的数据均为空(null或者默认值)。

复现

测试代码:
pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.keaper</groupId>
<artifactId>thrift-test</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.11.0</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>net.sf.ezmorph</groupId>
<artifactId>ezmorph</artifactId>
<version>1.0.6</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies>

<properties>
<thrift.executable.shell>/usr/local/bin/thrift</thrift.executable.shell>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.thrift.tools</groupId>
<artifactId>maven-thrift-plugin</artifactId>
<version>0.1.10</version>
<configuration>
<thriftExecutable>${thrift.executable.shell}</thriftExecutable>
<generator>java</generator>
<outputDirectory>src/main/java</outputDirectory>
</configuration>
<executions>
<execution>
<id>thrift-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

定义一个thrift struct作为测试的类 student.thrift

1
2
3
4
5
6
namespace java cn.keaper

struct Student {
1: string name;
2: i32 age;
}

再自定义一个普通的javaBean,作对比之用Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Person {

String name;
int age;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testJSON() {
String json = "{name:\"edward\",age:\"20\"}";
JSONObject jsonObject = JSONObject.fromObject(json);
System.out.println(jsonObject.toString());

Student student = (Student) JSONObject.toBean(jsonObject,Student.class);
System.out.println(student.toString());

Person person = (Person) JSONObject.toBean(jsonObject,Person.class);
System.out.println(person.toString());
}

数据结果:

1
2
3
4
5
6
7
{"name":"edward","age":"20"}
Student(name:null, age:0)
九月 16, 2018 4:58:42 下午 net.sf.json.JSONObject toBean
信息: Property 'name' of class cn.keaper.Student has no write method. SKIPPED.
九月 16, 2018 4:58:42 下午 net.sf.json.JSONObject toBean
信息: Property 'age' of class cn.keaper.Student has no write method. SKIPPED.
Person{name='edward', age=20}

我们可以看到我们自定义的javaBean使用toBean方法转换是没有问题的,但是thrift生成的java类在使用toBean方法时是不能正确获取到数据的。

问题溯源

通过单步跟踪发现:
在JSONObject.toBean方法中执行到以下代码:

1
2
3
4
5
6
PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(bean, key);
if (pd != null && pd.getWriteMethod() == null) {
log.info("Property '" + key + "' of " + bean.getClass() + " has no write method. SKIPPED.");
}else{
......
}

这里的执行对应了上文的日志输出,可见问题的原因是没有找到合适的set方法去对类的属性赋值。

我们来看看thrift生成的java类中setter,getter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public String getName() {
return this.name;
}

public Student setName(String name) {
this.name = name;
return this;
}
.....

public int getAge() {
return this.age;
}

public Student setAge(int age) {
this.age = age;
setAgeIsSet(true);
return this;
}

这里可以看到其中是有set方法的。
那么为什么没找到呢?继续跟踪,中间过程略长,已略过,最后我们来到了Introspector.getTargetPropertyInfo()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
try {
if (argCount == 0) {
if (name.startsWith(GET_PREFIX)) {
// Simple getter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null);
} else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
// Boolean getter
pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
}
} else if (argCount == 1) {
if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
} else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
// Simple setter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
} else if (argCount == 2) {
if (void.class.equals(resultType) && int.class.equals(argTypes[0]) && name.startsWith(SET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
}
} catch (IntrospectionException ex) {
// This happens if a PropertyDescriptor or IndexedPropertyDescriptor
// constructor fins that the method violates details of the deisgn
// pattern, e.g. by having an empty name, or a getter returning
// void , or whatever.
pd = null;
}

可见其中判断set方法的依据是参数个数为1,返回类型为void,方法名以set开头,所以回头看thrift生成的java类

1
2
3
4
5
6
7
8
9
10
11
public Student setName(String name) {
this.name = name;
return this;
}
.....

public Student setAge(int age) {
this.age = age;
setAgeIsSet(true);
return this;
}

其中的set方法与我们自定义javaBean的set方法不同的是它返回void,而是返回对象本身,所以它不能够被识别为一个setter,所以导致最后没有序列化成功。

总结

  1. Introspector这个类是jdk中用来处理javaBean的类。

    The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.
    所以其中对setter的处理是符合Java Bean规范的。

  2. json-lib中使用的是commons-beanutils包,这个包中使用的就是jdk中Introspector来处理Java Bean,所以导致按照Java Bean 规范并不能正确的得到一个Java Bean。

  3. fastJson

    fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

经测试,使用fastjson的JSON.parseObject方法是可以正确反序列化出thrift生成的JAVA类的,而且直接传入json字符串即可。

第六章 接口、lambda 表达式与内部类

发表于 2018-08-12 | 更新于 2020-09-15

接口

  1. 接口中的方法默认属于public,声明接口时,不必提供public关键字,但是实现接口时必须指定public关键字。
  2. 接口中不能包含成员变量,但是可以包含常量,接口中的域自动为public static final,不必指定关键字。
  3. 接口不是类,尤其不能使用new运算符实例化一个接口,尽管不能构造接口的对象,却能声明接口的变量,接口变量必须引用实现了该接口的对象。可以使用instanceof检查一个对象是否实现了某个接口。
  4. 一个类可以实现多个接口,但只能继承一个类。JAVA不可以多重继承。
  5. java 8 中允许在接口中增加静态方法,还可以为接口提供一个默认实现,用default修饰符标记方法即可。
  6. 默认方法冲突。如果先在一个接口中将一个方法定义为默认方法 , 然后又在子类或另一个接口中定义了同样的方法, 会发生什么情况?
    • 超类优先原则。如果子接口中定义了与父接口中同名且参数类型相同的默认方法,那么父接口中的默认方法会被忽略。
    • 接口冲突。如果一个接口提供了一个默认方法 , 另一个接口提供了一个同名而且参数类型 ( 不论是否是默认参数 ) 相同的方法,而一个类同时实现这两个接口,那么这个类必须覆盖这个方法。注意这里只要有一个方法是默认方法,就会存在冲突,如果两个都不是默认方法,则不存在冲突。
  7. 一个类继承了一个父类,同时实现了一个接口 , 并从父类和接口继承了相同的方法。这种情况遵循“类优先”原则,接口中的默认方法会被忽略。

对象克隆、Cloneable接口

  1. Java默认的克隆操作时浅拷贝,它并没有克隆包含在对象中的内部对象。
  2. Cloneable是一个标记接口,接口中不包含方法,使用它的唯一目的是可以用instanceof进行类型检查。clone()方法是从Object中继承而来的,并不是Cloneable接口的方法。
  3. 所有数组类型都有一个public的clone方法,可以用这个方法建立一个新数组,包含原数组所有元素的副本。
    1
    2
    3
    4
    int [] num = {1,2,3,4};
    int [] clone = num.clone();
    clone[3] = 5;
    System.out.println(num[3]); // output:4
  4. 如果一个类想要实现深拷贝,需要实现Cloneable接口并重新定义clone方法,并指定public访问修饰符。指定public修饰符的原因是Object类中的clone()是protected的,只有子类才可以调用,所以必须重新定义clone()为public才能允许所有方法克隆对象。

lambda表达式

  1. lambda表达式语法
    1
    2
    3
    String [] ss = new String []{"54321","9876","123"};
    Arrays.sort(ss,(a,b) -> a.length() - b.length());
    System.out.println(Arrays.toString(ss));//output:[123, 9876, 54321]
  2. 如果lambda 表达式没有参数, 仍然要提供空括号。
  3. 如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型。
    1
    2
    Comparator<String> comp = (first, second) // Same as (String first, String second)
    -> first.length() - second.length();
  4. 如果方法只有一参数,而且这个参数的类型可以推导得出,那么甚至还可以省略小括号。
    1
    2
    3
    ActionListener listener =  event ->
    System.out.println("The time is " + new Date());
    // Instead of (event) -> . . . or (ActionEvent event) -> . . .
  5. 对于只有一个抽象方法的接口,需要这种接口的对象时就可以提供一个lambda表达式。这种接口称为函数式接口(functional interface)。
  6. Java中,lambda表达式可以转换为函数式接口,需要函数式接口的地方就可以用lambda表达式。
  7. 方法引用。主要有三种情况:

object::instance Method
Class::static Method
Class::instance Method

在前2 种情况中,方法引用等价于提供方法参数的lambda表达式。

`System.out::println`等价于`x ->System.out.println(x)`。 

`Math::pow`等价于`(x,y) ->Math.pow(x, y)`。

对于第3 种情况,第1个参数会成为方法的目标。
`String::compareToIgnoreCase` 等同于`(x, y) -> x.compareToIgnoreCase(y)`
  1. 构造器引用。构造器引用与方法引用很类似,只不过方法名为new。

  2. lambda 表达式有3个部分:

    • 一个代码块;
    • 参数;
    • 自由变量的值,这是指非参数而且不在代码中定义的变量。
  3. lambda 表达式中捕获的变量必须实际上是final变量(effectively final),意思是这个变量初始化之后就不会再为它赋新值。

  4. lambda 表达式的代码块与嵌套块有相同的作用域。

内部类

  1. 内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域.
  2. 内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。
  3. 内部类中声明的所有静态域都必须是final。原因很简单。我们希望一个静态域只有一个实例,不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不是final , 它可能就不是唯一的。
  4. 内部类不能有static方法。
  5. 编译器将会把内部类翻译成用$分隔外部类名与内部类名的常规类文件。
  6. 可以在方法汇中定义局部内部类,作用域仅限于声明这个局部类的中,对外部不可见,同时也不可以用public或者private修饰。
  7. 局部内部类不仅可以访问外围类的变量,还可以访问局部变量。不过,那些局部变量必须事实上为final。事实上为final的意思与上面lambda表达式访问捕获的变量意思相同。
  8. 匿名内部类。语法格式为
    1
    2
    3
    4
    new SuperType(construction parameters)
    {
    inner class methods and data
    }
    SuperType可以是ActionListener这样的接口,内部类就要实现这个接口。SuperType也可以是一个类,内部类就要扩展它。
  9. 匿名内部类不能有构造器。而是要将构造方法传递给父类构造器。
    1
    2
    3
    4
    5
    Person queen = new Person("Mary") ;
    // 一个Person对象
    Person count = new Person("Dracula") { . . .
    // 一个继承自Person的匿名内部类
    }
  10. 双括号初始化实际是内部类语法。
    1
    invite(new ArrayList<String>0 {{ add("Harry") ; add("Tony") ; }}) ;
    外层括号建立了ArrayList的一个匿名子类。内层括号则是一个对象构造块。
  11. 使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所冇内部类完全一样。

第五章 继承

发表于 2018-08-11 | 更新于 2020-09-15
1. JAVA中所有继承都是公有继承。

子类会继承父类的每一个域和方法(存疑),但是父类中域的访问控制修饰符会决定子类中可不可以直接访问父类的域。例如,子类中不能直接访问父类的私有域,只能通过公有的get方法访问。同样方法的访问权限也是由父类中访问控制修饰符决定的。
关于继承会不会继承私有的域和方法,http://www.cnblogs.com/aademeng/articles/6191691.html

2. super与this

this关键字有两个用途:

  1. 引用隐式参数,this为指向本对象的引用。
  2. 调用该类的其他构造器。
    super关键字有两个用途:
  3. 调用超类的方法以及访问超类的域(访问权限由超类访问控制修饰符决定)。
  4. 调用超类的构造器。

注意调用构造器语句只能作为另一构造器的第一条语句。
this是一个引用,可以将其赋值给另一对象变量。而super只是关键字,不能将其赋值给对象变量。

3. 上转型对象。

将子类对象的引用赋值给父类对象时,这个父类对象称为上转型对象。

  1. 上转型对象不能访问的子类对象新增的域和方法。
  2. 上转型对象可以操作子类继承或隐藏的域,也可以使用子类继承的或重写的方法。
  3. 如果子类中有与父类中同名的域,那么访问的是父类中未被重写的域。
  4. 如果子类中有重写的方法,那么访问的是子类中重写的方法,并且该方法访问可以子类中新增的域和方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    public class Main {

    public static void main(String[] args) throws InterruptedException {

    Person person = new Student("super",21,"1407084125","sub");
    System.out.println(person.name);
    System.out.println(person.getName());
    person.printInfo();

    Thread.sleep(1000*60);
    }
    }

    //Person.java
    public class Person {

    public String name;

    private int age;

    public Person(String name, int age) {
    this.name = name;
    this.age = age;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getName(){
    return name;
    }

    public void printInfo(){
    System.out.println("Person{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}');
    }

    }

    //Student.java

    public class Student extends Person {

    private String studentNum;

    public String name;

    public Student(String superName, int age, String studentNum, String subName) {
    super(subName, age);
    this.studentNum = studentNum;
    this.name = subName;
    }

    public String getStudentNum() {
    return studentNum;
    }

    public void setStudentNum(String studentNum) {
    this.studentNum = studentNum;
    }

    @Override
    public String getName() {
    return name;
    }

    @Override
    public void printInfo(){
    System.out.println("Student{" +
    "studentNum='" + getStudentNum() + '\'' +
    ", name='" + name + '\'' +
    '}');
    }
    }
    4. 方法调用过程
  1. 编译器查看对象的声明类型和方法名,假设调用x.f(param),且x声明为C类的对象。编译器将会 一 一列举所有C类中名为f的方法和其超类中可访问且名为f的方法。需要注意的是:有可能存在多个名为f,但参数类型不一样的方法。至此,编译器已获得所有可能被调用的候选方法。
  2. 编译器将查看调用方法时的参数类型。在之前获取到的候选方法列表中存在与调用方法参数完全匹配,那么就调用这个方法。这个过程被称为重载解析(overloading resolution)。 至此,编译器获取到了需要调用的方法名称和参数类型。(方法名称和参数类型被称为方法签名。)
  3. 如果是private方法、static方法、final方法 或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式称为静态绑定 (static binding)。于此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定(dynamic binding)。
  4. 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型是D,它是C类的子类。如果D类定义了方法f(String),就直接调用它;否则,将在D类的超类中寻找f(String),以此类推。

其实这个地方不是理解的很透彻,等看完《深入理解JAVA虚拟机》再琢磨。还有动态绑定和静态绑定。先留一片文章图解JVM执行引擎之方法调用 - 陈洋Cy - 博客园

5. final类的方法自动成为final方法,但不包括域。final类和final方法的主要目的是不在子类中改变语义。
6. instanceof

instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。

7. 抽象类
  1. 包含一个或者多个抽象方法的类必须声明为抽象类,但是还可以包含具体的数据和方法。
  2. 即使不含有抽象方法,也可以声明为抽象类。
  3. 抽象类不能被实例化,但是可以定义一个抽象类的对象变量,但是他只能引用非抽象子类的对象。
  4. 子类可以部分实现抽象方法或者不实现,只要仍然含有抽象方法就必须声明为抽象的。
8. ArrayList类

ArrayList是一个泛型类。

  1. add() 添加元素.
  2. remove() 删除元素
  3. set() 设置元素,注意不能超过size()的大小。
  4. get() 访问元素
9. 对象包装器
  1. 有时候需要将基本类型转换为对象。所有的基本类型都有与之对应的包装类。这些类均为不可变类。
  2. 编译器能够自动装箱和自动拆箱。编译器会在编译时自动插入必要的方法调用。
10. 可变参数方法

main()方法中的Sring[] args参数便是一个可变参数的例子,同样可以将main方法定义为
public static void main(String ... args) {}
效果是一样的。

1
2
3
4
5
6
7
8
9
10
public static int max(int ... nums){
int mmax = Integer.MIN_VALUE;
for (int i:nums){
mmax = Integer.max(mmax,i);
}
return mmax;
}
public static void main(String ... args) {
System.out.println(max(1,2,3,4));
}
11. 枚举类
  1. 最基本的枚举类:
    1
    2
    3
    4
    5
    public enum Size {
    SMALL,
    MEDIA,
    LARGE
    }
    枚举默认有两个域,name和ordinal,name即为上面定义中的SMALL、MEDIA、LARGE,而orinal为枚举常量的 位置(下标),从零开始计数。
  2. 如果需要,可以在枚举类型中添加构造器域和方法。构造器会在构造枚举常量的时候用到。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public enum Sex {
    MALE("男"),
    FEMALE("女");

    private String value;

    Sex(String value) {
    this.value = value;
    }

    public String getValue() {
    return value;
    }

    public void setValue(String value) {
    this.value = value;
    }


    @Override
    public String toString() {
    return "Sex{" +
    "value='" + value + '\'' +
    '}';
    }
    }
  3. 默认toString()返回name值,可以重载,例如上面的代码。
  4. valueOf(Class enumClass , String name)返回指定类型指定名字的枚举类。
  5. 每个枚举类型都有一个静态的values()方法,返回包含所有枚举值的数组。
11. 异常

http://blog.csdn.net/hguisu/article/details/6155636

异常有两种类型:已检查异常(checked exception,又称编译时异常)和未检查异常(unchecked exception,又称运行时异常)。

  1. 已检查异常
    除了运行时异常以外的所有异常都被称作已检查异常,编译器会在编译期间去检查程序是否处理了它们。如果这些异常没有 被处理,将会出现编译错误。
    已检查异常的例子:
  • ClassNotFoundException
  • IllegalAccessException
  • NoSuchFieldException
  • EOFException
  1. 未检查异常
    运行时异常也被称作为未检查异常。编译器并不会检查是否有程序员处理这些异常,但程序员有处理这些异常的责任,并程序提供一个安全的退出。
    未检查异常的例子:
  • ArithmeticException
  • ArrayIndexOutOfBoundsException
  • NullPointerException
  • NegativeArraySizeException
  1. 异常的层次结构

    检查异常:除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常。
    未检查异常 :包括运行时异常(RuntimeException与其子类)和错误(Error)。
  2. 异常的处理方式
  • try-catch捕获异常
  • throws,throw抛出异常
12. Class类
  1. Object类的getClass()方法可以返回一个Class类的实例
  2. Class类getName() 方法返回类的名字,如果在一个包里,则包名作为类名的一部分
  3. Class类的静态方法forName(String className) 获得类名对应的Class对象。
  4. 一个Class类对象表示的是一个类型,而这个类型未必一定是一种类。如int不是类,但int.class 是一个Class类的对象。
  5. Class类的newInstance()可以创建一个该类的实例化对象,调用默认构造函数,如果没有默认构造函数,抛出异常。
13. 利用反射检查类的结构。

在java.lang.reflect包中有三个类Field、Method和Constructor分别描述类的域、方法和构造器。这三个类都有一个叫getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属的Class对象。Method和Constructor类有能够报告参数类型的方法,Method还有一个可以报告返回类型的方法。这三个类有一个getModifiers的方法,它将返回一个整数值,用不同的位开关描述public和static这样的修饰符的使用状况,还可以利用Modifier.toString方法将修饰符打印出来。

Class类的getFields、getMethods和getCostructors方法可以获得类提供的public域(Returns an array containing Field objects reflecting all the accessible public fields of the class or interface represented by this Class object.)、方法(including those declared by the class or interface and those inherited from superclasses and superinterfaces)和构造器数组(Returns an array containing Constructor objects reflecting all the public constructors of the class represented by this Class object.),其中包括从超类继承的公有成员。Class类的getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

14. 运行时利用反射分析类。

利用反射机制可以查看在编译时还不清楚的对象域。
查看对象域的关键方法是Field类中的get方法。如果f是一个Field类型的对象,obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj与的当前值。
既然能够得到域的值,那么也就能设置域的值。可以使用Field类中的set方法设置域的值,用法为f.set(obj,value),obj是包含f域的对象,value是要设置的值。
还要注意,如果f是一个私有域,那么直接使用get方法会抛出一个IllegalAccessException异常。除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域而不允许得去域的值。
反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。可以调用Field、Method或Constructor类中的setAccessible方法达到这个目的:
f.setAccessible(true);
setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的超类。

15. 反射泛型数组

暂时不看。

16. 调用任意方法

在C和C++中,可以从函数指针执行任意函数。虽然Java没有提供函数指针,但反射机制允许调用任意方法。
和Field中的get方法类似,Method类中有个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:
Object invoke(Object obj,Object... args);
如果是静态方法,第一个参数会被忽略,可以将其设置为null。

第四章 对象与类

发表于 2018-08-11 | 更新于 2020-09-15
1. 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。类似c++中对象指针。
2. C++通常在类外定义方法,定义在类内会生成内联方法(编译器也会干预),JAVA所有方法都定义在类内,由编译器决定是否内联。
3. 不应该返回可变对象的引用。这样会破坏类的封装性,应该返回对象的clone()。
4. 方法可以访问该类的任何一个对象的私有域,不仅仅是本对象的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class User {

private int age;

public User(int age) {
this.age = age;
}

public int getAge() {
return age;
}
//可以不调用getAge()方法,直接通过user.age访问。
public void accessOther(User user){
System.out.println(user.age);
}

}
5. final大都用于基本类型或者不可变类。用于可变类会给人造成误解,因为本质上只是这个引用变量不可变,而他所指向的对象仍然可以改变。
6. 可以使用对象调用静态方法(类方法),对象也可以访问公有的静态变量,但是实际访问的访问的都是类的方法和变量。并不建议使用。
7. 每个类都可以有main()方法。根据运行时的主类来确定执行那个main方法。
8. JAVA参数只有值传递。对象参数传递的是引用,本质也是值传递。
9. 成员变量没有显式赋值的话,会被赋为默认值。而局部变量使用前必须初始化。
10. C++中一个构造器不能调用另一个构造器,JAVA可以。
11. 静态初始化块会在类加载的时候执行,初始化块会在构造对象的时候执行。调用构造器的基本步骤:
  1. 所有数据被初始化为默认值。
  2. 按照类声明中出现的次序依次执行所有的域初始化语句和初始化块。
  3. 执行构造器主体。如果调用其他构造器,则执行其他构造器。
12. finalize()方法会在垃圾回收器清除对象之前调用。
14. 子包和父包没有关系,使用 * 可以导入本包所有的类,但是不能导入子包中的类,两者是独立的。
15. JAVA访问修饰符。
  1. private — 只有本类中可以访问。
  2. default(默认,不写)— 本包中的类可以访问。
  3. protected — 本包中的类及其子类(包外也可以)可以访问。
  4. public — 所有的类均可访问。

第三章 JAVA的基本程序设计结构

发表于 2018-08-11 | 更新于 2020-09-15
1.字面值常量

http://www.cnblogs.com/hwaggLee/p/4507968.html
字面值大体上可以分为整型字面值、浮点字面值、字符和字符串字面值、特殊字面值。

  1. 整形字面值
    一般情况下,字面值创建的是int类型,但是int字面值可以赋值给byte short char long int,只要字面值在目标范围以内,Java会自动完成转换,如果试图将超出范围的字面值赋给某一类型(比如把128赋给byte类型),编译通不过。而如果想创建一个int类型无法表示的long类型,则需要在字面值最后面加上L或者l。通常建议使用容易区分的L。所以整型字面值包括int字面值和long字面值两种。
  2. 浮点字面值
    分为float字面值和double字面值,如果在小数后面加上F或者f,则表示这是个float字面值,如11.8F。如果小数后面不加F(f),如10.4。或者小数后面加上D(d),则表示这是个double字面值。
  3. 字符及字符串字面值
    Java中字符字面值用单引号括起来,如‘@’‘1’。
    字符串字面值则使用双引号,字符串字面值中同样可以包含字符字面值中的转义字符序列。
2. char类型大小

16bit,两个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* The number of bits used to represent a <tt>char</tt> value in unsigned
* binary form, constant {@code 16}.
*
* @since 1.5
*/
public static final int SIZE = 16;

/**
* The number of bytes used to represent a {@code char} value in unsigned
* binary form.
*
* @since 1.8
*/
public static final int BYTES = SIZE / Byte.SIZE;
3. 变量使用之前一定要初始化。

可以不在定义是初始化,但一定要在使用之前初始化

4. &和|用于布尔值

&&,|| 和&,| 用于boolean的差别在于,前者有短路特性,后者没有。
用于整数。
而在C++中&和|都是按照整数处理的。

5. >>>和>>,C++中的右移位

>>>表示算术移位,>>表示逻辑移位。
C++中,无符号数逻辑右移,有符号数算术右移。

6. char如何转换int

char转换int返回code point。

7. 类型转换

int转换float,int转换double,long转换double都会有精度丢失。但是可以进行自动转换,无需强制转换。
两个数值类型进行计算时,如果类型不匹配,会将”低类型””转化为”高类型”,然后在进行计算。

如果有一个为double,转换另一个为double
否则,如果有一个为float,转换另一个为float
否则,如果有一个为long,转换另一个为long
否则,都转换为int。

不要使用Boolean转换类型

8. string类对象不可变字符串

String对象创建的时候,

  1. 如果直接用字符创字面值常量创建,如:String s = "abc",如果string literal pool(常量池)中不存在”abc”这个对象,那么会创建一个对象,如果存在,则不会创建;
  2. 如果new String(),那么总共会创建两个对象,一个对象为字面值常量本身,另一个为单独的字符串对象。
    参考:http://www.cnblogs.com/javaminer/p/3923484.html
9. code point 和 code unit

String length()返回的是code unit的数量。
codePointCount()可以返回code point 的数量。
charAt(n)返回位置为n的code unit。
codePointAt(n) 返回位置为n的code point。
这种差别会在一些字符上表现出来,UTF-16 - Wikipedia上可以找到两个字符,’𐐷’和’𤭢’都是由两个code unit来表示的。

10. StringBuilder和SttingBuffer

这两个类的对象是可以被修改的,与String不同。两个类 的API相同。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

11. Console类

Console类可以用来从控制台读取密码,输入不可见。
https://docs.oracle.com/javase/7/docs/api/java/io/Console.html

12. printf对日期处理,参数下标

JAVA 中增加了printf对日期的格式化。
支持参数下标,实现一个参数多次使用。
这两种方式输出是相同的:

1
2
3
System.out.printf("%1$s %2$tB %2$te, %2$tY","date:",new Date());

System.out.printf("%s %tB %<te, %<tY","date:",new Date());
13. Scanner读文件PrintWriter写文件
1
2
3
Scanner scanner = new Scanner(Paths.get("a.txt"));

PrintWriter out = new PrintWriter("a.txt");
14. 不能在嵌套的块中定义同名变量

与 C++ 不同,C++ 中定义的同名变量外层会被内层覆盖,而JAVA是不允许的。

15. 带标签的break,continue

JAVA中为了支持goto,break可以带标签,该语句会跳到标签定义处,可以实现跳到外层循环。
continue也可以跳到标签指定处。

16. 命令行参数第一个参数

C++中命令行参数的第一个参数为文件名,而JAVA是不会将文件名计算在参数内的。

17. 数组
  1. 数组也是会存放在常量池中的,所以用 = 赋值个另一个数组变量时,两者指向同一个引用。修改一个另一个也会变化。如果想要拷贝一个新的数组,可以用Arrays.copy()。
  2. for each 不能自动处理多维数组
  3. deepToString()快速打印多维数组。
  4. JAVA多维数组长度可以不相等,多维数组中,每个元素存的都是另一个数组的引用。

webpack 配置antd

发表于 2018-02-23 | 更新于 2020-09-15

webpack配置antd

webpack配置文件 wepack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//省略
module:{
rules: [{
test: /\.js[x]?$/,
use:['babel-loader?cacheDirectory=true'],
include:path.join(__dirname,'./src/')
},{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader"
},
]
},]
},
//省略

bable配置文件 .bablerc:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"presets": [
"es2015",
"react",
"stage-0"
],
"plugins": [[
"import", {
"libraryName": "antd",
"style": "css"
}]
]
}

babel-plugin-import 插件用法:https://github.com/ant-design/babel-plugin-import#usage

IntelliJ IDEA知识集锦

发表于 2018-01-21 | 更新于 2020-09-15

非常完整的IDEA概念及常见问题:

https://blog.gmem.cc/intellij-idea-faq

浏览器输入网址发生了什么?

发表于 2017-10-17 | 更新于 2020-09-15

浏览器输入网址后发生了什么?
这是一道很经典的 面试题目。

  1. 浏览器解析URL,生成HTTP请求报文。

  2. 如果输入的是域名,则需要根据域名向DNS服务器查询IP地址。
    调用sockt库中的获取域名函数(gethostbyname())解析输入的域名。解析过程如下:

    - 委托操作系统内部协议栈发送UDP报文,包含请求消息
    - ...
    - 中间包含IP层和数据链路层的封装,解封,以及路由寻址等(下面会提到)
    - ...
    - 操作系统内部协议栈接受UDP报文,包含响应消息
    - 函数返回
  3. 知道了IP 地址之后,就可以委托操作系统内部的协议栈向这个目标IP地址发送消息。
    通过socket库与服务器通信。socket通信过程如下:

  1. socket会调用 TCP协议模块 去建立连接(三次握手),收发数据,断开连接(四次挥手)。TCP模块会在原始数据上加上TCP控制信息,例如端口信息等。

  2. TCP在执行连接,收发,断开等各个阶段操作时都需要委托 IP协议模块 封装数据包发送通信对象。IP协议模块接受TCP协议模块的委托负责包的收发,他会生成IP头部(包括发送方和接收方的IP地址等)附加在TCP头部前面。

  3. 通过路由表确定下一跳路由器地址。

  4. 为了到达目标路由器,还需要知道目标路由器的MAC地址,MAC地址通过ARP协议获得。网卡将IP报文封装成以太网帧(添加MAC地址等信息),然后转换成物理信号发送到链路上。

  5. 中间经过路由,交换,数据到达web服务器。

  6. 服务器端接受数据。

    - 网卡将接受到的信号转化为数字信息
    - IP协议模块接受数据,去掉IP头部,交给TCP模块
    - TCP模块连接,收发数据,断开操作,将数据返回给socket库
    - socket库交给应用程序,通常是WEB服务器软件,如Tomcat,IIS,Apache等
  7. WEB 服务器解析请求并作出响应,生成HTTP响应报文,返回给客户端。

  8. 响应报文发送到客户端的过程与客户端到服务器端的过程基本相同。

  9. 客户端收到响应报文,并显示内容在浏览器上。

6种排序算法总结

发表于 2017-10-15 | 更新于 2020-09-15

整理一下各种排序算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
class Sort{
public:
//冒泡排序,O(N*N)
void bubbo_sort(vector<int>& arr){
for(int i=0;i<arr.size()-1;i++){
for(int j=arr.size()-1;j>i;j--){
if(arr[j]<arr[j-1]) swap(arr[j],arr[j-1]);
}
}
}
//直接插入排序,O(N*N)
void insert_sort(vector<int> &arr){
for(int i = 1;i < arr.size();i++){
int key = arr[i];
int j = i;
while(j>0&&arr[j-1]>key){
arr[j] = arr[j-1];
j--;
}
arr[j] = key;
}
}
//选择排序,O(N*N)
void select_sort(vector<int> &arr){
for(int i=0;i<arr.size();i++){
int minVal = arr[i];
int minPos = i;
for(int j=i+1;j<arr.size();j++){
if(minVal>arr[j]){
minVal = arr[j];
minPos = j;
}
}
swap(arr[i],arr[minPos]);
}
}
//快速排序,O(N*LogN)
void quick_sort(vector<int> & arr,int l,int r){
if(l>=r) return ;
int i = l,j = r,key = arr[l];
while(i<j){
while(i < j && arr[j] > key) j--;
if(i < j) arr[i++] = arr[j];
while(i < j && arr[i] < key) i++;
if(i < j) arr[j--] = arr[i];
}
arr[i] = key;
quick_sort(arr,l,i-1);
quick_sort(arr,i+1,r);
}
//归并排序,O(N*LogN)
void merge_sort(vector<int> &arr,int l,int r){
vector<int> tmp(r-l+1);
if(l>=r) return ;
int mid = (l+r) / 2;
merge_sort(arr,l,mid);
merge_sort(arr,mid+1,r);
int i = l,j = mid+1;
int k = 0;
while(i<=mid&&j<=r){
if(arr[i]<arr[j])
tmp[k++] = arr[i++];
else
tmp[k++] = arr[j++];
}
while(i<=mid){
tmp[k++] = arr[i++];
}
while(j<=r){
tmp[k++] = arr[j++];
}
for(int i=0;i<=r-l;i++){
arr[l+i] = tmp[i];
}
}

void shiftDown(vector<int>& arr,int index,int heapSize){
int key = arr[index];
int father = index,child;
while((child=father*2+1)<heapSize){
if(child+1<heapSize && arr[child] < arr[child+1]) child++;
if(key > arr[child]) break;
arr[father] = arr[child];
father = child;
}
arr[father] = key;
}
//堆排序,O(N*LogN)
void heap_sort(vector<int> &arr){
//construct max heap
for(int i=arr.size()/2-1;i>=0;i--){
shiftDown(arr,i,arr.size());
}
// for(int i=0;i<arr.size();i++){
// cout<<arr[i]<<" ";
// }
// cout<<endl;
for(int i=arr.size()-1;i>=0;i--){
swap(arr[0],arr[i]);
shiftDown(arr,0,i);
}
}
};
<i class="fa fa-angle-left" aria-label="上一页"></i>123…7<i class="fa fa-angle-right" aria-label="下一页"></i>
Keaper

Keaper

62 日志
12 标签
GitHub
© 2020 Keaper
由 Hexo 强力驱动
|
主题 – NexT.Mist