第4節:Java基礎 - 必知必會(中)

第4節:Java基礎 - 必知必會(中)

本小節是Java基礎篇章的第二小節,主要講述抽象類與接口的區別,註解以及反射等知識點。

一、抽象類和接口有什麼區別

抽象類和接口的主要區別可以總結如下:

  • 抽象類中可以沒有抽象方法,JDK8版本開始提供了接口總方法的default實現

  • 抽象類和類一樣是單繼承的;接口可以實現多個父類

  • 抽象類中可以存在普通的成員變量;接口中的變量必須是static final類型的,必須被初始化,接口中只能有常量,沒有變量

     

    解析

    在Java中,我們用abstract來定義抽象類,通過interface關鍵字來定義接口。接口和抽象類中都可以定義抽象方法,然後交由其實現類來實現該抽象方法。

     

    抽象類和接口應該如何選擇?分別在什麼情況下使用呢?

 

根據抽象類和接口的不同之處,當我們僅僅需要定義一些抽象方法而不需要其餘二外的具體方法或者變量的時候,我們嗯可以使用接口。反之,則需要使用抽象類,因爲抽象類中可以有非抽象方法和變量

 

默認方法

  既然說到了JDK8接口中的方法可以實現,那麼我們來看下default方法的具體實現。我們先給出一個接口中的default方法Demo,如下所示:

public interface MyInterface {
    // 定義一個已經實現的方法,使用default表明
    default void say(String message){
        System.out.println("Hello "+message);
    }
    // 普通的抽象方法
    void test();
}

  當一個類實現該接口時,可以繼承到該接口中的默認方法,如下所示:

public interface MyInterface {
    // 定義一個已經實現的方法,使用default表明
    default void say(String message){
        System.out.println("Hello "+message);
    }
    // 普通的抽象方法
    void test();
}
 
class MyClass implements MyInterface{
    @Override
    public void test() {
        System.out.println("test...");
    }
}
class Main{
    public static void main(String[] args) {
        MyClass client = new MyClass();
        client.test();
        client.say("World...");
    }
}

  這樣的話,大家就會有疑問,如果兩個接口中存在同樣的默認方法,實現類繼承的是哪一個呢?Demo如下:

public interface MyInterface {
    // 定義一個已經實現的方法,使用default表明
    default void say(String message){
        System.out.println("Hello "+message);
    }
    // 普通的抽象方法
    void test();
}
interface MyInterface2{
    // 定義一個已經實現的方法,使用default表明
    default void say(String message){
        System.out.println("[2]-Hello "+message);
    }
}
// 此處會編譯錯誤
class MyClass implements MyInterface, MyInterface2{
    @Override
    public void test() {
        System.out.println("test...");
    }
}

這個時候,實現類那裏會編譯錯誤 .我們有兩種處理方式,如下所示:

  • 重寫多個接口中的相同的默認方法

  • 在實現類中指定要使用哪個接口中的默認方法

a、重寫多個接口中的相同的默認方法

class MyClass implements MyInterface, MyInterface2{
    @Override
    public void say(String message) {
        System.out.println("[Client]-Hello "+message);
    }
    @Override
    public void test() {
        System.out.println("test...");
    }
}

b、在實現類中指定要使用哪個接口中的默認方法:

class MyClass implements MyInterface, MyInterface2{
    // 手動指定哪個默認方法生效
    public void say(String message) {
        MyInterface.super.say(message);
    }
    @Override
    public void test() {
        System.out.println("test...");
    }
}

那麼JDK8中爲什麼會出現默認方法呢?

使用接口,使得我們可以面向抽象編程,但是其有一個缺點就是當接口中有改動的時候,需要修改所有的實現類。在JDK8中,爲了給已經存在的接口增加新的方法並且不影響已有的實現,所以引入了接口中的默認方法實現。

默認方法允許在不打破現有繼承體系的基礎上改進接口,解決了接口的修改與現有的實現不兼容的問題。該特性在官方庫中的應用是:給java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。在我們實際開發中,接口的默認方法應該謹慎使用,因爲在複雜的繼承體系中,默認方法可能引起歧義和編譯錯誤

二、java中的8中基本數據類型及其取值範圍

Java種的8種基本數據類型分別是:byte,short,int,long,float,double,char以及boolean。boolean類型的取值爲true和false兩種,其餘每一種基本類型都佔有一定的字節,並且擁有着最大值和最小值。比如int的取值範圍爲 Integer.MIN_VALUE 到 Integer.MAX_VALUE。這個題目的答案,我希望大家可以自己動手輸入代碼來一一查看以加深記憶。這裏給出每種基本類型所佔用的字節數:

  • byte:1字節

  • short:2字節

  • int:4個字節

  • long:8字節

  • float:4字節

  • double:8字節

  • char:2字節

  • boolean:Java規範中並沒有規定boolean類型所佔字節數

解析:

這是一個特別基礎的題目,面試時候基本會考察各個基本類型所佔的字節數,需要準確記憶與理解。關於取值範圍,如果實在記憶有點缺失,可以和面試官說通過哪個字段可以獲取到其取值範圍也算尚可

三、Java中的元素註解有哪些?

Java中提供了4個元註解,元註解的作用是負責註解其它註解。

@Target

說明註解所修飾的對象範圍,關鍵源碼如下:

 

public @interface Target { 
    ElementType[] value(); 
} 
public enum ElementType { 
  TYPE,FIELD,METHOD,PARAMETED,CONSTRUCTOR,LOCAL_VARIABLE,ANNOCATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE 
} 

例如,如下的註解使用@Target標註,表明MyAnn註解就只能作用 在類/接口和方法上。

 @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyAnn { } 

@Rentention

保留策略定義了該註解被保留的時間長短。關鍵源碼如下:

 public @interface Retention { RetentionPolicy value(); } public enum RetentionPolicy { SOURCE, CLASS, RUNTIME } 

 

其中,SOURCE:表示在源文件中有效(即源文件保留);CLASS:表示在class文件中有效(即class保留);RUNTIME:表示在運行時有效(即運行時保留)。例如,@Retention(RetentionPolicy.RUNTIME)標註表示該註解在運行時有效。

@Documented

該註解用於描述其它類型的annotation應該被作爲被標註的程序成員的公共API,因此可以被javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。關鍵源碼如下:

public @interface Documented {
}

  

@Inherited

該註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。關鍵源碼如下:

public @interface Inherited {
}

  

解析:

Java中的註解是一個基礎知識點,我們在程序中頻繁的使用註解。使用註解可以代替配置文件,比如SpringBoot就是提供了大量的註解來代替配置文件,從而極大的方便了Web項目的搭建與開發。那麼,我們再來詳細闡述下Java中的註解吧。

註解的作用:

代替繁雜的配置文件,簡化開發。

何定義一個註解?

定義註解類不能使用class、enum以及interface,必須使用@interface。下邊是一個簡單的註解定義:

public @interface MyAnn{}

如何定義註解的屬性?

public @interface MyAnn { 
    String value(); 
    int value1(); 
}
// 使用註解MyAnn,可以設置屬性
@MyAnn(value1=100,value="hello") 
public class MyClass { 
}

  

定義註解時候的value就是屬性,看着是一個方法,但我們稱它爲屬性。當爲註解指定屬性後,那麼在使用註解時就必須要給屬性賦值了。

四、Java中反射機制

反射機制是指在運行中,對於任意一個類,都能夠知道這個類的所有屬性和方法。對於任意一個對象,都能夠調用它的任意一個方法和屬性。即動態獲取信息和動態調用對象方法的功能稱爲反射機制。

反射機制的作用

  • 在運行時判斷任意一個對象所屬的類

  • 在運行時構造一個類的對象

  • 在運行時判斷任意一個類所具有的成員變量和方法

  • 在運行時調用任意一個對象的方法,生成動態代理

    與反射相關的類:

  • Class:表示類,用於獲取類的相關信息

  • Field:表示成員變量,用於獲取實例變量和靜態變量等

  • Method:表示方法,用於獲取類中的方法參數和方法類型等

  • Constructor:表示構造器,用於獲取構造器的相關參數和類型等

這裏我們講述下如何獲取Class類吧,獲取Class類有三種基本方式:

(1)通過類名稱.class來獲取Class類對象:

Class c = int.class;
Class c = int[ ].class;
Class c = String.class

  

(2)通過對象.getClass( )方法來獲取Class類對象:

Class c = obj.getClass( );

(3)通過類名稱加載類Class.forName( ),只要有類名稱就可以得到Class:

Class c = Class.forName(“cn.ywq.Demo”);

接下來,我們給出一個以反射方式來創建對象的Demo:

package com.ywq;
 
public class Demo1 {
    public static void main(String[] args) throws Exception {
        String className = "com.ywq.User";
        // 獲取Class對象
        Class clazz = Class.forName(className);
        // 創建User對象
        User user = (User)clazz.newInstance();
        // 和普通對象一樣,可以設置屬性值
        user.setUsername("yangwenqiang");
        user.setPassword("19931020");
 
        System.out.println(user);
    }
}
 
class User {
    private String username;
    private String password;
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
 
    @Override
    public String toString() {
        return "User [username=" + username + ", password=" + password + "]";
    }
}

附加

clazz.newInstance()和clazz.new()區別總結如下:

1.構造方法實例化, 會判斷實例化的對象的Class是否已經加載,如果沒有加載需先加載類,然後初始化類,返回一個實例.

clazz.newInstance()方法必須在類加載完成之後才能調用.也就是說,這一步不會有類加載的步驟.類加載必須在此之前加載完成,否則不能調用該方法.

比如Class.forName(Stirng clazzName)包含加載和連接兩個階段,連接階段包含:驗證,準備,解析三個階段;

然後初始化類,返回一個實例

2.clazz調用的是無參構造方法,而實例化構造方法使用有參構造方法. 當然需要public修飾. 反正要能訪問的到.

3.clazz反射可以用來解耦合,動態的構造實例. 比如IOC控制反轉,以及基於接口編程.可以把實現類或者子類,強轉爲接口類或父。

 

關於反射這塊的詳細API接口介紹,建議大家參考官方提供的API接口文檔 。

總結

本小節是Java基礎篇章的第二小節,本小節中介紹的接口和抽象類的區別,建議大家熟練掌握。註解和反射機制在Java基礎中具有一定的難度,希望大家可以多加理解與掌握。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章