网站备案 时间更新,江西住房和城乡建设信息网站,做棋牌网站抓到会怎么量刑,佛山建网站费用目录
潜在类型机制
支持潜在类型机制的语言
Python的潜在类型机制
C的潜在类型机制
Java中的直接潜在类型机制
潜在类型机制的替代方案
反射
将方法应用于序列中的每个元素
Java 8的潜在类型机制#xff08;间接实现#xff09;
潜在类型机制的使用例#xff08;S…目录
潜在类型机制
支持潜在类型机制的语言
Python的潜在类型机制
C的潜在类型机制
Java中的直接潜在类型机制
潜在类型机制的替代方案
反射
将方法应用于序列中的每个元素
Java 8的潜在类型机制间接实现
潜在类型机制的使用例Suppliers
总结 本笔记参考自 《On Java 中文版》 潜在类型机制 通过泛型我们应该可以向“将代码写得更通用一点”这一理念更进一步。特别是在编写简单的Java泛型时泛型可以在不了解具体类型的情况下执行方法。然而随着对Java泛型的了解逐渐深入类型擦除无疑使泛型的作用打了折扣并限制了“泛型”这一概念。 一些语言会提供潜在类型机制又称结构化类型机制它还有一个更有意思的名称鸭子类型机制。比方说“如果某件事物走路像鸭子说话也像鸭子那么就可以把它看做鸭子”。 与泛型不同潜在类型机制只对方法本身有所要求而不需要实现特别的类或接口。 潜在类型机制可以超越类的层次结构调用不属于某个公共接口的方法。
支持潜在类型机制的语言 有许多语言支持潜在类型机制例如Python、C和Go等。
Python的潜在类型机制 先看看Python中的潜在类型机制
【例子Python中的潜在类型机制】
class Dog:def speak(self):print(汪)def sit(self):print(坐着)def reproduce(self):passclass Robot:def speak(self):print(锵)def sit(self):print(咔)def oilChange(self):passdef perform(anything):anything.speak()anything.sit()a Dog()
b Robot()
perform(a)
perform(b) 程序执行的结果是 在perform(anything)中并不包含任何关于anything的类型信息anything只是一个标识符。在后台anything相当于一个被隐藏的接口它包含了perform()要求的操作。但我们无需显式地表明它因为它是潜在的。 而如果向perform()传入不支持操作的对象就会报错 ----------
C的潜在类型机制 同样可以用C来实现上面的例子
【例子C中的潜在类型机制】
#includeiostream
using namespace std;class Dog {
public:void speak() {cout 汪 endl;}void sit() {cout 坐着 endl;}
};class Robot {
public:void speak() {cout 锵 endl;}void sit() {cout 咔 endl;}void oilChange() {}
};templateclass T void perform(T anything) {anything.speak();anything.sit();
}int main() {Dog d;Robot r;perform(d);perform(r);
} 程序执行的结果相同 若试图传入错误的类型编译器也会报错。不同与PythonC会在运行时抛出错误尽管C的错误信息出了名的冗长。但这两门语言都保证了类型不会错用。
---------- 也可以使用Go实现这个例子
【例子Go中的潜在类型机制】
package main
import fmttype Dog struct{}
func (this Dog) speak() {fmt.Printf(汪\n)
}
func (this Dog) sit(){fmt.Printf(坐着\n)
}
func (this Dog) reproduce(){
}type Robot struct{}
func (this Robot) speak() {fmt.Printf(锵\n)
}
func (this Robot) sit(){fmt.Printf(咔\n)
}
func (this Robot) oilChange(){
}func perform(speaker interface {speak(); sit()}){speaker.speak()speaker.sit()
}func main(){perform(Dog{})perform(Robot{})
} 程序会得到相同的结果 Java中的直接潜在类型机制 Java的泛型加入得较晚因此没有支持潜在类型机制。因此若要在Java中实现上面所述的效果通常会需要使用接口并且使用边界
【例子通过接口模拟潜在类型机制】
import reflection.pets.Dog;class PerformingDog extends Dog implements Performs {Overridepublic void speak() {System.out.println(汪);}Overridepublic void sit() {System.out.println(坐下);}public void reproduce() {}
}class Robot implements Performs {Overridepublic void speak() {System.out.println(锵);}Overridepublic void sit() {System.out.println(咔);}public void oilChange() {}
}class Communicate {// 通过边界调用接口的方法public static T extends Performsvoid perform(T performer) {performer.speak();performer.sit();}
}public class DogsAndRobots {public static void main(String[] args) {Communicate.perform(new PerformingDog());Communicate.perform(new Robot());}
} 程序执行的结果是 然而仔细考虑这种做法会发现Communicate.perform()并不需要使用到泛型它可以直接使用Performs接口
class Communicate {public static void perform(Performs performer) {performer.speak();performer.sit();}
}
说到底无论是PerformingDog还是Robot都已经强制实现了Performs接口。 潜在类型机制的替代方案 尽管Java并没有直接支持潜在类型机制但我们依旧可以想办法创建出真正意义上的泛型代码实现方法的跨层次应用。
反射 一个方案是使用反射
【例子使用反射创建泛型代码】
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;// 这个类没有实现Perform接口
class Mime {public void walkAgainstTheWind() {}public void sit() {System.out.println(假装坐着);}public void pushInvisibleWalls() {}Overridepublic String toString() {return 哑剧;}
}class SmartDog {public void speak() {System.out.println(汪);}public void sit() {System.out.println(坐下);}public void reproduce() {}
}class CommunicateReflectively {public static void perform(Object speaker) {Class? spkr speaker.getClass();try {try {Method speak spkr.getMethod(speak);speak.invoke(speaker);} catch (NoSuchMethodException e) {System.out.println(speaker 没法说话);}try {Method sit spkr.getMethod(sit);sit.invoke(speaker);} catch (NoSuchMethodException e) {System.out.println(speaker 无法坐下);}} catch (SecurityException |IllegalAccessException |IllegalArgumentException |InvocationTargetException e) {throw new RuntimeException(speaker.toString(), e);}}
}public class LatentReflection {public static void main(String[] args) {CommunicateReflectively.perform(new SmartDog());CommunicateReflectively.perform(new Robot());CommunicateReflectively.perform(new Mime());}
} 程序执行的结果是 在这里SmartDog、Robot和Mime之间没有任何的直接联系我们直接通过反射动态调用speak()和sit()。 将方法应用于序列中的每个元素 还可以进一步地开发反射。假设我们需要为一个序列中的每个元素应用方法只使用接口仍是不够的因此我们可以这样做
【例子将一个方法应用于序列】
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class Apply {public static T, S extends IterableTvoid apply(S seq, Method f, Object... args) {try {for (T t : seq)f.invoke(t, args);} catch (IllegalAccessException |IllegalArgumentException |InvocationTargetException e) {throw new RuntimeException(e); // 可能会因为不正确的使用该方法导致异常}}
} apply()方法可以接受任意数量的序列元素并将方法f()应用于所有元素。这种做法有一个好处f.invoke()可以接受任意长度的序列因此可以认为apply()也可以做到这点。 除此之外apply()方法使用了for-in语法。 这表示着S可以是任何实现了Iterable接口的类。 接下来就对apply()方法进行测试
【例子apply()方法的使用例】 首先创建一个简单的继承结构这个结构包含一个父类Shape和一个子类Square。
父类Shape
public class Shape {private static long counter 0;private final long id counter;Overridepublic String toString() {return getClass().getSimpleName() id;}public void rotate() {System.out.println(this rotate);}public void resize(int newSize) {System.out.println(this resize newSize);}
}
子类Square
public class Square extends Shape {} 接下来的就是测试类ApplyTest。
ApplyTest
import onjava.Suppliers;import java.util.ArrayList;
import java.util.List;public class ApplyTest {public static void main(String[] args)throws Exception {ListShape shapes Suppliers.create(ArrayList::new, Shape::new, 3);Apply.apply(shapes,Shape.class.getMethod(rotate));Apply.apply(shapes,Shape.class.getMethod(resize, int.class), 7);System.out.println();ListSquare squares Suppliers.create(ArrayList::new, Square::new, 3);Apply.apply(squares,Shape.class.getMethod(rotate));Apply.apply(squares,Shape.class.getMethod(resize, int.class), 7);System.out.println();Apply.apply(new FilledList(Shape::new, 3),Shape.class.getMethod(rotate));Apply.apply(new FilledList(Square::new, 3),Shape.class.getMethod(rotate));}
} 程序执行的结果是 首先解释Suppliers.create()看如下代码 这行代码等价于将Shape类的构造器作为生成器生成3个对象并将结果放入一个ArrayList对象中。 尽管反射可以使代码看起来很优雅但要注意反射的运行速度通常会慢于非反射的实现。究其原因反射在运行时处理了太多东西。尽管我们不应该只因为这个理由放弃使用反射但这无疑是我们必须考虑的一点。 现在考虑Java 8加入的函数式方式下面的例子通过它重写了ApplyTest
【例子使用函数式方式重写ApplyTest】
import java.util.stream.Stream;public class ApplyFunctional {public static void main(String[] args) {Stream.of(Stream.generate(Shape::new).limit(2),Stream.generate(Square::new).limit(2)).flatMap(c - c) // 将所有元素扁平化合成一条流.peek(Shape::rotate).forEach(s - s.resize(7));System.out.println();new FilledList(Shape::new, 2).forEach(Shape::rotate);new FilledList(Square::new, 2).forEach(Shape::rotate);}
} 程序执行的结果是 这种重写方式已经抛弃了Apply.apply()了。 这么做的好处是它更加简洁、可读性高并且不会抛出异常。因此现在可以这么说我们只需要在某些只能使用反射来解决的场景中使用反射就好了。 Java 8的潜在类型机制间接实现 Java 8带来的未绑定方法引用可以在某种程度上实现潜在类型机制。
【例子使用方法引用实现潜在类型机制】
import reflection.pets.Dog;import java.util.function.Consumer;class PerformingDogA extends Dog {public void speak() {System.out.println(汪);}public void sit() {System.out.println(坐下);}public void reproduce() {}
}class RobotA {public void speak() {System.out.println(锵);}public void sit() {System.out.println(咔);}public void oilChange() {}
}class CommunicateA {public static P void perform(P performer, ConsumerP action1, ConsumerP action2) {action1.accept(performer);action2.accept(performer);}
}public class DogsAndRobotMethodReferences {public static void main(String[] args) {CommunicateA.perform(new PerformingDogA(),PerformingDogA::speak, PerformingDogA::sit);CommunicateA.perform(new RobotA(),RobotA::speak, RobotA::sit);CommunicateA.perform(new Mime(),Mime::walkAgainstTheWind, Mime::pushInvisibleWalls);}
} 程序执行结果是 因为CommunicateA.perform()没有对P做出限制因此它可以是任何类型。perform()只要求提供可供ConsumerP使用的方法。因此我们可以向其中传入任何与其签名一致的方法。 然而和真正的潜在类型机制相比这种做法需要我们显式地提供perform()会用到的方法引用。 但这种方式也有更好的一点它其实不会对传入其中的方法名做出要求。在这个意义上这种方法要更加通用。
潜在类型机制的使用例Suppliers 现在可以通过潜在类型机制创建Suppliers这个类在之前的笔记中也曾出现。这个类的方法都用于填充集合
【例子潜在类型机制的使用例】
import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Stream;public class Suppliers {// 根据factory创建一个新的集合并且填充它public static T, C extends CollectionT Ccreate(SupplierC factory, SupplierT gen, int n) {return Stream.generate(gen).limit(n).collect(factory, C::add, C::addAll);}// 填充已存在的集合collpublic static T, C extends CollectionTC fill(C coll, SupplierT gen, int n) {Stream.generate(gen).limit(n).forEach(coll::add);return coll;}// 使用到未绑定方法引用adder可以生成更加通用的方法public static H, A H fill(H holder, BiConsumerH, A adder,SupplierA gen, int n) {Stream.generate(gen).limit(n).forEach(a - adder.accept(holder, a));return holder;}
} 在第一个fill()方法中我们返回了coll即传入的容器的类型信息这样就能保证不会丢失类型信息了。 第二个fill()方法使用了未绑定的方法引用adder。通过adder.accept()我们可以将操作a应用于对象holder。 接下来可以尝试使用Suppliers了。
【例子使用Suppliers】
import onjava.Suppliers;import java.util.ArrayList;
import java.util.List;class Customer {private static long counter 1;private final long id counter;Overridepublic String toString() {return Customer id;}
}class Teller {private static long counter 1;private final long id counter;Overridepublic String toString() {return Teller id;}
}class Bank {private ListBankTeller tellers new ArrayList();public void put(BankTeller bt) {tellers.add(bt);}
}public class BankTeller {public static void serve(Teller t, Customer c) {System.out.println(t 服务于 c);}public static void main(String[] args) {// 使用create()RandomListTeller tellers Suppliers.create(RandomList::new, Teller::new, 4);// 演示第一个fill()ListCustomer customers Suppliers.fill(new ArrayList(), Customer::new, 12);customers.forEach(c -serve(tellers.select(), c));// 演示潜在类型机制Bank bank Suppliers.fill(new Bank(), Bank::put, BankTeller::new, 3);// 或使用第二个fill()ListCustomer customers2 Suppliers.fill(new ArrayList(), List::add, Customer::new, 12);}
} 程序执行的结果是 注意第二个fill()的两个使用我们不仅可以将有所关联的Bank类型还可以将其用于List。 总结 尽管Java中加入的泛型存在着一些问题但在此之前我们需要注意一点无论一门语言多么强大都有可能被用来编写一个无比糟糕的程序。泛型常常被用于集合之类的场景但除此之外泛型还能做到什么 泛型的概念应该就像它的名字一样追求的是一种更加“泛型泛化”的代码使一种代码能够适应更多的场景。