Notice——注意,Pitfall——陷阱,Caue——原因,Vice Versa——反之亦然
备注:一、二……等标号代表电子版笔记章节,(1)(2、3)……等标号对应书面笔记章节。
一、Introduction(1)(20130115)
Java三个版本:Standard、Micro、Enterprise。
Java Development Kit,JDK包含编译和运行所需的一切。Java Runtime Environment,JRE包含运行所需的一切。
目录:bin-binary二进制可执行文件的目录,其中javac.exe是java compile。
配置:Window环境下运行文件时,会从系统变量中Path中所有的目录中找,因此,为了使javac、java能够在cmd下被识别,应该将所有目录附加在系统变量的Path中(环境变量也可)。
环境变量:JAVA_HOME = c:\program files\java\jdk1.x.x
path += ;.;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
classpath = %JAVA_HOME%\lib;%JAVA_HOME%\jre\lib;
编写第一个java file:用NotePad即可,保存时以.java后缀。
编译:以类名(而非文件名)生成.class文件 -javac xxx.java。
执行:-java xxx
总结:Java执行的是字节码而非binary,通过JVM即时解释运行,Java之所以跨平台因为执行字节码的JVM(C实现)与System关联。(每个System都有对应的JVM)
二、Primitive Data Type/Reference Type(2、3)
int a;//定义一个变量
a = 2;//为变量赋值,右边赋值给左边
int a = 2;//等价上2行代码
常量——值不会变化的量。
Java中变量命名不能以数字开头,同时不建议大写or“$”开头。
8种Primitive Data Type:
int(4byte)、byte(I/OStream中常用)、short(2byte)、long(8byte)、float(4byte)、double(8byte)、char(2byte)、boolean
Pitfall:Java中浮点型默认使用double,因此,在未进行类型强转的情况下即使所赋值处于float范围内也会报错。
Notice:variable使用前应先赋值,给定type,低type可以赋值给高type,not vice versa。
关于注释:
//单行注释
/*
多行注释,源自C++
*/
/**
*文档注释,用于产生help documents
*/
三、Operator(4)
+、-、*、/当若干variable参与运算,结果取决于表示范围最大的type。
//混合类型运算判断技巧:
int a = 3;
float b = 3.2f;
float c = a*b;//以等号为界,赋值前结果类型取决于参与运算的表示范围最大的variable type,赋值时遵循表示范围小可赋值给表示范围大的原则。
Notice:取模运算的结果的符号只与被除数有关。(被除数)5/3(除数)
四、Relation Operator(5)
>、<、==、!=、>=、<=、&&、||、!
它们的result都是boolean。
双目运算符:有两个操作数的运算符。
variable的字增减:++、--、+=、-=、*=、/=。
五、Flow Control Statement(6、7)
//三元表达式:
d==a?b:c//(d==a)为true返回b,为false返回c
//判断
if(boolean expression)
{fragment code}
else
{fragment code}
//循环一
for(int i=0;i<10,i++)//当想以计数循环时(可预见的计数范围)
{fragment code}
//循环二
do//do程序块的代码至少会被执行一次
{fragment code}
while(boolean expression)//当仅想以某种状态运行至得到一种结果时
{fragment code
continue;//跳出本次loop,但不中止loop,继续下次loop,即continue可以实现当满足某种情况就跳过continue以下的代码
}
//选择
switch(variable){
case 1:……break;//break结束本次控制语句,不加则继续执行后续case&default
case 2:……break;
……
default:……break;
}
六、Inside Object Oriented Programming:OOP(8)
什么是OOP?
两个重要概念:
class——一种抽象概念,包含了Data&Behavior。
object——一种具体概念,包含property、attribute、member variable(三者属于一个概念,都是data)&method(对数据的操作,就是behavior)。
七、OOP Feature(9)
1、Inheritance。2、Encapsulation。3、Polymorphism 。
八、Java Memory(10、11)
九、OO Summary(12、13、14)
Reference type是用于object的,一个object可被多个reference所指,一个reference只能指向唯一object。
一个reference对指向object的修改会反映到其他指向该object的reference。
十、方法参数传递(15)
一个java源文件中可以定义多个类,但只能有一个public class,并且当有public class存在,main方法只能写在public class中。
如果一个源文件有两个以上类,那么它也会被编译成相应数量的.class文件。
当method的实参接收的是primitive type,那么它与形参之间是值传递。
当method的实参接收的是reference type,那么它与形参之间是引用传递。
这与C&C++是相似的,只不过Java把它称为reference,C&C++把他们称为Pointer。
十一、Overload(early binding)(16)
条件:1)param个数不同、2)param类型不同。Overload与返回值无关。
重载构造方法同上,可以通过this(param)调用其它构造方法。
十二、Inheritance(Inheritence)(17)
对Java特性的认识,应多思考其在memory中的加载过程。
当SubClass extends SuperClass,new SubClass();时会先调用SuperClass的不带参的construction,然后再是SubClass的不带参的construction。
Notice:当SubClass的construction通过super(param);显示制定调用SuperClass中的某一construction时,那么执行时就不会再调用不带参的construction。
Notice:super、this用于调用construction时,前面都不能有其它可执行语句,即super();this();必须位于首行。
十三、Override(late binding)(18)
条件:1)方法名一致、2)参数一致、3)返回类型一致。BasicClass可通过Super();调用SuperClass中被重写的方法。
Notice:Overload属于平行关系,仅发生在类内部。Override属于层次关系,发生于SuperClass&BasicClass。
Notice:Polymorphism的表现形式之一就是SuperClass的引用可指向SubClass的实例,not vice versa。如果不是late binding,就一定不是Polymorphism。
十四、Polymorphism(19、20、21)
Notice:当一个Class不继承于任何Class时,它就隐士地继承了Object。Object是所有Java类的根源。
请区别upcast(SuperClass superClass = subClass;)、downcast(SubClass subClass = (SubClass)superClass;)。
abstract class无法实例化,其内部方法有声明无实现,均以”;“结尾(abstract void method();)
abstract method只能存在于abstract class,abstract class不一定要有abstract method,也可以有implemented method。
sub class必须实现abstract class的所有abstract method。
Notice:abstract method的存在是为了制定约束&规范,而实现或者说如何具体满足这种规范交由sub class处理(比如由二次开者完成)。
虽然创建一个general super class,让继承它的sub class override它的方法也可实现这一机制,但这就显得无意义了。
十五、Interface(22)
Interface的地位等价于class,Interface中所有method都是abstract method(故key word abstract被忽略)。
通过key word imlements实现接口,Interface可看作一个特殊的abstract class。
一个sub class只可extends一个super class,但可以implements多个Interface(以“,”隔开)。
Interface中允许定义member variable,通常它们被默认public static final(不然则无意义)。
十六、static&final(23、24)
1、static
在了解static前,请先区别下列数据的作用范围:
1)局部变量。(位于method内)
2)实例变量、成员变量、Instance Methods。(位于class内)
3)全局变量、类变量、Class Methods。(位于class内,且用static修饰)
Notice:类变量较为特殊,无论声明了多少个引用指向它,都共享一个实例。
Notice:Class Methods(类方法/静态方法)只能继承,不能override。
2、final
final可以直观地从字面意思理解,final class不能被extends,final method不能被override,final attribute不能被改变。
Notice:final修饰primitives,则该primitives的值不能被修改。final修饰reference,则该reference的指向不能被修改,被指向的对象可以修改。
Notice:final attribute必须赋初值||在construction内为其赋值,但只能进行一次赋值!
类加载过程简述:从继承结构的最顶层的static block开始,依次向下,然后是construction,其中static block仅在类加载时执行一次。
十七、Singleton Patterns(25)
public abstract final class Test{……}//思考这样写对吗?
Design Patterns:
每一种设计模式都包含了一种思想
//Singleton Patterns(创建型模式分支)
//一个类只会生成唯一一个对象
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton()
{
}
public static Singleton getInstance()
{
return instance;
}
}
十七、包与导入语句(26)
package用于对不同功能的java文件进行分类。
命名规则:将公司域名反转作为包名,每个字都要小写。eg:Realmname——www.microsoft.com,Package——com.microsoft.xxx。
import com.microsoft.xxx;//import为导入包关键字
十八、Access Modifier(27)
Modifier | Class | Package | Sub Class | All |
private | yes | |||
default | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
Notice:Reference instanceof ClassName可用于判断某个对象是否是某个类的实例。(返回boolean)
十九、Object(28)
每个Java类都默认继承Object类,因此无需显示import java.lang.Object。
学会自查API,Application Programming Interface了解陌生对象的使用方法。
System.out.println(Obj);
//等价于
System.out.println(Obj.toString());
//未被Override的toString()
public String toString()
{
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
Cause:当试图打印任何一个对象时,如果这个对象不是String类型,都将自动调用它的toString();(所有对象都继承自Object根类,因此都具有toString())。
如果Override了toString(),那么System.out.println()的输出也会发生相应的变化。
Supplement:hash code存放于内存中对象的头部,对象头部(overhead)中有三类信息:hash code、reference info、garbage collection info。
二十、String(29、30)
首先思考:
String a = "hello";
String b = "hello";
String c = "hel";
String d = "lo";
String e = new String("hello");
String f = new String("hello);
a==b;//true
a==(c+d)//false
a==e//false
e==f//false
Notice:在一般对公项目中,60%以上都是对字符串的处理,因为无论是用户输入的信息,还是输出给用户的信息都是字符串。因此,掌握并精通String类型的处理时必要的,这也是很多企业的面试重点。
Object对象中的十一种method必须掌握,它们分别是:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString、wait*3。
//String重写的equals方法
public boolean equals(Object anObject)
{
if(this == anObject)
{
return true;
}
if(anObject instanceof String)
{
String anotherString = (String)anObject;
int n = count;
if(n == anotherString.count)//判断两个字符串长度是否相等
{
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while(n--!=0)
{
if(v1[i++] != v2[i++])//逐个字符判断是否相等
return false;
}
return true;//全等返回true
}
}
return false;
}
通过重写equals(),可完成一些自定义类型的对比,以适应不同的业务需求。
Notice:要将所学灵活运用,必须有耐心、恒心、且细心。
String是constant,其对象一旦创建完毕无法改变。当时用“+”进行拼接,会产生一个新的对象。
StringPool运作流程:
String a = "test";//创建String实例a,赋值时,查找StringPool中是否存在“test”值,不存在则写入“test”并返回地址
String b = "test";//创建String实例b,赋值时,从StringPool中找到“test”值,返回地址
思考Object的equals()与String的equals()的区别
public boolean equals(Object obj)
{
return (this == obj);//对比两个对象的地址
}
intern()会返回String实例在String Pool中的引用。
a.intern() == b.intern();
等价于
a.equals(b);
二十一、StringBuffer(31)
StringBuffer sb = new StringBuffer();
sb.append("hello").append(" world");
sb = sb.append(" welcome");
//Result:hello world welcome
不同于String是不可变的,StringBuffer虽未final class,但可通过append进行信息追加。
String a = "hel";
a += "lo";//String的实例a在追加字符串操作后hashcode会产生变化,即产生一个新对象
StringBuffer sb = new StringBuffer();
sb.append("hel").append("lo");//StringBuffer的实例sb在追加字符串操作后hashcode不会产生变化
Notice:8种primitive为了满足某些对象只能对对象操作的需要,都有对应的Wrapper Class,比如int的Wrapper Class为Integer。
int x = 5;//1
Integer y = new Integer(x);//2
int z = y.intValue();//3
//1到2的过程称为boxing,2到3的过程称为unboxing
二十二、Array(32)
Array是相同类型的数据的集合。
int[] a = new int[10];/Java推荐使用这种形式
//等价于
int a[] = new int[10];
//初始化并赋值
int[] a = {1,2,3};
int[] a = new int[]{1,2,3};
every Array have a attribute——length,表数组长度(不是数组内存在多少个对象,而是最多允许存在多少个对象昂)。
如果Array中装的是对象,那一定是对象的引用。
思考:为什么有些地方用的是length(),而有些是length?
二十三、2DArray(33)
int[][] a = new int[][]{{1,2},{3,4}};
二十四、Swap入门(34、36)(20130116)
int a = 4,b = 3;
a = a + b;
b = a - b;
a = a - b;
思考,为什么产生这样的输出?
public static void swap(char[] ch,char c)
{
ch[0] = 'B';//数组作为实参传递的是引用(地址),所以赋值会改变其值
c = 'D';//primitive作为实参传递的是值(把值copy给形参),因此不会改变其值
}
public static void main(String[] args)
{
char[] ch = {'A','C'};
swap(ch,ch[1]);
for(int i = 0;i < ch.length;i++)
{
System.out.println(ch[i]);
}
}//Result:B C
JDK为我们提供了一个为Array查找、排序的类java.util.Arrays;
int[] a = {1,2,3};
int[] b = {1,2,3};
System.out.println(a.equals(b));//false
System.out.println(Arrays.equals(a,b));//true
int[] c = new int[3];
System.arrayCopy(a,0,c,0,3);//从a的第0个下标开始依次拷贝3个元素到c的第0个下标开始
冒泡排序Example:
//BubbleSort core:
for(int i = 0;i < array.length-1;i++)
{
for(int j = 0;j < array.length-i-1;j++)
{
if(array[j] > array[j+1])
{
array[j] = array[j] - array[j+1];
array[j+1] = array[j] + array[j+1];
array[j] = array[j+1] - array[j];
}
}
}
线性查找Example:
//LinearSearch core:
for(int i = 0;i < array.length;i++)
{
if(key == array[i]
return i;
}
二分查找Example:
//BinarySearch core:
int lh = 0;rh = array.length-1,middle;
while(lh <= rh)
{
middle = (lh+rh)/2;
if(array[middle] == key)
return middle+1;//返回位置
if(array[middle] > key)
rh = middle-1;
if(array[middle] < key)
lh = middle+1;
}
return -1;//没找到返回-1
二十五、3DArray(35)
三维数组在Game Development中用得较多,如果想从事此行业对傅里叶、线性代数、概率论、统计、级数等要熟悉。
二十六、Math、Random(37、38)(20130117)
double ceil = Math.ceil(16.34);//返回大于指定数的最小整数
double floor = Math.floor(12.34);//返回小于指定数的最大整数
long round = Math.round(12.54);//四舍五入
double pow = Math.pow(2,3);//幂运算,2的3次方
double random1 = Math.random();//伪随机数,大于等于0.0,小于1.0
int random2 = (int)(Math.random()*10 + 1);//随机输出1~10
Random r = new Random();//同为伪随机数
int random3 = r.nexInt(10);//无需强转,直接返回int随机数,括号内10为“乘10”
Java中牵涉范围的都会遵循min<=n<max
Constant命名规则:所有字母大写、单词间下划线隔开、通常是public static final的。
二十七、Collection——ArrayList(39、40)
对于集合下的一些常用方法,应当熟记。
List两大子类ArrayList、Linked。
ArrayList<Integer> list = new ArrayList<Integer>();
int x = 5;
Integer y = new Integer(x);//int==>Integer is boxing
list.add(y);//ArrayList只接受Object,因此对于primitives需要wrap后,才能存入。
Integer z = (Integer)list.get(0);
int f = z.intValue();//Integer==>int is unboxing
从Java5.0开始,boxing/unboxing已经由底层自动完成,即primitives也可直接俄存入ArrayList。
由ArrayList源码可见,ArrayList的插入、删除操作,需要对ArrayList整体进行复制操作,开销较大。
二十八、数据结构简介(41)
1、一般将数据结构分为两大类
1)线性数据结构:线性表、栈、队列、串、数组、文件。
2)非线性数据结构:树、图。
2、线性表——n个数据元素的有限序列(a、a1、a2、a3、……an),必存在唯一一个“第一”和“最后”。同一线性表中,所有数据元素类型相同。
按存储结构可分为:
1)顺序表,eg:ArrayList
2)链表:
(1)单向链表,date1|next==>date2|next==>date3|next
(2)循环链表,date1|next==>date2|next==>date3|next==>date1|……
(3)双向循环链表,previous|date1|next<==>previous|date2|next<==>previous|date3|next<==>previous|date1|……
二十九、Collection——LinkedList(42)
LinkedList底层是用双向链表实现的。
性能比较 | ArrayList | LinkedList |
插入/删除 | yes | |
搜索 | yes |
ArrayList与LinkedList虽然有着很多类似的方法,但这些方法的底层实现却完全不同。
Notice:学习过程中,首要掌握的是分析源码的能力,其次是编写代码的能力(创新),最后加上一定的经验,才能更好的完成架构。
思考:分别用ArrayList&LinkedList实现Stack&Queue的机制。
三十、Stack&Queue(43、44)
Stack:一种特殊的线性表(Last In First Out),限定尽在表尾插入or删除,物理存储可以用顺序or链式。
Queue:限定一端进入,另一端出去的线性表(First In First Out),插入端称Rear,删除端称Front,物理存储可以用顺序or链式。
java.util包有Stack&Queue。
三十一、Collection——HashSet&TreeSet(45、46、47)
Set是无序的,不允许重复元素的。
HashSet.add()不会添加重复元素,它的执行过程是这样的,比较两个element的hashcode,不同直接加入,相同再用equals()进行第二次比较,不同加入,相同不加入。
HashSet.iterator()返回实现Iterator接口的一个实例,这个接口使用迭代器模式(行为型模式分支)。
Notice:每个Collection类都提供了一个iterator函数,用于返回一个对类集头的迭代接口。
HashSet内部是一个HashMap,TreeSet内部是一个TreeMap。
TreeSet是一种有序集合,为加入的元素进行自动排序。
Pitfall:当加入的元素类型是primitives时,元素会被正常加入,因为它们具有自然顺序。当加入的元素类型是Object时,可能会抛出ClassCastException,解决方法是在TreeSet初始化时给出一套Sort规则。
MyComparator mc = new MyComparator();
TreeSet ts = new TreeSet(mc);//为TreeSet限定一套Sort规则,就能正常对Object进行比较了
//MyComparator实现Comparator接口,至少实现compare(T e1,T e2)方法
class MyComparator implements Comparator
{
public int compare(Object arg0,Object arg1)
{
String s1 = (String)arg0;
String s2 = (String)arg1;
return s2.compareTo(s2);//compareTo()以字典顺序对两个String进行比较
}
}
Notice:相对于Array提供了一个工具类Arrays,Collection也有一个工具类Collections。
Comparator r = Collections.reverseOrder();//按自然顺序的反序进行排序
Collections.sort(list,r);//按指定规则“r”对集合“list”进行排序
Collection.shuffle(list);//将集合顺序打乱
Collection.min/max(list);//返回集合中最小/最大的数,该方法也有比较器形式min/max(list,comparator)
三十二、Collection——HashMap(48、49、50)
Map是无序的,一个key只能对应一个value。
思考:为何keySet()返回Set<K>,而values()返回Collection<V>?
eg:使用HashMap对单词出现频率计数。
for(int i = 0;i < args.length;i++)
{
if(!map.containsKey(args[i]))
{
map.put(args[i],1);
}
else
{
int count = map.get(args[i]);
map.put(args[i],++count);
}
}
Map.Entry<K,V>是Map的内部类
Set set = map.entrySet();
for(Iterator iter = set.iterator();iter.hashNext();)
{
Map.Entry entry = (Map.Entry)iter.next();
String key = (String)entry.getKey();
Strng value = (String)entry.getValue();
}
思考:使用HashMap实现50个随机数出现频率的存取。
TreeMap是有序的,使用方法参照API与TreeSet,根据key为Map内元素排序,同样,允许在构造时限定Comparator排序规则。
三十三、Strategy Pattern(51)
Strategy Pattern策略模式(行为型模式分支)
体现:封装变化的概念,编程中使用接口。
定义:将每个算法封装,使用时可互换,且互不影响。
意义:软件各组成部分间是弱连接关系,且可替换(可重用性)。
三十四、Collection总结(52)
List是有序的,允许重复的元素。
Set是无序的,不允许重复的元素。
Map是无序的,允许重复的元素,一个key只能对应一个value。
Tree前缀代表有序,且允许在构造时限定Comparator接口实现的排序规则。
性能比较 | ArrayList | LinkedList |
插入/删除 | yes | |
搜索 | yes |
ArrayList与LinkedList虽然有着很多类似的方法,但这些方法的底层实现却完全不同。
LinkedList底层是用双向链表实现的。
HashSet内部是一个HashMap(由于HashSet只用到HashMap的key,因此定义了一个Object常量用于填充value),TreeSet内部是一个TreeMap。
TreeSet是一种有序集合,为加入的元素进行自动排序。
TreeMap是有序的,使用方法参照API与TreeSet,根据key为Map内元素排序,同样,允许在构造时限定Comparator排序规则。
loadfactor与hash有关,由于linearSearch开销巨大,使用binarySearch前需要对Array进行sort,因此衍生了hash函数,其基本思想是:对输入Data进行hash计算得出内存中存放地址。http://zh.wikipedia.org/wiki/Hash
HashMap底层是采用数组+链表实现的,当一个Data通过put方法添加到HashMap中,首先,会通过Hash函数计算出即将存入的Array[index],如果该index位置已被其他Data占用,则将输入的Data存入Array[index],将已存在的Data放置到new Data的next中,形成链表。
三十五、Vector(53、54、55、56)
Vector(同步)&ArrayList(不同步)相似,实际开发中,几乎无人使用Vector,属于逐渐淘汰对象。
HashTable(同步)&HashMap(不同步)相似,HashTable&Vector都是Java旧版本时代的产物。
Properties是HashTable的一个子类,经常使用。
类集框架提供了一个功能强大的设计方案,这些在今后开发中会频繁使用,必须达到熟练使用的程度。
一、Generics(20130223修改)
JDK5.0 New Feature:
1)Generics、2)Enhanced For Loop、3)Autoboxing、4)Type Safe Enums、5)Static Import、6)Var Args。
其中Generics最为重要,也会在今后开发中大量使用,编译时类型安全,减少运行时ClassCastException抛出。
public class GenericFoo<T>
{
private T foo;
public void setFoo(T foo)
{
this.foo = foo;
}
public T getFoo()
{
return foo;
}
}
GenericFoo foo = new GenericFoo();//“〈〉”中传入何种类型,该类中的泛型就会转化为该类型,泛型:即变量类型参数化
Generic定义时,可使用extends关键字限制该Generic必须继承某个类or实现某个接口。
public class genericFoo<T extends List>{//泛型T必须是List接口层次体系下的一员}
GenericFoo<? extends List> foo = null;//?为通配符,声明了foo可以指向实现List接口的对象,进一步将强了Generic的灵活性
foo = new GenericFoo<ArrayList>();
foo = new GenericFoo<LinkedList>();
Notice:GenericFoo<T extends List>与GenericFoo<? extends List>的区别,前者代表该泛型可以取继承何种类&接口的子类,后者代表引用可以指向继承了何种类&接口的子类。
for(Type buf : src){//缺点:会丢失Array&Collection的subscript}
Example:使用new feature精简单词出现次数的Program
Map<String, Integer> map = new HashMap<String, Integer>();
for(String word : args)
{
map.put(word, (null == map.get(word)) ? 1 : map.get(word)+1);
}
Enum是一个特殊的类别,与class、interface作用相似,作用:限制某个特别用途的变量的取值范围。
使用enum定义枚举类型,实际上是定义了一个extends java.lang.Enum的类型。
每一个Enum成员都是该Enum的Instance,且自动预设为public final static。
当Enum第一次使用时,会对其每一个成员进行初始化(调用其construction method),比如:enum WeekDay{ SUN,MON,TUE,WED,THU,FRI,SAT }第一次使用时,会调用7次construction method。
public enum Coin
{
penny("PENNY"), nickel("NICKEL"), dime("DIME"),RMB{ public Coin nextCoin(){ return "人民币" } };//每个枚举成员都是一个对象(类似子类),可以在成员内定义方法,但不能是抽象方法
private String value;
public String getValue()
{
return value;
}
public abstract getCoin();
}
1、Reflection反射机制:
动态获取/调用一个类or任意一个对象的信息(属性、方法等)的机制,通俗的讲,就是把java类中的各种成分映射成相应的java类(eg:Field、Mehtod、Contructor、Package等对象都可以通过反射获得)。
主要功能可在运行时:
1)判断任意一个对象的所属的类。
2)构造任意一个类的对象。
3)判断任意一个类所具有的fields和methods。
4)调用任意一个对象的方法。
Reflection是Java被视为准动态语言的一个关键性质。它允许我们对已知名称的class的内部信息进行取得或在运行时加载一个编译期未知的class。
Introspection - the ability of the program to examine itself.
Reflection&Introspection是常被并提的两个术语。
API:java.lang.relect
Dynamic Programming language:允许程序在运行时改变其结构的编程语言;例如新的函数、对象,甚至代码可以被引入,已有的函数可以被删除或是其他结构上的变化。
Java中,无论生成某个类的多少个对象,这些对象都对应于同一class对象:
Object | anClassInstance | anClassInstance | anClassInstance |
Class | AnClass |
2、获得这个class的方式有多种:
Class<?> clazz = AnClass.class;//通过Java内置对象
Class<?> clazz = Class.forName("com.relection.AnClass");
Object instance = clazz.newInstance();//创建该class对象所代表的类的实例
Mehotd method = clazz.getMehotd("methodName", new Class[]{int.class, int.class});//指定方法的pramType
Object result = method.invoke(instance, new Object[]{1, 3});//指定调用哪个实例的方法并传参给方法
System.out.println((Integer)result);
获得某个类or对象所对应的Class对象的3种常用的方式:
1)使用Class类的静态方法Class.forName("java.lang.String");,这是常用的方式,因为可以用字符串暂替类名,以便运行时决定具体类型。
2)使用类的内置对象String,class;。
3)使用Object的final方法str.getClass();区别于前两者作用于类对象,getClass作用于已实例化对象。
Notic:通俗的讲,无论你是“类名.class”还是“Class.forName(类名)”方式,获得的都是该类在内存中的字节码对象(取字节码方式:直接从jvm中取,若没有则加载进jvm再取)。
clazz.newInstance();等价于new MyClass();,但这种方式只能处理Constructor不带参的类,对于处理带参的要使用以下方式:
Class<?> clazz = obj.getClass();getFields()获得对象的public的所有成员变量,getDeclaredFields()获得对象所有已声明的成员变量,包括private。
COnstructor cons = clazz.getConstructor(new Class[]{String.class, int.class});
Object instance = cons.newInstance(new Object[]{"name", 25});
Field[] f = clazz.getDeclaredFields();for(Field fbuf : f){ String name = fbuf.getName();//获得的方法名为全小写,所以要将其首字母转换为大写 String firstL = name.substring(0,1).toUpperCase();//截取方法名首字母并转换为大写 String getMN = "get" + firstL + name.substring(1);//拼接get方法 String setMn = "set" + firstL + name.substring(1);//拼接set方法 Method getM = clazz.getMethod(getMN, new Class[]{});//声明get方法实例 Method setM = clazz.getMethod(setMN, new Class[]{fbuf.getType()});//声明set方法实例 Object value = getM.invoke(obj, new Object[]{});//通过get方法实例调用方法,将返回值赋给value setM.invoke(instance, new Object[]{ value});//通过set方法实例调用方法,把value当作实参传入}//用Reflection进行String对象中的字符替换public static void compareTypeString(Object obj){ Field[] fields = obj.getClass().getFields(); for(Field field : fields) { if(field.getType() == String.class) { String buf = (String)field.get(obj);//从obj对象中取出field对应的值,downcast到String String nbuf = buf.replace("b", "a");//将String中的b替换为a field.set(obj, nbuf);//将obj对象中field对应的值设置为nbuf } }}
Notice:加载config.properties时,利用反射AnClass.class.getClassLoader().getResourceAsStream(path/name);可以从classpath所在的根目录寻找该配置文件
二、JavaBean(20130216)
IntroSpector(内省)==》JavaBean==》一种特殊的Java类==》主要用于传递数据信息==》JavaBean中的方法用于访问私有的字段==》方法名符合某种命名规则(set/get)
JavaEE开发,很多环境中需要以JavaBean的方式进行开发。
以下是JavaAPI提供的对JavaBean操作的类PropertyDescript、BeanInfo、Introspector:
ReflectPoint rp1 = new ReflectPoint(3,5);//该类有一个构造方法接收两个int赋给局部变量x、y,即x=3,y=5
String propertyName = "x";
//JavaBean的简单内省操作,get方法
PropertyDescript pd = new PropertyDescript(propertyName,rp1.class);//Java提供的PropertyDescript属性描述符
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(rp1);
System.out.println(retVal)//result=3;
//JavaBean的简单内省操作,set方法
PropertyDescript pd2 = new PropertyDescript(propertyName,rp1.class);
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(rp1,8);//关于set的invoke方法调用,它本身是接收一个对象类型的Integer,但由于jdk5开始具备auto boxing功能,所以会把int型的8自动转变为Integer的8
以上方式,在对于不同JavaBean中的set/get方法的反射调用,具有一定程度的共通性,因此可以把其中的一些通用代码提取出来写成一个接收propertyName的方法。
public static Object getProperty(Object obj1, String propertyName)
{
BeanInfo beanInfo = Introspector.getBeanInfo(obj1.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object retVal = null;
for(PropertyDescriptor pd : pds)
{
if(pd.getName().equals(propertyName))
{
Method methodGetX = pd.getReadMethod();
retVal = methodGetX.invoke(obj1);
break;
}
}
return retVal;
}
Apache的commons项目提供了BeanUtils类对类似以上的JavaBean的操作方式进行了封装,因此,实际开发可用BeanUtils。
三、Annotation(20130217)
未来很多框架会越来越基于注解形式的配置,这是一种趋势。
@Deprecated表示该类或方法“已过时”。
@SuppressWarnings("deprecation")消除”已过时“的方法或类提出的警告。
@Override重写父类方法,若不符合重写范式提出警告。
注解相当于一种标记,java编译器或开发工具通过反射了解项目中的类或方法有无注解。
应用结构:
@interface A//注解类 { } |
==> | @A class B//应用注解的类 { } |
==> | Class C//对“应用注解的类”进行反射操作的类 { B.class.isAnnotationPresent(A.class) A a = B.class.getAnnotation(A.class) } |
一个简单的注解实例:
@Retention(RetentionPolicy.RUNTIME)//元注解,Retention有三种等级:SOURCE、CLASS、RUNTIME
@Target(ElementType.CLASS)//也是元注解,表示注解可用于什么地方
public @interface AnnotationExample
{
}
Retention的三种等级,指明了注解的生命周期,SOURCE表示注解只保留在源码阶段,CLASS表示注解保留至class文件阶段,RUMTIME表示注解保留至运行期。
如何在另一个类中应用注解:
@AnnotationExample
public class AnnotationApplication
{
public static void main(String args[])
{
//检查类上是否有注解对象
if(AnnotationApplication.class.isAnnotationPresent(AnnotationExample.class))
{
//得到注解对象,只有当注解上被标记了元注解@Retention(RetentionPolicy.RUNTIME)时才会被保留到运行期间
AnnotationExample ae = (AnnotationExample)AnnotationApplication.class.getAnnotation(AnnotationExample.class);
}
}
}
为注解增加属性:
public @interface AnnotationExample
{
String name() default "custom";//属性默认值为custom
String value();//当且仅当注解只有一个属性需要赋值时,可以直接用双引号+值进行赋值
MetaAnnotation annotationAttr() default @MetaAnnotation("change");;
}
使用带属性的注解:
@AnnotationExample("value")//如果要对两个属性都进行赋值,就必须这么写@AnnotationExample(annotationAttr=@MetaAnnotation("fix"),name="name",value="value")
public class AnnotationApplication
{
}
四、Thread
1、线程的概述(Introduction)
线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。
Notice:Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。
2、线程的定义(Defining)
定义一个线程有两种方法:继承Thread类、实现Runnable接口
/**
* 方式1:继承Thread类
*/
public class ThreadTest extends Thread {
/**
* 重写(Override)run()方法 JVM会自动调用该方法
*/
@Override
public void run() {
System.out.println("I'm running!");
}
/**
* 重载(Overload)run()方法 和普通的方法一样,并不会在该线程的start()方法被调用后被JVM自动运行
*/
public void run(int times) {
System.out.println("I'm running!(Overload)");
}
}
Notice:run()方法在该线程的start()方法被调用后,由JVM自动调用,但不建议使用此方法定义线程,因为采用继承Thread的方式定义线程后,你不能在继承其他的类了,导致程序的可扩展性大大降低。
/**
* 方式2:实现Runnable接口
*/
public class ThreadTest implements Runnable {
public void run() {
System.out.println("I'm running!");
}
}
3、线程的启动(Starting)
任何一个线程的执行的前提都是必须有Thread class的实例存在,并且通过调用run()方法启动线程。
//1)如果线程是继承Thread类,则创建方式如下:
ThreadTest1 tt = new ThreadTest1();
tt.start();
//2)如果是实现Runnable接口,则创建方式如下:
ThreadTest2 tt = new ThreadTest2();
Thread t = new Thread(tt);
t.start();
4、线程的状态(State)
1)新生状态(New): 当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,此时该线程处于新生(new)状态,处于新生状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是活着的(not alive);
2)就绪状态(Runnable): 通过调用线程实例的start()方法来启动线程使线程进入就绪状态(runnable);处于就绪状态的线程已经具备了运行条件,但还没有被分配到CPU即不一定会被立即执行,此时处于线程就绪队列,等待系统为其分配CPCU,等待状态并不是执行状态; 此时线程是活着的(alive);
3)运行状态(Running): 一旦获取CPU(被JVM选中),线程就进入运行(running)状态,线程的run()方法才开始被执行;在运行状态的线程执行自己的run()方法中的操作,直到调用其他的方法而终止、或者等待某种资源而阻塞、或者完成任务而死亡;如果在给定的时间片内没有执行结束,就会被系统给换下来回到线程的等待状态;此时线程是活着的(alive);
4)阻塞状态(Blocked):通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞(blocked)状态;处于Blocking状态的线程仍然是活着的(alive)
5)死亡状态(Dead):当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡(dead)状态。此时可能仍然存在一个该Thread的实例对象,当该Thready已经不可能在被作为一个可被独立执行的线程对待了,线程的独立的call stack已经被dissolved。一旦某一线程进入Dead状态,他就再也不能进入一个独立线程的生命周期了。对于一个处于Dead状态的线程调用start()方法,会出现一个运行期(runtime exception)的异常;处于Dead状态的线程不是活着的(not alive)。
5、状态图
6、线程的方法(Method)、属性(Property)
1)优先级(priority):每个类都有自己的优先级,一般property用1-10的整数表示,默认优先级是5,优先级最高是10;优先级高的线程并不一定比优先级低的线程执行的机会高,只是执行的机率高;默认一个线程的优先级和创建他的线程优先级相同;
2)Thread.sleep()/sleep(long millis):当前线程睡眠/millis的时间(millis指定睡眠时间是其最小的不执行时间,因为sleep(millis)休眠到达后,无法保证会被JVM立即调度);sleep()是一个静态方法(static method) ,所以他不会停止其他的线程也处于休眠状态;线程sleep()时不会失去拥有的对象锁。 作用:保持对象锁,让出CPU,调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留一定的时间给其他线程执行的机会。
3)Thread.yield():让出CPU的使用权,给其他线程执行机会、让同等优先权的线程运行(但并不保证当前线程会被JVM再次调度、使该线程重新进入Running状态),如果没有同等优先权的线程,那么yield()方法将不会起作用。
4)thread.join():使用该方法的线程会在此之间执行完毕后再往下继续执行。
5)object.wait():当一个线程执行到wait()方法时,他就进入到一个和该对象相关的等待池(Waiting Pool)中,同时失去了对象的机锁—暂时的,wait后还要返还对象锁。当前线程必须拥有当前对象的锁,如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常,所以wait()必须在synchronized block中调用。
6)object.notify()/notifyAll():唤醒在当前对象等待池中等待的第一个线程/所有线程。notify()/notifyAll()也必须拥有相同对象锁,否则也会抛出IllegalMonitorStateException异常。
7)Synchronizing Block:Synchronized Block/方法控制对类成员变量的访问;Java中的每一个对象都有唯一的一个内置的锁,每个Synchronized Block/方法只有持有调用该方法被锁定对象的锁才可以访问,否则所属线程阻塞;机锁具有独占性、一旦被一个Thread持有,其他的Thread就不能再拥有(不能访问其他同步方法),方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
备注:所有代码可能忽略异常(Exception)处理。(20130112)
一、基础回顾:学会使用JavaAPI
Java提供了标准类库,但对于初学者要一次掌握这样庞大的类库无异于学习C++那样困难,因此,只需要掌握几个基本、常用的即可。
然后,学会查找JavaAPI,在开发中需要使用其它类时再进行查询。
Example:
1、System(该类无构造函数,说明不能new对象,那么会想到该类中的方法都是静态方法)
Properties pro = System.getProperties();//获得系统属性
System.setProperty("myKey","myValue');//设置系统属性
for(Object obj : pro.keySet())//Properties是Hashtable的子类(通过JavaAPI查得),也是Map的子类,那么可以通过Map的方法取出其中的元素
{
String tmp = (String)pro.get(obj);//Properties集合中存储的都是字符串
System.out.println(obj + " :: " + tmp);//系统属性用处很大,比如:可以通过系统属性得知当前操作系统是什么,然后判断是否能安装本软件
}
2、Runtime(与System相同无构造函数,但也无静态方法,说明该类会提供获取本类对象的方法)
Runtime r = Runtime.getRuntime();//获得Runtime对象(单例模式)
Process p = r.exec("winmine.exe");//启动进程
Thread.sleep(4000);//线程休息4000毫秒
p.destroy();//杀死进程(只能杀死Runtime启动的进程)
3、Date
Date d = new Date();
System.out.println(d);//默认以系统格式显示,往往满足不了我们的需要
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");//将模式封装到SimpleDateFormat对象中,JavaAPI有详尽的格式介绍
String time = sdf.format(d);//返回字符串,如要进行日期计算就需要额外转换(麻烦)
System.out.println("time= " + time);
4、Calendar
Calendar c = Calendar.getInstance();
c.add(Calendar.MONTH,-1);//月份减1,eg:1月-1=12月
String[] mons = {"一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"};//使用下标法将数字转换成中文
System.out.println(c.get(Calendar.YEAR) + "年" + mons[c.get(Calendar.MONTH)] + "月");
二、IO Stream(86~96)
Java通过IO Stream操作数据,IO Stream按数据分(字节流、字符流),按流方向分(输入流、输出流)。
常用基类:字节流的抽象基类(InputStream、OutputStream)、字符流的抽象基类(Reader、Writer)。
由此派生出来的子类名称都是以他们四个父类作为子类后缀名的。
Example:(20130113)
1、字符流的读写
//写演示
FileWriter fw = null;
try
{
fw = new FileWriter("demo.txt");//建立字符流,它会抛出一个IO异常
//fw = new FileWriter("demo.txt",true);//FileWriter的第二个构造函数,指定为true后启用追加修改
fw.write("Testing1");//写入数据,暂存于缓存
fw.flush();//刷新流,将缓冲区的数据写入文件
fw.write("Testing2");
}
catch(IOException e)//FileNotFoundException是IOException的一个子类
{
System.out.println(e.toString());
}
finally
{
try
{
if(fw != null)
fw.close();//刷新流,并关闭流
}
catch(IOException e)//如果初始化抛出异常而没创建成功,那么执行fw.close()就会得到异常
{
System.out.println(e.toString());
}
}
//读演示
FileReader fr = new FileReader("demo.txt");//读取文件
int ch = 0;
while((ch = fr.read()) != -1)//读取方式一,单个字符读取
{
System.out.print((char)ch);
}
char[] buf = new char[1024];
int num = 0;
while((num = fr.read(buf)) != -1)//读取方式二,以定长字符数组作为缓存读取
{
System.out.print(new String(buf,0,num));
}
2、字符流用于文件复制
FileReader fr = null;
FileWriter fw = null;
try
{
fr = new FileReader("Source.txt");
fw = new FileWriter("Destination.txt");
char[] buf = new char[1024];
int len = 0;
while((len = (fr.read(buf)) != -1)//把字符读取到缓冲字符数组buf中
{
fw.write(buf,0,len);//将缓冲字符数组buf的内容写入
}
}
catch(IOException e)
{
throw new RuntimeException("复制失败");
}
finally
{
try
{
if(fr != null)
fr.close();
}
catch(IOException e){}
try
{
if(fw != null)
fw.close();
}
catch(IOException e){}
}
3、字符流的缓冲区
缓冲区的出现提高了对数据的读写效率。
对应类:BufferedWriter、BufferedReader。
缓冲区要结合流才可以使用,在流的基础上对流的功能进行增强。
//写演示
FileWriter fw = new FileWriter("demo.txt");
BufferedWriter bw = new BufferedWriter(fw);//缓冲技术的关键是在其中封装了数组
bw.write("testing");
bw.newLine();//这是为了避免不同操作系统中换行符表示不同所采用的
bw.flush();
bw.close();
fw.close();
//读演示
FileReader fr = new FileReader("demo.txt");
BufferedReader br = new BufferedReader(fr);
String line = null;
while((line = br.readLine()) != null)
{
System.out.println(line);
}
br.close();
fr.close();
4、自己封装一个缓冲区
public class MyBufferedReader//这里使用了“装饰设计模式”,想要对已有对象进行增强时,将已有对象传入,基于已有功能,提供加强功能,那么自定义的该类称为装饰类
{
private Reader r = null;
public MyBufferedReader(Reader r)//原有类通过构造方法传入
{
this.r = r;
}
public String myReadLine() throws IOException//对原有类进行增强
{
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = r.read()) != -1)
{
if(ch == '\r')
continue;
if(ch == '\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length != 0)
return sb.toString();
return null;
}
public void myClose() throws IOException
{
r.close();
}
}
装饰模式VS继承:如果使用继承,对于每一种形式的流操作都需要为它写一个子类,这意味着可能要写多个子类,但如果使用装饰模式,则可以只写一个子类,根据需要把不同形式的流操作通过构造函数传入,进行功能增强。
LineNumberReader跟踪行号的缓冲字符输入流,增加了setLineNumber()(设置起始行号)和getLineNumber()(获得当前行号)两个方法,使用方法与上类似。
5、字节流的读写
//读操作1
FileInputStream fis = new FileInputStream("demo.txt");
byte[] buf = new byte[fis.available()];
fis.read(buf);
System.out.println(new String(buf));
fis.close();
//读操作2
FileIutputSteam fis = new FileIutputStream("demo.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1)
{
System.out.println(new String(buf,0,len));
}
fis.close();
//写操作
FileOutputStream fos = new FileOutputStream("demo.txt");
fos.write("abcdefg".getBytes());
fos.close();
6、字节流用于复制图片
FileOutputStream fos = new FileOutputStream("c:\\2.bmp");
FileInputStream fis = new FileInputStream("c:\\1.bmp");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1)
{
fos.write(buf,0,len);
}
7、字节流缓冲区
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("demo.txt"));//不难发现使用方式与字符流类似
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("demo2.txt"));
int buf = 0;
while((buf = bis.read()) != -1)
{
fos.write(buf);
}
bis.close();
bos.close();
8、从键盘录入
InputStream in = System.in;//in标准“输入流”,从键盘 out标准“输出流”,到控制台
int buf = in.read();//等待用户输入
System.out.println(buf);
9、键盘录入转换
通过键盘录入一行数据,就类似readLine()方法,但readLine()方法是字符流BufferedReader中的方法,而键盘录入的read方法是字节流InputStream的方法,那么该如何转换呢?
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);//将字节流转换为字符流
BufferedReader br = new BufferedReader(isr);//br.read()
//之后操作参考上面的例子,同样,也有字符流转换字节流的OutputStreamWriter
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bw = new BufferedWriter(osw);
10、流操作规律(20130114)
思考I/O Stream如何使用时,应以Java程序本身为第一人称,比如我们键盘录入数据到文本,键盘录入System.in,需要把数据写入Java程序(InputStream或Reader),Java程序再把数据输出到文本(OutputStream或Writer)。
System.setIn(new FileInputStream("Demo.txt"));//该方法可改变输入源,同样也有改变输出源的System.setOut(new PrintStream("demo.txt"));
11、维护日志
利用流把错误信息输出到维护日志
//实现一
catch(Exception e)
{
e.printStackTrace(new PrintStream("Exception_log.txt"));//将错误信息输出到Exception_log.txt日志中,注意:PrintStream本身还会抛出异常需要处理
}
//实现二
catch(Exception e)
{
Date d = new Date();
PrintStream ps = new PrintStream("Exception_log.txt");
ps.print(d.toString());//在输出错误信息前加入时间信息
System.setOut(ps);
e.printStackTrace(System.out);//通过改变输出源,将错误信息保存进日志,也可以在此加入时间
}
有时也需要将系统信息输出到维护日志
Properties p = System.getProperties();
p.list(new PrintStream("Exception_log.txt"));
12、File
不要被File的名字骗了,File即可以代表一个文件,也可以代表一系列文件的集合。
该类弥补了I/O Stream的不足,因为流只能操作文件中的数据,想要操作文件本身标的信息就要依靠File对象。
File f = new File("c:"+File.separator()+"demo.txt");//separator方法提供跨平台文件的分隔符
f.createNewFile();//创建成功返回true
f.delete();//删除文件
f.createTempFile();//创建临时文件,用于保存程序运行发生的临时数据(故障恢复时用)
f.deleteOnExit();//在程序退出时删除文件,通常和创建临时文件一起使用
f.getPath();//获得相对路径,Result:demo.txt
f.getAbsolutePath();//获得绝对路径,Result:c:\demo.txt
//文件过滤
File f = new File("c:\\");
String[] arr = f.list(new FilenameFilter(){
public boolean accept(File dir,String name)
{
System.out.println(dir+" "+name);
/*以下是错误的示范
if(name.endsWith(".bmp"))
return true;
else
return false;
*/
return name.endsWith(".bmp");
}
});
//学会查看JavaAPI,需要时查阅,再做一些简单的测试既能掌握它的用法,Java的产生主旨就是要让编程变得简单!
13、递归显示文件列表
14、删除文件
原理:window中,文件删除必须从里向外,只有把一个文件夹里的内容全部删除才能删除该文件夹,以此类推。
15、Properties
Properties以键值对的形式保存信息(键=值,“=”为分隔符,#为注释标记),使用Properties对象创建配置文件
Properties p = new Properties();
FileInputStream fis = new FileInputStream("config.txt");
p.load(fis);//load方法通过流读取某一配置文件中的配置信息(载入配置文件)
p.list(System.out);//将信息输出到控制台
p.setProperty("myKey","myValue);//修改某一属性值
FileOutputStream fos = new FileOutputStream("config.txt");
p.store(fos);//保存配置信息
16、特殊流
打印流的便捷之处在于可以将各种数据类型的数据都原样打印。
字节输出流PrintStream(可接收File、String、OutputStream),字符输出流PrintWriter(可接收File、String、OutputStream、Writer)。
BufferedReader br = new BufferedReader(new InputStream(System.in));
PrintWriter pw = new PrintWriter(System.out,true);
String line = null;
while((line = br.readLine()) != null)
{
pw.println(line);
}
pw.close();
br.close();
合并流SequenceInputStream
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("demo1.txt"));
v.add(new FileInputStream("demo2.txt"));
v.add(new FileInputStream("demo3.txt"));
Enumeration<FileInputStream> enum = v.elements();
SequenceInputStream sis = new SequenceInputStream(enum);
FileOutputStream fos = new FileOutputStream("Destination.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = sis.read(buf)) != -1)
{
fos.write(buf,0,len);
}
sis.close();
fos.close();
切割文件
FileInputStream fis = new FileInputStream("Source.txt");
FileOutputStream fos = null;
byte[] buf = new byte[1024*1024];
int len = 0,count = 0;
while((len = fis.read(buf)) != -1)
{
fos = new FileOutputStream((count++) + ".part");//以文件切割次数作为碎片的序列名称
fos.write(buf,0,len);
fos.close();
}
17、对象的序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj_tmp.txt"));
oos.writeObject(new ObjDemo("value",111));//向文本写入一个对象
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj_tmp.txt"));
ObjDemo od1 = (ObjDemo)ois.readObject();
System.out.println(od1);
ois.close();
//至此,可以进行初步的对象序列化(即存入磁盘),但当原有的ObjDemo发生改变(比如其中的name属性由public变为private),被序列化的obj_tmp就无法匹配新对象了。此时,让ObjDemo继承Serializable对象,并且给定固定标识(static final long serialVersionUID = 42L),就能保证新类也能匹配旧的序列化对象
//注意:transient可关键词用于修饰不想被序列化的属性
18、管道流
class Read implements Runable
{
private PipedInputStream in;
Read(PipedInputStream in)
{
this.in = in;
}
public void run()
{
byte[] buf = new byte[1024];
System.out.println("Read:start reading...");
int len = in.read(buf);//阻塞读取数据,若无数据进入等待状态
System.out.println("Read:read over.");
String s = new String(buf,0,len);
System.out.println(s);
in.close();
}
}
class Write implements Runable
{
private PipedOutputStream out;
Write(PipedOutputStream out)
{
this.out = out;
}
public void run()
{
System.out.println("Write:stop 6ms...")
Thread.sleep(6000);
out.write("piped is come in".getBytes());//写数据
out.close();
}
}
main
{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);//连接管道
Read r = new Read();
Write w = new Write();
new Thread(r).start();
new Thread(w).start();
}
19、RandomAccessFile
该类不属于I/O体系中一员,直接继承自Object,但该类是I/O包中成员,因为该类自身拥有通过指针对文件随机读写的能力(内部封装了字节输入&输出流)。
通过构造函数可以看出该类只能对文件操作,且具有固定模式。
RandomAccessFile raf = new RandomAccessFile("demo.txt","rw);
raf.write("value1".getBytes());
raf.writeInt(97);
raf.write("value2".getBytes());
raf.writeInt(99);
raf.close();
//输出完成,下面开始输入
RandomAccessFile raf = new RandomAccessFile("demo.txt","rw);
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+" "+age);
//若只想取第二个数据
RandomAccessFile raf = new RandomAccessFile("demo.txt","rw);
raf.seek(8);//指针向前偏移8个字节,使用此方法的前提是数据必须具有规律性!
//raf.skipBytes(8);效果同seek,但seek可以前移后移,但skipBytes只能后移!
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+" "+age);
//流在操作数据时只能按顺序读或写,但RandomAccessFile允许跳至任何位置写(即随机写),就是使用seek&skipBytes方法实现的!
注意:流对象允许在构建时指定编码表(UTF-8、ASCII、GB2312、Unicode、ISO8859-1、GBK)。
三、网络编程(20130128)(106~112)
1、网络模型:OSI(Open System Interconnection)参考模型、TCP/IP(Transmission Control Protocol/Internet Protocol)模型。
OSI参考模型 | TCP/IP参考模型 | 各层作用 |
应用层 | 应用层 | 处理网络应用 |
表示层 | 数据表示 | |
会话层 | 主机间通讯 | |
传输层 | 传输层 | 端到端的连接 |
网络层 | 网际层 | 寻址&最短路径 |
数据链路层 | 主机至网络层 | 介质访问(接入) |
物理层 | 二进制传输 |
Notice:除了TCP/IP协议,还有其他协议可以使多机通讯,但前提是通讯双方的主机所安装的协议需要一致。因此,一些特别组织会安装特殊协议以保证网络安全性。
2、网络通讯要素:
1)IP
本地回环地址:127.0.0.1,可以通过ping本地回环地址测试网卡是否正常。主机名:localhost。
192.168.1段的地址是作为局域网的最常用的保留地址段。
子网掩码:http://zh.wikipedia.org/wiki/子网
2)Prot
用于标识进程的逻辑地址,不同进程的标识。
0~6535是端口允许的范围,其中0~1024被系统使用。
3)Protocol
两种常用的传输协议:
TCP,Transfer Control Protocol——是一种面向连接的保证可靠传输的协议,通过TCP传输,得到的是一个顺序无差错的数据流,发送者&接收者之间必须建立成对的Socket连接。
UDP,User Datagram Protocol——是一种无连接协议,每一个数据报(限制64KB内)都是一个独立的信息,包括完整的源地址或目的地址、时间及内容正确性不能保证,通常用于音频/视频传输。
3、Java的TCP&UDP实现
java支持TCP&UDP通过java.net包中的类进行通讯。
1)InetAddress是java的IP对象,Example:
InetAddress address = InetAddress.getLocalHost();
address = InetAddress.getByName("www.infoq.com");
InetAddress[] address = InetAddress.getAllByName("www.baidu.com");//获得该主机名的所有IP
//以上3个方法都是创建InetAddress实例(工厂方法)
System.out.println(address.getHostAddress());
System.out.println(address.getHostName());
2)Socket
网络编程指的就是Socket编程,而一些教程书上的写的JSP、Servlet、SSH等技术是JavaEE对网络应用框架的封装,虽然底层是Socket,但它们并不是网络编程,而是网络应用编程。
Socket就是为网络服务提供的一种机制,通讯两端都有Socket,网络通讯就是Socket间的通讯,数据在两个Socket间通过I/O传输。
3)UDP Example:
//数据发送
DatagramSocket ds_send = new DatagramSocket();//1、通过DatagramSocket对象创建UDP服务(发送端),若不指定端口,系统会自动分配端口
byte[] buf = "this is a udp package.".getBytes();//2、确定数据,并封装成数据包
DatagramPacket dp_send = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10000);
ds_send.sent(dp_send);//3、通过Socket服务,将已有数据包通过send()发送出去
ds_send.close();//4、关闭资源
//数据接收
DatagramSocket ds_receive = new DatagramSocket(10000);//1、通过DatagramSocket对象创建UDP服务(接收端),指定监听端口
byte[] buf = new bytes[1024];//2、定义数据包,用于存储数据
DatagramPacket dp_receive = new DatagramPacket(buf, buf.length);
ds_receive.receive(dp_receive);//3、通过receive()将数据接收到数据包中,阻塞式方法,未接收到数据就等待
String ip = dp_receive.getAddress().getHostAddress();//4、通过数据包的方式获取其中的数据
String data = new String(dp_receive.getData(), 0, dp_receive.getLength());
int port = dp_receive.getPort();
System.out.println(ip + " : " + prot + " :: " + data);
ds_receive.close();
用UDP通讯的聊天室(无线程版)Example:
//发送端
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
DatagramSocket ds_send = new DatagramSocket(10002);
String line = null;
while(null != (line = br.readLine))
{
if(line.equals("886"))
break;
byte buf = line.getBytes();
DatagramPacket dp_send = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10001);
ds_send.sent(dp_send);
}
ds_send.close();
//接收端
DatagramSocket ds_receive = new DatagramSocket(10001);
while(true)
{
byte[] buf = new bytes[1024];
DatagramPacket dp_receive = new DatagramPacket(buf, buf.length);
ds_receive.receive(dp_receive);
String ip = dp_receive.getAddress().getHostAddress();
String data = new String(dp_receive.getData(), 0, dp_receive.getLength());
int port = dp_receive.getPort();
System.out.println(ip + " : " + prot + " :: " + data);
}
ds_receive.close();
用UDP通讯的聊天室(线程版)Example:
//发送线程
class Send implements Runnable
{
private DatagramSocket ds;
public Send(DatagramSocket ds)
{
this.ds = ds;
}
public void run()
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while(null != (line = br.readLine()))
{
if("886".equals(line))
break;
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPocket(buf, buf.length, InetAdress.getByName("127.0.0.1"), 10002);
ds.send(dp);
}
ds.close();
}
}
//接收线程
class Receive implements Runnable
{
private DatagramSocket ds;
public Receive(DatagramSocket ds)
{
this.ds = ds;
}
public void run()
{
while(true)
{
byte[] buf = new byte[1024];
DatagramPocket dp = new DatagramPocket(buf, buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip + " : " + data);
}
ds.close();
}
}
//主线程
public class ChatDemo
{
public static void main(String[] args)
{
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receiveSocket = new DatagramSocket(10002);
new Thread(new Send(sendSocket)).start();
new Thread(new Receive(receiveSocket)).start();
}
}
4)TCP Example:
//客户端
Socket s = new Socket("127.0.0.1", 10003);//创建客户端的Socket,指定目的主机和端口
OutputStream os = s.getOutputStream();//为了发送数据,应该获取Socket流中的输出流
os.write("this is a tcp message.".getBytes());
InputStream is = s.getInputStream();//阻塞式方法等待数据
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
os.close();
is.close();
s.close();
//服务端
ServerSocket ss = new ServerSocket(10003);//建立服务端Socket,并监听一个端口
Socket s = ss.accept();//通过accept()获取连接过来的客户端对象
InputStream is = s.getInputStream();//使用读取流获取客户端发送来的数据,阻塞式方法等待数据
byte buf = new byte[1024];
int len = is.read(buf);
System.out.println(s.getInetAddress().getHostAddress() + " : " + new String(buf, 0, len));
OutputStream os = s.getOutputStream();
os.write("recevie success!".getBytes());
is.close();
os.close();
s.close();//关闭该客户端Socket,释放资源
并发式(多线程)上传图片Example:
//客户端
class PicClient
{
public static void main(String[] args)
{
Socket s = new Socket("127.0.0.1", 10005);
FileInputStream fis = new FileInputStream("c:\\test.bmp");
OutputStream os = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1)
{
os.write(buf, 0, len);
}
s.shutdownOutput();//告诉服务端数据已写完
InputStream is = s.getInputStream();
len = is.read(buf);
System.out.println(new String(buf, 0, len));
fis.close();
os.close();
is.close();
s.close();
}
}
//服务端
class PicServerThread implements Runnable
{
private Socket s;
PicServerThread(Socket s)
{
this.s = s;
}
public void run()
{
int count = 1;
String ip = s.getInetAddress().getHostAddress();
InputStream is = s.getInputStream();
File file = new File(ip + "(" + count + ")" + "_test.bmp");
while(file.exists())//如果名字存在,计数+1
file = new File(ip + "(" + count + ")" + "_test.bmp");
FileOutputStream fos = new FileOutputStream(ip + "_test.bmp");
byte[] buf = new byte[1024];
int len = 0;
while((len = is.read(buf)) != -1)
{
fos.write(buf, 0, len);
}
OutputStream os = s.getOutputStream();
os.write("receive success!".getBytes());
fos.close();
os.close();
is.close();
s.close();
}
}
class PicServer
{
public static void main(String[] args)
{
ServerSocket ss = new ServerSocket(10005);
while(true)
{
Socket s = ss.accept();
new Thread(new PicServerThread(s)).start();
}
ss.close();
}
}
4、Browser/Server架构
1)通过Browser访问ServerSocket,Example:
ServerSocket ss = new ServerSocket(11000);启动以上服务端Socket,然后在浏览器地址栏键入http://127.0.0.1:11000/,回车就会收到反馈信息。
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress());
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
pw.println("<font color='red' size='7'>Hello,Client!</font>");
s.close();
ss.close();
也可以在DOS下通过telnet 127.0.0.1 11000命令访问主机。
2)通过自定义浏览器访问Tomcat,Example:
String path = "/myweb/demo.html";//如果使用图形化界面,可通过修改path改变访问地址
Socket s = new Socket("192.168.1.1", 8080);
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
out.println("GET + path + HTP/1.1");
out.println("Accept: */*");
out.println("Accept-Language: zh-cn");
out.println("Host: 192.168.1.1:11000");
out.println("Connection: closed");
out.println();
out.println();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line = br.read()) != null)
{
System.out.println(line);
}
s.close();
打开Tomcat服务器,然后通过自定义浏览器(报头遵循国际标准)访问Tomcat查看接收到什么数据。
3)URL,Uniform Resource Locator统一资源定位符,包括两个主要部分:协议标识符(http、ftp、file等)、资源名称(主机名.文件名.端口号.引用)。
Exampe:
URL url = new URL("http://java.sun.com:80/docs/books/demo.html?name=test&age=30");
String protocol = url.getProtocol();
String host = url.getHost();
String file = url.getFile();//获得目录/docs/books/demo.html?name=test&age=30
String query = url.getQuery();//获得?后边的参数name=test&age=30
int port = url.getPort();
String ref = url.getRef();
//将infoq的页面通过I/O Stream保存至本地
URL url = new URL("http://www.infoq.com");
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
//以上2句等价于InputStream is = url.openStream();
OutputStream os = new FileOutputStream("c:\\infoq.txt");
byte[] buf = new byte[2048];
int length = 0;
while(-1 != (length = is.read(buf, 0, buf.length)))
os.write(buf, 0, length);
is.close();
os.close();
//使用字符流+缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
String line = null;
while(null != (line = br.readLine()))
System.out.println(line);
5、小知识
InetSocketAddress(extends SocketAddress)封装了IP+Port,InetAddress封装了IP。
ServerSocket在对象建立的时候,有一个构造方法ServerSocket(int port, int backlog),backlog允许指定同时接入客户端的最大数。
域名解析过程:查看传输协议(http)==》公网DNS服务器==》查找域名&IP映射表==》域名对应IP被返回客户端==》通过IP访问目标服务端
那么,当访问localhost时,电脑并未连入公网,又是如何查找到对应IP127.0.0.1的呢?因为,localhost与127.0.0.1的映射关系被保存在本机了(位置:C:\Windows\System32\drivers\etc\hosts)。
为了提高访问速度&屏蔽一些恶意网站,可以把常用的域名&IP映射表保存在hosts文件中。
五、JDBC
JDBC(Java Data Base Connectivity),创建一个JDBC程序,包含7个步骤:
/**
* 1、加载JDBC驱动程序:<br/>
* 在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机),<br/>
* 这通过java.lang.Class类的静态方法forName(String className)实现。
*/
try
{
// 加载MySql的驱动类
Class.forName("com.mysql.jdbc.Driver");
}
catch (ClassNotFoundException e)
{
System.out.println("找不到驱动程序类 ,加载驱动失败!");
e.printStackTrace();
}
// 成功加载后,会将Driver类的实例注册到DriverManager类中。
/**
* 2、提供JDBC连接的URL:<br/>
* 连接URL定义了连接数据库时的协议、子协议、数据源标识。 <br/>
* 书写形式——协议:子协议:数据源标识<br/>
* 协议:在JDBC中总是以jdbc开始。<br/>
* 子协议:是桥连接的驱动程序或是数据库管理系统名称。<br/>
* 数据源标识:标记找到数据库来源的地址与连接端口。
*/
String url = "jdbc:mysql://192.168.137.211/test?useUnicode=true&characterEncoding=utf-8";
// useUnicode=true:表示使用Unicode字符集。如果characterEncoding设置为gb2312或GBK,本参数必须设置为true。characterEncoding=utf-8:字符编码方式。
/**
* 3、创建数据库的连接:<br/>
* 要连接数据库,需要向java.sql.DriverManager请求并获得Connection对象,该对象就代表一个数据库的连接。<br/>
* 使用DriverManager的getConnectin(String url , String username , String
* password )方法传入指定的欲连接的数据库的路径、数据库的用户名和 密码来获得。
*/
String username = "root";
String password = "root";
Connection conn = null;
try
{
conn = DriverManager.getConnection(url, username, password);
}
catch (SQLException se)
{
System.out.println("数据库连接失败!");
se.printStackTrace();
}
/**
* 4、创建一个Statement:<br/>
* 要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3 种类型:<br/>
* 1、执行静态SQL语句。通常通过Statement实例实现。<br/>
* 2、执行动态SQL语句。通常通过PreparedStatement实例实现。<br/>
* 3、执行数据库存储过程。通常通过CallableStatement实例实现。
*/
Statement stmt = conn.createStatement();
PreparedStatement pstmt = conn.prepareStatement("sql");
CallableStatement cstmt = conn.prepareCall("{CALL demoSp(? , ?)}");
/**
* 5、执行SQL语句:<br/>
* Statement接口提供了三种执行SQL语句的方法:executeQuery、executeUpdate和execute。<br/>
* 1、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句
* ,返回一个结果集(ResultSet)对象。<br/>
* 2、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或
* DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等。<br/>
* 3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的语句。
*/
ResultSet rs = stmt.executeQuery("SELECT * FROM ...");
int rows = stmt.executeUpdate("INSERT INTO ...");
boolean flag = stmt.execute("sql");
/**
* 6、处理结果 两种情况:<br/>
* 1、执行更新返回的是本次操作影响到的记录数。<br/>
* 2、执行查询返回的结果是一个ResultSet对象。
* ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些 行中数据的访问。
* 使用结果集(ResultSet)对象的访问方法获取数据:
*/
while (rs.next())
{
String name = rs.getString("name");
String pass = rs.getString(1); // 此方法比较高效
}
// (列是从左到右编号的,并且从列1开始)
/**
* 7、关闭JDBC对象:<br/>
* 操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声明顺序相反:<br/>
* 1、关闭记录集。2、关闭声明。3、关闭连接对象。
*/
if (rs != null)
{ // 关闭记录集
try
{
rs.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
if (stmt != null)
{ // 关闭声明
try
{
stmt.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
if (conn != null)
{ // 关闭连接对象
try
{
conn.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
一、Proxy(20130223)
1、Proxy Pattern
1)Proxy Pattern(结构型模式分支):为其他对象提供一种代理以控制对这个对象的访问权,比如:spring。
2)Proxy Pattern组件:
抽象角色(声明真实对象和代理对象的共同接口)
代理角色(内部含有对真实对象的引用,通过引用操作真实对象,对外提供与真实对象的接口,可附加对真实对象调用执行时的额外行为)
真实角色(代理角色所代表的真实角色,是我们最终要引用的)
3)Proxy Pattern分类:
Static Proxy Pattern具有明显的缺点:真实角色必须事先存在,并将其作为代理对象的内部属性实际使用,真实角色过多会造成过度膨胀。
Dynamic Proxy Pattern填补了上述缺点,所有代理类都由JVM在运行期动态生成,在Java中提供了实现这一功能的两个类,InvocationHandler在代理中动态实现、Proxy动态代理类。
要充分理解Dynamic Proxy就必须打破传统Java思维方式,因为它是通过ClassLoader、Interface、InvcationHandler在运行时动态生成代理类的技术。
4)Proxy Pattern的作用
为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,比如:异常处理、日志、运行时间等。但问题是我们没有源码,那该怎么做呢?
Proxy编程应用非常广泛,渐渐衍生出一个术语AOP(Aspect Oriented Program),其目的是为了让交叉业务模块化,即用统一方式,在目标方法执行到特定步骤的时候自动加入某一操作,而非直接频繁地修改目标方法内部,比如Spring通过xml配置对Java Beans进行实例注入。
5)A simple Dynamic Proxy Example
Class clazzProxy1 = Proxy.getProxyClass(
Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());
System.out
.println("------------begin constructor list------------------");
Constructor[] constructors = clazzProxy1.getConstructors();
for (Constructor constructor : constructors)
{
String name = constructor.getName();
StringBuilder sb = new StringBuilder(name);
// StringBuilder&StringBuffer的区别,应用上都是动态往字符串上加内容,
// 但StringBuffer是Thread Safe的,因此StringBuilder在Single Thread情况下效率高,
// 而Multi Thread情况下StringBuffer效率高
sb.append('{');
Class[] clazzParams = constructor.getParameterTypes();
for (Class clazzParam : clazzParams)
{
sb.append(clazzParam.getName()).append(',');
}
if (clazzParams != null && clazzParams.length != 0)
sb.deleteCharAt(sb.length() - 1);
sb.append('}');
System.out.println(sb);
}
System.out.println("------------begin method list------------------");
Method[] methods = clazzProxy1.getMethods();
for (Method method : methods)
{
String name = method.getName();
StringBuilder sb = new StringBuilder(name);
sb.append('{');
Class[] clazzParams = method.getParameterTypes();
for (Class clazzParam : clazzParams)
{
sb.append(clazzParam.getName()).append(',');
}
if (clazzParams != null && clazzParams.length != 0)
sb.deleteCharAt(sb.length() - 1);
sb.append('}');
System.out.println(sb);
}
System.out
.println("------------begin create instance-------------------");
Constructor constructor = clazzProxy1
.getConstructor(InvocationHandler.class);
class MyInvocationHandler1 implements InvocationHandler
{
@Override
// 参数的含义:proxy当前的代理对象,method代理对象的哪个方法,args方法所需参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
// TODO Auto-generated method stub
return null;
}
}
Collection proxy1 = (Collection) constructor
.newInstance(new MyInvocationHandler1());
System.out.println(proxy1);// null
proxy1.clear();// invoked
// proxy1.size();// java.lang.NullPointerException
Collection proxy2 = (Collection) constructor
.newInstance(new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable
{
// TODO Auto-generated method stub
return null;
}
});
Collection proxy3 = (Collection) Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[] { Collection.class }, new InvocationHandler()
{
ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable
{
long beginTime = System.currentTimeMillis();//这种取得时间的方式是硬编码
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();//一旦代码被封装就无法更改,后续会进行更改
System.out.println(method.getName()
+ " time-consuming: " + (endTime - beginTime));
return retVal;
}
});
proxy3.add("111");// 若target对象写在invoke方法内部就会发生java.lang.NullPointerException
proxy3.add("222");// 我们调用proxy的add方法,proxy内部调用invoke方法,获得返回值retVal
System.out.println(proxy3.size());
// 疑问:proxy3内部target是一个ArrayList,但为何打印其名结果仍是sun.proxy.$Proxy0呢?
// 答案:所有Proxy都继承自Java根类Object,而Java只把hashCode、equals、toString三个方法交由Handler实现。
// 除这三个方法以外的Object中的其他方法都有自己的实现,因此输出结果是sun.proxy.$Proxy0
System.out.println(proxy3.getClass().getName());
例子中,动态生成的类实现了Collection接口,生成的类有Collection接口中的所有方和一个如下接受InvocationHandler参数的构造方法。构造方法接受一个InvocationHandler对象,它的内部实现是怎样的?
既然Proxy构造方法接收了一个InvocationHandler(实现该接口必须重写invoke方法),Proxy内部必定有一个InvocationHandler的成员变量,而后,通过例子又看到我们在invoke接口中增加的额外操作会在每次执行Proxy的任何方法时被调用,自然可以想到它内部(通过reflect机制)调用了invoke方法。
为了解决硬编码的问题,以上例中proxy3为例抽取其作为一个方法:
//Advice.java
public interface Advice
{
// Advice代表Java的系统建议,一般有5个方法,异常、方法内前后、方法外前后
public void beforeMethod(Method method);
public void afterMethod(Method method);
}
//MyAdvice.java
public class MyAdvice implements Advice
{
long beginTime;
long endTime;
@Override
public void beforeMethod(Method method)
{
beginTime = System.currentTimeMillis();
}
@Override
public void afterMethod(Method method)
{
endTime = System.currentTimeMillis();
System.out.println(method.getName() + " time-consuming: "
+ (endTime - beginTime));
}
}
//ProxyTest.java
public class ProxyTest
{
public static void main(String[] args) throws Exception
{
Collection proxy3 = (Collection) getProxy(new ArrayList(),
new MyAdvice());
}
private static Object getProxy(final Object target, final Advice advice)
{
Object proxy3 = Proxy.newProxyInstance(target.getClass()
.getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable
{
// long beginTime = System.currentTimeMillis();
advice.beforeMethod(method);
Object retVal = method.invoke(target, args);
// long endTime = System.currentTimeMillis();
// System.out.println(method.getName()+" time-consuming: " + (endTime - beginTime));
advice.afterMethod(method);
return retVal;
}
});
return proxy3;
}
}
6)Dynamic Proxy Summary
嵌套结构图:Client==>Proxy.add()==>add方法内invocationHandler.invoke()==>invoke方法内(还可增加一些额外操作比如log()等)method.invoke()==>invoke方法内target.add()
创建一个被代理类(真实角色)及接口(抽象角色)
创建一个实现InvocationHandler接口的类(代理角色)必须实现invoke
通过Proxy.newProxyInstance(loader,interface,handler)创建一个代理
通过代理调用方法