五、新型 for 循环 for—each,用于追求数组与集合的遍历方式统一
1、数组举例:
int[] ss = {1,2,3,4,5,6};
for(int i=0; i<ss.length; i++){
System.out.print(ss[i]);
} //以上是以前的 for 循环遍历,比较下面的for—each
System.out.println();
for(int i : ss){
System.out.print(i);
2、集合举例:
List ll = new ArrayList();
for(Object o : ll ){
System.out.println(o);
}
注:凡是实现了java.lang.Iterable接口的类就能用 for—each遍历
用 for—each时,不能用list.remove()删除,因为他内部的迭代器无法调用,造成多线程出错。
这时只能用 for 配合迭代器使用。
六、泛型 Generic
1、为了解决类型安全的集合问题引入了泛型。
泛型是编译检查时的依据,也是编译期语法。
(编译期语法:编译期有效,编译后擦除,不存在于运行期)
2、简单的范型应用:集合(ArrayList, Set, Map, Iterator, Comparable)
List<String> l = new ArrayList<String>();
<String>:表示该集合中只能存放String类型对象。
3、使用了泛型技术的集合在编译时会有类型检查,不再需要强制类型转换。
String str = l.get(2); //因为List<String> l, 所以 Error
注:一个集合所允许的类型就是这个泛型的类型或这个泛型的子类型。
4、List<Number> l = new ArrayList<Integer> //Error
List<Integer> l = new ArrayList<Integer> //Right
必须类型一致,泛型没有多态
5、泛型的通配符<?>
泛型的通配符表示该集合可以存放任意类型的对象。但只有访问,不可以修改。
static void print( Cllection<?> c ){
for( Object o : c )
out.println(o);
}
6、带范围的泛型通配符
泛型的声明约定T表示类型,E表示元素
(1)、上界通配符,向下匹配:<? extends Number> 表明“extends”或“implements”,认为是 final 的
表示该集合元素可以为Number类型及其子类型(包括接口),例如 Number,Integer,Double
此时集合可以进行访问但不能修改。即不允许调用此对象的add,set等方法;但可以使用 for-each 或 get.
(2)、下界通配符,向上匹配:<? super Number>
表示该集合元素可以为Number类型及其父类型,直至 Object。
可以使用 for-each,add,addAll,set,get等方法
(3)、接口实现:<? extends Comparable>
表示该集合元素可以为实现了Comparable接口的类
7、泛型方法
在返回类型与修饰符之间可以定义一个泛型方法,令后面的泛型统一
这里只能用 extends 定义,不能用 super ;后面可以跟类(但只能有一个,且要放在首位)其余是接口
符号只有 & //“&”表示“与”;逗号表示后面的另一部分
静态方法里面,不能使用类定义的泛型,只能用自己定义的;因为静态方法可以直接调用;
所以普通方法可以使用类定义的及自己定义的泛型
public static <T> void copy(T[] array,Stack<T> sta){……}
public static <T,E extends T> void copy (T[] array,Stack<E> sta){…..}
public static <T extends Number&Comparable> void copy(List<T> list,T[] t);
8、不能使用泛型的情况:
(1)、带泛型的类不能成为 Throwable 类和 Exception 类的子类
因为cathc()中不能出现泛型。
(2)、不能用泛型来 new 一个对象
如:T t = new T();
(3)、静态方法不能使用类的泛型,因为静态方法中没有对象的概念。
9、在使用接口的时候指明泛型。
class Student implements Comparable<Student>{…….}
10、泛型类
/********************************************************************/
class MyClass<T>{
public void m1(T t){}
public T m2(){
return null;
}}
/********************************************************************/
第九章:
内部类(nested classes) (非重点)
1.定义:定义在其他类中的类,叫内部类(内置类)。内部类是一种编译时的语法,编译后生成
的两个类是独立的两个类。内部类配合接口使用,来强制做到弱耦合(局部内部类,或私有成员内部类)。
2.内部类存在的意义在于可以自由的访问外部类的任何成员(包括私有成员),但外部类不能直接访问内部类的
成员。所有使用内部类的地方都可以不使用内部类;使用内部类可以使程序更加的简洁(但牺牲可读性),
便于命名规范和划分层次结构。
3.内部类和外部类在编译时是不同的两个类,内部类对外部类没有任何依赖。
4.内部类可用 static,protected 和 private 修饰。(而外部类只能使用 public 和 default)。
5.内部类的分类:成员内部类、局部内部类、静态内部类、匿名内部类。
(注意:前三种内部类与变量类似,可以对照参考变量)
① 成员内部类(实例内部类):作为外部类的一个成员存在,与外部类的属性、方法并列。
成员内部类可看作外部类的实例变量。
在内部类中访问实例变量:this.属性
在内部类访问外部类的实例变量:外部类名.this.属性。
对于一个名为outer 的外部类和其内部定义的名为inner 的内部类。
编译完成后出现outer.class 和outer$inner.class 两类。
不可以有静态属性和方法(final 的除外),因为 static 在加载类的时候创建,这时内部类还没被创建
如果在外部类的外部访问内部类,使用out.inner.***
建立内部类对象时应注意:
在创建成员内部类的实例时,外部类的实例必须存在:
在外部类的内部可以直接使用inner s=new inner(); 因为外部类知道 inner 是哪个类。
而在外部类的外部,要生成一个内部类对象,需要通过外部类对象生成。
Outer.Inner in = new Outer().new Inner();
相当于:Outer out = new Outer(); Outer.Inner in = out.new Inner();
错误的定义方式:Outer.Inner in=new Outer.Inner()。
② 局部内部类:在方法中定义的内部类称为局部内部类。
类似局部变量,不可加修饰符 public、protected 和 private,其范围为定义它的代码块。
可以访问外部类的所有成员,此外,还可以访问所在方法中的 final 类型的参数和变量。
在类外不可直接生成局部内部类(保证局部内部类对外是不可见的)。
要想使用局部内部类时需要生成对象,对象调用方法,在方法中才能调用其局部内部类。
局部内部类不能声明接口和枚举。
③ 静态内部类:(也叫嵌套类)
静态内部类定义在类中,在任何方法外,用 static 定义。
静态内部类能直接访问外部类的静态成员;
不能直接访问外部类的实例成员,但可通过外部类的实例(new 对象)来访问。
静态内部类里面可以定义静态成员(其他内部类不可以)。
生成(new)一个静态内部类不需要外部类成员,这是静态内部类和成员内部类的区别。
静态内部类的对象可以直接生成: Outer.Inner in=new Outer.Inner();
对比成员内部类:Outer.Inner in = Outer.new Inner();
而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类。
静态内部类不可用 private 来进行定义。例子:
对于两个类,拥有相同的方法:
/*************************************************************************/
/*class People{void run();}
interface Machine{void run();}
此时有一个robot类:
class Robot extends People implement Machine.
此时run()不可直接实现。*/
interface Machine{ void run();}
class Person{ void run(){System.out.println("run");}}
class Robot extends Person{
private class MachineHeart implements Machine{
public void run(){System.out.println("heart run");}
}
public void run(){System.out.println("Robot run");}
Machine getMachine(){return new MachineHeart();}
}
class Test{
public static void main(String[] args){
Robot robot=new Robot();
Machine m=robot.getMachine();
m.run();
robot.run();
}}
/*************************************************************************/
注意:当类与接口(或者是接口与接口)发生方法命名冲突的时候,此时必须使用内部类来实现。
这是唯一一种必须使用内部类的情况。
用接口不能完全地实现多继承,用接口配合内部类才能实现真正的多继承。
④ 匿名内部类:
【1】匿名内部类是一种特殊的局部内部类,它是通过匿名类实现接口。
【2】不同的是他是用一种隐含的方式实现一个接口或继承一个类,而且他只需要一个对象
【3】在继承这个类时,根本就没有打算添加任何方法。
【4】匿名内部类大部分情况都是为了实现接口的回调。
注:匿名内部类一定是在 new 的后面
其隐含实现一个接口或实现一个类,没有类名,根据多态,我们使用其父类名。
因其为局部内部类,那么局部内部类的所有限制都对其生效。
匿名内部类是唯一一种无构造方法类。
注:这是因为构造器的名字必须和类名相同,而匿名内部类没有类名。
匿名内部类在编译的时候由系统自动起名Out$1.class。
因匿名内部类无构造方法,所以其使用范围非常的有限。
结尾需加上分号。
匿名内部类的例子:
/*************************************************************************/
public class test{
public static void main(String[] args){
B.print(new A(){
public void getConnection(){ System.out.println("Connection....");}
});
}}
interface A{ void getConnection();}
class B{
public static void print(A a){ a.getConnection();}
}
/*************************************************************************/
枚举和接口可以在类的内部定义,但不能在方法内部定义。
接口里面还可以定义多重接口和类。
类放在什么位置,就相当于什么成员。
内部类的用途:
封装类型:把标准公开,把标准的实现者作为内部类隐藏起来,
强制要求使用者通过标准访问标准的实现者,从而强制做到弱耦合!
直接访问外部类的成员
配合接口,实现多继承,当父类和接口方法定义发生冲突的时候,就必须借助内部类来区分
模板回调
从内部类继承:
由于直接构造实例内部类时,JVM会自动使内部类实例引用它的外部类实例。
但如果下面Sample类通过以下方式构造对象时:Sample s = new Sample();
JVM无法决定Sample实例引用哪个Outer实例,为了避免这种错误的发生,在编译阶段,java编译器会要求Sample类的构造方法必须通过参数传递一个Outer实例的引用,然后在构造方法中调用super语句来建立Sample实例与Outer实例的关联关系。
/*************************************************************************/
public class Sample extends Outer.Inner{
//public Sample(){} //编译错误
public Sample(Outer o){ o.super(); }
public static void main(String args[]){
Outer outer1=new Outer(1);
Outer outer2=new Outer(2);
Outer.Inner in=outer1.new Inner();
in.print();
Sample s1=new Sample(outer1);
Sample s2=new Sample(outer2);
s1.print(); //打印a=1
s2.print(); //打印a=2
}}
/*************************************************************************/
内部接口:
在一个类中也可以定义内部接口
在接口中可以定义静态内部类,此时静态内部类位于接口的命名空间中。
在接口中还可以定义接口,这种接口默认也是public static 的,如Map.Entry就是这种接口
第八章 Collection FrameWork
《集合框架》
((Iterator接口 ←- Iterable接口 )) ← Collection接口
↑
┌--------------------------------┬---------------┐
Set接口 List接口 Queue接口
↑ ↑ ↑
┌----------┐ ┌-----------+---------┐ ┌-----------┐
HashSet SortedSet接口 Vector ArrayList LinkedList PriorityQueue
↑
TreeSet
Map接口
↑
┌----------┐
HashMap SortedMap接口
↑
TreeMap
各接口的主要方法:
Iterable: +iterator()
Iterator: +hasNext() +next() +remove()
Collection: +add() +remove() +clear() +isEmpty() +size() +contains()
List: +get() +set() +remove()
Queue: +element() +offer() +peek() +poll()
Set:
SortedSet: +comparator() +first() +last() +headSet() +tailSet()
Map: +clear() +containsKey() +containsValue() +get() +keySet()
+isEmpty() +remove() +put()会替换重复键 +size() +values()
SortedMap: +comparator() +firstKey() +lastKey() +headMap() +tailMap()
一、集合(容器,持有对象):是一个用于管理其他多个对象的对象,且只能保存对象的引用,不是放对象。
1、Collection: 集合中每一个元素为一个对象,这个接口将这些对象组织在一起,形成一维结构。
2、List: 有序、可重复。
ArrayList: 数组。查询快,增删慢。(List是链表)
Vector: 线程安全,但效率很差(现实中基本不用)
3、Set: 无序,且不可重复(不是意义上的重复)。(正好与List 对应)
HashSet: 用 hashCode() 加 equals() 比较是否重复
SortedSet: 会按照数字将元素排列,为“可排序集合”默认升序。
TreeSet: 按二叉树排序(效率非常高); 按Comparable接口的 compareTo() 比较是否重复
4、Map: 其中每一个元素都是一个键值对( Key-Value)。键不能重复。可有一个空键。
SortedMap: 根据 key 值排序的 Map。
HashMap: 用 hashCode() 加 equals() 比较是否重复
5、Queue: 队列:先进先出。
PriorityQueue: 优先队列:元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序
注意:在“集合框架”中,Map 和Collection 没有任何亲缘关系。
Map的典型应用是访问按关键字存储的值。它支持一系列集合操作,但操作的是键-值对,而不是独立的元素
因此 Map 需要支持 get() 和 put() 的基本操作,而 Set 不需要。
《常用集合列表》
' 存放元素 存放顺序 元素可否重复 遍历方式 排序方式 各自实现类
List Object 有序 可 迭代 (2) ArrayList, TreeSet
Set Object 无序 不可 迭代 SortedSet HashSet
SortedSet Object 无序 不可 迭代 已排序 TreeSet
Map (1) Key无序 Key不可,value可 对Key迭代 SortedMap HashMap
SortedMap (1) 无序,有排序 Key不可,value可 对Key迭代 已对键值排序 TreeMap
(1)Object(key)—Object(value);
(2)Collections.sort();
注:以上有序的意思是指输出的顺序与输入元素的顺序一致
HashSet、HashMap通过hashCode(),equals()来判断重复元素
在java中指定排序规则的方式只有两种:1、实现java.util包下的Comparator接口
2、实现java.lang包下的Comparable接口
二、迭代器:Iterator
1、使用Iterator接口方法,您可以从头至尾遍历集合,并安全的从底层Collection中除去元素
2、remove() 由底层集合有选择的支持。底层集合支持并调用该方法时,最近一次next()返回的元素将被删
3、Collection 接口的iterator() 方法返回一个Iterator
4、Iterator中的hasNext()用于判断元素右边是否有数据,返回True则有。然后就可以调用next()动作。
5、Iterator中的next()方法会将游标移到下一个元素,并返回它所跨过的元素。(通常这样遍历集合)
6、用于常规Collection 的Iterator 接口代码如下:
/*迭代遍历*/
List l = new ArrayList();
Iterator it = l.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
注:工具类是指所有的方法都是公开静态方法的类。
Java.util.collections就是一个工具类;
三、对集合的排序
1、我们可以用Java.util.collections中的sort(List l)方法对指定的List集合进行排序;
但是如果List中存放的是自定义对象时,这个方法就行不通了,必须实现Comparable接口并且指定排序规则。
这里我们再来看一下sort(List l)方法的内部实现;
/**********************************************************/
class Collections2{
public static void sort(List l){
for(int i=0;i<l.size()-1;i++){
for(int j=i+1;j<l.size();j++){
Object o1 = l.get(i);
Object o2 = l.get(j);
Comparable c1 = (Comparable)o1;
Comparable c2 = (Comparable)o2;
if(c1.compareTo(c2)>0){
Collections.swap(l,i,j);
}}}}} //其实用的算法就是个冒泡排序。
/******************************************************/
2、实现Java.lang.Comparable接口,其实就是实现他的 public int compareTo(Object obj)方法;
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
其规则是当前对象与obj 对象进行比较,其返回一个 int 值,系统根据此值来进行排序。
如当前对象 > obj,则返回值>0;
如当前对象 = obj,则返回值=0;
如当前对象 < obj,则返回值<0。
注意:String类型已经实现了这个接口,所以可以直接排序;
/******************************************************/
class Student implements Comparable{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public int compareTo(Object obj) {
Student s = (Student)obj;
return s.age-this.age;
}}
/******************************************************/
四、ArrayList和LinkedList集合
1、ArrayList 底层是object 数组,所以ArrayList 具有数组的查询速度快的优点以及增删速度慢的缺点。
Vector 底层实现也是数组,但他是一个线程安全的重量级组件。
2、而在LinkedList 的底层是一种双向循环链表。
在此链表上每一个数据节点都由三部分组成:
前指针(指向前面的节点的位置)、 数据、 后指针(指向后面的节点的位置)。
最后一个节点的后指针指向第一个节点的前指针,形成一个循环。
3、双向循环链表的查询效率低但是增删效率高。所以LinkedList 具有查询效率低但增删效率高的特点。
4、ArrayList 和LinkedList 在用法上没有区别,但是在功能上还是有区别的。
LinkedList 经常用在增删操作较多而查询操作很少的情况下:队列和堆栈。
队列:先进先出的数据结构。
堆栈:后进先出的数据结构。
(堆栈就是一种只有增删没有查询的数据结构)
注意:使用堆栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。
LinkedList 提供以下方法:(ArrayList 无此类方法)
addFirst(); +removeFirst(); +addLast(); +removeLast();
在堆栈中,push 为入栈操作,pop 为出栈操作。
Push 用addFirst();pop 用removeFirst(),实现后进先出。
用isEmpty()--其父类的方法,来判断栈是否为空。
在队列中,put 为入队列操作,get 为出队列操作。
Put 用addFirst(),get 用removeLast()实现队列。
List 接口的实现类 Vector 与ArrayList 相似,区别是Vector 是重量级组件,消耗的资源较多。
结论:在考虑并发的情况下用Vector(保证线程的安全)。
在不考虑并发的情况下用ArrayList(不能保证线程的安全)。
5、面试经验(知识点):
java.util.stack(stack 即为堆栈)的父类为Vector。可是stack 的父类是最不应该为Vector 的。
因为Vector的底层是数组,且Vector有get方法(意味着它可能访问任意位置的元素,很不安全)。
对于堆栈和队列只能用push 类和get 类。(这是早期的某个java编写工程师的失误造成)
Stack 类以后不要轻易使用。实现堆栈一定要用LinkedList。
(在JAVA1.5 中,collection 有queue 来实现队列。)
五、HashSet集合
1、HashSet是无序的,没有下标这个概念。HashSet集合中元素不可重复(元素的内容不可重复);
2、HashSet 底层用的也是数组。
3、HashSet如何保证元素不重复?Hash算法和equals方法。
当向数组中利用add(Object obj)添加对象的时候,系统先找对象的hashCode:
int hc=obj.hashCode(); 返回的hashCode 为整数值。
int I=hc%n;(n 为数组的长度),取得余数后,利用余数向数组中相应的位置添加数据,以n 为6 为例,
如果I=0则放在数组a[0]位置,如果I=1则放在数组a[1]位置。
如果equals()返回true,则说明数据重复。如果equals()返回false,则再找其他的位置进行比较。
这样的机制就导致两个相同的对象有可能重复地添加到数组中,因为他们的hashCode 不同。
如果我们能够使两个相同的对象具有相同hashcode,才能在equals()返回为真。
在实例中,定义student 对象时覆盖它的hashcode。
因为String类会自动覆盖,所以比较String 类的对象时,不会出现相同的string 对象的情况。
现在,在大部分的JDK 中,都已经要求覆盖了hashCode。
结论:如将自定义类用hashSet 来添加对象,一定要覆盖hashcode()和equals(),
覆盖的原则是保证当两个对象hashcode 返回相同的整数,而且equals()返回值为True。
如果偷懒,直接将hashCode方法的返回值设为常量;虽然结果相同,但会多次地调用equals(),影响效率。
我们要保证相同对象的返回的hashCode 一定相同,也要保证不相同的对象的hashCode 尽可能不同
(因为数组的边界性,hashCode 还是有极微几率相同的)。
六、TreeSet集合
1、TreeSet是SortedSet的实现类TreeSet通过实现Comparable接口的compareTo来实现元素不重复。
2、TreeSet由于每次插入元素时都会进行一次排序,因此效率不高。
3、java.lang.ClassCastException是类型转换异常。
4、在我们给一个类用CompareTo()实现排序规则时
5、从集合中以有序的方式抽取元素时,可用TreeSet,添加到TreeSet的元素必须是可排序的。
“集合框架”添加对Comparable 元素的支持。
一般说来,先把元素添加到HashSet,再把集合转换为TreeSet 来进行有序遍历会更快。
七、Map
1、HashMap集合
(1)HashMap就是用hash算法来实现的Map
(2)在实际开发中一般不会用自定义的类型作为Map的Key。做Key的无非是八中封装类。
(3)HashMap的三组操作:
【1】改变操作,允许从映射中添加和除去键-值对。键和值都可以为null。
不能把Map作为一个键或值添加给自身。
–Object put(Object key, Object value)
–Object remove(Object key)
–void clear()
【2】查询操作允许您检查映射内容:
–Object get(Object key)
–intsize()
–boolean isEmpty()
【3】最后一组方法允许您把键或值的组作为集合来处理。
–public Set KeySet();
–public Collection values()
(4)HashMap和HashTable的区别等同于ArrayList和Vector的区别。
只不过HashTable中的Key和Value不能为空,而HashMap可以。
(5)HashMap底层也是用数组,HashSet底层实际上也是HashMap,HashSet类中有HashMap属性(查API)。
HashSet 实际上为(key.null)类型的HashMap。有key 值而没有value 值。
2、HashMap 类和TreeMap 类
•集合框架提供两种常规Map 实现:HashMap和TreeMap。
•在Map 中插入、删除和定位元素,HashMap 是最好选择。
•如果要按顺序遍历键,那么TreeMap 会更好。
•根据集合大小,先把元素添加到HashMap,再把这种映射转换成一个用于有序键遍历的TreeMap 可能更快。
•使用HashMap 要求添加的键类明确定义了hashCode() 实现。
•有了TreeMap 实现,添加到映射的元素一定是可排序的
•HashMap和TreeMap 都实现Cloneable 接口。
异常 Exception
1.概念: JAVA将所有的错误封装成为一个对象,其根本父类为Throwable。异常处理可以提高我们系统的容错性。
Throwable 有两个子类:Error 和Exception。
Error:一般是底层的不可恢复的错误。
Object
↑
Throwable
↑
┌---------┐
Error Exception
↑ 非RuntimeException
┌-----------------------┬---------------┐
RuntimeException InterruptedException IOException
↑
┌----------------------┬-----------...
NullpointerException ArrayIndexOutOfBoundsException
2.Exception分类:
Runtime exception(未检查异常)和 非Runtime exception(已检查异常)。
未检查异常是因为程序员没有进行必要的检查,因为他的疏忽和错误而引起的异常。可避免。
几个常见的未检查异常:
①java.lang.ArithmeticException //如:分母为0;
②java.lang.NullPointerException //如:空指针操作;
③java.lang.ArrayIndexoutofBoundsExceptio //如:数组越界;数组没有这元素;
④java.lang.ClassCastException //如:类型转换异常;
已检查异常是不可避免的,对于已检查异常必须处理。
3、异常对象的传递。
当一个方法中出现了异常而又没做任何处理,则这个方法会返回该异常对象。
异常依次向上层调用者传递,直到传到JVM,虚拟机退出。 (用一句话说就是沿着方法调用链反向传递)
应该在合适的位置处理异常,不让它一直上抛到 main 方法,应遵循的规则:
谁知情谁处理,谁负责谁处理,谁导致谁处理。
4、如何来处理异常(这里主要是针对已检查异常)
【1】throws 消极处理异常的方式。
方法名(参数表)throws 后面接要往上层抛的异常。
表示该方法对指定的异常不作任何处理,直接抛往上一层。
【2】积极处理方式 try、catch
try {可能出现错误的代码块} catch(exception e){进行处理的代码} ;
一个异常捕获只会匹配一次 try,catch.
一个异常一旦被捕获就不存在了。
catch 中要求必须先捕获子类异常再捕获父类异常。 catch 要求有零到多个,但异常的名字不能重复。
【3】finally (紧接在 catch 代码块后面)
finally 的代码块是无论如何都会被执行的(除非虚拟机退出),所以里面一般写释放资源的代码。
return 也无法阻止 finally,但System.exit(0):退出虚拟机则可以。
有 finally 的 try/catch 流程
如果 try 块失败了,抛出异常,程序马上转移到 catch 块,完成后执行 finally 块,再执行其后程序。
如果 try 块成功,程序跳过 catch 块并去到 finally 块,finally 块完成后,继续执行其后程序。
如果 try 或 catch 块有 return 语句,finally 还是会执行;程序会跳去执行 finally 块然后再回到 return 语句
/********** 使用try/catch的例子 ********************************************/
class MyException{
void myException(){
System.out.println("MyException");
// if(1==1)return; //测试没有异常时
System.out.println(1/0);
}
public static void main(String[] args) {
MyException me = new MyException();
try{
System.out.println("try");
// if(1==1)return;
me.myException();
}catch(ArithmeticException ae){
System.out.println("cath");
}finally{
System.out.println("finally");
}}}
/*************************************************************************/
5、自定义异常(与一般异常的用法没有区别)
class MyException extends Exception{
public MyException(String message){ super(message);}
public MyException(){}
}
6、如何控制 try 的范围
根据操作的连动性和相关性,如果前面的程序代码块抛出的错误影响了后面程序代码的运行,
那么这个我们就说这两个程序代码存在关联,应该放在同一个 try 中。
7、不允许子类比父类抛出更多的异常。
8、断言:只能用于代码调试时用。
一般没什么用,只在测试程序时用
/********* 断言的列子 ******************************************************/
public class TestAssertion {
public static void main(String[] args){
int i = Integer.parseInt(args[0]);
assert i==1:"ABCDEFG"; //格式: assert (布尔表达式/布尔值) : String;
/*断言语句:(表示断言该boolean语句返回值一定为真,如果断言结果为false就会报Error错误)
“ : ”后面跟出现断言错误时要打印的断言信息。 */
System.out.println(i);
}}
//javac -source 1.4 TestAssertion.java //表示用1.4的新特性来编译该程序。
//java -ea TestAssertion 0 //表示运行时要用到断言工具
/*************************************************************************/
Module 10 Reflection And Annotation
一、 Reflection 反射
1、反射主要用于工具的开发。所有的重要Java技术底层都会用到反射。反射是一个底层技术。
是在运行时动态分析或使用一个类的工具(是一个能够分析类能力的程序)
2、反射使我们能够在运行时决定对象的生成和对象的调用。
3、Class
(1)定义:在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。
虚拟机利用类型标识选用相应的方法执行。
可以通过专门的类访问这些信息。保存这些信息的类被称为Class(类类)
(2)类对象(类类:用于存储和一个类有关的所有信息),用来描述一个类的类。
类信息通过流读到虚拟机中并以类对象的方式保存。
一个类的类对象在堆里只有一个。
注:简单类型也有类对象。
在反射中凡是有类型的东西,全部用类对象来表示。
4.获得类对象的3种方式:
(1)通过类名获得类对象 Class c1 = String.class; //类.Class;
(2)通过对象获得类对象 Class c2 = s.getClass(); //类对象.getClass();
(3)通过Class.forName(“类的全名”)来获得类对象 //Class.forName(包名.类名);
Class c3 = Class.forName(“Java.lang.String”);//这会强行加载类到内存里,前两种不加载
注:第三种方式是最常用最灵活的方式。第三种方式又叫强制类加载。
5.java.lang.reflect .Field 对象,描述属性信息。
6.java.lang.reflect .Constructor 描述构造方法信息。
7.java.lang.reflect .Method 描述方法信息。
8.在反射中用什么来表示参数表?
Class[] cs2 = {StringBuffer.class};//表示一个参数表
Constructor c = c1.getConstructor(cs2);//返回一个唯一确定的构造方法。
Class[] cs2 = {String.class,int.class}
Method m = c1.getMethod(methodName,cs3);
9.可以通过类对象来生成一个类的对象。
Object o = c.newInstance();
10、反射是一个运行时的概念。反射可以大大提高程序的通用性。
一个关于反射的例子:
/*********************************************************/
import java.lang.reflect.*;
public class TestClass2 {
public static void main(String[] args) throws Exception{
//0.获得在命令行输入的类的类对象
Class c=Class.forName(args[0]);//需处理异常(ClassNotFoundException)
//Object o=c.newInstance();
//1.得到构造方法对象
Class[] cs1={String.class};
Constructor con=c.getConstructor(cs1);
//2.通过构造方法对象去构造对象
Object[] os1={args[1]};
Object o=con.newInstance(os1);
//3.得到方法对象
String methodName=args[2];
Class[] cs2={String.class};
Method m=c.getMethod(methodName,cs2);
//4.调用方法
Object[] os2={args[3]};
m.invoke(o,os2);
/* 以上相当于知道类的情况时,这样直接用
Student s=new Student("Liucy");
s.study("CoreJava"); */
}}
/**********************************************************/
下面是用反射调用私有方法的一个例子:
/**********************************************************/
public class TestClass2 {
public static void main(String[] args) throws Exception{
System.out.println("请输入需要读取的类名:");
Scanner scanner = new Scanner(System.in);
String str = scanner.next(); //输入“AA”
Class c = Class.forName(str);
Method[] m = c.getDeclaredMethods();//读取它的全部方法
Method m1 = m[0];//拿其中的第一个方法
m1.setAccessible(true);//把private的属性设成可访问,否则不能访问
AA b = new AA();
m1.invoke(b);
}}
class AA{
private void print(){
System.out.println("print()");
}}
/**********************************************************/
要求学会的内容:
概念:类类,类对象,类的对象,对象类(Object类)
类对象:Class,指向类的对象。
类对象包括:属性对象 Feild,方法对象Method,构造方法对象Constructor。
类对象能做什么:探查类定义的所有信息:父类,实现的接口,所有属性及方法,以及构造方法。
类的修饰符,属性以及方法的修饰符,方法的返回类型,方法的
...
构造一个类的对象(类对象.newInstance())
强制修改和访问一个对象的所有属性(包括私有属性)
调用一个对象的方法(普通方法,静态方法)
Method.invoke(方法所在的对象(类对象,null),给方法传参数
...
构造数组的另一种用法(动态构造数组,不定长度)
注释 Annotation
1、定义:Annotation描述代码的代码(给机器看的)。
区别:描述代码的文字,给人看的,英语里叫Comments。
任何地方都可以使用Annotation注释,它相当于一段代码,可用来作自动检测。
一个注释其实是一种类型(类class,接口interface,枚举enum,注释Annotation),注释本质上是接口。
定义注释 public @interface Test{},注释都是Annotation接口的子接口
2、注释的分类:
(1)、标记注释:没有任何属性的注释。@注释名
(2)、单值注释:只有一个属性的注释。@注释名(value="***")
在单值注释中如果只有一个属性且属性名就是value,则"value="可以省略。
(3)、多值注释:有多个属性的注释。多值注释又叫普通注释。
@注释名(多个属性附值,中间用逗号隔开)
3、内置注释(java.lang):
(1)、@Override(只能用来注释方法)
表示一个方法声明打算重写超类中的另一个方法声明。
如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。
(2)、@Deprecated
有 @Deprecated 注释的程序元素,不鼓励程序员使用,通常是因为它很危险或存在更好的选择。
在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。
(3)、@SuppressWarnings(抑制警告,该注释效果与版本相关)
指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。
4、自定义注释
(1)、定义注释类型
自定义注释默认就是java.lang.annotation.Annotation接口的子接口。注释本质上就是一个接口。
public @interface TestAnnotation {}
(2)、为注释添加注释
import java.lang.annotation.*;
@Documented //能在帮助文档里出现
@Inherited //能否被继承下去
@Retention(value = {RetentionPolicy.RUNTIME}) //注释该注释运行时仍然保留
//@Retention默认是CLASS(保留到编译期),最短期是SOURCE(原代码级,编译时丢弃)
@Target(value={ElementType.METHOD,ElementType.FIELD})
/*用来注释该注释能用来注释方法和属性,还可以定义它用来注释其他的,如类、注释、构造方法等等*/
/*如果不写Target,默认是可以注释任何东西*/
public @interface TestAnnotation {...}
(3)、为注释添加属性方法
import java.lang.annotation.*;
@Target(value={ElementType.TYPE})
public @interface TestAnnotation {
//如果一个注释不是标记注释,则还要定义属性;这属性同时也是方法,但不可能有参数,只可以有默认值
String parameter() default "liucy";
//给属性方法parameter添加一个默认值"liucy"
//parameter()括号里不能写其他东西,类型只能是24种基本类型之一
//24种类型:8种基本数据类型、String、枚举、注释、Class、以及它们的一维数组
}
@TestAnnotation("haha")
public class MyClass{...}
5、注释的注释:(元注释 meta annotation)
都在 java.lang. annotation 包中
(1)、Target:指示注释类型所适用的程序元素的种类。
一个注释只能出现在其该出现的位置,Target是给注释定位的。
例:@Target(value = {ElementType.METHOD}); //说明该注释用来修饰方法。
(2)、Retention:指示注释类型的注释要保留多久。
如果注释类型声明中不存在 Retention 注释,则保留策略默认为 RetentionPolicy.CLASS。
例:Retention(value = {RetentionPolicy.xxx})
当x为CLASS表示保留到类文件中,运行时抛弃。
当x为RUNTIME表示运行时仍保留(最常用)
当x为SOURCE时表示编译后丢弃。
(3)、Documented:指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化。
应使用此类型来注释这些类型的声明:其注释会影响由其客户端注释的元素的使用。
(4)、Inherited:指示注释类型被自动继承。
如果在注释类型声明中存在 Inherited 元注释,并且用户在某一类声明中查询该注释类型,
同时该类声明中没有此类型的注释,则将在该类的超类中自动查询该注释类型。
注:在注释中,一个属性既是属性又是方法。
使用注释
/*********************************************/
Class c = Class.forName(args[0]);
Object o = c.newInstance();
Method[] ms = c.getMethods();
for(Method m:ms){
//判断m方法上有没有Test注释
if (m.isAnnotationPresent(Test.class)){
//得到m之上Test注释parameter属性值
Test t=m.getAnnotation(Test.class);
String parameter=t.parameter();
m.invoke(o,parameter);
}}
/*********************************************/
图型界面(非重要:不常用、难学)
1、Awt:抽象窗口工具箱,它由三部分组成:
①组件:界面元素;
②容器:装载组件的容器(例如窗体);
③布局管理器:负责决定容器中组件的摆放位置。
2、图形界面的应用分四步:
① 选择一个容器:
⑴window:带标题的容器(如Frame);
⑵Panel:面板通过add()向容器中添加组件。
注:Panel不能作为顶层容器。
Java 的图形界面依然是跨平台的。但是调用了窗体之后只生成窗体;必须有事件的处理,关闭按钮才工作。
②设置一个布局管理器:用setLayout();
③向容器中添加组件;
jdk1.4用getContentPare()方法添加主件。
③ 添加组件的事务处理。
Panel 也是一种容器:但是不可见的,很容易忘记设置它们的可见性。
Panel pan=new Panel;
Fp.setLayout(null);//表示不要布局管理器。
3、五种布局管理器:
(1)、Flow Layout(流式布局):按照组件添加到容器中的顺序,顺序排放组件位置。
默认为水平排列,如果越界那么会向下排列。排列的位置随着容器大小的改变而改变。
FlowLayout layout = new FlowLayout(FlowLayout.LEFT);//流式布局,可设对齐方式
Panel 默认的布局管理器为Flow Layout。
(2)、BorderLayout:会将容器分成五个区域:东西南北中。
语句:Button b1=new Botton(“north”);//botton 上的文字
f.add(b1,”North”);//表示b1 这个botton 放在north 位置
f.add(b1, BorderLayout.NORTH);//这句跟上句是一样的效果,不写方位默认放中间,并覆盖
注:一个区域只能放置一个组件,如果想在一个区域放置多个组件就需要使用Panel 来装载。
Frame 和Dialog 的默认布局管理器是Border Layout。
(3)、Grid Layout(网格布局管理器):将容器生成等长等大的条列格,每个块中放置一个组件。
f.setLayout GridLayout(5,2,10,10)//表示条列格为5 行2 列,后面为格间距
(4)、CardLayout(卡片布局管理器):一个容器可以放置多个组件,但每次只有一个组件可见(组件重叠)。
使用first(),last(),next()可以决定哪个组件可见。可以用于将一系列的面板有顺序地呈现给用户。
(5)、GridBag Layout(复杂的网格布局管理器):
在Grid中可指定一个组件占据多行多列,GridBag的设置非常烦琐。
注:添加滚动条:JScrollPane jsp = new JScrollPane(ll);
4、常用的组件:
(1)、JTextArea:用作多行文本域
(2)、JTextField:作单行文本
(3)、JButton:按钮
(4)、JComboBox:从下拉框中选择记录
(5)、JList:在界面上显示多条记录并可多重选择的列表
(6)、JMenuBar:菜单栏
(7)、JScrollPane:滚动条
/***********************************************************/
//最简单的图形用户界面,学会其中的四大步骤
import java.awt.*;
import javax.swing.*;
class FirstFrame{
public static void main(String[] args){
//1、选择容器
JFrame f = new JFrame();//在JFrame()的括号里可以填写窗口标题
//2、选择布局管理器
LayoutManager lm = new BorderLayout();
f.setLayout(lm);
//3、添加组件
JButton b = new JButton("确定");
f.add(b);
//4、添加事件,显示
JOptionPane.showMessageDialog(null, "哈哈,你好!");//对话窗口
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//用窗口的开关控制程序的结束
f.setSize(400, 300);//窗口的大小
f.setVisible(true); //让窗口可见,默认不可见的
}}
/***********************************************************/
/*****************例题 画出计算器的界面*****************************
界面如下:
1 2 3 +
4 5 6 -
7 8 9 *
0 . = /
*******************/
import java.awt.*;
import javax.swing.*;
class Calculator {
public static void main(String[] args){
JTextField text = new JTextField();
JFrame f = new JFrame("计算器");
Font font = new Font("宋体", Font.BOLD, 25);//"宋体"想写成默认,则写“null”
text.setFont(font); //定义字体
text.setHorizontalAlignment(JTextField.RIGHT);//令text的文字从右边起
text.setEditable(false);//设置文本不可修改,默认可修改(true)
f.add(text, BorderLayout.NORTH);//Frame和Dialog的默认布局管理器是Border Layout
JPanel buttonPanel = new JPanel();//设法把计算器键盘放到这个Jpanel按钮上
String op = "123+456-789*0.=/";
GridLayout gridlayout = new GridLayout(4,4,10,10);
buttonPanel.setLayout(gridlayout);//把计算器键盘放到buttonPanel按钮上
for(int i=0; i<op.length(); i++){
char c = op.charAt(i);
JButton b = new JButton(c+"");
buttonPanel.add(b);
}//这个循环很值得学习,很常用
f.add(buttonPanel/*, BorderLayout.CENTER*/); //默认添加到CENTER位置
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关窗口时结束程序
f.setSize(300, 250);
f.setVisible(true);//这句要放到最后,等事件完成后再显示
}}
/******学过事件之后,可以实现计算器的具体功能*******************************/
观察者模式:
事件源一旦产生事件,监察者立即作出相应处理。
事件源:产生一个事件的对象
事件对象:由事件源产生的一个对象
事件监听者(观察者):处理这个事件对象的对象
事件注册:给一个事件源注册一个(或多个)事件监听者
事件模型(重点)
1.定义: 事件模型指的是对象之间进行通信的设计模式。
事件模型是在观察者模式基础上发展来的。
2.对象1 给对象2 发送一个信息相当于对象1 引用对象2 的方法。
3.事件对象分为三种:
(1)事件源:发出事件者;
(2)事件对象:发出的事件本身(事件对象中会包含事件源对象)
事件对象继承:java.util.EventObjct类.
(3)事件监听器:提供处理事件指定的方法。
标记接口:没有任何方法的接口;如EventListene接口
监听器接口必须继承java.util.EventListener接口。
监听接口中每一个方法都会以相应的事件对象作为参数。
4.授权:Java AWT 事件模型也称为授权事件模型,指事件源可以和监听器之间事先建立一种授权关系:
约定那些事件如何处理,由谁去进行处理。这种约定称为授权。
当事件条件满足时事件源会给事件监听器发送一个事件对象,由事件监听器去处理。
事件源和事件监听器是完全弱偶合的。
一个事件源可以授权多个监听者(授权也称为监听者的注册);事件源也可以是多个事件的事件源。
监听器可以注册在多个事件源当中。监听者对于事件源的发出的事件作出响应。
在java.util 中有EventListener 接口:所有事件监听者都要实现这个接口。
java.util 中有EventObject 类:所有的事件都为其子类。
注意:接口因对不同的事件监听器对其处理可能不同,所以只能建立监听的功能,而无法实现处理。
//监听器接口要定义监听器所具备的功能,定义方法
/************下面程序建立监听功能***************************/
import java.awt.*;
import javax.swing.*;
public class TestEvent {
public static void main(String[] args) {
JFrame f = new JFrame("测试事件");
JButton b = new JButton("点击");//事件源:鼠标点击
JTextArea textArea = new JTextArea();
textArea.setFont(new Font(null, Font.BOLD+Font.ITALIC, 26));
JScrollPane scrollPane = new JScrollPane(textArea);
f.add(scrollPane, "Center");
ButtonActionListener listener = new ButtonActionListener(textArea);
b.addActionListener(listener);
f.add(b, BorderLayout.SOUTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(400, 300);
f.setLocation(250, 250);
f.setVisible(true);
}}
//事件对象,可以直接调用系统写好的
/*class ActionEvent extends EventObject{
public ActionEvent(Object source) {
super(source);
}}*/
//监听接口,同样可以调用系统写好的
/*interface ActionListener extends EventListener{
public void actionPerformed(ActionEvent event);
}*/
//监听者
class ButtonActionListener implements ActionListener{
private JTextArea textArea;
public ButtonActionListener(JTextArea textArea) {
this.textArea = textArea;
}
public void actionPerformed(ActionEvent e) {//必须覆盖它的actionPerformed()
//JOptionPane.showMessageDialog(null, "按钮被按了一下");
textArea.append("哈哈,放了几个字\n");
textArea.append("哈哈,又放了几个字\n");
}}
/*********************************************************/
注意查看参考书:事件的设置模式,如何实现授权模型。
事件模式的实现步骤:开发事件对象(事件发送者)——接口——接口实现类——设置监听对象
重点:学会处理对一个事件源有多个事件的监听器(在发送消息时监听器收到消息的排名不分先后)。
事件监听的响应顺序是不分先后的,不是谁先注册谁就先响应。
事件监听由两个部分组成(接口和接口的实现类)。
一定要理解透彻Gril.java 程序。
事件源 事件对象 事件监听者
gril EmotinEvent EmotionListener(接口)、Boy(接口的实现类)
鼠标事件:MouseEvent,接口:MouseListener。
注意在写程序的时候:import java.awt.*;以及import java.awt.event.*注意两者的不同。
在生成一个窗体的时候,点击窗体的右上角关闭按钮激发窗体事件的方法:
窗体Frame 为事件源,用WindowsListener 接口调用Windowsclosing()。
为了配合后面的实现,必须实现WindowsListener所有的方法;除了Windowsclosing方法,其余的方法均为空实现。这样的程序中实现了许多不必要的实现类,虽然是空实现。为了避免那些无用的实现,可以利用WindowEvent 的一个WindowEvent 类,还是利用windowsListener。WindowAdapter类,实现于WindowsListener。它给出的全是空实现,那就可以只覆盖其中想实现的方法,不必再写空实现。
/******练习:写一个带button 窗体,点关闭按钮退出。*************/
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
public class TestAdapter {
public static void main(String[] args) {
JFrame f = new JFrame("测试适配器");
MyWindowListener listener = new MyWindowListener();
//f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//现在有上面这一句话,就可以不必再创建一个类了
f.addWindowListener(listener);
f.setSize(400, 300);
f.setVisible(true);
}
private static class MyWindowListener extends WindowAdapter{
public void windowClosing(WindowEvent e) {
System.exit(0);
}}}
/*********************************************************/
注意:监听过多,会抛tooManyListener 例外。
缺省试配设计模式:如果一个接口有太多的方法,我们可以为这个接口配上一个对应的抽象类。
《多线程》
一.线程:线程是一个并发执行的顺序流,一个进程包括多个顺序执行流程,这执行流程称为线程。
线程是一个操作系统创建并维护的一个资源,对操作系统来说JVM就是一个进程。
对于单个CPU系统来说,某一个时刻只可能由一个线程在运行。
一个Thread对象就表示一个线程。
线程由三部分组成:
(1).CPU分配给线程的时间片
(2).线程代码(写在run方法中)
(3).线程数据
进程是独立的数据空间,线程是共享的数据空间.
线程对象存在于虚拟机进程空间的一块连续的地址空间(静态)
//main()也是一个线程。
注意:
1.线程是动态的,与线程对象是两回事.
2.线程对象与其他对象不同的是线程对象能够到底层去申请管理一个线程资源。
3.只有对线程对象调用start()方法才是到底层去申请管理一个线程资源。
4.任务并发执行是一个宏观概念,微观上是串行的。
二.进程的调度
进程的调度由OS负责(有的系统为独占式(Windows),有的系统为共享式(Unix),根据重要性,进程有优先级)
由OS 将时间分为若干个时间片。JAVA 在语言级支持多线程。分配时间的仍然是OS。
三.线程有两种实现方式:
第一种方式:
class MyThread extends Thread{
public void run(){
//...需要进行执行的代码,如循环。
}}
public class TestThread{
public static void main(String[]args){
Thread t1=new Mythread();
t1.start();
}}
只有等到所有的线程全部结束之后,进程才退出。
第二种方式:通过接口实现继承
Class MyThread implements Runnable{
Public void run(){
Runnable target=new MyThread();
Thread t3=new Thread(target);
t3.start();//启动线程,这种方式跟前者一样,只是可以继承
}}
/******************多线程事例*******************************/
class TestThread{
public static void main(String[] args){
System.out.println("Main Thread Start!");
//Thread t = new MyThread();
Runnable r = new MyRunnable();
Thread t1 = new Thread(r);//启动另一个带任务的线程
Thread t = new MyThread(t1);//这个线程需要join(t1),否则用上面没参的一句
t.start();//启动一个线程
t1.setPriority(10);//设置优先级
t1.start();
System.out.println("Main Thread End!");
}}
class MyThread extends Thread{
private Thread t;
public MyThread (){}
public MyThread (Thread t){this.t = t;}
public void run(){
for(int i=0; i<100; i++){
System.out.println(i+" $$$$");
if(i==65){ //join()加入其他线程,等其运行完后再运行
try{t.join();}
catch(Exception e){e.printStackTrace();}
}
//当i=50时,放弃CPU占用,让其他程序或线程使用
if(i==50){Thread.yield();}//没有sleep睡眠方法时,此句才可看出效果
//阻塞,睡眠5毫秒,中途会被打断而抛异常
try{Thread.sleep(5);} catch(Exception e){}
}}}
class MyRunnable implements Runnable{
//Runnable是线程任务接口,让你可以继承其他类;Thread是类,不能继承
public void run(){
for(int i=0; i<100; i++){
System.out.println(i+" ****");
try{Thread.sleep(5);} catch(Exception e){}
}}}
/*********************************************************/
四.线程中的7 种非常重要的状态: 初始New、可运行Runnable、运行Running、阻塞Blocked、
锁池lock_pool、等待队列wait_pool、结束Dead
有的书上认为只有五种状态:将“锁池”和“等待队列”都看成是“阻塞”状态的特殊情况:这种认识也是正确的。
将“锁池”和“等待队列”单独分离出来有利于对程序的理解。
┌--------------------< 阻塞
↓ (1)(2)(3) 结束
①②③ OS调度 ↑ ↑
初始-------> 可运行 ↹---------------↹ 运行 >-----------┤
t.start()启动 ↑ ↓ ↓o.wait()
└-----< 锁池 ←---------<┘←-------< 等待队列
获得锁标志 synchronized(o)
注意:图中标记依次为
①输入完毕; ②wake up ③t1 退出
⑴等待输入(输入设备进行处理,而CUP 不处理),则放入阻塞,直到输入完毕。
⑵线程休眠sleep()
⑶t1.join()将t1 加入运行队列,直到t1 退出,当前线程才继续。
特别注意:①②③与⑴⑵⑶是一一对应的。
进程的休眠:Thread.sleep(1000);//括号中以毫秒为单位
当线程运行完毕,即使在结束时时间片还没有用完,CPU 也放弃此时间片,继续运行其他程序。
T1.join 实际上是把并发的线程编成并行运行。
五.线程的优先级:
设置线程优先级:setPriority(Thread. MAX_PRIORITY);
setPriority(10); //设置优先级,独占式的操作系统按优先级分配CPU,共享式操作系统按等待时长分配
JAVA的优先级可以是 1~10,默认是5,数据越大,优先级越高。//windows只有6个优先级,会自动转换来分配
为了跨平台,最好不要使用优先级决定线程的执行顺序。
//跨平台性的含义:除了程序能够正常运行,还必须保证运行的结果。
线程对象调用yield()时会马上交出执行权,交由一个高优先级的线程进入可运行状态;自己等待再次调用。
程序员需要关注的线程同步和互斥的问题。
多线程的并发一般不是程序员决定,而是由容器决定。
六.多线程出现故障的原因:
(1).多个线程同时访问一个数据资源(该资源称为临界资源),形成数据发生不一致和不完整。
(2).数据的不一致往往是因为一个线程中的多个关联的操作(这几个操作合成原子操作)未全部完成。
避免以上的问题可采用对数据进行加锁的方法,如下
七.对象锁 Synchronized
防止打断原子操作,解决并发访问的故障。
//原子操作:不可分割的几个操作,要么一起不做,要么不能被干扰地完成。
1.互斥锁标记:每个对象除了属性和方法,都有一个monitor(互斥锁标记),
用来将这个对象交给一个线程,只有拿到monitor 的线程才能够访问这个对象。
2.Synchronized:这个修饰词可以用来修饰方法和代码块
Object obj; Obj.setValue(123);
Synchronized用来修饰代码块时,该代码块成为同步代码块。
Synchronized 用来修饰方法,表示当某个线程调用这个方法之后,其他的事件不能再调用这个方法。
只有拿到obj 标记的线程才能够执行代码块。
注意:
(1)Synchronized 一定使用在一个方法中。
(2)锁标记是对象的概念,加锁是对对象加锁,目的是在线程之间进行协调。
(3)当用Synchronized 修饰某个方法的时候,表示该方法都对当前对象加锁。
(4)给方法加Synchronized 和用Synchronized 修饰对象的效果是一致的。
(5)一个线程可以拿到多个锁标记,一个对象最多只能将monitor 给一个线程。
(6)构造方法和抽象方法不能加synchronized;
(7)一般方法和静态方法可以加synchronized同步。//静态方法把类看作对象时可加锁
(8)Synchronized 是以牺牲程序运行的效率为代价的,因此应该尽量控制互斥代码块的范围。
(9)方法的Synchronized 特性本身不会被继承,只能覆盖。
八.锁池
1.定义:线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池。
2.每个对象都有自己的锁池的空间,用于放置等待运行的线程。这些线程中哪个线程拿到锁标记由系统决定。
3.死锁:线程互相等待其他线程释放锁标记,而又不释放自己的;造成无休止地等待。
死锁的问题可通过线程间的通信解决。
九.线程间通信:
1. 线程间通信机制实际上也就是协调机制。
线程间通信使用的空间称之为对象的等待队列,这个队列也属于对象的空间。
注:现在,我们已经知道一个对象除了由属性和方法之外还有互斥锁标记、锁池空间和等待队列空间。
2. wait()
在运行状态中,线程调用wait(),表示这线程将释放自己所有的锁标记,同时进入这个对象的等待队列。
等待队列的状态也是阻塞状态,只不过线程释放自己的锁标记。
用notify()方法叫出之后,紧跟着刚才wait();的位置往下执行。
3. Notify()
如果一个线程调用对象的notify(),就是通知对象等待队列的一个线程出列。进入锁池。
如果使用notifyall()则通知等待队列中所有的线程出列。
注意:只能对加锁的资源(Synchronized方法里)进行wait()和notify()。
我们应该用notifyall取代notify,因为我们用notify释放出的一个线程是不确定的,由OS决定。
释放锁标记只有在Synchronized 代码结束或者调用wait()。
锁标记是自己不会自动释放,必须有通知。
注意:在程序中判定一个条件是否成立时要注意使用WHILE 要比使用IF 更严密。
//WHILE 循环会再次回来判断,避免造成越界异常。
4. 补充知识:
suspend()将运行状态推到阻塞状态(注意不释放锁标记)。恢复状态用resume()。Stop()释放全部。
这几个方法上都有Deprecated 标志,说明这个方法不推荐使用。
一般来说,主方法main()结束的时候线程结束,可是也可能出现需要中断线程的情况。
对于多线程一般每个线程都是一个循环,如果中断线程我们必须想办法使其退出。
如果想结束阻塞中的线程(如sleep 或wait),可以由其他线程对其对象调用interrupt()。
用于对阻塞(或锁池)会抛出例外Interrupted。
5.Exception。
这个例外会使线程中断并执行catch 中代码。
十.5.0的新方法:
//参看 java.util.concurrent.*;包下的Callable,ExecutorService,Executors;
1. ExecutorService代替Thread,它不会销毁线程,效率更高,用空间换时间,适用于服务器。
ExecutorService exec = Executors.newFixedThreadPool(3);//创建3个等待调用的线程
Callable<String> c1 = new Task();//用Callable的Task子类实现任务,其call()代替run()
Future<String> f1 = exec.submit(c1);//Futrue获得运行线程后的结果,这线程与主程序分开
String s1 = f1.get();//Futrue的获得结果的方法,会返回异常
2. 用Callable接口代替Runnable
因为Runnable不能抛异常,且没有返回值。
3. Lock对象 替代Synchronized标志符,获得的更广泛的锁定操作,允许更灵活的结构。
还有tryLock()尝试加锁。
解锁用unLock();可以主动控制解锁,方便复杂的方法调用。
//下列方法参考 java.util.concurrent.locks
在这里wait()用await()替代;Notify(),notifyall()用signal(),signalAll()替代。
ReadWriteLock读写锁:
writeLock()写锁,排他的,一旦加上,其他任何人都不能来读写。
readLock()读锁,共享的,加上之后,别人可以读而不能写。可以加多个读锁。
重点: 实现多线程的两种方式, Synchronized, 生产者和消费者问题
练习:① 存车位的停开车的次序输出问题。
② 写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z。要求用线程间的通信。
注:分别给两个对象构造一个对象o,数字每打印两个或字母每打印一个就执行o.wait()。
在o.wait()之前不要忘了写o.notify()。
补充:通过Synchronized,可知Vector与ArrayList的区别就是Vector几乎所有的方法都有Synchronized。
所以Vector 更为安全,但效率非常低下。 同样:Hashtable 与HashMap 比较也是如此。
/****************多线程间的锁及通信**************************/
//生产者-消费者问题,见P272
public class TestProducerAndConsumer {
public static void main(String[] args) {
MyStack ms = new MyStack();
Thread producer = new ProducerThread(ms);
Thread consumer = new ConsumerThread(ms);
producer.start();
consumer.start();
Thread producer2 = new ProducerThread(ms);
producer2.start();//用第2个生产者,说明while循环判断比if更严密
}}
class MyStack{ //工厂
private char[] cs = new char[6];//仓库只能容6个产品
private int index = 0;
public synchronized void push(char c){
//用while来循环判断,避免多个push进程时的下标越界异常。如果用if,只能在一个push进程时能正常
while(index==6){try{wait();}catch(Exception e){}}
cs[index] = c;
System.out.println(cs[index]+ " pushed!");
index++;
}
public synchronized void pop(){
while(index==0){try{wait();}catch(Exception e){}}
index--;
System.out.println(cs[index]+" poped!");
notify();
cs[index] = '\0';
}
public void print(){
for(int i=0; i<index; i++){System.out.print(cs[i]+"\t");}
System.out.println();
}}
class ProducerThread extends Thread{ //生产者
private MyStack s;
public ProducerThread(MyStack s){this.s = s;}
public void run(){
for(char c='A'; c<='Z'; c++){
s.push(c);
try{Thread.sleep(20);}
catch(Exception e){e.printStackTrace();}
s.print();
}}}
class ConsumerThread extends Thread{ //消费者
private MyStack s;
public ConsumerThread(MyStack s){
this.s = s;
}
public void run(){
for(int i=0; i<26*2; i++){
s.pop();
try{Thread.sleep(40);}
catch(Exception e){e.printStackTrace();}
s.print();
}}}
/*********************************************************/
Daemon Threads(daemon 线程)
是服务线程,当其他线程全部结束,只剩下daemon线程时,虚拟机会立即退出。
Thread t = new DaemonThread();
t.setDaemon(true);//setDaemon(true)把线程标志为daemon,其他的都跟一般线程一样
t.start();//一定要先setDaemon(true),再启动线程
在daemon线程内启动的线程,都定为daemon线程
day20 I/O流
一. I/O 流(java 如何实现与外界数据的交流)
1. Input/Output:指跨越出了JVM 的边界,与外界数据的源头或者目标数据源进行数据交换。
注意:输入/输出是针对JVM 而言。
2. 流的分类:
按流向分为输入流和输出流;
按传输单位分为字节流和字符流;
按功能还可以分为节点流和过滤流。(以Stream结尾的类都是字节流。)
节点流:负责数据源和程序之间建立连接;(相当于电线中的铜线,过滤流相当于电线的塑料皮)
过滤流:用于给节点增加功能。(相当于功能零部件)
过滤流的构造方式是以其他流位参数构造,没有空参构造方法(这样的设计模式称为装饰模式)。
注:I/O流使用完后建议调用close()方法关闭流并释放资源。
在关闭流时只需关闭最外层的流,会自动关闭内层的流。
3. File 类(java.io.*)可表示一个文件,也有可能是一个目录
在JAVA 中文件和目录都属于这个类中,而且区分不是非常的明显。
4. Java.io 下的方法是对磁盘上的文件进行磁盘操作,但是无法读取文件的内容。
注意:创建一个文件对象和创建一个文件在JAVA 中是两个不同的概念。前者是在虚拟机中创建了一个文件,但却并没有将它真 正地创建到OS 的文件系统中,随着虚拟机的关闭,这个创建的对象也就消失了。而创建一个文件是指在系统中真正地建立一个文件。
例如:File f=new File(“11.txt”);//创建一个名为11.txt 的文件对象
f.CreateNewFile(); //这才真正地创建文件
f.CreateMkdir();//创建目录
f.delete();//删除文件
getAbsolutePath();//打印文件绝对路径
getPath();//打印文件相对路径
f.deleteOnExit();//在进程退出的时候删除文件,这样的操作通常用在临时文件的删除。
5. 对于跨平台:File f2=new file("d:\\abc\\789\\1.txt")
//这文件路径是windows的,"\"有转义功能,所以要两个
这个命令不具备跨平台性,因为不同的OS的文件系统很不相同。
如果想要跨平台,在file 类下有separtor(),返回锁出平台的文件分隔符。
File.fdir=new File(File.separator);
String str=”abc”+File.separator+”789”;
6. List():显示文件的名(相对路径)
ListFiles():返回Files 类型数组,可以用getName()来访问到文件名。
使用isDirectory()和isFile()来判断究竟是文件还是目录。
使用I/O流访问file中的内容。
JVM与外界通过数据通道进行数据交换。
二. 字节流
1. 字节输入流:io包中的InputStream为所有字节输入流的父类。
Int read();//读入一个字节(每次一个);
可先使用new byte[]=数组,调用read(byte[] b)
read (byte[])返回值可以表示有效数;read (byte[])返回值为-1 表示结束。
2. 字节输出流:io包中的OutputStream为所有字节输入流的父类。
Write和输入流中的read相对应。
3. 在流中close()方法由程序员控制。因为输入输出流已经超越了JVM的边界,所以有时可能无法回收资源。
原则:凡是跨出虚拟机边界的资源都要求程序员自己关闭,不要指望垃圾回收。
4. 以Stream结尾的类都是字节流。
FileOutputStream f = new FileOutputStream("1.txt");//如果之前有这文件,将会覆盖
FileOutputStream f = new FileOutputStream("1.txt",true);//如果有这文件,只会追加
DataOutputStream,DataInputStream:可以对八种基本类型加上String类型进行写入。
因为每种数据类型的不同,所以可能会输出错误。
所有对于:DataOutputStream DataInputStream两者的输入顺序必须一致。
/**输入、输出流;字节流**************************/
public static void main(String[]args) throws IOException{
FileOutputStream fos=null;//输出流,字节流
fos = new FileOutputStream("a.txt");//会抛异常;如果不用绝对路径,将生成所在类的路径中
for(int i = 32; i<=126 ;i++) fos.write(i);//直接写进
// String s = "ABCDE";byte[] ss = s.getBytes();fos.write(ss,0,4);//直观地写进
fos.close();
FileInputStream fis = new FileInputStream("a.txt");//输入流
//for(int i=0;(i=fis.read())!=-1;) System.out.print((char) i);
//上句是直接读取;fis.read()类似next().
byte[] b= new byte[1024];//在内存划分空间以便装载从文件读来的数据
fis.read(b);fis.close();
for(int i=0;i<b.length;i++) System.out.print((char)b[i]);
}//打印出键盘上的所有数字、大小写字母和标准符号
/********************************************/
过滤流:(装饰模式,油漆工模式,修饰字节流)
bufferedOutputStream
bufferedInputStream
在JVM的内部建立一个缓冲区,数据先写入缓冲区,直到缓冲区写满再一次性写出,效率提高很多。
使用带缓冲区的输入输出流(节点流)会大幅提高速度,缓冲区越大,效率越高。(典型的牺牲空间换时间)
切记:使用带缓冲区的流,如果数据数据输入完毕,使用flush方法将缓冲区中的内容一次性写入到外部数据源。
用close()也可以达到相同的效果,因为每次close前都会使用flush。一定要注意关闭外部的过滤流。
管道流(非重点):也是一种节点流,用于给两个线程交换数据。
PipedOutputStream
PipedInputStream
输出流: connect(输入流)
RondomAccessFile 类允许随机访问文件同时拥有读和写的功能。
GetFilepoint()可以知道文件中的指针位置,使用seek()定位。
Mode(“r”:随机读;”w”:随机写;”rw”:随机读写)
字符流: reader\write 只能输纯文本文件。
FileReader 类:字符文件的输出
三、字节流的字符编码:
字符编码把字符转换成数字存储到计算机中,按ASCii 将字母映射为整数。
把数字从计算机转换成相应的字符的过程称为解码。
乱码的根源是编码方式不统一。任何一种编码方式中都会向上兼容ASCII码。所以英文没有乱码。
编码方式的分类:
ASCII(数字、英文):1个字符占一个字节(所有的编码集都兼容ASCII)
ISO8859-1(欧洲,拉丁语派):1个字符占一个字节
GB-2312/GBK:1 个字符占两个字节。GB代表国家标准。
GBK是在GB-2312上增加的一类新的编码方式,也是现在最常用的汉字编码方式。
Unicode: 1 个字符占两个字节(网络传输速度慢)
UTF-8:变长字节,对于英文一个字节,对于汉字两个或三个字节。
原则:保证编解码方式的统一,才能不至于出现错误。
I/O学习种常犯的两个错误:1.忘了flush 2.没有加换行。
四、字符流
以reader或write结尾的流为字符流。 Reader和Write是所有字符流的父类。
Io 包的InputStreamReader 称为从字节流到字符流的桥转换类。这个类可以设定字符解码方式。
OutputStreamred:字符到字节
Bufferread 有readline()使得字符输入更加方便。
在I/O 流中,所有输入方法都是阻塞方法。
最常用的读入流是BufferedReader.没有PrintReader。
Bufferwrite 给输出字符加缓冲,因为它的方法很少,所以使用父类PrintWriter,它可以使用字节流对象构造,省了桥转换这一步,而且方法很多。注:他是带缓冲的。最常用的输出流。
对象的持久化:把对象保存文件,数据库
对象的序列化:把对象放到流中进行传输
对象的持久化经常需要通过序列化来实现。
四大主流:InputStream,OutputStream,Read,Write
day21
一.对象序列化
1.定义:把一个对象通过I/O流写到文件(持久性介质)上的过程叫做对象的序列化。
2.序列化接口:Serializable
此接口没有任何的方法,这样的接口称为标记接口。
3.不是所有对象都能序列化的,只有实现了Serializable的类,他的实例对象才是可序列化的。
4.Java种定义了一套序列化规范,对象的编码和解码方式都是已经定义好的。
5.class ObjectOutputStream和ObjectInputStream也是带缓冲的过滤流,使节点流直接获得输出对象
可以用来向文件中写入八种基本数据类型和对象类型。
最有用的方法:
(1)writeObject(Object b)
(2)readObject()返回读到的一个对象,但是需要我们注意的是,该方法不会以返回null表示读到文件末尾。
而是当读到文件末尾时会抛出一个IOException;
6.序列化一个对象并不一定会序列化该对象的父类对象
7.瞬间属性(临时属性)不参与序列化过程。
8.所有属性必须都是可序列化的,特别是当有些属性本身也是对象的时候,要尤其注意这一点。
序列化的集合就要求集合中的每一个元素都是可序列化的。
9.用两次序列化把两个对象写到文件中(以追加的方式),与用一次序列化把两个对象写进文件的大小是不同的。
因为每次追加时都会在文件中加入开始标记和结束标记。所以对象的序列化不能以追加的方式写到文件中。
二、transient关键字
1.用transient修饰的属性为临时属性。
三.分析字符串工具java.util. StringTokenizer;
1.string tokenizer 类允许应用程序将字符串分解为标记
2.可以在创建时指定,也可以根据每个标记来指定分隔符(分隔标记的字符)集合。
3.StringTokenizer(s,”:”) 用“:”隔开字符,s 为String对象。
/*********************************************************/
import java.util.StringTokenizer;
public class TestStringTokenizer {
public static void main(String[] args) {
String s = "Hello:Tarena:Chenzq";
StringTokenizer st = new StringTokenizer(s,":");
while(st.hasMoreTokens()){
String str = st.nextToken();
System.out.println(str);
}}}
/********************************************************/
四、网络的基础知识:
1、ip:主机在网络中的唯一标识,是一个逻辑地址。
127.0.0.1 表示本机地址。(没有网卡该地址仍然可以用)
2、端口:端口是一个软件抽象的概念。如果把Ip地址看作是一个电话号码的话,端口就相当于分机号。
进程一定要和一个端口建立绑定监听关系。端口号占两个字节。
3、协议:通讯双方为了完成预先制定好的功能而达成的约定。
4、TCP/IP网络七层模型:
物理层Physical(硬件)、 数据链路层DataLink(二进制) 、网络层Network(IP协议:寻址和路由)
传输层Transport(TCP协议,UDP协议) 、会话层Session(端口)
表示层Presentation、应用层Application(HTTP,FTP,TELET,SMTP,POPS,DNS)
注:层与层之间是单向依赖关系。对等层之间会有一条虚连接。Java中的网络编程就是针对传输层编程
5、网络通信的本质是进程间通信。
6、Tcp协议和UDP协议
TCP:开销大,用于可靠性要求高的场合。TCP的过程相当于打电话的过程。面向连接,可靠,低效
UDP:用在对实时性要求比较高的场合。UDP的过程相当于写信的过程。无连接,不可靠,效率高
五、网络套节字Socket(TCP)
1、一个Socket相当于一个电话机。
OutputStream相当于话筒
InputStream相当于听筒
2、服务器端要创建的对象:java。Net。ServerSocket
3、创建一个TCP服务器端程序的步骤:
1). 创建一个ServerSocket
2). 从ServerSocket接受客户连接请求
3). 创建一个服务线程处理新的连接
4). 在服务线程中,从socket中获得I/O流
5). 对I/O流进行读写操作,完成与客户的交互
6). 关闭I/O流
7). 关闭Socket
/***********************************************************/
import java.net.*;
import java.io.*;
public class TcpServer{//服务器端
public static void main(String[] args) {
ServerSocket ss = null;
Socket s = null;
try{ ss= new ServerSocket(10222);
s = ss.accept();//客户端连上后返回Socket,监听端口
OutputStream os = s.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println("欢迎欢迎!");//要换行,否则不能读取
pw.flush();//从内存输出去
}catch(Exception e){}
finally{if(s!=null )try{s.close(); }catch(Exception e){}
if(ss!=null)try{ss.close();}catch(Exception e){}
}}}
public class TcpClient {//接受端
public static void main(String[] args) throws Exception {
Socket s = new Socket("10.3.1.79", 10222);
BufferedReader br = new BufferedReader(new InputStreamReader
(s.getInputStream()));
System.out.println(br.readLine());
s.close();
}}
/***********************************************************/
4、建立TCP客户端
创建一个TCP客户端程序的步骤:
1). 创建Socket
2). 获得I/O流
3). 对I/O流进行读写操作
4). 关闭I/O流
5). 关闭Socket
5、网络套节字Socket(UDP)
1.UDP编程必须先由客户端发出信息。
2.一个客户端就是一封信,Socket相当于美国式邮筒(信件的收发都在一个邮筒中)。
3.端口与协议相关,所以TCP的3000端口与UDP的3000端口不是同一个端口
6、URL:统一资源定位器
唯一的定位一个网络上的资源
如:http://www.tarena.com.cn:8088
/***下载程序**************************************************************/
import java.net.*;
import java.io.*;
class TestUrl{
public static void main(String[] args) throws Exception{
String str = "http://192.168.0.23:8080/project_document.zip";
URL url = new URL(str);//上句指定下载的地址和文件
URLConnection urlConn = url.openConnection();
urlConn.connect();
InputStream is = urlConn.getInputStream();
FileOutputStream fos = new FileOutputStream("/home/sd0807/down.zip");
byte[] buf = new byte[4096]; //上句指定下载的地址和下载后的名称
int length = 0;
while((length=is.read(buf))!=-1){
fos.write(buf, 0, length);
}
fos.close();
is.close();
}}
/***************************************************************************/