通用编程准则
本文是我們名為“ 高級Java ”的學(xué)院課程的一部分。
本課程旨在幫助您最有效地使用Java。 它討論了高級主題,包括對象創(chuàng)建,并發(fā),序列化,反射等。 它將指導(dǎo)您完成Java掌握的過程! 在這里查看 !
目錄
1.簡介 2.可變范圍 3.類字段和局部變量 4.方法參數(shù)和局部變量 5.裝箱和拆箱 6.接口 7.琴弦 8.命名約定 9.標(biāo)準(zhǔn)庫 10.不變性 11.測試 12.接下來是什么 13.下載源代碼1.簡介
在本部分的教程中,我們將繼續(xù)討論Java良好編程風(fēng)格和健壯設(shè)計(jì)的一般原理。 我們已經(jīng)在本教程的前面部分中看到了其中一些原則,但是在此過程中將引入許多新的實(shí)用建議,以提高您作為Java開發(fā)人員的技能。
2.可變范圍
在本教程的第3部分 , 如何設(shè)計(jì)類和接口中 ,我們討論了如何將可見性和可訪問性應(yīng)用于類和接口成員,從而限制了它們的范圍。 但是,我們尚未討論在方法實(shí)現(xiàn)中使用的局部變量。
在Java語言中,每個(gè)局部變量(一旦聲明)都有一個(gè)作用域。 從聲明位置到聲明的方法(或代碼塊)的末尾,該變量將變?yōu)榭梢姟R虼?#xff0c;只有一條規(guī)則可遵循:將局部變量聲明為靠近其所在的位置盡可能使用。 讓我們看一些典型的例子:
for( final Locale locale: Locale.getAvailableLocales() ) {// Some implementation here }try( final InputStream in = new FileInputStream( "file.txt" ) ) {// Some implementation here }在這兩個(gè)代碼段中,局部變量的范圍都限于它們在其中聲明的執(zhí)行塊。一旦塊結(jié)束,局部變量將超出范圍,并且不再可見。 看起來簡潔明了,但是隨著Java 8的發(fā)布和lambda的引入,使用局部變量的許多眾所周知的習(xí)慣用法已經(jīng)過時(shí)了。 讓我們重寫前面示例中的for-each循環(huán),改為使用lambda:
Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> {// Some implementation here} );局部變量成為函數(shù)的參數(shù),該函數(shù)本身作為forEach方法的參數(shù)傳遞。
3.類字段和局部變量
Java中的每個(gè)方法都屬于某個(gè)類(如果是Java 8,則該接口屬于某個(gè)接口,并且該方法被聲明為default )。 這樣,在方法實(shí)現(xiàn)中使用的局部變量與類成員之間可能會(huì)發(fā)生名稱沖突。 Java編譯器可以從范圍中選擇正確的變量,盡管它可能不是開發(fā)人員打算使用的變量。 現(xiàn)代Java IDE進(jìn)行了大量工作,以向開發(fā)人員提示發(fā)生此類沖突的時(shí)間(警告,突出顯示等),但是在開發(fā)時(shí)最好考慮一下。 讓我們看一下這個(gè)例子:
public class LocalVariableAndClassMember {private long value;public long calculateValue( final long initial ) {long value = initial; value *= 10;value += value;return value;}}該示例看起來很簡單,但是有一個(gè)陷阱。 方法calculateValue引入一個(gè)具有名稱value的局部變量,并以此隱藏具有相同名稱的類成員。 第08行本應(yīng)該將類成員和局部變量相加,但是它所做的卻非常不同。 正確的版本可能如下所示(使用關(guān)鍵字this ):
public class LocalVariableAndClassMember {private long value;public long calculateValue( final long initial ) {long value = initial; value *= 10;value += this.value; return value;}}雖然有些天真的實(shí)現(xiàn),但它突出了重要的問題,在某些情況下,調(diào)試和故障排除可能要花費(fèi)數(shù)小時(shí)。
4.方法參數(shù)和局部變量
Java開發(fā)人員經(jīng)常遇到的另一個(gè)陷阱是使用方法參數(shù)作為局部變量。 Java允許用不同的值重新分配非final方法參數(shù)(但是,它對原始值沒有任何影響)。 例如:
public String sanitize( String str ) {if( !str.isEmpty() ) {str = str.trim();}str = str.toLowerCase();return str; }它不是一段漂亮的代碼,但足以說明問題:將方法參數(shù)str重新分配給另一個(gè)值(基本上用作局部變量)。 在所有情況下(無一例外),可以并且應(yīng)該避免這種模式(例如,通過將方法參數(shù)聲明為final )。 例如:
public String sanitize( final String str ) {String sanitized = str;if( !str.isEmpty() ) {sanitized = str.trim();}sanitized = sanitized.toLowerCase();return sanitized; }即使遵循引入本地變量的代價(jià),遵循此簡單規(guī)則的代碼也更易于遵循和推理。
5.裝箱和拆箱
裝箱和拆箱都是Java語言中用于在原始類型(例如int , long , double )之間轉(zhuǎn)換為各自的原始類型包裝器(例如Integer , Long , Double )的相同技術(shù)的名稱。 在本教程的第4部分“ 如何以及何時(shí)使用Generics”中 ,我們已經(jīng)在討論原始類型包裝器作為泛型類型參數(shù)時(shí)看到了它的實(shí)際作用。
盡管Java編譯器試圖通過執(zhí)行自動(dòng)裝箱來盡力隱藏這些轉(zhuǎn)換,但有時(shí)它會(huì)使情況變得更糟并導(dǎo)致意外的結(jié)果。 讓我們看一下這個(gè)例子:
public static void calculate( final long value ) {// Some implementation here }final Long value = null;calculate( value );上面的代碼段可以很好地編譯,但是當(dāng)Long和long之間的轉(zhuǎn)換發(fā)生時(shí),它將在第02行拋出NullPointerException 。 這里的建議是更喜歡使用原始類型(但是,我們已經(jīng)知道,這并不總是可能的)。
6.接口
在本教程的第3部分“ 如何設(shè)計(jì)類和接口”中 ,我們討論了基于接口和基于契約的開發(fā),并著重強(qiáng)調(diào)了在可能的情況下,接口應(yīng)優(yōu)先于具體類的事實(shí)。 本節(jié)的目的是通過展示真實(shí)的示例來說服您再有時(shí)間首先考慮接口。
接口不依賴于任何特定的實(shí)現(xiàn)(默認(rèn)方法是一個(gè)例外)。 它們只是合同,因此,它們在履行合同的方式上提供了很多自由和靈活性。 當(dāng)實(shí)施涉及外部系統(tǒng)或服務(wù)時(shí),這種靈活性變得越來越重要。 讓我們看一下以下簡單接口及其可能的實(shí)現(xiàn):
public interface TimezoneService {TimeZone getTimeZone( final double lat, final double lon ) throws IOException; }public class TimezoneServiceImpl implements TimezoneService {@Overridepublic TimeZone getTimeZone(final double lat, final double lon) throws IOException {final URL url = new URL( String.format("http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo",lat, lon) );final HttpURLConnection connection = ( HttpURLConnection )url.openConnection();connection.setRequestMethod( "GET" );connection.setConnectTimeout( 1000 );connection.setReadTimeout( 1000 );connection.connect();int status = connection.getResponseCode();if (status == 200) {// Do something here}return TimeZone.getDefault();} }上面的代碼段演示了典型的接口/實(shí)現(xiàn)模式。 該實(shí)現(xiàn)使用外部HTTP服務(wù)( http://api.geonames.org/ )來檢索特定位置的時(shí)區(qū)。 但是,由于聯(lián)系方式是由界面驅(qū)動(dòng)的,因此很容易引入另一個(gè)使用數(shù)據(jù)庫或什至平面文件的實(shí)現(xiàn)。 這樣,接口可以極大地幫助設(shè)計(jì)可測試的代碼。 例如,在每次測試運(yùn)行中調(diào)用外部服務(wù)并不總是可行的,因此提供替代的虛擬實(shí)現(xiàn)(也稱為存根或模擬)是有意義的:
public class TimezoneServiceTestImpl implements TimezoneService {@Overridepublic TimeZone getTimeZone(final double lat, final double lon) throws IOException {return TimeZone.getDefault();} }此實(shí)現(xiàn)可在需要TimezoneService接口的每個(gè)地方使用,從而將測試方案與對外部組件的依賴隔離開來。
在Java標(biāo)準(zhǔn)集合庫中封裝了許多適當(dāng)使用接口的出色示例。 Collection , List , Set ,所有這些接口都有幾種實(shí)現(xiàn)的支持,當(dāng)傾向于使用合同時(shí),這些實(shí)現(xiàn)可以無縫且可互換地替換,例如:
public static< T > void print( final Collection< T > collection ) {for( final T element: collection ) {System.out.println( element );} }print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) ); print( new Vector< Long >( /* ... */ ) );7.琴弦
字符串是Java以及大多數(shù)編程語言中使用最廣泛的類型之一。 Java語言通過本機(jī)支持串聯(lián)和比較,大大簡化了字符串常規(guī)操作。 另外,Java標(biāo)準(zhǔn)庫提供了許多不同的類來提高字符串操作的效率,這就是我們將在本節(jié)中討論的內(nèi)容。
在Java中,字符串是不可變的對象,以UTF-16格式表示。 每次連接字符串(或執(zhí)行任何修改原始字符串的操作)時(shí),都會(huì)創(chuàng)建String類的新實(shí)例。 因此,連接操作可能會(huì)變得非常無效,從而導(dǎo)致創(chuàng)建許多中間字符串實(shí)例(通常來說,會(huì)生成垃圾)。
但是Java標(biāo)準(zhǔn)庫提供了兩個(gè)非常有用的類,旨在促進(jìn)字符串操作: StringBuilder和StringBuffer (它們之間的唯一區(qū)別是StringBuffer是線程安全的,而StringBuilder不是)。 讓我們看看使用這些類之一的幾個(gè)示例:
final StringBuilder sb = new StringBuilder();for( int i = 1; i <= 10; ++i ) {sb.append( " " );sb.append( i ); }sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" );盡管建議使用StringBuilder / StringBuffer來處理字符串,但在連接兩個(gè)或三個(gè)字符串的簡單情況下,它看起來可能會(huì)過分殺傷,因此可以使用常規(guī)+運(yùn)算符代替,例如:
String userId = "user:" + new Random().nextInt( 100 );通常,直接連接的更好替代方法是使用字符串格式設(shè)置,并且Java標(biāo)準(zhǔn)庫也通過提供靜態(tài)幫助器方法String.format來提供幫助。 它支持一組豐富的格式說明符,包括數(shù)字,字符,日期/時(shí)間等(有關(guān)完整參考,請?jiān)L問官方文檔 )。 讓我們通過示例來探索格式化的力量:
String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12%String.format方法提供了一種干凈方便的方法來從不同的數(shù)據(jù)類型構(gòu)造字符串。 值得一提的是,某些現(xiàn)代Java IDE能夠根據(jù)傳遞給String.format方法的參數(shù)來分析格式規(guī)范,并在檢測到任何不匹配時(shí)向開發(fā)人員發(fā)出警告。
8.命名約定
Java作為一種語言并沒有強(qiáng)迫開發(fā)人員嚴(yán)格遵守任何命名約定,但是社區(qū)已經(jīng)開發(fā)了一套易于遵循的規(guī)則,這些規(guī)則使Java代碼在標(biāo)準(zhǔn)庫和所有其他Java項(xiàng)目中看起來都是一致的。
- 軟件包名稱以小寫形式輸入: org.junit , com.fasterxml.jackson , javax.json
- 類,枚舉,接口或注釋名稱以大寫字母鍵入: StringBuilder , Runnable , @Override
- 方法或字段名 ( static final除外)以駝峰形式輸入: isEmpty , format , addAll
- 靜態(tài)最終字段或枚舉常量名稱以大寫字母鍵入, 并用下劃線 “ _” MIN_RADIX : LOG , MIN_RADIX , INSTANCE
- 局部變量和方法參數(shù)名稱以駝峰式鍵入: str , newLength , minimumCapacity
- 泛型類型參數(shù)名稱通常以大寫形式表示為一個(gè)字符 : T , U , E
通過遵循這些簡單的約定,您正在編寫的代碼將與其他任何庫或框架看起來簡潔明了,并沒有區(qū)別,給人的印象是它是由同一人創(chuàng)作的(約定真正起作用的罕見情況之一)。
9.標(biāo)準(zhǔn)庫
無論您從事哪種Java項(xiàng)目,Java標(biāo)準(zhǔn)庫都是您最好的朋友。 是的,很難不同意它們有一些粗糙的邊緣和奇怪的設(shè)計(jì)決策,但是在99%的情況下,這是專家編寫的高質(zhì)量代碼。 值得學(xué)習(xí)。
每個(gè)Java版本都為現(xiàn)有庫帶來了許多新功能(可能會(huì)淘汰舊功能),并增加了許多新庫。 Java 5帶來了在java.util.concurrent包下協(xié)調(diào)的新并發(fā)庫。 Java 6提供了(很少javax.script知道)腳本支持( javax.script包)和Java編譯器API(在javax.tools包下)。 Java 7對java.util.concurrent了很多改進(jìn),在java.nio.file包下引入了新的I / O庫,并通過java.lang.invoke包支持了動(dòng)態(tài)語言。 最后,Java 8提供了一個(gè)期待已久的日期/時(shí)間API,該API在java.time程序包下java.time 。
Java作為平臺正在不斷發(fā)展,緊跟這一發(fā)展非常重要。 每當(dāng)您打算將第三方庫或框架引入您的項(xiàng)目時(shí),請確保Java標(biāo)準(zhǔn)庫中沒有所需的功能(實(shí)際上,有許多專門的高性能算法實(shí)現(xiàn)要優(yōu)于標(biāo)準(zhǔn)庫中的實(shí)現(xiàn))但在大多數(shù)情況下,您實(shí)際上并不需要它們)。
10.不變性
不變性遍及本教程,在這一部分中,它提醒您:請認(rèn)真對待不變性。 如果您正在設(shè)計(jì)的類或正在實(shí)現(xiàn)的方法可以提供不變性保證,那么它幾乎可以在任何地方使用,而不必?fù)?dān)心并發(fā)修改。 這將使您作為開發(fā)人員的生活更加輕松(也希望與您的隊(duì)友一起生活)。
11.測試
測試驅(qū)動(dòng)開發(fā)(TDD)做法在Java社區(qū)中非常流行,從而提高了所編寫代碼的質(zhì)量標(biāo)準(zhǔn)。 與所有這些考慮TDD在桌子上帶來的好處,這是可悲的,觀察到Java標(biāo)準(zhǔn)庫不包含任何測試框架或棚架今天的。
盡管如此,測試已成為現(xiàn)代Java開發(fā)中必不可少的部分,在本節(jié)中,我們將介紹使用出色的JUnit框架的一些基礎(chǔ)知識。 本質(zhì)上,在JUnit中 ,每個(gè)測試都是關(guān)于期望對象狀態(tài)或行為的一組斷言。
編寫出色測試的秘訣在于使它們簡短而簡單,一次只測試一件事。 作為練習(xí),讓我們編寫一組測試來驗(yàn)證“ 字符串 ”部分中的String.format函數(shù)是否返回了所需的結(jié)果。
package com.javacodegeeks.advanced.generic;import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo;import org.junit.Test;public class StringFormatTestCase {@Testpublic void testNumberFormattingWithLeadingZeros() {final String formatted = String.format( "%04d", 1 );assertThat( formatted, equalTo( "0001" ) );}@Testpublic void testDoubleFormattingWithTwoDecimalPoints() {final String formatted = String.format( "%.2f", 12.324234d );assertThat( formatted, equalTo( "12.32" ) );} }測試看起來非常易讀,其執(zhí)行是實(shí)例化。 如今,普通的Java項(xiàng)目包含數(shù)百個(gè)測試用例,為開發(fā)人員提供了有關(guān)正在開發(fā)的回歸或功能的快速反饋。
12.接下來是什么
本教程的這一部分完成了與Java編程實(shí)踐和指南相關(guān)的一系列討論。 在下一部分中,我們將通過探索Java異常的世界,其類型,使用方式和使用時(shí)間來返回到語言功能。
13.下載源代碼
這是關(guān)于“通用編程準(zhǔn)則”的課程,是“高級Java”課程的課程。 您可以在此處下載源代碼: AdvancedJavaPart7
翻譯自: https://www.javacodegeeks.com/2015/09/general-programming-guidelines.html
總結(jié)
- 上一篇: (linux的ftp文件)
- 下一篇: 如何以及何时使用例外