Java标准库中一处不太合理的地方
泛型
众所周知,Java的泛型非常拉稀。虽然我认为在表达能力差不多的情况下,别的都可以忍忍。Java的表达能力差在哪里呢:
不能创建泛型数组
可以用Object[]加强制转型,与此同时方法还要加上@SuppressWarnings(“unchecked”),不然会有编译期警告。不能通过泛型创建对象
可以把Class<T>作为参数,在内部用反射调构造函数。这种情况非常普遍,但在Java8或以上版本中并不合适。我们可以把参数Class<T>改为Supplier<T>,讲究一点可以写成Supplier<? extends T>,然后调用的时候传入构造器方法引用。(目前还想不出特别好的例子展示这两者的区别,想到再补)1
2
3
4
5
6static <E> E construct(Supplier<? extends E> sup){
return sup.get();
}
public static void main(String[] args) {
String s = construct(String::new);
}举一个netty的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class) // 看这里
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
}
p.addLast(new EchoClientHandler());
}
});而channel函数是这么定义的:
1
2
3
4
5public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}这里ChannelFactory等价于Supplier<T extends Channel>,
只需要用channelFactory(NioSocketChannel::new)就可以了。::new还比.class少一个字符,在java9以上版本反射可能会出现微妙的问题,而反射唯一的优点是能调用私有的构造函数。不支持协变逆变,或者说以一个难看的方式支持部分协变逆变
暂时没有第四
可变性(协变/逆变)
Java的可变性和其他语言(比如Scala, C#)里最大的区别在于Java的可变性在使用的时候标明(比如声明一个变量,代入类型参数),而其他语言在定义类/接口/特质的时候标明。
1 | Collection<? extends A> collections = ...; |
1 | class Collection[+A]{ |
问题
标准库里的PriorityQueue<E>,也就是我们常说的堆,定义是这样的:
1 | public class PriorityQueue<E> extends AbstractQueue<E> |
这段代码的意思是,可以手动指定Comparator,如果T本身是Comparable的子类型的话也可以不用指定,此时comparator为空,比较的时候会强制转型为Comparable。但是如果这个时候我不小心创建了一个没有实现Comparable的优先级队列,编译期不会有任何问题,运行的时候就炸了。也太不小心了
但是其实是可以在编译期避免这种情况:
1 | public class Heap<E> { |
当然这里是静态方法,因为用构造器的话行不通。