Java学习(泛型-1)

将一个对象放入集合中,集合不会记住这个对象的具体类型(向上转型Object tmp = new Date();)当再次从集合中取出对象时,该对象的编译期类型变成了Object类型,但是其运行时类型仍然为原始的类型

public static void main(String[] args) {
		List list = new ArrayList();
		list.add("123");
		list.add(123);// 集合中不能存放原生类型,这里会涉及自动装箱操作
		list.add(new Date());

		for (int i = 0; i < list.size(); i++) {
			Object tmp = list.get(i);
			Date dd = (Date) tmp;// 没有语法报错
			System.out.println(dd);//这里需要调用Date类型的getYear方法,所以需要进行窄化操作
		}
//		运行时java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Date
	}

报错原因:进行强制类型转换之前没有进行类型判定

		List list = new ArrayList();
		list.add("123");
		list.add(123);// 集合中不能存放原生类型,这里会涉及自动装箱操作
		list.add(new Date());

		for (int i = 0; i < list.size(); i++) {
			Object tmp = list.get(i);
			if (tmp != null && tmp instanceof Date) {
				Date dd = (Date) tmp;// 没有语法报错
				System.out.println(dd.getYear() + 1900);
			}
		}
  • 集合对象没有记录元素的具体类型,获取数据时需要进行类型转换,很容易出现运行时异常ClassCastException
  • 存储数据时没有进行类型判定

需要一种方法实现集合能够记住集合中元素的类型,编译期能够进行合法类型判断,获取时不需要进行类型转换:

泛型:可以将运行时的类型检查搬到编译期实现,直接获取指定类型数据避免强制类型转换操作

public static void main(String[] args) {
		List<Date> list = new ArrayList<>();
//	list.add("123");编译期会报错,因为集合中要求只能存放Date类型数据
		list.add(new Date());
		for (int i = 0; i < list.size(); i++) {
			Date dd = list.get(i);
			System.out.println(dd.getYear() + 1900 + ":" + dd.getMonth() + 1);
		}
	}

泛型

泛型是 JDK 1.5 引入的一种机制,就是将数据类型参数化,作为一种类型安全机制而产生的。

泛型机制就是将类型检查从运行时提前到编译期,使用范型编写的代码比杂乱的使用Object,并需要再执行窄化操作处理的机制具备更好的可读性和安全性。

泛型在本质上就是类型的参数化,

泛型的定义

public interface List<E> extends Collection<E> {//这里<>中的内容就是类型参数,一般建议使用全大写的方式进行定义,例 如 T、E、ID 之类的形式 
    boolean add(E e);//定义 add 方法,要求传入的数据类型为 E 
    E get(int index);//E 表示获取的数据类型就是定义接口时指定的类型
}

调用

List<String> list = new ArrayList<String>();//就是将String传递给E,用于替代定义中的E

String str = list.get(0);

如果在调用时不进行声明,则系统默认为Object

Lsit<Date> list = new ArrayList<>();//使用菱形语法,从JDK1.7开始支持泛型推导
list.add("123");//编译时就进行语法报错,因为编译期会进行类型检查,"123"不是Date类型
list.add(123);//语法报错
list.add(new Date());//编译通过,类型合法

典型应用

案例:获取两个整数中的较大值

Integer max(Integer a,Integer b){
    return a>b?a:b;
}

如果需要比较的不是Integer类型,而是double或者float类型,就需要再写max方法。引入泛型的目的就是在于定义max方法时可以不确定参数a和参数b的数据类型,而是等到调用的时候再确定参数的具体数据类型,这样只需要定义一个max方法即可,从而降低编程的工作量

对象比较Comparable接口

java预定义的接口

public interface Comparable<T> {
		public int compareTo(T o);
	}

这里使用了泛型<T>,表示用于规范类的可比较性

compareTo(T o)当前类型对象比较时需要返回一个 int 整数,当当前对象大于参数 o 时返回一个正数,小于时返回一个负整数,等于返回为 0,进行比较的参数 o 必须是 T 类型

例如要求整数类型是可比较的

public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc {}

对应的方法实现

public int compareTo(Integer anotherInteger){
    return compare(this.value,anotherInteger.value);
}

调用另外的方法

public static int compare(int x, int y){
    return (x,y) ? -1 : ((x == y) ? 0 : 1);
}

使用泛型的方式定义max方法

public class MyTest{
	public static<T extends Comparable<T>> T max(T t1,T t2){//静态方法中直接使用泛型的语法为<T>T,定义泛型时T extends Comparable,表示传入的类型必须实现了Comparable接口,否则编译错误
        return t1.compareTo(t2)>0?t1:t2;
    }
}

如果进行整型数据比较,需要先确定Integer必须实现了Comparable接口
Integer kk=MyTest.max(12,15);
如果是double类型数据,要先确定Double必须实现了Comparable接口

Double dd=MyTest.max(12.12,13.);

public class Test3 {
	public static void main(String[] args) {
		Integer kk = MyTest.max(12, 15);
		System.out.println(kk);

		double dd = MyTest.max(12.34, 55.);
		System.out.println(dd);

		Person p1 = new Person(1234.56);
		Person p2 = new Person(345.67);
		Person pp = MyTest.max(p1, p2);
		System.out.println(pp);
	}
}

class Person implements Comparable<Person> {
	private double salary;

	public Person(double salary) {
		super();
		this.salary = salary;
	}

	@Override
	public int compareTo(Person o) {
		double res = this.salary - o.salary;
		if (Math.abs(res) < 1e-6)
			return 0;
		else if (res > 0)
			return 1;
		else
			return -1;
	}

	@Override
	public String toString() {
		return "Person [salary=" + salary + "]";
	}
}

在泛型出现之前,只有一种变通的方法就是将参数类型定义为Object,这种方法不能保证类型安全。泛型弥补了Object 这种做法所缺乏的类型安全,也简化了过程

使用泛型的好处

解决类型安全性的隐患,泛型类或者接口在取出对象时不需要再进行向下类型转换,因为可以认为存储的时候就是这种类型。泛型的使用让安全问题再编译时就报错,而不是运行时报错,这样方便及时准确的发现问题。

  • 可读性,从字面上就可以判断集合中的内容类型
  • 类型检查,避免插入非法类型的数据
  • 获取数据时不再需要强制类型转换

泛型类

泛型类也叫做参数化类型,就是具有一个或者多个类型参数的类,一个泛型类可以有多个泛型声明,所有的泛型声明都应该在<>内部。

在当前类中 T 就是一个类型的说明,可以用在说明任何实例方法中的局部变量、方法的形参以及方法的返回值,类的成员变量;但是类型 T 不能直接使用在静态方法中

public class Generic<T>{ //<>中包含的全大写的名称就是泛型:形式参数 
	private T name; //在类中就可以使用使用泛型名称当作具体类型使用 
	public T getName(){ //方法可以直接使用泛型 
		return name; 
	}
	
	public void setName(T name){ 
		this.name=name; 
	} 
}

使用

Generic<String> en=new Generic<>(); //使用时在具体确定 T 对应的类型为 String,第二处<>中没有内容,叫做泛型推导 
en.setName("yanjun"); //en.setName(123)语法报错,因为 123 不是 String 类型 
System.out.println(en.getName()); 
//注意传递给泛型参数的类型必须时类类型,不能使用 int 或者 char 之类的简单类型 

如果定义了泛型类,但是引用时不声明泛型对应的类型值,则系统识别为 Object 类型

Generic en=new Generic(); 
en.setName("yanjun"); //有警告信息,但是语法正确,因为 String 也是 Object 类型 
en.setName(123); //有警告信息,但是编译可以通过。因为 123 经过自动装箱操作后,可以识别为 Object 类型 
带多个类型参数的泛型类

如果引用多个类型,可以使用逗号作为分隔符,例如<S,D>

类型参数名称可以使用任意字符串,但是一般建议使用有代表含义的单个字符,以便于和普通类型名称进行区分。

例如 T 代表 type,源数据 S,目标数据 D,子元素类型 E

注意问题

没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储到同一个集合中

List list=new ArrayList(); 
list.add(new Date()); 
list.add(123); 
list.add(123.456); 
list.add(new Date()); 

系统是按照 Object 识别类型。如果在 JDK1.5 之后还使用将各种类型放入到同一个集合中的写法,编译器会报一个 unChecked 警告信息

使用泛型集合时,可以将一个集合中的所有元素限定为一个特定类型,这样集合中就只能存储特定的类型的对象,这样比较安全;并且获取集合中存储的数据时,编译器也直到这个元素的类型,不需要进行窄化处理,这样使用也比较方便

List<Integer> list=new ArrayList<>(); 第 2 个类型可以不写,JDK1.7 引入的泛型推导

list.add(123.456) 编译期就可以发现这个错误,类型不合法

**记住:**Collection和 Collection是两个没有任何关系的参数化类型接口,不存在什么谁可以赋值给谁的可能

public class Test{
    public static void mian(String[] args){
        Collection<Object> coll = new ArrayList<String>();
    }
}
集合类中的泛型

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

Map<String,Object> map=......;

List<Map<String,Object>> list=....

泛型只能使用引用类型,而不能使用基本类型,例如 List是错误

最佳软件实践:保持良好的编程风格,尽量使用泛型

有界类型

在实际应用中可能需要对传递的类型参数的具体类型进行限制。

public class MyClass {} //定义的泛型类要求传入的具体类型必须是 Number 的子类,也就是要求传入的具体类型必须是数值型。这里可以传入 Integer、Double 等类型,但是不能传入 String

java 中提供了有界类型,在指定一个类型参数时,可以指定一个上界,声明所有的实际类型都必须时这个超类的直接或者间接子类。

需求:要求对数值类型的不确定个数的数据进行累加

如果使用泛型定义,但是不使用上界约束

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLprw2Rr-1646899567536)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217195827718.png)]

使用上界约束

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AplkAo5M-1646899567537)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217195925244.png)]

注意:这里的关键字 extends 不是表示继承,只是用于说明传入的类型必须是 Number 类型或者 Number 类型的后代类型

调用方法 1:

Generic g=new Generic<>();

//注意传入 T 对应的类型为 Long

double sum=g.sum(1L, 2L, 3L, 4L);

System.out.println(sum);

可以给 T 传递类型 Long,是因为 Long 是 Number 的子类型,如果更换其它类型,则必须判断传入的类型是否为 Number 的后代类型

Generic<String> g=new Generic<>(); //编译报错,因为传入的 String 类型,不是 Number 类型的后代类型

调用方法 2:

Generic<Number> g=new Generic<>();

Double sum=g.sum(1, 2, 3, 4L); //因为 Integer 和 Long 类型都是 Number 的子类型

接口和类都可以作为泛型的上界,当使用接口作为上界时,关键字还是 extends,而不是 implements。同时允许一个参数类型有多个限界,限界类型可以使用&符号分割。如果使用只使用类型作为上界,则只能定义一个类型上界;由于 java 的单根继承体系的要求,所以不能使用同时使用多个类作为类型上界,即使是父子类型也不能定义。允许使用一个类和多个接口或者多个接口

语法错误

public class Generic<T extends Number & Integer>{}

语法正确

public class Generic<T extends Number & Comparable<T> & Serializable & Cloneable>{}

接口 Comparable

如果一个类实现了 Comparable 接口,则表示当前类型的对象是可比较大小的,也就是说可以进行排序。实现了Comparable 接口的类支持排序,也就是可以使用工具类 Collections 对集合对象进行排序

Comparable 接口中定义一个比较方法 compareTo(T obj),返回的 int 类型数据用于表示大小

public class Pig implements Comparable<Pig> { 

private Long id; 

private double weight; 

public int compareTo(Pig pig){ 

double res=this.weight-pig.weight; 

if(Math.abs(res)<1e-6) return 0; 

else if(res>0) return 1; 

else return -1; 

} 

}


使用

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

Random r=new Random(); 

for(int i=0;i<10;i++){ 

Pig tmp=new Pig(1L+i, r.nextDouble()*480+20); 

list.add(tmp); 

}

Collections.sort(list); 

for(Pig p:list) System.out.println(p); 

接口 Comparator

Comparator 是比较器接口。如果类本身不支持排序比较,即实现 Comparable 接口,则可以建一个类型的比较器专门用于排序比较。

例如 Pig 类没有实现 Comparable 接口,则使用 Collections.sort(list)则会报错。报错的原因是 Collection 工具类中的方法定义

public static <T extends Comparable <? super T >> void sort (List<T> list){
	list.sort(null);
}

在方法上已经声明了要求传入的 T 类型必须实现了 Comparable 接口

根据开闭原则不修改源代码,可以额外引入比较器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N05rEgHw-1646899567537)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217200755551.png)]

参数有 2 个,第一个参数为 List,第二个参数是 Comparator,? super T 表示传入的类型必须是 T 的超类

Comparator 比较器接口的定义,接口上的注解@FunctionalInterface 声明是一个函数式接口,可以使用 lambda表达式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-958vI6S7-1646899567538)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217200823908.png)]

具体编码实现

	List<Pig> list = new ArrayList<>();
	Random r = new Random();
	for (int i = 0; i < 10; i++) {
		Pig tmp = new Pig(1L + i, r.nextDouble() * 480 + 20);
		list.add(tmp);
	}
	Comparator<Pig> c= (obj1,obj2)->{
		double res = obj1.getWeight() - obj2.getWeight();
		if (Math.abs(res)<(1e - 6)) {
			return 0;
		}else if (res > 0) {
			retrun 1;
		}else {
			return -1;
		}
	};
	Collectctions.sort(list,c);							list.forEach(Syetem.out::println);
Comparable 和 Comparator 接口比较

Comparable 接口是排序接口,如果一个类实现了 Comparable 接口就意味着该类型的对象是可比较的,而Comparator 接口是比较器,如果需要控制某个类的次序,可以临时建议一个该类的比较器进行排序

可以将 Comparable 当作内部比较器,而 Comparator 相当于外部比较器

通配符参数

通配符参数一般用于方法中接收参数类型的定义

void doSomething(Status<?> ob)表示传入参数是任意的 Status 类型,其中?表示一个不确定的类型,它的具体值会在调用时才能确定下来

public class Test{

​ public void pp(List<?> list){ 传入的具体实参可以时 List 的任意类型

​ list.add(123); //语法报错

​ }

}

代码

public class Test1 {
	public void pp(List<?> list) {
		//list.add("123");
		//这里使用list时不能涉及list中元素的类型,否则报错
		for (Object tmp : list)
			System.out.print1n(tmp);
		System.out.print1n(list.size());
		list = new LinkedList<Date>();
		//list.add(new Date());报错, 因为编译器识别的类型是不确定的类型
		System.out.print1n(list.size());

        //语法细节
		//List<Object> alist=new ArrayList<String>();//语法错误
		List<?> alist=new ArrayList<String>();
		//alist.add("bbbb");语法报错
		//alist. add(new object());语法报错
    }
    
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("999"); 
		Test1 t1 = new Test1();
		t1.pp(1ist);
	}

注意:List<?>中的?表示可以传入任意类型,到底?是什么类型,只有运行时才能确定,所以List<?> list=new ArrayList<String>()和 List<?> list=new ArrayList<Integer>()都可以,但是试图添加元素则会出现问题

基础语法

<? extends E>上限通配符,用以类型的上限

public class B1<T>{

public void pp(List<? extends T> list){} 表示 list 中的元素类型必须是 T 类型或者 T 类型的后代类型

}

public class Test2<T extends Comparable<T>> {
    public void sort(List<? extends T> list) {
		for(inti=0;i<list.size()-1;i++){
			System . out . print1n(i +:"+ (i+1) + ":" + list.get(i).compareTo(list.get(i + 1)));
        } //这里可以直接调用compareTo方法,是因为T extends Comparable<T>
    }
	public static void main(String[] args) {
		Test2<A> t2 = new Test2<>();
			//List<A> list=new ArrayList<B>(); 语法报错
		List<B> list=new ArrayList<>();
		list.add(new B());
		list. add(new B());
		t2. sort(list);
		//传入的lis中元素的类型是A的子类型
	}
}
class A implements Comparable<A> {
	public int compareTo(A o) {
		return θ;
	}
}
class B extends A{
}
<? super T>下限统配符,用于限制类型的下限 ```java public class B1{ public void pp(List<? super T> list){} 表示 list 中的元素类型必须是 T 类型或者 T 类型的祖先类型 } ``` 类型统配符写法 Box<? extends Number>其中?表示可以是 Number 类型或者 Number 的子类型,例如Integer、Double 之类的类型 类型通配符写法为 Box<? super Integer>其中?表示可以是 Integer 类型或者 Integer 类型的父类型,例如Number 类型 ```java public class Test3 { public static void main(String[] args) { List list1 = new ArrayList<>(); List<? super Apple> list2 = list1; List list3=new Arraylist<>(); list2=list3; } } //水果类 class Fruit { } class Apple extends Fruit { } ``` ### 小结泛型类 **定义泛型类** public class MyClass
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值