Java[学习总结]-泛型

1.为什么要引入泛型?

在未引入泛型时,人们只能确定具体的数据类型,代码无法通用,为了解放这种限制,引入了泛型机制。
在没有泛型之前,一旦把一个对象“丢进”Java集合中,集合就会忘记对象的类型,当再次|取出该对象的时候,该对象的编译类型就变成了Object类型(运行时的类型没有变换)。当进行强制类型转换会增加程序的复杂度,也可能引入ClassCastException。
从JDK1.5以后,Java引入了“类型参数化(parameterized-type)”的概念,允许我们在创建集合的时候指定集合元素的类型。Java的参数化类型被称为泛型(Generic)。
“泛型”术语:适用于许多许多类型。

2.简单泛型

泛型相当重要的一个作用便是创建容器类,与简单的数组相比较,容器类更加灵活,具备更多不同的功能,允许持有各种对象。
2.1 创建泛型类
程序定义了带泛型声明的Apple类,实际上使用Apple类时会为T形参传入实际类型,这样就可以生成Apple、Apple…形式上的逻辑子类。
注意:当创建带泛型声明的自定义类,为该类定义构造方法时,构造方法依然是原来的类名,不需要增加泛型。

class Test<T> {
    private T a;//T即为你打算传进的参数类型
    public void set(T a){
        this.a = a;
    }
    public Test(T a){
        this.a = a;
    }
    public T get(){
        return a;
    }
    public static void main(String[] args) {
        Test<String> test = new Test<String>("Test");//传入String类型
    }
 }

2.2 泛型接口
接口使用泛型与类使用泛型没什么区别

public interface Generator<T> {
        T next();
}

2.3 泛型方法
是否拥有泛型方法,与其所在的类是否是泛型没有关系。泛型方法使得该方法能够独立于类而产生变化。

 public <T> void f(T x){}

使用泛型方法的时候,通常不必指明参数类型,编译器会为我们找出具体的类型,这称为“类型参数推断”。

public class GenericMethods {
    public <T> void f(T x){
        System.out.println(x.getClass().getName());
    }
    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(1.0);
        gm.f(1.0f);
        gm.f('c');
        gm.f(gm);
    }
}

2.4 可变参数和泛型方法
泛型方法与可变参数列表可以很好的共存

public static <T>  List<T>  test(T...args){
List<T> result  = new ArrayList<T>();
for(T item:args){
	result.add(item);
}
return resuilt;
}

3.泛型处理

3.1 从泛型类派生子类
当为定义泛型的类、接口派生子类的时候。需要注意在定义子类使用父类名字时不需要包含类型形参。
下面代码是哪些错误的:

public class A extends Apple<T>{ }
error
public class A1 extends Apple<String>{ }
ok
public class A2 extends Apple{ }
OK

public class A1 extends Apple<String> {}

如果从Apple- 类派生子类,则在父类中所有使用T类型形参的地方都自动被替换成String类型。如果子类需要重写父类的方法,必须注意这一点。

public class A2 extends Apple{ }

如果从Apple类没有传入实际的类型参数,Java编译器会发出警告。此时系统会将Apple类里的T当作0bject类型处理。

//对应2.1代码示例
class Main extends Test<String>{
    public Main(String a) {
        super(a);
    }
}

实际上,泛型对其所有的可能类型参数都是具有相同的行为,从而可以把相同的类当作许多不同的类来处理。但是这种泛型定义却不能使用在静态方法和变量上。

3.2并不存在泛型类
虚拟机没有泛型类型对象一一所有对象都属于普通类型。无论何时定义一一个泛型类型,都自动提供一个相应的原始类型
(rawtype)。原始类型的名字就是删除参数后的泛型类型名,就好像泛型没有引入Java语言之前已经实现的那样。
虽然把ArrayList类当成ArrayList的子类,事实ArrayList对象只能添加String对象作为集合元素。但是实际上,系统并没有为ArrayList 生成新的class文件,而且也不会把ArrayList当成新类来处理。

List<String> l1=new ArrayList<String>;
List<Integer> l2=new ArrayList<Integer>;
System.out.printla(1.getClass0--l2.getClass0);

由于系统中并不会真正生成泛型类,所以instanceofi运算符后面不能使用泛型类。

Collection c= new ArrayList<String>0;
if(c instanceof Listr<String>){
... ... ...
.
}

3.3 类型变量的限定

public static<T> Pair<String> minmax(String[] a)}{
if(a==nulla.length== =0) return null;
Strng min=a[0];
String max=a[0];
for( int i=0;i<a.length;i++){
if(min.compareTo(a [i])>0) min=a[i];
if(max.compareTo(a[il)<0) max=a[i];
}
return new Pair<>(min,max);
}

将里面的String替换为T,类型T意味着它可以是任何一个类的对象。怎么才能确信T所属的类有compareTo方法呢?
解决这个问题的方法就是将T限制为实现了Comparable接口的类。可以通过对类型变量T设置限定(bound)实现这一点:

public static <T extends Comparable> Pair<T> minmax(T[] a);
<T extends Bounding Type>

表示T应该是绑定类型的子类型,T和绑定类型可以是类、也可以是接口。

T extends Comparable&Serializable

在Java继承中,可以根据需要拥有多个接口类型但是限定中至多有一一个类。 如果用一个类作为限定,必须是限定列表中的第一个。

3.4使用类型通配符
3.4.1 概念
为了表示各种泛型的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号|作为类型的参数传入List集合,写作List<?> .这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。

public void test(List<?> c){
for (inti=0;i < c.size0; i++)
{ System.out.println(c.get()); }}

3.4.2 只能加入null
任何类型的List都可以传入方法中,依然可以访问其中每个元素,其类型是Object。
List<?>: ?代表可以使任意类型,任意的意思也.就是未知的类型。List<?> 是各种泛型类型的父类.可以说List<?>是List、List ,List 的父类。任何引用类型的List都可以传入,其类型是Object。不能往List<?>中添
加任何内容,除了null。List :是具体化的泛型,只能是引用数据类型,可以在其中添加Object类或者是它的子类

List<String> is = Arrays.asList("one", "two", "'three");
List<?> list =is;
is.add(new String("four");//Ok
list.add(new String("four")://Error
list.add(null);//ok

现假设


list.add(new String("four");//Ok
list.add(new Integer(4));//Ok

问题出现了,is里面只能存放String类型的对象,将is赋值给list之后,两者同时指向同一块内存地址。此时假设list加入String可以的,那么Integer对象也可以放入is中,但是和is定义的类型发生了错误。
我们发现往List中的“add”操作都可能引发类型不兼容问题,因为传入的参数是未知的。所以java为了保护其类型一致,禁止向List<?>添加任意对象,不过却可以添加null。List<?>是一个只读的集合! ! ! !

通配符不是类型变量,因此不能再编写代码的时候使用”?”作为一一种类型。


public static void swap(Pair<?> p){
? t=p.getFirst(;//error
Object t=p.getFirst();//ok
p.setFirst(p.getSecondQ);//error
p.setSecond(t);//error

3.4.3 设置通配符上限
List<? extends Shape>
表示所有Shape泛型List的父类,Java 可以通过被限制的泛型通配符表达。此处的(?)表示–个未知的类型,但是由于后面的限制,此处我们知道这个未知的类型总是一个Shape的–个子类(也可以是Shape本身)。Shape就是这个通配符的上限
(upper bound)。


public void addRectangle(List<? extends Shape> c){
//下面会引起编译错误
e.add(new Reetangle0);

3.4.4 设置通配符下限
有一个经理数组,并想把奖金最高和最低的经理放–.个Pair对象中。这里,Pair 和Pair都可以.可以放入Manager类型或者是它的父类。

minmaxBonus(Manager[] a, Pair<? super Manager> result){
if(a== =null|la.length= =0) return;
Manager min=a[0];
Manager max =a[0]; 
for(int i=0;i<a.length;i++){
if(min.getSalary0>a[il.getSalaryO) min=a[i];
if(max.getSalary0<a[il.getSalaryO) max=a[i];
result.setFirst(min);
PairTest3.java
result.setSecond(max);

4.泛型擦除

(1)编译器可使用泛型信息,但是这些泛型信息在运行时是不可用的,这种被称为类型消除。泛型是使用一种类型消除(typeerasure)的方法来实现的。编译器使用泛型类型信息来编译代|码,但是随后会消除它。所以泛型信息在运行时是不可以用的。这种方法可以使泛型代码向后兼|容使用原始类型的遗留代码。

ArrayList<String> list = new ArrayList(String)<>;
list.add(“OKlaHome" );
String state = list.get(0);

(2)编译器会检查上述代码的泛型是否正确使用,然后将它翻译成如下图所示的在运行时使用的等价代码。

ArrayList list = new ArrayList<>;
list.add(“OKlaHome ”);
String state = (String ) list.get(0); 
public static <E> void print(E[] list){
for(int i= 0; i<list.length;i++){
System.outprint(list[ij]+““);
System.out.println();

(3) 当编译泛型类、接口和方法时,编译器用Object类型代替泛型类型。例如,编译器会将上图的方法转为下图的方法。

public static void print(Object[] list){
for(inti = 0; i<list.length;i++){
System.out.print(list[i]+““);
System.out.println0;
public static <E extends GeometricObject> boolean equalArea(
E object1, E object2){
return object1.getArea0 == object2.getArea0;
}

(4) 如果一个泛型类型是受限的,那么编译器就会用该受限类型来替换它。

public static boolean equaLArea(
GeometricObject object1,
GeometricObject object2 ){
return
object1.getArea( == object2.getArea0);

5.约束及局限性

5.1不能用基本类型实例化类型参数
没有ArrayList,只有ArrayList运行时类型查询只适用于原始类型虚拟机中对象总有一个特定的非泛型类型。因此|,所有的类型查询只产生原始类型。


if( a instanceof Pair<String>) //error
if( a instanceof Pair<T>) //error
Pair<String> stringPair= new Pair<>0;
Pair<Integer> intPair= new Pair<>0;
if(stringPair.getClass0= =intPairgetClassO) //true

5.2不能创建参数化的数组


Pair<String>[ table= new Pair<String>[10]//error

需要说明的是,只是不允许创建这些数组,而声明类型还为Pair[]的变量仍是合法的。不过不能用new Pair[10]初始化这个变量。但是可以声明通配类型数组,然后进行类型转换

Pair<String>[] table= (Pair<String>[])new Pair<?>

但是在运行过程中可能会出现ClassCastException异常

5.3不能实例化类型变量
不能使用像new T0,new T…或T.class这样的表达式中类型变量。.

Public Pair( { first= new TO; second =new T0;}//error

泛型类的静态上下文中类型变量无效.

Public class singleton<T>{
private static T singleInstance;//error
private static T getsingle Instance0{//error
............●
}

5.4不能捕获或抛出泛型类的实例

pu blic static<T extends Throwable> void
doWork(Class<T> t){
try{
dowork
}catch(T e//error{
.......
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值