Java基礎知識回顧之二 ----- 修飾符和String


前言

在上一篇中,回顧了Java的基本數據類型 ,這篇就來回顧下Java中的一些修飾符以及String。

修飾符介紹

Java修飾符主要分為兩類:

  • 訪問修飾符
  • 非訪問修飾符

其中訪問修飾符主要包括 private、default、protected、public。
非訪問修飾符主要包括 static、final、abstract、synchronized。

訪問修飾符

訪問修飾符可以使用下圖這張表來說明訪問權限:

修飾符 當前類 同一包內 子類 其它包
public Y Y Y Y
protected Y Y Y N
default Y Y N N
private Y N N N

簡單點查看訪問級別的話,級別是由低到高。

 private<default<protected<public

private

被private修飾的變量、方法僅限在本類中使用。
所以private是最嚴的的訪問級別,主要用於隱藏類的一些細節實現和保護類的數據。
例如pojo類就是使用private修飾變量,對外提供setter和getter的方法。
還有如果使用private用來修飾構造方法的話,該類是不能實例化的。這種在單例模式中可以經常看到!

雖然private主要用於修飾變量和方法,不過也可以修飾內部類,只不過是內部類。

例如:

public class Test{
	//修飾一個私有變量
	private int count=1;
	//修飾一個私有方法
	private int add(int i,int j){
		return i+j;
   }
	private class Test1{    		
	}
}

注意:private不能修飾外部類。

因為Test類中的變量和方法是私有的,所以其他類無法調用!
例:

public class Test2 {
	public static void main(String[] args) {
		Test t=new Test();
		//下面的變量和方法是無法獲取的
		//t.count=2;
		//t.add(1,2);
	}
}

說明:其實private修飾的方法和變量是可以使用反射調用,不過這里就不說明了。

default

default:就是不使用任何修飾符。類、接口、變量、方法都可以使用。不過僅限在同一包下。

例如:

class Test{
	 int count=1;
	 int add(int i,int j){
			return i+j;
	 }
	interface Test1{
	}
}

protected

被protected修飾的變量、方法僅僅對同一包內的類和所有子類可見。

例如:

public class Test{
	protected int count=1;
	protected int add(int i,int j){
			return i+j;
	 }
	 protected class Test1{
	 }
}

在同包下可以直接調用,如果不在同包,則需要繼承才可以使用。

public class Test2 extends Test{
	public static void main(String[] args) {
		Test t=new Test();
		t.count=2;
		t.add(1,2);
	}
}

注意:protected不能修飾外部類。

public

public:修飾的類、接口、變量、方法對所有類都可以使用。

例如:

   public class Test{
	public int count=1;
	public int add(int i,int j){
			return i+j;
	 }
  }

非訪問修飾符

為了實現一些其他的功能,Java 也提供了許多非訪問修飾符。

static

static: 用來修飾類變量和類方法。

靜態變量
static在修飾類變量的時候,無論該類被實例化了多少次,它的靜態變量只有一份拷貝。靜態變量也被稱為類變量。局部變量是不能被聲明為static變量的。

靜態方法
static在修飾類方法的時候,靜態方法是不能使用類的非靜態變量。靜態方法可以直接通過類名調用,因此靜態方法中是不能用thissuper關鍵字的。

示例:


 public class Test{
    public  String name="xuwujing";
    public  static String name2="xuwujing";
    
    public  static String getName() {
    //這個一句 會報錯  因為靜態方法是不能使用類的非靜態變量
    //String reult=name;
    //這一句就可以
    String reult=name2;
    return reult;
     }
    
    //main方法是靜態方法,可以直接調用本類中的靜態方法和靜態變量
    public static void main(String[] args) {
	    System.out.println(name2);
	    System.out.println(getName());
    }
    
    //該方法是不靜態方法,所以調用本類中的靜態方法和靜態變量時,
    //需要使用classname.variablename和 classname.methodname的方式訪問
    private void print(){
	    System.out.println(Test.name2);
	    System.out.println(Test.getName());
     }
    }

在這里順便提一下,static 靜態塊。
在JVM類加載機制中,如果類存在直接的父類並且這個類還沒有被初始化,那么就先初始化父類;如果類中存在初始化語句,就依次執行這些初始化語句。
可能上述的兩句話不太好理解,那么這里我們來運行下代碼查看其結果,通過結果可能就能更好的理解上述語句的話了。

示例:

class HelloA {

    public HelloA() {
        System.out.println("HelloA"); 
    }
    
    { System.out.println("I'm A class"); } 
    
    static { System.out.println("static A"); } 

}
public class HelloB extends HelloA{
    public HelloB() {
        System.out.println("HelloB"); 
    }
    
    { System.out.println("I'm B class"); }  
    
    static { System.out.println("static B"); } 
    
    public static void main(String[] args) {
    	new HelloB();   
}

結果:

static A
static B
I'm A class
HelloA
I'm B class
HelloB

那么根據這個類返回的結果是不是感覺更好理解了呢?
創建對象時構造器的調用順序是:

先初始化靜態成員,然后調用父類構造器,再初始化非靜態成員,最后調用自身構造器。

那么static修飾符這塊的運用可以總結如下:

  1. 靜態變量在內存中只有一個拷貝,在類的所有實例中共享。
  2. 在靜態方法中不能直接訪問實例方法和實例變量,反之可以。
  3. 在靜態方法中不能使用this和super關鍵字。
  4. 靜態方法不能被abstract修飾。
  5. 靜態方法和靜態變量都可以通過類名直接訪問。
  6. 當類被加載時,靜態代碼塊只被加載一次。有多個靜態變量或塊時,按聲明順序加載。

final

final :用來修飾類、方法和變量。
final 修飾的類不能夠被繼承,修飾的方法不能被繼承類重新定義,修飾的變量為常量,是不可修改的。
如果上述語句不好理解的話,我們可以通過編寫相關代碼進行實驗。
定義一個final修飾的變量、方法以及類。然后進行相關的測試

示例:


 public class Test{
    	//定義一個final修飾的變量
    public  static final String name="xuwujing";
    
  public static void main(String[] args) {
  		//這句會報錯  因為該變量已經被final修飾了
  		name="張三";
  	}
  	//類加上final之后,該類是無法被繼承的
  	final class Test2{
  	}
  	//這句會報錯,因為Test2是被final修飾的類
  	class Test3 extends Test2{
  	}
  	
  	class Test4{
  		//定義一個被final修飾的方法
  		 final Date getTime(){
  			return new Date();
  		}
  	}
  	
  	class Test5 extends Test4{
  		//這句會報錯,因為final方法是不能被子類修改的。
  		Date getTime(){
		return new Date();
  		}
  	}
  }
 

從上述 代碼結果,我們可以得出一下結論:

final修飾類:表示該類不能被繼承;
final修飾方法:表示方法不能被重寫;
final修飾變量:表示變量只能一次賦值以后值不能被修改(常量);

abstract

abstract :用來創建抽象類和抽象方法。

Java是面向對象的語言,而抽象類是Java語言中對抽象概念進行定義的一種機制,也正是因為這個,所以賦予了Java強大的面向對象的能力。

修飾類

會使這個類成為一個抽象類,這個類將不能生成對象實例,但可以做為對象變量聲明的類型(見后面實例),也就是編譯時類型。抽象類就相當於一類的半成品,需要子類繼承並覆蓋其中的抽象方法。

修飾方法

會使這個方法變成抽象方法,也就是只有聲明而沒有實現,需要子類繼承實現。

這里依舊使用一個簡單例子來進行理解。

public class AbstractTest{
	public static void main(String[] args) {
		//這句會報錯,因為抽象類不能實例化
		// Animal a=new Animal();
		//抽象類可以實例化重寫該類抽象方法的子類
		Animal a = new Dog();
		a.show();
	}
}
abstract class Animal{
	abstract void show();
	public void print(){
		System.out.println("Animal");
	}
}
//繼承抽象類需要實現抽象類的方法
class Dog extends Animal{	
	@Override
	void show() {
		System.out.println("This is Dog!");
	}
}

總結:

1、抽象類和抽象方法都需要被abstract修飾。抽象方法一定要定義在抽象類中。
2、抽象類不可以創建實例,原因:調用抽象方法沒有意義。
3、只有覆蓋了抽象類中所有的抽象方法后,其子類才可以實例化。否則該子類還是一個抽象類。

注意事項:

1、抽象類不能用來實例化對象,聲明抽象類的唯一目的是為了將來對該類進行擴充。 2、一個類不能同時被 abstract 和 final
修飾。如果一個類包含抽象方法,那么該類一定要聲明為抽象類,否則將出現編譯錯誤。
3、抽象方法是一種沒有任何實現的方法,該方法的的具體實現由子類提供。 4、抽象方法不能被聲明成 final 和 static。
5、任何繼承抽象類的子類必須實現父類的所有抽象方法,除非該子類也是抽象類。
6、如果一個類包含若干個抽象方法,那么該類必須聲明為抽象類。抽象類可以不包含抽象方法。

synchronized

synchronized: 修飾的方法同一時間只能被一個線程訪問。在多線程中運用很常見。
synchronized 的解釋如下:

synchronized 方法控制對類成員變量的訪問:每個類實例對應一把鎖,每個 synchronized方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明synchronized 的成員函數中至多只有一個處於可執行狀態(因為至多只有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問沖突(只要所有可能訪問類成員變量的方法均被聲明為 synchronized)。

簡單的來說,就是使用synchronized 修飾的方法,在多線程進行同時訪問的時候,只會讓一個線程先進行訪問,其它的線程等候,當這個線程訪問完了之后,再讓下一個進行訪問,依次類推。

Java中還有兩個不太常見的修飾符,transientnative

transient:被 transient 修飾的實例變量時,java 虛擬機(JVM)跳過該特定的變量。
native: 被native修飾的方法實際是由另一種語言進行實現的本地方法。例如Java中獲取的Long類型的時間戳 :System.currentTimeMillis(); 實際是由native 修飾的,
源碼為:

 public static native long currentTimeMillis();

String

String 類型可能就是我們最常用的的對象了。
首先說明,String並不是基本數據類型,而是一個對象,並且是不可變的對象。查看源碼可以String類是被final修飾的,是不可被繼承的!

String的在未被初始化的時候為null,表示它還沒有被創建,自然也就沒有分配空間;
而" "和 new String()不是null,它們是已經被創建,只是值為空而已!並且也分配了內存空間。

String有15種構造方法,有兩種是過時的,其中包含char[],byte[],int[],String,StringBuffer,StringBuilder。
我們在創建String對象的的時候,一般是使用 String str="xxx",但有時也會用new String()來初始話字符串。
例如:

    String hello="hello";
    String newHello=new String("hello");
    char []cHello ={'h','e','l','l','o'};
    String str=new String(cHello);

注意:String 類是不可改變的,所以你一旦創建了 String 對象,那它的值就無法改變了。

String常用方法

大概講述了String的用法之后,這里我們來列舉一些String常用的方法。

1.length :返回此字符串的長度。
2.charAt:返回指定索引處的 char 值。
3.compareTo:把這個字符串和另一個對象比較。
4.concat:將指定字符串連接到此字符串的結尾。
5.split:根據給定正則表達式的匹配拆分此字符串。
6.equals:將此字符串與指定的對象比較。
7.endsWith:測試此字符串是否以指定的后綴結束。
8.startsWith:測試此字符串是否以指定的前綴結束。
9.getBytes: 使用平台的默認字符集將此 String 編碼為 byte 序列,並將結果存儲到一個新的 byte 數組中。
10.indexOf:返回指定字符在此字符串中第一次出現處的索引。
11.replace:返回一個新的字符串,它是通過用 newChar 替換此字符串中出現的所有 oldChar 得到的。 12:substring:返回一個新的字符串,它是此字符串的一個子字符串。
...

更多可以參考Api文檔。

String對象比較

String作為我們最常用的對象,在面試中估計也會接觸不少。一般來說,會考到String的常量池相關問題,主要是使用String進行比較的時候,==和equals這兩種方法來判斷是否相當。這里收集了一些String經常遇到的問題。
代碼如下:

  
	        String s1 = "test";
	        String s2 = new String("test");
	        String s3 = "te";
	        String s4 = "st";
	        String s5 = "te" + "st";
	        String s6 = s3 + s4;
	        String s7 = new String(s1);
	        System.out.println(s1 == s2); 
	        System.out.println(s1 == s5); 
	        System.out.println(s1 == s6); 
	        System.out.println(s7==s1);       
	        System.out.println(s7.equals(s1)); 
	    

結果:

false
true
false
false
true

如果有經驗的話,大概可以一眼看出結果。但是如果經驗不足的話,往往會吃這個虧。這里來解釋下為什么會出現這種結果。

1.雖然看起來是一樣的,但是新建一個String類的時候會重新分配引用地址,而 == 就是比較引用地址,所以為false。
2.在編譯之前就可以確認s5=test, 並且引用地址一樣,所以為true;
3.字符串常量池的原則 這時 s6 的值是在運行時得到的,它會重新構造字符串對象 所以為false。
4.和第一個一樣的,就是換湯不換葯,所以為false。
5.equals 只比較值相等,不關心它的引用地址。

看完上面的例子之后,再來看看下面的這個
代碼示例:

 String ab="ab";
 String c="c";
 String ab_c=ab+c;
 String ab_c1="ab"+"c";
 String abc="abc";
 System.out.println(ab_c == abc + " : " + ab_c.equals(abc));

 System.out.println((ab_c == abc) + " : " + ab_c.equals(abc));

 System.out.println((ab_c1 == abc) + " : " + ab_c1.equals(abc));

運行結果:

false
false : true
true : true

到這里,可能就會詫異了,為什么和我想的不一樣呢?
這里其實是有陷阱的,也就是運算符的優先級。
第一個結果就是優先級的問題導致的,它會先計算 abc + " : " + ab_c.equals(abc) ,然后再來進行比較,所以為false。同理,下面的也是如此,基本和上面的那個例子差不多,這里就不再概述了。

String、StringBuffer和StringBuilder

String、StringBuffer和StringBuilder的區別:

  • String: String的特點是一旦賦值,便不能更改其指向的字符對象,如果更改,則會指向一個新的字符對象。
  • StringBuffer:StringBuffer對象可以調用其方法動態的進行增加、插入、修改和刪 除操作,且不用像數組那樣事先指定大小,從而實現多次插入字 符,一次整體取出的效果,因而操作字符串非常靈活方便。並且生成數據之后可以toString轉為String,線程安全。
  • StringBuilder:它是在單線程環境下使用的,因為它的所有方面都沒有被synchronized修飾,因此它的效率也比StringBuffer要高。

關於字符串拼接方式,在String類中,我們最常用的是 + ,其次是使用StringBuffer或StringBuilder 的append方法,至於String類中的concat幾乎很少用到。
一般來說,如果在少量的字符串進行拼接的話,我們會使用+,如果拼接過多的話,單線程使用 StringBuilder ,多線程使用StringBuffer 進行拼接。因為使用String 的 + 在過多的字符串進行拼接的時候會極大的使用內存,因為它在憑借的時候還是使用 append()方法,然后再進行toString轉換,如果是少量的時候,是感覺不到差異的,但是在大量拼接的時候就會明顯感受得到。

代碼示例:

String str="Hello World";
String str1="";
StringBuffer sbr=new StringBuffer(str); 
StringBuilder sbd=new StringBuilder(str); 
long start=System.currentTimeMillis();
   for(int i=0;i<10000;i++){
  	 str1+=str;
   }
   System.out.println("String累加用時:"+(System.currentTimeMillis()-start)+"ms");
   long start2=System.currentTimeMillis();
   for(int i=0;i<10000;i++){
  	 sbr.append(str);
   }
   System.out.println("StringBuffer累加用時:"+(System.currentTimeMillis()-start2)+"ms");
   long start3=System.currentTimeMillis();
   for(int i=0;i<10000;i++){
  	 sbd.append(str);
   }
   System.out.println("StringBuilder累加用時:"+(System.currentTimeMillis()-start3)+"ms");

結果:

String累加用時:701ms
StringBuffer累加用時:2ms
StringBuilder累加用時:0ms

這里從輸出結果中可以看到String 的+拼接方法的耗時了。但是使用 + 實在是方便。所以在這里建議如果字符串拼接次數在10一下,可以使用+,過多的則用StringBuffer或StringBuilder。

其它

參考:
https://blog.csdn.net/qiumengchen12/article/details/44939929

https://blog.csdn.net/chenssy/article/details/13004291

到此,本文就結束了,謝謝閱讀!歡迎留言和點贊,你的支持是我寫作最大的動力!
版權聲明:
作者:虛無境
博客園出處:http://www.cnblogs.com/xuwujing
CSDN出處:http://blog.csdn.net/qazwsxpcm    
個人博客出處:http://www.panchengming.com
原創不易,轉載請標明出處,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM