JAVA碎碎念【一】


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結(jié)構(gòu).jpg

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):

  1. 少用繼承忘晤,這次的案例中一定程度是因?yàn)槎鄬哟蔚睦^承關(guān)系隱蔽了錯誤宛蚓;
  2. 對基礎(chǔ)類的修改,需要謹(jǐn)慎设塔,團(tuán)隊(duì)的所有成員需要對此保持警惕凄吏,細(xì)心評估可能現(xiàn)階段及未來可能帶來的風(fēng)險。
  3. 對鏈接的有效性測試闰蛔,在單元測試層面添加測試是非常有必要的痕钢。
  4. 除了單元測試,當(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.classInteger.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 方法對比

  1. submit 提交的任務(wù)可以獲取執(zhí)行結(jié)果擅这,而execute 則不能
  2. execute 會拋出異常澈魄,而submit 如果不獲取執(zhí)行結(jié)果的話會“吞掉”異常

詳細(xì)分析:http://blog.techbeta.me/2016/08/ThreadPoolExecutor/


ThreadPoolExecutor 線程池對異常處理

  1. 捕獲 execute 拋出異常
  2. submit 執(zhí)行完成必須 get 一下檢測異常
  3. 擴(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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市结笨,隨后出現(xiàn)的幾起案子包晰,更是在濱河造成了極大的恐慌,老刑警劉巖炕吸,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伐憾,死亡現(xiàn)場離奇詭異,居然都是意外死亡赫模,警方通過查閱死者的電腦和手機(jī)树肃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瀑罗,“玉大人胸嘴,你說我怎么就攤上這事±啵” “怎么了筛谚?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵磁玉,是天一觀的道長停忿。 經(jīng)常有香客問我,道長蚊伞,這世上最難降的妖魔是什么席赂? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮时迫,結(jié)果婚禮上颅停,老公的妹妹穿的比我還像新娘。我一直安慰自己掠拳,他們只是感情好癞揉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溺欧,像睡著了一般喊熟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姐刁,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天芥牌,我揣著相機(jī)與錄音,去河邊找鬼聂使。 笑死壁拉,一個胖子當(dāng)著我的面吹牛谬俄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弃理,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼溃论,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痘昌?” 一聲冷哼從身側(cè)響起蔬芥,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎控汉,沒想到半個月后笔诵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姑子,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年乎婿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片街佑。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡谢翎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沐旨,到底是詐尸還是另有隱情森逮,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布磁携,位于F島的核電站褒侧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谊迄。R本人自食惡果不足惜闷供,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望统诺。 院中可真熱鬧歪脏,春花似錦、人聲如沸粮呢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啄寡。三九已至豪硅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間这难,已是汗流浹背舟误。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姻乓,地道東北人嵌溢。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓眯牧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赖草。 傳聞我的和親對象是個殘疾皇子学少,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容