<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->在項(xiàng)目中使用Spring的注解,關(guān)于spring的注解,由兩種注解方式,
基于注釋(Annotation)的配置有越來越流行的趨勢,Spring 2.5 順應(yīng)這種趨勢,提供了完全基于注釋配置 Bean、裝配 Bean 的功能,您可以使用基于注釋的 Spring IoC 替換原來基于 XML 的配置。本文通過實(shí)例詳細(xì)講述了 Spring 2.5 基于注釋 IoC 功能的使用。
注釋配置相對于 XML 配置具有很多的優(yōu)勢:
- 它可以充分利用 Java 的反射機(jī)制獲取類結(jié)構(gòu)信息,這些信息可以有效減少配置的工作。如使用 JPA 注釋配置 ORM 映射時(shí),我們就不需要指定 PO 的屬性名、類型等信息,如果關(guān)系表字段和 PO 屬性名、類型都一致,您甚至無需編寫任務(wù)屬性映射信息——因?yàn)檫@些信息都可以通過 Java 反射機(jī)制獲取。
- 注釋和 Java 代碼位于一個(gè)文件中,而 XML 配置采用獨(dú)立的配置文件,大多數(shù)配置信息在程序開發(fā)完成后都不會調(diào)整,如果配置信息和 Java 代碼放在一起,有助于增強(qiáng)程序的內(nèi)聚性。而采用獨(dú)立的 XML 配置文件,程序員在編寫一個(gè)功能時(shí),往往需要在程序文件和配置文件中不停切換,這種思維上的不連貫會降低開發(fā)效率。
因此在很多情況下,注釋配置比 XML 配置更受歡迎,注釋配置有進(jìn)一步流行的趨勢。Spring 2.5 的一大增強(qiáng)就是引入了很多注釋類,現(xiàn)在您已經(jīng)可以使用注釋配置完成大部分 XML 配置的功能。在這篇文章里,我們將向您講述使用注釋進(jìn)行 Bean 定義和依賴注入的內(nèi)容。
Spring 2.5 引入了 @Autowired
注釋,它可以對類成員變量、方法及構(gòu)造函數(shù)進(jìn)行標(biāo)注,完成自動(dòng)裝配的工作。
Spring 通過一個(gè) BeanPostProcessor
對 @Autowired
進(jìn)行解析,所以要讓 @Autowired
起作用必須事先在 Spring 容器中聲明 AutowiredAnnotationBeanPostProcessor
Bean。
在Spring中配置如下:
<!-- 該 BeanPostProcessor 將自動(dòng)起作用,對標(biāo)注 @Autowired 的 Bean 進(jìn)行自動(dòng)注入 -->
<bean class="org.springframework.beans.factory.annotation.
AutowiredAnnotationBeanPostProcessor"/>
當(dāng) Spring 容器啟動(dòng)時(shí),AutowiredAnnotationBeanPostProcessor
將掃描 Spring 容器中所有 Bean,當(dāng)發(fā)現(xiàn) Bean 中擁有 @Autowired
注釋時(shí)就找到和其匹配(默認(rèn)按類型匹配)的 Bean,并注入到對應(yīng)的地方中去。
按照上面的配置,Spring 將直接采用 Java 反射機(jī)制對 Boss 中的 car
和 office
這兩個(gè)私有成員變量進(jìn)行自動(dòng)注入。所以對成員變量使用 @Autowired
后,您大可將它們的 setter 方法(setCar()
和 setOffice()
)從 Boss 中刪除。
當(dāng)然,您也可以通過 @Autowired
對方法或構(gòu)造函數(shù)進(jìn)行標(biāo)注,
當(dāng)候選 Bean 數(shù)目不為 1 時(shí)的應(yīng)對方法
在默認(rèn)情況下使用 @Autowired
注釋進(jìn)行自動(dòng)注入時(shí),Spring 容器中匹配的候選 Bean 數(shù)目必須有且僅有一個(gè)。當(dāng)找不到一個(gè)匹配的 Bean 時(shí),Spring 容器將拋出 BeanCreationException
異常,并指出必須至少擁有一個(gè)匹配的 Bean。
當(dāng)不能確定 Spring 容器中一定擁有某個(gè)類的 Bean 時(shí),可以在需要自動(dòng)注入該類 Bean 的地方可以使用 @Autowired(required = false)
,這等于告訴 Spring:在找不到匹配 Bean 時(shí)也不報(bào)錯(cuò)。
一般情況下,使用 @Autowired
的地方都是需要注入 Bean 的,使用了自動(dòng)注入而又允許不注入的情況一般僅會在開發(fā)期或測試期碰到(如為了快速啟動(dòng) Spring 容器,僅引入一些模塊的 Spring 配置文件),所以 @Autowired(required = false)
會很少用到。
和找不到一個(gè)類型匹配 Bean 相反的一個(gè)錯(cuò)誤是:如果 Spring 容器中擁有多個(gè)候選 Bean,Spring 容器在啟動(dòng)時(shí)也會拋出 BeanCreationException
異常。
Spring 允許我們通過 @Qualifier
注釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:
清單 13. 使用 @Qualifier 注釋指定注入 Bean 的名稱
@Autowired public void setOffice(@Qualifier("office")Office office) { this.office = office; } |
@Qualifier("office")
中的 office
是 Bean 的名稱,所以 @Autowired
和 @Qualifier
結(jié)合使用時(shí),自動(dòng)注入的策略就從 byType 轉(zhuǎn)變成 byName 了。@Autowired
可以對成員變量、方法以及構(gòu)造函數(shù)進(jìn)行注釋,而 @Qualifier
的標(biāo)注對象是成員變量、方法入?yún)ⅰ?gòu)造函數(shù)入?yún)ⅰU怯捎谧⑨寣ο蟮牟煌?Spring 不將 @Autowired
和 @Qualifier
統(tǒng)一成一個(gè)注釋類。下面是對成員變量和構(gòu)造函數(shù)入?yún)⑦M(jìn)行注釋的代碼:
對成員變量進(jìn)行注釋:
對成員變量使用 @Qualifier 注釋
public class Boss { @Autowired private Car car; @Autowired @Qualifier("office") private Office office; … } |
對構(gòu)造函數(shù)入?yún)⑦M(jìn)行注釋:
清單 15. 對構(gòu)造函數(shù)變量使用 @Qualifier 注釋
public class Boss { private Car car; private Office office; @Autowired public Boss(Car car , @Qualifier("office")Office office){ this.car = car; this.office = office ; } } |
@Qualifier
只能和 @Autowired
結(jié)合使用,是對 @Autowired
有益的補(bǔ)充。一般來講,@Qualifier
對方法簽名中入?yún)⑦M(jìn)行注釋會降低代碼的可讀性,而對成員變量注釋則相對好一些。
Spring 不但支持自己定義的 @Autowired
的注釋,還支持幾個(gè)由 JSR-250 規(guī)范定義的注釋,它們分別是 @Resource
、@PostConstruct
以及 @PreDestroy
。
@Resource
的作用相當(dāng)于 @Autowired
,只不過 @Autowired
按 byType 自動(dòng)注入,面 @Resource
默認(rèn)按 byName 自動(dòng)注入罷了。@Resource
有兩個(gè)屬性是比較重要的,分別是 name 和 type,Spring 將 @Resource
注釋的 name 屬性解析為 Bean 的名字,而 type 屬性則解析為 Bean 的類型。所以如果使用 name 屬性,則使用 byName 的自動(dòng)注入策略,而使用 type 屬性時(shí)則使用 byType 自動(dòng)注入策略。如果既不指定 name 也不指定 type 屬性,這時(shí)將通過反射機(jī)制使用 byName 自動(dòng)注入策略。
Resource 注釋類位于 Spring 發(fā)布包的 lib/j2ee/common-annotations.jar 類包中,因此在使用之前必須將其加入到項(xiàng)目的類庫中。來看一個(gè)使用 @Resource
的例子:
清單 16. 使用 @Resource 注釋的 Boss.java
package com.baobaotao; import javax.annotation.Resource; public class Boss { // 自動(dòng)注入類型為 Car 的 Bean @Resource private Car car; // 自動(dòng)注入 bean 名稱為 office 的 Bean @Resource(name = "office") private Office office; } |
一般情況下,我們無需使用類似于 @Resource(type=Car.class)
的注釋方式,因?yàn)?Bean 的類型信息可以通過 Java 反射從代碼中獲取。
要讓 JSR-250 的注釋生效,除了在 Bean 類中標(biāo)注這些注釋外,還需要在 Spring 容器中注冊一個(gè)負(fù)責(zé)處理這些注釋的 BeanPostProcessor
:
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/> |
CommonAnnotationBeanPostProcessor
實(shí)現(xiàn)了 BeanPostProcessor
接口,它負(fù)責(zé)掃描使用了 JSR-250 注釋的 Bean,并對它們進(jìn)行相應(yīng)的操作。
Spring 容器中的 Bean 是有生命周期的,Spring 允許在 Bean 在初始化完成后以及 Bean 銷毀前執(zhí)行特定的操作,您既可以通過實(shí)現(xiàn) InitializingBean/DisposableBean 接口來定制初始化之后 / 銷毀之前的操作方法,也可以通過 <bean> 元素的 init-method/destroy-method 屬性指定初始化之后 / 銷毀之前調(diào)用的操作方法。關(guān)于 Spring 的生命周期,筆者在《精通 Spring 2.x—企業(yè)應(yīng)用開發(fā)精解》第 3 章進(jìn)行了詳細(xì)的描述,有興趣的讀者可以查閱。
JSR-250 為初始化之后/銷毀之前方法的指定定義了兩個(gè)注釋類,分別是 @PostConstruct 和 @PreDestroy,這兩個(gè)注釋只能應(yīng)用于方法上。標(biāo)注了 @PostConstruct 注釋的方法將在類實(shí)例化后調(diào)用,而標(biāo)注了 @PreDestroy 的方法將在類銷毀之前調(diào)用。
清單 17. 使用 @PostConstruct 和 @PreDestroy 注釋的 Boss.java
package com.baobaotao; import javax.annotation.Resource; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class Boss { @Resource private Car car; @Resource(name = "office") private Office office; @PostConstruct public void postConstruct1(){ System.out.println("postConstruct1"); } @PreDestroy public void preDestroy1(){ System.out.println("preDestroy1"); } … } |
您只需要在方法前標(biāo)注 @PostConstruct
或 @PreDestroy
,這些方法就會在 Bean 初始化后或銷毀之前被 Spring 容器執(zhí)行了。
我們知道,不管是通過實(shí)現(xiàn) InitializingBean
/DisposableBean
接口,還是通過 <bean> 元素的 init-method/destroy-method
屬性進(jìn)行配置,都只能為 Bean 指定一個(gè)初始化 / 銷毀的方法。但是使用 @PostConstruct
和 @PreDestroy
注釋卻可以指定多個(gè)初始化 / 銷毀方法,那些被標(biāo)注 @PostConstruct
或 @PreDestroy
注釋的方法都會在初始化 / 銷毀時(shí)被執(zhí)行。
通過以下的測試代碼,您將可以看到 Bean 的初始化 / 銷毀方法是如何被執(zhí)行的:
package com.baobaotao; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AnnoIoCTest { public static void main(String[] args) { String[] locations = {"beans.xml"}; ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(locations); Boss boss = (Boss) ctx.getBean("boss"); System.out.println(boss); ctx.destroy();// 關(guān)閉 Spring 容器,以觸發(fā) Bean 銷毀方法的執(zhí)行 } } |
這時(shí),您將看到標(biāo)注了 @PostConstruct
的 postConstruct1()
方法將在 Spring 容器啟動(dòng)時(shí),創(chuàng)建 Boss
Bean 的時(shí)候被觸發(fā)執(zhí)行,而標(biāo)注了 @PreDestroy
注釋的 preDestroy1()
方法將在 Spring 容器關(guān)閉前銷毀 Boss
Bean 的時(shí)候被觸發(fā)執(zhí)行。
![]() ![]() |
![]()
|
使用 <context:annotation-config/> 簡化配置
Spring 2.1 添加了一個(gè)新的 context 的 Schema 命名空間,該命名空間對注釋驅(qū)動(dòng)、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道注釋本身是不會做任何事情的,它僅提供元數(shù)據(jù)信息。要使元數(shù)據(jù)信息真正起作用,必須讓負(fù)責(zé)處理這些元數(shù)據(jù)的處理器工作起來。
而我們前面所介紹的 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
就是處理這些注釋元數(shù)據(jù)的處理器。但是直接在 Spring 配置文件中定義這些 Bean 顯得比較笨拙。Spring 為我們提供了一種方便的注冊這些 BeanPostProcessor
的方式,這就是 <context:annotation-config/>。請看下面的配置:
清單 19. 調(diào)整 beans.xml 配置文件
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:annotation-config/> <bean id="boss" class="com.baobaotao.Boss"/> <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" 紅旗 CA72"/> <property name="price" value="2000"/> </bean> </beans> |
<context:annotationconfig/> 將隱式地向 Spring 容器注冊 AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
、PersistenceAnnotationBeanPostProcessor
以及 equiredAnnotationBeanPostProcessor
這 4 個(gè) BeanPostProcessor。
在配置文件中使用 context 命名空間之前,必須在 <beans> 元素中聲明 context 命名空間。
![]() ![]() |
![]()
|
雖然我們可以通過 @Autowired
或 @Resource
在 Bean 類中使用自動(dòng)注入功能,但是 Bean 還是在 XML 文件中通過 <bean> 進(jìn)行定義 —— 也就是說,在 XML 配置文件中定義 Bean,通過 @Autowired
或 @Resource
為 Bean 的成員變量、方法入?yún)⒒驑?gòu)造函數(shù)入?yún)⑻峁┳詣?dòng)注入的功能。能否也通過注釋定義 Bean,從 XML 配置文件中完全移除 Bean 定義的配置呢?答案是肯定的,我們通過 Spring 2.5 提供的 @Component
注釋就可以達(dá)到這個(gè)目標(biāo)了。
下面,我們完全使用注釋定義 Bean 并完成 Bean 之間裝配:
清單 20. 使用 @Component 注釋的 Car.java
package com.baobaotao; import org.springframework.stereotype.Component; @Component public class Car { … } |
僅需要在類定義處,使用 @Component
注釋就可以將一個(gè)類定義了 Spring 容器中的 Bean。下面的代碼將 Office
定義為一個(gè) Bean:
清單 21. 使用 @Component 注釋的 Office.java
package com.baobaotao; import org.springframework.stereotype.Component; @Component public class Office { private String officeNo = "001"; … } |
這樣,我們就可以在 Boss 類中通過 @Autowired
注入前面定義的 Car
和 Office Bean
了。
清單 22. 使用 @Component 注釋的 Boss.java
package com.baobaotao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component("boss") public class Boss { @Autowired private Car car; @Autowired private Office office; … } |
@Component
有一個(gè)可選的入?yún)ⅲ糜谥付?Bean 的名稱,在 Boss 中,我們就將 Bean 名稱定義為“boss
”。一般情況下,Bean 都是 singleton 的,需要注入 Bean 的地方僅需要通過 byType 策略就可以自動(dòng)注入了,所以大可不必指定 Bean 的名稱。
在使用 @Component
注釋后,Spring 容器必須啟用類掃描機(jī)制以啟用注釋驅(qū)動(dòng) Bean 定義和注釋驅(qū)動(dòng) Bean 自動(dòng)注入的策略。Spring 2.5 對 context 命名空間進(jìn)行了擴(kuò)展,提供了這一功能,請看下面的配置:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:component-scan base-package="com.baobaotao"/> </beans> |
這里,所有通過 <bean> 元素定義 Bean 的配置內(nèi)容已經(jīng)被移除,僅需要添加一行 <context:component-scan/> 配置就解決所有問題了——Spring XML 配置文件得到了極致的簡化(當(dāng)然配置元數(shù)據(jù)還是需要的,只不過以注釋形式存在罷了)。<context:component-scan/> 的 base-package 屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會被處理。
<context:component-scan/> 還允許定義過濾器將基包下的某些類納入或排除。Spring 支持以下 4 種類型的過濾方式,通過下表說明:
過濾器類型 | 說明 |
---|---|
注釋 | 假如 com.baobaotao.SomeAnnotation 是一個(gè)注釋類,我們可以將使用該注釋的類過濾出來。 |
類名指定 | 通過全限定類名進(jìn)行過濾,如您可以指定將 com.baobaotao.Boss 納入掃描,而將 com.baobaotao.Car 排除在外。 |
正則表達(dá)式 | 通過正則表達(dá)式定義過濾的類,如下所示: com\.baobaotao\.Default.* |
AspectJ 表達(dá)式 | 通過 AspectJ 表達(dá)式定義過濾的類,如下所示: com. baobaotao..*Service+ |
下面是一個(gè)簡單的例子:
<context:component-scan base-package="com.baobaotao"> <context:include-filter type="regex" expression="com\.baobaotao\.service\..*"/> <context:exclude-filter type="aspectj" expression="com.baobaotao.util..*"/> </context:component-scan> |
值得注意的是 <context:component-scan/> 配置項(xiàng)不但啟用了對類包進(jìn)行掃描以實(shí)施注釋驅(qū)動(dòng) Bean 定義的功能,同時(shí)還啟用了注釋驅(qū)動(dòng)自動(dòng)注入的功能(即還隱式地在內(nèi)部注冊了 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
),因此當(dāng)使用 <context:component-scan/> 后,就可以將 <context:annotation-config/> 移除了。
默認(rèn)情況下通過 @Component
定義的 Bean 都是 singleton 的,如果需要使用其它作用范圍的 Bean,可以通過 @Scope
注釋來達(dá)到目標(biāo),如以下代碼所示:
清單 24. 通過 @Scope 指定 Bean 的作用范圍
package com.baobaotao; import org.springframework.context.annotation.Scope; … @Scope("prototype") @Component("boss") public class Boss { … } |
這樣,當(dāng)從 Spring 容器中獲取 boss
Bean 時(shí),每次返回的都是新的實(shí)例了。
![]() ![]() |
![]()
|
Spring 2.5 中除了提供 @Component
注釋外,還定義了幾個(gè)擁有特殊語義的注釋,它們分別是:@Repository
、@Service
和 @Controller
。在目前的 Spring 版本中,這 3 個(gè)注釋和 @Component
是等效的,但是從注釋類的命名上,很容易看出這 3 個(gè)注釋分別和持久層、業(yè)務(wù)層和控制層(Web 層)相對應(yīng)。雖然目前這 3 個(gè)注釋和 @Component
相比沒有什么新意,但 Spring 將在以后的版本中為它們添加特殊的功能。所以,如果 Web 應(yīng)用程序采用了經(jīng)典的三層分層結(jié)構(gòu)的話,最好在持久層、業(yè)務(wù)層和控制層分別采用 @Repository
、@Service
和 @Controller
對分層中的類進(jìn)行注釋,而用 @Component
對那些比較中立的類進(jìn)行注釋。
![]() ![]() |
![]()
|
是否有了這些 IOC 注釋,我們就可以完全摒除原來 XML 配置的方式呢?答案是否定的。有以下幾點(diǎn)原因:
- 注釋配置不一定在先天上優(yōu)于 XML 配置。如果 Bean 的依賴關(guān)系是固定的,(如 Service 使用了哪幾個(gè) DAO 類),這種配置信息不會在部署時(shí)發(fā)生調(diào)整,那么注釋配置優(yōu)于 XML 配置;反之如果這種依賴關(guān)系會在部署時(shí)發(fā)生調(diào)整,XML 配置顯然又優(yōu)于注釋配置,因?yàn)樽⑨屖菍?Java 源代碼的調(diào)整,您需要重新改寫源代碼并重新編譯才可以實(shí)施調(diào)整。
- 如果 Bean 不是自己編寫的類(如
JdbcTemplate
、SessionFactoryBean
等),注釋配置將無法實(shí)施,此時(shí) XML 配置是唯一可用的方式。 - 注釋配置往往是類級別的,而 XML 配置則可以表現(xiàn)得更加靈活。比如相比于
@Transaction
事務(wù)注釋,使用 aop/tx 命名空間的事務(wù)配置更加靈活和簡單。
所以在實(shí)現(xiàn)應(yīng)用中,我們往往需要同時(shí)使用注釋配置和 XML 配置,對于類級別且不會發(fā)生變動(dòng)的配置可以優(yōu)先考慮注釋配置;而對于那些第三方類以及容易發(fā)生調(diào)整的配置則應(yīng)優(yōu)先考慮使用 XML 配置。Spring 會在具體實(shí)施 Bean 創(chuàng)建和 Bean 注入之前將這兩種配置方式的元信息融合在一起。
![]() ![]() |
![]()
|
Spring 在 2.1 以后對注釋配置提供了強(qiáng)力的支持,注釋配置功能成為 Spring 2.5 的最大的亮點(diǎn)之一。合理地使用 Spring 2.5 的注釋配置,可以有效減少配置的工作量,提高程序的內(nèi)聚性。但是這并不意味著傳統(tǒng) XML 配置將走向消亡,在第三方類 Bean 的配置,以及那些諸如數(shù)據(jù)源、緩存池、持久層操作模板類、事務(wù)管理等內(nèi)容的配置上,XML 配置依然擁有不可替代的地位。
什么叫控制反轉(zhuǎn)呢?套用好萊塢的一句名言就是:你呆著別動(dòng),到時(shí)我會找你。
什么意思呢?就好比一個(gè)皇帝和太監(jiān)
有一天皇帝想寵幸某個(gè)美女,于是跟太監(jiān)說,今夜我要寵幸美女
皇帝往往不會告訴太監(jiān),今晚幾點(diǎn)會回宮,會回哪張龍床,他只會告訴太監(jiān)他要哪位美女
其它一切都交由太監(jiān)去安排,到了晚上皇帝回宮時(shí),自然會有美女出現(xiàn)在皇帝的龍床上
這就是控制反轉(zhuǎn),而把美女送到皇帝的寢宮里面去就是注射
太監(jiān)就是是框架里面的注射控制器類BeanFactory,負(fù)責(zé)找到美女并送到龍床上去
整個(gè)后宮可以看成是Spring框架,美女就是Spring控制下的JavaBean
而傳統(tǒng)的模式就是一個(gè)饑渴男去找小姐出臺
找領(lǐng)班,幫助給介紹一個(gè)云云,于是領(lǐng)班就開始給他張羅
介紹一個(gè)合適的給他,完事后,再把小姐還給領(lǐng)班,下次再來
這個(gè)過程中,領(lǐng)班就是查詢上下文Context,領(lǐng)班的一個(gè)職能就是給客戶找到他們所要的小姐
這就是lookup()方法,領(lǐng)班手中的小姐名錄就是JNDI//Java Naming and Directory Interface
小姐就是EJB,饑渴男是客戶端,青樓是EJB容器
看到區(qū)別了么?
饑渴男去找小姐出臺很麻煩,不僅得找,用完后還得把小姐給還回去
而皇帝爽翻了,什么都不用管,交給太監(jiān)去處理,控制權(quán)轉(zhuǎn)移到太監(jiān)手中去了而不是皇帝,
必要時(shí)候由太監(jiān)給注射進(jìn)去就可以了
*
* @author liuguangyi
* @content ejb3注解的API定義在javax.persistence.*包里面。
*
* 注釋說明:
* @Entity —— 將一個(gè)類聲明為一個(gè)實(shí)體bean(即一個(gè)持久化POJO類)
* @Id —— 注解聲明了該實(shí)體bean的標(biāo)識屬性(對應(yīng)表中的主鍵)。
* @Table —— 注解聲明了該實(shí)體bean映射指定的表(table),目錄(catalog)和schema的名字
* @Column —— 注解聲明了屬性到列的映射。該注解有如下的屬性
* name 可選,列名(默認(rèn)值是屬性名)
* unique 可選,是否在該列上設(shè)置唯一約束(默認(rèn)值false)
* nullable 可選,是否設(shè)置該列的值可以為空(默認(rèn)值false)
* insertable 可選,該列是否作為生成的insert語句中的一個(gè)列(默認(rèn)值true)
* updatable 可選,該列是否作為生成的update語句中的一個(gè)列(默認(rèn)值true)
* columnDefinition 可選,為這個(gè)特定列覆蓋sql ddl片段(這可能導(dǎo)致無法在不同數(shù)據(jù)庫間移植)
* table 可選,定義對應(yīng)的表(默認(rèn)為主表)
* length 可選,列長度(默認(rèn)值255)
* precision 可選,列十進(jìn)制精度(decimal precision)(默認(rèn)值0)
* scale 可選,如果列十進(jìn)制數(shù)值范圍(decimal scale)可用,在此設(shè)置(默認(rèn)值0)
* @GeneratedValue —— 注解聲明了主鍵的生成策略。該注解有如下屬性
* strategy 指定生成的策略(JPA定義的),這是一個(gè)GenerationType。默認(rèn)是GenerationType. AUTO
* GenerationType.AUTO 主鍵由程序控制
* GenerationType.TABLE 使用一個(gè)特定的數(shù)據(jù)庫表格來保存主鍵
* GenerationType.IDENTITY 主鍵由數(shù)據(jù)庫自動(dòng)生成(主要是自動(dòng)增長類型)
* GenerationType.SEQUENCE 根據(jù)底層數(shù)據(jù)庫的序列來生成主鍵,條件是數(shù)據(jù)庫支持序列。(這個(gè)值要與generator一起使用)
* generator 指定生成主鍵使用的生成器(可能是orcale中的序列)。
* @SequenceGenerator —— 注解聲明了一個(gè)數(shù)據(jù)庫序列。該注解有如下屬性
* name 表示該表主鍵生成策略名稱,它被引用在@GeneratedValue中設(shè)置的“gernerator”值中
* sequenceName 表示生成策略用到的數(shù)據(jù)庫序列名稱。
* initialValue 表示主鍵初始值,默認(rèn)為0.
* allocationSize 每次主鍵值增加的大小,例如設(shè)置成1,則表示每次創(chuàng)建新記錄后自動(dòng)加1,默認(rèn)為50.
* @GenericGenerator —— 注解聲明了一個(gè)hibernate的主鍵生成策略。支持十三種策略。該注解有如下屬性
* name 指定生成器名稱
* strategy 指定具體生成器的類名(指定生成策略)。
* parameters 得到strategy指定的具體生成器所用到的參數(shù)。
* 其十三種策略(strategy屬性的值)如下:
* 1.native 對于orcale采用Sequence方式,對于MySQL和SQL Server采用identity(處境主鍵生成機(jī)制),
* native就是將主鍵的生成工作將由數(shù)據(jù)庫完成,hibernate不管(很常用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "native")
* 2.uuid 采用128位的uuid算法生成主鍵,uuid被編碼為一個(gè)32位16進(jìn)制數(shù)字的字符串。占用空間大(字符串類型)。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "uuid")
* 3.hilo 要在數(shù)據(jù)庫中建立一張額外的表,默認(rèn)表名為hibernate_unque_key,默認(rèn)字段為integer類型,名稱是next_hi(比較少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "hilo")
* 4.assigned 在插入數(shù)據(jù)的時(shí)候主鍵由程序處理(很常用),這是<generator>元素沒有指定時(shí)的默認(rèn)生成策略。等同于JPA中的AUTO。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "assigned")
* 5.identity 使用SQL Server和MySQL的自增字段,這個(gè)方法不能放到Oracle中,Oracle不支持自增字段,要設(shè)定sequence(MySQL和SQL Server中很常用)。等同于JPA中的IDENTITY
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "identity")
* 6.select 使用觸發(fā)器生成主鍵(主要用于早期的數(shù)據(jù)庫主鍵生成機(jī)制,少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "select")
* 7.sequence 調(diào)用謹(jǐn)慎數(shù)據(jù)庫的序列來生成主鍵,要設(shè)定序列名,不然hibernate無法找到。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "sequence",
* parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
* 8.seqhilo 通過hilo算法實(shí)現(xiàn),但是主鍵歷史保存在Sequence中,適用于支持Sequence的數(shù)據(jù)庫,如Orcale(比較少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "seqhilo",
* parameters = { @Parameter(name = "max_lo", value = "5") })
* 9.increnment 插入數(shù)據(jù)的時(shí)候hibernate會給主鍵添加一個(gè)自增的主鍵,但是一個(gè)hibernate實(shí)例就維護(hù)一個(gè)計(jì)數(shù)器,所以在多個(gè)實(shí)例運(yùn)行的時(shí)候不能使用這個(gè)方法。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "increnment")
* 10.foreign 使用另一個(gè)相關(guān)的對象的主鍵。通常和<on
* 例:@Id
* @GeneratedValue(generator = "idGenerator")
* @GenericGenerator(name = "idGenerator", strategy = "foreign",
* parameters = { @Parameter(name = "property", value = "info") })
* Integer id;
* @On
* EmployeeInfo info;
* 11.guid 采用數(shù)據(jù)庫底層的guid算法機(jī)制,對應(yīng)MySQL的uuid()函數(shù),SQL Server的newid()函數(shù),ORCALE的rawtohex(sys_guid())函數(shù)等
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "guid")
* 12.uuid.hex 看uudi,建議用uuid替換
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "uuid.hex")
* 13.sequence-identity sequence策略的擴(kuò)展,采用立即檢索策略來獲取sequence值,需要JDBC3.0和JDK4以上(含1.4)版本
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "sequence-identity",
* parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
*
* @On
* 方法一
* 主表: ?@On
* @PrimaryKeyJoinColumn
* public 從表類 get從表類(){return 從表對象}
* 從表:沒有主表類。
* 注意:這種方法要求主表與從表的主鍵值想對應(yīng)。
* 方法二
* 主表:?@On
* @JoinColumn(name="主表外鍵") //這里指定的是數(shù)據(jù)庫中的外鍵字段。
* public 從表類 get從表類(){return 從表類}
* 從表:@On
* public 主表類 get主表類(){return 主表對象}
* 注意:@JoinColumn是可選的。默認(rèn)值是從表變量名+"_"+從表的主鍵(注意,這里加的是主鍵。而不是主鍵對應(yīng)的變量)。
* 方法三
* 主表:@On
* @JoinTable( name="關(guān)聯(lián)表名",
* joinColumns = @JoinColumn(name="主表外鍵"),
* inverseJoinColumns = @JoinColumns(name="從表外鍵")
* )
* 從表:@On
* public 主表類 get主表類(){return 主表對象}
* @ManyToOne 設(shè)置多對一關(guān)聯(lián)
* 方法一
* @ManyToOne(cascade={CasCadeType.PERSIST,CascadeType.MERGE})
* @JoinColumn(name="外鍵")
* public 主表類 get主表類(){return 主表對象}
* 方法二
* @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
* @JoinTable(name="關(guān)聯(lián)表名",
* joinColumns = @JoinColumn(name="主表外鍵"),
* inverseJoinColumns = @JoinColumns(name="從表外鍵")
* )
* @On
* 方法一 使用這種配置,在為“一端”添加“多端”時(shí),不會修改“多端”的外鍵。在“一端”加載時(shí),不會得到“多端”。如果使用延遲加載,在讀“多端”列表時(shí)會出異常,立即加載在得到多端時(shí),是一個(gè)空集合(集合元素為0)。
* “一端”配置
* @On
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法二
* “一端”配置
* @On
* @MapKey(name="“多端”做為Key的屬性")
* public Map<“多端”做為Key的屬性的類,主表類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法三 使用這種配置,在為“一端”添加“多端”時(shí),可以修改“多端”的外鍵。
* “一端”配置
* @On
* @JoinColumn(name="“多端”外鍵")
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
*
*
*/
1、重寫只能出現(xiàn)在繼承關(guān)系之中。當(dāng)一個(gè)類繼承它的父類方法時(shí),都有機(jī)會重寫該父類的方法。一個(gè)特例是父類的方法被標(biāo)識為final。重寫的主要優(yōu)點(diǎn)是能夠定義某個(gè)子類型特有的行為。
class Animal {
public void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
}
2、對于從父類繼承來的抽象方法,要么在子類用重寫的方式設(shè)計(jì)該方法,要么把子類也標(biāo)識為抽象的。所以抽象方法可以說是必須要被重寫的方法。
3、重寫的意義。
重寫方法可以實(shí)現(xiàn)多態(tài),用父類的引用來操縱子類對象,但是在實(shí)際運(yùn)行中對象將運(yùn)行其自己特有的方法。
public class Test {
public static void main (String[] args) {
Animal h = new Horse();
h.eat();
}
}
class Animal {
public void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
public void buck(){
}
}
一個(gè)原則是:使用了什么引用,編譯器就會只調(diào)用引用類所擁有的方法。如果調(diào)用子類特有的方法,如上例的h.buck(); 編譯器會抱怨的。也就是說,編譯器只看引用類型,而不是對象類型。
4、重寫方法的規(guī)則。
若想實(shí)現(xiàn)一個(gè)合格重寫方法,而不是重載,那么必須同時(shí)滿足下面的要求!
A、重寫規(guī)則之一:重寫方法不能比被重寫方法限制有更嚴(yán)格的訪問級別。
(但是可以更廣泛,比如父類方法是包訪問權(quán)限,子類的重寫方法是public訪問權(quán)限。)
比如:Object類有個(gè)toString()方法,開始重寫這個(gè)方法的時(shí)候我們總?cè)菀淄沺ublic修飾符,編譯器當(dāng)然不會放過任何教訓(xùn)我們的機(jī)會。出錯(cuò)的原因就是:沒有加任何訪問修飾符的方法具有包訪問權(quán)限,包訪問權(quán)限比public當(dāng)然要嚴(yán)格了,所以編譯器會報(bào)錯(cuò)的。
B、重寫規(guī)則之二:參數(shù)列表必須與被重寫方法的相同。
重寫有個(gè)孿生的弟弟叫重載,也就是后面要出場的。如果子類方法的參數(shù)與父類對應(yīng)的方法不同,那么就是你認(rèn)錯(cuò)人了,那是重載,不是重寫。
C、重寫規(guī)則之三:返回類型必須與被重寫方法的返回類型相同。
父類方法A:void eat(){} 子類方法B:int eat(){} 兩者雖然參數(shù)相同,可是返回類型不同,所以不是重寫。
父類方法A:int eat(){} 子類方法B:long eat(){} 返回類型雖然兼容父類,但是不同就是不同,所以不是重寫。
D、重寫規(guī)則之四:重寫方法不能拋出新的異常或者比被重寫方法聲明的檢查異常更廣的檢查異常。但是可以拋出更少,更有限或者不拋出異常。
import java.io.*;
public class Test {
public static void main (String[] args) {
Animal h = new Horse();
try {
h.eat();
}
catch (Exception e) {
}
}
}
class Animal {
public void eat() throws Exception{
System.out.println ("Animal is eating.");
throw new Exception();
}
}
class Horse extends Animal{
public void eat() throws IOException{
System.out.println ("Horse is eating.");
throw new IOException();
}
}
這個(gè)例子中,父類拋出了檢查異常Exception,子類拋出的IOException是Exception的子類,也即是比被重寫的方法拋出了更有限的異常,這是可以的。如果反過來,父類拋出IOException,子類拋出更為寬泛的Exception,那么不會通過編譯的。
注意:這種限制只是針對檢查異常,至于運(yùn)行時(shí)異常RuntimeException及其子類不再這個(gè)限制之中。
E、重寫規(guī)則之五:不能重寫被標(biāo)識為final的方法。
F、重寫規(guī)則之六:如果一個(gè)方法不能被繼承,則不能重寫它。
比較典型的就是父類的private方法。下例會產(chǎn)生一個(gè)有趣的現(xiàn)象。
public class Test {
public static void main (String[] args) {
//Animal h = new Horse();
Horse h = new Horse();
h.eat();
}
}
class Animal {
private void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
}
這段代碼是能通過編譯的。表面上看來違反了第六條規(guī)則,但實(shí)際上那是一點(diǎn)巧合。Animal類的eat()方法不能被繼承,因此Horse類中的eat()方法是一個(gè)全新的方法,不是重寫也不是重載,只是一個(gè)只屬于Horse類的全新的方法!這點(diǎn)讓很多人迷惑了,但是也不是那么難以理解。
main()方法如果是這樣:
Animal h = new Horse();
//Horse h = new Horse();
h.eat();
編譯器會報(bào)錯(cuò),為什么呢?Horse類的eat()方法是public的啊!應(yīng)該可以調(diào)用啊!請牢記,多態(tài)只看父類引用的方法,而不看子類對象的方法!
二、方法的重載。
重載是有好的,它不要求你在調(diào)用一個(gè)方法之前轉(zhuǎn)換數(shù)據(jù)類型,它會自動(dòng)地尋找匹配的方法。方法的重載是在編譯時(shí)刻就決定調(diào)用哪個(gè)方法了,和重寫不同。最最常用的地方就是構(gòu)造器的重載。
1、基本數(shù)據(jù)類型參數(shù)的重載。
public class Test {
static void method(byte b){
System.out.println ("method:byte");
}
static void method(short s){
System.out.println ("method:short");
}
static void method(int i){
System.out.println ("method:int");
}
static void method(float f){
System.out.println ("method:float");
}
static void method(double d){
System.out.println ("method:double");
}
public static void main (String[] args) {
method((byte)1);
method('c');
method(1);
method(1L);
method(1.1);
method(1.1f);
}
}
輸出結(jié)果:
method:byte
method:int
method:int
method:float
method:double
method:float
可以看出:首先要尋找的是數(shù)據(jù)類型正好匹配方法。如果找不到,那么就提升為表達(dá)能力更強(qiáng)的數(shù)據(jù)類型,如上例沒有正好容納long的整數(shù)類型,那么就轉(zhuǎn)換為float類型的。如果通過提升也不能找到合適的兼容類型,那么編譯器就會報(bào)錯(cuò)。反正是不會自動(dòng)轉(zhuǎn)換為較小的數(shù)據(jù)類型的,必須自己強(qiáng)制轉(zhuǎn)換,自己來承擔(dān)轉(zhuǎn)變后果。
char類型比較特殊,如果找不到正好匹配的類型,它會轉(zhuǎn)化為int而不是short,雖然char是16位的。
2、重載方法的規(guī)則。
A、被重載的方法必須改變參數(shù)列表。
參數(shù)必須不同,這是最重要的!不同有兩個(gè)方面,參數(shù)的個(gè)數(shù),參數(shù)的類型,參數(shù)的順序。
B、被重載的方法與返回類型無關(guān)。
也就是說,不能通過返回類型來區(qū)分重載方法。
C、被重載的方法可以改變訪問修飾符。
沒有重寫方法那樣嚴(yán)格的限制。
D、被重載的方法可以聲明新的或者更廣的檢查異常。
沒有重寫方法那樣嚴(yán)格的限制。
E、方法能夠在一個(gè)類中或者在一個(gè)子類中被重載。
3、帶對象引用參數(shù)的方法重載。
class Animal {}
class Horse extends Animal{}
public class Test {
static void method(Animal a){
System.out.println ("Animal is called.");
}
static void method(Horse h){
System.out.println ("Horse is called.");
}
public static void main (String[] args) {
Animal a = new Animal();
Horse h = new Horse();
Animal ah = new Horse();
method(a);
method(h);
method(ah);
}
}
輸出結(jié)果是:
Animal is called.
Horse is called.
Animal is called.
前兩個(gè)輸出沒有任何問題。第三個(gè)方法為什么不是輸出“Horse is called.”呢?還是那句老話,要看引用類型而不是對象類型,方法重載是在編譯時(shí)刻就決定的了,引用類型決定了調(diào)用哪個(gè)版本的重載方法。
4、重載和重寫方法區(qū)別的小結(jié)。
如果能徹底弄明白下面的例子,說明你對重載和重寫非常了解了,可以結(jié)束這節(jié)的復(fù)習(xí)了。
class Animal {
public void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
public void eat(String food){
System.out.println ("Horse is eating " + food);
}
}
public class Test {
public static void main (String[] args) {
Animal a = new Animal();
Horse h = new Horse();
Animal ah = new Horse();
a.eat();
h.eat();
h.eat("apple");
ah.eat();
//a.eat("apple");
//ah.eat("apple");
}
}
四個(gè)輸出分別是什么?被注釋的兩條語句為什么不能通過編譯?
第一條:a.eat(); 普通的方法調(diào)用,沒有多態(tài),沒什么技術(shù)含量。調(diào)用了Animal類的eat()方法,輸出:Animal is eating.
第二條:h.eat(); 普通的方法調(diào)用,也沒什么技術(shù)含量。調(diào)用了Horse類的eat()方法,輸出:Horse is eating.
第三條:h.eat("apple"); 重載。Horse類的兩個(gè)eat()方法重載。調(diào)用了Horse類的eat(String food)方法,輸出:Horse is eating apple
第四條:ah.eat(); 多態(tài)。前面有例子了,不難理解。輸出:Horse is eating.
第五條:a.eat("apple"); 低級的錯(cuò)誤,Animal類中沒有eat(String food)方法。因此不能通過編譯。
第六條:ah.eat("apple"); 關(guān)鍵點(diǎn)就在這里。解決的方法還是那句老話,不能看對象類型,要看引用類型。Animal類中沒有eat(String food)方法。因此不能通過編譯。
小結(jié)一下:多態(tài)不決定調(diào)用哪個(gè)重載版本;多態(tài)只有在決定哪個(gè)重寫版本時(shí)才起作用。
重載對應(yīng)編譯時(shí),重寫對應(yīng)運(yùn)行時(shí)。夠簡潔的了吧!
三、構(gòu)造方法。
構(gòu)造方法是一種特殊的方法,沒有構(gòu)造方法就不能創(chuàng)建一個(gè)新對象。實(shí)際上,不僅要調(diào)用對象實(shí)際類型的構(gòu)造方法,還要調(diào)用其父類的構(gòu)造方法,向上追溯,直到Object類。構(gòu)造方法不必顯式地調(diào)用,當(dāng)使用new關(guān)鍵字時(shí),相應(yīng)的構(gòu)造方法會自動(dòng)被調(diào)用。
1、構(gòu)造方法的規(guī)則。
A、構(gòu)造方法能使用任何訪問修飾符。包括private,事實(shí)上java類庫有很多都是這樣的,設(shè)計(jì)者不希望使用者創(chuàng)建該類的對象。
B、構(gòu)造方法的名稱必須與類名相同。這樣使得構(gòu)造方法與眾不同,如果我們遵守sun的編碼規(guī)范,似乎只有構(gòu)造方法的首字母是大寫的。
C、構(gòu)造方法不能有返回類型。
反過來說,有返回類型的不是構(gòu)造方法
public class Test {
int Test(){
return 1;
}
}
這個(gè)方法是什么東西?一個(gè)冒充李逵的李鬼而已,int Test()和其他任何普通方法沒什么兩樣,就是普通的方法!只不過看起來很惡心,類似惡心的東西在考試卷子里比較多。
D、如果不在類中創(chuàng)建自己的構(gòu)造方法,編譯器會自動(dòng)生成默認(rèn)的不帶參數(shù)的構(gòu)造函數(shù)。
這點(diǎn)很容易驗(yàn)證!寫一個(gè)這樣簡單的類,編譯。
class Test {
}
對生成的Test.class文件反編譯:javap Test,可以看到:
D:"JavaCode"bin>javap Test
Compiled from "Test.java"
class Test extends java.lang.Object{
Test();
}
看到編譯器自動(dòng)添加的默認(rèn)構(gòu)造函數(shù)了吧!
E、如果只創(chuàng)建了帶參數(shù)的構(gòu)造方法,那么編譯器不會自動(dòng)添加無參的構(gòu)造方法的!
F、在每個(gè)構(gòu)造方法中,如果使用了重載構(gòu)造函數(shù)this()方法,或者父類的構(gòu)造方法super()方法,那么this()方法或者super()方法必須放在第一行。而且這兩個(gè)方法只能選擇一個(gè),因此它們之間沒有順序問題。
G、除了編譯器生成的構(gòu)造方法,而且沒有顯式地調(diào)用super()方法,那么編譯器會插入一個(gè)super()無參調(diào)用。
H、抽象類有構(gòu)造方法。
四、靜態(tài)方法的重載與重寫(覆蓋)。
1、靜態(tài)方法是不能被覆蓋的。可以分兩種情況討論:
A、子類的非靜態(tài)方法“覆蓋”父類的靜態(tài)方法。
這種情況下,是不能通過編譯的。
static void print(){
System.out.println ("in father method");
}
}
class Child extends Father{
void print(){
System.out.println ("in child method");
}
}
static方法表示該方法不關(guān)聯(lián)具體的類的對象,可以通過類名直接調(diào)用,也就是編譯的前期就綁定了,不存在后期動(dòng)態(tài)綁定,也就是不能實(shí)現(xiàn)多態(tài)。子類的非靜態(tài)方法是與具體的對象綁定的,兩者有著不同的含義。
B、子類的靜態(tài)方法“覆蓋”父類靜態(tài)方法。
這個(gè)覆蓋依然是帶引號的。事實(shí)上把上面那個(gè)例子Child類的print方法前面加上static修飾符,確實(shí)能通過編譯!但是不要以為這就是多態(tài)!多態(tài)的特點(diǎn)是動(dòng)態(tài)綁定,看下面的例子:
static void print(){
System.out.println ("in father method");
}
}
class Child extends Father{
static void print(){
System.out.println ("in child method");
}
}
class Test{
public static void main (String[] args) {
Father f =new Child();
f.print();
}
}
輸出結(jié)果是:in father method
從這個(gè)結(jié)果可以看出,并沒有實(shí)現(xiàn)多態(tài)。
但是這種形式很迷惑人,貌似多態(tài),實(shí)際編程中千萬不要這樣搞,會把大家搞懵的!
它不符合覆蓋表現(xiàn)出來的特性,不應(yīng)該算是覆蓋!
總而言之,靜態(tài)方法不能被覆蓋。
2、靜態(tài)方法可以和非靜態(tài)方法一樣被重載。
這樣的例子太多了,我不想寫例程了。看看java類庫中很多這樣的例子。
如java.util.Arrays類的一堆重載的binarySearch方法。
在這里提一下是因?yàn)椴橘Y料時(shí)看到這樣的話“sun的SL275課程說,靜態(tài)方法只能控制靜態(tài)變量(他們本身沒有),靜態(tài)方法不能被重載和覆蓋……”
大家不要相信啊!可以重載的。而且靜態(tài)與非靜態(tài)方法可以重載。
從重載的機(jī)制很容易就理解了,重載是在編譯時(shí)刻就決定的了,非靜態(tài)方法都可以,靜態(tài)方法怎么可能不會呢?
1、String中的每個(gè)字符都是一個(gè)16位的Unicode字符,用Unicode很容易表達(dá)豐富的國際化字符集,比如很好的中文支持。甚至Java的標(biāo)識符都可以用漢字,但是沒人會用吧(只在一本清華的《Java2實(shí)用教程》看過)。
2、判斷空字符串。根據(jù)需要自己選擇某個(gè)或者它們的組合
if ( s == null ) //從引用的角度
if ( s.length() == 0 ) //從長度判別
if ( s.trim().length () == 0 ) //是否有多個(gè)空白字符
trim()方法的作用是是移除前導(dǎo)和尾部的Unicode值小于'"u0020'的字符,并返回“修剪”好的字符串。這種方法很常用,比如需要用戶輸入用戶名,用戶不小心加了前導(dǎo)或者尾部空格,一個(gè)好的程序應(yīng)該知道用戶不是故意的,即使是故意的也應(yīng)該智能點(diǎn)地處理。
判斷空串是很常用的操作,但是Java類庫直到1.6才提供了isEmpty()方法。當(dāng)且僅當(dāng) length() 為 0 時(shí)返回 true。
3、未初始化、空串""與null。它們是不同的概念。對未初始化的對象操作會被編譯器擋在門外;null是一個(gè)特殊的初始化值,是一個(gè)不指向任何對象的引用,對引用為null的對象操作會在運(yùn)行時(shí)拋出異常NullPointerException;而空串是長度為0的字符串,和別的字符串的唯一區(qū)別就是長度為0。
例子:
public class StringTest{
static String s1;
public static void main(String[] args) {
String s2;
String s3 = "";
System.out.print(s1.isEmpty()); //運(yùn)行時(shí)異常
System.out.print(s2.isEmpty()); //編譯出錯(cuò)
System.out.print(s3.isEmpty()); //ok!輸出true
}
}
4、String類的方法很多,在編寫相關(guān)代碼的時(shí)候看看JDK文檔時(shí)有好處的,要不然花了大量時(shí)間實(shí)現(xiàn)一個(gè)已經(jīng)存在的方法是很不值得的,因?yàn)榫帉憽y試、維護(hù)自己的代碼使項(xiàng)目的成本增加,利潤減少,嚴(yán)重的話會導(dǎo)致開不出工資……
5、字符串的比較。
Java不允許自定義操作符重載,因此字符串的比較要用compareTo() 或者 compareToIgnoreCase()。s1.compareTo(s2),返回值大于0則,則前者大;等于0,一般大;小于0,后者大。比較的依據(jù)是字符串中各個(gè)字符的Unicode值。
6、toString()方法。
Java的任何對象都有toString()方法,是從Object對象繼承而來的。它的作用就是讓對象在輸出時(shí)看起來更有意義,而不是奇怪的對象的內(nèi)存地址。對測試也是很有幫助的。
7、String對象是不變的!可以變化的是String對象的引用。
String name = "ray";
name.concat("long"); //字符串連接
System.out.println(name); //輸出name,ok,還是"ray"
name = name.concat("long"); //把字符串對象連接的結(jié)果賦給了name引用
System.out.println(name); //輸出name,oh!,變成了"raylong"
上述三條語句其實(shí)產(chǎn)生了3個(gè)String對象,"ray","long","raylong"。第2條語句確實(shí)產(chǎn)生了"raylong"字符串,但是沒有指定把該字符串的引用賦給誰,因此沒有改變name引用。第3條語句根據(jù)不變性,并沒有改變"ray",JVM創(chuàng)建了一個(gè)新的對象,把"ray","long"的連接賦給了name引用,因此引用變了,但是原對象沒變。
8、String的不變性的機(jī)制顯然會在String常量內(nèi)有大量的冗余。如:"1" + "2" + "3" +......+ "n" 產(chǎn)生了n+(n+1)個(gè)String對象!因此Java為了更有效地使用內(nèi)存,JVM留出一塊特殊的內(nèi)存區(qū)域,被稱為“String常量池”。對String多么照顧啊!當(dāng)編譯器遇見String常量的時(shí)候,它檢查該池內(nèi)是否已經(jīng)存在相同的String常量。如果找到,就把新常量的引用指向現(xiàn)有的String,不創(chuàng)建任何新的String常量對象。
那么就可能出現(xiàn)多個(gè)引用指向同一個(gè)String常量,會不會有別名的危險(xiǎn)呢?No problem!String對象的不變性可以保證不會出現(xiàn)別名問題!這是String對象與普通對象的一點(diǎn)區(qū)別。
乍看起來這是底層的機(jī)制,對我們編程沒什么影響。而且這種機(jī)制會大幅度提高String的效率,實(shí)際上卻不是這樣。為連接n個(gè)字符串使用字符串連接操作時(shí),要消耗的時(shí)間是n的平方級!因?yàn)槊績蓚€(gè)字符串連接,它們的內(nèi)容都要被復(fù)制。因此在處理大量的字符串連接時(shí),而且要求性能時(shí),我們不要用String,StringBuffer是更好的選擇。
8、StringBuffer類。StringBuffer類是可變的,不會在字符串常量池中,而是在堆中,不會留下一大堆無用的對象。而且它可將字符串緩沖區(qū)安全地用于多個(gè)線程。每個(gè)StringBuffer對象都有一定的容量。只要StringBuffer對象所包含的字符序列的長度沒有超出此容量,就無需分配新的內(nèi)部緩沖區(qū)數(shù)組。如果內(nèi)部緩沖區(qū)溢出,則此容量自動(dòng)增大。這個(gè)固定的容量是16個(gè)字符。我給這種算法起個(gè)名字叫“添飯算法”。先給你一滿碗飯,不夠了再給你一滿碗飯。
例子:
StringBuffer sb = new StringBuffer(); //初始容量為 16 個(gè)字符
sb.append("1234"); //這是4個(gè)字符,那么16個(gè)字符的容量就足夠了,沒有溢出
System.out.println(sb.length()); //輸出字符串長度是4
System.out.println(sb.capacity()); //輸出該字符串緩沖區(qū)的容量是16
sb.append("12345678901234567"); //這是17個(gè)字符,16個(gè)字符的容量不夠了,擴(kuò)容為17+16個(gè)字符的容量
System.out.println(sb.length()); //輸出字符串長度是17
System.out.println(sb.capacity()); //輸出該字符串緩沖區(qū)的容量是34
sb.append("890").reverse().insert(10,"-");
System.out.println(sb); //輸出0987654321-09876543214321
字符串的長度和字符緩沖區(qū)的容量是兩個(gè)概念,注意區(qū)別。
還有串聯(lián)的方式看起來是不是很酷!用返回值連接起來可以實(shí)現(xiàn)這種簡潔和優(yōu)雅。
10、StringBuilder類。 從J2SE 5.0 提供了StringBuilder類,它和StringBuffer類是孿生兄弟,很像。它存在的價(jià)值在于:對字符串操作的效率更高。不足的是線程安全無法保證,不保證同步。那么兩者性能到底差多少呢?很多!
請參閱:http://book.csdn.net/bookfiles/135/1001354628.shtml
實(shí)踐:
單個(gè)線程的時(shí)候使用StringBuilder類,以提高效率,而且它的API和StringBuffer兼容,不需要額外的學(xué)習(xí)成本,物美價(jià)廉。多線程時(shí)使用StringBuffer,以保證安全。
11、字符串的比較。
下面這條可能會讓你暈,所以你可以選擇看或者不看。它不會對你的職業(yè)生涯造成任何影響。而且謹(jǐn)記一條,比較字符串要用equals()就ok了!一旦用了“==”就會出現(xiàn)很怪異的現(xiàn)象。之所以把這部分放在最后,是想節(jié)省大家的時(shí)間,因?yàn)檫@條又臭又長。推薦三種人:一、沒事閑著型。二、想深入地理解Java的字符串,即使明明知道學(xué)了也沒用。三、和我一樣愛好研究“茴”字有幾種寫法。
還是那句老話,String太特殊了,以至于某些規(guī)則對String不起作用。個(gè)人感覺這種特殊性并不好。看例子:
例子A:
String str1 = "java";
String str2 = "java";
System.out.print(str1==str2);
地球上有點(diǎn)Java基礎(chǔ)的人都知道會輸出false,因?yàn)?=比較的是引用,equals比較的是內(nèi)容。不是我忽悠大家,你們可以在自己的機(jī)子上運(yùn)行一下,結(jié)果是true!原因很簡單,String對象被放進(jìn)常量池里了,再次出現(xiàn)“java”字符串的時(shí)候,JVM很興奮地把str2的引用也指向了“java”對象,它認(rèn)為自己節(jié)省了內(nèi)存開銷。不難理解吧 呵呵
例子B:
String str1 = new String("java");
String str2 = new String("java");
System.out.print(str1==str2);
看過上例的都學(xué)聰明了,這次肯定會輸出true!很不幸,JVM并沒有這么做,結(jié)果是false。原因很簡單,例子A中那種聲明的方式確實(shí)是在String常量池創(chuàng)建“java”對象,但是一旦看到new關(guān)鍵字,JVM會在堆中為String分配空間。兩者聲明方式貌合神離,這也是我把“如何創(chuàng)建字符串對象”放到后面來講的原因。大家要沉住氣,還有一個(gè)例子。
例子C:
String str1 = "java";
String str2 = "blog";
String s = str1+str2;
System.out.print(s=="javablog");
再看這個(gè)例子,很多同志不敢妄言是true還是false了吧。愛玩腦筋急轉(zhuǎn)彎的人會說是false吧……恭喜你,你會搶答了!把那個(gè)“吧”字去掉你就完全正確。原因很簡單,JVM確實(shí)會對型如String str1 = "java"; 的String對象放在字符串常量池里,但是它是在編譯時(shí)刻那么做的,而String s = str1+str2; 是在運(yùn)行時(shí)刻才能知道(我們當(dāng)然一眼就看穿了,可是Java必須在運(yùn)行時(shí)才知道的,人腦和電腦的結(jié)構(gòu)不同),也就是說str1+str2是在堆里創(chuàng)建的,s引用當(dāng)然不可能指向字符串常量池里的對象。沒崩潰的人繼續(xù)看例子D。
例子D:
String s1 = "java";
String s2 = new String("java");
System.out.print(s1.intern()==s2.intern());
intern()是什么東東?反正結(jié)果是true。如果沒用過這個(gè)方法,而且訓(xùn)練有素的程序員會去看JDK文檔了。簡單點(diǎn)說就是用intern()方法就可以用“==”比較字符串的內(nèi)容了。在我看到intern()方法到底有什么用之前,我認(rèn)為它太多余了。其實(shí)我寫的這一條也很多余,intern()方法還存在諸多的問題,如效率、實(shí)現(xiàn)上的不統(tǒng)一……
例子E:
String str1 = "java";
String str2 = new String("java");
System.out.print(str1.equals(str2));
無論在常量池還是堆中的對象,用equals()方法比較的就是內(nèi)容,就這么簡單!看完此條的人一定很后悔,但是在開始我勸你別看了……
后記:用彪哥的話說“有意思嗎?”,確實(shí)沒勁。在寫這段的時(shí)候我也是思量再三,感覺自己像孔乙己炫耀“茴”字有幾種寫法。我查了一下茴 ,回,囘,囬,還有一種是“口”字里面有個(gè)“目”字,后面這四個(gè)都加上草字頭……