[翻译]未初始化对象与对象初始化为null

原文

问题:

我正从事Java的工作.我通常如下设置一些对象:

1
2
3
4
5
6
7
8
9
10
11
public class Foo {
private SomeObject someName;
// do stuff
public void someMethod() {
if (this.someName != null) {
// do some stuff
}
}
}

问题是:someName在这个例子里等于null,我是否可以可靠地为所有对象假设为空检查未初始化对象,这是否准确?

最佳回答:
正确,在Java里所有引用类型的静态以及实例成员,没有显式地初始化的,都会被设为null.这个规则同样适用于数组成员.

从Java语言规范, 4.12.5部分可知:

1
2
3
4
5
6
7
变量的初始值
在程序里的每一个变量在使用之前都必须有一个值:
每个类变量,实例变量或者数据部分在被创建的时间会被初始化为一个默认值.
[...] 对于所有引用类型,默认值为null.

注意,以上规则不包括局部变量:它们必须显式地初始化,否则程序不会被编译.

集群中Session解决方案之Spring Session

什么是 Spring Session

主要功能如下:

  • 从任意的环境中访问一个会话 (例如. web, messaging infrastructure, 等等)
  • WEB 环境下
  • 供应商中立的情况下支持集群
  • 可插拔策略可以确定 session id
  • WebSocket 活跃的时候可以简单的保持 HttpSession

主要特性

  • API and implementations (i.e. Redis) for managing a user’s session
  • HttpSession
  • Clustered Sessions
  • Multiple Browser Sessions
  • RESTful APIs
  • 支持WebSocket

实现原理

因为规范里的 HttpSession 以及 HttpServletRequest 都是一种接口,所以可以通过实现该接口来处理我们自定义的逻辑。

Spring Session里的自定义实现HttpServletRequest的逻辑代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
public SessionRepositoryRequestWrapper(HttpServletRequest original) {
super(original);
}
public HttpSession getSession() {
return getSession(true);
}
public HttpSession getSession(boolean createNew) {
// create an HttpSession implementation from Spring Session
}
// ... other methods delegate to the original HttpServletRequest ...
}

HttpServletRequestWrapperHttpServletRequest实现的一种包装器,可通过继承它来实现自己的主要逻辑代码,而不用直接实现HttpServletRequest接口来写大部分复杂的代码,只需要关注自己想要覆盖逻辑代码即可。
在这里,因为我们想要实现的是Session共享机制,所以这里只加入了处理Session的业务逻辑即可。
Spring Session默认情况下只有两种Session策略。一种是使用Map来实现,一种是使用Redis来实现。

如果我们想要实现自定义的Session持久化机制,我们可以实现该接口,然后添加到Session策略实现方式即可。

包装完自定义的HttpServletRequest后,就可使用了。方法是通过 Filter 来将Spring包装的HttpServletRequest代替容器原有的即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class SessionRepositoryFilter implements Filter {
public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
SessionRepositoryRequestWrapper customRequest =
new SessionRepositoryRequestWrapper(httpRequest);
chain.doFilter(customRequest, response, chain);
}
// ...
}

Maven

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<type>pom<type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>

Hello World(以Spring为基础,其他框架,也可以参考该思路作小修改即可)

web.xml 添加以下 Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
<filter>
<filter-name>spring-session</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSession</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>spring-session</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

spring.xml 添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--这里添加的是Redis,因为使用的是Spring里自带的RedisSession策略 -->
<bean id="v2redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="10.0.0.40" p:port="6379" p:use-pool="true" p:database="8" />
<bean id="stringRedisSerializer"
class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="v2redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="v2redisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:hashValueSerializer-ref="stringRedisSerializer" />
<!-- 这里的是为了下面的 Session策略过滤器提供构造函数传入的参数,因为Session过滤器要依赖该对象来构造,所以创建一个先 -->
<bean name="redisOperationsSessionRepository" class="org.springframework.session.data.redis.RedisOperationsSessionRepository">
<constructor-arg ref="v2redisConnectionFactory"></constructor-arg>
</bean>
<!-- 这个是Session策略过滤器,即将容器原有的Session持久化机制,代替为SpringRedis持久化Session机制。 -->
<!-- 注意,这个名字与 web.xml里的targetBean的下value是要一致的。 -->
<bean name="springSession" class="org.springframework.session.web.http.SessionRepositoryFilter">
<constructor-arg ref="redisOperationsSessionRepository"></constructor-arg>
</bean>

使用上跟普通使用 Session 的方式是一样的。

打完收工。

《啊哈!算法》学习笔记之栈

实现也很简单,只需要一个一维数组和一个指向栈顶的变量 top 就可以了。我们通过 top 来对栈进行插入和删除操作。

特点:后进先出

利用栈判断是否回文(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
26
public static void main(String[] args) {
char[] character = { 'a', 'a', 'h', 'a', 'a' };
char[] stack = new char[10];
int top = -1;
int mid = character.length % 2 == 0 ? character.length / 2 - 1 : character.length / 2;
for (int i = 0; i <= mid; i++) {
top++;
stack[top] = character[i];
}
if (character.length % 2 == 0) {
mid++;
}
for (int i = mid; i < character.length; i++) {
if (top >= 0) {
if (stack[top] != character[i]) {
break;
}
}
top--;
}
if (top == -1) {
System.out.println("YES");
} else {
System.out.println("NO");
}
}

《啊哈!算法》学习笔记之队列

列队的主要特点:先进先出

文中题目

规则是这样的:首先将第 1个数删除,紧接着将第 2 个数放到这串数的末尾,再将第 3 个数删除并将第 4 个数放到这串数的末尾,再将第 5 个数删除……直到剩下最后一个数,将最后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一起就是小哈的 QQ啦.

Java实现

例如:加密过的一串数是 “6 3 1 7 5 8 9 2 4”,输出应该是“6 1 5 9 4 7 2 8 3”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
int[] array = { 6, 3, 1, 7, 5, 8, 9, 2, 4 };
int[] queue = new int[100];
int head = 0;
int tail = 0;
for (int i = 0; i < array.length; i++) {
queue[i] = array[i];
tail++;
}
while (head < tail) {
System.out.println(queue[head]);
head++;
queue[tail] = queue[head];
tail++;
head++;
}
}

《啊哈!算法》学习笔记之快速排序

基本思想

每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的一边,将大于等于基准点的数全部放到基准点的另一边.

时间复杂度

因此快速排序的最差时间复杂度和冒泡排序是一样的,都是 O(N^2 ),它的平均时间复杂度为 O(NlogN)。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public static void main(String[] args) {
int length = 20;
int maxValue = 20;
int[] array = new int[length];
Random r = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = r.nextInt(maxValue);
}
System.out.println(Arrays.toString(array));
quickSort(array, 0, length-1);
System.out.println(Arrays.toString(array));
}
// 假设以左边的为基数,那就要从右边开始移动。左为大于基数,右为小于基数
public static void quickSort(int[] array, int leftPoint, int rightPoint) {
if (leftPoint > rightPoint) {
return;
}
int swapTmp = 0;
int baseNumber = array[leftPoint];// 以左边为基准
int i = leftPoint;
int j = rightPoint;
while (leftPoint != rightPoint) {
// 从右边开始向左移动,到找第一个大于基数时停止
while (array[rightPoint] <= baseNumber && leftPoint < rightPoint) {
rightPoint--;
}
// 从左边开始向右移动,到找第一个小于基数时停止
while (array[leftPoint] >= baseNumber && leftPoint < rightPoint) {
leftPoint++;
}
// 没有相遇时,就交换
if (leftPoint < rightPoint) {
swapTmp = array[leftPoint];
array[leftPoint] = array[rightPoint];
array[rightPoint] = swapTmp;
}
}
//恢复新一轮的基准,将旧的基准归位,然后又重新定一个新的左边基准位。即将基准数与中间数互换
array[i] = array[leftPoint];
array[leftPoint] = baseNumber;
//递归左,右两边
quickSort(array, i, leftPoint-1);
quickSort(array, leftPoint+1, j);
}

《啊哈!算法》学习笔记之冒泡排序

基本思想

冒泡排序的基本思想是:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。

以从大到小为例。

每次都是比较相邻的两个数,如果后面的数比前面的数大,则交换这两个数的位置。一直比较下去直到最后两个数比较完毕后,最小的数就在最后一个了。就如同是一个气泡,一步一步往后“翻滚” ,直到最后一位。所以这个排序的方法有一个很好听的名字“冒泡排序” 。

每将一个数归位我们将其称为“一趟” 。每一趟,都是将小的归位(先是最小,后次小,次后第三小…)。

如果有 n 个数进行排序,只需将 n-1 个数归位,也就是说要进行n-1 趟操作。而“每一趟”都需要从第 1 位开始进行相邻两个数的比较,将较小的一个数放在后面,比较完毕后向后挪一位继续比较下面两个相邻数的大小,重复此步骤,直到最后一个尚未归位的数, 已经归位的数则无需再进行比较 (已经归位的数你还比较个啥, 浪费表情) 。

核心思想

冒泡排序的核心部分是双重嵌套循环。

时间复杂度:O(N^2)

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
26
27
28
29
30
31
32
33
public static void main(String[] args) {
int length = 10;
int maxValue = 20;
int[] array = new int[length];
Random r = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = r.nextInt(maxValue);
}
System.out.println("before");
print(array);
bubbleSort(array);
System.out.println("after");
print(array);
}
public static void bubbleSort(int[] array) {
int tmp = 0;
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length-1; j++) {
if (array[j] < array[j+1]) {//如果前一个比后一个小,则进行交换。这样子就可以是从大到小。
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
public static void print(int[] array) {
System.out.println(Arrays.toString(array));
}

总结

冒泡比较耗时

《啊哈!算法》学习笔记之桶排序

时间复杂度:O(M+N)

M:桶的个数(也是该数值的最大数)
N:待排序个数

Java实现

随便输入N个不大于M的数字,然后从小到大输出:(从大到小,作一下小修改即可)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
int M= 100;
int N = 5;
int[] array = new int[M+ 1];
Scanner scan = new Scanner(System.in);
int in = 0;
for (int i = 0; i < N; i++) {
in = scan.nextInt();
array[in] = array[in] + 1;
}
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i]; j++) {
System.out.println(i);
}
}
}

个人总结

这种算法适合于范围比较小的排序,并且是需要知道输入的最大值。不然就不适用了。

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
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
1.Tomcat配置:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
2.文件:必须设置统一编码
JSP:<%@ page language="java" pageEncoding="UTF-8"%>
HTML:<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
ServletRequest:
request.setCharacterEncoding("UTF-8")是把提交内容的字符集设为UTF-8
ServletResponse:
response.setCharacterEncoding("UTF-8")
3. MySQL
default-character-set=utf-8
mysql里数据库和表也都设为utf8_unicode_ci
jdbc:mysql://localhost/mydb?useUnicode=true&characterEncoding=utf-8
4. PostgreSQL
postgresql.conf文件中的client_encoding改成UTF-8,
5. 注意:使用 FileInputStream 读取文件时,使用的是系统默认的字符编码。如果用在UTF-8系统中,很容易出现问题。
这时,可以使用:
FileInputStream in = new FileInputStream("g:\\yufa.txt");
InputStreamReader reader = new InputStreamReader (in,"GBK"); // 用这个方法读取,并指定编码
6. Java中的 properties 文件,如果是使用 utf-8 文件格式的,这个格式是无 BOM 格式的UTF-8的。切记
7. Spring
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

response.setCharacterEncoding(),response.setContentType()区别

1
2
3
response.setContentType指定 HTTP 响应的编码,同时指定了浏览器显示的编码.
response.setCharacterEncoding设置HTTP 响应的编码,如果之前使用response.setContentType设置了编码格式,则使用response.setCharacterEncoding指定的编码格式覆盖之前的设置.与response.setContentType相同的是,调用此方法,必须在getWriter执行之前或者response被提交之前.

关于Java中多线程造成的变量共享问题

起因

在自己写的一段代码中,使用到了Redis。代码的逻辑是这样子的,先取出数据,然后判断,再然后将该数据进行自减。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public boolean isOK(String invitationCode, boolean isSub) {
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
Object count = vo.get(key + invitationCode);
if (count != null && Integer.parseInt(count.toString()) >= 1) {
if (isSub) {
vo.increment(key + invitationCode, -1);
}
return true;
} else {
return false;
}
}

分析

在提交代码的时候,总监复查代码时,提出这段代码可能在并发的时候出现问题:比如有两条线程,都进入到了if(sub){xxx}这段代码,这个时候,就造成了重复多次自减的问题。比如,如果该value是5,那应该减到0就不会再减了,但是这段代码在高并发情况下,可能会出现减到-1的情况。这虽然对于现有的业务影响不大,但是,如果是对于其他的一些比较敏感并且需要精确的数据时,就需要特别注意了。

关键是之前一直没有怎么意识到这段代码可能会造成的问题。之前一直认为,对于局部变量是不会出现多线程问题的,这倒是没有错,但关键是如果是从其他地方传过来而且在多处地方允许被使用的情况下,就需要注意多线程问题。

[翻译]为什么在Java里不能将Integer强制转换成String

为何Integer不能转换为String

原文

因为 StringInteger 不是在同一个对象阶层。

1
2
3
4
Object
/ \
/ \
String Integer

当你尝试强制转换时,仅仅会在同一个对象阶层转换。比如:

1
2
3
4
5
6
7
Object
/
/
A
/
/
B

在这种情况,(A)objB 或者 (Object)objB 或者 (Object)objA 可以进行转换。

正如其他人已经提到,将integer转换成String可以使用以下方法:

基本类型的整型时使用:String.valueOf(integer)或者Integer.toString(integer) 来转换成String
Integer对象型时使用:Integer.toString()转换成String


写代码过程中,居然遇到这种错误(在使用Spring 来操作 Redis 经常出现,但百思不得其姐。和同事讨论时才发现,原来Redis在底层永远是保存String的,即使在写代码的时候是写成 set(String, Object)。这时,如果get这个缓存出来,而且想要的是整型时,就会报java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String错误。),出来工作差不多2年了,这种错误的本质原因,直到今天才明白。实在是太惭愧了。很可能是被所有对象可以以字符串的形式表示而导致的,以为所有的对象在使用 (String)obj时,会使用 obj.toString(),所以才造成这种认为理所当然的错觉。