1. 程式人生 > 其它 >第一講: 淺拷貝與深拷貝 內部類與lambda表示式

第一講: 淺拷貝與深拷貝 內部類與lambda表示式

技術標籤:javalambda

第一講: 淺拷貝與深拷貝、內部類

import com.demo01.pojo.User;

public class Demo01 {
    public static void main(String[] args) {
        User u = new User(100,"張三");

        User user = new User(100, "張三" );
        u = user;

        System.out.println(u);
        // 以上程式碼,我們首先建立了一個只有初始值的物件
// 然後我們建立了一個已經賦值的物件 // 我現在的想法是將user賦值給u,這樣的話u所指向的物件就有值了? // 這顯然不行。這僅僅只是把user這個引用所儲存的物件的記憶體首地址給到了u // u原來所指向的物件被GC回收,u這時候和user指向了同一個物件User(100,"張三") // 這叫做淺拷貝 // 我現在就是想要在堆裡面有兩個一模一樣的物件,他們的記憶體地址不一樣 // 1. 構造方法 // 2. clone } }

1. 深拷貝

  • 方法1:構造方法,構造兩個一模一樣的物件
  • 方法2:clone()
 @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

重寫clone()方法,因為Object的clone()protected的,它在java.lang包下,我們不可能在lang包下建立應用程式,所以跨包無法訪問Object的clone()方法。我們思考一下,為什麼要用protected修飾?克隆的本意是複製一個全新的物件出來,脫離了物件,一直呼叫Object的clone方法是不是不夠用啊。所以一定要重寫。    
第一種方案:先將其做成父型別的引用的指向子型別的物件,然後向下轉型。
    也就是,重寫的方法的返回值型別是Object的。(
在本例中) public class Demo01 { public static void main(String[] args) { User u = new User(100, "張三"); User user = null; try { user = (User) u.clone();// clone()之後再強轉 } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.println(user); } } 第二種方案: 重寫的時候可以改變Object類中的clone()方法的返回值型別。從這裡我們補充一下重寫的規則:方法的重寫一定要有繼承關係,這是大前提,沒有這個前提沒有重寫機制。然後,private修飾的不能重寫。重寫的時候方法名、引數列表不能變;訪問許可權不能更低,只能相等或更高。這裡可以看到,方法的返回值型別是可以改變的,但一定要是父型別或者是父型別派生的子型別。 @Override public User clone() throws CloneNotSupportedException{ return (User) super.clone(); } 使用的時候,就不用強制型別轉換了,因為在這裡已經強轉了。
  • 第三種方案:用得最多的方式,序列化與反序列化,以後再說。
    • 大概的意思:一個物件是一個實實在在的東西,比如一臺電腦是一個物件,它放在你家的書桌上,我們可以把這臺電腦一個部件一個部件的拆開,郵寄到學校,然後一個部件一個部件的裝上。在記憶體中,我們傳送出去的是一份拷貝。

2.內部類

  • 內部類在專案中使用不多,但一定要會,尤其是匿名內部類和lambda表示式,我們用得不多,但其他人用得多,一定要看得懂這種奇怪的語法。
  • 內部類的用法及演變過程
// 定義一個介面
public interface IRun {
    void run();
}

// 寫一個介面的實現類
public class Person implements IRun {
    @Override
    public void run() {
        System.out.println("人跑步!");
    }
}

// 測試
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.run();// 人跑步
    }
}

  • 成員內部類:相當於一個成員屬性
內部類編譯之後得到的.class檔案,兩個類名之間帶有$符號:Animal$AnimalRun.class
注意語法上與正常的類的定義沒有區別,只是相當於成員屬性。我們以前學過類中有引用型別的成員屬性,這種型別的成員屬性是不是一定要new出來?內部類也一樣,只是語法上有個包含關係。
OuterClass.InnerClass 識別符號 = new OuterClass().new InnerClass();
public class Animal {
    public Person person;
    class AnimalRun implements IRun {

        @Override
        public void run() {
            System.out.println("動物跑!");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.run();

        Animal animal = new Animal();
        Animal.AnimalRun ar = animal.new AnimalRun();
        ar.run();

        Animal.AnimalRun ar1 = new Animal().new AnimalRun();
    }
}
  • 靜態內部類:相當於一個靜態屬性
public class Animal {
    public Person person;
//    class AnimalRun implements IRun {
//
//        @Override
//        public void run() {
//            System.out.println("動物跑!");
//        }
//    }

    static class AnimalRun implements IRun {
        @Override
        public void run() {
            System.out.println("動物跑!");
        }
    }
}


public class Animal {
    public Person person;
//    class AnimalRun implements IRun {
//
//        @Override
//        public void run() {
//            System.out.println("動物跑!");
//        }
//    }

    static class AnimalRun implements IRun {
        @Override
        public void run() {
            System.out.println("動物跑!");
        }
    }
}
  • 區域性內部類:相當於一個區域性變數
public class Animal {
//    class AnimalRun implements IRun {
//
//        @Override
//        public void run() {
//            System.out.println("動物跑!");
//        }
//    }

//    static class AnimalRun implements IRun {
//        @Override
//        public void run() {
//            System.out.println("動物跑!");
//        }
//    }

    public void walk() {
        class LocalClass implements IRun {
            @Override
            public void run() {
                System.out.println("區域性內部類---run!");
            }
        }
       // new LocalClass().run();
        LocalClass lc = new LocalClass();
        lc.run();

    }
}

public class Test {
    public static void main(String[] args) {
//        Person person = new Person();
//        person.run();

        Animal animal = new Animal();
//        Animal.AnimalRun ar = animal.new AnimalRun();
//        ar.run();
//
//        Animal.AnimalRun ar1 = new Animal().new AnimalRun();

//        Animal.AnimalRun animalRun = new Animal.AnimalRun();
//        animalRun.run();
        animal.walk();
    }
}
  • 重點(很重要) 匿名內部類
    • 思考一個問題:匿名就是沒有名字,匿名內部類就是沒有名字的內部類,請問一個類沒有名字如何例項化物件?不能例項化物件。
    • 所以,匿名內部類的定義方式為:
      • new 介面名[或者是父類名] {
        • 這裡是實現介面的抽象方法或者是父類中的抽象方法
        • };後面一定要加上分號。
        • 這就好比是new Person();
public class Animal {
//    class AnimalRun implements IRun {
//
//        @Override
//        public void run() {
//            System.out.println("動物跑!");
//        }
//    }

//    static class AnimalRun implements IRun {
//        @Override
//        public void run() {
//            System.out.println("動物跑!");
//        }
//    }

    public void walk() {
//        class LocalClass implements IRun {
//            @Override
//            public void run() {
//                System.out.println("區域性內部類---run!");
//            }
//        }
//       // new LocalClass().run();
//        LocalClass lc = new LocalClass();
//        lc.run();

        new IRun(){
            @Override
            public void run() {
                System.out.println("匿名內部類---run!!");
            }
        };
    }
}

//要是用匿名內部類中的方法:
new IRun(){
            @Override
            public void run() {
                System.out.println("匿名內部類---run!!");
            }
        }.run();
  • 匿名內部類的另外一種呼叫方式:在呼叫的方法的時候,以引數的形式寫匿名內部類。
public interface IRun {
    void run();
}

public class Animal {
    public void walk(IRun iRun) {
        iRun.run();
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.walk(new IRun(){

            @Override
            public void run() {
                System.out.println("匿名內部類的另外一種呼叫方式");
            }
        });
    }
}
寫在方法體中的匿名內部類:
public void test(){
    new xxx() {
        m1(){
            ....
        }
    }.m1();
}


第二種形式:
public void test(Interface i){
    i.m1();
}
可以在方法被呼叫時,直接寫在引數列表中。
    test(new Interface() {
        public void m1() {
            ....
        }
    });
這樣就不需要再次呼叫m1方法了。    
new IRun(){
            @Override
            public void run() {
                System.out.println("匿名內部類---run!!");
            }
        }.run();

解讀一下這種語法:
	new 是為了產生一個物件,new IRun()這是不成立的,因為IRun是介面,介面不能被例項化,所以不能將new IRun()理解為new一個介面型別的物件,這是錯的!!!!
    
    new IRun()在匿名內部類中才能這麼寫,其他地方不能這麼寫,寫了就錯了。這是匿名內部類特有的寫法,只屬於匿名內部類。意思是:new一個IRun介面的實現類,它的實現類是沒有名字的,new這個實現類的依據是{}中的程式碼,這個{}所包裹的程式碼就是匿名內部類的程式碼。
    {
            @Override
            public void run() {
                System.out.println("匿名內部類---run!!");
            }
        }
	這是一個完整的匿名內部類。他一定要配合new 介面名()來使用,否則無法給它建立物件,因為它連個名字都沒有,所以我們只好用它父類的名字來指代它。
    並不是new了一個介面型別的物件,沒有這種玩法,編譯都通不過。
  • 匿名內部類的簡化:lambda表示式(λ)
        animal.walk(()->{
            System.out.println("匿名內部類runrunrun");
        });


        animal.walk(()-> System.out.println("faefawefawefawefawefawe"));

        // 以上,兩種叫做lambda表示式。這個一定要知道怎麼來的,要看得懂。否則執行緒就沒法學
  • 帶引數的lambda表示式
public interface IEat {
    /**
     *
     * @param str 表示吃早飯還是中飯、晚飯的描述
     */
    void eat(String str);
}

public class Person {
    public void dinner(IEat iEat,String str) {
        iEat.eat(str);
    }


    public static void main(String[] args) {
        Person person = new Person();
        person.dinner(str-> System.out.println(str),"晚餐");
    }
}
  • lambda表示式的前提:這個非常重要。
    • 函式式介面。什麼是函式式介面?只含有一個抽象方法的介面叫做函式式介面
    • 只有這樣才能使用lambda表示式。
    • 也就是說:介面中有且僅有一個方法才能使用lambda表示式。
    • 記住它,記死它!!!!!!
    • lambda表示式是 java1.8 出來的新特性。這是一個很重要的特性。