概述
接上文java 泛型简介
https://blog.csdn.net/nvd11/article/details/29391263
1. 泛型类
1.1 泛型类的定义语法
class 类名<泛型标识, 泛型标识..>{
private 泛型标识 变量名;
...
}
例子:
/**
*
* @param <T> Generic Identification - Type formal parameter
* T the type of the specified object when the object is created.
*/
@Slf4j
public class Shooter<T> {
/**
* T, will be specified by outer class/object
*/
private T target;
public void setTarget(T target){
this.target = target;
}
public T getTarget(){
return this.target;
}
public void shoot(){
if (!Objects.isNull(this.getTarget())){
log.info("shoot target: {}", this.getTarget());
}
}
}
1.2 常用的泛型标识: T, E, K, V
T means Type 类型 最常用的泛型标识
E means Element , 一般用于集合Collection
K, V 表示键值对, 一般同时使用
1.3 泛型类的使用方法
1.3.1 使用语法
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
jdk 1.7 后, 后面的<具体数据类型>可以省略(所谓的菱形语法)
类名<具体的数据类型> 对象名 = new 类名<>();
例子:
Shooter<String> strShooter = new Shooter<>();
strShooter.setTarget("A Rabbit");
strShooter.shoot();
输出:
shoot target: A Rabbit
1.3.2 注意事项
泛型不支持传入基本类型, 只能是Object类型的子类。
如果构建对象时没有传入指定类型吗, 那么默认的操作类型是Object
泛型类使用泛型的成员方法不能设置成静态方法(static)
1.3.3 使用泛型的类的getClass
我们再看一个例子
Shooter<String> strShooter = new Shooter<>();
strShooter.setTarget("A Rabbit");
Shooter<Integer> intShooter = new Shooter<>();
intShooter.setTarget(2333);
log.info("strShooter's class is {}", strShooter.getClass());
log.info("intShooter's class is {}", intShooter.getClass());
log.info("{}", strShooter.getClass() == intShooter.getClass());
输出:
strShooter's class is class com.home.javacommon.study.generics.genclass.Shooter
intShooter's class is class com.home.javacommon.study.generics.genclass.Shooter
true
可以看出, 无论传入了什么类型参数, Shooter 对象的类是一样的都是同1个类, 在jvm里并没有区别处理,所谓的泛型擦除
1.3.4 泛型类继承
-
如果继承泛型类的子类也是泛型类, 则子类必须含有父类的泛型标识
class ChildGeneric<T> extends Generic<T>如果子类想扩展泛型的使用数量也可以的
class ChildGeneric<T,G> extends Generic<T> -
若子类不是泛型类, 则要明确继承父类的数据类型
class Child extends Generic<String>
泛型类例子:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
class Parent<T>{
private T attr;
public void printAttr(){
log.info("Parent: attr is {}", this.getAttr());
}
}
/*
Do not use @Data @AllArgsConstructor, will have conflicts
*/
@Slf4j
class Child<G> extends Parent<G> {
public Child(G g){
super(g);
}
public void printAttr(){
log.info("Child: attr is {}", this.getAttr());
}
}
class Client{
public static void main(String args[]){
Parent<String> parent = new Parent<>("hello");
Child<Integer> child = new Child<Integer>(200);
parent.printAttr();
child.printAttr();
child.setAttr(100);
child.printAttr();
}
}
输出:
20:42:49.494 [main] INFO com.home.javacommon.study.generics.genclass.extend.Parent - Parent: attr is hello
20:42:49.501 [main] INFO com.home.javacommon.study.generics.genclass.extend.Child - Child: attr is 200
20:42:49.501 [main] INFO com.home.javacommon.study.generics.genclass.extend.Child - Child: attr is 100
可以看到, 父类的泛型标识是T
而子类定义时用的泛型标识是G,但是只需要在extends Parant
顺便说句,在子类尽量不要使用@Data, @AllArgsConstructor 等注解, 容易发生冲突。
非泛型子类的例子:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
class Parent<T>{
private T attr;
public void printAttr(){
log.info("Parent: attr is {}", this.getAttr());
}
}
@Slf4j
class Son extends Parent<Integer>{
public Son(Integer i){
super(i);
}
public void printAttr(){
log.info("Son: attr is {}", this.getAttr());
}
}
class Client2{
public static void main(String args[]){
Son son = new Son(200);
son.printAttr();
}
}
很容易理解
2. 泛型接口
其实当我们熟悉了泛型类的使用后, 泛型接口就很简单了
2.1 泛型接口使用要点
- 若实现类不是泛型类, 接口需要明确数据类型, 如果不明确数据类型, java会默认数据类型是Object
- 若实现类也是泛型类, 实现类必须含有接口的泛型标识
要点跟上面泛型类的继承基本一样
2.2 实现类不是泛型类的例子:
public class InterfaceClient {
public static void main(String[] args){
Fruit apple = new Fruit("Apple");
apple.printName();
}
}
interface Generic<T>{
T getKey();
}
@Slf4j
@AllArgsConstructor
class Fruit implements Generic<String>{
private String key;
@Override
public String getKey() {
return this.key;
}
public void printName(){
log.info("Fruit name is: {}", this.getKey());
}
}
输出:
21:41:17.264 [main] INFO com.home.javacommon.study.generics.genericinterface.Fruit - Fruit name is: Apple
2.2 实现类是泛型类的例子:
@Slf4j
public class InterfaceClient2 {
public static void main(String[] args){
Pair<Integer, String> pair = new Pair<>(100, "Apple");
log.info("pair key is {}",pair.getKey());
log.info(pair.toString());
}
}
interface GenericKey<K>{
public K getKey();
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Pair<K,V> implements GenericKey<K>{
private K key;
private V value;
@Override
public K getKey() {
return this.key;
}
}
这个例子实现类扩展了泛型标识的数量, 也是可以的
输出:
21:50:25.055 [main] INFO com.home.javacommon.study.generics.genericinterface.InterfaceClient2 - pair key is 100
21:50:25.061 [main] INFO com.home.javacommon.study.generics.genericinterface.InterfaceClient2 - Pair(key=100, value=Apple)
3. 泛型方法
由上面2章节可以知道,
泛型类 - 是在实例化对象时指明泛型的具体类型
泛型接口 - 是在实现接口的时候指名泛型的具体类型
而泛型方法 - 是在调用方法的时候指明泛型的具体类型,厉害了!
3.1 泛型类里返回值类型是T的方法并不是泛型方法
例如上面例子中的
public K getKey() {
return this.key;
}
只是一般的方法, 只是返回值类型有待明确
3.2 语法
修饰符 <T, E..> 返回值类型 方法名(形参列表){
方法体..
}
- public/private 与返回值类型 中间的 非常重要, 可以理解为声明此方法是泛型方法
- 只要声明了的方法才是泛型方法, 泛型类中使用了泛型成员的方法并不是泛型方法
- 表明此方法将使用反省类型T, 此时才可以在方法体中使用泛型类型T
- 与泛型类的定义一样, 此处T可以随便写为任意标识, 常见的如T,E, K, V 等。
- 泛型方法的泛型独立于类的泛型,即使使用相同的泛型标识T, 也就是讲我们可以在普通类上面写泛型方法。
3.3 一个抽奖器例子
需求很简单,就是支持放入一些production,然后有个1个方法, 能随机得到1个产品。
当然,产品的类型是待定的。
@Slf4j
public class GenericMethodClient {
public static void main(String[] args){
RandomGetter<String> strGetter = new RandomGetter();
Set<String> strProdSet = Stream.of("iphone", "Thinkpad T14", "Chromebook", "Samson S22").collect(Collectors.toSet());
strProdSet.forEach(x->strGetter.addProduct(x));
String prod = strGetter.getRandom();
if (!Objects.isNull(prod)){
log.info("You got the production: {} and the product class is {}", prod, prod.getClass().getName() );
}else {
log.info("You got nothing!");
}
Set<Integer> moneySet = Stream.of(100,200,300,500,1000,1500,2000).collect(Collectors.toSet());
Integer money = strGetter.getRandom(moneySet);
log.info("You got the money: {} and the money class is {}", money, money.getClass().getName() );
}
}
class RandomGetter<T>{
private Set<T> productSet = new HashSet<>();
private Random random = new Random();
public void addProduct(T t){
productSet.add(t);
}
/**
* It's not a generic method, the T in return or parameters must be the one when instantiate the class object
* @return T
*/
public T getRandom() {
if (productSet.size() == 0){
return null;
}
T prod = productSet.stream().collect(Collectors.toList()).get(random.nextInt(productSet.size()));
return prod;
}
/**
* It's the generic method, the <T> defined in method has no any relationship of the one defined behind class name!
* @param prodSet
* @param <T>
* @return
*/
public <T> T getRandom(@NotNull Set<T> prodSet){
if (Objects.isNull(prodSet) || 0 == prodSet.size()){
return null;
}
T prod = prodSet.stream().collect(Collectors.toList()).get(random.nextInt(prodSet.size()));
return prod;
}
}
输出:
00:58:38.149 [main] INFO com.home.javacommon.study.generics.genericmethod.GenericMethodClient - You got the production: Thinkpad T14 and the product class is java.lang.String
00:58:38.153 [main] INFO com.home.javacommon.study.generics.genericmethod.GenericMethodClient - You got the money: 500 and the money class is java.lang.Integer
上面定义了两个getRandom方法(重载), 其中无参数的只不过是泛型类的普通成员方法,而有参数的则是泛型方法。方法定义的<T> 于类名后面的<T>无关.
所以在main方法中, 我们实例化1个基于String的strGetter 对象, 在调用泛型方法时可以指定另1个Integer类型, 就是这么灵活!
3.4 泛型静态方法
上面说了使用泛型标识的普通成员方法是不允许写成static的, 而泛型方法可以。
例子:
public class StaticGenericMethodClient {
public static void main(String... args){
StaticGen.getClassName("hello", true, 123);
}
}
@Slf4j
class StaticGen{
public static <T,T2,T3> void getClassName(T t, T2 t2, T3 t3){
log.info("t1 class is {}", t.getClass());
log.info("t2 class is {}", t2.getClass());
log.info("t3 class is {}", t3.getClass());
}
}
输出:
01:13:18.693 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen - t1 class is class java.lang.String
01:13:18.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen - t2 class is class java.lang.Boolean
01:13:18.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen - t3 class is class java.lang.Integer
而且 泛型方法同样支持可变泛型参数…
上面T1, T2, T3写得不灵活, 我们可以写成
public class StaticGenericMethodClient {
public static void main(String... args){
StaticGen2.getClassName("hello", true, 123);
}
}
@Slf4j
class StaticGen2{
public static <T> void getClassName(T...t){
Arrays.stream(t).forEach(x->log.info(x.getClass().getName()));
}
}
注意T… t 这里传入的t类型是不需要相同的, 好神奇
01:16:23.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen2 - java.lang.String
01:16:23.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen2 - java.lang.Boolean
01:16:23.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen2 - java.lang.Integer
关于泛型的下一篇文章将会是泛型类型通配符!
最后
以上就是优雅小白菜为你收集整理的Java 泛型进阶的全部内容,希望文章能够帮你解决Java 泛型进阶所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复