使用链式实现堆栈
实现思想:这个例子使用了一个 末端标识 (end sentinel) 来判断栈何时为空。这个末端标识是在构造 LinkedStack 时创建的。然后,每次调用 push() 就会创建一个 Node<T> 对象,并将其链接到前一个 Node<T> 对象。当你调用 pop() 方法时,总是返回 top.item,然后丢弃当前 top 所指向的 Node<T>,并将 top 指向下一个 Node<T>,除非到达末端标识,这时就不能再移动 top 了。如果已经到达末端,程序还继续调用 pop() 方法,它只能得到 null,说明栈已经空了。
public class LinkedStackTest<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<>();
private void push(T item){
top = new Node<>(item, top);
}
private T pop(){
T item = top.item;
if (!top.end()){
top = top.next;
}
return item;
}
public static void main(String[] args) {
LinkedStackTest<String> linkedStackTest = new LinkedStackTest<>();
for (String s : "hello zheng world".split(" ")){
linkedStackTest.push(s);
}
String s;
while ((s=linkedStackTest.pop())!=null){
System.out.println(s);
}
}
}
设计一个可以随机返回持有特定对象的容器
public class RandomList <T> extends ArrayList<T> {
private Random random = new Random(47);
public T select(){
return get(random.nextInt(size()));
}
public static void main(String[] args) {
RandomList<String> rs = new RandomList<>();
Arrays.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add);
IntStream.range(0 , 11).forEach(i ->
System.out.println(rs.select()));
}
}
泛型也可以应用于接口。例如 生成器,这是一种专门负责创建对象的类。实际上,这是 工厂方法 设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。
import java.util.Iterator;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* @Author: zheng
* @Data :2020/5/14 20:21
*/
class Coffee {
private static long counter = 0;
private final long id = counter++;
@Override
public String toString() {
return getClass().getSimpleName() + " " + id;
}
}
class Latte extends Coffee {}
class Mocha extends Coffee {}
class Cappuccino extends Coffee {}
class Americano extends Coffee {}
class Breve extends Coffee {}
public class CoffeeSupplier implements Supplier<Coffee> ,Iterable<Coffee> {
private Random random = new Random(47);
private Class<?>[] types = {Latte.class,Mocha.class,Cappuccino.class,Americano.class,Breve.class};
private int size;
public CoffeeSupplier(){}
public CoffeeSupplier(int size){
this.size = size;
}
@Override
public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}
@Override
public Coffee get() {
Coffee cf = null;
try {
cf =(Coffee) types[random.nextInt(types.length)].newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return cf;
}
public class CoffeeIterator implements Iterator<Coffee>{
int count =size;
@Override
public boolean hasNext() {
return count>0;
}
@Override
public Coffee next() {
count--;
return CoffeeSupplier.this.get();
}
}
public static void main(String[] args) {
Stream.generate(new CoffeeSupplier())
.limit(5)
.forEach(System.out::println);
for (Coffee c : new CoffeeSupplier(5)){
System.out.println(c);
}
}
}
Java 泛型的一个局限性:基本类型无法作为类型参数。
泛型方法独立于类而改变方法。作为准则,请“尽可能”使用泛型方法。通常将单个方法泛型化要比将整个类泛型化更清晰易懂。
如果方法是 static 的,则无法访问该类的泛型类型参数,因此,如果使用了泛型类型参数,则它必须是泛型方法。
要定义泛型方法,请将泛型参数列表放置在返回值之前,如下所示:
/**
* @Author: zheng
* @Data :2020/5/14 20:46
*/
public class GenericMethods {
public <T> void f(T t){
System.out.println(t.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f(1);
gm.f(" ");
gm.f(25.22);
gm.f('s');
gm.f(gm);
}
}
对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 类型参数推断。
变长参数和泛型方法
@SafeVarargs注解:在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。
@SafeVarargs 注解保证我们不会对变长参数列表进行任何修改,这是正确的,因为我们只从中读取。如果没有此注解,编译器将无法知道这些并会发出警告。
import java.util.ArrayList;
import java.util.List;
/**
* @Author: zheng
* @Data :2020/5/14 20:56
*/
public class GenericVerge {
public static <T> List<T> makeList(T... args){
List<T> list = new ArrayList<>();
for (T t : args){
list.add(t);
}
return list;
}
public static void main(String[] args) {
List<String> list = makeList("A");
System.out.println(list);
list = makeList("Abcdft".split(""));
System.out.println(list);
}
}
一个泛型的 Supplier
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* @Author: zheng
* @Data :2020/5/14 21:09
*/
class BaseSupplier<T> implements Supplier<T>{
private Class<T> tClass;
public BaseSupplier(Class<T> type){
tClass = type;
}
@Override
public T get() {
T t = null;
try {
t = tClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return t;
}
public static <T> BaseSupplier create(Class<T> tClass){
return new BaseSupplier(tClass);
}
}
class CountDemo{
private static long i = 0;
private final long j = i++;
public long id(){
return j;
}
@Override
public String toString() {
return "CountDemo "+id();
}
}
public class BasicSupplierDemo {
public static void main(String[] args) {
Stream.generate(BaseSupplier.create(CountDemo.class))
.limit(5)
.map(n -> n+"")
.forEach(System.out::println);
}
}
此类提供了产生以下对象的基本实现:
1.是 public 的。 因为 BasicSupplier 在单独的包中,所以相关的类必须具有 public 权限,而不仅仅是包级访问权限。
2.具有无参构造方法。要创建一个这样的 BasicSupplier 对象,请调用 create() 方法,并将要生成类型的类型令牌传递给它。通用的 create() 方法提供了 BasicSupplier.create(MyType.class) 这种较简洁的语法来代替较笨拙的 new BasicSupplier <MyType>(MyType.class)。
返回一个未参数化的对象。编译器不会在这里警告 ,因为返回值未以参数化方式使用。从某种意义上说,它被“向上转型”为一个未参数化的 。 但是,如果尝试将未参数化的结果放入到参数化的对象中,则编译器将发出警告。
本示例使用 EnumSet 轻松从 enum 中创建 Set 。
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
/**
* @Author: zheng
* @Data :2020/5/14 21:59
*/
enum Watercolors {
ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW,
ORANGE, BRILLIANT_RED, CRIMSON, MAGENTA,
ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE,
PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE,
PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN,
YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER,
BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK
}
public class WatercolorSets {
public static void main(String[] args) {
Set<Watercolors> set1 = EnumSet.range(Watercolors.BRILLIANT_RED, Watercolors.VIRIDIAN_HUE);
System.out.println(set1);
}
}
接下来的例子展示 java.util 包中各种 Collection 和 Map 类之间的方法差异:
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @Author: zheng
* @Data :2020/5/16 22:49
*/
public class CollectionMethodDifferences {
static Set<String> methodSet(Class<?> type){
return Arrays.stream(type.getMethods())
.map(Method::getName)
.collect(Collectors.toCollection(TreeSet::new));
}
static <T> Set<T> difference(Set<T> superSet, Set<T> subSet){
Set<T> result = new HashSet<>(superSet);
result.removeAll(subSet);
return result;
}
static Set<String> object = methodSet(Object.class);
static {
object.add("clone");
}
static void differences(Class<?> supSet, Class<?> subSet){
System.out.println(supSet.getSimpleName()+" extends "+subSet.getSimpleName());
Set<String> comp = difference(methodSet(supSet), methodSet(subSet));
comp.removeAll(object);
System.out.println(comp);
interfaces(supSet);
}
static void interfaces(Class<?> type){
System.out.println("Interface::"+type.getSimpleName());
System.out.println(Arrays.stream(type.getInterfaces())
.map(Class::getSimpleName)
.collect(Collectors.toList()));
}
public static void main(String[] args) {
System.out.println("Collection:"+methodSet(Collection.class));
interfaces(Collection.class);
differences(Map.class, Collection.class);
}
}
泛型的一个重要好处是能够简单安全地创建复杂模型。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;
import java.util.function.Supplier;
/**
* @Author: zheng
* @Data :2020/5/17 23:14
*/
public class Product {
private final int id;
private String description;
@Override
public String toString() {
return "Product{" +
"id=" + id +
", description='" + description + '\'' +
", price=" + price +
'}';
}
private Double price;
Product(int id, String description, Double price){
this.id =id;
this.description = description;
this.price = price;
}
public static Supplier<Product> generate = new Supplier<Product>() {
private Integer i = 0;
Random random = new Random(47);
@Override
public Product get() {
return new Product(random.nextInt(1000),"Test"+ i++ ,
Math.round(random.nextDouble()*1000)+0.99);
}
};
public static void main(String[] args) {
System.out.println(new Store(4,5,9));
}
}
class Self extends ArrayList<Product>{
Self(int nProduce){
Suppliers.fill(this, Product.generate, nProduce);
}
}
class Aisle extends ArrayList<Self>{
Aisle(int nShelves, int nProduct){
for(int i =0 ; i <nShelves; i++){
add(new Self(nProduct));
}
}
}
class Store extends ArrayList<Aisle>{
Store(int nAisle, int nshelves, int nProduct){
for (int i =0 ;i<nAisle ; i++){
add(new Aisle(nshelves, nProduct));
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for (Aisle aisle : this){
for (Self self : aisle){
for (Product product : self){
System.out.println(product);
result = result.append(product);
System.out.println(result);
}
}
}
return result.toString();
}
}
泛型擦除
在泛型代码内部,无法获取任何有关泛型参数类型的信息。
Java 泛型是使用擦除实现的。这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。
因为擦除,Java 编译器无法将需求映射到 具体方法这个事实上。为了调用 方法,我们必须协助泛型类,给定泛型类一个边界,以此告诉编译器只能接受遵循这个边界的类型。这里重用了 extends 关键字。
泛型只有在类型参数比某个具体类型(以及其子类)更加“泛化”——代码能跨多个类工作时才有用。因此,类型参数和它们在有用的泛型代码中的应用,通常比简单的类替换更加复杂。但是,不能因此认为使用 <T extends HasF> 形式就是有缺陷的。例如,如果某个类有一个返回 T 的方法,那么泛型就有所帮助,因为它们之后将返回确切的类型:
public class ReturnGenericType<T extends HasF> {
private T obj;
ReturnGenericType(T x) {
obj = x;
}
public T get() {
return obj;
}
}
泛型的所有动作都发生在边界处——对入参的编译器检查和对返回值的转型。这有助于澄清对擦除的困惑,记住:“边界就是动作发生的地方”。
因为擦除,我们将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型:
成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。
由于擦除,数组的运行时类型只能是 Object[] 。 如果我们立即将其转换为 T[] ,则在编译时会丢失数组的实际类型,并且编译器可能会错过一些潜在的错误检查。因此,最好在集合中使用 Object[] ,并在使用数组元素时向 T 添加强制类型转换。
import java.lang.reflect.Array;
/**
* @Author: zheng
* @Data :2020/5/18 20:31
*/
public class GenericArrays<T> {
private Object[] array;
public GenericArrays(int size){
array = new Object[size];
}
public void put(int index, T item){
array[index] = item;
}
public T get(int index){
return (T)array[index];
}
@SuppressWarnings("unchecked")
public T[] rep(Class<T> tClass, int sz){
//return (T[]) array ;
return (T[])Array.newInstance(tClass, sz);
}
public static void main(String[] args) {
GenericArrays<Integer> gai = new GenericArrays<>(10);
for (int i =0; i <10 ; i++){
gai.put(i ,i);
}
for (int i = 0; i<10 ; i++){
System.out.println(gai.get(i));
}
Integer[] ia = null;
try {
ia = gai.rep(Integer.class, 15);
}catch (Exception e){
System.out.println(e.getMessage());
}
System.out.println(ia);
}
}
有一种特殊情况需要使用 <?> 而不是原生类型。如果向一个使用 <?> 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 @SuppressWarnings 注解被移除之后才能起作用:
本节将阐述在使用 Java 泛型时会出现的各类问题。
1.任何基本类型都不能作为类型参数
一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。下面是产生这种冲突的情况:
网友评论