第一講: 淺拷貝與深拷貝 內部類與lambda表示式
阿新 • • 發佈:2021-02-01
第一講: 淺拷貝與深拷貝、內部類
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();
- new 介面名[或者是父類名] {
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 出來的新特性。這是一個很重要的特性。