泛型的作用
泛型可以让我们在使用时变换任意类型,使用泛型的话,编写一套代码就可以运用所有类型
泛型类的定义和使用
public interface Collection<E> extends Iterable<E> {}
public interface Map<K, V> {}
- Java中这些集合就是标准的泛型类
public class ArrayList<E> {
transient Object[] elementData;
public E get(int index) {
return (E) elementData[index];
}
public boolean add(E e) {
...
return true;
}
}
- 定义一个泛型类,只需在类名后面,使用尖括号将一个或多个符号包裹起来,然后在类中就可以使用该符号替换掉具体类型了,这个符号可以看作是一个参数
- 因为可以像参数那样动态变化,所以这个符号我们称之为类型参数或者叫泛型参数
public class Generic<RudeCrab, T, E, K, V> {}
- 类型参数可以用任意的合法标识命名,A,B,C之类的都行,我们约定俗成采用简短的大写形式,像T,E,K,V都是我们管用的标识
ArrayList<String> list = new ArrayList<>();
list.add("今天是2020-07-20,深圳天气:晴转暴雨,心情:平静");
list.add("路西法98");
list.add(98); //编译报错
list.add(new Person()); //编译报错
for (String s: list) {
System.out.println(s.length());
}
- 我们来看一下使用泛型能够带来什么好处,首先,那些泛型参数的方法只能接收指定的类型,如果对象的类型不对就会编译失败,有效地避免了调用者乱传参数
- 需要注意一点,如果我们在使用泛型类时不指定类型,那么类型参数就会将其视为Object
泛型并不只存在于Java中,它是一种编程范式,很多语言都有泛型机制,不过不同语言的泛型,背后的实现会有所不同,带来的实际效果也会有差异,Java实现的泛型机制有其特殊性和局限性
泛型方法的定义和使用
// 泛型类
class Generic<T> {
public <E> void process(E e) {}
}
// 普通类
class Normal {
public <E> void process(E e) {}
}
- 泛型方法和泛型类没有必然联系,它可以定义在泛型类中也可以定义在普通类中
public class GenericMethod {
public <T> T get<T obj> {
//...
return obj;
}
}
- 定义一个泛型方法,需要在返回值前面,使用尖括号声明一个或多个泛型参数,然后在方法中及可以用到声明的泛型参数了
GenericMethod genericMethod = new GenericMethod();
String s = genericMethod.get("路西法98");
Person p = genericMethod.get(new Person());
Integer i = genericMethod.get(98);
Double d = genericMethod.get(99.9);
调用泛型方法时,我们通常不需要像泛型类那样手动指定具体类型,因为编译器会根据你的调用,自动推导出具体类型,比如你操作的是String类型的对象,那此时参数类型就是String类型,你操作的是Person类型的对象,那此时参数类型就是Person类型,这里可以发现,参数类型只能是引用类型,不能是基本类型,就算你传入的是基本类型,编译器也会将其推导为包装类
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(123);
Integer integer = list1.get(0);
ArrayList<int> list2 = new ArrayList<>(); //编译错误
我们在使用泛型类时也是如此,只能使用引用类型,不能指定基本类型,不过有自动装箱机制,在泛型类中使用基本类型时会自动装箱成包装类
上面看的是成员方法,下面我们来看静态方法
public class Generic<T> {
private static T data; //编译错误
public static void setData(T data) { //编译错误
this.data = data;
}
}
泛型类有一个局限,就是静态方法和静态属性,访问不了类上定义的泛型参数
ArrayList<String> strlist = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
因为我们在指定泛型参数的具体类型时,是在实例化该类时指定的,实例化,跟对象有关,静态的访问不了对象实例
public class GenericMethod {
public static <T> T get(T t) {
return t;
}
}
所以,静态方法要使用泛型,就必须定义成泛型方法,和成员泛型方法一样,在返回值前面用尖括号声明泛型参数即可
泛型类 | 泛型方法 | |
---|---|---|
定义 | 在类名后面定义泛型参数 | 在方法返回值前面定义泛型参数 可以定义在泛型类中也可以定义在普通类中 |
使用 | 实例化时指定具体类型 | 调用者推导类型 |
静态方法 | 不能访问泛型参数 | 可以访问泛型参数 |
使用场景 | 泛型参数需要在多个方法或成员属性间扭转 | 泛型参数只需作用于某个方法 |
总结:泛型方法和泛型类没有必然联系
//泛型类
class Generic<T> {
public <E> void process(E e) {}
}
//普通类
class Normal {
public <E> void process(E e) {}
}
可以在泛型类中定义泛型方法,也可以在普通类中定义
ArrayList<String> strList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
泛型类中泛型参数,需要在实例化该类时指定具体类型
GenericMethod genericMethod = new GenericMethod();
String s = genericMethod.get("今天是2020-07-21,深圳天气:晴,心情:还好")
Person p = genericMethod.get(new Person());
Integer i = genericMethod.get(98);
Double d = genericMethod.get(99.9);
而泛型方法会根据调用来进行类型推导
public class Generic<T> {
private static T data; //编译错误
public static void setData(T data) { //编译错误
this.data = data;
}
}
静态方法访问不了泛型类的泛型参数
public class GenericMethod {
public static <T> T get(T t) {
return t;
}
}
要使用泛型的话就只能将静态方法泛型化
知道了泛型类和泛型方法的特点后,也就能理解两者的使用场景
public class ArrayList<E> {
transient Object[] elementData;
public E get(int index) {
return (E) elementData[index];
}
public boolean add(E e) {
...
return true;
}
}
如果当你的泛型参数,需要在多个方法或成员属性间扭转,那就得用泛型类,比如集合这种典型的泛型类
public class GenericMethod {
public <T> T get(T obj) {
//...
return obj;
}
}
当泛型参数只需作用于某个方法,那就可以用泛型方法
一般来说,能用泛型方法就用泛型方法,因为这样不会影响到整个类
public interface Generic<T> {
void process(T t);
}
除了泛型类和泛型方法外,泛型还可以作用在接口上,泛型接口和泛型类的定义与使用基本一致,懂了泛型类也就懂了泛型接口。
元组是什么?
- 一种数据结构,看起来像是数组或者列表
- 一般用”n元组“来描述,n为自然数
- eg:(“yellow”, 5, 99.99, true)
元组的特点
- 有序
- 有限
- 不可更改
- 多类型存储
public class TupleTriple<X,Y,Z> {
public final X x;
public final Y y;
public final Z z;
public TupleTriple(X x,Y y,Z z) {
this.x = x;
this.y = y;
this.z = z;
}
}
class DemoProjApplicationTests {
public static void main(String[] args) {
String s = "luxifa";
Integer i = 99;
Date d = new Date();
TupleTriple<String, Integer, Date> tt = new TupleTriple<String, Integer, Date>(s,i,d);
System.out.println(tt);
System.out.println(tt.x);
System.out.println(tt.y);
System.out.println(tt.z);
}
输出结果:
com.luxifa.demoproj.pojo.TupleTriple@7cc355be
luxifa
99
Wed Jul 20 14:23:38 CST 2022
什么情况下使用元组?
当需要传递一组多类型数据,而这组数据本身描述了一个特定形状,又不需要更改。
举例
设计一个游戏,游戏中有一些NPC,每次游戏开始时会出现在特定的地点,特定的时间,做特定的事情,他们的属性和功能都是不变的。
(国王,宫廷花园,散步,午夜) ---------->(Human, Place, Event, Timewinow)
(哥伦布,洞穴,巡逻,白天) ---------->(Monster, Place, Event, Timewinow)