“泛型”意思就是:适用于许多许多的类型
1 与C++比较
C++模版,了解泛型的边界所在
2 简单泛型
使用泛型的目的之一: 指定容器持有什么类型,让编译器确保正确性,而不是在运行期发现错误
java泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节
2.1 元组
元组允许读取元素,但不能插入新元素,不可以修改元素值,因为元素被设置为final。
元组可以任意长度,可以存储任何类型对象。
创建一个元组,使其返回一组任意类型的对象。
package generics;
import generics.e3.SixTuple;
import net.mindview.util.*;
class Amphibian{} //两栖动物
class Vehicle{}//车辆
public class TupleTest {
static TwoTuple<String,Integer> f(){
return new TwoTuple<String,Integer>("hi",47);
}
static ThreeTuple<Amphibian,String,Integer> g(){
return new ThreeTuple<Amphibian,String,Integer>(new Amphibian(),"hi",47);
}
static FourTuple<Vehicle,Amphibian,String,Integer> h(){
return new FourTuple<Vehicle,Amphibian,String,Integer>(new Vehicle(),new Amphibian(),"hi",47);
}
static FiveTuple<Vehicle,Amphibian,String,Integer,Double> k(){
return new FiveTuple<Vehicle,Amphibian,String,Integer,Double>(new Vehicle(),new Amphibian(),"hi",47,11.1);
}
static SixTuple<Vehicle,Amphibian,String,Integer,Double,Float> i(){
return new SixTuple<Vehicle,Amphibian,String,Integer,Double,Float>(new Vehicle(),new Amphibian(),"hi",47,11.1,22.22F);
}
public static void main(String[] args) {
TwoTuple<String,Integer> ttsi = f();
// ttsi.first = "there"; //编译错误,final
System.out.println(ttsi);
System.out.println(g());
System.out.println(h());
System.out.println(k());
System.out.println(i());
}
}
/*
*/
2.2 一个堆栈类
传统的下推堆栈
11章使用LinkedList
不用LinkedList,实现自己内部的链式存储机制
package generics;
public class LinkedStack<T> {
private static class Node<U> {
U item;
Node<U> next;
Node() {
item = null;
next = null;
}
Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}
boolean end() {
return item == null && next == null;
}
}
private Node<T> top = new Node<T>(); // End Sentinel 末端哨兵
public void push(T item) {
top = new Node<T>(item, top);
// push "Phasers"时,创建对象Node<String>("Phasers",top),top.item == null top.next == null ,
// 并将创建的Node结点,指向top,即top.item == "Phasers",top.next == item和next为null的结点
// push "on"时,创建对象Node<String>("on",top),将"Phasers"的结点,作为"on"的next
// push "stun!"时,创建对象Node<String>("stun!",top),将"on"的结点,作为"stun!"的next
}
public T pop() {
T result = top.item; // 取值
if (!top.end())
top = top.next; // 下移
return result; // 返回取出的值
}
public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<String>();
for (String s : "Phasers on stun!".split(" "))
lss.push(s);
String s;
while ((s = lss.pop()) != null)
System.out.println(s);
}
}
/*
stun!
on
Phasers
*/
内部类可以访问外部类堆类型参数
package generics.e5;
public class LinkedStack<T> {
private class Node{ //将原来的嵌套类 修改 为普通内部类。即Node不能为static
T item;
Node next;
Node() {
item = null;
next = null;
}
Node(T item, Node next) {
this.item = item;
this.next = next;
}
boolean end() {
return item == null && next == null;
}
}
private Node top = new Node(); // End Sentinel 未端哨兵
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;
}
public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<String>();
for (String s : "Phasers on stun!".split(" "))
lss.push(s);
String s;
while ((s = lss.pop()) != null)
System.out.println(s);
}
}
/*
stun!
on
Phasers
*/
复习 嵌套内部类 和普通内部类
两种内部类
嵌套类(静态内部类 static):形式上(写法上)和外部类有关系, 其实在逻辑上和外部类并没有直接的关系。可以嵌套类做测试代码main,上线后,将测试类删除
普通内部类:不仅在形式上和外部类有关系(写在外部类的里面), 在逻辑上也和外部类有联系。
1. 内部类对象的创建依赖于外部类对象;
2. 内部类对象持有指向外部类对象的引用。
public class Outer {
int outerField = 0;
class Inner{
void InnerMethod(){
int i = outerField;
}
}
}
javac Outer.java
Outer$Inner.class Outer.class
javap -v Outer\$Inner.class
Classfile /Users/erin/JavaProject/thinking_in_java_example/src/main/java/generics/e5/Outer$Inner.class
Last modified 2019-11-27; size 435 bytes
MD5 checksum 0e4b5eee2db5f187c00dec5216b5ad8a
Compiled from "Outer.java"
class generics.e5.Outer$Inner
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Fieldref #4.#16 // generics/e5/Outer$Inner.this$0:Lgenerics/e5/Outer;
#2 = Methodref #5.#17 // java/lang/Object."<init>":()V
#3 = Fieldref #18.#19 // generics/e5/Outer.outerField:I
#4 = Class #20 // generics/e5/Outer$Inner
#5 = Class #23 // java/lang/Object
#6 = Utf8 this$0
#7 = Utf8 Lgenerics/e5/Outer;
#8 = Utf8 <init>
#9 = Utf8 (Lgenerics/e5/Outer;)V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 InnerMethod
#13 = Utf8 ()V
#14 = Utf8 SourceFile
#15 = Utf8 Outer.java
#16 = NameAndType #6:#7 // this$0:Lgenerics/e5/Outer;
#17 = NameAndType #8:#13 // "<init>":()V
#18 = Class #24 // generics/e5/Outer
#19 = NameAndType #25:#26 // outerField:I
#20 = Utf8 generics/e5/Outer$Inner
#21 = Utf8 Inner
#22 = Utf8 InnerClasses
#23 = Utf8 java/lang/Object
#24 = Utf8 generics/e5/Outer
#25 = Utf8 outerField
#26 = Utf8 I
{
final generics.e5.Outer this$0; //在内部类Outer$Inner中, 存在一个名字为this$0 , 类型为Outer的成员变量, 并且这个变量是final的。 其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用”。但是我们在定义这个内部类的时候, 并没有声明它, 所以这个成员变量是编译器加上的
descriptor: Lgenerics/e5/Outer;
flags: ACC_FINAL, ACC_SYNTHETIC
generics.e5.Outer$Inner(generics.e5.Outer); //编译器会为内部类的构造方法添加一个参数, 参数的类型就是外部类的类型。
descriptor: (Lgenerics/e5/Outer;)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0 // 将局部变量表中的第一个引用变量加载到操作数栈。 这里有几点需要说明。 局部变量表中的变量在方法执行前就已经初始化完成;局部变量表中的变量包括方法的参数;成员方法的局部变量表中的第一个变量永远是this;操作数栈就是执行当前代码的栈。所以这句话的意思是: 将this引用从局部变量表加载到操作数栈。
1: aload_1 //将局部变量表中的第二个引用变量加载到操作数栈。 这里加载的变量就是构造方法中的Outer类型的参数。
2: putfield #1 // Field this$0:Lgenerics/e5/Outer;使用操作数栈顶端的引用变量为指定的成员变量赋值。 这里的意思是将外面传入的Outer类型的参数赋给成员变量this$0 。 这一句putfield字节码就揭示了, 指向外部类对象的这个引用变量是如何赋值的。
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 5: 0
void InnerMethod();
descriptor: ()V
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: getfield #1 // Field this$0:Lgenerics/e5/Outer;
4: getfield #3 // Field generics/e5/Outer.outerField:I
7: istore_1
8: return
LineNumberTable:
line 7: 0
line 8: 8
}
SourceFile: "Outer.java"
InnerClasses:
#21= #4 of #18; //Inner=class generics/e5/Outer$Inner of class generics/e5/Outer
关于内部类如何访问外部类的成员, 主要是通过以下几步做到的:
1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;
2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;
3 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。
https://blog.csdn.net/weixin_39214481/article/details/80372676
2.3 RandomList
package generics;
import java.util.ArrayList;
import java.util.Random;
public class RandomList<T> {
private ArrayList<T> storage = new ArrayList<T>();
private Random rand = new Random(47);
public void add(T item) {
storage.add(item);//ArrayList 的 boolean add(E e)
}
public int size() {
return storage.size();
}
public T select() {
return storage.get(rand.nextInt(storage.size()));
} //ArrayList 的 E get(int index)
public static void main(String[] args) {
RandomList<String> rs = new RandomList<String>();
for (String s : ("The quick brown fox jumped over " +
"the lazy brown dog").split(" "))
rs.add(s);
for (int i = 0; i < rs.size(); i++)
System.out.print(rs.select() + " ");
}
}
/*
brown over fox quick quick dog brown The brown lazy
*/
3 泛型接口
用Coffee和斐波那契数列 分别实现 Generator接口,Iterator接口方式,展示泛型接口。
泛型也可以用于接口,例如生成器,生成器是专门负责创建对象类。
是工厂方法设计模式的一种应用。但工厂方法一般需要参数,
生成器不需要任何参数,一般只定义一个方法。
public interface Generator
这个例子中,有两种繁殖Coffee的方法,
一种是实现了有next()的Generator,
第二种是实现有遍历功能Iterable
这里两种都是运用了泛型,在Generator和Iterable的泛型类型中加入了Coffee
下面是书上例子,后续对书上例子拆分,清楚显示两种方法。
其中第二个例子中,实现Iterable的Iterator方法,生成Coffee的Iterator方法(hasNext(),next())使用类第一种方法的next方法。
package generics.coffee;
import net.mindview.util.Generator;
import java.util.Iterator;
import java.util.Random;
public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
private Class[] types = {Latte.class, Mocha.class,
Cappuccino.class, Americano.class, Breve.class,};
private static Random rand = new Random(47);
public CoffeeGenerator() {
}
//For iteration
private int size = 0;
public CoffeeGenerator(int sz) {
size = sz;
}
class CoffeeItertor implements Iterator<Coffee>{
int count =size;
@Override
public boolean hasNext() {
return count > 0;
}
@Override
public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}
public void remove() { // 不实现
throw new UnsupportedOperationException();
}
}
@Override
public Iterator<Coffee> iterator() {
return new CoffeeItertor();
}
@Override
public Coffee next() {
try {
return (Coffee) types[rand.nextInt(types.length)].newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
CoffeeGenerator gen = new CoffeeGenerator();
for (int i =0;i < 5;i ++)
System.out.println(gen.next());
System.out.println("=====");
for (Coffee c: new CoffeeGenerator(5))
System.out.println(c);
}
}
将上面的例子拆分下,第一种方法,使用Generator的next方法生成coffee
package generics.coffee;
import net.mindview.util.Generator;
import java.util.Random;
public class CoffeeMethod1Generator implements Generator<Coffee> {
private Class[] types = {
Latte.class,
Mocha.class,
Cappuccino.class,
Americano.class,
Breve.class
};
public CoffeeMethod1Generator() {
}
private static Random rand = new Random(47);
@Override
public Coffee next() {
try {
return (Coffee) (types[rand.nextInt(types.length)]).newInstance();// 这句话是关键,随机生成一个指定类型范围内的coffee实例
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
CoffeeMethod1Generator coffee1 = new CoffeeMethod1Generator();
for (int i = 0; i < 5; i++)
System.out.println(coffee1.next());
}
}
/*
Americano 0
Latte 1
Americano 2
Mocha 3
Mocha 4
*/
将上面的例子拆分下,第二种方法
package generics.coffee;
import java.util.Iterator;
import java.util.Random;
public class CoffeeMethod2Iterable implements Iterable<Coffee> {
private Class[] types = {
Latte.class,
Mocha.class,
Cappuccino.class,
Americano.class,
Breve.class
};
private Random rand = new Random(47);
public Coffee next(){
try {
return (Coffee) (types[rand.nextInt(types.length)]).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private int size;
public CoffeeMethod2Iterable(int sz){size = sz;} //因为使用Iterable接口,所以需要有size的构造方法,作为末端哨兵的功能
@Override
public Iterator<Coffee> iterator() {
return new CoffeeIerator();
}
private class CoffeeIerator implements Iterator<Coffee> {
public int index;
@Override
public boolean hasNext() {
return index <= size;
}
@Override
public Coffee next() {
index++;
return CoffeeMethod2Iterable.this.next();// 外部类对象的引用,使用的方法1中的next方法,获取下一个Coffee对象
}
}
public static void main(String[] args) {
CoffeeMethod2Iterable coffees = new CoffeeMethod2Iterable(5);
for (Coffee coffee: coffees)
System.out.println(coffee);
}
}
以下用另外一个例子,是Generator
方法一,使用Generator方法
类型参数为Integer ,基本类型无法作为类型参数
自动打包、自动拆包 可以在基本类型和相应的包装器类型之间进行转换
package generics;
import net.mindview.util.Generator;
public class Fib2 implements Generator<Integer> {// 实现Generator(只有next方法)接口
//public class Fib2 implements Generator<int> {// type argument cannot be of primitive type
private int count = 0;
public int fib(int cnt) {
if (cnt < 2) return 1;// 斐波那契数列,当为0,1时,为1
return fib(cnt - 2) + fib(cnt - 1);//大于1时,为前两个数字之和
}
@Override
public Integer next() {
return fib(count++);
}
public static void main(String[] args) {
Fib2 fib2 = new Fib2();
for (int i = 0; i < 10; i++)
System.out.print(fib2.next() + " ");
}
}
使用实现Iterable接口生成斐波那契生成器,通过使用继承
package generics;
import java.util.Iterator;
public class FibIterator extends Fib2 implements Iterable<Integer> {
private int n;
public FibIterator(int count) {
n = count;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
@Override
public boolean hasNext() {
return n > 0;
}
@Override
public Integer next() {
n--;
return FibIterator.this.next();// 继承通过Generator(生成next方法),创建适配器,外部类类对象的引用方法
}
};
}
public static void main(String[] args) {
// FibIterator fibIterator = new FibIterator(10);
// for (Integer integer: fibIterator)
for (Integer integer: new FibIterator(10)) //foreach语句,需要边界值。用于hasNext知道何时返回false
System.out.print(integer + " ");
}
}
使用实现Iterable接口生成斐波那契生成器,通过使用组合
e7 使用组合代替继承
package generics.e7;
import generics.Fib2;
import java.util.Iterator;
public class IterableFibonacci implements Iterable<Integer> {
Fib2 fib2 = new Fib2();// 通过组合方式
private int count;
public IterableFibonacci(int cnt){count = cnt;}
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
@Override
public boolean hasNext() {
return count >0;
}
@Override
public Integer next() {
count--;
return fib2.next();
}
};
}
public static void main(String[] args) {
for (int i : new IterableFibonacci(10))
System.out.print(i + " ");
}
}
作业8,与coffee例子一样
package generics.e8;
import net.mindview.util.Generator;
import java.util.Iterator;
import java.util.Random;
class StoryCharacter{
private static long counter;
private final long id = counter++;
public String toString(){
return getClass().getSimpleName() + " " +id;
}
}
class GoodGuy extends StoryCharacter{
public String toString(){
return super.toString() + " is a good guy";
}
}
class BadGuy extends StoryCharacter{
public String toString(){
return super.toString() + " is a bad guy";
}
}
class Morton extends BadGuy{}
class Frank extends BadGuy{}
class Harmonica extends GoodGuy{}
class Cheyenne extends GoodGuy{}
class CharacterGenerator implements Generator<StoryCharacter>,Iterable<StoryCharacter>{
private Class[] types = {
Morton.class,
Frank.class,
Harmonica.class,
Cheyenne.class
};
private Random rand = new Random(47);
// for iteratation;
private int size;
public CharacterGenerator(){}
public CharacterGenerator(int count){
size = count;
}
@Override
public Iterator<StoryCharacter> iterator() {
return new Iterator<StoryCharacter>() {
@Override
public boolean hasNext() {
return size > 0;
}
@Override
public StoryCharacter next() {
size--;
return CharacterGenerator.this.next();
}
};
}
@Override
public StoryCharacter next() {
try {
return (StoryCharacter)(types[rand.nextInt(types.length)]).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class E08_CharacterGenerator {
public static void main(String[] args) {
CharacterGenerator cg= new CharacterGenerator();
for (int i = 0; i < 5; i++)
System.out.println(cg.next());
System.out.println("======");
for (StoryCharacter sc: new CharacterGenerator(5))
System.out.println(sc);
}
}
4 泛型方法
泛型方法所在的类可以是泛型类,也可以不是泛型类,
并且泛型标识符可以完全不一样,也就是说泛型方法和泛型类无关。
普通static方法无法访问泛型类的类型参数,如果要是使用泛型就要定义成泛型静态方法
定义泛型方法只需要将泛型参数列表置于返回值前
public
package generics;
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);
}
}
package generics.e10;
public class GenericMethods {
public <B,C> void f(String a,B b,C c) {//多个类型不同参数,非参数化类型
System.out.println(a.getClass().getName());
System.out.println(b.getClass().getName());
System.out.println(c.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("",1,gm);
}
}
4.1 杠杆利用类型参数推断
使用泛型方法时编译期会通过类型参数推断来为我们找出具体类型,而不必自己声明时什么类型
类型推断只对赋值操作有效,其它时候不起作用 JDK8不存在例子中编译问题
显示类型说明
package generics;
import net.mindview.util.New;
import typeinfo.pets.*;
import java.util.*;
public class LimitsOfInference {
static void
f(Map<Person, List<? extends Pet>> petPeople) {
}
public static void main(String[] args) {
f(New.<Person, List<? extends Pet>>map()); // 显示的类型说明
// 如果是在定义该方法的类的内部,需要this.
// static方法,需要 ClassName.
f(New.map()); // Does not compile JDK8 正常
}
}
4.2 可变参数与泛型方法
泛型方法与可变参数列表能够很好地共存
下面的方法展示了和类库java.util.Arrays.asList()方法相同的功能
package generics;
//: generics/GenericVarargs.java
import java.util.*;
public class GenericVarargs {
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
for (T item : args)
result.add(item);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
} /* Output:
[A]
[A, B, C]
[, A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
*///:~
4.3 用于Generator的泛型方法
package generics;
//: generics/Generators.java
// A utility to use with Generators.
import generics.coffee.*;
import java.util.*;
import net.mindview.util.*;
public class Generators {
public static <T> Collection<T>
fill(Collection<T> coll, Generator<T> gen, int n) { //Generator的泛型方法
for(int i = 0; i < n; i++)
coll.add(gen.next());
return coll;
}
public static void main(String[] args) {
Collection<Coffee> coffee = fill(
new ArrayList<Coffee>(), new CoffeeGenerator(), 4);
for(Coffee c : coffee)
System.out.println(c);
Collection<Integer> fnumbers = fill(
new ArrayList<Integer>(), new Fibonacci(), 12);
for(int i : fnumbers)
System.out.print(i + ", ");
}
} /* Output:
Americano 0
Latte 1
Americano 2
Mocha 3
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,
*///:~
4.4 一个通用的Generator
package generics;
import net.mindview.util.Generator;
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) {
throw new RuntimeException(e);
}
}
public static <T> Generator<T> create(Class<T> type){
return new BasicGenerator<T>(type);
}
}
package generics;
public class CountedObject {
private static long counter = 0;
private final long id = counter++;
public long id(){return id;}
public String toString(){
return "CountedObject " + id;
}
}
package generics;
import net.mindview.util.Generator;
public class BasicGeneratorDemo {
public static void main(String[] args) {
Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class);
// BasicGenerator<CountedObject> gen = new BasicGenerator(CountedObject.class); 等价
for (int i = 0; i < 5;i ++)
System.out.println(gen.next());
}
}
4.5 简化元组的使用
4.6一个Set实用工具
5 匿名内部类
泛型用于内部类及匿名内部类。
示例使用匿名内部类实现Generator接口
package generics;
import net.mindview.util.Generator;
import java.util.*;
class Customer {
private static long counter = 1;
private final long id = counter++;
//私有化构造方法,只能通过Generator 获取实例
private Customer() {
}
public String toString() {
return "Customer " + id;
}
// A method to produce Generator objects:
// Customer 对象生成器
// Generator 每次调用 都会创建 一个 Generator对象,但这不是必要的。
public static Generator<Customer> generator() {
return new Generator<Customer>() {
@Override
public Customer next() {
return new Customer();
}
};
}
}
class Teller {
private static long counter = 1;
private final long id = counter++;
private Teller() {
}
public String toString() {
return "Teller " + id;
}
// A single Generator object:
// 匿名内部类2
// 单例Generator对象:
// 可以对比Customer的generator方法 这里只会创建一个generator实例
public static Generator<Teller> generator =
new Generator<Teller>() {
@Override
public Teller next() {
return new Teller();
}
};
}
public class BankTeller {
public static void serve(Teller t, Customer c) {
System.out.println(t + " serves " + c);
}
public static void main(String[] args) {
Random rand = new Random(47);
Queue<Customer> line = new LinkedList<Customer>();
Generators.fill(line, Customer.generator(), 15);
List<Teller> tellers = new ArrayList<Teller>();
Generators.fill(tellers, Teller.generator, 4);
for (Customer c : line)//遍历line,随机取出teller与 Customer 按序输出
serve(tellers.get(rand.nextInt(tellers.size())), c);
}
}