集合的背后----》数据结构:描述和组织数据的。
1.1 泛型的概念
一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《Java编程思想》对泛型的介绍。泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。
1.2 引出泛型
问题:实现一个能够存放任何类型的数组。
问题:此代码维护成本高,虽然可以存放多种类型,但只是方便了存数据,对取数据造成了极大的麻烦,每取一个数据还得进行强转。
为了解决问题,故引出泛型。
1.3 泛型语法
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
现在我们可以改良一下上面的代码
在类名后面用接括号中包含泛型‘T’(也可以别的),将全篇所有的Object更换为T,用的时候直接按数据基本类型存和拿取,即达成了目的,又使得数据更加简洁,维护成本低。
泛型两个最大的意义:
1. 存放数组时会进行类型的检查。
2. 取出数据时泛型会自动帮你转换数据的类型,没有必要再进行强转了。
并且泛型主要是编译时期的一种机制,这种机制的主要表现方式是擦除机制。
通常:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
T[] array = (T[])new Object[10];是否就足够好,答案是未必的。这块问题一会儿介绍。
1.4 类型推导
MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 String
2. 泛型是如何编译的
2.1 擦除机制
就是在编译的时候把所以的T替换成了Object
泛型的主要意义在于转换和检查:如上面那串代码,在编译时期,所有泛型所代表的<T>被擦除机制替换为了Object,而在使用使< >接括号中是什么类型,编译期间就会检查存放数据类型是否与其相同。
3. 如何实例化数组类数组
直接利用泛型类实例化是错误的
因为根据擦除机制,编译时会将T成Object,而Object无法用Integer接收。
public MyArray(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
4. 利用泛型设计一个找最大数的程序
默认最大值为arr[0]将arr[0]与数组中的其他数字比较,如果小将将这个数更换为max。
注意这里的if()括号中不能直接放布尔类型,因为这里的max与arr[i]都是<T>类型,属于引用类型无法直接比较需要用compareTo()接口来比较,但 泛型<T>的擦除机制擦成的Object没有引用
compareTo()接口需要手动进行连接,故引出泛型的上界机制。
class Alg<T extends Comparable<T>>
另外,泛型是可以实例自定义类的,
class Person implements Comparable<Person>只要该类连接了和主类一样的接口。
拿上面的程序举例,
class Alg<T extends Comparable<T>>{
public T findMax(T[] arr){
T max=arr[0];
for (int i = 1; i < arr.length; i++) {
if(max.compareTo(arr[i])<0){
max=arr[i];
}
}
return max;
}
}
class Person implements Comparable<Person>{
public int age;
public Person(int age) {
this.age = age;
}
@Override
public int compareTo(Person o) {
return this.age-o.age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
public class Test{
public static void main(String[] args) {
Alg<Person> person=new Alg<>();
Person[] people={new Person(15),new Person(20)};
System.out.println(person.findMax(people));
}
public static void main2(String[] args) {
Alg<Integer> max=new Alg<>();
Integer[] arr={1,2,3,4,5,6,55};
System.out.println(max.findMax(arr));
}
}
5. 泛型的上界
如上面:的叫做泛型的上界。
泛型上界的语法:
class 泛型类名称<类型形参 extends 类型边界> {
...
}
public class MyArray<E extends Number> {
...
}
<E extends Number>中的E一定是Number的子类或者Number本身。
就是说在main方法中转换泛型类型之时可以传Number本身或者Number的子类,如:
泛型是没有下界的。
注意:泛型类是不可以加static的,因为加了static就会在代码编译过程中运行而这时还没有进入实例化步骤,自然也不能将实例化中的<>接括号中的要求转换的类型带入编码。
6. 泛型方法
但我们要思考,如果静态类传参不依赖实例化对象,我们要如何传参呢?
我们就可以在static后面用接括号传参,此时这个方法就变为了泛型方法,而此时该类已经不是泛型类了。
而此时,在脱离了实例化对象的过程中,我们可以可以将传参放到调用方法前面 。
如果我们不行要泛型类却需要泛型方法,则可以这样:
将接括号放到返回类型的前面,依然可以完成实例化对象的传参。
这样的话,不需要类型的转换即可完成传参。
但如果需要也就可以加上。
7. 通配符
class Message<T> {
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo {
public static void main(String[] args) {
Message<String> message = new Message() ;
message.setMessage("hello world");
fun(message);
}
public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}
}
这样一串代码,如果现在传的参数不是字符串而是Integer类型,那是否要更改fun方法中的String类型呢?有没有一种方法可以尽量简便处理。
这里就要引入通配符了:
在编译器看到的两串代码都是上面那样的,所以说如果同时存在只改变类型的重载,系统会报错。
通配符相当于替代了空白位置。
7. 2 通配符的上界
public static void fun(Message<? extends Fruit> temp){
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
Fruit b = temp.getMessage();
System.out.println(b);
}
通配符的上界无法进行数据的输入,因为无法确定类型,只能进行数据的读取。
7.3 通配符的下界
public static void fun(Message<? super Fruit> temp){
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
temp.setMessage(new Apple());//这个是Fruit的子类
temp.setMessage(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getMessage());//只能直接输出
}