欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入了解JAVA泛型

程序员文章站 2022-06-03 15:06:32
什么是泛型泛型的概念:java泛型(generics)是jdk1.5中引入的一个新特性,泛型提供了编译时的类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是类型参数化,也...

什么是泛型

泛型的概念:java泛型(generics)是jdk1.5中引入的一个新特性,泛型提供了编译时的类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。

泛型的本质就是类型参数化,也就是所操作的数据类型被指定为一个参数。

使用泛型的好处:

1  在编译期间提供了类型检查

2  取数据时无须进行类型装换

泛型类、接口

泛型类

语法:

class 类名称 <泛型标识,泛型标识,泛型标识,...> {
 private 泛型标识 变量名;
 // ...
}

常用的泛型标识:t、e、k、v

使用语法:

类名 <具体的数据类型> 对象名 = new 类名<具体的数据类型>();

jdk 1.7 之后,后面的 <> 中的具体的数据类型可以省略不写。

定义一个简单的泛型类:

/**
 * 泛型类 t:类型形参,在类创建对象时,指定具体的数据类型
 * @author rainszj
 * 2020/3/19
 */
public class genericdemo01<t> {

 private t value;
 public genericdemo01() {
 }

 public genericdemo01(t value) {
  this.value = value;
 }

 @override
 public string tostring() {
  return "genericdemo01{" +
    "value=" + value +
    '}';
 }

 public t getvalue() {
  return value;
 }

 public void setvalue(t value) {
  this.value = value;
 }
}

测试一下:

public class test {

 public static void main(string[] args) {
  // 在创建对象时指定具体的数据类型
  genericdemo01<string> genericdemo01 = new genericdemo01<>("java");
  // 泛型类不支持基本数据类型,但可以使用基本类型对应的包装类
  genericdemo01<integer> genericdemo02 = new genericdemo01<>(1);
  // 在泛型类对象时,不指定具体的数据类型,将会使用object类型来接收

  // 同一个泛型类,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板
  // class com.rainszj.genericdemo01
  system.out.println(genericdemo01.getclass());
  // class com.rainszj.genericdemo01
  system.out.println(genericdemo02.getclass());
  // true
  system.out.println(genericdemo01.getclass() == genericdemo02.getclass());
  
 }
}

注意事项:

泛型类,如果没有指定具体的数据类型,按object类型来接收

泛型的类型参数只能是类类型,也就是引用数据类型,不能是基本数据类型

泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型

/**
 * 抽奖池
 *
 * @author rainszj
 * 2020/3/19
 */
public class productgetter<t> {
 // 奖品
 private t product;

 private arraylist<t> list = new arraylist<>();

 /**
  * 添加奖品
  *
  * @param product
  */
 public void addproduct(t product) {
  list.add(product);
 }

 /**
  * 抽取随机奖品
  *
  * @return
  */
 public t getproduct() {
  return list.get(new random().nextint(list.size()));
 }

 @override
 public string tostring() {
  return "productgetter{" +
    "product=" + product +
    '}';
 }
}

public static void main(string[] args) {
 productgetter<string> productgetter1 = new productgetter<>();
 // 奖品类型 礼物
 string[] products1 = {"华为手机", "苹果手机", "扫地机器人", "微波炉"};
 // 添加奖品
 for (int i = 0, length = products1.length; i < length; i++) {
 productgetter1.addproduct(products1[i]);
 }
 // 获取奖品
 string product1 = productgetter1.getproduct();
 system.out.println("恭喜您抽中了," + product1.tostring());

 productgetter<integer> productgetter2 = new productgetter<>();
 // 奖品类型 money
 integer[] products2 = {1000, 3000, 10000, 500};
 for (integer money : products2) {
 productgetter2.addproduct(money);
 }
 integer product2 = productgetter2.getproduct();
 system.out.println("恭喜您抽中了," + product2.tostring());
}

从泛型类派生子类

子类也是泛型类,子类的泛型标识 t 要和父类的泛型标识 t 保持一致,或者是包含关系,子类的泛型标识包含父类的泛型标识

class childgeneric<t> extends parentgeneric<t>
class childgeneric<t, e> extends parentgeneric<t>

子类不是泛型类,父类要明确泛型的数据类型

class childgeneric extends parentgeneric<string>

泛型接口

语法:

interface 接口名称 <泛型标识,泛型标识,...> {
 泛型标识 方法名();
}

实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型

public class apple implements generic<string> {}

实现类也是泛型类,实现类和接口的泛型类型要一致,或者是包含关系,实现类的泛型标识包含泛型接口的泛型标识

public class apple<k> implements generic<k> {}
public class apple<k, v> implements generic<k> {}

定义一个泛型接口

public interface generic<k> {
 k getkey();

}

实现其中方法:

/**
 * 泛型接口的实现类,是一个泛型类,
 * 那么要保证实现接口的泛型类的泛型标识包含泛型接口的泛型标识
 */
public class pair<k, v> implements generic<k> {
 private k key;
 private v value;
 
 public pair() {
 }

 public pair(k key, v value) {
  this.key = key;
  this.value = value;
 }

 @override
 public k getkey() {
  return key;
 }

 public v getvalue() {
  return value;
 }

 @override
 public string tostring() {
  return "pair{" +
    "key=" + key +
    ", value=" + value +
    '}';
 }
}

测试:

public class mytest {
 public static void main(string[] args) {
  pair<string, integer> pair = new pair<>("数学", 100);
  system.out.println(pair.tostring());
  // pair{key=数学, value=100}
 }
}

泛型方法

普通泛型方法

泛型类,是在实例化类时指明泛型的具体类型。

泛型方法,是在调用方法时,指明泛型的具体类型。

语法:

修饰符 <t,e,...> 返回值类型 方法名(形参列表) {
 // 方法体...
}

public 与返回值中间 <t,e,...> (泛型列表)非常重要,可以理解为声明此方法为泛型方法。

只有声明了 <t,e,...> 的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法

<t> 表明该方法将使用泛型类型 t,此时才可以在方法中使用泛型类型 t。

public class productsetter<t> {

 private t product;
 private random random= new random();
 private arraylist<t> list = new arraylist<>();

 public void addproduct(t product) {
  list.add(product);
 }

 /**
  * @param list
  * @param <e> 泛型方法的类型,是在调用泛型方法时确定的
  * @return
  */
 public <e> e getproduct(arraylist<e> list) {
  return list.get(random.nextint(list.size()));
 }

 public t getproduct() {
  return list.get(random.nextint(list.size()));
 }

 @override
 public string tostring() {
  return "productsetter{" +
    "product=" + product +
    '}';
 }
}

测试:

public static void main(string[] args) {
 productsetter<string> productsetter = new productsetter<>();
 string[] products1 = {"华为手机", "苹果手机", "扫地机器人", "微波炉"};
 for (int i = 0; i < products1.length; i++) {
  productsetter.addproduct(products1[i]);
 }
 system.out.println(productsetter.getproduct());

 arraylist<string> list1 = new arraylist<>();
 list1.add("华硕电脑");
 list1.add("苹果电脑");
 list1.add("华为电脑");
 string product1 = productsetter.getproduct(list1);
 system.out.println(product1 + "\t" + product1.getclass().getsimplename());
 // 华为电脑 string
 arraylist<integer> list2 = new arraylist<>();
 list2.add(1);
 list2.add(2);
 list2.add(3);

 integer product2 = productsetter.getproduct(list2);
 system.out.println(product2 + "\t" + product2.getclass().getsimplename());
 // 2 integer
}

静态泛型方法

public static <t, e, k> void pringtype(t k1, e k2, k k3) {
 system.out.println(k1 + "\t" + k1.getclass().getsimplename());
 system.out.println(k2 + "\t" + k2.getclass().getsimplename());
 system.out.println(k3 + "\t" + k3.getclass().getsimplename());
}
// 方法的调用
productsetter.pringtype(1, "hello", false);

// 输出结果
1 integer
hello string
false boolean

注意:

// 在泛型类中无法添加静态的 带有泛型成员方法,但可以添加静态的 泛型方法
public class test<t> {
 
 // 带有泛型的成员方法
	// 错误
	public static t getkey(t key) {
	 return key;
	}
 
 // 泛型方法
	// 正确
	public static <e> e getkey(e key) {
	 return key;
	}
}

泛型方法中的可变参数

public class mytest {
 public static void main(string[] args) {
  mytest.print(1, 2, 3);
 }

 /**
  * 泛型方法中的可变长参数
  * @param value
  * @param <e>
  */
 public static <e> void print(e ... value) {
  for (int i = 0; i < value.length; i++) {
   system.out.println(value[i]);
  }
 }
}

总结:

泛型方法能使方法独立于类而产生变化。

如果 static 方法要使用泛型能力,就必须使其成为泛型方法。

类型通配符

类型通配符一般是使用 ? 代替具体的类型实参。

类型通配符是类型实参,而不是类型形参。

我们先来定义一个简单的泛型类:

public class box<t> {
 private t width;
 public static void showbox(box<number> box) {
  number width = box.getwidth();
  system.out.println(width);
 }

 public t getwidth() {
  return width;
 }

 public void setwidth(t width) {
  this.width = width;
 }
}

main方法:

public static void main(string[] args) {
 box<number> box1 = new box<number>();
 box1.setwidth(100);
 showbox(box1);
}

当我们在 main 方法中增加这一段代码时,就会报错

box<integer> box2 = new box<>();
box2.setwidth(200);
showbox(box2);

虽然 integer 类继承自 number 类,但在类型通配符中不存在继承这一概念!

也许你会使用方法的重载,但是 在同一个泛型类中,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板,所以无法通过方法的重载,传递不同的泛型类型。

这时可以使用类型通配符 ?,来代表具体的类型实参!

public static void showbox(box<?> box) {
 object width = box.getwidth();
 system.out.println(width);
}

类型通配符的上限

在我们上面的showbox()代码中,发现 box.getwidth()得到的还是object类型,这和我们不使用类型通配符,得到的结果是一样的。这时我们可以使用类型通配符的上限。

语法:

类/接口 <? entends 实参类型> 

要求该泛型的类型,只能是实参类型,或者是实参类型的子类类型。

public static void showbox(box<? extends number> box) {
 number width = box.getwidth();
 system.out.println(width);
}

public static void main(string[] args) {
 box<integer> box2 = new box<>();
 box2.setwidth(200);
 showbox(box2);
}

使用类型通配符的下限,无法得知该类型具体是指定的类型,还是该类型的子类类型,因此无法在 list 集合中执行添加该类或者该类子类的操作!

public static void showanimal(list<? extends cat> list) {
  // 错误
  list.add(new cat());
 	 list.add(new minicat());
}

类型通配符的下限

语法

类/接口 <? super 实参类型> 

要求该泛型的类型,只能是实参类型,或者是实参类型的父类类型。

下面通过 treeset 集合中的一个构造方法来进一步理解 类型通配符的下限

public treeset(comparator<? super e> comparator) {
 this(new treemap<>(comparator));
}

首先是一个animal类,只有一个 name 属性

public class animal {
 private string name;

 public animal(string name) {
  this.name = name;
 }

 public string getname() {
  return name;
 }

 public void setname(string name) {
  this.name = name;
 }

 @override
 public string tostring() {
  return "animal{" +
    "name='" + name + '\'' +
    '}';
 }
}

然后它的一个子类,cat添加一个属性:age

public class cat extends animal {
 private int age;

 public cat(string name, int age) {
  super(name);
  this.age = age;
 }

 public int getage() {
  return age;
 }

 public void setage(int age) {
  this.age = age;
 }

 @override
 public string tostring() {
  return "cat{" +
    "age=" + age +
    '}';
 }
}

最后是 cat 的子类,minicat,再添加一个属性 level

public class minicat extends cat {
 private int level;

 public minicat(string name, int age, int level) {
  super(name, age);
  this.level = level;
 }

 public int getlevel() {
  return level;
 }

 public void setlevel(int level) {
  this.level = level;
 }

 @override
 public string tostring() {
  return "minicat{" +
    "level=" + level +
    '}';
 }
}

测试,首先我们要在mytest类通过静态内部类的方式,实现比较的接口,在构造treeset时,传递比较器

public class mytest {
 public static void main(string[] args) {
  	// 正常
  // treeset<cat> animals = new treeset<cat>(new comparator1());
  // 正常
  treeset<cat> animals = new treeset<cat>(new comparator2()); 
  	// 报错
				// treeset<cat> animals = new treeset<cat>(new comparator3()); 
  list<cat> list = arrays.aslist(new cat("a", 12), new cat("c", 9), new cat("b", 20));
  animals.addall(list);

  animals.foreach(system.out::println);

 }

 public static class comparator1 implements comparator<animal> {

  @override
  public int compare(animal o1, animal o2) {
   return o1.getname().compareto(o2.getname());
  }
 }

 public static class comparator2 implements comparator<cat> {

  @override
  public int compare(cat o1, cat o2) {
   return o1.getage() - o2.getage();
  }
 }

 public static class comparator3 implements comparator<minicat> {

  @override
  public int compare(minicat o1, minicat o2) {
   return o1.getlevel() - o2.getlevel();
  }
 }
}

结论:

通过以上的比较,我们可以看出,类型通配符的下限,只能传递实参类型的或者实参类型的父类类型。

我们每次比较使用的都是 cat 类型,但在 comparator1比较的是 animal 中的 name 属性,这是因为 我们在初始化 cat 对象的时候,一定会先初始化 animal 对象,也就是创建子类对象的时候,一定会先创建父类对象,所以才可以进行比较。

如果是使用 类型通配符的上限,在创建对象时,比较的是该类的子类对象中的属性,就会造成空指针异常!也就是comparator3无法使用的原因, 所以在 treeset 中才会使用 <? super e> ,类型通配符的下限。

类型擦除

泛型是java 1.5 引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前的代码兼容。那是因为,泛型信息只存在编译阶段,在进入 jvm 之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除

无限类型擦除

先定义一个泛型类:

public class erasure<t> {
 private t key;

 public t getkey() {
 return key;
 }

 public void setkey(t key) {
 this.key = key;
 } 
}

输出结构:

public static void main(string[] args) {
 erasure<integer> erasure = new erasure<>();
 class<? extends erasure> cls = erasure.getclass();
 field[] fields = cls.getdeclaredfields();
 for (field field : fields) {
 system.out.println(field.getname() + ":" + field.gettype().getsimplename()); // key:object
 }
}

可以发现在编译完成后的字节码文件中,t --> object 类型

有限类型擦除

还是刚才的泛型类,只不过加了泛型的上限

public class erasure<t extends number> {// ...}

测试不变,输出结果:

key:number

当我们指定了泛型的上限时,它会将我们的泛型擦除为上限类型

同样对泛型方法,也是一样的道理

// 泛型方法
public <e extends list> e test(e t) {
 return t;
}
method[] methods = cls.getdeclaredmethods();
for (method method : methods) {
 system.out.println(method.getname() + ":" + method.getreturntype().getsimplename());
}

// 输出结果
// getkey:number
// test:list
// setkey:void

桥接方法

泛型接口

public interface info<t> {

 t test(t value);
}

泛型接口的实现类

public class infoimpl implements info<integer> {
 @override
 public integer test(integer value) {
  return value;
 }
}

测试

public static void main(string[] args) {
 class cls = infoimpl.class;
 method[] methods = cls.getdeclaredmethods();
 for (method method : methods) {
  system.out.println(method.getname() + ":" + method.getreturntype().getsimplename());
 }
}

// 输出结果:
// test:integer
// test:object

原本 infoimpl 中只是实现了 info 接口中的一个方法,但通过反射却拿到了两个方法。其中返回值为 object 的方法就是桥接方法。

在编译完成后,类型擦除的结果是这样的:

public interface info {

 object test(object value);
}
public class infoimpl implements info {

 public integer test(integer value) {
  return value;
 }
	
 	// 桥接方法:保持接口和类的实现关系
 @override
 public object test(object value) {
  return (integer)value;
 }
}

泛型数组

开发中,一般常用的是泛型集合

泛型数组的创建:

可以声明带泛型的数组引用,但是不能直接创建带泛型数组对象。

可以通过 java.lang.reflect.array newinstance(class<t>, int)创建 t[ ] 数组。

// 可以创建带泛型的数组引用
arraylist<string>[] arraylists1 = new arraylist[3];
// 无法创建带泛型的数组对象
arraylist<string>[] arraylists2 = new arraylist<string>[3];

简单使用 java.lang.reflect.array newinstance(class<t>, int)创建 t[ ] 数组。 封装一个泛型数组

public class genericarray<t> {
 private t[] array;

 public genericarray(class cls, int length) {
  this.array = (t[]) array.newinstance(cls, length);
 }

 public void put(int index, t item) {
  this.array[index] = item;
 }

 public t get(int index) {
  return this.array[index];
 }

 public t[] getarray() {
  return this.array;
 }

 public static void main(string[] args) {
  genericarray<string> ga = new genericarray<>(string.class, 3);
  ga.put(0, "白虎");
  ga.put(1, "青龙");
  ga.put(2, "朱雀");

  system.out.println(arrays.tostring(ga.getarray()));

 }
}

泛型和反射

反射常用的泛型类:

class

constructor

通过反射创建对象,带泛型和不带泛型

class<cat> catclass1 = cat.class;
try {
 constructor<cat> c1 = catclass1.getconstructor();
 cat cat = c1.newinstance();
} catch (exception e) {
 e.printstacktrace();
}

class catclass2 = cat.class;
try {
 constructor c2 = catclass2.getconstructor();
 object cat2 = c2.newinstance();
} catch (exception e) {
 e.printstacktrace();
}

以上就是深入了解java泛型的详细内容,更多关于java泛型的资料请关注其它相关文章!