《Effective Java》读书笔记 - 5.泛型

Chapter 5 Generics

Item 23: Don’t use raw types in new code

虽然你可以把一个List<String>传给一个List类型(raw type)的参数,但你不应该这么做(因为允许这样只是为了兼容遗留代码),举个例子:

// Uses raw type (List) - fails at runtime!
public static void main(String[] args) {List<String> strings = new ArrayList<String>();unsafeAdd(strings, new Integer(42));String s = strings.get(0); // Compiler会自动加上cast
}
private static void unsafeAdd(List list, Object o) {list.add(o);
}

以上代码说明如果你把List<String>类型转换成List类型,就可以往里面加任何东西了,编译器不会做检查,就很危险。但是你也不能把List<String>转换成List<Object>,但是你可以把List<String>转换成List<?>,读作List of some type(某种类型的List),但这时候你就不能忘里面add东西了(除了null),因为这个List包含某种类型,但你又不知道(或不关心)是具体是什么类型,那你加进去的东西很可能跟它应有的类型不匹配,所以你只能拿出来一个东西(并定义成Object类型)。如果你实在想add,可以用generic methods或bounded wildcard types。

由于在运行时“都变成了raw type”,所以instanceof后面只能用raw type:

// Legitimate use of raw type - instanceof operator
if (o instanceof Set) { // Raw typeSet<?> m = (Set<?>) o; // Wildcard type
}

这里的Cast不会造成编译器warning。

Item 24: Eliminate unchecked warnings

当写Generic的时候,通常会遇到很多warning。而你应该尽量eliminate掉每一个warning,这样到了runtime你的代码才更可能不会抛出ClassCastException。如果你没法消除这个warning,但你能证明是typesafe的,那你应该用@SuppressWarnings("unchecked")。SuppressWarnings可以用在类上,方法上,变量声明上,你应该将其用在the smallest scope possible,因为如果你比如用在整个class上,那你可能mask了一些关键性的warning,所以千万别这么做。有时候为了这个原则,你还不得不把某句语句拆成两句写,比如:

return (T[]) Arrays.copyOf(elements, size, a.getClass());

由于不能在return语句上加SuppressWarnings,所以你只能拆成两句:

@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
return result;

每次你用@SuppressWarnings("unchecked")时,都应该注释一下你为什么要这么做。

Item 25: Prefer lists to arrays

数组是covariant的,意思就是如果Sub是Super的子类,那么Sub[]就是Super[]的“子类型”,这也就意味着你可以把String[]转成Object[],然后加一个Object对象进去,然后到runtime会报错。而泛型是invariant的,也就是对于任何的x和y,List<x>List<y>没有任何关系。
你无法new一个跟泛型有关的数组,比如以下都是错误的:new List<E>[], new List<String>[], new E[]。为什么?书上举了个例子我懒得写了,反正我个人总结下来就是:都怪擦除,因为T[]到运行时其实就相当于Object[],你可以往里面放任何东西,但按理说你应该只能放是T的东西进去。所以说不要把varargs和泛型混用,因为varargs其实就相当于创建了一个数组当成参数。由于这种无法创建数组的限制以及数组的协变性,你可以考虑用List<T>代替T[],比如可以用new ArrayList<E>(list)代替(E[]) list.toArray(),会安全得多。
总结来说,泛型提供了编译时但非运行时的type safety,而数组恰好相反。

Item 26: Favor generic types

generic types就是Generic classes and interfaces。这个item就是教你怎么把你的类变成泛型类,比如我们要把item 6中基于Object[]的Stack升级为泛型的,那我们就把所有“Object”替换成“E”,并在类声明中加上泛型参数。这样会有一个问题就是:elements = new E[DEFAULT_INITIAL_CAPACITY]这句话不能通过编译。因为你不能创建泛型数组,解决方法是:
1.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
因为elements是个private的field,不会泄露给client,而唯一给这个数组“装元素”的地方就是push方法,而push方法push进去的东西保证是E类型的(通过编译器),所以你可以安心地加上@SuppressWarnings("unchecked")。(给这句所在constructor加,因为这句是赋值不是声明,所以加不了)
2.先elements = new Object[DEFAULT_INITIAL_CAPACITY]; 然后把elements的定义改成Object[]类型的,最后E result = (E) elements[--size];就行了。
同理,因为push的时候已经确保元素肯定是E,所以这里的warning也可以suppress掉。这两种方法都可以,基本只是个人喜好问题。

Item 27: Favor generic methods

“Static utility methods”通常是泛型化的good candidates。在调用泛型方法的时候不需要显式指定泛型参数的具体类型,编译器会自己推断出来,这叫type inference。
后面这个generic singleton factory我来回看了几遍终于有那么一点似乎看懂了,首先例子代码如下:

interface UnaryFunction<T> {T apply(T arg);
}
private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {public Object apply(Object arg) {return arg;}
};
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction() {return (UnaryFunction<T>) IDENTITY_FUNCTION;
}

一开始我在想new UnaryFunction<Object>(){...}这句话是什么意思,为什么这里是Object而不能是T,后来一想:匿名类是同时声明和创建的,而创建一个泛型类的实例必须指定具体的type parameter,所以这里就相当于声明了一个 实现了UnaryFunction<T>的类,然后创建了一个它的实例(泛型参数是Object)。然后identityFunction方法是一个泛型的static factory method,会把UnaryFunction<Object>类型转换成 “调用这个泛型方法的时候被推断出来的类型 的类型的UnaryFunction”。先看一下用法:

public static void main(String[] args) {String[] strings = { "jute", "hemp", "nylon" };UnaryFunction<String> sameString = identityFunction();for (String s : strings)System.out.println(sameString.apply(s));Number[] numbers = { 1, 2.0, 3L };UnaryFunction<Number> sameNumber = identityFunction();for (Number n : numbers)System.out.println(sameNumber.apply(n));
}

第一次调用identityFunction()的时候被推断出来的类型是String,第二次是Number。然后以第一次为例,在调用sameString.apply(s)的时候,相当于编译器就会调用UnaryFunction<String>接口中的public String apply(String arg)方法,所以编译器此时会检查s这玩意儿是不是Sting,发现没问题,OK,返回的结果也会被编译器cast成String,而这里你的实际方法(return arg;)啥都没做,所cast肯定不会报错。这个例子的意思关键在于static factory method里面的那个cast: (UnaryFunction<T>) IDENTITY_FUNCTION,正是因为这个cast,所以client代码才能让 任何类型的UnaryFunction 都共享同一个实例(IDENTITY_FUNCTION )。
但是我在普通的client代码里面试了一下,无法将List<Object> cast成 List<String>,看来这个技巧也只能在泛型方法里面用了。C#的类似实现(虽然可能不是单例):

static void Main(string[] args)
{String[] strings = { "jute", "hemp", "nylon" };var sameString = IdentityFunction<String>();foreach (var s in strings){Console.WriteLine(sameString(s));}int[] ints = { 1, 2, 3 };var sameInt = IdentityFunction<int>();foreach (var s in ints){Console.WriteLine(sameInt(s));}
}
public static Func<T, T> IdentityFunction<T>()
{return arg => arg;
}

听起来很玄乎的一个概念recursive type bound,比如:<T extends Comparable<T>> may be read as “for every type T that can be compared to itself,”。
总之,generic methods和generic types都不需要client进行各种cast。

Item 28: Use bounded wildcards to increase API flexibility

为了更好的灵活性,应该在输入参数上用wildcard types。如果这个输入参数代表了一个producer就用entends,如果代表了一个consumer就用super,比如如果一个方法的参数声明是Collection<E> container,如果在方法内部只会从container读取E的instance(也就是E只能作为container方法中的返回类型),也就是container只是作为生产者,那么就应该把声明改成Collection<? extends E> container。(你可以这么记,不管返回什么,反正放到E类型的变量里肯定是安全的)同理,如果在方法内部,比如会把E的instance加到container里去,那么container就是消费者(也就是E会作为输入类型),那么就应该声明成Collection<? super E> container。如果既是生产者又是消费者,那就不能用bounded wildcards了。
注意不要在返回类型上用wildcard types,因为如果你这么做了会迫使client code里面也要受到wildcard types的限制,比如你返回了一个Set<E extends E>,那么得到这个东西的client就只能在它上面进行get操作,而不能add了。正确的用法是,你应该让client根本不用去思考wildcard types,wildcard types只是让你的API能接受更多的类型。
因为type inference的规则很复杂,有时候type inference会推理错,这时候你需要显示地指定一个type parameter,比如:Union.<Number>union(integers, doubles)
item 27中有这么一个方法:

// Returns the maximum value in a list - uses recursive type bound
public static <T extends Comparable<T>> T max(List<T> list) {Iterator<T> i = list.iterator();T result = i.next();while (i.hasNext()) {T t = i.next();if (t.compareTo(result) > 0)result = t;}return result;
}

现在我们把它增强成用wildcard type,变成这样:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)

首先把list的类型改成“生产者”很好理解,因为list只返回一个Iterator<E>,而这个Iterator<E>的next方法的声明是“E next()”,E是返回类型。但为什么这里要把Comparable<T>改成Comparable<? super T>。首先看一下Comparable<T>的定义:

public interface Comparable<T>{int compareTo(T o)
}

看到了吗,T是输入参数,所以Comparable<T>的“实例”是个消费者。 比如如果你这么调用上面的那个方法:

List<Apple> apples = new...;
Apple maxApple = max(apples);

那么这里的Apple类并不一定要实现Comparable<Apple>,也可以只实现Comparable<Fruit>,当然Fruit是Apple的基类。假设Apple只知道怎么和Fruit比,然后当运行到方法体内“t.compareTo(result)”这句的时候,t是一个Apple,result也是一个Apple,但是t只知道怎么和另一个Fruit比,但是result是一个Apple当然也是一个Fruit,所以没问题。其实上面解释地这么麻烦,不如你只要记住“always use Comparable<? super T> in preference to Comparable<T>”就行了(Comparator也一样)。
最后记得还要把方法体中的Iterator<T>改成Iterator<? extends T>

下面是两种等价的方法声明:

// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);

作者说第二种更好,因为更简单,且不需要考虑type parameter。如果一个type parameter在方法声明中只出现了一次,那么就应该把它替换成unbounded wildcard或bounded wildcard。为什么必须是“只出现了一次”?书上没说但我个人理解是因为如果出现了两次:public static <E> void swap(List<E> list1,List<E> list2),那么这里的list1包含的元素和list2包含的元素应该是相同的类型,如果你全都换成“?”,那么list1和list2完全可以包含不同的类型。
但是问题又来了,如果你单纯这么实现:

public static void swap(List<?> list, int i, int j) {list.set(i, list.set(j, list.get(i)));
}

会发现不行,因为不能放东西到List<?>里去,解决办法是依靠“type capture”,写个helper方法:

public static void swap(List<?> list, int i, int j) {swapHelper(list, i, j);
}
// Private helper method for wildcard capture
private static <E> void swapHelper(List<E> list, int i, int j) {list.set(i, list.set(j, list.get(i)));
}

虽然书上的解释有点莫名其妙,但我选择“信了”,感觉记住就行,只是个小技巧。我觉得可以这么理解:因为一个泛型方法被调用的时候肯定要指定泛型参数具体是什么,如果你不指定那就只能靠编译器推断,而在这里编译器就会“capture”到?代表的东西。

Item 29: Consider typesafe heterogeneous containers

这一小节就是告诉你怎么实现这么一个类,保存 你最喜欢的 某个类型的一个实例:

public class Favorites {private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();public <T> void putFavorite(Class<T> type, T instance) {if (type == null) throw new NullPointerException("Type is null");favorites.put(type, type.cast(instance));}public <T> T getFavorite(Class<T> type) {return type.cast(favorites.get(type));}
}

你可以用putFavorite来存一个“T类型的实例”,然后通过getFavorite来获取这个实例。
所以说这里的T和T类型的value是一一对应的,你可以放很多种不同的类型进去。你可以这样使用:

Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);

其实这里用Class<T>来作为“Key”是因为自JDK1.5来Class类就被泛型化了,比如String.class是Class<String>的类型、Integer.class是Class<Integer>的类型,当把这样一个“class literal”(应该就是指“String.class”这种写法)传给某个方法的时候,通常把它叫做type token。而你完全可以自己写一个类,比如Holder<T>来作为“Key”,但是不如Class好,因为Class有一些方法比如cast可以不用让你suppress warning(我个人认为的)。上面的type.cast方法其实就是Java’s cast operator对应的“动态版本”,它只是检查一下它的参数是不是被自己代表的类型,不是的话就抛出ClassCastException:

public class Class<T> {T cast(Object obj);
}

另外,说些没什么关联的事儿:如果你把Class<?>类型转换成,比如Class<? extends Apple>,会得到warning,那么你可以用asSubclass这个方法,比如假设你得到了一个Class<?>类型的变量apple,然后你可以apple.asSubclass(Apple.class),意思就是“把Class<?>变成Class<Apple>”(反正你就这么理解吧),如果这个apple指向的对象并不是一个“Apple对象”的Class object,那就抛出异常。

转载于:https://www.cnblogs.com/raytheweak/p/7190157.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/355158.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

输入框长度校验

输入框长度校验1.例子 <input id"username" onkeyup"checkLength(this,80)"/>80是限制的长度 //输入长度校验,当长度超出限制&#xff0c;截取0到限制长度的字符串 function checkLength(obj, length) {if ($(# $(obj).attr("id")).val…

集群没有leader_ZooKeeper 集群中 Leader 与 Follower 的4种数据同步策略

首先要声明一点&#xff0c;zk集群中&#xff0c;leader服务器有着比较重要的存在&#xff0c;Follower 服务器只是处理非事务性请求&#xff0c;leader服务器主要负责事务性请求&#xff0c;Follower 服务器在遇到事务性请求以后还是会转发给leader服务器处理&#xff0c;所以…

JavaOne 2015:高级模块化开发

JavaOne 2015看到了Project Jigsaw团队关于Java 9中的模块化的一系列讨论 。它们都是非常有趣的&#xff0c;并且充满了宝贵的信息&#xff0c;我敦促每个Java开发人员都注意它们。 除此之外&#xff0c;我想给社区一种搜索和引用它们的方法&#xff0c;因此我在这里总结一下&…

Halcon学习笔记——机器视觉应用工程开发思路及相机标定

机器视觉应用工程开发思路 机器视觉应用工程主要可划分为两大部分&#xff0c;硬件部分和软件部分。 1.硬件部分&#xff0c;硬件的选型至关重要&#xff0c;决定了后续工作是否可以正常开展&#xff0c;其中关键硬件部分包括&#xff1a;光源&#xff0c;相机以及镜头。 2.软件…

输入框不可以输入中文

输入框不可以输入中文1.输入框不可以输入中文&#xff0c;中文直接消失 <input oninput "valuevalue.replace(/[\u4e00-\u9fa5]{0,}$/g,)"/>2.说明 oninput 事件在元素值发生变化是立即触发 匹配使用正则表达式 正则表达式在线测试网站

图片上传js验证图片长宽_js判断图片上传时的文件大小,和宽高尺寸

今天在做图片上传的小功能&#xff0c;使用了一个kissy上传组件。很好奇它是如何在图片上传前&#xff0c;检测到图片的大小和尺寸的&#xff1f;我们来写个小实例实现一下吧如何读取图片的size首先&#xff0c;原生input file控件有个files属性&#xff0c;该属性是一个数组。…

必填校验加变色,点击颜色消失

必填校验加变色&#xff0c;点击颜色消失1.例子 <td changeColorForNull"ah0"><input class"noNull" type"text" id"ah0"name"ah" notNull"案号" onfocus"myFocus(this)" value"" …

一个关于python装饰器参数的问题

看到廖雪峰python教程上&#xff0c;python装饰器一章 https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318435599930270c0381a3b44db991cd6d858064ac0000 在最后的作业题上 再思考一下能否写出一个log的decorator&#xff0c;使它既…

action怎么获得 ajax date参数_ajax()gt;load()事件的新用法!!!

load()函数用于从服务器加载数据&#xff0c;并使用返回的html内容替换当前匹配元素的内容。load()函数默认使用GET方式&#xff0c;如果提供了对象形式的数据&#xff0c;则自动转为POST方式。load()函数只会替换每个匹配元素的内部内容(innerHTML)。你还可以在URL字符串后面追…

apache shiro_Apache Shiro第1部分–基础

apache shiroApache Shiro &#xff08;最初称为JSecurity&#xff09;是Java安全框架。 它被接受并于2010年成为Apache顶级项目。它的目标是功能强大且易于使用。 该项目正在积极开发中&#xff0c;用户和开发人员的邮件列表均处于活动状态。 最重要的区域记录在其网页上。 但…

js编码解码

js编码解码//对输出结果编码 function encodeStr(val) {return encodeURIComponent(encodeURIComponent(trim(val))); }// 对参数解码 function decodeStr(val) {return decodeURIComponent(decodeURIComponent(trim(val))); }后端解码 public static String urlDecode(String …

PHP 中文文件名 空格等 CURL 读取

用rawurlencode 对文件名进行编码转载于:https://www.cnblogs.com/zhaoyun4122/p/7198895.html

无显示器u盘安装centos_最新版 CentOS 8.1.1911 安装教程及常见问题图文详解

基于笔记本(华硕)操作&#xff0c;使用软碟通(UltraISO)制作的系统启动盘(U盘&#xff0c;内存大于8G)操作可自行百度&#xff0c;非常简单(或可留言&#xff0c;择情况出一期U盘制作启动盘教程)。a、登录 centos 官网下载镜像文件官网地址&#xff1a;https://www.centos.org阿…

谨慎使用JUnit的预期异常

有时&#xff0c;当我们收到对jOOQ或其他库的拉取请求时&#xff0c;人们会将单元测试中的代码更改为更“惯用的JUnit”。 特别是&#xff0c;这意味着他们倾向于更改此代码&#xff08;公认的不是那么漂亮的代码&#xff09;&#xff1a; Test public void testValueOfIntInv…

plupload使用例子

plupload使用例子1. 例子 <li><a id"uploadFile">上传</a></li>//文书上传和显示 $(function () {var uploader new plupload.Uploader({runtimes: html5,flash,silverlight,html4,// 指定上传方式browse_button: uploadFile,unique_names…

mysql获取相隔时间段的数据

思路&#xff1a;为时间段内的数据进行编序号&#xff0c;然后计算好相隔时间&#xff0c;拿到id作为搜索条件 SELECT * FROM ( SELECT (i:i1) as i, id, data_send_time FROM jl_pims_machine_time mt,(select i:0) as it where mt.company_id 1001 AND mt.machine_id 1 ord…

bom实现方块移动_从0开始实现一个俄罗斯方块

写在前面得话&#xff1a;这篇文章主要记录了我是怎么一步一步写出俄罗斯方块&#xff0c;整个代码用的函数编程&#xff0c;主要是为了让一些不熟悉es6, 面向对象写法得 新手能更容易看明白&#xff0c;全部得代码中都是一些js的基础知识&#xff0c;很容易理解。要说有点麻烦…

字符串工具类

字符串工具类import javax.servlet.http.HttpServletRequest; import java.util.UUID;public class CommonUtil {/*** param request 请求* return java.lang.String 返回路径* description 获取绝对路径* date 2021/7/14 20:45*/public static String getUrlPath(HttpServletR…

JSonP跨域请求

JSonP跨域请求 我们在通过自己的页面或程序通过ajax请求其它网站或服务时&#xff0c;会存在一个ajax直接请求普通文件存在跨域无权限访问的问题&#xff0c;甭管你是静态页面、动态网页、web服务、WCF&#xff0c;只要是跨域请求&#xff0c;一律不准。不过我们又发现&#xf…

cli3解决 ie11语法错误 vue_基于 Vue + Koa2 + MongoDB + Redis 实现一个完整的登录注册...

项目地址&#xff1a;https://github.com/caochangkui/vue-element-responsive-demo/tree/login-register通过 vue-cli3.0 Element 构建项目前端&#xff0c;Node.js Koa2 MongoDB Redis 实现数据库和接口设计&#xff0c;包括邮箱验证码、用户注册、用户登录、查看删除用户…