join的锁
join是一个线程的方法,一个线程调用join方法,那么它需要先执行完毕。
一个需要重点记住的就是join会放弃锁,这个是重点!!
1 | package com.example.demo; |
不同包中的权限问题
1 | package library; |
上面代码中,PackagePrivate不是公共的,所以不可以再其他包里面直接使用他的成员方法。
多层次的反射
1 | package com.example.demo; |
线程中断的问题
1 | package com.example.demo1; |
输出1
2Interrupted: false
Interrupted: true
类的惰性初始化
(这个谜团我暂时无法理解)
检查一个类是否被初始化一般有几种状态
- 这个类尚未被初始化。
- 这个类正在被当前线程初始化:这是对初始化的递归请求。
- 这个类正在被其他线程而不是当前线程初始化。
- 这个类已经被初始化。
当前线程,也就是那个后台线
程,会等待 Class 对象直到初始化完成。遗憾的是,那个正在进行初始化工作的
线程,也就是主线程,正在等待着后台线程运行结束。因为这 2 个线程现在正相
互等待着,该程序就死锁了(deadlock)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Lazy {
private static boolean initialized = false;
static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});
t.start();
try{
t.join();
}catch (InterruptedException e){
throw new AssertionError(e);
}
}
public static void main(String[] args){
System.out.println(initialized);
}
}
泛型的擦除
1 | package com.example.demo1; |
序列化中的规则问题
1 | package com.example.demo1; |
1 | 为了更具体一些,让我们看看程序中在反序列化 Sub 实例的时候发生了什么。首 |
浮点数表示不了一些小数
1 | public class Change{ |
1.10这个数不可以用浮点数进行表示,造成了误差
使用下面的方法修改1
2
3
4
5
6import java.math.BigDecimal;
public class Change1{
public static void main(String args[]){
System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
}
}
不经意的溢出操作
1 | final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000; |
虽然使用long声明计算结果,可是计算过程使用的是int,所以int(结果溢出)->long,这一步操作已经太迟了。
使用下面方法修正:
1 | final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000; |
java中long转int原理
long转为int需要强转都很熟悉,那么介绍一下强转的具体原理。
首先在计算机中,计算加法减法是用补码计算的。
正数的补码为其二进制表示。负数的补码为其模的二进制表示取反加一。
加入为八位二进制加法,如:+3+(-5)
+3的补码为00000011
-5的模为+5,二进制表示为00000101,取反为11111010,加一后为11111011,所以-5的补码为11111011
加法计算公式为:两个数的补码相加,如果是正,则直接输出,如果是负,则除符号位以外取反加一
+3和-5的补码之和为11111110,为负数,去掉符号位第一位 1 ,剩下1111110,取反加一后为10
10为十进制中的2,所以带上符号位为-2。
众所周知,java中long为8字节64位,int为4字节32位。
java如果long强转为int,做的事为:直接取低位32位作为值,但是看做补码。
代码如下:1
2
3
4
long long= 37444124124l;
System.out.println((int)long);
输出值为-1210581540
首先37444124124 转为二进制为
100010110111110101111111110111011100
后32位为:10110111110101111111110111011100
这个后32位直接当做计算结果输出:符号位为1,则是负数,负数取后31位取反加一
1001000001010000000001000100100
为:1210581540
加上符号位为-1210581540
int 转 long
1 | System.out.println(Long.toHexString(0x100000000L + 0xcafebabe)); |
cafebabe
ffffffffcafebabe
100000000
不加L的十六进制高位默认f
三元表达式中悄悄的转型
1 | public class DosEquis{ |
i是int类型,char可以转换成int类型
System.out.println(false ? i(int类型) : x(byte、short、char);
会发生变量提升。所以尽量第二操作和第三操作有相同的数据类型
自动转型丢失精度的问题
1 | short x = 0; |
请注意括号的使用,优先级问题可能导致一些莫名其妙的问题
1 | final String pig = "length: 10"; |
byte转String最好提供一个字符集
1 | String str = new String(bytes, "ISO-8859-1"); |
使用正则表达式的小问题
1 | .replaceAll("\\.","/") + ".class"); |
要想只匹配句点符号,在正则表达式中的句点必须在其前面添加一个反斜杠(\)
进行转义。因为反斜杠字符在字面含义的字符串中具有特殊的含义——它标识转
义字符序列的开始——因此反斜杠自身必须用另一个反斜杠来转义,这样就可以
产生一个转义字符序列,它可以在字面含义的字符串中生成一个反斜杠。
类名的匹配
1 | System.out.println(Test.class.getName(). |
漂亮的代码
1 | 使用这个代码替代switch case |
类型不同的数字的比较问题
1 | public class BigDelight { |
说明(类型提升的锅):
拿一个 byte 与一个 int 进行的比较是一个混合类型比较(mixed-type
comparison)。如果你把 byte 数值想象为苹果,把 int 数值想象成为桔子,那
么该程序就是在拿苹果与桔子比较。请考虑表达式((byte)0x90 == 0x90),尽管
外表看起来是成立的,但是它却等于 false。
为了比较 byte 数值(byte)0x90 和 int 数值 0x90,Java 通过拓宽原始类型转换
将 byte 提升为一个 int[JLS 5.1.2],然后比较这两个 int 数值。因为 byte 是
一个有符号类型,所以这个转换执行的是符号扩展,将负的 byte 数值提升为了
在数字上相等的 int 数值。在本例中,该转换将(byte)0x90 提升为 int 数值-112,
它不等于 int 数值 0x90,即+144。
++的小坑
1 | j = j++; |
循环中最大值的回归
1 | public class InTheLoop { |
不会输出count,回归到min的值,所以使用1
2
3
4int i = START;
do {
count++;
}while (i++ != END)
这些不等于来进行判断比较好
循环中一个很皮的东西(无限循环)
1 | String i = "helll java" |
优柔寡断
1 | public class Indecisive { |
最后return false
静态代码块的妙用
1 | public class UnwelcomeGuest { |
使用静态代码块解决final的一次性赋值问题
实例化中堆栈溢出
1 | public class Reluctant { |
静态方法的调用问题
1 | class Dog { |
当一个程序调用了一个静态方法时,要被调用的方法都是
在编译时刻被选定的,而这种选定是基于修饰符的编译期类型而做出的,修饰符
的编译期类型就是我们给出的方法调用表达式中圆点左边部分的名字。在本案
中,两个方法调用的修饰符分别是变量 woofer 和 nipper,它们都被声明为 Dog
类型。因为它们具有相同的编译期类型,所以编译器使得它们调用的是相同的方
法:Dog.bark。这也就解释了为什么程序打印出 woof woof。尽管 nipper 的运
行期类型是 Basenji,但是编译器只会考虑其编译器类型。
初始化中的循环问题
1 | public class Elvis { |
- Elvis 类的初始化是由虚拟机对其 main 方法的调用而触发的。首
先,其静态域被设置为缺省值[JLS 4.12.5],其中 INSTANCE 域被设置为 null,
CURRENT_YEAR 被设置为 0。 - 接下来,静态域初始器按照其出现的顺序执行。第一
个静态域是 INSTANCE,它的值是通过调用 Elvis()构造器而计算出来的。
这个构造器会用一个涉及静态域 CURRENT_YEAR 的表达式来初始化 beltSize。通
常,读取一个静态域是会引起一个类被初始化的事件之一,但是我们已经在初始
化 Elvis 类了。递归的初始化尝试会直接被忽略掉[JLS 12.4.2, 第 3 步]。因此,
CURRENT_YEAR 的值仍旧是其缺省值 0。这就是为什么 Elvis 的腰带尺寸变成了
-1930 的原因。 - 最后,从构造器返回以完成 Elvis 类的初始化,假设我们是在 2006 年运行该程
序,那么我们就将静态域 CURRENT_YEAR 初始化成了 2006。遗憾的是,这个域现
在所具有的正确值对于向 Elvis.INSTANCE.beltSize 的计算施加影响来说已经
太晚了,beltSize 的值已经是-1930 了。这正是后续所有对
Elvis.INSTANCE.beltSize()的调用将返回的值。
该程序表明,在 final 类型的静态域被初始化之前,存在着读取它的值的可能,
而此时该静态域包含的还只是其所属类型的缺省值。这是与直觉相违背的,因为
我们通常会将 final 类型的域看作是常量。final 类型的域只有在其初始化表达
式是常量表达式时才是常量[JLS 15.28]。
子类覆盖父类方法在初始化中的问题
1 | class Point { |
父类构造器->子类的makeName(此时color未完成初始化)
父类调用子类的makeName,是因为子类重新了它这个方法。
累计的玩笑
1 | class Cache { |
粗心的错误
1 | public class ColorPoint { |
这里的ColorPoint不是构造方法,不会运行。
关于变量覆盖的问题
1 | class Base { |
变量的隐藏,那么提供比他小的变量,至少一样多。这里继承过来的变量是私有的,所以编译是失败的
不同类中覆盖失败问题
1 |
|
程序中的这两个 twoMessage
方法是无关的,它们仅仅是具有相同的名字而已。当程序在 hack 包内调用
printMessage 方法时,运行的
final变量的覆写(和方法的不同之处)
1 | class Jeopardy { |
可以证明,final 修饰符对方法和域而言,意味着某些完全不同的事情。对于方
法,final 意味着该方法不能被覆写(对实例方法而言)或者隐藏(对静态方法
而言)[JLS 8.4.3.3]。对于域,final 意味着该域不能被赋值超过一次[JLS
8.3.1.2]。关键字相同,但是其行为却完全不相关。
名字重用的术语表
覆写 (override )
一个实例方法可以覆写( override )在其超类中可访问到的具有相同签名的所有实例方法 [JLS
8.4.8.1] ,从而使能了动态分派( dynamic dispatch );换句话说, VM 将基于实例的运行期类
型来选择要调用的覆写方法 [JLS 15.12.4.4] 。覆写是面向对象编程技术的基础,并且是唯一
没有被普遍劝阻的名字重用形式:
class Base {
public void f() { }
}
class Derived extends Base {
public void f() { } // overrides Base.f()
}
隐藏 (hide )
一个域、静态方法或成员类型可以分别隐藏( hide )在其超类中可访问到的具有相同名字(对
方法而言就是相同的方法签名)的所有域、静态方法或成员类型。隐藏一个成员将阻止其被
继承 [JLS 8.3, 8.4.8.2, 8.5] :
class Base {
public static void f() { }
}
class Derived extends Base {
private static void f() { } // hides Base.f()
}
重载 (overload )
在某个类中的方法可以重载( overload )另一个方法,只要它们具有相同的名字和不同的签
名。由调用所指定的重载方法是在编译期选定的 [JLS 8.4.9, 15.12.2] :
class CircuitBreaker {
public void f(int i) { } // int overloading
public void f(String s) { } // String overloading
}
遮蔽 (shadow )
一个变量、方法或类型可以分别遮蔽( shadow )在一个闭合的文本范围内的具有相同名字
的所有变量、方法或类型。如果一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;
根据实体的不同,有时你根本就无法引用到它 [JLS 6.3.1] :
class WhoKnows {
static String sentence = "I don't know.";
public static woid main(String[ ] args) {
String sentence = “I know!”; // shadows static field
System.out.println(sentence); // prints local variable
}
}
伪装的多线程程序
1 | public class PingPong{ |
捣乱锁的妖怪
1 | import java.util.*; |
主要需要注意join是会放弃锁的
修改程序的方法如下1
2
3
4
5
6
7
8
9
10
11
12
13
14private final Object lock = new Object();
// It's quitting time, wait for worker - Called by good boss
void quit() throws InterruptedException{
synchronized (lock){
quittingTime = true;
join();
}
}
// Rescind quitting time - Called by evil boss
void keepWorking(){
synchronized(lock){
quittingTime = false;
}
}
包外访问需要注意类需要是public和成员的限定符为public才可以
1 | package com.example.demo; |
1 | package com.example.demo1; |
如果PackagePrivate不是public 的类那边不可以进行包外访问了
方法重名&遮蔽引起的问题
1 |
|
改java类会编译错误,因为Thread里面sleep方法找不到,所以需要避免重名。
单例模式的问题
1 | public class Dog extends Exception { |
而 Exception 实现了 java.io.Serializable。这就意味着 Dog 是可序列化的
改进如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;
public class CopyDog{ // Not to be confused with copycat
public static void main(String[] args){
Dog newDog = (Dog) deepCopy(Dog.INSTANCE);
System.out.println(newDog == Dog.INSTANCE);
System.out.println(newDog);
}
// This method is very slow and generally a bad idea!
static public Object deepCopy(Object obj){
try{
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(obj);
ByteArrayInputStream bin =
new ByteArrayInputStream(bos.toByteArray());
return new ObjectInputStream(bin).readObject();
} catch(Exception e) {
throw new IllegalArgumentException(e);
}
}
}
修正的方法:
private Object readResolve(){
// Accept no substitues!
return INSTANCE;
}