Android开发入门(二)——基本语法2
来源:互联网 发布:我打软件技巧 编辑:程序博客网 时间:2024/06/05 08:06
参考资料:《Android系统下Java编程详解》
本文摘录了该书的一些知识点,适合有面向对象编程基础的开发者。
第8章 Java异常处理
1 异常概述
Java中,异常对象分为两大类:Error和Exception
Error处理的是Java运行环境中的内部错误或者硬件问题,对于这种错误,程序基本无能为力。对于Exception,他处理的是因为程序设计的瑕疵而引起的问题或是外在的输入引起的一般性问题。
Error类只有四个子类:AWTError、LinkageError、VirtualMachineError以及ThreadDeath。
Exception的子类很多,可以大致将它们分为3类:有关I/O的IOException(IO系统出现阻塞等原因引起)、有关运行时的异常RuntimeException(由于程序编写过程中的不周全的代码引起的)以及其他异常。
引起IOExpection的原因包括:
① 试图从文件结尾处读取信息;
② 试图打开一个不存在或者格式错误的URL。
引起RuntimeException的原因包括:
① 错误的类型转换;
② 数组越界访问;
③ 数学计算错误;
④ 试图访问一个空对象。
2 Java中异常的处理
Java程序在执行过程中出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时的环境,这个过程被称为抛出(throw)异常。
当Java运行时环境接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这一过程称为捕获(catch)异常。如果Java运行时环境找不到可以捕获异常的方法,则运行时环境将终止。
try{ //可能会抛出特定异常的代码段}catch(MyExceptionType myException){ //如果myException被抛出,则执行这段代码 //可以没有也可以不止一个catch}finally{ //无条件执行的语句 //可以没有但是有的话只有一个}
import java.io.*;public class CatchException{ public static void main(String[] args){ FileInputStream fis=null; try{ fis=new FileInputStream("c:/a.txt"); int b; b=fis.read(); while(b!=-1){ System.out.print((char)b); b=fis.read(); } } catch(FileNotFoundException e) { System.out.println("FineNotFoundException:"+e.getMessage()); } catch(IOException e1) { System.out.println("IOException:"+e1.getMessage()); } finally { try { if(fis!=null) fis.close(); } catch(IOException ios) { System.out.println("关闭文件出错!"); } } } }
注意:IOException和FinleNotFoundException存在继承关系,FileNotFoundException是IOException的子类,所以只能将FileNotFoundException放在IOException前面捕获输出,否则编译时会抛出error。
只要有finally语句存在,它就一定会被执行,哪怕try中有return语句。但是,try从句或者catch()从句有System.exit()语句的时候,整个程序就会退出执行,finally从句就无法执行。
可以对下列情形在方法定义中抛出异常:
① 方法中调用了一个会抛出“已检查异常”的方法;
② 程序运行过程中发生了错误,并且用throw字句抛出了一个“已检查异常”
不要抛出如下异常:
① 从Error中继承来的错误
② 从RuntimeException中派生的哪些异常,如NullPointerException等。
如果一个异常没有在当前的try-catch模块中处理,则会抛出到它的调用方法。
如果一个异常回到main()方法仍然没有得到处理,程序会异常中止。
示例:将方法中的异常抛出给调用者处理:
import java.io.*;public class ThrowExam { public void readFile() throws FileNotFoundException, IOException { FileInputStream fis=new FileInputStream("c:/a.txt"); int b; b=fis.read(); while(b!=-1){ System.out.print((char)b); b=fis.read(); } fis.close(); } public static void main(String[] args) { ThrowExam te=new ThrowExam(); try { te.readFile(); } catch(FileNotFoundException e) { System.out.println("FileNotFoundException:"+e.getMessage()); } catch(IOException e1) { System.out.println("IOException:"+e1.getMessage()); } }}
import jave.io.*;public class ThrowExam1 { public void readFile() throws FileNotFoundException,IOException { File f=new File("c:/a.txt"); if(!f.exists()){ throw new FileNotFoundException("File cao't be founde!"); } FileInputStream fis=new FileInputStream(f); int b; b=fis.read(); while(b!=-1) { System.out.print((char) b); b=fis.read(); } fis.close(); } public static void main(String[] args) { ThrowExam1 te=new ThrowExam1(); try { te.readFile(); } catch(FileNotFoundException e) { System.out.println("FileNotFoundException:"+e.getMessage()); } catch(IOException e1) { System.out.println("IOException:"+e1.getMessage()); } }}
使用throw关键字抛出对象的时候,抛出的必须是Throwable或者它的子类的实例,而不能是其他的任何类型的对象。
当子类中的方法覆盖父类中的方法时,可以抛出异常。覆盖方法时,可以抛出与被覆盖方法的异常相同的异常,或者被覆盖方法的异常的子类异常。如果父类方法没有声明抛出异常,那么子类覆盖方法不可以声明抛出“已检查”异常,也不能抛出除弗雷方法中声明的异常外的其他“已检查”异常。
3 自定义异常
通过继承Exception或者它的子类,可以实现自己的异常类。在实现自己的异常类时,会给这个异常类设计两个构造器:一个参数为空的构造器,以及一个带String类型参数的构造器,用来传递详细的出错信息。
public class MyDivideException extends ArithmeticException { public MyDivideException() { super(); } public MyDivideException(String msg) { super(msg); } public String toString() { //覆盖了父类中的toString方法 return "除以零引起的例外!"; }}
可以用printStackTrace()方法追踪异常。
public class TestPrintStackTrace { public static void main(String args[]){ try { firstMethod(); } catch(SelfDefineException e) { e.printStackTrace(); } }//其他内容}
打印异常时,会从最开始触发异常的位置开始打印。
第9章 Android中的Java线程
1 线程概述
线程是程序执行流的最小单元。进程是某种程度上相互隔离的、独立运行的程序。
多线程和多进程的区别:
① 地址空间与其他资源:进程间相互独立,同一进程的各线程键共享。某进程内的线程在其他进程中不可见。
② 通信:进程间通信用IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
③ 调度和切换:线程上下文切换比进程上下文切换要快得多、
④ 在多线程OS中,进程不是一个可执行的实体。
2 Java线程通信
计算机程序得以执行的三个要素是:CPU、程序代码和可存取的数据。
Java中多线程机制通过虚拟CPU实现,Java的线程通过java.lang.Thread类来实现,它内部实现了虚拟CPU的功能,能够接收和处理传递给它的代码和数据,并提供了独立的运行控制功能。
每个Java应用程序都至少有一个线程,这就是主线程。它有JVM创建并调用Java应用程序的main(0方法。
3 创建线程
Java中创建线程的一种方式是通过Thread实现,另一种是通过Runnable接口并实现接口中定义的唯一方法run(),可以定义一个线程。
继承Thread创建线程:
Thread(ThreadGroup group,Runnable target,String name):创建一个隶属于group线程组、目标为target,名字为name的线程。(这几个参数不都是必须的)
首先,将一个类继承Thread,然后覆盖Thread中的run()方法,这样就让这个类本身成了线程类,方法run()称为线程体,:
public class Aclass extends Thread{ ... public void run() {...} ...}
public class TestThread extends Thread { public void run() { for(int i=1;i<10;i++) { System.out.println("Count:"+i); } } public static void main(String[] args) { TestThread tt=new TestThread(); tt.start(); }}
调用start()方法,线程进入Runnable状态,它将向线程调度器注册这个线程,调用start()方法不一定马上会执行这个线程,它只是进入Runnable状态而不是Running。
不要在程序中直接调用线程的run()方法,因为可能存在线程等待的情况,直接调用run()方法容易造成死锁。
实现Runnable接口创建线程:
① 将实现Runnable接口的类实例化;
② 建立一个Thread对象,并将第一步实例化后的对象作为参数传入Thread类的构造方法。
最后通过Thread类的start方法建立线程。
package mythread;public class MyRunnable implements Runnable{ public void run() { System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { MyRunnable t1=new MyRunnable(); MyRunnable t2=new MyRunnable(); Thread thread1=new Thread(t1,"MyThread1"); Thread thread2=new Thread(t2); thread2.setName("MyThread2"); thread1.start(); thread2.start(); }}
两种方法的区别在于:
① 使用Runnable接口:可以将CPU,代码和数据分开,形成清晰的模型;还可以从其他类继承;保持程序风格的一致性。
② 直接继承Thread类:不能再从其他类继承;编写简单,可以直接操纵线程,无须使用Thread.currentThread()。
如果继承Thread类,则这个类本身可以调用start方法,也就是说这个继承了Thread的类当作目标对象,而如果实现Runnable接口,则这个类必须被当作其他线程的目标对象。
后台线程:在后台运行,为其他线程提供服务,例如定时器(Timer)。经常用于任务结束时的善后处理,优先级比其他线程的优先级低。
用户线程:和后台线程先对的其他线程。如果一个应用中只有后台现场在运行,JVM将退出该应用程序。
可以通过setDaemon(boolean d)将一个普通的线程设置为后台线程,用方法isDeamon()可以测试特定的线程是否为后台线程。
public class DeamonThread extends Thread { public void run() { while(true) { System.out.println("Deamon thread running..."); } } public static void main(String[] args) { DeamonThread dt=new DeamonThread(); //下面一行将此线程设置为后台线程 dt.setDeamon(true); //这个线程运行一段时间后会自动退出,因为只有一个后台线程在运行,但是如果把上面这行注释掉,这个应用程序就会一直运行下去 dt.start(); }}
4 线程运行机制
线程的状态分4中:创建(New),可运行(Runnable),阻塞(Blocked)和死亡(Dead)。
用new新键一个线程时,它处于创建状态,这个时候线程未进行任何操作。
然后调用start()方法, 来向线程叼杜程序(通常是JVM或操作系统)注册一个线程,这个时候线程一切就绪,就等待CPU时间了。
线程调度程序调用线程的run方法给已经注册的线程执行的机会,被调度执行的线程进入运行(Running)状态,当线程的run()方法执行完毕,线程将被抛弃,进入死亡(Dead)状态。
不能用restart()方法重新开始一个处于死亡状态的线程,但是可以调用处于死亡状态的线程对象的各个方法。
如果线程在运行状态因为I/O阻塞、等待键盘输入、调用了线程的sleep()方法、调用了对象的wait()方法等,则线程将进入阻塞(Blocked)状态,直到这些阻塞原因被解除,线程将返回到Runnable状态并等待调度程序的调度。
可以用stop()或者subspend()方法中断现场,但是这两种方法可能导致数据不同步发生死锁。如果需要中止一个线程,可以使用以下方法;
① 让线程的run()方法执行完,线程自然结束(这种方法最好);
② 通过轮询和共享标志位的方法来结束线程,例如while(flag){},flag的初始值为真,当需要结束时,将flag中的值置为false(这种方法也不是很好,因为如果while(flag){}方法阻塞了,则flag会失效);
③ 线程抛出一个未捕获到的Exception或Error,通过调研interrupt方法和捕获InterruptedException异常来中止线程;
最好的方法是使用线程池,当线程不用了,就让它sleep并放进队列中,这样就可以最大限度地利用资源。
比如:Thread.sleep(2000);
5 线程控制
测试线程:
isAlive():测试线程的状态,true表示已经start,false表示是一个新线程或是dead。
Thread.sleep():线程暂时中止执行(睡眠)一定的时间,在指定睡眠时间内一定不会再次得到运行机会;
Thread.yiedl():线程放弃运行,将CPU控制权让出,有可能马上被系统的调度机制选中运行;
利己线程:当一个线程在执行一个很长的循环时,它应该自始至终在适当的时候用sleep()或yield()方法,以确保其他的线程能够得到运行的机会。如果一个线程不遵循这个规律,那么这样的线程称为利己线程。
getPriority():获得线程的运行优先级;
setPriority():设置线程的运行优先级;
Java线程中,每一个线程都有一个优先级,可以通过常量NORM_PRIORITY获得,值是5。默认情况下,子类的优先级将继承父类的优先级。在Thread类中,有一个MIN_PRIORITY表示最低优先级,为1,有一个MAX_PRIORITY表示最高优先级,为10。
Thread.join():阻塞调用线程,直到被join()方法加入的目标线程完成为止。通常由使用线程的程序调用。
public class TestJoin { static int[] a=new int[20]; public static void main(String args[]) { JoinThread r=new JoinThread(); Thread t=new Thread(r); t.start(); try{ t.join(); } catch(InterruptedException e) { System.out.println(e.getMessage()); } for(int i=0;i<20;i++) { System.out.println("Printing array a["+i+"]: "+a[i]); } } static class JoinThread implements Runnable { public void run() { for(int i=0;i<20;i++) { System.out.println("Initializing array a["+i+"]: "+(i-50)); a[i]=i-50; } } }}
如果不使用join,可能会先调度main()的进程,使用join后,会先处理t。
6 多线程编程
多线程描述的是一种情形:在主线程中有多个线程在运行。在通常情况下,我们提及多线程的时候,指的是如下特定情况:
① 多个线程来自同一个Runnable实例;
② 多个线程使用同样的数据和代码。
在Java语言中,引入对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象对应一个可称为互斥锁的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键词synchronized用来与对象的互斥锁联系,当某个对象用synchronize修饰时,表明该对象在任一时刻只能由一个线程访问。
在Java中有两种使用synchronized的方式:
(1) 放在方法前面,这样,调用该方法的线程均将获得对象的锁;
(2) 放在代码块前面,用于修饰对象引用,它也有两种形式:
① 用于修饰当前对象的引用——sychronized(this){...}
和synchronized{...}
:代码块中的代码将获得当前对象引用的锁;
② 用于修饰指定的对象——sychoronized(otherObj){...}
:代码块中的代码将获得指定对象引用的锁。
synchronized锁定的不是方法或者代码块,而是对象。
synchronized也可以用来修饰类,当synchronized用在类前面时,表示这个类的所有方法都是synchronized的。
线程归还锁的时机如下:
① 当线程执行到synchronized块结束时,释放对象锁;
② 当在synchronized块中遇到break,return或抛出Exception,则自动释放对象锁;
③ 当一个线程调用wait()方法时,它放弃拥有对象的锁并进入等待队列。
public class Safestack implements StackInterface { private int top=0; private int[] values=new int[10]; public void push(int n) { synchronized(this) { //锁的是当前对象 values[top]=n; System.out.println("压入数组"+n+"步骤1完成"); top++; System.out.println("压入数字完成"); } } public int[] pop() { synchronized(this) { System.out.print("弹出"); top--; int[] test={values[top],top}; return test; } }}
避免死锁的一种方式是,确保在获取多个锁时,在所有的线程中都以相同的顺序获取锁。
线程间通信:wai()会让调用线程等待,并放弃对象锁,直到用Thread.interrupt()中断它、过了指定的时间或者另一个线程用notify()或notifyAll()唤醒它。wait()方法也可以接受一个参数,表示暂停一定的时间(毫秒),它可以不需要使用notify()/notifyAll()来唤醒。它和sleep()的区别主要在于,wait()方法会释放对象锁,而sleep()方法不会。
定时器:在java.util包中, 有两个类和定时相关:Timer和TimerTask。Timer是一个定时器,它可以定时地执行一些任务,而它定时执行的任务是由TimerTask定义的。TimerTask是一个实现了Runnable接口的类,所以它本质上是一个线程。
要实现一个定时功能,可以根据以下两个步骤完成:
① 定义一个类,让它继承TimerTask,并且将需要定时执行的动作定义在run()方法中;
② 实例化一个Timer对象,然后,将上面定义的TimerTask对象作为Timeer对象的schedule()方法参数,并且在这个方法中设置定时排程。
schedule(TimerTask tt,Date data,long period):在指定时间data开始第一次执行TimerTask指定的任务,每个period时间重复;
schedule(TimerTask tt,long delay,long period):在指定时间delay后第一次执行TimerTask指定的任务,每个period时间重复;
import java.util.Timer;import java.util.TimerTask;public class TimerWork { public static void main(String[] args) { Timer tmr=new Timer(); tmr.schedule(new TimerPrinter(),0,2000); }}class TimerPrinter extends TimerTask {//继承了TimerTask并覆盖了run()方法 int i=0; public void run() { System.out.println("No."+i++); }}
7 多线程编程的一般规则
① 如果两个或两个以上的线程都修改一个对象,那么把执行修改的方法定义为synchronized,如果对象更新影响到只读方法,那么只读方法也要定义为synchronized;
② 如果一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部。它可以调用一个被synchronized的方法,并让这个方法调用wait();
③ 每当一个方法返回某个对象的锁时,它应当调用notify()/notifyAll()来让等待队列中的其他线程有机会执行;
④ 仔细查看每次调用的wait()方法,使得它都有相应的notify()/notifyAll()方法,使它们均作用于同一对象;
⑤ 针对wait()、notify()/notifyAll()使用旋锁;
⑥ 优先使用notifyAll()而不是notify();
⑦ 按照固定的顺序获得多个对象锁,以避免死锁;
⑧ 不要对上锁的对象改变它的引用;
⑨ 不要滥用同步机制,避免无谓的同步控制。
8 java.util.concurrent中的同步API
通过java.util.concurrent.ThreadPoolExecutor类和其他相关类实现线程池。
① ThreadPoolExcutor(int corePoolSize,int maximum PoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue。
corePoolSize:线程池维护线程的最少数量;
maximumPoolSize:线程池维护线程的最大数量;
keepAliveTime:线程池维护线程所允许的空闲时间;
unit:线程池维护线程所允许的空闲时间单位;
work Queue:线程池所使用的缓冲队列
此外,还可以加这些参数:
RejectedExecutionHandler handler:表示线程池对拒绝任务的处理策略;
ThreadFactory threadFactory:用于创建线程池对象的工厂;
还可以使用execute()方法,它接收一个Runnable类型的参数,表示往线程池中添加一个在将来某个时刻执行的任务,任务将会被放入任务缓冲队列workerQueue中。
处理线程的优先级为:核心线程corePoolSize,任务队列workQueue,最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。当线程池中的线程数量大于corePoolSize时,如果某线程的空闲时间超过keepAliveTime,线程将被中止,这样线程池可以动态调整池中的线程数。
参考资料:http://825635381.iteye.com/blog/2184680
wait notify机制与synchronized关键字相比的好处:避免轮询带来的性能损失。
public class ThreadPoolTask implements Runnable { private String taskName; ThreadPoolTask(String taskName) { this.taskName=taskName; } public void run(){ System.out.println("执行任务:"+taskName); try { //执行需要处理的任务,此处用休眠一段时间来模拟 Thread.sleep(1000); } catch(Excetpion e) { e.printStackTrace(); } } public String getTaskName() { return this.taskName; }}
import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class PoolTest { private static final int TASK_SLEEP_TIME=2; private static final int TASK_MAX_NUMBER=10; public static void main(String[] args) { //构造一个线程池 ThreadPoolExecutor threadPool=new ThreadPoolExcutor(2,4,2,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable> 3,new ThreadPoolExecutor.CallerRunspolicy()); for(int i=1;i<=TASK_MAX_NUMBER;I++) { try { //产生一个任务,并将其加入到线程池 String task="任务# "+i; System.out.println("往线程池放入任务:"+task); threadPool.execute(new ThreadPoolTask(task)); //为便于观察,等待一段时间 Thread.sleep(TASK_SLEEP_TIME); //获得线程池中的活动线程数 System.out.println("线程池中的线程数:"+threadPool.getActiveCount()); } catch(Exception e) { e.printStackTrace(); } } threadPool.shutdown();//关闭线程池,释放资源,必不可少。 }}
分析:
在创建的线程池中,至少有2个线程在等待提供服务,如果积压的任务过多,多到任务列表放不下(超过3个)的时候,就创建新的线程来处理,但是线程池中的总数不能多于4个。
如果4个线程都在处理任务,有新的任务添加时,任务就会通过程序中之地当的策略来处理,这里的处理方式是不停地重新派发,直到线程接受这个任务为止。
如果线程池中的线程比较空闲,在3秒钟都没有新的任务需要处理,有的线程就会被销毁,但是线程池中的线程数量至少要保留2个。
JDK5中,针对资源的锁进行了增强,相关类位于java.util.concurret.locks包中。有三个主要的接口:Condition,Lock,Read Lock。
Condition的作用在于线程之间进行沟通,告知线程目前的情况,它的主要方法await(),signal()和signalAll()和Object的wait(),notify(),notifyAll()类似。
Lock和ReadLock的作用和synchronized类似。Lock有三个主要方法Llock(),unlock(),和newCondition()。lock()方法用于获得对共享对象的锁定,unlock()用于接触对对象的锁定,通常用同一个Lock对象来调用lock()和unlock()方法,newConditon()用于创建一个和Lock对象关联的Condition对象。
private ReadWriteLock mylock;//其他代码myLock.writeLock().lock();//其他代码myLock.writeLock().unlock();
- Android开发入门(二)——基本语法2
- Android开发入门(一)——基本语法
- Android App开发从零开始之入门篇(二)—Android Studio的基本使用
- Kotlin开发之旅《二》—Kotlin的基本语法
- Kotlin与Android的奇妙之旅——基本语法(二)
- javaScript入门(二)-javaScript 基本语法结构
- Android开发入门(二)
- Swift入门(一)——基本语法
- shell入门二:基本语法介绍
- kotlin入门系列二---基本语法
- PHP基本语法(二)—— 运算符
- 从零开始学C#——基本语法(二)
- Python基础(二)——基本语法
- Java基础语法(二)—基本数据类型转换
- Freemarker学习笔记二—基本语法
- Kotlin 开发Android :基本语法
- angular的基本语法——打破高难度入门观念 (基础篇2)
- java开发基本语法——总结
- 如何将数据序列化为XML格式
- 【贪心】洛谷 P1007 独木桥
- 获取项目文件目录下的所有文件路径
- Spring整合JUnit4测试使用注解引入多个配置文件
- Where条件的in里面放太多数据导致很慢
- Android开发入门(二)——基本语法2
- ADO.Net ExecuteScalar、ExecuteReader不只是我想的这样的用处。
- 深入浅出讲述提升 WordPress 性能的九大秘笈
- 愚蠢的高级
- 我纠结的sql返回受影响行数与判断是否成功的问题
- .net对于Xml的常规操作
- 一行排奇数列的HTML排版
- css Hover的巧用
- 修改myeclipse发布的项目名称