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在修飾類方法的時候,靜態方法是不能使用類的非靜態變量。靜態方法可以直接通過類名調用,因此靜態方法中是不能用this和super關鍵字的。
示例:
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修飾符這塊的運用可以總結如下:
- 靜態變量在內存中只有一個拷貝,在類的所有實例中共享。
- 在靜態方法中不能直接訪問實例方法和實例變量,反之可以。
- 在靜態方法中不能使用this和super關鍵字。
- 靜態方法不能被abstract修飾。
- 靜態方法和靜態變量都可以通過類名直接訪問。
- 當類被加載時,靜態代碼塊只被加載一次。有多個靜態變量或塊時,按聲明順序加載。
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中還有兩個不太常見的修飾符,transient 和native。
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
原創不易,轉載請標明出處,謝謝!
Java基礎知識回顧之二 ----- 修飾符和String