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();
}}
/***************************************************************************/
JAVAC的帮助,输入[sd0807@localhost ~]$ javac
编译:javac ***.java
用法: javac <options> <source files>
用法:javac <选项> <源文件>
其中,可能的选项包括:
-g 生成所有调试信息
-g:none 不生成任何调试信息
-g:{lines,vars,source} 只生成某些调试信息
-nowarn 不生成任何警告
-verbose 输出有关编译器正在执行的操作的消息
-deprecation 输出使用已过时的 API 的源位置
-classpath <路径> 指定查找用户类文件和注释处理程序的位置
-cp <路径> 同上(是 classpath 的缩写)
-sourcepath <路径> 指定查找输入源文件的位置
-bootclasspath <路径> 覆盖引导类文件的位置
-extdirs <目录> 覆盖安装的扩展目录的位置
-endorseddirs <目录> 覆盖签名的标准路径的位置
-proc:{none,only} 控制是否执行注释处理和/或编译。
-processor <class1>[,<class2>,<class3>...]
要运行的注释处理程序的名称;绕过默认的搜索进程
-processorpath <路径> 指定查找注释处理程序的位置
-d <目录> 指定存放生成的类文件的位置
-s <目录> 指定存放生成的源文件的位置
-implicit:{none,class} 指定是否为隐式引用文件生成类文件
-encoding <编码> 指定源文件使用的字符编码
-source <版本> 提供与指定版本的源兼容性
-target <版本> 生成特定 VM 版本的类文件
-version 版本信息
-help 输出标准选项的提要
-Akey[=value] 传递给注释处理程序的选项
-X 输出非标准选项的提要
-J<标志> 直接将 <标志> 传递给运行时系统
输入[sd0807@localhost ~]$ java
运行(虚拟机): java ***
Usage: java [-options] class [args...]
(to execute a class)
or java [-options] -jar jarfile [args...]
(to execute a jar file)
where options include:
-d32 use a 32-bit data model if available
-d64 use a 64-bit data model if available
-client to select the "client" VM
-server to select the "server" VM
-hotspot is a synonym for the "client" VM [deprecated] The default VM is client.
-cp <class search path of directories and zip/jar files>
-classpath <class search path of directories and zip/jar files>
A : separated list of directories, JAR archives,
and ZIP archives to search for class files.
-D<name>=<value> set a system property
-verbose[:class|gc|jni] enable verbose output
-version print product version and exit
-version:<value> require the specified version to run
-showversion print product version and continue
-jre-restrict-search | -jre-no-restrict-search
include/exclude user private JREs in the version search
-? -help print this help message
-X print help on non-standard options
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>] enable assertions
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>] disable assertions
-esa | -enablesystemassertions enable system assertions
-dsa | -disablesystemassertions disable system assertions
-agentlib:<libname>[=<options>]
load native agent library <libname>, e.g. -agentlib:hprof
see also, -agentlib:jdwp=help and -agentlib:hprof=help
-agentpath:<pathname>[=<options>] load native agent library by full pathname
-javaagent:<jarpath>[=<options>]
load Java programming language agent, see java.lang.instrument
-splash:<imagepath> show splash screen with specified image