文章目录
泛型
什么是泛型?
长什么样?
<字母> 或 <类型单词>
泛型,又被称为参数化的类型,即把类型当成参数传递。即在<>中传递类型。
泛型的好处
没有泛型,需要向下转型,而且有风险,可能在运行时发生ClassCastException。
有了泛型,可以在编译时就做类型检查,而不是等到运行时发生ClassCastException才发现有问题。
泛型的应用场景之一:泛型类或泛型接口(重点)
java.lang.Comparable:这个在接口名后面,把Comparable称为泛型接口。
java.util.Comparator:它也是泛型接口。
实现泛型接口
package com.gj.generic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Comparable<Student>{
private int id;
private String name;
private int score;
@Override
public int compareTo(Student o) {
return this.id-o.id;
//在compare方法中有局部变量id与成员变量id重名吗?没有,就可以省略this.
}
}
package com.gj.generic;
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
public class TestGeneric {
@Test
public void test1(){
//定义一个定制比较器实现Comparator接口,用于比较两个学生对象的成绩
Comparator<Student> c = new Comparator<Student>(){
@Override
public int compare(Student o1, Student o2) {
return o1.getScore()-o2.getScore();
}
};//匿名内部类实现泛型接口Comparator
Student[] arr = new Student[3];
arr[0] = new Student(2,"熊二",89);
arr[1] = new Student(1,"熊大",100);
arr[2] = new Student(3,"光头强",99);
System.out.println("按照id排序:");
Arrays.sort(arr);
for (Student student : arr) {
System.out.println(student);
}
System.out.println("按照成绩排序:");
Arrays.sort(arr, c);
for (Student student : arr) {
System.out.println(student);
}
}
}
泛型类创建对象
package com.gj.generic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/*
坐标类,包含x,y坐标
x,y的类型可能是String,可能是double,可能是int类型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Coordinate<T> {
private T x;
private T y;
/* public Coordinate() {
}
public Coordinate(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
@Override
public String toString() {
return "Coordinate{" +
"x=" + x +
", y=" + y +
'}';
}*/
}
package com.gj.generic;
import org.junit.Test;
public class TestCoordinate {
@Test
public void test1(){
/*
x代表维度:北纬23.5
x代表经度:东经46.6
*/
Coordinate<String> c = new Coordinate<>("北纬23.5","东经46.6");
//从JDK7开始,左边<>里面写了类型,右边<>里面可以省略,但是<>本身不能省略,否则有警告
System.out.println(c);
}
@Test
public void test2(){
/*
数轴的坐标,x=5.2, y=3.6
*/
// Coordinate<double> c = new Coordinate<>();
//泛型只支持引用数据类型,不支持基本数据类型
//对于基本数据类型,要用包装类
Coordinate<Double> c = new Coordinate<>(5.2,3.6);
System.out.println(c);
}
}
继承泛型类
在子类继承时,直接明确泛型父类具体是什么类型
package com.gj.generic;
import lombok.Data;
@Data
public class Father<T> {
private T t;
public Father() {
}
public Father(T t) {
this.t = t;
}
}
package com.gj.generic;
/*
情况1:继承泛型父类时,就明确<T>是什么类型,子类恢复普通类的身份
*/
public class Son extends Father<String>{
public Son(String s) {
super(s);
}
public Son() {
}
}
子类新定义泛型字母,来代替父类的泛型字母
这个泛型字母可以与父类的一样,也可以换一个字母。
package com.gj.generic;
/*public class Sub<T> extends Father<T>{
}*/
public class Sub<U> extends Father<U>{
public Sub() {
}
public Sub(U u) {
super(u);
}
}
泛型的应用场景之二:泛型方法(重点)
在方法的修饰符与返回值类型之间定义了<泛型字母>,这种方法被称为泛型方法。
泛型方法的声明格式
【修饰符】 class 类名{
【修饰符】 <泛型字母> 返回值类型 方法名(【形参列表】)【throws 异常类型列表】{
方法体语句;
}
}
泛型方法的<泛型字母>在调用它时,由实参的类型“自动”确定,不需要手动指定。因为泛型方法的<泛型字母>会被用于形参类型的定义。
例如:在java.util.Arrays工具类中有很多泛型方法:
public static <T> T[] copyOf(T[] original, int newLength):复制一个数组,新数组的长度是newLength。
package com.gj.generic;
public class MyArrays {
public static Object[] copyOf(Object[] oldArr, int newLength){
//(1)创建新数组对象,长度为newLength
Object[] arr = new Object[newLength];
//(2)复制元素
// for(int i=0; i<oldArr.length && i<newLength; i++){
for(int i=0; i<Math.min(oldArr.length,newLength); i++){
arr[i] = oldArr[i];
}
//(3)返回新数组
return arr;
}
}
package com.gj.generic;
import org.junit.Test;
import java.time.LocalDate;
import java.util.Arrays;
public class TestMyArrays {
@Test
public void test1(){
String[] strings = {"hello","java","atguigu"};
Object[] objects = MyArrays.copyOf(strings, 5);
//使用我们自己定义的MyArrays工具类,不够完美,返回的新数组类型只能是Object[]
/*String[] newArr = (String[]) objects;
for (String s : newArr) {
System.out.println(s);
}*/
for (Object object : objects) {
System.out.println(object);
}
}
@Test
public void test2(){
String[] strings = {"hello","java","atguigu"};
//使用系统定义好的Arrays工具类
String[] newArr = Arrays.copyOf(strings, 5);
for (String s : newArr) {
System.out.println(s);
}
}
@Test
public void test3(){
LocalDate[] arr = {LocalDate.now(),LocalDate.of(2024,1,1)};
LocalDate[] newArr = Arrays.copyOf(arr, 5);
for (LocalDate d : newArr) {
System.out.println(d);
}
}
}
对比:把泛型定义在类名后面或方法的前面
作用域范围不同
package com.gj.generic;
public class Demo<T>{
public void m1(T t){
System.out.println("Demo.m1");
}
public void m2(T t){
System.out.println("Demo.m2");
}
}
package com.gj.generic;
import org.junit.Test;
public class TestDemo {
@Test
public void test1(){
Demo<String> d = new Demo<>();
d.m1("hello");
d.m2("java");
}
}
package com.gj.generic;
public class Example {
public static <T> void m1(T t){
System.out.println("Example.m1");
}
public static <T> void m2(T t){
System.out.println("Example.m2");
}
}
package com.gj.generic;
import org.junit.Test;
public class TestExample {
@Test
public void test1(){
Example e = new Example();
e.m1(1.0);
e.m2("hello");
}
}
是否适用于静态成员
泛型字母的声明和使用要求
1、无论是在类名后面还是在方法的返回值类型前面,如果想要定义一个<泛型字母>,建议使用单个的大写字母
,不要写单词。
- T:Type的首字母,type是类型的意思,泛指某种类型
- E:Element的首字母,element是元素的意思
- K,V:Key的首字母,Value的首字母,(key,value)代表键值对
2、为<泛型字母>指定具体类型时,必须指定引用数据类型,不能是基本数据类型。
3、可以定义多个<泛型字母>,字母之间使用逗号分割
4、如果对类型有范围要求,还可以指定上限,而且上限可以有多个。但是上限中类名只能有1个,而且在最左边
package com.gj.generic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
//这里extends表示的是<=的关系,不是新定义一个Z类
public class Base <T,U,Z extends Number & Comparable & Cloneable >{
private T t;
private U u;
private Z z;
}
package com.gj.generic;
import org.junit.Test;
public class TestBase {
@Test
public void test1(){
// Base<int,double,int> b = new Base<int, double, int>();//错误,泛型不能指定基本数据类型
// Base<String,Integer,Double> b1 = new Base<>("hello",1,1.0);
// Base<String,Integer,String> b2 = new Base<>("hello",1,"world");
}
}
泛型擦除
泛型擦除后,会失去类型信息,不推荐这么干。Java是严格的、强制类型的语言。
泛型通配符
必须配合泛型类或泛型接口使用,即<>的前面必须有类名或接口名,而且?必须在<>里面。
- <?> :代表任意引用数据类型
- <? extends A>:代表<=A类型
- <? super A>:代表>=A类型
package com.gj.generic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Couple<H,W> {//夫妻
private H husband;
private W wife;
}
package com.gj.generic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Human {//人类
private String name;
}
package com.gj.generic;
public class Chinese extends Human{//中国人
public Chinese(String name) {
super(name);
}
public Chinese() {
}
}
package com.gj.generic;
public class American extends Human{//美国人
}
package com.gj.generic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Robot {//机器人
private String name;
}
package com.gj.generic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Demon {//妖
private String name;
}
package com.gj.generic;
import org.junit.Test;
public class TestCouple {
//单独?表示任意引用数据类型
public void marry1(Couple<?,?> c){
System.out.println(c);
}
//? extends A 类型,代表<=A的类型
public void marry2(Couple<? extends Human,? extends Human> c){
System.out.println(c);
}
//? super A 类型,代表 >= A的类型
public void marry3(Couple<? super Human,? super Human> c){
System.out.println(c);
}
@Test
public void test1(){
Couple<Robot,Human> c = new Couple<>();
c.setHusband(new Robot("爱德华"));
c.setWife(new Human("朱丽叶"));
marry1(c);
// marry2(c);//第一个Robot不满足
}
@Test
public void test2(){
Couple<Human,Demon> c = new Couple<>();
c.setHusband(new Human("许仙"));
c.setWife(new Demon("白娘子"));
marry1(c);
// marry2(c);//第二个Demon不满足
}
@Test
public void test3(){
Couple<Human,Human> c = new Couple<>();
c.setHusband(new Human("邓超"));
c.setHusband(new Human("孙俪"));
marry1(c);
marry2(c);
}
@Test
public void test4(){
Couple<Object,Object> c = new Couple<>();
c.setHusband(new Human("邓超"));
c.setWife(new Human("孙俪"));
marry1(c);
// marry2(c);//Object不满足<=Human
marry3(c);//Object满足>=Human类型
}
@Test
public void test5(){
//Chinese继承了Human
Couple<Chinese,Chinese> c = new Couple<>();
c.setHusband(new Chinese("邓超"));
c.setHusband(new Chinese("孙俪"));
marry1(c);
marry2(c);//Chinese 满足 <= Human类型
// marry3(c);//Chinese 不满足 >= Human类型
}
}
使用通配符的地方,一般不会涉及到修改属性或元素的值。
package com.gj.generic;
import org.junit.Test;
public class TestCouple2 {
public void marry1(Couple<?,?> c){
//这里set设置什么类型的对象都会编译报错
//因为编译时不清楚实参传进来的Couple对象指定的<H,W>具体是什么类型,下面的代码无法满足所有类型
//无论设置哪种类型,都有风险
/* c.setHusband(new Human());
c.setHusband(new Robot());
c.setHusband(new Demon());
c.setHusband(new Chinese());
c.setHusband(new Object());*/
}
public void marry2(Couple<? extends Human,?> c){
//这里set设置什么类型的对象都会编译报错
//因为编译时不清楚实参传进来的Couple对象指定的<H,W>具体是什么类型,下面的代码无法满足所有类型
//无论设置哪种类型,都有风险
/* c.setHusband(new Human());
c.setHusband(new Robot());
c.setHusband(new Demon());
c.setHusband(new Chinese());
c.setHusband(new Object());*/
}
public void marry3(Couple<? super Human,?> c){
//这里set设置什么类型的对象都会编译报错
//因为编译时不清楚实参传进来的Couple对象指定的<H,W>具体是什么类型,下面的代码无法满足所有类型
//无论设置哪种类型,都有风险
c.setHusband(new Human());
// c.setHusband(new Robot());
// c.setHusband(new Demon());
c.setHusband(new Chinese());
c.setHusband(new American());
// c.setHusband(new Object());
}
@Test
public void test3(){
Couple<Human,Human> c = new Couple<>();
marry3(c);
//Chinese也是Human的子类
Couple<Chinese,Chinese> c2 = new Couple<>();
// marry3(c2);//marry3要求?代表>=Human类型,Chinese不满足>=Human的类型
//American也是Human的子类
Couple<American,American> c3 = new Couple<>();
// marry3(c3);//marry3要求?代表>=Human类型,American不满足>=Human的类型
}
@Test
public void test2(){
Couple<Human,Human> c = new Couple<>();
marry2(c);
//Chinese也是Human的子类
Couple<Chinese,Chinese> c2 = new Couple<>();
marry2(c2);
//American也是Human的子类
Couple<American,American> c3 = new Couple<>();
marry2(c3);
}
@Test
public void test1(){
Couple<Human,Human> c = new Couple<>();
marry1(c);
Couple<Robot,Robot> c2 = new Couple<>();
marry1(c2);
}
}