JavaSE學習筆記(5) 內部類和String類 一.內部類基礎 轉自 "菜鳥教程" 在 Java 中,可以將一個類定義在另一個類裡面或者一個方法裡面,這樣的類稱為內部類。廣泛意義上的內部類一般來說包括這四種:成員內部類、局部內部類、匿名內部類和靜態內部類。下麵就先來瞭解一下這四種內部類的用法 ...
JavaSE學習筆記(5)---內部類和String類
一.內部類基礎
轉自菜鳥教程
在 Java 中,可以將一個類定義在另一個類裡面或者一個方法裡面,這樣的類稱為內部類。廣泛意義上的內部類一般來說包括這四種:成員內部類、局部內部類、匿名內部類和靜態內部類。下麵就先來瞭解一下這四種內部類的用法。
1.成員內部類
成員內部類是最普通的內部類,它的定義為位於另一個類的內部,形如下麵的形式:
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //內部類
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
這樣看起來,類Draw像是類Circle的一個成員,Circle稱為外部類。成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)。
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //內部類
public void drawSahpe() {
System.out.println(radius); //外部類的private成員
System.out.println(count); //外部類的靜態成員
}
}
}
不過要註意的是,當成員內部類擁有和外部類同名的成員變數或者方法時,會發生隱藏現象,即預設情況下訪問的是成員內部類的成員。如果要訪問外部類的同名成員,需要以下麵的形式進行訪問:
外部類.this.成員變數
外部類.this.成員方法
雖然成員內部類可以無條件地訪問外部類的成員,而外部類想訪問成員內部類的成員卻不是這麼隨心所欲了。在外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的對象,再通過指向這個對象的引用來訪問:
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必須先創建成員內部類的對象,再進行訪問
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //內部類
public void drawSahpe() {
System.out.println(radius); //外部類的private成員
}
}
}
成員內部類是依附外部類而存在的,也就是說,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象。創建成員內部類對象的一般方式如下:
public class Test {
public static void main(String[] args) {
//第一種方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必須通過Outter對象來創建
//第二種方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
內部類可以擁有 private 訪問許可權、protected 訪問許可權、public 訪問許可權及包訪問許可權。比如上面的例子,如果成員內部類 Inner 用 private 修飾,則只能在外部類的內部訪問,如果用 public 修飾,則任何地方都能訪問;如果用 protected 修飾,則只能在同一個包下或者繼承外部類的情況下訪問;如果是預設訪問許可權,則只能在同一個包下訪問。這一點和外部類有一點不一樣,外部類只能被 public 和包訪問兩種許可權修飾。我個人是這麼理解的,由於成員內部類看起來像是外部類的一個成員,所以可以像類的成員一樣擁有多種許可權修飾。
2.局部內部類
局部內部類是定義在一個方法或者一個作用域裡面的類,它和成員內部類的區別在於局部內部類的訪問僅限於方法內或者該作用域內。
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部內部類
int age =0;
}
return new Woman();
}
}
註意: 局部內部類就像是方法裡面的一個局部變數一樣,是不能有 public、protected、private 以及 static 修飾符的。
3.匿名內部類
匿名內部類應該是平時我們編寫代碼時用得最多的,在編寫事件監聽的代碼時使用匿名內部類不但方便,而且使代碼更加容易維護。下麵這段代碼是一段 Android 事件監聽代碼:
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
這段代碼為兩個按鈕設置監聽器,這裡面就使用了匿名內部類。這段代碼中的:
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
就是匿名內部類的使用。代碼中需要給按鈕設置監聽器對象,使用匿名內部類能夠在實現父類或者介面中的方法情況下同時產生一個相應的對象,但是前提是這個父類或者介面必須先存在才能這樣使用。當然像下麵這種寫法也是可以的,跟上面使用匿名內部類達到效果相同。
private void setListener()
{
scan_bt.setOnClickListener(new Listener1());
history_bt.setOnClickListener(new Listener2());
}
class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
class Listener2 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
這種寫法雖然能達到一樣的效果,但是既冗長又難以維護,所以一般使用匿名內部類的方法來編寫事件監聽代碼。同樣的,匿名內部類也是不能有訪問修飾符和 static 修飾符的。
匿名內部類是唯一一種沒有構造器的類。正因為其沒有構造器,所以匿名內部類的使用範圍非常有限,大部分匿名內部類用於介面回調。匿名內部類在編譯的時候由系統自動起名為 Outter$1.class。一般來說,匿名內部類用於繼承其他類或是實現介面,並不需要增加額外的方法,只是對繼承方法的實現或是重寫。
4.靜態內部類
靜態內部類也是定義在另一個類裡面的類,只不過在類的前面多了一個關鍵字static。靜態內部類是不需要依賴於外部類的,這點和類的靜態成員屬性有點類似,並且它不能使用外部類的非static成員變數或者方法,這點很好理解,因為在沒有外部類的對象的情況下,可以創建靜態內部類的對象,如果允許訪問外部類的非static成員就會產生矛盾,因為外部類的非static成員必須依附於具體的對象。
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
String類
String類又稱作不可變字元序列。
String位於java.lang包中,Java程式預設導入java.lang包下的所有類。
Java字元串就是Unicode字元序列,例如字元串“Java”就是4個Unicode字元’J’、’a’、’v’、’a’組成的。
- Java沒有內置的字元串類型,而是在標準Java類庫中提供了一個預定義的類String,每個用雙引號括起來的字元串都是String類的一個實例。
Java允許使用符號"+"把兩個字元串連接起來。
String類的實例
String e = "" ; // 空字元串
String greeting = " Hello World ";
字元串連接
String s1 = "Hello";
String s2 = "World! ";
String s = s1 + s2; //HelloWorld!
n-符號"+"把兩個字元串按給定的順序連接在一起,並且是完全按照給定的形式。
n-當"+"運算符兩側的操作數中只要有一個是字元串(String)類型,系統會自動將另一個操作數轉換為字元串然後再進行連接。
"+"連接符
int age = 18;
String str = "age is" + age; //str賦值為"age is 18"
//這種特性通常被用在輸出語句中:
System.out.println("age is" + age);
常量池
在Java的記憶體分析中,我們會經常聽到關於“常量池”的描述,實際上常量池也分了以下三種:
1. 全局字元串常量池(String Pool)
全局字元串常量池中存放的內容是在類載入完成後存到String Pool中的,在每個VM中只有一份,存放的是字元串常量的引用值(在堆中生成字元串對象實例)。
2. class文件常量池(Class Constant Pool)
class常量池是在編譯的時候每個class都有的,在編譯階段,存放的是常量(文本字元串、final常量等)和符號引用。
3. 運行時常量池(Runtime Constant Pool)
運行時常量池是在類載入完成之後,將每個class常量池中的符號引用值轉存到運行時常量池中,也就是說,每個class都有一個運行時常量池,類在解析之後,將符號引用替換成直接引用,與全局常量池中的引用值保持一致。
常量池示例
String str1 = "abc";
String str2 = new String("def");
String str3 = "abc";
String str4 = str2.intern();
String str5 = "def";
System.out.println(str1 == str3);// true
System.out.println(str2 == str4);// false
System.out.println(str4 == str5);// true
示例中首先經過編譯之後,在該類的class常量池中存放一些符號引用,然後類載入之後,將class常量池中存放的符號引用轉存到運行時常量池中,然後經過驗證,準備階段之後,在堆中生成駐留字元串的實例對象(也就是上例中str1所指向的“abc”實例對象),然後將這個對象的引用存到全局String Pool中,也就是String Pool中,最後在解析階段,要把運行時常量池中的符號引用替換成直接引用,那麼就直接查詢String Pool,保證String Pool里的引用值與運行時常量池中的引用值一致,大概整個過程就是這樣了。
回到示例的程式,現在就很容易解釋整個程式的記憶體分配過程了,首先,在堆中會有一個“abc”實例,全局String Pool中存放著“abc”的一個引用值,然後在運行第二句的時候會生成兩個實例,一個是“def”的實例對象,並且String Pool中存儲一個“def”的引用值,還有一個是new出來的一個“def”的實例對象,與上面那個是不同的實例,當在解析str3的時候查找String Pool,裡面有“abc”的全局駐留字元串引用,所以str3的引用地址與之前的那個已存在的相同,str4是在運行的時候調用intern()函數,返回String Pool中“def”的引用值,如果沒有就將str2的引用值添加進去,在這裡,String Pool中已經有了“def”的引用值了,所以返回上面在new str2的時候添加到String Pool中的 “def”引用值,最後str5在解析的時候就也是指向存在於String Pool中的“def”的引用值,那麼這樣一分析之後,結果就容易理解了。
String類常用方法
轉自尚學堂筆記
String類是我們最常使用的類。字元串類的方法我們必須非常熟悉!我們列出常用的方法,請大家熟悉。
String類的常用方法列表
String類常用方法一
public class StringTest1 {
public static void main(String[] args) {
String s1 = "core Java";
String s2 = "Core Java";
System.out.println(s1.charAt(3));//提取下標為3的字元
System.out.println(s2.length());//字元串的長度
System.out.println(s1.equals(s2));//比較兩個字元串是否相等
System.out.println(s1.equalsIgnoreCase(s2));//比較兩個字元串(忽略大小寫)
System.out.println(s1.indexOf("Java"));//字元串s1中是否包含Java
System.out.println(s1.indexOf("apple"));//字元串s1中是否包含apple
String s = s1.replace(' ', '&');//將s1中的空格替換成&
System.out.println("result is :" + s);
}
}
執行結果如圖所示:
String類常用方法二
public class StringTest2 {
public static void main(String[] args) {
String s = "";
String s1 = "How are you?";
System.out.println(s1.startsWith("How"));//是否以How開頭
System.out.println(s1.endsWith("you"));//是否以you結尾
s = s1.substring(4);//提取子字元串:從下標為4的開始到字元串結尾為止
System.out.println(s);
s = s1.substring(4, 7);//提取子字元串:下標[4, 7) 不包括7
System.out.println(s);
s = s1.toLowerCase();//轉小寫
System.out.println(s);
s = s1.toUpperCase();//轉大寫
System.out.println(s);
String s2 = " How old are you!! ";
s = s2.trim();//去除字元串首尾的空格。註意:中間的空格不能去除
System.out.println(s);
System.out.println(s2);//因為String是不可變字元串,所以s2不變
}
}
執行結果如圖所示:
字元串相等的判斷
equals方法用來檢測兩個字元串內容是否相等。如果字元串s和t內容相等,則s.equals(t)返回true,否則返回false。
要測試兩個字元串除了大小寫區別外是否是相等的,需要使用equalsIgnoreCase方法。
判斷字元串是否相等不要使用"=="。
忽略大小寫的字元串比較
"Hello".equalsIgnoreCase("hellO");//true
字元串的比較"=="與equals()方法
public class TestStringEquals {
public static void main(String[] args) {
String g1 = "北京尚學堂";
String g2 = "北京尚學堂";
String g3 = new String("北京尚學堂");
System.out.println(g1 == g2); // true 指向同樣的字元串常量對象
System.out.println(g1 == g3); // false g3是新創建的對象
System.out.println(g1.equals(g3)); // true g1和g3裡面的字元串內容是一樣的
}
}
執行結果如圖所示:
示例的記憶體分析如圖所示: