Java學習筆記(七):內部類、靜態類和泛型


內部類

在Java中,可以將一個類定義在另一個類里面或者一個方法里面,這樣的類稱為內部類。廣泛意義上的內部類一般來說包括這四種:成員內部類、局部內部類、匿名內部類和靜態內部類。下面就先來了解一下這四種內部類的用法。

成員內部類

成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)。

 1 class Circle {
 2     private double radius = 0;
 3     public static int count =1;
 4     public Circle(double radius) {
 5         this.radius = radius;
 6     }
 7      
 8     class Draw {     //內部類
 9         public void drawSahpe() {
10             System.out.println(radius);  //外部類的private成員
11             System.out.println(count);   //外部類的靜態成員
12         }
13     }
14 }

不過要注意的是,當成員內部類擁有和外部類同名的成員變量或者方法時,會發生隱藏現象,即默認情況下訪問的是成員內部類的成員。如果要訪問外部類的同名成員,需要以下面的形式進行訪問:

外部類.this.成員變量
外部類.this.成員方法

雖然成員內部類可以無條件地訪問外部類的成員,而外部類想訪問成員內部類的成員卻不是這么隨心所欲了。在外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的對象,再通過指向這個對象的引用來訪問:

 1 class Circle {
 2     private double radius = 0;
 3  
 4     public Circle(double radius) {
 5         this.radius = radius;
 6         getDrawInstance().drawSahpe();   //必須先創建成員內部類的對象,再進行訪問
 7     }
 8      
 9     private Draw getDrawInstance() {
10         return new Draw();
11     }
12      
13     class Draw {     //內部類
14         public void drawSahpe() {
15             System.out.println(radius);  //外部類的private成員
16         }
17     }
18 }

成員內部類是依附外部類而存在的,也就是說,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象。創建成員內部類對象的一般方式如下:

 1 public class Test {
 2     public static void main(String[] args)  {
 3         //第一種方式:
 4         Outter outter = new Outter();
 5         Outter.Inner inner = outter.new Inner();  //必須通過Outter對象來創建
 6          
 7         //第二種方式:
 8         Outter.Inner inner1 = outter.getInnerInstance();
 9     }
10 }
11  
12 class Outter {
13     private Inner inner = null;
14     public Outter() {
15          
16     }
17      
18     public Inner getInnerInstance() {
19         if(inner == null)
20             inner = new Inner();
21         return inner;
22     }
23       
24     class Inner {
25         public Inner() {
26              
27         }
28     }
29 }

內部類可以擁有private訪問權限、protected訪問權限、public訪問權限及包訪問權限。比如上面的例子,如果成員內部類Inner用private修飾,則只能在外部類的內部訪問,如果用public修飾,則任何地方都能訪問;如果用protected修飾,則只能在同一個包下或者繼承外部類的情況下訪問;如果是默認訪問權限,則只能在同一個包下訪問。這一點和外部類有一點不一樣,外部類只能被public和包訪問兩種權限修飾。我個人是這么理解的,由於成員內部類看起來像是外部類的一個成員,所以可以像類的成員一樣擁有多種權限修飾。

另外,由於成員內部類可以訪問類的成員,所以必須從類的實例才能創建成員內部類。

局部內部類

局部內部類是定義在一個方法或者一個作用域里面的類,它和成員內部類的區別在於局部內部類的訪問僅限於方法內或者該作用域內。

局部內部類定義在方法中,比方法的范圍還小。是內部類中最少用到的一種類型。

像局部變量一樣,不能被public, protected, private和static修飾。

只能訪問方法中定義的final類型的局部變量。

局部內部類在方法中定義,所以只能在方法中使用,即只能在方法當中生成局部內部類的實例並且調用其方法。

我們來看一個例子:

 1 public class Goods1 {
 2     public Destination dest(String s) {
 3         class GDestination implements Destination {
 4             private String label;
 5 
 6             private GDestination(String whereTo) {
 7                 label = whereTo;
 8             }
 9 
10             public String readLabel() {
11                 return label;
12             }
13         }
14         return new GDestination(s);
15     }
16 
17     public static void main(String[] args) {
18         Goods1 g = new Goods1();
19         Destination d = g.dest("Beijing");
20     }
21 }

在方法dest中我們定義了一個內部類,最后由這個方法返回這個內部類的對象。如果我們在用一個內部類的時候僅需要創建它的一個對象並創給外部,就可以這樣做。當然,定義在方法中的內部類可以使設計多樣化,用途絕不僅僅在這一點。

匿名內部類

匿名內部類應該是平時我們編寫代碼時用得最多的,在編寫事件監聽的代碼時使用匿名內部類不但方便,而且使代碼更加容易維護。下面這段代碼是一段事件監聽代碼:

1 btn.setOnClickListener(new OnClickListener() {
2     @Override
3     public void onClick(View v) {
4         // TODO Auto-generated method stub
5         
6     }
7 });

使用匿名內部類能夠在實現父類或者接口中的方法情況下同時產生一個相應的對象,但是前提是這個父類或者接口必須先存在才能這樣使用。

而內部類可以直接訪問外部類的所有屬性及方法(包括私有),所以使用匿名內部類可以實現其他語言的回調寫法(如C#的委托或Lamdba,js的Function)。

final

Java8之前,匿名內部類只能訪問使用final修飾的變量,這個限制在Java8中取消,下面我們來看看:

 1 package org.hammerc.study;
 2 
 3 public class Main
 4 {
 5     public static void main(String[] args)
 6     {
 7         new Main();
 8     }
 9 
10     private ICallback callback;
11 
12     public Main()
13     {
14         test(100);
15     }
16 
17     private void test(final int a)
18     {
19         final String b = "abc";
20 
21         callback = new ICallback()
22         {
23             @Override
24             public void Handler()
25             {
26                 System.out.println("a: " + a + ", b: " + b);
27             }
28         };
29 
30         callback.Handler();
31     }
32 }
33 
34 interface ICallback
35 {
36     void Handler();
37 }

Java8之前為什么需要添加final修飾呢?主要的問題是,Java8之前的版本中還沒有很好的對閉包進行支持,由於局部變量的生命周期與局部內部類的對象的生命周期的不一致性,回調可能會在離開其訪問的對象的作用域時調用,比如一個異步的操作,這樣就會導致回調執行獲取一個參數或臨時變量時,該參數或變量已經被銷毀的情況。

在最新的Java8中,已經支持閉包,對外部對象的訪問不需要添加final修飾了。

靜態內部類

靜態內部類也是定義在另一個類里面的類,只不過在類的前面多了一個關鍵字static。靜態內部類是不需要依賴於外部類的,這點和類的靜態成員屬性有點類似,並且它不能使用外部類的非static成員變量或者方法,這點很好理解,因為在沒有外部類的對象的情況下,可以創建靜態內部類的對象,如果允許訪問外部類的非static成員就會產生矛盾,因為外部類的非static成員必須依附於具體的對象。

我們都知道,Java中一個文件只能包含一個對外公開的類,且類名必須和文件名一致,那么如果我們需要再一個類中定義多個對外公開的類該怎么辦?答案就是使用靜態內部類。

如果使用過Google的Protobuf,就可以看見,生成的類文件大量的使用靜態內部類來定義,避免創建類文件夾過多的問題。

下面我們來看一個例子:

 1 package org.hammerc.study;
 2 
 3 public class Main
 4 {
 5     public static void main(String[] args)
 6     {
 7         Main.Test test = new Main.Test();
 8         test.print();
 9     }
10 
11     public static class Test
12     {
13         public void print()
14         {
15             System.out.println("Hello World!");
16         }
17     }
18 }

使用靜態內部類我們可以看做定義了一個新類一樣,僅僅在使用時需要先寫外部類的名字,如例子中的“Main.Test”一樣,實現了一個類文件定義多個公開類的功能。

內部類的優點

Java為什么要設計出內部類呢?

隱藏實現

對應成員內部類和局部內部類,通過在類中定義一個類來隱藏部分實現。

多重繼承

對應成員內部類,比較另類,我就直接上代碼了:

1 package test;
2 public class Class1 {
3     public String getName() {
4         return "Luck";
5     }
6 }
1 package test;
2 public class Class2 {
3     public int getAge() {
4         return 25;
5     }
6 }
 1 package test;
 2 public class MainClass {
 3     public static class Test1 extends Class1 {
 4         @Override
 5         public String getName() {
 6             return super.getName();
 7         }
 8     }
 9     public static class Test2 extends Class2 {
10         @Override
11         public int getAge() {
12             return super.getAge();
13         }
14     }
15     public String showName() {
16         return new Test1().getName();
17     }
18     public int showAge() {
19         return new Test2().getAge();
20     }
21     public static void main(String args[]) {
22         MainClass example = new MainClass();
23         System.out.println("name:" + example.showName());
24         System.out.println("age:" + example.showAge());
25     }
26 }

實現Lamdba

對應匿名內部類,由於Java最小的單位是類,沒有向C#一樣提供委托,也沒有向動態語言一樣,最小單位是函數,如果希望編寫一個事件監聽,可以用到匿名內部類來實現。

定義多個類

對應靜態內部類,Java不能在一個類中添加多個公開類,導致的問題是會產生多個類文件,如果希望向C++,C#一樣可以在一個文件內添加多個類就要用到靜態內部類了。

靜態類

Java中唯一可以用static修飾的class只有上面的靜態內部類,而普通的類是不能使用static修飾的,我們普遍說的Java靜態類一般有下面三個特點:

  • 使用final修飾,不允許繼承;
  • 只包含靜態方法和屬性;
  • 構造函數私有化,不允許外部實例化;

而C#中是可以使用static修飾class的,其作用正好就是上面提到的三個特點;

靜態類和單例的選擇

  1. 單例可以被繼承;
  2. 單例可以實現自某接口,可以繼承自某類。靜態類也可以繼承自某類,但是就沒法使用父類里面的protect成員了;
  3. 單例可以比較方便地擴展為有限實例;
  4. 如果希望在類加載的時候做復雜的操作,那么在靜態類中,需要引入static塊來初始化數據,如果期間拋出了異常,就可能發生一個“ClassDefNotFoundError”的詭異錯誤,這對問題定位是不利的;

好文推薦:

http://raychase.iteye.com/blog/1471015

http://www.2cto.com/kf/201311/260911.html

泛型

泛型在JDK1.5之后添加,泛型可以使類型參數化,從而實現了算法上的代碼重用。

同時由於去掉了強制轉換的操作,使用泛型還可以提高程序的運行速度。

我們先看看Java自帶的使用了泛型的類:

 1 package org.hammerc.study;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class Main
 7 {
 8     public static void main(String[] args)
 9     {
10         List<Integer> list1 = new ArrayList<Integer>();
11         list1.add(1);
12         int i = list1.get(0);
13 
14         List<String> list2 = new ArrayList<String>();
15         list2.add("Hello");
16         String s = list2.get(0);
17     }
18 }

通過使用泛型,我們可以重復利用ArrayList提供的功能,而不用每個類型對應去寫一個ArrayList的類。

泛型在類上的實現

下面我們自己使用泛型編寫一個簡單的類,如下:

 1 package org.hammerc.study;
 2 
 3 public class Main
 4 {
 5     public static void main(String[] args)
 6     {
 7         Test<Integer> test1 = new Test<Integer>();
 8         test1.setMyValue(100);
 9         System.out.println(test1.getMyValue());
10 
11         Test<String> test2 = new Test<String>();
12         test2.setMyValue("Hello");
13         System.out.println(test2.getMyValue());
14     }
15 
16     public static class Test<T>
17     {
18         private T _myValue;
19 
20         public void setMyValue(T value)
21         {
22             _myValue = value;
23         }
24 
25         public T getMyValue()
26         {
27             return _myValue;
28         }
29     }
30 }

Test類中的尖括號里面的T即為泛型,其可以表示任意的類型。

泛型約束

我們上面示例中的T可以使用任意的類型,那么如果我們只希望T是某類型或某類型的子類該怎么辦呢?

public class Test<T extends IComparable>

如果這樣寫,則表示T必須是實現了IComparable接口的對象。

多個類型的情況

多個類型的寫法如下:

public class Test<T, K>
public class Test<T extends IComparable, K extends ICloneable>

如上所示,定義多個類型使用逗號分隔即可。

創建類型的情況

在Java中不能直接創建泛型,如下面的代碼是錯誤的:

1 public class Test<T>
2 {
3     private T _test;
4     
5     public Test()
6     {
7         _test = new T();
8     }
9 }

因為對於編譯器來說,不能確定創建的對象的參數,但是在C#中可以通過new()關鍵字指定該泛型一定存在一個無參構造函數,所以C#支持創建泛型對象。

設定為默認值

Java中,只有引用類型可以作為泛型的參數,int、boolean等基礎類型不能作為泛型參數,所以設為默認值即置空即可。

泛型繼承

子類也有相同的泛型時:

1 public class A<T>
2 { }
3 
4 public class B<T> extends A<T>
5 { }

當然,你可以使用另外的名稱,只要能對應上即可:

1 public class A<T>
2 { }
3 
4 public class B<K> extends A<K>
5 { }

子類指定好類型:

1 public class A<T>
2 { }
3 
4 public class B extends A<String>
5 { }

子類添加新類型:

1 public class A<T>
2 { }
3 
4 public class B<T, K> extends A<T>
5 { }

泛型在方法上的實現

如果要在方法上添加類上沒有指定的類型,可以直接在方法上添加泛型:

 1 package org.hammerc.study;
 2 
 3 public class Main
 4 {
 5     public static void main(String[] args)
 6     {
 7         Test test = new Test();
 8         String s = test.tell("Hello");
 9         int i = test.tell(123);
10     }
11 
12     public static class Test
13     {
14         public <T> T tell(T t)
15         {
16             return t;
17         }
18     }
19 }

泛型接口

在JDK1.5之后,泛型也可以用在接口上,其使用方式和用在類上的使用方式一致,如下:

 1 package org.hammerc.study;
 2 
 3 public class Main
 4 {
 5     public static void main(String[] args)
 6     {
 7         ITest test = new Test1<Integer>();
 8         int i = (int) test.tell(100);
 9         
10         //也可以寫全, 外部使用不用轉換
11         //ITest<Integer> test = new Test1<Integer>();
12         //int i = test.tell(100);
13 
14         ITest test2 = new Test2();
15         String s = (String) test2.tell("World");
16         
17         //也可以寫全, 外部使用不用轉換
18         //ITest<String> test2 = new Test2();
19         //String s = test2.tell("World");
20     }
21 
22     public interface ITest<T>
23     {
24         T tell(T t);
25     }
26 
27     public static class Test1<T> implements ITest<T>
28     {
29         public T tell(T t)
30         {
31             return t;
32         }
33     }
34 
35     public static class Test2 implements ITest<String>
36     {
37         public String tell(String s)
38         {
39             return "Hello " + s;
40         }
41     }
42 }

泛型和靜態字段與方法

泛型也可以用在靜態方法上。

 1 package org.hammerc.study;
 2 
 3 public class Main
 4 {
 5     public static void main(String[] args)
 6     {
 7         String s = Test.tell("Hello");
 8     }
 9 
10     public static class Test<T>
11     {
12         //public static T t;//報錯,靜態屬性不能使用類上定義的泛型
13 
14         //public static void func1(T t)//報錯,靜態方法不能使用類上定義的泛型
15         //{
16         //}
17 
18         public static <K> K tell(K k)
19         {
20             return k;
21         }
22     }
23 }

如上所示,靜態屬性和方法不能使用定義在類上的泛型;另外靜態屬性不能使用泛型。

通配符

我們先看下面的一種情況:

 1 package org.hammerc.study;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class Main
 7 {
 8     public static void main(String[] args)
 9     {
10         List<A> a = new ArrayList<A>();
11         List<B> b = new ArrayList<B>();
12         
13         func1(b);
14         func2(a);
15     }
16     
17     public static void func1(List<A> list) {}
18     public static void func2(List<B> list) {}
19     
20     public static class A {}
21     public static class B extends A {}
22 }

調用func1和func2的地方會報錯,雖然B繼承自A,但是List<A>和List<B>被編譯器理解為兩個類型,不能同意,再這種情況下就要使用到通配符了;

 1 package org.hammerc.study;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class Main
 7 {
 8     public static void main(String[] args)
 9     {
10         List<A> a = new ArrayList<A>();
11         List<B> b = new ArrayList<B>();
12 
13         func1(b);
14         func2(a);
15     }
16 
17     public static void func1(List<? extends A> list) {}
18     public static void func2(List<?> list) {}
19 
20     public static class A {}
21     public static class B extends A {}
22 }

使用“?”號表示匹配任意類型,使用“? extends A”表示匹配類型A即A的子類,這樣修改后,調用就不會報錯了;

上界和下界

Java中,使用通配符可以指定類型的上界和下界;

  • 上界由extends指定:表示該類型必須是指定類型或其子類;
  • 下界由super指定:表示該類型必須是指定類型或其父類;

一些要注意的地方

使用了通配符的List是不能進行添加操作的,但是可以添加null;

1 public static void func(List<?> list)
2 {
3     list.add(123);//報錯,編譯器不能得到實際的類型
4     list.add(null);//不報錯
5 }

List<Object>與List<?>並不等同,不能往List<?> list里添加任意對象,除了null,但是List<Object>沒有這個限制。

泛型數組

泛型數組是和泛型方法搭配使用的,具體的情況是在傳遞參數或返回值時使用,如下:

 1 public class Main
 2 {
 3     public static void main(String[] args)
 4     {
 5         String[] s = {"a", "b", "c"};
 6         tell(s);
 7         
 8         Integer[] i = {1, 2, 3};
 9         tell(i);
10     }
11     
12     public static <T> T[] tell(T[] arr)
13     {
14         for (int i = 0; i < arr.length; i++) {
15             System.out.println(arr[i]);
16         }
17         return arr;
18     }
19 }

和C#的對比

Java和C#的泛型實現是不一樣的,具體的內容可以看下面這篇文章:

http://www.cnblogs.com/JeffreyZhao/archive/2010/02/22/why-not-csharp-on-jvm-type-erasure.html


免責聲明!

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



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