package com.cheng.improve151suggest; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.ListView; import java.util.Arrays; import com.cheng.highqualitycodestudy.R; import com.cheng.improve151suggest.adapter.I151SuggestListAdapter; /** 第11章 开源世界 建议139: 大胆采用开源工具 建议140: 推荐使用guava扩展工具包 建议141: apache扩展包 建议142: 推荐使用joda日期时间扩展包 建议143: 可以选择多种collections扩展 */ public class I151SChapter11Activity extends AppCompatActivity { private static final String TAG = "I151SChapter11Activity"; private ListView mChapterLV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_i151suggestchapter); initView(); initData(); } private void initView() { this.mChapterLV = (ListView) findViewById(R.id.isi_chapter_lv); } private void initData() { String[] suggests = getResources().getStringArray(R.array.i151schapter11); I151SuggestListAdapter adapter = new I151SuggestListAdapter(this, Arrays.asList(suggests)); mChapterLV.setAdapter(adapter); } /** * 建议139: 大胆采用开源工具 */ private void suggest139() { /* 在选择开源工具和框架时要遵循一定的原则: -普适性原则 选择一个工具或框架就必须考虑项目成员的整体技术水平,不能有太大的跨度或跳跃性,要确保大部分项目 成员对工具都比较熟悉 -唯一性原则 相同的工具只选择一个或一种,不要让多种相同或相似职能的工具共存。例如集合工具可以使用Apache Commons 的collections包,当然也可以使用Google Guava的Collections工具包,但是在项目开发前就应该确认 下来,不能让两者共存 -“大树纳凉”原则 在选择工具包时得寻找比较有名的开源组织,比如Apache、Spring、Google等,这些开源组织一则具有固 定的开发和运作风格,二则具有广阔的使用人群(很多情况下,我们不会是第一个发现Bug的人),在这样 大树下,我们才有时间和精力纳凉,而不会把大好的时间消耗在排查Bug上 -精而专原则 选择的工具包应该是精而专的,而不是广而多的,比如虽然Spring框架提供了Utils工具包,但是一般情况 下不要使用它,因为它不专,Utils工具包只是Spring框架中的一个附加功能而已,要用就用Apache Commons 的BeanUtils、Lang等工具包 -高热度原则 一个开源项目的热度越高,更新得越频繁,使用的人群就越广,Bug的曝光率就越快,修复效率也就越高,这 对我们项目的稳定性来说是非常重要的 */ /** * 注意 * 对于开源工具,我们应该大胆采用,仔细筛选,如果确实所有的开源工具都无法满足我们的需求,那就自己 * 开发一个开源项目,为千千万万的Java人服务,也为Java的生态系统贡献自己的力量 */ } /** * 建议140: 推荐使用Guava扩展工具包 */ private void suggest140() { /* 2008年Google发布了Google-collections扩展包,主要是对JDK的Collection包进行了扩展,2010年Google 发布了Guava项目,其中包含了collections、caching、primitives support、concurrency libraries、 common annotations、I/O等,这些都是项目编码中的基本工具包,大致浏览下它的主要功能: 1)Collections com.google.common.collect包中主要包括四部分:不可变集合(Immutable Collections)、多值Map、 Table表和集合工具类 -不可变集合 不可变集合包括ImmutableList、ImmutableMap、ImmutableSet、ImmutableSortedMap、ImmutableSortedSet 等,它比不可修改集合(Unmodifiable Collections)更容易使用,效率更高,而且占用的内存更少。示例: // 不可变列表 ImmutableList<String> list = ImmutableList.of("A", "B", "C"); // 不可变Map ImmutableMap<Integer, String> map = ImmutableMap.of(1, "壹", 2, "贰", 3, "叁"); 其中的of方法有多个重载,其目的就是为了便于在初始化的时候直接生成一个不可变集合 -多值Map 多值Map比较简单,在JDK中,Map中的一个键对应一个值,在put一个键值对时,如果键重复了,则会覆盖原 有值,在大多数情况下这比较符合实际应用,但有的时候确实会存在一个键对应多个值的情况,比如我们的通 讯录,一个人可能会对应两个或三个号码,此时使用JDK的Map就有点麻烦了。在这种情况下,使用Guava的 Multimap可以很好地解决问题,代码如下: // 多值Map Multimap<String, String> phoneBook = ArrayListMultimap.create(); phoneBook.put("张三", "110"); phoneBook.put("张三", "119"); System.out.println(phoneBook.get("张三")); -Table表 在GIS(Geographic Information System,地理信息系统)中,我们经常会把一个地点坐标标注在一个坐 标上,比如把上海人民广场标注在北纬31.23、东经121.48的位置上,也就是说只要给出准确的经度和纬度就 可以进行精确的定位--两个键决定一个值,这在Guava中是使用Table来表示的,示例代码如下: Table<Double, Double, String> g = HashBasedTable.create(); // 定义人民广场的经纬坐标 g.put(31.23, 121.48, "人民广场"); // 输出坐标点的建筑物 g.get(31.23, 121.48); 其实Guava的Table类与我们经常接触的DBRMS表非常类似,可以认为它是一个没有Schema限定的数据表,如: // Table,完全类似于数据库表 Table<Integer, Integer, String> user = HashBasedTable.create(); // 第一行、第一列的值是张三 user.put(1, 1, "张三"); // 第一行、第二列的值是李四 user.put(1, 2, "李四"); // 第一行第一列是谁 user.get(1, 1); -集合工具类 Guava的集合工具类分得比较细,比如Lists、Maps、Sets分别对应的是List、Map、Set的工具类,它们的 使用方法比较简单,比如Map的过滤,如下所示: // 姓名、年龄键值对 Map<String, Integer> user = new HashMap<String, Ingeger>(); user.put("张三", 20); user.put("李四", 22); user.put("王五", 25); // 所有年龄大于20岁的人员 Map<String, Integer> filtedMap = Maps.filterValues(user, new Predicate<Integer>() { public boolean apply(Integer _age) { return _age>20; } }); 2)字符串操作 Guava提供了两个非常好用的字符串操作工具:Joiner连接器和Splitter拆分器。当然字符串的连接和拆分 使用JDK的方法也可以实现,但是使用Guava更简单一些,比如字符串的连接,代码如下: // 定义连接符号 Joiner joiner = Joiner.on(", "); // 可以连接多个对象,不局限于String;如果有null,则跳过 String str = joiner.skipNulls().join("嘿", "Guava很不错的。"); Map<String, String> map = new HashMap<String, String>(); map.put("张三", "普通员工"); map.put("李四", "领导"); // 键值之间以“是”连接,多个键值以空格分隔 System.out.println(Joiner.on("\r\n").withKeyValueSeparator(" 是 ").join(map)); Joiner不仅能够连接字符串,还能够把Map中的键值对串联起来,比直接输出Map优雅了许多。Splitter是 做字符拆分的,使用方法也比较简单,示例代码如下: String str = "你好,Guava"; // 以“,”中文逗号分隔 for (String s : Splitter.on(",").split(str)) { System.out.println(s); } // 按照固定长度分隔 for (String s : Splitter.fixedLength(2).split(str)) { System.out.println(s); } 注意fixedLength方法,它是按照给定长度进行拆分的,比如在进行格式化打印的时候,一行最大可以打印 120个字符,此时使用该方法就非常简单了 3)基本类型工具 基本类型工具在primitives包中,是以基本类型名 +s 的方式命名的,比如Ints是int的工具类,Doubles 是double的工具类,注意这些都是针对基本类型的,而不是针对包装类型的。代码如下: int[] ints = {10, 9, 20, 40, 80}; // 从数组中取出最大值 System.out.println(Ints.max(ints)); List<Integer> integers = new ArrayList<Integer>(); // 把包装类型的集合转为基本类型数组 ints = Ints.toArray(integers); */ /** * 注意 * Guava还提供了其他操作(如I/O操作),有兴趣自己研究 */ } /** * 建议141: Apache扩展包 */ private void suggest141() { /* Apache Commons通用扩展包,一般情况下lang包用作JDK的基础语言扩展,Collections用作集合扩展, DBCP用作数据库连接池等,下面简要介绍: 1)Lang -字符串操作工具类 JDK提供的String工具不足以满足开发需求,Lang包弥补了这个缺陷,它提供了诸如StringUtils(基本的 String操作类)、StringEscapeUtils(String的转义工具)、RandomStringUtils(随机字符串工具) 等非常实用的工具,示例代码如下: // 判断一个字符串是否为空,null或“”都返回true StringUtils.isEmpty(str); // 释放为数字 StringUtils.isNumeric(str); // 最左边两个字符 StringUtils.left(str, 2); // 统计子字符串出现的次数 StringUtils.countMatches(str, subString); // 转义XML标识 StringEscapeUtils.escapeXml(str); // 随机生成,长度为10的仅字母的字符串 RandomStringUtils.randomAlphabetic(10); // 随机生成,长度为10的ASCII字符串 RandomStringUtils.randomAscii(10); // 以一个单词为操作对象,首字母大写,输出结果为:Abc Bcd WordUtils.capitalize("abc bcd"); -Object工具类 每个类都有equals、hashCode、toString方法,如果自己编写的类需要覆写这些方法,就需要考虑很多 的因素了,特别是equals方法,如果使用lang包就会简单得多,示例代码如下: class Person { private String name; private int age; // ... public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (obj.getClass() != getClass()) return false; Person p = (Person) obj; // 只要姓名相同,就认为两个对象相等 return new EqualsBuilder() .appendSuper(super.equals(obj)) .append(name, p.name) .isEquals(); } } // 自定义hashCode public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } -可变的基本类型 基本类型都有相应的包装类型,但是包装类型不能参与加、减、乘、除运算,要运算还是得转化为基本类型, 那如果希望使用包装类进行运算该怎么办呢?使用Lang包的示例如下: // 声明一个可变的int类型 MutableInt mi = new MutableInt(10); // mi加10 mi.add(10); // 自加1 mi.increment(); -其他Utils工具 Lang包在日期处理方面主要提供了DateUtils和DateFormatUtils两个工具类 Lang包还提供了诸如ArrayUtils、LocaleUtils、NumberUtils等多个工具类 2)BeanUtils 它是JavaBean的操作工具包,不仅可以实现属性的拷贝、转换等,还可以建立动态的Bean,甚至建立一些 自由度非常高的Bean -属性拷贝 在分层开发时经常会遇到PO(Persistence Object)和VO(Value Object)之间的转换问题,不过,有 多种方法可以解决之,但是最好的办法就是使用BeanUtils来操作,代码如下: // PO对象 User user = new User(); // VO对象 Person person = new Person(); // 两个Bean属性拷贝 PropertyUtils.copyProperties(person, user); // 把Map中的键值对拷贝到Bean上 Map<String, String> map = new HashMap<String, String>(); map.put("name", "张三"); PropertyUtils.copyProperties(person, map); -动态Bean和自由Bean 要在运行期生成一个动态Bean,或者在需要生成无固定格式的Bean时,使用普通Bean就无法实现了。可以 使用BeanUtils包解决该问题,示例代码如下: // 动态Bean,首先定义Bean类 DynaProperty[] props = new DynaProperty[]{ new DynaProperty("name", String.class), new DynaProperty("age", int.class)}; BasicDynaClass dynaClass = new BasicDynaClass("people", null, props); // 动态Bean对象 DynaBean people = dynaClass.newInstance(); / people的get/set操作 / // 自由Bean DynaBean user = new LazyDynaBean(); // 直接定义属性和值 user.set("name", "张三"); // 定义属性名,限定属性类型为Map user.set("phoneNum", "tel", "021"); user.set("phoneNum", "mobile", "138"); // 属性类型为ArrayList user.set("address", 0, "上海"); user.set("address", 1, "北京"); -转换器 如果期望把一个Bean的所有String类型属性在输出之前都加上一个i额前缀,该如何做呢?一个一个进行 属性过滤?或者使用反射来检查属性类型是否为String,然后加上前缀?这样可以解决,但不优雅,看 BeanUtils如何解决: // 一个简单的Bean对象 User user = new User("张三", 18); // 转换工具 ConvertUtilsBean cub = new ConvertUtilsBean(); // 注册一个转换器 cub.register(new Converter() { public Object convert(Class type, Object value) { // 为每个String类型的属性加上前缀 return "prefix=" + value; } }, String.class); // 建立一个依赖特定转换工具的Bean工具类 BeanUtilsBean beanUtils = new BeanUtilsBean(cub); // 输出结果为:prefix-张三 beanUtils.getProperty(user, "name"); 3)Collections Collections工具包提供了ListUtils、MapUtils等基本集合操作工具,介绍3个不太常用的结合对象: -Bag Bag是Collections中的一种,它可以容纳重复元素,与List的最大不同点是它提供了重复元素的统计功 能,比如一个盒子中有100个球,现在要计算出蓝色球的数量,使用Bag就很容易实现,代码如下: // 一个盒子中装了4个球 Bag box = new HashBag(Arrays.asList("red", "blue", "black", "blue")); // 又增加了3个蓝色球 box.add("blue", 3); // 球的数量为7 box.size(); // 蓝色球数量为5 box.getCount("blue"); -lazy系列 “在我需要的时候,你再出现”,lazy系列的集合就是起这样的作用的,在集合中的元素被访问时它才会 生成,这也就涉及一个元素的生成问题了,可通过Factory的实现类来完成,示例代码如下: // 把一个List包装成一个lazy类型 List<String> lazy = LazyList.decorate(new ArrayList(), new Factory() { public String create() { return "A"; } }); // 访问了第4个元素,此时0、1、2元素为null String obj = lazy.get(3); // 追加一个元素 lazy.add("第五个元素"); // 元素总数为5个 lazy.size(); -双向Map JDK中的Map要求键必须唯一,而双向Map则要求键、值都必须唯一,也就是键值是一一对应的,此类 Map的好处就是即可以根据键进行操作,也可以反向根据值进行操作,比如删除、查询等,代码如下: // key、value都不允许重复的Map BidiMap bidiMap = new TreeBidiMap(); bidiMap.put(1, "壹"); // 根据key获取value bidiMap.get(1); // 根据value获取key bidiMap.getKey("壹"); // 根据value删除键值对 bidiMap.removeValue("壹"); */ /** * 注意 * Apache commons项目有很多非常好用的工具,可以自己去发掘 */ } /** * 建议142: 推荐使用joda日期时间扩展包 */ private void suggest142() { /* 在JDK中,日期时间的操作比较麻烦,例如1000小时后是星期几,伦敦时间是几点等,这里介绍一下通过Joda 开源包来操作时间的方法,非常简单方便 1)本地格式的日期时间 依据操作系统或指定的区域输出日期或时间,例如: // 当前时间戳 DateTime dt = new DateTime(); // 输出英文星期 dt.dayOfWeek().getAsText(Locale.ENGLISH); // 本地日期格式 dt.toLocalDate(); // 日期格式化 dt.toString(DateTimeFormat.forPattern("yyyy 年 M 月 d 日")); 2)日期计算 这是Joda最方便的地方,也是JDK最麻烦的地方,比如要计算100天后是星期几,直接使用JDK提供的日期类 会非常麻烦,使用Joda就简单很多,例如: // 当前时间戳 DateTime dt = new DateTime(); // 加100小时是星期几 dt.plusHours(100).dayOfWeek(); // 100天后的日期 dt.plusDays(100).toLocalDate(); // 10年前的今天是星期几 dt.minusYears(10).dayOfWeek().getAsText(); 这里需要注意的是,DateTime是一个不可变类型,与String非常类似,即使通过plusXXX、minusXX等方法 进行操作,dt对象仍然不会变,只是新生成一个DateTime对象而已。但是,Joda也提供了一个可变类型的日 期对象:MutableDateTime类,这样,日期的加减操作就更加方便了,比如列出10年内的黑色星期五,实现 代码如下(此实现若用JDK的类来计算会异常复杂): // 当前可变时间 MutableDateTime mdt = new MutableDateTime(); // 10年后的日期 DateTime destDateTime = mdt.plusYears(10); while (mdt.isBefore(destDateTime)) { // 循环一次加1天 mdt.addDays(1); // 是13号,而且是星期五 if (mdt.getDayOfMonth()==13 && mdt.getDayOfWeek()==5) { // 打印出10年内所有的黑色星期五 System.out.println("黑色星期五:" + mdt); } } 3)时区时间 这个比较简单实用,给定一个时区或地区代码即可计算出该时区的时间,比如在一个全球系统中,数据库中 全部是按照标准时间来记录的,但是在展示层要按照不同的用户、不同的时区展现,这就涉及时区计算了, 代码如下: // 当前时间戳 DateTime dt = new DateTime(); // 当前伦敦市的时间 dt.withZone(DateTimeZone.forID("Europe/London")); // 计算出标准时间 dt.withZone(DateTimeZone.UTC); Joda还有一个优点,它可以与JDK的日期库方便地进行转换,可以从java.util.Date类型转为Joda的 DateTime类型,也可以从Joda的DateTime转为java.util.Date,代码如下: DateTime dt = new DateTime(); // Joda的DateTime转为JDK的Date Date jdkDate = dt.toDate(); // JDK的Date转为Joda的DateTime dt = new DateTime(jdkDate); 经过这样的转换,Joda可以很好地与现有的日期保持兼容,在需要复杂的日期计算时使用Joda,在需要与 其他系统通信或写到持久层中则使用JDK的Date */ /** * 注意 * Joda是一个令人惊奇的高效工具,无论是计算日期、打印日期,或是解析日期,Joda都是首选,当然日 * 期工具类也可以选择date4j,它也是一个不错的开源工具 */ } /** * 建议143: 可以选择多种collections扩展 */ private void suggest143() { /* 再介绍三个比较有个性的Collections扩展工具包 1)fastutil fastutil主要提供了两种功能:一种是限定键值类型(Type Specific)的Map、List、Set等,另一种是 大容量的集合。先来看示例代码: // 明确键类型的Map Int2ObjectMap<String> map = new Int2ObjectOpenHashMap<String>(); map.put(100, "A"); // 超大容量的List,注意调整JVM的Heap内存 BigList<String> bigList = new ObjectBigArrayBigList<String>(1L + Integer.MAX_VALUE); // 基本类型的集合,不再使用Integer包装类型 IntArrayList arrayList = new IntArrayList(); 这里要特别注意的是大容量集合,什么叫大容量集合呢?我们知道一个Collection的最大容量是Integer的 最大值(2 147 483 647),不能超过这个容量,一旦我们需要把一组超大的数据放到集合中,就必须要考 虑对此进行拆分了,这会导致程序的复杂性提高,而fastutil则提供了Big系列的集合,它的最大容量是Long 的最大值,这已经是一个非常庞大的数字了,超过这个容量基本上是不可能的。但在使用它的时候需要考虑 内存溢出的问题,注意调节Java的mx参数配置 2)Trove Trove提供了一个快速、高效、低内存消耗的Collection集合,并且还提供了过滤和拦截的功能,同时还提 供了基本类型的集合,示例代码如下: // 基本类型的集合,不使用包装类型 TIntList intList = new TIntArrayList(); // 每个元素值乘以2 intList.transformValues(new TIntFunction() { public int execute(int element) { return element * 2; } }); // 过滤,把大于200的元素组成一个新的列表 TIntList t2 = intList.grep(new TIntProcedure() { public boolean execute(int _element) { return _element > 200; } }); // 包装为JDK的List List<Integer> list = new TIntListDecorator(intList); // 键类型确定Map TIntObjectMap<String> map = new TIntObjectHashMap<String>(); Trove的最大优势是在高性能上,在进行一般的增加、修改、删除操作时,Trove的响应时间比JDK的集合少 一个数量级,比fastutil也会高很多,因此在高性能项目中要考虑使用Trove 3)lambdaj lambdaj是一个纯净的集合操作工具,它不会提供任何的集合扩展,只会提供对集合的操作,比如查询、过滤、 统一初始化等,特别是它的查询操作,非常类似与DBRMS上的SQL语句,而且也会提供诸如求和、求平均值的 方法,示例代码如下: List<Integer> ints = new ArrayList<Integer>(); // 计算平均值 Lambda.avg(ints); // 统计每个元素出现的次数,返回一个Map Lambda.count(ints); // 按照年龄排序 List<Person> persons = new ArrayList<Person>(); Lambda.sort(persons, Lambda.on(Person.class).getAge())); // 串联所有元素的指定属性,输出为:张三、李四、王五 Lambda.joinFrom(persons).getName(); // 过滤出年龄大于20岁的所有元素,输出为一个子列表 Lambda.select(persons, new BaseMatcher<Person>() { @Override public boolean matches(Object _person) { Person p = (Person) _person; return p.getAge() > 20; } public void describeTo(Description desc) { } }); // 查找出最大年龄 Lambda.maxFrom(persons).getAge(); // 抽取出所有姓名形成一个数组 Lambda.extract(persons, Lambda.on(Person.class).getName())); */ /** * 注意 * Collections的扩展很多,按需选择 */ } }