1. 程式人生 > >抽象類、介面、內部類

抽象類、介面、內部類

抽象類

抽象方法和抽象類

抽象方法和抽象類必須使用abstract修飾符來定義,有抽象方法的類只能被定義為抽象類,抽象類裡可以沒有抽象方法;

規則如下:

  • 抽象類與抽象方法必須使用abstract修飾符來修飾,抽象方法不能有方法體;
  • 抽象類不能被例項化。即使抽象類裡不包含抽象方法,這個抽象類也不能建立例項;
  • 抽象類可以包含成員變數、方法(普通方法和抽象方法都可以)、構造器、初始化塊、內部類(介面、列舉)5種成分。抽象類的構造器不能用於建立例項,主要用於被其子類呼叫;
  • 含有抽象方法的類(包括直接定義了一個抽象方法、繼承了一個抽象父類,但沒有完全實現父類包含的抽象方法、實現了一個介面,但沒有完全實現介面包含的抽象方法三種情況)只能被定義為抽象類;
public abstract class Shape
{
	{
		System.out.println("執行Shape的初始化塊...");
	}

	private String color;
	
	//定義一個計算周長的抽象方法
	public abstract double calPerimeter();
	//定義一個返回形狀的抽象方法
	public abstract String getType();

	//定義Shape的構造器,該構造器並不是用於建立Shape物件,而是用於被子類呼叫
	public Shape(){}
	public Shape(String color)
{ System.out.println("執行Shape的構造器..."); this.color = color; } public void setColor(String color) { this.color = color; } public String getColor() { return this.color; } }

抽象類不能用於建立例項,只能當作父類被其他子類繼承:

public class Triangle extends Shape
{
	//定義三角形的三邊
	private double a;
	private double
b; private double c; public Triangle(String color , double a , double b , double c) { super(color); this.setSides(a , b , c); } public void setSides(double a , double b , double c) { if (a >= b + c || b >= a + c || c >= a + b) { System.out.println("三角形兩邊之和必須大於第三邊"); return; } this.a = a; this.b = b; this.c = c; } //重寫Shape類的的計算周長的抽象方法 public double calPerimeter() { return a + b + c; } //重寫Shape類的的返回形狀的抽象方法 public String getType() { return getColor() + "三角形"; } }

當使用 abstract 修飾類時,表明這個類只能被繼承;當使用 abstract 修飾方法時,表明這個方法必須由子類提供實現(即重寫)。而 final 修飾的類不能被繼承,final 修飾的方法不能被重寫。因此, final 和 abstract 永遠不能同時使用;

除此之外,當使用 static 修飾一個方法時,表明這個方法屬於該類本身,即通過類就可以呼叫該方法,但如果該方法被定義為抽象方法,則將導致通過該類來呼叫該方法時出現錯誤(呼叫了一個沒有方法體的方法肯定會引起錯誤)。因此 static 和 abstract 不能同時修飾某個方法,即沒有所謂的類抽象方法;

注意:static 和 abstract 並不是絕對互斥的,static 和 abstract 雖然不能同時修飾某個方法,但它們可以同時修飾內部類;

注意:abstract 關鍵字修飾的方法必須被其子類重寫才有意義,否則這個方法將永遠不會再有方法體,因此abstract 方法不能被定義為 private 訪問許可權,即 private 和 abstract 不能同時修飾方法;

Java8的介面(interface)

簡介

接口裡不能包含普通方法,接口裡的所有方法都是抽象方法。Java8對介面進行了改進,允許在介面中定義預設方法,預設方法可以提供方法實現;

介面的定義

  • 修飾符可以是 public 或者省略,如果省略了 public 訪問控制符,則預設採用包許可權訪問控制符,即只有在相同包結構下才可以訪問該介面;
  • 介面名與類名採用相同的命名規則;
  • 一個介面可以有多個直接父介面,但介面只能繼承介面,不能繼承類;

接口裡不能包含構造器和初始化塊定義。接口裡可以包含成員變數(只能是靜態常量)、方法(只能是抽象方法例項、類方法或預設方法)、內部類(包括內部介面、列舉)定義;

接口裡的所有成員,包括常量、方法、內部類和內部列舉都是 public 訪問許可權。定義介面成員時,可以省略訪問控制修飾符,如果指定訪問控制修飾符,則只能使用 public 訪問控制修飾符;

對於接口裡定義的靜態常量而言,系統會自動為這些成員變數增加 static 和 final 兩個修飾符。也就是說,在介面中定義成員變數時,不管是否使用 public static final 修飾符,接口裡的成員變數總是使用這三個修飾符來修飾。而且接口裡沒有構造器和初始化塊,因此接口裡定義的成員變數只能在定義時指定預設值;

//系統自動為接口裡定義的成員變數增加 public static final 修飾符
int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;

接口裡定義的方法只能是抽象方法、類方法、預設方法,因此如果不是定義預設方法,系統將自動為普通方法增加 abstract 修飾符;定義接口裡的普通方法不管是否使用 public abstract 修飾符,接口裡的普通方法總是使用 public abstract 來修飾。接口裡的普通方法不能有方法實現(方法體);但類方法、預設方法都必須有方法實現(方法體);

public interface Output
{
	//接口裡定義的屬性只能是常量
	int MAX_CACHE_LINE = 50;
	//接口裡定義的只能是public的抽象例項方法
	void out();
	void getData(String msg);
    
    //在介面中定義預設方法,需要使用 default 修飾,預設方法總是使用 public 修飾---沒有指定,系統會自動新增;
	default void print(String...msgs)
	{
		for(String msg : msgs)
		{
			System.out.println(msg);
		}
	}

	//在介面中定義預設方法,需要使用 default 修飾
	default void test()
	{
		System.out.println("預設的 test() 方法");
	}

	//在接口裡定義類方法,需要使用 static 修飾,類方法總是使用 public 修飾---沒有指定,系統會自動新增;
	static String staticTest()
	{
		return "接口裡的類方法";
	}
}

介面的繼承

介面的繼承和類繼承不一樣,介面完全支援多繼承,即一個介面可以有多個介面父介面;

interface interfaceA
{
	int PROP_A = 5;
	void testA();
}
interface interfaceB
{
	int PROP_B = 6;
	void testB();
}
interface interfaceC extends interfaceA, interfaceB    
{
	int PROP_C = 7;
	void testC();
}
public class TestInterfaceExtends 
{
	public static void main(String[] args)
	{
		System.out.println(interfaceC.PROP_A);
		System.out.println(interfaceC.PROP_B);
		System.out.println(interfaceC.PROP_C);
	}
}

使用介面

一個類可以實現一個或多個介面,實現使用 implements 關鍵字;

一個類實現了一個或多個介面之後,這個類必須完全實現這些接口裡所定義的全部抽象方法(也就是重寫這些抽象方法);否則,該類將保留從父介面那裡繼承到的抽象方法,該類也必須定義成抽象類;

介面和抽象類

介面和抽象類很像,它們具有如下特徵:

  • 介面和抽象類都不能被例項化,它們都位於繼承樹的頂端,用於被其他類實現和繼承;
  • 介面和抽象類都可以包含抽象方法,實現介面或繼承抽象類的普通子類必須實現這些抽象方法;

介面和抽象類在用法上存在如下差別:

  • 接口裡只能包含抽象方法、靜態方法和類方法;抽象類則完全可以包含普通方法;
  • 接口裡只能定義靜態常量,不能定義普通成員變數;抽象類則既可以定義普通成員變數,也可以定義靜態常量;
  • 接口裡不包含構造器;抽象類則可以包含構造器,抽象類裡的構造器不是用於建立物件,而是讓其子類呼叫這些構造器來完成屬於抽象類的初始化操作;
  • 接口裡不能包含初始化塊;但抽象類則完全可以包含初始化塊;
  • 一個類最多隻能有一個直接父類,包括抽象類;但一個類可以直接實現多個介面;

內部類

把一個類放在另一個類的內部定義,這個定義在其他類內部的類就被稱為內部類,包含內部類的類也被稱為外部類;

定義內部類與定義外部類存在如下兩點差別:

  1. 內部類比外部類可以多使用三個修飾符:private、protected、static-----外部類不可以使用這三個修飾符;
  2. 非靜態內部類不能擁有靜態成員;

非靜態內部類

大部分時候,內部類都被作為成員內部類定義,而不是作為區域性內部類(方法裡定義的內部類被稱為區域性內部類)。成員內部類是一種與成員變數、方法、構造器和初始化塊相似的類成員;區域性內部類和匿名內部類則不是類成員;

成員內部類分為兩種:

  1. 靜態內部類:使用 static 修飾的成員內部類是靜態內部類;
  2. 非靜態內部類:沒有使用 static 修飾的成員內部類是非靜態內部類;
public class Cow
{
	private double weight;
	//外部類的兩個過載的構造器
	public Cow(){}
	public Cow(double weight)
	{
		this.weight = weight;
	}
	//定義一個內部類
	private class CowLeg
	{
		//內部類的兩個屬性
		private double length;
		private String color;
		//內部類的兩個過載的構造器
		public CowLeg(){}
		public CowLeg(double length , String color)
		{
			this.length = length;
			this.color = color;
		}
		public void setLength(double length)
		{
			this.length = length;
		}
		public double getLength()
		{
			 return this.length;
		}
		public void setColor(String color)
		{
			this.color = color;
		}
		public String getColor()
		{
			 return this.color;
		}
		//內部類方法
		public void info()
		{
			System.out.println("當前牛腿顏色是:" + color + ", 高:" + length);
			//直接訪問外部類的private屬性:weight
			System.out.println("本牛腿所在奶牛重:" + weight);
		}
	}
	public void test()
	{
		CowLeg cl = new CowLeg(1.12 , "黑白相間");
		cl.info();
	}
	public static void main(String[] args)
	{
		Cow cow = new Cow(378.9);
		cow.test();
	}
}

在外部類裡使用非靜態內部類時,與平常使用普通類並沒有太大的區別;

編譯上面程式,看到在檔案所在路徑生成了兩個 class 檔案,一個是 Cow.class,另一個是 Cow $ CowLeg.class,前者是外部類 Cow 的 class 檔案,後者是內部類 CowLeg 的 class 檔案,即成員內部類(包括靜態內部類、非靜態內部類)的 class 檔案總是這種形式:OuterClass$InnerClass.class;

在非靜態內部類裡可以直接訪問外部類的 private 成員。這是因為在非靜態內部類物件裡,儲存了一個它所寄生的外部類物件的引用;
在這裡插入圖片描述

當在非靜態內部類的方法內訪問某個變數時。系統優先在該方法內查詢是否存在該名字的區域性變數,如果存在就使用該變數;如果不存在,則到該方法所在的內部類中查詢是否存在該名字的成員變數,如果存在則使用該成員變數;如果不存在,則到該內部類所在的外部類中查詢是否存在該名字的成員變數,如果存在則使用該成員變數;如果依然不存在,系統將出現編譯錯誤:提示找不到該變數;

因此,如果外部類成員變數、內部類成員變數與內部類裡方法的區域性變數同名,則可通過使用 this、外部類類名.this 作為限定來區分:

public class DiscernVariable
{
	private String prop = "外部類屬性";
	private class InClass
	{
		private String prop = "內部類屬性";
		public void info()
		{
			String prop = "區域性變數";
			//通過 外部類類名.this.varName 訪問外部類例項屬性
			System.out.println("外部類的屬性值:" + DiscernVariable.this.prop);
			//通過 this.varName 訪問外內部類例項的屬性
			System.out.println("內部類的屬性值:" + this.prop);
			//直接訪問區域性變數
			System.out.println("區域性變數的屬性值:" + prop);
		}
	}
	public void test()
	{
		InClass in = new InClass();
		in.info();
	}
	public static void main(String[] args) 
	{
		new DiscernVariable().test();
	}
}

非靜態內部類的成員可以訪問外部類的 private 成員,但反過來就不成立了。如果外部類需要訪問非靜態內部類的成員,必須顯式建立非靜態內部類物件來呼叫訪問其例項成員:

public class Outer
{
	private int outProp = 9;
	class Inner
	{
		private int inProp = 5;
		public void acessOuterProp()
		{
			//內部類可以直接訪問外部類的成員
			System.out.println("外部類的outProp屬性值:" + outProp);
		}
	}
	public void accessInnerProp()
	{
		//外部類不能直接訪問內部類屬性,下面程式碼出現編譯錯誤
		//System.out.println("內部類的inProp屬性值:" + inProp);
		//如需訪問內部類成員,必須顯式建立內部類物件
		System.out.println("內部類的inProp屬性值:" + new Inner().inProp);

	}
	public static void main(String[] args)
	{
		//執行下面程式碼,只建立了外部類物件,還未建立內部類物件
		Outer out = new Outer();
	}
}

在這裡插入圖片描述

Java不允許在非靜態內部類裡定義靜態成員:

public class InnerNoStatic
{
	private class InnerClass
	{
		/*
		下面三個靜態宣告都將引發如下編譯錯誤:
		非靜態內部類不能有靜態宣告
		*/
		static
		{
			System.out.println("==========");
		}
		private static int inProp;
		private static void test(){};
	}
}

非靜態內部類裡不能有靜態方法、靜態成員變數、靜態初始化塊;

靜態內部類

使用 static 修飾的內部類稱為靜態內部類;

靜態內部類可以包含靜態成員,也可以包含非靜態成員。

根據靜態成員不能訪問非靜態成員的規則,靜態內部類不能訪問外部類的例項成員,只能訪問外部類的類成員。即使是靜態內部類的例項方法也不能訪問外部類的例項成員,只能訪問外部類的靜態成員;

public class TestStaticInnerClass
{
	private int prop1 = 5;
	private static int prop2 = 9;
	static class StaticInnerClass
	{
		private static int age;
		public void accessOuterProp()
		{
			//下面程式碼出現錯誤:靜態內部類無法訪問外部類的例項成員
			System.out.println(prop1);
			//下面程式碼正常
			System.out.println(prop2);
		}
	}
}

外部類依然不能直接訪問靜態內部類的成員,但可以使用靜態內部類的類名作為呼叫者來訪問靜態內部類的類成員,也可以使用靜態內部類物件作為呼叫者來訪問靜態內部類的例項成員:

public class AccessStaticInnerClass
{
	static class StaticInnerClass
	{
		private static int prop1 = 5;
		private int prop2 = 9;
	}
	public void accessInnerProp()
	{
		//System.out.println(prop1);
		//上面程式碼出現錯誤,應改為如下形式:通過類名訪問靜態內部類的類成員
		System.out.println(StaticInnerClass.prop1);
		//System.out.println(prop2);
		//上面程式碼出現錯誤,應改為如下形式:通過例項訪問靜態內部類的例項成員
		System.out.println(new StaticInnerClass().prop2);
	}
}

Java允許在接口裡定義內部類,接口裡定義的內部類預設使用 public static 修飾,也就是說,介面內部類只能是靜態內部類;

如果為介面內部類指定訪問控制符,則只能指定 public 訪問控制符;如果定義介面內部類時省略訪問控制符,則該內部類預設是 public 訪問控制權限;

使用內部類

1:在外部類內部使用內部類
通過 new 呼叫內部類構造器來建立例項;

唯一存在的一個區別是:不要在外部類的靜態成員(包括靜態方法和靜態初始化塊)中使用非靜態內部類,因為靜態成員不能訪問非靜態成員;

2:在外部類以外使用非靜態內部類
在外部類以外的地方訪問內部類(包括靜態和非靜態兩種),則內部類不能使用 private 訪問控制權限, private 修飾的內部類只能在外部類內部使用。對於其他訪問修飾符修飾的內部類,則能在訪問控制符對應的訪問許可權內使用;

  • 省略訪問控制符的內部類:只能被與外部類處於同一包中的其他類所訪問;
  • protected 修飾的內部類:可被與外部類處於同一包中的其他類和外部類的子類所訪問;
  • public 修飾的內部類:可以在任何地方被訪問;

在外部類以外的地方使用內部類時,內部類完整的類名應該是:OuterClass.InnerClass;

由於非靜態內部類的物件必須寄生在外部類的物件裡,因此建立非靜態內部類物件之前,必須先建立其外部類物件。在外部類以外的地方建立非靜態內部類例項的語法:

OuterInstance.new InnerConstructor()
class Out
{
	//定義一個非靜態內部類,不使用訪問控制符,即同一個包中其他類可訪問該內部類
	class In
	{
		public In(String msg)
		{
			System.out.println(msg);
		}
	}
}
public class CreateInnerInstance
{
	public static void main(String[] args) 
	{
		Out.In in = new Out().new In("測試資訊");
		/*
		上面程式碼可改為如下三行程式碼:
		使用OutterClass.InnerClass的形式定義內部類變數
		Out.In in;
		建立外部類例項,非靜態內部類例項將寄存在該例項中
		Out out = new Out();
		通過外部類例項和new來呼叫內部類構造器建立非靜態內部類例項
		in = out.new In("測試資訊");
		*/
	}
}

3:在外部類以外使用靜態內部類
因為靜態內部類是外部類類相關的,因此建立靜態內部類物件時無須建立外部類物件;

new OuterClass.InnerConstructor()
class StaticOut
{
	//定義一個靜態內部類,不使用訪問控制符,即同一個包中其他類可訪問該內部類
	static class StaticIn
	{
		public StaticIn()
		{
			System.out.println("靜態內部類的構造器");
		}
	}
}
public class CreateStaticInnerInstance
{
	public static void main(String[] args) 
	{
		StaticOut.StaticIn in = new StaticOut.StaticIn();
		/*
		上面程式碼可改為如下兩行程式碼:
		使用OutterClass.InnerClass的形式定義內部類變數
		StaticOut.StaticIn in;
		通過new來呼叫內部類構造器建立靜態內部類例項
		in = new StaticOut.StaticIn();
		*/
	}
}

區域性內部類

方法裡定義的內部類稱為區域性內部類。區域性內部類也不能使用訪問控制符和 static 修飾符修飾;

public class LocalInnerClass
{
	public static void main(String[] args) 
	{
		//定義區域性內部類
		class InnerBase
		{
			int a;
		}
		//定義區域性內部類的子類
		class InnerSub extends InnerBase
		{
			int b;
		}
		//建立區域性內部類的物件
		InnerSub is = new InnerSub();
		is.a = 5;
		is.b = 8;
		System.out.println("InnerSub物件的a和b屬性是:" + is.a + "," + is.b);
	}
}

在這裡插入圖片描述

匿名內部類

匿名內部類適合建立那種只需要一次使用的類;

匿名內部類必須繼承一個父類,或實現一個介面,但最多隻能繼承一個父類,或實現一個介面;

匿名內部類有如下兩條規則:

  1. 匿名內部類不能是抽象類;
  2. 匿名內部類不能定義構造器。由於匿名內部類沒有類名,所以無法定義構造器,但匿名內部類可以定義初始化塊,可以通過例項初始化塊來完成構造器需要完成的事情;
interface Product
{
	public double getPrice();
	public String getName();
}
public class TestAnonymous
{
	public void test(Product p)
	{
		System.out.println("購買了一個" + p.getName() + ",花掉了" + p.getPrice());
	}
	public static void main(String[] args) 
	{
		TestAnonymous ta = new TestAnonymous();
		//呼叫test方法時,需要傳入一個Product引數,此處傳入其匿名實現類的例項
		ta.test(new Product()
		{
			public double getPrice()
			{
				return 567.8;
			}
			public String getName()
			{
				return "AGP顯示卡";
			}
		});
	}
}

對於上面建立Product實現類物件的程式碼,可以拆分成如下程式碼:

class AnonymousProduct implements Product
{
	public double getPrice()
	{
		return 567.8;
	}
	public String getName()
	{
		return "AGP顯示卡";
	}
}
ta.test(new AnonymousProduct());

通過繼承父類來建立匿名內部類時,匿名內部類將擁有和父類相似的構造器(擁有相同的形參列表);

Java8之前,Java要求被區域性內部類、匿名內部類訪問的區域性變數必須使用 final 修飾,從Java8開始這個限制被取消