教育网站平面设计,农业技术网站建设原则,企业老板培训课程,泗阳网站定制Chapter 5 Generics Item 23: Don’t use raw types in new code 虽然你可以把一个ListString传给一个List类型#xff08;raw type#xff09;的参数#xff0c;但你不应该这么做#xff08;因为允许这样只是为了兼容遗留代码#xff09;#xff0c;举个例子String传给一个List类型raw type的参数但你不应该这么做因为允许这样只是为了兼容遗留代码举个例子 // Uses raw type (List) - fails at runtime!
public static void main(String[] args) {ListString strings new ArrayListString();unsafeAdd(strings, new Integer(42));String s strings.get(0); // Compiler会自动加上cast
}
private static void unsafeAdd(List list, Object o) {list.add(o);
} 以上代码说明如果你把ListString类型转换成List类型就可以往里面加任何东西了编译器不会做检查就很危险。但是你也不能把ListString转换成ListObject但是你可以把ListString转换成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和yListx和Listy没有任何关系。 你无法new一个跟泛型有关的数组比如以下都是错误的new ListE[], new ListString[], new E[]。为什么书上举了个例子我懒得写了反正我个人总结下来就是都怪擦除因为T[]到运行时其实就相当于Object[]你可以往里面放任何东西但按理说你应该只能放是T的东西进去。所以说不要把varargs和泛型混用因为varargs其实就相当于创建了一个数组当成参数。由于这种无法创建数组的限制以及数组的协变性你可以考虑用ListT代替T[]比如可以用new ArrayListE(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 UnaryFunctionT {T apply(T arg);
}
private static UnaryFunctionObject IDENTITY_FUNCTION new UnaryFunctionObject() {public Object apply(Object arg) {return arg;}
};
SuppressWarnings(unchecked)
public static T UnaryFunctionT identityFunction() {return (UnaryFunctionT) IDENTITY_FUNCTION;
} 一开始我在想new UnaryFunctionObject(){...}这句话是什么意思为什么这里是Object而不能是T后来一想匿名类是同时声明和创建的而创建一个泛型类的实例必须指定具体的type parameter所以这里就相当于声明了一个 实现了UnaryFunctionT的类然后创建了一个它的实例泛型参数是Object。然后identityFunction方法是一个泛型的static factory method会把UnaryFunctionObject类型转换成 “调用这个泛型方法的时候被推断出来的类型 的类型的UnaryFunction”。先看一下用法 public static void main(String[] args) {String[] strings { jute, hemp, nylon };UnaryFunctionString sameString identityFunction();for (String s : strings)System.out.println(sameString.apply(s));Number[] numbers { 1, 2.0, 3L };UnaryFunctionNumber sameNumber identityFunction();for (Number n : numbers)System.out.println(sameNumber.apply(n));
} 第一次调用identityFunction()的时候被推断出来的类型是String第二次是Number。然后以第一次为例在调用sameString.apply(s)的时候相当于编译器就会调用UnaryFunctionString接口中的public String apply(String arg)方法所以编译器此时会检查s这玩意儿是不是Sting发现没问题OK返回的结果也会被编译器cast成String而这里你的实际方法return arg;啥都没做所cast肯定不会报错。这个例子的意思关键在于static factory method里面的那个cast (UnaryFunctionT) IDENTITY_FUNCTION正是因为这个cast所以client代码才能让 任何类型的UnaryFunction 都共享同一个实例IDENTITY_FUNCTION 。 但是我在普通的client代码里面试了一下无法将ListObject cast成 ListString看来这个技巧也只能在泛型方法里面用了。C#的类似实现虽然可能不是单例 static void Main(string[] args)
{String[] strings { jute, hemp, nylon };var sameString IdentityFunctionString();foreach (var s in strings){Console.WriteLine(sameString(s));}int[] ints { 1, 2, 3 };var sameInt IdentityFunctionint();foreach (var s in ints){Console.WriteLine(sameInt(s));}
}
public static FuncT, T IdentityFunctionT()
{return arg arg;
} 听起来很玄乎的一个概念recursive type bound比如T extends ComparableT 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比如如果一个方法的参数声明是CollectionE 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的限制比如你返回了一个SetE extends E那么得到这个东西的client就只能在它上面进行get操作而不能add了。正确的用法是你应该让client根本不用去思考wildcard typeswildcard types只是让你的API能接受更多的类型。 因为type inference的规则很复杂有时候type inference会推理错这时候你需要显示地指定一个type parameter比如Union.Numberunion(integers, doubles)。 item 27中有这么一个方法 // Returns the maximum value in a list - uses recursive type bound
public static T extends ComparableT T max(ListT list) {IteratorT 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只返回一个IteratorE而这个IteratorE的next方法的声明是“E next()”E是返回类型。但为什么这里要把ComparableT改成Comparable? super T。首先看一下ComparableT的定义 public interface ComparableT{int compareTo(T o)
} 看到了吗T是输入参数所以ComparableT的“实例”是个消费者。 比如如果你这么调用上面的那个方法 ListApple apples new...;
Apple maxApple max(apples); 那么这里的Apple类并不一定要实现ComparableApple也可以只实现ComparableFruit当然Fruit是Apple的基类。假设Apple只知道怎么和Fruit比然后当运行到方法体内“t.compareTo(result)”这句的时候t是一个Appleresult也是一个Apple但是t只知道怎么和另一个Fruit比但是result是一个Apple当然也是一个Fruit所以没问题。其实上面解释地这么麻烦不如你只要记住“always use Comparable? super T in preference to ComparableT”就行了Comparator也一样。 最后记得还要把方法体中的IteratorT改成Iterator? extends T。 下面是两种等价的方法声明 // Two possible declarations for the swap method
public static E void swap(ListE 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(ListE list1ListE 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(ListE 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 MapClass?, Object favorites new HashMapClass?, Object();public T void putFavorite(ClassT type, T instance) {if (type null) throw new NullPointerException(Type is null);favorites.put(type, type.cast(instance));}public T T getFavorite(ClassT 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); 其实这里用ClassT来作为“Key”是因为自JDK1.5来Class类就被泛型化了比如String.class是ClassString的类型、Integer.class是ClassInteger的类型当把这样一个“class literal”应该就是指“String.class”这种写法传给某个方法的时候通常把它叫做type token。而你完全可以自己写一个类比如HolderT来作为“Key”但是不如Class好因为Class有一些方法比如cast可以不用让你suppress warning我个人认为的。上面的type.cast方法其实就是Java’s cast operator对应的“动态版本”它只是检查一下它的参数是不是被自己代表的类型不是的话就抛出ClassCastException public class ClassT {T cast(Object obj);
} 另外说些没什么关联的事儿如果你把Class?类型转换成比如Class? extends Apple,会得到warning那么你可以用asSubclass这个方法比如假设你得到了一个Class?类型的变量apple然后你可以apple.asSubclass(Apple.class)意思就是“把Class?变成ClassApple”反正你就这么理解吧如果这个apple指向的对象并不是一个“Apple对象”的Class object那就抛出异常。 转载于:https://www.cnblogs.com/raytheweak/p/7190157.html