*注:本文为本人结合网上资料翻译 Apache Spark 2.x for Java developers 一书而来,仅作个人学习研究之用,支持转载,但务必注明出处。
一、泛型(Generics)
1.简介
Generics在java 1.5中被引入。Generics帮助用户创建定义时拥有抽象类型的通用代码,那个抽象类型能够在实现时被任何一种具体类型所代替。
举个例子,list接口或者它的实现:ArrayList, LinkedList等等,用generics定义。使用者在实现表的时候可以提供如Integer, Long, 或 String等具体类型
List<Integer> list1 =new ArrayList<Integer>();
List<String> list2 =new ArrayList<String>();
这里,list 1是一个整型顺序表,list 2是一个字符串顺序表。从Java 7开始,编译器能够推断类型。所以前述代码也可如下编写:
List<Integer> list1 =new ArrayList<>();
List<String> list2 =new ArrayList<>();
泛型的另一个巨大优势是提供了编译阶段的安全性。让我们不是用generics来创建一个list:
List list =new ArrayList<>();
list.add(1);
list.add(2);
list.add("hello");
list.add("there");
你能发现这个list包含整型和字符串两种类型。如果你从中检索一个元素,它将缺省为object 类型
Object object = list.get(0);
所以使用者必须特地将其投射(cast)给正确的类型:
Integer object = (Integer)list.get(0);
这使得代码易于出错。因为如果投射正确,它在运行时将抛出ClassCastException。我们来使用generics创建同样的list:
List<Integer> listGeneric =new ArrayList<>();
listGeneric.add(1);
listGeneric.add(2);
listGeneric.add("hello"); // won't compile
listGeneric成为了整型的list,如果你尝试加入字符串,编译器不会编译。
因此,现在你从list中检索一个元素,它将给你返回一个整型元素。不需要进行类型投射,使我们在运行时不会抛出ClassCastException。因此,generics提供编译时的安全性。
Integer intObject = listGeneric.get(0);
2.创建你自己的泛型
在这部分,我们将用泛型类型T来创建一个类。类将由类型T的变量组成,并实现通用的方法来返回类型T的变量:
public class MyGeneric<T> {
T input;
public MyGeneric(T input) {
this.input=input;
}
public T getInput()
{
return input;
}
}
如同前面描述的, MyGeneric是一个类。它由类型T的变量输入组成,构造函数构造MyGenric类的对象并且初始化变量输入,还有一个返回T的值的一个方法。
现在,让我们来实现它:
public class MyGenericsDemo {
public static void main(String[] args) {
MyGeneric<Integer> m1 =new MyGeneric<Integer>(1);
System.out.println(m1.getInput());
//It will print '1'
MyGeneric<String> m2 =new MyGeneric<String>("hello");
System.out.println(m2.getInput());
//It will print 'hello'
}
}
这里,我们创建两个MyGeneric类的变量,一个是int类型,另一个是string类型。当我们对两个对象都调用getInput方法的时候,他们将返回他们各自类型的值。
二、接口(Interfaces)
1.简介
接口(Interfaces)是java中的引用类型(reference types)。它们被用来定义类之间的contracts(试着解释:面向对象的程序设计严格区分做什么和怎么做的概念。“做什么”被描述为一个方法集(有时还指公用数据)及其关联的语义。这种组合(方法,数据和语义)实际上是类的设计者和使用类的程序员之间的一种约定(contract),因为它说明了当调用对象上的某个方法时会发生什么。这个约定定义了一个类型,该类型的所有实例对象都被认为是遵从该约定的。)。任何类实现一个接口都必须粘附(adhere)接口定义的contract。
举个例子,在下面我们定义了一个由三个抽象方法组成的接口car:
public interface Car {
void shape();
void price();
void color();
}
任何实现这个接口的类必须实现这个接口所有的抽象方法,除非这个类是一个抽象类。接口只能被其他接口实现或继承,他们不能被实例化。
在java 8之前,接口只能由abstract方法和final变量组成。在java 8 中,接口中也可能包含default方法和static方法。
2.接口中的static方法
接口中的static方法类似于类中的static方法。使用者不能重写(override)他们。因此,即使一个类实现了一个接口,它不能重写接口中的static方法。
就像类中的static方法,接口中的静态方法通过使用接口名称或类类型引用来调用,而不使用实例变量:
public interface MyInterface {
static String hello()
{
return "Inside static method in interface";
}
void absmethod();
}
这是一个有抽象方法和静态方法的接口。
public class MyInterfaceImpl implements MyInterface{
@Override
public void absmethod() {
System.out.println("Abstract method implementaion in class");
}
}
这是一个类实现了前面给出的接口,因此粘附(adhere)了它(指这个类)——作为一个非抽象类——必须提供抽象方法的实现作为约定(contract)。然而,他并不需要实现静态方法。
现在,让我们调用抽象方法:
public class MyInterfaceDemo {
public static void main(String[] args) {
System.out.println(MyInterface.hello()); // it works
MyInterfaceImpl obj =new MyInterfaceImpl();
obj.hello(); // wont-complie
}
}
通过接口名引用调用静态方法是可行的,但是通过实现接口的类的实例变量来调用静态方法不可行。
3.接口中的默认类
default类是java 8 中提供的接口的另一个拓展。一个接口可以有default方法组成。实现接口的类可以实现也可以不实现default方法。default对于扩展一个已经存在的接口十分有用。假设你有一个接口已经被许多类实现。现在,你想要继承接口的功能。
第一个选择是在接口中增加一个新的抽象方法并强制每一个类去实现它,这个会打破现有的实现,并且需要编写很多新的代码。另一个选择是在接口中增加新的默认方法,如果某个类想要重写它们,可以自由地去做。在这个选择中,所有现有的实现不会被打破。在可迭代的接口中的foreach方法是一个使用默认方法扩展功能的经典例子。
default方法可以通过default关键字来创建。让我们将前述例子中的static方法改为default方法:
public interface MyInterface {
default String hello() {
return "Inside static method in interface";
}
void absmethod();
}
这个类实现这个接口,不需要提供对这个方法的实现。
public class MyInterfaceImpl implements MyInterface {
@Override
public void absmethod() {
System.out.println("Abstract method implementaion in class");
}
}
现在,让我们尝试调用default方法
public class MyInterfaceDemo {
public static void main(String[] args) {
System.out.println(MyInterface.hello()); // won't compile
MyInterfaceImpl obj =new MyInterfaceImpl();
obj.hello(); // works
}
}
注意default方法是实例方法。它们能够通过实现了接口的类的实例来调用。
4.如果一个类继承了两个接口,这两个接口有相同名称的default方法怎么办:
暂且略过
5.匿名内部类(Anonymous inner classes)
匿名内部类是Java中基本的内部类。因为它们没有名字所以被称为匿名。它们的声明和实例化是同时的。
无论你是需要提供对接口的实现还是重写一个类的抽象方法,都可以使用匿名内部类。当接口或抽象类的实现仅仅需要使用一次时,它们十分有效。
让我们来看看FilenameFilter接口, 这个接口从JDK1.0起就包含在java中了。
假设你想要打印一个库中所有名字以java结尾的文件。
第一个方法就是如下创建一个类实现了FilenameFilter接口:
public class MyFileNameFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return name.endsWith("java");
}
}
然后如下来使用你实现的这个类的对象:
public class MyFilterImpl {
public static void main(String[] args) {
File dir = new File("src/main/java");
dir.list(new MyFileNameFilter());
}
}
然而,也许你的代码中对这个接口的这个实现仅仅只需要使用这一次。因此,我们来创建一个单独的类,它匿名地实现这个接口,如下:
public class MyFilterImpl {
public static void main(String[] args) {
File dir = new File("src/main/java");
dir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith("java");
}
});
}
}
这是匿名内部类的一个实例。代码看起来也许显得很怪异,但是如果仅需要使用一次的话,这是一个非常简洁的实现接口或者抽象类的方法。
下期预告: Lambda表达式和词法域(Lexical scoping)
参考文献:
Apache Spark 2.x for Java developers
S Gulati,S Kumar - 2017 - 被引量: 0
Gulati, Sourav; Kumar, Sumit
http://bbs.csdn.net/topics/320074783
http://www.importnew.com/7302.html