带你入门Java的泛型

网友投稿 210 2022-10-13


带你入门Java的泛型

目录泛型1、简单泛型(1)元组(2)堆栈2、泛型接口3、泛型方法(1)类型推断(2)通用的Generator(3)Set实用工具实现数学方法4、擦除(1)迁移兼容性(2)擦除的问题5、擦除的补偿(1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isInstance()。(2)创建类型实例(3)泛型数组6、边界7、通配符(1)List extends Fruit>协变(2)List super Fruit>逆变(3)无界通配符List>(4)捕获转换8、问题(1)任何基本类型都不能作为类型参数(2)实现参数化接口(3)转型和警告(4)重载(5)基类劫持了接口9、自限定10、异常总结

泛型

1、简单泛型

泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。

泛型暂时不指定类型,在使用时决定具体使用什么类型。通过来实现,T就是类型参数。

(1)元组

class TwoTuple{

public final A first;

public final B second;

public TwoTuple(A a,B b){

first = a;

second = b;

}

@Override

public String toString() {

return "{ " + first +

", " + second +

'}';

}

}

(2)堆栈

class LinkedStack{

private class Node {

T item;

Node next;

Node() { item = null; next = http://null; }

Node(T item, Node next) {

this.item = item;

this.next = next;

}

boolean end() { return item == null && next == null; }

}

private Node top = new Node();

public void push(T item) { top = new Node(item, top); }

public T pop() {

T result = top.item;

if(!top.end())

top = top.next;

return result;

}

}

(3)RandomList

class RandomList{

private ArrayList storage = new ArrayList<>();

private Random rand = new Random(47);

public void add(T item){

storage.add(item);

}

public T select(){

return storage.get(rand.nextInt(storage.size()));

}

}

2、泛型接口

泛型也可以应用于接口,例如生成器,这是一种专门负责创建对象的类。

import net.mindview.util.Generator;

import java.util.Iterator;

class Fibonacci implements Generator {

private int count = 0;

public Integer next(){

return fib(count++);

}

private int fib(int n){

if(n<2) return 1;

return fib(n-2) + fib(n-1);

}

}

class IterableFibonacci implements Iterable {

private Fibonacci fib = new Fibonacci();

private int n;

public IterableFibonacci(int count){

n = count;

}

@Override

public Iterator iterator() {

return new Iterator() {

@Override

public boolean hasNext() {

return n>0;

}

@Override

public Integer next() {

n--;

return fib.next();

}

public void remove() { // Not implemented

throw new UnsupportedOperationException();

}

};

}

}

3、泛型方法

泛型方法使得该方法能够独立于类而产生变化。使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。

class GenericMethods{

public void f(T x){

System.out.println(x.getClass().getSimpleName());

}

}

(1)类型推断

使用泛型有时候需要向程序中加入更多的代码。如下所示:

Map> petPerson =

new HashMap>();

在泛型方法中可以通过类型推断来简化一部分工作。如下所示:

class New{

public static Map map(){

return new HashMap();

}

public static void main(String[] args) {

Map> petPerson = New.map();

}

}

类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法的结果作为参数,传递给另一个方法时,另一个方法需要显式的类型说明。如下所示:

public class ExplicitTypeSpecification{

static void f(Map> petPerson){}

public static void main(String[] args) {

f(New.>map());

}

}

(2)通用的Generator

import net.mindview.util.Generator;

public class BasicGenerator implements Generator{

private Class type;

public BasicGenerator(Class type){

this.type = type;

}

public T next(){

try {

return type.newInstance();

}catch (Exception e){

throw new RuntimeException(e);

}

}

public static Generator create(Class type){

return new BasicGenerator(type);

}

}

(3)Set实用工具实现数学方法

public class Sets{

@SuppressWarnings("unchecked")

protected static Set copy(Set s) {

if(s instanceof EnumSet)

return ((EnumSet)s).clone();

return new HashSet(s);

}

//并集

public static Set union(Set a, Set b) {

Set result = copy(a);

result.addAll(b);

return result;

}

//交集

public static Set intersection(Set a, Set b) {

Set result = copy(a);

result.retainAll(b);

return result;

}

//差集

public static Set difference(Set superset, Set subset) {

Set result = copy(superset);

result.removeAll(subset);

return result;

}

//包含除了交集以外的所有元素

public static Set complement(Set a, Set b) {

return difference(union(a, b), intersection(a, b));

}

}

4、擦除

Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List和List在运行时事实上是相同的类型,都被擦除成它们的“原生”类型List。

(1)迁移兼容性

泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为他们的非泛型上界。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。

(2)擦除的问题

泛型的QkuLosNIYG所有关于参数的类型信息都丢失了,所以不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。

5、擦除的补偿

(1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isInstance()。

public class ClassTypeCapture{

Class kind;

public ClassTypeCapture(Class kind){

this.kind = kind;

}

public boolean f(Object arg){

return kind.isInstance(arg);

}

}

(2)创建类型实例

通过工厂对象来创建实例。如果使用类型标签,就可以使用newInstance()来创建这个类型的新对象。

class ClassAsFactory{

T x;

public ClassAsFactory(Class kind){

try{

x = kind.newInstance();

}catch(Exception e){

throw new RuntimeException(e);

}

}

}

如果类没有默认的构造器,上面的案例会创建失败。为了解决这个问题,可以通过显示的工厂来实现。

interface FactoryI{

T create();

}

class Foo2{

private T x;

public > Foo2(F factory){

x = factory.create();

}

}

class IntegerFactory implements FactoryI{

public Integer create(){

return new Integer(6);

}

}

另一种方式是模板方法设计模式。

abstract class GenericWithCreate{

final T element;

GenericWithCreate(){ element = create(); }

abstract T create();

}

class X{}

class Creator extends GenericWithCreate{

X create(){ return new X(); }

}

(3)泛型数组

无法通过 T[] array = new T[sz] 来创建泛型数组,一般的解决方法是在需要泛型数组的地方都使用ArrayList。

在创建泛型数组时,有以下三种情况:

①创建时强制转型

public class GenericArray{

private T[] array;

@SuppressWarnings("unchecked")

public GenericArray(int sz){

array = (T[])new Object[sz];

}

public T[] rep(){ return array; }

public static void main(String[] args) {

GenericArray gai = new GenericArray(10);

Integer[] ia = gai.rep();//引起ClassCastException

Object[] oa = gai.rep();

}

}

②方法返回时强制转型

class GenericArray2{

private Object[] array;

@SuppressWarnings("unchecked")

public GenericArray(int sz){

array = new Object[sz];

}

public T[] rep(){ return (T[])array; }

public static void main(String[] args) {

GenericArray gai = new GenericArray(10);

Integer[] ia = gai.rep();//引起ClassCastException

Object[] oa = gai.rep();

}

}

③使用Array.newInstance()

以上两种方法都无法创建具体类型的数组,无法推翻底层的数组类型,只能是Object[]。通过传入类型标记Class,可以从擦除中恢复。

class GenericArray3{

private T[] array;

@SuppressWarnings("unchecked")

public GenericArray(Class type,int sz){

array = (T[]) Array.newInstance(type,sz);

}

public T[] rep(){ return array; }

public static void main(String[] args) {

GenericArray gai = new GenericArray(Integer.class,10);

Integer[] ia = gai.rep();//可以正常运行

Object[] oa = gai.rep();

}

}

6、边界

边界使得你可以在用于泛型的参数类型上设置限制条件,可以按照自己的边界类型来调用方法。

public class Test {

public static void main(String[] args) {

Man m = new Man();

m.hear();

m.smell();

}

}

interface SuperPower{}

interface SuperHearing extends SuperPower{

void hearSubtleNoises();

}

interface SuperSmell extends SuperPower{

void trackBySmell();

}

class SuperHero{

POWER power;

SuperHero(POWER power){ this.power = power; }

POWER getPower(){ return power; }

}

class CaineHero extends SuperHero{

CaineHero(POWER power){ super(power); }

void hear(){ power.hearSubtleNoises(); }

void smell(){ power.trackBySmell(); }

}

class SuperHearSmell implements SuperHearing,SuperSmell{

@Override

public void hearSubtleNoises() {

System.out.println("hearSubtleNoises");

}

@Override

public void trackBySmell() {

System.out.println("trackBySmell");

}

}

class Man extends CaineHero{

Man(){ super(new SuperHearSmell()); }

}

7、通配符

(1)List extends Fruit>协变

表示具有任何从Fruit继承的类型的列表。List extends Fruit>可以合法地指向一个List。一旦执行这种类型的向上转型,就将丢失掉向其中传递任何对象的能力,甚至是传递Object也不行。

List extends Fruit> flist =

Arrays.asList(new Apple());

//Compile Error:can't add any type of object

//add()的参数是 extends Fruit>,编译器不知道需要Fruit的哪个

//具体的子类型,因此不接受任何类型的Fruit

//flist.add(new Apple());

//flist.add(new Fruit());

//flist.add(new Object());

flist.add(null);//Legal but uninteresting

Apple a = (Apple)flist.get(0);//No warning

Fruit f = flist.get(0);//No warning

flist.contains(new Apple());//参数是Object

flist.indexOf(new Apple());//参数是Object

(2)List super Fruit>逆变

超类型通配符可以安全地传递一个类型对象到泛型类型中。List super Fruit>意味着向其中添加Fruit或Fruit的子类型是安全的。

List super Fruit> flist = new ArrayList();

flist.add(new Apple());

flist.add(new Fruit());

//Error:Incompatible Type

//Fruit f = flist.get(0);

Object f = flist.get(0);//OK,but type information has been lost

(3)无界通配符List>

List实际上表示“持有任何Object类型的原生List”,List>表示“具有某种特定类型的非原生List,只是我们不知道那种类型是什么”,List extends Object>表示“类型是Object的导出类”。

无界通配符的一个重要应用:处理多个泛型参数时,允许一个参数可以是任何类型,同时为其他参数确定某种特定类型。

Map map = new HashMap;

map = new HashMap;

原生Holder与Holder>是大致相同的事物,但存在不同。它们会揭示相同的问题,但是后者将这些问题作为错误而不是警告报告。

static void rawArgs(Holder holder,Object arg){

//holder.set(arg);

//Warning:Unchecked call to set(T) as member

//of the raw type Holder

//holder.set(new Wildcards());//Same Warning

//Can't do this:don't have any 'T'

//T t = holder.get();

//OK,but type infomation has been lost

Object obj = holder.get();

}

//Similar to rawArgs(),but errors instead of warnings

static void unboundedArg(Holder> holder,Object arg){

//holder.set(arg);

//Error:set(capture of ?) in Holder

//cannot be applied to (Object)

//holder.set(new Wildcards());//Same Error

//Can't do this:don't have any 'T'

//T t = holder.get();

//OK,but type infomation has been lost

Object obj = holder.get();

}

(4)捕获转换

未指定的通配符类型被捕获,并被转换为确切类型。在f2()中调用f1(),参数类型在调用f2()的过程中被捕获,因此它可以在对f1()的调用中被使用。不能从f2()中返回T,因为T对于f2()来说是未知的。

static void f1(Holder holder){

T t = holder.get();

System.out.println(t.getClass().getSimpleName());

}

static void f2(Holder holder){

f1(holder);

}

8、问题

(1)任何基本类型都不能作为类型参数

(2)实现参数化接口

一个类不能实现同一个泛型接口的两种变体。将泛型参数移除掉后,这段代码就可以正常编译了。

interface Payable{}

class Employee implements Payable{}

//Compile Error:cannot be inherited with different type arguments

class Hourly extends Employee implements Payable{}

(3)转型和警告

使用带有泛型类型参数的转型或instanceof不会有任何效果。

由于擦除原因,编译器无法知道这个转型是否安全,并且pop()方法实际上并没有执行任何转型。如果没有@SuppressWarnings注解,编译器将对pop()产生“Unchecked cast”警告。

private int index = 0;

private Object[] storage;

@SuppressWarnings("unchecked")

public T pop(){ return (T)storage[--index]; }

(4)重载

由于擦除的原因,重载方法将产生相同的类型签名,导致程序不能编译。

public class UseList{

void f(List v){}

void f(List v){}

}

(5)基类劫持了接口

一旦为Comparable确定了ComparablePet参数,那么其他任何实现类都不能与ComparablePet之外的任何对象比较。在前面的“实现参数化接口”章节里面的第一个例子,就体现了基类劫持接口。

public class ComparablePet

implements Comparable {

public int compareTo(ComparablePet arg) {

return 0;

}

}

class Cat extends ComparablePet implements Comparable{

// Error: Compahttp://rable cannot be inherited with

// different arguments: and

public int compareTo(Cat arg) { return 0; }

} ///:~

class Hamster extends ComparablePet

implements Comparable{

public int compareTo(ComparablePet arg) {

return 0;

}

}

9、自限定

class Subtype extends BasicHolder {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。

从使用上来说,Subtype对象本身的类型是Subtype,且Subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是Subtype了(这就是自限定的重要作用)。这样Subtype对象就只允许和Subtype对象(而不是别的类型的对象)交互了。

class BasicHolder {

T element;

void set(T arg) { element = arg; }

T get() { return element; }

void f() {

System.out.println(element.getClass().getSimpleName());

}

}

class Subtype extends BasicHolder {}

public class CRGWithBasicHolder {

public static void main(String[] args) {

Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype();

st1.set(st2);

st2.set(st3);

Subtype st4 = st1.get().get();

st1.f();

}

} /* Output:

Subtype

*/

10、异常

由于擦除原因,将泛型应用于异常是非常受限的。但是,类型参数可能会在一个方法的throws子句中用到,这使得你可以编写随检查型异常的类型而发生变化的泛型代码。

interface

Processor {

void process(List resultCollector) throws E;

}

class

ProcessRunner

extends ArrayList> {

List processAll() throws E {

List resultCollector = new ArrayList();

for(Processor processor : this)

processor.process(resultCollector);

return resultCollector;

}

}

class Failure extends Exception {}

class Processor1 implements

Processor {

static int count = 3;

public void process(List resultCollector)

throws Failure1_1, Failure1_2 {

if(count-- > 1)

resultCollector.add("Hep!");

else

resultCollector.add("Ho!");

if(count < 0)

throw new Failure1();

}

}

public class Test {

public static void main(String[] args) {

ProcessRunner runner =

new ProcessRunner();

for(int i = 0; i < 3; i++)

runner.add(new Processor1());

try {

System.out.println(runner.processAll());

} catch(Failure e) {

System.out.println(e);

}

}

}

总结

本篇文章就到这里了,希望能给您带来帮助,也希望您能够多多关注我们的更多内容!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:环网交换机的工作原理是什么?(工业环网交换机工作原理)
下一篇:光纤交换机的产品功能和工作原理介绍(光纤交换机作用通俗讲)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~