Java泛型基础(二)泛型接口泛型方法# 总结

本文首发于个人网站:Java中的泛型(二)

泛型可以应用于同一个类,该类可以针对多种类型使用,例如构建一个RedisTemplateService组件,用于处理当前应用中所有对象的缓存操作。这篇文章主要介绍泛型应用于接口、方法和匿名内部类的一些知识点和使用案例,也包括《Java编程思想》中对应的练习题的解读。

泛型接口

泛型应用于接口,是工厂方法设计模式的一种应用。我使用《Java编程思想》中的例子进行了练习。

下面这个例子中,CoffeeGenerator用于生成随机的Coffee对象。

package org.java.learn.generics.coffee;

import org.apache.commons.lang3.RandomUtils;
import org.java.learn.util.Generator;

import java.util.Iterator;

/**
 * 实现Iterable接口,表示当前类可以用在循环语句中
 *
 * 作用: User: duqi Date: 2017/11/30 Time: 22:58
 */
public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
    private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class};
    private int size = 0;

    public CoffeeGenerator() {
    }

    /**
     * 末端哨兵,在case2中for-each语句中,告诉程序什么时候停止
     * @param size
     */
    public CoffeeGenerator(int size) {
        this.size = size;
    }

    @Override
    public Coffee next() {
        try {
            return (Coffee) types[RandomUtils.nextInt(0, types.length-1)].newInstance();
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    class CoffeeIterator implements Iterator<Coffee> {
        /**
         * 内部类可以直接访问外部类的属性
         */
        int count = size;

        @Override
        public boolean hasNext() {
            return count > 0;
        }

        @Override
        public Coffee next() {
            count--;
            return CoffeeGenerator.this.next();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public Iterator<Coffee> iterator() {
        return new CoffeeIterator();
    }

    public static void main(String[] args) {
        //case1:测试CoffeeGenerator的next()方法;
        CoffeeGenerator gen = new CoffeeGenerator();
        for (int i = 0; i < 5; i++) {
            System.out.println(gen.next());
        }

        //case2: 测试在for-each语句中生成对象
        for (Coffee coffee: new CoffeeGenerator(5)) {
            System.out.println(coffee);
        }
    }
}

再看一个例子,使用Generator<T>接口生成Fibonacci数列。

package org.java.learn.generics.coffee;

import org.java.learn.util.Generator;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 13:59
 */
public class Fibonacci implements Generator<Integer> {

    private int count = 0; //全部是int基本类型,但是类型参数是Integer

    @Override
    public Integer next() {
        return fib(count++);
    }

    private int fib(int n) {
        if (n < 2) {
            return 1;
        }
        return fib(n - 2) + fib(n - 1);
    }

    public static void main(String[] args) {
        Fibonacci fibonacci = new Fibonacci();
        for (int i = 0; i < 18; i++) {
            System.out.print(fibonacci.next() + " ");
        }
    }
}

如果希望将这个Fibonacci生成器用于循环语句,书中的例子用的是继承Fibonacci,写一个IterableFibonacci类,该类实现了Iterable接口。在练习7中,作者提示可以使用“组合代替继承”实现同样的功能,我尝试自己做了下,这是我的实现:

package org.java.learn.generics;

import java.util.Iterator;

/**
 * 组合代替继承,实现适配器模式
 *
 * IterableFibonacci2适配Fibonacci为可被循环语句使用的生成器
 *
 * 作用: User: duqi Date: 2017/12/2 Time: 14:08
 */
public class IterableFibonacci2 implements Iterable<Integer> {

    //末端哨兵
    private int n;

    private Fibonacci fibonacci; //组合代替继承

    public IterableFibonacci2(int n, Fibonacci fibonacci) {
        this.n = n;
        this.fibonacci = fibonacci;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            @Override
            public boolean hasNext() {
                return n > 0;
            }

            @Override
            public Integer next() {
                n--;
                return fibonacci.next();
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    public static void main(String[] args) {
        for (int i: new IterableFibonacci2(18, new Fibonacci())) {
            System.out.print(i + " ");
        }
    }
}
  • 上面三个例子中,提到了两种设计模式:工厂方法设计模式适配器模式
  • 在泛型中,基本类型无法作为类型参数,但是Java提供了自动打包和拆包的功能;

泛型方法

知识点总结

  • 如果使用泛型方法可以取代将整个类(或接口)泛型化,那么就应该只使用泛型方法;
  • static方法要使用泛型能力,就必须成为泛型方法;
  • 类型推断:这是编译器的特性。在使用泛型类的时候,必须在创建对象的时候指定类型参数的值,但是在使用泛型方法时候,不必指明参数类型。
    • 类型推断只对赋值操作有效
  • 泛型方法与可变参数可以一起使用

例子1:使用Generator的泛型方法

package org.java.learn.generics;

import org.java.learn.generics.coffee.Coffee;
import org.java.learn.generics.coffee.CoffeeGenerator;
import org.java.learn.util.Generator;

import java.util.ArrayList;
import java.util.Collection;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 14:58
 */
public class Generators {

    /**
     * 泛型方法的定义格式——将泛型参数列表放在方法的返回值左面
     */
    public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) {
        for (int i = 0; i < n; i++) {
            coll.add(gen.next());
        }
        return coll;
    }

    public static void main(String[] args) {
        Collection<Coffee> coffees = fill(new ArrayList<>(), new CoffeeGenerator(), 4);
        for (Coffee coffee : coffees) {
            System.out.println(coffee);
        }

        Collection<Integer> numers = fill(new ArrayList<>(), new Fibonacci(), 12);
        for (int i : numers) {
            System.out.print(i + " ");
        }
    }
}

例子2:一个通用的Generator

下面这个例子,是一个通用的生成器,只需要传入指定的类型,就可以生成对应类型的对象。

package org.java.learn.util;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 15:04
 */
public class BasicGenerator<T> implements Generator<T> {

    private Class<T> type;

    public BasicGenerator(Class<T> type) {
        this.type = type;
    }

    @Override
    public T next() {
        try {
            return type.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public static <T> Generator<T> create(Class<T> type) {
        return new BasicGenerator<>(type);
    }
}

上面这段代码可以创建什么类的对象呢?(1)public的类;(2)含有默认构造器的类;这里给出一个例子:

package org.java.learn.generics;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 15:10
 */
public class CountedObject {

    private static long counter = 0;
    private final long id = counter++;

    public long getId() {
        return id;
    }

    @Override
    public String toString() {
        return "CounteredObject " + id;
    }
}

然后再给出一个使用上述构造器的例子,书中的例子是使用BasicGenerator的create()方法,我这里还实现了练习14中提到的方法,参见:

package org.java.learn.generics;

import org.java.learn.util.BasicGenerator;
import org.java.learn.util.Generator;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 15:11
 */
public class BasicGeneratorDemo {

    public static void main(String[] args) {
        Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class);
        for (int i = 0; i < 5; i++) {
            System.out.println(gen.next());
        }

        //练习14:不使用create方法,使用显式的构造器
        Generator<CountedObject> generator = new BasicGenerator<>(CountedObject.class);
        for (int i = 0; i < 5; i++) {
            System.out.println(generator.next());
        }
    }
}

例子3:简化元组的使用

之前的一篇文章里,已经实现过TwoTuple、ThreeTuple等工具类,但是使用的时候还不太方便,这里利用泛型方法实现一个工具类,可以简化元组的使用。

package org.java.learn.util.tuple;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 15:21
 */
public class Tuple {

    public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
        return new TwoTuple<>(a, b);
    }

    public static <A, B, C> ThreeTuple<A, B, C> tuple(A a, B b, C c) {
        return new ThreeTuple<>(a, b, c);
    }

    public static <A, B, C, D> FourTuple<A, B, C, D> tuple(A a, B b, C c, D d) {
        return new FourTuple<>(a, b, c, d);
    }
}

这个工具类的使用例子如下:

package org.java.learn.util.tuple;

import org.java.learn.generics.coffee.Breve;
import org.java.learn.generics.coffee.Cappuccino;

import static org.java.learn.util.tuple.Tuple.*;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 15:24
 */
public class TupleTest {

    static TwoTuple<String, Integer> f() {
        return tuple("hi", 47);
    }

    static TwoTuple f2() {
        return tuple("hi", 47);
    }

    static ThreeTuple<Breve, String, Integer> g() {
        return tuple(new Breve(), "hi", 44);
    }

    static FourTuple<Cappuccino, Breve, String, Integer> h() {
        return tuple(new Cappuccino(), new Breve(), "hi", 447);
    }

    public static void main(String[] args) {
        TwoTuple<String, Integer> ttsi = f();
        System.out.println(ttsi);
        /**
         * 这里没有发出告警,是因为我们将f2()的返回值直接返回,并没有再尝试转为参数化对象;
         */
        System.out.println(f2());
        System.out.println(g());
        System.out.println(h());

        /**
         * 练习15:这里尝试将f2的返回值转为一个参数化对象,就收到了报警
         */
        TwoTuple<String, Integer> ttsi2 = f2();
    }
}

例子4:一个Set实用工具

书中提供了一个Sets工具类,用于实现常用的集合操作,代码如下:

package org.java.learn.util;

import java.util.HashSet;
import java.util.Set;

/**
 * 作用: User: duqi Date: 2017/12/2 Time: 15:40
 */
public class Sets {

    /**
     * A和B的并集
     * @param a
     * @param b
     * @param <T>
     * @return
     */
    public static <T> Set<T> union(Set<T> a, Set<T> b) {
        Set<T> result = new HashSet<>(a);
        result.addAll(b);
        return result;
    }

    /**
     * A和B的交集
     * @param a
     * @param b
     * @param <T>
     * @return
     */
    public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
        Set<T> result = new HashSet<>(a);
        result.retainAll(b);
        return result;
    }

    /**
     * A和B的差集,将A中移除B中的元素
     * @param superset
     * @param subset
     * @param <T>
     * @return
     */
    public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
        Set<T> result = new HashSet<>(superset);
        result.removeAll(subset);
        return result;
    }

    /**
     * A和B中所有的元素,减去A和B的交集,剩下的元素
     * @param a
     * @param b
     * @param <T>
     * @return
     */
    public static <T> Set<T> complement(Set<T> a, Set<T> b) {
        return difference(union(a, b), intersection(a, b));
    }

}

# 总结

  1. 本节涉及的知识点:泛型接口、泛型方法
  2. 本节练习用的代码:LearnJava

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java、Spring、技术分享

fastjson详解

  fastjson用于将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

8261
来自专栏牛肉圆粉不加葱

[6] - 类和对象之进阶(二)

Scala 中的可见性非常灵活且复杂,这篇文章希望通过大量的示例来说清楚各种情况下的可见性是怎么样的。

732
来自专栏函数式编程语言及工具

Scalaz(56)- scalaz-stream: fs2-安全运算,fs2 resource safety

    fs2在处理异常及资源使用安全方面也有比较大的改善。fs2 Stream可以有几种方式自行引发异常:直接以函数式方式用fail来引发异常、在纯代码里隐式...

2075
来自专栏10km的专栏

fastjson:javabean按字段(field)序列化存储为Map并反序列化

大部分json工具对java对象整体序列化都提供了简单的调用方式,以fastjson为例: Model model = new Model(); String ...

3625
来自专栏函数式编程语言及工具

Scalaz(57)- scalaz-stream: fs2-多线程编程,fs2 concurrency

    fs2的多线程编程模式不但提供了无阻碍I/O(java nio)能力,更为并行运算提供了良好的编程工具。在进入并行运算讨论前我们先示范一下fs2 pip...

2056
来自专栏PhpZendo

带你入门 JavaScript ES6 (五) 集合

本章我们将学习 ES6 中的 Set(集合) 及 WeakSet 集合 的相关用法及使用场景。

1012
来自专栏Hongten

python开发_python代码风格(coding style)

1131
来自专栏java初学

java异常处理及自定义异常的使用

1297
来自专栏Golang语言社区

[Go语言]实现可以枚举的map

在golang-nuts上看到有人问怎么样去枚举一个map。在go语言层面,并不支持支持枚举map,也就是说你不能获得一个枚举器在任意时刻去枚举这个map,只能...

3427
来自专栏xingoo, 一个梦想做发明家的程序员

Oozie分布式工作流——EL表达式

oozie支持使用EL(expression language)表达式。 基本的EL常量 KB MB GB TB PB 基本EL函数 string fir...

2618

扫码关注云+社区

领取腾讯云代金券