遇到System.Data.OracleClient 需要 Oracle 客戶端軟件 8.1.7 或更高版本。一般第一反映都是會出處理 oracle_home 文件夾權限。可是有時時候 不管你怎么擺弄權限,怎么iisreset,怎么重啟電腦都解決不了,cmd path 明明可以看到有oracle_home 路徑啊。問題在于。環(huán)境變量中, ora10gInstant 精簡客戶端默認把變量添加到 Administrator 的用戶變量了,我們要做的是把 用戶變量中的 Path 值 轉到 系統(tǒng)變量中的 Path 中。
在項目中使用Spring的注解,關于spring的注解,由兩種注解方式,
基于注釋(Annotation)的配置有越來越流行的趨勢,Spring 2.5 順應這種趨勢,提供了完全基于注釋配置 Bean、裝配 Bean 的功能,您可以使用基于注釋的 Spring IoC 替換原來基于 XML 的配置。本文通過實例詳細講述了 Spring 2.5 基于注釋 IoC 功能的使用。
<!--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-->
概述
注釋配置相對于 XML 配置具有很多的優(yōu)勢:
- 它可以充分利用 Java 的反射機制獲取類結構信息,這些信息可以有效減少配置的工作。如使用 JPA 注釋配置 ORM 映射時,我們就不需要指定 PO 的屬性名、類型等信息,如果關系表字段和 PO 屬性名、類型都一致,您甚至無需編寫任務屬性映射信息——因為這些信息都可以通過 Java 反射機制獲取。
- 注釋和 Java 代碼位于一個文件中,而 XML 配置采用獨立的配置文件,大多數配置信息在程序開發(fā)完成后都不會調整,如果配置信息和 Java 代碼放在一起,有助于增強程序的內聚性。而采用獨立的 XML 配置文件,程序員在編寫一個功能時,往往需要在程序文件和配置文件中不停切換,這種思維上的不連貫會降低開發(fā)效率。
因此在很多情況下,注釋配置比 XML 配置更受歡迎,注釋配置有進一步流行的趨勢。Spring 2.5 的一大增強就是引入了很多注釋類,現(xiàn)在您已經可以使用注釋配置完成大部分 XML 配置的功能。在這篇文章里,我們將向您講述使用注釋進行 Bean 定義和依賴注入的內容。
使用 @Autowired 注釋
Spring 2.5 引入了 @Autowired
注釋,它可以對類成員變量、方法及構造函數進行標注,完成自動裝配的工作。
Spring 通過一個 BeanPostProcessor
對 @Autowired
進行解析,所以要讓 @Autowired
起作用必須事先在 Spring 容器中聲明 AutowiredAnnotationBeanPostProcessor
Bean。
在Spring中配置如下:
<!-- 該 BeanPostProcessor 將自動起作用,對標注 @Autowired 的 Bean 進行自動注入 -->
<bean class="org.springframework.beans.factory.annotation.
AutowiredAnnotationBeanPostProcessor"/>
當 Spring 容器啟動時,AutowiredAnnotationBeanPostProcessor
將掃描 Spring 容器中所有 Bean,當發(fā)現(xiàn) Bean 中擁有 @Autowired
注釋時就找到和其匹配(默認按類型匹配)的 Bean,并注入到對應的地方中去。
按照上面的配置,Spring 將直接采用 Java 反射機制對 Boss 中的 car
和 office
這兩個私有成員變量進行自動注入。所以對成員變量使用 @Autowired
后,您大可將它們的 setter 方法(setCar()
和 setOffice()
)從 Boss 中刪除。
當然,您也可以通過 @Autowired
對方法或構造函數進行標注,
當候選 Bean 數目不為 1 時的應對方法
在默認情況下使用 @Autowired
注釋進行自動注入時,Spring 容器中匹配的候選 Bean 數目必須有且僅有一個。當找不到一個匹配的 Bean 時,Spring 容器將拋出 BeanCreationException
異常,并指出必須至少擁有一個匹配的 Bean。
當不能確定 Spring 容器中一定擁有某個類的 Bean 時,可以在需要自動注入該類 Bean 的地方可以使用 @Autowired(required = false)
,這等于告訴 Spring:在找不到匹配 Bean 時也不報錯。
一般情況下,使用 @Autowired
的地方都是需要注入 Bean 的,使用了自動注入而又允許不注入的情況一般僅會在開發(fā)期或測試期碰到(如為了快速啟動 Spring 容器,僅引入一些模塊的 Spring 配置文件),所以 @Autowired(required = false)
會很少用到。
和找不到一個類型匹配 Bean 相反的一個錯誤是:如果 Spring 容器中擁有多個候選 Bean,Spring 容器在啟動時也會拋出 BeanCreationException
異常。
Spring 允許我們通過 @Qualifier
注釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:
清單 13. 使用 @Qualifier 注釋指定注入 Bean 的名稱
@Autowired
public void setOffice(@Qualifier("office")Office office) {
this.office = office;
}
|
@Qualifier("office")
中的 office
是 Bean 的名稱,所以 @Autowired
和 @Qualifier
結合使用時,自動注入的策略就從 byType 轉變成 byName 了。@Autowired
可以對成員變量、方法以及構造函數進行注釋,而 @Qualifier
的標注對象是成員變量、方法入參、構造函數入參。正是由于注釋對象的不同,所以 Spring 不將 @Autowired
和 @Qualifier
統(tǒng)一成一個注釋類。下面是對成員變量和構造函數入參進行注釋的代碼:
對成員變量進行注釋:
對成員變量使用 @Qualifier 注釋
public class Boss {
@Autowired
private Car car;
@Autowired
@Qualifier("office")
private Office office;
…
}
|
對構造函數入參進行注釋:
清單 15. 對構造函數變量使用 @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
結合使用,是對 @Autowired
有益的補充。一般來講,@Qualifier
對方法簽名中入參進行注釋會降低代碼的可讀性,而對成員變量注釋則相對好一些。
使用 JSR-250 的注釋
Spring 不但支持自己定義的 @Autowired
的注釋,還支持幾個由 JSR-250 規(guī)范定義的注釋,它們分別是 @Resource
、@PostConstruct
以及 @PreDestroy
。
@Resource
@Resource
的作用相當于 @Autowired
,只不過 @Autowired
按 byType 自動注入,面 @Resource
默認按 byName 自動注入罷了。@Resource
有兩個屬性是比較重要的,分別是 name 和 type,Spring 將 @Resource
注釋的 name 屬性解析為 Bean 的名字,而 type 屬性則解析為 Bean 的類型。所以如果使用 name 屬性,則使用 byName 的自動注入策略,而使用 type 屬性時則使用 byType 自動注入策略。如果既不指定 name 也不指定 type 屬性,這時將通過反射機制使用 byName 自動注入策略。
Resource 注釋類位于 Spring 發(fā)布包的 lib/j2ee/common-annotations.jar 類包中,因此在使用之前必須將其加入到項目的類庫中。來看一個使用 @Resource
的例子:
清單 16. 使用 @Resource 注釋的 Boss.java
package com.baobaotao;
import javax.annotation.Resource;
public class Boss {
// 自動注入類型為 Car 的 Bean
@Resource
private Car car;
// 自動注入 bean 名稱為 office 的 Bean
@Resource(name = "office")
private Office office;
}
|
一般情況下,我們無需使用類似于 @Resource(type=Car.class)
的注釋方式,因為 Bean 的類型信息可以通過 Java 反射從代碼中獲取。
要讓 JSR-250 的注釋生效,除了在 Bean 類中標注這些注釋外,還需要在 Spring 容器中注冊一個負責處理這些注釋的 BeanPostProcessor
:
<bean
class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
|
CommonAnnotationBeanPostProcessor
實現(xiàn)了 BeanPostProcessor
接口,它負責掃描使用了 JSR-250 注釋的 Bean,并對它們進行相應的操作。
@PostConstruct 和 @PreDestroy
Spring 容器中的 Bean 是有生命周期的,Spring 允許在 Bean 在初始化完成后以及 Bean 銷毀前執(zhí)行特定的操作,您既可以通過實現(xiàn) InitializingBean/DisposableBean 接口來定制初始化之后 / 銷毀之前的操作方法,也可以通過 <bean> 元素的 init-method/destroy-method 屬性指定初始化之后 / 銷毀之前調用的操作方法。關于 Spring 的生命周期,筆者在《精通 Spring 2.x—企業(yè)應用開發(fā)精解》第 3 章進行了詳細的描述,有興趣的讀者可以查閱。
JSR-250 為初始化之后/銷毀之前方法的指定定義了兩個注釋類,分別是 @PostConstruct 和 @PreDestroy,這兩個注釋只能應用于方法上。標注了 @PostConstruct 注釋的方法將在類實例化后調用,而標注了 @PreDestroy 的方法將在類銷毀之前調用。
清單 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");
}
…
}
|
您只需要在方法前標注 @PostConstruct
或 @PreDestroy
,這些方法就會在 Bean 初始化后或銷毀之前被 Spring 容器執(zhí)行了。
我們知道,不管是通過實現(xiàn) InitializingBean
/DisposableBean
接口,還是通過 <bean> 元素的 init-method/destroy-method
屬性進行配置,都只能為 Bean 指定一個初始化 / 銷毀的方法。但是使用 @PostConstruct
和 @PreDestroy
注釋卻可以指定多個初始化 / 銷毀方法,那些被標注 @PostConstruct
或 @PreDestroy
注釋的方法都會在初始化 / 銷毀時被執(zhí)行。
通過以下的測試代碼,您將可以看到 Bean 的初始化 / 銷毀方法是如何被執(zhí)行的:
清單 18. 測試類代碼
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();// 關閉 Spring 容器,以觸發(fā) Bean 銷毀方法的執(zhí)行
}
}
|
這時,您將看到標注了 @PostConstruct
的 postConstruct1()
方法將在 Spring 容器啟動時,創(chuàng)建 Boss
Bean 的時候被觸發(fā)執(zhí)行,而標注了 @PreDestroy
注釋的 preDestroy1()
方法將在 Spring 容器關閉前銷毀 Boss
Bean 的時候被觸發(fā)執(zhí)行。
使用 <context:annotation-config/> 簡化配置
Spring 2.1 添加了一個新的 context 的 Schema 命名空間,該命名空間對注釋驅動、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道注釋本身是不會做任何事情的,它僅提供元數據信息。要使元數據信息真正起作用,必須讓負責處理這些元數據的處理器工作起來。
而我們前面所介紹的 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
就是處理這些注釋元數據的處理器。但是直接在 Spring 配置文件中定義這些 Bean 顯得比較笨拙。Spring 為我們提供了一種方便的注冊這些 BeanPostProcessor
的方式,這就是 <context:annotation-config/>。請看下面的配置:
清單 19. 調整 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 個 BeanPostProcessor。
在配置文件中使用 context 命名空間之前,必須在 <beans> 元素中聲明 context 命名空間。
使用 @Component
雖然我們可以通過 @Autowired
或 @Resource
在 Bean 類中使用自動注入功能,但是 Bean 還是在 XML 文件中通過 <bean> 進行定義 —— 也就是說,在 XML 配置文件中定義 Bean,通過 @Autowired
或 @Resource
為 Bean 的成員變量、方法入參或構造函數入參提供自動注入的功能。能否也通過注釋定義 Bean,從 XML 配置文件中完全移除 Bean 定義的配置呢?答案是肯定的,我們通過 Spring 2.5 提供的 @Component
注釋就可以達到這個目標了。
下面,我們完全使用注釋定義 Bean 并完成 Bean 之間裝配:
清單 20. 使用 @Component 注釋的 Car.java
package com.baobaotao;
import org.springframework.stereotype.Component;
@Component
public class Car {
…
}
|
僅需要在類定義處,使用 @Component
注釋就可以將一個類定義了 Spring 容器中的 Bean。下面的代碼將 Office
定義為一個 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
有一個可選的入參,用于指定 Bean 的名稱,在 Boss 中,我們就將 Bean 名稱定義為“boss
”。一般情況下,Bean 都是 singleton 的,需要注入 Bean 的地方僅需要通過 byType 策略就可以自動注入了,所以大可不必指定 Bean 的名稱。
在使用 @Component
注釋后,Spring 容器必須啟用類掃描機制以啟用注釋驅動 Bean 定義和注釋驅動 Bean 自動注入的策略。Spring 2.5 對 context 命名空間進行了擴展,提供了這一功能,請看下面的配置:
清單 23. 簡化版的 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:component-scan base-package="com.baobaotao"/>
</beans>
|
這里,所有通過 <bean> 元素定義 Bean 的配置內容已經被移除,僅需要添加一行 <context:component-scan/> 配置就解決所有問題了——Spring XML 配置文件得到了極致的簡化(當然配置元數據還是需要的,只不過以注釋形式存在罷了)。<context:component-scan/> 的 base-package 屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會被處理。
<context:component-scan/> 還允許定義過濾器將基包下的某些類納入或排除。Spring 支持以下 4 種類型的過濾方式,通過下表說明:
表 1. 掃描過濾方式
過濾器類型 |
說明 |
注釋 |
假如 com.baobaotao.SomeAnnotation 是一個注釋類,我們可以將使用該注釋的類過濾出來。 |
類名指定 |
通過全限定類名進行過濾,如您可以指定將 com.baobaotao.Boss 納入掃描,而將 com.baobaotao.Car 排除在外。 |
正則表達式 |
通過正則表達式定義過濾的類,如下所示: com\.baobaotao\.Default.* |
AspectJ 表達式 |
通過 AspectJ 表達式定義過濾的類,如下所示: com. baobaotao..*Service+ |
下面是一個簡單的例子:
<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/> 配置項不但啟用了對類包進行掃描以實施注釋驅動 Bean 定義的功能,同時還啟用了注釋驅動自動注入的功能(即還隱式地在內部注冊了 AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
),因此當使用 <context:component-scan/> 后,就可以將 <context:annotation-config/> 移除了。
默認情況下通過 @Component
定義的 Bean 都是 singleton 的,如果需要使用其它作用范圍的 Bean,可以通過 @Scope
注釋來達到目標,如以下代碼所示:
清單 24. 通過 @Scope 指定 Bean 的作用范圍
package com.baobaotao;
import org.springframework.context.annotation.Scope;
…
@Scope("prototype")
@Component("boss")
public class Boss {
…
}
|
這樣,當從 Spring 容器中獲取 boss
Bean 時,每次返回的都是新的實例了。
采用具有特殊語義的注釋
Spring 2.5 中除了提供 @Component
注釋外,還定義了幾個擁有特殊語義的注釋,它們分別是:@Repository
、@Service
和 @Controller
。在目前的 Spring 版本中,這 3 個注釋和 @Component
是等效的,但是從注釋類的命名上,很容易看出這 3 個注釋分別和持久層、業(yè)務層和控制層(Web 層)相對應。雖然目前這 3 個注釋和 @Component
相比沒有什么新意,但 Spring 將在以后的版本中為它們添加特殊的功能。所以,如果 Web 應用程序采用了經典的三層分層結構的話,最好在持久層、業(yè)務層和控制層分別采用 @Repository
、@Service
和 @Controller
對分層中的類進行注釋,而用 @Component
對那些比較中立的類進行注釋。
注釋配置和 XML 配置的適用場合
是否有了這些 IOC 注釋,我們就可以完全摒除原來 XML 配置的方式呢?答案是否定的。有以下幾點原因:
- 注釋配置不一定在先天上優(yōu)于 XML 配置。如果 Bean 的依賴關系是固定的,(如 Service 使用了哪幾個 DAO 類),這種配置信息不會在部署時發(fā)生調整,那么注釋配置優(yōu)于 XML 配置;反之如果這種依賴關系會在部署時發(fā)生調整,XML 配置顯然又優(yōu)于注釋配置,因為注釋是對 Java 源代碼的調整,您需要重新改寫源代碼并重新編譯才可以實施調整。
- 如果 Bean 不是自己編寫的類(如
JdbcTemplate
、SessionFactoryBean
等),注釋配置將無法實施,此時 XML 配置是唯一可用的方式。
- 注釋配置往往是類級別的,而 XML 配置則可以表現(xiàn)得更加靈活。比如相比于
@Transaction
事務注釋,使用 aop/tx 命名空間的事務配置更加靈活和簡單。
所以在實現(xiàn)應用中,我們往往需要同時使用注釋配置和 XML 配置,對于類級別且不會發(fā)生變動的配置可以優(yōu)先考慮注釋配置;而對于那些第三方類以及容易發(fā)生調整的配置則應優(yōu)先考慮使用 XML 配置。Spring 會在具體實施 Bean 創(chuàng)建和 Bean 注入之前將這兩種配置方式的元信息融合在一起。
小結
Spring 在 2.1 以后對注釋配置提供了強力的支持,注釋配置功能成為 Spring 2.5 的最大的亮點之一。合理地使用 Spring 2.5 的注釋配置,可以有效減少配置的工作量,提高程序的內聚性。但是這并不意味著傳統(tǒng) XML 配置將走向消亡,在第三方類 Bean 的配置,以及那些諸如數據源、緩存池、持久層操作模板類、事務管理等內容的配置上,XML 配置依然擁有不可替代的地位。
什么叫控制反轉呢?套用好萊塢的一句名言就是:你呆著別動,到時我會找你。
什么意思呢?就好比一個皇帝和太監(jiān)
有一天皇帝想寵幸某個美女,于是跟太監(jiān)說,今夜我要寵幸美女
皇帝往往不會告訴太監(jiān),今晚幾點會回宮,會回哪張龍床,他只會告訴太監(jiān)他要哪位美女
其它一切都交由太監(jiān)去安排,到了晚上皇帝回宮時,自然會有美女出現(xiàn)在皇帝的龍床上
這就是控制反轉,而把美女送到皇帝的寢宮里面去就是注射
太監(jiān)就是是框架里面的注射控制器類BeanFactory,負責找到美女并送到龍床上去
整個后宮可以看成是Spring框架,美女就是Spring控制下的JavaBean
而傳統(tǒng)的模式就是一個饑渴男去找小姐出臺
找領班,幫助給介紹一個云云,于是領班就開始給他張羅
介紹一個合適的給他,完事后,再把小姐還給領班,下次再來
這個過程中,領班就是查詢上下文Context,領班的一個職能就是給客戶找到他們所要的小姐
這就是lookup()方法,領班手中的小姐名錄就是JNDI//Java Naming and Directory Interface
小姐就是EJB,饑渴男是客戶端,青樓是EJB容器
看到區(qū)別了么?
饑渴男去找小姐出臺很麻煩,不僅得找,用完后還得把小姐給還回去
而皇帝爽翻了,什么都不用管,交給太監(jiān)去處理,控制權轉移到太監(jiān)手中去了而不是皇帝,
必要時候由太監(jiān)給注射進去就可以了
/**
*
* @author liuguangyi
* @content ejb3注解的API定義在javax.persistence.*包里面。
*
* 注釋說明:
* @Entity —— 將一個類聲明為一個實體bean(即一個持久化POJO類)
* @Id —— 注解聲明了該實體bean的標識屬性(對應表中的主鍵)。
* @Table —— 注解聲明了該實體bean映射指定的表(table),目錄(catalog)和schema的名字
* @Column —— 注解聲明了屬性到列的映射。該注解有如下的屬性
* name 可選,列名(默認值是屬性名)
* unique 可選,是否在該列上設置唯一約束(默認值false)
* nullable 可選,是否設置該列的值可以為空(默認值false)
* insertable 可選,該列是否作為生成的insert語句中的一個列(默認值true)
* updatable 可選,該列是否作為生成的update語句中的一個列(默認值true)
* columnDefinition 可選,為這個特定列覆蓋sql ddl片段(這可能導致無法在不同數據庫間移植)
* table 可選,定義對應的表(默認為主表)
* length 可選,列長度(默認值255)
* precision 可選,列十進制精度(decimal precision)(默認值0)
* scale 可選,如果列十進制數值范圍(decimal scale)可用,在此設置(默認值0)
* @GeneratedValue —— 注解聲明了主鍵的生成策略。該注解有如下屬性
* strategy 指定生成的策略(JPA定義的),這是一個GenerationType。默認是GenerationType. AUTO
* GenerationType.AUTO 主鍵由程序控制
* GenerationType.TABLE 使用一個特定的數據庫表格來保存主鍵
* GenerationType.IDENTITY 主鍵由數據庫自動生成(主要是自動增長類型)
* GenerationType.SEQUENCE 根據底層數據庫的序列來生成主鍵,條件是數據庫支持序列。(這個值要與generator一起使用)
* generator 指定生成主鍵使用的生成器(可能是orcale中的序列)。
* @SequenceGenerator —— 注解聲明了一個數據庫序列。該注解有如下屬性
* name 表示該表主鍵生成策略名稱,它被引用在@GeneratedValue中設置的“gernerator”值中
* sequenceName 表示生成策略用到的數據庫序列名稱。
* initialValue 表示主鍵初始值,默認為0.
* allocationSize 每次主鍵值增加的大小,例如設置成1,則表示每次創(chuàng)建新記錄后自動加1,默認為50.
* @GenericGenerator —— 注解聲明了一個hibernate的主鍵生成策略。支持十三種策略。該注解有如下屬性
* name 指定生成器名稱
* strategy 指定具體生成器的類名(指定生成策略)。
* parameters 得到strategy指定的具體生成器所用到的參數。
* 其十三種策略(strategy屬性的值)如下:
* 1.native 對于orcale采用Sequence方式,對于MySQL和SQL Server采用identity(處境主鍵生成機制),
* native就是將主鍵的生成工作將由數據庫完成,hibernate不管(很常用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "native")
* 2.uuid 采用128位的uuid算法生成主鍵,uuid被編碼為一個32位16進制數字的字符串。占用空間大(字符串類型)。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "uuid")
* 3.hilo 要在數據庫中建立一張額外的表,默認表名為hibernate_unque_key,默認字段為integer類型,名稱是next_hi(比較少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "hilo")
* 4.assigned 在插入數據的時候主鍵由程序處理(很常用),這是<generator>元素沒有指定時的默認生成策略。等同于JPA中的AUTO。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "assigned")
* 5.identity 使用SQL Server和MySQL的自增字段,這個方法不能放到Oracle中,Oracle不支持自增字段,要設定sequence(MySQL和SQL Server中很常用)。等同于JPA中的IDENTITY
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "identity")
* 6.select 使用觸發(fā)器生成主鍵(主要用于早期的數據庫主鍵生成機制,少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "select")
* 7.sequence 調用謹慎數據庫的序列來生成主鍵,要設定序列名,不然hibernate無法找到。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "sequence",
* parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
* 8.seqhilo 通過hilo算法實現(xiàn),但是主鍵歷史保存在Sequence中,適用于支持Sequence的數據庫,如Orcale(比較少用)
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "seqhilo",
* parameters = { @Parameter(name = "max_lo", value = "5") })
* 9.increnment 插入數據的時候hibernate會給主鍵添加一個自增的主鍵,但是一個hibernate實例就維護一個計數器,所以在多個實例運行的時候不能使用這個方法。
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "increnment")
* 10.foreign 使用另一個相關的對象的主鍵。通常和<on
e-to-one>聯(lián)合起來使用。
* 例:@Id
* @GeneratedValue(generator = "idGenerator")
* @GenericGenerator(name = "idGenerator", strategy = "foreign",
* parameters = { @Parameter(name = "property", value = "info") })
* Integer id;
* @OneToOne
* EmployeeInfo info;
* 11.guid 采用數據庫底層的guid算法機制,對應MySQL的uuid()函數,SQL Server的newid()函數,ORCALE的rawtohex(sys_guid())函數等
* 例:@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策略的擴展,采用立即檢索策略來獲取sequence值,需要JDBC3.0和JDK4以上(含1.4)版本
* 例:@GeneratedValue(generator = "paymentableGenerator")
* @GenericGenerator(name = "paymentableGenerator", strategy = "sequence-identity",
* parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
*
* @OneToOne 設置一對一個關聯(lián)。cascade屬性有五個值(只有CascadeType.ALL好用?很奇怪),分別是CascadeType.PERSIST(級聯(lián)新建),CascadeType.REMOVE(級聯(lián)刪除),CascadeType.REFRESH(級聯(lián)刷新),CascadeType.MERGE(級聯(lián)更新),CascadeType.ALL(全部四項)
* 方法一
* 主表: ?@OneToOne(cascade = CascadeType.ALL)
* @PrimaryKeyJoinColumn
* public 從表類 get從表類(){return 從表對象}
* 從表:沒有主表類。
* 注意:這種方法要求主表與從表的主鍵值想對應。
* 方法二
* 主表:?@OneToOne(cascade = CascadeType.ALL)
* @JoinColumn(name="主表外鍵") //這里指定的是數據庫中的外鍵字段。
* public 從表類 get從表類(){return 從表類}
* 從表:@OneToOne(mappedBy = "主表類中的從表屬性")//例主表User中有一個從表屬性是Heart類型的heart,這里就填heart
* public 主表類 get主表類(){return 主表對象}
* 注意:@JoinColumn是可選的。默認值是從表變量名+"_"+從表的主鍵(注意,這里加的是主鍵。而不是主鍵對應的變量)。
* 方法三
* 主表:@OneToOne(cascade=CascadeType.ALL)
* @JoinTable( name="關聯(lián)表名",
* joinColumns = @JoinColumn(name="主表外鍵"),
* inverseJoinColumns = @JoinColumns(name="從表外鍵")
* )
* 從表:@OneToOne(mappedBy = "主表類中的從表屬性")//例主表User中有一個從表屬性是Heart類型的heart,這里就填heart
* public 主表類 get主表類(){return 主表對象}
* @ManyToOne 設置多對一關聯(lián)
* 方法一
* @ManyToOne(cascade={CasCadeType.PERSIST,CascadeType.MERGE})
* @JoinColumn(name="外鍵")
* public 主表類 get主表類(){return 主表對象}
* 方法二
* @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
* @JoinTable(name="關聯(lián)表名",
* joinColumns = @JoinColumn(name="主表外鍵"),
* inverseJoinColumns = @JoinColumns(name="從表外鍵")
* )
* @OneToMany 設置一對多關聯(lián)。cascade屬性指定關聯(lián)級別,參考@OneToOne中的說明。fetch指定是否延遲加載,值為FetchType.LAZY表示延遲,為FetchType.EAGER表示立即加載
* 方法一 使用這種配置,在為“一端”添加“多端”時,不會修改“多端”的外鍵。在“一端”加載時,不會得到“多端”。如果使用延遲加載,在讀“多端”列表時會出異常,立即加載在得到多端時,是一個空集合(集合元素為0)。
* “一端”配置
* @OneToMany(mappedBy="“多端”的屬性")
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法二
* “一端”配置
* @OneToMany(mappedBy="“多端”的屬性")
* @MapKey(name="“多端”做為Key的屬性")
* public Map<“多端”做為Key的屬性的類,主表類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
* 方法三 使用這種配置,在為“一端”添加“多端”時,可以修改“多端”的外鍵。
* “一端”配置
* @OneToMany
* @JoinColumn(name="“多端”外鍵")
* public List<“多端”類> get“多端”列表(){return “多端”列表}
* “多端”配置參考@ManyToOne.
*
*
*/
一、方法的重寫。
1、重寫只能出現(xiàn)在繼承關系之中。當一個類繼承它的父類方法時,都有機會重寫該父類的方法。一個特例是父類的方法被標識為final。重寫的主要優(yōu)點是能夠定義某個子類型特有的行為。
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、對于從父類繼承來的抽象方法,要么在子類用重寫的方式設計該方法,要么把子類也標識為抽象的。所以抽象方法可以說是必須要被重寫的方法。
3、重寫的意義。
重寫方法可以實現(xiàn)多態(tài),用父類的引用來操縱子類對象,但是在實際運行中對象將運行其自己特有的方法。
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(){
}
}
一個原則是:使用了什么引用,編譯器就會只調用引用類所擁有的方法。如果調用子類特有的方法,如上例的h.buck(); 編譯器會抱怨的。也就是說,編譯器只看引用類型,而不是對象類型。
4、重寫方法的規(guī)則。
若想實現(xiàn)一個合格重寫方法,而不是重載,那么必須同時滿足下面的要求!
A、重寫規(guī)則之一:重寫方法不能比被重寫方法限制有更嚴格的訪問級別。
(但是可以更廣泛,比如父類方法是包訪問權限,子類的重寫方法是public訪問權限。)
比如:Object類有個toString()方法,開始重寫這個方法的時候我們總容易忘記public修飾符,編譯器當然不會放過任何教訓我們的機會。出錯的原因就是:沒有加任何訪問修飾符的方法具有包訪問權限,包訪問權限比public當然要嚴格了,所以編譯器會報錯的。
B、重寫規(guī)則之二:參數列表必須與被重寫方法的相同。
重寫有個孿生的弟弟叫重載,也就是后面要出場的。如果子類方法的參數與父類對應的方法不同,那么就是你認錯人了,那是重載,不是重寫。
C、重寫規(guī)則之三:返回類型必須與被重寫方法的返回類型相同。
父類方法A:void eat(){} 子類方法B:int eat(){} 兩者雖然參數相同,可是返回類型不同,所以不是重寫。
父類方法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();
}
}
這個例子中,父類拋出了檢查異常Exception,子類拋出的IOException是Exception的子類,也即是比被重寫的方法拋出了更有限的異常,這是可以的。如果反過來,父類拋出IOException,子類拋出更為寬泛的Exception,那么不會通過編譯的。
注意:這種限制只是針對檢查異常,至于運行時異常RuntimeException及其子類不再這個限制之中。
E、重寫規(guī)則之五:不能重寫被標識為final的方法。
F、重寫規(guī)則之六:如果一個方法不能被繼承,則不能重寫它。
比較典型的就是父類的private方法。下例會產生一個有趣的現(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ī)則,但實際上那是一點巧合。Animal類的eat()方法不能被繼承,因此Horse類中的eat()方法是一個全新的方法,不是重寫也不是重載,只是一個只屬于Horse類的全新的方法!這點讓很多人迷惑了,但是也不是那么難以理解。
main()方法如果是這樣:
Animal h = new Horse();
//Horse h = new Horse();
h.eat();
編譯器會報錯,為什么呢?Horse類的eat()方法是public的啊!應該可以調用啊!請牢記,多態(tài)只看父類引用的方法,而不看子類對象的方法!
二、方法的重載。
重載是有好的,它不要求你在調用一個方法之前轉換數據類型,它會自動地尋找匹配的方法。方法的重載是在編譯時刻就決定調用哪個方法了,和重寫不同。最最常用的地方就是構造器的重載。
1、基本數據類型參數的重載。
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);
}
}
輸出結果:
method:byte
method:int
method:int
method:float
method:double
method:float
可以看出:首先要尋找的是數據類型正好匹配方法。如果找不到,那么就提升為表達能力更強的數據類型,如上例沒有正好容納long的整數類型,那么就轉換為float類型的。如果通過提升也不能找到合適的兼容類型,那么編譯器就會報錯。反正是不會自動轉換為較小的數據類型的,必須自己強制轉換,自己來承擔轉變后果。
char類型比較特殊,如果找不到正好匹配的類型,它會轉化為int而不是short,雖然char是16位的。
2、重載方法的規(guī)則。
A、被重載的方法必須改變參數列表。
參數必須不同,這是最重要的!不同有兩個方面,參數的個數,參數的類型,參數的順序。
B、被重載的方法與返回類型無關。
也就是說,不能通過返回類型來區(qū)分重載方法。
C、被重載的方法可以改變訪問修飾符。
沒有重寫方法那樣嚴格的限制。
D、被重載的方法可以聲明新的或者更廣的檢查異常。
沒有重寫方法那樣嚴格的限制。
E、方法能夠在一個類中或者在一個子類中被重載。
3、帶對象引用參數的方法重載。
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);
}
}
輸出結果是:
Animal is called.
Horse is called.
Animal is called.
前兩個輸出沒有任何問題。第三個方法為什么不是輸出“Horse is called.”呢?還是那句老話,要看引用類型而不是對象類型,方法重載是在編譯時刻就決定的了,引用類型決定了調用哪個版本的重載方法。
4、重載和重寫方法區(qū)別的小結。
如果能徹底弄明白下面的例子,說明你對重載和重寫非常了解了,可以結束這節(jié)的復習了。
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");
}
}
四個輸出分別是什么?被注釋的兩條語句為什么不能通過編譯?
第一條:a.eat(); 普通的方法調用,沒有多態(tài),沒什么技術含量。調用了Animal類的eat()方法,輸出:Animal is eating.
第二條:h.eat(); 普通的方法調用,也沒什么技術含量。調用了Horse類的eat()方法,輸出:Horse is eating.
第三條:h.eat("apple"); 重載。Horse類的兩個eat()方法重載。調用了Horse類的eat(String food)方法,輸出:Horse is eating apple
第四條:ah.eat(); 多態(tài)。前面有例子了,不難理解。輸出:Horse is eating.
第五條:a.eat("apple"); 低級的錯誤,Animal類中沒有eat(String food)方法。因此不能通過編譯。
第六條:ah.eat("apple"); 關鍵點就在這里。解決的方法還是那句老話,不能看對象類型,要看引用類型。Animal類中沒有eat(String food)方法。因此不能通過編譯。
小結一下:多態(tài)不決定調用哪個重載版本;多態(tài)只有在決定哪個重寫版本時才起作用。
重載對應編譯時,重寫對應運行時。夠簡潔的了吧!
三、構造方法。
構造方法是一種特殊的方法,沒有構造方法就不能創(chuàng)建一個新對象。實際上,不僅要調用對象實際類型的構造方法,還要調用其父類的構造方法,向上追溯,直到Object類。構造方法不必顯式地調用,當使用new關鍵字時,相應的構造方法會自動被調用。
1、構造方法的規(guī)則。
A、構造方法能使用任何訪問修飾符。包括private,事實上java類庫有很多都是這樣的,設計者不希望使用者創(chuàng)建該類的對象。
B、構造方法的名稱必須與類名相同。這樣使得構造方法與眾不同,如果我們遵守sun的編碼規(guī)范,似乎只有構造方法的首字母是大寫的。
C、構造方法不能有返回類型。
反過來說,有返回類型的不是構造方法
public class Test {
int Test(){
return 1;
}
}
這個方法是什么東西?一個冒充李逵的李鬼而已,int Test()和其他任何普通方法沒什么兩樣,就是普通的方法!只不過看起來很惡心,類似惡心的東西在考試卷子里比較多。
D、如果不在類中創(chuàng)建自己的構造方法,編譯器會自動生成默認的不帶參數的構造函數。
這點很容易驗證!寫一個這樣簡單的類,編譯。
class Test {
}
對生成的Test.class文件反編譯:javap Test,可以看到:
D:"JavaCode"bin>javap Test
Compiled from "Test.java"
class Test extends java.lang.Object{
Test();
}
看到編譯器自動添加的默認構造函數了吧!
E、如果只創(chuàng)建了帶參數的構造方法,那么編譯器不會自動添加無參的構造方法的!
F、在每個構造方法中,如果使用了重載構造函數this()方法,或者父類的構造方法super()方法,那么this()方法或者super()方法必須放在第一行。而且這兩個方法只能選擇一個,因此它們之間沒有順序問題。
G、除了編譯器生成的構造方法,而且沒有顯式地調用super()方法,那么編譯器會插入一個super()無參調用。
H、抽象類有構造方法。
四、靜態(tài)方法的重載與重寫(覆蓋)。
1、靜態(tài)方法是不能被覆蓋的。可以分兩種情況討論:
A、子類的非靜態(tài)方法“覆蓋”父類的靜態(tài)方法。
這種情況下,是不能通過編譯的。
class Father{
static void print(){
System.out.println ("in father method");
}
}
class Child extends Father{
void print(){
System.out.println ("in child method");
}
}
static方法表示該方法不關聯(lián)具體的類的對象,可以通過類名直接調用,也就是編譯的前期就綁定了,不存在后期動態(tài)綁定,也就是不能實現(xiàn)多態(tài)。子類的非靜態(tài)方法是與具體的對象綁定的,兩者有著不同的含義。
B、子類的靜態(tài)方法“覆蓋”父類靜態(tài)方法。
這個覆蓋依然是帶引號的。事實上把上面那個例子Child類的print方法前面加上static修飾符,確實能通過編譯!但是不要以為這就是多態(tài)!多態(tài)的特點是動態(tài)綁定,看下面的例子:
class Father{
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();
}
}
輸出結果是:in father method
從這個結果可以看出,并沒有實現(xiàn)多態(tài)。
但是這種形式很迷惑人,貌似多態(tài),實際編程中千萬不要這樣搞,會把大家搞懵的!
它不符合覆蓋表現(xiàn)出來的特性,不應該算是覆蓋!
總而言之,靜態(tài)方法不能被覆蓋。
2、靜態(tài)方法可以和非靜態(tài)方法一樣被重載。
這樣的例子太多了,我不想寫例程了。看看java類庫中很多這樣的例子。
如java.util.Arrays類的一堆重載的binarySearch方法。
在這里提一下是因為查資料時看到這樣的話“sun的SL275課程說,靜態(tài)方法只能控制靜態(tài)變量(他們本身沒有),靜態(tài)方法不能被重載和覆蓋……”
大家不要相信啊!可以重載的。而且靜態(tài)與非靜態(tài)方法可以重載。
從重載的機制很容易就理解了,重載是在編譯時刻就決定的了,非靜態(tài)方法都可以,靜態(tài)方法怎么可能不會呢?
Java的String太特別了,也太常用了,所以重要。我初學Java就被它搞蒙了,太多混淆的概念了,比如它的不變性。所以必須深入機制地去理解它。
1、String中的每個字符都是一個16位的Unicode字符,用Unicode很容易表達豐富的國際化字符集,比如很好的中文支持。甚至Java的標識符都可以用漢字,但是沒人會用吧(只在一本清華的《Java2實用教程》看過)。
2、判斷空字符串。根據需要自己選擇某個或者它們的組合
if ( s == null ) //從引用的角度
if ( s.length() == 0 ) //從長度判別
if ( s.trim().length () == 0 ) //是否有多個空白字符
trim()方法的作用是是移除前導和尾部的Unicode值小于'"u0020'的字符,并返回“修剪”好的字符串。這種方法很常用,比如需要用戶輸入用戶名,用戶不小心加了前導或者尾部空格,一個好的程序應該知道用戶不是故意的,即使是故意的也應該智能點地處理。
判斷空串是很常用的操作,但是Java類庫直到1.6才提供了isEmpty()方法。當且僅當 length() 為 0 時返回 true。
3、未初始化、空串""與null。它們是不同的概念。對未初始化的對象操作會被編譯器擋在門外;null是一個特殊的初始化值,是一個不指向任何對象的引用,對引用為null的對象操作會在運行時拋出異常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()); //運行時異常
System.out.print(s2.isEmpty()); //編譯出錯
System.out.print(s3.isEmpty()); //ok!輸出true
}
}
4、String類的方法很多,在編寫相關代碼的時候看看JDK文檔時有好處的,要不然花了大量時間實現(xiàn)一個已經存在的方法是很不值得的,因為編寫、測試、維護自己的代碼使項目的成本增加,利潤減少,嚴重的話會導致開不出工資……
5、字符串的比較。
Java不允許自定義操作符重載,因此字符串的比較要用compareTo() 或者 compareToIgnoreCase()。s1.compareTo(s2),返回值大于0則,則前者大;等于0,一般大;小于0,后者大。比較的依據是字符串中各個字符的Unicode值。
6、toString()方法。
Java的任何對象都有toString()方法,是從Object對象繼承而來的。它的作用就是讓對象在輸出時看起來更有意義,而不是奇怪的對象的內存地址。對測試也是很有幫助的。
7、String對象是不變的!可以變化的是String對象的引用。
String name = "ray";
name.concat("long"); //字符串連接
System.out.println(name); //輸出name,ok,還是"ray"
name = name.concat("long"); //把字符串對象連接的結果賦給了name引用
System.out.println(name); //輸出name,oh!,變成了"raylong"
上述三條語句其實產生了3個String對象,"ray","long","raylong"。第2條語句確實產生了"raylong"字符串,但是沒有指定把該字符串的引用賦給誰,因此沒有改變name引用。第3條語句根據不變性,并沒有改變"ray",JVM創(chuàng)建了一個新的對象,把"ray","long"的連接賦給了name引用,因此引用變了,但是原對象沒變。
8、String的不變性的機制顯然會在String常量內有大量的冗余。如:"1" + "2" + "3" +......+ "n" 產生了n+(n+1)個String對象!因此Java為了更有效地使用內存,JVM留出一塊特殊的內存區(qū)域,被稱為“String常量池”。對String多么照顧啊!當編譯器遇見String常量的時候,它檢查該池內是否已經存在相同的String常量。如果找到,就把新常量的引用指向現(xiàn)有的String,不創(chuàng)建任何新的String常量對象。
那么就可能出現(xiàn)多個引用指向同一個String常量,會不會有別名的危險呢?No problem!String對象的不變性可以保證不會出現(xiàn)別名問題!這是String對象與普通對象的一點區(qū)別。
乍看起來這是底層的機制,對我們編程沒什么影響。而且這種機制會大幅度提高String的效率,實際上卻不是這樣。為連接n個字符串使用字符串連接操作時,要消耗的時間是n的平方級!因為每兩個字符串連接,它們的內容都要被復制。因此在處理大量的字符串連接時,而且要求性能時,我們不要用String,StringBuffer是更好的選擇。
8、StringBuffer類。StringBuffer類是可變的,不會在字符串常量池中,而是在堆中,不會留下一大堆無用的對象。而且它可將字符串緩沖區(qū)安全地用于多個線程。每個StringBuffer對象都有一定的容量。只要StringBuffer對象所包含的字符序列的長度沒有超出此容量,就無需分配新的內部緩沖區(qū)數組。如果內部緩沖區(qū)溢出,則此容量自動增大。這個固定的容量是16個字符。我給這種算法起個名字叫“添飯算法”。先給你一滿碗飯,不夠了再給你一滿碗飯。
例子:
StringBuffer sb = new StringBuffer(); //初始容量為 16 個字符
sb.append("1234"); //這是4個字符,那么16個字符的容量就足夠了,沒有溢出
System.out.println(sb.length()); //輸出字符串長度是4
System.out.println(sb.capacity()); //輸出該字符串緩沖區(qū)的容量是16
sb.append("12345678901234567"); //這是17個字符,16個字符的容量不夠了,擴容為17+16個字符的容量
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ū)的容量是兩個概念,注意區(qū)別。
還有串聯(lián)的方式看起來是不是很酷!用返回值連接起來可以實現(xiàn)這種簡潔和優(yōu)雅。
10、StringBuilder類。 從J2SE 5.0 提供了StringBuilder類,它和StringBuffer類是孿生兄弟,很像。它存在的價值在于:對字符串操作的效率更高。不足的是線程安全無法保證,不保證同步。那么兩者性能到底差多少呢?很多!
請參閱:http://book.csdn.net/bookfiles/135/1001354628.shtml
實踐:
單個線程的時候使用StringBuilder類,以提高效率,而且它的API和StringBuffer兼容,不需要額外的學習成本,物美價廉。多線程時使用StringBuffer,以保證安全。
11、字符串的比較。
下面這條可能會讓你暈,所以你可以選擇看或者不看。它不會對你的職業(yè)生涯造成任何影響。而且謹記一條,比較字符串要用equals()就ok了!一旦用了“==”就會出現(xiàn)很怪異的現(xiàn)象。之所以把這部分放在最后,是想節(jié)省大家的時間,因為這條又臭又長。推薦三種人:一、沒事閑著型。二、想深入地理解Java的字符串,即使明明知道學了也沒用。三、和我一樣愛好研究“茴”字有幾種寫法。
還是那句老話,String太特殊了,以至于某些規(guī)則對String不起作用。個人感覺這種特殊性并不好。看例子:
例子A:
String str1 = "java";
String str2 = "java";
System.out.print(str1==str2);
地球上有點Java基礎的人都知道會輸出false,因為==比較的是引用,equals比較的是內容。不是我忽悠大家,你們可以在自己的機子上運行一下,結果是true!原因很簡單,String對象被放進常量池里了,再次出現(xiàn)“java”字符串的時候,JVM很興奮地把str2的引用也指向了“java”對象,它認為自己節(jié)省了內存開銷。不難理解吧 呵呵
例子B:
String str1 = new String("java");
String str2 = new String("java");
System.out.print(str1==str2);
看過上例的都學聰明了,這次肯定會輸出true!很不幸,JVM并沒有這么做,結果是false。原因很簡單,例子A中那種聲明的方式確實是在String常量池創(chuàng)建“java”對象,但是一旦看到new關鍵字,JVM會在堆中為String分配空間。兩者聲明方式貌合神離,這也是我把“如何創(chuàng)建字符串對象”放到后面來講的原因。大家要沉住氣,還有一個例子。
例子C:
String str1 = "java";
String str2 = "blog";
String s = str1+str2;
System.out.print(s=="javablog");
再看這個例子,很多同志不敢妄言是true還是false了吧。愛玩腦筋急轉彎的人會說是false吧……恭喜你,你會搶答了!把那個“吧”字去掉你就完全正確。原因很簡單,JVM確實會對型如String str1 = "java"; 的String對象放在字符串常量池里,但是它是在編譯時刻那么做的,而String s = str1+str2; 是在運行時刻才能知道(我們當然一眼就看穿了,可是Java必須在運行時才知道的,人腦和電腦的結構不同),也就是說str1+str2是在堆里創(chuàng)建的,s引用當然不可能指向字符串常量池里的對象。沒崩潰的人繼續(xù)看例子D。
例子D:
String s1 = "java";
String s2 = new String("java");
System.out.print(s1.intern()==s2.intern());
intern()是什么東東?反正結果是true。如果沒用過這個方法,而且訓練有素的程序員會去看JDK文檔了。簡單點說就是用intern()方法就可以用“==”比較字符串的內容了。在我看到intern()方法到底有什么用之前,我認為它太多余了。其實我寫的這一條也很多余,intern()方法還存在諸多的問題,如效率、實現(xiàn)上的不統(tǒng)一……
例子E:
String str1 = "java";
String str2 = new String("java");
System.out.print(str1.equals(str2));
無論在常量池還是堆中的對象,用equals()方法比較的就是內容,就這么簡單!看完此條的人一定很后悔,但是在開始我勸你別看了……
后記:用彪哥的話說“有意思嗎?”,確實沒勁。在寫這段的時候我也是思量再三,感覺自己像孔乙己炫耀“茴”字有幾種寫法。我查了一下茴 ,回,囘,囬,還有一種是“口”字里面有個“目”字,后面這四個都加上草字頭……
快速入門
(1)模板 + 數據模型 = 輸出
FreeMarker基于設計者和程序員是具有不同專業(yè)技能的不同個體的觀念他們是分工勞動的:
設計者專注于表示——創(chuàng)建HTML文件、圖片、Web頁面的其它可視化方面;
程序員創(chuàng)建系統(tǒng),生成設計頁面要顯示的數據。
經常會遇到的問題是:在Web頁面(或其它類型的文檔)中顯示的信息在設計頁面時是無效的,是基于動態(tài)數據的。在這里,你可以在HTML(或其它要輸出的文本)中加入一些特定指令,F(xiàn)reeMarker會在輸出頁面給最終用戶時,用適當的數據替代這些代碼。
先來解釋一下freemaker的基本語法了,
<# ... > 中存放所有freemaker的內容,之外的內容全部原樣輸出。
<@ ... /> 是函數調用
兩個定界符內的內容中,第一個符號表示指令或者函數名,其后的跟隨參數。freemaker提供的控制包括如下:
<#if condition><#elseif condition><#else> 條件判斷
<#list hash_or_seq as var> 遍歷hash表或者collection(freemaker稱作sequence)的成員
<#macro name param1 param2 ... ><#nested param> 宏,無返回參數
<#function name param1 param2><#return val>函數,有返回參數
var?member_function(...) 用函數對var進行轉換,freemaker稱為build-ins。實際內部實現(xiàn)類似member_function(var, ...)
stringA[M .. N] 取子字符串,類似substring(stringA, M, N)
{key:value, key2:value2 ...} 直接定義一個hash表
[item0, item1, item2 ...] 直接定義一個序列
hash0[key0] 存取hash表中key對應的元素
seq0[5] 存取序列指定下標的元素
<@function1 param0 param1 ... /> 調用函數function1
<@macro0 param0 param1 ; nest_param0 nest_param1 ...> nest_body </@macro> 調用宏,并處理宏的嵌套
<#assign var = value > 定義變量并初始化
<#local var = value> 在 macro 或者 function 中定義局部變量并初始化
<#global var = value > 定義全局變量并初始化
${var} 輸出并替換為表達式的值
<#visit xmlnode> 調用macro匹配xmlnode本身及其子節(jié)點
<#recurse xmlnode> 調用macro匹配xmlnode的子節(jié)點
下面是一個例子:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome ${user}!</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
這個例子是在簡單的HTML中加入了一些由${…}包圍的特定代碼,這些特定代碼是FreeMarker的指令,而包含F(xiàn)reeMarker的指令的文件就稱為模板(Template)。
至于user、latestProduct.url和latestProduct.name來自于數據模型(data model)。
數據模型由程序員編程來創(chuàng)建,向模板提供變化的信息,這些信息來自于數據庫、文件,甚至于在程序中直接生成。
模板設計者不關心數據從那兒來,只知道使用已經建立的數據模型。
下面是一個可能的數據模型:
(root)
|
+- user = "Big Joe"
|
+- latestProduct
|
+- url = "products/greenmouse.html"
|
+- name = "green mouse"
數據模型類似于計算機的文件系統(tǒng),latestProduct可以看作是目錄。
2、數據模型
(1)基礎
在快速入門中介紹了在模板中使用的三種基本對象類型:scalars、hashes 和sequences,其實還可以有其它更多的能力:
- hashes:充當其它對象的容器,每個都關聯(lián)一個唯一的查詢名字
- sequences:充當其它對象的容器,按次序訪問
通常每個變量只具有上述的一種能力,但一個變量可以具有多個上述能力,如下面的例子:
(root)
|
+- mouse = "Yerri"
|
+- age = 12
|
+- color = "brown">
mouse既是scalars又是hashes,將上面的數據模型合并到下面的模板:
${mouse} <#-- use mouse as scalar -->
${mouse.age} <#-- use mouse as hash -->
${mouse.color} <#-- use mouse as hash -->
輸出結果是:
Yerri
12
brown
(2)Scalar變量
Scalar變量存儲單值,可以是:
- 字符串:簡單文本,在模板中使用引號(單引號或雙引號)括起
- 日期:存儲日期/時間相關的數據,可以是日期、時間或日期-時間(Timestamp);通常情況,日期值由程序員加到數據模型中,設計者只需要顯示它們
- 布爾值:true或false,通常在<#if …>標記中使用
(3)hashes 、sequences和集合
有些變量不包含任何可顯示的內容,而是作為容器包含其它變量,者有兩種類型:
- hashes:具有一個唯一的查詢名字和它包含的每個變量相關聯(lián)
- sequences:使用數字和它包含的每個變量相關聯(lián),索引值從0開始
集合變量通常類似sequences,除非無法訪問它的大小和不能使用索引來獲得它的子變量;集合可以看作只能由<#list …>指令使用的受限sequences
(4)方法
方法變量通常是基于給出的參數計算值。
下面的例子假設程序員已經將方法變量avg放到數據模型中,用來計算數字平均值:
The average of 3 and 5 is: ${avg(3, 5)}
The average of 6 and 10 and 20 is: ${avg(6, 10, 20)}
The average of the price of python and elephant is:
${avg(animals.python.price, animals.elephant.price)}
(5)宏和變換器
宏和變換器變量是用戶自定義指令(自定義FTL標記),會在后面講述這些高級特性
(6)節(jié)點
節(jié)點變量表示為樹型結構中的一個節(jié)點,通常在XML處理中使用,會在后面的專門章節(jié)中講
3、模板
(1)整體結構
模板使用FTL(FreeMarker模板語言)編寫,是下面各部分的一個組合:
- 文本:直接輸出
- Interpolation:由${和},或#{和}來限定,計算值替代輸出
- FTL標記:FreeMarker指令,和HTML標記類似,名字前加#予以區(qū)分,不會輸出
- 注釋:由<#--和-->限定,不會輸出
下面是以一個具體模板例子:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<#-- Greet the user with his/her name -->
<h1>Welcome ${user}!</h1>
<p>We have these animals:
<ul>
<#list animals as being>
<li>${being.name} for ${being.price} Euros
</#list>
</ul>
</body>
</html>
注意事項:
- FTL區(qū)分大小寫,所以list是正確的FTL指令,而List不是;${name}和${NAME}是不同的
<#if <#include 'foo'>='bar'>...</if>
- 注釋可以位于FTL標記和Interpolation內部,如下面的例子:
<h1>Welcome ${user <#-- The name of user -->}!</h1>
<p>We have these animals:
<ul>
<#list <#-- some comment... --> animals as <#-- again... --> being>
...
(2)指令
在FreeMarker中,使用FTL標記引用指令。有三種FTL標記,這和HTML標記是類似的:
- 開始標記:<#directivename parameters>
- 空內容指令標記:<#directivename parameters/>
有兩種類型的指令:預定義指令和用戶定義指令。
用戶定義指令要使用@替換#,如<@mydirective>...</@mydirective>(會在后面講述)。
FTL標記不能夠交叉,而應該正確的嵌套,如下面的代碼是錯誤的:
<ul>
<#list animals as being>
<li>${being.name} for ${being.price} Euros
<#if use = "Big Joe">
(except for you)
</#list>
</#if> <#-- WRONG! -->
</ul>
如果使用不存在的指令,F(xiàn)reeMarker不會使用模板輸出,而是產生一個錯誤消息。
FreeMarker會忽略FTL標記中的空白字符,如下面的例子:
<#list
animals as
being
>
${being.name} for ${being.price} Euros
</#list >
但是,<、</和指令之間不允許有空白字符。
(3)表達式
直接指定值
使用單引號或雙引號限定
如果包含特殊字符需要轉義,如下面的例子:
${"It's \"quoted\" and
this is a backslash: \\"}
${'It\'s "quoted" and
this is a backslash: \\'}
輸出結果是:
It's "quoted" and
this is a backslash: \
It's "quoted" and
this is a backslash: \
下面是支持的轉義序列:
轉義序列 |
含義 |
\" |
雙引號(u0022) |
\' |
單引號(u0027) |
|
反斜杠(u005C) |
\n |
換行(u000A) |
\r |
Return (u000D) |
\t |
Tab (u0009) |
\b |
Backspace (u0008) |
\f |
Form feed (u000C) |
\l |
< |
\g |
> |
\a |
& |
\{ |
{ |
\xCode |
4位16進制Unicode代碼 |
有一類特殊的字符串稱為raw字符串,被認為是純文本,其中的\和{等不具有特殊含義,該類字符串在引號前面加r,下面是一個例子:
${r"${foo}"}
${r"C:\foo\bar"}
輸出的結果是:
${foo}
C:\foo\bar
直接輸入,不需要引號
精度數字使用“.”分隔,不能使用分組符號
目前版本不支持科學計數法,所以“1E3”是錯誤的
不能省略小數點前面的0,所以“.5”是錯誤的
數字8、+8、08和8.00都是相同的
true和false,不使用引號
由逗號分隔的子變量列表,由方括號限定,下面是一個例子:
<#list ["winter", "spring", "summer", "autumn"] as x>
${x}
</#list>
輸出的結果是:
winter
spring
summer
autumn
列表的項目是表達式,所以可以有下面的例子:
[2 + 2, [1, 2, 3, 4], "whatnot"]
可以使用數字范圍定義數字序列,例如2..5等同于[2, 3, 4, 5],但是更有效率,注意數字范圍沒有方括號
可以定義反遞增的數字范圍,如5..2
由逗號分隔的鍵/值列表,由大括號限定,鍵和值之間用冒號分隔,下面是一個例子:
{"name":"green mouse", "price":150}
鍵和值都是表達式,但是鍵必須是字符串
獲取變量
- 頂層變量: ${variable},變量名只能是字母、數字、下劃線、$、@和#的組合,且不能以數字開頭
可以使用點語法或方括號語法,假設有下面的數據模型:
(root)
|
+- book
| |
| +- title = "Breeding green mouses"
| |
| +- author
| |
| +- name = "Julia Smith"
| |
| +- info = "Biologist, 1923-1985, Canada"
|
+- test = "title"
下面都是等價的:
book.author.name
book["author"].name
book.author.["name"]
book["author"]["name"]
使用點語法,變量名字有頂層變量一樣的限制,但方括號語法沒有該限制,因為名字是任意表達式的結果
- 從序列獲得數據:和散列的方括號語法語法一樣,只是方括號中的表達式值必須是數字;注意:第一個項目的索引是0
序列片斷:使用[startIndex..endIndex]語法,從序列中獲得序列片斷(也是序列);startIndex和endIndex是結果為數字的表達式
- 特殊變量:FreeMarker內定義變量,使用.variablename語法訪問
字符串操作
可以使用${..}(或#{..})在文本部分插入表達式的值,例如:
${"Hello ${user}!"}
${"${user}${user}${user}${user}"}
可以使用+操作符獲得同樣的結果
${"Hello " + user + "!"}
${user + user + user + user}
${..}只能用于文本部分,下面的代碼是錯誤的:
<#if ${isBig}>Wow!</#if>
<#if "${isBig}">Wow!</#if>
應該寫成:
<#if isBig>Wow!</#if>
例子(假設user的值為“Big Joe”):
${user[0]}${user[4]}
${user[1..4]}
結果是(注意第一個字符的索引是0):
BJ
ig J
序列操作
<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
- ${user}
</#list>
輸出結果是:
- Joe
- Fred
- Julia
- Kate
散列操作
- 連接操作:和字符串一樣,使用+,如果具有相同的key,右邊的值替代左邊的值,例如:
<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}
輸出結果是:
- Joe is 30
- Fred is 25
- Julia is 18
算術運算
${x * x - 100}
${x / 2}
${12 % 10}
輸出結果是(假設x為5):
-75
2.5
2
操作符兩邊必須是數字,因此下面的代碼是錯誤的:
${3 * "5"} <#-- WRONG! -->
使用+操作符時,如果一邊是數字,一邊是字符串,就會自動將數字轉換為字符串,例如:
${3 + "5"}
輸出結果是:
35
使用內建的int(后面講述)獲得整數部分,例如:
${(x/2)?int}
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}
輸出結果是(假設x為5):
2
1
1
-1
-1
使用=(或==,完全相等)測試兩個值是否相等,使用!= 測試兩個值是否不相等
=和!=兩邊必須是相同類型的值,否則會產生錯誤,例如<#if 1 = "1">會引起錯誤
Freemarker是精確比較,所以對"x"、"x "和"X"是不相等的
對數字和日期可以使用<、<=、>和>=,但不能用于字符串
由于Freemarker會將>解釋成FTL標記的結束字符,所以對于>和>=可以使用括號來避免這種情況,例如<#if (x > y)>
另一種替代的方法是,使用lt、lte、gt和gte來替代<、<=、>和>=
&&(and)、||(or)、!(not),只能用于布爾值,否則會產生錯誤
例子:
<#if x < 12 && color = "green">
We have less than 12 things, and they are green.
</#if>
<#if !hot> <#-- here hot must be a boolean -->
It's not hot.
</#if>
內建函數的用法類似訪問散列的子變量,只是使用“?”替代“.”,下面列出常用的一些函數
html:對字符串進行HTML編碼
cap_first:使字符串第一個字母大寫
lower_case:將字符串轉換成小寫
upper_case:將字符串轉換成大寫
trim:去掉字符串前后的空白字符
size:獲得序列中元素的數目
int:取得數字的整數部分(如-1.9?int的結果是-1)
例子(假設test保存字符串"Tom & Jerry"):
${test?html}
${test?upper_case?html}
輸出結果是:
Tom & Jerry
TOM & JERRY
操作符組 |
操作符 |
后綴 |
[subvarName] [subStringRange] . (methodParams) |
一元 |
+expr、-expr、! |
內建 |
? |
乘法 |
*、 / 、% |
加法 |
+、- |
關系 |
<、>、<=、>=(lt、lte、gt、gte) |
相等 |
==(=)、!= |
邏輯and |
&& |
邏輯or |
雙豎線 |
數字范圍 |
.. |
(4)Interpolation
Interpolation有兩種類型:
- 通用Interpolation:${expr}
- 數字Interpolation:#{expr}或#{expr; format}
注意:Interpolation只能用于文本部分
插入字符串值:直接輸出表達式結果
插入數字值:根據缺省格式(由#setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string格式化單個Interpolation,下面是一個例子:
<#setting number_format="currency"/>
<#assign answer=42/>
${answer}
${answer?string} <#-- the same as ${answer} -->
${answer?string.number}
${answer?string.currency}
${answer?string.percent}
輸出結果是:
$42.00
$42.00
42
$42.00
4,200%
插入日期值:根據缺省格式(由#setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string格式化單個Interpolation,下面是一個使用格式模式的例子:
${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")}
${lastUpdated?string("EEE, MMM d, ''yy")}
${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")}
輸出的結果類似下面的格式:
2003-04-08 21:24:44 Pacific Daylight Time
Tue, Apr 8, '03
Tuesday, April 08, 2003, 09:24:44 PM (PDT)
插入布爾值:根據缺省格式(由#setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string格式化單個Interpolation,下面是一個例子:
<#assign foo=true/>
${foo?string("yes", "no")}
輸出結果是:
yes
- 數字Interpolation的#{expr; format}形式可以用來格式化數字,format可以是:
mX:小數部分最小X位
MX:小數部分最大X位
例子:
<#-- If the language is US English the output is: -->
<#assign x=2.582/>
<#assign y=4/>
#{x; M2} <#-- 2.58 -->
#{y; M2} <#-- 4 -->
#{x; m1} <#-- 2.6 -->
#{y; m1} <#-- 4.0 -->
#{x; m1M2} <#-- 2.58 -->
#{y; m1M2} <#-- 4.0 -->
4、雜項
(1)用戶定義指令
宏和變換器變量是兩種不同類型的用戶定義指令,它們之間的區(qū)別是宏是在模板中使用macro指令定義,而變換器是在模板外由程序定義,這里只介紹宏
宏是和某個變量關聯(lián)的模板片斷,以便在模板中通過用戶定義指令使用該變量,下面是一個例子:
<#macro greet>
<font size="+2">Hello Joe!</font>
</#macro>
作為用戶定義指令使用宏變量時,使用@替代FTL標記中的#
<@greet></@greet>
如果沒有體內容,也可以使用:
<@greet/>
在macro指令中可以在宏變量之后定義參數,如:
<#macro greet person>
<font size="+2">Hello ${person}!</font>
</#macro>
可以這樣使用這個宏變量:
<@greet person="Fred"/> and <@greet person="Batman"/>
輸出結果是:
<font size="+2">Hello Fred!</font>
and <font size="+2">Hello Batman!</font>
宏的參數是FTL表達式,所以下面的代碼具有不同的意思:
<@greet person=Fred/>
這意味著將Fred變量的值傳給person參數,該值不僅是字符串,還可以是其它類型,甚至是復雜的表達式
可以有多參數,下面是一個例子:
<#macro greet person color>
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
可以這樣使用該宏變量:
<@greet person="Fred" color="black"/>
其中參數的次序是無關的,因此下面是等價的:
<@greet color="black" person="Fred"/>
只能使用在macro指令中定義的參數,并且對所有參數賦值,所以下面的代碼是錯誤的:
<@greet person="Fred" color="black" background="green"/>
<@greet person="Fred"/>
可以在定義參數時指定缺省值,如:
<#macro greet person color="black">
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
這樣<@greet person="Fred"/>就正確了
宏的參數是局部變量,只能在宏定義中有效
用戶定義指令可以有嵌套內容,使用<#nested>指令執(zhí)行指令開始和結束標記之間的模板片斷
例子:
<#macro border>
<table border=4 cellspacing=0 cellpadding=4><tr><td>
<#nested>
</tr></td></table>
</#macro>
這樣使用該宏變量:
<@border>The bordered text</@border>
輸出結果:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
The bordered text
</tr></td></table>
<#nested>指令可以被多次調用,例如:
<#macro do_thrice>
<#nested>
<#nested>
<#nested>
</#macro>
<@do_thrice>
Anything.
</@do_thrice>
輸出結果:
Anything.
Anything.
Anything.
嵌套內容可以是有效的FTL,下面是一個有些復雜的例子:
<@border> <ul> <@do_thrice> <li><@greet person="Joe"/> </@do_thrice> </ul> </@border> }}} 輸出結果:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
<ul>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
</ul>
</tr></td></table>
宏定義中的局部變量對嵌套內容是不可見的,例如:
<#macro repeat count>
<#local y = "test">
<#list 1..count as x>
${y} ${count}/${x}: <#nested>
</#list>
</#macro>
<@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")}</@repeat>
輸出結果:
test 3/1: ? ? ?
test 3/2: ? ? ?
test 3/3: ? ? ?
用戶定義指令可以有循環(huán)變量,通常用于重復嵌套內容,基本用法是:作為nested指令的參數傳遞循環(huán)變量的實際值,而在調用用戶定義指令時,在<@…>開始標記的參數后面指定循環(huán)變量的名字
例子:
<#macro repeat count>
<#list 1..count as x>
<#nested x, x/2, x==count>
</#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>
輸出結果:
1. 0.5
2. 1
3. 1.5
4. 2 Last!
指定的循環(huán)變量的數目和用戶定義指令開始標記指定的不同不會有問題
調用時少指定循環(huán)變量,則多指定的值不可見
調用時多指定循環(huán)變量,多余的循環(huán)變量不會被創(chuàng)建
(2)在模板中定義變量
在模板中定義的變量有三種類型:
- plain變量:可以在模板的任何地方訪問,包括使用include指令插入的模板,使用assign指令創(chuàng)建和替換
- 局部變量:在宏定義體中有效,使用local指令創(chuàng)建和替換
- 循環(huán)變量:只能存在于指令的嵌套內容,由指令(如list)自動創(chuàng)建
宏的參數是局部變量,而不是循環(huán)變量;局部變量隱藏(而不是覆蓋)同名的plain變量;循環(huán)變量隱藏同名的局部變量和plain變量,下面是一個例子:
<#assign x = "plain">
1. ${x} <#-- we see the plain var. here -->
<@test/>
6. ${x} <#-- the value of plain var. was not changed -->
<#list ["loop"] as x>
7. ${x} <#-- now the loop var. hides the plain var. -->
<#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here -->
8. ${x} <#-- it still hides the plain var. -->
</#list>
9. ${x} <#-- the new value of plain var. -->
<#macro test>
2. ${x} <#-- we still see the plain var. here -->
<#local x = "local">
3. ${x} <#-- now the local var. hides it -->
<#list ["loop"] as x>
4. ${x} <#-- now the loop var. hides the local var. -->
</#list>
5. ${x} <#-- now we see the local var. again -->
</#macro>
輸出結果:
1. plain
2. plain
3. local
4. loop
5. local
6. plain
7. loop
8. loop
9. plain2
內部循環(huán)變量隱藏同名的外部循環(huán)變量,如:
<#list ["loop 1"] as x>
${x}
<#list ["loop 2"] as x>
${x}
<#list ["loop 3"] as x>
${x}
</#list>
${x}
</#list>
${x}
</#list>
輸出結果:
loop 1
loop 2
loop 3
loop 2
loop 1
模板中的變量會隱藏(而不是覆蓋)數據模型中同名變量,如果需要訪問數據模型中的同名變量,使用特殊變量global,下面的例子假設數據模型中的user的值是Big Joe:
<#assign user = "Joe Hider">
${user} <#-- prints: Joe Hider -->
${.globals.user} <#-- prints: Big Joe -->
(3)名字空間
通常情況,只使用一個名字空間,稱為主名字空間
為了創(chuàng)建可重用的宏、變換器或其它變量的集合(通常稱庫),必須使用多名字空間,其目的是防止同名沖突
下面是一個創(chuàng)建庫的例子(假設保存在lib/my_test.ftl中):
<#macro copyright date>
<p>Copyright (C) ${date} Julia Smith. All rights reserved.
<br>Email: ${mail}</p>
</#macro>
<#assign mail = "jsmith@acme.com">
使用import指令導入庫到模板中,F(xiàn)reemarker會為導入的庫創(chuàng)建新的名字空間,并可以通過import指令中指定的散列變量訪問庫中的變量:
<#import "/lib/my_test.ftl" as my>
<#assign mail="fred@acme.com">
<@my.copyright date="1999-2002"/>
${my.mail}
${mail}
輸出結果:
<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
<br>Email: jsmith@acme.com</p>
jsmith@acme.com
fred@acme.com
可以看到例子中使用的兩個同名變量并沒有沖突,因為它們位于不同的名字空間
可以使用assign指令在導入的名字空間中創(chuàng)建或替代變量,下面是一個例子:
<#import "/lib/my_test.ftl" as my>
${my.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}
輸出結果:
jsmith@acme.com
jsmith@other.com
數據模型中的變量任何地方都可見,也包括不同的名字空間,下面是修改的庫:
<#macro copyright date>
<p>Copyright (C) ${date} ${user}. All rights reserved.</p>
</#macro>
<#assign mail = "${user}@acme.com">
假設數據模型中的user變量的值是Fred,則下面的代碼:
<#import "/lib/my_test.ftl" as my>
<@my.copyright date="1999-2002"/>
${my.mail}
輸出結果:
<p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>
Fred@acme.com
補充(靜態(tài)方法的調用):
方法1:
_Validator=com.longyou.util.Validator
_Functions=com.longyou.util.Functions
_EscapeUtils=com.longyou.util.EscapeUtils
/調用代碼
${_Functions.toUpperCase("Hello")}<br>
${_EscapeUtils.escape("狼的原野")}
方法2:
${stack.findValue("@package.ClassName@method")}
補充:常用語法
EG.一個對象BOOK
1.輸出 ${book.name}
空值判斷:${book.name?if_exists },
${book.name?default(‘xxx’)}//默認值xxx
${ book.name!"xxx"}//默認值xxx
日期格式:${book.date?string('yyyy-MM-dd')}
數字格式:${book?string.number}--20
${book?string.currency}--<#-- $20.00 -->
${book?string.percent}—<#-- 20% -->
插入布爾值:
<#assign foo=ture />
${foo?string("yes","no")} <#-- yes -->
2.邏輯判斷
a:
<#if condition>...
<#elseif condition2>...
<#elseif condition3>......
<#else>...
其中空值判斷可以寫成<#if book.name?? >
b:
<#switch value>
<#case refValue1>
...
<#break>
<#case refValue2>
...
<#break>
...
<#case refValueN>
...
<#break>
<#default>
...
3.循環(huán)讀取
<#list sequence as item>
...
空值判斷<#if bookList?size = 0>
e.g.
<#list employees as e>
${e_index}. ${e.name}
輸出:
1. Readonly
2. Robbin
freemarker中Map的使用
<#list testMap?keys as testKey>
< option value="${testKey}" >
${testMap[testKey]}
</option>
</#list>
freemarker的Eclipse插件
- If you use Eclipse 2.x:
- Open the Window menu, then Open Perspective -> Install/Update
- Click with the right mouse button on the Feature Updates view, then select New -> Site Bookmark
- In the displayed dialog box, type "FreeMarker" for Name and "http://www.freemarker.org/eclipse/update" for URL. Leave the "Bookmark type" radio buttons on "Eclipse update site".
- Click Finish
- Open the tree node under the newly created update site named "FreeMarker", select the "FreeMarker X.Y.Z" feature, and install it using the Install now button in the preview pane.
- If you use Eclipse 3.x:
- Help -> Software updates -> Find and install....
- Choose "Search for new features to install".
- Click Add Update Site..., and type "FreeMarker" for Name and "http://www.freemarker.org/eclipse/update" for URL.
- Check the box of the "FreeMarker" feature.
- "Next"-s until it is installed...
摘要: 一、什么是注釋
說起注釋,得先提一提什么是元數據(metadata)。所謂元數據就是數據的數據。也就是說,元數據是描述數據的。就象數據表中的字段一樣,每個字段描述了這個字段下的數據的含義。而J2SE5.0中提供的注釋就是java源代碼的元數據,也就是說注釋是描述java源代碼的。在J2SE5.0中可以自定...
閱讀全文
Alt+回車 導入包,自動修正 Y#a 5Q;%C
Ctrl+N 查找類 }8dN-@%
Ctrl+Shift+N 查找文件 \$:/ad
Ctrl+Alt+L 格式化代碼 ^vz>q?)N
Ctrl+Alt+O 優(yōu)化導入的類和包 d B)QPI$
Alt+Insert 生成代碼(如get,set方法,構造函數等) 8 A,,7dI
Ctrl+E或者Alt+Shift+C 最近更改的代碼 {dS(}X&qw-
Ctrl+R 替換文本 m[NkF4K`
Ctrl+F 查找文本 nRgAKNV )
Ctrl+Shift+Space 自動補全代碼 ',+6'2ex
Ctrl+空格 代碼提示 k-1Xx>g
Ctrl+Alt+Space 類名或接口名提示 vgX;@
Ctrl+P 方法參數提示 V#" )7P
Ctrl+Shift+Alt+N 查找類中的方法或變量 =1ov
Alt+Shift+C 對比最近修改的代碼 r?PrlqOM
B[+:q]7
Shift+F6 重構-重命名 H["Jg)kJ
Ctrl+Shift+先上鍵 ==ko,FJSV
Ctrl+X 刪除行 rL 2+5|
Ctrl+D 復制行 gJmr $
Ctrl+/ 或 Ctrl+Shift+/ 注釋(// 或者/*...*/ ) hrkkRl
Ctrl+J 自動代碼 i*+'FH
Ctrl+E 最近打開的文件 TM~yQ6K
Ctrl+H 顯示類結構圖 7OE|#
Ctrl+Q 顯示注釋文檔 (LlsVoL2
Alt+F1 查找代碼所在位置 Fv}![ 7|
Alt+1 快速打開或隱藏工程面板 ]N='WJ(8/
Ctrl+Alt+ left/right 返回至上次瀏覽的位置 A[b.}3%f
Alt+ left/right 切換代碼視圖 XnN*gM l!
Alt+ Up/Down 在方法間快速移動定位 ^N=<b0rq
Ctrl+Shift+Up/Down 代碼向上/下移動。 m4$mF/-Ny+
F2 或Shift+F2 高亮錯誤或警告快速定位 %}V4b
wsPWQvT
代碼標簽輸入完成后,按Tab,生成代碼。 &3"n7 fmT
選中文本,按Ctrl+Shift+F7 ,高亮顯示所有該文本,按Esc高亮消失。 5H`rQKU
Ctrl+W 選中代碼,連續(xù)按會有其他效果 Ia87
選中文本,按Alt+F3 ,逐個往下查找相同文本,并高亮顯示。 ?Ge~7h-
Ctrl+Up/Down 光標跳轉到第一行或最后一行下 m@eohmV>
Ctrl+B 快速打開光標處的類或方法
在使用InelliJ IDEA的過程中,通過查找資料以及一些自己的摸索,發(fā)現(xiàn)這個眾多Java程序員喜歡的IDE里有許多值得一提的小竅門,如果能熟練的將它們應用于實際開發(fā)過程中,相信它會大大節(jié)省你的開發(fā)時間,而且隨之而來的還會有那么一點點成就感:)Try it!
1、寫代碼時用Alt-Insert(Code|Generate…)可以創(chuàng)建類里面任何字段的getter與setter方法。
2、右鍵點擊斷點標記(在文本的左邊欄里)激活速查菜單,你可以快速設置enable/disable斷點或者條件它的屬性。

'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
3、CodeCompletion(代碼完成)屬性里的一個特殊的變量是,激活Ctrl-Alt-Space可以完成在或不在當前文件里的類名。如果類沒有引入則import標志會自動創(chuàng)建。

'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
4、使用Ctrl-Shift-V快捷鍵可以將最近使用的剪貼板內容選擇插入到文本。使用時系統(tǒng)會彈出一個含有剪貼內容的對話框,從中你可以選擇你要粘貼的部分。
5、利用CodeCompletion(代碼完成)屬性可以快速地在代碼中完成各種不同地語句,方法是先鍵入一個類名地前幾個字母然后再用Ctrl-Space完成全稱。如果有多個選項,它們會列在速查列表里。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
6、用Ctrl-/與Ctrl-Shift-/來注釋/反注釋代碼行與代碼塊。
-/用單行注釋標記(“//…”)來注釋/反注釋當前行或者選擇地代碼塊。而Ctrl-Shift-/則可以用塊注釋標記(“/ V;}HGn
… dy/?S GW
/”)把所選塊包圍起來。要反注釋一個代碼塊就在塊中任何一個地方按Ctrl-Shift-/即可。
7、按Alt-Q(View|Context Info)可以不需要移動代碼就能查看當前方法地聲明。連續(xù)按兩次會顯示當前所編輯的類名。
8、使用Refactor|Copy Class…可以創(chuàng)建一個所選擇的類的“副本”。這一點很有用,比如,在你想要創(chuàng)建一個大部分內容都和已存在類相同的類時。
9、在編輯器里Ctrl-D可以復制選擇的塊或者沒有所選塊是的當前行。
10、Ctrl-W(選擇字)在編輯器里的功能是先選擇脫字符處的單詞,然后選擇源代碼的擴展區(qū)域。舉例來說,先選擇一個方法名,然后是調用這個方法的表達式,然后是整個語句,然后包容塊,等等。
11、如果你不想讓指示事件細節(jié)的“亮球”圖標在編輯器上顯示,通過按Alt-Enter組合鍵打開所有事件列表然后用鼠標點擊它就可以把這個事件文本附件的亮球置成非活動狀態(tài)。
這樣以后就不會有指示特殊事件的亮球出現(xiàn)了,但是你仍然可以用Alt-Enter快捷鍵使用它。
12、在使用CodeCompletion時,可以用逗點(.)字符,逗號(,)分號(;),空格和其它字符輸入彈出列表里的當前高亮部分。選擇的名字會隨著輸入的字符自動輸入到編輯器里。
13、在任何工具窗口里使用Escape鍵都可以把焦點移到編輯器上。
Shift-Escape不僅可以把焦點移到編輯器上而且還可以隱藏當前(或最后活動的)工具窗口。
F12鍵把焦點從編輯器移到最近使用的工具窗口。
14、在調試程序時查看任何表達式值的一個容易的方法就是在編輯器中選擇文本(可以按幾次Ctrl-W組合鍵更有效地執(zhí)行這個操作)然后按Alt-F8。
15、要打開編輯器脫字符處使用的類或者方法Java文檔的瀏覽器,就按Shift-F1(右鍵菜單的External JavaDoc)。
要使用這個功能須要把加入瀏覽器的路徑,在“General”選項中設置(Options | IDE Settings),另外還要把創(chuàng)建的Java文檔加入到工程中(File | Project Properties)。
16、用Ctrl-F12(View | File Structure Popup)鍵你可以在當前編輯的文件中快速導航。
這時它會顯示當前類的成員列表。選中一個要導航的元素然后按Enter鍵或F4鍵。要輕松地定位到列表中的一個條目,只需鍵入它的名字即可。
17、在代碼中把光標置于標記符或者它的檢查點上再按Alt-F7(右鍵菜單中的Find Usages…)會很快地查找到在整個工程中使用地某一個類、方法或者變量的位置。
18、按Ctrl-N(Go to | Class…)再鍵入類的名字可以快速地在編輯器里打開任何一個類。從顯示出來的下拉列表里選擇類。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
同樣的方法你可以通過使用Ctrl-Shift-N(Go to | File…)打開工程中的非Java文件。
19、要導航代碼中一些地方使用到的類、方法或者變量的聲明,把光標放在查看項上再按Ctrl-B即可。也可以通過按Ctrl鍵的同時在查看點上單擊鼠標鍵調轉到聲明處。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
20、把光標放到查看點上再按Ctrl-Alt-B可以導航到一個抽象方法的實現(xiàn)代碼。
21、要看一個所選擇的類的繼承層次,按Ctrl-H(Browse Type Hierarchy)即可。也可以激活編輯器中的繼承關系視圖查看當前編輯類的繼承關系。

'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
22、使用Ctrl-Shift-F7(Search | Highlight Usages in File)可以快速高亮顯示當前文件中某一變量的使用地方。按Escape清除高亮顯示。
23、用Alt-F3(Search | Incremental Search)在編輯器中實現(xiàn)快速查查找功能。
在“Search for:”提示工具里輸入字符,使用箭頭鍵朝前和朝后搜索。按Escape退出。
24、按Ctrl-J組合鍵來執(zhí)行一些你記不起來的Live Template縮寫。比如,鍵“it”然后按Ctrl-J看看有什么發(fā)生。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
25、Introduce Variable整合幫助你簡化代碼中復雜的聲明。舉個例子,在下面的代碼片斷里,在代碼中選擇一個表達式:
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
然后按Ctrl-Alt-V(Refactor | Introduce Variable)就會出現(xiàn)下面的結果:
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
26、Ctrl-Shift-J快捷鍵把兩行合成一行并把不必要的空格去掉以匹配你的代碼格式。
27、Ctrl-Shift-Backspace(Go to | Last Edit Location)讓你調轉到代碼中所做改變的最后一個地方。
多按幾次Ctrl-Shift-Backspace查看更深的修改歷史。
28、用Tools | Reformat Code…根據你的代碼樣式參考(查看Options | IDE Setting | Code Style)格式化代碼。
使用Tools | Optimize Imports…可以根據設置(查看Options | IDE Setting | Code Style | Imports)自動“優(yōu)化”imports(清除無用的imports等)。
29、使用IDEA的Live Templates | Live Templates讓你在眨眼間創(chuàng)建許多典型代碼。比如,在一個方法里鍵入
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
再按Tab鍵看有什么事情發(fā)生了。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
用Tab鍵在不同的模板域內移動。查看Options | Live Templates獲取更多的細節(jié)。
30、要查看一個文件中修改的本地歷史,激活右鍵菜單里的Local VCS | Show History…。也許你可以導航不同的文件版本,看看它們的不同之處再回滾到以前的任何一個版本吧。
使用同樣的右鍵菜單條目還可以看到一個目錄里修改的歷史。有了這個特性你就不會丟失任何代碼了。
31、如果要了解主菜單里每一個條目的用途,把鼠標指針移到菜單條目上再應用程序框架的底部的狀態(tài)欄里就會顯示它們的一些簡短描述,也許會對你有幫助。
32、要在編輯器里顯示方法間的分隔線,打開Options | IDE Settings | Editor,選中“Show method separators”檢查盒(checkbox)。
33、用Alt-Up和Alt-Down鍵可以在編輯器里不同的方法之間快速移動。
34、用F2/Shift-F2鍵在高亮顯示的語法錯誤間跳轉。
用Ctrl-Alt-Down/Ctrl-Alt-Up快捷鍵則可以在編譯器錯誤信息或者查找操作結果間跳轉。
35、通過按Ctrl-O(Code | Override Methods…)可以很容易地重載基本類地方法。
要完成當前類implements的(或者抽象基本類的)接口的方法,就使用Ctrl-I(Code | Implement Methods…)。
36、如果光標置于一個方法調用的括號間,按Ctrl-P會顯示一個可用參數的列表。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
37、要快速查看編輯器脫字符處使用的類或方法的Java文檔,按Ctrl-Q(在彈出菜單的Show Quick JavaDoc里)即可。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
38、像Ctrl-Q(Show Quick JavaDoc顯示簡潔Java文檔),Ctrl-P(Show Parameter Info顯示參數信息),Ctrl-B(Go to Declaration跳轉到聲明),Shift-F1(External JavaDoc外部Java文檔)以及其它一些快捷鍵不僅可以在編輯器里使用,也可以應用在代碼完成右鍵列表里。
39、Ctrl-E(View | Recent Files)彈出最近訪問的文件右鍵列表。選中文件按Enter鍵打開。
40、在IDEA中可以很容易地對你的類,方法以及變量進行重命名并在所有使用到它們的地方自動更正。
試一下,把編輯器脫字符置于任何一個變量名字上然后按Shift-F6(Refactor | Rename…)。在對話框里鍵入要顯示地新名字再按Enter。你會瀏覽到使用這個變量地所有地方然后按“Do Refactor”按鈕結束重命名操作。
41、要在任何視圖(Project View工程視圖,Structure View結構視圖或者其它視圖)里快速
選擇當前編輯地部分(類,文件,方法或者字段),按Alt-F1(View | Select in…)。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
42、在“new”字符后實例化一個已知類型對象時也許你會用到SmartType代碼完成這個特性。比如,鍵入
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
再按Ctrl-Shift-Space:
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
43、通過使用SmartType代碼完成,在IDEA中創(chuàng)建接口的整個匿名implementation也是非常容易的,比如,對于一些listener(監(jiān)聽器),可以鍵入
Component component;
component.addMouseListener(
new w w <caret is here>
);
然后再按Ctrl-Shift-Space看看有什么發(fā)生了。
44、在你需要設置一個已知類型的表達式的值時用SmartType代碼完成也很有幫助。比如,鍵入
String s = (<caret is here>
再按Ctrl-Shift-Space看看會有什么出現(xiàn)。
45、在所有視圖里都提供了速查功能:在樹里只需鍵入字符就可以快速定位到一個條目。
46、當你想用代碼片斷捕捉異常時,在編輯器里選中這個片斷,按Ctrl-Alt-T(Code | Surround with…)然后選擇“try/catch”。它會自動產生代碼片斷中拋出的所有異常的捕捉塊。在Options | File Templates | Code tab中你還可以自己定制產生捕捉塊的模板。
用列表中的其它項可以包圍別的一些結構。
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
47、在使用代碼完成時,用Tab鍵可以輸入彈出列表里的高亮顯示部分。
不像用Enter鍵接受輸入,這個選中的名字會覆蓋掉脫字符右邊名字的其它部分。這一點在用一個方法或者變量名替換另一個時特別有用。
48、在聲明一個變量時代碼完成特性會給你顯示一個建議名。比如,開始鍵入“private FileOutputStream”然后按Ctrl-Space
'300')this.width='300';if(this.height>'200')this.height='200';" border=0>
在Options | IDE Setting | Code Style中還可以為本地變量,參數,實例及靜態(tài)字段定制名字。