答:泛型是 Java SE 1.5 的新特性,泛型的本质是参数化类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。在 Java SE 1.5 之前没有泛型的情况的下只能通过对类型 Object 的引用来实现参数的任意化,其带来的缺点是要做显式强制类型转换,而这种强制转换编译期是不做检查的,容易把问题留到运行时,所以 泛型的好处是在编译时检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率,避免在运行时出现 ClassCastException。
JDK 1.5 引入了泛型来允许强类型在编译时进行类型检查;JDK 1.7 泛型实例化类型具备了自动推断能力,譬如 List list = new ArrayList(); 可以写成 List llist = new ArrayList<>(); 了,JDK 具备自动推断能力。下面几种写法可以说是不同版本的兼容性了:
//JDK 1.5 推荐使用的写法
List list = new ArrayList();
//JDK 1.7 推荐使用的写法
List llist = new ArrayList<>();
//可以使用,但不推荐,是为了兼容老版本,IDE 会提示警告,可以通过注解屏蔽警告
List list = new ArrayList();
//可以使用,但不推荐,是为了兼容老版本,IDE 会提示警告,可以通过注解屏蔽警告
List list = new ArrayList();
2. 问:Java 泛型是如何工作的?什么是类型擦除?
答:泛型是通过类型擦除来实现的,编译器在编译时擦除了所有泛型类型相关的信息,所以在运行时不存在任何泛型类型相关的信息,譬如 List 在运行时仅用一个 List 来表示,这样做的目的是为了和 Java 1.5 之前版本进行兼容。泛型擦除具体来说就是在编译成字节码时首先进行类型检查,接着进行类型擦除(即所有类型参数都用他们的限定类型替换,包括类、变量和方法),接着如果类型擦除和多态性发生冲突时就在子类中生成桥方法解决,接着如果调用泛型方法的返回类型被擦除则在调用该方法时插入强制类型转换。
3.问:Java 泛型类、泛型接口、泛型方法有什么区别?
答:泛型类是在实例化类的对象时才能确定的类型,其定义譬如 class Test {},在实例化该类时必须指明泛型 T 的具体类型。
泛型接口与泛型类一样,其定义譬如 interface Generator { E dunc(E e); }。
因为 load 的是同一个 class 文件,存在 ArrayList.class 文件但是不存在 ArrayList.class 文件,即便是通过 class.getTypeParameters() 方法获取类型信息也只能获取到 [T] 一样的泛型参数占位符。泛型是通过擦除来实现的,所以编译后任何具体的泛型类型都被擦除了(替换为非泛型上边界,如果没有指定边界则为 Object 类型),泛型类型只有在静态类型检查期间才出现,上面都被擦除成了 ArrayList 类型,所以运行时加载的是同一个 class 文件。
6. 问:为什么 Java 泛型要通过擦除来实现?擦除有什么坏处或者说代价?
答:可以说 Java 泛型的存在就是一个不得已的妥协,正因为这种妥协导致了 Java 泛型的混乱,甚至说是 JDK 泛型设计的失败。Java 之所以要通过擦除来实现泛型机制其实是为了兼容性考虑,只有这样才能让非泛化代码到泛化代码的转变过程建立在不破坏现有类库的实现上。正是因为这种兼容也带来了一些代价,譬如泛型不能显式地引用运行时类型的操作之中(如向上向下转型、instanceof 操作等),因为所有关于参数的信息都丢失了,所以任何时候使用泛型都要提醒自己背后的真实擦除类型到底是什么;此外擦除和兼容性导致了使用泛型并不是强制的(如 List list = new ArrayList(); 等写法);其次擦除会导致我们在编写代码时十分谨慎(如不想被擦除为 Object 类型时不要忘了添加上边界操作等)。
7. 问:下面三个 funcX 方法有问题吗,为什么?
class Product {
privatevoid func1(Object arg) {
if (arg instanceof T) {}
}
privatevoid func2() {
T var = new T();
}
privatevoid func3() {
T[] vars = new T[3];
}
}
List[] lsa = new List[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List li = new ArrayList();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
List>[] lsa = new List>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List li = new ArrayList();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK
答:这个无论我们通过 new ArrayList[10] 的形式还是通过泛型通配符的形式初始化泛型数组实例都是存在警告的,也就是说仅仅语法合格,运行时潜在的风险需要我们自己来承担,因此那些方式初始化泛型数组都不是最优雅的方式,我们在使用到泛型数组的场景下应该尽量使用列表集合替换,此外也可以通过使用 java.lang.reflect.Array.newInstance(Class componentType, int length) 方法来创建一个具有指定类型和维度的数组,如下:
publicclass ArrayWithTypeToken {
private T[] array;
public ArrayWithTypeToken(Class type, int size) {
array = (T[]) Array.newInstance(type, size);
}
publicvoid put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
} public T[] create() {
return array;
}
}
ArrayWithTypeToken arrayToken = new ArrayWithTypeToken(Integer.class, 100);
Integer[] array = arrayToken.create();
所以使用反射来初始化泛型数组算是优雅实现,因为泛型类型 T 在运行时才能被确定下来,我们能创建泛型数组也必然是在 Java 运行时想办法,而运行时能起作用的技术最好的就是反射了。
13. 问:Java 泛型对象能实例化 T t = new T() 吗,为什么?
答:不能,因为在 Java 编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件,所以自然就不行了,此外由于 T 被擦除为 Object,如果可以 new T() 则就变成了 new Object(),失去了本意。如果要实例化一个泛型 T 则可以通过反射实现(实例化泛型数组也类似),如下:
static T newTclass(Class clazz) throws InstantiationException, IllegalAccessException {
T obj = clazz.newInstance();
return obj;
}
答:限定通配符对类型进行限制,泛型中有两种限定通配符,一种是 extends T> 来保证泛型类型必须是 T 的子类来设定泛型类型的上边界,另一种是 super T> 来保证泛型类型必须是 T 的父类来设定类型的下边界,泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。非限定通配符 > 表示可以用任意泛型类型来替代,可以在某种意义上来说是泛型向上转型的语法格式,因为 List 与 List 不存在继承关系。
答:有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符,这两个 List 的声明都是限定通配符的例子,List extends T> 可以接受任何继承自 T 的类型的 List,而 List super T> 可以接受任何 T 的父类构成的 List。例如 List extends Number> 可以接受 List 或 List。Java 容器类的实现中有很多这种用法,比如 Collections 中就有如下一些方法:
publicstaticextends Comparable super T>> void sort(List list)
publicstaticvoid sort(List list, Comparator super T> c)
publicstaticvoid copy(List super T> dest, List extends T> src)
publicstatic T max(Collection extends T> coll, Comparator super T> comp)
18. 问:说说 和 extends E> 有什么区别?
答:它们用的地方不一样, 用于定义类型参数,声明了一个类型参数 T,可放在泛型类定义中类名后面、接口后面、泛型方法返回值前面。 extends E> 用于实例化类型参数,用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是 E 或 E 的某个子类型。虽然它们不一样,但两种写法经常可以达到相同的目的,譬如:
publicvoid addAll(Bean extends E> c)
publicextends E> void addAll(Bean c)
19. 问:说说 List 与 List 的关系和区别?
答:这两个东西没有关系只有区别。
因为也许很多人认为 String 是 Object 的子类,所以 List 应当可以用在需要 List 的地方,但是事实并非如此,泛型类型之间不具备泛型参数类型的继承关系,所以 List 和 List 没有关系,无法转换。
20. 问:下面两个代码片段有问题吗,为什么?
//Part 1
List obj = new ArrayList(); obj.add("I love Android!");
//Part 2
Object[] objArray = new Long[1]; objArray[0] = "I love Android!";
答:上面 Part 1 编译出错,Part 2 编译 OK,运行出错。
因为 List 和 ArrayList 没有继承关系,而 Java 的数组是在运行时类型检查的。
21. 问:如何把 int 值放入 ArrayList list = new ArrayList(); 的 list 列表中?
答:本题实质为泛型的擦除,不过答案比较多,常见的一种是通过兼容性,一种是通过反射的特性来处理。
通过泛型擦除兼容性实现如下:
ArrayList list = new ArrayList();
ArrayList list1 = list;
list1.add(12);
System.out.println(list1.get(0));
通过反射实现如下:
ArrayList list = new ArrayList();
Class clazz = list.getClass();
Method m = clazz.getMethod("add", Object.class);
m.invoke(list, 100);
由于在运行期间无法通过 getClass() 得知 T 的具体类型,所以 Gson 通过借助 TypeToken 类来解决这个问题,使用样例如下:
ArrayList list = new ArrayList();
list.add("java");
Type type = new TypeToken>(){}.getType();
String gStr = new Gson().toJson(list, type);
ArrayList gList = new Gson().fromJson(gStr, type);
通过上面的使用样例我们会发现使用 Gson 解析转换的 Bean 不存在特殊的构造方法,因此可以排除在泛型类的构造方法中显示地传入泛型类的 Class 类型作为这个泛型类的私有属性来保存泛型类的类型信息的实现方案,所以通过源码分析发现 Gson 使用了另一种方式来获取泛型的类型参数,其方法依赖 Java 的 Class 字节码中存储的泛型参数信息,Java 的泛型机制虽然在编译期间进行了擦除,但是在编译 Java 源代码成 class 文件中还是保存了泛型相关的信息,这些信息被保存在 class 字节码的常量池中,使用了泛型的代码处会生成一个 signature 签名字段,通过签名 signature 字段指明这个常量池的地址,JDK 提供了方法去读取这些泛型信息的方法,然后再借助反射就可以获得泛型参数的具体类型,具体实现原理如下:
Type mySuperClass = new ArrayList(){}.getClass().getGenericSuperclass();
Type type = ((ParameterizedType) mySuperClass).getActualTypeArguments()[0];
System.out.println(type);
所以获取泛型参数类型的实质就是通过 Class 类的 getGenericSuperClass() 方法返回一个 ParameterizedType 对象(对于 Object、接口和原始类型返回 null,对于数组 class 返回 Object.class),ParameterizedType 表示带有泛型参数类型的 Java 类型,JDK1.5 引入泛型后 Java 中所有的 Class 都实现了 Type 接口,ParameterizedType 继承了 Type 接口,所有包含泛型的 Class 类都会自动实现这个接口。
关于 class 文件中存储泛型参数类型的详细信息可以参考:http://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files
24. 问:下面程序的输出是什么?为什么?
publicclass Demo {
publicstaticvoid main(String[] args) throws Exception {
ParameterizedType type = (ParameterizedType) Bar.class.getGenericSuperclass();
System.out.println(type.getActualTypeArguments()[0]);
ParameterizedType fieldType = (ParameterizedType) Foo.class.getField("children").getGenericType();
System.out.println(fieldType.getActualTypeArguments()[0]);
ParameterizedType paramType = (ParameterizedType) Foo.class.getMethod("foo", List.class).getGenericParameterTypes()[0];
System.out.println(paramType.getActualTypeArguments()[0]);
System.out.println(Foo.class.getTypeParameters()[0].getBounds()[0]);
}
class Fooextends CharSequence> {
public List children = new ArrayList();
public List foo(List foo) {
returnnull;
}
publicvoid bar(List extends String> param) {
//empty
}
}
class Bar extends Foo {}
}
答:其运行结果如下。
class java.lang.String
class Demo$Bar
class java.lang.String
interface java.lang.CharSequence
openjdk上关于hotspot将移除永久带的描述非常详细,http://openjdk.java.net/jeps/122
JEP 122: Remove the Permanent Generation
Author Jon Masamitsu
Organization Oracle
Created 2010/8/15
Updated 2011/
PL/SQL也是一门高级语言,所以流程控制是必须要有的,oracle数据库的pl/sql比sqlserver数据库要难,很多pl/sql中有的sqlserver里面没有
流程控制;
分支语句 if 条件 then 结果 else 结果 end if ;
条件语句 case when 条件 then 结果;
循环语句 loop
/*
*开发子程序
*/
--子程序目是指被命名的PL/SQL块,这种块可以带有参数,可以在不同应用程序中多次调用
--PL/SQL有两种类型的子程序:过程和函数
--开发过程
--建立过程:不带任何参数
CREATE OR REPLACE PROCEDURE out_time
IS
BEGIN
DBMS_OUTPUT.put_line(systimestamp);
E
long 长的
show 给...看,出示
mouth 口,嘴
write 写
use 用,使用
take 拿,带来
hand 手
clever 聪明的
often 经常
wash 洗
slow 慢的
house 房子
water 水
clean 清洁的
supper 晚餐
out 在外
face 脸,