Java SE-北京圣思园教学视频
1. Java的分类:
l Java SE:Java Standard Edition
l Java ME: Java Micro Edition
l Java EE:Java Enterprise Edition
2. 相关术语:
JDK:Java Development Kit (Java开发必备)
JRE:Java Runtime Environment (Java执行环境)
JDK包含了JRE。
3. Java程序执行步骤:
Java程序的执行过程分为两步:
1. 编译:java **.java
2. 执行:javac **(注意,Test后面没有.class)
Class文件是字节码文件,程序最终执行的就是这个字节码(bytecode)文件。
4. Java是跨平台的语言,真正执行的不是二进制代码,而是字节码。
JVM(Java Virtual Machine,Java虚拟机)
Java是跨平台的,而JVM不是跨平台的(JVM是由C语言编写的)
Java之所以能够做到跨平台,本质原因在于JVM不是跨平台的。
5. Java数据类型分为两类:
原生数据类型和引用数据类型。
Java中原生数据类型供8种:
l byte(8bit) 、int(32bit)、short(16bit) 、long(64bit)、float、double、char、boolean
6. 类型相互赋值问题:
可以将小范围的变量赋值给大范围的变量,大范围的赋值给小范围的,必须通过强制类型转换。
7. 当多种变量参与运算时,结果取决于最高范围变量的类型。
8. 取模运算的符号总是与被除数相同,例如5%-3=2;
9. 逻辑运算符的短路特性:
逻辑与:如果第一个操作数为false,结果肯定为false。因此,不执行后面的运算。
逻辑或:如果第一个为true,结果肯定为true。因此,不执行后面的操作。
10. 三元表达式:
Type d = a ? b : c;
11. do while 与while的区别:
do while 至少执行一次,while则需要根据判定条件决定是否能够执行一次。
12. 面向对象特征:封装(Encapsulation)、继承(Inheritence)、多态(Polymorphism)
13. 封装:将数据与操纵数据的方法放在一个类中就构成了封装。
|-属性定义在类中,又称之为成员变量;定义在方法中的变量叫做局部变量。局部变量在使用的时候必须进行声明并赋值;成员变量在使用前必须声明,但是可以不赋值,其值由Java编译器视其类型而定。
|-每个类对象都有自己的属性,但无论一个类有多少个对象,这些对象共享一个方法。(属性一个对象一份,方法共享一份)
|-Java中的方法参数传递,无论是原生类型还是引用类型,传递的类型都是传值(这里需要注意的是,对于引用类型,传值传递的是其地址值,Java中没有传引用的概念)。Person p1 = new Person(); Person p2 = p1;这里的赋值指的是对象地址的赋值。例子如下:
public class Test { Public void change(Person person, Person person2) { Person.name=”lisi”; Person2.name=”wangwu”; } Public static void main(String[] args) { Person person = new Person(); Person.name=”zhangsan”; Person person2 = person;//这里是传地址 Test test = new Test(); System.out.println(person.name); } } |
结果输出为wangwu。 |
|-构造方法用于完成对象属性的初始化工作,其特点如下:
1. 构造方法的名称与类名相同;
2. 构造方法没有返回值;
3. 如果定义一个类时没有声明构造方法,那么编译器会自动生成默认的构造函数,无参数,无函数体。如果定义类的时候自定义一了构造方法,那么编译器就不会生成默认的无参构造函数了。
4. 构造方法不能显式调用,可以通过new关键字隐式调用。
|-new关键字完成的工作:
1. 为对象开辟内存
2. 调用类的构造方法
3. 将生成的类对象的地址返回
|-一个源文件中可以定义多个类,但是只有一个类的修饰为public,并且该文件的名字为public的类名.java。
14. 方法重载:
同一个类中,两个或者多个方法的名字形同,参数不同(参数个数不同,参数类型不同,参数顺序不同)。方法的返回值对于重载没有任何影响。在构造方法中调用另外一个构造函数,可以使用this()的方式调用,其中括号中的参数表示传递给该构造方法的参数,并且this语句必须是当前方法的第一行语句。
15. 继承(Inheritance):
|-Java是单继承的,使用extends完成子类继承父类。Java默认继承java.lang.Objece类。
|-当生成子类时,Java默认调用父类不带参数的构造方法。如想调用父类特定的一个构造方法,可以使用super()方法,其中,括号中表示传递给父类的构造方法的参数值。与this()相同,super()方法也必须是方法语句块中的第一句。
|-父类有的,子类一定有;父类没有的,子类可以增加;父类有的,子类可以改变。
|-构造方法不能被继承,方法和属性可以被继承。
16. 重写(override):父类和子类方法中,返回类型、方法名称、参数相同的方法称之为方法的重写。重写也被称为覆写。如果子类的方法名与父类相同,但参数不同,不构成重写。但是现在子类中完成了方法的重载。子类中可以通过super.方法名()语句调用父类的方法,并且该super语句不用放在语句块的第一句(调用构造方法this、super必须放第一句,非构造放不不用必须放在第一句)。
17. 多态(Polymorphism):多态的本质含义是父类的引用可以指向子类的对象。多态是在运行期间(不是编译期间)决定数据的类型。
18. Parent p = new Child(); p.sing(); 其中,child继承Parent,child中有sing方法,但是parent中没有,这样就会产生调用的错误,Parent类型中必须含有sing方法,然后子类自动调用自己的sing方法。这里一定要看清楚对象的类型。
19. 父类与子类进行强制类型转换时,被转换的对象可以被强转换为其实际指向的类型(Animal a = new Cat(); Cat c = (Cat)a; a实际指向的是Cat类型,因此可以转换Cat。
20. 一共有两种类型的强制类型转换:
|-向上类型转换(Upcast):子类转换成父类(Cat c = new Cat(); Animal a = cat;)可以不用写强制类型转换。
|-向下类型转换(Downcast):将父类型转换成子类型。必须写强制类型转换。例如:Animal a = new Cat(); Cat c = (Cat)a;
21. 对于Animal a= new Cat(); Cat c = (Cat) a;的理解:
|-a是Animal类型的;
|-a指向的是Cat类型;
|-a调用方法的时候看对象指向的类型,调用指向对象的方法;
|-类型转换的时候,看a具体是什么类型。
22. 编写程序说明多态是Runtime的:
Public class test { Public static void main(String[] args) { A a = null; If(args[0].equals(“1”)) { a = new B(); } Else if(args[0].equals(“2”)) { a = new C(); } a.method(); } } |
其中,A是B、C的父类。 |
23. 抽象类(Abstract Class):
|-使用关键字abstract进行声明。抽象类无法实例化,不能通过new创建一个抽象类的对象。
|-抽象方法:使用abstract修饰的方法。抽象方法有声明,无实现。
|-抽象方法一定要定义在抽象类中,不能定义在其他类中。
|-如果一个类包含抽象方法,则该类一定是抽象类。
|-如果一个类是抽象类,其可以包含具体的方法,也可以不包含抽象方法。
|-子类继承父类(抽象类),则子类必须实现父类所有的抽象方法。否则,该子类必须也定义成抽象方法,此时子类中也可以有具体的实现类(实现抽象方法),甚至可以和父类的抽象方法名称相同(重写抽象方法)。
24. 接口(interface):
|-接口的地位等同于class,使用interface声明,接口中的方法全部是abstract方法,但是abstract可以省略不写。
|-接口可以视为特殊的抽象类,没有任何具体的方法的抽象类。
|-类实现接口,使用关键字implements。子类必须实现接口的所有的方法。但是如果此类为抽象类,则其不必实现接口中的所有方法。
|-Java是单继承的,但是一个类可以实现多个接口。
|-接口中的方法全是public方法,接口可以定义属性。其属性全部public static final的。
25.Static关键字:
可以修饰类、成员变量、成员方法;
|-修饰类:静态类。
|-修饰属性:静态属性。无论一个类生成了多少对象,所有这些对象使用唯一一份静态成员变量,只要有一个对象改变了该成员变量,所有对象的成员变量都发生了改变。此时的成员属性可以通过类名.静态属性名调用,也可以通过对象.静态属性名调用。
|-修饰方法:静态方法。可通过类或者类对象调用该方法。
|-code:
Public class test { Public static void main(String[] args) { M m = new N(); m.output(); } } Class M { Public static void output() { System.out.println(“M”); } } Class N extends M { Public static void output() { System.out.println(“N”); } } |
此时,输出的结果是M,而不是m指向的类型N的输出方法输出的N。因为子类不能重写父类的静态方法。如果M中的output方法不是static的,而继承M的N的output是static,编译就会报错。因为静态方法不能重写,称之为隐藏。现在N中的output方法是static,编译成功。但父类的方法仍然存在,调用的时候要看调用对象的类型是什么就调用谁的方法。
|-静态的不能覆盖非静态,非静态也不能覆盖静态。
|-静态方法不能重写,只能隐藏。调用时看调用者的类型而不是指向的类型。
static静态代码块:
|-static代码块的执行顺序高于构造函数。在子类继承父类的情况下也是如此,静态代码块优先于所有的构造函数(子类的静态代码块优先与父类的构造函数),静态代码块之间的执行顺序按照父类-子类的调用顺序。
|-静态代码块只调用一次。每次生成对象,都会调用构造方法。
|-静态代码块是类加载时执行,构造函数是类生成对象是执行。
|-静态方法只能访问静态成员变量。非静态方法可以调用静态成员变量。
|-不能在静态方法中使用this,因为this是非静态的成员变量。
25. final关键字:可以修饰属性、方法、类。修饰的东西不能被改动。
|-修饰类:表示此类为终态类,不能被继承;
|-修饰方法:表示该方法为终态方法,不能被重写(override);
|-修饰属性:表示该属性不能被改写,此属性为常量。
Public class test { Public static void main(String[] args) { People people = new People(); people.age = 20; // error ,final类型的属性不能被改变 people.address = new Address(); //error, final属性不能被改变,这里new Address后,原先people的address属性的地址就发生了变化 people.address.name = “Shanghai”; //right, 因为address的地址并没有发生变化 } } Class People { Final int age = 10; Final Address address = new Address(); } Class Address { String name = “Beijing”; } |
Final修饰原生数据类型时,表示该原生数据类型的值不能发生变化,final修饰引用类型时,表示该引用类型不能再指向其他对象,但该对对象指向的内容是可以变化的。
|-final修饰成员变量的初始化赋值可以有两种:一种是定义的时候直接赋值,最常用;另一种是声明的时候不赋值,然后在每个成员方法中都要赋值。
|-public abstract final class Test{}是错误的,因为abstract要求子类继承,而final约定此类不能被继承,矛盾。因此,一个类不能即是final又是abstract。
26. 设计模式之单例模式(Singleton):一个类只会生成唯一一个可以使用的对象。
//实现一 Public class SingletonTest { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1==s2);//同一对象,地址相同 } Class Singleton { Private static Singleton singleton = new Singleton(); Private Singleton() { } Public static Singleton getInstance() { Return singleton; } } |
//实现二,但在多线程中可能出错 Public class SingletonTest { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1==s2);//同一对象,地址相同 } Class Singleton { Private static Singleton singleton; Private Singleton() { } Public static Singleton getInstance() { If(singleton==null) { singleton = new Singleton(); } Return singleton; } } |
27. 包(package):用于将不同功能的类放在不同的目录(包)下。包的命名规则:公司域名反转,用“.”分隔,全部小写。包使用package关键字。导入使用import关键字。例如有包aa.bb.cc,如果我们import aa.bb.*,系统不会导入cc中的类。编译带有package声明的Java源文件有两种方法:
|-直接编译,然后根据类中所定义的包名,逐一手工建立目录,最后将.class文件放到该目录下。
|-使用编译参数“-d”,方式为:javac –d . 源文件.java。这样编译器自动处理。
28. 访问修饰符(access modifier):
|-public(公共的):所有的类都可以访问。
|-private(私有的):被private修饰的属性和方法只能在类内访问。
|-protected(受保护的):类内部、相同包及其该类子类可以访问。
|-默认(不加任何访问修饰符):类内以及相同包的类可以访问。
29. instanceof:判断对象是否是某个类的实例。使用方法为:引用名 instanceof 类名。
People p = new Man(); p instanceof People; True。Man m=new Man(); m instanceof People;同样也是True!因为Man是People的子类,子类就是父类,因此man是people的实例。
31.相等性的比较:
|-符号“==”
|-对于原生数据类型,比较的是左右的值是否相等;
|-对于引用数据类型,比较的是左右是否指向同一个对象,即左右的引用地址是否相同。
|-equals
|-比较对象的实际值,类对象调用。对于Object,比较的是两个对象的地址。对于String,比较的是两个对象的内容是否相同。
32. java.lang.Object类:一共9大方法
|-equals:测试类对象的值是否相同。Object默认使用==判断两个对象地址是否相同,即是否为同一个对象。
|-toString:打印引用时,会默认调用Object的toString()方法。toString返回:类名+“@”+对象的16进制哈希码(相当于地址)。
33. String类:
|-重写equals方法:如果str.equals(str),即跟自己比较,返回真;如果str不是字符串,返回false。然后逐个字符比较字符串,完全相同则返回true。总结为判断该字符串与传进来的字符串是否相同。
|-对于字符串的相等性判断,需要使用equals,而不能使用==。
|-String是final类,不能被继承。
|-任何一个字符串都是String的实例,String是constant(常量),创建之后不能被改变。因此String s1 = “Hello”; String s2 = “World”; String s3 = s1+s2;其中的s3为新生成的String对象,为“Hello World”。“字符串1”+“字符串2”会生成3个String对象。使用+号拼接字符串时会生成新的字符串对象,而不是原有字符串的拼加。
|-对于使用字符串赋值String a = “aaa”;:Java对于String维护着一个String Pool(字符串池,位于栈中)。对于直接使用字符串赋值的String,Java首先检查池中有没有改字符串的对象,没有就创建该对象。如果已经含有,就不会再创建新对象,而是直接返回原有的对象地址。因此:String s1 = “a”;Stirng s2=”a”;str == str4为true。S2那句代码没有创建对象。但是String a = new String(“a”); String b = new String(“a”);则创建了a、b两个对象,因为使用了new关键字,创建了对象,a、b是引用。
|-对于使用new(”aaa”)的方式:首先查找String Pool中有无aaa的String对象,没有就在String Pool栈中创建该字符串,然后也在堆中创建该字符串对象,最后将堆中对象地址返回到栈中(多在栈中创建了对象)。如果有,就不在栈中创建aaa对象,但依然在堆中创建一个String对象,并直接将String Pool栈中的字符串返回给堆中的新对象。总结:使用new创建字符串,堆中一定创建对象,并将其加入到栈中的String Pool。New方式创建的字符串,返回的地址是堆中的地址,因此String s1=new(”a”);String s2=new(”a”); s1!=s2;
|-“Hello”.intern(),如果String Pool中有Hello字符串,就将该字符串返回;如果没有,则添加Hello到String Pool并返回其引用对象。
|-其余类型与字符串运算时,都会首先变成字符串。
|-理解以下程序:
String hello = “Hello ”, lo = “lo”; hello == “Hello” ->true Other.hello == “Hello” ->true(Other是另一个类,含有一个hello属性) hello == (“Hel”+”lo”)->true hello==(“Hel”+lo)->false 常量+变量,形成一个新的字符串常量,特别记忆 hello == (“Hel”+lo).intern()->true |
34. StringBuffer(线程安全):
|-final class,不能被继承;
|-String是常量,创建后不能改;StringBuffer是变量,创建后可以改,可以追加。
|-使用append追加字符串并返回当前字符串对象。
|-使用其toString()方法返回该StringBuffer中的数据。
此外还有StringBuilder,非线程安全,性能>StringBuffr。
35. 包装类(Wrapper Class):针对原生数据包装成对象。所有的包装类都位于java.lang包下,都是final class。
Integer、Byte、Short、Long、Boolean、Character、Float、Double
原生->包装类:Integer I = new Integer(100);
包装类->原生:i.intValue();
36. 数组(Array):相同类型的数据集合叫做数组。Java中,数组是对象。
|-定义数组:数据类型[] 数组名 = new 数据类型[数组长度];int [] a = new int[12];
|-包含length属性,表示数组的长度。
|-数组也是引用,继承Object,因此数组继承Object的equals方法,比较数组是否相同不能使用equals方法。
|-数组名是数组的引用,其值等于数组首个元素的地址。
|-二维数组定义方式:type[][] 数组名 = new type[行数] [列数];
37. 不使用中间变量完成变量值交换:
int a = 3; int b = 4; a = a+b; b = a-b; a= a-b; |
38. 如下语句:
I[] i = new I[2]; True,可以编译通过,通过i[0] = new C()初始化数组; I i = new I(); False,接口不能实例化 Interface I{} Class C implements I{} |
39. Arrays类:
|-位于java.util包下;
|-用于操纵数组,包括查询、排序,将数组转换成list等;
|-所有方法都是静态方法(Static)
40. 数组拷贝可以使用System.arrayCopy方法,代码如下:System.arrayCopy (a,0,b,0,4);参数依次为:起始数组,起始地址,目标数组,起始地址,拷贝长度。
41. 排序问题:
冒泡排序(复杂度O(N^2)):1跟2比,2跟3比,3跟4比…
public static int[] order(int[] a) { int tmp; for(int i=0;i { for(int j=0;j { if(a[j]>a[j+1]) { tmp = a[j]; a[j] = a[j+1]; a[j+1] = tmp; } } return a; } } |
选择排序(复杂度O(N^2)):对待排序的记录序列进行n-1遍的处理,第1遍处理是将L[1..n]中最小者与L[1]交换位置,第2遍处理是将L[2..n]中最小者与L[2]交换位置,......,第i遍处理是将L[i..n]中最小者与L[i]交换位置。这样,经过i遍处理之后,前i个记录的位置就已经按从小到大的顺序排列好了。
public static int[] Select(int[] a) { int tmp; for(int i=0;i { int samllLocation =i; tmp = a[i]; for(int j=i+1;j { if(a[j] { tmp = a[j]; samllLocation = j; } } a[samllLocation] = a[i]; a[i] = tmp; } return a; } |
插入排序(复杂度O(N^2)):基本思想是,经过i-1遍处理后,L[1..i-1]己排好序。第i遍处理仅将L[i]插入L[1..i-1]的适当位置,使得L[1..i]又是排好序的序列。要达到这个目的,我们可以用顺序比较的方法。首先比较L[i]和L[i-1],如果L[i-1]≤ L[i]说明[1..i]已排好序,第i遍处理就结束了;否则交换L[i]与L[i-1]的位置,继续比较L[i-1]和L[i-2],直到找到某一个位置j(1≤j≤i-1),使得L[j] ≤L[j+1]时为止。
简言之,插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。
//插入排序 public static int[] Insert(int[] a) { for(int i=1;i { int tmp = a[i]; int j = i; while(a[j-1]>tmp) { a[j] = a[j-1]; j--; if(j<=0) break; } a[j] = tmp; } return a; } |
42. 查询问题:
|-直接查找
|-二分查找(BinarySearch):待查找数组必须要有序
public static int search(int[] a,int b) { int low = 0; int high = tmp.length; int mid = 0; while(low <= high) { mid = (low + high)/2; if(tmp[mid]==b) { return mid; } if(b { high = mid -1; } if(b>tmp[mid]) { low = mid +1; } return -1; } return 0; } } |
43. 随机生成50个数,每个数的范围是[10,50],统计每个数字出现的次数以及出现次数最多的数字和他的个数,最后将每个数字及其出现的次数打印出来,出现次数为0不打印。
public static void main(String[] args) { //生成50个数字,位于区间[10-50]并统计字数、打印 Random random = new Random(); int[] gennum = new int[41];//一共能生成41种数字 for(int i = 0;i<50;++i) { //Java中返回数据都是[),左开右闭 int num =(random.nextInt(41))+10; gennum[num-10]++; //很关键,利用数组的下标 } for(int i=0;i { if(0==gennum[i]) { continue; } System.out.println((10+i)+"出现次数为:"+gennum[i]); } int max = gennum[0]; for(int i=0;i { if(max { max = gennum[i]; } } for(int i=0;i { if(max==gennum[i]) { System.out.println(i+10); } } } |
44. Java中的常量都用大写字母命名,如果有多个单词,之间用下划线连接。Java中的常量一般都是 public static final类型的。之所以用static是因为这样每个对象只维护一份即可。项目中一般将常量统一维护在一个类中,如访问权限管理。
45. 集合(Collection):Java中的重中之重!
集合框架接口(以下都是接口),集合中存放的还是引用;类似于Arrays类至于数组,Collection对应Collections类提供对集合的辅助功能。例如Collections.reverseOrder返回一个比较器,Collections.sort用于排序,Collectins.shuffle用于打乱顺序,Collections.min/max返回最大值等。
Collection
|-Set
|-SortedSet/HashSet
|-List:有序集合;
Map
|-SortedMap/HashMap
46. List
|-ArrayList
|-LinkedList
47. ArrayList:数组实现,顺序表。
|-add方法添加元素
|-get方法取出元素,根据元素的索引,类似于数组,从0开始
|-可以存储重复的元素
|-size返回列表中元素的个数
|-toArray函数将ArrayList中的元素返回成一个数组,不能将Object[]强转换成Integer[],但可以通过遍历,将Object转换成Integer(数组不能强制类型转换)。
|-不能add(原生数据类型),因为原生数据类型不是Object(对象)
|-ArrayList底层使用数组实现,当使用不带参数的构造方法是,会生成长度为10的Object数组,但此时该ArrayList的size仍然为0。当加入的对象超过10的时候,底层的数组长度会按照以前数组长度的1.5倍+1的长度增加数组长度。然后将原数组的内容复制到新数组中。当新数组无法容纳时,在重复以上过程。
|-使用ArrayList删除其中元素时,因为底层为数组实现,所以ArrayList底层的数组长度并没有发生变化,被删除的元素位置后边的元素向前移动l。执行插入时也是如此。因此,ArrayList执行插入、删除时,代价非常高,因为涉及大量元素的移动问题。
48. LinkedList:链表实现,非顺序表。提供诸如addLast、addFirst等扩展功能。
|-addFirst、addLast
|-add(1,”fa”)在指定位置加入元素
|-set(3,”fd”)设置指定位置的值
|-底层不用数组实现,而是使用双向链表实现,执行插入、删除时快速。
|-LinkedList中存放的不是对象,也不是对象的引用,而是将一个Entry类型的对象放到LinkedList。Entry包含向前、向后引用和存放对象。
ArrayList与LinkedList比较
|-ArrayList底层使用数组实现,LinkedList底层使用双向链表实现;
|-当执行插入或者删除时,LinkedList较好;当执行搜索操作时,ArrayList较好。
|- ArrayList是顺序表,LinkedList是非顺序表。
|-存放到ArrayList中的是对象的引用,存放到LinkedList中的是根据对象构建的Entry对象。
49. 数据结构分为线性结构(线性表等)和非线性结构(树、图);栈和堆也是特殊的线性结构。
|-栈(Stack):Last In First Out
|-队列(Queue):First In First Out
使用LinkedList实现Statck和Queue:
Public class MyStack { Private LinkedList list = new LinkedList(); Public Object remove() { Return list.removeFirst(); } Public void push(Object o) { List.addFirst(); } Public Boolean isEmpty() { Return list.isEmtpy(); } } Public class MyQueue { Private LinkedList list = new LinkedList(); Public void put (Object o) { List.addLast(o); } Public Object get() { Return list.removeFirst(); } Public Boolean isEmpty() { Return list.isEmpty(); } } |
50. Object类equals的特点:
|-自反性:x.equals(x) == true; x不能为Null
|-对称性:x.equals(y)==true <==> y.equals(x)==true
|-传递性:x.equals(y)==true并且y.equals(x)==true =>x.equals(z)==true
|-一致性:x.equals(y)第一次调用为true,那么其第N此调用都为true,前提是在调用时一直没有修改x,y。
|-对于非空引用x,x.equals(null)返回false。
Object类的hashCode()特点:
|-在Java应用的一次执行过程中,对于同一对象的hashCode方法的多次调用,他们返回应该返回同样的值,前提是该对象的信息没有发生变化。
|-对于两个对象来说,如果使用equals方法比较为真,那么这两个对象的hashCode值一定是相同的。
|-对于两个对象来说,如果使用equals方法比较为假,并不要求其hashCode一定不同。但是如果不同,则可以提高其应用的性能。
|-对于Object类来说,不同的Object对象的hashCode值是不同的,因为Object类的hashCode表示的是对象的地址。
如果我们重写equals方法,那么就要重写hashCode方法;如果重写hashCode方法,也要重写equals方法(equals和hashCode荣誉与共)。如下例:
Public class people { String name; Public int hashCode() { Return this.name.hashCode();//name为String类型,String已经重写Object的hashCode方法,如果name相同,hashCode相同。 } Public Boolean equals(Object obj) { If(this==obj)//通用的equals流程! { Return ture; } If(null != obj && obj instanceof Student) { If(name.equals(s.name)) { Return ture; } } Return false; } } |
51. Set接口,不能包含重复的数据
|-HashSet类
|-SortedSet接口,默认实现排序功能
|-TreeSet(默认实现排序功能)
52. HashSet:无序存放,不能存放重复值。注意HashSet set = new HashSet();Peple p = new People(“a”); set.add(p);set.add(new People(“a”));两个都能加入,因为他们是两个对象。String s = new String(“a”); set.add(s);set.add(new String(“a”))却只能加入一次,因为String重载Object的hashCode方法。对于字符串而言,内容相同,hashCode就相同 。
|-当使用HashSet时,hashCode方法会调用,判断已经增加的对象的hashCode与新增的对象的hashCode是否形同。如果不一致,直接加进去;如果一致,再进行equals方法的比较。Equals方法返回true,表示对象已经加入,就不会加入这个对象。如果equals返回false,加入该对象。(eauqls相等,hashCode一定相等;hashCode相等,equals不一定相等)
53. TreeSet:默认带排序功能。
|-使用add添加元素。添加自定义对象时,添加一个没有问题,但是添加多个就要求元素必须能够进行比较,否则跑出castException。这样我们需要加入TreeSet集合中的类,实现Comparator接口的compare方法,该方法接受的参数为我们新建的类。
|-除了默认的构造方法为,还有接受一个Comparator的构造方法(策略模式)。
54. Iterator迭代器:
|-hasNext():还有剩余元素返回真
|-next():返回迭代当中的下一个元素
|-每一个Collection都提供iterator()方法让我们获取Iterator对象
55. Map(映射),key-value的形式,无序存放,底层存放Entry
56. HashMap:
|-与HashSet不同的是,新放入的元素会覆盖已有的同key元素,而不是不能放入,保证key值的唯一。
|-put方法加入元素,get(key)获取value值,contains(key)判断是否存在该key;
|-keySet返回key的Set(保证key的唯一性),values返回value的Collection(Value可以相同)
|-Map的遍历:
方法一: HashMap map = new HashMap(); Map.put(“a”,”aa”); Map.put(“b”,”bb”); Set set = map.keySet(); For (Iterator iter=set.iterator();iterator.hasNext()) { Object key = iter.next(); String value = (String) map.get(key); } 方法二: HashMap map = new HashMap(); Map.put(“a”,”aa”); Map.put(“b”,”bb”); Set set = map.entrySet(); For (Iterator iter=set.iterator();iterator.hasNext()) { Map.Entry enty = (Map.Entry)iter.next(); String key =(String) entry.getKey(); String value = (String) entry .getValue(); } |
57. TreeMap:
|-跟TreeSet类似,支持Comparator比较器(设计模式中的策略模式)
58. 命令行输入单词,统计单词出现的次数:
HashMap map = new HashMap(); For(int i=0;i { If(map.get(args[i])==null) { Map.put(args[i],new Integer(1)); } Else { Integer in = (Integer)map.get(args[i]); In = new Integer(in.intValue())+1; Map.put(args[i],in); } } |
59. 使用集合实现43例
Public class RandomTest { Random random = new Random(); For(int i=0;i<50;i++) { Map map = new TreeMap(); Int number = random.nextInt(41) + 10; Integer in = new Integer(number); If(map.get(in)==null) { Map.put(in,new Integer(1)); } Else { Int value = ((Integer)map.get(in).)intValue(); Map.put(in,new Integer(value+1)); }
Set set = map.entrySet(); For(Interator iter = set.interator();iter,hasNext()) Map.entry entry = (Map.entry)iter.next(); Integer key = (Integer)entry.getKey(); Integer value=(Integer)entry.getValue(); System.out.println(key+”:”+value); } } |
60. 策略模式(Strategy Pattern):定义一组算法,将算法封装,使调用的时候可以互换。
|-封装变化的部分
|-编程中使用接口,而不是接口的实现
策略模式的组成:
|-抽象策略角色:策略类,例如,由接口和抽象类组成的Comparator
|-具体策略角色:包装相关的算法,例如Comparator的具体实现类
|-环境角色:持有一个策略类的引用TreeSet
策略模式的编写步骤:
|-对策略对象定义一个公共的接口
|-编写策略类
|-在使用策略对象的类中保存一个策略对象的引用
|-在使用策略方法的类中,使用set和get或者构造方法完成赋值
//抽象的策略角色 Public interface Strategy { Public int calculate(int a ,int b); } |
//具体的策略类 Public class SubstractAtrategy implements Strategy { Public int calculate(int a, int b) { Return a-b; } } |
//具体的策略类 Public class AddAtrategy implements Strategy { Public int calculate(int a, int b) { Return a+b; } }
|
//环境角色 Public class Environment { Public Strategy strategy; Public Environment(Strategy s) { This.strategy = strategy; } } //或者 Public class Environment { Public Strategy strategy; Public void setStrategy(Stragegy s) { This.strategy = strategy; }
Public Strategy getStrategy() { Return this.strategy; }
Public int calculate(int a ,int b) { Return strategy.calculate(a,b); } } |
61. HashSet底层使用HashMap实现;当使用add方法添加元素到Set时,实际上是将该对象放到维护的Map对象的key,而value则都是同一个Object对象public static final。
62. HashMap底层维护一个数组,我们向HashMap中存放的对象实际存放在Entry对象的数组中。Entyr对象维护着key、value(用于存放HashMap的key、value)以及指向next的Entry引用(经过hash得到位置后指向的位置)和hash(位置值)。这里跟哈希表不同,HashMap对于hash计算重复的元素使用next进行链接,而且新元素放在链的最上面,旧元素放到最下面(遵循这样的理念:最近被访问的元素在将来还会被访问)。
小结:当使用HashMap的的put一对键值是,他会根据key的hashCode计算出一个位置,该位置就是此对象准备存放的位置。如果该位置没有对象存在,就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(Entry类有Entry类型的next成员变量,指向该对象的下一个对象),如果此链上有对象,使用equals方法进行比较,如果此链上的某个对象equals的比较为false,则将该对象放到数组当中,将数组中该位置以前的那个对象链接到该对象的后面。
63. 哈希表:将元素放到数组之前,先经过hash函数运算得到位置,然后将该元素放到该位置。再放入元素的元素需要再次经过hash方法元素得到位置,如果已有位置已有元素发生碰撞,则再次进行hash方法,称之为再散列。如果还是该位置,则认为数组将满,需要重新开辟内存,增加数组长度。
负载因子决定是否认为数组将满,如负载因子为0.75,则数组容量达到75%是就需要开辟新空间,增加数组长度。
初始数组长度:数组开始的长度。
散列函数:计算哈希码的值,得到存放对象的位置。
64. Vector继承自List,用法同ArrayList,底层用数组实现,现在不常用。优点是在多线程时,Vector是同步的。
65. HashTable与Vector是一个时代的,HashMap与Arraylist是一个时代的,后者较常使用。
66. HashTable的子类Properties经常被使用,用作配置文件。格式如下:
name=zhangsan
age=10
address=rizhao
System.getProperties()返回Properties对象内容为当前系统的环境变量。可以通过如下方法遍历之:
Properties p = System.getProperties(); Set set = p.keySet(); for(Iterator iter = set.iterator();iter.hasNext()) { String key = (String)iter.next(); String value = p.getPeroperty(key); System.out.println(key:value); } |
67. JDK 5.0中的新特性(貌似有点过时哈)
|-泛型
|-增强的for
|-自动拆箱、装箱
|-类型安全的枚举
|-可变参数
|-静态导入
68. 泛型:
所谓泛型,就是变量类型的参数化。
class GenericFoo { private T foo; public void setFoo(T foo){this.foo=foo;} public T getFoo(){return this.foo;} } |
使用: GenericFoo GenericFoo Boolean b =foo1.getFoo();//编译时能够确定,返回Boolean类型,不使用泛型编译时不能确定返回具体的类型; Integer i = foo2.getFoo();//返回Integer |
使用泛型时不能foo1=foo2,我们要将泛型作为类型的一部分,很显然foo1是Generic |
泛型可以不使用,上面同样可以使用GenericFoo f = new GenericFoo();此时,约定T extends Object,即T可以为Object及其子类。 |
69. Java1.5之后的集合Collection都使用泛型编写,在使用集合的取出操作时,可以不用再写强制进行转换了。
Set遍历的第二种方法:
Set for(Interator { Map.Entry String key = entry.getKey(); String value = entry.getValue(); } |
70. 如果想限定泛型的类型时,使用extends关键字限定该类型必须是extends类型的本身或者子类,方法如下:
public class Test
{
private T[] data;
}
此处的extends表示类在定义时接受的类型。
71. 类型通配声明:
//类声明 public class A {} |
//使用 A extends List> a = null; //里面的泛型指向List的子类 a = new A a = new A |
A super List> a = null; //里面的泛型是List的父类 a = new A |
使用通配符 extends Object>时,可以简写为> |
此处的extends表示类在使用时其引用具体可以指向什么样的类型。
71. for each简化了对集合的遍历。其主要的用途也是如此,但对集合或者数组中特定的变量的访问就不建议使用for each了,因为其丢失了下标信息。
for(type element:array)
{
System.out.println(element);
}
72. 自动拆箱:Integer->int和自动装箱int->Integer
Integer类有一个缓存,它会缓存-128到127之间的整数。使用自动拆箱和自动装箱时,Integer i1=100;Integer i2=100;则i1==i2;如果i1和i2的值是200,则i1!=i2。但是此种情况仅仅适用于自动拆箱和自动装箱的时候,如果使用Integer i1 = new Integer(100) ;Integer i2 = new Integer(100);i1!=i2;因为使用了new,自动分配了内存空间。可以联想String Pool辅助记忆。
73. 可变参数:允许方法中可以写多个参数,但是可变参数必须是参数中的最后一个参数,即参数中最多能有一个可变参数。使用方法如下:
public int sum(int… nums)
{
int sum=0;
for(int num:nums) //结合for each使用
{
sum+=num;
}
}
使用:
int res = sum(1,2);
int res2 = sum(new int []{1,2,3}); //可以传数组
可变参数本质上就是一个数组,对于某个声明了可变参数的方法来说,我们既可以传递离散的值,也可以传递数组对象。
74. 枚举类型:
枚举是一个类型,不是类。使用enum定义枚举类型。
public enum Color
{
Red,
White,
}
使用:
Color myColor = Color.Red;
枚举类型还提供values和value两个静态的方法,建议结合for each使用。
for(Color color:Color.values()) //values()得到值的数组
{
System.out.println(color);
}
对于枚举可以有如下使用:
public enum Coin
{
penny(“hello”),nickel(“world”); //因为构造方法要求传递参数,所以采用这种形式!!!
private String value;
Coin(String value)
{
this.value = value;
}
public static void main(String[] args)
{
Coin coin = Coin.nickel;
System.out.println(coin.getValue());
}
}
枚举中可以有main方法、可以有成员变量、构造方法、一般方法等。
定义枚举类型本质上是在定义一个类别,只不过很多细节有由编译器实现,因此,在某种程度上,enum与class、interface处于同一级别。使用enum定义的枚举继承自java.lang.Enum类,每个枚举的成员是一个java.lang.Enum类的实例,预设为public static final的。换句话说,当定义一个枚举类型后,在编译时刻就能确定该枚举类型有几个实例,分别是什么。在运行期间我们就不能再创建新的实例了。这些实例在编译期间就已经完全确定下来了。
Enum常用于限定传递的参数类型,使得类型只能去那么几个值。
EnumSet只能存放enum,并且由于枚举在编译器已经确定枚举实例的个数,因此,不能使用add等方法添加枚举,而是使用其of函数添加枚举类型。
75. 静态导入:直接使用静态变量和静态方法,无须导入类名。
使用方法:
import static com.test.Test.AGE;//一直导入到静态方法、静态成员变量,而不是包
…
int a = AGE; //使用时直接使用即可
76. 反射机制(Reflection):
|-在运行期间判断一个对象所属的类
|-在运行期间构造一个类的对象
|-在运行期间判断一个类具有的成员变量和方法
|-在运行期间调用任意一个对象的方法
Java不是动态语言(例如Ruby等),但是反射机制使得Java具有动态语言的特性。
API位于java.lang.reflect包中:
|-Class:java.lang包下,继承自java.lang.Object类,代表一个类
|-Field:代表类的成员变量(类属性)
|-Method:代表类的方法
|-Constructor:代表类的构造方法
|-Array:提供动态创建数组,以及访问数组元素的静态方法
Class> classType = Class.forName(‘java.lang.String”);//获得String类 Method[] methods = classType.getDDeclaredMethods();//获取Stirng类Method信息 |
//使用反射操作类 Class> classType = Class.forName(com.test.MyClass); //或者 //Class> classType = MyClass.class; //生成类对象 Object myClass=classType.newInstance(); //要求类的构造方法不能使用参数 //或者使用Constructor,要求传入Object类型可变参数(可用数组替代) Constructor con = classType.getConstructor(new Class[]{}); //调用无惨 //Constructor con = classType.getConstructor(new Class[]{String.class,Integer.class}); Object obj = con.newInstance(new Class[]{}); System.out.println(myClass instanceof MyClass); //获取Methods Method[] methods = classType.getDDeclaredMethods(); //或者getMethod方法的参数为:方法名,参数构成的数组,通过这两个参数能唯一确定那个方法,利用参数构成的数组能够防止重载在成的方法同名。 Method method1 = classType.getMethod(“method1”,new Class[]{int.class,int.class}); //调用方法,参数为:调用方法的对象,方法的参数数组 Object result = method1.invoke(myClass,new Object[]{1,2}); System.out.println((Integer)result); |
java中,无论生成多少个类的对象,这些对象都会对应于同一个Class对象。
77. Object类的getClass方法返回调用对象的运行时的Class。该方法是final的,子类不能改变。可以通过getClass返回Class对象。至此获取某个类或对象对应的Class的三种方式如下:
1.使用forName:Class.forName(“java.lang.String”); |
2.使用类.class:String.class; |
3.使用对象的getClass()方法:String s = “A”; Class> class = s.getClass(); |
78. 若通过类的不带参数的构造方法生成对象,有如下两种方法:
1.先获得Class对象,然后通过该Class对象的newInstance方法直接获得。 |
2.先获得class对象,然后通过该对象获得对应的Constructor对象,然后通过Constructor对象的newInstance获得。 |
若想通过带参数的构造方法得到对象,只能通过以上的第二种方法,此时接受的参数为class的可变参数,可以用数组new Class[]{Stirng.class,Integer.class}填入。因为可变参数其实就是数组。获取对象后调用方法是可使用new Object[]{“conan”,1}传入参数。
79. 获得对象的所有成员变量使用getFields方法返回public的属性,getDecaredField返回所有变量,包括私有的成员变量。
Field[] fields = classType.getFields(); String name = fields.getName();//获取属性名字 String firstLetter = name.substring(0,1).toUpperCase(); String getMethodName = “get” + firstLetter + name.subString(1); //获得get方法名字 String setMethodName = “set” + firstLetter + name.subString(1); //获得set方法名字 |
80. 获得对象的所有方法使用getMethods(返回public方法)方法,getDecaredMethod返回所有声明的方法,包括私有的成员方法。
Method getMethod = classType.getDecaredMethod(getMethodName,new Class[]{}); Method setMethod = classType.getDecaredMethod(getMe thodName,new Class[]{String.class}); Object value = getMethod.invoke(object,new Object[]{}); setMethod.invoke(object,new Object[]{value}); |
81. Array类:提供动态访问、创建数组的静态方法
Class> classType = Class.forName(“java.lang.String”); Object array = Array.newInstance(classType,10); //数组类型,数组长度 Array.set(array,5,”hello”);//设置array数组的第5个元素为hello String str = (String)Array.get(array,5); //得到array数组的值 |
82. Integer.TYPE à返回原生数据类型int.class
Integer.class à返回java.lang.Integer.class
83. 使用反射调用对象的私有方法,访问私有成员
A a = new A();//A类有sayHello方法 Class> classType = Class.forName(“com.test.A”); Method method = classType.getDeclaredMethod(“sayHello”,new Clas[]{String.class}); //String str=(String)method.invoke(a,new Object[]{“ZhangSan”});编译出错,不能访问private的方法,能返回但是不能调用 method.setAccessible(true); //设置Accessible为True String str=(String)method.invoke(a,new Object[]{“ZhangSan”}); |
//通过getDecaredField获得所有属性,包括private Field f = classType.getDecaredField(); f.setAccessible(true); f.set(a,”B”); |
84. 代理模式(Proxy)的作用是为其他对象提供一种代理以控制对该对象的访问。代理对象在客户端和目标对象之间起到中介的作用。代理模式涉及的角色:
|-抽象角色:声明真实对象和代理对象的共同接口;->租房子
|-代理角色:代理角色内部含有真实对象的引用,能够操纵真实对象;->中介
|-真实角色:最终要引用的对象。->房东
//抽象角色,抽象类和接口都可以 Public abstract class Sbuject { Public abstract void request(); } |
//真实角色 Public RealSubject extends Subject { Public void request() { System.out.println(“From real subject!”); } } |
Public proxySubject extends Subject { Pubic RealSubject realSubject;//代理角色内部引用真实角色 Public void request() { this.preRequest(); If(null == realSubject) { realSubject = new RealSubject(); } realSubject.request();//完成真实角色完成的事情 this.postRequest(); } Private void preRequest()//代理角色自身完成的事情 { System.out.println(“PreRequest”); } Private void postRequest()//代理角色自身完成的事情 { System.out.println(“PostRequest”); } } |
//使用 Public class client { Public static void main(String[] args) { Subject subject = new ProxySubject();//new 的是代理类 Subject.request(); } } |
上述代码描述的是静态代码类,但存在一些问题。例如我们调用每次调用代理类,在其内部都会生成真实类,这造成真实类的大量膨胀。而且我们必须知道真实类才能调用代理。解决方法是使用动态代理类。
85. 动态代理类:在运行时生成类,需要,然后这个类就实现了这些接口。可以将类的实例作为提供的interface。Java动态代理类位于java.lang.reflect包下主要涉及两个类:
|-Interface InvocationHandler:需要实现该接口
|-Proxy:
动态代理创建步骤:
1. 创建实现接口InvocationHandler,实现invoke方法
2. 创建被代理的类以及接口
3. 通过Proxy静态方法newProxyInstance创建代理
//抽象角色 Public interface Subject { Public void request(); } |
//真实角色 Public class RealSubject { Public void request() { System.out.pirntln(“From real subject!”); } } |
//动态代理类 Public class DynamicSubject implements InvocationHandler { //该代理类的内部属性是Object类型,使用时传递进来一个对象。该类实现了invoke方法,该方法中的method.Invoke其实就是调用被甙类对象的将要执行的方法,方法参数是sub,表示该方法从属于sub,通过静态代理类,我们可以执行真实对象的方法前后加入自己一些额外的方法。 Private Object sub; //使用Object可以动态接受任何对象,而不限定为Subject Public DynamicSubject(Object obj) { This.sub = ojb; } @override Public Object invoke(Object proxy,Method method,Object[]args) { Sytem.out.println(“Before calling:” +method); method.invoke(sub,args); Sytem.out.println(“After calling:” +method); } } |
Public class Client { Public static void main(Stirng[] args) { RealSubject realSubject = new RealSubject(); DynamicSubject handler = new DynamicSubject(realSubject); //一次性生成代理,运行期间生成类对象,newProxyInstance参数:类的classLoader,类接口,代理类 Class> classType = handler.getClass(); //subject是动态生成的代理类,既不是RealSubject,也不DynamicSubject,而是实现了Subject接口的代理类。 Subject subject = Proxy.newProxyInstance (classType.getClassLoader(),realSubject.getclass().getInterfaces(),handler); subject.request(); //调用任何方法都激活Dynamic的invoke方法并执行。 } } |
86. Java注解(Annotation):类、接口、Enum、Annotation同出一个级别。
使用@interface定义Annotation,实际上自动继承了java.lang.annotation.Annotation接口。但如果我们定义一个接口,并让该接口继承自Annotation,我们定义的接口依然是接口而不是注解,只能通过@interface定义注解。Annotation本身是接口而不是注解。在定义Annotation时不能继承其他Annotation的接口。继承自Annotation并不直接影响代码语义。
1. @override:子类需要重写父类的方法。RUNTIME
2. @Deprecated:不建议被使用。RUNTIME
3. @SuppressWarnings(“unchecked”):表示抑制警告。SOURCE
Public @interface AnnotationTest { String value() default “hello”; } //如果属性值为value,则在使用时不用写@Annotation(value=”hello”),直接写@Annotation(“hello”)即可。而属性值不为value(例如val),则需要使用@Annotation(val=”hello”)这种形式,明确指定给谁赋值。使用default关键字添加默认值。多个值采用逗号隔开:value1=”d”,value2=”f” |
Public class AnnotationUsage { @AnnotationTest(”hello”) Public void method() { @AnnotationTest(”world”) System.out.println(“Usage of annotation”); } Public static void main(String[] args) { AnnotationUsage au = new AnnotationUsage(); Au.method(); } } |
86. Retention和RetentionPolicy:RetentionPolicy指示编译程序如何对待自定义的注解,默认保存到.class文件中。RetentionPolicy是枚举类型:
1. CLASS:注解保存在.class文件当中,不会再运行时被虚拟机读取
2. SOURCE:注解将被编译器抛弃掉
3. RUNTIME:注解被保存在.class文件中并能被JVM利用反射读取到
@Retention(RetentionPolicy.RUNTIME) Public @interface MyAnnotation { String hello() default “Haha”; String world(); } |
//使用 @MyAnnotation(hello=”bei”,world=”shandong”) Public class MyTest() { @MyAnnotation(hello=”tian”,world=”shanxi”) Public void output() { System.out.println(“Out”); } } |
//获取注解信息 Public class MyReflection { Public static void main(Stirng[] args) { MyTest myTest = new MyTest(); Class Method method = c.getMethod(“output”,new Class[]{}); //判断注解是否存在 If(method.isAnnotationPresent(MyAnnotation.class)) { method.invoke(myTest,new Objct[]{}); //返回注解实例 MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); String hello = myAnnotation.hello(); String wrd = myAnnotation.world(); } //RetentionPolicy为RUNTIME,否则不能调用
//获取全部Annotation,同样要求RetentionPolicy为Runtime Annotation[] annotations = method.getAnnotations(); For(Annotation annotation: annotations) { System.out.println(annotation.annotationType()): } } } |
87. Target:定义注解使用时机,ElementType指定可以修饰哪些目标,变量?方法等。
|-TYPE:修饰类
|-METHOD:修饰方法
88. |-FIELD:修饰属性
@Target(ElementType.METHOD) Public @interface MyTarget {
} |
Public class MyTargetTest { @MyTarget(“hello”) Public void doSomething() { System.out.println(“Hello”); } } |
89. 子类默认不会继承父类的Annotation,如需继承,加上java.lang.annotation.Inherited类型的Annotation。
90. JUnit(3.8、4.x):3.8完全基于反射,4.x基于反射和Annotation。单元测试不是证明你是对的,而是证明你没有错误。
Public class Test extends TestCase//基于JUnit3 { Public void testAdd()//每个方法都要以test开头,才能以JUnit运行该方法 { System.out.println(“JUnit”); } } |
Public class Test1 //基于JUnit4 { @Test Public void testAdd()//每个方法都要以test开头,才能以JUnit运行该方法 { System.out.println(“JUnit”); } } |
JUnit执行的一般流程:
1. 首先获得带测试类所对应的Class对象
2. 然后通过该Class对象获得当前类中所有的public方法(JUnit要求测试方法为public)
3. 遍历该Mthod数组,取得每一个Method对象
4. 调用每个Method对象的isAnotationPresent(Test.class)方法,判断该方法是否被Test注解所修饰
5. 如果返回True,调用method。Invoke()方法。否则不执行。
91. 异常(Exception):所有的异常都继承自Exception这个父类。Exception继承自Throwable。Thorwable分为Exception和Error。
每个异常都是类,程序运行中产生异常就产生了一个异常的类对象。
Java中的异常分为两大类:
1. Checked exception:检查的异常,也就是非Runtime Exception。继承自Exception而没有继承Runtime Exception的异常都是非运行时异常。对于非运行时异常,必须进行处理,处理方法有两种:使用try catch捕获,此时程序仍然执行。或在调用异常产生的方法的地方用throws Exception抛出异常。抛出的异常由调用该方法的类方法处理,直至抛出到main方法,由JVM处理,此时程序不再执行。
2. Unchecked exception:未检查的异常,也就是Runtime Exception。Java中所有的运行时异常都会直接或者间接继承Runtime Exception。运行期间异常可处理也可不处理,推荐不进行处理。
|-常见的非运行时异常(检查异常):ClassNotFoundException、InllegalAccessException、NoSuchMethodException等。
|-常见的运行时异常:ClassCastException、NullPointerException、IndexOutOfBoundsException。
NullPointerException是空指针异常,出现该异常的原因在于某个引用为null,但却调用了它的方法,就出现该异常。
Int c = 0; try { Int a = 3; Int b=0; C= a/b; System.out.println(“A”); } catch(ArithmeticException e )//产生的异常对象赋值给e,e代表产生的异常对象 { e.printStackTrace(e); } finally { System.out.println(“Finally”); } //出现异常后,转而执行catch中的语句(不发生异常,catch中的不执行)。try块中出现异常之后的语句就不会执行了,如上例,不会打印A。可以使用finally执行后续的操作。finally中的语句一定会执行。 ArithmeticException是RuntimeException。 |
//也可以使用throw抛出异常 public class Test { //方法用thorws,内部用throw public void method() throws Exception { System.out.println(“hello”); throw new Exception();//抛出异常,由调用method方法的对象处理异常。 或者可以在这一句的地方用try…catch进行捕获异常 } public static void main(String[] args) throws Exception//main方法抛异常给JVM { //可以采用try…catch或者thorws Exception方法抛出异常 //try //{ // t.method(); //} //catch(Exception e ) //{ // e.printlnStackTrace(); //} Test t = new Test(); t.method(); } } |
|-throws:写在方法定义的后面,用于声明异常。
|-throw:写在方法中,用于抛出异常。抛出的异常要么自己用try…catch处理,要么用声明方法时throws抛出。
94. 所谓自定义异常,通常就是定义一个继承自Exception类的子类。
public class Test extends Exception { public Test() { super(); } public Test(String message) { super(message); } } |
//使用Test异常 public TestException { public void method(String str) throws Test//告诉调用该方法的对象处理 { if(null == str) { throw new Test(“传入的字符串参数不能为Null”); } else System.out.println(str); } /*方法一 public static void main(String[] args) throws Test { TestException t = new TestException(); t.method(“Hello”); } */ //方法二 public static void main(String[] args) { Test t = new Test(); try{t.method();} catch(Exception e ) { e.printStackTrace(); } finally { System.out.pirntln(“异常处理完毕”); } } } |
92. 一个try可以有多个catch捕获异常。catch匹配的时候按照catch书写的顺序进行,一旦被捕获,后面的catch变不会再执行。因此父类Exception应放到catch的最后再写。可以有try…catch、try…finally,try不能单独使用。
93. try块出现return语句,那么首先将finally中的语句执行完毕,然后方法返回退出。
try块中出现System.exit(0)(Java虚拟机退出),那么不会执行finally中的代码,因为System.exit(0)终止Java虚拟机,程序会在虚拟机终止前结束。
//执行顺序。输出结果:进入到try块,进入finally。 public class Test { public void method() { try{Systm.out.println(“进入try块”); return;} catch(Exception e){System.out.println(“异常发生”)} finally{System.out.println(“进入到finally”);} System.out.println(“异常后续代码”); } public static void main(String[] args) { Test t = new Test(); t.method(); } } |
94. GUI(Graphical User Interface),图形用户界面。Java GUI分为两种:一种是AWT(Abstract Window Toolkit)抽象窗口工具集,重量级的(绘制元素借助于操作系统,不同操作系统外观不同)。另一种是Swing,轻量级的,不依赖于底层细节。Eclipse提供插件用于图形化Swing编程。
95. AWT:
GUI:分为组件(Component)和容器(Container,也是Component的字了)。
Container:
1. Window:分为Frame和Dialog
2. Panel:必须放到Window中,用于装载组件。定位组件使用布局管理器。
五中布局管理器:
1. BorderLayout:五个区域:东南西北中。
2. FlowLayout:不限制组件大小,放不开放到第二行
3. GridLayout:格式布局。
4. Card:标签布局。
5. GridBag:增强的格式布局。
Frame默认BoardLayout,Panel默认FlowLayout。
AWT事件处理:
1. 事件:描述发生了什么的对象
2. 事件源:事件的产生器
3. 事件处理器:接受事件、解释事件并处理用户交互的方法
当单机一个按钮时会产生一个事件(ActionEvent),然后检查是否有与该按钮关联的事件处理器,如果没有,那么什么都不执行。如果有,就会将该事件传递给与该按钮关联的事件处理器方法,作为该方法的参数,之后该方法会自动调用,并能够获得事件发生时与该事件以及事件源相关联的哪些信息。添加事件处理器,使用addXXXListener方法,ActionEvent的getActionCommand返回命令名,actionPerformed方法负责执行事件响应。
适配器(Adapter):在实现类继承Adapter类时,我们只需实现自己想实现的类。事实上,Adapter类实现所有接口,只是不做任何操作。
96. 观察者模式(Observer Pattern):定义一种一对多的依赖关系,让多个观察者同事监听某一个对象。这个对象的状态发生变化时,会通知所有观察者,让他们自动更新自己。观察者模式的组成:
1. 抽象主题角色:把所有观察者保存在一个集合中。抽象主题提供接口,可以增加、删除观察者。
2. 抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题通知时更新自己的状态。
3. 具体主题角色:具体主题内部状态发生变化是,给所有观察者发送消息。
4. 具体的观察者角色:实现抽象观察者角色的接口。
//抽象主题(被观察者) public interface Watched { public void addWatcher(Watcher watcher); public void removeWatcher(Watcher watcher); public void notifyWatchers(Stirng str); } |
//抽象观察者 public interface Watcher { public void update(String str); } |
//具体的主题角色 public ConcreteWatched implements Watched { private List public void addWatcher(Watcher watcher) { list.add(watcher); } public void removeWatcher(Watcher watcher) { list.remove(watcher); } public void notifyWatchers(Stirng str) { for(Watcher watcher:list) { watcher.update(); } } } |
//具体的观察者 public ConcreteWatcher implements Watcher { public void update(String str) { System.out.println(“Update”); } } |
//使用 public class Test { public static void main(Stirng[] args) { Watched girl = new ConcreteWatched(); Watcher watcher1 = new ConcreteWatcher(); Watcher watcher2 = new ConcreteWatcher(); Watcher watcher3 = new ConcreteWatcher(); girl.addWatcher(watcher1); girl.addWatcher(watcher2); girl.addWatcher(watcher3); girl.notifyWatcher(“Happy”); gril.removeWatcher(watcher2); } } |
97. JDK对观察者模式的支持。
|-Observable类:用于创建可以观测到你的程序的其他部分的子类---主题角色。
1. 如果它被改变,必须调用setChanged()方法
2. 通知观测者是必须调用notifyObservers()方法
|-Observer接口:观察者。
98. Java内部类(Inner Class)生成的.class文件为Out$Inner.class,内部类共分为四种:
1. 静态内部类
a. 采用public static class Inner修饰,定义在类中
b. 采用Out.Inner inner = new Out.Inner();生成内部类实例
c. 只能访问静态成员变量、方法。
2. 成员内部类
a. 定义在类内部,没有使用static修饰
b. 在外部类外面,采用Out.Inner inner = new Out().new Inner();生成内部类。如果内部类和外部类中有同名的方法、变量,在内部类中通过Out.this.方法/属性引用外部类方法/属性。在外部类中,使用this.new Inner();创建内部类实例。
c. 可以访问外部类的一切成员变量、方法
3. 局部内部类
a. 定义在方法中,不能被声明为public、private、protected和static等。
b. 只能访问方法中final的变量。
c. 无法在外部类外部创建局部内部类的引用,只能在外部类中创建。
4. 匿名内部类(Annonymous Inner Class)
a. 局部内部类的特殊形式,没有class,没有extends、implements也没有constructors。
b. 匿名内部类隐式继承父类或者调用接口。
c. System.out.println(new Date(){});使用匿名内部类,传到println()中的不是Date类的实例,而是继承自Date的匿名内部类的实例。注意,这里使用的是new Date(){},而不是new Date()!!!
d. 生成的.class文件为Outer$1表示,用数字表示匿名内部类的名字。
101. Java I/O系统:
File类:
|-一个File类的对象,表示了磁盘上的文件或者目录。
|-File类提供了与平台无关的方法来对磁盘上的目录和文件进行操作
|-File类不提供对文件的读写操作,只能创建删除文件,查看文件的信息(大小等)。
//创建文件 File file = new File(“C:/a.txt”);//在C盘中创建a.txt(或者使用c:\\a.txt但只限定在win) file.createNewFile(); |
//创建已有目录下文件 File file = new File(“c:/abc”); File file1 = new File(file,”hello.txt”); file1.createNewFile();//在已有文件夹abc中创建hello.txt |
//判断是目录还是文件 file.isFile()、file.isDirectory() //可读、可写 file.canRead();file.canWrite(); |
//创建目录、返回目录名 File file = new File(“c:/abc/xyz/hello”); file.mkdir();//在c:abc/xyz下创建目录hello,要确保上级目录存在 file.mkdirs();//创建所有文件目录,不用保证其上级目录是否存在 String[] name=file.list();//返回目录下的文件及目录的String名字,不打印子目录文件目录 File[] files = file.listFiles();//返回所有的file(目录+文件)对象 files.getParent();//打印父目录 |
//删除File file.delete(); |
//使用FilenameFilter接口,打印某种类型的文件 file.list(new FilenameFilter() { @Override public Boolean accept(File dir,Stirng name) { if(name.endWith(“.java”)) return true; else return false; } }); |
102. 递归(Recursion):就是方法自身去调用自身。对于递归来说,一定有一个出口让递归结束,以此保证不出现死循环。
//用递归实现阶乘 public int compute(int number) { if(1 == number||0==number) { return 1; } else { number*compute(number-1) } } |
//使用递归实现斐波拉切数 public int fab(int n)//打印第n个数 { if(1 == n || 2 == n) { return 1; } else { return fab(n-1)+fab(n-2); } } |
//使用递归删掉目录中的目录和文件(Delete不允许删除非空目录) public void deleteAll(Fiile file) { if(file.isFile()||file.list().length==0)// file.delete(); else { File[] files = file.listFiles(); for(File f : files) { deleteAll(f); f.delete(); } } } |
//使用递归做文件目录展示 public class ListAll { //用于判断文件或目录所处的层次 private static int time; //整理文件数组,使目录排在文件之前 public static File[] sort(File[] files) { ArrayList for(File f:files)//寻找所有目录 { if(f.isDirectory()) sorted.add(f); } for(File f:files)//寻找所有文件 { if(f.isFile()) sorted.add(f); } return sorted.toArray(new File[files.length]); } //处理缩进 private static String getTabs(int time) { StringBuffer buffer = new StringBuffer(); for(int i=0;i |
103. 流(Stream):Java通过流完成输入和输出。从功能分为输入流和输出流输入流指从磁盘流入到内存,输出流指从内存输出到磁盘。
从结构分为:字节流和字符流。底层均使用字节流实现。
还可分为:结点流和过滤流(FilterInput[Output]Stream)。结点流:从特定的地方读写的流类。过滤流:使用结点流作为输入或输出。可表示为:结点流->过滤流->过滤流->Java程序
字节流:(最重要的方法为Read和Write)
|-InputStream(输入流,抽象类):
|-OutputStream(输出流,抽象类):
字符流:
|-Reader(输入流,抽象类):
|-Writer(输出流,抽象类):
//读数据逻辑 open a stream->while more information->read information->close the stream //写数据逻辑 open a stream->while more information->write information->close the stream |
操纵文件的InputStream和OutputStream类(底层通过native使用C语言实现):
//FileInputStream public class Test { public static void main(String[] args) { InputStream is = new FIleInputStream(“c:/hello.txt”); byte[] buffers = new byte[200]; int length = 0; //实际读出的byte长度,返回-1表示读完了 while(-1!=(length=is.read(buffers,0,200)))//从位置0开始,每次读200个 { String str = new String(buffers,0,length);将字节转成字符串 System.out.println(str); } is.close(); } } |
//FileOutputStream public class Test { public static void main(String[] args) { //如果文件不存在,创建;如果文件存在,清空并写入 OutputStream os = new FileOutputStream(new File(“c:/test.txt”)); //添加内容到结尾 //OutputStream os = new FileOutputStream(new File(“c:/test.txt”),true); String str = “hello”; byte[] buffer = str.getBytes(); os.write(buffer); os.close(); } } |
104. I/O流链接:
Input Stream Chain:
FileInputStream(从文件中获取输入字节)->BufferedInputStream(增加缓冲的功能)->DataInputStream(增加了读取Java基本数据类型的功能)
Output Stream Chain:
FileOutputStream(从文件中获取输入字节)->BufferedOutputStream(增加缓冲的功能)-> DataOutputStream(增加了读取Java基本数据类型的功能)
105. BufferInputStream、BufferOutputStream都是过滤流(接受字节流),提供缓冲的功能,能够提高效率。FileInputStream、FileOutputStream频繁跟外设打交道,降低效率。
//BufferOutputStream public class Test { public static void main(String[] args) { OutputStream os = new FileOutputStream(“1.txt”); BufferedOutputStream bos = new BufferedOutputStream(os); bos.write(“Hello”.getBytes()); bos.close();//写入并清空缓冲流、然后关闭字符流关闭外层即可 os.close();//写上关闭也行 } } |
106. 字节数组输入流ByteArrayInputStream:
//读两次,一次大写,一次小写 byte b[] = “Hello”.getBytes(); ByteArrayInputStream input = new ByteArrayInputStream(b); for(int i=0;i<2;++i) { int c; while(-1!=(c=input.read)) { if(0==i) { System.out.pirntln((char)c);//Java中的字符内部都是整形实现的,因 //读取出来是int型,需要转换为char。 } else { System.out.println(Character.toUpperCase((char)c)); } input.reset();//重置到流的开始位置 } } input.close(); |
字节数组输出流ByteArrayOutputStream:
ByteArrayOutputStream f = new ByteArrayOutputStream(); String str = “Hello”; byte[] byte = str.getBytes(); f.write(buffer);//将buffer中的字节写入到f当中 byte[] result = f.toByteArray();//将f流中的字节放到result OutputStream os = new FileOutputStream(“2.txt”); f.writeTo(os);//f中的字节数组写入到os中 f.close(); os.close(); |
107. DataOutputStream、DataInputStream数据输出、输入流,能够输出java数据类型的数据,也是过滤流。
//保存、读取的是二进制的数据 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream (new FileOutputStream(“data.txt”))); byte b=9; int i=12; char ch=’a’; float f=3.3f; dos.writeByte(b); dos.writeInt(i); dos.writeChar(ch) dos.writeFloat(f); dos.close(); DataInputStream dis = new DataInputStream(new BufferedInputStream( new FileInputStream(“data.txt”))); //读和写的顺序要保持一致 System.out.println(dis.readByte()); System.out.println(dis.readInt()); System.out.println(dis.readChar()); System.out.println(dis.readFloat()); |
108. 装饰模式(Decorator):I/O中大量应用。装饰模式是类继承的替代方案,可以在不创建更多子类的情况下扩展对象的功能。例如:new DataOutputStream(new BufferedOutputStream(new FileOutputStream(“data.txt”)));在不增加类的情况下可以完成更多的功能。
装饰模式把客户端的调用委托到被装饰的类。装饰模式角色有:
|-抽象的构件角色(Component):给出一个接口,用以规范接受附加责任的类。
|-具体的构件角色(Concrete Component):将要接受附加责任的类。
|-抽象装饰角色(Decorator):持有一个构件对象的引用,并定义一个与抽象角色一样的接口。
|-具体装饰角色(Concrete Decorator):给构件对象加上附加的责任。
//抽象的构件角色(Component): Public interface Component { Public void doSomething(); } |
//具体的构件角色(Concrete Component) Public class ConcreteComponent { Public void doSomething() { System.out.println(“Function A”); } } |
//装饰角色(Decorator):实现抽象构件接口,持有构件的引用。用构件引用实现接口方法。 Public class Decorator implements Component { Public Component component; Public Decorator(Component component) { This.component = component; } Public void doSomething() { Component.doSomething(); } } |
//具体装饰角色(Concrete Decorator) Public class ConcreteDecoratorA extends Decorator { Public ConcreteDecoratorA(Component component) { Super(component); } Public void doSomething() { super.doSomething(); this.doAnotherthing(); } Private void doAnotherthing() { System.out.println(“Function AA”); } } Public class ConcreteDecoratorB extends Decorator { Public ConcreteDecoratorB(Component component) { Super(component); } Public void doSomething() { super.doSomething(); this.doAnotherthing(); } Private void doAnotherthing() { System.out.println(“Function AB”); } } |
//使用 Public class Test { Public static void main() { //结点流 Component component = new ConcreteComponent(); //过滤流 Component component1 = new ConcreteDecoratorA(component); //过滤流 Component component2 = new ConcreteDecoratorB(component1); Component2.doSomething(); } } |
109. 字符流:顶层为Reader和Writer。字节流不能操纵Unicode字符(Unicode使用2个字节表示,能够表示所有语言),而字符流能够处理。Java采用16位的Unicode字符。即两个字节表示一个字符,一个字符占16位。字节流一次读取一个字节,字符流读取两个字节。字符流的类和方法同字节流的类、方法十分相同。
110. InputSteamReader(继承于Reader)和OutputStreamWriter(Writer)用于处理字符流的基本类。这两个类是字节流和字符流之间的桥梁。其中InputStream、OutputStream对应的是byte[],而Reader和Writer对应的是char[]。
Public class StreamTest { Public static void main(String[] args) { FileOutputStream fos = new FileOutputStream(“file.txt”); OutputStreamWriter osw = new OutputStreamWriter(fos); BufferWriter bw = new BufferedWriter(osw); bw.write(“Hello World!\n”); bw.write(“Hello Kitty!”); bw.close();
FileInputStream fis = new FileInputStream(“file.txt”); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); System.out.pirntln(br.readLine());//读一行,碰到回车停止。如果没有了,返回null(可作为结束读取的标志)。 br.close(); } } |
//System.out和System.in Public class StreamTest2 { { InputStreamReader isr = new InputStreamReader(System.in);//接受键盘输入 BufferedReader br = new BufferedReader(isr); String str; While(null!=(str=br.readLine())) System.out.pirntln(str); } } |
111. FileReader:创建一个文件的字符流读取器
FileReader fr = new FileRead(“com”);//接受File或者String BufferedReader br = new BufferedReader(fr); String str; While(null!=(str=br.readLine())) System.out.println(str); br.close(); |
FileWriter:创建一个写文件的Writer
String str = “hello world welcome”; char[] buffer = new char[str.length()]; str.getchar(0,str.length(),buffer,0);//将字符串拷贝到字符数组 FileWriter f = new FileWriter(“file.txt”); For(int i=0;i { f.write(buffer[i]); } f.close(); |
CharArrayReader:字符数组作为源的字符流输入流
Stirn tmp = “helelo”; Char[] ch = new char[tmp.length]; Tmp.getChars(0,tmp.length,ch,0); CharArrayReader input = new CharArrayReader(ch); Int i; While(-1!=(i=input.read())) System.out.println(i); |
BufferedReader、BufferedWriter用法同BufferedInputStream、BufferedOutputStream。
112. ReadomAccessFile:父类为Object,能够写入数据类型。
Public class RandomAccessFileTest { Person p = new Person(1,”a”,3.3); RandomAccessFile raf = new RandomAccessFile(“a.txt”,rw);文件名和访问模式 p.write(raf);//以二进制写入 raf.seek(0);//将指针移动至开头 Person p2 = new Person(); P2.read(raf);//以二进制读出 } Class Person { Int id; String name; double height; Public Person(){} Public Person(int id,String name,double height) {this.id=id;this.name=name;this.height=height} Public void write(RandomAccessFile raf) { Raf.writeInt(this.id);Raf.writeUTF(this.name);raf.writeDouble(this.height); } Public void read(RandomAccessFile raf)//先写什么就先读什么 { This.id=raf.readInt();this.name=raf.readUTF();this.height=raf.readDouble(); } } |
113. 序列化将对象转换为字节流保存起来,并在以后还原这个对象。对象若想被序列化,需要实现Serializable接口(该接口没有任何方法,只是作为一个标识性的类,标识该类可序列化)。
|-对象被序列化时,只保存对象的非静态成员变量。因为序列化时存储对象的信息,而静态成员变量是类的。
|-如果一个对象的成员变量是一个对象,那么该对象的数据成员也将会被保存。
|-如果一个可序列化的对象包含某个不可序列化对象的引用,那么序列化失败。但是我们可以使用关键字transient修饰那个不可序列化的对象,这样就可以实现序列化了。
|-如果一个类可以序列化,其子类都可以序列化。
ObjectOutputStream:将对象输出为字节流。
ObjectInputStream:将字节流读取为对象。
Public class SeriableTest { Public static void main() { Person p1 = new Person(20,”a”); FileOutputStream fos = new FileOutputStream(“Object.txt”); ObjectOutputStream oos = new ObjectOutputStream(fos); Oos.writeObject(p1); Oos.close();
FileInputStream fis = new FileInputStream(“Object.txt”); ObjectInputStream ois = new ObjectInputStream(fis); Person p = null; P=(Person)ois.readObject(); Ois.close(); } } Class Person implements Serializable { Int age; String name; //定义成transient和静态的变量不会被序列化 Public Person(int age,String name){this.age=age;this.name=name;} } |
当我们在一个待序列化/反序列化的类中实现了:writeObject和readObject两个private方法后,那么就允许我们更加底层、更加细粒度的控制序列化、反序列化的过程。
114. 多线程(Multi-Thread):
1. 线程:程序中单独顺序的流控制。不能单独运行,只能用于程序中。
2. 进程:运行中的程序。(程序是静态的概念,进程是动态的概念)
3. 线程与进程的区别:
i. 多个进程的内部数据和状态都是完全独立的,而多线程时共享一块内存空间和一组系统资源,有可能相互影响。
ii. 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程的负担小。
4. 多线程:单个程序中,可以同时运行多个不同的线程执行不同的任务。多线程的目的是:最大限度的利用CPU资源。
5. 多任务处理有两种解决方法:多线程(打印同时编辑文字)和多进程(运行java并运行pdf)。
115. Java中实现多线程:
run方法用于完成线程操作,start方法用于启动线程,首先调用系统的资源,然后调用run方法。
|-对于单核CPU来说,某一时刻只能有一个线程来执行(微观串行),从宏观角度来看,一段时间执行A,一段时间执行B,多个线程同时执行(宏观并行)。
|-对于多核CPU来说,可以真正做到微观并行。
1. 继承Thread并重写run方法
Public class ThreadTest { Public static void main() { Thread1 t1 = new Thread1(); t1.start(); Thread2 t2 = new Thread1(); t2.start(); //输出的结果顺序不能估计。如果t1和t2调用的是run方法,那么首先打印Thread1的然后打印Thread2的内容 } } Class Thread1 extends Thread { Public void run() { For(int i=0;i<100;i++){System.out.println(i);} } } Class Thread extends Thread { For(int i=0;i<100;i++){System.out.println(“welcome”+i);}
} |
2. 通过实现Runnable接口进而实现run方法
Public class ThreadTest { Public static void main() { Thread t1 = new Thread (new Thread1()); t1.start(); Thread t2 = new Thread(new Thread2()); t2.start(); //输出的结果顺序不能估计。如果t1和t2调用的是run方法,那么首先打印Thread1的然后打印Thread2的内容 } } Class Thread1 implements Runnable { Public void run() { For(int i=0;i<100;i++){System.out.println(i);} } } Class Thread2 implements Runnable { For(int i=0;i<100;i++){System.out.println(“welcome”+i);}
} |
线程的消亡不能通过调用stop()命令,而是让run方法自然停止。
关于Thread类:
1. Thread类也实现了Runnable接口,因此实现了Runnable接口中的run方法。
2. 当生成一个线程对象时,如果没有为其设定名字,那么该线程对象的名字使用如下形式:Thread-number。该number是自动增加的,并被所有的Thread对象所共享。我们可以重写父类Thread的构造方法(含一个String参数)自定义自己的线程名字。
3. 当使用第一种方式(继承Thread类)生成线程对象时,我们需要重写run方法,因为Thread类的run方法此时什么事情都不做。
4. 当使用第二种方式来生成线程对象时,我们需要实现Runnable接口的run方法,然后使用new Thread(new MyThread())(假如MyThread已经实现Runnable接口)来生成线程对象,这时的线程对象的run方法就会调用MyThread类的run方法。
总结:两种都需要调用start方法。通常,当一个线程已经继承另一个类时,只能实现Runnable(Java单继承)。
116. 线程的生命过程:
1. 创建状态:使用new创建线程,此时系统并未分配资源。
2. 可运行状态:执行start方法后,分配资源,然后调用run方法。
3. 不可运行状态:处于运行状态的线程会转入到不可运行状态。
1. 运行sleep()方法
2. 线程调用wait()方法
3. 线程输入/输出阻塞
返回可运行状态:
1. 处于睡眠的线程在指定的时间过去后
2. 等待条件的线程,得到另一个对象notify()或者notifyAll()方法通知
3. 线程因为输入输入阻塞,输入输出完成
4. 消亡状态:run方法结束后,线程自然消亡。
线程的状态转换图:
线程的优先级:
1. 子类继承父类的优先级
2. 使用setPriority()方法设定优先级
3. 优先级是1-10的正整数。MAX_PRIORITY(10)、NORMAL_PRIORITY(5)、NIN_PRIORITY(1);优先级Java可以动态改变的,不能通过优先级来设定线程的执行情况。
4. 线程调度器优先选择线程即最高的线程。但是如下几种情况将会终止线程:
a) 线程调用yield方法,让出对CPU的占用权
b) 线程调用sleep方法
c) 线程由于I/O阻塞
d) 另一个更高级别的线程出现
e) 在支持时间片的系统中,该线程的时间片用完
117. 多线程的同步:
|-对于成员变量,多个线程对同一对象的成员变量操作时,他们对该成员变量是彼此影响的。对于局部变量,多个线程对同一对象的局部变量操作时,每个线程都会有此局部变量的拷贝,彼此不相互影响。
|-停止线程的方式,不能使用Thread的stop方法。一般需要设定一个变量,在run方法中设定循环,每次都检查该变量,如果满足条件就继续,不满足跳出。
|-不能依靠线程的优先级来决定线程的执行顺序。
|-线程同步需要使用加锁、解锁操作(synchronized)。Synchronized可以修饰方法和语句块。当synchronized修饰方法时,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock)或者称为监视器。当访问某个对象synchronized方法时,表示将该对象上锁,此时,其他任何线程都无法再访问该synchronized方法了,直到之前的那个线程执行完毕或者抛出异常,那么该对象的锁将释放掉,其他线程才有可能再去访问该synchronized方法。
Public class FetchMoney { Public static void main() { Bank bank = new Bank(); Thread t1 = new MoneyThread(bank);//柜台取 Thread t2 = new MoneyThread(bank);//提款机取 t1.start(); t2.start(); } } Class Bank { Private int money = 1000;//如果加static表示主卡和副卡取钱 Public synchronized int getMoney(int number) { If(number<0) Return -1; Else if(number>money) Return -1; Else if(money<0) Return -3; Else { Thread.sleep(1000); Money-=number; Return number; } } } |
Class MoneyThread extends Thread { Private Bank bank; Public MoneyThread(Bank bank){this.bank=bank} Public void run(){System.out.println(bank.getMoney(800));} } |
|-对于synchronized的理解:如果一个对象有多个synchronized方法,某一个时刻某个线程已经进入到某个synchronized方法,那么该方法执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
|-如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象唯一对应一个Class对象,因此当线程分别访问同一个类的两个对象的两个static synchronized方法时,他们的执行顺序也是顺序的。也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。
Example example = new Example(); Thread t1 = new TheThread1(example);//执行example的synchronized方法一 Thread t2 = new TheThread2(example);//执行example的synchronized方法二 t1.start(); t2.start(); |
方法一和方法二都是同步方法。t1.start()调用后,执行 example的synchronized方法,该方法对对象example方法上锁,因此,t2.start()中启动的example方法二一直不能执行! |
如果方法一和方法二都是静态的,执行上面的代码,还是方法一先执行,执行完毕后执行方法二(也有可能先执行方法二,再执行方法一,但是肯定是一个先执行,完毕后再执行另一个)。 对于如下代码: Example example = new Example(); Thread t1 = new TheThread1(example);//执行example的synchronized方法一 example = new Example(); Thread t2 = new TheThread2(example);//执行example的synchronized方法二 t1.start(); t2.start(); 这样执行的时候(方法一和方法二都是同步静态),虽然是两个对象,但是执行的顺序还是先执行方法一,再执行方法二(因为是static,无论多少对象,对应的都是那个类的对象,为同一对象)。如果方法一是同步静态,方法二同步非静态,那么交替执行。 |
Synchronized代码块:使用时要告诉线程锁那个对象。使用synchronized(this)可以达到和synchronized方法同样的效果。
Synchronized方法和synchronized的比较:synchronized方法是粗粒度的并发控制,某一时刻只能有一个线程执行该方法。synchronized块是细粒度并发控制的,只会讲块中代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。通常建议使用synchronized块。
Class Example { Private Object obj = new Object(); Public void execute() { synchronized(obj) { For(int i=0;i<20;i++){System.out.println(i);}//待执行的方法。 } } } |
118. 线程的相互作用
|-死锁(deaklock):是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
线程间相互作用常用方法:wait、notify是都定义在Object中,是fianl,不能被重写。这两个方法要求线程在调用时,线程已经获得了对象的锁,因此对这两个方法的调用要放在synchronized方法或块中。当线程执行wait方法时,它会释放掉对象的锁。Sleep()方法也能让线程暂停,但是sleep()方法在休眠是不会释放对象的锁。
|-Wait():必须在synchronized块或者方法中。一旦执行,thread的锁释放。然后等待获取,从wait后面的语句开始执行。
|-notify():必须在synchronized块或者方法中,用于唤醒线程,唤醒哪一个由系统执行。
|-notifyAll():跟notify效果差不多。
Class Simple { Private int number; Public synchronized void increase() { If(0!=number)//不为零就等待 { Wait(); } this.number++; notify(); } Public synchronized void decrease() { If(0==number)//为零就等待 { Wait(); } this.number--; notify(); } } |
Publilc class IncreaseThread extends Thread { Private Sample sample; Public IncreaseThread(Sample sample){this.sample=sample;} Public void run() { For(int i=0;i<20;i++){Thread.sleep(Thread.sleep((long)(Math.random()*1000)));} Sample.increase(); } } |
Publilc class DecreaseThread extends Thread { Private Sample sample; Public DecreaseThread(Sample sample){this.sample=sample;} Public void run() { For(int i=0;i<20;i++){Thread.sleep(Thread.sleep((long)(Math.random()*1000)));} Sample.decrease(); } } |
Public class MainTest { Public static void main() { Sample sample = new Sample(); Thread t1 = new IncreaseThread(); Thread t2 = new DecreaseThread(); Thread t3 = new IncreaseThread(); Thread t4 = new DecreaseThread(); t1.start(); t2.start();//如果没有t3,t4,输出0、1交替进行 t3.start(); t4.start();//如果有t3,t4,输出不交替进行。原因是wait()完了不能直接执行,而应该改if为while,这样便能判断多次,if只判断一次。即Simplle方法中的if变成while。 } } |
118. Singleton的两种实现方式区别:
方法一:定义静态私有变量是new。线程安全。
方法二:在if判断时new。多线程容易出现问题,可能无法只产生一个对象。
119. 对象的克隆:
|-浅拷贝(shallow clone):被复制的对象所有变量都含有与原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。浅拷贝只复制值,不复制其所引用的对象。
|-深拷贝(deep clone):深赋值把所有要复制的对象都复制一遍,包括对象引用的对象也进行复制。
Clone方法:Object中的clone方法是protected要求如下:
|-x.clone()!=x;克隆对象与原对象不是同一对象
|-x.clone().getClass()==x.getClass();克隆对象与原对象类型相同
|-x.clone().equals(x)==true;如果x的equals方法定义恰当(要求equals()方法进行改写)那么两个对象的内容相同。
Java中对象的克隆(实现Cloneable):
|-利用Object的clone方法
|-在派生类中覆盖父类的clone方法,并声明为public(Object中clone为protected)
|-在派生类的clone方法中,调用super.clone();此步骤用于运行时刻识别出待克隆的对象并为其分配空间,然后进行对象的复制,将原对象的内容一一复制到新对象的存储空间。继承自Object类的clone方法是浅复制。
|-在派生类中实现Cloneable接口。Cloneable也是一个标识性接口,没有任何方法。
Public class CloneTest { Student s = new Student(); Student.setAge(20); Student.setName(“lan”); Student s2 = (Student)student.clone();//浅拷贝 System.out.println(s2.getAge()+s2.getName()); S2.setName(“conan”); System.out.pirntln(s.getName()); System.out.println(s2.getName());//输出lan、conan。因为浅拷贝,开始s和s2都指向lan,但是经过s2.setName(“conan”)后,s2的name转而指向”conan”。【Java中都是传引用!!!】 } Class Student implements Cloneable { Private int age; Private String name; //setter & getter @Override public Object clone() { Object object = super.clone();//浅拷贝 } } |
Public class CloneTest2 { Public static void main() { Teacher teacher = new Teacher(); Teacher.setAge(23); Teacher.setName(“Ho”); Student s1 = new Student(); S1.setAge(22); S1.setName(“s”); S1.setTeacher(teacher); Student s2 = (Student)s1.clone(); teacher.setName(“HH0”); //修改s1指向的teacher不会影响s2的teacher,因为s2深拷贝。 System.out.pirntln(s1.teahcer.getName()); System.out.println(s2.teacher.getName()); } } Class Teacher implement Cloneable { Private int age; Private String name; //setter & getter @Override Public Object clone() { Return super.clone(); } } Class Student implements Cloneable { Private int age; Private String name; Private Teacher teacher; //setter & getter @Override Public Object clone() { Student student= super.clone();//浅复制 student.setTeacher((Teacher)student.getTeacher().clone()); return student; } } |
利用序列化来做深复制:先使对象实现Serializable接口,然后把对象写到流中,然后从流(不只是文件的流,任何流都行)中读取回来。这样就实现了深复制。但是transient和静态变量不能序列化,因此不能clone这两种变量。
Public class CloneTest { Public static void main() { Teacher teacher = new Teacher(); Teacher.setAge(23); Teacher.setName(“Ho”); Student s1 = new Student(); S1.setAge(22); S1.setName(“s”); S1.setTeacher(teacher); Student s2 = (Student)s1.deepCopy(); } } Class Teacher implement Serializable { Private int age; Private String name; //setter & getter } Class student implements Serializable { Private int age; Private String name; Private Teacher teacher; //setter & getter Public Object deepCopy() { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); Oos.writeObject(this);//写入到流 ByteInputStream bis = newByteInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); Return ois.readObject(); } } |
SerialVersionUID:当一个类实现Serializable,Eclipse要求为这个类提供一个serialVersionUID的属性。这个属性可以让该类具有更好的兼容性(对象保存之后又对类进行了修改,反序列化是不兼容)。
120. Java网络编程
|-URL:创建和使用URL访问网上资源。组成如下:
1. 协议标示符:HTTP/FTP/File等
2. 资源名字:主机名,文件名,端口号,引用等
3. 常用本地方法(不用联网,对URL本身解析):getProtocol、getHost、getFile、getPort、getRef等。
4. 常用网络方法:
//openConnection:打开网络连接 URL url = new URL(“http://www.baidu.com”); URLConnection conn = url.openConnection(); InputStream is = conn.getInputStream(); OutputStream os =new FileOutputStream(“d:\\baidu.txt”); byte[] buffer = new byte[2048]; int length = 0; while(-1!=(length=is.read(buffer,0,buffer.length))) { Os.write(buffer,0,length); } Is.close(); Os.close(); |
//打开流连接 URL url = new URL(“http://www.baidu.com”); //URLConnection conn = url.openConnection(); //InputStream is = conn.getInputStream(); InputStream is = url.openStream(); OutputStream os =new FileOutputStream(“d:\\baidu.txt”); byte[] buffer = new byte[2048]; int length = 0; while(-1!=(length=is.read(buffer,0,buffer.length))) { Os.write(buffer,0,length); } Is.close(); Os.close(); |
//使用字符流 URL url = new URL(“http://www.baidu.com”); BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream())); String line = null; While(null!=(line=br.readLine())) { System.out.println(ln); } Br.close(); |
|-InetAddress类:代表IP地址的类。
1. getLocalHost:返回本机地址
2. getByName:获取特定主机名的信息
|-使用TCP/IP的套接字Socket:
1. Socket:连接运行在网络上的两个程序间的双向通讯的端点。
2. Socket通讯的基本过程:
a) 服务器将一个套接字绑定到特定的端口,等待并监听客户连接请求。
b) 客户程序根据服务程序的主机名和端口号发送请求。
c) 服务器接收连接请求,并获得一个新的绑定到不同端口地址的套接字。
|-使用ServerSocket和Socket实现服务器和客户端的Socket通信:
过程如下:
1. 建立Socket连接
2. 获取输入输出流
3. 读写数据
4. 关闭输入输出流
5. 关闭Socket
//TCPServer Public class TCPServer { Public static void main() { ServerSocket ss = new ServerSocket(5000); Socket socket = ss.accept();//阻塞式,等待客户端连接,后面的代码在客户端未去的连接前不执行 InputStream is = socket.getInputStream(); byte[] buffer = new byte[200]; int length=0; while(-1!=(length=is.read(buffer,0,buffer.length))) { String str = new String(buffer,0,length);System.out.println(str); } OutputStream os = socket.getOutputStream(); Os.write(“welcome”.getBytes()); Is.close(); Os.close(); Socket.close(); } } |
//TCPClient Public class TCPClient { Public static void main() { Socket socket = new Socket(“127.0.0.1”,5000);//客户端执行到此,服务器端便建立了连接,服务器accept后的代码得以执行。 OutputStream os = socket.getOutputStream(); Os.write(“hello world”.getByte()); InputStream is = socket.getInputStream(); byte[] buffer = new byte[200]; int length=0; while(-1!=(length=is.read(buffer,0,buffer.length))) { String str = new String(buffer,0,length);System.out.println(str); } Is.close(); Os.close(); Socket.close();
} } |
利用多线程实现Socket通信。
//MainServer Public class MainServer { Public static void main() { ServerSocket serverSocket = new ServerSocket(4000); While(true) { Socket socket = serverSocket.accept(); //启动读写线程 new ServerInputThread(socket).start(); new ServerOutputThread(socket).start(); } } } |
Public class ServerInputThread extends Thread { Private Socket socket; Public ServerInputThread(Socket socket) {this.socket=socket;} Public void run() { InputStream is = socket.getInputStream(); While() { byte[] buffer = new byte[1024]; int length = is.read(buffer);//阻塞式 String str = new String(buffer,0,length); System.out.println(str); }
} } |
Public class ServerOutputThread extends Thread { Private Socket socket; Public ServerOutputThread(Socket socket) {this.socket=socket;} Public void run() { OutputStream is = socket.getOutputStream(); While() { BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); String line = reader.realLine();//阻塞式 Os.wirte(line.getBytes()); }
} } |
//MainClient Public class MainClient { Public static void main() { Socket socket = new Socket(“127.0.0.1”,400); New ClientInputThread(socket).start(); New ClientInputThread(socket).start(); } } |
Public class ClientrInputThread extends Thread { Private Socket socket; Public ClientInputThread(Socket socket) {this.socket=socket;} Public void run() { InputStream is = socket.getInputStream(); While() { byte[] buffer = new byte[1024]; int length = is.read(buffer);//阻塞式 String str = new String(buffer,0,length); System.out.println(str); }
} } |
Public class ClientOClientutputThread extends Thread { Private Socket socket; Public ClientOutputThread(Socket socket) {this.socket=socket;} Public void run() { OutputStream is = socket.getOutputStream(); While() { BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); String line = reader.realLine();//阻塞式 Os.wirte(line.getBytes()); } } } |
121. 使用UDP进行通信
使用Datagram和DatagramPacket实现利用UPD协议的c/ssocket通信send发送数据报,receive方法接收数据包。
Public class UdpTest1 { Public static void main() { DatagramSocket socket = new DatagramSocket(); String str = “Hello World”; DatagramPacket packet = new DatagramPacket( str.getBytes(),str.length,Inet.address.getByName(“localhost”),7000); Socket.send();//发送 byte[] buffer = new byte[1000]; DatagramPacket packet2=new DatagramPacket(buffer,100); Socket.receive(pocket2);//接受 System.out.pintln(new String(buffer,0,packet2.length())); Socket.close(); } } |
Public class UdpTest2 { Public static void main() { DatagramSocket socket = new DatagramSocket(7000); Byte[] buffer = new byte[1000]; DatagramPacket packet = new DatagramPacket(buffer,1000); Socket.receive(packet); System.out.println(new String(buffer,0,packet.getLength())); String str = “welcome”; DatagramPacket packet2=new DatagramPacket( str.getBytes(),str.length(),packet.getAddress(),packet.getPort()); Socket.send(pocket2);//接受 Socket.close(); } } |
122. CVS(并行版本控制):
软件的发布:alpha(内部测试)->Beta(公开测试)->Release Candidate(RC)发布候选版->General Avaibility或者Final(最终发布版)
123. JVM:Java 虚拟机终止条件。
1. System.exti();
2. 执行结束
3. 发生异常或错误
4. 操作系统出错导致虚拟机终止
124. ClassLoader(类加载器):加载Java类到内存。执行类的顺序(指的是类,不涉及对象):
1. 加载:查找并加载类的二进制数据,由硬盘到内存。将其存放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类加载的最终产品是位于堆区中的class对象,该对象封装了类在方法区内的数据结构的所有接口。加载.class文件的方式有:
i. 直接加载class文件(本地系统)
ii. 通过网络下载class文件
iii. 从zip、jar等归档文件中加载class
iv. 从专有的数据库中加载class
v. 将Java源文件动态编译为class文件
2. 连接:读入的class文件合并到JVM的运行时环境中。
i. 验证:确保被加载类的正确性。
ii. 准备:为类的静态变量分配内存,并将其初始化默认值。
iii. 解析:把类中的符号引用转换为直接引用。
3. 初始化:为类中的静态变量赋予正确的初始值。
Private static int a =3; //首先在连接阶段初始化静态变量a=0;然后执行初始化阶段,a赋值为3。 上述代码等价于: Public static int a; Static{a=3;} |
Java程序对类的使用:
1. 主动使用(六种):
a) 创建类的实例:new Test();
b) 访问某个类或者接口的静态变量,或者对该静态变量赋值;int b = Test.a;(a是Test类的静态变量)
c) 调用类的静态方法:Test.doSomething();
d) 反射,如Class.forName();Class.forName(“com.Test”);
e) 初始化一个类的子类:class Parent{}class Child extends Parent{ public static int a =3;}另一个类的main方法中:Child.a = 4;这样也对父类进行了主动使用 。
f) Java虚拟机启动时被表明为启动类的类(使用启动命令java test,test就是启动类的类)
2. 被动使用:除了上面六中都成为被动使用,被动使用不执行初始化。
所有的Java虚拟机实现必须在每个类或者接口被Java程序首次主动使用的时候才初始化他们。
类加载器:
1. Java虚拟机自带的加载器
a) 根类加载器(bootstrap,c++代码),加载系统核心库,getClassLoader放回null。
b) 扩展类加载器(Extension,java代码),其父类加载器为根类加载器。从jre\lib\ext中加载class文件。
c) 系统类加载器/应用加载器(System,java代码),其父类为扩展类加载器,它从换件变量classpath中加载,用于加载自定义的类。系统加载器是用户自定义的类加载器的默认父加载器。这里所说的父子加载器并非京城关系,也就是说子加载器不一定是继承了父加载器,可以是委托的关系。
2. 用户自定义的类加载器。均为java.lang.ClassLoader的子类。
对于错误的类,如果一个类一直没有被程序主动使用,类加载器就不会报告错误。
class singleton { Private static Singleton singleton = new Singleton();<1> Public static int counter1; Public static int counter2=0; //Private static Singleton singleton = new Singleton();<2> Private Singleton() {counter1++;counter2++;} Public static Singleton getInstance() {return singleton;} } Public class MyTest { Public static void main() { Singleton singleton = Singleton.getInstance(); System.out.println(singleton.counter1); System.out.println(singleton.counter1); } } |
如果<1>:在MyTest的main方法中Singleton.getInstance()静态方法调用,属于主动调用,初始化静态变量:singleton赋值为null,counter赋值为0,counter2赋值为0。赋值完成之后开始初始化,singleton首先初始化,初始化过程中需要调用其构造方法,counter1和counter2变为1。然后初始化counter1,由于没有指定初始化的数值,因此counter1还是1,。而counter2指定为0,因此counter2等于0。最终打印为1,0; 如果<2>。MyTest方法中的singleton.getInstance()调用静态方法,主动调用,初始化静态变量。singleton赋值为null,counter1、counter2赋值为0.然后进行真正的初始化,首先初始化counter1为0,counter为0,接着singleton初始化,调用构造方法,counter1和counter2初始化为1。因此打印出1,1. |
编译常量:
Public class Test { Public static void main() { System.out.println(FinalTest.x); } } Class FinalTest { Public static final int x = 6/3; Static { System.out.println(“FinalTest static block”); } } //输出结果为2,静态代码块未执行。X在编译时能计算出来,x是编译时常量。使用时类不会被初始化。Static块不执行。 |
Public class Test { Public static void main() { System.out.println(FinalTest.x); } } Class FinalTest { Public static final int x = new Random().nextInt(100); Static { System.out.println(“FinalTest static block”); } } //输出结果为:FinalTest static block + 3(随机数)。X在编译时不能能计算出来,x不是编译时常量,而需要通过初始化确定其值,一段初始化,变执行static块中内容。 |
子类的初始化会首先初始化父类,但是子接口的初始化不会导致父接口的初始化。
Public class Test { Static {System.out.println(“Test static block”);} Public static void main() { System.out.println(Child.b); } } Class Parent { Static int a =3; Static {System.out.println(“Parent static block”);} } Class Child extends Parent { Static int b=8; Static {System.out.println(“Children static block”);} } //输出结果为:Test static block->Parent static block->Children static block->8 |
Public class Test { Static {System.out.println(“Test static block”);} Public static void main() { Parent parent; System.out.println(“------”); Parent = new Parent(); System.out.println(Parent.a); System.out.println(Child.b); } } Class Parent { Static int a =3; Static {System.out.println(“Parent static block”);} } Class Child extends Parent { Static int b=8; Static {System.out.println(“Children static block”);} } //输出结果为:Test static block->”--------”->Parent static block->3->Children static block->8。分析如下:首先调用main中的静态代码块,然后执行main方法,Parent parent;是声明,并没有主动使用类对象,不进行初始化。然后打印---,然后new Parent是主动使用,调用初始化操作,打印Parent static block,再打印3,然后调用Children.b,因为Parent已经初始化,因此不再初始化,直接初始化Children,打印Child static block,最后输出8. |
只有当程序访问的静态变量或者静态方法确实在当前类或接口中定义时,才认为是对类或者接口的主动使用。
Public class Test { Public static void main() { System.out.println(Child.a); Child.doSomething(); } } Class Parent { Static int a =3; Static {System.out.println(“Parent static block”);} static void doSomething(){System.out.pirntln(“doSomething”);} } Class Child extends Parent { Static {System.out.println(“Children static block”);} } //输出结果为:Parent static block->3->doSomething。分析如下:Children.a并不是对a的主动使用,因为a定义在Parent而不是Child类中。这里是对父类Parent的主动使用。 |
125. ClassLoader:调用ClassLoader不是主动调用。
Public class Test { Public static void main() { //获得系统类加载器 ClassLoader loader = ClassLoader.getSystemClassLoader(); Class> clazz = loader.loadClass(“C”);//加载C类,不是主动使用,不初始化 //不打印Class C Clazz = Class.forName(“C”);//此句打印Class C } } Class C { Static{system.out.println(“Class C”);} } |
类加载器加载采用父类委托机制(Parent Delegation)。除了系统自带的根类加载器意外,其余的加载器有且只有一个父加载器。加载的时候,有其父类加载器加载。如果父类还有父类,继续向上委托。如果父类无法加载,那么就由子类进行加载。如果子类也无法加载,抛出ClassNotFoundException。这里的前提是此类尚未被加载,如果已经加载了,就直接返回加载类的实例。
定义类加载器:如果某个类加载器能够加载一个类,那么该类加载器就称作:定义类加载器;定义类加载器机器所有子加载器都称作:初始类加载器。
当生成一个自定义的类加载器实例是,如果没有指定他的父加载器,那么系统类加载器就将成为该类加载器的父加载器。比如A委托B加载,那么B就是A的父加载器。
用于自定义类加载器:
Public class MyClassLoader extends ClassLoader { Private String name;//加载器名 Private String path = “d:\\”;//加载类路径 Private final String fileType = “.class”;//class文件的扩展名 Pulbi MyClassLoader(String name) { Super();//让系统类加载器成为该类的父加载器 This.name = name; Public MyClassLoadr(ClassLoader parent,String name) { Super(parent);//显示指定该类加载器的父类加载器 This.name=name; } Pulbic String toString() {return this.name;} Private byte[] loadClassData(String name) { InputStream is = null; Byte[] data = null; ByteArrayOutpuyStream baos =null; This.name=this.name.replace(“.”,”\\”);//替换.为\ Is=new FileInputStream(new File(path+name+fileType)); Baos = new ByteArrayOutputStream(); Int ch=0; While(-1!=(ch=is.read())) { Baos.write(ch); } Data = baos.toByteArray(); Is.close(); Baos.close(); Return data; } //重写findClase Public Class> findClass(String name) { Byte[] data = this.loadClassData(name); Return this.defineClass(name,data,0,data.length);//将字节数组转换为class类实例 } Public static main() { MyClassLoader loader1 = new MyClassLoader(“loader1”); Loader1.setPath(“d:\\tst”);//自定义类中实现setter和getter方法 MyClassLoader loader2 = new MyClassLoader(“loader2”); Loader2.setPath(loader1,“d:\\tst1”);//自定义类中实现setter和getter方法 MyClassLoader loader3 = new MyClassLoader(null,“loader3”); Loader3.setPath(“d:\\tt”); test(loader2); test(loader3); } Public static void test(ClassLoader loader) { Class clazz = loader.loadClass(“com.Test”); Object object = clazz.newInstance(); } } |
Public class Sample { Public int v1=1; Public Sample() { System.out.println(“Sample is loader by:”+this.getClass().getClassLoader()); } } |
Pulic class Dog { Public Dog() { System.out.pirntln(“Dog is loader by:”+this.getClass().getClassLoader()); New Dog(); } } |
输出结果:sample is loader by loader1、Dog is loader by loader1、Sample is loader by loader3、 Dog is loader by : loader 3. |
126. 类的卸载:
由用户自定义的类加载器所加载的类是可以被卸载的。