目錄
前言:
1. String
1.1 字符串常量池
1.1.1?創(chuàng)建對象的思考
1.1.2?字符串常量池(StringTable)
1.1.3?再談String對象創(chuàng)建
1.1.4?intern方法
2.?反射
2.1?反射的定義
2.2 反射的用途
2.3 反射的基本信息
2.4 反射相關(guān)的類
2.4.1?Class類(反射機制的起源)
2.4.1.1?Class類中的相關(guān)方法
2.5?反射實例
2.5.1?獲得Class對象的三種方式
2.5.2?反射的使用
2.5.2.1?反射創(chuàng)建對象
2.5.2.2?反射調(diào)用私有的構(gòu)造方法
2.5.2.3?反射獲取私有的屬性
2.5.2.4 反射獲取私有方法
2.5.3?反射的優(yōu)缺點
3.?枚舉
3.1 枚舉的使用
3.2?枚舉的常用方法
3.3?枚舉的優(yōu)缺點
4.?枚舉和反射
4.1?枚舉是否可以通過反射,拿到實例對象呢?
5. Lambda表達式
5.1?lambda表達式的語法
5.2?函數(shù)式接口
5.3?Lambda表達式的基本使用
5.4?變量捕獲
5.4.1?匿名內(nèi)部類
5.4.2?匿名內(nèi)部類的變量捕獲
5.5?Lambda的變量捕獲
5.6?Lambda在集合當中的使用
5.6.1?Collection接口
5.6.2?List接口
5.6.3?Map接口
5.7?lambda表達式的優(yōu)缺點
6.?泛型進階
6.1?什么是泛型
6.2?引出泛型
6.3?語法
6.4?泛型類的使用
6.4.1?語法
6.4.2?裸類型
6.5?泛型的編譯
6.5.1?擦除機制
6.5.2 為什么不能實例化泛型類數(shù)組
6.6?泛型的上界
6.7?泛型方法
6.7.1?定義語法
6.8?通配符
6.8.1?通配符解決什么問題
6.8.2?通配符上界
6.8.3?通配符的下界
結(jié)束語:
前言:
這節(jié)中小編主要與大家分享一下有關(guān)于String方面之前沒有分享的剩下的知識點,主要了解什么是字符串常量池,創(chuàng)建對象時的一些小知識點,在反射、枚舉以及l(fā)ambda表達式中主要掌握反射以及枚舉以及l(fā)ambda表達式的基本使用,在泛型進階中我們主要了解通配符的使用,好了話不多說我們來給大家一一講解吧。
1. String
1.1 字符串常量池
1.1.1?創(chuàng)建對象的思考
我們先來思考一下下面這兩種創(chuàng)建String對象的方式是否相同。
代碼:
package 再談String;
public class Test1 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s3 == s4);//false
}
}
結(jié)果:
在上述的結(jié)果中我們可以看到s1和s2引用的是同一個對象,而s3和s4不是同一個對象這是為什么呢?
在Java程序中,類似于:1,2,3,3,3.14,“hello”等字面類型的常量經(jīng)常頻繁使用,為了使程序的運行速度更快更節(jié)省內(nèi)存,Java為8種基本數(shù)據(jù)類型和String類都提供了常量池。
“池”是編程中常見的一種概念,是重要的提升效率的一種方式,我們會在未來的學習中遇到各種“內(nèi)存池”、“線程池”、“數(shù)據(jù)庫連接池”......
舉個例子,比如:在大學的時候家里給大家大生活費的方式
- 家里經(jīng)濟拮據(jù),每個月定時打生活費,有時候可能會晚,最差的情況下可能需要向家里張口要,速度就會比較慢。
- 家里有礦,一次性打一年的生活費放到銀行卡中,隨用隨取,效率非常的高,常見的池的技術(shù)比如:數(shù)據(jù)庫連接池、線程池等。
為了節(jié)省存儲空間以及程序的運行效率,java中引入了:
- class文件常量池:每個.java源文件編譯后生成.class文件中會保存當前類中的字面常量以及符號信息。
- 運行時常量池:在.class文件被加載時,.class文件中的常量池被加載到內(nèi)存中稱為運行時常量池,運行時常量池每個類都有一份。
- 字符串常量池
1.1.2?字符串常量池(StringTable)
字符串常量池在JVM中是StringTable類,實際是一個固定大小的HashTable(一種高效用來查找的數(shù)據(jù)結(jié)構(gòu)),不同JDK版本下字符串常量池的位置以及默認大小是不同的:
JDK版本 | 字符串常量池的位置 | 大小設置 |
Java6 | (方法區(qū))永久代 | 固定大?。?009 |
Java7 | 堆中 | 可設置,沒有大小限制,默認大?。?0013 |
Java8 | 堆中 | 可設置,有范圍限制,最小是1009 |
關(guān)于方法區(qū)、堆等內(nèi)存結(jié)果的具體局部,后續(xù)JVM中會給大家詳細介紹。
1.1.3?再談String對象創(chuàng)建
由于不同的JDK版本對字符串常量池的處理方式不同,此處在java8?HotSpot上分析。
1.直接使用字符串常量進行賦值
代碼:
package 再談String;
public class Test2 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);//true
}
}
結(jié)果:
2.通過new創(chuàng)建String類對象
代碼:
package 再談String;
public class Test3 {
public static void main(String[] args) {
String s3 = new String("world");
String s4 = new String("world");
System.out.println(s3 == s4);//false
}
}
結(jié)果:
下面來簡單模擬一下創(chuàng)建對象的過程。
?從上面我們可以看出只要是new出來的對象,都是唯一的。
通過上述的例子可以看出來,使用常量串創(chuàng)建String類型對象的效率更高,而且更節(jié)省空間,用戶也可以將創(chuàng)建出來的字符串對象通過intern方式添加進字符串常量池中。至于什么是intern方法我們下面來給大家介紹一下。
1.1.4?intern方法
intern是一個native方法(Native方法指:底層使用C++實現(xiàn)的,看不到其實現(xiàn)的源代碼),該方法的作用是手動將創(chuàng)建的String對象添加到常量池中。
代碼:
package 再談String;
public class Test4 {
public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch);//s1對象并不在常量池中
s1.intern();//s1.intern;調(diào)用之后,會將s1對象的引用放入到常量池中
String s2 = "abc";//"abc"在常量池中存在了,s2創(chuàng)建時直接使用常量池中的"abc"的引用
System.out.println(s1 == s2);
}
}
結(jié)果:
注意:如果沒有調(diào)用intern函數(shù)的話,那么結(jié)果就是false,在Java6和Java7、8中Intern的實現(xiàn)會有些許的差別。
2.?反射
2.1?反射的定義
Java的反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意方法和屬性,既然能拿到,那么我們就可以修改部分信息;這種動態(tài)獲取信息以及動態(tài)調(diào)用對象的方法功能就稱之為java語言的反射(reflection)機制。
2.2 反射的用途
- 在日常的第三方應用開發(fā)過程中,經(jīng)常會遇到某個類的某個成員變量、方法或是屬性是私有的或者是只對系統(tǒng)應用開放,這時候就可以利用Java的反射機制通過反射來獲取所有私有成員或是方法。
- 反射最重要的用途就是開發(fā)各種通用框架,比如在Spring中,我們將所有的類Bean交給Spring容器管理,無論是XML配置Bean還是注解配置,當我們從容器中獲取Bean來依注入時,容器會讀取配置,而配置中給的就是類的信息,Spring根據(jù)這些信息,需要創(chuàng)建哪些Bean,Spring就動態(tài)創(chuàng)建這些類。
2.3 反射的基本信息
Java程序中許多對象在運行時會出現(xiàn)兩種類型:運行時類型(RTTI)和編譯時類型,例如Person p = new Student();這句代碼中p在編譯時類型為Person,運行時類型就為Student,程序需要在運行時發(fā)現(xiàn)對象和類的真實信息,而通過使用反射程序就能判斷出該對象和類屬于哪些類。
2.4 反射相關(guān)的類
類名 | 用途 |
class類 | 代表類的實體,在運行的Java應用程序中表示類和接口 |
Field類 | 代表類的成員變量/類的屬性 |
Method類 | 代表類的方法 |
Constructor類 | 代表類的構(gòu)造方法 |
2.4.1?Class類(反射機制的起源)
Class類代表類的實體,在運行的Java應用程序中表示類和接口。
Java文件被編譯后,生成了.class文件,JVM此時就去要解讀.class文件,被編譯后的Java文件.class也被解析為一個對象,這個對象就是java.lang.Class。這樣當程序在運行時,每個java文件最終變成了Class類對象的一個實例。我們通過Java的反射機制應用到這個實例,就可以去獲取甚至去添改變這個類的屬性和動作,使得這個類成為一個動態(tài)的類。
2.4.1.1?Class類中的相關(guān)方法
- 常獲得類相關(guān)的方法:
方法 | 用途 |
getClassLoader() | 獲得類的加載器 |
getDeclaredClasses() | 返回一個數(shù)組,數(shù)組中包含該類中所有類和接口類的對象(包括私有的) |
forName(String className) | 根據(jù)類名返回類的對象 |
newInstance() | 創(chuàng)建類的實例 |
getName() | 獲得類的完整路徑的名字 |
- 常用獲得類中屬性相關(guān)的方法:(以下返回值為Field)
方法 | 用途 |
getField(String?name) | 獲得某個公有屬性對象 |
getField() | 獲得所有公有的屬性對象 |
getDeclaredField(String?name) | 獲得某個屬性對象 |
getDeclaredFields() | 獲得所有屬性對象 |
- 獲得類中注解相關(guān)的方法:
方法 | 用途 |
getAnnotation(Class?annotationClass) | 返回該類中參與類型匹配的公有注解對象 |
getAnnotations() | 返回該類所有公有注解對象 |
getDeclaredAnnotation(Class?annotationClass) | 返回該類中參與參數(shù)類型匹配的所有注解對象 |
getDeclaredAnnotations() | 返回該類所喲的注解對象 |
- 獲得類中構(gòu)造器相關(guān)的方法(以下返回值為Constructor)
方法 | 用途 |
getConstructor(Class...<?>parameterTypes) | 獲得該類中與參數(shù)匹配的公有構(gòu)造方法 |
getConstructors() | 獲得該類的所有公有構(gòu)造方法 |
getDeclaredConstructor(Class...<?>parameterTypes) | 獲得該類中與參數(shù)類型匹配的構(gòu)造方法 |
getDeclaredConstructors() | 獲得該類所有構(gòu)造方法 |
- 獲得類中方法相關(guān)的方法(以下方法返回值為Method)方法
方法 | 用途 |
getMethod(String?name,Class...<?>?parameterTypes) | 獲得該類某個公有的方法 |
getMethods() | 獲得該類所有公有的方法 |
getDeclaredMethod(String?name,?Class...<?>?parameterTypes) | 獲得該類某個方法 |
getDeclaredMethods() | 獲得該類所有方法 |
2.5?反射實例
2.5.1?獲得Class對象的三種方式
在反射之前,我們需要做的第一步就是先拿到當前需要反射的類的Class對象,然后通過Class對象的核心方法,達到反射的目的,即:在運行狀態(tài)中,對于任意一個類,都能夠知道這個類所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意方法和屬性,既然能拿到,我們就可以修改部分類型的信息。
第一種:使用Class.forName("類的全路徑名");靜態(tài)方法。
前提:已經(jīng)明確類的全路徑名。
第二種:使用.class方法。
說明:僅適合在編譯前就已經(jīng)明確要操作的Class。
第三種:使用類對象的getClass()方法。
代碼演示:
package 反射;
class Student{
//私有屬性
private String name = "bit";
//公有屬性
public int age = 19;
//不帶參數(shù)的構(gòu)造方法
public Student() {
System.out.println("Student()");
}
private Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,name)");
}
private void eat() {
System.out.println("i am eat");
}
public void sleep() {
System.out.println("i am pig");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test1 {
public static void main(String[] args) {
//1.通過getClass獲取Class對象
Student s1 = new Student();
Class<?> c1 = s1.getClass();
//2.直接通過類名.class的方式得到,該方法最為安全可靠,程序性能更高
// 這說明任何一個類都有一個隱含的靜態(tài)成員變量class
Class<?> c2 = Student.class;
//3.通過Class對象的forName()靜態(tài)方法來獲取,用的最多,
// 但可能會拋出ClassNotFoundException異常
Class<?> c3 = null;
try {
//注意這里是類的全路徑,如果有包需要加包的路徑
c3 = Class.forName("反射.Student");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
//一個類只能擁有一個class對象
//一個類在JVM中只會有一個Class實例,即我們對上面獲取的
// c1,c2,c3進行equals比較,發(fā)現(xiàn)都是true
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c2.equals(c3));
}
}
結(jié)果展示:
注意:
在上述三種獲取class對象的方法中我們最常使用的就是第三種方法。通過.forName來進行獲取。
2.5.2?反射的使用
接下來我們開始使用反射,我們依舊反射上面的Student類,把反射的邏輯寫到另外的類當中進行理解。
注意:所有和反射相關(guān)的包都在import java.lang.reflect包下面。
2.5.2.1?反射創(chuàng)建對象
代碼:
package 反射;
//在類外通過反射來創(chuàng)建出一個對象
public class ReflectClassDemo {
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("學生對象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
reflectNewInstance();
}
}
結(jié)果:
通過反射創(chuàng)建出來一個對象之后然后調(diào)用之前類中的不帶參數(shù)的構(gòu)造方法。?
2.5.2.2?反射調(diào)用私有的構(gòu)造方法
代碼:
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//在類外通過反射來創(chuàng)建出一個對象
public class ReflectClassDemo {
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("學生對象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射.Student");
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
//相當于是程序的開關(guān),確定是否運行上述代碼
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("gaolele",20);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// reflectNewInstance();
reflectPrivateConstructor();
}
}
結(jié)果:
2.5.2.3?反射獲取私有的屬性
代碼:
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
//在類外通過反射來創(chuàng)建出一個對象
public class ReflectClassDemo {
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("學生對象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射.Student");
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
//相當于是程序的開關(guān),確定是否運行上述代碼
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("gaolele",20);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateField() {
try {
Class<?> c1 = Class.forName("反射.Student");
Field field = c1.getDeclaredField("name");
field.setAccessible(true);
//獲取一個對象
Student student = (Student) c1.newInstance();
field.set(student,"唐老鴨");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// reflectNewInstance();
// reflectPrivateConstructor();
reflectPrivateField();
}
}
結(jié)果:
2.5.2.4 反射獲取私有方法
代碼:
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectClassDemo {
//在類外通過反射來創(chuàng)建出一個對象
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("學生對象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//通過反射調(diào)用私有的構(gòu)造方法
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射.Student");
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
//相當于是程序的開關(guān),確定是否運行上述代碼
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("gaolele",20);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//通過反射獲取私有屬性
public static void reflectPrivateField() {
try {
Class<?> c1 = Class.forName("反射.Student");
Field field = c1.getDeclaredField("name");
field.setAccessible(true);
//獲取一個對象
Student student = (Student) c1.newInstance();
field.set(student,"唐老鴨");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//通過反射調(diào)用私有方法
public static void reflectPrivateMethod() {
try {
Class<?> c1 = Class.forName("反射.Student");
Method method = c1.getDeclaredMethod("function", String.class);
method.setAccessible(true);
//獲取一個對象
Student student = (Student) c1.newInstance();
method.invoke(student,"我是一個參數(shù)!");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// reflectNewInstance();
// reflectPrivateConstructor();
// reflectPrivateField();
reflectPrivateMethod();
}
}
結(jié)果:
2.5.3?反射的優(yōu)缺點
優(yōu)點:
- 對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法。
- 增加程序的靈活性和擴展性,降低耦合性,提高子樹自適能力。
- 反射已經(jīng)運用在了很多流行框架中,如:Struts、Hibernate、Spring等等。
缺點:
- 使用反射會有效率問題,會導致程序效率降低。
- 反射技術(shù)繞過了源代碼的技術(shù),因而會帶來維護問題,反射代碼比相應的直接更復雜。
3.?枚舉
枚舉是在JDK1.5以后引入的。主要用途是:將一組常量組織起來,在這之前表示一組常量通常使用定義常量的方式:
public?static?final?int?RED = 1;
public?static?final?int?GREEN?= 2;
public?static?final?int BLACK?= 3;
但是常量舉例有不好的地方,例如:可能碰巧有個數(shù)字1,但是他有可能誤會為是RED,現(xiàn)在我們可以直接用枚舉來進行組織,這樣一來,就擁有了類型,枚舉類型。而不是普通的整形1。
public?enum?TestEnum{
? ? ? ? RED,BLACK,GREEN;
}
優(yōu)點:將常量組織起來統(tǒng)一進行管理
場景:錯誤狀態(tài)碼,消息類型,顏色的劃分,狀態(tài)機等等...
本質(zhì):是java.lang.Enum的子類,也就是說,自己寫的枚舉類,就算沒有顯示的繼承Enum,但是其默認繼承了這個類。
3.1 枚舉的使用
代碼實例:
package 枚舉;
public class Test1 {
public enum TestEnum{
RED,BLACK,WHITE,GREED;
}
public static void main(String[] args) {
TestEnum testEnum = TestEnum.BLACK;
switch (testEnum) {
case RED:
System.out.println("red");
break;
case BLACK:
System.out.println("black");
break;
case WHITE:
System.out.println("white");
break;
case GREED:
System.out.println("greed");
break;
default:
break;
}
}
}
結(jié)果展示:
3.2?枚舉的常用方法
Enum類的常用方法
方法名稱 | 描述 |
values() | 以數(shù)組形式返回枚舉類型的所有成員 |
ordinal() | 獲取枚舉成員的索引位置 |
valueOf() | 將普通字符串轉(zhuǎn)換為枚舉實例 |
comparTo() | 比較兩個枚舉成員在定義時的順序 |
代碼1:
package 枚舉;
public class Test2 {
public enum TestEnum{
RED,BLACK,GREEN,WHITE;
}
public static void main(String[] args) {
//以數(shù)組的形式返回枚舉類型的所有實例
TestEnum[] testEnums = TestEnum.values();
//打印枚舉成員以及獲取枚舉成員的索引位置
for (int i = 0; i < testEnums.length; i++) {
System.out.println(testEnums[i] + " " + testEnums[i].ordinal());
}
System.out.println("======================");
//將普通字符串轉(zhuǎn)換為枚舉實例
System.out.println(TestEnum.valueOf("GREEN"));
}
}
?
結(jié)果1:
代碼2:
package 枚舉;
import static 枚舉.Test3.TestEnum.BLACK;
import static 枚舉.Test3.TestEnum.RED;
public class Test3 {
public enum TestEnum{
RED,BLACK,GREEN,WHITE;
}
public static void main(String[] args) {
//拿到枚舉實例BLACK
TestEnum testEnum = BLACK;
//拿到枚舉實例RED
TestEnum testEnum1 = RED;
System.out.println(testEnum.compareTo(testEnum1));
System.out.println(BLACK.compareTo(RED));
System.out.println(RED.compareTo(BLACK));
}
}
結(jié)果2:
剛剛說過,在java當中枚舉實際上就是一個類,所以我們在定義枚舉的時候,還可以這樣定義和使用枚舉:
注意:枚舉的構(gòu)造方法默認是私有的
代碼:
package 枚舉;
import javax.swing.text.html.parser.TagElement;
public class Test4 {
public enum TestEnum{
RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
private String name;
private int key;
/**
* 1.當枚舉對象有參數(shù)后,需要提供相應的構(gòu)造函數(shù)
* 2.枚舉的構(gòu)造函數(shù)默認是私有的,這個一定要記住
*/
private TestEnum(String name, int age) {
this.name = name;
this.key = age;
}
//獲取枚舉類中的索引值所對應的枚舉成員
public static TestEnum getEnumKey(int key){
for (TestEnum t : TestEnum.values()) {
if (t.key == key) {
return t;
}
}
return null;
}
public static void main(String[] args) {
System.out.println(getEnumKey(2));
}
}
}
結(jié)果:
3.3?枚舉的優(yōu)缺點
優(yōu)點:
- 枚舉常量更簡單安全。
- 枚舉具有內(nèi)置方法,代碼更優(yōu)雅。
缺點:
- 不可以繼承,無法擴展。
4.?枚舉和反射
4.1?枚舉是否可以通過反射,拿到實例對象呢?
我們剛剛在反射里邊看到了,任何一個類,哪怕其構(gòu)造方法是私有的,我們也可以通過反射拿到它的實例對象,那么枚舉的構(gòu)造方法也是私有的,我們是否可以拿到呢?接下來小編就帶著大家一起來探究一下吧。
同樣我們使用上面提供的枚舉類來進行舉例。
代碼:
package 反射與枚舉;
import java.lang.reflect.Constructor;
public enum TestEnum {
RED("red", 1), BLACK("black", 2), WHITE("white", 3), GREEN("green", 4);
private String name;
private int key;
private TestEnum(String name, int key) {
this.name = name;
this.key = key;
}
public static TestEnum getEnumKey(int key) {
for (TestEnum t : TestEnum.values()) {
if (t.key == key) {
return t;
}
}
return null;
}
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射與枚舉.TestEnum");
//注意傳入對應的參數(shù),獲得對應的構(gòu)造對象,當前枚舉類是提供了兩個參數(shù)
//分別是String和int
Constructor<?> declareConstructorStudent = c1.getDeclaredConstructor(String.class, int.class,String.class, int.class);
//設置為true后可修改訪問權(quán)限
declareConstructorStudent.setAccessible(true);
Object objectStudent = declareConstructorStudent.newInstance("綠色",666,"橙色",666);
TestEnum testEnum = (TestEnum) objectStudent;
System.out.println("獲得枚舉的私有構(gòu)造函數(shù):" + testEnum);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
reflectPrivateConstructor();
}
}
結(jié)果:
注意:上述結(jié)果我們可以看出枚舉可以避免反射問題。
5. Lambda表達式
lambda表達式是JavaSE8中一個重要的新特性,lambda表達式允許你通過表達式功能接口,lambda表達式就和方法一樣,它提供了一個正常的參數(shù)列表和一個使用這些參數(shù)主體(body,可以是一個表達式或一個代碼塊)。lambda表達式是基于數(shù)學中的λ演算得名,也可以稱為閉包。
5.1?lambda表達式的語法
基本語法:(parameters)->?expression或(parameters)->{statements;}
lambda表達式由三部分組成:
- paramaters:類似方法中的形參列表,這里的參數(shù)是函數(shù)式接口里的參數(shù),這里的參數(shù)類型可以明確的聲明也可以不聲明有JVM隱含的推斷,另外當只有一個推斷類型時可以省略掉圓括號。
- ->:可理解為“被用于”的意思。
- 方法體:可以是表達式也可以是代碼塊,是函數(shù)式接口里的方法的實現(xiàn),代碼塊可以返回一個值或者什么都不返回,這里的代碼塊等同于方法的方法體,如果是表達式,也可以返回一個值或者什么都不返回。
5.2?函數(shù)式接口
要了解lambda表達式,首先需要了解什么是函數(shù)式接口,函數(shù)式接口定義:一個接口有且只有一個抽象方法。
注意:
- 如果一個接口只有一個抽象方法,那么該接口就是一個函數(shù)式接口。
- 如果我們在某個接口上聲明了@FunctionalInterface注解,那么編譯器就會按照函數(shù)式接口的定義來要求該接口,這樣如果有兩個抽象方法,程序編譯就會報錯的。所以從某種意義上來說,只有你保證你的接口中只有一個抽象方法,你可以不加這個注解,加上就會自動進行檢測。
定義方式:
@FunctionalInterface interface NoParameterNoReturn{ //注意:只能有一個抽象方法 void test(); }
也可以通過下述方法來定義:
@FunctionalInterface interface NoParameterNoReturn{ void test1(); default void test() { System.out.println("JDK1.8新特征,default默認方法可以有具體的實現(xiàn)"); } }
5.3?Lambda表達式的基本使用
首先我們先來準備好幾個接口:
@FunctionalInterface
interface NoParameterNoReturn{
//注意:只能有一個抽象方法
void test();
}
//無返回值一個參數(shù)
@FunctionalInterface
interface OneParameterNoReturn{
void test(int a);
}
//無返回值多個參數(shù)
@FunctionalInterface
interface MoreParameterNoReturn{
void test(int a, double b);
}
//有返回值無參數(shù)
@FunctionalInterface
interface NoParameterReturn{
int test();
}
//有返回值一個參數(shù)
@FunctionalInterface
interface OneParameterReturn{
int test(int a);
}
//有返回值多個參數(shù)
@FunctionalInterface
interface MoreParameterReturn{
int test(int a, int b);
}
我們在上面提到過,lambda可以理解為:lambda就是匿名內(nèi)部類的簡化,實際上是創(chuàng)建了一個類,實現(xiàn)了接口,重寫了接口的方法。
沒有使用lambda表達式的時候調(diào)用方式:
public static void main(String[] args) {
NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
@Override
public void test() {
System.out.println("hello");
}
};
noParameterNoReturn.test();
}
具體的使用如下代碼所示:
package lambda表達式;
@FunctionalInterface
interface NoParameterNoReturn{
//注意:只能有一個抽象方法
void test();
}
//@FunctionalInterface
//interface NoParameterNoReturn{
// void test1();
// default void test() {
// System.out.println("JDK1.8新特征,default默認方法可以有具體的實現(xiàn)");
// }
//}
//無返回值一個參數(shù)
@FunctionalInterface
interface OneParameterNoReturn{
void test(int a);
}
//無返回值多個參數(shù)
@FunctionalInterface
interface MoreParameterNoReturn{
void test(int a, double b);
}
//有返回值無參數(shù)
@FunctionalInterface
interface NoParameterReturn{
int test();
}
//有返回值一個參數(shù)
@FunctionalInterface
interface OneParameterReturn{
int test(int a);
}
//有返回值多個參數(shù)
@FunctionalInterface
interface MoreParameterReturn{
int test(int a, int b);
}
class Demo<T> {
}
public class Test1 {
public static void main(String[] args) {
NoParameterNoReturn noParameterNoReturn = () ->{
System.out.println("無參數(shù)無返回值");
};
noParameterNoReturn.test();
OneParameterNoReturn oneParameterNoReturn = (int a) -> {
System.out.println("一個參數(shù)無返回值:" + a);
};
oneParameterNoReturn.test(3);
//注意:當前后參數(shù)的類型一致的時候可以省略類型
MoreParameterNoReturn moreParameterNoReturn = (a,b) -> {
System.out.println("多個參數(shù)無返回值:" + a + " " + b);
};
moreParameterNoReturn.test(3,6);
NoParameterReturn noParameterReturn = () -> {
System.out.println("有返回值無參數(shù)!");
return 40;
};
int ret1 = noParameterReturn.test();
System.out.println(ret1);
OneParameterReturn oneParameterReturn = (int a) -> {
System.out.println("有返回值一個參數(shù)!");
return a;
};
int ret2 = oneParameterReturn.test(50);
System.out.println(ret2);
MoreParameterReturn moreParameterReturn = (int a, int b) -> {
System.out.println("有返回值多個參數(shù)!");
return a + b;
};
int ret3 = moreParameterReturn.test(60,70);
System.out.println(ret3);
}
public static void main2(String[] args) {
NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
@Override
public void test() {
System.out.println("hello");
}
};
noParameterNoReturn.test();
}
public static void main1(String[] args) {
//<>當中的數(shù)據(jù)類型不參與類型的組成 JVM當中沒有泛型的概念 泛型 只存在于編譯階段
Demo<String> demo = new Demo<>();
System.out.println(demo);
Demo<Integer> demo1 = new Demo<>();
System.out.println(demo1);
}
}
結(jié)果如下所示:
注意:
- 參數(shù)類型可以省略,如果需要省略,每個參數(shù)的類型都要省略。
- 參數(shù)的小括號里面只有一個參數(shù),那么小括號也可以省略。
- 如果方法體中只有一句代碼,那么大括號也可以省略。
- 如果方法體中只有一條語句,且是return語句,那么大括號可以省略,且去掉return關(guān)鍵字。
5.4?變量捕獲
Lambda表達式中存在變量捕獲,了解了變量捕獲之后,我們才能更好的理解Lambda表達式的作用域,Java當中匿名類中,會存在變量捕獲。
5.4.1?匿名內(nèi)部類
匿名內(nèi)部類就是沒有名字的內(nèi)部類,我們這里只是為了說明變量捕獲,所以,匿名內(nèi)部類只要會使用就好了,匿名接下來我們簡單的看一下匿名內(nèi)部類的使用就好了。
代碼展示:
package lambda表達式;
class Test{
public void func() {
System.out.println("func()");
}
}
public class Test2 {
public static void main(String[] args) {
new Test(){
@Override
public void func() {
System.out.println("我是內(nèi)部類,且重寫了func這個方法!");
}
}.func();
}
}
結(jié)果展示:
注意:要調(diào)用內(nèi)部類的時候直接在后面進行?.函數(shù)?即可。
上述我們看到的是一個簡單的匿名內(nèi)部類的例子,下面我們來演示一下匿名內(nèi)部類的變量是如何捕獲的。
5.4.2?匿名內(nèi)部類的變量捕獲
代碼展示:
package lambda表達式;
class Demo1{
public void func() {
System.out.println("func()");
}
}
public class Test3 {
public static void main(String[] args) {
int a = 100;
new Demo1() {
@Override
public void func() {
System.out.println("我是內(nèi)部類,其重寫了func()這個方法!");
System.out.println("我是捕獲到變量a == " + a + " 我是一個常量,或者是一個沒有改變過值的變量!");
}
}.func();
}
}
結(jié)果展示:
注意:在上述代碼當中變量a就是捕獲的變量,這個變量要么是被final修飾,如果不是被final修飾的你要保證在使用之前沒有修改,如下代碼就是錯誤的代碼?。。?/span>
由于有編譯錯誤所以小編這里就不運行代碼了。
我們可以看到上述代碼中我在內(nèi)部類中是不可以對變量進行修改的。
5.5?Lambda的變量捕獲
在lambda當中也可以進行變量的捕獲,具體我們看一下代碼。?
代碼展示:
package lambda表達式;
@FunctionalInterface
interface NoParameterNoReturn1{
void test();
}
public class Test5 {
public static void main(String[] args) {
int a = 10;
NoParameterNoReturn1 noParameterNoReturn1 = () -> {
// a = 99;//error
System.out.println("捕獲變量:" + a);
};
noParameterNoReturn1.test();
}
}
結(jié)果展示:
5.6?Lambda在集合當中的使用
為了能夠讓lambda和Java的集合類集更好的一起使用,集合當中也新增了部分接口,以便于lambda表達式對接。
對應的接口 | 新增的方法 |
Collection | removeIf()?spliterator()?stream()?parallelStream()?forEach() |
List | replaceAll() sort() |
Map | getOrDefault() forEach() replaceAll() putlfAbsent() remove() replace() computeIfAbsent() computeIfPresent()?compute()?merge() |
下面我們將演示一些方法,注意:Collection和forEach()方法是從接口java.lang.lterable拿過來的。
5.6.1?Collection接口
forEach方法的演示:
該方法子啊接口Iterable當中,原型如下所示:
?
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
該方法表示:對容器中的每一個元素執(zhí)行action指定的動作。
代碼如下所示:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
結(jié)果如下所示:
那么既然我們上面學習了有關(guān)于lambda的使用那么這里我們就可以修改上述代碼。
代碼展示:
package lambda表達式;
import java.util.ArrayList;
public class Test7 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
//表示調(diào)用一個不帶參數(shù)的方法,其執(zhí)行花括號內(nèi)的語句,為原來的函數(shù)體內(nèi)容。
list.forEach(s->{
System.out.print(s + " ");
});
}
}
結(jié)果展示:
5.6.2?List接口
sort()方法的演示:
sort方法源碼:該方法根據(jù)c指定的比較規(guī)則對容器元素進行排序。
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
使用如下所示:
代碼展示:
package lambda表達式;
import java.util.ArrayList;
import java.util.Comparator;
public class Test8 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//注意這里比較的是長度
return o1.length() - o2.length();
}
});
System.out.println(list);
}
}
結(jié)果展示:
修改為lambda表達式:
代碼展示:
package lambda表達式;
import java.util.ArrayList;
public class Test9 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
//調(diào)用帶有兩個參數(shù)的方法,且返回長度的差值
list.sort((s1,s2) -> s1.length() - s2.length());
System.out.println(list);
}
}
結(jié)果展示:
?
5.6.3?Map接口
HashMap的forEach()
該方法原型如下所示:
?
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
具體的使用,它的作用是對Map中的每個映射執(zhí)行action指定的操作。
代碼展示:
package lambda表達式;
import java.util.HashMap;
import java.util.function.BiConsumer;
public class Test10 {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"Hello");
map.put(2,"bit");
map.put(3,"hello");
map.put(4,"lambda");
map.forEach(new BiConsumer<Integer, String>() {
@Override
public void accept(Integer k, String v) {
System.out.println(k + " = " + v);
}
});
}
}
結(jié)果展示:
使用lambda表達式后的代碼:
代碼展示:
package lambda表達式;
import java.util.HashMap;
public class Test11 {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"Hello");
map.put(2,"bit");
map.put(3,"hello");
map.put(4,"lambda");
map.forEach((k,v) -> System.out.println(k + " = " +v));
}
}
結(jié)果展示:
5.7?lambda表達式的優(yōu)缺點
優(yōu)點:
- 代碼簡潔,開發(fā)迅速。
- 方便函數(shù)式編程。
- 非常容易進行并行計算。
- Java引入lambda,改善了集合操作。
缺點:
- 代碼可讀性變差。
- 在非并行計算中,很多計算未必有傳統(tǒng)的for性能要高。
- 不容易進行調(diào)試。
6.?泛型進階
6.1?什么是泛型
一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義的類,如果要編譯可以應用于多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。泛型是在JDK1.5引入的新的語法,通俗講,泛型:就是適用許多類型,從代碼上講,就是對類型實現(xiàn)了參數(shù)化。
6.2?引出泛型
實現(xiàn)一個類,類中包含一個數(shù)組成員,使得數(shù)組中可以存放任何類型的數(shù)據(jù),也可以根據(jù)成員方法返回數(shù)組中某個下標的值。
思路:
- 我們以前學過的數(shù)組,只能存放指定類型的元素,例如:int[] array = new int[10];String[] str = new String[10];
- 所有類的父類,默認為Object類,數(shù)組是否可以創(chuàng)建為Object?
?如下所示:
以上代碼實現(xiàn)后發(fā)現(xiàn):
- 任何類型數(shù)據(jù)都可以存放。
- 1號下標本身就是字符串,但是卻編譯報錯,必須進行強制類型轉(zhuǎn)換。
雖然在這種情況下,當前數(shù)組任何數(shù)據(jù)都可以存放,但是更多情況下,我們還是希望他只能夠持有一種數(shù)據(jù)類型。而不是同時持有這么多類型,所以,泛型的主要目的就是:指定當前的容器,要持有什么類型的對象,讓編譯器去做檢查,此時就需要把類型,作為參數(shù)傳遞,需要什么類型,就傳入什么類型。
6.3?語法
class?泛型類名稱<類型形參列表> {
? ? ? ? //這里可以使用類型參數(shù)?
}
class?ClassName<T1,T2,...,Tn> {
}
class?泛型類名稱<類型形參列表> extends?繼承類{
? ? ? ? //這里可以使用類型參數(shù)
}
class?ClassName<T1,T2,...,Tn>?extends?ParentClass<T1> {
? ? ? ? //可以只使用部分類型參數(shù)
}
將上述代碼改寫后:
package lambda表達式;
class MyArray1<T> {
public T[] array = (T[])new Object[10];
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos, T val) {
this.array[pos] = val;
}
}
public class Test13 {
public static void main(String[] args) {
MyArray1<Integer> myArray1 = new MyArray1<>();
myArray1.setVal(0, 10);
myArray1.setVal(1, 12);
int ret = myArray1.getPos(1);
System.out.println(ret);
// myArray1.setVal(2,"bit");//后面就會出現(xiàn)編譯錯誤,因為上面我們指定的類型是Integer
}
}
結(jié)果如下所示:?
代碼解釋:
- 類名后的<T>代表占位符,表示當前類是一個泛型類。
- 在代碼中我們使用的是:public T[] array = (T[])new Object[10];而不是T[] array = new T[10];意味著這樣寫是不對的。
- 在<>中我們加入Integer指定當前的類型。
- 既然以及指定類型那么我們在取數(shù)據(jù)的時候就不需要進行強制類型轉(zhuǎn)換。
- myArray1.setVal(2,"bit");在此處代碼會編譯出錯,此時因為上面已經(jīng)指定了類型,所以編譯器會在存放元素的時候幫助我們進行類型檢查。
類型形參一般使用一個大小寫字母表示,常用的名稱有:
- E:表示Element
- K:表示Key
- V:表示Value
- N:表示NUmber
- T:表示Type
6.4?泛型類的使用
6.4.1?語法
泛型類<類型實參>?變量名;//定義一個泛型類引用
new?泛型類<類型實參>(構(gòu)造方法實參);//實例化一個泛型類對象
MyArray<Integer> list = new MyArray<Integer>();
注意:泛型只能接收類,所有的基本數(shù)據(jù)類型必須使用包裝類。
6.4.2?裸類型
裸類型是一個泛型類但沒有帶著類型實參,例如MyArray list就是一個裸類型。
如:MyArray list = new MyArray();
注意:我們不要自己去使用裸類型,裸類型是為了兼容老版本的API保留的機制。
6.5?泛型的編譯
6.5.1?擦除機制
什么是擦除機制呢?泛型到底是怎么編譯的,我們可以通過查看字節(jié)碼文件就會發(fā)現(xiàn)所有的T都是Object。在編譯的過程中,將所有的T替換為Object這種機制,我們稱為:擦除機制。
Java的泛型機制是在編譯級別實現(xiàn)的,編譯器生成的字節(jié)碼在運行期間并不包含泛型的類型信息。
6.5.2 為什么不能實例化泛型類數(shù)組
代碼:
package lambda表達式;
class MyArray2<T> {
public T[] array = (T[])new Object[10];
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos, T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
public class Test14 {
public static void main(String[] args) {
MyArray2<Integer> myArray2 = new MyArray2<>();
Integer[] strings = myArray2.getArray();
}
}
結(jié)果:?
原因:
替換后的方法為:
public Object[] getArray() { return array; }
將Object分配給Integer[]引用 ,程序就會報錯,通俗的講就是返回的Object數(shù)組里面,可能存放的是任何的數(shù)據(jù)類型,可能是String,可能是Person,運行的時候,直接轉(zhuǎn)給Integer類型的數(shù)組,編譯器認為是不安全的。
正確的做法如下所示:
?
package lambda表達式;
import java.lang.reflect.Array;
class MyArray3<T> {
public T[] array;
public MyArray3() {
}
//通過反射創(chuàng)建,指定類型的數(shù)組
public MyArray3(Class<T> clazz, int capacity){
array = (T[]) Array.newInstance(clazz, capacity);
}
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos, T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
public class Test15 {
public static void main(String[] args) {
MyArray3<Integer> myArray3 = new MyArray3<>(Integer.class,10);
Integer[] integers = myArray3.getArray();
}
}
6.6?泛型的上界
在定義泛型類的時候,有時需要對傳入的類型變量做一定的約束,可以通過類型邊界來約束。
語法:
class?泛型類名稱<類型形參 extends?類型邊界>{}
如下所示:
public?class?MyArray<E?extends?Number> {
}
只接受NUmber的子類作為E的類型實參。
- MyArray<Integer>? //正常,因為Integer是Number的子類。
- MyArray<String>? //編譯錯誤,因為String不是Number的子類。
注意:如果沒有指定類型的邊界E,可以視為E?extends?Object。
復雜類型:
public?class?MyArray<E?extends Comparable<E>>{
}
注意:E必須是實現(xiàn)了Comparator接口的。
6.7?泛型方法
6.7.1?定義語法
方法限定符<類型形參列表>?返回值類型?方法名稱(形參列表){...}
實例演示:
package 泛型;
import java.util.Arrays;
public class Test5 {
//靜態(tài)的泛型方法需要在static后用<>聲明泛型類型參數(shù)
public static <E> void swap(E[] array, int i, int j) {
E t = array[i];
array[i] = array[j];
array[j] = t;
}
public static void main(String[] args) {
//使用類型推導
Integer[] a = {1,2,3,4};
swap(a,0,3);
System.out.println(Arrays.toString(a));
String[] b = {"hello", "bit","hello", "world"};
swap(b,0,3);
for (String str : b) {
System.out.print(str + " ");
}
//不使用類型推導
Integer[] c = {2,3,4,5,6};
Test5.<Integer>swap(c,0,4);
}
}
結(jié)果展示:
6.8?通配符
?用于在泛型的使用,即為通配符。
6.8.1?通配符解決什么問題
代碼如下所示:
package 泛型;
class Message<T> {
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test6 {
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setMessage("Hello world");
fun(message);
}
private static void fun(Message<String> message) {
System.out.println(message.getMessage());
}
}
結(jié)果如下所示:
以上程序會帶來新的問題,如果現(xiàn)在泛型的類型設置不是String,而是Integer。
那么我們需要的解決方案:可以接收所有泛型類型,但是又不能夠讓用戶隨意修改,這種情況就需要通配符“?”來處理。
如下所示:
package 泛型;
class Message1<T> {
private T message;
public T getMessage1() {
return message;
}
public void setMessage1(T message) {
this.message = message;
}
}
public class Test7 {
public static void main(String[] args) {
Message1<Integer> message1 = new Message1<>();
message1.setMessage1(55);
fun(message1);
}
//此時使用通配符“?”描述的是可以接收任意類型,但是由于不確定類型,所以無法修改。
private static void fun(Message1<?> message1) {
System.out.println(message1.getMessage1());
}
}
結(jié)果展示:
在 “?”的基礎(chǔ)上又產(chǎn)生了兩個子通配符:
- ?extends?類:設置通配符上限。
- ?super類:設置統(tǒng)配符下限。
接下來我們就一一看一下吧。
6.8.2?通配符上界
語法:
<? extends?上界>?
<??extends?Number> //可以傳入實參類型是Number或者Number的子類。
如下面例子所示:
?
package 泛型;
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Banana extends Fruit{
}
class Message3<T> {
//設置泛型
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test8 {
public static void main(String[] args) {
Message3<Apple> message3 = new Message3<>();
message3.setMessage(new Apple());
fun(message3);//1
Message3<Banana> message31 = new Message3<>();
message31.setMessage(new Banana());
fun(message31);//2
}
//此時使用統(tǒng)配符“?”描述的是他可以直接接收任意類型,但是由于不確定類型,所以無法修改。
private static void fun(Message3<? extends Fruit> message3) {
System.out.println(message3.getMessage());
}
}
結(jié)果:
?
?
注意:統(tǒng)配符的上界只能讀取數(shù)據(jù),不能寫入數(shù)據(jù)。
6.8.3?通配符的下界
語法:
<? super?下界>
<? super Integer> //代表可以傳入的實參的類型是Integer或者是Integer的父類類型。
代碼:
package 泛型;
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Plate<T> {
private T plate;
public T getPlate() {
return plate;
}
public void setPlate(T plate) {
this.plate = plate;
}
}
public class Test9 {
public static void main(String[] args) {
Plate<Fruit> plate1 = new Plate<>();
plate1.setPlate(new Fruit());
fun(plate1);
Plate<Food> plate2 = new Plate<>();
plate2.setPlate(new Food());
fun(plate2);
}
private static void fun(Plate<? super Fruit> plate1) {
//此時可以修改,添加的是Fruit或者是Fruit的子類
plate1.setPlate(new Apple());//這個是Fruit的子類。
plate1.setPlate(new Fruit());//這個是Fruit本身
// Fruit fruit = plate1.getPlate();//不能接收,這里無法確定是哪個父類
System.out.println(plate1.getPlate());//只能直接輸出
}
}
結(jié)果:文章來源:http://www.zghlxwxcb.cn/news/detail-515714.html
注意:統(tǒng)配符的下界,不能讀取數(shù)據(jù),只能寫入數(shù)據(jù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-515714.html
結(jié)束語: ?
到了這里,關(guān)于String、反射、枚舉、lambda表達式以及泛型進階(數(shù)據(jù)結(jié)構(gòu)系列16)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!