Java泛型

一、作用:记住java集合中元素的类型

在不使用泛型时,任何元素被放入集合中,会自动当成Object类型处理,当从程序中取出元素时,往往需要强制类型转换,这种转换不仅会代码臃肿,还容易引起CastClassException异常。

import java.util.List;
import java.util.ArrayList;
public class Test {
	public static void main(String[] args) {
		List str=new ArrayList();
		str.add("hello");
		str.add("world");
        //“不小心”添加了一个Integer类型的数据,编译时不出错
		str.add(2);
        //运行报错:因为Integer类型无法强制装化为String
		str.forEach(str->System.out.println((String)str).length());
	}
}

使用泛型改进上述程序:

import java.util.List;
import java.util.ArrayList;

public class Test {
	public static void main(String[] args) {
        //创建String类型的List集合,该集合只能储存String类型的元素
		List<String> str=new ArrayList<String>();
		str.add("hello");
		str.add("world");
        //编译时就会出错
		str.add(2);
		str.forEach(str->System.out.println((String)str).length());
	}
}

二、泛型定义方式

方式1;在集合接口、类后增加尖括号<>,里面写上数据类型即可,如List str=new ArrayList();

方式2:java9增加的“菱形”语法,省略构造器中的数据类型,只写一个尖括号<>即可,如:List str=new ArrayList<>();

import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;
public class Test {
	public static void main(String[] args) {
		//String类型的List
		List<String> str=new ArrayList<>();
		str.add("hello");
		str.add("world");
		str.forEach(ele->System.out.println(ele.length()));
		//该Map集合的key是String类型的,value是类型为String的List集合
		Map<String,List<String>> map=new HashMap<>();
		map.put("世界,你好",str);
		map.forEach((key,value)->System.out.println(key+"-->"+value));
	}
}
/*输出:
5
5
世界,你好-->[hello, world]
*/

方式3:使用通配符,即<?>形式,此时相当于其上限为Object,但是这种情况下不能向集合中添加任何元素

import java.util.Set;
import java.util.HashSet;
public class Test {
	public static void main(String[] args) {
		Set<?> set=new HashSet<>();
        //报错
		set.add("hi");
	}
}

三、泛型使用场合:

1、集合是最重要的使用场合,可以在Set、List、Deque、Collection、Map等接口及其实现类中使用

2、也可以为任何类、接口增加泛型声明。
//自定义类中使用泛型
class A<T>{
	//使用T类型定义实例变量
	private T str;
	public A() {}
    //使用T类型定义构造器
	public A(T str) {
		this.setStr(str);
	}
	public void setStr(T str2) {
		this.str=str;
	}
	public T getStr() {
		return this.str;
	}
}
public class Test{
	public static void main(String[] args) {
		//T类型为String,所以构造器里的参数只能是String
		A<String> a1=new A<>("苹果");
		System.out.println(a1.getStr());
		//传入T的参数是Double,所以构造器里的参数只能是Double
		A<Double> a2=new A<>(3.14);
		System.out.println(a2.getStr());
	}
}

!!!注意:当创建带泛型声明的自定义类,为该类创建构造器时,构造器名还是原来的类名,不需要增加泛型声明

3、从泛型类派生子类

当创建了带泛型的接口、类父后,就可以为这些接口创建实现类,或从父类派生子类,但是**!!!注意:此时这些接口、父类不可以在包含泛型形参**

//自定义类中使用泛型
class A<T>{
	//使用T类型定义实例变量
	private T str;
	public A() {}
	public A(T str) {
		this.setStr(str);
	}
	public void setStr(T str2) {
		this.str=str;
	}
	public T getStr() {
		return this.str;
	}
}
//定义B继承A类
class B extends A<T>{
	//写法错误,此时的A类不可以带泛型声明
}

(1)继承类或实现接口时传入实际的类型

此时A中所有用到T的地方都变成了String,那么要注意在B类中进行方法重写时,必须保证也是String类

//定义B继承A类
class B extends A<String>{
	//重写父类的getStr()方法
	public String getStr() {
		return "子类"+super.getStr();
	}
	//这种重写错误,因为方法的类型是Object,而不是String
	public Object getStr() {
		return "子类"+super.getStr();
	}
}

(2)不传入实际的类型

此时可能会产生泛型警告,并且会将A类中的T当场Object类处理

//定义B继承A类
class B extends A{
	//这种形式被称为“原始类型”,其中的T默认为Object
}

!!!注意1:泛型并没有生成一个新的类,无论T的类型是什么,它都是同一个类A,所以在静态方法、初始化块、静态变量的声明和初始化中不允许使用泛型形参

class A<T>{
	//下面两种定义方式都是错误的
	static T str;
	public static void bar(T str) {
	}
}

!!!注意2:如果B是A的子类,G是具有泛型声明的类或接口,那么G< B>并不是G< A>的子类,虽然B是A的子类

public class Test {
	public static void main(String[] args) {
        //虽然String是Object的子类,但是List<String>并不是List<Object>的子类,所以不能直接str赋值给obj
		List<String> str=new ArrayList<>();
		List<Object> obj=str;  //报错:cannot convert from List<String> to List<Object>
        //正确,数组可以向上自动转型
        String[] str=new String[3];
		Object[] obj=str;
	}
}

四、泛型通配符

1、使用<?>表示,但是此时不能向集合中添加元素,默认是Object类

2、设定类型通配符的上限:<? extends A>,最高的类型就是类A,可以是A及其子类,此时只能从集合中取出元素(取出元素的上限就是A),不能添加元素(因为添加的元素类型可能是A或A的子类)。并且该方式可以避免上面说到的注意2.
//定义一个抽象类
abstract class Shape{
	public abstract void draw(Color c);
}
//定义Shape的子类Circle
class Circle extends Shape{
	public void draw(Color c) {
		System.out.println("在"+c+"画布上画一个圆");
	}
}
//定义Shape的子类Circle
class Rectangle extends Shape{
	public void draw(Color c) {
		System.out.println("在"+c+"画布上画一个矩形");
	}
}
class Color{
	public void drawAll(List<Shape> shapes) {
		//同时在画布上绘制多个形状
		for(Shape s:shapes) {
			s.draw(this);
		}
	}
}
public class Test {
	public static void main(String[] args) {
		List<Circle> cl=new ArrayList();
		Color c=new Color();
		//代码编译错误,因此c1是List<Circle>类的,但Color类的drawAll方法需要传入的参数类型是List<Shape>。前面已经说过,虽然Circle是Shape的子类,但List<Circle>不是List<Shape>的子类,所以参数类型不符
		c.drawAll(cl);
	}
}

将上述Color类改为:

class Color{
    //使用了通配符,此时表示drawAll方法的参数类型可以是Shape类型及其子类,此时可以是List<Shape>/List<Circle>/List<Retangle>
	public void drawAll(List<? extends Shape> shapes) {
		for(Shape s:shapes) {
			s.draw(this);
		}
	}
}
3、设定通配符下限:<? super A>,最低的类型是A,可以是A及其父类。此时只能往集合中添加元素(因为都有A及其父类类型来接收),不能取出元素(因为取出的元素不知道到底是A还是A的父类对象)
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class upTest {
    //定义一个copy方法,将src集合中的元素赋值到dest集合中,由于指定了src的类型是T,那么dest要想盛放src的数据,那么其类型必须大于等于T,因此dest的类型采用了通配符下限
	public static<T> T copy(Collection<? super T> dest,Collection<T> src) {
		T last=null;
		for(T ele:src) {
			last=ele;
			dest.add(ele);
		}
		return last;
	}
	public static void main(String[] args) {
		List<Number> ln=new ArrayList<>();
		List<Integer> li=new ArrayList<>();
		li.add(5);  //li是Integer类型,因此传入过去的T为Integer
		Integer last=copy(ln,li);
		System.out.println(last);
	}
}
4、协变与逆变

假设A是B的子类或实现接口,G是具有泛型声明的类或接口

协变:使用通配符上限,<? extends B> 表示类型可以是A或B

逆变:使用通配符下限,<? super A> 表示类型可以是A、B、或B的父类、Object、

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值