Java程序员常见的疑惑和陷阱-总结

902 阅读9分钟

最近看了一篇关于Java的一些常见疑惑和陷阱的问题,感觉收获良多,写的非常的好。我这里也进行了一个自我的总结,看完非常有用!看完之后可以去实践一下,自己跑一跑,相信你会发现有些坑是之前真的没有发现。希望有收获。~


目录

Java基础的常见陷阱 

 集合框架的系统梳理 

揭开神秘的锁机制 

窥视Java并发包(JUC) 

一些学习体会

Java基础的常见陷阱 

不一样的数字的宽类型和窄类型 

令人崩溃的字符串常量池和subString() 

不正常的finally和null 

equals()也不容易 

Java常见陷阱(一)

发生在我们身边的事 

StringBuffer clienetCookieList = new StringBuffer("<h1>Client</h1> : </br>");
Set<Entry<String, String>> cookieSet = TaobaoSession.getCookiesPool().entrySet();
while (cookieSet.iterator().hasNext()) {
Entry entry = cookieSet.iterator().next();
clienetCookieList.append(entry.getKey() + " " + entry.getValue() + "</br>");
} 

for(Entry<String,String> e:TaobaoSession.getCookiesPool().entrySet())
clienetCookieList.append(e.getKey() + " " + e.getValue() + "</br>");

解答:

常规问题采用常规的方式处理 

不确定问题可以增加一些特殊/特定的条件(比如while循环中增加一些强 制退出机制)

常见陷阱(二)

诡异的数字

System.out.println(12345+5432l);

66666?

List l = new ArrayList<String>();
l.add("Foo");
System.out.println(1);
0x100000000L + 0xcafebabe = ?
解答:
Long num = 0x111111111L; 变量名称永远不要用l
数字结尾永远不要用l,表示long使用L

for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
if (b == 0x99)
System.out.print("Found it!");
}    

解答:

byte 与int 进行比较时,会进行有符号扩展 

窄类型与宽类型比较时特别需要注意符号扩展

b == (byte)0x99

 (b&0xFF)==0x99

不经意的规则

X = x +1; x+=1;

byte x = 10;
x += 1234; // x?
x = x + 1234; // x?
Object taobao= “taobao”;
String ali = “ali”;
taobao = taobao+ali; // taobao?
taobao += ali; // taobao?

解答:

复合赋值表达式自动地将它们所执行的计算的结果转型为其左侧变量的类型 

赋值操作数据越界导致编译错误。 

复合赋值只能是基本类型,基本类型的包装类型以及String,String的复合操作 

左侧必须是String类型。 

使用原则是:宽类型向低类型赋值一定不要用复合操作。

 重新认识字符串

“A”+”B” ‘A’+’B’
new StringBuffer().append(‘A’).append(‘B’)
new StringBuffer(‘A’).append(‘B’)
new StringBuffer(“A”).append(“B”)
String s = "who";
System.out.println("who" == s);
System.out.println("who" == "who");
System.out.println("who" == new String("who"));
System.out.println("who" == new String("who").intern());

解答:

不要指望== 

不要指望常量池,尽量不用intern() 

不要往常量池扔过多东西,会导致持久代OOM

subString() 陷阱
public String substring(int beginIndex, int endIndex) {
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}

解答:

如果只是普通的比较,匹配,传参,临时变量等,可以直接使用subString() 

如果需要成为常驻内存对象,需要包装下new String(s.subString(from,to))

关于类的潜规则

public class StrungOut {
public static void main(String[] args) {
String s = new String("Hello world");
System.out.println(s);
}
}
class String {
private final java.lang.String s;
public String(java.lang.String s) {
this.s = s;
}
public java.lang.String toString() {
return s;
}
}

解答:

规则1:永远不要命名为java.lang 下的类名 

规则2:包名不能命名为 java/javax/java.lang/java.util/javax.s ql等开头 

规则3:永远不能类命名完全一 致而实现不一致 

规则4:尽可能的避免相同名称, 尽量不使用默认包

 关于类的潜规则

public class ILoveTaobao{
public static void main(String[] args) {
System.out.print("I love ");
http://www.taobao.com
System.out.println("taobao!");
}
}

解答:

非常正常的输出了: I love taobao! 

永远不要用Label 特性(多亏没有goto )

类的初始化

public class ClassInitDemo {
final String result;
public ClassInitDemo(String x, String y) {
this.result = add(x, y);
}
public String add(String x, String y) {
return x + y;
}
static class SubClass extends ClassInitDemo {
String z;
public SubClass(String x, String y, String z) {
super(x, y);
this.z = z;
}
public String add(String x, String y) {
return super.add(x, y) + z;
}
}
public static void main(String[] args) {
System.out.println(new SubClass("A", "B", "C").result);
}
}

解答:

原则:finally里面不允许有return/break/continue/throw等改变正常退出的逻辑。

能调用null的方法么?

public class Null {
public static void greet() {
System.out.println("Hello world!");
}
public static void main(String[] args) {
Null x = null;
x.greet(); //(1)
((Null)x).greet(); //(2)
((Null) null).greet(); //(3)
}
}

解答:

能够输出“Hello world!”么???

                                                  

equals到底是什么东东?

public class Name {
private String first, last;
public Name(String first, String last) {
this.first = first;
this.last = last;
}
public boolean equals(Object o) {
Name n = (Name)o;
return n.first.equals(first) && n.last.equals(last);
}
public static void main(String[] args) {
Set s = new HashSet();
s.add(new Name("Mickey", "Mouse"));
System.out.println(s.contains(new Name("Mickey", "Mouse")));
}
}

解答:

True or False? 

Set/HashSet换成List/ArrayList或者Map/HashMap又会怎样呢?

Equals 特性
自反性:对于任意引用x ,x.equals(x) ==true 。
对称性:对于任意引用x,y ,x.equals(y) == y.equals(x)
传递性:对于任意引用x,y,z ,如果
x.equals(y)==y.equals(z)==true 那么,x.equals(z)==true
一致性:对于任意引用x,y ,多次调用x.equals(y) 应该返
回相同的值
非空性:对于任意非空引用x ,x.equals(null)==false

解答:最佳实践:equals() 和hashCode() 总是成对出现。

override & hidden

class Parent {
public String name = "Parent";
}
class Child extends Parent {
private String name = "Child";
}
public class PrivateMatter {
public static void main(String[ ] args) {
System.out.println(new Child().name);
}
}

解答:

override 针对实例方法 

hidden 针对静态方法、属性、内部类 

override 父类方法不能被调用,除非在子类内部使用super 

hidden 父类属性、静态方法等可以通过强制类型转换被调用


我的疑惑

 集合 – List/Set/Map/ConcurrentMap 

 锁 – synchronized/volatile/lock 

 线程池 – ThreadPool/Timer/Future


集合框架的系统梳理

JCF概览 

ArrayList/HashMap原理 

JCF 常见疑惑和陷阱


ArrayList原理

特性

 – 容量可变集合

 – 随机访问

 – 可克隆 

– 可序列化 

 原理 

– 数组

 – 1.5倍扩容 

– modCount特性

ArrayList注意点

静态引用防止内存泄露:clear()/trimToSize() 

循环注意并发修改 

带索引迭代:list.listIterator() 

循环删除方式

for(Iterator<String> it =
list.iterator();it.hasNext();) {
String item = it.next();
if("2".equals(item))
it.remove();
}

for(int i=list.size()-1;i>0;i--) {
if("3".equals(list.get(i)))
list.remove(i);
}

HashMap原理

Map的特性

– 任意Key编码

– 快速定位元素 

– 自动扩充容量 

 HashMap实现 

– hashCode() 

– indexFor()

– resize()/rehash()

碰撞”问题 

– hashCode() 

– equals()

static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}

HashMap注意点

静态引用防止内存泄露:clear()/capacity 

循环并发修改 

循环使用entrySet()

JCF 常见疑惑和陷阱

框架过于庞大、复杂怎么办? 

“内存泄露”到底该怎么办? 

 如果进行扩展、性能优化?

揭开神秘的锁机制

挃令重排序与volatile 

原子操作与CAS 

可重入锁与读写锁 

条件变量与线程挂起、唤醒

指令重排序与Volatile

线程安全 – 当多个线程访问一个类时,如果不用考虑这些线程 在运行时环境下的调度和交替运行,并丐不需要额 外的同步及在调用方代码不必做其他的协调,这个 类的行为仍然是正确的,那么这个类就是线程安全 的。 

挃令重排序 – JVM能够根据处理器的特性(CPU的多级缓存系统、 多核处理器等)适当的重新排序机器挃令,使机器 挃令更符合CPU的执行特点,最大限度的发挥机器 的性能。

指令重排序

public class ReorderingDemo {
static int x = 0, y = 0, a = 0, b = 0;
public static void main(String[] args) throws Exception {
Thread one = new Thread() {
public void run() {
a = 1;
x = b;
}
};
Thread two = new Thread() {
public void run() {
b = 1;
y = a;
}
};
one.start();
two.start();
one.join();
two.join();
System.out.println(x + " " + y);
}
}

Happens-before法则

1.同一个线程中的每个Action都happens-before于出现在其后的任何一 个Action。 

2. 对一个监视器的解锁happens-before于每一个后续对同一个监视器的 加锁。

3. 对volatile字段的写入操作happens-before于每一个后续的同一个字段 的读操作。

4. Thread.start()的调用会happens-before于启动线程里面的动作。 

5. Thread中的所有动作都happens-before于其他线程检查到此线程结束 戒者Thread.join()中返回戒者Thread.isAlive()==false。 

6. 一个线程A调用另一个另一个线程B的interrupt()都happens-before 于线程A发现B被A中断(B抛出异常戒者A检测到B的isInterrupted() 戒者interrupted())。 

7. 一个对象构造函数的结束happens-before与该对象的finalizer的开始 

8. 如果A动作happens-before于B动作,而B动作happens-before与C动作, 那么A动作happens-before于C动作。

volatile语义

synchronized的弱实现 

Java 存储模型不会对valatile挃令的操作进行 重排序:这个保证对volatile变量的操作时 挄照挃令的出现顺序执行的。 

 volatile变量的修改,其它线程总是可见的, 并丐不是使用自己线程栈内部的变量。 

 非线程安全

指令重排序与volatile案例


原子操作与CAS

 原子操作 

 多个线程执行一个操作时, 其中任何一个线程要么完全 执行完此操作,要么没有执 行此操作的任何步骤,那么 这个操作就是原子的。

CAS操作 

 非阻塞算法(nonblocking algorithms) 

 一个线程的失败戒者挂起不应该影响其他线程 的失败戒挂起。 

 CAS(Compare and Swap) 

CAS有3个操作数,内存值V,旧的预期值A,要 修改的新值B。当丐仅当预期值A和内存值V相 同时,将内存值V修改为B,否则什么都不做。

CAS原理

 boolean compareAndSet(int expect, int update)

while(false == compareAndSet(expect,update)){
expect = getCurrent();
}


可重入锁与读写锁

ReentrantLock 

 lock/tryLock 

 synchronized 

ReadWriteLock 

readLock() 

 writeLock()

lock()

while(synchronization state does not allow acquire){
enqueue current thread if not already queued;
possibly block current thread;
}
dequeue current thread if it was queued;

unlock ()

update synchronization state;
if(state may permit a blocked thread to acquire)
unlock one or more queued threads;

线程池的最佳实践

public ThreadPoolExecutor( (
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler )

并发的小陷阱

public class Runner{
int x,y;
Thread thread;
public Runner(){
this.x=1;
this.y=2;
this.thread=new MyThread();
this.thread.start();
}
}

Map map=Collections.synchronizedMap(new HashMap());
if(!map.containsKey("a")){
map.put("a", value);
}

public class ThreadSafeCache{
int result;
public int getResult(){
return result;
}
public synchronized void setResult(int result)
{
this.result=result;
}
}

总结:

作为一枚Java程序员真的是太强大了,除了要解决日常的bug还要对付这么多莫名其妙的陷阱,一不小心就掉坑里了。损失的都是自己的头发啊。~~Java中常见的坑还有很多,由于篇幅过长就不一 一展现了。

更多《Java程序员常见的疑惑和陷阱》欢迎查看PPT,写的非常详细,建议多读几遍!!

如果你需要获取完整ppt,可以在搜索微信【Java耕耘者】公号对话框回复: “PPT” 即可获取完整文件!

好了各位,本文到这里就结束了!如果本文有任何错误,请批评指教,不胜感激 !