guava集合类中神奇transform方法研究

之前我就提过guava这个东西,最近在项目中使用的越来越多,越来越发现这东西好神奇 ,用起来真顺手。不得不佩服,谷歌出品,必属精品。
平时业务开发中经常遇到需要转换List的需求。例如,假设现在有一个类如下:

1
2
3
4
5
6
class Customer{
private long id;
private String costomerName;

//below is getters and setters
}

然后有这样一个List<Customer> customerList的List,现在我想要一个包含Customerid的List: List<Long> customerIdList。那如何做呢?

Guawa中tryParse的研究

今天发现项目中在转化string为int时使用了guawa的Ints.tryParse方法。之前没有见过这样的用法,好奇,对其相关知识进行了研究。

为什么要使用tryParse

String转Integer,JDK本身就提供了响应的方法。就是Integer.parseInt(String s),那为什么还要再造轮子呢,因为JDK提供的这个方法抛一个NumberFormatException,由于这样,每次调用这个方法就必须用try catch来捕获这个异常,或者是再向上抛给上层的方法。
为什么需要这个异常
可以看到,这个方法接受的参数是个String, 如果传入的参数不是数字,或者格式非法,如传入abc,这样的字符串是无法转换成数字的,所以需要抛出这样的异常。
这样有什么不好
这样做不是正确的吗,有什么缺点呢?要知道,抛异常代价是很大的,详细原因可以看这里。总结一下就是:

  1. 抛异常必须生成stacktrace。生成过程消耗资源
  2. 异常处理需要特殊的流程控制。
  3. 异常处理消耗时间无法量化。原因:异常处理与stacktrace深度有关,如果stracktrace深度很深,会很消耗资源。
    所以,如果使用这样的方法,在非法字符串很多的时候,系统抛很多异常是很影响性能的事情。
    还有一点:程序员写程序,使用这样的方法会很影响程序的鲁棒性。
    所以,需要重新造一个更好的轮子,来解决这个问题。

    解决方案

    那么,一定要像JDK这样解决String转Integer的问题吗?答案当然是否定的。
    JDK的实现方式是使用抛异常的方式来解决字符串非法的问题。判断非法字符的方法有很多,不一定用抛异常的方式啊,这种方式是最不好的。
    方法详见链接
    总结下:
  4. 遍历string确定每个字符都是数字
  5. 使用正则表达式扫描
  6. 使用NumberFormat进行扫描
  7. 使用java.util.Scanner进行扫描

在Guawa中使用的是方案1,在将字符转化为数字的过程中使用了JDK的Character.digit方法,实现原理如下:

public static Integer tryParse(String string) {
return AndroidInteger.tryParse(string, 10);
}

首先调用了guawa自己的AndroidInteger中的tryParse方法,这个方法的第二个参数是基数。基数是什么呢,这样说吧,在这里传入10就会返回string的10进制对应的int值,如果传入的是2或者16则会返回二进制或者16进制对应的int值,例如AndroidInteger.tryParse('F',16)则会返回数值15.
AndroidInteger的tryParse源码:

static Integer tryParse(String string, int radix) {
checkNotNull(string);
checkArgument(radix >= Character.MIN_RADIX,
“Invalid radix %s, min radix is %s”, radix, Character.MIN_RADIX);
checkArgument(radix <= Character.MAX_RADIX,
“Invalid radix %s, max radix is %s”, radix, Character.MAX_RADIX);
int length = string.length(), i = 0;
if (length == 0) {
return null;
}
boolean negative = string.charAt(i) == ‘-‘;
if (negative && ++i == length) {
return null;
}
return tryParse(string, i, radix, negative);
}

可以看到,这里主要先是判断输入参数的合法性(checkArgument方法),再判断数字的符号(boolean negative = string.charAt(i) == '-';),剩下的事情都交给了return中的tryParse去做。

下面是return中tryParse代码:

private static Integer tryParse(String string, int offset, int radix,
boolean negative) {
int max = Integer.MIN_VALUE / radix;
int result = 0, length = string.length();
while (offset < length) {
int digit = Character.digit(string.charAt(offset++), radix);
if (digit == -1) {
return null;
}
if (max > result) {
return null;
}
int next = result * radix - digit;
if (next > result) {
return null;
}
result = next;
}
if (!negative) {
result = -result;
if (result < 0) {
return null;
}
}

这里可以看到,该方法主要使用了Character.digit(string.charAt(offset++), radix)将字符转化为int,并判断输入字符是否合法。将字符转化成int后int next = result * radix - digit;将原数值还原。最后判断正负号,然后返回值。
至此,实现原理大白于天下。

详细用法点此处)了解。值得一提的是,如果传入的string为带正号,如String s = "+12345"会被拒绝,认为是非法字符。

##总结
JDK自带的Integer.parseInt()方法会抛异常,抛异常影响程序的性能。所以Guawa重新造轮子,有了tryParse方法。原理为扫描字符串中字符,判断是否为数字。

Java中的PECS

最近在看Guava源码的过程中发现了通用类型的用法. 通用类型是JAVA5以后引入的新的功能,用处是当定义一个方法或者一个类,但是不太确定需要传入的参数的类型时,这时候使用通用类型。例子如Java中的List,Map
通用类型中有一个通配符?,在读代码的过程中发现了有? extends, ? super的用法,出于好奇,对这个用法进行了一些多一点的研究。发现了一个原则就是:PECS(Producer extends, consumer super)。

一、什么是PECS?

PECS这个概念是从集合类的概念来讲的。

enum和常量

今天遇到了一个问题,让我对enum以及常量有了更好的认识。
上回书说enum类型天生是final和static的。Java中的常量,我们知道也一般定义为static final。例如:

1
private static final int intConstant = 1;

上面这行代码中,变量intConstant就是一个常量,常量意味着intConstant的值在编译时就能够确定,并且无法改变。
这样听起来被定义为static final的变量就能称之为常量了啊,enum天生就是static final的,enum天生就是常量啦?

为什么要用enum?

最近发现公司的代码中存在大量的使用int枚举的方式,也看到了一些使用enum进行枚举的方式。对此很好奇,进行了研究,估写此文总结,来说一说为什么要使用enum。

null是个什么玩意儿!

最近由于帮公司修改NPE的问题,接触到null比较多,这货也是经常见了,于是就想详细了解一下这是个啥玩意儿。
null在很多语言里都有,但是在java中它具有以下特性:

  1. 它不是任何类型,所以使用instance of关键字去判断任意null的引用或者null其本尊的时候,返回结果总是false
  2. 它是很多未赋值的对象引用的默认值
  3. 可以简单的理解为null就是JAVA中的一个关键字而已。

Null的恶

引用null之父C. A. R. Hoare的原话就是:

I call it my billion-dollar mistake.

为什么说是billion-dollar mistake?举个栗子:

修改NPE问题的收获

最近为公司的项目修改NullPointerException的问题,有些收获。
null可以被强制转换成任意类型的引用,强制过程不会抛异常
在修改一处NPE问题时,本来看日志已经定位了大概的范围,但是就是找不到具体是哪里发生了问题。引用看起来都不可能为空啊,其中有一个引用就是通过强制转换,转换成另外一个类型的。当时觉得,这里也不能是空指针异常的地方啊,如果被强转引用为空,则强转不能成功啊。
后来发现,事实不是这样的,23333.举个栗子:

Longest Consecutive Sequence

最近打算将做过的leetcode题也都总结下思路,感觉做算法题还是可以提高编程能力的。
最长连续序列题目如下:

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.

For example,
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.

Your algorithm should run in O(n) complexity.

翻译一下就是:

有一个无序整型数组,找到数组中最长连续元素序列。
例如:[100, 4, 200, 1, 3, 2]中最长连续元素序列是[1, 2, 3, 4],所以返回结果是4.
算法复杂度要求0(n)

foreach循环优于传统for循环

今天在用for each语句的时候突然想到for each与普通for循环有什么区别,于是谷歌之。发现答案居然就在《effective java》里,看来还是要多啃书。
总结下:

  1. foreach循环隐藏了索引变量或迭代器,语法更加简介,减少出错可能。
  2. foreach循环对数组索引边界值只计算一次,传统for循环中,可能由于程序员习惯,将索引边界值写在for的表达式里,会使边界值计算执行多次。从这点上看, foreach循环还有点效率优势。

split方法返回的数组的长度不会为0

前两天在公司写代码的时候犯了一个巨傻逼的错误,暴露了JAVA基础不够扎实的缺陷。事情是这样的:
我需要将获得到的string用split(",")方法将其分成数组。为了防止获得的string不能正确的被split,我对split后的数组进行了判断。大概如下:

1
2
3
4
String [] strArray = str.split(",");
if(strArray != null && strArray.length !=0){
do something here
}