Java面向對象詳解

Java OOP

什么是面向對象思想?

把一組數據和處理他們的方法組成對象(object),把相同行為的對象歸納為類(class),通過類的封裝(encapsulation)隱藏內部細節,通過繼承(inheritance)實現類的的特化(specialization)/泛化(generalization),通過多態(polymorphism)實現基于對象類型的動態分派(dynamic dispatch). --- 來源于知乎(如何用一句話說明什么是面向對象思想?)

這個定義指出了面向對象的四個特點:抽象、封裝、繼承和多態

類和對象

對象

具有一組數據和處理這些數據的方法組成一個對象。例如:

對象 person

數據 姓名,年齡

方法 說話,吃飯,睡覺

類可以看成是對象的抽象,是可以生成眾多對象的一個模板。例如我們有一個Person類,有姓名年齡兩個數據。這就是個模板,它不代表一個人person,但是能夠通過這個模板創造出眾多person。

// 定義類
權限修飾符 class 類名 {
    屬性
    方法  
}

public class Person {
    String name; //名字
    int age;     //年齡
    // 吃飯方法
    void eat() {
        
    }
    // 說話方法
    void speak() {
        
    }
}

構造函數(Constructor)

  • 構造函數概念

    構造函數是用來初始化對象的一段代碼塊。它具有如下特點:

    1. 沒有返回值。
    2. 構造函數的名字和類名必須一致。
    3. 不能有static, final, abstract, synchronised 等關鍵字修飾。
  • 如何用構造函數初始化對象

    // Java通過new關鍵字生成對象,并且調用Person類的構造函數初始化這個新的對象
    Person person = new Person();
    
  • 構造函數的類型

    • 默認構造函數: 如果一個類沒有構造函數,編譯器會在編譯階段自動添加一個無參的構造函數。

      默認構造函數
    • 無參數的構造函數: 沒有參數的構造函數,和默認的構造函數的一個區別是無參的構造函數里面可以有代碼,而默認構造函數的函數體里面沒有任何代碼。

    • 有參數的構造函數: 構造函數有參數

      public class Person {
          int age;
          String name;
          
          Person(int age, String name) {
              this.age = age;
              this.name = name;
          }
          
          public static void main(String[] args) {
              Person person = new Person(18, "Johnny");
              System.out.println(person.name);
              System.out.println(person.age);
          }
      }
      
    > 注意:如果類有構造函數,則編譯器不會在編譯階段添加默認構造函數。所以如果在類有有參數的構造函數但是沒有無參數的構造函數的情況下,調用無參數的構造函數編譯器會報錯。
    > ![](https://upload-images.jianshu.io/upload_images/4401597-84dde9ec70a7f84c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/485) 
  • 父類的構造函數---super()

    如果子類的構造函數沒有調用父類的特定構造函數,編譯器會默認添加調用父類的無參數構造函數(或者默認構造函數) --- super()。

    public class Person {
        int age;
        String name;
        
        public Person() {
            System.out.println("Person init");
        }
        
        public static void main(String[] args) {
            Man man = new Man(18, "Johnny");
        }
        
    }
    
    class Man extends Person {
        
        public Man(int age, String name) {
            System.out.println("Man init");
        }
        
    }
    

    結果:

    Person init
    Man init
    

    如果父類沒有默認構造函數(或者說無參構造函數), 則編譯器會報錯。
    所以需要指定一個父類的構造函數,例如 super(age, name);

    public class Person {
        int age;
        String name;
        
        public Person(int age, String name) {
            this.age = age;
            this.name = name;
            System.out.println("Person init");
        }
        
        public static void main(String[] args) {
            Man man = new Man(18, "Johnny");
            System.out.println(man.name); // Johnny
        }
        
    }
    
    class Man extends Person {
        
        public Man(int age, String name) {
            super(age, name);
            System.out.println("Man init");
        }
        
    }
    

static關鍵字

static關鍵字可以修飾類、變量、方法和代碼塊。被static修飾的成員屬于類,而不是對象。
  • static修飾成員變量

    當所有的對象的某個屬性都是一樣的時候應考慮使用static關鍵字修飾。例如我們需要表示體育行業的一些人。

    public class Person {
        int age;
        String name;
        String industry = "sport"; // 體育行業
        
        public Person(int age, String name) {
            this.age = age;
            this.name = name;
        }
        
        public static void main(String[] args) {
            Person personA = new Person(30, "姚明");
            Person personB = new Person(31, "劉翔");
            Person personC = new Person(32, "李娜");          
        }
    }
    

    內存布局示意圖:

    內存布局示意圖

    存在兩個主要問題:1,每個對象都有一片內存存儲相同的一個內容;2,如果需要統一修改這個變量,每個對象都需要修改。

    解決方案:用static修飾變量 static String industry = "sport";

    內存布局示意圖:

    內存布局示意圖
由于static修飾的屬性是公共屬性,屬于類。可以通過**“類.屬性名”** 的方式進行獲取和修改。例如

```
Person.industry = "music";              // 修改
System.out.println(Person.industry);  // 獲取 
```
  • static修飾方法

    Java的類的方法是存儲在靜態存儲區的。如下圖:

    內存布局示意圖:


    事實上,多個對象對應的是同一個方法,也就是說所有對象的方法是相同的。 static修飾方法在數據存儲方面沒有區別。最大的作用是可以用 “類.方法名”直接調用,避免了先要new出對象的繁瑣和資源消耗

    Person.talk();

  • static代碼塊(static block)

    靜態代碼塊的作用是統一初始化靜態變量,只會在類加載的時候執行一次, 可以用來優化程序性能。如果有多個靜態代碼塊,會按照書寫的順序依次執行多個靜態代碼塊

    public class StaticBlockDemo {
    
        static int number;
        static String str;
        
        static { // 靜態代碼塊1執行1次
            number = 10;
            str = "name";
        }
        
        static { // 靜態代碼塊2,在靜態代碼塊1之后執行1次
            number = 20;
            str = "name2";
        }
        
        public static void main(String[] args) {
            System.out.println(number);
            System.out.println(str);
        }
        
    }
    

    知識擴展:靜態代碼塊、構造代碼塊、構造函數和普通代碼塊的區別?

    1. 執行順序:靜態代碼塊 -> 構造代碼塊 -> 構造函數 -> 代碼塊
    2. 執行次數:靜態代碼塊只在類的加載時執行一次;構造代碼塊在每次構造函數調用前調用,如果有多個構造函數,特定構造函數有可能執行也可能不執行;代碼塊在函數執行時調用。
    public class StaticBlockDemo {
        
        static int number;
            
        static {
            number = 10;
            System.out.println("靜態代碼塊");
        }
            
        {
            System.out.println("構造代碼塊");
        }
            
        public StaticBlockDemo() {
            System.out.println("構造函數");
        }
            
        public void func() {
            {
                int a = 10;
                System.out.println("代碼塊");
            }
        }
            
        public static void main(String[] args) {
            new StaticBlockDemo().func(); // 靜態代碼塊 -> 構造代碼塊 -> 構造函數 -> 代碼塊  
        }
            
    }
    
  • 靜態內部類(static class)

    如果一個類要被聲明為static的,只有一種情況,就是靜態內部類

    • 靜態內部類只能訪問靜態的成員變量和方法;
    • 靜態內部類不需要引用外部類
    public class Outer {
        
        int age;  // 普通變量
        static String name; // 靜態變量
        
        static { // 靜態代碼塊
            name = "Johnny";
        }
        
        { // 構造代碼塊
            age = 20;
        }
        
        // 靜態內部類
        public static class StaticInner {
            public void sayName() {
                System.out.println(name);
            }
        }
        
        // 普通的內部類
        public class NormalInner {
            public void sayAge() {
                System.out.println(age);
            }
        }
        
        public static void main(String[] args) {
            // 靜態內部類的初始化
            StaticInner staticInner = new Outer.StaticInner();
            staticInner.sayName(); // Johnny
            
            // 普通內部類的初始化
            Outer outer = new Outer();
            NormalInner normalInner = outer.new NormalInner();
            normalInner.sayAge(); // 20
        }
        
    }
    

繼承(Inheritance)

繼承是指一個類獲取另外一個類的屬性和方法的過程,繼承的主要目的是復用代碼。獲取另外一個類的特性的類被稱為子類,而特性被另外一個類利用的類則相對應稱為父類。
  • 繼承語法 --- extends

    class Son extends Parent
    {
    }
    
  • 繼承類型

    Java的類只支持前面三種繼承類型:

    • 單繼承:一個類只繼承另外一個類
    Class A
    {
       public void methodA()
       {
         System.out.println("Method A");
       }
    }
    
    Class B extends A
    {
       public void methodB()
       {
         System.out.println("Method B");
       }
       
       public static void main(String args[])
       {
         B b = new B();
         b.methodA(); //可以調用父類的方法
         b.methodB(); //調用本地方法
      }
    }
    
    • 多級繼承:一級一級繼承,有多個層次的繼承關系
    Class A {
       public void methodA() {
         System.out.println("Class A method");
       }
    }
    
    Class B extends A {
        public void methodB() {
            System.out.println("class B method");
        }
    }
    
    Class C extends B {
       public void methodC() {
         System.out.println("class C method");
       }
       
       public static void main(String args[]) {
         C c = new C();
         c.methodA(); //可以調用祖父類的方法
         c.methodB(); //可以調用父類的方法
         c.methodC(); //調用本地方法
      }
    }
    
    • 層次繼承:多個類繼承自同一個父類
    class A {
       public void methodA() {
          System.out.println("method of Class A");
       }
    }
    
    class B extends A {
       public void methodB() {
          System.out.println("method of Class B");
       }
    }
    
    class C extends A {
      public void methodC() {
         System.out.println("method of Class C");
      }
    }
    
    class D extends A {
      public void methodD() {
         System.out.println("method of Class D");
      }
    }
    
    class JavaExample
    {
      public static void main(String args[]) {
         B obj1 = new B();
         C obj2 = new C();
         D obj3 = new D();
         // 所有的類都有A的方法
         obj1.methodA();
         obj2.methodA();
         obj3.methodA();
      }
    }
    

    Java的類不支持多繼承是因為如果兩個父類有同樣的方法,子類不知道到底調用哪個父類的方法,造成混亂。但是Java中有一個接口的概念,是可以多繼承的。后面會介紹。

super

super是一個Java關鍵字,相當于是指當當前對象的直接父類中的成員

  • 獲取父類的屬性值(也包括static修飾的值)

    class SuperClass {
        int number = 10;
        static int number2 = 30;
    }
    
    public class SubClass extends SuperClass {
        int number = 20;
        
        private void printNumber() {
            System.out.println(number);       // 20
            System.out.println(super.number); // 10
            System.out.println(super.number2);// 30
        }
        
        public static void main(String[] args) {
            SubClass subClass = new SubClass();
            subClass.printNumber();
        }
        
    }
    
  • 顯示調用父類的無參構造函數或者有參構造函數 --- 詳見構造函數章節

  • 方法覆寫(Override)

    子類可以直接繼承父類的方法,但是有時候需要修改父類的方法,我們稱之為方法覆寫。方法覆寫是指子類定義了一個返回值,參數類型和參數數量都和父類都完全一致的方法。

    注意事項:

    1. 方法覆寫是子類的訪問權限(具體后面介紹)(Java中有四種訪問權限,并且public > default > protected > private)不能比父類的訪問權限更嚴格。例如如果父類的訪問權限是default,則子類的訪問權限只能是public 或者 default。
    1. private, static, final 修飾的方法不能被覆寫,因為這些方法都是本類所獨有的。雖然可以在子類里面存在和父類里面一樣 static 和 private 所修飾的方法,但是和父類沒有任何關系,不能稱之為方法覆寫。
    • 不需要覆寫:正常的情況是直接從父類繼承過來方法
    class Parentclass {
        public void print() {
            System.out.println("Parentclass Print");
        }
    }
    
    class Subclass extends Parentclass {
    }
    
    public class Test {
        public static void main(String args[]) {
            Subclass sub = new Subclass();
            sub.print(); // 方法從父類繼承而來 ---> Parentclass Print
        }
    }
    
    • 覆寫情形一:從父類繼承方法邏輯,子類添加一些邏輯
    class SuperClass {
        void print() {
            System.out.println("super print");
        }
    }
    
    public class SubClass extends SuperClass {
        void print() {
            super.print(); //父類的邏輯
            System.out.println("sub print"); //子類的邏輯
        }
        
        public static void main(String[] args) {
            SubClass subClass = new SubClass();
            subClass.print();
        }
    }   
    
    • 覆寫情形二:子類獨立的邏輯
    class SuperClass {
        void print() {
            System.out.println("super print"); //父類的邏輯
        }
    }
    
    public class SubClass extends SuperClass {
        void print() {
            System.out.println("sub print"); //子類的邏輯
        }
        
        public static void main(String[] args) {
            SubClass subClass = new SubClass();
            subClass.print();
        }
    }   
    
  • this && super

    this是指當前對象本身。在構造函數一節已經有接觸過this關鍵字,this和super一樣也可以調用屬性、調用方法和調用構造函數。那這兩個關鍵字有什么區別呢?

    關鍵字 this super
    本質 一個指向本對象的指針 一個Java關鍵字
    調用方法 訪問本類中的方法 直接訪問父類中的方法
    訪問屬性 訪問本類中的屬性,沒有繼續在父類中尋找 訪問父類中的屬性
    調用構造器 調用本類構造器,放在構造器首行 調用父類構造器,放在子類構造器的首行

    說明:構造函數里面只能調用一次super()或者this(), 并且super()或者this()必須放在第一行,是Java為了保證創建對象的不出現異常情況。

方法重載(Method Overloading)

方法重載是指一個類可以有多個方法名相同但是方法的參數個數或者參數類型不同的多個方法存在,和構造函數可以有多個類似

  • 重載的類型

    • 參數個數不同

      add(int, int)
      add(int, int, int)
      
    • 參數類型不同

      add(int, int)
      add(int, float)
      
    • 參數的順序不同

      add(int, float)
      add(float, int)
      

    注意事項: 方法的返回值不同不能算是方法重載,并且編譯器會報錯。

      int add(int, int)
      float add(int, int)
    

綁定(binding)

綁定是指將一個方法的調用與方法所在的類關聯起來。Java中的綁定分為靜態綁定和動態綁定,又被稱作前期綁定和后期綁定。

  • 靜態綁定是指編譯器能在編譯時確定的綁定。主要是static, private, final 修飾的方法。因為這些方法不能夠被子類重寫。

    class Human {
        public static void walk() {
            System.out.println("Human walks");
        }
    }
    
    class Boy extends Human{
        
        public static void walk() {
            System.out.println("Boy walks");
        }
        
        public static void main( String args[]) {
    
            Human obj = new Boy();
            Human obj2 = new Human();
            obj.walk();  // Human walks --- 方法綁定的是Human類
            obj2.walk(); // Human walks --- 方法綁定的是Human類
            
        }
    }
    
  • 動態綁定是指編譯器不能在編譯時確定的綁定。方法覆寫是一個典型的動態綁定,因為父類和子類都有相同的方法,只有在運行時才能確定是調用的那個類的方法。

    class Human {
        public void walk() {
            System.out.println("Human walks");
        }
    }
    
    class Boy extends Human{
        
        public void walk() {
            System.out.println("Boy walks");
        }
        
        public static void main( String args[]) {
    
            Human obj = new Boy();
            Human obj2 = new Human();
            obj.walk();  // Boy walks  --- 方法綁定的是Boy類
            obj2.walk(); // Human walks --- 方法綁定的是Human類
        }
    }
    

多態性(Polymorphism)

多態字面意思就是多種狀態,例如同樣的一個方法具有不同功能的特性,多態也可以分為編譯時多態和運行時多態。

  • 運行時多態---方法覆寫為代表

    Animal.java

    public class Animal {
       public void say() {
          System.out.println("Animal");   
       }
    }
    

    Dog.java

    class Dog extends Animal {
        
        @Override
        public void say() {
            System.out.println("Dog");
        }
        
        public static void main(String args[]){
            Animal obj = new Dog();
            obj. say(); // Dog
        }
    }
    

    Cat.java

    public class Cat extends Animal {
    
        @Override
        public void say() {
            System.out.println("Cat");
        }
        
        public static void main(String args[]){
            Animal obj = new Cat();
            obj. say(); // Cat
        }
    }
    

    通過代碼我們可能得到,雖然Cat和Dog對象都是調用的say()方法,但是表現卻有很大差異。

  • 編譯時多態---方法重載為代表

    class AddClass
    {
        void add(int a) {
           System.out.println ("a: " + a);
        }
        
        void add(int a, int b) {
           System.out.println ("a and b: " + a + "," + b);
        }
        
        double add(double a) {
           System.out.println("double a: " + a);
           return a*a;
        }
    }
    
    class PolyDemo
    {
        public static void main (String args [])
        {
            AddClass obj = new AddClass();
            obj.add(10);      // a: 10
            obj.add(10, 20);  // a and b: 10,20
            double result = obj.add(5.5); // double a: 5.5
            System.out.println("result : " + result); // result : 30.25
        }
    }
    

    通過代碼我們可能得到,雖然obj都是調用的add()方法,但是表現卻有很大差異。

擴展:抽象類的抽象方法和接口也是實現多態的兩種方式,會在接下來的章節詳細介紹。

抽象類(Abstract Class)

abstract關鍵字修飾的類就是抽象類(Abstract Class),抽象類里面可以有抽象方法(沒有方法體)也可以有普通方法。但是普通類里面不能定義抽象方法。

注意:抽象類不能被實例化,就是說不能被用來創建對象。

  • 抽象類應用場景?

    想象一下這種情況,有一個動物類,有一個發聲(sound)的方法,動物類有一些子類,例如貓類,狗類,獅子類,老虎類等等,這些子類也需要有覆寫發聲(sound)的方法,但是動物類沒有一個通用的發聲(sound)需要,所以父類的sound其實是不需要執行任何操作的。

    所以抽象類的場景就是父類有方法不需要任何操作,但是子類必須覆寫這個方法

```
abstract class Animal {
    abstract void sound(); // 這個方法讓子類去自己實現
    
    void anothermethod() { // 普通的方法
        System.out.println("another method");
    }
}

class Dog extends Animal {
    @Override
    void sound() { // 必須實現這個方法
        System.out.println("wang wang wang");
    }
}
```
  • 抽象類的規則

    1. 如果父類有方法不需要自己實現,就可以把父類申明為抽象類(abstract class)。繼承抽象類的子類必須實現未實現的抽象方法。
    2. 抽象類不能被實例化,如果要使用必須用一個子類繼承抽象類并實現抽象方法,然后用子類對象調用相應的方法。
    3. 抽象類的子類如果實現抽象類的所有方法,那子類也必須定義為抽象類,子類也不能被實例化。

    抽象類為什么不能實例化?因為抽象類不是完整的類,有的方法沒有實現。如果允許抽象類實例化,那么對象調用未實現的方法就會出現問題。可以理解抽象類為一個模板,必須繼承抽象類然后再使用。

接口(Interface)

接口和類相似但是又不是一個類,接口的方法都不能有實現(完全抽象),接口的屬性訪問修飾是public,static,final

  • 定義

    接口定義用interface關鍵字,interface里面的所有方法都不需要實現。

    interface MyInterface {
        // 屬性只能是常量,且必須是 public static final 修飾
        int number = 1; // 等價于 public static final int number = 1;
        
        // 接口所有的方法都是抽象的
        public void func1();
        public void func2();
    }
    

    接口的使用是用 類 implements 接口, 需要實現接口的所有方法。

    class InterfaceDemo implements MyInterface {
    
        @Override
        public void func1() {
            System.out.println("implementation of func1");
        }
    
        @Override
        public void func2() {
            System.out.println("implementation of func2");
        }
        
    }
    
  • 接口繼承

    接口里的方法沒有實現,所以不能實現另外(implements)一個接口,但是可以繼承(extends)另外一個接口。

    interface Interface1 {
        public void func1();
    }
    
    interface Interface2 extends Interface1 { // 接口繼承
        public void func2();
    }
    
    class InterfaceDemo implements Interface2 {
    
        @Override
        public void func1() {
            System.out.println("func1");
        }
    
        @Override
        public void func2() {
            System.out.println("func2");
        }
        
    }
    
  • 標記接口(Marker Interface)

    標記接口有時也叫標簽接口(Tag interface),即接口不包含任何方法. JDK里的Serializable接口就是一個標記接口. 和名字對應,標記接口的作用就是標識某個類型或者元數據。

    public interface Serializable {
    }
    
  • 嵌套接口

    接口可以定義在接口或者類的內部,這些接口被稱為嵌套接口,或者也可以稱為內部接口。例如Java的Map接口內部定義的Entry接口,我們可以用Map.Entry去訪問這個接口,而不能直接用Entry去訪問它。

  • 接口的一些關鍵點總結

    1. 接口不能初始化對象
    2. 接口的方法都是抽象方法,接口的屬性都是 public static final且必須賦值
    3. 接口的使用是實現implements
    4. 接口的方法都是abstract public,所以實現的類前面都必須加上public
    5. 類必須實現接口的所有方法,否則只能定義為抽象類
    6. 接口不能定位為private和protected
    7. 接口可以繼承其他的接口,但是不能實現其他接口
    8. 一個類可以實現任意數量的接口
    9. 如果有多個接口里面有相同的方法,只需要實現一個即可。
    10. 如果多個接口里面有方法名和參數一致但是返回值不一樣的情況是不能允許的。
    11. 如果多個接口有相同的屬性名,可以通過接口.屬性加以區分。例如A.x 和 B.x
  • 接口和抽象類的區別

    抽象類 接口
    抽象類只能繼承其他的類或者抽象類 接口只能繼承接口
    抽象類可以有個抽象方法和正常方法 接口都是抽象方法
    定義抽象方法前面的abstract是必須的 接口都是abstract方法,所以abstract可寫可不寫
    抽象類的屬性可以是protected和public的訪問權限 接口都是public的訪問權限
    抽象類的屬性的權限沒有限制 接口屬性是public static final

封裝(Encapsulation)

封裝包括隱藏數據和功能的具體實現,是將該類中所有對象的屬性和行為隱藏起來,并為其他對象提供一些訪問的方法。例如:把屬性定義為私有的,定義一些外部類能訪問的方法對私有屬性進行操作;再例如把Person類的speak功能做為一個方法,外部直接調用speak功能。

public class Person {
    // 一些私有的屬性,外部類不能訪問
    private int age;
    private String name;
    
    // 定義一些外部類能訪問的方法,對私有屬性進行操作
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public void speak() {
        System.out.println(this.name);
    }
    
    public static void main(String[] args) {
        
        Person person = new Person();
        person.setName("Johnny");
        person.setAge(18);
        System.out.println(person.name);
        System.out.println(person.age);
        
        person.speak();
    }
    
}
  • 封裝的好處

    1. 可以隱藏內部屬性,且可以把屬性變為只讀(只有get方法)或者只寫(只有set方法)或者讀寫皆可(有set和get方法)。
    2. 外部類可以不用關心內部實現,只需要調用就行。
    3. 增加代碼的可復用性,因為方法可以在任何時間被修改,外部不需要關心,只要調用同樣的一個方法可以實現多種功能。
  • 包(Package)

    Java中的包(Package)用來組織類和接口。系統定義了一些包,開發者也可以自定義自己的包。以java.util.Random; 為例: java是一個頂級包,util是一個工具類包,而Random是util包下面的一個類。

    • 包的優勢

      1. 代碼復用---項目中可能有很多重復的功能需要實現,這樣就可以把這些功能用一個包組織起來,當需要使用的時候直接引用包就可以了。
      2. 代碼結構更清晰---可以用包把各個功能模塊區分出來,每個包負責相應的功能,這樣代碼結構更清晰,且使用起來更有效率。
      3. 解決命名沖突---在不同的包里面可以有相同的類,這樣就解決了命名沖突。
    • 包的使用方式

      新建一個名稱為com.johnny.Calculate的包(package),然后在這個包里面新建一個Calculator類, Calculator類里面添加一個add方法。

      package com.johnny.Calculate;
      
      public class Calculator {
          
          public int add(int num1, int num2) {
              return num1 + num2;
          }
      }
      

      說明:類的頂部需要有包的申明。package com.johnny.Calculate;

      使用方法一: 不引用包,直接用包.類來使用類。

      package com.johnny;
      
      public class Math {
          public static void main(String[] args) {
              // 直接用包名.類名 不需要import包
              com.johnny.Calculate.Calculator calculator = new com.johnny.Calculate.Calculator();
              int result = calculator.add(10, 20);
              System.out.println(result); // 30
          }
      }
      

      使用方法二:在com.johnny包的Math類中引入包import com.johnny.Calculate.Calculator;, 然后就可以直接使用Calculator類。

      package com.johnny;
      import com.johnny.Calculate.Calculator;
      
      public class Math {
          public static void main(String[] args) {
              Calculator calculator = new Calculator();
              int result = calculator.add(10, 20);
              System.out.println(result); // 30
          }
      }
      
      1. 包的申明package com.johnny;必須寫在引入包import com.johnny.Calculate.Calculator;的前面
      2. import的作用就是為了減少使用類或者接口時前面需要帶上一長串的包名,讓代碼的書寫更加的簡潔和易讀。類似于不用import時使用一個類不許用全名,而import后只需要叫名就可以了。
    • 子包

      一個包在另外一個包里面,就把這個包稱為子包。再以import java.util.Random;為例:util包就是java包的子包,上面例子com.johnny.Calculate, Calculate包就是johnny包的子包,johnny包是com包的子包。包的層級用"."連接。

- **static引包**      
    
    我們普通引包主要是為了復用其他包的public的類和接口,而static引包是為了使用其他包定義的靜態的屬性和方法,而不需要在屬性和方法前面加上類名。
    `java.lang.Math`包中有很多數學計算的靜態方法,例如sqrt,tan等,正常的引用方法如下:
    
    ```
    // 普通引包-- import包名
    import java.lang.Math;
    
    public class Demo {
        
        public static void main(String[] args) {
            // 如果是普通引包,使用類.靜態方法和類.靜態屬性
            double number1 = Math.sqrt(4);
            System.out.println(number1); // 2.0
        }
        
    }
    ```
    
    ```
    // 靜態引包--在import 和 包名.類名 中間加上 static 關鍵字
    import static java.lang.Math.*;
    
    public class Demo {
        
        public static void main(String[] args) {
            // 如果是靜態引包,可以直接使用靜態方法和靜態屬性
            double number1 = sqrt(4);
            System.out.println(number1); // 2.0
        }
        
    }
    ```
    
    > 說明:當使用類的靜態屬性或者靜態方法比較頻繁時候比較實用,例如需要頻繁進行數學計算方法時建議用static引入。靜態引入的缺點是對包的信息不了解,容易造成沖突和混淆。
    
- ** 關于包的兩點說明**

    1. 多個包中可能出現命名沖突,此時使用時需要用**包名.類名**。
        例如A包里面有一個Person類,B包里面也有一個Person類。此時不能申明引入兩個Person類,因為有命名沖突。
        
        ```
        import A.Person;
        import B.Person; // 會有編譯錯誤,因為命名沖突了
        ```
        
        可以用通配符引入的方式即引入包下的所有類去解決,使用時候需要指明包名。
        
        ```
        import A.*;
        import B.*;
        
        public class PackageDemo {
            
            public static void main(String[] args) {
                
                A.Person personA = new A.Person();
                B.Person personB = new B.Person();
                
            }
        }
        ```
        
    2. 用通配符引入的時候需要特別注意,假設有個A包,A包下面有Person類,A包下面還有一個B包,B包下面有Dog,Cat兩個類。如果用 `import A.*;` 此時只引入了Person類,并未引入了B包下的Dog和Cat類。如果使用`import A.B.*;`, 此時引入的是Dog和Cat類。如果要把三個類都引入需要使用`import A.*; import A.B.*;`
  • 訪問修飾符

    Java中的訪問修飾符有四種,public,protected, default, private,對應的訪問權限如下;

    訪問修飾符 同類 同包不同類(不含子類) 同包子類 不同包子類 不同包不同類(不含子類)
    public YES YES YES YES YES
    protected YES YES YES YES NO
    default YES YES YES NO NO
    private YES NO NO NO NO

final

final關鍵字能修飾屬性,方法和類,接下來進行詳細介紹。

  • final修飾變量

    final修飾的變量可以被認為是常量, 變量一旦賦值后不能修改。

    public class Person {
        final String name = "Johnny"; //申明時賦值
        
        public void changeName() {
            name = "Liu"; //此時編譯器會報錯
        }
    }
    

    final修飾的變量可以不在申明時候定義,此時必須在構造函數中進行賦值,且以后不能修改。用于定義不變的的一些變量。

    public class Person {
        
        final String name;
        
        public Person(String name) {
            this.name = name;
        }
        
    }
    

    如果final申明的static變量在申明時未賦值,則必須在static代碼塊中賦值.

    public class Person {
        
        final static String name;
        
        static {
            name = "Johnny";
        }
        
    }
    
    
  • final修飾方法

    final修飾的方法是不能被子類重寫的。否則會編譯錯誤。

    說明點:構造函數不能定義為final;

  • final修飾類

    final修飾的類是不能被子類化的,也就是說其他類不能繼承final類。Java中的String,Integer 等類都是final類。
    系統提供的final類 --- enum

    在其他語言中,枚舉(enum)一般是一系列整型或者字符串集合,但是Java中的枚舉其實是一個final類,可以表示一系列的數據的集合,也能有方法和變量。

    用枚舉定義常量

    enum WeekDay {  
        Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday;
    }
    
    System.out.println(WeekDay.Monday);  // Monday 注意此時不是字符串
    

    枚舉可以添加變量和方法

    enum PersonEnum {   
        Johnny(18), Lan(17), Wen(1);
        
        private int age;
        
        // 定義了屬性必須要有構造方法,且必須為私有方法
        private PersonEnum(int age) {
            this.setAge(age);
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    枚舉可以實現接口

    
    public interface EnumInterface {
        void speak();
    }
    
    // 實現接口
    enum InterfaceEnum implements EnumInterface {
        A, B;
        
        @Override
        public void speak() {
            System.out.println(this);
        }
        
    }
    
    // 使用
    InterfaceEnum.A.speak(); //A
    
    

    說明:枚舉可以使用在switch語句中。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,721評論 18 399
  • 廢話不多說,自己進入今天的主題 1、面向對象的特征有哪些方面? 答:面向對象的特征主要有以下幾個方面: - 抽象:...
    傳奇內服號閱讀 2,369評論 1 31
  • 晶,小毛毛,嗯!有點小冷酷,不過挺小巧的,如你的名字一樣。 芮逸嬌,床下的床鋪空空的,有沒有想起以前給你講的那個特...
    小智噠噠閱讀 125評論 0 0
  • 這個周末,我在廣州玩了一場人生游戲,從20歲開始玩起,我在40的時候就破產,最后選擇“自殺”。 回來后,我一直在想...
    彭小六閱讀 6,592評論 11 163
  • 日暮蒼山遠,天寒白屋貧。柴門聞犬吠,風雪夜歸人。 -劉長卿《逢雪宿芙蓉山主人》 1 ...
    eileen01閱讀 496評論 5 1