Java面向對象語法彙總
接着上一篇《Java語法彙總-基礎語法篇》,來學習彙總Java的面向對象相關語法,總體感覺跟C#的語法差不多,基本上理念都是相通的,而且兩門語言近年來相互借鑑的也比較頻繁,只要掌握一門,那麼學習另一門也是分分鐘的事。
1. 基本類的定義
// 定義Person類
class Person {
// 定義字段
private String name;
private int age;
// 無參默認構造方法
public Person() {
}
// 有參構造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 調用另一個構造方法Person(String, int)
public Person(String name) {
this(name, 18);
}
// 屬性賦值方法
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
if (age < 0 || age > 100) {
throw new IllegalArgumentException("invalid age value");
}
this.age = age;
}
// 方法重載
public void hello() {
System.out.println("Hello, world!");
}
public void hello(String name) {
System.out.println("Hello, " + name + "!");
}
public void hello(String name, int age) {
if (age < 18) {
System.out.println("Hi, " + name + "!");
} else {
System.out.println("Hello, " + name + "!");
}
}
}
2. 繼承與轉型
- 子類繼承父類
class Student extends Person {
private int score;
public Student(String name, int age, int score) {
super(name, age); // 調用父類的構造方法Person(String, int)
this.score = score;
}
public int getScore() {
return this.score;
}
public void setScore(int score) {
this.score=score;
}
}
- 父子類轉型
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Person p = new Student();
if (p instanceof Student) {
// 只有判斷成功纔會向下轉型:
Student s = (Student) p; // 一定會成功
}
3. Override與多態
- 在繼承關係中,子類如果定義了一個與父類方法簽名完全相同的方法,被稱爲覆寫(Override)。
- 多態是指,針對某個類型的方法調用,其真正執行的方法取決於運行時期實際類型的方法。
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
// 加上@Override可以讓編譯器幫助檢查是否進行了正確的覆寫。
// 但是@Override不是必需的。
@Override
public void run() {
System.out.println("Student.run");
}
}
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 打印Student.run
}
}
- 多態綜合應用實例:
// 收入
class Income {
protected double income;
public Income(double income) {
this.income = income;
}
// 應繳稅款
public double getTax() {
return income * 0.1; // 稅率10%
}
}
//薪水
class Salary extends Income {
public Salary(double income) {
super(income);
}
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}
// 津貼補助
class SpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}
@Override
public double getTax() {
return 0;
}
}
public class Main {
public static void main(String[] args) {
// 給一個有普通收入、工資收入和享受特殊津貼的小夥伴算稅:
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new SpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}
public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
}
- 重寫Object方法
Object定義了幾個重要的方法:
- toString():把instance輸出爲String;
- equals():判斷兩個instance是否邏輯相等;
- hashCode():計算一個instance的哈希值。
class Person {
...
// 顯示更有意義的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}
// 比較是否相等:
@Override
public boolean equals(Object o) {
// 當且僅當o爲Person類型:
if (o instanceof Person) {
Person p = (Person) o;
// 並且name字段相同時,返回true:
return this.name.equals(p.name);
}
return false;
}
// 計算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}
- 密封關鍵字 final
如不允許被重寫,則添加final關鍵字
- final修飾的方法可以阻止被覆寫;
- final修飾的class可以阻止被繼承;
- final修飾的field必須在創建對象時初始化,隨後不可修改。
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
final class Person {
protected String name;
}
class Person {
public final String name = "Unamed";
}
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
4. 抽象類和接口
- 抽象類
如果一個class定義了方法,但沒有具體執行代碼,這個方法就是抽象方法,抽象方法用abstract修飾。
abstract class Person {
public abstract void run();
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run();
}
}
- 接口
接口interface不能有連字段,接口定義的所有方法默認都是public abstract的。一個類可以實現多個接口。
- 基礎定義
// 定義接口Person
interface Person {
void run();
String getName();
}
// 實現接口
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public String getName() {
return this.name;
}
}
// 一個類可以實現多個接口
class Student implements Person, Hello { // 實現了兩個interface
...
}
interface Hello {
void hello();
}
// 接口繼承接口
interface Person extends Hello {
void run();
String getName();
}
- default方法
實現類可以不必覆寫default方法。新增default方法時,子類不必全部修改,只需要在需要覆寫的地方去覆寫新增方法。
default方法和抽象類的普通方法是有所不同的。因爲interface沒有字段,default方法無法訪問字段,而抽象類的普通方法可以訪問實例字段。
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public class Main {
public static void main(String[] args) {
Person p = new Student("Xiao Ming");
p.run();
}
}
5. 靜態字段和靜態方法
class Person {
public static int number;
public static void setNumber(int value) {
number = value;
}
}
public class Main {
public static void main(String[] args) {
Person.setNumber(99);
System.out.println(Person.number);
}
}
public interface Person {
// 編譯器會自動加上public statc final:
int MALE = 1;
int FEMALE = 2;
}
6. 作用域
- Java內建的訪問權限包括public、protected、private和package權限;
- Java在方法內部定義的變量是局部變量,局部變量的作用域從變量聲明開始,到一個塊結束;
- final修飾符不是訪問權限,它可以修飾class、field和method;
- 一個.java文件只能包含一個public類,但可以包含多個非public類。
7. 異常的使用
- 捕獲異常
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException | NumberFormatException e) {
System.out.println("IO error");
} finally {
System.out.println("END");
}
}
- 拋出異常
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
}
static void process2() {
throw new NullPointerException();
}
// 方法如果不處理異常,就先拋出來
static byte[] toGBK(String s) throws UnsupportedEncodingException {
// 用指定編碼轉換String爲byte[]:
return s.getBytes("GBK");
}
public static void main( String[] args )
{
try {
process1();
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( "Hello World!" );
}
- 自定義異常
一個常見的做法是自定義一個BaseException作爲“根異常”,然後,派生出各種業務類型的異常。
BaseException需要從一個適合的Exception派生,通常建議從RuntimeException派生,其他業務類型的異常就可以從BaseException派生。
public class BaseException extends RuntimeException {
}
public class UserNotFoundException extends BaseException {
}
public class LoginFailedException extends BaseException {
}
// 自定義的BaseException應該提供多個構造方法
public class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
- 空指針異常處理
如果調用方一定要根據null判斷,比如返回null表示文件不存在,那麼考慮返回Optional,這樣調用方必須通過Optional.isPresent()判斷是否有結果。
public Optional<String> readFromFile(String file) {
if (!fileExist(file)) {
return Optional.empty();
}
...
}
8. 反射的使用
反射就是Reflection,Java的反射是指程序在運行期可以拿到一個對象的所有信息。
- 獲取 Class
// 直接通過一個class的靜態變量class獲取
Class cls = String.class;
// 可以通過該實例變量提供的getClass()方法獲取
String s = "Hello";
Class cls = s.getClass();
// 可以通過靜態方法Class.forName()獲取
Class cls = Class.forName("java.lang.String");
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
}
public static void main(String[] args) {
printClassInfo("".getClass());
printClassInfo(Runnable.class);
printClassInfo(java.time.Month.class);
printClassInfo(String[].class);
printClassInfo(int.class);
}
- 獲取字段
- getName():返回字段名稱,例如,“name”;
- getType():返回字段類型,也是一個Class實例,例如,String.class;
- getModifiers():返回字段的修飾符
class Student extends Person {
public int score;
private int grade;
}
class Person {
public String name;
public Person(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Class stdClass = Student.class;
// 獲取public字段"score":
System.out.println(stdClass.getField("score"));
// 獲取繼承的public字段"name":
System.out.println(stdClass.getField("name"));
// 獲取private字段"grade":
System.out.println(stdClass.getDeclaredField("grade"));
Object p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
// 強制允許訪問
f.setAccessible(true);
// 獲取字段的值
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"
}
}
9. 註解的使用
註解是放在Java源碼的類、方法、字段、參數前的一種特殊“註釋”,註釋會被編譯器直接忽略,註解則可以被編譯器打包進入class文件,因此,註解是一種用作標註的“元數據”。
- 定義註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 0;
int max() default 255;
}
- 實現註解
void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
// 遍歷所有Field:
for (Field field : person.getClass().getFields()) {
// 獲取Field定義的@Range:
Range range = field.getAnnotation(Range.class);
// 如果@Range存在:
if (range != null) {
// 獲取Field的值:
Object value = field.get(person);
// 如果值是String:
if (value instanceof String) {
String s = (String) value;
// 判斷值是否滿足@Range的min/max:
if (s.length() < range.min() || s.length() > range.max()) {
throw new IllegalArgumentException("Invalid field: " + field.getName());
}
}
}
}
}
- 使用註解
public class Person {
@Range(min=1, max=20)
public String name;
@Range(max=10)
public String city;
}
public static void main( String[] args )
{
Person p =new Person();
p.city="23456789101";
try {
check(p);
}catch (Exception e)
{
System.out.println(e.getMessage());
}
System.out.println( "Hello World!" );
}