JAVA學習筆記9,抽象類和介面及內部類
阿新 • • 發佈:2021-02-08
第九章 抽象類和介面及內部類
一 抽象類和抽象方法
1.1 抽象類
用abstract關鍵字來修飾一個類,這個類叫做抽象類。
- 此類不能例項化
- 抽象類中一定有構造器,便於子類例項化時呼叫
- 開發中,都會提供抽象類的子類,讓子類物件例項化,完成相關的操作
1.2 抽象方法
用abstract來修飾一個方法,該方法叫做抽象方法。
- 只有方法的宣告,沒有方法的實現。以分號結束
- 含有抽象方法的類必須被宣告為抽象類。反之,抽象類中可以沒有抽象方法的。
- 抽象類是用來被繼承的,抽象類的子類必須重寫父類的抽象方法,並提供方法體。若沒有重寫全部的抽象方法,仍為抽象類,需要用abstract修飾。
1.3 abstract使用注意事項
- 不能用abstract修飾變數、程式碼塊、構造器;
- 不能用abstract修飾私有方法、靜態方法、final的方法、final的類。
1.4 練習
- 編寫工資系統,實現不同型別員工(多型)的按月發放工資。如果當月出現某個Employee物件的生日,則將該僱員的工資增加100元。
- 定義一個Employee類,該類包含:private成員變數name,number,birthday,其中birthday 為MyDate類的物件;abstract方法earnings();toString()方法輸出物件的name,number和birthday。
- MyDate類包含:private成員變數year,month,day ;
toDateString()方法返回日期對應的字串:xxxx年xx月xx日 - 定義SalariedEmployee類繼承Employee類,實現按月計算工資的員工處理。該類包括:private成員變數monthlySalary;實現父類的抽象方法earnings(),該方法返回monthlySalary值;toString()方法輸出員工型別資訊及員工的name,number,birthday。
- 參照SalariedEmployee類定義HourlyEmployee類,實現按小時計算工資的員工處理。該類包括:private成員變數wage和hour;實現父類的抽象方法earnings(),該方法返回wage*hour值;toString()方法輸出員工型別資訊及員工的name,number,birthday。
- 建立Employee變數陣列並初始化,該陣列存放各類僱員物件的引用。利用迴圈結構遍歷陣列元素,輸出各個物件的型別,name,number,birthday,以及該物件生日。當鍵盤輸入本月月份值時,如果本月是某個Employee物件的生日,還要輸出增加工資資訊。
import java.util.Scanner;
public class Test3 {
public static void main(String[] args) {
Employee[] emps= new Employee[]{
new SalariedEmployee("tom", new MyDate(1999, 2, 11), 6000),
new SalariedEmployee("john", new MyDate(1992, 5, 28), 7000),
new HourlyEmployee("wang", new MyDate(2000, 8, 1), 60, 31)
};
Scanner scan = new Scanner(System.in);
System.out.print("輸入月份:");
int m=scan.nextInt();
for (int i = 0; i < emps.length; i++) {
System.out.println(emps[i]);
if (m==emps[i].getBirthday().getMonth()) {
System.out.println("今天是你的生日,增加工資100元");
}
}
scan.close();
}
}
class HourlyEmployee extends Employee {
private double wage;
private int hours;
public HourlyEmployee(String name, MyDate birthday, double wage, int hours) {
super(name, birthday);
this.wage = wage;
this.hours = hours;
}
@Override
public double earnings() {
return wage*hours;
}
@Override
public String toString() {
return "HourlyEmployee [名字:"+getName()+" 員工號:"+getNumber()+" 薪水:" + earnings() +" 生日:"+getBirthday() +"]";
}
}
class SalariedEmployee extends Employee {
private double monthlySalary;
@Override
public double earnings() {
return monthlySalary;
}
public SalariedEmployee(String name, MyDate birthday, double monthlySalary) {
super(name,birthday);
this.monthlySalary = monthlySalary;
}
@Override
public String toString() {
return "SalariedEmployee [名字:"+getName()+" 員工號:"+getNumber()+" 薪水:" + earnings() +" 生日:"+getBirthday() +"]";
}
}
class MyDate{
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public String toString() {
return year+"年" + month + "月" + day + "日";
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
}
abstract class Employee{
private String name;
private int number;
private MyDate birthday;
public abstract double earnings();
private static int total=0;
private static int id=1000;
public Employee(String name,MyDate birthday) {
this.name = name;
this.number = (total++)+id;
this.birthday = birthday;
}
public String getName() {
return name;
}
public int getNumber() {
return number;
}
public MyDate getBirthday() {
return birthday;
}
public void setName(String name) {
this.name = name;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
}
二 介面interface
介面(interface)是抽象方法和常量值定義的集合。
2.1 介面的特點:
- 用interface來定義。
- 介面中的所有成員變數都預設是由public static final修飾的。
- 介面中的所有抽象方法都預設是由public abstract修飾的。
- 介面中沒有構造器。
- 介面採用多繼承機制。
2.2 介面語法格式
先寫extends,後寫implements
class SubClass extends SuperClass implements InterfaceA{ }
interface Person{
String name="人";//省略public final static
public final static String con="中國";
void eat();//省略public abstract
public abstract void walk();
}
interface Student extends Person{
void study();
}
class Pupil implements Student{
@Override
public void eat() {
}
@Override
public void walk() {
}
@Override
public void study() {
}
}
2.3 注意事項
- 一個類可以實現多個介面,介面也可以繼承其它介面。
- 實現介面的類中必須提供介面中所有方法的具體實現內容,方可例項化。否則,仍為抽象類。
- 介面的主要用途就是被實現類實現。(面向介面程式設計)
- 與繼承關係類似,介面與實現類之間存在多型性
- 介面和類是並列關係,或者可以理解為一種特殊的類。從本質上講,介面是一種特殊的抽象類,這種抽象類中只包含常量和方法的定義(JDK7.0及之前),而沒有變數和方法的實現。
2.4 介面和抽象類之間的對比
- 抽象類
- 包含抽象方法的類
- 由構造方法、抽象方法、普通方法、常量、變數組成
- 子類繼承抽象類(extends)
- 抽象類可以實現多個介面
- 抽象類有單繼承的侷限
- 常見的設計模式有模板方法
- 介面
- 主要是抽象方法和全域性常量的集合
- 由常量、抽象方法、(jdk8.0:預設方法、靜態方法)構成
- 子類實現介面(implements)
- 介面不能繼承抽象類,但允許繼承多個介面
- 常見的設計模式有簡單工廠、工廠方法、代理模式
- 相同點
- 都通過物件的多型性產生例項化物件
- 使用建議
- 如果抽象類和介面都可以使用的話,優先使用介面,避免單繼承的侷限
在開發中,常看到一個類不是去繼承一個已經實現好的類,而是要麼繼承抽象類,
要麼實現介面。
2.5 interface Java 8新特性
- 靜態方法:使用 static 關鍵字修飾。可以通過介面直接呼叫靜態方法,並執行其方法體。可以在標準庫中找到像Collection/Collections或者Path/Paths這樣成對的介面和類。
- 預設方法:預設方法使用 default 關鍵字修飾。可以通過實現類物件來呼叫。在已有的介面中提供新方法的同時,還保持了與舊版本程式碼的相容性。比如:Java 8 API中對Collection、List、Comparator等介面提供了豐富的預設方法。
- 若一個介面中定義了一個預設方法,而另外一個介面中也定義了一個同名同參數的方法(不管此方法是否是預設方法),在實現類同時實現了這兩個介面時,會出現介面衝突。
- 解決辦法:實現類必須覆蓋介面中同名同參數的方法,來解決衝突。
- 若一個介面中定義了一個預設方法,而父類中也定義了一個同名同參數的非抽象方法,則不會出現衝突問題。此時遵守:類優先原則。介面中具有相同名稱和引數的預設方法會被忽略。
- 若一個介面中定義了一個預設方法,而另外一個介面中也定義了一個同名同參數的方法(不管此方法是否是預設方法),在實現類同時實現了這兩個介面時,會出現介面衝突。
2.6 練習
- 定義一個介面CompareObject用來實現兩個物件的比較。
- 若返回值是 0 , 代表相等; 若為正數,代表當
前物件大;負數代表當前物件小 - 定義一個Circle類,宣告redius屬性,提供getter和setter方法
- 定義一個ComparableCircle類,繼承Circle類並且實現CompareObject介面。在ComparableCircle類中給出介面中方法compareTo的實現體,用來比較兩個圓的半徑大小。
- 定義一個測試類InterfaceTest,建立兩個ComparableCircle物件,呼叫compareTo
方法比較兩個類的半徑大小。 - 思考 :參照上述做法定義矩形類 Rectangle 和 ComparableRectangle類,在ComparableRectangle類中給出compareTo方法的實現,比較兩個矩形的面積大小。
public class Test4 {
public static void main(String[] args) {
ComparableCircle cc1 = new ComparableCircle(1.5);
ComparableCircle cc2 = new ComparableCircle(1.5);
System.out.println(cc1.compareTo(cc2));//0
}
}
class ComparableCircle extends Circle implements CompareObject {
@Override
public int compareTo(Object o) {
if(o == null)
throw new RuntimeException();
if(this == o)
return 0;
if(o instanceof ComparableCircle){
ComparableCircle cc=(ComparableCircle) o;
if (cc.getRedius()==this.getRedius()){
return 0;
}else if(this.getRedius()<cc.getRedius()){
return -1;
}else{
return 1;
}
}
throw new RuntimeException();
}
public ComparableCircle(double redius) {
super(redius);
}
}
class Circle{
private double redius;
public Circle() {
}
public Circle(double redius) {
this.redius = redius;
}
public double getRedius() {
return redius;
}
public void setRedius(double redius) {
this.redius = redius;
}
}
interface CompareObject{
int compareTo(Object o);
}
三 內部類
3.1 內部類定義
- 一個類A的定義位於另一個類B的內部,前者A稱為內部類,後者B稱為外部類。
- Inner class一般用在定義它的類或語句塊之內,在外部引用它時必須給出完整的名稱。
- Inner class的名字不能與包含它的外部類類名相同;
3.2 分類
- 成員內部類(static成員內部類和非static成員內部類)
- 作為外部類的成員
- 和外部類不同,Inner class還可以宣告為private或protected;
- 可以呼叫外部類的結構
- Inner class 可以宣告為static的,但此時就不能再使用外層類的非static的成員變數;
- 作為一個類
- 可以在內部定義屬性、方法、構造器等結構
- 可以宣告為abstract類 ,因此可以被其它的內部類繼承
- 可以宣告為final的
- 編譯以後生成OuterClass$InnerClass.class位元組碼檔案(也適用於區域性內部類)
- 注意
- 非static的成員內部類中的成員不能宣告為static的,只有在外部類或static的成員內部類中才可宣告static成員。
- 外部類訪問成員內部類的成員,需要“內部類.成員”或“內部類物件.成員”的方式
- 成員內部類可以直接使用外部類的所有成員,包括私有的資料
- 當想要在外部類的靜態成員部分使用內部類時,可以考慮內部類宣告為靜態的
- 作為外部類的成員
public class Test5 {
private int num = 123;
public class Inner {
private int num = 456;
public void mb(int num) {
System.out.println(num); // 區域性變數
System.out.println(this.num); // 內部類物件的屬性
System.out.println(Test5.this.num); // 外部類物件屬性s
}
}
public static void main(String args[]) {
Test5 out = new Test5();
Test5.Inner inner = out.new Inner();
inner.mb(110);
}
}
- 區域性內部類(不談修飾符)、匿名內部類
- 區域性內部類的申明
class 外部類{
方法(){//方法內
class 區域性內部類{ }
}
{//程式碼塊內
class 區域性內部類{ }
}
}
- 區域性內部類使用
- 只能在宣告它的方法或程式碼塊中使用,而且是先聲明後使用。除此之外的任何地方都不能使用該類
- 但是它的物件可以通過外部方法的返回值返回使用,返回值型別只能是區域性內部類的父類或父介面型別
- 區域性內部類的特點
- 內部類仍然是一個獨立的類,在編譯之後內部類會被編譯成獨立的.class檔案,但是前面冠以外部類的類名和$符號,以及數字編號。
- 只能在宣告它的方法或程式碼塊中使用,而且是先聲明後使用。除此之外的任何地方都不能使用該類。
- 區域性內部類可以使用外部類的成員,包括私有的。
- 區域性內部類可以使用外部方法的區域性變數,但是必須是final的。由區域性內部類和區域性變數的宣告週期不同所致。
- 區域性內部類和區域性變數地位類似,不能使用public,protected,預設,private
- 區域性內部類不能使用static修飾,因此也不能包含靜態成員
- 匿名內部類
- 不能定義任何靜態成員、方法和類,只能建立匿名內部類的一個例項。一個匿名內部類一定是在new的後面,用其隱含實現一個介面或實現一個類。
- 格式:
new 父類構造器(實參列表)|實現介面(){ //匿名內部類的類體部分 }
- 匿名內部類的特點
- 匿名內部類必須繼承父類或實現介面
- 匿名內部類只能有一個物件
- 匿名內部類物件只能使用多型形式引用
public class Test5 {
public static void main(String args[]) {
Test5 test = new Test5();
test.getRunnable1().run();//我是區域性內部類
test.gRunnable2().run();//我是匿名區域性內部類
}
public Runnable getRunnable1() {
class Runn implements Runnable{
@Override
public void run() {
System.out.println("我是區域性內部類");
}
}
return new Runn();
}
public Runnable gRunnable2() {
return new Runnable(){
@Override
public void run() {
System.out.println("我是匿名區域性內部類");
}
};
}
}
Runnable{
@Override
public void run() {
System.out.println("我是區域性內部類");
}
}
return new Runn();
}
public Runnable gRunnable2() {
return new Runnable(){
@Override
public void run() {
System.out.println("我是匿名區域性內部類");
}
};
}
}