默认地址比较
如果没有覆盖重写equals方法,那么Object类中默认进行==运算符的对象地址比较,只要不是同一个对象,结果必然为false。
对象内容比较
如果希望进行对象的内容比较,即所有或指定的部分成员变量相同就判定两个对象相同,则可以覆盖重写equals方法。例如:
import java.util.Objects; public class Person { private String name; private int age; @Override public boolean equals(Object o) { // 如果对象地址一样,则认为相同 if (this == o) return true; // 如果参数为空,或者类型信息不一样,则认为不同 if (o == null || getClass() != o.getClass()) return false; // 转换为当前类型 Person person = (Person) o; // 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果 return age == person.age && Objects.equals(name, person.name); } }要 求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
分析:
1.定义一个异常类---->运行异常 2.当产生异常的时候,就创建该异常类的对象,并使用throw 关键字把该异常抛出即可 3.处理该异常首先定义一个登陆异常类LoginException:
// 业务逻辑异常 public class LoginException extends Exception { /** * 空参构造 */ public LoginException() { } /** * * @param message 表示异常提示 */ public LoginException(String message) { super(message); } }模拟登陆操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否存在方法用于判断。
public class Demo { // 模拟数据库中已存在账号 private static String[] names = {"bill","hill","jill"}; public static void main(String[] args) { //调用方法 try{ // 可能出现异常的代码 checkUsername("nill"); System.out.println("注册成功");//如果没有异常就是注册成功 }catch(LoginException e){ //处理异常 e.printStackTrace(); } } //判断当前注册账号是否存在 //因为是编译期异常,又想调用者去处理 所以声明该异常 public static boolean checkUsername(String uname) throws LoginException{ for (String name : names) { if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常 throw new LoginException("亲"+name+"已经被注册了!"); } } return true; } }如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)
需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
模拟票:
public class Ticket implements Runnable { Object lock = new Object(); private int ticket = 100; /* * 执行卖票操作 */ @Override public void run() { //每个窗口卖票的操作 //窗口 永远开启 while (true) { synchronized(obj){ if (ticket > 0) {//有票 可以卖 //出票操作 //使用sleep模拟一下出票时间 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖:" + ticket--); } } } } }测试类:
public class Demo { public static void main(String[] args) { //创建线程任务对象 Ticket ticket = new Ticket(); //创建三个窗口对象 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); //同时卖票 t1.start(); t2.start(); t3.start(); } }Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
那么我们之前遇到过这种状态吗?答案是并没有,但并不妨碍我们进行一个简单深入的了解。我们通过一段代码来学习一下:
public class WaitingTest { public static Object obj = new Object(); public static void main(String[] args) { // 演示waiting new Thread(new Runnable() { @Override public void run() { while (true){ synchronized (obj){ try { System.out.println( Thread.currentThread().getName() +"=== 获取到锁对象,调用wait方法,进入waiting状态,释放锁对象"); obj.wait(); //无限等待 //obj.wait(5000); //计时等待, 5秒 时间到,自动醒来 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( Thread.currentThread().getName() + "=== 从waiting状态醒来,获取到锁对象,继续执行了"); } } } },"等待线程").start(); new Thread(new Runnable() { @Override public void run() { // while (true){ //每隔3秒 唤醒一次 try { System.out.println( Thread.currentThread().getName() +"----- 等待3秒钟"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj){ System.out.println( Thread.currentThread().getName() +"----- 获取到锁对象,调用notify方法,释放锁对象"); obj.notify(); } } // } },"唤醒线程").start(); } }生产者与消费者问题 包子资源类: public class BaoZi { String pier ; String xianer ; boolean flag = false ;//包子资源 是否存在 包子资源状态 } 吃货线程类: public class ChiHuo extends Thread{ private BaoZi bz;
public ChiHuo(String name,BaoZi bz){ super(name); this.bz = bz; } @Override public void run() { while(true){ synchronized (bz){ if(bz.flag == false){//没包子 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子"); bz.flag = false; bz.notify(); } } } }包子铺线程类:
public class BaoZiPu extends Thread { private BaoZi bz; public BaoZiPu(String name,BaoZi bz){ super(name); this.bz = bz; } @Override public void run() { int count = 0; //造包子 while(true){ //同步 synchronized (bz){ if(bz.flag == true){//包子资源 存在 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 没有包子 造包子 System.out.println("包子铺开始做包子"); if(count%2 == 0){ // 冰皮 五仁 bz.pier = "冰皮"; bz.xianer = "五仁"; }else{ // 薄皮 牛肉大葱 bz.pier = "薄皮"; bz.xianer = "牛肉大葱"; } count++; bz.flag=true; System.out.println("包子造好了:"+bz.pier+bz.xianer); System.out.println("吃货来吃吧"); //唤醒等待线程 (吃货) bz.notify(); } } } }测试类:
public class Demo { public static void main(String[] args) { //等待唤醒案例 BaoZi bz = new BaoZi(); ChiHuo ch = new ChiHuo("吃货",bz); BaoZiPu bzp = new BaoZiPu("包子铺",bz); ch.start(); bzp.start(); } }执行效果:
包子铺开始做包子 包子造好了:冰皮五仁 吃货来吃吧 吃货正在吃冰皮五仁包子 包子铺开始做包子 包子造好了:薄皮牛肉大葱 吃货来吃吧 吃货正在吃薄皮牛肉大葱包子 包子铺开始做包子 包子造好了:冰皮五仁 吃货来吃吧 吃货正在吃冰皮五仁包子测试类:
public class Test { public static void main(String[] args) { MyTread6 my = new MyTread6(); new Thread(new Runnable() { @Override public void run() { while (true) { my.method1(); } } }, "吴亦凡线程1").start(); new Thread(new Runnable() { @Override public void run() { while (true) { my.method2(); } } }, "范冰冰程1").start(); new Thread(new Runnable() { @Override public void run() { while (true) { my.method3(); } } }, "张艺兴线程1").start(); } }线程任务类:
public class MyTread6 { Object obj = new Object(); int num = 1; public void method1() { synchronized (obj) { while (num != 1) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("A"); System.out.print("B"); System.out.print("C"); System.out.print("D"); System.out.println(); num = 2; obj.notifyAll(); } } public void method2() { synchronized (obj) { while (num != 2) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("1"); System.out.print("2"); System.out.print("3"); System.out.print("4"); System.out.println(); num = 3; obj.notifyAll(); } } public void method3() { synchronized (obj) { while (num != 3) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("O"); System.out.print("P"); System.out.print("Q"); System.out.print("R"); System.out.println(); num = 1; obj.notifyAll(); } } }Runnable实现类代码:
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("我要一个教练"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("教练来了: " + Thread.currentThread().getName()); System.out.println("教我游泳,交完后,教练回到了游泳池"); } }线程池测试类:
public class ThreadPoolDemo { public static void main(String[] args) { // 创建线程池对象 ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象 // 创建Runnable实例对象 MyRunnable r = new MyRunnable(); //自己创建线程对象的方式 // Thread t = new Thread(r); // t.start(); ---> 调用MyRunnable中的run() // 从线程池中获取线程对象,然后调用MyRunnable中的run() service.submit(r); // 再获取个线程对象,调用MyRunnable中的run() service.submit(r); service.submit(r); // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。 // 将使用完的线程又归还到了线程池中 // 关闭线程池 //service.shutdown(); } }给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数、无返回值。如下:
public interface Cook { void makeFood(); }在下面的代码中,请使用Lambda的标准格式调用invokeCook方法,打印输出“吃饭啦!”字样:
public class Demo05InvokeCook { public static void main(String[] args) { // TODO 请在此使用Lambda【标准格式】调用invokeCook方法 } private static void invokeCook(Cook cook) { cook.makeFood(); } } public static void main(String[] args) { invokeCook(() -> { System.out.println("吃饭啦!"); }); }备注:小括号代表Cook接口makeFood抽象方法的参数为空,大括号代表makeFood的方法体。
给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值:
public interface Calculator { int calc(int a, int b); }在下面的代码中,请使用Lambda的标准格式调用invokeCalc方法,完成120和130的相加计算:
public class Demo08InvokeCalc { public static void main(String[] args) { // TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß } private static void invokeCalc(int a, int b, Calculator calculator) { int result = calculator.calc(a, b); System.out.println("结果是:" + result); } } public static void main(String[] args) { invokeCalc(120, 130, (int a, int b) -> { return a + b; }); }备注:小括号代表Calculator接口calc抽象方法的参数,大括号代表calc的方法体。
分析:num的累和 = num + (num-1)的累和,所以可以把累和的操作定义成一个方法,递归调用。
实现代码:
public class DiGuiDemo { public static void main(String[] args) { //计算1~num的和,使用递归完成 int num = 5; // 调用求和的方法 int sum = getSum(num); // 输出结果 System.out.println(sum); } /* 通过递归算法实现. 参数列表:int 返回值类型: int */ public static int getSum(int num) { /* num为1时,方法返回1, 相当于是方法的出口,num总有是1的情况 */ if(num == 1){ return 1; } /* num不为1时,方法返回 num +(num-1)的累和 递归调用getSum方法 */ return num + getSum(num-1); } }阶乘:所有小于及等于该数的正整数的积。
n的阶乘:n! = n * (n-1) … 3 * 2 * 1
分析:这与累和类似,只不过换成了乘法运算,学员可以自己练习,需要注意阶乘值符合int类型的范围。
推理得出:n! = n * (n-1)!代码实现:
public class DiGuiDemo { //计算n的阶乘,使用递归完成 public static void main(String[] args) { int n = 3; // 调用求阶乘的方法 int value = getValue(n); // 输出结果 System.out.println("阶乘为:"+ value); } /* 通过递归算法实现. 参数列表:int 返回值类型: int */ public static int getValue(int n) { // 1的阶乘为1 if (n == 1) { return 1; } /* n不为1时,方法返回 n! = n*(n-1)! 递归调用getValue方法 */ return n * getValue(n - 1); } }分析:
目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录。遍历目录时,获取的子文件,通过文件名称,判断是否符合条件。代码实现:
public class DiGuiDemo3 { public static void main(String[] args) { // 创建File对象 File dir = new File("D:\\aaa"); // 调用打印目录方法 printDir(dir); } public static void printDir(File dir) { // 获取子文件和目录 File[] files = dir.listFiles(); // 循环打印 for (File file : files) { if (file.isFile()) { // 是文件,判断文件名并输出文件绝对路径 if (file.getName().endsWith(".java")) { System.out.println("文件名:" + file.getAbsolutePath()); } } else { // 是目录,继续遍历,形成递归 printDir(file); } } } }小贴士:
流的关闭原则:先开后关,后开先关。
逐行读取文本信息。
解析文本信息到集合中。
遍历集合,按顺序,写出文本信息。
public class BufferedTest { public static void main(String[] args) throws IOException { // 创建map集合,保存文本数据,键为序号,值为文字 HashMap<String, String> lineMap = new HashMap<>();
// 创建流对象 BufferedReader br = new BufferedReader(new FileReader("in.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt")); // 读取数据 String line = null; while ((line = br.readLine())!=null) { // 解析文本 String[] split = line.split("\\."); // 保存到集合 lineMap.put(split[0],split[1]); } // 释放资源 br.close(); // 遍历map集合 for (int i = 1; i <= lineMap.size(); i++) { String key = String.valueOf(i); // 获取map中文本 String value = lineMap.get(key); // 写出拼接文本 bw.write(key+"."+value); // 写出换行 bw.newLine(); } // 释放资源 bw.close(); }}
指定GBK编码的转换流,读取文本文件。
使用UTF-8编码的转换流,写出文本文件。
public class TransDemo { public static void main(String[] args) { // 1.定义文件路径 String srcFile = “file_gbk.txt”; String destFile = “file_utf8.txt”; // 2.创建流对象 // 2.1 转换输入流,指定GBK编码 InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , “GBK”); // 2.2 转换输出流,默认utf8编码 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile)); // 3.读写数据 // 3.1 定义数组 char[] cbuf = new char[1024]; // 3.2 定义长度 int len; // 3.3 循环读取 while ((len = isr.read(cbuf))!=-1) { // 循环写出 osw.write(cbuf,0,len); } // 4.释放资源 osw.close(); isr.close(); } }
把若干学习对象 ,保存到集合中。
把集合序列化。
反序列化读取时,只需要读取一次,转换为集合类型。
遍历集合,可以打印所有的学生信息
public class SerTest { public static void main(String[] args) throws Exception { // 创建 学生对象 Student student = new Student(“老王”, “laow”); Student student2 = new Student(“老张”, “laoz”); Student student3 = new Student(“老李”, “laol”);
ArrayList<Student> arrayList = new ArrayList<>(); arrayList.add(student); arrayList.add(student2); arrayList.add(student3); // 序列化操作 // serializ(arrayList); // 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt")); // 读取对象,强转为ArrayList类型 ArrayList<Student> list = (ArrayList<Student>)ois.readObject(); for (int i = 0; i < list.size(); i++ ){ Student s = list.get(i); System.out.println(s.getName()+"--"+ s.getPwd()); } } private static void serializ(ArrayList<Student> arrayList) throws Exception { // 创建 序列化流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt")); // 写出对象 oos.writeObject(arrayList); // 释放资源 oos.close(); }}
System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏",改变它的流向。
public class PrintDemo { public static void main(String[] args) throws IOException { // 调用系统的打印流,控制台直接输出97 System.out.println(97); // 创建打印流,指定文件的名称 PrintStream ps = new PrintStream("ps.txt"); // 设置系统的打印流流向,输出到ps.txt System.setOut(ps); // 调用系统的打印流,ps.txt中输出97 System.out.println(97); } }