JavaSE模块四笔记

第一阶段——模块四

一、异常(重点)

1. 自定义异常类

    1. 继承Exception或者RunTimeException类
    1. 实现有参构造和无参构造
public class AgeException extends Exception {

    static final long serialVersionUID = -3387516993124229948L;

    public AgeException() {
    }

    public AgeException(String message) {
        super(message);
    }
}

2. 自定义类的使用

  • 直接在方法内部处理异常
public class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        setName(name);
        setAge(age);
    }

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }
	// 在这里直接处理异常
    public void setAge(int age) {
        if (age > 0 && age <= 120) {
            this.age = age;
        }else {
            try {
                throw new AgeException("年龄不合理哦");
            } catch (AgeException e) {
                e.printStackTrace();
            }
        }
    }
public static void main(String[] args) {
    Person p = new Person("张三", 130);// 如果异常直接在setAge方法里面处理了,那么这里即使年龄不合理,还是可以构造对象的。
    System.out.println(p);
}
  • 向外抛出异常
public class Person {

    private String name;
    private int age;
	// 抛出异常
    public Person(String name, int age) throws AgeException {
        setName(name);
        setAge(age);
    }

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }
	// 抛出异常
    public void setAge(int age) throws AgeException {
        if (age > 0 && age <= 120) {
            this.age = age;

        }else {
            throw new AgeException("年龄不合理哦");
        }
    }

    public static void main(String[] args) {
        Person p = null;
        try {
            p = new Person("张三", 130);	// 在构造对象的时候处理异常,年龄不正确则无法创建对象,更合理些
        } catch (AgeException e) {
            e.printStackTrace();
        }
        System.out.println(p);
    }

二、File类

1. 概念

用于描述文件或者目录的特征信息

2. 常用方法

方法声明 功能介绍
File(String pathname) 构造方法,传入一个路径
long length() 获取文件大小
String getName() 获取文件/目录名字
boolean exists() 文件/目录是否存在
boolean createNewFile() 创建新文件
boolean delete() 删除文件/目录
boolean mkdir() 创建目录
boolean mkdirs() 创建多级目录
File[] listFiles() 获取该目录下所有内容
Boolean isFile() 判断是否为文件
boolean isDirectory() 判断是否为目录

3. 文件(夹)过滤器

通过listFiles()方法,我们可以获取到一个目录下的所有文件和文件夹,但能不能对其进行过滤呢?比如我们只想要一个目录下的指定扩展名的文件,或者包含某些关键字的文件夹呢?

  • FilenameFilter

listFiles(FilenameFilter filter) 可以传入一个文件名过滤器。该过滤器是一个接口,需要实现其accept方法。

示例代码(已经掌握)

public class FilenameFilterTest {

    public static void main(String[] args) {

        // 正常的获取文件夹下所有文件和目录的方法
        /*File f = new File("d://data");
        final File[] files = f.listFiles();
        for (File file: files) {
            System.out.println(file.getName());
        }*/

        // 使用过滤器筛选出满足条件的文件
        File f = new File("d://data");
        final File[] files = f.listFiles(new MyFilenameFilter());
        for (File file: files) {
            System.out.println(file.getName());
        }
    }
}

public class MyFilenameFilter  implements FilenameFilter {
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(".png");
    }
}
  • Filefilter

三、IO流(重点)

1. 基本分类

  • 按照读写数据的基本单位不同,分为字节流字符流

    字节流可以读写任意类型的文件

    字符流只能读写文本文件,以字符(2个字节)位单位进行数据读写的流。

  • 按照读写数据的方向不同,分为输入流输出流

2. 体系结构

分类 字节输入流 字节输出流 字符(节点)输入流 字符(节点)输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutPutStream FileReader FileWriter
缓冲流 BufferedInputStream BufferedOutPutStream BufferedReader BufferedWriter
转换流 —— —— InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
特殊流 DataInputStream DataOutPutStream —— ——

3. FileWriter

实例代码

public class FileWriterTest {

    public static void main(String[] args) {

        FileWriter fw = null;
        try {
            //fw = new FileWriter("d://a.txt");               // 覆盖的方式写入文件
            fw = new FileWriter("d://a.txt", true); // 追加的方式写入文件
            fw.write("a");                // 写入字符串

            char[] cArr = new char[]{'h', 'e', 'l', 'l', 'o'};
            fw.write(cArr, 1, 3);    // 写入部分字符数组
            System.out.println("写入数据成功!!!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 上面在执行fw = new FileWriter("d://a.txt")时,有可能会产生异常,导致fw最后的结果有可能为空
            // 当fw为空时,执行fw.close()则会报空指针异常,为了避免空指针异常,这里需要先判断。
            if (null != fw){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4. FileReader

示例代码

public class FileReaderTest {

    public static void main(String[] args) {

        FileReader fr = null;
        try {
            fr = new FileReader("d://a.txt");

            // 1.读取单个字符
            /*int res = fr.read();
            System.out.println("读取到的单个字符是:" + (char)res);*/

            // 2.读取所有的字符
            /*int res = 0;
            while ((res = fr.read()) != -1) {
                System.out.println("读取到的单个字符是:" + (char)res);
            }*/

            // 3.读取到字符数组
            // 准备一个字符数组来保存读取到的数据内容
            char[] cArr = new char[5];
            // 期望读满字符数组中的一部分空间,也就是读取3个字符放入数组cArr中下标从1开始的位置上
            int res = fr.read(cArr, 1, 3);
            for (char c: cArr) {
                System.out.println("读取到的单个字符是:" + (char)c);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fr) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5. FileInputStream

6. FileOutputStream

7. read方法读取到缓存数组(理解)

读取到字节数组原理详解

@ override
public int read(byte[] b)throws IOException {
    int getData = read(); 	// 这里调用的是FileInputStream类中的read方法
    if (getData == -1) {
        return -1;
    } else {
        b[0] = getData;
        for (int i = 1; i < b.length(); i++) {
            getData = read();
            if (getData == -1) {
                return i;
            } 
            b[i] = getData;
            }
        }
    return b.length;
    }
}

8. BufferedInputStream

9. BufferedOutPutStream

10. BufferedWriter

11. BufferedReader

缓冲字符流:有缓冲区时,会一次性读取很多数据(默认的缓冲大小1024*8),然后按要求(即调用者是一次读取一个字符,还是读取一个字符数组)交给上层调用者。

12. OutputStreamWriter

字符流转换为字节流

示例代码

public static void writeCN() throws Exception {
		//创建与文件关联的字节输出流对象
		FileOutputStream fos = new FileOutputStream("c:\\cn8.txt");
		//创建可以把字符转成字节的转换流对象,并指定编码
		OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
		//调用转换流,把文字写出去,其实是写到转换流的缓冲区中
		osw.write("你好");//写入缓冲区。
		osw.close();
	}

13. InputStreamReader

字节流转换为字符流

示例代码1:将编码表进行转换

public class InputStreamReaderDemo {
	public static void main(String[] args) throws IOException {
		//演示字节转字符流的转换流
		readCN();
	}
	public static void readCN() throws IOException{
		//创建读取文件的字节流对象
		InputStream in = new FileInputStream("c:\\cn8.txt");
		//创建转换流对象 
		//InputStreamReader isr = new InputStreamReader(in);这样创建对象,会用本地默认码表读取,将会发生错误解码的错误
        InputStreamReader isr = new InputStreamReader(in, "utf-8");

		int ch = 0;
		while((ch = isr.read())!=-1){
			System.out.println((char)ch);
		}
		//关闭流
		isr.close();
	}
}

示例代码2:将键盘标准输入流(InputStream流)转换为字符流

public class FileInputStreamReaderTest1 {

    public static void main(String[] args) {
        
        // 说明:BufferedReader需要传入的是Reader类型的引用,而Reader类是一个抽象类,实参只能传递子类对象
        // System.in代表键盘输入,返回值类型是InputStream类型的
        // 这里就需要将字节流类型转换为字符流类型,需要用到转换流  即 InputStreamReader
        BufferedReader br = null;
        PrintStream ps = null;
        try {
            br = new BufferedReader(new InputStreamReader(System.in));
            ps = new PrintStream(new FileOutputStream("d://a2.txt"));

            // 声明一个boolean类型的变量作为发送方代表
            boolean flag = true;
            while (true) {
                System.out.println("请" + (flag ? "张三": "李四") + "输入需要发送的聊天内容:");
                String line = br.readLine();
                if ("bye".equals(line)) {
                    System.out.println("聊天结束");
                    break;
                }
                
                // 相当于保存到文件
                ps.println(line);
                flag = !flag;
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (ps != null) {
                ps.close();
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

14. 总结

转换流OutputStreamWriter同字符流FileWriter写入文本文件的区别

  • 转换流实际上还是用字节流写入的,在写入的时候会按照指定的编码格式取查找编码表。
  • 转换流在写入文本文件的时候可以指定编码格式,而字符流在写入文本文件的时候不能指定编码格式

四、多线程(重中之重)

1. 线程的创建

  • 无参构造的方法创建线程

    调用run方法时不会执行run方法里的任何代码

    public class TreadTest {
    
        public static void main(String[] args) {
    
            // 由源码可知,Thread类中的成员变量target的数值为null
            Thread t = new Thread();
            // 由源码可知,当target为null时,run方法什么都没做
            /*public void run() {
                if (target != null) {
                    target.run();
                }*/
    
            t.run();
            System.out.println("我想看看你到底是不是什么都没干");
            
            // 说明,用无参构造方法创建线程时,调用run方法不会执行任何代码。
        }
    }
    
  • 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法

    public class SubRunnableTest {
    
        public static void main(String[] args) {
    
            SubRunnable sr = new SubRunnable();
    
            Thread t = new Thread(sr);
    
            t.start();
    
            for (int i = 0; i < 20; i++) {
                System.out.println("main方法中的打印结果:" + i);
            }
        }
        
    }
    
    public class SubRunnable implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println(i);
            }
        }
    }
    

    小结:两种方式创建多线程的优缺点

    继承Thread类的方式,如果后续该类还需要继承其他类,那么这个时候就比较麻烦了,因为java不能多继承。

    实现Runnable接口的方式,后续如果需要继承其他类,可以扩展。

  • 匿名内部类的方式创建多线程

    public class ThreadNoNameTest {
    
        public static void main(String[] args) {
    
            Thread t = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println(i);
                    }
                }
            };
            t.start();
    
            Runnable r = new Runnable(){
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println(i);
                    }
                }
            };
            Thread t1 = new Thread(r);
            t1.start();
    
            // lambda表达式:接口的匿名内部类支持,java8开始
            Runnable r1 = () -> {
                for (int i = 0; i < 20; i++) {
                    System.out.println(i);
                }
            };
        }
    }
    

2. 常用方法

方法声明 功能介绍
static void sleep(times) 使当前线程从Running放弃处理器进入Block状态,休眠times毫秒、
int getPriority() 获取线程的优先级
void setPripority(int newPripority) 修改线程优先级,优先级越高的线程不一定先执行,但该线程获取到的时间片的机会会更多一些。
void join() 等待该线程结束
boolean isDaemon() 用于判断是否为守护线程
void setDaemon() 用于设置线程为守护线程

void setPripority(int newPripority)

示例代码

public class ThreadPriorityTest extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程 i = " + i);
        }
    }

    public static void main(String[] args) {

        ThreadPriorityTest tp = new ThreadPriorityTest();
        // 设置优先级只是会让该线程获取到的时间片的机会更多一些
        tp.setPriority(MAX_PRIORITY);
        tp.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("--主线程 i = " + i);
        }
    }
}

void join()

示例代码

public class ThreadJoinTest extends Thread {

    @Override
    public void run() {

        System.out.println("新年倒计时开始");
        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("新年快乐");
    }

    public static void main(String[] args) {

        ThreadJoinTest tj = new ThreadJoinTest();
        tj.start();
        try {
            tj.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("终于等到你,还好我没放弃!");
    }
}

boolean isDaemon() 、 void setDaemon()

示例代码

public class ThreadDaemonTest extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("子线程 i = " + i);
        }
    }

    public static void main(String[] args) {

        ThreadDaemonTest td = new ThreadDaemonTest();
        // System.out.println(td.isDaemon()? "是守护线程": "不是守护线程");
        // 如果不是守护线程,那么主线程执行完之后,如果子线程还没执行完,那么需要等子线程执行完之后才能结束程序
        // 如果是守护线程,那么主线程执行完之后,就会结束子线程。
        // 设置是否为守护线程需要在start方法之前
        td.setDaemon(true);
        td.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("主线程 i = " + i);
        }
    }
}

小结:join()和setDaemon()的区别

  • join():主线程A中,创建了子线程B,并且在主线程中调用了B.join()方法,那么主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行。
  • setDaemon():主线程A中,创建了子线程B,并且在主线程A中调用了B.setDaemon(true)方法,这个意思是把主线程A设置为守护线程,这个时候,要是主线程A执行结束了,就不用管子线程B是否完成,一并和主线程A退出。

3. 部分代码的锁定

格式如下:synchronized(类类型的引用) { 编写所需要锁定的代码块;}

银行取钱案例

  • 使用实现Runnable接口的方法
public class AccountRunnableTest implements Runnable {

    private int balance;
    private Demo demo = new Demo();
    
    public AccountRunnableTest() {
    }
    
    public AccountRunnableTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public void run() {
        System.out.println("当前线程的名字是:" + Thread.currentThread().getName());
        // 为了确保最后余额的准确性,这里需要使用synchronized关键字来锁住有可能造成数据异常的代码
        // 对象锁,可以是任意一个对象引用
        // 但是不能使用synchronized (new Demo()),因为不是同一个对象锁了
        synchronized (demo){
            int temp = getBalance();
            if (temp >= 200) {
                System.out.println("正在出钞,请稍后...");
                temp -= 200;

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("请取走您的钞票!");
            } else {
                System.out.println("余额不足,请核对您的账户!");
            }
            setBalance(temp);
        }
    }

    public static void main(String[] args) {

        AccountRunnableTest ar = new AccountRunnableTest(1000);
        Thread t1 = new Thread(ar);
        Thread t2 = new Thread(ar);
        t1.start();
        t2.start();

        System.out.println("主线程开始等待");
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + ar.getBalance());
    }
}

class Demo {

}

  • 使用继承Thread的方法
public class AccountThreadTest  extends Thread {

    private  int balance;
    // 需要用static修饰,确保demo是同一个对象引用
    private static Demo demo = new Demo();

    public AccountThreadTest() {
    }

    public AccountThreadTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public void run() {
        System.out.println("当前线程的名字是:" + Thread.currentThread().getName());
        // 对象锁,可以是任意一个对象引用
        synchronized (demo){
            int temp = getBalance();
            if (temp >= 200) {
                System.out.println("正在出钞,请稍后...");
                temp -= 200;

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("请取走您的钞票!");
            } else {
                System.out.println("余额不足,请核对您的账户!");
            }
            setBalance(temp);
        }
    }

    public static void main(String[] args) {
        AccountThreadTest at1 = new AccountThreadTest(1000);
        AccountThreadTest at2 = new AccountThreadTest(1000);

        at1.start();
        at2.start();

        System.out.println("主线程开始等待");
        try {
            at1.join();
            at2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + at1.getBalance());	//800,因为创建了两个对象,这里只是测试锁是否能锁住

    }
}

4. 所有代码的锁定

直接用synchronized关键字修饰整个方法即可,该方式等价于:synchronized(this){整个方法体代码}

public class AccountRunnableTest implements Runnable {

    private int balance;
    private Demo demo = new Demo();

    public AccountRunnableTest() {
    }

    public AccountRunnableTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public synchronized void run() {
        // 使用synchronized关键字修饰方法等价于下面的synchronized (this)
        //synchronized (this) {
            System.out.println("当前线程的名字是:" + Thread.currentThread().getName());
            int temp = getBalance();
            if (temp >= 200) {
                System.out.println("正在出钞,请稍后...");
                temp -= 200;

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("请取走您的钞票!");
            } else {
                System.out.println("余额不足,请核对您的账户!");
            }
            setBalance(temp);
        //}
    }

    public static void main(String[] args) {

        AccountRunnableTest ar = new AccountRunnableTest(1000);
        Thread t1 = new Thread(ar);
        Thread t2 = new Thread(ar);
        t1.start();
        t2.start();

        System.out.println("主线程开始等待");
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + ar.getBalance());
    }
}

class Demo {

}

5. 静态方法的锁定

// 用synchronized关键字修饰静态方法
public synchronized static void test() {
    // 等价于下面的代码
    //synchronized(AccountRunnableTest.class) {    
    //}
} 

6. 使用Lock(锁)实现线程同步

6.1 常用方法

方法声明 功能介绍
ReentranLock() 使用无参方式构造对象
void lock() 获取锁
void unlock() 释放锁

6.2 与synchronized方式的比较

  • Lock是显式锁,需要手动开启和关闭操作;而synchronized是隐式锁,执行锁定代码后自动释放。
  • Lock只有同步代码块的方式;而synchronized既可以锁定代码块,也可以锁定锁定方法。
  • 使用Lock锁方式时,java虚拟机将花费更少的时间来调度线程,因此性能更好。

7. Object类中的wait方法和notify方法在多线程中的使用

线程之间的通信,示例代码如下

public class ThreadCommunicateTest  implements Runnable {
    private int cnt = 0;
    
    // 这里run方法里的内容是两个线程在执行,加上synchronized关键字之后,同一时间只能一个线程访问。
    // 当一个线程(线程一)进入到run方法之后,立即执行notify(),这时候就会唤醒在阻塞状态下的另外一个线程(线程二),
    // 被唤醒之后的线程二就会进入就绪状态,等待jvm虚拟机的调用。这个时候线程一已经在执行notify()下面的的代码了
    // 如果,线程二这个时候被jvm虚拟机调用,也开始执行,到了run方法以后,碰到synchronized,
    // 这个时候,线程二还不能执行里面的代码,需要等待,当线程一执行完代码之后,到了wait()方法,这个时候线程一
    // 就进入阻塞状态了,并且释放了对象锁,然后线程二就可以进来执行代码了。就这样反复进行下去。
    // 因此,加了notify();和wait(); 之后,两个线程就会交叉执行,不会出现一个线程连续执行多次的情况。
    
    // 如果没有加notify();和wait(); 就有可能出现这样的情况,当线程一抢占到资源开始执行run方法里面的代码之后,在	 // 下一次,线程一又可能再次抢占到资源进入run方法,执行代码。
    
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();   // 这里不能写在if条件语句的里面
                if (cnt <= 100) {
                    // notify(); 当if条件不满足时,就不能唤醒另一个线程,那么另一个线程就会一直处于阻塞状态
                    System.out.println("线程" + Thread.currentThread().getName() + "中:cnt = " + cnt);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    cnt++;
                    // 当前线程打印完毕一个整数之后,为了防止继续打印下一个数据,则调用wait方法
                    try {
                        wait(); // 当前线程进入阻塞状态,自动释放对象锁。必须在锁定的代码中调用
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {

        ThreadCommunicateTest tc1 = new ThreadCommunicateTest();
        Thread t1 = new Thread(tc1);
        Thread t2 = new Thread(tc1);
        t1.start();
        t2.start();

        System.out.println("主线程等待中...");
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//两个线程会交叉打印结果
//线程Thread-0中:cnt = 0
//线程Thread-1中:cnt = 1
//线程Thread-0中:cnt = 2
//线程Thread-1中:cnt = 3
//...

8. 生产者和消费者模式

示例代码如下:

StoreHouse类

public class StoreHouse {
    private int cnt = 0;  // 用户记录产品的数量
    public synchronized void Produce() {
        notify();
        if (cnt < 10) {
            System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt + 1) + "个产品");
            cnt++;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void Consume() {
        notify();
        if (cnt > 0) {
            System.out.println("线程" + Thread.currentThread().getName() + "正在消费第" + cnt + "个产品");
            cnt--;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

ProducerThread类

public class ProducerThread extends Thread{

    private StoreHouse sh;

    public ProducerThread(StoreHouse sh) {
        this.sh = sh;
    }
    @Override
    public void run() {
        while (true) {
            sh.Produce();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

ConsumerThread类

public class ConsumerThread extends Thread{
    private StoreHouse sh;

    public ConsumerThread(StoreHouse sh) {
        this.sh = sh;
    }
    @Override
    public void run() {
        while (true) {
            sh.Consume();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类

public class ProducerConsumerTest {

    public static void main(String[] args) {
        StoreHouse sh = new StoreHouse();
        ProducerThread ct2 = new ProducerThread(sh);
        ConsumerThread ct1 = new ConsumerThread(sh);
        ct1.start();
        ct2.start();
    }
}

9. 创建线程的第三种方法

实现Callable接口, 适用于执行该线程需要有返回值的情况。

public class callableTest implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 10000; i++) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) {

        callableTest ct = new callableTest();
        FutureTask ft = new FutureTask(ct);
        Thread t = new Thread(ft);
        t.start();
        try {
            System.out.println(ft.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

10. 线程运行内存原理

每新创建一个线程都会为该线程创建一个独立的栈区。也就是说每一个执行线程都有一片属于自己的栈内存空间。

五、网络编程(熟悉)

1. 基于tcp协议的编程模型(重点)

1.1 编程模型

  • 服务器

    (1) 创建ServerSocket类型的对象并提供端口号;

    (2) 等待客户端的连接请求,调用accept方法;

    (3) 使用输入输出流进行通信;

    (4) 关闭Socket;

  • 客户端

    (1) 创建Socket类型的对象并连接服务器的Ip地址和端口号;

    (2) 使用输入输出流通信;

    (3) 关闭Socket;

1.2 相关类和方法的解析

(1) ServerSocket类

  • 常用方法如下

    方法声明 功能介绍
    ServerSocket(int port) 根据参数指定的端口号来构建对象
    Socket accept() 倾听并接收此套接字的连接请求
    void close() 关闭套接字
  • 示例代码

    服务器端

    public class ServerSocketTest {
    
        public static void main(String[] args) {
            ServerSocket ss = null;
            Socket socket = null;
    
            try {
                // 1.创建ServerSocket对象,并指定端口号
                ss = new ServerSocket(8888);
                // 2.等待客户端连接
                System.out.println("等待客户端连接...");
                socket = ss.accept();
                System.out.println("客户端连接成功!!!");
                // 3.进行通信
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭套接字
                if (null != socket) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != ss) {
                    try {
                        ss.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    客户端

    public class ClientSocketTest {
    
        public static void main(String[] args) {
            Socket s = null;
            try {
                // 创建Socket对象,并连接服务端
                s = new Socket("127.0.0.1", 8888);
                System.out.println("连接服务器成功!!!");
                // 进行通信
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
    
                // 关闭套接字
                if (null != s) {
                    try {
                        s.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    

(2) Socket类

  • 常用方法如下

2. 基于udp协议的编程模型(熟悉)

3. InetAddress类

用于描述IP地址的信息

常用方法如下:

方法声明 功能介绍
static InetAddress getLocalHost 用于获取当前主机的ip地址
static InetAddress getByName(String host) 根据参数指定的主机名获取ip地址

六、反射机制(重点)

1. 基本概念

  • 通常情况下编写代码都是固定的,无论运行多少次执行结果都是一样的。在某些特定场合中,编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也叫反射机制。
  • 通俗来说,反射机制就是用于动态创建对象并且动态创建调用方法的机制。

2. Class类

2.1 基本概念

  • 如何理解Class类,即java中的类也是对象,即Class类的实例。
  • java.lang.Class类的实例可以用于描述Java应用程序中类和接口,也就是一种数据类型。
  • 该类没有公共构造方法,该类的实例有jvm和类加载器自动构造完成,本质上就是加载到内存中的运行时的类。

2.2 获取Class对象的方式

  • 使用数据类型.class的方式获取
  • 使用引用/对象.getClass()的方式获取
  • 使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象
  • 使用Class.forName()方式获取
  • 使用类加载器ClassLoader的方式获取

2.3 常用方法

方法声明 功能介绍
static Class<?> forName(String className) 用于获取参数指定的类型对应的Class对象
T newInstance() 用于创建该Class对象所表示的类的新实例
Constructor getConstructor(Class<?>... parametersTypes) 用于获取此Class对象所表示类型中参数指定的公共构造方法
Constructor[] getConstructors() 用于获取此Class对象所有的公共构造方法
Field getDeclaredField(String name) 用于获取此Class对象所表示的类中参数指定的单个成员变量信息
Field[] getDeclaredFields() 用于获取此Class对象所表示的类中所有的成员变量信息
Class p2 = Class.forName("pers.kyle.java.reflect.Person");
Object o = p2.newInstance();  // java11中该方法过时了
System.out.println("通过反射机制创建的对象:" + o);

3. Constructor类

常用方法

方法声明 功能介绍
T newInstance(Object... initargs) 使用此Constructor对象描述的构造方法来构造Class对象代表类型的新实例
int getModifiers() 获取方法的访问修饰符
String getName() 获取方法的名称
Class<?> getparametersTypes() 获取方法所有参数的类型
Class p2 = Class.forName("pers.kyle.java.reflect.Person");
Constructor constructor = p2.getConstructor(); // 推荐使用该方法
System.out.println("无参方式创建的对象是:" + constructor.newInstance());

Constructor constructor1 = p2.getConstructor(String.class, int.class);
System.out.println("有参方式创建的对象是:" + constructor1.newInstance("张飞", 18));

在不知道构造方法里的参数的情况下,可以先获取所有的构造方法,然后再获取构造方法中的参数信息,根据构造方法中的参数来创建对象。

Class p2 = Class.forName("pers.kyle.java.reflect.Person");
Constructor[] constructors = p2.getConstructors();
for (Constructor c: constructors) {
    System.out.println("构造方法的访问修饰符是:" + c.getModifiers());
    System.out.println("构造方法的名称是:" + c.getName());
    Class[] parameterTypes = c.getParameterTypes();
    System.out.println("构造方法的参数类型是:");
    for (Class s: parameterTypes) {
        System.out.print(s + " ");
    }
    System.out.println();
    System.out.println("-----------------------");
}

4. Field类

主要用于描述获取到的单个成员变量信息

4.1 常用方法

方法声明 功能介绍
Object get(Object obj) 获取参数对象obj中此field对象所表示成员变量的值
void set(Object obj, Object, value) 将成员变量的值修改为参数指定的value
int getModifiers() 获取成员变量的访问修饰符
Class<?> getType() 获取成员变量的数据类型
String getName() 获取成员变量的名称
void setAccessable(Boolean b) 设置 java语言的访问检查

示例代码

public class PersonConstructorTest {

    public static void main(String[] args) throws Exception {

        // 普通的方式构造对象
        Person p1 = new Person();
        System.out.println("普通方式创建的对象:" + p1);

        // 反射的方式构造对象
        // 1. forName里面的参数可以放到配置文件,实现动态创建对象。
        Class p2 = Class.forName("pers.kyle.java.reflect.Person");
        Object o = p2.newInstance();
        System.out.println("通过反射机制创建的对象:" + o);

        // 2. 通过Constructor类
        Constructor constructor = p2.getConstructor();
        System.out.println("无参方式创建的对象是:" + constructor.newInstance());

        Constructor constructor1 = p2.getConstructor(String.class, int.class);
        System.out.println("有参方式创建的对象是:" + constructor1.newInstance("张飞", 18));

        // 3. 在不知道构造方法里的参数的情况下,可以先获取所有的构造方法,然后再获取构造方法中的参数,根据构造方法中的参数来创建对象
        Constructor[] constructors = p2.getConstructors();
        for (Constructor c: constructors) {
            System.out.println("构造方法的访问修饰符是:" + c.getModifiers());
            System.out.println("构造方法的名称是:" + c.getName());
            Class[] parameterTypes = c.getParameterTypes();
            System.out.println("构造方法的参数类型是:");
            for (Class s: parameterTypes) {
                System.out.print(s + " ");
            }
            System.out.println();
            System.out.println("-----------------------");
        }

        // 4. 通过反射获取成员变量信息
        Constructor constructor2 = p2.getConstructor(String.class, int.class);
        Object obj = constructor2.newInstance("张飞", 18);

        // 调用Class对象的getDeclaredField方法获取参数指定的成员变量
        Field field = p2.getDeclaredField("name");

        // 如果是private修饰,但还是想访问该成员变量的话,可以
        field.setAccessible(true);

        // 获取成员变量的名字
        String name = field.getName();
        // 获取成员变量的值
        System.out.println("获取到的成员变量的值为:" + field.get(obj));
        // 获取成员变量的修饰符
        int modifiers = field.getModifiers();
        
        // 5.通过反射修改成员变量的值
        field.set(obj, "关羽");
        System.out.println("修改后的成员变量的值为:" + field.get(obj));
        System.out.println("--------------------------------------");

        // 5. 获取Class对象对应类中的所有成员变量
        Field[] declaredFields = p2.getDeclaredFields();
        for (Field f: declaredFields) {
            f.setAccessible(true);
            System.out.println("获取到的成员变量的值为:" + f.get(obj));
            System.out.println("获取到的成员变量的修饰符为:" + f.getModifiers());
        }
    }
}

5. Method类

5.1 常用方法

5.1.1 Class类的常用方法
方法声明 功能介绍
Method getMethod(String name, Class<?>... parameterTypes) 用于获取该Class对象表示类中名字为name,参数为parameterTypes的指定的公共成员方法
Method[] getMethods() 获取所有的成员方法
5.1.2 Method类常用方法
方法声明 功能介绍
Object invoke(Object obj, Object... args) 使用对象obj来调用此Method对象所表示的成员方法,实参传递args
int getModifiers() 获取方法的访问修饰符
Class<?> getReturnType() 获取方法的返回值类型
String getName() 获取方法的名称
Class<?>[] getParameterTypes() 获取方法所有参数的类型
Class<?>[] getExceptionTypes() 获取方法的异常信息
5.1.3 获取其他结构信息
方法声明 功能介绍
Package getPackage() 获取所在包信息
Class<? super T> getSpuerClass() 获取继承的父类信息
Class<?>[] getInterface() 获取实现的所有接口
Annotation[] getAnnotations() 获取注解信息
Type[] getGenericlnterfaces() 获取泛型信息
posted @ 2020-10-16 21:53  凯尔哥  阅读(89)  评论(0编辑  收藏  举报