Java-泛型

本文详细介绍了Java泛型的概念,包括类型安全、消除强制转换、泛型类的语法和实例、泛型接口、泛型方法、泛型数组、嵌套泛型、边界设定以及通配符的使用。同时阐述了不变性、协变和逆变的原理及其应用场景。
摘要由CSDN通过智能技术生成

泛型

一、概念

泛型就是变量类型的参数化。也就是说,所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口和泛型方法。(使得变量的数据类型是动态的)

二、好处

1、类型安全

指定具体类型后,Java编译器会对错误的类型在编译时被捕获,而不是在运行时当作ClassCastException展示出来。从而提高程序的安全性和可靠性。

List<String> list = new ArrayList<String>() ;

list对象只能存放String对象,否则会编译出错

2、消除强制类型转换

//需要强制类型转换
List list = new ArrayList() ;
String str = (String)list.get(0) ;

//不需要类型转换
List<String> list = new ArrayList<String>() ;
String str = list.get(0) ;

3、潜在的性能收益

三、分类

1、泛型类

1)语法
// 第一:定义泛型类 - 指定泛型参数
[访问修饰符] class 类名<泛型1[,泛型2,...]> {
	泛型1 泛型成员1 ;
	泛型2 泛型成员2;
	...
}


// 第二:实例化泛型类型对象 - 指定具体的数据类型
类名<数据类型> 对象名 = new 类名<数据类型>();

// 简写,类型推导
类名<数据类型> 对象名 = new 类名<>();

说明:

  • 泛型参数名称:任意合法标识符 + 大写,如T、E、K、V
  • 在实例化泛型类对象时,如果没有指定具体的数据类型,则默认为Object
... class 类名<泛型>
=
... class 类名<泛型 extend Object>
  • 指定泛型具体数据类型时,不能使用基本类型
// 错误
List<int> list = new ArrayList<int>();

// 正确 - 使用基本类型对应的包装类
List<Integer> list = new ArrayList<Integer>()

案例:

//定义一个泛型
public class People<A> {
	private String name;
	private String sex;
	private A pet;
}

//定义多个泛型
public class People<AB> {
	private String name;
	private String sex;
	private A pet;
  private B phone ;
}

2)要点

2.1)泛型相当于类中一种特殊的类型。这种类型的特点是:在实例化该类时可指定为某种具体的实际类型。(不能是基本类型)

2.2)泛型1、泛型2…可以是JAVA的任意合法标识符(大写),如:T、E、K、V

2.3)class People<A>声明了一个泛型类,这个A没有任何限制,实际上相当于Object类型,实际上相当于 class People<A extends Object>

class People<T> == class People<T extends Object>

2.4)与Object泛型类相比,使用泛型所定义的类在声明和构造实例的时候,可以使用“<实际类型>”来一并指定泛型类型持有者的真实类型。如:

People<Dog> p1= new People<Dog>() ;

People<Cat> p2 = new People<Cat>() ;

People<Animal> p3 = new People<Animal>() ;

2.5)当然,也可以在构造对象的时候不使用尖括号指定泛型类型的真实类型,但是你在使用该对象的时候,就需要强制转换了。如:

People p = new People("张三","男",new Cat("小花猫", 3, "雌")) ;

Cat c = (Cat)p.getPet() ;

实际上,当构造对象时不指定类型信息的时候,默认会使用Object类型,这也是要强制转换的原因。

案例:

/**
 * 泛型类
 */
public class GenericTest<T> {
	private T data ;

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}
}

/**
 * 测试
 */
public class MainTest {
	public static void main(String[] args) {
      // 实例化一个泛型类对象,一般要指定具体的数据类型,如果没有指定的数据类型,则默认的是Object类型
      // GenericTest gt1 = new GenericTest();
      // gt1.setData(100);
      
      //创建泛型类对象,并指定泛型为Integer
      GenericTest<Integer> gt = new GenericTest<Integer>() ;

      //封装数据,只能封装Integer类型数据
      gt.setData(100) ;

      //获取数据,不需要类型转换
      int r =  gt.getData() ;

      //输出
      System.out.println(r);

      //根据需要,还可以创建其它类型的泛型类对象,封装相关类型的数据
      //GenericTest<Float> gt = new GenericTest<Float>() ;
      //...
	}
}

2、泛型类继承

1)语法
[访问修饰符] interface 接口名<泛型1,泛型2,...> {
  泛型1 泛型成员1 ;
  泛型2 泛型成员2 ;
  ...
}

2)案例
/**
 * 第一:定义泛型接口
 */
public interface GenericTest<T> {
	// 1. 常量
	public static final String name = "AAA";

	// 2.抽象方法
	public void sayHello(T sth);
}


/**
 * 第二:实现接口
 * 当实现泛型接口时,必须指定具体的数据类型
 */
public class GenericTestIntegerImpl implements GenericTest<Integer> {

	@Override
	public void sayHello(Integer sth) {
		System.out.println("你好,"+sth);
	}
}

/**
 * 第二:实现接口
 * 当实现泛型接口时,必须指定具体的数据类型
 */
public class GenericTestStringImpl implements GenericTest<String> {

	@Override
	public void sayHello(String sth) {
		System.out.println("你好,"+sth);
	}
}

/**
 * 测试
 * 第三:使用泛型接口
 */
public class MainTest {
	public static void main(String[] args) {
		GenericTest<Integer> gt1 = new GenericTestIntegerImpl() ;
		gt1.sayHello(100) ;
    
    GenericTest<String> gt2 = new GenericTestStringImpl() ;
    gt2.sayHello("张三") ;
	}
}

3、泛型接口

1)语法
[访问修饰符] interface 接口名<泛型1,泛型2,...> {
  泛型1 泛型成员1 ;
  泛型2 泛型成员2 ;
  ...
}
2)案例

// 第一:定义泛型接口
public interface GenericTest<T> {
    // 注意:在泛型接口中,泛型不能定义常量
    String msg = "好好学习,天天向上";

    // 接口中的泛型可以定义方法参数、方法返回值
    T sayHello(T msg);
}


/**
 * 第二:泛型接口的实现一
 * 注意:实现泛型接口时,一般要指定具体的数据类型,否则默认为Object
 */
public class GenericTestImpl1 implements GenericTest {

    @Override
    public Object sayHello(Object msg) {
        return null;
    }
}


/**
 * 第二:泛型接口的实现二
 * 注意:实现泛型接口时,一般要指定具体的数据类型,否则默认为Object
 */
public class GenericTestImpl2 implements GenericTest<String> {

    @Override
    public String sayHello(String msg) {
        System.out.println("你好," + msg);
        return "好好学习";
    }
}


/**
 * 测试
 * 第三:使用泛型接口
 */
public class MainTest {
    public static void main(String[] args) {
        // 实例化接口对象
        GenericTest<String> gt1 = new GenericTestImpl2();

        // 错误 - 接口泛型 与 实现类实现接口指定的泛型不一致
        // GenericTest<Integer> gt2 = new GenericTestImpl2();
    }
}

4、泛型方法

1)语法
[访问修饰符] <泛型列表> 返回值 方法名(参数列表) {

}
2)要点:
  1. 泛型列表中可以指定多个泛型,使用逗号分隔,如:<T1,T2>
  2. 泛型可用于声明方法的返回值、方法的参数、方法内的局部变量
  3. 泛型方法中定义的泛型只作用于当前方法
public interface ProxyHandler {
		<T> T createProxy(Class<T> targetClass);
}

// 泛型方法中定义的泛型只作用于当前方法
public class ProxyHandler {
  public <T> createProxy(Class<T> targetClass) {
    T t;
  }
  
  // 错误
  public <P> sayHello() {
    T t;
  }
}
3)案例
/**
 * 第一:定义泛型方法
 */
public class GenericTest {

  // 定义一个泛型
  // 注:泛型方法中的泛型只作用于本方法
	public <T> void sayHello(T sth) {
		// 泛型定义局部变量
		T a = sth;
		System.out.println(a.getClass().getSimpleName() + ":" + sth);
	}

  // 定义多个泛型
	public <T1, T2, T3> void sayGoodBye(T1 s1, T2 s2, T3 s3) {
		System.out.println(s1.getClass().getName()+":"+s1);
		System.out.println(s2.getClass().getName()+":"+s2);
		System.out.println(s3.getClass().getName()+":"+s3);
	}
}


/**
 * 第二:调用泛型方法
 * 直接传递具体数据即可
 */
public class MainTest {
	public static void main(String[] args) {
		GenericTest gt = new GenericTest() ;
		gt.sayHello(100) ;
		
		gt.sayGoodBye("AA", 100, true) ;
	}
}

5、泛型数组

注意:不能实例化泛型数组

解决方法:

  • setter方法
  • Array.newInstance(类型的class对象,长度)
/**
*	第一:定义泛型类,使用泛型定义数组
*/

public class GenericTest<T> {
	T[] arr;

	public T[] getArr() {
		return arr;
	}

	public void setArr(T[] arr) {
		this.arr = arr;
	}
  	
  	/**
     * 实例化泛型数组
     */
    public void createArr(Class<?> clazz, int len) {
        Object obj = Array.newInstance(clazz, len);
        this.arr = (T[])obj ;
    }

}


/**
* 第二:定义泛型类对象,调用方法,传递数组
* 语法一
*/
public static void main(String[] args) {
  		// 实例化泛型类对象
        GenericTest<String> gt = new GenericTest<>() ;

        // 定义数组
        String[] arr = {"AA","BB","CC"} ;

        // 填充数据
        gt.setArr(arr);

        // 获取泛型数组,并打印输出 - Arrays.toString():转化为字符串
        System.out.println(Arrays.toString(gt.getArr()));
	}


/**
* 第二:定义泛型类对象,调用方法,传递数组
* 语法二
*/
public static void main(String[] args) {
        // 实例化泛型类对象
        GenericTest<String> gt = new GenericTest<>() ;

        // 错误 - 泛型中的数据只是声明而已,并没有实例化
        // String[] arr = gt.getArr();
        // arr[0] = "你好" ;

        gt.createArr(String.class,3) ;
        String[] arr = gt.getArr();
        arr[0] = "你好" ;
        arr[1] = "我好" ;
        arr[2] = "大家好,才是真的好" ;

        System.out.println(Arrays.toString(arr));
    }

6、嵌套泛型:在泛型类中使用另一个泛型类

在某泛型类中引用一个泛型类(包含)。 1)泛型类:宠物

/**
 * 定义泛型类 - 宠物
 *
 * @author zing
 */
public class Pet<T> {
	private T data;

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}
}

2)泛型类:主人

/**
 * 定义一个泛型类 - 主人
 *
 * @author zing
 */
public class People<T> {
	/**
     * 定义主人所养的宠物
     *
     * 嵌套泛型:在泛型类中使用另一个泛型类 -- 泛型类People嵌套使用泛型类Pet
     *
     * 注意:在这里,泛型类Pet指定的泛型为String
     */
    private Pet<String> pet ;

    public Pet<String> getPet() {
        return pet;
    }

    public void setPet(Pet<String> pet) {
        this.pet = pet;
    }
}

3)测试

/**
 * 嵌套泛型:在某泛型类中引用一个泛型类(包含)
 *
 * @author zing
 *
 */
public class MainTest {

	public static void main(String[] args) {
		People<Double> zs = new People<>();

        Pet<String> xh = new Pet<>() ;
        Pet<Double> xb = new Pet<>() ;

        zs.setPet(xh);

        // 错误 - 泛型类型不一致 - 泛型的不变性
        // xb 指定的是 Double类型,但 zs 对象封装的 Pet 指定的类型为 String
        // zs.setPet(xb);
	}
}

四、边界:把泛型默认为 Object 类型,收窄到指定类型的范围。

1、为什么需要边界

// 当定义一个泛型时,如果不知道具体的类型,则默认为Object

// T类型默认为Object,不存在compareTo方法,编译错误
// 如果T类型是实现了Comparable接口的类型,则编译通过 - 设置边界解决
public static <T> int compare(T x, T y){
  return x.compareTo(y);
}

// 解决方法 - 设置边界
public static <T extends Comparable> int compare(T x, T y){
  return x.compareTo(y);
}

2、语法

给类型参数添加限定就称之为给类型参数设定边界,设定边界的语法为:

// 语法一:指定一个边界
<T extends Xxx>
  
// 语法二:指定多个边界
<T extends Xxx & Yyy & Zzz & ...>

说明:

  • Xxx是边界类型,它可以是也可以是接口,这个边界类型称之为上边界
  • 类型实参(T)必须是这个边界类型或者是其子类型(包括子接口)或实现类,其它类型则报错
  • extends关键字在这里不是继承的含义,应该理解为:
    • T是Xxx接口或是Xxx接口的子接口
    • T是实现Xxx接口的实现类
    • T是Xxx类
    • T是Xxx类的子类
  • 如果不限制边界,则默认是Object
    • <T extends Object>
    • <T>

通过设置上边界,解决上面的compare方法:

// 限定T是实现Comparable接口的任意实现类,则就可以识别出compareTo方法,编译才通过
// 否则,默认为Object对象,并不存在compareTo方法,因此编译失败
public static <T extends Comparable> int compare(T x, T y){
  return x.compareTo(y);
}

五、通配符

1、为什么需要通配符

由于Java泛型的不变性,父类泛型不能接收子类泛型,因此需要通配符实现协变、逆变。

必须保证泛型类型的一致性

2、语法

通配符用?表示,表示某一个未知的类型。它主要的目标是给泛型实例确立父子关系。

1)通配符上边界
// 协变:满足条件诸如 List<Cat> 是 List<? extends Animal> 的子类型时,称为协变。
<? extends Xxx>

说明:?代表未知类型,extends关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

2)通配符下边界
// 逆变:满足条件 List<Animal> 是 List<? super Cat> 的子类型时,称为逆变。
<? super Xxx>

说明:?代表未知类型,super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类。

六、型变

概念:表示泛型类型的父子关系。

1、不变

赋值运行符左右两边的泛型必须一致

// 泛型是不变的 - 必须指定相同类型的泛型
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();


// 错误
List<Number> list2 = new ArrayList<Integer>();
ArrayList<Number> list2 = new ArrayList<Integer>();
2、协变

子类型的泛型可以赋值给父类型的泛型

  • 语法:<? extends T>
  • 为了确保类型安全,无法调用协变后的对象中含泛型参数的方法,协变集合不能添加元素
List<? extends Number> list = new ArrayList<Integer>() ;

// 错误
list.add(1.0) ;
list.set(1,1.0) ;	
  • 应用:泛型的集合作为生产者时,用extends关键字
3、逆变

父类型的泛型可以赋值给子类型的泛型

  • 语法:<? super T>
  • 逆变集合可以添加T类型及其子类型的数据,但不能取集合中的元素(不能查询元素
解决方法:
  • 强制类型转换
  • 使用Object接收
// 本来,Pig 是 Animal的子类型 - 通过逆变操作后,Animal 变成了 ? super Pig 的子类型
List<? super Pig> list = new ArrayList<Animal>();

// 逆变集合可以添加Pig类及其子类,因为正式接收的是包含Pig或其父类的集合,
// 如上面的 ArrayList<Animal> ,当然也可以是 ArrayList<Pig>
// 当添加CartoonPig、Pig类型对象时,Pig类型或Animal类型都可以接收
list.add(new CartoonPig("乔治"));
list.add(new Pig());

// 错误:逆变可以添加元素,但不能获取元素 - 获取的元素类型为 Animal 类型,向下转型为Pig类型失败
// Pig object = list.get(0);

// 解决一:强制类型转换
Pig obj1 = (Pig)list.get(0);

// 解决二:使用Object接收
Object obj2 = list.get(0) ;

System.out.println(obj1);
System.out.println(obj2)
  • 应用:当集合作为消费者时,用super关键字
  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java泛型Java 5引入的新特性,可以提高代码的可读性和安全性,降低代码的耦合度。泛型是将类型参数化,实现代码的通用性。 一、泛型的基本语法 在声明类、接口、方法时可以使用泛型泛型的声明方式为在类名、接口名、方法名后面加上尖括号<>,括号中可以声明一个或多个类型参数,多个类型参数之间用逗号隔开。例如: ```java public class GenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T> { T getData(); void setData(T data); } public <T> void genericMethod(T data) { System.out.println(data); } ``` 其中,`GenericClass`是一个泛型类,`GenericInterface`是一个泛型接口,`genericMethod`是一个泛型方法。在这些声明中,`<T>`就是类型参数,可以用任何字母代替。 二、泛型的使用 1. 泛型类的使用 在使用泛型类时,需要在类名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericClass<String> gc = new GenericClass<>(); gc.setData("Hello World"); String data = gc.getData(); ``` 在这个例子中,`GenericClass`被声明为一个泛型类,`<String>`指定了具体的类型参数,即`data`字段的类型为`String`,`gc`对象被创建时没有指定类型参数,因为编译器可以根据上下文自动推断出类型参数为`String`。 2. 泛型接口的使用 在使用泛型接口时,也需要在接口名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericInterface<String> gi = new GenericInterface<String>() { private String data; @Override public String getData() { return data; } @Override public void setData(String data) { this.data = data; } }; gi.setData("Hello World"); String data = gi.getData(); ``` 在这个例子中,`GenericInterface`被声明为一个泛型接口,`<String>`指定了具体的类型参数,匿名内部类实现了该接口,并使用`String`作为类型参数。 3. 泛型方法的使用 在使用泛型方法时,需要在方法名前面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java genericMethod("Hello World"); ``` 在这个例子中,`genericMethod`被声明为一个泛型方法,`<T>`指定了类型参数,`T data`表示一个类型为`T`的参数,调用时可以传入任何类型的参数。 三、泛型的通配符 有时候,我们不知道泛型的具体类型,可以使用通配符`?`。通配符可以作为类型参数出现在方法的参数类型或返回类型中,但不能用于声明泛型类或泛型接口。例如: ```java public void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); } } ``` 在这个例子中,`printList`方法的参数类型为`List<?>`,表示可以接受任何类型的`List`,无论是`List<String>`还是`List<Integer>`都可以。在方法内部,使用`Object`类型来遍历`List`中的元素。 四、泛型的继承 泛型类和泛型接口可以继承或实现其他泛型类或泛型接口,可以使用子类或实现类的类型参数来替换父类或接口的类型参数。例如: ```java public class SubGenericClass<T> extends GenericClass<T> {} public class SubGenericInterface<T> implements GenericInterface<T> { private T data; @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } } ``` 在这个例子中,`SubGenericClass`继承了`GenericClass`,并使用了相同的类型参数`T`,`SubGenericInterface`实现了`GenericInterface`,也使用了相同的类型参数`T`。 五、泛型的限定 有时候,我们需要对泛型的类型参数进行限定,使其只能是某个类或接口的子类或实现类。可以使用`extends`关键字来限定类型参数的上限,或使用`super`关键字来限定类型参数的下限。例如: ```java public class GenericClass<T extends Number> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T extends Comparable<T>> { T getData(); void setData(T data); } ``` 在这个例子中,`GenericClass`的类型参数`T`被限定为`Number`的子类,`GenericInterface`的类型参数`T`被限定为实现了`Comparable`接口的类。 六、泛型的擦除 在Java中,泛型信息只存在于代码编译阶段,在编译后的字节码中会被擦除。在运行时,无法获取泛型的具体类型。例如: ```java public void genericMethod(List<String> list) { System.out.println(list.getClass()); } ``` 在这个例子中,`list`的类型为`List<String>`,但是在运行时,`getClass`返回的类型为`java.util.ArrayList`,因为泛型信息已经被擦除了。 七、泛型的类型推断 在Java 7中,引入了钻石操作符<>,可以使用它来省略类型参数的声明。例如: ```java List<String> list = new ArrayList<>(); ``` 在这个例子中,`ArrayList`的类型参数可以被编译器自动推断为`String`。 八、总结 Java泛型是一个强大的特性,可以提高代码的可读性和安全性,降低代码的耦合度。在使用泛型时,需要注意它的基本语法、使用方法、通配符、继承、限定、擦除和类型推断等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WyuanY.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值