java多线程做一件事
这篇文章最初是在jooq.org上发布的,这是一个特别系列文章的一部分,该系列文章从jOOQ的角度着眼于Java,SQL和软件开发的所有方面。
那么,您从一开始就一直在使用Java? 还记得那些被称为“ Oak”的日子,OO仍然是热门话题,C ++人士认为Java没有机会,Applet还是一件事吗?
我敢打赌,您至少不了解以下一半内容。 让我们从本周开始,对Java的内部运作产生一些惊喜。
1.没有被检查的异常
那就对了! JVM不知道任何这样的事情,只有Java语言知道。
今天,每个人都同意检查异常是一个错误。 正如Bruce Eckel在布拉格GeeCON闭幕致辞中所说的那样,在Java参与使用受检查的异常之后,没有其他语言可以使用,甚至Java 8也不再将它们包含在新的Streams API中( 这可能有些痛苦,当您的lambda使用IO或JDBC时 )。
您是否想要证明JVM不知道这样的事情? 尝试以下代码:
public class Test {
// No throws clause here
public static void main(String[] args) {
doThrow(new SQLException());
}
static void doThrow(Exception e) {
Test.<RuntimeException> doThrow0(e);
}
@SuppressWarnings("unchecked")
static <E extends Exception>
void doThrow0(Exception e) throws E {
throw (E) e;
}
}
这不仅会编译,而且实际上还会引发SQLException,您甚至不需要Lombok的@SneakyThrows。
2.您可以使方法重载仅在返回类型上有所不同
那不会编译,对吗?
class Test {
Object x() { return "abc"; }
String x() { return "123"; }
}
对。 Java语言不允许在同一个类中将两个方法“替代等效” ,无论它们的throws
子句或return
类型可能如何不同。
但是请稍等。 Class.getMethod(String, Class...)
出Class.getMethod(String, Class...)
的Javadoc。 内容为:
请注意,一个类中可能有多个匹配方法,因为Java语言禁止一个类声明具有相同签名但返回类型不同的多个方法,而Java虚拟机却没有。 虚拟机中这种增加的灵活性可用于实现各种语言功能。 例如,协变收益可以通过桥接方法实现; bridge方法和被重写的方法将具有相同的签名,但返回类型不同。
哇,是的,这很有意义。 实际上,这几乎就是您编写以下内容时发生的情况:
abstract class Parent<T> {
abstract T x();
}
class Child extends Parent<String> {
@Override
String x() { return "abc"; }
}
在Child
检查生成的字节码:
// Method descriptor #15 ()Ljava/lang/String;
// Stack: 1, Locals: 1
java.lang.String x();
0 ldc <String "abc"> [16]
2 areturn
Line numbers:
[pc: 0, line: 7]
Local variable table:
[pc: 0, pc: 3] local: this index: 0 type: Child
// Method descriptor #18 ()Ljava/lang/Object;
// Stack: 1, Locals: 1
bridge synthetic java.lang.Object x();
0 aload_0 [this]
1 invokevirtual Child.x() : java.lang.String [19]
4 areturn
Line numbers:
[pc: 0, line: 1]
因此, T
实际上只是字节码中的Object
。 很好理解。
合成桥方法实际上是由编译器生成的,因为在某些调用位置可能期望Parent.x()
签名的返回类型为Object
。 如果没有这种桥接方法,则以二进制兼容的方式添加泛型是不可能的。 因此,更改JVM以使其具有此功能的痛苦就较小(这也使协变量重载成为副作用……)聪明吧?
您是否熟悉语言细节和内部知识? 然后在这里找到一些更有趣的细节 。
3.所有这些都是二维数组!
class Test {
int[][] a() { return new int[0][]; }
int[] b() [] { return new int[0][]; }
int c() [][] { return new int[0][]; }
}
对,是真的。 即使您的心理分析器可能无法立即理解上述方法的返回类型,它们也是相同的! 类似于以下代码:
class Test {
int[][] a = {{}};
int[] b[] = {{}};
int c[][] = {{}};
}
你觉得这很疯狂吗? 想象一下在上面使用JSR-308 / Java 8类型注释 。 句法可能性的数量激增!
@Target(ElementType.TYPE_USE)
@interface Crazy {}
class Test {
@Crazy int[][] a1 = {{}};
int @Crazy [][] a2 = {{}};
int[] @Crazy [] a3 = {{}};
@Crazy int[] b1[] = {{}};
int @Crazy [] b2[] = {{}};
int[] b3 @Crazy [] = {{}};
@Crazy int c1[][] = {{}};
int c2 @Crazy [][] = {{}};
int c3[] @Crazy [] = {{}};
}
输入注释。 仅凭其力量超越神秘感的设备
换句话说:
当我这样做时,我的四周假期前的最后一次提交
我将为您找到上述任何一个用例的实际练习。
4.您没有条件表达式
因此,您认为使用条件表达式时就知道这一切吗? 我告诉你,你没有。 你们大多数人会认为以下两个片段是等效的:
Object o1 = true ? new Integer(1) : new Double(2.0);
…一样吗?
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
不。 让我们进行快速测试。
System.out.println(o1);
System.out.println(o2);
该程序将打印:
1.0
1
是的 如果“需要”,则条件运算符将实现数字类型提升,并在“需要”上使用非常非常强烈的引号集。 因为,您希望该程序抛出NullPointerException吗?
Integer i = new Integer(1);
if (i.equals(1))
i = null;
Double d = new Double(2.0);
Object o = true ? i : d; // NullPointerException!
System.out.println(o);
5.您也没有得到复合赋值运算符
够古怪吗? 让我们考虑以下两段代码:
i += j;
i = i + j;
凭直觉,它们应该等效,对吗? 但猜猜怎么了。 他们不是! JLS指定:
形式为E1 op = E2的复合赋值表达式等效于E1 =(T)(((E1)op(E2))),其中T是E1的类型,只是E1仅被评估一次。
这是如此美丽,我想引用Peter Lawrey 对这个Stack Overflow问题的回答 :
这种转换的一个很好的例子是使用* =或/ =
byte b = 10;
b *= 5.7;
System.out.println(b); // prints 57
要么
>byte b = 100;
b /= 2.5;
System.out.println(b); // prints 40
要么
char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'
要么
char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
现在,这有多么有用? 我将在我的应用程序中强制转换/乘法字符。 因为,你知道...
6.随机整数
现在,这更像是一个难题。 尚未阅读解决方案。 看看您是否可以自己找到这个。 当我运行以下程序时:
for (int i = 0; i < 10; i++) {
System.out.println((Integer) i);
}
…然后“有时”,我得到以下输出:
92
221
45
48
236
183
39
193
33
84
这怎么可能呢??
。
。
。
。
扰流板...未来的解决方案...
。
。
。
。
好的, 解决方案就在这里 ,它与通过反射覆盖JDK的Integer
缓存有关,然后使用自动装箱和自动拆箱。 不要在家做! 换句话说,让我们再这样考虑一下:
当我这样做时,我的四周假期前的最后一次提交。
7.转到
这是我的最爱之一。 Java有GOTO! 输入...
int goto = 1;</pre>
This will result in:
<pre>Test.java:44: error: <identifier> expected
int goto = 1;
^
这是因为goto
是未使用的关键字 ,以防万一……
但这不是令人兴奋的部分。 令人兴奋的部分是,您实际上可以使用break
, continue
和带标签的块来实现goto:
向前跳
label: {
// do stuff
if (check) break label;
// do more stuff
}
在字节码中:
2 iload_1 [check]
3 ifeq 6 // Jumping forward
6 ..
向后跳
label: do {
// do stuff
if (check) continue label;
// do more stuff
break label;
} while(true);
</pre>
In bytecode:
<pre> 2 iload_1 [check]
3 ifeq 9
6 goto 2 // Jumping backward
9 ..
8. Java具有类型别名
在其他语言( 例如Ceylon )中,我们可以非常轻松地定义类型别名:
interface People => Set<Person>;
这样构造的People
类型可以与Set<Person>
互换使用:
People? p1 = null;
Set<Person>? p2 = p1;
People? p3 = p2;
在Java中,我们不能在顶级定义类型别名。 但是我们可以在类或方法的范围内这样做。 让我们考虑一下,我们对Integer
, Long
等的命名不满意,我们希望使用更短的名称: I
和L
简单:
class Test<I extends Integer> {
<L extends Long> void x(I i, L l) {
System.out.println(
i.intValue() + ", " +
l.longValue()
);
}
}
在上述程序中,对于Test
类的范围, Integer
被“别名”到I
,而对于x()
方法的范围, Long
被“别名”到L
然后我们可以像上面这样调用上面的方法:
new Test().x(1, 2L);
当然,这种技术不应被认真对待。 在这种情况下, Integer
和Long
都是最终类型,这意味着类型I
和L
实际上是别名(几乎,赋值兼容只是一种方式)。 如果我们使用了非最终类型(例如Object
),那么我们真的会使用普通的泛型。
这些愚蠢的把戏足够了。 现在换个真正了不起的东西!
9.一些类型关系是不确定的!
好吧,现在这将变得非常时髦,因此可以喝杯咖啡并集中精力。 请考虑以下两种类型:
// A helper type. You could also just use List
interface Type<T> {}
class C implements Type<Type<? super C>> {}
class D<P> implements Type<Type<? super D<D<P>>>> {}
现在,类型C
和D
甚至意味着什么?
它们有些递归,以类似于java.lang.Enum
递归的方式(但略有不同)。 考虑:
public abstract class Enum<E extends Enum<E>> { ... }
使用以上规范,实际的enum
实现仅仅是语法糖:
// This
enum MyEnum {}
// Is really just sugar for this
class MyEnum extends Enum<MyEnum> { ... }
考虑到这一点,让我们回到两种类型。 以下内容可以编译吗?
class Test {
Type<? super C> c = new C();
Type<? super D<Byte>> d = new D<Byte>();
}
艰苦的问题, 罗斯·泰特 ( Ross Tate)有了答案。 这个问题实际上是无法确定的:
C是Type <的子类型吗? 超级C>?
Step 0) C <?: Type<? super C>
Step 1) Type<Type<? super C>> <?: Type (inheritance)
Step 2) C (checking wildcard ? super C)
Step . . . (cycle forever)
然后:
D是Type <的子类型吗? 超级D <Byte >>?
Step 0) D<Byte> <?: Type<? super C<Byte>>
Step 1) Type<Type<? super D<D<Byte>>>> <?: Type<? super D<Byte>>
Step 2) D<Byte> <?: Type<? super D<D<Byte>>>
Step 3) List<List<? super C<C>>> <?: List<? super C<C>>
Step 4) D<D<Byte>> <?: Type<? super D<D<Byte>>>
Step . . . (expand forever)
尝试在Eclipse中编译以上代码,它将崩溃! ( 不用担心。我已经提交了一个错误 )
让它沉入……
Java中的某些类型关系是不确定的 !
如果您对有关此奇特的Java怪癖的更多详细信息感兴趣,请阅读Ross Tate的论文“在Java的类型系统中驯服通配符” (与Alan Leung和Sorin Lerner合着),或者阅读我们自己关于将子类型多态与泛型多态相关联的想法。
10.类型交点
Java具有一个非常独特的功能,称为类型交集。 您可以声明一个(通用)类型,它实际上是两种类型的交集。 例如:
class Test<T extends Serializable & Cloneable> {
}
泛型类型参数T
,你绑定的类的实例Test
必须实现 Serializable
和Cloneable
。 例如, String
不是可能的绑定,但Date
是:
// Doesn't compile
Test<String> s = null;
// Compiles
Test<Date> d = null;
Java 8中已重用了此功能,您现在可以在其中将类型转换为临时类型的交集。 这有什么用? 几乎没有,但是如果您想将lambda表达式强制为这种类型,则别无选择。 假设您的方法受到这种疯狂的类型约束:
<T extends Runnable & Serializable> void execute(T t) {}
您想要一个可Serializable
的Runnable
,以防您想在其他地方执行它并通过电线发送它。 Lambda和序列化有点古怪。
如果lambda表达式的目标类型和捕获的参数可序列化,则可以对其进行序列化。
但是,即使这是真的,他们也不会自动实现Serializable
标记接口。 要强制他们使用这种类型,必须强制转换。 但是当您只转换为Serializable
...
execute((Serializable) (() -> {}));
…然后,lambda将不再可运行。
嗯...
所以…
将其强制转换为两种类型:
execute((Runnable & Serializable) (() -> {}));
结论
我通常只说这是关于SQL的问题,但现在该是一篇包含以下内容的文章了:
Java是仅凭其力量无法超越的设备
翻译自: https://jaxenter.com/10-things-didnt-know-java-112603.html
java多线程做一件事