JavaSE模块四笔记
第一阶段——模块四
一、异常(重点)
1. 自定义异常类
-
- 继承Exception或者RunTimeException类
-
- 实现有参构造和无参构造
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() | 获取泛型信息 |