2017年我给同事讲的Java基础——String陷阱Integer缓存和手写连接池 2017年讲的那些Java基础——String陷阱、Integer缓存和手写连接池文章目录2017年讲的那些Java基础——String陷阱、Integer缓存和手写连接池一、这不是网上抄的教程是内部培训讲义二、1.equals(str) 而不是 str.equals(1)三、大量字符串拼接为什么不能用 四、Integer 5 的陷阱——IntegerCache五、引用传递——Long型修改为什么失效六、wait 和 sleep 的区别——不是让你背的七、手写一个连接池——五个需求八、结语一、这不是网上抄的教程是内部培训讲义2017年做Java基础培训。做的PPT没有花哨的排版每页都是一个问题 一段代码 一个解释。这些问题都是开发中真实踩过的坑不是教科书上的课后习题。现在回头看这些PPT内容不算高级——但解释方式有自己的理解。把这个讲义整理成文。二、“1”.equals(str) 而不是 str.equals(“1”)先看一段会抛空指针的代码Stringstrnull;if(str.equals(1)){System.out.println(好);}else{System.out.println(坏);}// 抛 NullPointerException为什么str是 null没有.equals()方法可以调。正确的写法是把常量放前面if(1.equals(str)){// 1 永远不会是 null这个看起来简单但在实际项目中出错率极高——因为参数从页面传过来有时候真的有可能是 null。一开始养成了str.equals(1)的习惯后面写一百个判断都不会想到要反过来。三、大量字符串拼接为什么不能用 Strings;for(inti0;i10000;i){ssi;// 每次循环创建一个新String对象}Java没有操作符重载——除了基本类型的。String 能用是因为编译器做了手脚s i被编译成new StringBuilder(s).append(i).toString()。但循环里每次都 new 一个 StringBuilder10000次循环就是10000个对象创建和销毁。正确做法是自己维护一个StringBuffer线程安全或StringBuilder非线程安全StringBuildersbnewStringBuilder();for(inti0;i10000;i){sb.append(i);}Stringssb.toString();// 只创建一次String四、Integer 5 的陷阱——IntegerCacheIntegera5;Integerb5;System.out.println(ab);// trueIntegerc200;Integerd200;System.out.println(cd);// false 为什么因为Integer a 5实际上被编译器转成了Integer.valueOf(5)而valueOf有一段缓存逻辑publicstaticIntegervalueOf(inti){if(iIntegerCache.lowiIntegerCache.high)returnIntegerCache.cache[i(-IntegerCache.low)];returnnewInteger(i);}IntegerCache.low是 -128high默认是 127。所以 -128到127之间的 Integer 是直接从缓存数组里拿的同一个对象。200 超出了范围走了new Integer(200)a 和 b 是不同对象比较的是地址。结论Integer之间比大小用equals()永远别用。比较的是栈上的地址Integer 是引用类型。五、引用传递——Long型修改为什么失效publicvoidtest(Longvalue){value999L;// 调用方拿不到这个修改}简单类型是值传递——传的是值的拷贝。复杂类型是引用传递——传的是地址。但 Long 是 immutable 的value 999L等价于value Long.valueOf(999)这是一个新的 Long 对象新分配了地址。相当于在函数内部把参数指向了一个新对象外层的引用还指着旧对象。要修改 Long 类型的值只能通过包装一个可变对象比如用long[]或AtomicLong。或者干脆用基本类型long 返回值。六、wait 和 sleep 的区别——不是让你背的wait()是 Object 的方法必须在 synchronized 块里调用释放锁等待notify()唤醒sleep()是 Thread 的方法不释放锁到时间自动醒一个简单记忆法wait 是我把锁交出来别人先用用完了叫我sleep 是我抱着锁睡一会谁也别动我的东西。七、手写一个连接池——五个需求连接池的本质不是拿来就用而是理解这几个问题1) 初始化最大连接数、初始连接数、增量 2) 获取连接拿一个空闲的没有就等 3) 自动扩展连接不够了但有上限最多不能超过最大连接数 4) 等待连接所有连接都在用新请求要等设超时 5) 关闭连接不是真关是归还到池子里核心数据结构就是一个ListConnection加上一个ListConnection一个空闲池、一个使用中池publicclassSimplePool{privateListConnectionfreenewArrayList();privateListConnectionusednewArrayList();privateintmaxSize;privateintstep;// 增量publicsynchronizedConnectionget(){if(free.isEmpty()){if(used.size()maxSize){wait(timeout);// 等别人归还}else{expand(step);// 不够但没到上限创建新连接}}Connectioncfree.remove(0);used.add(c);returnc;}publicsynchronizedvoidrelease(Connectionc){used.remove(c);free.add(c);notify();// 唤醒一个等待的}}不到100行代码但连wait/notify、增量扩容、超时等待全用上了。理解了这段代码用HikariCP的时候就知道它在底层做什么。八、结语这些内容不难难的是在写代码的时候意识到自己在踩这些坑。str.equals(1)抛空指针的时候如果不知道是因为str是 null就只能猜。知道了以后所有字符串比较都反着写。这就是培训的价值——不是讲高级概念是把踩过的坑标出来让别人不用再踩一次。