Long Luo's Life Notes

每一天都是奇迹

By Long Luo

最近在部门内部做了一次知识分享,关于Java反射,因此有了这篇文章:《5分钟学会Java反射》。这篇文章篇幅不长,用了大量示例,力求在很短的时间里让大家明白Java反射知识。

关于 Java 反射,我们需要弄懂以下几个问题:

  1. 反射是什么?
  2. 反射有什么用?
  3. 怎么用反射?

下面我们来一一进行讲解:

一、反射是什么?

Reflection 的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性及方法;对于任何一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象的方法的功能称为Java的反射机制。

1. 自省(Introspection) vs. 反射(Reflection)

反射经常和自省弄混,为了区别,我们先看看两者的详细定义:

自省(Introspection):

Introspection is the ability of a program to examine the type or properties of an object at runtime.

反射(Reflection):

Reflection is the ability of a program to examine and modify the structure and behavior of an object at runtime.

从上述定义,我们可以看出,自省是反射的子集。部分语言支持自省,但是不支持反射,比如C++。

2. 自省示例 vs. 反射示例

自省示例: instanceof操作符用于判断一个对象是否属于一个特定的类。

1
2
3
4
if(obj instanceof Dog) {
Dog d = (Dog)obj;
d.bark();
}

反射实例: Class.forName()方法返回了一个具体类/接口的对象,当然参数需要指定为特定的类名。

1
2
3
4
5
6
// with reflection
Class <?> c = Class.forName("classpath.and.classname");
Object dog = c.newInstance();

Method m = c.getDeclaredMethod("bark", new Class<?>[0]);
m.invoke(dog);

二、 为什么需要反射?

Java反射在框架开发中尤为重要。有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如JDBC),这是反射用得比较多的场景。

编译时我们对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。比如ORM框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。

反射机制提供的功能:

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法;
  4. 在运行时调用任意一个对象的方法。通过反射甚至可以调用到private的方法;
  5. 在运行时修改构造函数,变量和方法的访问权限。

解耦

假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译

在对类的调用和实例化的时候,通过在配置文件中配置相应的类名,在程序中读取类名,然后通过反射技术在程序中加载和实例化,如常见的数据库驱动程序类,为了达到不依赖特定数据库驱动类,将用到的数据库驱动类名放到配置文件中(常用的有XML文件、Properties文件和文本文件),然后在程序中加载驱动,来实现对数据库的解耦,也就是说只要修改配置文件,就可以方便地更改数据库类型。

例如, Spring使用如下的bean配置:

1
2
3
<bean id="someID" class="com.programcreek.Foo">
<property name="someField" value="someValue"/>
</bean>

Spring在处理<bean>时,会使用Class.forName(String),同时参数为"com.xxx.Foo"用于实例化这个Class。同时,使用反射设置<property>去用于设置特定的值。

这种机制同样也用于Servlet的web应用:

1
2
3
4
<servlet>
<servlet name>someServlet </s e rvl e t􀀀name>
<servlet class>com.programcreek.WhyReflectionServlet</servlet class>
<servlet>
阅读全文 »

By Long Luo

一个类的定义放在另一个类的定义内部,这就是内部类

先来段代码,对内部类有个直观认识:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class OuterClass {
private String name ;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

class InnerClass{
public InnerClass(){
name = "chenssy";
age = 23;
}
}
}

在这里InnerClass就是内部类。

Java中有4种不同类型的Java内部类,下面我们将一一用实例来介绍:

1. 静态内部类(static nested classes)

关键字static可以修饰成员变量、方法、代码块,还可以修饰内部类,而使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。

静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:

  1. 它的创建是不需要依赖于外围类的。
  2. 它不能使用任何外围类的非static成员变量和方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Outer {
static class Inner {
void go() {
System.out.println(" Inner class reference is: " + this);
}
}
}

public class Test {
public static void main(String[] args) {
Outer.Inner n = new Outer.Inner();
n.go();
}
}

Output:

1
Inner class reference is: com.longluo.java.interview.innerclass.Outer$Inner@15db9742
阅读全文 »

翻译 By Long Luo

本文翻译自The Interface and Class Hierarchy Diagram of Java Collections,主要通过一系列简单易懂的图片让你迅速了解Java容器类,容器接口以及类层级关系。

大段文字会看得很烦,图片才是王道!

一、 Collection vs Collections

“Collection”和”Collections”是2个完全不同的概念,在Java容器的类层级图中,“Collection”是一个根接口,但是”Collections”仅仅只是一个提供多种静态方法的类用于操作一些Collection类型。

Collection Vs Collections

二、 Collection的类层级图

下图展示了Collection的类层级图:

java-collection-hierarchy

三、 Map的类层级图

下图是一张Map的类层级图:

MapClassHierarchy

四、 总结

collection summary

五、 代码示例

下面展示容器类的一个代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
List<String> a1 = new ArrayList<String>();
a1.add("Program");
a1.add("Creek");
a1.add("Java");
a1.add("Java");
System.out.println("ArrayList Elements");
System.out.print("\t" + a1 + "\n");

List<String> l1 = new LinkedList<String>();
l1.add("Program");
l1.add("Creek");
l1.add("Java");
l1.add("Java");
System.out.println("LinkedList Elements");
System.out.print("\t" + l1 + "\n");

Set<String> s1 = new HashSet<String>(); // or new TreeSet() will order the elements;
s1.add("Program");
s1.add("Creek");
s1.add("Java");
s1.add("Java");
s1.add("tutorial");
System.out.println("Set Elements");
System.out.print("\t" + s1 + "\n");

Map<String, String> m1 = new HashMap<String, String>(); // or new TreeMap() will order based on keys
m1.put("Windows", "2000");
m1.put("Windows", "XP");
m1.put("Language", "Java");
m1.put("Website", "programcreek.com");
System.out.println("Map Elements");
System.out.print("\t" + m1);

输出如下:

1
2
3
4
5
6
7
8
ArrayList Elements
[Program, Creek, Java, Java]
LinkedList Elements
[Program, Creek, Java, Java]
Set Elements
[tutorial, Creek, Program, Java]
Map Elements
{Windows=XP, Website=programcreek.com, Language=Java}

以上!

In general, Map is a data structure consisting of a set of key-value pairs, and each key can only appears once in the map. This post summarizes Top 9 FAQ of how to use Java Map and its implemented classes. For sake of simplicity, I will use generics in examples. Therefore, I will just write Map instead of specific Map. But you can always assume that both the K and V are comparable, which means K extends Comparable and V extends Comparable.

1. Convert a Map to List

In Java, Map interface provides three collection views: key set, value set, and keyvalue set. All of them can be converted to List by using a constructor or addAll() method. The following snippet of code shows how to construct an ArrayList from a map.

1
2
3
4
5
6
// key list
List keyList = new ArrayList(map.keySet());
// value list
List valueList = new ArrayList(map.valueSet());
// key value list
List entryList = new ArrayList(map.entrySet());

2. iterate over each entry in a map

Iterating over every pair of key-value is the most basic operation to traverse a map. In Java, such pair is stored in the map entry called Map.Entry.

3. SORT A MAP ON THE KEYS

Map.entrySet() returns a key-value set, therefore the most efficient way of going through every entry of a map is

1
2
3
4
5
6
for (Entry entry:map.entrySet()) {
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}

Iterator can also be used, especially before JDK 1.5

1
2
3
4
5
6
7
8
Iterator itr = map.entrySet().iterator();
while(itr.hasNext ( ) ) {
Entry ent ry = i t r . next ( ) ;
/ / g e t k ey
K key = ent ry . getKey ( ) ;
/ / g e t v a l u e
V value = ent ry . getValue ( ) ;
}

60.3 sort a map on the keys Sorting a map on the keys is another frequent operation. One way is to put Map.Entry into a list, and sort it using a comparator that sorts the value. Li s t l i s t = new Ar rayLi s t (map. ent rySe t ( ) ) ; Co l l e c t i ons . s o r t ( l i s t , new Comparator ( ) { @Override public int compare ( Entry e1 , Entry e2 ) { return e1 . getKey ( ) . compareTo ( e2 . getKey ( ) ) ; } });

The other way is to use SortedMap, which further provides a total ordering on its keys. Therefore all keys must either implement Comparable or be accepted by the comparator.

4. SORT A MAP ON THE VALUES

One implementing class of SortedMap is TreeMap. Its constructor can accept a comparator. The following code shows how to transform a general map to a sorted map.

SortedMap sortedMap = new TreeMap (new Comparator ( ) { @Override public int compare (K k1 , K k2 ) { return k1 . compareTo ( k2 ) ; } } ) ; sortedMap . putAll (map) ; 60.4 sort a map on the values Putting the map into a list and sorting it works on this case too, but we need to compare Entry.getValue() this time. The code below is almost same as before. Li s t l i s t = new Ar rayLi s t (map. ent rySe t ( ) ) ; Co l l e c t i ons . s o r t ( l i s t , new Comparator ( ) { @Override public int compare ( Entry e1 , Entry e2 ) { return e1 . getValue ( ) . compareTo ( e2 . getValue ( ) ) ; } } ) ; We can still use a sorted map for this question, but only if the values are unique too. Under such condition, you can reverse the key=value pair to value=key. This solution has very strong limitation therefore is not really recommended by me. 60.5 initialize a static/immutable map When you expect a map to remain constant, it’s a good practice to copy it into an immutable map. Such defensive programming techniques will help you create not only safe for use but also safe for thread maps. 60.6. DIFFERENCE BETWEEN HASHMAP, TREEMAP, AND HASHTABLE 217 To initialize a static/immutable map, we can use a static initializer (like below). The problem of this code is that, although map is declared as static final, we can still operate it after initialization, like Test.map.put(3,“three”);. Therefore it is not really immutable. To create an immutable map using a static initializer, we need an extra anonymous class and copy it into a unmodifiable map at the last step of initialization. Please see the second piece of code. Then, an UnsupportedOperationException will be thrown if you run Test.map.put(3,“three”);. public c l a s s Tes t { pr ivate s t a t i c f ina l Map map; s t a t i c { map = new HashMap ( ) ; map. put ( 1 , ” one ” ) ; map. put ( 2 , ” two ” ) ; } } public c l a s s Tes t { pr ivate s t a t i c f ina l Map map; s t a t i c { Map aMap = new HashMap ( ) ; aMap. put ( 1 , ” one ” ) ; aMap. put ( 2 , ” two ” ) ; map = Co l l e c t i ons . unmodifiableMap (aMap) ; } } Guava libraries also support different ways of intilizaing a static and immutable collection. To learn more about the benefits of Guava’s immutable collection utilities, see Immutable Collections Explained in Guava User Guide. 60.6 difference between hashmap, treemap, and hashtable There are three main implementations of Map interface in Java: HashMap, TreeMap, and Hashtable. The most important differences include: The order of iteration. HashMap and HashTable make no guarantees as to the order of the map; in particular, they do not guarantee that the order 60.7. A MAP WITH REVERSE VIEW/LOOKUP 218 will remain constant over time. But TreeMap will iterate the whole entries according the “natural ordering” of the keys or by a comparator. key-value permission. HashMap allows null key and null values. HashTable does not allow null key or null values. If TreeMap uses natural ordering or its comparator does not allow null keys, an exception will be thrown. Synchronized. Only HashTable is synchronized, others are not. Therefore, “if a thread-safe implementation is not needed, it is recommended to use HashMap in place of HashTable.” A more complete comparison is | HashMap | HashTable | TreeMap i t e r a t i o n order | no | no | yes null key value | yes yes | yes yes | no yes synchronized | no | yes | no time performance | O( 1 ) | O( 1 ) | O( log n) implementation | buckets | buckets | red black t r e e Read more about HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap. 60.7 a map with reverse view/lookup Sometimes, we need a set of key-key pairs, which means the map’s values are unique as well as keys (one-to-one map). This constraint enables to create an “inverse lookup/view” of a map. So we can lookup a key by its value. Such data structure is called bidirectional map, which unfortunetely is not supported by JDK. 60.8 both apache common collections and guava provide implementation of bidirectional map, called bidimap and bimap, respectively. both enforce the restriction that there is a 1:1 relation between keys and values. 7. shallow copy of a map Most implementation of a map in java, if not all, provides a constructor of copy of another map. But the copy procedure is not synchronized. That means when 60.9. FOR THIS REASON, I WILL NOT EVEN TELL YOU HOW TO USE CLONE() METHOD TO COPY one thread copies a map, another one may modify it structurally. To prevent accidental unsynchronized copy, one should use Collections.synchronizedMap() in advance.

Map copiedMap = Co l l e c t i ons.synchronizedMap (map) ; Another interesting way of shallow copy is by using clone() method. However it is NOT even recommended by the designer of Java collection framework, Josh Bloch. In a conversation about “Copy constructor versus cloning“, he said I often provide a public clone method on concrete classes because people expect it.

It’s a shame that Cloneable is broken, but it happens. Cloneable is a weak spot, and I think people should be aware of its limitations.

60.9 for this reason, i will not even tell you how to use clone() method to copy a map. 8. create an empty map If the map is immutable, use map = Co l l e c t i ons . emptyMap ( ) ;

Otherwise, use whichever implementation. For example map = new HashMap ( ) ;

THE

翻译 By Long Luo

下面这些问题Stackoverflow上关于Java collections提问和讨论最多的问题。在你阅读这些问题之前,有必要先阅读下这篇文章3分钟速读:图解Java Collections的接口以及类层级关系

1. 什么时候用LinkedList?什么时候用ArrayList?

ArrayList本质上是一个数组。它的元素可以直接通过索引值直接访问。但是如果数组已经满了,那么需要重新分配一个更大的数组并且将全部的元素移动到新的数组需要花费O(n)的时间。当然从现有的数组中增加或者删除一个元素都需要移动现有的元素。这个可能是使用ArrayList中最大的不便之处。

LinkedList是一个双端链表。正因为如此,如果要获取一个链表中间的元素,需要从链表的头部开始查找。另一方面,增加或者删除链表中的元素将会很快,因为只需要在本地修改即可。

下表总结了最快情况下的比较需要耗费时间:

MethodArraylistLinkedList
get(index)O(1)O(n)
add(E)O(n)O(1)
add(E, index)O(n)O(n)
remove(index)O(n)O(n)
Iterator.remove()O(n)O(1)
Iterator.add(E)O(n)O(1)

不管运行时间,当大型列表需要额外考虑内存占用。LinkedList每个node至少需要2个额外的指针用于连接前后2个node。而在ArrayList中只需要数组存储元素值即可。

2. 当遍历容器时,高效等价于移除元素的操作?

最正确的方式就是在遍历容器时用Iterator.remove()去修改一个容器,如下代码所示:

1
2
3
4
5
Iterator<Integer> itr = list.iterator();
while(itr.hasNext()) {
// do something
itr.remove();
}

另外一个非常高频的使用但是不正确的代码是这样的:

1
2
3
for(Integer i: list) {
list.remove(i);
}

运行上面的代码时你会得到一个ConcurrentModificationException。原因是因为一个迭代器自生成之后(在for循环中),用于横贯这个列表。与此同时,这个列表同时也被Iterator.remove()修改了。在Java语言中,当一个线程在修改一个容器时而另外一个线程在遍历它是不允许的。

3. 如何将List转换成int[]?

最快捷的方式可能是用Apache Commons Lang库中的ArrayUtils

1
int[] array = ArrayUtils.toPrimitive(list.toArray(new Integer[0]));

在JDK中,没有快捷方式。请注意你不能使用List.toArray(),因为那会将列表转换成Integer[]。正确的方式应该是这样的:

1
2
3
4
int[] array = new int[list.size()];
for(int i=0; i < list.size(); i++) {
array[i] = list.get(i);
}
阅读全文 »
0%