Java学习 Part.2

18 异常

//本质上仍是一个lang包中的类
有些异常可以用if(){}else{}处理(当进行除法运算时,除数为0),但有些异常必须用try{}catch(){}处理(当输入int类型时,输入了char类型)。
(1) 编译时的问题,叫错误error;运行时的问题,叫异常exception。
error是系统错误,程序员无法处理这些异常;
exception是程序员必须捕获并处理的异常;
RuntimeException的子类异常是你可以处理也可以不处理的异常。
凡是继承自Exception但又不是RunTimeException子类的异常我们都必须捕获并进行处理。
异常是程序运行过程中发生的事件,该事件可以中断程序指令的正常执行流程。
***(2) 例:

    int i;//应改为int i=null;,进行初始化。
    try{
        i=2;//可能出错的语句:默认有可能执行失败,若失败,i则为垃圾值,所以必须提前给i赋值。无论该语句是否出错,该语句都不能初始化i。
    }
    catch(Exception e){//e用来接收抛出的异常对象
        对异常的处理;
    }
    System.Out.printf("%d",i);//若不给i赋值会显示错误:i可能尚未初始化。

(3) 异常机制
当程序运行时出现错误时,系统会自动检测到该错误,并立即生成一个与该错误相对应的异常对象。
然后把异常对象提交给Java虚拟机
Java虚拟机会自动寻找相应的处理代码来处理这个异常,如果没有被找到,则由java虚拟机做一些简单的处理后,即打印错误信息,程序被强行终止。
程序员可以自己编写代码来捕捉可能出现的异常,并编写代码来处理相应的异常。
***(4) 异常的分类
Throwable有连个子类:Error和Exception;Exception有多个子类其中一个为RuntimeException
error是系统错误,程序员无法处理这些错误。
Exception是程序员可以捕获并处理的异常。
RuntimeException的子类异常是可以处理也可以不处理的异常。
凡是继承自Exception但又不是RuntimeException子类的异常程序员都必须进行处理。
*(5) printStackTrace()
作用:打印出异常信息和出错路径

    try{
        可能出错的语句;
    }
    catch(Exception e){
        e.printStackTrace();
    }

(6) 常见指针异常
空指针异常:TestNullPointException,调用一个空引用中的成员。
下标越界异常:ArrayIndexOutOfBoundsException,使用数组的元素时,数组下标超出允许的范围。
算术异常:ArithmeticException,被除数不能为零。为RuntimeException的子类异常,可处理可不处理。 
(7) 异常的优缺点
优点:增加程序的安全性和健壮性;增强了程序的可控性;有利于代码的调试;把错误代码从常规代码中分离出来。
缺点:异常并不一定能使程序逻辑更清晰;异常并不能解决所有问题。

 

19 finally

(1)例:

    try{
        可能出现异常的代码;
    }
    catch(ExceptionName1 e)//可能三个都捕捉不到,但若捕捉到,则只有一个捕捉到。
    {
        当产生ExceptionName1异常时的处理;
    }
    catch(ExceptionName2 e)
    {
        当产生ExceptionName2异常时的处理;
    }
    catch(ExceptionName3 e)
    {
        当产生ExceptionName3异常时的处理;
    }
    ......
    finally{
        无论是否捕捉到都必须处理的代码;
    }

*(2) 无论try所指定的程序块中是否抛出异常,也无论catch语句的异常类型是否与所抛弃的异常的类型一致,finally中的代码一定会得到执行。
(3) finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其他部分以前,能够对程序的状态作统一的管理。
(4) 通常在finally语句中可以进行资源的清除工作,如关闭打开的文件、删除临时文件等。

 

20 throw

*(1) throw用来程序员手动抛出异常,手动抛出的异常必须伴随处理语句,否则即使语句没有出错编译时也会报错。
(2) 格式:throw new 异常名(参数);
*要抛出的异常必须是throwable的子类,所有的异常都是throwable的子类,除非自定义方法继承throwable类。
*(3) 处理throw抛出异常的两种方式
假设f方法抛出了Exception异常,则f方法有两种方式来处理Exception异常
try catch语句处理或throws交给被调用者处理(main方法若throws则给java虚拟机处理)。
    void f() throws Exception{......}:谁调用f方法,谁处理Exception异常,f方法本身不处理Exception异常。
    try{......}catch(){......}:f方法本身自己来处理Exception异常。

 

21 自定义异常

//一般用不到
(1) 定义一个类继承Throwable或Exception//一般选择选择Exception,若选择Throwable,无法把自定义异常对象赋值给Exception引用,因为Throwable是Exception的父类。
(2) 定义一个接受形参的构造方法
(3) 写一条super(形参);
定义:

    class DivisorIsZeroException extends Exception
    {
        public DivisorIsZeroException(String errorMessage)//errorMessage代表错误信息
        {
            super(errorMessage);//调用Exception类的构造方法。
        }
    }

使用:
throw new DivisorIsZeroException("除数不能为零!");
处理:
throws DivisorIsZeroException
try{}catch(Exception e){}

 

22 throws

(1) 使用:

    void f() throws Exception{//是一个抛出声明而非抛出对象,throws后面的是一个类,throw后面的是new一个对象。
        ......
    }

throws Exception表示调用f方法时f方法可能抛出Exception类异常,建议调用f方法的方法最好对f方法可能抛出的Exception类异常进行捕捉。
throws Exception不表示f方法一定会抛出Exception类异常。throws Exception,f方法也可以不抛出Exception异常。
throws Exception不表示调用f方法时,必须对Exception类异常进行捕捉处理。若该异常为RunTimeException子类异常,可不处理。
***(2) 建议:如果一个方法内部没有throw异常或已对异常进行处理,就不要再throws了;
若方法有throws语句,则对throws出的所有异常进行处理,无论是不是RunTimeException子类异常。
(3) 我的想法:throw语句伴随一定要处理语句(除非是RunTimeException子类异常),否则即使语句没有出错编译时也会报错,通过两种方式:throws或try{}catch(){}。
有throw可以使用throws,有throws不一定有throw,也不一定要处理(除非是RunTimeException子类异常),
但建议:有throws一定要有throw,一定要进行处理(通过try{}catch(){}),论是不是RunTimeException子类异常。
**(4) 标准写法:方法体写throw new Exception();
->方法签名写void f()throws Exception{......}
->调用f方法的方法写try{a.f()}catch(Exception e){......};
***(5) 若该子类继承了父类,throws之前先看看父类抛出异常范围是否大于子类抛出异常范围。

 

23 异常注意问题

//try{}中定义的局部变量只能在try{}中使用。try{}中定义的局部变量也不能在catch(){}和finally{}中使用。
//try中的代码块能访问该代码块之前的代码。
//若要访问try中代码块所包含的变量,必须提前定义好该变量,使其初始化为NULL。
(1) 所有的catch只能有一个被执行,有可能所有的catch都没有执行。
(2) 先catch子类异常再catch父类异常,如果先catch父类异常再catch子类异常,则编译时会报错。
//异常捕获范围从小到大,不可颠倒。

    try{
        可能出现异常的代码;
    }
    catch(子类异常 e)
    {
        当产生子类异常时的处理;
    }
    catch(父类异常 e)
    {
        当产生父类异常时的处理;
    }

原因:父类异常包括子类异常,若先catch父类异常,子类异常就就不会被执行。所以catch异常必须按从小到大的顺序来catch。
(3) catch和catch之间是不能有其他代码的。
***(4) 重写方法抛出异常的范围不能大于被重写方法的异常范围,即父类抛出异常范围必须大于子类抛出异常范围。

    class M{
        void f() throws A,B{
    
        }
    }
    class N extends M{
        void f() throws A,B{//可以throwsA或B,也可以throwsA和B,也可以不throws,总之不能超过父类。
    
        }
    }

原因:子类从父类中继承的方法抛出的异常若超过父类,则难以实现多态。
//实现多态时,只需复制抛出父类所抛出的异常代码,若子类抛出的异常多于父类,则统一代码难以确定抛出的异常个数。

 

24 toString()

*(1) Object类是lang包中的方法,所有的类都默认自动继承了Object类,toString()是Object类中的一个public return String的方法。
(2) Object类中的toString()方法返回的是类的名字和该对象哈希码组成的一个字符串。输出内容:类名@类对象内存地址(十六进制)
*(3)System.out.println(类对象名.toString()/new 类名().toString()/new 类名());实际输出的是该对象toString()方法所返回的字符串。
(System.out.printf("%s\n",类对象名.toString()/类对象名);可达到同样效果)
*(4) 为了实际需要,建议子类重写从父类Object继承的ToString()方法。
重写后System.out.println(类对象名.toString()/类对象名/new 类名().toString()/new 类名());和System.out.printf("%s\n",类对象名.toString()/类对象名);会成为重写后的效果。
例:

    public String toString(){
        return "哈哈哈";
        //不能system.out.printf(),因为重写的函数返回值为string。
    }

重写以后,以上三句输出值为“哈哈哈”。

 

25 equals()

(1) 所有类都从Object类中继承了equals方法。
(2) Object类中的equals方法源代码如下:

    public boolean equals(Object obj){//object是所有类的父类,所有对象都可以接受。
        return this==obj;//如果当前引用地址与obj地址一样,返回true。
    }

使用:system.out.println(a1.equals.(a2));
输出:false/true
(3) Object中的equals方法是直接判断this和obj本身的值是否相等,即用来判断调用equals的对象和形参obj所引用的对象是否是同一对象。
所谓同一对象就是指内存中同一块存储单元,如果this和obj指向的是同一块内存对象,则返回true,如果this和obj指向的不是同一块内存,则返回false。
注意:即便是内容完全相等的两块不同的内存对象,也会返回false。
(4) 如果是同一块内存,则Object中的equals方法返回true,如果是不同的内存,则返回false。
(5) 何时需要重写equals方法:如果希望不同内存但相同内容的两个对象equals时返回true,则我们需要重写父类的equals方法。

    class A{
        public int i;
        pubilc A(int i){
            this.i=i;
        }
        //重写equals方法:
        public boolean equals(Object obj){//main方法将为子类对象的实参发送给obj,所以obj此时指向子类对象。
            A aa=(A)obj;//*****obj为父类引用,不能调用子类中特有的成员i,所以强制转换成子类引用,来调用i。
            if(this.i==aa.i && this.j=aa.j)//当前对象的i与obj代表的i相等
                return true;
            else
                return false;
            或
            return this.i==aa.i && this.j=aa.j;
        }
    }

(6) a1、a2同为A的类对象,system.out.println(a1==a2);system.out.println(a1.equals(a2));都会输出false。
因为二者比较的是引用中的对象内存地址。

 

26 String类中的equals()、==、字符串常量

(1) 我们使用java.lang包下的类时,是不需要import类的,默认导入,包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类。
(2) String类中的equals()用法

    String str1="abc";
    String str2="abc";
    system.out.println(str1.equals(str2));

    运行结果:true
不同地址但内容相同返回true,表示String类中的equals重写了,String类中的equals()用来判断引用指向的对象的内容是否相等。
(3) String类中的str1==str2用法

    String str1=new String("abc");//两个abc在堆中的两块空间
    String str2=new String("abc");//引用在栈分配空间,指向数据常量在数据区分配的空间(若两个字符串常量相同,则不在分配空间)。若为new()出的对象,则指向对象在堆中分配空间(若两个字符串常量相同,仍再次分配空间)。
    if(str1==str2)
        System.out.println("相等");
    else
        System.out.println("不相等");

    运行结果:不相等
不同地址但内容相同输出不相等,表示str1==str2比较的是两个引用自身的内容(地址)是否相等。
(4)字符串常量

    String str1="abc";
    String str2="abc";//两个abc是数据区的同一块内存空间,同时被str1和str2指向。
    if(str1==str2)
        System.out.println("相等");
    else
        System.out.println("不相等");

    运行结果:相等
引用在栈中分配空间,对象在堆中分配空间,引用指向对象。
字符串常量在数据区分配空间,字符串引用指向字符串常量,两个相同的字符串常量只分配一次空间,两个字符串引用指向同一块字符串常量所在的空间。
//如果==两边是的对象引用,则==比较的是地址;若两边是基本数据类型的常量,则==比较的是内容。

 

27 String类常用方法//lang包中的类

(1) 使用:

//"abc"本质上仍是一个类对象,所以"abc"也可调用string中的方法,例:"abc".方法名(参数);
    String s;
    system.out.println("s.方法名(参数)");、system.out.print("%s或%b",s.方法名(参数));//返回值为String或Boolean
    String str=s.方法名;//返回值为String
    if(s.方法名){}//返回值为Boolean

嵌套使用:
    s.方法名(s.方法名(参数))
(2) 常用String方法
***//以下方法均不修改原字符串
返回字符串中第index个字符(index代表的是下标)
    public char charAt(int index)
返回字符串的长度
    public int length()
返回字符串中出现(第一个)str的第一个位置(从0下标开始数的位置;若没有返回-1)
    public int indexof(string str)
返回字符串中从fromIndex开始出现(第一个)str的第一个位置(从0下标开始数的位置;若没有返回-1)
    public int indexof(string str,int fromIndex)
比较字符串与another是否一样,忽略大小写(一样返回true,不一样返回false)
    public boolean equalsIgnoreCase(string another)
在字符串中用newchar字符替换oldchar字符 
    public string replace(char oldChar,char newChar)
判断字符串是否以prefix字符串开头
    public boolean startsWith(string prefix)
判断字符串是否以prefix字符串结尾
    public boolean endsWith(string suffix)
返回一个字符串为该字符串的大写形式
    public string toUpperCase()
返回一个字符串为该字符串的小写形式
    public string toLowerCase()
返回该字符串从beginIndex开始到结尾的子字符串(从0下标开始数的位置)
    public string substring(int beginIndex)
返回该字符串从beginIndex开始到endIndex结尾的子字符串(从beginIndex开始到endIndex之前停止)
    public string substring(int beginIndex,int endIndex)
返回该字符串去掉左边或右边的空格子字符串
    public String trim(string str)
****根据给定正则表达式的匹配拆分此字符串。返回字符串数组,它是根据给定正则表达式的匹配拆分此字符串确定的。
    public String[] split(String regex)
(3) 静态重载方法
public static String valueOf(...)可以将基本类型数据转换为字符串:
    public static String valueOf(double d)
    public static String valueOf(int i)
(4) 分隔字符串
方法public String[] split(String regex)可以将一个字符串按照指定的分隔符分隔,返回分隔后的字符串数组。
例: String str = "aaa,bbb,ccc,";
String[] split = str.split(",", 1);
(5) Character//lang包中的类
Character也是lang包之下,无需import。
    public static boolean isLowerCase(char ch)
判断字符是否为小写,返回值为Boolean。
    public static boolean isUpperCase(char ch)
判断字符是否为大写,返回值为Boolean。
以上两个方法,并非String类内的方法或是String类所继承的方法,所以不能用 str.isLowerCase(参数) 的方式访问。
而是Character类内的方法,但由于两者是static,可以用 Character.isLowerCase(参数) 的方式访问。

 

28 输入与输出

*(1) scanner用法:
先导入util包中的scanner类:import java.util.Scanner;
创建一个scanner类对象:Scanner sc = new Scanner(System.in);
通过该对象调用输入方法:int i = sc.nextInt();
关闭该对象:sc.close;
(2) nextLine()方法和 next()方法的区别
/* 建议全部使用nextLine() */
nextLine()能读取空格,next()不能读取空格。
next()方法读取到空白符就结束;nextLine()读取到回车结束也就是“\r”。
(3) printf和println区别:printf不换行,println换行。
(4) printf和println用法:

    System.out.printf("文字");System.out.printf("文字、格式控制符%",变量);
    System.out.println(变量);或System.out.println("文字"+变量);

 

 

29 StringBuffer

//lang包中的类
*(1) String类对象一旦创建就不可更改。
(2) 如果经常对字符串内容进行修改,则使用StringBuffer。
***生成对象:StringBuffer sb=new StringBuffer();
(3) 如果经常对字符串内容进行修改而使用String的话,既耗费空间,又耗费时间。
//修改字符串时,原字符串内容不能被更改。只能通过再单独生成另外的字符串来不断保存变量来得到想获得值。
//这些过程是对用户不可见的。

    String s1="abc",s2="123";
    String s3=s1+s2;//只能单独生成一个新字符串来保存"abc123",原字符串不改变。

删除s1中的"b"//只能生成一个字符串保存"a",再生成一个字符串保存"c",最后生成一个字符串保存两个字符串相连的"ac"。且原有字符串不被修改。
*(4) StringBuffer对象的内容是可以改变的。
(5) 因此String类中没有修改字符串的方法,但是StringBuffer类中却有大量修改字符串的方法。
(6) StringBuffer类的**构造函数**
    public StringBuffer()
创建一个空的没有任何字符的StringBuffer对象
    public StringBuffer(int capacity)
创建一个不带字符,但具有指定初始容量的字符串缓冲区。
    public StringBuffer(String str)
创建一个StringBuffer对象,包含与str对象相同的字符序列
*(7) StringBuffer常用方法
***//是否用变量接收返回值,要看是否关心返回值。若方法作用更侧重与修改而非查询,则可不必用变量接收返回值。
***//以下方法均修改原StringBuffer内容
重载方法public StringBuffer append(...)可以为该StringBuffer对象添加字符序列,返回添加后的该stringBuffer对象引用,例如:
    public StringBuffer append(string str)
    public StringBuffer append(stringBuffer sbuf)
    public StringBuffer append(char[] str)
    public StringBuffer append(char[] str,int offset,int len)
    public StringBuffer append(double d)
    public StringBuffer append(object obj)
    ......
重载方法public StringBuffer insert(...)可以为该StringBuffer对象在指定位置插入字符序列,返回修改后的该stringBuffer对象引用,例如:
    public stringBuffer insert(int offset,string str)
    public stringBuffer insert(int offset,double d)
方法delete()可以删除从start开始到end为止的一段字符序列,返回修改后的该StringBuffer对象引用。
    public StringBuffer delete(int start,int end)
和String类含义类似的方法:
    public int indexOf(string str)
    public int indexOf(string str,int fromIndex)
    public string substring(int start)
    public string substring(int start,int end)
    public int length()
方法reverse()用于将字符序列逆序,返回修改后的该StringBuffer对象引用
    public StringBuffer reverse()
*(8) 将StringBuffer对象赋给String对象
    String str=StrBu.toString();

 

30 数组

(1) 定义
方式1:

    int[] arr;//int[] 表示的是数据类型,意思是整型数组。
    arr=new int[]/int[长度];

    或

    int[] arr=new int[]/int[长度];//arr代表首地址 

方式2:

    int[] arr=new int[]{数据1,数据2,数据3};//若初始化数组,则int[]内不能写长度,因为初始化时的数据数量就代表了长度。

方式3:

    int[] arr={数据1,数据2,数据3};

(2) 赋值

    arr[下标]=值;
    arr=new int[]{数据1,数据2};//arr指向由原对象变成{数据1,数据2}。

(3) 输出数组方法:
一维数组不能通过system.out.println();来输出。//数组是对象,打印时默认调用对象的toString方法,数组的toString方法并没有实现遍历输出。

    public static void showArr(int[] arr)
        {
            for (int i = 0; i<arr.length; ++i)//数组的length即不是方法,也不是字段。Java专门为Array定义了取得长度的指令。 
                System.out.println(arr[i]);
        }    

(4) 二维数组

int[][] arr=new int[2][3];//等长数组
    int[][] arr={{3,2,5}{2,4}{2}};//不等长数组

(5) Arrary、Arrays类

java.lang.Object
    java.lang.reflect.Array

Array、Arrays不是lang包的的类,使用该类方法前要导入import java.lang.reflect.Array
(6) 数组的排序
java.unti.Arrays类中的sort方法可以实现对数组的排序
    public static void sort(Object[] a)
*使用:Arrays.sort(arr);//Arrays类须import
(7) 数组的拷贝
    public static void arraycopy( Object arr1,int pos1,Object arr2,int pos2,int length);
将arr1所指向的数组中下标从pos1开始的总共length个元素覆盖掉arr2所指向的数组中从pos2开始的length个元素。
*使用:System.arraycopy(arr1,1,arr2,1,2);//属于system类
注意:
arr1是源数组arr2是目的数组!
arraycopy全是小写,不能是大写!
***(8) for(int e : arr){}//每次循环将数组中的一个元素赋给e。
等价于 for(int i = 0; i<arr.length; ++i){}
(9) 二维数组长度
    int[][] arr=new int[1][2];
    arr.length 表示二维数组的行数。
    //length是数组对象中的一个属性
    arr[i].length 表示二维数组第i行的列数。

 

31 线程

(1) 进程:它只是一个静态的实体进程是程序在某个数据集上的执行。
(2) 线程:线程是一个程序单的不同执行路径。
以前所编写的程序,每个程序都有一个入口、一个出口以及一个顺序执行的序列,在程序执行过程中的任何指定时刻,都只有一个单独的执行点。
事实上,在单个程序内部是可以在同一时刻进行多种运算的,这就是所谓的多线程。
(3)*** 创建一个线程的第一种方法
    创建一个继承Thread的类(假定类名为A),并重写Thread中的run方法。
    构造一个A类对象假定对象名为a。
    调用a的start()方法,start()方法是从Thread继承过来的。
例:

    class A extends Thread//属于lang包
    {
        public void run()//run()继承自thread类,经重写后使用。
        {
            while (true)//死循环
            {
                System.out.println("AAAA");
            }            
        }
    }
    
    public class TestThread_1
    {
        public static void main(String[] args)
        {
            A a = new A();//自此,线程已存在,等待开启。
            a.start(); //start()开启线程。start()继承自thread类,会自动调用run方法。此时该线程处于就绪状态。
            //a.start(); 一个thread对象不能执行两次start()方法。
            while (true)
            {
                System.out.println("BBBB");
            }
        }
    }

输出结果:AAAA与BBBB交错输出。
(4) 注意问题
Thread中start()方法的功能就是创建一个新的线程,并自动调用该线程的run()方法,*直接调用run方法是不会创建一个新的线程的。
例:a.run()无法创建一个新线程,只是调用run()方法。
***执行一个线程实际就是执行该线程run方法中的代码,执行完a.start()后并不表示a所对应的线程就一定会立即得到了执行
a.start()执行完后只是表示a线程具有了可以立即被CPU执行的资格,即线程处于就绪状态,等待执行。但由于想抢占CPU执行的线程很多,***CPU并不一定会立即去执行a所对应的线程。
一个*Thread对象*能且只能代表一个线程。若要再利用该类创建一个线程,则要再创建一个Thread对象来调用start()方法。
一个*Thread对象*不能调用两次start()方法,否则会抛出java.lang.IllegalThreadStateException异常。
(5)*** 创建一个新线程的第二种方法
    定义一个实现了Runnable接口的类,假定为A。
    创建A类对象a,代码为:A a =new A();
    利用a构造一个Thread对象t,代码为:Thread t = new Thread(a);或 Thread t= new Thread(new A());
    利用t调用t中的start方法,代码为:t.start();
例:

    class A implements Runnable
    {
        public void run()
        {
            while (true)
            {
                System.out.println("AAAA\n");
            }
        }
    }
    
    class B
    {
    }
    
    public class TestThread_2
    {
        public static void main(String[] args)
        {
            A a = new A();//自此,线程已存在,等待开启。
            //a.start();  //A类实现了Runnable接口,Runnable接口中没有start()方法。
            Thread t = new Thread(a);  //Thread类属于Lang包,Thread类中有构造方法public Thread(Runnbale r)。
            //B b = new B();
            //Thread t2 = new Thread(b); //Thread类中的构造方法Thread(Runnbale r)指定形参类型为Runnbale。B类未继承Runnbale类。
    
            t.start();//start()开启线程。Thread t = new Thread(a)将a与t联系起来,实现“当调用t中start()方法时,则调用a中的run()方法”的效果。
            
            while (true)
            {
                System.out.println("BBBB\n");
            }
        }
    }

*(6) Thread的常用方法
设置当前线程的名字
    public final void setName(String name)
    使用:类对象名.setName(String name);
          类对象名.start();//先设置线程名,再开启线程。
返回对当前正在执行的线程对象的*引用*。
    public static Thread currentThread()
    使用:Thread.currentThread();//静态方法通过类名调用。
返回当前线程的名字
    public final String getName()
    使用:类对象名.getName();
返回当前正在执行的线程的名字

    System.out.printf("%s在执行!\n", Thread.currentThread().getName());

 

32 线程的控制

(1) 线程控制的基本方法
判断线程是否还"活"着,即线程是否还未终止。
    isAlive()
获得线程的优先级数值。
    getPriority()
设置线程的优先级数值。
    setPriority()
将当前线程睡眠指定毫秒数。
    Thread.sleep()
调用某线程的该方法,将当前线程与该线程合并,即等待该线程结束,再恢复当前线程的运行。
    join()
让出CPU,当前线程进入就绪队列等待调度。
    yield()
当前线程进入对象的wait pool。
    wait()
唤醒对象的waitpoo1中的一个/所有等待线程。
    notify()/notifyAll()
(2) 线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器*按照线程的优先级决定*应调度哪个线程来执行。
//同优先级近乎随机执行,所以会出现“一个线程执行一段时间,另一个线程执行一段时间”的情况。
***总结:同优先级随机执行,不同优先级比优先级。
线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。主线程的缺省优先级是5,子线程的优先级默认与其父线程相同。
    public static final int MIN_PRIORITY
    Thread.MIN_PRIORITY 代表 1//satic方法,可直接类名调用。
    public static final int NORM_PRIORITY
    Thread.MAX_PRIORITY 代表 10
    public static final int MAX_PRIORITY
    Thread.NORM_PRIORITY 代表 5
使用下述线方法获得或设置线程对象的优先级。
    public final int getPriority()
    public final void setPriority(int newPriority)
例:
    类对象名.setPriority(Thread.NORM_PRIORITY + 3);//表示将该引用代表的线程优先级设为5+3=8
通常高优先级的线程将先于优先级低的线程执行,但**并不总是这样**,因此实际开发中不可依赖优先级来决定线程运行次序。
(3) 线程的休眠
暂停执行当前运行中的线程,使之进入**阻塞状态**,待经过指定的“延迟时间”后再醒来并转入到就绪状态。
//阻塞状态转换为就绪状态也需要时间。就绪之后不一定执行,需等待调度。
Thread类提供的相关方法:
    public static void sleep(long millis)//毫秒,该时间单位对一条语句来说已经很庞大了。
    public static void sleep(long millis, int nanos)//纳秒
由于是静态方法,可以由Thread直接调用
例:

    try{
        Thread.sleep(1);//static方法
    }
    catch(lnterruptedException e){
        对错误的处理;
    }

sleep()方法会抛出lnterruptedException异常,我们必须得对其进行捕捉。
***无论是继承Thread类的run方法还是实现Runnable接口的run方法,都不能抛出任何异常,只能try{}catch(){}处理。
原因:重写方法抛出异常的范围不能大于被重写方法(Thread)抛出的异常范围。
(4) 线程的让步
让出CPU给其他线程执行的机会,让**运行中的**线程主动放弃当前获得的CPU处理机会,但不是使该线程阻塞,而是使之转入**就绪状态**
    public static void yield()
(5) 线程的串行化
在多线程程序中,如果在一个线程运行的过程中要用到另一个线程的运行结果,则可进行线程的串型化处理。
    public final void join() throws InterruptedException
    例:

        public class TestJoin {    
            public static void main(String args[]){
                MyRunner r = new MyRunner();
                Thread t = new Thread(r);
                t.start();
                try{
                    t.join(); //7行  暂停当前***正在执行t.join();的线程***,直到t所对应的线程运行终止之后,当前线程才会获得继续执行的机会
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                for(int i=0;i<50;i++){
                    System.out.println("主线程:" + i);
                }
            }
        }
        
        class MyRunner implements Runnable {
            public void run() {
                for(int i=0;i<50;i++) {
                    System.out.println("子线程: " + i);
                }
            }
        }

(6) 线程的终止(已过时,了解即可)

    class A implements Runnable
    {
        private boolean flag = true;//flag默认为为真。
        
        public void run()//flag默认为为真。
        {
            while (flag)//当flag为真时,默认执行。
            {
                System.out.println("AAAA");//缺点:当当flag为真时,则为一个死循环语句。
                  
            }
        }
        
        public void shutDown()//调用shutDown()方法,则使flag为假,从而终止执行。
        {
            this.flag = false;
        }
    }

(7) 线程的挂起和恢复
线程挂起:暂时停止当前运行中的线程,使之转入阻塞状态,并且不会自动恢复运行。
线程恢复:使得一个已挂起的线程恢复运行。
Thread类提供的相关方法:
    public final void suspend()
    public final void resume()
suspend方法挂起线程时并不释放其锁定的资源。这可能会影响其他线程的运行,且容易导致线程的死锁, 已*不提倡使用*
**(8) 线程的同步
买票程序:TestTickets(错误案例)
错误:同一张票会卖两次,且买票顺序有误。
线程可以在任意一点切入,所以,0线程在执行中切换到了1线程,但0的进程立即终止,执行完1线程后,再继续执行0线程剩余语句,打乱了原有的语句顺序。
原因:进程切换,使用同一段代码,操作相同的资源
用创建线程的第一种方式来买票:TestTickets_3
用创建线程的第二种方式来买票:TestTickets_2、TestTickets_9

 

33 Synchronized关键字

(1) synchronized可以用来修饰一个方法或一个方法内部的某个代码块。
*(2) Synchronized修饰代码块
格式:

    synchronized(类对象名a)
    {
        同步代码块
    }

功能:
***synchronized(类对象名a)的含义是:判断a是否已经被其他线程霸占,如果发现已经被其他线程霸占,则当前线程陷入等待中。
//必须保证多个线程竞争的是同一个对象。
//static或者创建进程的第二种方式用同一个对象生成多个线程。
如果发现a没有被其他线程霸占,则当前线程霸占住a对象,并执行第3行的同步代码块。
在当前线程执行第3行代码时,其他线程将无法再执行第3行的代码,因为当前线程已经霸占a对象。
当前线程执行完第3行的代码后,会自动释放对a对象的霸占,此时其他线程会相互竞争对a的霸占,最终CPU会选择其中的某一个线程执行。
***最终导致的结果是:*一个线程正在操作某资源的时候,将不允许其它线程操作该资源,即,此时,一次只允许一个线程处理该资源。*
(3) Synchronized修饰方法
Synchronized修饰一个方法时,实际霸占的是该方法的*this指针所指向的对象*,即Synchronized修饰一个方法时,实际霸占的***正在调用该方法的对象***
附注:霸占的专业术语叫锁定,霸占住的那个对象专业术语叫做*监听器*
(4) 我的总结
***若使用Synchronized修饰方法时,必须确保两个线程的监听器是同一个。
可以将监听器作为类中的一个属性,并通过创建线程的第二种方式使两个线程关联同一个对象,来保证使用类中的同一个属性。
还可以,将监听器作为类中的一个属性,并通过创建线程的第一种方式来创建线程,并用static来修饰它,来保证使用类中的同一个属性。
最后,还可以定义一个字符串常量作为局部变量放入run()方法中,利用计算机物理属性,来保证各线程使用同一个监听器。

 

34 生产与消费

***ProducerConsumer.java

 

35 notify和wait方法

(1)功能:
    不是叫醒正在执行this.notify();的当前线程,而是叫醒一个现在正在wait this对象的其他线程。
    //即:notify()只能唤醒 被调用notify的对象调用wait()方法的线程。
    如果有多个线程正在wait this对象,通常是叫醒最先wait this对象的线程。但具体是叫醒哪一个,这是由系统调度器控制,程序员无法控制。
(2) 假设现在有T1、T2、T3、T4四个线程
我们在T4线程中执行了a.notify语句,则即便此时T1 T2 T3没有一个线程因为Wait a对象而陷入阻塞状态,T4线程中执行a.notify方法时也不会有任何错误。
(3) notify和wait方法总结
    a.wait()
将执行a.wait()的当前线程转入阻塞状态,让出CPU的控制权,释放对a的锁定。
    a.notify()
假设执行a.notify()的当前线程为T1,如果当前时刻有其他线程因为执行了a.wait而陷入阻塞状态,则叫醒其中的一个。
所谓叫醒某个线程就是令该线程从因为wai而陷入阻塞的状态转入就绪状态。
    a.notifyAll()
叫醒其他所有的因为执行了a.wait()而陷入阻塞状态的线程。
*(4) wait/notify/notifyAll 都是Object 类中的方法    
***(5)this.wait();不是让当前对象wait,而是让当前锁定this对象的线程wait,同时释放对this的锁定。
如果该对象没有被锁定,则调用wait方法就会报错!即只有在**同步方法**或者**同步代码块**中才可以调用wait方法,notify同理。

 

36 GUI

***进行以下操作时,都需import java.awt.*;
(1) 组件
组件(Component)是图形用户界面的基本组成元素,凡是能够以图形化方式显示在屏幕上并能够与用户进行交互的对象均为组件,如菜单、按钮、标签、文本框、滚动条等。
组件分类

    java.awt.Component
    Java.awt.MenuComponent

 

说明:抽象类java.awt.Component是除菜单相关组件之外所有JavaAWT组件类的根父类。
该类规定了GUI组件的基本特性,如尺寸,位置和颜色效果等,并实现了作为一个GUI部件所应具备的基本功能。
(2) 容器
//此容器非彼容器,是存放组件的容器,而非存放元素的容器。

java.lang.Object
      java.awt.Component
          java.awt.Container

Frame继承了java.awt.Window,Panel未继承。
组件通常不能独立地显示出来,必须将组件放在一定的容器中才可以显示出来。
有一类特殊的组件是专门用来包含其他组件的,这类组件叫做容器。java.awt.container是所有容器的父类。
java.awt.Container继承自java.awt.Component,容器类对象本身也是一个组件,具有组件的所有性质,但反过来组件却不一定是容器。
(3) Frame
*Frame的构造方法为:
    Frame() 
    Frame(String title) 给Frame添加标题。
*Frame的常用方法:
    public void setBounds(int x,int y,int width, int height)
设置窗体的位置和大小,x和y表示窗体左上角距离屏幕的水平和垂直距离,width和height是窗体本身的宽度和高度。
    public void setSize(int width, int height)
设置窗体的大小,width和heiqht是窗体本身的宽度和高度。
    public void setVisible(boolean flag)
设置窗体是否可见,true表示可见,false表示不可见。
    public void setBackground(Color c)//以上四类均为Component类中的方法。
把当前组件对象添加到当前容器对象中
    public Component add(Component comp)//java.awt.Container类中的一个方法
调整此窗口的大小,以适合其子组件的首选大小和布局。
    public void pack()//类 java.awt.Window 中的方法,只能在有布局管理器时使用。panel无该方法。
设置窗体的背景色
***使一个容器可见:创建对象,设置布局管理器,调整大小,置为可见。
    ***先定义Frame f=new Frame("容器名");对象,再使用setLayout()语句,使用 f.setsize("宽,高")或 f.pack(),最后f.setVisible(true)。
(4) Panel
Panel是容纳其他组件的组件,panel是容器,***Panel不能单独存在,必须得被添加到其他容器中,因此可实现**嵌套**。
*Panel的构造方法为:
    Panel()使用默认的FlowLayout类布局管理器初始化。
    Panel(LayoutManagerlayout)使用指定的布局管理器初始化。
*Panel的常用方法:
    setBounds(int x,int y,int width,int height)//包含以下两个方法的功能。
    setSize(int width,int height)
    setLocation(int x,int y)
    setBackground(Color c)//以上四类均为Component类中的方法。
    setBackground("red"/ Color.RED)//静态方法
    setLayout(LayoutManager mgr)//该该方法为Container中的方法。
    setLayout(布局管理器对象名);//静态方法
    setLayout(new 布局管理器对象名());//静态方法
    add(Component comp)
(5) Button
Button的构造方法为:
    Button() 构造一个标签字符串为空的按钮。
    Button(String label) 构造一个带指定标签的按钮。
(6) TextField

java.lang.Object
  java.awt.Component
      java.awt.TextComponent
          java.awt.TextField

TextField的构造方法为:
    public TextField() 构造新文本字段。
    public TextField(String text)构造使用指定文本初始化的新文本字段。
    public TextField(int columns)构造具有指定列数的新空文本字段。
    public TextField(String text,int columns)构造使用要显示的指定文本初始化的新文本字段,宽度足够容纳指定列数。
Frame的常用方法:
将此文本组件显示的文本设置为指定文本。 
    public void setText(String t)
返回此文本组件表示的文本。
    public String getText()//继承自java.awt.TextComponent
(7) Label
Label的构造方法
    public Label()构造一个空标签。
    public Label(String text)使用指定的文本字符串构造一个新的标签,其文本对齐方式为左对齐。

 

37 布局管理器

(1) 容器对其中所包含组件的排列方式,包括组件的位置和大小设定,被称为容器的布局(Layout)。
为了使图形用户界面具有良好的平台无关性,Java语言提供布局管理器来管理容器的布局,而不建议直接设置组件在容器中的位置和尺寸。
每个容器都有一个默认的布局管理器,当容器需要对某个组件进行定位或判断其大小尺寸时,就会自动调用其对应的布局管理器。
(2) 常见的布局管理器:
    BorderLayout
    FlowLayout
    GridLayout
    ***修改:容器对象名.setLayout(new 布局管理器名());
    ***或 容器对象名.setLayout(布局管理器对象名);
(3) FlowLayout布局管理器 
*FlowLayout是Panel类的默认布局管理器。
FlowLayout布局管理器对组件逐行定位,行内从左到右,一行排满后换行。
不改变组件的大小,按组件原有尺寸显示组件,可设置不同的组件间距行距以及对齐方式。
FlowLayout布局管理器默认的对齐方式是居中。
***FlowLayout的构造方法为:
    FlowLayout() 构造一个新的 FlowLayout,它是居中对齐的,默认的水平和垂直间隙是 5 个单位。 
    FlowLayout(int align) 构造一个新的 FlowLayout,它具有指定的对齐方式,默认的水平和垂直间隙是 5 个单位。 
    FlowLayout(int align, int hgap, int vgap) 创建一个新的流布局管理器,它具有指定的对齐方式以及指定的水平和垂直间隙。 
例:
使用缺省的居中对齐方式,水平和垂直间距为缺省值5。
    new FlowLayout();
左对齐,水平和垂直间距为缺省值5。
    new FlowLayout(FlowLayout.LEFT);
右对齐,组件之间水平间距20个像素,垂直间距40个像素。
    new FlowLayout(FlowLayout.RIGHT,20,40);
(4) BorderLayout布局管理器
*BorderLayout是Frame类的默认布局管理器。
//郝斌记法:F(Frame)B(BorderLayout)I
BorderLayout将整个容器的布局划分成东(EAST)西(WEST)南(SOUTH)北(NORTH)中(CENTER)五个区域,组件只能被添加到指定的区域。
    ***容器对象名.add(组件对象名, BorderLayout.EAST/WEST/SOUTH/NORTH);
如不指定组件的加入部位,则默认加入到CENTER区。
每个区域只能加入一个组件,如加入多个,则先前加入的会**被覆盖**
BorderLayout型布局容器尺寸缩放原则:
北、南两个区域在水平方向缩放,东、西两个区域在垂直方向缩放,中部可在两个方向上缩放。
***BorderLayout的构造方法为:
    BorderLayout() 构造一个组件之间没有间距的新边框布局。 
    BorderLayout(int hgap, int vgap) 构造一个具有指定组件间距的边框布局。 
(5) GridLayout布局管理器
GridLayout型布局管理器将空间划分成规则的矩形网格,每个单元格区域大小相等。组件被添加到每个单元格中,先从左到右添满一行后换行,再从上到下。
***GridLayout的构造方法为:
    GridLayout() 创建具有默认值的网格布局,即每个组件占据一行一列。 
    GridLayout(int rows, int cols) 创建具有指定行数和列数的网格布局。 
    GridLayout(int rows, int cols, int hgap, int vgap) 创建具有指定行数和列数、行列间距的网格布局。 
在 GridLavout构造方法中指定分割的行数和列数:
例:
    GridLayout(3,4),必定为三行,由计算机计算共三行的基础上分几列。
***GridLayout是以行数为准的。
(6) 布局管理器总结
Frame是一个顶级窗口,Frame的缺省布局管理器为BorderLayout。
*Panel无法单独显示,必须添加到某个容器中,Panel的缺省布局管理器为FlowLayout。
当把Panel作为一个组件添加到某个容器中后,该***Panel仍然可以有自己的布局管理器***
使用布局管理器时,布局管理器负责各个组件的大小和位置,因此用户无法在这种情况下设置组件大小和位置属性。
如果试图使用java语言提供的setLocation(),setSize(),setBounds()等方法,则都会被布局管理器覆盖。
***如果用户确实需要亲自设置组件大小或位置,则应取消该容器的布局管理器,方法为:
    setLayout(null);//静态方法

 

38 事务处理

***进行以下操作时,都需import java.awt.event.*;;
(1) 事件处理相关概念
事件(Event):用户对组件的一个操作,称之为一个事件。
事件源(EventSource):能够产生事件的GUI组件对象,如按钮、文本框等。
事件处理方法(EventHandler):能够接收、解析和处理事件类对象,实现与用户交互功能的方法。
事件监听器(EventListener):可以处理事件的一个类。
(2) 默认情况下事件源不会自动产生任何事件,程序员需要做两件事:
**告诉事件源可以自动产生哪类事件,即,向事件源注册某种事件的事件监听器对象。//组件名.addXXXXListener(....);
**设计好可以处理这种事件的事件监听器。//class 类名 implements XXXXListener(XXXXEvent e){}
*一旦完成了这两步操作,当用户对事件源进行操作时,事件源就会自动产生事件,事件源就会自动把产生的事件封装成一个事件对象,事件源就会自动把封装好的事件对象传递给事件监听器。
*事件监听器收到事件源发送过来的事件时,事件监听器就会自动调用相应的事件处理方法来对该事件进行相应的处理。
//必须要明白哪些操作是编译器自动完成的,那些操作是程序员手动完成的。
(3) 事件处理步骤
假设事件为XXXX
***向事件源注册某种事件的事件监听器对象 addXXXXListener(....);//....为实现该监听器方法的对象。
***设计好可以处理这种事件的事件监听器 

    class 类名 implements XXXXListener(XXXXEvent e){//事件被封装成一个事件对象,传递给监听器。
        重写XXXXListener接口中的方法;
    }

说明:
要想设计出能够处理XXXX事件的监听器,只需要编写出实现了XXXXListener接口的类就OK了,因为XXXXListener接口中已经定义了可以处理XXXX事件的方法。
(4) 事件有哪些?
java.awt.event包中含有所有的事件,常用的事件有:
    ActionEvent:激活组件时发生的事件
    KeyEvent:操作键盘时发生
    MouseEvent:操作鼠标时发生
    WindowEvent:操作窗口时发生的事件,如最大化或最小化某一窗口。
    ***(大多使用WindowAdapter,因为使用WindowEvent需要重写其中许多抽象方法。)
一个事件源可以自动产哪些事件?
第三方IDE软件会自动显示,不需要记忆!
(5) 我的疑问
当一个组件添加多个监听器时,组件如何分辨操作对应的时哪一个监听器?
组件会根据操作的类型,比如键盘操作或鼠标操作,来选择使用那个监听器。
当一个监听器内有多个方法时,监听器如何选择对应的方法?
监听器会根据得到的事件对象中的内容,比如关闭、最小化、打开等,来选择对应的方法。

 

39 内部类和匿名类

(1) 内部类
//所有方法外部
*定义:在A类的内部但是所有方法的外部定义了一个B类,则B类就是A类的内部类,A是B的外部类。
***原则:内部类的方法可以访问外部类所有的成员,外部类的方法不可以直接访问内部类的成员。
优点:可以让一个类方便的访问另一个类中的所有成员。增加程序的安全性,有效避免其他不相关类对该类的访问。
***何时使用:如果一个A类要使用B类的所有成员,并且A类不需要被除B类以外的其他类访问,则我们应当把A类定义为B类的内部类。
***本质:我们可以把内部类当做外部类的一个成员。
//内部类不可直接生成对象。几乎也不存在这种问题。
(2) 匿名类
//某方法内部
匿名类是一种特殊的内部类。
如果在一个方法内部定义了一个匿名类,则该匿名类可以访问外部类的所有成员,包裹该匿名类的方法中的所有final类型的局部变量。
***注意:非fianl类型的局部变量无法被名类访问
***创建匿名类的三种方式:
*假设A是接口名
格式:

    new A(){
        实现接口中方法的代码;
    }

功能:生成一个实现了A接口的匿名类对象。
*假设A是抽象类
格式:

    new A(){
        实现了A类的所有抽象类的方法代码;
        添加自己的方法或属性代码;【不建议,因为没有实际意义】
    }

功能:生成一个匿名类,该匿名类必须得实现了A类的所有抽象方法,当然该匿名类也可以定义自己的属性和方法。
*假设A是个类名
格式:

    new A(){
        重写了A类的方法代码;
        添加自己的属性和方法;【不建议,因为没有实际意义】
    }

*功能:生成一个A类的子类对象,该匿名类对象继承了A的所有非private成员。
匿名类的优缺点:
如果一个类的语句比较少,逻辑比较简单,而且不经常变动,这个时侯可以使用匿名类!
如果一个类包含了很重要的逻辑,将来要经常修改,则这个类就不应该当做匿名类来使用,诺名类会导致代码的混乱。

 

posted @ 2023-02-07 19:39  10kcheung  阅读(24)  评论(0)    收藏  举报