title: JAVA碎碎念【一】
date: 2018-03-04 20:10:21
tags: Java
categories: Java
過去的 2017 年哑子,關(guān)于 Java 大大小小的坑還是踩了不少籽腕。這里回顧一下還記得的問題。
數(shù)組與List的轉(zhuǎn)換
常見做法:
List<String> list = Arrays.asList(arr);
問題:
多數(shù)我們都認(rèn)為Arrays.asList(arr)
方法返回的是ArrayList.
實(shí)際上返回的是java.util.Arrays.ArrayList
,其完整ArrayList聲明:
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, Serializable
實(shí)現(xiàn)方法如下圖:
java.util.Arrays.ArrayList
并沒有實(shí)現(xiàn)所有ArrayList
的方法
所以當(dāng)我們期望得到一個真正的ArrayList
時需要如下操作:
ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
類似的問題:
java.util.ArrayList.subList()
返回的不是 ArrayList传于。當(dāng)在有些場景下需要依賴容器特性(例如是否序列化)時要確認(rèn)一下集合操作的返回類型碉克,如有必要最好顯示的轉(zhuǎn)換。
集合中移除元素
例如赁还,循環(huán)刪除List中所有元素妖泄,很容易寫出下面的代碼:
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
list.remove(i);
}
問題:
每次根據(jù)索引刪除元素后List的大小及元素索引會發(fā)生變化,需要使用迭代器實(shí)現(xiàn)艘策,使用迭代器很容易寫出下面的代碼:
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (String s : list) {
if (s.equals("a"))
list.remove(s);
}
實(shí)際還是錯誤的蹈胡,正確寫法如下(我?guī)缀跷ㄒ挥玫鞯膱鼍埃?/p>
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String s = iter.next();
if (s.equals("a")) {
iter.remove();
}
}
Integer.parseInt(String s)/valueOf(String s)
Integer.parseInt(String s)
和 Integer.valueOf(String s)
的區(qū)別
將一個數(shù)字字符串轉(zhuǎn)數(shù)字,例如:
// “123” -> 123
String numStr = "123";
Integer.parseInt(numStr);
Integer.valueOf(numStr);
Integer.parseInt()
和 Integer.valueOf()
的區(qū)別主要在于放回的類型上:
Integer.parseInt()
返回 int
Integer.valueOf()
返回Integer
對性能敏感的地方或循環(huán)內(nèi)部,應(yīng)當(dāng)避免不必要的拆箱裝箱
ConcurrentMap.putIfAbsent(key,value)
錯誤用法:
ConcurrentMap<Dept, Person> cache = new ConcurrentMap.putIfAbsent<>();
// ...
public Dept getDept(Person person) {
Dept dept = cache.get(person);
if (dept == null) {
dept = person.getDept();
cache.putIfAbsent(person, dept);
}
return dept;
}
putIfAbsent
方法完整簽名如下:public V putIfAbsent(K var1, V var2)
看到返回值了有木有罚渐,這點(diǎn)很關(guān)鍵却汉。
多線程環(huán)境下,可能線程A首先獲取失敗荷并,當(dāng)A創(chuàng)建完value對象合砂,準(zhǔn)備插入時已經(jīng)有別的線程插入了,這時候再返回A創(chuàng)建的value可能存在問題源织。
所以翩伪,如果當(dāng)前put失敗,則返回已存在的value谈息,否則返回null缘屹。
正確的寫法:
ConcurrentMap<Dept, Person> cache = new ConcurrentMap.putIfAbsent<>();
// ...
public Dept getDept(Person person) {
Dept dept = cachecache.get(person);
if (dept == null) {
dept = person.getDept();
Dept deptPuted = cache.putIfAbsent(person, dept);
if (deptPuted != null) {
dept = deptPutted;
}
}
return dept;
}
過長的參數(shù)
過長參數(shù)是不合理的,通常超過3個就要謹(jǐn)慎侠仇,超過5個要被禁止轻姿。如果一個方法有一個長長的參數(shù),首先考慮的不是通過把多個參數(shù)封裝在一起逻炊。
如果一個方法需要多個參數(shù)不能從自己的宿主類獲得互亮,那么這個方法的位置可能有問題,要警惕這點(diǎn)余素。
雙胞胎Class.forName
Class.forName:返回與給定的字符串名稱相關(guān)聯(lián)類或接口的Class對象
該方法有兩種形式:
Class.forName(String name, boolean initialize, ClassLoader loader)
- name表示的是類的全名
- initialize表示是否初始化類
- loader表示加載時使用的類加載器
Class.forName(String className)
等價于:Class.forName(name, true, this.getClassLoader)
鏈接的有效性測試
Web開發(fā)中頁面與控制器的映射通常是使用配置完成胳挎。
靜態(tài)語言一大優(yōu)勢是編譯期的檢查,幫助我們重構(gòu)代碼溺森,發(fā)現(xiàn)錯誤慕爬。但無法檢查配置是否正確,例如:
在維護(hù)Struct1項(xiàng)目過程中我們有一個基本的抽象ActionExtendAction
提供了頁面CRUD訪問的入口屏积,多數(shù)的Action都繼承ExtendAction
医窿,現(xiàn)在有個報表的抽象類AbstractReportAction
也是繼承了ExtendAction
。
某次修改希望為報表Action增加新功能炊林,目前看到需求報表都沒有使用到CRUD功能姥卢,因此擴(kuò)展的方式是通過重構(gòu),引入了新的抽象Action代替了AbstractReportAction
的父類ExtendAction
另一個需求需要使用到CRUD的功能渣聚,因?yàn)槔^承的AbstractReportAction
是繼承自ExtendAction
的可以直接使用独榴。
現(xiàn)在兩個需求開發(fā)完成,分別在自己分支上測試完成奕枝,合并代碼后棺榔,編譯正確。
如果合并后隘道,結(jié)果依賴分支測試症歇,沒有充分集成測試郎笆,上線后可能就會造成事故。
改進(jìn):
- 少用繼承忘晤,這次的案例中一定程度是因?yàn)槎鄬哟蔚睦^承關(guān)系隱蔽了錯誤宛蚓;
- 對基礎(chǔ)類的修改,需要謹(jǐn)慎设塔,團(tuán)隊(duì)的所有成員需要對此保持警惕凄吏,細(xì)心評估可能現(xiàn)階段及未來可能帶來的風(fēng)險。
- 對鏈接的有效性測試闰蛔,在單元測試層面添加測試是非常有必要的痕钢。
- 除了單元測試,當(dāng)多個功能集成后钞护,集成測試也很關(guān)鍵盖喷。
單元測試框架補(bǔ)充——Junit
平時對JUnit使用比較簡單爆办,今天看到一個介紹Junit的:
-
JUnit中的Assert方法:
void assertEquals(boolean expected, boolean actual)
檢查兩個變量或者等式是否平衡void assertFalse(boolean condition)
檢查條件是假的void assertNotNull(Object object)
檢查對象不是空的void assertNull(Object object)
檢查對象是空的void assertTrue(boolean condition)
檢查條件為真void fail()
在沒有報告的情況下使測試不通過
-
JUnit中的注解
-
@BeforeClass
:針對所有測試难咕,只執(zhí)行一次,且必須為static void -
@Before
:初始化方法 -
@Test
:測試方法距辆,在這里可以測試期望異常和超時時間 -
@After
:釋放資源 -
@AfterClass
:針對所有測試余佃,只執(zhí)行一次,且必須為static void -
@Ignore
:忽略的測試方法
-
-
時間測試
@Test(timeout = 1000) public void testTimeoutSuccess() { // do nothing }
-
異常測試
@Test(expected = NullPointerException.class) public void testException() { throw new NullPointerException(); }
反射中的isAccessible
isAccessible()返回值的含義:
true:直接訪問跨算,不受檢查
false:不能直接訪問爆土,接受檢查
所有的accessible標(biāo)志默認(rèn)都為false,哪怕它使public的诸蚕。
setAccessible設(shè)置是否開啟直接訪問(關(guān)閉安全檢查)步势,反射調(diào)用提升速度很關(guān)鍵。
這里很容易誤解成背犯,private 的成員調(diào)用 isAccessible 返回 true坏瘩,public 的成員調(diào)用 isAccessible 返回 false。
舉個例子:
public class Employee {
private String id;
public String name;
// 省略構(gòu)造漠魏,toString()
}
public class AccessibleTest {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
Employee employee = new Employee("1", "ZS");
System.out.println(employee);
Class<?> clazz = employee.getClass();
printFieldAccess(clazz, "id");
// id isPublic:false id isAccessible:false
printFieldAccess(clazz, "name");
// name isPublic:true name isAccessible:false
changeFieldValue(employee, "name");
// Employee{id='1', name='TEST'}
changeFieldValue(employee, "id");
// java.lang.IllegalAccessException
}
private static void printFieldAccess(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(fieldName);
System.out.println(field.getName() + " isPublic:" + Modifier.isPublic(field.getModifiers()));
System.out.println(field.getName() + " isAccessible:" + field.isAccessible() + "\n");
}
private static void changeFieldValue(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.set(object, "TEST");
System.out.println(object);
}
}
反射API中的兄弟方法
反射API中有幾對兄弟倔矾,可以概括為getXxx()
/getDeclaredXxx()
getXxx()
:獲取所有的公有Xxx,包括繼承來的
getDeclaredXxx
:獲取“當(dāng)前類“的所有Xxx柱锹,不受訪問權(quán)限限制
基本/包裝類型在反射中坑
JDK5之后基本類型與包裝類型之間轉(zhuǎn)換已經(jīng)可以做到自動拆箱/裝箱了哪自,日常開發(fā)不再留意這些,但是反射中還是要自己注意類型禁熏,例如:
public class ReflectBoxingTest {
private static class Employee {
public void testBaeType(int x) {
}
public void testBoxingType(Integer x) {
}
}
public static void main(String[] args) {
Class<Employee> clazz = Employee.class;
getMethod(clazz, "testBaeType", int.class);
getMethod(clazz, "testBaeType", Integer.class);
getMethod(clazz, "testBoxingType", int.class);
getMethod(clazz, "testBoxingType", Integer.class);
}
private static void getMethod(Class<?> clazz, String method, Class<?>...classes) {
try {
clazz.getDeclaredMethod(method, classes);
} catch (NoSuchMethodException e) {
System.out.println("can't get method " + method
+ "(" + Arrays.toString(classes) + ")");
}
}
}
很容易讓人覺得上述四種方式均能過去到method壤巷,實(shí)際上int.class
與Integer.class
之間是不能自動轉(zhuǎn)型的。
輸出結(jié)果:
can't get method testBaeType([class java.lang.Integer])
can't get method testBoxingType([int])
引申:在ORM框架中的問題等等瞧毙。
謹(jǐn)慎使用DB中not-null="false"
如果對一個字段設(shè)置了not-null=true
,那么在以后的使用當(dāng)中無論是發(fā)送SQL語句還是處理實(shí)體類隙笆,都必須先對其作出判空處理锌蓄。例子:
新添加了一個字段期望管理員限定是否只在移動端訪問
Hibernate配置文件中:
<property
name="fdMobileOnly"
column="fd_mobile_only"
update="true"
insert="true"
not-null="false"
length="1" />
HQL語句:
where = ....
where += " and entity.fdMobileOnly != 1";
代碼發(fā)布后,管理員沒有第一時間更新所有字段撑柔,用戶訪問后出錯瘸爽,當(dāng)然這不是管理員的錯。這樣的錯誤在開發(fā)階段是可以避免的铅忿,測試階段剪决,發(fā)布階段都可以做些什么,但是我們?nèi)鄙傧鄳?yīng)的支持檀训。
內(nèi)省
比反射更便捷的操作 bean 實(shí)例柑潦,使用內(nèi)省機(jī)制。內(nèi)省Demo
異常處理
盡可能的簡單峻凫,復(fù)雜的異常處理過程本身就可能包含了新的異常渗鬼,在異常處理中發(fā)生新的異常可能就會覆蓋原始異常荧琼,這不是我們想看到的譬胎。對業(yè)務(wù)的異常增加 warn 日志,非業(yè)務(wù)的異常增加 error 日志命锄。
Thread.run()/start()
start():用于啟動線程
run():普通方法
直接調(diào)用run方法將不會開啟新的線程堰乔,依舊是順序執(zhí)行。
小數(shù)問題
System.out.println(0.1f+0.1f); // 0.2
System.out.println(0.1f*0.1f); // 0.010000001
第一行結(jié)果接近0.2所以輸出時系統(tǒng)選擇輸出了0.2
泛型問題
Java實(shí)現(xiàn)泛型是使用泛型擦除的
Java的泛型不能協(xié)變
可以使用通配符指定不確定的泛型
- 生產(chǎn)者使用extends
- <? extends T> 聲明的類型是T的子類脐恩,具體哪個子類不知道镐侯,所以不能隨便給?賦引用驶冒,因?yàn)榭赡艹霈F(xiàn)轉(zhuǎn)型錯誤苟翻,但是知道是T的子類,肯定實(shí)現(xiàn)了T的方法
- 消費(fèi)者使用super
- <? super T> 聲明的類型是T的父類骗污,具體哪個父類不知道崇猫,反正至少T類型,所以T的子類型都可以被身堡?引用邓尤,但引用的是什么類型不知道,所以根據(jù)贴谎?獲取的都是object
線程創(chuàng)建
在Java中創(chuàng)建線程汞扎,一個線程默認(rèn)就會預(yù)留1M的空間,那么1G的內(nèi)存也不過只能支持1000個線程創(chuàng)建而已
獲取泛型類型T的類實(shí)例
import org.springframework.core.GenericTypeResolver;
public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{
@Autowired
private SessionFactory sessionFactory;
private final Class<T> genericType;
private final String RECORD_COUNT_HQL;
private final String FIND_ALL_HQL;
@SuppressWarnings("unchecked")
public AbstractHibernateDao()
{
this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
this.RECORD_COUNT_HQL ="select count(*) from" + this.genericType.getName();
this.FIND_ALL_HQL ="from" + this.genericType.getName() +" t";
}
ThreadPoolExecutor.submit()/execute()
ThreadPoolExecutor 線程池中 submit 和 execute 方法對比
- submit 提交的任務(wù)可以獲取執(zhí)行結(jié)果擅这,而execute 則不能
- execute 會拋出異常澈魄,而submit 如果不獲取執(zhí)行結(jié)果的話會“吞掉”異常
詳細(xì)分析:http://blog.techbeta.me/2016/08/ThreadPoolExecutor/
ThreadPoolExecutor 線程池對異常處理
- 捕獲 execute 拋出異常
- submit 執(zhí)行完成必須 get 一下檢測異常
- 擴(kuò)展線程池,實(shí)現(xiàn) beforeExecute 方法
轉(zhuǎn)型問題
即使是同一個類文件仲翎,如果是由不同的類加載器實(shí)例加載的痹扇,那么它們的類型是不相同的铛漓。例如:
public void run(){
try {
// 每次都創(chuàng)建出一個新的類加載器
HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"});
Class cls = cl.loadClass("Foo");
Object foo = cls.newInstance();
Method m = foo.getClass().getMethod("sayHello", new Class[]{});
m.invoke(foo, new Object[]{});
} catch(Exception ex) {
ex.printStackTrace();
}
}
cls 是由 HowswapCL 加載的,而 foo 變量類型聲名和轉(zhuǎn)型里的 Foo 類卻是由 run 方法所屬的類的加載器(默認(rèn)為 AppClassLoader)加載的鲫构,因此是完全不同的類型浓恶,所以會拋出轉(zhuǎn)型異常
解決:
http://www.wisedream.net/2017/01/17/programming/type-cast-across-classloader/