Java标准库中一处不太合理的地方

泛型

众所周知,Java的泛型非常拉稀。虽然我认为在表达能力差不多的情况下,别的都可以忍忍。Java的表达能力差在哪里呢:

  1. 不能创建泛型数组
    可以用Object[]加强制转型,与此同时方法还要加上@SuppressWarnings(“unchecked”),不然会有编译期警告。

  2. 不能通过泛型创建对象
    可以把Class<T>作为参数,在内部用反射调构造函数。这种情况非常普遍,但在Java8或以上版本中并不合适。我们可以把参数Class<T>改为Supplier<T>,讲究一点可以写成Supplier<? extends T>,然后调用的时候传入构造器方法引用。(目前还想不出特别好的例子展示这两者的区别,想到再补)

    1
    2
    3
    4
    5
    6
    static <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
    14
    Bootstrap b = new Bootstrap();
    b.group(group)
    .channel(NioSocketChannel.class) // 看这里
    .option(ChannelOption.TCP_NODELAY, true)
    .handler(new ChannelInitializer<SocketChannel>() {
    @Override
    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
    5
    public 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以上版本反射可能会出现微妙的问题,而反射唯一的优点是能调用私有的构造函数。

  3. 不支持协变逆变,或者说以一个难看的方式支持部分协变逆变

  4. 暂时没有第四

可变性(协变/逆变)

Java的可变性和其他语言(比如Scala, C#)里最大的区别在于Java的可变性在使用的时候标明(比如声明一个变量,代入类型参数),而其他语言在定义类/接口/特质的时候标明。

1
Collection<? extends A> collections = ...;
1
2
3
class Collection[+A]{

}

问题

标准库里的PriorityQueue<E>,也就是我们常说的堆,定义是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class PriorityQueue<E> extends AbstractQueue<E>

private final Comparator<? super E> comparator;

public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}

public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}

public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}

public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}

//看这里
private static <T> void siftUpComparable(int k, T x, Object[] es) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = es[parent];
if (key.compareTo((T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = key;
}

这段代码的意思是,可以手动指定Comparator,如果T本身是Comparable的子类型的话也可以不用指定,此时comparator为空,比较的时候会强制转型为Comparable。但是如果这个时候我不小心创建了一个没有实现Comparable的优先级队列,编译期不会有任何问题,运行的时候就炸了。也太不小心了
但是其实是可以在编译期避免这种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Heap<E> {
Comparator<? super E> comp;
E[] array;
@SuppressWarnings("unchecked")
private Heap(Comparator<? super E> comp, int size) {
this.comp = comp;
this.array = (E[]) new Object[size];
}

static <E extends Comparable<? super E>> Heap<E> newHeap() {
return newHeap(Comparable::compareTo);
}

static <E> Heap<E> newHeap(Comparator<? super E> comp) {
return newHeap(comp,16);
}

static <E> Heap<E> newHeap(Comparator<? super E> comp, int size) {
return new Heap<>(comp,size);
}
}

当然这里是静态方法,因为用构造器的话行不通。