文章目录
java泛型的理解
问题1、 Java中的泛型是什么 ?
java的参数化类型被称为泛型,再细致点来讲,所谓泛型,就是允许在定义类、接口、方法的时候使用类型形参,这个类型形参在声明变量、创建对象和调用方法的时候被动态指定(即传入实际的类型参数,也可以称为类型实参)。
参数化类型:允许在创建集合的时候指定类型,如List
类型形参:运行时表明参数所绑定的值类型或引用类型的标识符
问题2、使用泛型的好处是什么?
- 在没有泛型以前,在集合中存储对象,集合并不会记住对象的类型,只会将对象以object的类型保存,在使用的集合中的对象的时候再进行强制类型转换,很容易出现ClassCastException。泛型避免了这种情况的发生,它提供了编译期的类型安全,只要在编译期间没有出现异常,在运行时候一定不会发生类型转换异常。
- 使得代码更加简洁,程序更加健壮。
- 增强了枚举类、反射等方面的功能。
问题3、Java的泛型是如何工作的?
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。
问题4、什么是类型擦除?
当把一个具有泛型信息的对象赋值给一个没有泛型信息的变量的时候,这个变量将会丢失掉所有在尖括号之间的类型信息。比如一个List对象被转换成List,那么它的受查类型上限将变成Object。
package Generic;
/**
* @ClassName ErasureTest
* @Description TODO
* @Author ROG
* @Date 2019/10/13 16:40
* @Version 1.0
*/
public class ErasureTest {
public static void main(String[] args){
Orange<Integer> orange = new Orange<>(6);
Integer as = orange.getSize();
System.out.println(as);
// 在这里orange引用变量所指向的对象是具有泛型信息的对象,将它赋值给没有泛型信息的orange1变量
// 则orange1变量受查类型变成Object,由于第20行代码是类型转换所以编译异常
Orange orange1 = orange;
Object size = orange1.getSize();
Integer size2 = orange1.getSize();//报异常代码
}
}
class Orange<T>{
private T size;
public Orange(){}
public Orange(T size){
this.size = size;
}
public T getSize() {
return size;
}
public void setSize(T size) {
this.size = size;
}
}
问题5、什么是泛型中的限定通配符和非限定通配符?
限定通配符对类型进行了限制,有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须使用限定内的类型来初始化,否则会导致编译错误。另一方面<? >表示了非限定通配符,因为<? >可以用任意类型来替代。
package Generic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class MyUtils {
// 静态泛型方法,如果使用这个方法来执行第36行代码,会报异常,因为根据泛型方法我们可以
// 推断出来T为Number最终这个方法返回的数据类型为Number
public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
T last = null;
for(T ele: src){
last = ele;
dest.add(ele);
}
return last;
}
// 静态泛型方法,如果使用这个方法来执行第37行代码,不再报异常
// 因为最终返回的数据类型就是Integer,这是使用了类型通配符下限
public static <T> T copy2(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> list = new ArrayList<>();
List<Integer> list1 = new ArrayList<>();
// Integer integer = copy(list,list1);
Integer integer = copy2(list,list1);
A<String> a = new A<>();
String s = a.test("hamai");
System.out.println(s);
Integer as = a.test(45);
System.out.println(as);
A<Integer> abs = new A<>();
Integer sds = abs.dd(45);
abs.printMsg("ab","abc","abcd");
}
}
class A<T>{
public <T> T test(T value){
return value;
}
public T dd(T value){
return value;
}
//泛型方法中使用形参个数可变的参数
public <E> void printMsg(E... args){
for(E t:args){
System.out.println(t);
}
}
public static<T> void show(T t){
System.out.println(t);
}
}
**类型通配符:**类型通配符是一个“ ?”,将一个问号作为类型实参传给List集合,写作List<?>(意思是元素类型未知的List)。这个"?"被称为“通配符”,它的元素类型可以匹配到任意类型。
类型通配符上限:当直接使用List<?>这种形式的时候,表明这个List集合可以是任何泛型List的父类,但是有时候并不希望它代表所有泛型List的父类,而只是代表某一类泛型List的父类,则可以用List<? extends T>这个形式表示所有T类型List泛型的子类,称之为类型通配符上限。
**类型通配符下限:**当直接使用List<?>这种形式的时候,表明这个List集合可以是任何泛型List的父类,但是有时候并不希望它代表所有泛型List的父类,而只是代表某一类泛型List的父类,则可以用List<? super T>这个形式表示所有T类型List泛型的父类,称之为类型通配符下限。
注意:这种带有通配符的List仅仅表示它是各种泛型List的父类,并不能把元素带入其中。
List<?> c = new ArrayList<>();
//下面这行代码会导致编译异常,因为不确定泛型引用变量c具体的类型形参是什么
c.add(new Object());
问题6、List<? extends T>和List <? super T>之间有什么区别 ?
这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List。
问题7、如何编写一个泛型方法,让它能够接受泛型参数并返回泛型类型?
package Generic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @ClassName Apple
* @Description TODO 定义泛型类
* @Author ROG
* @Date 2019/10/7 20:00
* @Version 1.0
*/
public class Apple<T> {
private T info;
// 不管为泛型类传入哪一种类型实参,对于java来讲,它们仍然被当成同一个类来处理,占用相同的内存空间
// 所以在静态方法、静态变量、静态初始化块中不允许出现类型形参,以静态变量为例,因为既然类内存空间固定,如果使用了static关键字
// 说明堆内存中成员变量内存固定,那么需要数据类型固定,而类型形参根据传入的参数不同会使得数据类型不固定,也就与static有冲突
// static T od;
public Apple(){
}
public Apple(T info){
this.info = info;
}
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
public <T> T test(T value){
return value;
}
public static void main(String[] args){
Apple<String> apple = new Apple<>("苹果");
System.out.println(apple.getInfo());
Apple<Integer> apple1 = new Apple<>(5);
System.out.println(apple1.getInfo());
A1 a1 = new A1();
// 下面语句输出“子类null”
System.out.println(a1.getInfo());
// 不管泛型的实际类型参数是什么,它们在运行的时候总是同样的类(class),它们在内存中都占有相同的内存空间
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass() == l2.getClass());
// 泛型类不是真正的类,所以无法使用instanceof运算符
// Collection<String> cs = new ArrayList<>();
// if(cs instanceof ArrayList<String>)
}
}
class A1 extends Apple{
// 在使用super限定字的时候一定要确定父类中包含无参数的构造方法
public String getInfo(){
return ("子类" + super.getInfo()).toString();
}
}
问题8、你可以把List< String>传递给一个接受List< Object>参数的方法吗?
是不可以的,这样做会报异常,如果Foo是Bar的一个子类型(子类或者子接口),G是一个具有泛型声明的类或者接口,那么G不是G的一个子类型。
package Generic;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName typeWildcard
* @Description TODO 类型通配符
* @Author ROG
* @Date 2019/10/8 11:06
* @Version 1.0
*/
public class typeWildcard {
public void test(List<Object> c){
for (int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
}
public static void main(String[] args){
typeWildcard typeWildcard = new typeWildcard();
List<String> list = new ArrayList<>();
list.add("ok");
/**
* 如果Foo是Bar的一个子类型(子类或者子接口),G是一个具有泛型声明的类或者接口,那么G<Foo>不是G<Bar>的一个子类型
* 数组和泛型有所不同,假设Foo是Bar的一个子类型,那么Foo[]仍然是Bar[]的一个子类型
* @param args
*/
// 'test(java.util.List<java.lang.Object>)' in 'Generic.typeWildcard'
// cannot be applied to '(java.util.List<java.lang.String>)'
// typeWildcard.test(list);
}
}
问题9、Array中可以使用泛型吗?
Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。
package Generic;
/**
* @ClassName ArrayError
* @Description TODO
* @Author ROG
* @Date 2019/10/13 19:24
* @Version 1.0
*/
public class ArrayError {
public static void main(String[] args){
Integer[] la = new Integer[5];
Number[] sa = la;
//下面代码编译时正常,但是运行时候会报错,因为0.5并不是Integer
sa[0] = 0.5;
}
}