首页
关于
留言
统计
友链
壁纸
影视
留言找电影
Search
1
java jdk17版本使用cglib报错问题解决
321 阅读
2
在Java中修复json数据,json格式不正确,如何在Java中修复不正确的json
118 阅读
3
Java实现双链表插入排序~渡星河全网首发
107 阅读
4
Java打印某年某月的日历~不调用函数
76 阅读
5
npm nvm 淘宝镜像最新可用
73 阅读
代码笔记
Java
css
Javascript
数据库
安卓开发
Java 功能实例
Java 封装
我的日常
影音分享
文案
恋爱问答
资源分享
电脑游戏
实用APP
登录
/
注册
Search
标签搜索
java
java作业
恋爱
动漫
3A大作
独行月球
电影分享
总之就是非常可爱
JavaScript
office
破解
mysql
vue
java打包
跨域
tomcat解决乱码
bug解决
tomcat
vscode
渡星河
累计撰写
94
篇文章
累计收到
30
条评论
首页
栏目
代码笔记
Java
css
Javascript
数据库
安卓开发
Java 功能实例
Java 封装
我的日常
影音分享
文案
恋爱问答
资源分享
电脑游戏
实用APP
页面
关于
留言
统计
友链
壁纸
影视
留言找电影
搜索到
93
篇与
的结果
2023-02-12
java servlet实现将hashmap转换数组
要在 Java Servlet 中实现这样的 JSON 格式,需要使用一些第三方的 JSON 库,例如 Gson、Jackson 等。其中,Gson 是一个非常流行的 JSON 库。下面是一个使用 Gson 实现的示例:{lamp/}import com.google.gson.Gson; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置返回类型为 JSON response.setContentType("application/json;charset=UTF-8"); // 创建一个存储数据的 Map Map<String, Object> map = new HashMap<>(); map.put("status", 1); map.put("msg", "登录成功"); Map<String, Object> data = new HashMap<>(); data.put("userAccount", 10001); data.put("nickname", "admin"); data.put("motto", "心中无女人代码自然神"); data.put("userqq", "10001"); data.put("power", 1); map.put("data", data); // 使用 Gson 将数据转换为 JSON 格式 Gson gson = new Gson(); String json = gson.toJson(map); // 将 JSON 格式的数据写回到客户端 response.getWriter().write(json); } } 您可以通过以下方式封装这种 JSON 格式:import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; public class Result { private int status; private String msg; private Map<String, Object> data = new HashMap<String, Object>(); public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Map<String, Object> getData() { return data; } public void setData(Map<String, Object> data) { this.data = data; } public Result addData(String key, Object value) { this.data.put(key, value); return this; } public String toJson() throws Exception { ObjectMapper mapper = new ObjectMapper(); return mapper.writeValueAsString(this); } } 实现Result result = new Result(); result.setStatus(1); result.setMsg("登录成功"); result.addData("userAccount", 10001); result.addData("nickname", "admin"); result.addData("motto", "心中无女人代码自然神"); result.addData("userqq", "10001"); result.addData("power", 1); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(result.toJson()); 注意:需要引入 jackson-databind 库才能使用 ObjectMapper 类。
2023年02月12日
14 阅读
0 评论
1 点赞
2023-02-11
基于JavaServlet实现发送邮件
邮件发送需要下载下面的两个jar包导入到项目activation-1.1.1.jar mail-1.4.7.jarjar包下载网站jar下载网站{lamp/}代码实现import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Properties; @WebServlet(name = "SendEmailServlet", urlPatterns = {"/sendEmail"}) public class SendEmailServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); // 获取表单传递过来的数据 String from = request.getParameter("from"); String password = request.getParameter("password"); String to = request.getParameter("to"); String subject = request.getParameter("subject"); String content = request.getParameter("content"); // 配置发送邮件的环境信息 final Properties props = new Properties(); // 表示SMTP发送邮件,必须进行身份验证 props.put("mail.smtp.auth", "true"); // 此处填写SMTP服务器,QQ的是smtp.qq.com props.put("mail.smtp.host", "smtp.163.com"); // 端口号,QQ邮箱给出了两个端口,我这里使用的网易邮箱所以是25,QQ好像是465 props.put("mail.smtp.port", "25"); // 此处填写你的账号 props.put("mail.user", from); // 此处的密码就是前面说的16位STMP口令 props.put("mail.password", password); // 构建授权信息,用于进行SMTP进行身份验证 Authenticator authenticator = new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { // 用户名、密码 String userName = props.getProperty("mail.user"); String password = props.getProperty("mail.password"); return new PasswordAuthentication(userName, password); } }; // 使用环境属性和授权信息,创建邮件会话 Session mailSession = Session.getInstance(props, authenticator); // 创建邮件消息 MimeMessage message = new MimeMessage(mailSession); // 设置发件人 InternetAddress form; try { form = new InternetAddress(from); message.setFrom(form); // 设置收件人 InternetAddress toAddress = new InternetAddress(to); message.setRecipient(Message.RecipientType.TO, toAddress); // 设置邮件标题 message.setSubject(subject); // 设置邮件的内容体 message.setContent(content, "text/html;charset=UTF-8"); // 发送邮件 Transport.send(message); out.println("邮件发送成功"); } catch (MessagingException e) { e.printStackTrace(); } } }
2023年02月11日
13 阅读
0 评论
1 点赞
2023-02-02
网站樱花飘落代码实现
将代码放到head中<script type="text/javascript">//樱花 var system ={}; var p = navigator.platform; system.win = p.indexOf("Win") == 0; system.mac = p.indexOf("Mac") == 0; system.x11 = (p == "X11") || (p.indexOf("Linux") == 0); if(system.win||system.mac||system.xll){//如果是电脑 $.getScript("https://cdn.jsdelivr.net/gh/ivampiresp/special-JavaScript/yinghua.js"); }else{ //如果是手机 } </script>
2023年02月02日
8 阅读
0 评论
0 点赞
2023-02-02
基于Java原生SercerSocket和I/O流进行登录注册接口实现,可部署服务端前端直接调用接口
SeversSockrt基于Java原生SercerSocket和I/O流进行登录注册接口实现,可部署服务端前端直接调用接口可以通过使用线程池和循环来对服务端进行简单优化采用get方式进行访问-------------------必看-------------------必须修改RequestData中 //用户目录位置 private final String userPath = "E:\\Users\\"; 请修改为你自己的,否则会报错,这里以Windows系统路径测试,其它系统请使用其它系统路径格式用户保存位置方式例子一个用户 账号为123456 那么在本地的文件保存为E:\Users\123456 密码保存为E:\Users\123456\password.txt 注册的时候会通过数据分析自动创建账户和密码文件夹和文件(采用IO流)接口格式## 登录接口 http://localhost/api/login?user=用户名&password=密码 ## 注册接口 http://localhost/api/register?user=用户名&password=密码 可以添加其它数据,按照格式添加 &[数据名]=[数据] 例如 http://localhost/api/register?user=用户名&password=密码&name=张山接口格式解析http://localhost/api/login?user=用户名&password=密码 localhost 为服务器IP api 文件夹 login 文件夹下的login类 问号后面为提交的数据目录结构建议对前后端数据进行加密,例如aes加密。对数据作过期验证。{mtitle title="代码如下"/}NbServerpackage MuDaoServer; import java.net.ServerSocket; import java.net.Socket; public class NbServer extends ServerMethod{ public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(80); Socket socket = serverSocket.accept(); //处理前端数据 getClientData(socket); //反射获得需要输出的数据 String val = reflex(RequestData.FullName); //输出数据 ServerMethod.outBrowse(socket,val); socket.close(); } } ServerMethodpackage MuDaoServer; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; import java.net.URL; import java.util.Objects; //导入api文件夹下面的所有类,用于获取路径 import MuDaoServer.Controller.Annotations; import MuDaoServer.api.*; public class ServerMethod extends RequestData{ //获取用户客户端信息 public static void getClientData(Socket socket) throws IOException { //获取客户端内容 InputStream getBrowserData = socket.getInputStream(); //将字节流转换为字符流 InputStreamReader change = new InputStreamReader(getBrowserData); BufferedReader reader = new BufferedReader(change); String val; //获取第一行,用于解析用户使用了哪个文件,提交了哪些数据 String line1 = reader.readLine(); RequestData.firstLineData=line1; //将第一行以空格进行分割分别得到提交方法,提交路径数据,http协议版本 GET / HTTP/1.1,这里我们只需要路径 RequestData.data = line1.split(" ")[1]; //判断获取的路径是否包含/api/数据,不包含则返回错误json,目前只提供api文件夹下的文件 if (RequestData.data.contains("/api/")){ //将问号前面的数据放到RequestData中的path中 RequestData.path=RequestData.data.split("\\?")[0]; //判断请求的类是否存在 GET /api/login?user=202228902&password=QWERTY HTTP/1.1 String className = RequestData.data.split("\\?")[0].split("/")[2];//获取类名 String path = ServerMethod.class.getResource("./") +"api/"+className+".class"; path = path.substring(6); path = path.replaceAll("/","\\\\\\\\"); //判断文件是否存在 if (fileExists(path)){ //把全限定名放到数据中 String[] split = ServerMethod.class.getResource("./").toString().split("/"); String fullName = split[split.length - 1]+".api."+className; RequestData.FullName=fullName; System.out.println("全限定"+fullName); }else { val = "{\"code\":\"-1\",\"msg\":\"该接口不存在\"}"; outBrowse(socket,val); } }else { val = "{\"code\":\"-1\",\"msg\":\"该接口不存在\"}"; outBrowse(socket,val); } } //反射 public static String reflex(String fullName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class<?> clazz = Class.forName(fullName); String invoke = "{\"code\":\"-1\",\"msg\":\"反射出错\"}";; //判断是否使用了注解 if (clazz.isAnnotationPresent(Annotations.class)) { Annotations annotation = clazz.getAnnotation(Annotations.class); //获取注解的value String value = annotation.value(); //判断注解中路径是否正确, if (value.equals(RequestData.path)) { //通过反射创建该servlet对象,然后调用该对象的service方法 Object instance = clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getDeclaredMethod("service"); //把类型强转为字符串 invoke = (String) method.invoke(instance); } } return invoke; } //通用输出到网页 public static void outBrowse(Socket socket,String value) throws IOException { String stringBuilder = "HTTP/1.1" + " " + 200 + " " + "ok" + "\n" + "Content-Type" + ":" + "text/html;charset=UTF-8" + "\n" + "\n" + value; OutputStream outputStream = socket.getOutputStream(); PrintWriter output = new PrintWriter(outputStream); output.println(stringBuilder); output.flush(); outputStream.close(); } //判读文件或者文件夹是否存在 public static boolean fileExists(String path) { File file = new File(path); return file.exists(); } //创建一个文件 public static boolean createFile(String path) throws IOException { File file = new File(path); return file.createNewFile(); } //获取api文件夹下的类路径 public static String classPath(String className) { URL url = login.class.getResource("./"); return ""; } } 注解Annotationspackage MuDaoServer.Controller; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Annotations { String value() default ""; } RequestData 数据存放package MuDaoServer; import java.util.Map; public class RequestData { //用户目录位置 private final String userPath = "E:\\Users\\"; public String getUserPath() { return userPath; } //存放类名 private String clazz; //请求方法get/post private String method; public String getPath() { return path; } public void setPath(String path) { this.path = path; } //请求数据 public static String data; //全限定名 public static String FullName; //用户访问的接口路径 public static String path; //存放第一行数据 public static String firstLineData; public String getFirstLineData() { return firstLineData; } public void setFirstLineData(String firstLineData) { RequestData.firstLineData = firstLineData; } //存放其他头文件数据 private Map<String, String> headers; public String getClazz() { return clazz; } public void setClazz(String clazz) { this.clazz = clazz; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getData() { return data; } public void setData(String data) { this.data = data; } public void setHeaders(Map<String, String> headers) { this.headers = headers; } @Override public String toString() { return "RequestData{" + "clazz='" + clazz + '\'' + ", method='" + method + '\'' + ", path='" + path + '\'' + ", firstLineData='" + firstLineData + '\'' + ", headers=" + headers + '}'; } } login 登录代码逻辑package MuDaoServer.api; import MuDaoServer.Controller.Annotations; import MuDaoServer.RequestData; import java.io.*; @Annotations("/api/login") public class login { public String service() throws IOException { String[] split = RequestData.data.split("&"); String user = split[0].split("=")[1]; String pass = split[1].split("=")[1]; File file = new File("E:\\Users"); String json = ""; if (!file.exists()) { file.mkdirs(); json="{\"code\":\"-1\",\"msg\":\"提交参数不完整\"}"; }else { File file1 = new File("E:\\Users\\"+user); if (!file1.exists()) { json="{\"code\":\"-1\",\"msg\":\"用户不存在\"}"; }else { BufferedReader bufferedReader = new BufferedReader(new FileReader("E:\\Users\\"+user+"\\password.txt")); if (pass.equals(bufferedReader.readLine())){ json="{\"code\":\"200\",\"msg\":\"登录成功\"}"; }else { json="{\"code\":\"-1\",\"msg\":\"密码错误\"}"; } } } return json; } } register注册代码逻辑package MuDaoServer.api; import MuDaoServer.Controller.Annotations; import MuDaoServer.RequestData; import java.io.*; import java.util.HashMap; import static MuDaoServer.ServerMethod.fileExists; @Annotations("/api/register") public class register { public String service() throws IOException { String json = ""; //获取data的数据 String data = RequestData.data.split("\\?")[1]; System.out.println("data:"+data); //对data数据进行分析 HashMap<String, String> dataMap = new HashMap<>();//存放get请求的数据 String[] split = data.split("&"); for (String s : split){ String[] split1 = s.split("="); dataMap.put(split1[0],split1[1]); } //判断必填数据是否存在 if (dataMap.containsKey("user") && dataMap.containsKey("password")){ //判断用户是否存在 RequestData requestData = new RequestData(); if (!fileExists(requestData.getUserPath()+dataMap.get("user"))){ String path1 = requestData.getUserPath()+dataMap.get("user"); File file = new File(path1); //创建用户文件夹 boolean mkdirs = file.mkdirs(); if (mkdirs){ //创建文件 FileOutputStream out = new FileOutputStream(path1 + "\\\\\\\\password.txt"); //写入文件 out.write(dataMap.get("password").getBytes()); out.close(); json = "{\"code\":\"200\",\"msg\":\"账号注册成功\"}"; } }else { json = "{\"code\":\"-1\",\"msg\":\"该账号已存在\"}"; } }else { json = "{\"code\":\"-1\",\"msg\":\"提交参数不完整\"}"; } return json; } } 获取用户列表package MuDaoServer.api; import MuDaoServer.Controller.Annotations; import java.io.*; import java.util.Arrays; @Annotations("/api/listUser") public class listUser { public String service() throws IOException { File file = new File("E:\\Users"); File[] files = file.listFiles(); String value =Arrays.toString(files); System.out.println(value); return value; } } 有什么错误请评论交流
2023年02月02日
6 阅读
0 评论
0 点赞
2023-01-12
Java基础,学完就可以学框架了
一、计算机语言语言:是人与人之间用于沟通的一种方式。例如:中国人与中国人用普通话沟通。而 中国人要和英国人交流,就要学习英语。计算机语言:人与计算机交流的方式。 如果人要与计算机交流,那么就要学习计算机语言。 计算机语言有很多种。如:C ,C++ ,Java ,Python, Go, Ruby等。1、编程语言的发展第一代语言:机器语言,程序使用原始的01010110101编写。第二代语言:汇编语言,使用助记符(因为字符和符号)编写程序void multstore (long x,long y, long *dest) { long t = mult2(x, y) ; *dest=t; }第三代语言:高级语言,代表语言有:C, C++, Java, PHP, Python, Go, Rust, Ruby, C#等下图简要介绍了编程语言的发展2、为什么选择Java?就业率市面上应用型系统几乎都是java开发二、Java快速入门1、Java语言的发展(了解)1991年4月,由James Gosling博士领导的绿色计划(Green Project)开始启动,此计划最初的目标是开发一种能够在各种消费性电子产品(如机顶盒、冰箱、收音机等)上运行的程序架构。这个计划的产品就是Java语言的前身:Oak(得名于James Gosling办公室外的一棵橡树)。Oak当时在消费品市场上并不算成功,但随着1995年互联网潮流的兴起,Oak迅速找到了最适合自己发展的市场定位并蜕变成为Java语言。 2、Java技术体系平台JavaSE:包括了Java核心API,也是我们第一阶段的重点Java EE:企业级开发,包含Servlet, Jsp等,主要用于web开发,第二阶段的学习重点Java ME:主要运行在移动终端,如手机,Pad等。Java Card:运行在小内存设备上,如智能门锁。3、Java应用领域企业级应用:主要有企业级软件(如银行系统,OA系统,政务系统等)、各种类型的网站后台服务(如淘宝,京东等)。Java的安全机制以及 它的跨平台的优势,使它在分布式系统领域开发中有广泛应用。应用领域包括金融、电 信、交通、电子商务等。Android平台应用:Android应用程序使用Java语言编写。Android开发水平的高低 很大程度上取决于Java语言核心能力是否扎实。大数据平台开发:各类框架有Hadoop,spark,storm,flink等,就这类技术生态 圈来讲,还有各种中间件如flume,kafka等等 ,这些框架以及工具大多数 是用Java编写而成,但提供诸如Java,scala,Python,R等各种语言API供编程。移动领域应用:主要表现在消费和嵌入式领域,是指在各种小型设备上的应用,包括手 机、PDA、机顶盒、汽车通信设备等。4、Java语言的特性面向对象:Java是一个面向对象的语言。跨平台(一处编译,到处执行):Java编译成字节码,然后通过解释器运行。这一特点让Java具备跨平台特性。安全性高:Java是一款安全性相对较高的语言,比如去掉了指针,这一就避免伪造指针操作内存,进而提升了安全性。简易学:去掉了指针、多继承、手动垃圾回收等,对于开发人员非常友好、简单、使得学习成本降低。多线程:提供更加简易的实现多线程的编写方式。5、Java各版本介绍以及我们该使用哪个版本6、JDK安装配置检查本机是否安装java环境:通过打开windows的dos窗口,然后输入java,回车之后出现以下信息表示本机没有Java环境安装java环境步骤:第一步:去官网: https://www.oracle.com/java/technologies/downloads/ 下载对应的jdk版本以下载jdk11为例:下载时所需账号密码:账号: 13707259624@163.com 密码:Java123456第二步:双击开始安装Java环境,一致点击下一步即可,唯一点就是记录自己安装时选择的安装目录。第三步:安装完毕,再次打开DOS窗口,输入命令java,显示以下信息表示java环境安装成功理论上有了java环境我们就可以编写java程序。编写完成之后就可以运行该程序,但是在编译java程序时出现以下信息,该信息是由于安装java环境后没有配置环境变量所致。所以需要配置环境变量,让系统找到avac编译器。windows配置java环境变量步骤在本机上找到以下内容此时就可以在环境变量窗口配置java环境变量把JAVA_HOME添加到path里添加完path后点击下图中的确定按钮最后关闭之前打开dos窗口重新打开。输入javac,如果有如下信息则表示本机已经可以使用javac编译器有了环境我们就可以真正开始编写java程序。7、第一个java程序//第一个java程序 //public公共的,表示其他java程序可以访问到该类 //class 类 //HelloWorld 类名 public class HelloWorld { //以下代码片段叫做方法,方法名字叫做 main,所有的java程序,入口都叫main。 //main之后括号内的内容叫做 参数,String表示参数类型,args表示参数名。 //public static void 是java语法规定的关键字,此处可以暂时按照字面意思理解。 public static void main(String[] args) { //System是java自带的一个类,也可以叫做工具。作用是用于在控制台输出内容。 //println()是System.out下的一个方法,里边被英文输入法下双引号包裹的内容叫做参数,也叫字符串。 //以下内容先背下来。 System.out.println("Hello World!"); } }怎么运行java程序第一个程序需要注意的点:一个java文件建议只写一个类,并且该类使用public修饰。一个类会对应生成一个class文件。6、介绍jdk、jre、jvm三者之间的关系。java里的注释注释:写道程序里,对程序逻辑进行描述,但是不会参与程序的运行java里有三种注释方式:单行注释,多行注释,文档注释。DOS常用命令:切换盘符:磁盘名: 进行切换,如要切换到d磁盘 d:或D:进入文件夹:cd 文件夹名回到上级目录:cd ..列出当前目录下有哪些文件:dir清屏命令:cls常用的DOS命令列表dir : 列出当前目录下的文件以及文件夹md : 创建目录rd : 删除目录cd : 进入指定目录cd.. : 退回到上一级目录cd\: 退回到根目录del : 删除文件exit : 退出 dos 命令行补充:echo javase>1.doc常用快捷键← →:移动光标↑ ↓:调阅历史操作命令Delete和Backspace:删除字符8、开发工具的安装:notepad++9、Homework编写第一个HelloWorld程序,并给程序加上注释说明程序的作用按照身份证的上的格式,输出个人信息。编写一个程序,在控制台打印输出一下图案public static void main(String[] args) { System.out.println("千千千千千千 锋 教 教 育"); System.out.println(" 千千 锋锋 教 教 育育"); System.out.println(" 千千 锋 锋 教 教 育 育"); System.out.println(" 千千 锋 锋 教 教 育 育"); System.out.println(" 千千 锋锋锋锋锋锋 教 教 育育育育育育"); System.out.println("千 千千 锋 锋 教 教 育 育"); System.out.println("千 千千 锋 锋 教教 育 育"); System.out.println("千千千千 锋 锋 教 育 育"); }三、Java语法基础1、变量1.1 标识符命名规范,可以是字母、数字、下划线等字符,但是不能以数字开头。标识符的取名要做到见名知意。1.2 变量概念为了操作数据,需要把数据存放到内存中。所谓内存在程序看来就是一块有地址编号的连续的空间,数据放到内存中的某个位置后,为了方便地找到和操作这个数据,需要给这个位置起一个名字。编程语言通过变量这个概念来表示这个过程。声明一个变量,比如int a,其实就是在内存中分配了一块空间,这块空间存放int数据类型,a指向这块内存空间所在的位置,通过对a操作即可操作a指向的内存空间,比如a=5这个操作即可将a指向的内存空间的值改为5。之所以叫“变”量,是因为它表示的是内存中的位置,这个位置存放的值是可以变化的。1.3 变量名命名规范,可以是字母、数字、下划线等字符,但是不能以数字开头。标识符的取名要做到见名知意。1.2 变量类型类型决定了操作系统需要给这个变量分配多大的内存空间。1.3 变量的值变量值就是具体存储到内存空间中的值。1.4 变量的类型1.4.1 局部变量定义在方法体中,或方法参数中,以及代码块中的变量。1.4.2 全局变量1.4.2 .1 静态变量定义在方法或代码块之外,并且使用关键字static 修饰的变量,静态变量属于类。1.4.2.2 成员变量定义在方法或代码块之外的变量,属于对象,需要通过创建对象,然后适用对象的引用去访问该变量。/* 变量的使用 */ //要写一个java程序,第一就是新建一个类 public class Variable { //定义静态变量 static int b = 123; //定义成员变量 int c = 234; //编写程序入口main方法 public static void main(String[] args) { //定义一个变量,变量的三要素:数据类型、变量名、变量值 int a = 100; System.out.println(100);//在屏幕输出具体的值 System.out.println(a);//输出变量 //获取静态变量的方式,通过 “类名.静态变量名” 的形式访问 System.out.println(Variable.b); //应为b属于类Variable,而我们的main也是写在Variable类中,所以访问该静态变量可以省略前边的类名 System.out.println(b); //访问非静态的全局变量,需要通过对象才能访问 //首先需要通过关键字new创建包含了这个变量的类的一个对象, Variable vr = new Variable(); System.out.println(vr.c); } }演示变量的作用域:/* 变量的使用 */ //要写一个java程序,第一就是新建一个类 public class Variable { //定义静态变量 static int b = 123; //定义成员变量 int c = 234; //编写程序入口main方法 public static void main(String[] args) { //定义一个变量,变量的三要素:数据类型、变量名、变量值 int a = 100; System.out.println(100);//在屏幕输出具体的值 System.out.println(a);//输出变量 //获取静态变量的方式,通过 “类名.静态变量名” 的形式访问 System.out.println(Variable.b); //应为b属于类Variable,而我们的main也是写在Variable类中,所以访问该静态变量可以省略前边的类名 System.out.println(b); //访问非静态的全局变量,首先需要通过关键字new创建包含了这个变量的类的一个对象, Variable vr = new Variable(); System.out.println(vr.c); //访问类 V 里的变量 abc 和 v1 //访问 abc,使用 类名.变量名 System.out.println("V的静态变量abc: " + V.abc); //访问 v1, 因为是非静态成员变量,所以需要基于类V创建对象 V v = new V(); System.out.println("V的非静态变量v1: " + v.v1);//加号表示把字符串和后边变量的值拼接起来。 //如果现在类Variable里访问 V类里的man方法中的ss变量,是没有办法可以访问的。 //如果man方法和我们的main方法处在同一个类中,此时想要访问man方法中的ss变量也是不能访问到的。 } //定义一个方法,并且在方法内定义一个局部变量 public static void man() { //局部变量 int ss = 132; } } //定义一个新类。这种方式不推荐,一个类我们是建议定义在一个单独的文件中,此处为了演示 class V { //定义一个静态变量 public static int abc = 120; //定义一个成员变量 public int v1 = 520; //定义一个方法,并且在方法内定义一个局部变量 public static void man() { //局部变量 int ss = 132; } }注意:关于变量的作用域会在讲解方法章节做具体介绍。2、数据类型数据类型用于对数据归类,以便于理解和操作。对Java语言而言,有如下基本数据类型。世界万物都是由元素周期表中的基本元素组成的,基本数据类型就相当于化学中的基本元素,而对象就相当于世界万物。byte : 字节,1个字节, 8位short :短整型,2个字节,16位int : 整型, 4个字节, 32位long : 长整型, 8个字节,64位float : 浮点型, 4个字节,32位double :浮点型, 8个字节,64位char : 字符,2个字节, 16位boolean : 布尔, 根据实际场景确定占一个字节或者int类型的大小boolean 类型被编译成 int 类型来使用,占 4 个 byte 。boolean 数组被编译成 byte 数组类型,每个 boolean 数组成员占 1 个 byte 。在 Java 虚拟机里,1 表示 true ,0 表示 false 。这只是 Java 虚拟机的建议。可以肯定的是,不会是 1 个 bit 。2.1 整数有4种整型byte、short、int、long,分别有不同的取值范围2.2 浮点数(小数)有两种类型单精度浮点数:float 双精度浮点数:double有不同的取值范围和精度2.3 字符char,表示单个字符2.4 布尔boolean,表示真(true)或假(false),当变量定义为boolean类型时占用四个字节,当定义布尔数组时数组内的每一个布尔元素占一个字节。八种基本数据类型定义变量代码演示:/* 数据类型 */ //定义一个新类 public class DataType { //编写程序入口main方法 public static void main(String[] args) { //定义一个布尔类型的变量 boolean a = true; System.out.println("布尔变量:a = " + a); //定义字符类型的变量 //字符和字符串的区别:字符串属于引用类型,字符属于基本数据类型,定义方式也不一样,字符串使用双引号,字符使用单引号。 char ch = 'A'; //char ch1 = 'abc';//会报错 char ch2 = 64; System.out.println("字符 ch = " + ch); System.out.println("字符 ch2 = " + ch2); //定义整数类型 byte b = 123; short s = 234; int i = 102; long l = 888L; System.out.println("b = " + b + " s = " + s + " i = " + i + " l = " + l); //定义小数,也叫做浮点数。 float f = 1.0F;//单精度 double d = 1.0;//双精度 System.out.println("f = " + f + " d = " + d); } }2.5 转义字符java里在一些字符前加上“\"来表示转义字符。/*转义字符*/ //定义类 public class EscapeCharacter { //程序入口 public static void main(String[] args) { //用字符表示换行 \n System.out.println("Hello \n World"); //制表符 \t System.out.println("\tHello World"); //双引号 \"\" System.out.println("\"Hello World\""); char ch = 7;//响铃 System.out.println(ch); } }2.6 字符集常用字符集ascii 1(byte) 0 ~127 latin1 1(byte) 128 ~ 255 兼容ascii gb2312 2(byte) 兼容ascii gbk 2(byte) 兼容ascii utf-8 3(byte),utf-8字符集是Unicode编码标准的一个具体实现 兼容ascii练习定义8中基本数据类型的变量,不要给它们赋值,然后打印查看其默认值3、数据的表示与和处理在十进制中,如:123,可以表示为:1 10^2 + 2 10^1 + 3 * 10^0,位权上的值从右到左分别是10^0, 10^1, 10^2.....10^n-1。数值位上的值有十种可能0~9。在二进制中,如:1010,可以表示为:12^3 + 02^2 + 12^1 + 02^0 = 8 + 0 + 2 + 1 = 10;位权上和十进制类似。只是数值位上的值只有两种0或1。3.1 整数3.1.1 正数十进制与二进制转换方法:通过除2取余后倒序排列即可得到十进制的二进制例:十进制13,计算二进制?①13除2,商是6,余数是1;②6除2,商是3,余数是0;③3除2,商是1,余数是1;④1除2,商是0,余数是1。倒序排列余数④③②①---> 1101二进制转十进制二进制:1010;十进制:1*2^3 + 0*2^2 + 1*2^1 + 0*2^0 = 8 + 0 + 2 + 1 = 10;二进制转十六进制二进制四个位对应十六进制一位,十六进制表示法都有一个前缀:0x;用数字和字母0-F表示0-15,其中0-9对应十进制0-9,10-15对应A-F,字母不区分大小写。二进制:1010 0011;十六进制:0xA3 或 0xa3十六进制转二进制其他进制之间的转换十进制转十六进制通过除16取余数建议把十进制转成二进制直接通过二进制转成16二进制每四位对应一位十六进制,二进制每三位对应一位八进制二进制、十进制和十六进制转换表:练习将0x39A7F8转换为二进制 :0011 1001 1010 0111 1111 1000将二进制1100 1001 0111 1011转换为十六进制0xC97B3.1.2 负数十进制表示负数直接在数值前加一个符号“-”即可。例如123,负数就是 -123,但二进制如何表示负数呢?其实概念是类似的,二进制使用最高位表示符号位,用1表示负数,用0表示正数。但哪个是最高位呢?整数有4种类型byte、short、int、long,分别占1、2、4、8个字节,即分别占8、16、32、64位,每种类型的符号位都是其最左边的一位。为方便举例,下面假定类型是byte,即从右到左的第8位表示符号位。但负数表示不是简单地将最高位变为1,比如:byte a=-1,如果只是将最高位变为1,二进制应该是10000001,但实际上,它应该是11111111。byte a=-127,如果只是将最高位变为1,二进制应该是11111111,但实际上,它却应该是10000001。和我们的直觉正好相反,这是什么表示法?这种表示法称为补码表示法,而符合我们直觉的表示称为原码表示法,补码表示就是在原码表示的基础上取反然后加1。取反就是将0变为1,1变为0。负数的二进制表示就是对应的正数的补码表示,十进制转二进制-1:1的原码表示是00000001,取反是11111110,然后再加1,就是11111111。-2:2的原码表示是00000010,取反是11111101,然后再加1,就是11111110。-127:127的原码表示是01111111,取反是10000000,然后再加1,就是10000001。二进制转十进制给定一个负数的二进制表示,要想知道它的十进制值,可以采用相同的补码运算。10010010,首先取反,变为01101101,然后加1,结果为01101110,它的十进制值为110,所以原值就是-110。直觉上,应该是先减1,然后再取反,但计算机只能做加法,而补码的一个良好特性就是,对负数的补码表示做补码运算就可以得到其对应正数的原码,正如十进制运算中负负得正一样。所以一个byte型的数据能表示的范围是:1000 0000 到 0111 1111 ----> -128 到 127,其他数据类型类似3.2 整数二进制运算计算机是只能做加法的,1 - 1 会转成 1 + (-1)用符合直觉的原码表示,1-1的结果是-2,计算结果是不对的。换成补码表示,结果就是0理解了二进制的加减之后我们就能解释为什么在程序里两个数相加减会得到一个预料外的值System.out.println((byte)(127 + 1));//-128正数的,原码和补码反码都是同一个数。原码取反就是等于反码,反码加1可得补码。3.3 小数的二进制表示(了解)小数,在编程语言里又叫浮点数。为什么叫浮点数。为什么要叫浮点数呢?这是由于小数的二进制表示中,表示那个小数点的时候,点不是固定的,而是浮动的。比如:float f = 0.1f * 0.1f; System.out.println(f);结果是0.010000001,为什么是这样?这里并不是计算机计算出了错,而是计算机本来就没法精确的表示许多小数。同样,在我们的十进制的世界里,也存在不能精确表示的小数,如三分之一。那么哪些小数是可以精确表示的?十进制:可以表述为10的多少次方和的数就能精确表示,比如12.345,实际上表示的是1× 10+2× 1+3× 0.1+4× 0.01+5× 0.001二进制:可以精确表示为2的某次方之和的数可以精确表示,其他数则不能精确表示java里浮点数的表示遵循 IEEE-754标准,其中单精度和双精度使用不同位表示,如下使用一个单精度浮点数列子来说明:十进制小数 123.456,使用科学计数法可以写成 1.23456 * 10的二次方同理,二进制也可以使用类似方法表示一个数,如十进制小数:7.125,它的二进制小数等于0111.001,因为计算机不能存储点,所以肯定不能把0111.001存储到计算机中。我们使用类似十进制的科学计数法表示二进制0111.001可以写成1.11001 * 2^2。根据上图,s(sign)表示符号位,而7.125是正数,所以,s为0,1.11001 * 2^2表达式中2的阶是2,所以exp=基数+阶码=127 + 2 = 129,129二进制为:10000001。最后尾数部分frac,就是二进制小数点右边的数,此处是11001,frac占23位,11001占5位,就在后面补18个0,最后frac=11001000000000000000000最后7.125保存在计算机中的二进制为 s + exp + frac = 0 10000001 110010000000000000000004、赋值声明变量之后,就在内存分配了一块位置,但这个位置的内容是未知的,赋值就是把这块位置的内容设为一个确定的值。Java中基本类型、数组、对象的赋值是不同的,本节介绍基本类型的赋值,4.1 整数类型赋值(byte/short/int/long)byte b = 23; short s = 3333; int i = 8888; long l = 32323;但是,在给long类型赋值时,如果常量超过了int的表示范围,需要在常量后面加大写或小写字母L,即L或l,例如:long a = 3232343433L;之所以需要加L或l,是因为数字常量默认为是int类型。4.2 小数类型赋值(float/double)对于double,直接把熟悉的小数表示赋值给变量即可double d = 999.99;但对于float,需要在数字后面加大写字母F或小写字母f,这是由于小数常量默认是double类型。float f = 999.99f;4.3 字符类型赋值(char)字符类型char用于表示一个字符,这个字符可以是中文字符,也可以是英文字符,char占用的内存空间是两个字节。赋值时把常量字符用单引号括起来,不要使用双引号;大部分的常用字符用一个char就可以表示,但有的特殊字符用一个char表示不了。char c = 'A'; char z = '马'; char c1 = 39532; char c2 = 0x9a6c; char c3 = '\u9a6c';第1种赋值方式是最常见的,将一个能用ASCII码表示的字符赋给一个字符变量。第2种赋值方式也很常见,但这里是个中文字符,需要注意的是,直接写字符常量的时候应该注意文件的编码,比如,GBK编码的代码文件按UTF-8打开,字符会变成乱码,赋值的时候是按当前的编码解读方式,将这个字符形式对应的Unicode编号值赋给变量,“马”对应的Unicode编号是39 532,所以第2种赋值方式和第3种赋值方式是一样的。第3种赋值方式是直接将十进制的常量赋给字符。第4种赋值方式是将十六进制常量赋给字符,第5种赋值方式是按Unicode字符形式。所以,第2、3、4、5种赋值方式都是一样的,本质都是将Unicode编号39 532赋给了字符。4.4 真假类型赋值(boolean)真假(boolean)类型很简单,直接使用true或false赋值,分别表示真和假boolean b = true; b = false;4.5 其他赋值4.5.1 把变量赋给变量int a = 666; int b = a;4.5.2 将运算结果赋给变量int a = 1; int b = 2; int c = 2 * a * b;4.5.3 字符串赋值String s = "Hello World";4.6 强制类型转换自动转换从小到大会进行类型的自动升级,也叫做自动转换/*低位到高位的类型转换*/ public class LowToHighCast { //定义程序入口 public static void main(String[] args) { byte bt = 12; short st = bt; int it = st;//变量赋值给变量 System.out.println(bt); System.out.println(st); System.out.println(it); float ft = st; System.out.println(ft); } }强制转换把多字节的数据赋值给少字节的变量时会报错,因为存储不下,如果非要强制存储,此时就需要显示转换。/*高位到低位的类型转换*/ public class HighToLowCast { //定义程序入口 public static void main(String[] args) { int it = 256;//变量赋值给变量 //括号里的byte就是告诉java虚拟机需要把it的数据类型转换成指定类型,转成的类型需要和接收的类型保持一致 byte bt = (byte)it; short st = (short)it; System.out.println(bt); System.out.println(st); boolean bl = true;//布尔类型没有向上和向下的转型 char cr = 'a';//因为字符最终会被保持为字符表里对应的整数,所以可以和表示整数的类型相互转换 byte be = (byte)cr; System.out.println(be); } }4.7 练习题 1、变量使用的三要素是? 2、变量的命名规则包括几点? 3、Java中常用的数据类型有哪些? 4、Java中的数据类型转换包括哪两种? 5、Java中的数据类型转需要注意哪些? 6、不转换为十进制或二进制计数下列表达式0x503c + 0x8 =0x503c - 0x40 =0x503c + 64 = 0x50ea - 0x503c =5、算术运算5.1 运算符优先级5.2 加、减、乘、除,符号分别是+、-、*、//*算术运算符*/ public class ArithmeticOperator { public static void main(String[] args) { //+ int数据类型和int数据类型相加 int a = 12; int b = 3; int c = a + b;//两个变量相加 int d = a + 20;//变量和常量相加 int e = 15 + 150;//常量与常量相加 //被双引号包起来的部分会在dos窗口或控制台原样输出 System.out.println("a + b = " + c); System.out.println("a + 20 = " + d); System.out.println("15 + 150 = " + e); //+ int数据类型和非int数据类型相加 byte bt = 101; byte bt1 = (byte)(bt + a);//bt和a相加的结果是一个int类型,所以这里赋值给byte型需要强制类型转换 System.out.println(a + bt); int it = bt + bt1;//因为bt和bt1都是byte,只占一个字节,相加后赋值给int型属于从低位到高位,会自动转换类型 System.out.println(it); //+ int数据类型和浮点数数据类型相加 float ft = 3.0F; //ft变量的值3.0F中的F千万不可省略,省略后相加结果会被当做double类型。省略后使用强制类型转换也会报错 float ft1 = ft + a; System.out.println(ft1); //+ 加号能使用到布尔类型变量 boolean bl = true; //int a = bl + 10;//会报错,不能用于布尔变量 //+ 加号用于字符串,起拼接的作用 String s = "hello"; String s1 = "world"; String s2 = s + s1; System.out.println(s2); } }以上代码演示了加号的使用,乘除减法的使用类似,只需要把以上代码里的+变成想要符号即可,但是对于字符串其他符号就不能使用。注意:运算符中只有加号能够用于字符串,起着拼接左右字符串的作用,其他的算术运算不能用于字符串。5.3 % 数学中的求余数例如,5%3是2,10%5是0//% 取余操作 7 除 2 等于 3,余数是 1 System.out.println("7 % 2 = " + 7 % 2); int a = 15; int b = 6; int c = a % b; System.out.println(c); System.out.println(15.0F % 6.0F);5.4 自增(++)/自减(--)自增/自减是对自己做加1或减1操作,但每个都有两种形式,一种是放在变量后,例如a++、a--,另一种是放在变量前,例如++a、--a。如果只是对自己操作,这两种形式也没什么差别,区别在于还有其他操作的时候。放在变量后(a++)是先用原来的值进行其他操作,然后再对自己做修改,而放在变量前(++a)是先对自己做修改,再用修改后的值进行其他操作。 // ++自增,当++出现在变量左边就是先自增在使用,出现右边先使用后自增 --自减和自增一样。 int it = 1; System.out.println(++it); int it1 = 10; System.out.println(it1++); float ft = 3.1F; System.out.println(++ft); int it2 = 9; System.out.println(--it2); System.out.println(it2--); //System.out.println(--80);自增运算符不能直接使用到常量上 //求下列程序的结果 int x = 10; int y = 8; System.out.println(x++ - --x + y-- + x++ - --y);//12 System.out.println(x);//11 System.out.println(y);//6在大多数计算机中,CPU运行时是以4个字节来处理数据的。因此当变量小于4个字节时,并且进行算数运算,会发生整型提升,这就是 byte c = a + b会报错的原因, byte d = 1 + 2不会报错是因为 1 和 2 是常量。5.5 字符串中使用加号 //+ 加号用于字符串,起拼接的作用 String s = "hello"; String s1 = "world"; String s2 = s + s1; System.out.println(s2);5.6 +=, -=, *=, /=, %=/*算术运算符*/ public class ArithmeticOperator { public static void main(String[] args) { //+=加等于, -=, *=, /=, %= int a = 10; a += 5;//a = a + 5; System.out.println("a += 5 : " + a); int b = 20; b -= 21; // b = b - 21; System.out.println("b -= 21 : " + b); int c = 30; c *= 2;//c = c * 2; System.out.println("c *= 2 : " + c); int d = 40; d /= 20;//d = d / 20 System.out.println("d /= 20 : " + d); int e = 15; e %= 7; //e = e % 7 System.out.println("e %= 7 : " + e); } }5.7 加、减、乘、除注意事项5.7.1 溢出运算时要注意结果的范围,使用恰当的数据类型。两个正数都可以用int表示,但相乘的结果可能就会超出,超出后结果会令人困惑int a = 2147483647 * 2;//2147483647是int能表示的最大值 short a1 = 255; a1 += 1; byte c = (byte)(a1); System.out.println(c);a的输出结果是-2,因为计算的结果超出了int能表示的范围,所以出现了溢出。为什么会是-2如何避免这种溢出?要避免这种情况,我们的结果类型应使用long,但只改为long也是不够的,因为运算还是默认按照int类型进行,需要将至少一个数据表示为long形式,即在后面加L或l,下面这样才会出现期望的结果int a = 2147483647 * 2L;5.7.2 舍弃整数相除不是四舍五入,而是直接舍去小数位double d = 8 / 5;//1.0 整数相除不是四舍五入,而是直接舍去小数位 double d1 = 8.0 / 5.0;//1.6 System.out.println(d); System.out.println(d1);结果是2而不是2.5,如果要按小数进行运算,需要将至少一个数表示为小数形式,或者使用强制类型转化,即在数字前面加(double),表示将数字看作double类型,如下所示任意一种形式都可以double d1 = 8 / 5.0; double d2 = 8 / (double)5;5.7.3 浮点数计算结果不精确无论是使用float还是double,进行运算时都会出现一些非常令人困惑的现象float f = 0.1f * 0.1f; System.out.println(f); double d = 0.1 * 0.1; System.out.println(d);输出是:0.0100000010.010000000000000002这是怎么回事?看上去这么简单的运算,计算机计算的结果怎么不精确呢?但事实就是这样,究其原因,我们需要理解float和double的二进制表示。6、关系运算符关系运算符用来比较运算符左右两边的数据,返回的结果只会是boolean型的值。true/false暂时无法在飞书文档外展示此内容大于(>)大于等于(>=)小于(<)小于等于(<=)等于(==)不等于(! =)/*关系运算符*/ public class RelationOperator { public static void main(String[] args) { //==, !=, >, <, >=, <= System.out.println(5==6);//false System.out.println(5==5);//true System.out.println(5!=6);//true System.out.println(5>6);//false System.out.println(5<6);//true System.out.println(5>=6);//false System.out.println(5<=6);//true //可以用于变量 int a = 123; System.out.println(a >= 234);//false //用于表达式 System.out.println( (15 + 20) != (3 * 20) ); //用于布尔型数据,布尔变量只能使用比较运算符的等于和不等于。 System.out.println(true == false); } }7、逻辑运算暂时无法在飞书文档外展示此内容与(&):两个都为true才是true,只要有一个是false就是false,可用于操作二进制位或(|):只要有一个为true就是true,都是false才是false,可用于操作二进制位非(!):针对一个变量,true会变成false, false会变成true;异或(^):两个相同为false,两个不相同为true;短路与(&&):和&类似;短路或(||):与|类似。需要注意的是&和&&,以及|和||的区别。如果只是进行逻辑运算,它们也都是相同的,区别在于同时有其他操作的情况下,:/*逻辑运算符*/ public class LogicOperator { public static void main(String[] args) { //与 &, 符号左右两边的表达式同时位true,整体返回true,其他情况都是false boolean result1 = (5 > 3) & (5==6); System.out.println(result1); //或 | 符号两边都为false时结果返回false,其他情况都返回true boolean result2 = (5 > 3) | (5==6); System.out.println(result2); //非 ! System.out.println(true); System.out.println(!true); System.out.println(!((3+2) == 6)); System.out.println("----------------&&-------||-------------"); //与 &&, 符号左右两边的表达式同时位true,整体返回true,其他情况都是false boolean result3 = (5 > 3) && (5==6); System.out.println(result3); //或 || 符号两边都为false时结果返回false,其他情况都返回true boolean result4 = (5 > 3) || (5==6); System.out.println(result4); //以下内容演示短路或与 或(|)的区别 boolean a1 = true; int b1 = 0; boolean flag1 = a1 | b1++>0; System.out.println("b1 : " + b1); boolean a2 = true; int b2 = 0; boolean flag2 = a2 || b2++>0; System.out.println("b2 : " + b2); System.out.println('a' | 'b');//会计算两个字符的二进制 System.out.println(true ||false); //以下内容演示短路与(&&) 与 与(&)的区别 boolean a3 = false; int b3 = 0; boolean flag3 = a3 & b3++ > 0; System.out.println("b3 : " + b3); int b4 = 0; boolean flag4 = a3 && b4++ > 0; System.out.println("b4 : " + b4); System.out.println(15 & 8);//会与左右两个数的二进制 } }按位与&:两位都为1才为1。按位或|:只要有一位为1,就为1。按位取反~:1变为0,0变为1。按位异或^:相异为真,相同为假。class BitDemo { public static void main(String[] args) { int bitmask = 0x1; int val = 0x0; // prints "2" System.out.println(val & bitmask); System.out.println(val | bitmask); System.out.println(val ^ bitmask); System.out.println(~bitmask); System.out.println(~val); } }8、位运算Java 7之前不能单独表示一个位,但可以用byte表示8位,用十六进制写二进制常量。比如,0010表示成十六进制是0x2,110110表示成十六进制是0x36。位运算有移位运算和逻辑运算。移位有以下几种左移:操作符为<<,向左移动,右边的低位补0,高位的就舍弃掉了,将二进制看作整数,左移1位就相当于乘以2。无符号右移:操作符为>>>,向右移动,右边的舍弃掉,左边补0。有符号右移:操作符为>>,向右移动,右边的舍弃掉,左边补什么取决于原来最高位是什么,原来是1就补1,原来是0就补0,将二进制看作整数,右移1位相当于除以2。/*位运算*/ public class BitOperator { public static void main(String[] args) { int a = 8; System.out.println(a / 2);//4 //右移 System.out.println(a >> 1);//4 //左移 System.out.println(1 << 5);//32, 0000 0001 << 5 0010 0000 = 32; System.out.println(-10 >> 2);//计算过程如下。会先计算-10的二进制补码,再把补码右移两位 //0000 0000 0000 0000 0000 0000 0000 1010 //1111 1111 1111 1111 1111 1111 1111 0101 + 1 //1111 1111 1111 1111 1111 1111 1111 0110 //001111 1111 1111 1111 1111 1111 1111 01 //在c语言有算术右移和逻辑右移的区别。java里只有逻辑右移 // >>> System.out.println(8 >>> 2); //-1的移动 System.out.println(-1 << 2); //按位取反 ~,把数值二进制位上的每一位取反 byte bt = 10; System.out.println(~bt); //异或 ^,两个二进制位上只要相同结果就是0,不同为1 byte bt1 = 7; byte bt2 = 8; System.out.println(bt1 ^ bt2); System.out.println(-1 ^ -1);// 1111 1111 ^ 1111 1111 } }9、三目运算符/*三目运算符*/ public class TriadOperator { public static void main(String[] args) { //第一种写法 int a = false ? 1 : 2; System.out.println(a); //第二种写法 int b = (10 > 120) ? 1 : 2; System.out.println(b); //第三种写法,在三目运算符里调用方法 int invokeMethod = (10 > 120) ? 1 : ma(); System.out.println(invokeMethod); //第四种写法,在三目运算符里做计算 int ath = (10 > 120) ? (3+2-5*0) : (8 ^ 7); System.out.println(ath); //第五种写法,在三目运算符里嵌三目运算符 int nest = (10 < 120) ? (5 == 5) ? 1 : 2 : (8 ^ 7); System.out.println(nest); //第六种写法,在三目运算符里返回字符串 String str = (10 > 120) ? (5 != 5) ? "hello" : "world" : "ikun"; System.out.println(str); } public static int ma() { return 50; } }10、Scanner类Scanner类属于java类库,java里的输入输出。输入使用Scanner类。输出System.out类。在java语言里要使用类,需要首先创建该类的一个对象,创建对象使用关键字new10.1 创建Scanner对象10.2 调用Scanner类的next...方法next()方法会阻塞当前程序的执行流程,等待用户输入数据并按下回车键//告诉程序Scanner类放在java里的什么位置 import java.util.Scanner; /*java里输入输出*/ public class ScannerDemo { public static void main(String[] args) { //输出, 使用类库中System类 System.out.println("hello world"); //输入,首先需要找到输入类在java类库中的位置 //找到输入类之后需要创建该类的对象 Scanner scanner = new Scanner(System.in); System.out.println("请输入:"); //调用Scanner类的方法来读取内容 String str = scanner.next();//该方法读取的值都会被当成字符串解析 System.out.println("你输入的数据是 : " + str); //读取scanner输入的整数 System.out.println("请输入整数:"); String a = scanner.nextInt();//方法调用必须是方法名加括号 System.out.println("a : " + a); } }10.3 hasNext(String w)方法//告诉程序Scanner类放在java里的什么位置 import java.util.Scanner; /*java里输入输出*/ public class ScannerDemo { public static void main(String[] args) { //输出, 使用类库中System类 System.out.println("hello world"); //输入,首先需要找到输入类在java类库中的位置 //找到输入类之后需要创建该类的对象 Scanner scanner = new Scanner(System.in); System.out.println("请输入:"); //调用Scanner类的方法来读取内容 //next()方法会阻塞当前程序的执行流程,等待用户输入数据并按下回车键 String str = scanner.next();//该方法读取的值都会被当成字符串解析 System.out.println("你输入的数据是 : " + str); //读取scanner输入的整数 System.out.println("请输入整数:"); int a = scanner.nextInt();//方法调用必须是方法名加括号 System.out.println("a : " + a); System.out.println("请输入:"); //只有子键盘输入的内容等于hasNext()括号内的内容时,结果才会是true。否则都是false System.out.println(scanner.hasNext("0")); } }作业1、00101010 | 00010111语句的执行结果为2、00101010 & 00010111语句的执行结果为3、37.2%10的运算结果为 7.24、定义一个华氏度,转换成相应的摄氏度输出。(转换规则:摄氏度=5/9*(华氏度-32)) 5、定义一个三位整数,分别输出其个位、十位和百位6、定义一个四位整数,分别输出其个位、十位和百位、千位7、完成打印输出Java所有基本数据类型及所占字节数,格式如效果图。 类型 所占字节 取值范围 占多少位 byte 1字节 -2^7~2^7-1 8位 short 2字节 -2^15~2^15-1 16位 int 4字节 -2^31~2^31-1 32位 ...8、从键盘输入三角形的底和高,并输出三角形的面积。9、从控制台输入学员王浩3门课程(Java、SQL、Php)成绩,编写程序实现 (1)Java课和SQL课的分数之差 (2)3门课的平均分10、定义两个变量int a = 10 int b = 20 ,交换两个变量的值int a = 10, b = 20; int c = a; a = b; b = c;11、选做题某个公司采用公用电话传递数据,数据是四位的整数,在传递过程中是加密的, 加密规则如下:每位数字都加上3然后除以10的余数代替该数字, 再将第一位和第四位交换,第二位和第三位交换。 要求:键盘上输入四位号码,求加密后的号码为多少?四、流程控制程序有三种执行方式:顺序执行、分支选择执行、循环执行。条件判断里,如果if或else后边只有一行代码,那么if...else后的大括号可以省略在条件判断语句中,else语句是可选的,else不能脱离if独立存在。1、条件判断1.1、if语法结构 if(条件表达式) { //程序 } 条件表达式返回的结果必须要是布尔类型的值。 /*if语句*/ import java.util.Scanner; public class IfDemo { public static void main(String[] args) { //创建键盘输入对象 System.out.println("请输入年龄:"); Scanner sc = new Scanner(System.in); //读取键盘输入的年龄 int age = sc.nextInt(); //判断此人年龄是否大于18 if(age > 18) { System.out.println("请进"); } if(age < 18 && age > 0) { System.out.println("未成年"); } System.out.println("程序结束"); } }1.2、if...else语法结构 if(条件表达式) { //程序 } else { //程序 } 注意:if和else两者里的代码一定会执行一个,不会出现都执行或者都不执行的情况 import java.util.Scanner; public class IfDemo { public static void main(String[] args) { //创建键盘输入对象 System.out.println("请输入年龄:"); Scanner sc = new Scanner(System.in); //读取键盘输入的年龄 int age = sc.nextInt(); //判断此人年龄是否大于18 if(age > 18) System.out.println("请进"); //System.out.println("Test"); else System.out.println("get out"); System.out.println("程序结束"); } }1.3、多重if语法结构 if(条件判断) { //代码块 } else if(条件判断) { //代码块 } else if(条件判断) { //代码块 } else if(条件判断) { //代码块 } else { //代码块 } import java.util.Scanner; public class IfDemo { public static void main(String[] args) { //创建键盘输入对象 Scanner sc = new Scanner(System.in); //需求,超市会员积分大于3000,可以享受6折优惠,大于2000小于3000享受8折,大于800小于2000可享受9折。 //其他情况享受9.5折 //定义一件商品的价格 double price = 699.0; //定义一个变量记录用户的积分 System.out.println("请输入你的积分:"); int score = sc.nextInt(); if(score > 3000) { System.out.println("6折后的价格是:" + (699.0 * 0.6)); } else if(score > 2000 && score < 3000) { System.out.println("8折后的价格是:" + (699.0 * 0.8)); } else if(score > 800 && score < 2000){ System.out.println("9折后的价格是:" + (699.0 * 0.9)); } else { System.out.println("9.5折后的价格是:" + (699.0 * 0.95)); } } }随堂练习根据键盘输入一个年龄,然后判断此人是属于人的哪一个阶段,人的阶段分为:婴儿[0~1岁)、幼儿[1~5岁)、儿童[5~12)、少年[12~18)、青年[18~36)、中年[36~60)、老年[60~125)。System.out.println("请输入年龄:"); Scanner sc = new Scanner(System.in); int age = sc.nextInt(); if (age ==0 ) System.out.println("婴儿[0~1岁)"); else if (age >=1 && age < 5) System.out.println("幼儿"); else if (age >=5 && age < 12) System.out.println("儿童[5~12)"); else if (age >=12 && age < 18) System.out.println("少年[12~18)"); else if (age >=18 && age < 36) System.out.println("青年[18~36)"); else if (age >=36 && age < 60) System.out.println("中年[36~60)"); else if (age >=60 && age < 125) System.out.println("老年[60~125)"); else System.out.println("请输入0—124的年龄");1.4、嵌套if语法结构 if(条件判断) { //属于外层if的代码块 if(条件判断) { //代码块 } } public static void main(String[] args) { int a = 37; //a需要满足是偶数并且还要能够被6整数 if( (a % 2) == 0) { System.out.println("a是一个偶数"); //该数是否可以整除6 if((a % 6) == 0) { System.out.println("满足条件"); } else { System.out.println("不满足条件"); } } else { System.out.println("该数不是偶数"); //不是偶数就检查是否能够被7整除 if((a % 7) == 0) { System.out.println("能被7整除"); } else { System.out.println("不能被7整除"); } } if(true) { //if内可以不写任何代码,但是这类写法没有实际意义。 } }1.5、switch语法结构 switch(value) {//value的数据类型只能时整数类型(byte,short, char, int),String以及枚举(enum) case val1: break; case val2: break; case val3: break; default: System.out.println(); } package Stage1; import java.util.Random; //中午吃啥?听系统的! public class Lunch { public static void main(String[] args) { Random random = new Random(); int number = random.nextInt(5); switch (number){ case 0: System.out.println("中午饿着吧"); break; case 1: System.out.println("中午吃面"); break; case 2: System.out.println("中午吃粉"); break; case 3: System.out.println("中午吃饭"); break; case 4: System.out.println("中午吃卤味"); break; case 5: System.out.println("中午吃面包"); break; } } } //G System.out.println("请输入您的分数:"); Scanner scanner = new Scanner(System.in); int number = scanner.nextInt(); switch (number / 10) { case 0: case 1: case 2: case 3: case 4: case 5: System.out.println("不及格,请家长"); break; case 6: System.out.println("及格"); break; case 7: System.out.println("良好"); break; case 8: System.out.println("优秀"); break; case 9: case 10: System.out.println("奖励小红花一枚"); default: System.out.println("输入的成绩无效"); }2、循环2.1、for注意:当循环体只有一条语句时,大括号可以省略不写。语法结构 for(初始语句; 逻辑判断语句; 更新语句) { //循环体,也就是代码块。 } 初始语句: 就是一个简单的变量定义过程,如:int i = 0; 逻辑判断语句:和if圆括号内写的逻辑判断一模一样 如: i > 10; 更新语句:更新语句只要是指更新初始语句时定义的变量。 初始语句控制循环的开始,更新语句控制程序何时结束。 /*for循环*/ public class ForDemo { public static void main(String[] args) { /* for(初始语句; 逻辑判断语句; 更新语句) { 循环体,也就是代码块。 } */ int a = 10; for(int i = 0; i < 20; i++) { System.out.println("i : " + i); } } }请使用for循环计算100的累加和。int num = 0;//记录最后累加的和 for (int i = 1; i <= 100;i++){ num += i; } System.out.println("1~100的累加和为:" + num);请输出100以内的偶数,不要换行(print)。System.out.print("1-100以内的所有偶数有:" ); for (int i=1;i<=100;i++){ if (i%2==0){ System.out.print( +i + " "); } } for (int i=2;i<101;i+=2){//i+=2 自增2 System.out.print(i+" "); }嵌套for循环语法结构 for(初始语句; 逻辑判断语句; 更新语句) { //循环体 //代码 for(初始语句; 逻辑判断语句; 更新语句) { //循环体 } //代码 } /*嵌套for循环*/ public class NestForLoop { public static void main(String[] args) { //需求:在终端输出 5 x 5的*号矩形 for(int i = 0; i < 5; i++) { //不换行输出 for(int j = 0; j < 5; j++) { System.out.print(" * "); } System.out.println();//换行 } } }使用for打印九九乘法表//鲨鱼辣椒 int num = 0; for (int i = 1; i <= 9;i++){ for (int j = 1;j <= i;j++){ System.out.print(i + "*" + j + "=" + num + "\t"); } System.out.println(); }for循环的多种写法/*for循环的多种写法*/ public class ForLoopMultiType { public static void main(String[] args) { //第一种写法 for(int i = 0; i < 5; i++) { System.out.print("i = " + i + " "); } System.out.println(); //第二种 int j = 0; for(; j < 5; j++) { System.out.print("j = " + j + " "); } System.out.println(); //第三种写法 int x = 0; for(;x < 5;) { System.out.print("x = " + x + " "); x++; } System.out.println(); //第四种写法 for(;;) { System.out.println("停不下来------"); } /*for(;true;) { System.out.println("停不下来------"); }*/ } } //渡星河 //这里是控制有多少行的,乘法表有九行,所以我们的判断条件就是小于10 for(int i = 1;i<10;i++){ //这里是控制列,就是输出多少的,为什么要小于等于i,而不是10 //我简单的说一下,因为我们是顺序输出的,乘法表是一个三角形, //我们的循环次数要控制数量,不可能写个10每次都来循环9次 for(int j = 1;j<=i;j++){ //这里这样写的原因是要让乘法表对齐,可以只要第二句 //输出System.out.print(j+"×"+i+"="+(i*j)+" "); if(j==3&&i==3||j==3&&i==4){ System.out.print(" "+j+"×"+i+"="+(i*j)+" "); }else{ System.out.print(j+"×"+i+"="+(i*j)+" "); } } //打印换行 System.out.println(""); }打印三角形//Godv //固定行数 for (int i=1;i<=5;i++){ for (int q=1;q<=5-i;q++){ System.out.print(" "); } for (int j=1;j<=i*2-1;j++){ System.out.print("+"); } System.out.println(""); } //利用scanner输入行数 Scanner scanner = new Scanner(System.in); System.out.println("输入需要打印等腰三角形的行数:"); int rows = scanner.nextInt(); System.out.println("等腰三角形效果如下:"); for (int i=1; i<=rows; i++) { //这里控制的是需要打印三角行的高度,也就是行数 for (int j = 1; j <= rows - i; j++) { //这里控制的是空格,来显示出需要打印等腰三角的效果 System.out.print(" "); } for (int j = 1; j <= 2 * i - 1; j++) { //控制每层的星星个数 这里的j <= 2 * i - 1;是根据上面控制行数那个i来乘再减1 //如果是第一行就是 2 * 1 - 1 //就是输出一颗星 System.out.print("+"); } System.out.println(); }//渡星河 for (int i = 0;i<=7;i++){ //打印左边空白 for (int z =7;z>i;z--)System.out.print(" "); //打印半边三角形 for (int zs = 0;zs<i;zs++)System.out.print("*"); //打印右边 for (int ys = 1;ys<i;ys++) System.out.print("*"); //换行 System.out.println(); }打印菱形//xhb //打印等腰三角形 for (int i = 1;i <= 5;i++){ //控制三角形的层数,及高 for (int j = 0;j <= 5-i;j++){ //打印出空白三角形 System.out.print(" "); } for (int k = 1;k <=(2*i)-1;k++){ //打印出*等腰三角形 System.out.print("*"); } System.out.println(); } //打印倒等腰三角形 for (int i = 1;i <= 5;i++){ //控制三角形的层数,及高 for (int j = 0;j <= i;j++){ //打印出空白三角形 System.out.print(" "); } for (int k = 1;k <=(2*(5-i))-1;k++){ //打印出*等腰三角形 System.out.print("*"); } System.out.println(); } //Godv //两个等腰三角形来拼接 //这是上面一个 for (int i=1;i<=5;i++){ for (int t=1;t<=5-i;t++){ System.out.print(" "); } for (int j=1;j<=i*2-1;j++){ System.out.print("+"); } System.out.println(""); } //这是下面一个 for (int i=1;i<=5;i++){ for (int t=1;t<=i+1-1;t++){ System.out.print(" "); } for (int j=7;j>=i*2-1;j--){ System.out.print("+"); } System.out.println(""); }2.2、while//for循环的第三种写法 int x = 0; for(;x < 5;) { System.out.print("x = " + x + " "); x++; } while循环的语法结构 while(逻辑表达式) { //循环体 } /*while循环*/ public class WhileLoop { public static void main(String[] args) { //需求:使用while循环计算100以内的偶数累加和 //定义变量接收累加后的结果 int sum = 0; //初始表达式 int even = 2; while(even < 101) {//逻辑判断语句 sum += even; //更新语句 even += 2; } System.out.println("结果 : " + sum); } }2.3、do while语法结构 do { //循环体 } while(条件判断语句); /*do...while循环*/ public class DoWhileLoop { public static void main(String[] args) { //需求:使用do...while循环计算100以内的偶数累加和 //记录累加的结果 int sum = 0; //初始语句 int count = 1; do { //打印偶数 if( (count % 2) == 0 ) sum += count; count++;//更新语句 } while(count < 101);//条件判断 System.out.println("sum = " + sum); } }循环互相嵌套/*九九乘法表*/ public class NestForWhileDoWhile { public static void main(String[] args) { for(int i = 1; i <= 9; i++) { int x = 1; while(x <= i) { System.out.print("-----------"); if(i > 1)System.out.print("--"); x++; } System.out.println(); int j = 1; do { System.out.print(String.format("|%s x %s = %s| ", j, i, (j*i))); j++; } while(j <= i); System.out.println(); } } }2.5、死循环通常用在实现服务器public class NestForWhileDoWhile { public static void main(String[] args) { /*for(;;) { System.out.println("炸裂"); }*/ /*while(true) { System.out.println("炸裂"); }*/ do { System.out.print("doger"); } while(true); } }2.6、关键字break强行终止当层循环for(int i = 0; i < 100; i++) { //当i等于50时使用break终止循环 if(i == 50)break; System.out.println(" i : " + i); }break label跳转到label标识的位置 one: for(int i = 0; i < 100; i++) { two: for (int j = 0; j < 50; j++) { if(j == 20)break two; System.out.println("j = " + j); } }continue结束本次循环继续执行下一次循环for(int i = 0; i < 100; i++) { //当i等于50时使用break终止循环 if((i%2) == 0)continue; System.out.println(" i : " + i); }continue label作业1、输出100以内,所有不是3的倍数的数2、计算1至50中是7的倍数的数值之和3、循环输入数字1-7 ,输出对应的星期几。(输入0时循环结束) 4、打印空心矩形5、计算2000年1月1日到2020年1月1日相距多少天(能被4整除但不能被100整除,能被400整除的为闰年。)6、有数列:9,99,999,9999...,编程计算前10项值的和。7、输出得斐波那契数列前20项的值并输出。8、需求说明:会员购物时,根据输入积分的不同享受不同的折扣计算会员购物时获得的折扣输出实付金额会员积分x 折扣x < 2000 9折2000 ≤ x < 4000 8折4000 ≤ x < 8000 7折x ≥ 8000 6折9、用户输入两个数a和b,如果a能被b整除或者a加b大于100,则输出a,否则输出b。10、定义一个数,求得其二进制(正数)选做题1、打印指定月份的日历信息(实现从键盘输入1900年之后的任意的某年、某月,输出该月的日历)如下:2022年6月星期一星期二星期三星期四星期五星期六星期日 123456789101112131415161718192021222324252627282930 2、使用循环输出杨辉三角函数int sum = 0; for(int i = 1; i <= 5;i++) { sum=1; for(int k = 20-2*i;k > 0;k--) System.out.print(" "); for(int j = 1;j <= i;j++) { if(j > 1) sum = sum*(i-j+1) / (j-1); //公式 System.out.print(String.format("%5d",sum)); } System.out.println(); }五、方法方法的定义执行指定功能的代码块。简单方法定义暂时无法在飞书文档外展示此内容返回值类型 方法名() { //方法体 } void showMultiTable() {}复杂方法定义无返回值方法暂时无法在飞书文档外展示此内容返回值类型 方法名(形参列表 形参名) {注意:形参都属于局部变量 //方法体 } 调用方法的时候在括号内写的参数叫做实际参数,简称实参 void calculateSum(int max) {} //定义有返回值类型的方法 //需求:返回计算后的结果,也就是方法内部计算出来结果后要返回到调用的地方 //有返回值方法的定义 int calculateSumResult() {}有返回值方法暂时无法在飞书文档外展示此内容形参和实参形参定义方法时写到方法名后括号内的参数称为形式参数,简称形参。形参可以有多个,可以是各种数据类型。具体可参查看下方“方法调用”模块的代码示例实参调用方法时填入括号内的参数叫做实参,实参必须要和被调方法的形参数量、形参数据类型、形参顺序保持一致。具体可参查看下方“方法调用”模块的代码示例方法的分类关键字static静态方法被static修饰的方法和属性以及代码块。被修饰的方法和属性(变量)叫做静态方法和静态属性成员方法没有被static修饰的方法和属性叫做成员方法和成员属性(变量),成员方法和成员属性必须要通过创建对象去访问。方法调用调用方法的两种方式一:创建类的对象来调用, 通过对象的 变量名或叫做引用.方法名() 的形式调用。以下代码示例中,Test类里的SimpleMethod sm = new SimpleMethod();语句就是通过关键字new创建对象。而此处的sm就是变量名或叫做引用。//SimpleMethod.java public class SimpleMethod { //无返回值方法 void calculateSum(int max) {//业务代码} //有返回值方法 int calculateSumResult() { //业务代码 return 100; } //有参方法 void calculateSum(int min, int max) {//业务代码} } //Test.java public class Test { public static void main(String[] args) { SimpleMethod sm = new SimpleMethod();//创建对象 /*此处的50就是调用calculateSum(int max)方法的实参,而max叫做形参。 实参要和形参的数据类型必须保持一致。*/ sm.calculateSum(50);//调用方法 //调用有返回值的方法 int result = sm.calculateSumResult();//可以接收返回值 sm.calculateSumResult();//也可以选择不接收返回值 //sm.calculateSum(10, 50);该调用括号内的实参顺序要和方法 //calculateSum(int min, int max)内的新参顺序保持一致, //也就是实参10会传给min形参, 实参50会被传给max形参 //注意:如果方法定了为有参方法,那就必须要在调用时带上实参,否则程序会报错。 sm.calculateSum(10, 50); } }调用有返回值方法时注意事项:方法的返回值使用 return关键字,如:return value 此处value可以是常量、变量、表达式、方法方法的返回值也就是return关键字后边跟的value值数据类型要和方法定义中的返回值类型一致或者数据表示返回大于返回的值调用方法代码里可以不用接收返回的值,也可以选择接收。当需要接收方法的返回值时,接收方定义的变量必须要和方法的返回值类型保持一致//sm.calculateSum(10, 50);该调用括号内的实参顺序要和方法calculateSum(int min, int max) //内的新参顺序保持一致,也就是10会传给min形参, 50会被传给max形参 注意:如果方法需要调用方提供参数,那就必须要在调用是填写上实参,否则程序会报错。 SimpleMethod sm = new SimpleMethod(); sm.calculateSum(10, 50); void calculateSum(int min, int max) {}二:使用 类名.方法名()` ` 的形式调用要使用这种方式调用方法的前提:方法必须要被static关键字修饰暂时无法在飞书文档外展示此内容该种调用方法的形式,在形参和实参的处理上和上一种调用方式一样。//SimpMethod.java public class SimpleMethod { //打印九九乘法表 static void showMultiTable() { System.out.println("打印九九乘法表"); for (int i = 1; i <= 9;i++){ for (int j = 1;j <= i;j++){ System.out.print(String.format("%d*%2d=%2d ", j, i, (j*i))); } System.out.println(); } } } //Test.java public class Test { public static void main(String[] args) { SimpleMethod.showMultiTable(); } }静态方法和成员方法调用代码示例//MethodInvoke.java public class MethodInvoke { void method01() {} int method02(String s, int a) {return 10;} static void method03() {} static int method04(int a) {return 3;} static String method05(String s, double d){return "ok";} } //Test.java 测试类 public class Test { public static void main(String[] args) { //调用静态方法 MethodInvoke.method03();//无参方法调用 int result = MethodInvoke.method04(10);//10叫做实参,result是用来接收返回值的变量。 //该方法有两个实参,第一个数据类型是字符串,第二个是整型 //response是用来接收返回值的变量。 String response = MethodInvoke.method05("good", 101.0); //调用成员方法 MethodInvoke mi = new MethodInvoke();//创建对象, mi叫引用 mi.method01();//通过引用调用方法 int res = mi.method02("good", 101); } }为什么静态方法可以使用类名访问,而成员方法却需要创建对象?暂时无法在飞书文档外展示此内容上图中有对象的引用mi1和mi2,两个引用代表两个对象,这两个对象都有独立的内存空间,并且都有属于自己的方法method01和method02。main方法和使用类在同一个文件时的方法调用示例public class MethodDemo01 { static int a = 0; int b = 0; //定义成员方法 void m1(){ System.out.println("方法m()"); } void m2(char ch) { System.out.println("方法m2()"); } String m3(int a, String s) { System.out.println("方法m3()"); return "ok"; } //定义静态方法 static void m4() { System.out.println("static 方法m4()"); } static void m5(int a) { System.out.println("static 方法m5()"); } static String m6(String s, int a) { System.out.println("static 方法m6()"); return ""; } //测试方法 public static void main(String[] args) { //调用成员方法,首先创建对象 MethodDemo01 methodDemo01 = new MethodDemo01(); //调用方法m1 引用名.方法([参数列表]) methodDemo01.m1(); methodDemo01.m2('a'); methodDemo01.m3(12, "你好"); //调用静态方法1 m4(); m5(10); int c = 200; m6("hessfsd", c); //调用静态方法2 MethodDemo01.m4(); MethodDemo01.m5(c); methodDemo01.m6("uuuu", c); } }修饰符publicjava里完整的方法定义Java的方法组成如下: 修饰符 返回值类型 方法名(参数类型 参数名...){ //方法体 }示例public class MethodDemo01 { public static int a = 0; public int b = 0; //定义成员方法 public void m1(){ System.out.println("方法m()"); } public void m2(char ch) { System.out.println("方法m2()"); } public String m3(int a, String s) { System.out.println("方法m3()"); return "ok"; } //定义静态方法 public static void m4() { System.out.println("static 方法m4()"); } public static void m5(int a) { System.out.println("static 方法m5()"); } public static String m6(String s, int a) { System.out.println("static 方法m6()"); return ""; } //测试方法 public static void main(String[] args) { //调用成员方法,首先创建对象 MethodDemo01 methodDemo01 = new MethodDemo01(); //调用方法m1 引用名.方法([参数列表]) methodDemo01.m1(); methodDemo01.m2('a'); methodDemo01.m3(12, "你好"); //调用静态方法1 m4(); m5(10); int c = 200; m6("hessfsd", c); //调用静态方法2 MethodDemo01.m4(); MethodDemo01.m5(c); methodDemo01.m6("uuuu", c); } }return关键字从当前的方法中退出,返回到该调用方法的语句处,继续执行。返回一个值给调用该方法的语句,返回值数据类型必须与方法的声明中返回值的类型一致,可以使用强制类型转换来使数据类型一致。return语句主要有两个用途: 一方面用来表示一个方法返回的值,另一方指它导致该方法退出,并返回那个值。根据方法的定义,每一个方法都有返回类型,该类型可以是基本类型,也可以是引用类型,同时每个方法都必须有个结束标志,因此,return起到了这个作用。在返回类型为void的方法里面,有个隐含的return语句,因此,在void方法里面可以省略不写。 2、Java中,return,break,continue以及goto的区别。但是goto不常用,感兴趣的可以了解。 return语句:是指结束该方法,继续执行方法后的语句。 break语句:是指在循环中直接退出循环语句(for,while,do-while,foreach),break之后的循环体里面的语句也执行。跳出switch分支。 continue语句:是指在循环中中断该次循环语句(for,while,do-while,foreach),本次循环体中的continue之后语句不执行,直接跳到下次循环。 注意:return可以用在有返回值的方法中return也可以用在没有返回值的方法中:在return的后面不能直接写任何代码,因为不可能执行到此处的代码。没有返回值的方法可以写return,有返回值的方法必须要写return 返回值;方法的重载方法名相同,方法参数的数据类型,参数个数以及参数顺序不同称为方法的重载。重载的作用是为了功能类似的方法名使用同一个,便于记忆,因此使用起来更方便。暂时无法在飞书文档外展示此内容/** * 演示方法重载,方法名相同,参数个数,参数的数据类型以及参数顺序不同就是方法的重载 */ public class OverLoad { public static void main(String[] args) { //方法的调用 show(); show((short) 45); show((short) 75, 89); show(105, (short)879); } public static void show() { System.out.println("no parameter-----"); } public static void show(short s) { System.out.println("a parameter-------"); } //下边两个方法演示:参数顺序不同就可以使用相同的方法名 public static void show(short s, int a) { System.out.println("show(short s, int a)"); } public static void show(int a, short s) { System.out.println("show(int a, short s)"); } }递归定义递归(英语:Recursion),在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。简单理解就是方法自己调用自己。练习:使用递归输出斐波拉契前8项// 调用fblqRecursion(1,1,8); public static int fblqRecursion(int a,int b,int n){//斐波拉契前8项 n = n - 1; if(a==1 && b==1){ System.out.print("\t"+ a + "\t"+ b); return fblqRecursion(b,a+b, n); } if(n > 0){//第八次条件 System.out.print("\t"+ (a + b)); return fblqRecursion(b,a+b, n); } else return 0; } static int count = 2; static int povit = 0; public static void main(String[] args) { fibonacci(8); } public static int fibonacci(int n) { if (n < 3) { //因为在递归调用的过程中会有很多次n < 3,为了控制只输出两次, //所以使用一个计数变量 if (count > 0) System.out.print(1 + " "); count--; return 1; } int a = fibonacci(n-1) + fibonacci(n-2); //a的值可能会出现中间结果,但是我们不希望中间结果也输出,所以使用if判断 //如果输出的中间结果小于下一个要输出的结果,那就不输出中间结果 if (povit < a) { System.out.print(a + " "); povit = a; } return a; } public static void main(String[] args) { for (int i = 1;i <= 8;i++){ System.out.print(Fibonacci(i) + " "); } } /* * 使用递归输出斐波拉契前8项 * */ public static int Fibonacci(int n){ if (n <= 2){ return 1; }else { return Fibonacci(n - 1) + Fibonacci(n - 2); } }汉诺塔/** * * @param n 表示柱子上的盘子数 * @param a 表示第一根柱子 * @param b 表示第二根柱子 * @param c 表示第三根柱子 */ public static void hanoi(int n, char a, char b, char c) { if(n == 1) { System.out.println("把第"+ n + "个碟片从 " + a + " 移动到 " + c); return; } hanoi(n-1, a, c, b); System.out.println("把第"+ n + "个碟片从 " + a + " 移动到 " + c); hanoi(n-1, b, a, c); } //调用 hanoi(4, 'A', 'B', 'C');方法的优点使主程序更简洁,更易于理解。代码可以重复使用易于开发、调试、测试更方便团队合作方法使用注意事项方法必须定义在类里方法不能嵌套在return的后面不能直接写任何代码,因为不可能执行的到没有返回值的方法可以写return,有返回值的方法必须要写return 返回值;方法在某些语言中被称为过程、函数。processe, function, methodString类该类是java自带的,表示字符串。String属于引用类型,要使用引用类型,首先要创建对象。public class StringDemo { public static void main(String[] args) { //第一种定义字符串的形式 String s = "abc"; //第二种定义字符串的形式 String str = new String("abc"); String s1 = "abcsaf"; //字符串比较推荐使用equals方法, 此方法才会真正比较字符串的内容。==是比较地址。 System.out.println(s.equals(s1)); System.out.println(s.equals(str)); System.out.println(s == str); System.out.println(s == s1);//true的原因是地址一样 //获取字符串长度 System.out.println(s1.length()); //字符串替换 System.out.println(s1); System.out.println(s1.replace("csa", " ")); //把小写字符串转成大写 System.out.println(s1.toUpperCase()); //把大写转小写 System.out.println(s1.toUpperCase().toLowerCase()); //查看字符串中是否包含自定子串 System.out.println(s1.contains("sf")); } }作业写一个方法,打印九九乘法表写一个方法,输入三角形的三条边,输出是否能构成三角形//曾昭洋 triangle(3,4,2); private static void triangle(int a,int b,int c) { if (a + b >c && a - b < c && b - a < c ){//两边之和 > 第三边,两边之差 < 第三边 System.out.println("能构成三角形"); }else System.out.println("不能构成三角形"); }写一个方法,传入用户名和密码,返回是否登录成功(默认正确的用户名密码为 admin和123456)写一个方法,传入两个数字,并传入一个类型(例如:传入+号就做加法运算,*号就是乘法运算)。返回两个数计算的结果定义一个方法,计算商品的价格(参数1:数量 参数2:苹果(3.5元) 香蕉 (4元) 橘子(5元))定义一个方法,传入一个数字,判断是否是质数,返回boolean定义一个方法,求1000以内,最大的水仙花数,并返回//曾昭洋 System.out.println("最大水仙花数为:" + narcissus()); private static int narcissus() { int max = 0; for (int i = 100; i < 1000; i++) { double a = Math.pow((i % 10), 3);//求出 个位数 3次方 double b = Math.pow(((i / 10) % 10), 3); double c = Math.pow((i / 100), 3); if (a + b + c == i){ if (max < i){//如果当前 水仙花数>之前最大的水仙花数 max = i;//那么就代替掉之前最大的数 } } } return max; }定义一个方法,求100以内,所有的质数并输出定义一个方法,传入三个数,输出最大的那个数,最小的那个数// public void nine(int a ,int b ,int c ){ System.out.printf("最大的数是%d%n最小的数是%d",(a>b&&a>c)?a:(b>a&&b>c)?b:c,(a<b&&a<c)?a:(b<a&&b<c)?b:c); }定义一个方法,接收一个参数,使用递归输出该参数每一位上的值验证哥德巴赫猜想:任何一个大于6的偶数,都能分解成两个质数的和,要求输入一个整数,输出这个数被被分解成哪两个质数的和。如:1414 = 3 + 1114 = 7 + 7六、数组为什么要学习数组?一维数组在计算机科学中,数组数据结构(英语:array data structure),简称数组(英语:Array),是由相同类型的元素(element)的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引(index)可以计算出该元素对应的存储地址。最简单的数据结构类型是一维数组。例如,索引为0到9的32位整数数组,可作为在存储器地址2000,2004,2008,...2036中,存储10个变量,因此索引为i的元素即在存储器中的2000+4×i地址。数组第一个元素的存储器地址称为第一地址或基础地址。静态初始化语法结构:数据类型[] 数组名 = {value1, value2, value3, ......, valuen};数据类型可以是八种基本数据类型和引用类型数组名即变量名,可以自定义,遵循变量命名规则:不能以数字开头,取名做到见名之意,多个单词使用驼峰标识暂时无法在飞书文档外展示此内容/** * 数据类型[] 数组名 = {value1, value2, value3, ..., valuen}; */ public class StaticInit { public static void main(String[] args) { //定义字符串数组 String[] students = {"Bobs", "Steven", "Kobe"}; //获取数组内的元素使用语法 : 数组名[下标] String studentName = students[0]; System.out.println(studentName); //获取数组的长度使用语法 : 数组名.length int len = students.length; System.out.println("数组长度 : " + len); System.out.println(students[2]); //更新和插入数组内的元素语法 : 数组名[下标] = 更新或插入后的新值 students[2] = "Curry"; System.out.println(students[2]); } }语法结构:数据类型[] 数组名 = new 数据类型[数组长度];数据类型可以是八种基本数据类型和引用类型数组名即变量名,可以自定义,遵循变量命名规则:不能以数字开头,取名做到见名之意,多个单词使用驼峰标识new是java的关键字,必须保持不变。数组长度即是分配数组后,数组分成多少个单元格。动态初始化/** * 动态初始化数组 数据类型[] 数组名 = new 数据类型[数组长度]{} */ public class DynamicInit { public static void main(String[] args) { //定义一个字符串数组 String[] students = new String[3]; //定义一个int型数组 int[] ages = new int[3]; //打印输出数组默认值 System.out.println(students[0]); System.out.println(students[1]); System.out.println(students[2]); //获取数组长度 System.out.println(students.length); //获取年龄数组的长度 System.out.println(ages.length); //给students数组添加元素 System.out.println("students数组第一个元素" + students[0]); students[0] = "Bobs";//在数组下标为0的位置添加值 "Bobs", 即数组的第一个元素 students[2] = "Kobe";//在数组下标为2的位置添加值 "Bobs",即数组的第三个元素 System.out.println("students数组第一个元素" + students[0]); //修改第一个元素为Jackson students[0] = "Jackson"; System.out.println("students数组第一个元素" + students[0]); //获取超过数组下标范围的元素 //System.out.println(students[students.length]);数组下标越界 //给数组内的元素在运行时动态赋值 String name = "国伟"; students[1] = name; System.out.println(students[1]); } }练习代码完成静态和动态初始化创建数组使用动态初始化定义各种数据类型的数组,然后打印查看数组的默认值。遍历/** * 遍历数组 */ public class TraverseArray { public static void main(String[] args) { //静态初始化定义字符串数组 String[] students = {"Bobs", "Steven", "Kobe"}; //访问数组元素 for (int i = 0; i < students.length; i++) { //输出每一个下标位置的元素 System.out.print(String.format("数组的第 %d 个元素是 %s \n", (i+1),students[i])); } } }练习1、从键盘录入五个名字到数组中,遍历数组输出这五个名字public static void main(String[] args) { String name[]=new String[5]; Scanner scanner = new Scanner(System.in); System.out.println("”...”可结束输入!"); for (int i=0;i< name.length;i++) { System.out.print("输入名字"+(i+1)+":"); String namennn = scanner.next(); if (namennn.equals("...")){ break; }else { name[i] = namennn; } } bianli(name); } public static void traverse(String a[]){ for (int i=0;i< a.length;i++) { System.out.print(String.format("数组的第 %d 个元素是 %s \n", (i+1),a[i])); } } //zyy2、给定一个数组,求数组的最大值int[] arr = {1, 26, 7, 12, 30, 8}; public int max() { int max = 0; for( int i = 0; i < arr.length; i++) if(arr[i] > max) max = arr[i]; //曾昭洋:max = Math.max(arr[i],max); return max; //System.out.println(String.format("最大的数是:%d ",max)); }3、给定一个数组,求数组的最小值int[] arr = {1, 26, 7, 12, 30, 8}; public int min() { int min = 0; for( int i = 0; i < arr.length; i++) if(arr[i] < min) min = arr[i]; return min; }4、给定一个数组,求数组的平均值int[] arr = {1, 26, 7, 12, 30, 8}; public int min() { int sum = 0 for( int i = 0; i < arr.length; i++) sum += i; return sum/arr.length; }5、给定一个数组,传入一个数字,如果在数组中存在这个数字,返回这个数字在数组中的下标,否则返回-1int[] arr = {1, 26, 7, 12, 30, 8}; public int find(int x) { int sum = 0 for( int i = 0; i < arr.length; i++) if(x == arr[i]) return i; return -1; }6、思考题:在第一题的基础上。但是要保证五个名字不重复String[] names = new String[5]; int count = 0; public void add(String name) { if(count >= arr.length)return; for( int i = 0; i < count; i++) if(names[i].equals(name)) System.out.println("名字已经存在"); names[count++]; }拷贝数组/** * 拷贝数组 */ public class CopyArray { public static void main(String[] args) { //静态初始化定义字符串数组 String[] students = {"Bobs", "Steven", "Kobe"}; System.out.println("拷贝前的数组"); traverse(students); students = copyArray(students, 1, 1); System.out.println("拷贝后的数组"); traverse(students); } /** * 拷贝数组 * @param oldArr 数组引用 * @param index 需要拷贝数组的起始下标 * @param count 需要拷贝的元素个数 */ public static String[] copyArray(String[] oldArr, int index, int count) { if (index < 0 || index >= oldArr.length || count < 1) { System.out.println("无效参数,拷贝失败!"); return oldArr; } //新数组长度 int newArrLen = index + count; if (newArrLen > oldArr.length) count = oldArr.length - index; //根据传入的count确定新建数组大小 String[] newArr = new String[count]; //第一种 for (int i = 0; i < newArr.length; i++) { newArr[i] = oldArr[index++]; } return newArr; } public static void traverse(String[] arr) { for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); } }Arrays和System类拷贝数组import java.util.Arrays; /** * System和Arrays拷贝数组 */ public class JDKArrayCopy { public static void main(String[] args) { //静态初始化定义字符串数组 String[] students = {"Bobs", "Steven", "Kobe"}; //使用System的拷贝方法 /*show(students); System.arraycopy(students, 0, students, 1, 2); show(students);*/ String[] students02 = new String[students.length]; show(students02); System.arraycopy(students, 0, students02, 0, students.length); show(students02); System.out.println("----------------------------------------"); //使用Arrays里的拷贝方法 String[] results = Arrays.copyOf(students, students.length); show(results); } //遍历查看数组元素 public static void show(String[] stus) { for (int i = 0; i < stus.length; i++) System.out.print(stus[i] + " "); System.out.println(); } }将数组传递给方法/** * 演示值传递和引用传递 */ public class ArrayPassParameters { public static void main(String[] args) { //定义变量 int a = 10; //调用方法 updataIntVira(a); System.out.println("a的值 : " + a); //静态初始化定义字符串数组 String[] students = {"Bobs", "Steven", "Kobe"}; updateArrayValue(students); System.out.println(students[0]); } /** * 值传递示例 * @param a */ public static void updataIntVira(int a) { a = 101; } /** * 引用传递示例 * @param stus */ public static void updateArrayValue(String[] stus ) { stus[0] = "Error"; } }扩容import java.util.Arrays; /** * 遍历数组 */ public class GrowArray { public static void main(String[] args) { //静态初始化定义字符串数组 String[] students = {"Bobs", "Steven", "Kobe"}; show(students); //使用JDK自带的Arrays完成扩容,数组大小扩容为原来的两倍 students = Arrays.copyOf(students, students.length << 1); //扩容后继续添加元素 students[3] = "James"; show(students);//调用遍历方法 } //遍历数组 public static void show(String[] arr) { for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); } }JDK ArrayListpublic class MyArray { //全局数组 static String[] names = null; //定义变量记录数组的有效元素个数 static int size = 0; public static void main(String[] args) { //初始化数组 initArray(3); //添加数据 add("张一"); add("张二"); add("张三"); add("张四"); add("张五"); add("张六"); add("张七"); add("张八"); show(); System.out.println(names.length); delete("张七"); delete("张八"); delete("张六"); show(); System.out.println(names.length); System.out.println("size : " + size); }0 public static void initArray(int capacity) { names = new String[capacity]; } //扩容和缩容 public static void updateArraySize(int len) { //创建新数组,容量是旧数组的两倍 String[] newArr = new String[len]; //拷贝元素 for (int i = 0; i < size; i++) { newArr[i] = names[i]; } //改变引用 names = newArr; newArr = null; } //添加名字 public static void add(String value) { //当size等于数组长度时进行扩容 if (size == names.length) { //names.length << 1 表示长度除以二 updateArraySize(names.length << 1); } names[size++] = value; } //删除 public static void delete(String value) { int index = find(value); if (index == -1) { System.out.println(value + " 不存在系统中"); return; } //移动下标index之后的所有有效元素 for (int i = index + 1; i < size; i++) { names[i-1] = names[i]; } size--; //如果空闲空间达到数组的一半,就进行缩容 boolean free = ((names.length / size) >= 2) ? true : false; if (free) { //就进行缩容 //新建数组 updateArraySize(size); } } //查找指定元素并返回下标 public static int find(String value) { //循环数组比较元素 for (int i = 0; i < size; i++) { if (names[i].equals(value)) { return i; } } return -1; } //遍历数组 public static void show() { for (String name : names) System.out.print(name + " "); System.out.println(); } }练习不要使用系统自带类完成数组扩容。public static void main(String[] args) { String arr[]={"小芳","小华","小朱","小渣渣"}; String newArray[]=Expansion(arr); newArray[4]="小美"; traverse(newArray); } public static void traverse(String a[]){//遍历 for (int i=0;i< a.length;i++) { System.out.print(a[i]+" "); } } public static String[] Expansion(String a[]){//扩容 String newArr[]=new String[2*a.length]; for (int i=0;i<a.length;i++){ newArr[i]=a[i]; } return newArr; }使用System.arraycopy()完成拷贝。//曾昭洋 String[] arr ={"张三","李四","王五"}; String[] newArr = new String[arr.length << 1]; show(sysArr(arr,newArr)); private static String[] sysArr(String[] arr,String[] newArr) { // 原数组,原数组起始位置,目标数组,目标数组起始位置,复制的数量 System.arraycopy(arr,0,newArr,0,arr.length); return newArr; } public static void show(String[] arr){ for (int i = 0; i < arr.length; i++) System.out.println(arr[i]); }方法形参可变长列表语法格式:public void 方法名(数据类型... 参数名) {//方法体}这里的参数名其实是一个数组的引用。它会根据传入的参数个数动态生成一个数组来存放接收到的参数。/** * 可变长方法参数列表 */ public class DynamicMethodParameter { public static void main(String[] args) { int[] b = method01(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6); System.out.println(b[2]); method01(1,2); method01(); } public static int[] method01(int... a) { System.out.println(a.length); return a; } }foreach循环jdk1.5之后的新语法,用来代替for遍历数组或集合,称为增强for循环。只能逐个访问数组的元素,不能操作数组的下标。语法格式:for(数据类型 变量名 : 数组引用){ System.out.println(变量名); } /** * 增强for循环 foreach */ public class ForeachArray { public static void main(String[] args) { //静态初始化定义字符串数组 String[] students = {"Bobs", "Steven", "Kobe"}; for (String student : students) System.out.print(student + " "); System.out.println(); int[] ages = {12, 18, 19}; for (int age : ages) System.out.print(age + " "); System.out.println(); //布尔型数组 boolean[] flags = {true, false, false, true, false}; for (boolean flag : flags) System.out.print(flag + " "); } }命令行参数public class TestMain { public static void main(String[] args) { System.out.println("测试调用main方法"); System.out.println(args.length); System.out.println(args[0]); System.out.println(args[1]); } }idea上调用给main方法传参数设置dos窗口给main方法传参javac TestMain.javajava TestMain 参数1 参数2 参数3 参数4 参数5 ........排序与查找冒泡排序public static void bubbleSort(int[] arr) { //控制循环多少次 for(int i = 0; i < arr.length; i++) { //循环找到余数组中最大的值 for(int j = 1; j < arr.length-i; j++) { //交换元素的值 if(arr[j] < arr[j-1]) { int temp = arr[j-1]; arr[j-1] = arr[j]; arr[j] = temp; } } } }选择排序public static void selectsort(int[] arr) { for (int i = 0; i < arr.length-1; i++){ int minindex = i; for (int j = minindex + 1; j < arr.length; j++){ if (arr[j] < arr[minindex]){ min = j; } } int temp = arr[i]; arr[i] = arr[minindex]; arr[minindex] = temp; } }线性查找遍历数组,逐个比较,直到找到需要的元素就返回二分查找/** * int [] a = {2,1,6,5,8,3,7}; * System.out.println(binaryFind(1,a,0,a.length-1));//调用 * 递归方法二分查找 * @param a 需要查找的数 * @param arr 查找源 * @param star 开始下标,默认为0 * @param last 最后下标,默认array.length-1 * @return true or false */ public static boolean binaryFind(int a, int [] arr , int star , int last){ int mid = (star+last)/2; System.out.println("查找的位置:array["+mid+"]"); if(arr[mid] == a) return true; else{ if( mid-1 >= 0 && arr[mid-1] >= a){ return binaryFind(a , arr ,star , mid-1); } else if (arr[mid+1] <= a){ return binaryFind(a , arr ,mid+1 , last); } } return false; }作业复习今天的内容。课上的代码最好都能自己实现一遍。键盘输入1-12任意一个数,然后程序打印输出该月份对应的英文单词。禁止使用条件分支语句实现。移动元素,把数组第一个元素移动到数组最后一个位置上。保证数组内的内容完整。编写一个程序 ,找到大于所有项平均值的那些项。输入一个不大于8位的数字判断这个数是不是回文数字。提示:所谓"回文",就是正着读和反着读是一样的System.out.println("\r请输入一个小于八位数的数"); String string = scanner.next(); //反转字符串 //String reverse = new StringBuffer(string).reverse().toString(); //字符串转换为数组 char[] arr = string.toCharArray(); String reverse = ""; //进行字符串拼接 for (char i : arr) reverse = i + reverse; if (string.equals(reverse)) System.out.println(string+"是回文数字"); else System.out.println("不是回文"); public static void huiwen(String n){ char[] chars = n.toCharArray(); a: for (int i=0;i<chars.length;i++){ if (chars[i]!=chars[chars.length-i-1]) { System.out.println("不是回文"); return; }else{ continue a; } } System.out.println("是回文"); }//对还是错?for循环无意义,无论如何只进入一次循环 现在ok不?二维数组二维数组每一个元素都是一个一维数组。逻辑上可以把二维数组想象为一个表格二维数组语法结构:数据类型[][] 数组名 = {{值}, {值}, {值}}; 数据类型[][] 数组名 = new 数据类型[二维数组长度][一维数组长度]; 一维数组的长度不是必须的,但二维数组的长度必须要填写。for (int i=0;i<array.length;i++){ for (int j=0;j<array[i].length;j++){ System.out.print(array[i][j]+" "); } } //曾昭洋 int[][] arr = {{1,2,3,4,5,6,7},{10,12,13,14,15,16,17},{100,22,23,24,25,26,27}}; for (int i = 0; i < arr.length; i++) { arr[i][0] *= 2; //简单修改一个值 for (int arrs:arr[i]) System.out.print(arrs + " "); //遍历全部 System.out.println(); }杨辉三角public static void main(String [] args){ int [][] arr1 = new int[7][]; tcArray(arr1); printyhArray(arr1); } /** * 填充数组 * @param array */ public static void tcArray(int [][] array){ for(int i=0; i < array.length; i++){ array[i] = new int[i+1]; for(int j=0; j<= i; j++){ if(j==0 || i==j) array[i][j] = 1; else if(j > 0 && i > j){ array[i][j] = array[i-1][j]+ array[i-1][j-1]; } } } } /** * 打印数组 * @param array */ public static void printyhArray(int [][] array){ for(int i=0; i < array.length; i++){ for(int j = array.length - i -1 ; j > 0; j--){ System.out.print("\t"); } for( int j = 0; j<array[i].length; j++){ //if(array[i][j] != 0 ) System.out.print(array[i][j]+"\t\t"); } System.out.println(); } }商品管理系统public static void kaiguang() { System.out.println(" _oo0oo_"); System.out.println(" o8888888o"); System.out.println(" 88\" . \"88"); System.out.println(" (| -_- |)"); System.out.println(" 0\\ = /0"); System.out.println(" ___/`---'\\___"); System.out.println(" .' \\| |// '."); System.out.println(" / _||||| -:- |||||- \\"); System.out.println(" | | \\\\\\ - /// | |"); System.out.println(" | \\_| ''\\---/'' |_/ |"); System.out.println(" \\ .-\\__ '-' ___/-. /"); System.out.println(" ___'. .' /--.--\\ `. .'___"); System.out.println(" .\"\" '< `.___\\_<|>_/___.' >' \"\"."); System.out.println(" | | : `- \\`.;`\\ _ /`;.`/ - ` : | |"); System.out.println(" \\ \\ `_. \\_ __\\ /__ _/ .-` / /"); System.out.println(" =====`-.____`.___ \\_____/___.-`___.-'====="); System.out.println(" `=---='"); System.out.println(" "); System.out.println(" "); System.out.println(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ "); System.out.println(" 佛祖保佑 永无BUG "); }七、面向对象概念英文:Object Oriented Programming(OOP)OOP : 使用对象构造应用程序。对象万事万物皆对象。对象具有的行为特征特征:程序里的属性行为:程序里的方法类抽象同一种对象的共有属性和行为,形成一个类。类是抽象的,对象是具体的。人是一种抽象,但是具体的某个人,如:cxk就是一个对象。詹姆斯就是一个具体的对象。如何通过程序来表现类。语法格式:关键字<public, protected, default, private>` 关键字<class> 抽象事物的名字{}`比如:我们要类的概念抽象人 :public class Person {},此时,Person就是一个类 类的概念抽象车子 : public class Vehicle {} Vehicle也是一个类 类的概念抽象动物 : public class Animal {} Animal是一个类 类的概念抽象商品:public class Product{}, Product即是一个类 类的概念抽象细胞:public class Cell{}类在程序中怎么表现:/** * 用程序表现人这种抽象。 */ public class Person { //特征:人这种抽象具有什么特性,也就是属性 String name;//每个人都有姓名 boolean gender;//每个人都有性别 int age;//每个人都有年龄 String address;//每个人都有一个住址 String role;//每个人都扮演着自己的角色 //行为: 人这种抽象具有哪些行为,也就是方法 void eat(){}//每一个具体的人都会吃饭 void sleep(){}//都会睡觉 void playGame(){}//都会玩游戏 }暂时无法在飞书文档外展示此内容类和对象的关系类是抽象,对象是具象。类是一个模板或叫做蓝本,而对象就是根据该模板创建出来的一个具体的事物。如房屋建造图纸是一个模板,蓝本,可以叫做类,而根据图纸这种模板建起来的一栋栋房屋就是一个个的对象。类与对象之间的关系图:暂时无法在飞书文档外展示此内容创建对象java里使用关键字new来创建对象。创建对象必须基于类。要创建对象必须先定义类。java里创建对象:new 类名();创建对象并赋值给某个引用:类名` 变量名(也叫引用) = `new 类名();在实际代码中的表示:/** * 基于类创建对象 */ public class Test { public static void main(String[] args) { //基于类Person,使用new关键字,创建具体的一个个对象 Person Bobs = new Person();//创建了一个名叫Bobs的人 Person Jobs = new Person();//创建了一个名叫Jobs的人。 //注意:以上的两个人能Bobs和Jobs,虽然都是基于类Person创建,但他们不是同一个对象。 } }以上代码示例在内存中的布局图暂时无法在飞书文档外展示此内容实例一个对象就是一个实例。常用术语:实例化一个对象。实例变量属于某个对象的变量就是实例变量。实例变量只能通过实例<对象>去访问。在一个类里,怎么区分实例变量?没有被static修饰的属性就是实例变量或叫做成员变量。代码示例:/** * 类里的实例变量 */ public class Person { //特征:人这种抽象具有什么特性,也就是属性 public String name;//每个人都有姓名 public boolean gender;//每个人都有性别 public int age;//每个人都有年龄 public String address;//每个人都有一个住址 public String role;//每个人都扮演着自己的角色 }要访问以上代码里的实例变量,就必须要创建对象或叫做创建实例。创建对象:Person Bobs = new Person();通过实例访问实例内的属性代码public static void main(String[] args) { //基于类Person,使用new关键字,创建具体的一个个对象 Person bobs = new Person();//创建了一个名叫Bobs的人 Person job = new Person();//创建了一个名叫Jobs的人。 //注意:以上的两个人能Bobs和Jobs,虽然都是基于类Person创建,但他们不是同一个对象。 //此处Bobs和Job是两个不同实例,我们可以使用该实例访问属于该实例内的属性和方法 System.out.println(bobs.name);//访问实例变量name System.out.println(bobs.address);//访问地址 //给实例里的属性赋值 bobs.address = "延安中路18"; System.out.println(bobs.address); //是否可以使用类名访问实例属性, //System.out.println(Person.name);访问时会报以下错误信息。 //Non-static field 'name' cannot be referenced from a static context }实例变量和局部变量实例变量:定义在类里、方法外、代码块外部的变量,并且没有被static修饰的变量。也叫成员变量,有时也可叫全局变量非静态变量,也叫参数。局部变量:定义在代码块内、方法体内部以及方法的参数列表里的变量都是局部变量。代码示例/** * 实例变量和局部变量 */ public class Animal { //定义成员变量 String name;//名字 String gender;//性别 //定义局部变量, 首先需要定义一个方法 public void eat(int amount) {//amount表示动物吃多少==数量 String time;//动物每天几点开始吃 } }实例变量和局部变量有什么区别:默认值:实例变量的默认值是该种数据类型的默认值,如String是null, int是0等局部变量分为方法的形参局部变量和方法体的局部变量。形参:因为在调用方法时必须要填入参数,所以无法测试其默认值。方法体内的局部变量必须要在定义时初始化。作用域:实例变量的作用域是整个对象。局部变量作用域仅限于定义该变量的方法中实例方法同实例变量一样,没有被static修饰的方法就是实例方法,也叫成员方法。成员或实例方法必须要通过实例或对象才能访问。语法格式:引用.方法名() 形式访问代码示例/** * 实例方法 */ public class Animal { //定义一个实例方法 public void eat(int amount) {//amount表示动物吃多少==数量 String time = null;//动物每天几点开始吃 System.out.println(amount); } //main方法因为被static修饰,所以他是一个静态方法,属于类。 public static void main(String[] args) { //访问类Animal里的实例属性 //第一步创建Animal对象 Animal dog = new Animal(); //dog就是它的一个实例, 调用该实例的eat方法: 引用.方法名() 形式访问 dog.eat(103); } }类变量和类方法被static关键字修饰的方法和属性叫做类`方法和类`变量,static还可以用来修饰代码块语法结构修饰 属性/变量 : static 数据类型 变量名,修饰变量时只能用来修饰全局变量,修饰的变量叫做全局静态变量修饰方法 :public static 数据类型 方法名(形参列表){//方法体},此时叫做静态方法修饰代码块:static {//代码}/** * 静态变量和静态方法以及静态代码块 */ public class StaticKeyWords { //static 修饰属性 注意:static只能用来修饰全局属性 public static String name; public static int age; //static 修饰方法 public static void main() { //static int a; 不允许定义局部的静态变量 System.out.println("静态方法main()-----"); } public static void me() { System.out.println("静态方法me()-----"); } //static 修饰代码块,会在类被加载到java运行时内存里的方法区时执行 static { System.out.println("静态代码块------"); } }使用静态的类变量和方法直接使用类调用的语法格式:类名.属性名 类名.方法名public static void main(String[] args) { //访问StaticKeyWords类里的静态属性 System.out.println(StaticKeyWords.name); System.out.println(StaticKeyWords.age); //访问StaticKeyWords类里的静态方法 StaticKeyWords.main(); StaticKeyWords.me(); }使用对象访问类里的静态属性和静态方法语法结构:对象引用.属性名 对象引用.方法名public static void main(String[] args) { //创建类StaticKeyWords的对象 StaticKeyWords staticKeyWords = new StaticKeyWords(); //访问该类里的静态属性 System.out.println(staticKeyWords.name); System.out.println(staticKeyWords.age); //访问该类里的静态方法 staticKeyWords.main(); staticKeyWords.me(); }使用类访问和使用对象引用访问的区别仅仅只是访问方式不同而已,被static修饰的属性和方法还是属于这个类。在静态方法里使用类访问实例属性和实例变量:java不支持该种访问方式总结成员变量和成员方法只能通过类的对象去访问静态全局变量和静态方法可以通过类名访问,也可以通过类的对象引用去访问。暂时无法在飞书文档外展示此内容构造方法构造方法用来创建对象和给类里的属性赋值。语法结构:修饰符<public, private> 类名() {}此处的类名就是方法名,但是不能随意改变,必须要和类的名字保持一致。此处的类名就是当前类的名字./** * 构造方法 修饰符<public, private> 类名() {} */ public class ContructorMethod { //定义构造方法 public ContructorMethod() { System.out.println("创建对象"); } public static void main(String[] args) { /** * 创建类ContructorMethod的对象,会调用类的默认构造方法 * 显示调用类ContructorMethod里的构造方法ContructorMethod(); * 当类ConstructorMethod里没有显示定义ConstructorMethod()构造方法时类会默认添加此方法。 */ new ContructorMethod(); } }每个类都有一个默认构造方法,此构造方法没有任何参数。创建对象时如果不跟上参数,都是在调用默认构造方法。默认构造方法的格式是:public 类名() {}。调用构造方法的语法格式:new关键字加上构造放方法----- new ContructorMethod ();构造方法没有显示的返回值,因为全部的构造方法都会默认返回创建出来的对象。构造方法重载构造方法重载可以按照普通方法重载去理解.方法重载:方法名相同,方法形参的数据类型,个数,顺序不同就满足方法重载。/** * 实例方法 */ public class Animal { public String name; public Animal() { System.out.println("无参构造方法"); } /** * 当类里定义了带参数的构造法时,默认构造方法就会失效。想要默认生效,就需要显示定义。 * @param val */ public Animal(String val) { name = val; System.out.println("name : " + name); } public static void main(String[] args) { //创建对象 Animal animal = new Animal();//这句话会调用Animal类的默认构造方法 Animal bobs = new Animal("HEllo");//调用有构造方法 } }练习1、把书抽象成一个类,然后基于该类创建各种具体的书对象,完成下列需求:a、通过构造方法给对象里的参数赋值。b、调用类的静态方法卖书c、调用具体书对象的修改方法完成书简介的修改书应该具有属性:书名、作者、简介、出版社、日期、价格等public class Books { //书具有的属性 public String name; public String introduction; public String author; public double price; //创建构造方法 public Books(){}默认构造方法,对象自带的。 public Books(){ } //创建有参构造方法 public Books(参数列表),一旦类里定义了有参构造方法,那么默认构造方法会失效 public Books(String nm, String intro, String auth, double money) { name = nm; introduction = intro; author = auth; price = money; } //卖书 public static void saleBook(String name) { System.out.println("卖书《" + name + "》成功"); } //定义方法修改书的简介, 需要告诉该方法你要修改哪本书。因此需要传递参数books。 public void modifyIntroduction(Books books, String info) { books.introduction = info; } //打印对象的信息,需要给方法传递对象 public void show(Books book) { System.out.println(book + "对象的信息如下:"); System.out.print("{name : " + book.name); System.out.print(", introduction : " + book.introduction); System.out.print(", author : " + book.author); System.out.print(", price : " + book.price + "}\n"); } public static void main(String[] args) { //访问类Books里的成员变量,需要创建对象 Books thinkInJava = new Books();//调用类Books里的默认构造方法 //通过 引用.属性名 的方式给对象thinkInJava里的属性初始化 thinkInJava.author = "Bruce Eckel"; thinkInJava.introduction = "java 入门神书"; thinkInJava.name = "Think In Java"; thinkInJava.price = 10.0; //调用方法show打印对象的信息 thinkInJava.show(thinkInJava); thinkInJava.saleBook(thinkInJava.name); //通过有参构造方法给对象的属性 赋值/初始化 Books csapp = new Books( "深入理解计算机系统", "很浅的介绍了OS", "Bryant", 68.8); //调用打印方法输出书本信息 csapp.show(csapp); //调用卖书的方法需要给方法传递书的名字。 csapp.saleBook(csapp.name); csapp.modifyIntroduction(csapp, "niubi"); csapp.show(csapp); } }2、抽象用户这个概念来创建一个类User。此类里包含用户信息:用户名,密码,年龄,地址,性别等。a、使用构造方法初始化用户信息b、定义一个方法判断用户是否注册过。c、定义一个静态方法判断用户是否成年。d、定义一个方法允许用户开始玩游戏,玩游戏的前提是该用户必须注册过和成年。成年必须要通过调用是否成年方法来判断。构造方法,构造方法重载,实例方法,实例方法重载,静态方法,静态方法重载代码示例/** * 构造方法,构造方法重载,实例方法,实例方法重载,静态方法,静态方法重载。 */ public class User { //定义成员变量,也叫做实例变量,全局变量 public String username; public String password; public int age; public boolean gender; public double price; //一个人的住址通常不轻易改变,所以把住址定义为静态变量 public static String address; //定义无参默认构造方法,也叫构造函数 public User(){} //定义有参构造方法,初始化对象的名字和密码 public User(String un, String pwd) { username = un; password = pwd; } //重载构造方法初始化用户的年龄和性别 public User(int year, boolean sex) { age = year; gender = sex; } //定义用户注册的方法 public void register() { System.out.println("注册成功"); } //定义游客用户登录的方法 public void login() { System.out.println("游客登录成功"); } //重载登录方法实现根据用户名和密码登录 public void login(String username, String password) { System.out.println("用户[" + username + "]登录上来了"); } //定义静态方法,也叫做类方法 public static void getAddress() { System.out.println("用户的住址是 : " + address); } //无参修改住址静态方法 public static void modifyAddress(){ System.out.println("无参修改用户地址方法"); } //重载修改用户住址的静态方法 public static void modifyAddress(String newAddr) { address = newAddr; } //实现一个方法打印指定对象的信息 public static void show(User user) { System.out.println("用户[" + user.username + "]的个人信息是:"); System.out.print("{username:" + user.username); System.out.print(", password:" + user.password); System.out.print(", age:" + user.age); System.out.print(", gender:" + user.gender); System.out.print(", price:" + user.price + "}\n"); } } public class TestUser { public static void main(String[] args) { //要使用类User的构造方法和成员变量以及成员属性,需要基于该类创建对象 User hanmeimei = new User();//调用无参构造方法 //调用注册方法 hanmeimei.register(); //调用登录方法 hanmeimei.login(); //使用用户名密码登录 hanmeimei.login("韩梅梅", "12345678"); //使用带参数的构造方法实例化一个用户 User james = new User("詹姆斯", "james"); james.gender = true; james.age = 18; //修改用户住址,注意modifyAddress是一个静态方法 james.modifyAddress("洛杉矶"); //打印对象james的信息 User.show(james); //查看用户james的住址 james.getAddress(); //查看用户hanmeimei的住址 //此处hanmeimei的地址也是洛杉矶,是因为User类里的地址变量是一个静态属性。 //静态的都是属于类的,只会保持一份。 hanmeimei.getAddress(); } }对象创建内存简图public class Person { public String gender = "男"; } public class TestPerson { public static void main(String[] args) { Person bob = new Person(); Person tom = new Person(); System.out.println(bob.gender); System.out.println(tom.gender); } }thisthis,代表指向对象自身的一个引用,在对象被创建时自动生成。this只能在对象里使用。不能在静态方法和静态代码块中使用this关键字。只要有对象就必定会有该对象对应的this引用, this分配在java运行时数据区域的栈区,它属于方法里的形式参数,具体解释可参考该文章:https://stackoverflow.com/questions/29472208/this-keyword-working-mechanism-in-java注意:使用时用的是哪个对象引用调用方法,this就代表该引用对象。/** */ public class Person { public String gender = "男"; static { //System.out.println(this.gender); } public Person() { System.out.println(this.gender); } public void m() { System.out.println(this.gender); } public static void m2() { //System.out.println(this.gender); } } public class TestPerson { public static void main(String[] args) { new Person().m(); //System.out.println(bob.gender); } }this初始化对象属性/** * 使用this帮助初始对象的属性 */ public class Person { public String username; public int age; public String address; public static String gender = "男"; public Person() { System.out.println("无参构造方法被调用"); } /** * 定义有参构造方法,初始化对象的属性 */ public Person(String username, int age, String address) { this.username = username; this.age = age; this.address = address; } /** * 打印对象属性 */ public static void show(Person obj) { System.out.println("用户[" + obj.username + "]的信息是:"); System.out.println("{username:" + obj.username + ", age:" + obj.age + ", address:" + obj.address + ", gender:" + gender + "}"); } } public class TestPerson { public static void main(String[] args) { Person bob = new Person("Bobs", 20, "阿三发射点发射点"); //调用show方法打印对象bob的信息 Person.show(bob); //改变对象bob的性别 bob.gender = "人妖"; //基于类Person创建对象 Person tom = new Person(); //调用方法show,打印对象tom的属性值 Person.show(tom); } }案例把数组章节的商品管理系统改成学生管理系统。支持添加、删除、修改、查询等功能。要求存储学生的基本信息,如:姓名、年龄、性别、名族、地址、学历、学校等。package(包)在大型项目开发的过程中,为了避免类名冲突,java引入了包的概念。package必须要写在java文件的第一行。包名的命名方式约定俗成按照公司域名反过来。文件里的反斜线使用点来代替,如com\yangshun, com.yangshunpackage com.yangshun; public class Dog { public static void main(String[] args) { System.out.println("杨顺的狗"); } }import(导入)导入包。import com.yangshun.Dog; public class Cat { public static void main(String[] args) { //要使用一个类,直接创建该类的一个对象即可. Dog dog = new Dog(); System.out.println("导入dog成功"); } }访问修饰符在java中,访问修饰符用于设置类,变量,方法,构造方法的可见性。java中的修饰符有四个,分别是private, default, protected, public修饰符本类本包不同包子类其他中文翻译private✔ 私有的default✔✔ 默认的protected✔✔✔ 受保护public✔✔✔✔公共的在实际项目开发中,使用最多的是private和public,尤其是在后边学习封装时,会大量使用private以及public,其他两个修饰符作为了解。我们自己写代码很少使用到。如果变量,方法没有使用任何修饰符修饰,那么默认就是default。当方法、变量被private修饰时,就不能在当前类之外访问它们。如果我们没有为方法,变量显示指定任何访问修饰符,那么默认情况下会使用default来修饰它们当方法、变量被protected修饰时,可以在同一个包以及子类中访问它们当方法、变量被public修饰时,我们可以在任何地方访问他们。三大特性继承什么是继承继承是面向对象(OOP)的关键特性之一。它允许我们从一个现有的类直接创建新的类。达到代码的复用。创建的新类称为子类或派生类。和派生出子类的类叫做父类或超类以及基类。java中使用关键字extends来实现继承功能。注意:java只支持单继承。就是一个子类只能有一个父类。但是一个父类可以有多个子类。java中不能实现多继承,要达到多继承的功能可以借助后边学习的接口来实现。继承语法结构:public class 子类名 extends 父类名 {}程序实例public class Animal {} public class Dog extends Animal{}上例中 Dog类通过关键字extends来实现继承Animal类。在这里,Dog是子类。Animal是父类。为什么要用继承代码复用,继承共性(类的属性,方法等)。可用以借用继承在java中实现多态。程序示例/** * 父类,超类。抽象所有的动物,动物都具有名字,性别等特征。都具有吃饭和睡觉等行为 */ public class Animal { public String name; public String gender; public void eat(String name) { System.out.println(name + "在吃饭"); } public void sleep(String name) { System.out.println(name + "在睡觉"); } } public class Dog extends Animal { //因为Dog继承了Animal,所以Dog就拥有了Animal中的属性和方法。 public void bite() { System.out.println("咬住十分钟"); } } public class Cat extends Animal{ //因为Dog继承了Animal,所以Dog就拥有了Animal中的属性和方法。 //注意:只要代码里写了有参构造方法,就一定要显示书写默认无参构造方法 public Cat() {} public Cat(String name, String gender){ this.name = name; this.gender = gender; } public void hoots() { System.out.println("猫叫"); } } public class TestAnimal { public static void main(String[] args) { Dog dog = new Dog(); dog.gender = "母狗"; dog.name = "舔狗"; System.out.println("狗的名字 : " + dog.name); System.out.println("狗的性别 : " + dog.gender); dog.eat(dog.name); dog.sleep(dog.name); dog.bite();//调用狗特有的方法 //使用构造方法初始化类的属性 Cat cat = new Cat("加菲", "公"); System.out.println("猫的名字 : " + cat.name); System.out.println("猫的性别 : " + cat.gender); cat.eat(cat.name); cat.sleep(cat.name); cat.hoots();//调用猫独有的方法 } }不可继承构造方法不能被继承。public class Animal { public String name; String gender; public Animal() { System.out.println("父类无参构造方法"); } } public class Dog extends Animal { //子类的无参构造方法 public Dog() { System.out.println("Dog的无参构造方法"); } } public class TestAnimal { public static void main(String[] args) { Dog dog = new Dog(); } }父类被private修饰的属性和方法不能被继承/** * 父类,超类。抽象所有的动物,动物都具有名字,性别等特征。都具有吃饭和睡觉等行为 */ public class Animal { public String name; private String gender; public void eat() { System.out.println("eat()-----"); } private void sleep() { System.out.println("sleep()-----"); } } public class Dog extends Animal { //子类里什么都不写,所有属性和方法都使用父类的。 } public class TestAnimal { public static void main(String[] args) { //创建子类对象 Dog dog = new Dog(); //使用子类对象的引用区访问父类的属性 //访问父类public修饰的属性 System.out.println(dog.name);//能正常访问到 //访问父类private修饰的属性。 //会报错,因为父类的gender是用private修饰的,所以不能访问 //System.out.println(dog.gender); //访问父类public修饰的方法 dog.eat();//能正常访问到 //访问父类private修饰的方法 //dog.sleep();//不能访问到。 } }父类中使用默认修饰符(default)修饰的属性和方法在不同包的子类中不能被继承。package oop.inherit; /** * 父类,超类。抽象所有的动物,动物都具有名字,性别等特征。都具有吃饭和睡觉等行为 */ public class Animal { public String name; String gender; public void eat() { System.out.println("eat()-----"); } void sleep() { System.out.println("sleep()-----"); } } package oop; import oop.inherit.Animal; public class Duck extends Animal{ } public class TestAnimal { public static void main(String[] args) { //创建子类对象 Duck duck = new Duck(); //使用子类对象的引用区访问父类的属性 //访问父类public修饰的属性 System.out.println(duck.name);//能正常访问到 //访问父类默认的属性。 //会报错,因为父类的gender是默认的,并且和父类不在同一个包下。所以不能访问 //System.out.println(duck.gender); //访问父类public修饰的方法 duck.eat();//能正常访问到 //访问父类默认方法 duck.sleep();//不能访问到。 } }方法重写(Override)概念:子类中有和父类相同方法签名和返回类型的方法称为方法的重写。public class Animal { public void eat() { System.out.println("父类eat()"); } } public class Dog extends Animal { //子类重写父类的eat方法。 public void eat() { System.out.println("子类eat()----"); } } public class TestAnimal { public static void main(String[] args) { //创建子类对象 Dog dog = new Dog(); //因为子类重写了父类方法,我们通过子类引用调用重写的方法时就不会去执行父类的方法。 dog.eat();//访问子类重写父类的eat方法 } }重写的特点:方法的签名必须要相同:方法名相同、参数相同。返回值相同,子类访问修饰符至少要大于或等于父类访问修饰符。public class Animal { public void eat(String name) { System.out.println("父类eat()"); } } public class Dog extends Animal { //@Override此处eat方法虽然返回值和方法名都和父类相同,但是因为新参不一样,所以不是从写 //此处加上重写注解@Override就会报错 public void eat(int age) { System.out.println("子类eat()----"); } }重写作用:父类中的方法无法满足子类的业务需求是,就需要重写父类的方法。注意:重写时建议在子类的方法上加上注解@Override, 构造方法不能被重写,不能被继承的方法不能够被重写。当子类重写父类方法和定义与父类相同属性时,在代码执行时,会使用子类自己定义的属性和重写后的方法。public class Animal { private void eat() { System.out.println("父类eat()"); } } public class Dog extends Animal { //此处方法不是从写,因为父类的eat方法是被private修饰的,所以子类不能重写。 //因为不是从写,所以加上重写的注解Override会报错 //@Override Method does not override method from its superclass public void eat() { System.out.println("子类eat()----"); } }不能被重写的方法被final关键字修饰的方法不能被子类重写被static修饰的方法也不能被子类重写,但是可以在子类中再次声明被private修饰的方法也不可以被重写public class Person { public String name = "人类"; /** * 被final关键字修饰的方法不能被子类重写 */ public final void study() {} /** * 被static修饰的方法也不能被子类重写,但是可以在子类中再次声明 */ public static void eat() {} /** * 被private修饰的方法也不可以被重写 */ private void run(){} } public class Man extends Person { @Override public void study() { System.out.println("男孩学习java"); } //@Override Method does not override method from its superclass public static void eat(){} //@Override Method does not override method from its superclass public void run(){} }重载(Overload)与重写(Override)区别重写(Override)重载(Overload)定义方法名相同、参数相同(参数类型、参数个数、顺序都要相同),返回值相同,子类访问修饰符至少要大于或等于父类访问修饰方法名相同,参数列表不同(参数类型、参数个数、顺序)权限子类访问修饰符至少要大于或等于父类访问修饰对权限没有任何要求范围发生在继承关系中发生在同一个类里super关键字this关键字代表当前对象,super关键字代表当前对象的父类对象。如果一个类有父类。那么当创建子类对象时会生成两个this和两个super,注意:创建哪个对象,生成的this和super引用就只能在哪个对象中使用。super作用:可以在子类使用super调用父类的属性、方法、构造方法使用super调用父类属性和方法public class Animal { public String name; public void eat() { System.out.println("父类eat()"); } } public class Dog extends Animal { public void m(String name) { //调用父类属性 super.name = name; //调用父类方法eat() super.eat(); } } public class TestAnimal { public static void main(String[] args) { //创建子类对象 Dog dog = new Dog(); //因为子类重写了父类方法,我们通过子类引用调用重写的方法时就不会去执行父类的方法。 dog.m("土狗"); System.out.println(dog.name); } }使用super调用父类构造方法public class Animal { public String name; public Animal() { System.out.println("父类无参构造方法"); } public Animal(String name) { this.name = name; } } public class Dog extends Animal { //定义无参构造方法 public Dog() { //调用父类无参构造方法 //当不显示写super()时虚拟机会默认添加上该调用 //super()调用必须要写在构造方法内的第一行 //调用父类有参构造方法 super("斑点狗"); System.out.println("子类无参构造方法"); //super("斑点狗");必须要写到第一行,否则报错。 } } public class TestAnimal { public static void main(String[] args) { //创建子类对象 Dog dog = new Dog(); System.out.println(dog.name); } }当创建一个对象时都会生成一个this和一个super引用//父类 public class Animal { String name="爹"; String gender; public Animal(){ System.out.println("这个是父类构造方法"); } public void eat(String name){ System.out.println(name+"吃草"); } } //子类 public class cat extends Animal { public cat(String name ,String gender ){ this.name=name ; this.gender=gender; System.out.println(name+gender); } public void jiao(String name){ System.out.println(name+"开始喵喵叫"+super.name); } @Override public void eat(String name){ System.out.println(super.name+"吃鱼小猫咪都吃罐罐"); } } //测试类 public class test { static cat c=new cat("kitey","girl"); static Animal animal=new Animal(); public static void main(String[] args) { System.out.println(c.name+" "+c.gender); System.out.println(animal.name); c.jiao(c.name); c.name="哆啦A梦"; c.eat(c.name); } }子类对象创建过程当类没有继承关系时的对象创建过程当程序遇到代码中使用new指令时,就会把new关键字之后的类加载到内容,并开始创建对象根据该类中定义的变量到内存中申请内存空间实例化该类里的成员变量执行该类的构造方法将创建出来的对象的地址赋给一个变量,该变量就叫做引用如下代码示例:public class Animal { public String name; } public class TestAnimal { public static void main(String[] args) { //创建子类对象 Animal animal = new Animal(); } }暂时无法在飞书文档外展示此内容当类有继承关系时对象的创建过程先创建其父类的对象,实例化父类的成员变量。执行父类的构造方法.创建子类对象。实例化子类的成员变量,执行子类的构造方法。多态概念父类引用指向子类对象。从而产生多态。通俗理解,同一个方法调用,产生不同的输出结果。public class Person { public String name = "人类"; public void study() { System.out.println(name + "学习"); } } public class Man extends Person { String name; public Man(){} public Man(String name) { this.name = name; } //重写父类的学习方法 @Override public void study() { System.out.println("男孩学习java"); } } public class Bobs extends Man{ String name; public Bobs(){} public Bobs(String name) { this.name = name; } } public class Girl extends Person{ String name; public Girl(String name) { this.name = name; } @Override public void study() { System.out.println("女孩学习HTML5"); } } public class TestPerson { public static void main(String[] args) { invokeStudy(new Man("男孩")); invokeStudy(new Girl("女孩")); //Bobs类没有重写study方法 invokeStudy(new Bobs("bobs")); } public static void invokeStudy(Person person) { //此处person就是父类引用,但具体执行代码时会根据传入的具体子类去执行子类的方法 //从而产生多态 person.study(); } }多态产生的条件必须要有继承必须要有方法的重写必须要有父类引用指向子类对象多态的特点如果发生多态,那么调用的一定是子类重写父类的方法如果发生多态,父类引用是无法调用子类自己独有的方法和属性。可以使用使用向下转型来访问子类的独有属性和方法向上转型和向下转型当我们把一个子类对象赋值给父类引用时就是一种默认的向上转型,语法结构:父类名 引用 = new 子类名(); 如:Person person = new Man();向下转型,类似于强制类型转换中的把高位的数据赋值给低位时做的强制转换类似:语法结构: 子类名 引用 = (子类名)父类引用;int a = 10; short s; s = (short) a;基本数据类型强制转换 把父类转成子类,就是从高到低的转换,我们称为向下转型 Person person = new Person(); Man man = (Man)person; public class Person { public String name = "人类"; public void study() { System.out.println(name + "学习"); } } public class Man extends Person { String name; String laryngeal = "喉结"; public Man(){} public Man(String name) { this.name = name; } //重写父类的学习方法 @Override public void study() { System.out.println("男孩学习java"); } public void run() { System.out.println("男孩会打球"); } } public static void main(String[] args) { //向上转型 Person person = new Man(); person.study(); System.out.println(person.name);// //System.out.println(person.laryngeal);//访问子类独有的属性 //person.run();//父类引用不能访问子类独有的方法,当然属性也是不能访问的 //如果非要访问子类独有的属性和方法,就需要把父类引用向下转型成子类对象 //向下转型 Man man = (Man)person; man.run();//此时可以访问子类独有的方法 man.study(); System.out.println(man.laryngeal);//访问子类独有的属性 }instanceOf关键字作用:判断某个对象是否属于某一种类型,属于就返回true,否则返回false;语法结构:对象名/引用 instanceOf 类型应用场景:在向下转型的过程中先使用它来判断需要被转的引用是否是目标类型。Man man = null; if (person instanceof Man) { man = (Man) person; }练习学生喂养三种宠物:猫、狗、鸟动物类(Animal):属性(name, age), 方法(speak, move, eat)猫类(Cat)继承动物类,重写父类的方法。添加方法play();狗类(Dog)继承动物类,重写父类的方法。添加方法play();鸟类(Bird)继承动物类,重写父类的方法。添加方法play();学生类(Student):属性(name), 方法(feed(Animal animal))public class Animal { //动物的属性 public String name; public int age; //行为---方法 public void speak() { System.out.println("Animal speak"); } public void move() { System.out.println("Animal move"); } public void eat() { System.out.println("Animal eat"); } } public class Cat extends Animal{ //定义构造方法给属性赋值 public Cat(String name, int age) { this.name = name; this.age = age; } //重写父类方法 @Override public void speak() { System.out.println("Cat speak"); } @Override public void move() { System.out.println("Cat move"); } @Override public void eat() { System.out.println("Cat eat"); } //定义子类独有的方法paly public void play() { System.out.println("Cat play"); } } public class Dog extends Animal{ public Dog(String name, int age) { this.name = name; this.age = age; } //重写父类方法 @Override public void speak() { System.out.println("Dog speak"); } @Override public void move() { System.out.println("Dog move"); } @Override public void eat() { System.out.println("Dog eat"); } //定义子类独有的方法paly public void play() { System.out.println("Dog play"); } } public class Bird extends Animal{ public Bird(String name, int age) { this.name = name; this.age = age; } //重写父类方法 @Override public void speak() { System.out.println("Bird speak"); } @Override public void move() { System.out.println("Bird move"); } @Override public void eat() { System.out.println("Bird eat"); } //定义子类独有的方法paly public void play() { System.out.println("Bird play"); } } public class Student { public String name;//学生名字 //通过构造方法给属性赋值 public Student(String name) { this.name = name; } //学生喂养宠物 public void feed(Animal animal) { System.out.println(name + "养了一只" + animal.name + "已经" + animal.age + "岁了"); animal.eat(); animal.move(); animal.speak(); //调用子类独有play方法,就需要把父类向下转型 if (animal instanceof Cat) ((Cat) animal).play(); else if(animal instanceof Dog) ((Dog) animal).play(); else if (animal instanceof Bird) ((Bird) animal).play(); } } public class Test { public static void main(String[] args) { //创建一个学生,名字叫Steven Student student = new Student("Steven"); student.feed(new Cat("Blue Cat", 3)); student.feed(new Dog("Labrador", 5)); student.feed(new Bird("bird", 12)); } }静态绑定和动态绑定概念Java中有两种绑定方式,一种是静态绑定,又称作前期绑定。另一种是动态绑定,亦称为后期绑定。两者的区别:静态绑定发生在编译时期(也就是javac阶段),动态绑定发生在运行时(就是执行java指令后)使用private或static或final修饰的变量或者方法,使用静态绑定。而被子类重写的方法则会根据运行时的对象进行动态绑定。静态绑定使用类信息来完成,而动态绑定则需要使用对象信息来完成。重载(Overload)的方法使用静态绑定完成,而重写(Override)的方法则使用动态绑定完成。静态绑定public class Test { public static void main(String[] args) { String str = new String(); Person person = new Person(); person.play(str); } static class Person { public void play(Object obj) { System.out.println("这是一个对象实例的play"); } public void play(String str) { System.out.println("这是一个字符串实例的play"); } } } //输出结果是: //"这是一个字符串实例的play"动态绑定案例一:class Person { protected String name = "我是person"; public void method() { System.out.println("person method"); } } class Boy extends Person { protected String name = "我是boy"; public void method() { System.out.println("boy method"); } } public class Test { public static void main(String[] args) { Person p = new Boy(); System.out.println(p.name); p.method(); } } //输出结果是: //我是person //boy method案例二:class Person { protected String name = "我是person"; public void method() { System.out.println("person method"); } } class Boy extends Person { protected String name = "我是boy"; } public class Test { public static void main(String[] args) { //1:编译器检查对象的声明类型和方法名。假设我们调用p.method()方法, // 并且p已经被声明为Boy类的对象,那么编译器会列举出Boy类中 // 所有的名称为method的方法和从Boy类的父类继承过来的method方法 //2:接下来编译器检查方法调用中提供的参数类型。如果在所有名称为method的 // 方法中有一个参数类型和调用提供的参数类型最为匹配,那么就调用这个方法, // 这个过程叫做“重载解析” //3:当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同p所指向的对象的 // 实际类型相匹配的方法版本。假设boy类定义了mehod()那么该方法被调用, // 否则就在Boy的父类(Person类)中搜寻方法method() Person p = new Boy(); System.out.println(p.name); p.method(); } } //输出结果是: //我是person //person method总结:1、子类的对象调用到的是父类的成员变量,动态绑定针对的范畴只是对象的方法,而属性采取静态绑定方式。 2、执行p.method()时会先去调用子类的method方法执行,若子类没有则向上转型去父类中寻找。 所以在向上转型的情况下,对象的方法可以找到子类,而对象的属性还是父类的属性。多态总结定义: 在Java中,多态是指不同类的对象在调用同一个方法时所呈现出的多种不同行为。说明: 通常来说,在一个类中定义的属性和方法被其他类继承或重写后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同形态。作用: 通过多态,消除了类之间的耦合关系,大大提高了程序的可扩展性和可维护性。注意: Java的多态性是由类的继承、方法重写以及父类引用指向子类对象体现的。由于一个父类可以有多个子类,多个子类都可以重写父类方法,并且多个不同的子类对象也可以指向同一个父类。这样,程序只有在运行时才能知道具体代表的是哪个子类对象,这就体现了多态性。封装什么是封装现实中的封装就是把事物的细枝末节隐藏,对外提供一个统一的入口在java的世界里,封装就是隐藏代码里的属性和一些不希望被用户访问到的方法。而提供一些公共的方法作为入口,供使用者通过这些入口来和我们的程序打交道,但是用户只能知道入口方法的名字以及方法有哪些参数,对于方法具体执行的逻辑还是不能暴露给用户。一个程序示例:封装一个java程序,叫做Person.java/** *程序的封装,所有属性都应该是封装起来,不允许用户直接访问。 */ public class Person { private String name = "我是person"; private String password = "sxxx"; private int age = 18; //如果某些属性是希望被其他用户访问的, //那么可以通过提供方法的形式让其访问或修改。这就是针对属性的get和set方法 /** * 获取用户名,也是需要通过方法的形式获取,避免使用引用.属性名 的方式访问 * @return */ public String getName() { return name; } /** * 修改用户名,通常都是通过set方法,而不允许用户直接修改属性 * @param name */ public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class Test { public static void main(String[] args) { //创建一个Person对象 Person person = new Person(); person.setName("张三"); //通过方法获取用户名字 System.out.println(person.getName()); } }为什么需要封装对象因为在对象外部,为对象赋值,可能存在非法数据的输入。但是就目前而言,是没有办法可以直接避免非法数据的输入的。即便把属性定义为private修饰,也不能完全屏蔽掉非法输入。用户还是可以通过反射技术访问对象内部private修饰的属性。不过把属性都设置为私有相对来说还是比较安全的一种做法。封装可以使我们的对象更安全的发布。通过封装我们可以把一个不是线程安全的对象变成一个线程的安全的。在设计一个类时尽量做到以下几点私有化属性,也就是把属性使用private修饰符修饰对于这些属性,对外提供公共的get和set方法提供有参无参构造方法final关键字final :java中的一个保留字。中文意思是:最后的,最终的,不可被修改的。final关键字可以被用来修饰类,方法,属性。final修饰类当final修饰一个类时,表示此类已经是一个完整的,最终的类,功能完备,不需要再被改变,所以被final修饰类不能被继承。java中有许多类就是被申明为fianl的,如String,Integer。/** * 演示被final修饰的类不可被继承 */ public final class One { } /** * 该类继承One类 */ public class Two extends One{//Cannot inherit from final 'oop.finalkeywords.One' //不能从final 类One 继承 }final修饰方法final修饰方法时,此方法不能被覆盖,也即是不能被重写。/** * 演示被final修饰的方法不能重写 */ public class One { public final void m1() { } } /** * 该类继承One类 */ public class Two extends One{ //重写父类的m方法,会报错 public void m1(){} }final修饰属性final修饰变量,只能显示赋值一次,此后该变量的值不能被改变。注意:系统默认赋值不算第一次赋值public class Test { final int b; public Test(int b) { this.b = b;//被final修饰的成员变量可以延迟到构造方法里赋值 } static final int c; static { c = 101;//静态的final类变量可以在静态代码块中赋值。 } public static void main(String[] args) { //定义一个final变量 final int a ; //修改final变量a的值 a = 101; //修改全局成员变量b的值 //b = 200;//报错原因是因为b已经是一个final类型的变量,并且被赋值过一次。 } }当final修饰变量时,该变量就是常量,常量又分为静态常量,成员常量以及局部常量。public class Test { //静态常量 static final int a = 10; //成员常量 final int b = 20; public static void main(String[] args) { //局部常量 final int c = 30; } }final修饰引用类型当final修饰的变量是一个引用时,那么该引用的地址内容不能再改变,而引用指向的那块内存空间中的值是可以修改的。final修饰对象引用当final修饰一个引用类型的变量时,所谓的不可变是指引用的地址不可变,而可变是指引用指向的具体内容可变。public class TestFinalRef { public static void main(String[] args) { //创建一个Person对象,并把该对象赋值给一个final修饰的变量 final Person person = new Person(); //修改person引用的值 //person = null; Cannot assign a value to final variable 'person' //修改person引用对象内部的属性 person.setName("Bobs"); } }final修饰数组变量public class TestFinalRef { public static void main(String[] args) { final String[] arr = new String[10]; //arr = null;因为arr已经被final修饰,所以引用地址不能再变 arr[0] = "hello";//但引用指向的内容是可以被修改的。 } }抽象类(abstract)java中使用关键字abstract来定义抽象。当用来修饰类时,该类就是一个抽象类,修饰方法时,该方法就是抽象方法。abstract只能用来修饰类和方法。当我们创建父类时,可以预先知道某些方法是一定会被子类重写的。因此这些一定会被重新覆盖掉的方法在父类中就没有必要写具体的实现,应该该方法定义为抽象方法。java中规定,拥有抽象方法的类叫做抽象类。特点:有构造方法,不能创建对象。public abstract class AbstractClassDemo { //定义构造方法 public AbstractClassDemo() { } } public class TestAbstractClassDemo { public static void main(String[] args) { //创建抽象类对象 AbstractClassDemo acd = new AbstractClassDemo();//会报错 } }修饰类的语法结构:<public> abstract class 类名{}public abstract class AbstractClassDemo { //类型可以不用写任何代码逻辑 }修饰方法的语法结构:<public> abstract void 方法名();public abstract class AbstractClassDemo { //类型可以不用写任何代码逻辑 //定义一个成员抽象方法 public abstract void m1(); //定义一个静态抽象方法,不允许这样定义,会报错:Illegal combination of modifiers: 'abstract' and 'static' public abstract static void m2(); }抽象类使用注意事项:抽象类里可以没有抽象方法public abstract class AbstractClassDemo { public void m1() { System.out.println("成员方法m1()"); } }抽象类里可以同时具有抽象方法和普通方法public abstract class AbstractClassDemo { public void m1() { System.out.println("成员方法m1()"); } //抽象方法 public abstract void m2(); }如果一个类里包含了抽象方法,那么该类必须要被定义为一个抽象类,否则会报错。如果子类不实现父类的抽象方法,那么子类也应该要被申明为抽象的类,否则报错。总结:抽象类更像是一种规范,它定一个统一的模版,让所有继承它的子孙都必须实现自己定义的模版。达到统一管理的目的,并且也可以实现向实际项目开发中的模块与模块之间代码的解耦。也是实现多态的关键要素之一。面试题(java2022刘浪提供)是不是所有没有方法体的方法都是抽象方法:答案:不是,比如native方法。接口(interface)接口就是一种标准,定义接口的过程,就是定义标准。定义接口相当于一种特殊的抽象类。定义方式、组成部分与抽象类类似。使用关键字interface来修饰语法:public interface 接口名{}public interface InterfaceDemo { }接口的特点:没有构造方法,不能创建对象。public interface InterfaceDemo { //定义构造方法 //public InterfaceDemo(){},不允许这样使用 } public class TestInterfaceDemo { public static void main(String[] args) { //创建接口对象 //new InterfaceDemo();因为接口不允许有构造方法,所以必然不允许创建对象 } }只能定义公共静态常量,公共抽象方法。在jdk8之后,新增可以在接口内部定义默认方法和静态方法。public interface InterfaceDemo { //定义常量 public static final int a = 10; //接口中的常量默认使用public static final修饰。所以可以省略不写 int b = 101; //公共抽象方法 public abstract void m1(); //定义默认方方法 default void m2() { System.out.println("接口中的默认方法"); } //静态方法 static void m5() { System.out.println("接口中的静态方法"); } }接口的实现java中使用关键字implements来实现一个接口,实现接口的子类必须要提供接口中抽象方法的实现public class InterfaceDemoImple implements InterfaceDemo{ public void m1() { System.out.println("实现接口中的方法"); } }因为接口不允许创建对象,所以要访问接口中的默认方法时只能通过创建子类对象去访问//通过创建子类对象去访问接口总的默认方法 new InterfaceDemoImple().m2();接口和抽象类的区别相同点:都可以被编译为字节码文件都不能创建对象都可以作为引用类型不同点:接口中的所有属性都是被public static final修饰的常量。接口中所有的方法都是public abstract修饰的。接口没有构造方法。代码块和静态代码块。接口使用注意事项任何类在实现接口时,必须实现接口中所有的方法,否则该类就是一个抽象类实现接口中的抽象方法时,访问修饰符必须是public修饰的接口的好处程序的耦合性降低可以更好的实现多态共容易设计程序的框架共容易更换具体实现设计与实现分离接口和抽象类联合使用我们都知道,java是不支持多继承,那么如果非要在java里实现多重,那就需要使用接口。以下代码示例演示普通类,接口,抽象类三者之间继承和实现的各种用法//一个类可以实现多个接口 class N implements B, C{} //在Java中使用多继承,Z继承A,但同时也要继承B和C class Z extends A implements B, C{} //抽象类实现接口 abstract class X implements B, C{} //接口与接口相互继承 interface T extends B{} //接口是否可以实现接口 //interface H implements C{}//java不支持接口实现接口 //接口继承抽象类 //interface P extends A{}//java不支持这种继承关系 //写一个抽象类 abstract class A {} //写两个接口 interface B{} interface C{}作业使用接口或抽象类改造学生喂养宠物的练习列出类与接口的区别相同点: 都可以被编译为字节码文件 都可以申明公共的静态常量 不同点: 接口中的所有属性都是被public static final修饰的常量。 接口中所只能定义两种方法,第一种被public abstract修饰,并且没有实现。另一种是default修饰的方法 接口没有构造方法。代码块和静态代码块。列出抽象类和类的区别相同点: 都可以被编译为字节码文件 都有构造方法和普通方法。 都可以定义变量,代码块 不同点: 抽象类必须要使用关键字abstract修饰 抽象类不能创建对象。 抽象类可以只定义方法体,而不需要写实现设计模式-工厂模式为什么要在此处讲解工厂模式?关于面向对象的三大特性:继承、封装、多态,不管怎么背概念和文字举例子,其实都很难做到第一次学就对这三大特性有很好的理解。即便是编写一些dos窗口操作的管理系统demo,也很难融合三大特性的特点。但是工厂模式把面向对象的三大特性应用的非常好,因此,此处只要我们能把工厂模式理解,并且在学习完后能独自把这种设计模式用代码实现出来,我相信你会对面向对象的三大特性有另一种不一样的认识。并且工厂模式也被应用到各种主流框架,如大名鼎鼎的spring框架。所以学好工厂模式,不仅能帮助理解三大特性,还能进一步体会到Java编程思想美。version-1.0,用户想订购披萨暂时无法在飞书文档外展示此内容/** * 客户类,需要订购披萨的用户 */ public class Customer { public static void main(String[] args) { //创建一个披萨店,调用披萨店的订购披萨方法完成披萨的订购 PizzaStore pizzaStore = new PizzaStore(); pizzaStore.orderPizza(); } } public class PizzaStore { public Pizza orderPizza() { Pizza pizza = new Pizza(); pizza.prepare();//准备 pizza.bake();//烘烤 pizza.cut();//切片 pizza.box();//打包 return pizza; } } public class Pizza { public void prepare() { System.out.println("准备"); System.out.println("搅拌面团..."); System.out.println("正在添加酱汁..."); System.out.println("添加浇头: "); } public void bake() { System.out.println("在350℃下烘烤25分钟"); } public void cut() { System.out.println("把披萨对角切成片"); } public void box() { System.out.println("将披萨放在官方披萨店的盒子里"); } }version-1.1,可以根据用户提供的口味提供不同的披萨给他们public class Customer { public static void main(String[] args) { PizzaStore02 pizzaStore02 = new PizzaStore02(); pizzaStore02.orderPizza("cheese"); } } public class PizzaStore02 { public Pizza orderPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) pizza = new CheesePizza(); else if (type.equals("greek")) pizza = new GreekPizza(); else if (type.equals("pepperoni")) pizza = new PepperoniPizza(); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box();//装盒子 return pizza; } } public interface Pizza { public abstract void prepare(); default void bake() { System.out.println("在350℃下烘烤25分钟"); } default void cut() { System.out.println("把披萨对角切成片"); } default void box() { System.out.println("将披萨放在官方披萨店的盒子里"); } } public class CheesePizza implements Pizza { public void prepare() { System.out.println("准备CheesePizza"); System.out.println("搅拌面团..."); System.out.println("正在添加酱汁..."); System.out.println("添加浇头: "); } }暂时无法在飞书文档外展示此内容version-2.0 简单工厂,把创建对象的过程交给工厂来完成public class Customer { public static void main(String[] args) { PizzaStore03 pizzaStore03 = new PizzaStore03(new SimplePizzaFactory()); pizzaStore03.orderPizza("cheese"); } } public class PizzaStore03 { SimplePizzaFactory factory; public PizzaStore03(SimplePizzaFactory factory) { this.factory = factory; } public Pizza orderPizza(String type) { Pizza pizza = factory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box();//装盒子 return pizza; } } public class SimplePizzaFactory { public Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) pizza = new CheesePizza(); else if (type.equals("greek")) pizza = new GreekPizza(); else if (type.equals("pepperoni")) pizza = new PepperoniPizza(); return pizza; } }严格来说,简单工厂不是一个设计模式,更像是一种编程习惯暂时无法在飞书文档外展示此内容version-2.1 把createPizza移到PizzaStore类里暂时无法在飞书文档外展示此内容工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样测试程序或用户使用的永远都是超类,而真正创建对象的子类,客户接触不到,这就实现了解耦。使用到的类以上代码画出平行视角类关系暂时无法在飞书文档外展示此内容工厂方法让类把实例化推迟到子类public class PizzaTestDrive { public static void main(String[] args) { AbstractPizzaStore nyStore = new NYPizzaStore(); AbstractPizzaStore chicagoStore = new ChicagoPizzaStore(); Pizza pizza = nyStore.orderPizza("cheese"); System.out.println("Jackson Ordered a " + pizza.getName() + "\n"); pizza = chicagoStore.orderPizza("cheese"); System.out.println("James ordered a " + pizza.getName() + "\n"); } } public abstract class AbstractPizzaStore { Pizza orderPizza(String type) { Pizza pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box();//装盒子 return pizza; } public abstract Pizza createPizza(String type); } public class NYPizzaStore extends AbstractPizzaStore{ @Override public Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) pizza = new NYStyleCheesePizza(); else if (type.equals("pepperoni")) pizza = new NYStylePepperoniPizza(); else if (type.equals("clam")) pizza = new NYStyleClamPizza(); else if (type.equals("veggie")) pizza = new NYStyleVeggiePizza(); return pizza; } } public class ChicagoPizzaStore extends AbstractPizzaStore{ @Override public Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) pizza = new ChicagoStyleCheesePizza(); else if (type.equals("pepperoni")) pizza = new ChicagoStylePepperoniPizza(); else if (type.equals("clam")) pizza = new ChicagoStyleClamPizza(); else if (type.equals("veggie")) pizza = new ChicagoStyleVeggiePizza(); return pizza; } } public abstract class Pizza { String name; String dough; String sauce; ArrayList toppings = new ArrayList(); public void prepare() { System.out.println("准备 " + name); System.out.println("搅拌面团..."); System.out.println("正在添加酱汁..."); System.out.println("添加浇头: "); //此处逻辑照抄就行 for (int i = 0; i < toppings.size(); i++) { System.out.println(" " + toppings.get(i)); } } public void bake() { System.out.println("在350℃下烘烤25分钟"); } public void cut() { System.out.println("把披萨对角切成片"); } public void box() { System.out.println("打包。。。。。。"); } public String getName() { return name; } } public class NYStyleCheesePizza extends Pizza{ public NYStyleCheesePizza() { name = "NY Style Sauce and Cheese Pizza"; dough = "Thin Crust Dough"; sauce = "Marinara Sauce"; //toppings.add("Grated Reggiano Cheese"); } } 请自己实现: NYStylePepperoniPizza.java, NYStyleClamPizza.java, NYStyleVeggiePizza.java public class ChicagoStyleCheesePizza extends Pizza{ public ChicagoStyleCheesePizza() { name = "Chicago Style Deep Dish Cheese Pizza"; dough = "Extra Thick Crust Dough"; sauce = "Plum Tomato Sauce"; toppings.add("Shredded Mozzarella Cheese"); } public void cut() { System.out.println("Cutting the pizza into square slices"); } } 也请你自己实现: ChicagoStylePepperoniPizza.java ChicagoStyleClamPizza.java ChicagoStyleVeggiePizza.java一个完整的类内部可以编写的代码模块程序的执行顺序:①静态代码块②动态代码块③构造方法。并且静态代码块只会被执行一次。public class CompleteClass { //属性 private String username; private static int age; //定义常量 public static final String GENDER = "男"; public final String address = "男"; //代码块 { System.out.println("动态代码块"); } static {//静态代码块只会执行一次。 System.out.println("静态代码块"); } //构造方法 //无参数,默认 public CompleteClass(){ System.out.println("无参构造方法"); } //有参数 public CompleteClass(Person person) { //调用无参构造方法 this(); System.out.println("有参够方法 = " + person); } //普通成员方法 public void getUsername() { System.out.println("username method"); } //普通静态方法 public static void getAge() { System.out.println("age method"); } public void setUsername(String username) { this.username = username; } public static void setAge(int age) { CompleteClass.age = age; } public static String getGENDER() { return GENDER; } public String getAddress() { return address; } public static void main(String[] args) { //访问类里边的属性,分为两种方式,访问静态的直接使用类名.的形式访问,访问非静态就需要创建对象 //使用对象的引用去访问。根据java封装的特性,类里边的属性通常都被定义为private的。所以都只能通过 //方法区访问类里的属性。 CompleteClass cc = new CompleteClass(); System.out.println(); //CompleteClass c = new CompleteClass(); //程序的执行顺序:①静态代码块②动态代码块③构造方法。并且静态代码块只会被执行一次。 System.out.println(cc.getAddress()); cc.getUsername(); //调用静态方法可以通过类或对象引用 CompleteClass.getAge(); cc.getAge(); //试图修改常量,会报错 //CompleteClass.GENDER = "女"; //cc.address = ""; } }八、嵌套类概念将一个类定义在另一个类或一个方法内部,称为内部类。内部类分为:成员内部类、局部内部类、匿名内部类和静态内部类。类的层次结构暂时无法在飞书文档外展示此内容内部类public class 外部类 { public class 内部类 { } } /** * 内部类 */ public class OuterClass { //定义静态属性 public static String username = "静态属性"; public String password = "动态属性"; private int age = 19; //构造方法 public OuterClass() { System.out.println("OuterClass 构造方法"); System.out.println(this); } //外部类成员方法 public void show() { System.out.println("外部类成员方法"); } public class InnerClass { //Inner classes cannot have static declarations //public static String username =""; public String username = "内部类的username"; //构造方法 public InnerClass() { System.out.println("InnerClass 构造方法"); System.out.println(this);//内部类引用 System.out.println(OuterClass.this);//获取外部类的this引用 } public int sum() { //访问外部类的属性 System.out.println("username = " + username); System.out.println("password = " + password); System.out.println("age = " + age); return 100 + 500; } //Inner classes cannot have static declarations //public static void show() {} } } public class TestOuterClass { public static void main(String[] args) { //创建外部类对象 OuterClass outerClass = new OuterClass(); //创建内部类对象 OuterClass.InnerClass innerClass = outerClass.new InnerClass(); //调用内部类的成员方法 System.out.println(innerClass.sum()); //调用外部类成员方法 outerClass.show(); //访问外部类的静态属性,可以通过类名或引用访问 System.out.println(OuterClass.username); System.out.println(outerClass.username); //访问外部类的成员属性 System.out.println(outerClass.password); //访问内部类的静态属性,不能实现,因为内部类里不能定义静态属性 //访问内部类的成员属性 System.out.println(innerClass.username); } }静态内部类public class 外部类 { public static class 内部类 { } } /** * 静态内部类可以定义和外部类相同名字的属性。 * 静态内部类不能够访问外部类里的成员属性 * 静态内部类不能够访问外部类里的成员方法。 * 无法在内部类里获取到外部类的this引用 * 静态内部类也有this引用 */ public class OuterClassStatic { //静态属性 public static String username = "outer static username"; //成员属性 public String password = "outer password"; //私有属性 private static int age = 20; public OuterClassStatic() { System.out.println("外部类构造方法"); } public void outerM1() { System.out.println(age); } public static class InnerClassStatic { //静态属性 public static String un = "inner static username"; //成员属性 public String pwd = "inner password"; //私有属性 private static int innerAge = 200; public InnerClassStatic() { System.out.println("内部类构造方法"); } public void innerM1() { //测试是否可以获取外部类的私有属性 System.out.println(age); //访问外部类的属性 System.out.println(username); //不允许访问外部类的成员属性,因为这是一个静态内部类。 //System.out.println(password); } } } public class TestOuterCLassStatic { public static void main(String[] args) { //创建外部类对象 OuterClassStatic ocs = new OuterClassStatic(); //创建内部类对象 OuterClassStatic.InnerClassStatic ics = new OuterClassStatic.InnerClassStatic(); //访问外部类的静态属性和成员属性 System.out.println(ocs.username); System.out.println(OuterClassStatic.username); System.out.println(ocs.password); //直接访问外部类的私有属性 //System.out.println(ocs.age); //System.out.println(OuterClassStatic.age); //访问内部类的属性 System.out.println(ics.un); System.out.println(OuterClassStatic.InnerClassStatic.un); System.out.println(ics.pwd); //调用内部类的方法访问内部的私有属性 ics.innerM1(); //调用外部类的方法访问外部类的私有属性 ocs.outerM1(); //测试静态内部类里的成员属性,成员属性属于具体的对象,没创建一个对象就会有一份单独的值。 OuterClassStatic.InnerClassStatic ics1 = new OuterClassStatic.InnerClassStatic(); ics1.pwd = "update inner password"; System.out.println(ics1.pwd); System.out.println(ics.pwd); //静态属性,不管创建多少个对象,内存中都只会保存一份值 ics1.un = "update inner static username"; System.out.println(ics1.un); System.out.println(ics.un); System.out.println(ics == ics1); } }匿名内部类当一个接口中只有一个方法,并且该接口被作为一个方法的参数时,就会使用匿名内部类。public class AnonymousClass { static String username = "helloworld"; public static void main(String[] args) { String s = "abc"; //定义父类引用指向子类对象 Father father = new Xiao(); //调用方法。 sum(father); //通过匿名内部类创建子类对象传递给方法的父类引用 sum(new Father() { @Override public int m1(int a, int b) { System.out.println(s);//只能使用外部变量,但是不能修改,因为当一个变量在匿名内部类中使用时,该变量就会变成final修饰的常量 username = "hhehe";//全局变量就可以改变。 System.out.println(username); return a * b; } }); } public static void sum(Father father) { int a = 10, b = 220; System.out.println(father.m1(a, b)); } } interface Father { public int m1(int a, int b); } class Xiao implements Father { public int m1(int a, int b) { System.out.println("m1"); return a + b; } }局部内部类局部内部类: 定义在方法中特点:1、成员内部类中只能定义非静态属性和方法2、成员内部类中可以访问外部类的成员,私有的可以,静态的也可以3、局部内部类只能在方法内创建对象局部内部类要访问局部属性,那么该属性必须是被final修饰的局部常量。在jdk1.7版本中,如果局部变量在局部内部类中使用必须要显式的加上final在jdk1.8版本中,final是默认加上的因为局部变量在方法结束后,就会被销毁,而局部内部类的对象却要等到内存回收机制进行销毁,所以如果是常量的话,那么常量就会被存放在常量池中。public class LocalClass { private String un = "outer username"; private static int age = 20; public static void main(String[] args) { //访问LocalClass里的成员方法m LocalClass localClass = new LocalClass(); localClass.m(); } public void m() { String state = "error"; System.out.println("m()方法"); class InnerClassMethod { //定义变量 public String username; //定义方法 public void m() { System.out.println(this); System.out.println(un); System.out.println("age = " + age); System.out.println("state = " + state); System.out.println("InnerClassMethod m()方法"); } } InnerClassMethod icm = new InnerClassMethod(); icm.m(); System.out.println(icm.username); } }随堂案例设计一个动物声音“模拟器”,希望模拟器可以模拟许多动物的叫声,要求如下:(1)编写接口Animal, Animal接口有2个抽象方法cry()和getAnimalName(),即要求实现接口的各种具体动物类给出自己的叫声和种类名称。(2)编写模拟器类Simulator,该类有一个playSound(Animal animal)方法,该方法的参数是Animal类型,即参数animal可以调用实现Animal接口类重写的cry()方法播放具体动物的声音,调用重写的getAnimalName()方法显示动物种类的名称。(3)编写实现Animal接口的Dog类和Cat类。(4)编写测试类进行测试,在测试类的main方法中至少包含如下代码。Simulator simulator = new Simulator();simulator.playSound(new Dog());simulator.playSound(new Cat());public interface Animal { public void cry();//叫声 public String getAnimalName();//获取具体动物的名字 } public class Dog implements Animal{ @Override public void cry() { System.out.println("汪汪汪-------"); } @Override public String getAnimalName() { return "我是条狗🐶"; } } public class Cat implements Animal{ @Override public void cry() { System.out.println("喵喵--------"); } @Override public String getAnimalName() { return "我是只猫"; } } public class Simulator { public void playSound(Animal animal) { System.out.println("当前是动物 " + animal.getAnimalName() + " 在叫"); animal.cry(); } } public class Test { public static void main(String[] args) { //创建声音模拟器 Simulator simulator = new Simulator(); //调用模拟器发声的方法 simulator.playSound(new Dog()); simulator.playSound(new Cat()); } }作业:1、重载一个名为area的方法,然后该名字的方法可以根据传入参数,计算圆形、正方形、三角形、梯形的面积。public class Homework { //定义一个常量 private static final double PI = 3.14; public static void area(double r) { System.out.print("圆形的面积:"); System.out.println(PI * (r * r)); } public static void area(double len, double width, String type) { if (type.equals("三角形")){ System.out.println(String.format("三角形形的面积:%s", len * width/2)); } else { System.out.println(String.format("正方形的面积:%s", len * width)); } } public static void area(double upBottom, double downBottom, double high) { System.out.println(String.format("正方形的面积:%s", (upBottom + downBottom)*high/2)); } public static void main(String[] args) { area(3); area(3, 2, "三角形"); area(3, 3, "正方形"); area(2, 3, 6); } }2、某公司的雇员分为以下若干类:I. Employee:这是所有员工总的父类。1). 属性:员工的姓名,员工的生日月份。2). 方法:getSalary(int month) 根据参数月份来确定工资,如果该月员工过生日,则公司会额外奖励 100 元。II. SalariedEmployee:Employee 的子类,拿固定工资的员工。1). 属性:月薪。III. HourlyEmployee:Employee 的子类,按小时拿工资的员工,每月工作超出160小时的部分按照1.5 倍工资发放。 1). 属性:每小时的工资、每月工作的小时数。IV. SalesEmployee:Employee 的子类,销售,工资由月销售额和提成率决定。1). 属性:月销售额、提成率。V. BasePlusSalesEmployee:SalesEmployee 的子类,有固定底薪的销售人员,工资由底薪加上销售提成部分。1). 属性:底薪。要求:I. 创建 SalariedEmployee、HourlyEmployee、SaleEmployee、BasePlusSalesEmployee 四个类的对象各一个。II. 并调用父类 getSalary(int money)方法计算某个月这四个对象各自的工资。注意:要求把每个类都做成完全封装,不允许非私有化属性。类图如下:public class TestEmployee { public static void main(String[] args) { Employee[] es = new Employee[4]; es[0] = new SalariedEmployee("John", 5, 5000); es[1] = new HourlyEmployee("Tom", 10, 25, 170); es[2] = new SalesEmployee("Lucy", 7, 200000, 0.03); es[3] = new BasePlusSalesEmployee("James", 8, 1000000, 0.02, 5000); for(int i = 0; i<es.length; i++){ System.out.println(es[i].getSalary(5)); } } } class Employee{ private String name; private int birthMonth; public Employee(String name,int birthMonth){ this.name=name; this.birthMonth=birthMonth; } public String getName(){ return name; } public double getSalary(int month){ if (this.birthMonth==month) return 100; else return 0; } } class SalariedEmployee extends Employee{ private double salary; public SalariedEmployee(String name,int birthMonth,double salary){ super(name,birthMonth); this.salary=salary; } public double getSalary(int month){ return salary+super.getSalary(month); } } class HourlyEmployee extends Employee{ private double salaryPerHour; private int hours; public HourlyEmployee(String name, int birthMonth, double salaryPerHour, int hours) { super(name, birthMonth); this.salaryPerHour = salaryPerHour; this.hours = hours; } public double getSalary(int month){ double result=0; if (hours>160) result=160*this.salaryPerHour+(hours-160)*this.salaryPerHour*1.5; else result=this.hours*this.salaryPerHour; return result+super.getSalary(month); } } class SalesEmployee extends Employee{ private double sales; private double rate; public SalesEmployee(String name, int birthMonth, double sales, double rate) { super(name, birthMonth); this.sales = sales; this.rate = rate; } public double getSalary(int month) { return this.sales*this.rate+super.getSalary(month); } } class BasePlusSalesEmployee extends SalesEmployee{ private double basedSalary; public BasePlusSalesEmployee(String name, int birthMonth, double sales, double rate, double basedSalary) { super(name, birthMonth, sales, rate); this.basedSalary = basedSalary; } public double getSalary(int month) { return this.basedSalary+super.getSalary(month); } }3、已知一个类 Student 代码如下:class Student{ String name; int age; String address; String zipCode; String mobile; }I. 把 Student 的属性都作为私有,并提供 get/set 方法以及适当的构造方法。II. 为 Student 类添加一个 getPostAddress 方法,要求返回 Student 对象的地址和邮编class Student{ private String name; private int age; private String address; private String zipCode; private String mobile; public Student(){} public Student(String name, String address, String mobile) { this.name = name; this.address = address; this.mobile = mobile; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public String getPostAddress() { return "地址 :" + this.address + " 邮编 :" + this.zipCode; } }九、常用类Object类概念Object 类1、Object 是所有的类的超类、基类。位于继承树的最顶层。2、任何一个没有显示定义extends父类的类。都直接继承Object,否则就是间接继承3、任何一个类都可以享有Object提供的方法4、Object类可以代表任何一个类(多态),可以作为方法的参数、方法的返回值5、所有的类,包括数组,都实现了该类里边的方法。Object中常用方法getClass方法此方法用于返回该对象的真实类型(运行时的类型)public final Class<?> getClass()//判断运行时d对象和c对象是否是同一个类型 Animal d = new Dog(); Animal c = new Cat(); //方式1:通过 instanceof 关键字判断 if((d instanceof Dog && c instanceof Dog) ||(d instanceof Cat && c instanceof Cat)) { System.out.println("是同一个类型"); }else { System.out.println("不是同一个类型"); } //方式2:通过getClass方法 判断 if(d.getClass() == c.getClass()) { System.out.println("是同一个类型"); }else { System.out.println("不是同一个类型"); }hashCode方法public native int hashCode();OpenJDK8 默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift scheme随机数算法得到的一个随机数。和对象内存地址无关。不同对象的 hashCode 可能相同;但 hashCode 不同的对象一定不相等自定义类要求重写该方法Student stu1 = new Student("zhangsan", 30); Student stu2 = new Student("zhangsan", 30); Student stu3 = stu1; System.out.println(stu1.hashCode()); System.out.println(stu2.hashCode()); System.out.println(stu3.hashCode()); //两个不同的值,hashcode值也有可能相同 String str1 = "通话"; String str2 = "重地"; System.out.println(str1.hashCode()); System.out.println(str2.hashCode());equals方法Object类的equals方法的作用是比较两个对象是否相等。比较的是内存地址。其原代码是==如果想实现内容的比较,那么需要重写equals方法public class TestObject { public static void main(String[] args) { //创建两个user对象 User1 user1 = new User1("abc", 19); User1 user2 = new User1("abc", 19); //判断两个对象是否相等 System.out.println(user1 == user2);//false //重写equals前返回false,重写equals后返回true System.out.println(user1.equals(user2)); //需求,只要对象内的内容相同,就判断是同一个对象,此时需要重写父类的equals方法 } } class User1 { private String username; private int age; //构造方法 public User1(String username, int age) { this.username = username; this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object obj) { //字符串重写了equals方法来比较两个字符串的内容 return this.age == ((User1)obj).age && this.username.equals(((User1)obj).username); } @Override public int hashCode() { //重写hashcode,让各个字段都参与hashcode的计算。 return Objects.hash(age, username); } }练习equals重写有类Employee, 该类包含属性:名字、年龄、地址、性别、学历、年薪。用代码实现该类,代码满足需求:如果两个Employee对象的名字、年龄、年薪、性别相同,那么两个对象就是同一个对象,否则就不是。public class TestObject { public static void main(String[] args) { //创建两个员工对象 Employee employee1 = new Employee("张三", 25, "abc", 0, "本科", 150000); Employee employee2 = new Employee("张三", 25, "abc", 0, "本科", 150000); //比较两个员工是不是同一个人,使用等号不能判断两人是否是同一人 System.out.println(employee1 == employee2); //因为要根据员工的名字、年龄、年薪、性别才能判断是否是同一人。所以此时需要重写类Employee的equals方法 System.out.println(employee1.equals(employee2)); } } //有类Employee, 该类包含属性:名字、年龄、地址、性别、学历、年薪。 class Employee { private String name; private int age; private String address; private int gender; private String education; private double yearSalary; public Employee(String name, int age, String address, int gender, String education, double yearSalary) { this.name = name; this.age = age; this.address = address; this.gender = gender; this.education = education; this.yearSalary = yearSalary; } //省略get和set方法...... //重写equals方法 public boolean equals(Object obj) { //向下转型 Employee employee = null; if(obj instanceof Employee) { employee = (Employee)obj; } if (employee != null) //比较对象里的每一个字段内容 return this.name.equals(employee.name) && this.age == employee.age && this.yearSalary == employee.yearSalary && this.gender == employee.gender; return false; } //重写hashcode方法 public int hashcode() { return Objects.hash(name, age, yearSalary, gender); } }重写equals方法public boolean equals(Object obj) { //1、非空判断 if(obj == null) { return false; } //2、如果当前对象与obj相等 if(this == obj) { return true; } //3、判断obj是否属于Student类型 if(obj instanceof Student) { Student stu = (Student)obj; //4、判断属性 if(this.name.equals(stu.name) && this.age == stu.age) { return true; } } return false; }总结:== 和 equals的区别两个都是用于比较的== 可以用于基本类型和引用类型equals只能用于引用类型的比较为什么重写equals方法就要重写hashcode方法?当我们对比两个对象是否相等时,我们就可以先使用 hashCode 进行比较,如果比较的结果是 true,那么就可以使用 equals 再次确认两个对象是否相等,如果比较的结果是 true,那么这两个对象就是相等的,否则其他情况就认为两个对象不相等。这样就大大的提升了对象比较的效率,这也是为什么 Java 设计使用 hashCode 和 equals 协同的方式,来确认两个对象是否相等的原因。toString方法返回对象的字符串表现形式全限定名+@+十六进制的hash值 commonclasses.TestToString@6e0be858如果直接输出一个对象,那么默认会调用这个对象的toString方法,而toString方法是Object类提供的,返回的是“对象的地址”。但是我们一般输出对象希望输出的是对象的属性信息,所以可以重写父类的toString方法如果希望输出对象内部每一个属性的值,就需要我们手动重写Object类的toString方法。public class TestToString { public static void main(String[] args) { User user = new User(); //没有重写toString方法前的打印输出 //System.out.println(user.toString());//commonclasses.User@fa17fa41 //重写toString方法后的打印输出 System.out.println(user.toString()); } } public class User { String username = "xiaoming"; String password = "xiaoming"; /** * 只要两个对象里的内容相同,就判断这两个对象是同一个 * @param obj * @return */ @Override public boolean equals(Object obj) { //获取具体的对象,通过instanceof if (obj instanceof User) { User user = (User) obj; boolean un = (this.username == user.username); boolean pwd = (this.password == user.password); return un && pwd; } return false; } @Override public int hashCode() { return Objects.hash(username, password); } //重写toString方法 @Override public String toString() { String res = "username:" + username + " password:" + password; return res; } }finalize方法当垃圾回收器回收垃圾对象的时候,自动调用public class Test5 { public static void main(String[] args) { Person p = new Person(); //手动将对象标记为垃圾对象 p =,098发垃圾回收器,回收垃圾对象 System.gc(); } } class Person{ @Override protected void finalize() throws Throwable { super.finalize();//不要删除 System.out.println("finalize方法执行了"); } }注意:finalize()方法会被一个低优先级的线程Finalizer执行,这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束。 这样做的原因是,如果某个对象的finalize()方法执行缓慢,或者更极端地发生了死循环clone克隆protected native Object clone() throws CloneNotSupportedException;clode()方法被声明为native的方法,因此,它并不是Java的原生方法,具体的实现是由C/C++完成的。clone英文翻译为"克隆",其目的是创建并返回此对象的一个副本。创建对象的方式:new,clone浅拷贝与深拷贝:浅拷贝会拷贝对象的常量以及引用。但是引用所指向的另一个对象不会继续拷贝public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { //创建Car的对象 Car car1 = new Car("五菱", "白色", 10); //使用Object类的clone方法创建car1的一个拷贝 Car car2 = (Car) car1.clone(); System.out.println(car1); System.out.println(car2); //修改克隆后的对象,看是否会改变原生对象 System.out.println("修改克隆对象后--------"); car2.setBrand("特斯拉"); car2.setYear(3); System.out.println(car1); System.out.println(car2); Car car3 = new Car("大众", "黑色",8); Person1 xiao = new Person1("小明", 17); car3.setPerson(xiao); //通过clone复制了一份car3 Car car4 = (Car) car3.clone(); System.out.println("car3:" + car3); System.out.println("car4:" + car4); //修改克隆后的对象 System.out.println("修改克隆对象后--------"); Person1 car4Person = car4.getPerson(); car4Person.setName("小红"); car4Person.setAge(29); System.out.println("car3:" + car3); System.out.println("car4:" + car4); } } /** * 如果想让一个类被克隆,那么需要告诉java虚拟机这个类是可以被克隆 */ class Car implements Cloneable{ private String brand; private String color; private int year; private Person1 person; public Car(String brand, String color, int year) { this.brand = brand; this.color = color; this.year = year; } //重写父类clone方法 @Override protected Object clone() throws CloneNotSupportedException { return super.clone();//该方法实现浅拷贝,因为对象里的引用类型字段指向的内容没有深入拷贝。 } //为了查看对象里每个属性的值,我们重写toString方法 @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", color='" + color + '\'' +new ", year=" + year + ", person=" + person + '}'; } } class Person1 implements Cloneable { private String name; private int age; public Person1(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Person1{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class TestClone implements Cloneable { int a = 10; String str = new String("clone method"); public static void main(String[] args) throws CloneNotSupportedException { //创建一个TestClone的对象 TestClone tc = new TestClone(); //通过克隆创建一个TestClone的副本 TestClone clone = (TestClone) tc.clone(); System.out.println(tc.a == clone.a); System.out.println(tc.str == clone.str); } }深拷贝对象里的常量和引用会被拷贝,并且引用指向的对象也会被拷贝。因为java的Object类提供的clone方法提供的拷贝属于浅拷贝,而拷贝引用的同时拷贝引用指向的对象属于深拷贝,此部分逻辑需要用户自动重现clone方法实现。@Override protected Object clone() throws CloneNotSupportedException { //return super.clone(); //实现深拷贝 Car car = (Car) super.clone();//拷贝车子对象 car.setPerson((Person1) car.getPerson().clone());//车子对象里的人也继续拷贝一份。 return car; }面试题Object obj = new Object();这个对象占几个字节:16个字节包装类思考3.1 概念为什么要有包装类因为基本数据类型不具有方法和属性。而引用数据类型可以拥有属性和方法,使用更加的灵活所以Java给8种基本数据类型提供对应8个包装类。包装类也就是引用数据类型java中包装类的继承关系基本类型包装类型byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean3.2 装箱和拆箱装箱就是将基本类型转换成包装类拆箱就是将包装类型转换成基本类型jdk1.5 之前装箱和拆箱//在jdk1.5之前 拆装箱的过程 int a = 10; //装箱 Integer integer = Integer.valueOf(a); System.out.println(integer); //拆箱 int b = integer.intValue(); System.out.println(b);jdk1.5 之后的装箱和拆箱//在jdk1.5之后 拆装箱的过程 int i = 10; //装箱 Integer i1 = i; System.out.println(i1); //拆箱 int i2 = i1; System.out.println(i2); public static void main(String[] args) { byte b = 127;//基本数据类型 //把基本数据类型的b赋值给引用类型的Byte,这个过程称为自动装箱 Byte b1 = b;//包装类型,自动装箱 //把包装类型的b1赋值给基本数据类型b2,这个过程称为自动拆箱 byte b2 = b1; System.out.println(b1.MIN_VALUE);//获取最小值 System.out.println(b1.MAX_VALUE);//获取最大值 }3.3 Number类Byte、Short、Integer、Long、Float、Double六个子类提供一组方法,用于将其中某一种类型转换成其他类型 xxxValue()方法Integer a = 100; Byte b = a.byteValue(); Short c = a.shortValue(); Long d = a.longValue(); Float e = a.floatValue(); Double f = a.doubleValue(); Integer g = a.intValue();3.4 常用的包装类Integer 、Double3.4.1定义方式//Integer、Double的定义方式 Integer ii1 = new Integer(100); //或者 Integer ii2 = 100;//自动装箱的过程 Double dd1 = new Double(100.2); //或者 Double dd2 = 100.2;//自动装箱的过程3.4.2 常用的属性System.out.println(Integer.MAX_VALUE); System.out.println(Integer.MIN_VALUE); System.out.println(Double.MAX_VALUE); System.out.println(Double.MIN_VALUE);3.4.3 常用的方法将字符串类型的数值转换成int或者是double类型//常用方法: //将字符串转换成int或者是double String s = "123"; //方式1: int i = Integer.parseInt(s); System.out.println(i); //方式2: int i2 = Integer.valueOf(s); System.out.println(i2); String s1 = "20.5"; //方式1: double d = Double.parseDouble(s1); System.out.println(d); //方式2: double d1 = Double.valueOf(s1); System.out.println(d1); //java.lang.NumberFormatException 数字格式化异常 String s2 = null; System.out.println(Integer.parseInt(s2));3.5 Integer缓冲区(面试题)public class Demo02 { public static void main(String[] args) { /** * 面试题:整数型包装类缓冲区 * 整数型的包装类定义缓冲区(-128~127),如果定义的数在这个范围你之内,那么直接从缓存数组中获取, * 否则,重新new新的对象 */ Integer i1 = new Integer(10); Integer i2 = new Integer(10); System.out.println(i1 == i2); //false System.out.println(i1.equals(i2));//true Integer i3 = 1000; //Integer i3 = new Integer(1000); Integer i4 = 1000; //Integer i3 = new Integer(1000); System.out.println(i3 == i4); //false System.out.println(i3.equals(i4));//true Integer i5 = 100; //IntegerCache.cache[i + (-IntegerCache.low)] Integer i6 = 100; //IntegerCache.cache[i + (-IntegerCache.low)] System.out.println(i5 == i6);//true System.out.println(i5.equals(i6));//true } }String类概念String类代表字符串。 Java程序中的所有字符串文字(例如"abc" )都被实现为此类的实例。String是不可修改的String创建对象直接赋值通过构造方法创建对象//String类的定义 //1、直接赋值 String s = "cxk"; System.out.println(s); //2、通过构造方法创建String类的对象 String s1 = new String("李四哈哈"); // String s1 = "李四哈哈"; System.out.println(s1); //通过字节数组变成一个字符串 byte[] b = {97,98,99}; //参数1:字节数组 参数2:起始下标 参数3:长度 String s2 = new String(b, 0, b.length); System.out.println(s2); //通过字符数组变成一个字符串 char[] c = {'z','y','x'}; String s3 = new String(c, 0, c.length); System.out.println(s3); String类常用方法//String str = "abcn12c3fcds"; //charAt(index) 获取指定下标对应的字符,返回char类型 //value['a', 'b', 'c', 'n', '1', '2', 'c', '3', 'f', 'c', 'd', 's'] //System.out.println(str.charAt(3)); //indexOf("字符串")返回此字符串中第一次出现的索引 //System.out.println(str.indexOf("cn1")); //返回此字符串中指定子字符串最后一次出现的索引 //System.out.println(str.lastIndexOf("b")); //length() 获取字符串的长度 //System.out.println(str.length()); //判断的方法---------------------------------------------------------------------------- //String str = "abcD"; //判断两个字符串是否相等 //System.out.println("abcd".equals(str)); //判断两个字符串是否相等,忽略大小写 //System.out.println("abcd".equalsIgnoreCase(str)); //判断字符串是否为空串 "" //System.out.println(str.isEmpty()); //String str = "123478923"; //判断字符串是否以指定的字符串开头 //System.out.println(str.startsWith("12")); //判断字符串是否以指定的字符串开头,指定开始位置 //System.out.println(str.startsWith("34", 2)); //判断字符串是否以指定的字符串结尾 //System.out.println(str.endsWith("23")); //判断字符串中是否包含自定的字符串 //System.out.println(str.contains("SB")); //其他方法---------------------------------------------------------------------------- // !!! 记得要重新赋值 !!! //String str = "hello,SB"; //将字符串与指定的字符串进行拼接 //str = str.concat("world"); //str = str + "world"; //System.out.println(str); //字符串替换:将字符串中指定的字符串替换成指定的字符串 //str = str.replace("SB", "**"); //System.out.println(str); //字符串截取,从指定的下标开始和结束 范围是左闭右开[0,5) //str = str.substring(0, 5); //System.out.println(str); //字符串截取,从指定的下标开始一直到最后 //str = str.substring(6); //System.out.println(str); //字符串切割,按照指定的字符串对原字符串进行切割 //String str = "zhangsanlisiwangwu"; //String[] s = str.split("s"); //System.out.println(Arrays.toString(s)); //去除字符串前后的空格 //String str = " n你好。。 哈哈 "; //str = str.trim(); //System.out.println(str); //String str = "abcd你好"; //将字符串变成字节数组 //byte[] b = str.getBytes(); //System.out.println(Arrays.toString(b)); //将字符串变成字符数组 //char[] c = str.toCharArray(); //System.out.println(Arrays.toString(c)); String str = "abcADC你好"; //将字符串中的字母变成大写 System.out.println(str.toUpperCase()); //将字符串中的字母变成小写 System.out.println(str.toLowerCase()); str.length(); //int i = 10; //方式1: //String s = i+""; //方式2:将其他的类型的数据转换成String类型 //String s2 = String.valueOf(i); //System.out.println(s); //System.out.println(s2);拓展:String字符串被创建就不能修改。因为不可变有着非常强大的功能,比如说,缓存、安全性、高性能等如何理解这个不可变?String str = "abc"; str = "bcd" System.out.println(str); //输出:bcd作业1、翻转字符串:String url = "https://www.runoob.com/manual/jdk11api/java.base/java/lang/String.html";翻转后的结果:String.html/lang/java/java.base/jdk11api/manual/com.runoob.www//:httpspublic static void main(String[] args) { String url = "https://www.runoob.com/manual/jdk11api/java.base/java/lang/String.html"; //String.html/lang/java/java.base/jdk11api/manual/com.runoob.www//:https String[] splits = url.split("://");//按照指定字符分割原字符串 //把uri按照 / 分割 String[] uris = splits[1].split("/"); String result = new String();//最终结果 //manual/jdk11api/java.base/java/lang/String.html for (int i = uris.length-1; i > 0; i--) result = result .concat(uris[i]).concat("/"); String[] domainNames = uris[0].split("\\."); //www.runoob.com for (int j = domainNames.length-1; j >=0; j--) result = result.concat(domainNames[j]).concat("."); //使用substring去掉最终多出的. result = result.substring(0, result.length()-1); result = result.concat("//:").concat(splits[0]); System.out.println(result); }2、写一个程序,把你全名的单词首字母打印出来如:String myName = "Fred F. Connell";public static void main(String[] args) { String myName = "Fred F. Connell"; //把字符串转成字符数组 char[] chars = myName.toCharArray(); for (char ch : chars) { //判断字符是否是大写 if (Character.isUpperCase(ch)) System.out.print(ch); } }3、键盘上随机输入一字符串,字符串中包含数字,字母,下划线等各种符号,要求只保留其中的字母。并按字母表升序排序。public static void main(String[] args) { String scanner = "sSFDwe7987sfwewe^&*(sdKwe2232"; //把字符串转成字符数组 char[] chars = scanner.toCharArray(); String result = new String(); for (char ch : chars) { //判断当前字符是否是字母 if (Character.isLetter(ch)) result = result.concat(String.valueOf(ch)); } //把字符串中的字母进行排序 char[] letters = result.toCharArray(); Arrays.sort(result.toCharArray()); result = Arrays.toString(letters); System.out.println(result); }可变字符串StringBufferStringBuilderStringBuffer、StringBuilder类常用方法append(String str);delete(int start, int end)insert(int offset, String str)reverse()toString()public class StringBufferDemo { public static void main(String[] args) { //创建StringBuffer对象 StringBuffer sb = new StringBuffer(); //常用方法: //在字符串的后面追加字符串 sb.append("abcdef"); System.out.println(sb); //abcdef //删除字符串,从指定的下标开始和结束 sb.delete(2, 4); System.out.println(sb);//abef //在指定下标位置添加指定的字符串 sb.insert(2, "123"); System.out.println(sb);//ab123ef //将字符串翻转 sb.reverse(); System.out.println(sb);//fe321ba //将StringBuffer转换成String类型 String s = sb.toString(); System.out.println(s); } }String、StringBuffer、StringBuilder区别String、StringBuffer、StringBuilder区别这三个类都可以用于表示字符串1、String类是字符串常量类,一旦定义不能改变2、StringBuffer、StringBuilder是可变的字符串,自带有缓冲区。jdk9之前默认缓冲区大小16个字符,之后是16字节。3、StringBuffer是线程安全的,所以效率低 StringBuilder是线程不安全的,所以效率高总结:在大量的字符串拼接的时候,使用 StringBuffer、StringBuilder。而不考虑线程安全的时候,选择StringBuilder,否则选择StringBuffer,StringBuilder和StringBuffer最大只能存放4G的数据。public class Demo01 { public static void main(String[] args) { //需求:做100000次字符串拼接 //获取当前系统时间的毫秒数 4918 // long start = System.currentTimeMillis(); // String str = ""; // for (int i = 0; i < 100000; i++) { // str = str + "a"; // } // long end = System.currentTimeMillis(); // System.out.println("耗时"+(end -start)); //239 // long start = System.currentTimeMillis(); // StringBuffer sb = new StringBuffer(); // for (int i = 0; i < 10000000; i++) { // sb.append("a"); // } // long end = System.currentTimeMillis(); // System.out.println("耗时"+(end -start)); //90 long start = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000000; i++) { sb.append("a"); } long end = System.currentTimeMillis(); System.out.println("耗时"+(end -start)); } }String类面试题package com.qf.string; public class StringDemo { public static void main(String[] args) { String s1 = "ab"; String s2 = "c"; String s3 = new String("abc"); String str1 = "abc"; String str2 = "abc"; String str3 = "ab" +"c"; String str4 = s1 +s2; String str5 = new String("abc"); String str6 = new String("ab")+"c"; String str7 = s3.intern(); /** * String str1 = "abc"; * String str2 = "abc"; * 这个“abc”存放在常量池中, * 常量池的特点: * 首先会去常量池中找是否有“abc”这个常量字符串,如果有直接用str1指向它, * 如果没有将“abc”放到常量池中,用str1指向它 * 再使用去常量池中找有没有“abc”,这个时候已经有了,直接使用str2指向它。 */ System.out.println(str1 == str2);//true /** * String str3 = "ab" +"c"; * 因为“ab”是常量与“c”拼接之后也会在常量池中。 */ System.out.println(str1 == str3);//true /** * String s1 = "ab"; * String s2 = "c"; * 当s1和s2拼接的时候,jdk会将s1和s2转换成StringBuilder类型,然后进行拼接操作, * 最终的内容实在堆内存中。 */ System.out.println(str1 == str4);//false /** * String str1 = "abc"; 在常量池 * String str5 = new String("abc"); 在堆内存 */ System.out.println(str1 == str5);//false /** * String str5 = new String("abc"); 在堆内存 * String str6 = new String("ab")+"c";在堆内存 */ System.out.println(str5 == str6);//false /** * String str1 = "abc"; 在常量池 * String str6 = new String("ab")+"c"; 在堆内存 */ System.out.println(str1 == str6);//false /** * String str7 = s3.intern(); * intern方法的含义:将String类型的对象指向常量池,如果有直接指向,如果没有放一个指向 */ System.out.println(str1 == str7);//true } }intern(),以下程序输出答案是什么?String str1 = new StringBuilder("计算机").append("软件").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2);定义: String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。 答案:这段代码在JDK 6中运行,会得到两个false,而在JDK 7中运行,会得到一个true和一个false。产生差异的原因是,在JDK 6中,intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用,而由StringBuilder创建的字符串对象实例在Java堆上,所以必然不可能是同一个引用,结果将返回false。而JDK 7(以及部分其他虚拟机,例如JRockit)的intern()方法实现就不需要再拷贝字符串的实例 到永久代了,既然字符串常量池已经移到Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可因 此 intern ( ) 返 回 的 引 用 和 由 StringBuilder 创 建 的 那个字符串实例就是同 一 个 。 而 对 str2 比 较 返回false ,这 是 因 为 “java”这 个 字 符 串 在 执 行 StringBuilder.toString( ) 之 前 就 已 经 出 现 过 了 , 字符串常量池中已经有它的引用,不符合intern()方法要求“首次遇到”的原则,“计算机软件”这个字符串则是首次出现的,因此结果返回true。sum.misc.Version类CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }u2是无符号的16位整数,因此理论上允许的的最大长度是2^16=65536。而 java class 文件是使用一种变体UTF-8格式来存放字符的,null 值使用两个 字节来表示,因此只剩下 65536- 2 = 65534个字节。Random类jdk提供生成随机数的一个类。public static void main(String[] args) { //创建对象 Random random1 = new Random(); //基于一个种子创建一个随机数对象,种子:java会根据传入的种子数,使用一个特殊的算法生产一个伪随机数。 //如果不传入种子,就会使用系统当前时间作为随机数算法的种子。 Random random2 = new Random(10000); System.out.println(Integer.MIN_VALUE); System.out.println(Integer.MAX_VALUE); random1.nextInt();//生成最小整数到最大整数之间的一个数。 int randVal = random1.nextInt(1000);//生成[0, 1000)的随机值 double v = random1.nextDouble();//生成0.0到1.0但是不包括1.0在内的随机小数。 System.out.println(v); }练习,生成6位随机验证码public static void main(String[] args) { //定义一个数组 String s = "1234567890qwertyuipasdfghjkzxcvbnmQWERTYUIPASDFGHJKZXCVBNM"; //创建随机数对象 Random random = new Random(); char[] chars = s.toCharArray(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 6; i++) { //通过随机函数获取数组下标。 sb.append(chars[random.nextInt(58)]); } System.out.println(sb); } //曾昭洋 String codes="1234567890qwertyuipasdfghjkzxcvbnmQWERTYUIPASDFGHJKZXCVBNM"; for (int i = 0; i < 6; i++) System.out.print(codes.charAt(new Random().nextInt(codes.length())) + "");日期Date表示一个特定时间,精确到毫秒。java.util.Date;public class DateDemo { public static void main(String[] args) { //创建Date类的对象 默认是系统当前时间 // Date d1 = new Date(); // //Fri Mar 12 11:35:00 CST 2021 CST可视为美国、澳大利亚、古巴或中国的标准时间。 // System.out.println(d1); /** * year:年份,默认从1900开始计算 * month:月份,默认是0-11 * date:日期 */ // Date d2 = new Date(1998-1900, 2-1, 20); // System.out.println(d2); // Date d3 = new Date(); // //返回当前date日期对应的时间的毫秒数从1970年开始计算的毫秒数 // System.out.println(d3.getTime()/1000/60/60/24/365); // Date d1 = new Date(); // Date d2 = new Date(1998-1900, 2-1, 20); // //判断d1是否在d2之前 // System.out.println(d1.before(d2)); // //判断d1是否在d2之后 // System.out.println(d1.after(d2)); //随堂案例:计算自己活了多长时间 Date d1 = new Date(1998-1900, 2-1, 20); Date d2 = new Date(); long time = d2.getTime() - d1.getTime(); System.out.println(time/1000/60/60/24); } }SimpleDateFormat日期格式化类。java.text.SimpleDateFormat;package com.qf.date; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleDateFormatDemo { public static void main(String[] args) { // //创建一个Date对象 // Date date = new Date(); // //创建日期格式化对象 2021年03月12日 14:15:30 // SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); // //调用日期格式化对象的format方法 // String time = sdf.format(date); // System.out.println(time); // //将字符串格式的时间转换成Date类型 // String time = "2021-03-12 14:21:30"; // //创建日期格式化对象 // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // //调用日期格式化对象的parse方法 // try { // Date date = sdf.parse(time); // System.out.println(date); // } catch (ParseException e) { // e.printStackTrace(); // } } }CalendarCalendar 类是一个抽象类,表示一个日历类。其包含有时间的三组方法java.util.Calendar;get(字段) 获取指定字段的值set(字段,值) 设置指定字段的指定值add(字段,值) 在指定的字段添加或者减去指定的值package com.qf.date; import java.util.Calendar; public class CalendarDemo { public static void main(String[] args) { //创建日历类对象 //Calendar.getInstance();默认表示的系统当前时 Calendar c = Calendar.getInstance(); //get方法 System.out.println(c.get(Calendar.YEAR)); //月份0-11 System.out.println(c.get(Calendar.MONTH)+1); System.out.println(c.get(Calendar.DATE)); System.out.println(c.get(Calendar.HOUR_OF_DAY)); System.out.println(c.get(Calendar.MINUTE)); System.out.println(c.get(Calendar.SECOND)); //星期天是1 System.out.println(c.get(Calendar.DAY_OF_WEEK)); System.out.println("===================================================="); //set方法 c.set(Calendar.MONTH, 3-1);//为给定日历字段设置值 System.out.println(c.get(Calendar.MONTH)+1); c.set(Calendar.DAY_OF_WEEK, 7); System.out.println(c.get(Calendar.DAY_OF_WEEK)); System.out.println("===================================================="); //add方法 c.add(Calendar.MONTH, -4); System.out.println(c.get(Calendar.MONTH)+1); System.out.println(c.get(Calendar.YEAR)); Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.DATE, 1);//将日期改为该月1号 System.out.println("roll后时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime())); calendar.roll(Calendar.DATE, -1); System.out.println("roll后时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime())); } }综合案例:打印当前月份的日历public static void main(String[] args) { Calendar c1 = Calendar.getInstance(); System.out.println(c1.get(Calendar.DATE)); Scanner scanner = new Scanner(System.in); System.out.println("请输入需要查看日历年份和月份"); System.out.print("年:"); int a = scanner.nextInt(); System.out.print("月:"); int b = scanner.nextInt(); //设置当前c1为2022年9月1日 c1.set(a, b-1, 1);//把月份设置为需要输出月份的1号 System.out.println("七\t一\t二\t三\t四\t五\t六"); //获取1日是星期几 int weekDay = c1.get(Calendar.DAY_OF_WEEK); for (int i = 1; i < weekDay; i++) { System.out.print("\t"); } //获取当前月份 for (int i = 1; i <= c1.get(Calendar.DATE); i++) { int day = c1.get(Calendar.DATE); System.out.print(day + "\t"); //重新获取星期几 weekDay = c1.get(Calendar.DAY_OF_WEEK); if (weekDay == Calendar.SATURDAY) { System.out.println();//如果是星期六就执行换行 } //每次循环天数+1 c1.add(Calendar.DATE, 1); } }JDK8之后新的日期时间API为什么要引进新的时间日期API?JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:可变性:像日期和时间这样的类应该是不可变的。偏移性:Date中的年份是从1900开始的,而月份都从0开始。格式化:格式化只对Date有用,Calendar则不行。此外,它们也不是线程安全的;不能处理闰秒等。JDK8中引进了新的时间API是java.time,新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。LocalDate、LocalTime、LocalDateTime类LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。java.time.LocalDate; java.time.LocalDateTime; java.time.LocalTime;LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。LocalTime表示一个时间,而不是日期。LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。示例:LocalDate localDate = LocalDate.now();// 获取当前的本地日期 LocalTime localTime = LocalTime.now();// 获取当前的本地时间 LocalDateTime localDateTime = LocalDateTime.now();// 获取当前的本地日期和本地时间 System.out.println(localDate); System.out.println(localTime); System.out.println(localDateTime); // of方法:设置年月日时分秒,没有偏移量 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 10, 24, 15, 27, 16); System.out.println(localDateTime1); //getXxx()方法 获取属性 System.out.println(localDateTime1.getDayOfMonth());// 24 获取当前对象对应的日期是这个月的第几天 System.out.println(localDateTime1.getDayOfWeek());// SATURDAY 获取当前对象对应的日期是星期几 System.out.println(localDateTime1.getDayOfYear());// 297 获取当前天是这一年的第几天 System.out.println(localDateTime1.getMonth());// OCTOBER 获取月份 System.out.println(localDateTime1.getSecond());// 16 获取秒数 // withXxx() 设置相关的属性(体现不可变性,原先对象的with操作不会改变原来对象的属性) LocalDateTime localDateTime2 = localDateTime.withHour(4); // 设置小时数为04 System.out.println(localDateTime); System.out.println(localDateTime2); // plusXxx() 加操作 LocalDateTime localDateTime3 = localDateTime.plusDays(5); // 在现有时间加上5天 System.out.println(localDateTime); System.out.println(localDateTime3); // minusXxx() 减操作 LocalDateTime localDateTime4 = localDateTime.minusMonths(2); // 在现有时间减去2个月 System.out.println(localDateTime.getMonth()); // AUGUST System.out.println(localDateTime4.getMonth());// JUNE常用还是LocalDateTime类。Instant类Instant类类似于Date类,位于包 java.time.Instant; 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。示例:// now():获取本初子午线当地的标准时间 Instant instant = Instant.now(); System.out.println(instant); // 在UTC时区的基础上加上8个时区(北京时间) OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8)); System.out.println(offsetDateTime); // 获取时间戳 System.out.println(instant.toEpochMilli()); // ofEpochMilli():通过给定的时间戳,获取Instant的实例 Instant instant1 = Instant.ofEpochMilli(1659839968542L); System.out.println(instant1);DateTimeFormatter类DateTimeFormatter类类似于SimpleDateFormat类,用于格式化与解析日期或时间。 java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)自定义的格式。如:ofPattern("yyyy-MM-dd hh:mm:ss"),常用是这种。示例:// 实例化方式一 预定义的标准格式。如:`ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME` DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; LocalDateTime localDateTime = LocalDateTime.now(); // 格式化:将日期转换为字符串,需要传入一个TemporalAccessor类型=的,而LocalDate、LocalTime和LocalDateTime都是 String str1 = formatter.format(localDateTime); System.out.println(localDateTime); // 2022-08-08T22:12:43.555 System.out.println(str1); // 2022-08-08T22:12:43.555 // 使用标准格式的格式化出来结果是:2022-08-08T22:12:43.555 // 解析:解析的字符串也必须是标准格式的字符创 TemporalAccessor parse = formatter.parse("2020-10-31T14:16:15.801854"); System.out.println(parse); // {},ISO resolved to 2020-10-31T14:16:15.801854// 实例化方式二:本地化相关的格式 DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);// 使用FormatStyle.SHORT进行格式化 // 格式化 String str2 = formatter1.format(localDateTime); System.out.println(str2); DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); // 格式化 String str3 = formatter2.format(LocalDateTime.now()); System.out.println(str3); // 解析 TemporalAccessor parse1 = formatter2.parse("2019-10-31 02:30:29"); System.out.println(parse1);System系统类常用方法:System.currentTimeMillis()System.exit(0)package com.qf.system; import java.text.SimpleDateFormat; public class SystemDemo { public static void main(String[] args) { //System 系统类 //获取系统当前时间,返回自1970年开始以来时间的毫秒数 System.out.println(System.currentTimeMillis()); //格式化时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String time = sdf.format(System.currentTimeMillis()); System.out.println(time); //结束当前虚拟机运行 0表示正常退出 System.exit(0); System.out.println("执行吗?"); } }Math数学计算的工具类,位于java.lang包package com.qf.math; public class MathDemo { public static void main(String[] args) { //求a的b次方法 参数1:底数 参数2:幂数 System.out.println(Math.pow(2, 10)); //求a平方根 参数1:要开方的数 System.out.println(Math.sqrt(100)); //求a立方根 参数1:要开立方的数 System.out.println(Math.cbrt(27)); //向上取整 System.out.println(Math.ceil(10.2)); //向下取整 System.out.println(Math.floor(10.9)); //四舍五入 System.out.println(Math.round(10.5)); //随机数 默认的范围[0,1) System.out.println(Math.random()); //需求:随机一个两位数 [0,1)*90 [0,90) + 10 System.out.println((int)(Math.random()*90)+10); } }BigDecimal为什么使用BigDecimal?java.math.BigDecimal;以下的代码的错误原因是0.9在计算中中的二进制是一个无限循环的double保存时近似值,所以计算的结果不精确double d1 = 1.0; double d2 = 0.9; System.out.println(d1-d2);//0.0999999998BigDeicmal基本用法位置:java.math包中。作用:精确计算浮点数。创建方式:BigDecimal bd=new BigDecimal(“1.0”)。常用方法:方法名描述BigDecimal add(BigDecimal bd)加BigDecimal subtract(BigDecimal bd)减BigDecimal multiply(BigDecimal bd)乘BigDecimal divide(BigDecimal bd)除package com.qf.math; import java.math.BigDecimal; import java.math.MathContext; public class BigDecimalDemo { public static void main(String[] args) { double d1 = 1.0; double d2 = 0.9; //0.09999999999999998 System.out.println(d1-d2); //使用BigDecimal类解决 //创建BigDecimal对象 BigDecimal bd1 = new BigDecimal("1.012097"); BigDecimal bd2 = new BigDecimal("0.22"); System.out.println(bd1.add(bd2)); System.out.println(bd1.subtract(bd2)); System.out.println(bd1.multiply(bd2)); //ArithmeticException 算术异常 /** * 参数说明: * 参数1:被除数 * 参数2:保留小数位数 * 参数3:舍入模式 * ROUND_CEILING 向上取整 * ROUND_FLOOR 向下取整 * ROUND_HALF_UP 四舍五入 */ System.out.println(bd1.divide(bd2,2,BigDecimal.ROUND_HALF_UP)); } }枚举java中使用关键字来定义枚举语法格式:enum 名字{VALUE1, VALUE2, VALUE3;}public class EnumDemo { public static void main(String[] args) { System.out.println(WeekDay.FRIDAY);//使用自定义类实现枚举,然后获取类里的值 //获取枚举变量的名字 System.out.println(WeekDays.MONDAY); //获取枚举常量的具体值 System.out.println(WeekDays.MONDAY.getKey()); System.out.println(WeekDays.MONDAY.getValue()); System.out.println(WeekDays.FRIDAY.toString()); } } //定义一个类表示周一到周日,该类只能表示1到7,其他天数不允许 //用类实现 class WeekDay { public static final WeekDay MONDAY = new WeekDay("星期一", "MONDAY"); public static final WeekDay TUESDAY = new WeekDay("星期一", "TUESDAY"); public static final WeekDay WEDNESDAY = new WeekDay("星期一", "WEDNESDAY"); public static final WeekDay THURSDAY = new WeekDay("星期一", "THURSDAY"); public static final WeekDay FRIDAY = new WeekDay("星期一", "FRIDAY"); public static final WeekDay SATURDAY = new WeekDay("星期一", "SATURDAY"); public static final WeekDay SUNDAY = new WeekDay("星期一", "SUNDAY"); private String cnWeek; private String enWeek; private WeekDay(String cnWeek, String enWeek) { this.cnWeek = cnWeek; this.enWeek = enWeek; } public String getCnWeek() { return cnWeek; } public void setCnWeek(String cnWeek) { this.cnWeek = cnWeek; } public String getEnWeek() { return enWeek; } public void setEnWeek(String enWeek) { this.enWeek = enWeek; } } //用枚举实现 enum WeekDays { /*public static final WeekDays MONDAY = new WeekDays("星期一", "MONDAY"); public static final WeekDays TUESDAY = new WeekDays("星期一", "TUESDAY"); public static final WeekDays WEDNESDAY = new WeekDays("星期一", "WEDNESDAY"); public static final WeekDays THURSDAY = new WeekDays("星期一", "THURSDAY"); public static final WeekDays FRIDAY = new WeekDays("星期一", "FRIDAY"); public static final WeekDays SATURDAY = new WeekDays("星期一", "SATURDAY"); public static final WeekDays SUNDAY = new WeekDays("星期一", "SUNDAY");*/ MONDAY("星期一", "MONDAY"), TUESDAY("星期二", "TUESDAY"), WEDNESDAY("星期三", "WEDNESDAY"), THURSDAY("星期四", "THURSDAY"), FRIDAY("星期五", "FRIDAY"), SATURDAY("星期六", "SATURDAY"), SUNDAY("星期七", "SUNDAY"); private String cnWeek; private String enWeek; WeekDays(String cnWeek, String enWeek) { this.cnWeek = cnWeek; this.enWeek = enWeek; } public String getKey() { return cnWeek; } public String getValue() { return enWeek; } @Override public String toString() { return "WeekDays{" + "cnWeek='" + cnWeek + '\'' + ", enWeek='" + enWeek + '\'' + '}'; } }作业一、必做题1、编程题a、完成一个抽查作业程序,定义一个字符串数组保存姓名,一次随机3个不重复的姓名并输出public static void one() { //定义一个字符串数组保存姓名,一次随机3个不重复的姓名并输出 Random random = new Random(); StringBuilder str = new StringBuilder(); //记录获取到的名字个数 int count = 0; while (count < 3) { //随机获取数组下标 int index = random.nextInt(names.length); if (!str.toString().contains(names[index])) { str.append(names[index]).append(" "); count++; } } System.out.println(str); } //曾昭洋 String[] s = {"张三","李四","王五","赵六","钱七"}; ArrayList<String> arrayList = new ArrayList<>();//装三个随机名字 for (int i = 0; i < 3; i++) { int random = new Random().nextInt(s.length);//下标 if (arrayList.contains(s[random])) i--;// 如果已经存在名字,本次不算 else arrayList.add(s[random]);//没存在就放入 } for (String str : arrayList) System.out.print(str+" ");//遍历 b、用两种方法将系统当前的时间并格式化成(2019-11-12 12 :30: 21)这种格式public static void two() { System.out.println(new SimpleDateFormat("y-M-d HH:mm:ss").format(new Date())); System.out.println(DateTimeFormatter.ofPattern("y-M-d HH:mm:ss").format(LocalDateTime.now())); } c、定义一个方法,可以计算出两个日期之间相差多少天例如: 2018年2月17日到2019年3月29日中间一共有多少天?public static void three() { //2018年2月17日到2019年3月29日中间一共有多少天? long one = LocalDate.of(2018, 2, 17).toEpochDay(); long two = LocalDate.of(2019, 2, 18).toEpochDay(); System.out.println(two - one); } //曾昭洋 //long 相差时间 = DAYS.between(开始时间,结束时间) System.out.println(DAYS.between(LocalDate.of(2018,2,17), LocalDate.of(2019,3,29)));2、完成一个猜拳的小游戏。 由系统随机一个数(0表示石头1表示剪刀2表示布),然后用户输入一个数(0表示石头1表示剪刀2表示布)。输出得出谁赢、谁输,还是平局。 输出的时候要输出“石头”、“剪刀”、“布”,不要输出0、1、2 可以写在一个循环里面。输入y表示还想玩,n表示退出游戏。结束之后要输出赢了多少次、输了多少次、平局多少次、记录游戏时长public static void four() { //获取游戏开始时间 long start = System.currentTimeMillis(); //构建二维数组枚举出用户胜负和平局 String[][] winOrLose = {{"平局", "用户胜", "用户败"}, {"用户败", "平局", "用户胜"}, {"用户胜", "用户败", "平局"}}; int[] os = {0, 1, 2}; String[] userGuess = {"石头", "剪刀", "布"}; String[] osGuess = {"石头", "剪刀", "布"}; //定义变量分别记录平局次数,用户胜和败多少次 int draw = 0, userWin = 0, userLose = 0; //定义随机函数 Random random = new Random(); //创建键盘输入对象 Scanner scanner = new Scanner(System.in); System.out.println("开猜拳游戏。。。。。。。。。。"); //猜拳游戏 do { System.out.println("请出拳:0[石头] 1[剪刀] 2[布]"); int userInput = scanner.nextInt();//用户出拳 int osInput = os[random.nextInt(os.length)];//系统出拳 //根据系统和用户出拳作为二维数组下标去二维数组里获取结果 String result = winOrLose[userInput][osInput]; if (result.equals("平局")) draw++; else if (result.equals("用户胜")) userWin++; else if (result.equals("用户败")) userLose++; System.out.println("本次游戏结束"); System.out.println("用户出: " + userGuess[userInput] + " 系统出 : " + osGuess[osInput]); System.out.println("输入[y]继续游戏,输入[n]退出游戏"); String val = scanner.next(); if (val.equals("n"))break; }while (true); System.out.println("本次游戏浪费了你 " + (System.currentTimeMillis() - start)/1000 + " 秒"); System.out.println("平局:" + draw); System.out.println("用户胜:" + userWin); System.out.println("用户败:" + userLose); System.out.println("系统胜:" + userLose); System.out.println("系统败:" + userWin); }3、使用枚举定义一个类表示一月到十二月enum Month { //January(一月)、February(二月)、March(三月)、April(四月)、 // May(五月)、June(六月)、July(七月)、August(八月)、September(九月)、 // October(十月)、November(十一月)、December(十二月) JAN("一月", "Jan"), FEB("二月", "Feb"), MAR("三月", "Mar"), APR("四月", "Apr"), MAY("五月", "May"), JUN("六月", "Jun"), JUL("七月", "Jul"), AUG("八月", "Aug"), SEP("九月", "Sep"), OCT("十月", "Oct"), NOV("十一月", "Nov"), DEC("十二月", "Dec"); private String key; private String value; Month(String key, String value) { this.key = key; this.value = value; } public String getKey() { return key; } public String getValue() { return value; } }二、选做题输入年份和月份,打印日历输入年份,打印一年的日历十、集合1、介绍1.1 概念集合也叫容器,是用来存放其他对象的对象。所以容器本身也是一个对象。1.2 集合架构1.3 为什么使用 Java 集合降低编程难度提高程序性能提高API间的互操作性降低学习难度降低设计和实现相关API的难度增加程序的重用性注意:java容器里只能存放对象,对于基本数据,会自动拆箱和装箱,虽然自动装拆箱会导致性能和空间的开销,但是这种设计方式可以简化开发人员的编码。1.4 体系结构容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对映射表。主要包含如下内容:ListArrayList:基于动态数组实现,支持随机访问。Vector:和 ArrayList 类似,但它是线程安全的,但在jdk1.5后被CopyOnWriteArrayList代替。LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。SetTreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。MapTreeMap:基于红黑树实现。HashMap:基于哈希表实现。HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。QueueLinkedList:可以用它来实现双向队列。PriorityQueue:基于堆结构实现,可以用它来实现优先队列。2、Collection 接口2.1、常用方法add()方法向集合中添加元素clear()方法,清空集合中所有元素contains()方法 判断集合是否包含某个元素isEmpty判断集合是否为空remove方法 移除集合中元素,返回boolean类型。如果集合中不包含次元素,则删除失败size()返回集合中元素的个数toArray将集合转换成数组。addAll 向一个集合中添加另一个集合containsAll 判断一个集合中是否包含另一个集合removeAll 从一个集合中移除另一个集合3、 迭代器3.1 Iterator迭代器用于遍历列表和修改元素。迭代器接口具有以下三种方法:public boolean hasNext() – 如果迭代器有更多元素,此方法返回 true。public object next() - 它返回元素并将光标指针移动到下一个元素。public void remove() - 此方法删除迭代器返回的最后一个元素。3.2 迭代器原理迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。3.3.2 迭代器使用常见问题1、迭代器迭代完成之后,迭代器的位置在最后一位。 所以迭代器只能迭代一次2、迭代器在迭代的时候,不要调用多次next方法,可能会出错 NoSuchElementException3、在迭代器迭代的时候,不能向集合中添加或者删除元素 ConcurrentModificationException4、泛型基本使用泛型:参数化类型 JDK1.5之后用途:泛型擦除: JDK1.7之后泛型需要注意的问题:5、List 接口List是可能包含重复元素的有序集合。它是一个扩展 Collection 接口的接口。List进一步分为以下几类:ArrayListLinkedListVectors5.1、ArrayList类ArrayList 是 List 接口的实现,可以在List中动态添加或删除元素。此外,如果添加的元素超过初始大小,则ArrayList的大小会动态增加。ArrayList注意点:默认构造参数不会初始化容器大小,但是可以调用带参的构造方法给他一个初始容量大小 关于arraylist扩容:扩容机制 扩容后的大小=老数组大小+(老数组大小/2) 调用默认构造方法创建arrylist时不会初始化容器大小, 需要等到我们真正在添加值的时候才会给容器分配大小,默认分配为10常用方法:add(int index, E element)remove(int index)set(int index, E element)get(int index)subList(int beginIndex,int endIndex)list.listIterator();随堂练习/** * ArrayList的使用: 它是一种可以存放重复元素,并且按照存储顺序排序的一种数据结构. */ public class ArrayListDemo { public static void main(String[] args) { //调用ArrayList的无参构造方法创建一个对象 ArrayList arrayList = new ArrayList(); //使用容器的引用调用方法存储数据 arrayList.add(1);//容器内部会自动把1包装成对象类存储 arrayList.add(new Person());//存储对象 arrayList.add("helloworld");//存储字符串 arrayList.add(true);//存储布尔类型 arrayList.add(m());//可以在add实参里调用方法 arrayList.add(3.14);//存储浮点数 //获取集合中0位置的元素 System.out.println(arrayList.get(0)); //修改集合中3位置的数据 System.out.println(arrayList.get(3)); arrayList.set(3,true);//会覆盖掉原来该位置的值 System.out.println(arrayList.get(3)); //删除指定内容的元数 arrayList.remove("helloworld"); //根据位置删除元素 arrayList.remove(1); arrayList.add(3, "Ten");//在指定位置插入指定值,原来该位置的值会向后移动 //返回某个元素在集合中的位置 System.out.println(arrayList.indexOf(3.14)); //使用迭代器iterator遍历集合。 Iterator iterator = arrayList.iterator(); while (iterator.hasNext()) { //获取当前游标指向的值 Object value = iterator.next(); System.out.print(value + " "); } System.out.println(); //使用游标遍历相对复杂,因此我们可以使用游标的一个语法糖来遍历,也即是增强for循环 for (Object obj : arrayList) System.out.print(obj + " "); System.out.println(); //使用普通for循环遍历集合 for (int i = 0; i < arrayList.size(); i++) System.out.print(arrayList.get(i) + " "); System.out.println(); //实际开发中,一种集合通常只能存储同一种数据类型。此时就需要给集合加上泛型限制 //定义一个集合只能存储字符串类型 Arraylist<数据类型> 引用名 = new ArrayList(); ArrayList<String> names = new ArrayList<>(); names.add("张三"); names.add("李四"); names.add("王五"); //存储整数 //names.add(0);报错,因为泛型已经限制该集合只能存储字符串 //定义一个集合只能存储User1这种对象 ArrayList<User1> users = new ArrayList<>();//该集合只能存User1的对象或者是User1的子类对象 users.add(new User1("张三")); users.add(new User1("王五")); users.add(new User2("赵强")); //使用增强for循环遍历集合users for (User1 user : users) System.out.println(user);//如果此时想查看对象内部具体的值,那么此对象需要提供toString方法 } public static String m() { return "你好"; } } class User1 { private String name; public User1(){} public User1(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User1{" + "name='" + name + '\'' + '}'; } } class User2 extends User1{ public User2(String name) { super(name); } }5.1.1 实现原理ArrayList其底层实现使用数组5.2、LinkedList类LinkedList是包含项目的链接序列。每个链接都包含与另一个链接的连接。5.2.1 常用方法常用的方法与ArrayList一致。自己独有一些向首尾添加移除等方法(可以模拟对列、堆栈等数据结构)package com.qf.demo02; import java.util.LinkedList; public class LinkedListDemo { public static void main(String[] args) { //创建LinkedList对象 LinkedList<String> list = new LinkedList<String>(); list.add("jack"); list.add("rose"); list.add("cxk"); list.add("李雷"); list.add("韩梅梅"); list.add(1, "马冬梅"); System.out.println(list); list.addFirst("尼古拉斯"); list.addLast("亚洲舞王"); System.out.println(list); System.out.println("================================================"); System.out.println(list.getFirst()); System.out.println(list.getLast()); System.out.println(list.get(3)); System.out.println("================模拟栈结构=================="); LinkedList<String> list1 = new LinkedList<String>(); list1.push("aa"); list1.push("bb"); System.out.println(list1.pop()); System.out.println(list1.pop()); System.out.println("================模拟对列结构=================="); LinkedList<String> list2 = new LinkedList<String>(); //向对列的尾部添加元素 list2.offer("哈哈"); list2.offer("呵呵"); list2.offer("嘻嘻"); list2.offer("hiahia"); //获取并移除对列的头部的元素 System.out.println(list2.poll()); System.out.println(list2.poll()); System.out.println(list2.poll()); System.out.println(list2.poll()); //获取但不移除对列的头部的元素 //System.out.println(list2.element()); //获取但不移除对列的头部的元素 //System.out.println(list2.peek()); System.out.println(list2); //LinkedList集合三种遍历方式 } }5.2.3 LinkedList实现原理Java LinkedList 类使用两种类型的链表来存储元素:单链表双向链表单链表:在单链表中,此列表中的每个节点都存储节点的数据以及指向列表中下一个节点的指针或引用。双链表:在双链表中,它有两个引用,一个指向下一个节点,另一个指向前一个节点。5.3、Vector类5.3.1 常用方法与ArrayList的方法基本一致public class VectorDemo { public static void main(String[] args) { Vector<String> vector = new Vector<String>(); vector.add("aa"); System.out.println(vector); /** * ArrayList、LinkedList、Vector的区别 * * ArrayList和Vector底层使用数组实现(增删慢、查询快) * ArrayList是当添加元素的时候,才会扩容数组,默认长度为10 * Vector是当创建对象的是,就创建长度为10的数组 * ArrayList线程不安全,效率高 * Vector是线程安全的,效率低 * * LinkedList底层使用双向链表实现(增删快、查询慢) */ } }5.3.2 实现原理与ArrayList的底层一致,使用数组实现回顾:6、Map集合6.1 Map集合特点Map集合是双列集合,由key和value组成。称之为键值对键的特点:无序,无下标,不重复。值的特点:无序,无下标,可重复6.2 Map集合体系结构暂时无法在飞书文档外展示此内容6.3 HashMap中文叫哈希表或散列表6.3.1 HashMap基本使用常用方法put(K key, V value)get(Object key)Set keySet()Collection values()Set<Map.Entry<K,V>> entrySet()boolean containsKey(Object key)boolean containsValue(Object value)V remove(Object key)int size()package com.qf.demo01; import java.util.Collection; import java.util.HashMap; import java.util.Map.Entry; import java.util.Set; public class HashMapDemo { public static void main(String[] args) { //创建HashMap HashMap<String, String> map = new HashMap<String, String>(12); //向map集合中添加元素 map.put("usa", "漂亮国"); map.put("jp", "日本"); map.put("en", "英国"); map.put("ca", "加拿大"); map.put("cn", "中华人民共和国"); map.put("cn", "中国"); map.put("china", "中国"); System.out.println(map); //从集合中获取元素的值。 根据key获取对应value System.out.println(map.get("cn")); System.out.println(map.get("usa")); //清空map集合中的元素 //map.clear(); //System.out.println(map); //判断是否包含指定的key System.out.println(map.containsKey("cxk")); //判断是否包含指定的value System.out.println(map.containsValue("中国")); //判断集合中的元素长度是否为0 System.out.println(map.isEmpty()); //根据key移除map中的元素 map.remove("jp"); System.out.println(map); //返回map集合中元素的个数 System.out.println(map.size()); System.out.println("================================="); //返回map集合中所有的key Set<String> keySet = map.keySet(); for (String key : keySet) { System.out.println(key); } System.out.println("================================="); //返回map集合中所有的value Collection<String> values = map.values(); for (String value : values) { System.out.println(value); } System.out.println("================================="); //返回map集合中所有的key和value (Entry) for (Entry<String, String> entry : entrySet) { System.out.println(entry.getKey()); System.out.println(entry.getValue()); } System.out.println("================================="); //遍历 for (Object entry: hashMap.entrySet()) { Map.Entry en = (Map.Entry) entry; System.out.print("<" + en.getKey() + ", "); System.out.print(en.getValue() + ">\n"); } //hashmap泛型基本使用 //如果希望hashmap存储指定类型的键或值,那么可以通过泛型来限制HashMap<数据类型, 数据类型> //尖括号内的数据类型不能是基本数据类型,必须都是引用类型 HashMap<String, Integer> hash1 = new HashMap<>(); hash1.put("张三", 18); //hash1.put("张三", "");不允许插入泛型限制之外的数据类型 hash1.put("李四", 28); HashMap<Object, Object> hash2 = new HashMap<>(); hash2.put(1, "1"); hash2.put(2, 3); HashMap<String, User3> hash3 = new HashMap<>();//value存放自定义对象类型 hash3.put("user1", new User3("user3")); hash3.put("user2", new User4());//可以存放泛型限制的子类 //hash3.put("user3", new Object());//不能存放父类 //如何访问hashmap中的值 System.out.println(hash1.get("张三"));//根据key获取value //注意:不能根据value获取key System.out.println(hash1.containsKey("李四"));//集合中是否包含指定key System.out.println(hash1.containsValue(18));//集合中是否包含指定value } }Hash函数LINEAR PROBE HASHING : 线性探测hash6.3.2 HashMap实际应用可以使用Map 表示一个实体类可以使用List<Map<String,Object>> 表示一个实体类集合public class HashMapDemo02 { /** * 表示一件商品:商品编号、名称、价格、产地、上架时间.... * * 实体类表示: * 一件商品:Product对象 * public class Product{ * private int id; * private String name; * private double price; * ..... * } * Product product = new Product(1,"手机",3000...); * * 多件商品:List<Product> * * Map表示: * 一件商品:Map对象 * Map<String,Object> map = new HashMap<>(); * map.put("id",1); * map.put("name","电脑"); * map.put("price",3000.5); * * 多件商品:List<Map<String,Object>> */ public static void main(String[] args) { //使用Map表示一件商品 Map<String, Object> map1 = new HashMap<String, Object>(); map1.put("id", 1); map1.put("name", "电脑"); map1.put("price",3000.5); map1.put("createDate", new Date()); System.out.println("map1 : " + map1); Map<String, Object> map2 = new HashMap<String, Object>(); map2.put("id", 2); map2.put("name", "电脑2"); map2.put("price",3002.5); map2.put("createDate", new Date()); System.out.println("map2 : " + map2); //使用List<Map>表示多件商品 List<Map<String, Object>> list = new ArrayList<Map<String,Object>>(); list.add(map1); list.add(map2); for (Map<String, Object> map3 : list) { System.out.println(map3); for (Map.Entry<String, Object> entry : map3.entrySet()) { System.out.println(entry.getKey() + "==" + entry.getValue()); } } //使用集合实现存储省市信息 } }6.3.4 HashMap练习案例:使用集合保存省市数据public static void main(String[] args) { Map<String, List<String>> map = new HashMap<String, List<String>>(); List<String> city1 = new ArrayList<String>(); city1.add("武汉"); city1.add("监利"); city1.add("黄冈"); city1.add("荆州"); map.put("湖北省", city1); List<String> city2 = new ArrayList<String>(); city2.add("长沙"); city2.add("岳阳"); city2.add("常德"); city2.add("湘潭"); map.put("湖南省", city2); List<String> city3 = new ArrayList<>(); city3.add("贵阳"); city3.add("六盘水"); city3.add("遵义"); city3.add("铜仁"); city3.add("兴义"); city3.add("毕节"); city3.add("安顺"); city3.add("凯里"); city3.add("都匀"); map.put("贵州省", city3); System.out.println(map); System.out.println(map.get("湖北省")); //遍历贵州各市 List<String> guizhou = map.get("贵州省");//根据key获取value for (String city : guizhou) System.out.print(city + " "); System.out.println(); //遍历hasmap for (Map.Entry<String, List<String>> entry : map.entrySet()) { System.out.print(entry.getKey() + " : "); //遍历各个市 for (String c : entry.getValue()) System.out.print(c + " "); System.out.println(); } }6.4 HashMap源码解析6.4.1 put的过程原码final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { //tab表示存放Node节点的数据 p表示当前节点 n表示长度 i表示节点在数组中的下标 Node<K,V>[] tab; Node<K,V> p; int n, i; //判断数组如果为空或者数组长度为0,那么就对数组进行扩容,数组默认初始大小为16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //将数组的长度-1与hash值进行与运算(计算的结果一定是0~数组长度-1)得到元素应该存放的下标 //如果当前下标位置为空,那么直接将Node节点存放在当前位置 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //如果当前位置不为空(分为三种情况) else { Node<K,V> e; K k; //情况1:要添加的元素与当前位置上的元素相同(hash(hashCode)、key(equals)一致),则直接替换 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //情况2:如果要添加的元素是红黑树节点,那么将其添加到红黑树上 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //情况3:如果要添加的元素是链表,则需要遍历 else { for (int binCount = 0; ; ++binCount) { //将当前元素的下一个节点赋给e //如果e为空,则创建新的元素节点放在当前位置的下一个元素上,并退出循环 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //如果链表的元素个数大于8个(且当数组中的元素个数大于64),则将其转换成红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //要添加的元素与当前位置上的元素相同(hash(hashCode)、key(equals)一致),则直接退出循环 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //如果返回的e不为null if (e != null) { // existing mapping for key //将e的值赋给oldValue V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); //返回以前的值(当添加的元素已经存在返回的是以前的值) return oldValue; } } ++modCount; //如果数组的元素个数大于阈值则进行扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }6.4.2 resize过程原码final Node<K,V>[] resize() { //oldTab 表示原来数组(如果是第二次扩容:长度为16的那个) Node<K,V>[] oldTab = table; //oldCap 表示原数组的容量(长度) int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldThr 表示数组原来的阈值 12 int oldThr = threshold; //newCap 新数组的容量 newThr 新数组的阈值 int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //新数组的容量扩大一半 newCap 32 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //新阈值扩大老阈值的一半 newThr 24 newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } //threshold 24 threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) //创建一个长度为32的数组 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //table指向新数组 table = newTab; if (oldTab != null) { //将原数组中的元素拷贝到新数组中 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; //如果当前位置元素不为空 if ((e = oldTab[j]) != null) { oldTab[j] = null; //情况1:当前位置上的下一个元素为空,则直接将这个元素拷贝到新数组中 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //情况2:当前位置上的元素红黑树类型,则需要进行切割 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //情况3:当前位置上的元素链表类型,则需要进行分散拷贝 else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }6.4.3 get的过程原码final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //当前first与要找到的hash和key都相等直接返回当前这个first元素 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; //如果当前first不为空(有两种情况) if ((e = first.next) != null) { //当前位置是一个红黑树 if (first instanceof TreeNode) //根据hash、key从红黑树上找到对应的元素 return ((TreeNode<K,V>)first).getTreeNode(hash, key); //当前位置是一个链表 do { //循环进行比较直到找到向的hash和key的元素,并返回 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } //如果数组的为空、数组的长度为0、当前下标位置上的值为null,这三种情况都返回null return null; }6.5 HashTableHashtable常用方法与HashMap一致HashMap与Hashtable区别:1、Hashtable是线程安全的,HashMap是线程不安全的2、Hashtable中不允许存储null作为key和value,而HashMap可以在实际开发中一般都是用HashMap。考虑线程安全使用ConCurrentHashMappublic static void main(String[] args) { Hashtable<Integer, String> hashtable = new Hashtable<>(); hashtable.put(2, "b"); hashtable.put(1, "a"); hashtable.put(5, "l"); hashtable.put(9, "h"); hashtable.put(20, "hz"); //hashtable.put(10, null);//hashtable不允许插入空键或者是值,会报NullPointerException hashtable.remove(10); System.out.println(hashtable); //遍历 for (Map.Entry<Integer, String> entry : hashtable.entrySet()) { System.out.print("<" + entry.getKey() + ", "); System.out.print(entry.getValue() + "> "); } }6.6 TreeMap根据给定的键来排序。如果键是包装类和字符串,那么就会按照升序进行排序。为什么包装类和字符能排序?是因为他们实现了Comparable接口并且重写了comparaTo方法。因此我们想通过TreeMap给自定义的类排序,就需要让类实现接口Comparable并且重写comparaTo方法。TreeMap按键的升序对条目进行排序。 public static void main(String[] args) { Map<Integer, String> map = new TreeMap(); map.put(2, "b"); map.put(1, "a"); map.put(5, "l"); map.put(9, "h"); map.put(20, "hz"); System.out.println(map); //遍历1 System.out.println("for-each遍历:"); for (Map.Entry<Integer, String> entry : map.entrySet()) { System.out.print("<" + entry.getKey() + ", "); System.out.print(entry.getValue() + "> "); } System.out.println("\n通过迭代器遍历:"); //遍历2 Set<Map.Entry<Integer, String>> entrySet = map.entrySet(); Iterator<Map.Entry<Integer, String>> iterator = entrySet.iterator(); while (iterator.hasNext()) { Map.Entry<Integer, String> next = iterator.next(); System.out.print("<" + next.getKey() + ", "); System.out.print(next.getValue() + "> "); } }6.7 LinkedHashMap/** * LinkedHashMap,保证插入的顺序 */ public class TestLinkedHashMap { public static void main(String[] args) { LinkedHashMap<Integer, String> lhm = new LinkedHashMap<>(); lhm.put(2, "b"); lhm.put(1, "a"); lhm.put(5, "l"); lhm.put(9, "h"); lhm.put(20, "hz"); System.out.println(lhm); //遍历 for (Map.Entry<Integer, String> entry : lhm.entrySet()) { System.out.print("<" + entry.getKey() + ", "); System.out.print(entry.getValue() + "> "); } } }HashMap没有维持任何顺序。 TreeMap按键的升序对条目进行排序。 LinkedHashMap保持插入顺序。7、Set 接口7.1、HashSet类7.1.1 HashSet基本使用常用方法与Collection接口中定义的方法一致特点:无序 --->原因:因为使用了hashmap作为底层存储,hashmap的hash函数算出来的值不能保证有序无下标-->使用hash算法算出存储位置,而不是使用下标不可重复-->因为set的add方法内部调用的是Hashmap的put方法,而我们添加的值是作为 HashMap的key来存储,所以根据hashmap的hash算法,如果同一个key,那么hash函数算出来的值一定相等,只要相等,就不会重复存储,所以就实现了去重。public static void main(String[] args) { //创建一个HashSet对象 HashSet hashSet = new HashSet(); hashSet.add(1); hashSet.add(1.0); hashSet.add("ssss"); hashSet.add(new String("String")); System.out.println(hashSet); //删除元素 hashSet.remove("String"); System.out.println(hashSet); System.out.println(hashSet.contains(1.0)); //使用迭代器遍历set Iterator iterator = hashSet.iterator(); while (iterator.hasNext()) { //hashSet.add(999);//迭代集合时不要做添加或删除操作 System.out.println(iterator.next()); } //如果想规定同一种Set只能存储同一种数据类型,可以通过泛型来限制 Set<String> set = new HashSet<>(); set.add("abc"); set.add("def"); set.add("ghk"); System.out.println(set); //使用增强for循环遍历集合 for (String val : set) { System.out.print(val + " "); } System.out.println(); //集合存对象 Set<User1> users = new HashSet<>(); users.add(new User1("张三", 18)); users.add(new User1("李四", 19)); for (User1 user : users) System.out.print(user.getName() + " "); }7.1.2 HashSet 去重原理HashSet底层去重:/** * Set集合的使用,hashset去重原理:因为set的add方法内部调用的是Hashmap的put方法,而我们添加的值是作为 * HashMap的key来存储,所以根据hashmap的hash算法,如果同一个key,那么hash函数算出来的值一定相等,只要相等, * 就不会重复存储,所以就实现了去重。 */ public class TestHashset { public static void main(String[] args) { //要使用一个集合,第一步就得先把集合对象创建出来。 //因为Set是一个接口,具体的实现类是HashSet,所以这儿是一个多态,父类引用指向子类对象 Set<Order> set = new HashSet(); //往集合里添加元素,因为我们结合在创建时限制了只能存储Order类型的值,所以需要创建Order对象 //使用无参构造方法创建一个类 Order order1 = new Order(); order1.setName("cloth"); order1.setId(1); set.add(order1); //使用带参构造方法创建对象 set.add(new Order(2, "shoes", 100, 399.0f )); set.add(new Order(2, "shoes", 100, 399.0f )); //输出集合 System.out.println(set); //使用迭代器遍历集合 Iterator<Order> iterator = set.iterator(); while (iterator.hasNext()) { Order order = iterator.next();//获取到的是集合里的单个对象 System.out.print(order.getName() + " "); } System.out.println(); //使用for each遍历 for (Order o : set) { System.out.print(o.getName() + " "); } System.out.println(); //如果需要判断自定义对象是否包含在集合内,我们需要重写equals方法和hashcode方法 System.out.println(set.contains(new Order(2, "shoes", 100, 399.0f ))); System.out.println("\n----------------------------------------"); //set集合里添加基本数据类型 HashSet<Integer> setInt = new HashSet<>(); setInt.add(100); setInt.add(200); setInt.add(300); setInt.add(400); setInt.add(500); System.out.println(setInt); //删除元素 setInt.remove(300); setInt.add(100); System.out.println(setInt); //判断集合是否包含某个元素 System.out.println(setInt.contains(500)); } }重写hashCode和equals方法为什么重写equals方法就要重写hashcode方法?当我们对比两个对象是否相等时,我们就可以先使用 hashCode 进行比较,如果比较的结果是 true,那么就可以使用 equals 再次确认两个对象是否相等,如果比较的结果是 true,那么这两个对象就是相等的,否则其他情况就认为两个对象不相等。这样就大大的提升了对象比较的效率,这也是为什么 Java 设计使用 hashCode 和 equals 协同的方式,来确认两个对象是否相等的原因。public class Order { private Integer id; private String name; private Integer number; private Float price; public Order() { } public Order(Integer id, String name, Integer number, Float price) { this.id = id; this.name = name; this.number = number; this.price = price; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getNumber() { return number; } public void setNumber(Integer number) { this.number = number; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } //重写toString @Override public String toString() { return "{id : " + id + " name:" + name + " number:" + number + " price:" + price + "}"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Order order = (Order) o; return Objects.equals(id, order.id) && Objects.equals(name, order.name) && Objects.equals(number, order.number) && Objects.equals(price, order.price); } @Override public int hashCode() { return Objects.hash(id, name, number, price); } }7.2、LinkedHashSet类LinkedHashSet特点:1、有序 保证插入顺序2、无下标3、不可重复与父类的方法一致,去重的原理,也与父类一致public class LinkedHashSetDemo { public static void main(String[] args) { //LinkedHashSet 有序(链表维护顺序) 不能重复 LinkedHashSet<String> set = new LinkedHashSet<>(); set.add("jack"); set.add("大娃"); set.add("二娃"); set.add("rose"); set.add("爷爷"); set.add("爷爷"); for (String s : set) { System.out.println(s); } //1、底层实现 (LinkedHashMap) //2、去重原理 (与hashSet一致) } }7.3、TreeSet类TreeSet特点:1、TreeSet类似于TreeMap,按升序对元素进行排序。2、无下标3、不可重复public static void main(String[] args) { //LinkedHashSet 有序(链表维护顺序) 不能重复 TreeSet<Integer> set = new TreeSet<>(); set.add(10); set.add(23); set.add(5); set.add(25); set.add(15); System.out.println(set); TreeSet<User1> users = new TreeSet<>(); users.add(new User1("张三", 16)); users.add(new User1("李四", 6)); users.add(new User1("王五", 26)); users.add(new User1("赵强", 12)); users.add(new User1("麻子", 10)); System.out.println(users); }7.3.1 常用方法与HashSet类的方法一致特点:使用TreeSet集合存储对象的时候,对象必须要实现Comparable接口7.3.2 实现原理TreeSet在存储元素的时候,会调用compareTo方法。两个作用:1、排序: 返回值大于0升序,返回值小于0降序2、去重(返回值为0) TreeSet认为返回0,两个对象就是相同的对象package com.qf.demo03; import java.util.TreeSet; public class TreeSetDemo { public static void main(String[] args) { //TreeSet的去重原理 TreeSet<Person> set = new TreeSet<Person>(); set.add(new Person("admin","123")); set.add(new Person("aa","bb")); set.add(new Person("jack","123")); set.add(new Person("cxk","abc")); set.add(new Person("rose123","123")); set.add(new Person("admin","123")); //如果两个对象的用户名和密码都相等,则认为就是相同的对象,且按照名字长度升序存放 for (Person person : set) { System.out.println(person); } /** * TreeSet集合在保存对象元素的时候,对象必须要实现Comparable接口重写compareTo方法。 * TreeSet的去重原理为:如果compareTo方法的返回值为0,则认为是相同的对象 * 如果compareTo方法的返回大于0则是升序排序,小于0则是降序排序 */ } } class Person implements Comparable<Person>{ String username; String password; public Person(String username, String password) { super(); this.username = username; this.password = password; } public Person() { } @Override public String toString() { return "User [username=" + username + ", password=" + password + "]"; } @Override public int compareTo(Person o) { if(!this.username.equals(o.username)) { return this.username.length() - o.username.length(); }else { if(this.password.equals(o.password)) { return 0; }else { return this.username.length() - o.username.length(); } } } }栈(Stack)栈(stack)是一种操作受限的线性序列只能在栈的顶部插入和删除数据底部为盲端抽象数据类型(stack)接口名描述push(int val)把元素入栈,val表示要入栈的值pop()出栈元素top()查看栈顶元素size()获取栈大小empty()判断栈是否为空栈是一种后进先出(LIFO)或先进后出(FILO)的数据结构public static void main(String[] args) { Stack stack = new Stack();//底层基于数组实现 //入栈元素 stack.push("星期二"); stack.push(12); System.out.println("stack : " + stack); //出栈元素 //Object obj = stack.pop(); //查看栈顶元素 Object obj1 = stack.peek(); //System.out.println("obj : " + obj); System.out.println("obj1 : " + obj1); }队列(Queeu)队列(queue)也是一种操作受限的线性序列只能在队尾插入数据只能在队头删除数据抽象数据类型(Queue)接口名描述enqueue(int val)把元素入队,val表示要入队的值dequeue()出队元素size()获取队列大小empty()判断队列是否为空队列是一种先进先出(FIFO1)或后进后出(LIFO)的数据结构public static void main(String[] args) { Queue<String> queue = new PriorityQueue();//多态 //队列插入元素的操作叫做"入队" queue.add("星期二"); queue.add("星期天"); System.out.println(queue); //取元素的过程叫做"出队" System.out.println(queue.poll()); }Collections工具类集合: 工具类(Collections)Collections.reverse(List<?> list)Collections.shuffle(List<?> list)Collections.sort(List<?> list)public class CollectionsDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("jack"); list.add("大娃"); list.add("二娃"); list.add("rose"); list.add("妖怪"); list.add("蛇妖"); list.add("蛇妖"); System.out.println(list); //按照字典顺序 Collections.sort(list); System.out.println(list); //将集合元素进行翻转 Collections.reverse(list); System.out.println(list); //将集合中的元素进行随机打乱 Collections.shuffle(list); System.out.println(list); //Arrays数组工具类 Collections集合工具类 } }Comparable/** * Comparable是一个接口,我们要让工具类给我们的自定义类按照某种属性排序,就要实现该接口里的comparaTo方法 */ public class TestComparable { public static void main(String[] args) { //创建一个集合 ArrayList<JavaStudent> arrayList = new ArrayList<>(); arrayList.add(new JavaStudent("wangyi")); arrayList.add(new JavaStudent("zhangsan")); arrayList.add(new JavaStudent("lisi")); //使用集合的排序工具类lections给我们的集合排序 Collections.sort(arrayList); System.out.println(arrayList); } } class JavaStudent implements Comparable<JavaStudent> { private String username; public JavaStudent(String username) { this.username = username; } /** * 对于表达式x.compareTo(y),如果返回值为0,则表示x和y相等,如果返回值大于0,则表示x大于y,如果返回值小于0,则表示x小于y. * 在ComparableTimSort类里的binarySort(Object[] a, int lo, int hi, int start)方法里有以下if逻辑: * if (pivot.compareTo(a[mid]) < 0)//这部分逻辑就会来调用自定义的compareTo方法 */ @Override public int compareTo(JavaStudent o) { System.out.println(this.username.compareTo(o.username)); return -this.username.compareTo(o.username); } /* 首先按照名字排序,如果名字相等,就按照年龄排序。 @Override public int compareTo(Person001 o) { int result = 0; result = this.name.compareTo(o.name); if (result == 0) { result = this.age - o.age; } return result; } */ @Override public String toString() { return "JavaStudent{" + "username='" + username + '\'' + '}'; } }Comparator是一个接口,内部提供了compare抽象方法,让用户自定义的类可以通过实现该接口重写compare方法来实现按照指定字段排序。开发中通常会会为每个字段写一个比较器,如下代码示例,通过实现Comparator匿名内部类的形式,我们为username和age字段实现了不同的比较器。/** * Comparable是一个接口,我们要让工具类给我们的自定义类按照某种属性排序,就要实现该接口里的comparaTo方法 */ public class TestComparable { public static void main(String[] args) { //创建一个集合 ArrayList<JavaStudent> arrayList = new ArrayList<>(); arrayList.add(new JavaStudent("wangyi", 13)); arrayList.add(new JavaStudent("zhangsan", 18)); arrayList.add(new JavaStudent("lisi", 15)); //使用集合的排序工具类lections给我们的集合排序 //Collections.sort(arrayList); //System.out.println(arrayList); //使用Comparator来按照指定属性排序 //使用匿名内部类重写比较器 /*Collections.sort(arrayList, new Comparator<JavaStudent>() { @Override public int compare(JavaStudent o1, JavaStudent o2) { return o1.getUsername().compareTo(o2.getUsername()); } });*/ //使用显示类重写比较器,等价于上边的匿名内部类形式 /* Collections.sort(arrayList, new UsernameComparator()); System.out.println(arrayList);*/ //按照年龄排序,因为年龄是int类型,所以没有自带的comparaTo方法 Collections.sort(arrayList, new Comparator<JavaStudent>() { @Override public int compare(JavaStudent o1, JavaStudent o2) { return o1.getAge() - o2.getAge(); } }); System.out.println(arrayList); } } /** * 实现Comparator接口重写compara方法实现比较器 */ class UsernameComparator implements Comparator<JavaStudent> { @Override public int compare(JavaStudent o1, JavaStudent o2) { return o1.getUsername().compareTo(o2.getUsername()); } } /** * 实现Comparable接口重写comparaTo方法实现比较器 */ class JavaStudent implements Comparable<JavaStudent> { private String username; private int age; public JavaStudent(String username, int age) { this.username = username; this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } /** * 对于表达式x.compareTo(y),如果返回值为0,则表示x和y相等,如果返回值大于0,则表示x大于y,如果返回值小于0,则表示x小于y. * 在ComparableTimSort类里的binarySort(Object[] a, int lo, int hi, int start)方法里有以下if逻辑: * if (pivot.compareTo(a[mid]) < 0)//这部分逻辑就会来调用自定义的compareTo方法 */ @Override public int compareTo(JavaStudent o) { return this.username.compareTo(o.username); } /* 首先按照名字排序,如果名字相等,就按照年龄排序。 @Override public int compareTo(Person001 o) { int result = 0; result = this.name.compareTo(o.name); if (result == 0) { result = this.age - o.age; } return result; } */ @Override public String toString() { return "JavaStudent{" + "username='" + username + '\'' + ", age=" + age + '}'; } }泛型使用泛型类泛型类 类名public class Box<T> { //T:表示任意的java类型 E、K、V private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public class MyGeneric<E> {//此处E代表任意类型 private Object[] names = new Object[10]; //定义下标记录数组和有效元素个数 int index = 0; //使用数组作为底层存储 /** * 提供一个add方法 往数组内插入元数 */ public void add(E val) { names[index++] = val; } } public class TestMyGeneric{ public static void main(String[] args) { MyGeneric generic = new MyGeneric(); //插入数据 generic.add("你好"); //如果我们想让MyGeneric可以支持插入任意数据类型。让我们MyGeneric支持泛型即可 generic.add("12"); } }泛型接口泛型接口 接口名public interface MyInterface<T> { } public interface GenericInterface<T> { void m1(T t); } public class GenericInterfaceImpl implements GenericInterface<String>{ @Override public void m1(String s) { } } class GenericInterfaceImpl1 implements GenericInterface { @Override public void m1(Object o) { } } public class GenericInterfaceTest { public static void main(String[] args) { GenericInterface gi = new GenericInterfaceImpl(); gi.m1("12"); GenericInterface<Integer> gif = new GenericInterfaceImpl1(); gif.m1(12); } }泛型方法泛型方法 public T 方法名(T t,...){}//泛型可以作为参数,(必须得先定义 <T> ) public <T> void m1(T t) { } /** * 自定义泛型方法 */ public class TestMyGeneric{ public static void main(String[] args) { m1(102); m1("102"); m1(true); System.out.println(m2("nel")); } //定义无返回值泛型方法 public static <E> void m1(E e) { System.out.println(e); } //定义返回值泛型方法 public static <E> E m2(E e) { return (E)(new String(e.toString())); } }泛型上下边界泛型上下边界语法:public class Demo01 { //? 表示不确定类型 此时的?表示Object public static void test01(List<?> list) { } public static void test02(List<? extends Number> list) { } public static void test03(List<? super Number> list) { } public static <T> void test04(List<? extends Comparable<T>> list) { } public static void main(String[] args) { List<String> list1 = new ArrayList<String>(); List<Integer> list2 = new ArrayList<Integer>(); List<Number> list3 = new ArrayList<Number>(); List<Object> list4 = new ArrayList<Object>(); test01(list1); test01(list2); test01(list3); test01(list4); //test02(list1); //错误,方法定义泛型的上边界,泛型只能是Number及其Number子类 test02(list2); test02(list3); //test02(list4); //错误,方法定义泛型的上边界,泛型只能是Number及其Number子类 //test03(list1); //错误,方法定义泛型的下边界,泛型只能是Number及其Number父类 //test03(list2); test03(list3); test03(list4);------------------ test04(list1); test04(list2); //test04(list3); //错误,方法定义泛型的上边界,泛型必须实现Comparable接口 //test04(list4); //错误,方法定义泛型的上边界,泛型必须实现Comparable接口 } }泛型擦除当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如<T>和<?>的类型参数都被替换为Object。当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如<T extends Number>和<? extends Number>的类型参数被替换为Number,<? super Number>被替换为Object。擦除方法定义中的类型参数原则和擦除类定义中的类型参数一样十一、异常之前我们介绍的基本类型、类、接口、枚举数据,操作的过程中可能有很多出错的情况,出错的原因可能是多方面的,有的是不可控的内部原因,比如内存不够了、磁盘满了,比如网络连接有问题,更多的可能是程序的编写错误,比如引用变量未初始化就直接调用实例方法。这些非正常情况在Java中统一被认为是异常,Java使用异常机制来统一处理。什么是异常异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。程序如果不处理这些异常,可能会造成意外的结果或是程序终止Java中的异常异常在Java中是一个对象。下图是异常的类继承关系图。Error和ExceptionError是Throwable的一个子类,表示应用程序不应尝试捕获的严重问题。如:内存溢出,内存泄露等。Exception是Throwable的子类,代表应用程序在编译期间或执行过程中应该捕获的问题。Checked(编译期) 和 Unchecked(运行时)的异常Checked:受检查异常,也叫非运行时异常或编译期异常,是指程序在编译期间就会检查。该类异常必须在写代码时就要求程序员捕获处理,否则程序不能正常编译。例如: CloneNotSupportedException, IOException, SQLException, InterruptedException, FileNotFoundExceptionUnchecked: 运行时异常,只有在程序真正执行的时候才会检测的异常,程序能正常编译生产字节码文件。例如: IllegalMonitorStateException, ConcurrentModificationException, NullPointerException, IndexOutOfBoundException, NumberFormatException异常关键字try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。throw – 用于抛出异常。throws – 用在方法签名中,用于声明该方法可能抛出的异常。如何处理异常异常通常使用术语叫做“捕获异常”。处理的方法通常有:try-catchtry-catch-finallytry-finallytry-with-resourcetry-catch块用于处理 Java 中的异常。语法try...catch:try { // code }catch(Exception e) { // code } //构造一个空指针异常 String s = null; //通过try...catch捕获空指针异常,通常空指针异常是不推荐使用异常捕获的,而是使用程序逻辑判断处理掉 try { //正常业务逻辑代码 System.out.println(s.length()); System.out.println("调用字符串长度方法结束"); } catch (NullPointerException e) { //想查看具体是什么类型的异常 e.printStackTrace();//打印出错的堆栈信息。 //出现异常后改怎么处理 //System.out.println("此处调用获取字符串长度的方法抛出了空指针异常"); }在这里,我们将可能产生异常的代码放在try块中。每个try块后面都有一个catch块。当异常发生时,它被catch块捕获。没有try块就不能使用catch块,反之亦如此。示例:public class TestTryCatch { public static void main(String[] args) { try { int divideByZero = 5 / 0; System.out.println("try块中的其余代码"); }catch (ArithmeticException e) { System.out.println("ArithmeticException => " + e.getMessage()); } } }在示例中,我们试图将一个数字除以0。在这里,此代码生成异常。为了处理异常,我们将代码5 / 0放在try块内。现在,当发生异常时,块内的其余代码将被跳过。该catch块捕获异常并执行 catch 块内的语句。如果try块中没有任何语句产生异常,则跳过该catch块。try-catch-finallytry { //程序代码。可能出现异常 } catch(Exception e) { //捕获异常 } finally { //一定会执行的代码 } //除零异常 int a = 10; try { int b = 0; System.out.println("b : " + b); } catch (ArithmeticException e) { System.out.println("异常信息"); } finally { System.out.println("finally"); } System.out.println("-------------------"); try { int b = 10 / 0;//构造除零异常 System.out.println("b : " + b); } catch (ArithmeticException e) { System.out.println("异常信息"); } finally { System.out.println("finally"); } InputStream is = null; try { is = new FileInputStream(""); //code //is.close();//如果程序再关闭文件流过程中发生异常,那么文件流就不能被正常关闭。 System.out.println(is); } catch (Exception e) { e.printStackTrace(); System.out.println("catch"); } finally { try { is.close(); } catch (IOException e) { System.out.println("关闭文件出现异常"); e.printStackTrace(); } } System.out.println("程序执行结束");执行的顺序当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;try-finally在Java中,无论是否有异常,finally块始终会被执行到。该finally块是可选的。对于每个try块,有且只能有一个finally块。finally块的基本语法是:try { //code } finally { // finally block always executes }示例:public static void main(String[] args) { m(); } public static int m() { try { System.out.println("程序开始运行"); //System.exit(0);//虚拟机停止工作。 return 1; //System.out.println("end"); } finally { System.out.println("finally"); } //System.out.println("main end"); }注意:使用finally块是一个好习惯。这是因为它可以包含重要的清理代码,例如。可能被 return、continue 或 break 意外跳过的代码关闭文件或连接多个 catch对于每个try块,可以有多个catch块。多个catch块允许我们以不同的方式处理每个异常。每个catch块的参数类型表示它可以处理的异常类型。例如,public static void main(String[] args) { InputStream is = null; try { is = new FileInputStream("");//编译期异常 int a = 10 / 0;//运行时异常 } catch (FileNotFoundException e) { System.out.println("文件未找到"); } catch (ArithmeticException e) { System.out.println("除零异常"); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("其他任务"); } class ListOfNumbers { public int[] arr = new int[10]; public void writeList() { try { arr[10] = 11; }catch (NumberFormatException e1) { System.out.println("NumberFormatException => " + e1.getMessage()); }catch (IndexOutOfBoundsException e2) { System.out.println("IndexOutOfBoundsException => " + e2.getMessage()); } } } public class TestMultiCatch { public static void main(String[] args) { ListOfNumbers list = new ListOfNumbers(); list.writeList(); } }输出IndexOutOfBoundsException => 索引 10 超出长度 10 的范围在这个例子中,我们创建了一个arr大小为10的整数数组。由于数组索引从0开始,因此数组的最后一个元素位于arr[9]。注意声明,arr[10] = 11;在这里,我们尝试为索引10分配一个值。因此,IndexOutOfBoundException发生。当try块中发生异常时,异常被抛出到第一个catch块。第一个catch块不处理IndexOutOfBoundsException,所以它被传递到下一个catch块。上面示例中的第二个catch块是适当的异常处理程序,因为它处理IndexOutOfBoundsException捕获多个异常catch从 Java SE 7 及更高版本开始,我们现在可以用一个块捕获不止一种类型的异常。这减少了代码重复并提高了代码的简单性和效率。可以由catch块处理的每种异常类型都使用竖线分隔|,该种方式可以代替多个catch块语法是:try { // code } catch (ExceptionType1 | Exceptiontype2 ex) { // catch block }示例:public class Main { public static void main(String[] args) { try { int array[] = new int[10]; array[10] = 30 / 0; } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) { System.out.println(e.getMessage()); } } }捕获基类异常Exceptionclass Main { public static void main(String[] args) { try { int array[] = new int[10]; array[10] = 30 / 0; } catch (Exception e) { System.out.println(e.getMessage()); } } }捕获基异常类和子异常类class Main { public static void main(String[] args) { try { int array[] = new int[10]; array[10] = 30 / 0; } catch (Exception | ArithmeticException | ArrayIndexOutOfBoundsException e) { System.out.println(e.getMessage()); } } }在这个例子中,ArithmeticException和ArrayIndexOutOfBoundsException都是类Exception的子类。所以,会得到一个编译错误。final、finally、 finalize的区别final可以用来修饰类、方法、变量,分别有不同的意义,final修饰的class代表不可以继承扩展,final的变量是不可以修改的,而final的方法也是不可以重写的(override)。finally则是Java保证重点代码一定要被执行的一种机制。我们可以使用try-finally或者try-catch-finally来进行类似关闭JDBC连接、保证unlock锁等动作。 finalize是基础类java.lang.Object的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize机制现在已经不推荐使用,并且在JDK 9开始被标记为deprecated。抛出异常throw和throwsthrow关键字用于显式抛出单个异常对象。当我们throw出现异常时,程序的流程从try块移动到catch块。示例:public class TestThrow { public static void divideByZero() { throw new ArithmeticException("Trying to divide by 0"); } public static void main(String[] args) throws ArithmeticException { //第一种,捕获接到的异常 /* try { divideByZero();//该方法会抛出一个异常对象,我们有两种处理方式 } catch (ArithmeticException e) { System.out.println(e.getMessage());// / by zero }*/ //第二种,把接到的异常继续抛到上一层,使用关键字throws divideByZero(); } }在上面的例子中,我们明确地抛出了ArithmeticException使用throw关键字。throws关键字用于在方法上抛出一个异常示例:Java throws 关键字import java.io.*; class Main { public static void findFile() throws IOException { File newFile = new File("test.txt"); FileInputStream stream = new FileInputStream(newFile); } public static void main(String[] args) { try { findFile(); } catch (IOException e) { System.out.println(e); } } }当我们运行这个程序时,如果文件test.txt不存在,则FileInputStream抛出一个继承自IOException类的FileNotFoundException。该findFile()方法指定抛出一个IOException。main()方法调用此方法并在抛出异常时处理该异常。如果方法不处理异常。好的编程习惯是不要在main方法里在把异常向上抛。try-with-resouces 语法糖该语法是jdk1.7开始实现的。此语法用来代替在finally语句块内关闭资源的语法形式,该语法糖只能使用到实现了AutoCloseable接口的具体类上。try后边圆括号内可以创建多个对象。语法结构:try(定义打开文件对象,当程序正常或者异常结束时,都会自动关闭打开的资源) { //正常业务逻辑代码 } catch(Exception e) { //发生异常后的处理逻辑 } public class Test01 { public static void main(String[] args) { InputStream is = null; try { is = new FileInputStream(""); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { //关闭流 if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } try(FileInputStream ism = new FileInputStream(""); FileOutputStream fous = new FileOutputStream("") ){ //读文件内容 //关闭文件流 System.out.println(ism); }catch (IOException e) { e.printStackTrace(); } //自定义实现AutoCloseable接口的类。 try(AutoClose ac = new AutoClose();) { System.out.println(ac); } catch (IOException e) { e.printStackTrace(); } //以上代码反编译后的代码如下 try{ AutoClose ac = new AutoClose(); System.out.println(ac); ac.close(); } catch (IOException e) { e.printStackTrace(); } } } class AutoClose implements AutoCloseable{ public void close() throws IOException {} }自定义异常1、自定义运行时异常2、自定义编译期异常应用场景:比如用户系统登录,出现用户名或者密码错误,可以使用自定义异常进行区分。今后学习MVC模型的时候,底层代码出现问题需要告知上层代码,也有很多使用自定义异常的场景。/** * 自定义异常 */ public class AgeException extends RuntimeException { public AgeException() { super();//调用父类无参构造方法 } public AgeException(String msg) { super(msg);//调用父类接收string类型参数的构造方法 } }使用自定义异常public static void main(String[] args) { try{ m1(130); } catch (AgeException e) { System.out.println(e.getMessage()); } } public static void m1(int age) { if (age > 125) { //超过正常年龄范围,抛出异常。 throw new AgeException("年龄不合法"); } else { System.out.println("正常年龄"); } }注意:子类重写父类的方法,不能抛出比父类更大的异常。异常实践尽量不要捕获 RuntimeException阿里巴巴 Java 开发手册上这样规定:尽量不要 catch RuntimeException,比如 NullPointerException、IndexOutOfBoundsException 等等,应该用预检查的方式来规避。public static void main(String[] args) { //判断数组是否为空和数组内容是否为空 if (args != null && args.length != 0) System.out.println(""); String s = null;//"" m(s); } public static void m(String s) { //判断字符串引用是否为空,字符串内容是否为空 if ( s != null && !s.isEmpty()) return; //System.out.println(s.length()); }尽量使用 try-with-resource 来关闭资源try(FileInputStream ism = new FileInputStream(""); FileOutputStream fous = new FileOutputStream("") ){ //读文件内容 //关闭文件流 System.out.println(ism); }catch (IOException e) { e.printStackTrace(); }不要捕获 Throwable不要省略异常信息的记录不要记录了异常又抛出了异常try{ int a = 9 / 0; } catch (Throwable t) { System.out.println("除零错误");//记录异常 throw new ArithmeticException();//抛出异常 }不要在 finally 块中使用 return如果在finally代码块中使用了return,try代码块中的执行结果就不会被正常返回。以下m方法希望返回10,但由于finally中又return,所以结果会返回100。public static int m(String s) { try{ return 10; } finally { return 100; } }阿里巴巴 Java 开发手册上这样规定:try 块中的 return 语句执行成功后,并不会马上返回,而是继续执行 finally 块中的语句,如果 finally 块中也存在 return 语句,那么 try 块中的 return 就将被覆盖。捕获具体的子类而不是捕获 Exception 类finally 块中永远不要抛出任何异常不要使用 printStackTrace() 语句或类似的方法应该尽可能的自定义异常的信息。只抛出和方法相关的异常作业1、Java 中所有的错误都继承自______类;在该类的子类中,______类表示严重的底层错误,对于这类错误一般处理的方式是______;______类表示例外、异常。2、异常类 java.rmi.AlreadyBoundException,从分类上说,该类属于______(已检查|运行时)异常,从处理方式上说,对这种异常______处理。 异常类 java.util.regex.PatternSyntaxException,从分类上说,该类属于______(已检查|运行时)异常,从处理方式上说,对这种异常______处理。十二、线程与并发线程共享代码、全局变量、堆区;独享栈和寄存器。1 概念1.1 进程和线程什么是进程?一个执行中的程序什么是线程?区别线程是进程中的一个实体,线程本身是不会独立存在的。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。操作系统在分配资源时是把资源分配给进程的,但是CPU资源比较特殊,它是被分配到线程的,因为真正要占用CPU运行的是线程,所以也说线程是CPU分配的基本单位。在Java中,当我们启动main函数时其实就启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,也称主线程。1.2 为什么需要多线程众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:CPU 增加了缓存,以均衡与内存的速度差异;(由此产生了可见性问题)操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;(由此产生原子性问题)编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。(由此产生有序性问题)1.3 多线程的利弊利:资源利用率更好程序设计在某些情况下更简单程序响应更快弊:使程序设计更复杂增加线程切换开销资源开销增大2 Java中创建线程【重点】2.1 创建线程的三种方式继承Thread类并重写run的方法创建,实现Runnable接口的run方法创建,实现Callable和Future接口创建线程(这种创建方式在学完线程池后才能讲解)2.2 通过继承Thread类/** * 通过继承创建一个线程 */ public class InheritCreateThread { public static void main(String[] args) { //创建自定义线程对象 MyThread01 thread01 = new MyThread01(); //调用start方法,启动线程 thread01.start();//启动线程必须要通过start()方法,而不能直接调用run方法 //thread01.run();//这叫做方法调用,而不是启动线程。 System.out.println(Thread.currentThread().getName() + " Main thread end"); } } /** * 通过继承Thread创建一个自定义线程,重写Thread类的run方法来实现线程的创建 */ class MyThread01 extends Thread { @Override public void run() { //打印线程id或线程名 System.out.println(Thread.currentThread().getName() + "子线程启动"); } }2.3 实现Runnable接口/** * 测试类 */ public class ImplementsRunnableCreateThread { public static void main(String[] args) { //启动实现Runnable接口的自定义线程 //第一:创建自定义线程对象 Mythread02 runnable = new Mythread02(); //第二:创建一个Thread对象,并且把自定义线程对象当做Thread的构造参数传入 Thread thread = new Thread(runnable); //第三:调用Thread的start方法启动线程 thread.start(); Thread thread1 = Thread.currentThread(); System.out.println(thread1.getName() + " ==> " + thread1.getId()); } } /** * 通过实现接口Runnable,并重写内部的run方法实现自定义线程 */ class Mythread02 implements Runnable { @Override public void run() { //打印当前线程的名字和id号 Thread thread = Thread.currentThread(); System.out.println(thread.getName() + " ==> " + thread.getId()); } }多种启动线程的写法/** * 测试类 */ public class ImplementsRunnableCreateThread { public static void main(String[] args) { //启动实现Runnable接口的自定义线程 //第一:创建自定义线程对象 Mythread02 runnable = new Mythread02(); //第二:创建一个Thread对象,并且把自定义线程对象当做Thread的构造参数传入 Thread thread = new Thread(runnable); //第三:调用Thread的start方法启动线程 thread.start(); Thread thread1 = Thread.currentThread(); System.out.println(thread1.getName() + " ==> " + thread1.getId()); //启动线程二 Thread thread2 = new Thread(new Mythread02()); thread2.start(); //启动线程三 new Thread(new Mythread02()).start(); //通过匿名内部类启动线程四 new Thread(new Runnable() { @Override public void run() { //打印当前线程的名字和id号 Thread thread = Thread.currentThread(); System.out.println(thread.getName() + " ==> " + thread.getId()); } }).start(); } } /** * 通过实现接口Runnable,并重写内部的run方法实现自定义线程 */ class Mythread02 implements Runnable { @Override public void run() { //打印当前线程的名字和id号 Thread thread = Thread.currentThread(); System.out.println(thread.getName() + " ==> " + thread.getId()); } }2.4 两者的区别使用继承Thread类的方式创建多线程优势:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。 劣势:线程类已经继承了Thread类,所以不能再继承其他父类。采用实现Runnable接口的方式创建多线程优势:线程类只是实现了Runnable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 劣势:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。启动线程需要注意的问题1、不要调用run方法2、一个线程只能调用一次start方法,如果调用多次会出异常java.lang.IllegalThreadStateException3 线程基础1、设置线程名称 (setName、getName、Thread.currentThread获取当前线程对象)2、设置线程的优先级 (setPriority、getPriority),不一定生效3、线程休眠 (Thread.sleep(毫秒数))4、线程礼让(Thread.yield()),但不一定成功5、线程加入(join()) 如果线程出现join操作,那么调用的线程将会阻塞。等到被调用的线程执行结束。sleepThread.sleep(),该方法在哪个线程内部使用,就会让哪个线程睡眠。public class TestThreadSleep { public static void main(String[] args) throws InterruptedException { //匿名内部类启动线程 new Thread(new Runnable() { @Override public void run() { //让线程睡眠3秒钟,注意:此处的时间是按照毫秒计算的。所以1秒等于1000毫秒 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //打印当前线程的名字和id号 Thread thread = Thread.currentThread(); System.out.println(thread.getName() + " ==> " + thread.getId()); } }).start(); Thread.sleep(5000);//让主线程睡眠5秒 Thread thread1 = Thread.currentThread(); System.out.println(thread1.getName() + " ==> " + thread1.getId()); } }yieldpublic class TestYield implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { if (i % 5 == 0) { System.out.println(Thread.currentThread() + " yield cpu....."); Thread.yield(); } } System.out.println(Thread.currentThread() + " is over"); } public static void main(String[] args) { new Thread(new TestYield()).start(); new Thread(new TestYield()).start(); new Thread(new TestYield()).start(); } }joinpublic class TestJoin { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }); thread.setName("sub thread one"); thread.start(); Thread.currentThread().setName("main thread"); try { thread.join();//阻塞等待被调线程任务执行结束才会继续往下执行。 } catch (InterruptedException e) { e.printStackTrace(); } //在主线程中运行的代码 for (int i = 100; i < 110; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }线程的状态初始状态 ------ 就绪状态 ------- 运行状态 ----- 限期等待 -----无限期等待 -------- 终止状态线程中断定义:一个线程在执行完任务后会自动结束,如果在运行过程中发生异常也会提前结束。interrupt()通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。public class TestInterrupt { private static class MyThread1 extends Thread { @Override public void run() { try { Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + " run"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { Thread thread = new MyThread1(); thread.start(); thread.interrupt(); System.out.println("Main Thread run"); } }interrupted()如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。public class TestInterrupted { private static class MyThread2 extends Thread { @Override public void run() { System.out.println(interrupted()); while (!interrupted()) { // .. } System.out.println(Thread.currentThread().getName() + " end"); } } public static void main(String[] args) throws InterruptedException { Thread thread2 = new MyThread2(); thread2.start(); thread2.interrupt(); System.out.println("Main Thread end"); } }线程分类daemon线程(守护线程)比如垃圾回收线程,jvm不会等待此类线程结束自己才退出。user线程(用户线程)只要有一个用户线程还没结束,正常情况下JVM就不会退出。注意:通常自定义的线程都属于用户线程。守护线程创建守护线程public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { public void run() { } }); //设置为守护线程 daemonThread.setDaemon(true); daemonThread.start(); }用户线程public static void main(String[] args) { Thread thread = new Thread(new Runnable() { public void run() { for(; ; ){} } }); //设置为守护线程 //thread.setDaemon(true); //启动子线程 thread.start(); System.out.print("main thread is over"); }4 并发编程【重点】在程序设计的角度,希望通过某些机制让计算机可以在一个时间段内,执行多个任务。线程安全问题共享资源所谓共享资源,就是同一份资源被多个线程所持有或者说多个线程访问。线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题因此在访问共享资源时需要要保证同步,保证被操作资源(比如变量)的原子性。private static int shared = 0; private static void incrShared(){ //自增操作会分为三步完成, //第一:把shared的数据读取到cpu寄存器中 //第二:把shared的值加1 //第三:把自增后的值写回到内存 shared++; } static class ChildThread extends Thread { @Override public void run() { incrShared(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new ChildThread(); Thread t2 = new ChildThread(); t1.start(); t2.start(); System.out.println(shared); }使用同步处理以上代码输出正确结果public class TestShare { private static int shared = 0; /** * 给自增操作加锁,保证自增操作的原子性。 */ private synchronized static void incrShared(){ shared++; } static class ChildThread extends Thread { @Override public void run() { incrShared(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new ChildThread(); Thread t2 = new ChildThread(); t1.start(); t2.start(); //在打印前保证子线程都执行完了自增操作 t1.join(); t2.join(); System.out.println(shared); } }线程同步synchronizedjava中使用关键字synchronized来实现线程的同步,该关键字对应两条jvm指令,monitor enter 和 monitor exit。0: aconst_null 1: astore_1 2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 5: aload_1 6: invokevirtual #3 // Method java/lang/String.length:()I 9: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 12: return同步方式方式一:同步代码块方式二:同步方法语法形式同步成员方法 public void m1() { synchronized(this) { //代码块 } } //以上同步方式等价于如下 public synchronized void m2() { } //同步静态代码块 public static void m1() { synchronized(this) { //代码块 } } //以上同步方式等价于如下 public synchronized static void m2() { }线程同步买票案例线程不安全循环中的代码并非原子操作,所以在线程执行的过程中,会有其他线程执行,会造成ticket < 0无效ticket-- 并非原子操作,其包含有三个步骤class TicketRunnable implements Runnable{ int ticket = 100; @Override public void run() { while(true) { if(ticket < 0) { break; } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } //获取线程的名称 System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票"); } } }同步代码块解决public class TestTicket { public static void main(String[] args) { new Thread(new TicketRunnable()).start(); new Thread(new TicketRunnable()).start(); //new Thread(new TicketRunnable()).start(); } } class TicketRunnable implements Runnable{ static int ticket = 100;//100张票 @Override public void run() { while(true) { //所有的线程都必须要获取到同一把锁之后才能运行卖票和减票两步操作。这就保证了两步操作的原子性。 //线程一旦获取到锁之后,会一直知道执行完synchronized代码块里的逻辑才会释放该锁。 synchronized(TicketRunnable.class) { if (ticket < 1) {//如果无票就跳出循环,线程执行结束 break; } //获取线程的名称 System.out.println(Thread.currentThread().getName() + "卖出了:" + ticket-- + "号票"); } } } }同步方法解决class TicketRunnable implements Runnable{ static int ticket = 50; final Object obj = new Object(); @Override public void run() { while(true) { sale(); } } //同步方法的锁对象是this,所以要保证当前对象的唯一 public synchronized(this) void sale() { if(ticket < 0) { System.exit(0); } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } //获取线程的名称 System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票"); } } public static void main(String[] args) { TicketRunnable ticketRunnable = new TicketRunnable(); for (int i = 0; i < 2; i++) new Thread(ticketRunnable).start(); }synchronized块是Java提供的一种原子性内置锁,Java中的每个对象都可以把它当作一个同步锁来使用,这些Java内置的使用者看不到的锁被称为内部锁,也叫作监视器锁。线程的执行代码在进入synchronized代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。另外,由于Java中的线程是与操作系统的原生线程一一对应的,所以当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作,而synchronized的使用就会导致上下文切换线程活跃问题线程死锁[理解]什么是死锁死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。死锁示例public class TestDeadLock { public static void main(String[] args) { Boy boy = new Boy(); Girl girl = new Girl(); boy.start(); girl.start(); } } class MyLock{ static Object left = new Object(); //左筷子 static Object right = new Object(); //右筷子 } class Boy extends Thread{ @Override public void run() { synchronized (MyLock.left) { //拥有左筷子 锁 System.out.println("boy获取到了左筷子,等待右筷子"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (MyLock.right) { System.out.println("boy可以吃饭"); } } } } class Girl extends Thread{ @Override public void run() { synchronized (MyLock.right) { System.out.println("Girl拥有右筷子,等待左筷子"); synchronized (MyLock.left) { System.out.println("Girl可以吃饭"); } } } }如何避免死锁?各线程之间访问资源保持顺序性, 如上的男孩女孩获取筷子的示例,我们只需要让男孩线程和女孩线程获取筷子的顺序保持一致,就可以避免死锁。public class TestDeadLock { public static void main(String[] args) { Boy boy = new Boy(); Girl girl = new Girl(); boy.start(); girl.start(); } } class MyLock{ static Object left = new Object(); //左筷子 static Object right = new Object(); //右筷子 } class Boy extends Thread{ @Override public void run() { synchronized (MyLock.left) { //拥有左筷子 锁 System.out.println("boy获取到了左筷子,等待右筷子"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (MyLock.right) { System.out.println("boy可以吃饭"); } } } } class Girl extends Thread{ @Override public void run() { synchronized (MyLock.left) { System.out.println("Girl拥有左筷子,等待右筷子"); synchronized (MyLock.right) { System.out.println("Girl可以吃饭"); } } } }效率问题创建线程:因为创建实现需要操作系统从用户态切换到内核态去申请内存空间,然后又要切换回用户态。线程上下文切换:如果线程过多,那么cpu大部分时间都用在切换线程上下文上,而不是用来执行任务,造成资源浪费。synchronized锁升级synchronized的使用通常有三种形式关于临界区。所谓“临界区”,指的是某一块代码区域,它同一时刻只能由一个线程执行。在上面的例子中,如果synchronized关键字在方法上,那临界区就是整个方法内部。而如果是使用synchronized代码块,那临界区就指的是代码块内部的区域。锁的状态总共有四种无锁 无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。也就是CAS(CAS是基于无锁机制实现的)ComparaAndSwap偏向锁 偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下,则一定会转化为轻量级锁或者重量级锁。轻量级锁 当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁优点减少传统的重量级锁使用操作系统互斥量产生的性能消耗靠多次CAS实现的,效率高重量级锁 Synchronized是通过对象内部的一个叫做 监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到内核态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为 “重量级锁”。Mutex(又叫 Lock),在多线程中,作为同步的基本类型,用来保证没有两个线程或进程同时在他们的关键区域自旋锁因为挂起线程以及恢复线程要转移到操作系统内核模式执行,这会给性能带来极大的影响。在JDK定义中,自旋锁默认的自旋次数为10次,用户可以使用参数-XX:PreBlockSpin来更改。自适应锁在JDK 1.6中引入了。获取锁的自旋次数不确定,根据之前的数据来确认自旋次数或者不自旋,让JVM变得更聪明。锁消除 锁消除是指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须再进行。变量是否逃逸,对于虚拟机来说是需要使用复杂的过程间分析才能确定的,但是程序员自己应该是很清楚的,怎么会在明知道不存在数据争用的情况下还要求同步呢?这个问题的答案是:有许多同步措施并不是程序员自己加入的,同步的代码在Java程序中出现的频繁程度也许超过了大部分读者的想象。我们来看看如下例子,这段非常简单的代码仅仅是输出三个字符串相加的结果,无论是源代码字面上,还是程序语义上都没有进行同步。public String concatString(String s1, String s2, String s3) { return s1 + s2 + s3; }String是一个不可变的类,对字符串的连接操作总是通过生成新的String对象来进行的,因此Javac编译器会对String连接做自动优化。在JDK 5之前,字符串加法会转化为StringBuffer对象的连续append()操作,在JDK 5及以后的版本中,会转化为StringBuilder对象的连续append()操作,以上代码会变成如下代码public String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer() sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }现在大家还认为这段代码没有涉及同步吗?每个StringBuffer.append()方法中都有一个同步块,锁 就是sb对象 。 虚拟机观察变量sb , 经过逃逸分析后会发现它的动态作用域被限制在concatString( ) 方 法内部 。 也就是sb的 所有引用都永远不会逃逸到 concatString( )方法之外 , 其他线程无法访问到它 , 所以这里虽然有锁,但是可以被安全地消除掉。在解释执行时这里仍然会加锁,但在经过服务端编译器的即时编译之后,这段代码就会忽略所有的同步措施而直接执行。锁粗化原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小——只在共享数据 的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变少,即使存在锁竞争,等 待锁的线程也能尽可能快地拿到锁。 大多数情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体之中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如上代码连续的append()方法就属于这类情况。如果虚拟机探测到有这样一串零碎的操作 都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部,扩展到第一个append()操作之前直至最后一个append()操作之后,这样只需要加锁一次就可 以了。使用Synchronized有哪些要注意的?锁对象不能为空,因为锁的信息都保存在对象头里作用域不宜过大,影响程序执行的效率。避免死锁(多个线程获取锁的顺保持一致)在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键字,因为代码量少,避免出错synchronized是公平锁吗?synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象5 Lock锁在jdk1.5之前实现线程同步只能使用synchronized和volatile两个关键字。因为synchronized是关键字,所以在使用上受限,使用synchronized实现同步,那么在等待过程中的其他线程会一直等待下去,没有机制打断他们,这很容易产生死锁。另外,synchronized是jvm支持,相对来说锁比较重。volatile关键字只能保证可见性和有序顺(禁止指令重排序从而保证顺序性),但是它不能保证原子型。保证原子性需要使用synchronized。ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被放入一个阻塞队列里面。JDK1.5加入,与synchronized比较,显示定义,结构更灵活。提供更多实用性方法,功能更强大、性能更优越。Lock锁ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能。语法格式Lock lock = new ReentrantLock; lock.lock;//加锁 try { //业务逻辑 } finally { lock.unlock();//释放锁 } class TicketRunnable implements Runnable { static int ticket = 10000;//100张票 //这种方法创建锁不能保证多个线程拿到的是同一把锁,所以不能保证数据不会被打乱 //要保证多个线程获取的是同一把锁,就需要使用静态初始化方式 //Lock lock = new ReentrantLock(); static Lock lock = new ReentrantLock();//静态初始化一把锁。 @Override public void run() { //加锁 while (true) { System.out.println("lock" + lock); lock.lock(); try { //所有的线程都必须要获取到同一把锁之后才能运行卖票和减票两步操作。这就保证了两步操作的原子性。 //线程一旦获取到锁之后,会一直知道执行完synchronized代码块里的逻辑才会释放该锁。 if (ticket < 1) {//如果无票就跳出循环,线程执行结束 break; } //获取线程的名称 System.out.println(Thread.currentThread().getName() + "卖出了:" + ticket-- + "号票"); } finally { lock.unlock();//程序无论正常还是异常结束,都要包装锁被释放 } } System.out.println("release lock"); } }随堂练习:用 ReentrantLock来实现一个简单的线程安全的 list读写锁ReentrantReadWriteLock:一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。支持多次分配读锁,使多个读操作可以并发执行。互斥规则:写-写:互斥,阻塞。读-写:互斥,读阻塞写、写阻塞读。读-读:不互斥、不阻塞。在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。public class TestReadWriteLock { static User user = new User(); public static void main(String[] args) { long start = System.currentTimeMillis(); //8个线程读 for (int i = 0; i < 8; i++) { new Thread(new ReadThread()).start(); } //2个线程写 for (int i = 0; i < 2; i++) { new Thread(new WriteThread()).start(); } } } class ReadThread implements Runnable{ @Override public void run() { System.out.println(TestReadWriteLock.user.getName()); } } class WriteThread implements Runnable{ @Override public void run() { TestReadWriteLock.user.setName("cxk"); } } class User{ String name; //创建读写锁对象 static ReadWriteLock rwl = new ReentrantReadWriteLock(); //读锁 static Lock readLock = rwl.readLock(); //写锁 static Lock writeLock = rwl.writeLock(); public void setName(String name) { try { writeLock.lock(); System.out.println(Thread.currentThread().getName() + " 正在写数据"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.name = name; } finally { writeLock.unlock(); } } public String getName() { try { readLock.lock(); System.out.println(Thread.currentThread().getName() + " 正在读数据"); return name; }finally { readLock.unlock(); } } }重入锁重入锁:一个线程获取到一个对象的锁之后还可以继续再次获得该锁,对象头有一个专门记录获得锁次数的标记。重入锁也叫作递归锁,指的是同一个线程外层方法获取到一把锁后,内层方法同样具有这把锁的控制权限synchronized和Lock锁都可以实现锁的重入public class TestReentrantLock { public static void main(String[] args) { //启动线程 new Thread(new MyRunnable()).start(); } } class MyRunnable implements Runnable{ @Override public void run() { a(); } //当前锁对象为this public synchronized void a() { System.out.println("a"); b(); } //当前锁对象为this public synchronized void b() { System.out.println("b"); c(); } public synchronized void c() { System.out.println("c"); } }公平锁根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁,公平锁表示线程获取锁 的顺序是按照线程请求锁的时间早晚来决定的,也就是最早请求锁的线程将最早获取到锁。 而非公平锁则在运行时闯入,也就是先来不一定先得 。ReentrantLock 提供了公平和非公平锁的实现 。·公平锁: ReentrantLockpairLock =new ReentrantLock(true)。 ·非公平锁: ReentrantLockpairLock=new ReentrantLock(false)。 如果构造函数不传递参数,则默认是非公平锁 。例如,假设线程 A 已经持有了锁,这时候线程 B 请求该锁其将会被挂起。 当线程 A 释放锁后,假如 当前有线程 C 也需要获取该锁,如果采用非公平锁方式,则根据线程调度 策略, 线程 B 和线程 C 两者之一可能获取锁,这时候不需要任何其他干涉,而如果使用 公平锁则需要把C挂起,让B获取当前锁 。在没有公平性需求的前提下尽量使用非公平锁,因为公平锁会带来性能开销 。synchronized的缺陷效率低:锁的释放情况少,只有代码执行完毕或者异常结束会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活无法知`道是否成功获得锁`,相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败,Lock解决相应问题Lock类有以下4个方法:lock(): 加锁unlock(): 解锁tryLock(): 尝试获取锁,返回一个boolean值tryLock(long,TimeUtil): 尝试获取锁,可以设置超时public class ReentrantLockList { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(3); MyList myList = new MyList(); service.submit(new Runnable() { @Override public void run() { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 10; i++) { myList.add(i); } } }); service.submit(new Runnable() { @Override public void run() { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 10; i < 20; i++) { myList.add(i); } } }); service.submit(new Runnable() { @Override public void run() { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 20; i < 30; i++) { myList.add(i); } } }); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(myList.list); service.shutdown(); } } class MyList { List<Integer> list = new ArrayList<>(); //创建锁 Lock lock = new ReentrantLock(); public void add(int val) { if (lock.tryLock()) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } list.add(val); lock.unlock(); } else { System.out.println(Thread.currentThread().getName() + "获取锁失败"); } } }synchronized锁只与一个条件(是否获取锁)相关联,多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了Synchronized和ReentrantLock除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。7 线程通信[了解]线程通信:线程之间可以通过共享内存的方式通信。若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。wait() 调用该对象的某个线程会阻塞挂起notify() 调用该对象的notify方法会唤醒在该对象上调用wait等待中的线程。具体是哪个线程,是随机的。notifyAll()/** * 生产者消费者 */ public class ProducerAndConsumer { static AppStore store = new AppStore(); public static void main(String[] args) { //创建一个商店对象 ; //生产者 for (int p = 0; p < 5; p++) new Thread(new Runnable() { @Override public void run() { //生产商品 for (int i = 0; i < 20; i++) { store.production(i); } } }).start(); //消费者 for (int q = 0; q < 5; q++) new Thread(new Runnable() { @Override public void run() { //消费商品 for (int j = 0; j < 20; j++) { store.consumption(); //打印 } } }).start(); } } //存放商品的区域 class AppStore<T> { //定义一个放接收到的商品的仓库,仓库必须要保证多个对象之间只能有一个。 static final Queue queue = new ConcurrentLinkedQueue(); //接收生产者生产的商品 public synchronized void production(T goods) { //当仓库满了时不允许在生产,规定仓库只能存放10个商品 while (queue.size() > 10) { System.out.println("仓库已满,需要等待"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //把商品放入仓库 queue.add(goods); System.out.println("把商品 " + goods + " 存到库存中"); //仓库有了商品后需要通知消费者开始消费 this.notifyAll(); } //把商品提供给消费者 public synchronized void consumption() { //当仓库没货的时候需要等待 while (this.queue.size() == 0) { try { System.out.println("仓库没货需要等待"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("消费了商品 : " + queue.poll()); //仓库有了多余空间就通知生产者线程开始生产 this.notifyAll(); } }Sleep和wait的区别:sleep会抱着锁睡,wait进入等待时会释放自己持有的锁。8 线程的生命周期[重点]线程的5种状态:创建状态---->就绪状态---->运行状态---->阻塞\等待状态---->终止状态9 线程池概念如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。线程池实现逻辑假设我们线程池里只有5个线程可用,但这时候来了50个请求,我们该怎么处理?3.3 线程池中常见的类常用的线程池接口和类(所在包java.util.concurrent)。Executor:线程池的顶级接口。ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。Executors工厂类:通过此类可以获得一个线程池。方法名描述newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。newCachedThreadPool()获得动态数量的线程池,如不够则创建新的。/** * 使用线程池技术来执行自定义的线程任务 */ public class TestThreadPool01 { //第二步:定义一个测试方法 public static void main(String[] args) { //第三步:使用线程池工厂获取一个线程池 ExecutorService executor = Executors.newFixedThreadPool(2); //第四步:创建自定义线程的一个对象 MyTask01 task01 = new MyTask01(); //第五步:把自定义线程对象的引用通过线程池的submit方法提交给线程池执行 executor.submit(task01); //打印主线程名 System.out.println(Thread.currentThread().getName()); //第六步:关闭线程池 executor.shutdown(); } } /** * 第一步:自定义一个线程类,基于实现runnable接口的形式 */ class MyTask01 implements Runnable { @Override public void run() { System.out.println("这是通过线程池启动的一个任务" + Thread.currentThread().getName()); } } public class TestExcutor { public static void main(String[] args) { //创建线程池对象 ExecutorService service = Executors.newFixedThreadPool(3); //往线程池内提交任务(线程) service.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); //通过继承Thread创建的线程提交给线程池。 service.submit(new TT()); System.out.println(Thread.currentThread().getName()); //注意:一定要关闭线程池 service.shutdown(); } } class TT extends Thread { @Override public void run() { System.out.println("extends thread"); } } /** * 使用无边界线程池执行线程任务 */ public class TestNewCachedThreadPool01 { public static void main(String[] args) { //通过线程池工厂类或一个线程池 ExecutorService service = Executors.newCachedThreadPool(); //往线程池里添加任务,任务通过匿名内部类的形式提供 service.submit(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } }); service.submit(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } }); //使用完线程池一定要记得关闭 service.shutdown(); } }线程池内部最重要一个构造函数各参数的含义public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) corePoolSize : 核心线程数 maximumPoolSize:最大线程数 keepAliveTime:线程空闲时间 TimeUnit: 时间单位 workQueue:任务队列,线程忙不过来时任务就放到该队列内 threadFactory:线程工厂 handler:拒绝策略:有四种Callable接口JDK1.5加入,与Runnable接口类似,实现之后代表一个线程任务。Callable具有泛型返回值、可以声明异常。public interface Callable< V >{ public V call() throws Exception; }public class TestCallable1 { public static void main(String[] args) { //1、创建线程池对象 ExecutorService es = Executors.newFixedThreadPool(2); //2、通过线程池提交线程并执行任务 es.submit(new MyCallable()); //3、关闭线程池 es.shutdown(); } } class MyCallable implements Callable{ @Override public Object call() throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"--->"+i); } return null; } }Future接口Future接口表示将要执行完任务的结果。get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。public class TestCallable2 { /** * Runnable接口和Callable接口的区别? * 1、这两个接口都可以当做线程任务提交并执行 * 2、Callable接口执行完线程任务之后有返回值,而Runnable接口没有返回值 * 3、Callable接口中的call方法已经抛出了异常,而Runnable接口不能抛出编译异常 * Future接口: * 用于接口Callable线程任务的返回值。 * get()方法当线程任务执行完成之后才能获取返回值,这个方法是一个阻塞式的方法 * * 随堂案例: * 使用两个线程,并发计算1-100的和, 一个线程计算1~50,另一个线程计算51~100, 最终汇总结果 */ public static void main(String[] args) throws InterruptedException, ExecutionException { //1、创建线程池对象 ExecutorService es = Executors.newFixedThreadPool(2); //2、通过线程池提交线程并执行任务 Future<String> future = es.submit(new MyCallable1()); //获取线程任务的返回值 System.out.println(future.get()); System.out.println("哈哈哈哈哈哈"); //3、关闭线程池 es.shutdown(); } } class MyCallable1 implements Callable<String>{ @Override public String call() throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"--->"+i); } return "这是Callable线程任务的返回值"; } }案例:计算1-1000结果,使用四个线程分别计算?即:第一个线程计算1-250 第二个 251~500 ...public class Test01 { public static void main(String[] args) throws InterruptedException, ExecutionException { //1、创建线程池 ExecutorService es = Executors.newFixedThreadPool(2); //2、提交两个线程任务 Future<Integer> f1 = es.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 51; i++) { sum = sum + i; } return sum; } }); Future<Integer> f2 = es.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 51; i < 101; i++) { sum += i; } return sum; } }); System.out.println(f1.get() + f2.get()); //3、关闭线程池 es.shutdown(); } }10 线程安全的集合集合结构通过Collections获取线程安全集合Collections工具类中提供了多个可以获得线程安全集合的方法。方法名public static Collection synchronizedCollection(Collection c)public static List synchronizedList(List list)public static Set synchronizedSet(Set s)public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)public static SortedSet synchronizedSortedSet(SortedSet s)public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)CopyOnWriteArrayList线程安全的ArrayList,加强版读写分离。写有锁,读无锁,读写之间不阻塞,优于读写锁。写写互斥写入时,先copy一个容器副本、再添加新元素,最后替换引用。使用方式与ArrayList无异。CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();CopyOnWriteArraySet线程安全的Set,底层使用CopyOnWriteArrayList实现。唯一不同在于,使用addIfAbsent()添加元素,会遍历数组。如存在元素,则不添加(扔掉副本)。CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>();ConcurrentHashMapJDK1.7实现初始容量默认为16段(Segment),使用分段锁设计。不对整个Map加锁,而是为每个Segment加锁。当多个对象存入同一个Segment时,才需要互斥。最理想状态为16个对象分别存入16个Segment,并行数量16。使用方式与HashMap无异。JDK1.8实现内部使用CAS交换算法+synchronized实现多线程并发安全11 CASCAS 方式为乐观锁,synchronized 为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。JDK 里面的 Unsafe提供了一个方法boolean compareAndSwapLong(Object obj,long i,long expect, long update)其中compareAndSwap的意思是比较并交换。CAS有四个操作数, 分别为:对象引用 、 对象中的变量 、 变量预期值,更新后的值 。 其操作含义是 , 如果 对象 obj 中 valueOffset的变量值为 expect,则使用新的值 update替换 旧的值 expect。 这是处理器提供的一个原子性指令。CAS只能保证一个共享变量的原子操作我们以一个简单的例子来解释这个过程:如果有一个多个线程共享的变量i原本等于5,我现在在线程A中,想把它设置为新的值6;我们使用CAS来做这个事情;首先我们用i去与5对比,发现它等于5,说明没有被其它线程改过,那我就把它设置为新的值6,此次CAS成功,i的值被设置成了6;如果不等于5,说明i被其它线程改过了(比如现在i的值为2),那么我就什么也不做,此次CAS失败,i的值仍然为2。那有没有可能我在判断了i为5之后,正准备更新它的新值的时候,被其它线程更改了i的值呢?不会的。因为CAS是一种原子操作,它是一种系统原语,是一条CPU的原子指令,从CPU层面保证它的原子性12 ABA问题关于CAS操作有个经典的ABA问题,因为CAS需要在操作值的时候,检查值有没有发生变化,比如没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时则会发现它的值没有发生变化,但是实际上却变化了。ABA 问题的产生是因为变量 的状态值产生 了环形转换,就是变量的值可 以从 A 到 B,然后再从 B 到 A。如果变量的值只能朝着一个方向转换 ,比如 A 到 B, B 到 C, 不构成环形,就不会存在 问题。 JDK 中AtomicStampedReference 类给每个变量的状态值都配备了一个时间戳, 从而避免了ABA问题的产生。十三、I/O流文件(File)File类介绍概念:表示操作系统磁盘上的文件或者是文件夹路径:相对路径 (相对于当前工程的根路径)绝对路径 (在磁盘上的完整路径)常见方法方法名描述createNewFile()创建一个新文件。mkdir()创建一个新目录。如果父目录不存在,则无法创建mkdirs()创建一个新目录。如果父目录不存在,则一起创建delete()删除文件或空目录。exists()判断文件或目录是否存在length()获取文件(夹)的大小(字节)getAbsolutePath()获取文件的绝对路径。getAbsoluteFile()获取文件(夹)的绝对路径:(返回File)getName()获取当前file对象的文件名或者是文件夹名getParent()获取当前file对象的父目录(返回String)isDirectory()是否是目录。isFile()是否是文件。getPath()获取文件(夹)的相对路径:(返回String)listFiles()列出目录中的所有内容。//创建File对象 File file = new File("d:\\a.txt"); if(!file.exists()) { //创建文件 file.createNewFile(); } System.out.println("判断文件或者是文件夹是否存在"+file.exists()); System.out.println("判断是否是文件:"+file.isFile()); System.out.println("判断是否是文件夹:"+file.isDirectory()); System.out.println("获取文件或者文件夹的名字:"+file.getName()); System.out.println("获取文件大小(字节):"+file.length()); System.out.println("获取文件的相对路径:"+file.getPath()); System.out.println("获取文件的绝对路径(String):"+file.getAbsolutePath()); System.out.println("获取文件的绝对路径(File):"+file.getAbsoluteFile()); System.out.println("获取文件的父目录(String):"+file.getParent()); System.out.println("获取文件的父目录(File):"+file.getParentFile()); System.out.println("获取文件所在位置磁盘的总空间:"+file.getTotalSpace()); System.out.println("获取文件所在位置磁盘的可用空间:"+file.getFreeSpace()); System.out.println("获取文件的最后修改时间(毫秒)"+file.lastModified()); System.out.println("判断文件是否可读"+file.canRead()); System.out.println("判断文件是否可写"+file.canWrite()); System.out.println("判断文件是否可执行"+file.canExecute()); System.out.println("判断文件是否是隐藏文件"+file.isHidden()); File file = new File("./");//斜线(/)表示根目录, ./表示当前目录 System.out.println(file.getAbsolutePath()); public static void main(String[] args) throws IOException { //创建一个File文件对象 File file1 = new File("/Users/fcp/aaaa.txt");//构造参数填写文件的绝对路径 //File file = new File("D:\\Users\\fcp\\test.txt"); windows //使用文件对象的引用调用创建文件方法来创建文件 //System.out.println(file1.createNewFile()); //创建File对象 File file2 = new File("/Users/fcp/jay111/jay"); //File file2 = new File("c:\\Users\\fcp\\jay"); windows //创建目录 //System.out.println(file2.mkdir()); //System.out.println(file2.mkdirs()); File file3 = new File("/Users/fcp/aaaa.txt"); //System.out.println(file3.delete()); File file4 = new File("/Users/fcp/test"); //删除有文件的目录 //System.out.println(file4.delete()); File file5 = new File("/Users/fcp/"); //判断目录是否存在 System.out.println(file5.exists()); File file6 = new File("/Users/fcp/test.txt"); //判断文件大小 System.out.println(file6.length()); File file7 = new File("io/test01.java"); System.out.println(file7.getAbsolutePath()); System.out.println(file7.getAbsoluteFile()); System.out.println(file6.getName()); System.out.println(file6.getParent()); //获取文件相对路径 System.out.println("相对路径:" + file7.getPath()); //d:\a\b\c\e.txt //d:\a\b\c\test.java //new File("e.txt"); m(file6); } public static void m(File file) { //此处不知道file是文件还是目录 //首先判断file是否存在 if (file.exists()) { //如果存在,再次判断是文件还是目录 if (file.isDirectory()) System.out.println("是一个目录"); else System.out.println("是一个文件"); } }FilenameFilter接口FilenameFilter:文件过滤器接口boolean accept(File pathname)。当调用File类中的listFiles()方法时,支持传入FilenameFilter接口实现类,对获取文件进行过滤,只有满足条件的文件的才可出现在listFiles()的返回值中。案例:使用文件过滤器获取所有java文件File[] files = f.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".java"); } }); for (File file2 : files) { System.out.println(file2.getName()); }文件搜索案例遍历出某文件夹中的所有的java文件public class FileDemo02 { public static void main(String[] args) { getJavaFile(new File("d:\\zz")); } //思考:删除一个文件夹 //思考:获取文件夹下所有的java文件 public static void getJavaFile(File file) { //遍历当前文件夹下所有的文件或者是文件 File[] files = file.listFiles(); //f有两种情况,文件或者是文件夹 for (File f : files) { if(f.isFile()) { if(f.getName().endsWith(".java")) { System.out.println(f.getName()); } }else { //递归调用getJavaFile getJavaFile(f); } } } }I/O流概念IO 即 Input/Output,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如磁盘,网络等)的过程称为输出。数据传输过程类似于水流,因此称为 IO 流。从传输方向上:IO 流可分为输入流和输出流,从数据处理方式上:可分为字节流和字符流。java中所有的流都继承自四个基类:InputStream/Reader: 字节输入流 / 字符输入流。OutputStream/Writer: 字节输出流 / 字符输出流。四个基类有几十个子类。下图列出部分子类。字节流InputStream字节输入流,因为InputStream是一个抽象类,不能具体实例化,所以需要创建该类的一个子类对象,才能实现文件的读取。常用子类:FileInputStream此类按字节从指定路径文件读取数据,每次读一个字节,所以读取速度相对较慢,如果想让读取速度加快,我们可以使用缓冲流:BufferedInputStream/** * 使用文件字节输入流读取数据 */ public class TestFileInputStream { public static void main(String[] args) { //创建文件字节流对象 File file = new File("outputstream.txt"); //InputStream is = new FileInputStream(file); try(InputStream is = new FileInputStream(file);) { //使用对象引用调用读取数据的方法 //第一种读取文件数据的方式 int b = 0; while ((b = is.read()) != -1) {//如果读取的数据不等于-1表示文件内容未读完 //System.out.print(b);//读取到的整数对应字符编码表里的数字 System.out.print((char) b);//通过强制类型转换把整数转成字符 } } catch (IOException e) { e.printStackTrace(); } System.out.println(); try(InputStream is = new FileInputStream(file);) { //第二种读取文件的方式,创建一个数组暂存读取到的字节数据 byte[] bytes = new byte[32]; //int count = is.read(bytes);//count表示读取了多少个字节 while (is.read(bytes) != -1) { System.out.println(new String(bytes)); } } catch (IOException e) { e.printStackTrace(); } } } public class TestBufferedInputStream { public static void main(String[] args) { File file = new File("/Users/fcp/Downloads/常用类2.mp4"); //创建字节缓冲流对象 //try(InputStream is = new FileInputStream(file)) { try(InputStream is = new BufferedInputStream(new FileInputStream(file))) { //开始时间 long start = System.currentTimeMillis(); //使用字节流读一个视频文件 int b = 0; //定义一个1MB的字节数组 //byte[] bytes = new byte[1024]; int count = 0; while ((b = is.read()) != -1) { count++;//记录循环读取了多少次 } System.out.println("count : " + count); //读取文件所花时间 System.out.println(System.currentTimeMillis()-start); } catch (IOException e) { e.printStackTrace(); } } }OutputStream字节输出流,常用两个类分别是:FileOutputStream和BufferedOutputStream。后者是前者的缓冲流。/** * 字节输出流 */ public class TestOutput { public static void main(String[] args) { //文件字节输出流 //fileOutput(); //inputOutput(); bufferedOutput(); } /** * 文件字节输出流:输出具体内容到指定文件,按照一个字节一个字节的方式输出。 */ public static void fileOutput() { //创建文件字节输出流对象 //当字符串指定的文件不存在时会自动帮我们创建出来该文件,但如果是目录就不会自动创建 try(FileOutputStream fos = new FileOutputStream("fileoutput.txt", false);) { fos.write(97);//存储到文件中的是该整数对应的字符 fos.write("你说把爱渐渐放下会走更远\n".getBytes());//按照字节数组输出到指定文件 fos.write("你说把爱渐渐放下会走更远\n".getBytes(), 3, 6);//要输出字节数组中的哪部分内容,通过数组下标指定 } catch (IOException e) { e.printStackTrace(); } } /** * 通过InputStream读取内容,然后通过OutputStream写到指定文件内 */ public static void inputOutput() { try(FileInputStream fis = new FileInputStream("/Users/fcp/Downloads/雨下一整晚.mp3"); FileOutputStream fos = new FileOutputStream("雨下一整晚.mp3")) { //读文件内容 /*int b = 0;//一个字节一个字节的读 while ((b = fis.read()) != -1) { //写入到指定文件 fos.write(b); }*/ //上边按照字节读太慢,我们只用数组作为缓冲区来读写文件 byte[] bytes = new byte[1024 * 10]; while (fis.read(bytes) != -1) { fos.write(bytes); } } catch (IOException e) { e.printStackTrace(); } } /** * 字节输出缓冲流 */ public static void bufferedOutput() { try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("/Users/fcp/Downloads/常用类2.mp4")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("常用类2.mp4"));) { int b = 0; while ( (b = bis.read()) != -1) { bos.write(b); } }catch (IOException e) { e.printStackTrace(); } } }字符流Reader主要用于读取文本文件。常用类FileReader, BufferedReader/** * 字符输入流 */ public class TestReader { public static void main(String[] args) { //创建字符输入流对象 try(Reader reader = new FileReader("javase/src/io/TestSerializable.java");) { //循环读取文件内容 /*int b = 0; while ((b = reader.read()) != -1) { System.out.print((char) b); }*/ //通过数组方式读 char[] buf = new char[512]; while (reader.read(buf) != -1) System.out.println(new String(buf)); } catch (IOException e) { e.printStackTrace(); } } }Writer主要用于写字符到指定文件内,常用类FileWriter, BufferedWriterpublic class TestWriter { public static void main(String[] args) { //创建字符输出流对象 try(Writer writer = new FileWriter("filewriter.txt")) { writer.write("明明就"); } catch (IOException e) { e.printStackTrace(); } } }BufferedReader和BufferedWriter代码示例public class TestBuffereReaderAndBufferedWriter { public static void main(String[] args) throws IOException { //使用BufferedReader和BufferedWriter拷贝文本文件 BufferedReader br = new BufferedReader(new FileReader("/Users/fcp/Downloads/JavaSE.md")); BufferedWriter bw = new BufferedWriter(new FileWriter("JavaSE.txt")); //读文件 /*String val = null; while ((val = br.readLine()) != null) { bw.write(val); }*/ /*int b = 0; while ((b = br.read()) != -1) bw.write(b);*/ char[] chars = new char[1024]; while (br.read(chars) != -1) bw.write(chars); bw.flush(); } }以下代码示例演示了使用字符流拷贝mp3文件,最终文件被损坏,无法播放/** * 使用字符流读取媒体文件看是否会损坏文件 */ public class TestReaderWriter { public static void main(String[] args) { copyMp3(); } public static void copyMp3() { try(FileReader fr = new FileReader("/Users/fcp/Downloads/雨下一整晚.mp3"); FileWriter fw = new FileWriter("雨下一整晚.mp3")) { int b = 0; while ((b = fr.read()) != -1) { fw.write(b); } }catch (IOException e) { e.printStackTrace(); } } }字节流和字符流的区别字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。操作对象划分文件流FileInputStreamint b; try(FileInputStream fis1 = new FileInputStream("/Users/fcp/test.txt");) { while ((b = fis1.read()) != -1) { System.out.print((char) b); } } catch (IOException e) { e.printStackTrace(); }FileOutputStreamtry(FileOutputStream fos = new FileOutputStream("/Users/fcp/test.txt");) { fos.write("海鸥不再眷恋大海 可以飞更远。".getBytes()); } catch (IOException e) { e.printStackTrace(); }FileReaderint b = 0; try(FileReader fileReader = new FileReader("/Users/fcp/test.txt");) { while ((b = fileReader.read())!=-1) { // 自动提升类型提升为 int 类型,所以用 char 强转 System.out.print((char)b); } } catch (IOException e) { e.printStackTrace(); }FileWritertry(FileWriter fileWriter = new FileWriter("/Users/fcp/test.txt");) { fileWriter.write("远方传来风笛 我只在意有你的消息。".toCharArray()); } catch (IOException e) { e.printStackTrace(); }缓冲流CPU 很快,它比内存快 100 倍,比磁盘快百万倍。程序和内存交互会很快,和硬盘交互相对较慢,这就会导致性能问题。为了减少程序和硬盘的交互,提升程序的效率,就引入了缓冲流,也就是类名前缀带有 Buffer 的那些,如: BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。数组流(了解)try(InputStream is =new BufferedInputStream(new ByteArrayInputStream( "但故事的最后你好像还是说了拜拜".getBytes(StandardCharsets.UTF_8)));) { byte[] flush =new byte[1024]; int len =0; while(-1!=(len=is.read(flush))){ System.out.println(new String(flush,0,len)); } } catch (IOException e) { e.printStackTrace(); } try(ByteArrayOutputStream bos =new ByteArrayOutputStream();) { byte[] info ="我们的开始,是很长的电影。".getBytes(); bos.write(info, 0, info.length); //获取数据 byte[] dest =bos.toByteArray(); System.out.println(new String(dest)); } catch (IOException e) { e.printStackTrace(); }管道流(了解)public static void main(String[] args) throws IOException { final PipedOutputStream pipedOutputStream = new PipedOutputStream(); final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "写数据"); pipedOutputStream.write("烟花易冷 人事易分".getBytes(StandardCharsets.UTF_8)); pipedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { byte[] flush =new byte[1024]; int len =0; while(-1!=(len=pipedInputStream.read(flush))){ System.out.println(Thread.currentThread().getName() + "读数据"); System.out.println(new String(flush,0,len)); } pipedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }); thread1.start(); thread2.start(); }数据流(了解)基本数据类型输入输出流是一个字节流,该流不仅可以读写字节和字符,还可以读写基本数据类型。DataInputStream 提供了一系列可以读基本数据类型的方法public static void main(String[] args) throws IOException { DataOutputStream das = new DataOutputStream(new FileOutputStream("/Users/fcp/test.txt")); das.writeByte(110); das.writeShort(120); das.writeInt(119); das.writeLong(12306L); das.writeFloat(3.14F); das.writeDouble(1.927); das.writeBoolean(true); das.writeChar('A'); DataInputStream dis = new DataInputStream(new FileInputStream("/Users/fcp/test.txt")); byte b = dis.readByte() ; short s = dis.readShort() ; int i = dis.readInt(); long l = dis.readLong() ; float f = dis.readFloat() ; double d = dis.readDouble() ; boolean bb = dis.readBoolean() ; char ch = dis.readChar() ; System.out.println(b + " " + s + " " + i + " " + l + " " + f + " " + d + " " + bb + " " + ch); }打印流(了解)public static void main(String[] args) throws IOException { StringWriter buffer = new StringWriter(); try (PrintWriter pw = new PrintWriter(buffer)) { pw.println("青石板街 回眸一笑你婉约"); } System.out.println(buffer.toString()); }转换流(了解)序列化和反序列对象流:把java创建的对象以字节的形式保存到磁盘上,但是需要被保存的对象都必须实现Serializable。public class TestSerializable { public static void main(String[] args) { serializable(); unSerializable(); } public static void serializable() { Employee e = new Employee(); e.name = "zhangsan"; e.address = "beiqinglu"; e.age = 20; try (FileOutputStream fileOut = new FileOutputStream("aa.txt"); ObjectOutputStream out = new ObjectOutputStream(fileOut);) { // 创建序列化流对象 // 写出对象 out.writeObject(e); // 释放资源 System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。 } catch (IOException i) { i.printStackTrace(); } } public static void unSerializable() { Employee e = null; try(FileInputStream fileIn = new FileInputStream("aa.txt"); ObjectInputStream in = new ObjectInputStream(fileIn);) { // 读取一个对象 e = (Employee) in.readObject(); // 释放资源 }catch(IOException i) { // 捕获其他异常 i.printStackTrace(); return; }catch(ClassNotFoundException c) { // 捕获类找不到异常 System.out.println("Employee class not found"); c.printStackTrace(); return; } // 无异常,直接打印输出 System.out.println("Name: " + e.name); // zhangsan System.out.println("Address: " + e.address); // beiqinglu System.out.println("age: " + e.age); // 0 } } class Employee implements java.io.Serializable { private static final long serialVersionUID = 1L; public String name; public transient String address; public transient int age; public void addressCheck() { System.out.println("Address check : " + name + " -- " + address); } }Properties的操作创建一个文件:database.properties,然后填入以下内容key1=value1 key2=value2 key3=value3 hello=world mysql=localhost:3306/test baidu=https://www.baidu.com在代码里读取database.properties的内容public class TestProperties { public static void main(String[] args) throws IOException { Properties hashmap = new Properties(); hashmap.load(new FileInputStream("database.properties")); System.out.println(hashmap.get("key1")); for (Map.Entry<Object, Object> entry : hashmap.entrySet()) { System.out.println(entry.getKey()); System.out.println(entry.getValue()); } } }作业:歌词内容如下Every night in my dreamsI see you I feel youThat is how I know you go onFar across the distanceAnd spaces between usYou have come to show you go onNear farWherever you areI believeThat the heart does go onOnce more you open the doorAnd you're here in my heartAnd my heart will go on and onLove can touch us one timeAnd last for a lifetimeAnd never let go till we're goneLove was when I loved youOne true time I hold toIn my life well always go onNear farWherever you areI believeThat the heart does go onOnce more you open the doorAnd you're here in my heartAnd my heart will go on and onyou're hereThere's nothing I fearAnd I knowThat my heart will go onWe'll stay forever this wayYou are safe in my heartAnd my heart will go on and on \1. 将上面歌词内容存放到本地磁盘D 根目录,文件命名为 word.txt \2. 选择合适的IO流读取word.txt文件的内容 \3. 统计每个单词出现的次数(单词忽略大小写) \4. 如果出现组合单词如 you're按一个单词处理 \5. 将统计的结果存储到本地磁盘D根目录下的wordcount.txt文件 wordcount.txt每行数据个数如下 and 10个 konw 20个public class Homework { public static void main(String[] args) { try(FileInputStream fis = new FileInputStream("word.txt"); FileOutputStream fos = new FileOutputStream("wordcount.txt")) { //定义字节数组存储读取到的内容 byte[] bytes = new byte[1024];//一次读取1KB //因为文件的内容可能大于1KB,所以我们需要循环读取多次 StringBuilder sb = new StringBuilder();//用于存放获取到的所有内容 while (fis.read(bytes) != -1) { sb.append(new String(bytes)); } String content = sb.toString(); //替换掉sb里的换行符 content = content.replace("\r", " "); content = content.replace("\n", " "); content = content.replace("\r\n", " "); //把内容转成小写 content = content.toLowerCase(); //按空格切分字符串 String[] contents = content.split(" "); HashMap<String, Integer> map = new HashMap<>();//用于统计每个单词出现的次数 for (String word : contents) { if (map.containsKey(word)) {//如果key相同,取出value,让其加1,然后再存回去 map.put(word, map.get(word) + 1); } else { map.put(word, 1); } } //把map的内容写到文件wordcount.txt里,每个key-value存做一行 //遍历map for (Map.Entry<String,Integer> entry : map.entrySet()) { fos.write((entry.getKey() + " " + entry.getValue() + "个\n").getBytes()); } } catch (IOException e) { e.printStackTrace(); } } } //曾昭洋 public class HomeWork { static ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>(); public static void main(String[] args) {handleFile();} private static void handleFile() { try(BufferedReader rd =new BufferedReader(new FileReader("D:\\word.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\wordcount.txt"))){ String line; while ((line =rd.readLine()) != null){ // 把每行内容读出来,存在line里 String[] wordGroup= line.toLowerCase().split(" "); // 把每行的内容先变成小写然后按空格分割,存到wordGroup里 for (String word : wordGroup) {//word代表每个单词 if (!(map.containsKey(word))) map.put(word, 1);// 如果没出现过就存进去 else map.put(word, map.get(word) + 1);// 如果出现过就把value+1覆盖之前的数据 } } for (ConcurrentHashMap.Entry<String,Integer> kvp : map.entrySet()) bw.write(String.format("%s %s个\n",kvp.getKey(),kvp.getValue()));//按照我指定的format格式写到wordcount文件中 }catch (IOException e){ e.printStackTrace(); } } }十四、网络编程计算机网络网络(Network):由若干节点(Node)和连接这些节点的链路(Link)组成互联网:多个网络通过路由器连接组成更大的网络,即互联网,因此,互联网是“网络的网络”因特网(Internet):世界上最大的互连网络internet与Internet的区别internet(互联网或互连网):泛指多个计算机网络互连而成的网络。在这些网络之间的通信协议可以是任意的。Internet(因特网)则是一个专用名词,它指当前全球最大的、开放的、由众多网络互连而成的特定计算机网络,它采用TCP/IP协议族作为通信的规则,其前身是美国的ARPANET。任意把几个计算机网络互连起来(不管采用什么协议),并能够相互通信,这样构成的是一个互连网(internet) ,而不是因特网(Internet)因特网发展三个阶段从单个网络ARPANET向互联网发展1969年,第一个分组交换网络ARPANET;70年代中期,研究多种网络之间的互连;1983年,TCP/IP协议成为ARPANET的标准协议(因特网从此诞生)逐步建成三级结构的因特网1985年,NSF(美国国家科学基金会)围绕六个大型计算机中心建设NSFNET(主干网,地区网和校园网);1990年,ARPANET任务完成,正式关闭;1991年,美国政府将因特网主干网交给私营公司,并开始对接入因特网的单位收费;逐步形成了多层次ISP结构的因特网1993年,NSFNET逐渐被若干个商用因特网主干网替代,政府机构不在负责因特网运营,让各ISP来运营;1994年,万维网WWW技术促使因特网迅猛发展;1995年,NSFNET停止运作,因特网彻底商业化。网络模型OSI标准参考模型OSI(Open System Interconnect),即开放式系统互联。是ISO组织在1985年研究的网络互联模型。该体系结构标准定义了网络互联的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层和应用层)。每层功能:应用层:应用层负责文件访问和管理、可靠运输服务、远程操作服务。(HTTP、FTP、SMTP)。表示层:表示层负责定义转换数据格式及加密,允许选择以二进制或ASCII格式传输。会话层:会话层负责使应用建立和维持会话,使通信在失效时继续恢复通信。(断点续传)。传输层:传输层负责是否选择差错恢复协议、数据流重用、错误顺序重排。(TCP、UDP)。网络层:网络层负责定义了能够标识所有网络节点的逻辑地址。(IP地址)。数据链路层:链路层在物理层上,通过规程或协议(差错控制)来控制传输数据的正确性。(MAC)。物理层:物理层为设备之间的数据通信提供传输信号和物理介质。(双绞线、光导纤维)。TCP/IP模型TCP/IP模型是因特网使用的参考模型,基于TCP/IP的参考模型将协议分成四个层次。该模型中最重要的两个协议是TCP和IP协议。每层功能:应用层:应用层负责传送各种最终形态的数据,是直接与用户打交道的层,典型协议是HTTP、FTP等。传输层:传输层负责传送文本数据,主要协议是TCP、UDP协议。网络层:网络层负责分配地址和传送二进制数据,主要协议是IP协议。网络接口层:也叫链路层,接口层负责建立电路连接,是整个网络的物理基础,典型的协议包括以太网、ADSL等等。OSI模型是理论上的国际标准,TCP/IP模型是事实上的国际标准。所以我们学习的是事实标准TCP/IPTCP/IP协议族在该协议族下,有许多的协议,但是通常都把他们统称为tcp/ip协议以下是两个应用层的主机间进行通信,二者都使用FTP协议,下图列出了该过程所涉及到的所有协议。 在图右边,我们注意到应用程序通常是一个用户进程,而下三层则一般在(操作系统)内核中执行。尽管这不是必需的,但通常都是这样处理的,例如UNIX操作系统。 在图中,顶层与下三层之间还有另一个关键的不同之处。应用层关心的是应用程序的细节,而不是数据在网络中的传输活动。下三层对应用程序一无所知,但它们要处理所有的 通信细节。 数据在传输的过程中都需要封装,如下图网络编程三要素三要素分别是:ip地址、端口号、协议(tcp, udp)IP地址IP地址分为IPv4和IPv6,但在实际工作中几乎都是用IPv4,所以我们这里只介绍IPv4相关概念ip地址:ip地址是给因特网上的每一台主机或路由器的每一个接口分配一个在全世界范围内唯一的32比特的标识符。IPv4可分为以下几类但在实际中我们使用到的只有A、B、C三类。A类地址:0.0.0.0-127.255.255.255 B类地址:128.0.0.0-191.255.255.255 C类地址:192.0.0.0-223.255.255.255InetAddress类public class TestIP { /** * * //方式一:获取本机的InetAddress对象 * //通过本地的ip地址信息获取InetAddress对象 * InetAddress ia = InetAddress.getLocalHost(); * //InetAddress对象的常用方法 * //获取主机名(域名) * String hostName = ia.getHostName(); * System.out.println(hostName); * //获取ip地址 * String hostAddress = ia.getHostAddress(); * System.out.println(hostAddress); * //方式二:根据指定的主机名获取InetAddress对象 * InetAddress ia = InetAddress.getByName("www.baidu.com"); * //InetAddress对象的常用方法 * String hostName = ia.getHostName(); * System.out.println(hostName); * String hostAddress = ia.getHostAddress(); * System.out.println(hostAddress); * //方式三:根据指定的ip地址获取InetAddress对象 * InetAddress ia = InetAddress.getByName("14.215.177.38"); * System.out.println(ia.getHostName()); * System.out.println(ia.getHostAddress()); */ public static void main(String[] args) throws UnknownHostException { //创建InetAddress对象 //方式四:根据指定的主机名获取所有的InetAddress InetAddress[] ias = InetAddress.getAllByName("www.baidu.com"); //使用lambda表达式遍历 /*Arrays.stream(ias).forEach(ia ->{ System.out.println(ia.getHostAddress()); System.out.println(ia.getHostName()); });*/ //使用foreach循环遍历 for (InetAddress ia : ias) { System.out.println(ia.getHostAddress()); System.out.println(ia.getHostName()); } } }端口号常见的端口号mysql端口号:3306oracle端口号:1521redis端口号:6379tomcat端口号:8080web服务器端口号:80FTP服务器端口号:21自己编程时用的端口号最好设置在1024~65535之间TCP/UDP协议UDP 和 TCP 是TCP/IP体系结构运输层中的两个重要协议Socket编程基于TCP案例1 【掌握】通过客户端向服务端发送信息/** * 服务器 */ public class ServerDemo02 { public static void main(String[] args) throws IOException { System.out.println("服务器启动......."); ServerSocket serverSocket = new ServerSocket(3305);//构造参数传端口号。 //要让socket监听在3306这个端口号上。 Socket socket = serverSocket.accept();//方法会阻塞等待。 //获取输入流读取客户端内容 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; inputStream.read(bytes); System.out.println(new String(bytes)); //关闭连接 inputStream.close(); socket.close(); serverSocket.close(); } }public class ClientDemo02 { public static void main(String[] args) throws IOException { //连接服务端,创建Socket对象 Socket socket = new Socket("localhost", 3305); //想监听的端口号写内容 OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello i am client".getBytes()); //关闭连接 outputStream.close(); socket.close(); } }让服务器不停止,一直接收多个客户端发的请求,代码如下:public class ServerDemo02 { public static void main(String[] args) throws IOException { System.out.println("服务器启动......."); ServerSocket serverSocket = new ServerSocket(3305);//构造参数传端口号。 int count = 10; //要让socket监听在3306这个端口号上。 //要做到服务器不停止工作,只需要让阻塞的方法一直循环下去 Socket socket = null; InputStream inputStream = null; while (count > 0) { socket = serverSocket.accept();//方法会阻塞等待。 //获取输入流读取客户端内容 inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; inputStream.read(bytes); System.out.println(new String(bytes)); count--; } //关闭连接 inputStream.close(); socket.close(); serverSocket.close(); } }案例2 【掌握】基于多线程实现客户端服务端通信/** * 客户端程序 */ public class ClientDemo02 { public static void main(String[] args) throws IOException { //使用循环模拟多个客户端发请求 for (int i = 1; i < 20000; i++) { //连接服务端,创建Socket对象 Socket socket = new Socket("localhost", 3305); //想监听的端口号写内容 OutputStream outputStream = socket.getOutputStream(); outputStream.write(("hello i am client " + i).getBytes()); //关闭连接 outputStream.close(); socket.close(); } } }/** * 服务器 */ public class ServerDemo02 { public static void main(String[] args) throws IOException, InterruptedException { //创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(16); System.out.println("服务器启动......."); ServerSocket serverSocket = new ServerSocket(3305);//构造参数传端口号。 int count = 20000; //要让socket监听在3306这个端口号上。 //要做到服务器不停止工作,只需要让阻塞的方法一直循环下去 while (count > 0) { Socket socket = serverSocket.accept(); //为了避免某个客户端任务处理时间过长,我们使用多线程来为每一个客户端任务创建一个单独的执行任务 threadPool.submit(new Runnable() { @Override public void run() { //方法会阻塞等待。 try(InputStream inputStream = socket.getInputStream();) { System.out.println(Thread.currentThread().getName() + "开始工作"); //获取输入流读取客户端内容 byte[] bytes = new byte[1024]; inputStream.read(bytes); System.out.println(new String(bytes)); } catch (IOException e) { e.printStackTrace(); } } }); count--; } //关闭连接 serverSocket.close(); } }案例3 【掌握】实现客户端文件上传功能,并从服务端向客户端发送数据/** * 服务器 */ public class ServerDemo02 { public static void main(String[] args) throws IOException, InterruptedException { //创建线程池 System.out.println("服务器启动......."); ServerSocket serverSocket = new ServerSocket(3305);//构造参数传端口号。 int count = 30; //要让socket监听在3306这个端口号上。 //要做到服务器不停止工作,只需要让阻塞的方法一直循环下去 Socket socket = null; InputStream inputStream = null; while (count > 0) { socket = serverSocket.accept();//方法会阻塞等待。 //向客户端发送文件 try(FileInputStream fis = new FileInputStream("/Users/fcp/Downloads/3306.mp3"); OutputStream outputStream =socket.getOutputStream();) { byte[] bytes = new byte[1024];//每次读取文件1KB内容 while (fis.read(bytes) != -1) { outputStream.write(bytes); } } count--; } //关闭连接 inputStream.close(); socket.close(); serverSocket.close(); } },public class ClientDemo03 { public static void main(String[] args) throws IOException { //连接服务端,创建Socket对象 Socket socket = new Socket("172.171.3.37", 3305); //接收服务器写的文件内容 try(InputStream inputStream = socket.getInputStream(); FileOutputStream fos = new FileOutputStream("jay.mp3");) { byte[] bytes = new byte[1024]; while (inputStream.read(bytes) != -1) { fos.write(bytes); } } socket.close(); } }使用Socket实现http服务器/** * 服务器 */ public class ServerDemo03 { public static void main(String[] args) throws IOException { //创建服务端socket通信对象 System.out.println("服务器启动......."); ServerSocket serverSocket = new ServerSocket(3305);//构造参数传端口号。 Socket socket = serverSocket.accept(); OutputStream outputStream = socket.getOutputStream(); PrintWriter out = new PrintWriter(outputStream); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("HTTP/1.1").append(" ").append(200).append(" ") .append("ok").append("\n"); //stringBuilder.append("Content-Length").append(":").append("Hello world!".getBytes()).append("\n"); stringBuilder.append("Content-Type").append(":") .append("text/html;charset=UTF-8").append("\n"); stringBuilder.append("\n"); //stringBuilder.append("欢迎使用Apache Tomcat"); stringBuilder.append("<h1 style='color:red;'>下载音乐</h>"); stringBuilder.append("<a href='/download'><button></button></a>"); out.print(stringBuilder.toString()); out.flush(); //关闭连接 /*inputStream.close(); socket.close(); serverSocket.close();*/ }基于UDP案例1 【掌握】发送端发送数据到接收端public class Send { public static void main(String[] args) throws Exception { //1、基于UDP协议创建Socket对象 无需指定端口号ip地址 DatagramSocket ds = new DatagramSocket(); //2、创建数据包对象 byte[] buf = "你好接收端".getBytes(); DatagramPacket dp = new DatagramPacket(buf,buf.length, InetAddress.getByName("192.168.73.210"),9999); //3、发送数据包 ds.send(dp); //4、释放资源 ds.close(); } }public class Receive { public static void main(String[] args) throws Exception { //1、基于UDP协议创建Socket对象 DatagramSocket ds = new DatagramSocket(9999); //2、创建数据包对象 byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf,buf.length); //3、接受数据包 receive阻塞式的方法 ds.receive(dp); //4、解析数据包的数据 //从数据包中获取数据 byte[] data = dp.getData(); //从数据包中获取InetAddress对象 InetAddress inetAddress = dp.getAddress(); //从数据包中获取端口号 int port = dp.getPort(); //获取数据包的数据字节数长度 int length = dp.getLength(); String str = new String(data,0,length); System.out.println("ip:"+inetAddress.getHostAddress()); System.out.println("port:"+port); System.out.println("data:"+str); //5、释放资源 ds.close(); } }案例2【了解】使用UDP协议向飞秋发送数据public class Send { public static void main(String[] args) throws Exception { //分析:只要搞清楚飞秋的数据包的格式 //1:100:主机名:昵称:32:hello,飞秋 //1、创建Socket对象 DatagramSocket ds = new DatagramSocket(); //2、创数据包对象 byte[] buf = "1:100:主机名:昵称:32:hello,飞秋".getBytes("GBK"); DatagramPacket dp = new DatagramPacket(buf,buf.length, InetAddress.getByName("192.168.73.210"),2425); //3、发送数据包 ds.send(dp); //4、释放资源 ds.close(); } }面试题TCP三次握手TCP 连接的建立采用客户服务器方式。主动发起连接建立的应用进程都是客户端服务端都是被动等待连接。最初两端的TCP进程都处于关闭状态一开始,TCP服务器进程首先创建传输控制块,用来存储TCP连接中的一些重要信息。例如TCP连接表、指向发送和接收缓存的指针、指向重传队列的指针,当前的发送和接收序号等之后,就准备接受TCP客户端进程的连接请求,此时,TCP服务器进程就进入监听状态,等待TCP客户端进程的连接请求TCP服务器进程是被动等待来自TCP客户端进程的连接请求,因此成为被动打开连接TCP客户进程也是首先创建传输控制块,由于TCP连接建立是由TCP客户端主动发起的,因此称为主动打开连接。然后,在打算建立TCP连接时,向TCP服务器进程发送TCP连接请求报文段,并进入同步已发送状态TCP连接请求报文段首部中同步位SYN被设置为1,表明这是一个TCP连接请求报文段序号字段seq被设置了一个初始值x,作为TCP客户端进程所选择的初始序号,注意:TCP规定SYN被设置为1的报文段不能携带数据,但要消耗掉一个序号TCP服务器进程收到TCP连接请求报文段后,如果同意建立连接,则向TCP客户进程发送TCP连接请求确认报文段,并进入同步已接收状态。TCP连接请求确认报文段首部中:①同步位SYN和确认位ACK都设置为1,表明这是一个TCP连接请求确认报文段序;②号字段seq被设置了一个初始值y,作为TCP服务器进程所选择的初始序号;③确认号字段ack的值被设置成了x+1,这是对TCP客户进程所选择的初始序号(seq)的确认请。注意:这个报文段也不能携带数据,因为它是SYN被设置为1的报文段,但同样要消耗掉一个序号TCP客户进程收到TCP连接请求确认报文段后,还要向TCP服务器进程发送一个普通的TCP确认报文段,并进入连接已连接状态。普通的TCP确认报文段首部中①确认位ACK被设置为1,表明这是一个普通的TCP确认报文段;②序号字段seq被设置为x+1,这是因为TCP客户进程发送的第一个TCP报文段的序号为x,所以TCP客户进程发送的第二个报文段的序号为x+1;③确认号字段ack被设置为y+1,这是对TCP服务器进程所选择的初始序号的确认。注意:TCP规定普通的TCP确认报文段可以携带数据,但如果不携带数据,则不消耗序号TCP四次挥手为什么要三次握手tcp之所以设计3次握手的原因并不是因为服务器连接资源浪费的问题。当然三次握手可以避免这种情况,但是当时设计的目的不是这个,而是为了告诉双方序列号。tcp是全双工可靠的传输协议。全双工意味着双方能够同时向对方发送数据。可靠意味着我发送的数据必须确认对方完整收到了。tcp是通过序列号来保证这两种性质的,比如发送端发送一个10字节的包,接受端回复一个对方序列号+10个字节的包,告诉对方你的这个包我收到了,并且是10字节。三次握手就是互换序列号的一次过程,2次则只能保证一方的序列号对方收到了了。4次则多余了。十五、反射与注解从本章开始,我们来探讨Java中的一些动态特性,包括反射、注解、动态代理、类加载器等。利用这些特性,可以优雅地实现一些灵活通用的功能,它们经常用于各种框架、库和系统程序中,比如:Jackson,利用反射和注解实现了通用的序列化机制。有多种库(如Spring MVC、Jersey)用于处理Web请求,利用反射和注解,能方便地将用户的请求参数和内容转换为Java对象,将Java对象转变为响应内容。有多种库(如Spring、Guice)利用这些特性实现了对象管理容器,方便程序员管理对象的生命周期以及其中复杂的依赖关系。应用服务器(如Tomcat)利用类加载器实现不同应用之间的隔离,JSP技术利用类加载器实现修改代码不用重启就能生效的特性。面向切面的编程AOP(Aspect Oriented Programming)将编程中通用的关注点(如日志记录、安全检查等)与业务的主体逻辑相分离,减少冗余代码,提高程序的可维护性,AOP需要依赖上面的这些特性来实现。14.1、反射在一般操作数据的时候,我们都是知道具体要操作数据的数据类型的。比如:1)根据类型使用new创建对象。2)根据类型定义变量,类型可能是基本类型、类、接口或数组。3)将特定类型的对象传递给方法。4)根据类型访问对象的属性,调用对象的方法。编译器也是根据类型进行代码的检查编译的。反射不一样,它是在运行时,而非编译时,动态获取类型的信息,比如接口信息、成员信息、方法信息、构造方法信息等,根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等。下面分为两个部分来学习反射技术介绍Class类的使用通过示例演示Class的完整使用什么是类对象类的对象:基于某个类 new 出来的对象,也称为实例对象。类对象:类加载的产物,封装了一个类的所有信息(类名、父类、接口、属性、方法、构造方法) 。注意:每个类加载到内存都会生成一个唯一的类对象。获取类对象如何获取Class对象,一共有三种方式获取类对象public static void test01(){ //1、通过对象的getClass方法 //Student stu = new Student(); //Class c = stu.getClass(); //class com.qf.reflect.Student 全的限定名(包名+类名) //System.out.println(c); //2、通过类的class属性 //Class c = Student.class; //class com.qf.reflect.Student //System.out.println(c); //3、通过Class类的forName方法 参数:全限定名 (此方法的作用:1、触发类加载 2、获取类对象) try { Class c = Class.forName("com.qf.reflect.Student"); System.out.println(c); } catch (ClassNotFoundException e) { e.printStackTrace(); } }类对象常用的方法Class对象的常用方法调用newInstance方法需要注意:public static void test02(){ try { //1、获取类对象 Class c = Class.forName("com.qf.reflect.Student"); //获取类的名称(全限定名) System.out.println(c.getName()); //获取类的名称 System.out.println(c.getSimpleName()); //获取类加载器(我们可以使用它获取src下的资源配置文件) System.out.println(c.getClassLoader()); //获取父类的类对象 Class superclass = c.getSuperclass(); System.out.println(superclass); //获取实现接口的类对象 Class[] interfaces = c.getInterfaces(); System.out.println(Arrays.toString(interfaces)); //获取Package对象 Package aPackage = c.getPackage(); System.out.println(aPackage); //通过类对象获取类的对象(注意:1、必须要保留无参构造2、构造方法必须使用public修饰) Object o = c.newInstance(); System.out.println(o); } catch (Exception e) { e.printStackTrace(); } }Field类Field类表示类对象中的属性获取它的目的:赋值和取值public static void test04(){ //3、通过Class类的forName方法 参数:全限定名 (此方法的作用:1、触发类加载 2、获取类对象) //此时引用c3就代表了一个Student类,我们需要通过这个类来创建对象。 Class c3 = Class.forName("oop.Student"); //创建对象 Object instance = c3.getDeclaredConstructor().newInstance(); //获取指定公开的属性 Field field = c3.getField("age"); System.out.println(field); System.out.println(field.getInt(instance));//获取instance这个对象里的field代表的属性的值 //对属性进行赋值, 把对象instance里的field代表的属性的值设置为102 field.set(instance, 102); //获取属性中的值 System.out.println(field.get(instance)); //获取所有公开的属性(包括父类的) Field[] fields = c3.getFields(); System.out.println(Arrays.toString(fields)); //获取执行的私有属性 Field field1 = c3.getDeclaredField("marriage"); System.out.println(field1); System.out.println(((Student)instance).isMarriage()); field1.setAccessible(true);//设置之后才能对private修饰的属性进行修改。 field1.set(instance, true); System.out.println(field1.get(instance)); //获取所有的属性(包括私有的) Field[] fieldss = c3.getDeclaredFields(); System.out.println(Arrays.toString(fieldss)); //获取public修饰的属性列表 Field[] fieldList = c3.getFields(); System.out.println(Arrays.toString(fieldList)); }Constructor类Constructor类表示类对象中的构造方法获取它的目的,创建对象public static void main(String[] args) throws Exception { //1、获取类对象 Class c = Class.forName("oop.Student");//c代表某个类的Class对象 //获取类Stuent里public修饰的构造方法 Constructor[] constructors = c.getConstructors(); System.out.println(Arrays.toString(constructors)); //获取全部构造方法,不区分访问修饰符 Constructor[] declaredConstructors = c.getDeclaredConstructors(); System.out.println(Arrays.toString(declaredConstructors)); //如果一个类里的构造方法被private修饰后,那么就不能直接使用new关键字来创建对象 //但是可以通过反射获取该private修饰的构造器,并且设置访问属性为true,即可访问私有构造方法 //同理,私有方法和私有属性也可以通过设置改访问属性后访问到。 Constructor privateConstructor = c.getDeclaredConstructor(); privateConstructor.setAccessible(true);//改法可以提升反射访问效率 Object instance = privateConstructor.newInstance(); System.out.println(instance); //获取带参数的构造方法 Constructor declaredConstructor = c.getDeclaredConstructor(String.class); declaredConstructor.setAccessible(true); Object obj = declaredConstructor.newInstance("小明"); System.out.println(((Student)obj).name);//该方式是通过写具体对象,实际场景不推荐这种写法。 //应该使用以下反射获取属性 Field field = c.getField("name"); System.out.println(field.get(obj)); }Method类Method类表示类对象的方法获取它的目的,调用它并获取方法的返回值(如果方法没有返回值则返回null)public static void test05(){ try { //1、获取类对象 Class c = Class.forName("oop.Student");//c代表某个类的Class对象 Object o = c.getDeclaredConstructor().newInstance();//具体类的对象 //获取指定公开的方法 Method method = c.getMethod("show");//获取Student类里的show无参数方法 //利用反射调用方法,并接收返回值 method.invoke(o);//会去执行show这个方法。 //获取所有的公开的方法(包括父类的公开的方法) //Method[] methods = c.getMethods(); //System.out.println(Arrays.toString(methods)); //获取指定的私有方法 //Method method = c.getDeclaredMethod("print"); //method.setAccessible(true); //利用反射调用方法,并接收返回值(如果方法没有返回值。那么invoke方法就返回null) //Object result = method.invoke(o); //System.out.println(result); //获取本类中所有的方法(包括私有的) Method[] methods = c.getDeclaredMethods(); System.out.println(Arrays.toString(methods)); } catch (Exception e) { e.printStackTrace(); } }综合练习public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { //创建服务端socket通信对象 System.out.println("服务器启动......."); ServerSocket serverSocket = new ServerSocket(3305);//构造参数传端口号。 Socket socket = serverSocket.accept();//程序阻塞等待客户端连接 InputStream inputStream = socket.getInputStream();//获取输入流读取浏览器的请求数据 byte[] bytes = new byte[1024*8]; inputStream.read(bytes); String content = new String(bytes);//把读取的数据转成字符串 //把请求内容里的换行替换为空格 content = content.replace("\r", " "); content = content.replace("\n", " "); content = content.replace("\r\n", " "); //把请求内容按照空格切割,切割后数组里的第二个元素就是路径名 String path = content.split(" ")[1]; System.out.println(path); //根据路径名到配置文件里找到对应的Servlet也就是java文件的全限定名 Properties properties = new Properties(); properties.load(new FileReader("web.properties")); String className = (String) properties.get(path); System.out.println("classname : " + className); //从配置文件读到全限定名后就可以使用反射创建对象 Class<?> aClass = Class.forName(className); Object instance = aClass.getDeclaredConstructor().newInstance(); //通过反射调用路径对应的java文件的service方法执行用户的具体请求操作 Method method = aClass.getMethod("service"); method.invoke(instance); OutputStream outputStream = socket.getOutputStream(); PrintWriter out = new PrintWriter(outputStream); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("HTTP/1.1").append(" ").append(200).append(" ") .append("ok").append("\n"); //stringBuilder.append("Content-Length").append(":").append("Hello world!".getBytes()).append("\n"); stringBuilder.append("Content-Type").append(":").append("text/html;charset=UTF-8").append("\n"); stringBuilder.append("\n"); //stringBuilder.append("欢迎使用Apache Tomcat"); stringBuilder.append("<h1 style='color:red;'>欢迎使用Tomcat</h>"); //stringBuilder.append("<img src=''>下载音乐</h>"); //stringBuilder.append("<a href='172.171.3.37:3305'><button></button></a>"); out.print(stringBuilder.toString()); out.flush(); //关闭连接 /*inputStream.close(); socket.close(); serverSocket.close();*/ }web.properties内容/login=socket.servlet.LoginServlet示例介绍了一堆Class的方法,他们有什么用呢?我们利用反射实现一个简单的通用序列化/反序列化功能,主要通过两个方法实现public static String serialization(Object obj);//序列化 public static Object deserialization(String str);//反序列化 { "age":12, "name":"小明" }serialization将对象obj转换为字符串,deserialization将字符串转换为对象。为简单起见,我们只支持最简单的类,即有默认构造方法,成员类型只有基本类型、包装类或String。测试案例public static void main(String[] args) { Student student1 = new Student("jay", 18, 2004); String studentStr = serialization(student1); System.out.println(studentStr); Student student2 = (Student) deserialization(studentStr); System.out.println(student2); }最后对象引用zhangsan2的输出内容要和zhangsan一致。代码实现实体类Studentpublic class Student { private String username; private Integer age; private Integer birthYear; public Student(){} public Student(String username, Integer age, Integer birthYear) { this.username = username; this.age = age; this.birthYear = birthYear; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getBirthYear() { return birthYear; } public void setBirthYear(Integer birthYear) { this.birthYear = birthYear; } @Override public String toString() { return "Student{" + "username='" + username + '\'' + ", age=" + age + ", birthYear=" + birthYear + '}'; } }功能实现类public class Demo01 { public static void main(String[] args) { Student student = new Student("Jay", 18, 2004); String studentStr = serialization(student); System.out.println(studentStr); Student deserializationObj = (Student) deserialization(studentStr); System.out.println(deserializationObj); } public static String serialization(Object obj) { Class<?> clazz = obj.getClass(); String clazzName = clazz.getName(); StringBuilder sb = new StringBuilder(clazzName); sb.append("\n{"); Field[] declaredFields = clazz.getDeclaredFields(); sb.append("\n"); for (Field field : declaredFields) { if (!field.isAccessible()) field.setAccessible(true); try { sb.append("\t").append("\""); sb.append(field.getName()).append("\":").append("\"" + field.get(obj) + "\","); } catch (IllegalAccessException e) { e.printStackTrace(); } sb.append("\n"); } String s = sb.toString(); s = s.substring(0, s.length()-2); s = s + "\n}"; return s; } public static Object deserialization(String str) { try { String[] lines = str.split("\n"); if(lines.length < 1) { throw new IllegalArgumentException(str); } Class<?> cls = Class.forName(lines[0]); Object obj = cls.getDeclaredConstructor().newInstance(); if(lines.length > 1) { for(int i = 2; i < lines.length-1; i++) { String[] map = lines[i].split(":"); if(map.length != 2) { throw new IllegalArgumentException(lines[i]); } Field f = cls.getDeclaredField(map[0]); if(!f.isAccessible()){ f.setAccessible(true); } //setFieldValue(f, obj, fv[1]); f.set(obj, map[1]); } } return obj; } catch(Exception e) { throw new RuntimeException(e); } } private static void setFieldValue(Field f, Object obj, String value) throws Exception { Class<?> type = f.getType(); if(type == int.class) { f.setInt(obj, Integer.parseInt(value)); } else if(type == byte.class) { f.setByte(obj, Byte.parseByte(value)); } else if(type == short.class) { f.setShort(obj, Short.parseShort(value)); } else if(type == long.class) { f.setLong(obj, Long.parseLong(value)); } else if(type == float.class) { f.setFloat(obj, Float.parseFloat(value)); } else if(type == double.class) { f.setDouble(obj, Double.parseDouble(value)); } else if(type == char.class) { f.setChar(obj, value.charAt(0)); } else if(type == boolean.class) { f.setBoolean(obj, Boolean.parseBoolean(value)); } else if(type == String.class) { f.set(obj, value); } else { Constructor<?> ctor = type.getConstructor( new Class[] { String.class }); f.set(obj, ctor.newInstance(value)); } } }实战练习使用反射实现把对象转成Json字符串,把Json字符串转成对象。类似Alibaba的FastJson以及google的Gson。public class Demo01 { public static void main(String[] args) throws Exception{ //创建对象 Student1 student = new Student1("小明", "贵州黔西南", 25, 1998); //调用一个方法实现把对象转成json字符串 String json = toJson(student); System.out.println(json); toObject(json); } /** * { * "age":12, * "name":"小明" * } * @param obj */ public static String toJson(Object obj) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { StringBuilder jsonBuffer = new StringBuilder(); //获取该对象的Class对象 Class<?> clazz = obj.getClass(); jsonBuffer.append(clazz.getName()); jsonBuffer.append("\n{\n"); //创建对象 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { //因为属性通常都是private修饰,所以需要设置访问权限 field.setAccessible(true); //获取变量名 String variableName = field.getName(); //获取变量的值 Object variableValue = field.get(obj); jsonBuffer.append("\"") .append(variableName).append("\":"); //判断,如果属性不是字符就不要加双引号 Class<?> type = field.getType(); if (type == int.class) { jsonBuffer.append(variableValue) .append(",\n"); } else { jsonBuffer.append("\"").append(variableValue) .append("\",\n"); } } String json = jsonBuffer.toString(); json = json.substring(0, json.length()-2); json = json + "\n}"; return json; } public static Object toObject(String json) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { //获取字符串里的类权限定名 String[] arr = json.split("\n"); String className = arr[0]; Class<?> clazz = Class.forName(className); Object instance = clazz.getDeclaredConstructor().newInstance(); for (int i = 2; i < arr.length-1; i++) { String variVal = arr[i].split(",")[0]; String[] split = variVal.split(":"); String atrribute = split[0]; atrribute = atrribute.substring(1, atrribute.length()-1); Field field = clazz.getDeclaredField(atrribute); field.setAccessible(true);//访问私有属性 Class<?> type = field.getType(); if (type == String.class) { String value = split[1]; value = value.substring(1, value.length()-1); field.set(instance, value); } else { field.set(instance, Integer.parseInt(split[1])); } } System.out.println(instance); return null; } } class Student1 { private String username; private String address; private int age; private int birthYear; public Student1(){} public Student1(String username, String address, int age, int birthYear) { this.username = username; this.address = address; this.age = age; this.birthYear = birthYear; } @Override public String toString() { return "Student1{" + "username='" + username + '\'' + ", address='" + address + '\'' + ", age=" + age + ", birthYear=" + birthYear + '}'; } }14.2、注解概念注解(Annotation):是一种引用数据类型。编译之后也会生成class文件自定义注解语法格式:[修饰符] @interface 注解名{ }注解可以用在类、属性、方法上。JDK内置了哪些注解?Java中目前有 5 种标准注解。5 种元注解,元注解用于注解其他的注解。标准注解@Override :表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示。@Deprecated :如果使用该注解的元素被调用,编译器就会发出警告信息。@SuppressWarnings :关闭不当的编译器警告信息。@SafeVarargs :在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。@FunctionalInterface :Java 8 中加入用于表示类型声明为函数式接口元注解@Target:表示注解可以用于哪些地方。可能的 ElementType 参数包括:CONSTRUCTOR :构造器的声明FIELD :字段声明(包括 enum 实例) LOCAL_VARIABLE :局部变量声明 METHOD :方法声明 PACKAGE :包声明 PARAMETER :参数声明 TYPE :类、接口(包括注解类型)或者 enum 声明@Retention:表示注解信息保存的时长。可选的 RetentionPolicy 参数包括:SOURCE :表示注解只被保留在java源文件中CLASS :注解在 class 文件中可用,但是会被 VM 丢弃。 RUNTIME :VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。@Documented:将此注解保存在 Javadoc 中@Inherited :允许子类继承父类的注解@Repeatable:允许一个注解可以被使用一次或者多次(Java 8)。自定义注解我们自定义一个@Test注解来对testExecute()进行注解。注解的定义看起来很像接口的定义。事实上,它们和其他 Java 接口一样,也会被编译成 class 文件。定义注解import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Demo01 { }除了 @ 符号之外,@Demo01 的定义看起来更像一个空接口。注解的定义也需要一些元注解(meta-annoation),比如@Target和@Retention。@Target 定义你的注解可以应用在哪里(例如是方法还是字段)。@Retention定义了注解在哪里可用,在源代码中(SOURCE),class文件(CLASS)中或者是在运行时(RUNTIME)。使用注解public class Testable { public void execute(){ System.out.println("Executing..."); } @Test public void testExecute() { execute(); } }不包含任何元素的注解称为标记注解(marker annotation),例如上例中的 @Demo01 就是标记注解。可以为注解定义一些参数,定义的方式是在注解内定义一些方法。@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Label { String value() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Format { String pattern() default "yyyy-MM-dd HH:mm:ss"; String timezone() default "GMT+8"; }实例演示public class COVID_19 { @Label("名称") private String name; @Label("出生日期") @Format(pattern = "yyyy/MM/dd") private Date born; public COVID_19() { } public COVID_19(String name, Date born) { this.name = name; this.born = born; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBorn() { return born; } public void setBorn(Date born) { this.born = born; } @Override public String toString() { return "COVID_19{" + "name='" + name + '\'' + ", born=" + born + '}'; } private static String format(Object obj) { try { Class<?> clazz = obj.getClass(); StringBuilder sb = new StringBuilder(); Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { if (!field.isAccessible()) field.setAccessible(true); Label label = field.getAnnotation(Label.class); String name = label != null ? label.value() : field.getName(); Object value = field.get(obj); if (value != null && field.getType() == Date.class) { value = formatDate(field, value); } sb.append(name + ": " + value + "\n"); } return sb.toString(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } private static Object formatDate(Field f, Object value) { Format format = f.getAnnotation(Format.class); if(format != null) { SimpleDateFormat sdf = new SimpleDateFormat(format.pattern()); sdf.setTimeZone(TimeZone.getTimeZone(format.timezone())); return sdf.format(value); } return value; } public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { COVID_19 covid_19 = new COVID_19("新冠病毒", sdf.parse("2020-01-01")); System.out.println(covid_19); System.out.println(format(covid_19)); } catch (ParseException e) { e.printStackTrace(); } } }案例练习:实现一个依赖注入功能。自定义注解MiniInject@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface MiniInject { }定义一个类LoginServicepublic class LoginService { public void login() { System.out.println("test mini inject function"); } }定义一个类LoginController,并且通过注解@MiniInject把对象LoginService注入到属性loginService上public class LoginController { @MiniInject private LoginService loginService; public void login() { loginService.login(); } }自定义容器类来负责生成客户代码需要的对象public class IocContainer { public static<T> T getInstance(Class<T> cls) { try { //创建实例 T instance = cls.newInstance(); //获取类里的属性列表,目的是为了检查属性是否又被注解修饰。 Field[] fields = cls.getDeclaredFields(); for (Field field : fields) { //检查属性是否被注解MiniInject修饰 if (field.isAnnotationPresent(MiniInject.class)) { if (!field.isAccessible()) field.setAccessible(true); //如果被注解MiniInject修饰,则注入依赖的对象 Class<?> fieldCls = field.getType(); //这里会递归调用getInstance方法,可以解决嵌套注入 field.set(instance, getInstance(fieldCls)); } } return instance; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } }测试public class TestMiniInject { public static void main(String[] args) { LoginController instance = IocContainer.getInstance(LoginController.class); instance.login(); } }14.2.3 实战练习(http服务器)基于注解实现MiniHttp服务器WebServlet注解类@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface WebServlet { String value() default ""; }html页面<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> <style> input { width: 200px; height: 30px; border-radius: 10px; } .container { width: 800px; margin-left: auto; margin-right: auto; margin-top: auto; margin-bottom: auto; } </style> </head> <body style="background-color: cornflowerblue"> <div class="container"> <label>用户名:</label> <input type="text" name="username" placeholder="username"><br><br> <label>密码:</label> <input type="password" name="username" placeholder="password"><br><br> <label>确认密码:</label> <input type="password" name="username" placeholder="......."><br><br> <label>邮箱:</label> <input type="email" name="username" placeholder="eamil"><br><br> <label>电话:</label> <input type="phone" name="username" placeholder="phone"> </div> </body> </html>服务器类public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { //创建服务端socket通信对象 System.out.println("服务器启动......."); ServerSocket serverSocket = new ServerSocket(80);//构造参数传端口号。 while (true) { Socket socket = serverSocket.accept();//程序阻塞等待客户端连接 service(socket); } } public static void service(Socket socket) throws IOException, ClassNotFoundException { InputStream inputStream = socket.getInputStream();//获取输入流读取浏览器的请求数据 InputStreamReader isr = new InputStreamReader(inputStream);//转换流 BufferedReader br = new BufferedReader(isr); Request request = new Request();//创建对象 int count = 1; HashMap<String, String> header = new HashMap<>(); for (int i = 0; i < 13; i++) { String b = br.readLine(); if (b == null || b.equals("")) break; if (count == 1) { String[] requests = b.split(" "); //初始化对象里的属性 request.setMethod(requests[0]); request.setUri(requests[1]); request.setVersion(requests[2]); } else { String[] split = b.split(":"); if (split != null && split.length ==2) { //因为浏览器请求的header头部是按照key:value的形式存放,所以我们可以使用集合的Hasmap存储这部分内容 header.put(split[0], split[1].trim()); } } count++; } //把请求内容按照空格切割,切割后数组里的第二个元素就是路径名 request.setHeaders(header); br.close(); isr.close(); inputStream.close(); toDoList(request, socket); } private static void toDoList(Request request, Socket socket) throws ClassNotFoundException, IOException { //获取java文件的class路径 URL url = MiniHttpServer.class.getResource("./"); String classFilePath = url.toString().split(":")[1]; String[] split = classFilePath.split("/"); classFilePath = split[split.length-1]; classFilePath = classFilePath + ".servlet"; System.out.println("classname : " + classFilePath); //获取某个路径下的所有文件,使用File类 File file = new File(url.toString().split(":")[1] + "servlet/");// /Users/fcp/food/java/java2202/out/production/javase/socket/servlet/ String s = null; //首先判断该file是否存在 if(file.exists()) { File[] files = file.listFiles(); for (File f : files) { if (f.getName().endsWith("Servlet.class")) {//只使用servlet文件 //通过字符串处理拼接类的全限定名 String classFileName = classFilePath; classFileName = classFileName + "." + f.getName(); classFileName = classFileName.substring(0, classFileName.length()-6); //通过反射获取该文件的Class对象。Class.forName("socket.servlet.xxxx"); Class<?> clazz = Class.forName(classFileName); //查看该对象上的注解是否是WebServlet if (clazz.isAnnotationPresent(WebServlet.class)) { WebServlet annotation = clazz.getAnnotation(WebServlet.class); //获取注解的value String value = annotation.value(); //判断该value是否和前端传入的地址相同 if (value.equals(request.getUri())) { //通过反射创建该servlet对象,然后调用该对象的service方法 /* Object instance = clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getDeclaredMethod("service"); method.invoke(instance);*/ //让服务器返回一个登录的html页面 login.html //使用IO流读取html文件内容 FileInputStream fis = new FileInputStream("/Users/fcp/food/java/java2202/javase/src/html/login.html"); byte[] bytes1 = new byte[1024*8]; fis.read(bytes1); s = new String(bytes1); } } } } } response(request, socket, s); } private static void response(Request request, Socket socket, String content) throws IOException { OutputStream outputStream = socket.getOutputStream(); PrintWriter out = new PrintWriter(outputStream); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(request.getVersion()).append(" ").append(200).append(" ") .append("ok").append("\n"); //stringBuilder.append("Content-Length").append(":").append("Hello world!".getBytes()).append("\n"); stringBuilder.append("Content-Type").append(":").append("text/html;charset=UTF-8").append("\n"); stringBuilder.append("\n"); stringBuilder.append(content); out.print(stringBuilder.toString()); out.flush(); //关闭连接 socket.close(); }Request类public class Request { /** * 请求方法 GET/POST/PUT/DELETE/OPTION... */ private String method; /** * 请求的uri */ private String uri; /** * http版本 */ private String version; /** * 请求头 */ private Map<String, String> headers; /** * 请求参数相关 */ private String message; }十六、函数式编程介绍概念面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象,函数式编程只在乎数据以及对数据做了什么操作,面向对象里的类,对象,方法,数据类型都不关心。为什么学习代码简洁,开发速度快。为了能够看懂框架、同事的代码。使代码的可读性更高。在大数据量下,对于集合的处理效率更高。优点代码简洁,开发快速接近自然语言,易于理解易于"并发编程"Lambda表达式概念Lambda表达式可以对某些匿名内部类的写法进行简化,它是JDK8中的一个语法糖。它是函数式编程思想的一个重要体现,让我们不用再关注业务逻辑里的对象,而只需关注我们要对数据进行什么操作。核心原则只要可以推导,都可以省略,即可以根据上下文推导出来的内容,都可以省略书写语法格式(参数列表) -> {代码}示例一在创建线程并启动时,我们可以使用匿名内部类的写法。new Thread(new Runnable() { @Override public void run() { System.out.println("我要的只是你在我身边"); } }).start();使用Lambda格式对以上代码进行修改,可以写出如下代码new Thread(() -> System.out.println("我要的只是你在我身边")).start();示例二有如下方法定义,IntBinaryOperator是一个接口,内部有一个抽象方法applyAsIntpublic static int addition(IntBinaryOperator opt) { int a = 82, b = 18; return opt.applyAsInt(a, b); }需求:调用以上代码,完成两个整数求和。//使用匿名内部类调用方法。 int sum = addition(new IntBinaryOperator() { @Override public int applyAsInt(int left, int right) { return left + right; } }); System.out.println(sum); //使用Lambda表达式替换匿名内部类写法 int sum1 = addition((left, right) -> { return left + right; }); System.out.println(sum1);示例三调用以下方法,打印输出偶数。public static void showOddNum(IntPredicate predicate) { int[] nums = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; for (int num : nums) if (predicate.test(num)) System.out.print(num + " "); } public static void main(String[] args) { //匿名内部类调用 showOddNum(new IntPredicate() { @Override public boolean test(int value) { return value%2 == 0; } }); showOddNum(value -> value%2 == 0); }示例四把字符串整数转成int型整数public static int typeConvert(Function<String, Integer> function) { String s = "12345"; return function.apply(s); } public static void main(String[] args) { //匿名内部类调用 int i = typeConvert(new Function<String, Integer>() { @Override public Integer apply(String s) { //work return Integer.valueOf(s); } }); System.out.println(i); int i1 = typeConvert(s -> Integer.valueOf(s)); System.out.println(i1); }示例五遍历数组public static void traverseArr(IntConsumer consumer) { int[] nums = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; for (int num : nums) consumer.accept(num); } public static void main(String[] args) { traverseArr(new IntConsumer() { @Override public void accept(int value) { System.out.print(value + " "); } }); traverseArr(value -> System.out.print(value + " ")); }省略原则参数类型可以省略。当方法体内只有一行代码时,return可以省略,分号可以省略,大括号也可以省略。方法只有一个参数时,圆括号可以省略。Stream流概念Stream 就好像一个高级的迭代器,但只能遍历一次,就好像一江春水向东流;在流的过程中,对流中的元素执行一些操作,比如“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream流可以更方便我们对集合和数组的操作。快速入门需求:获取作家的集合,打印年龄小于18的作家的名字,并去重。public static void main(String[] args) { //获取作家的集合,打印年龄小于18的作家的名字,并去重。 List<Author> authors = getAuthors(); authors.stream()//把集合转成流 .distinct()//去重 .filter(author -> author.getAge() < 18)//在流中获取年龄小于18的作者 .forEach(author -> System.out.println(author.getName()));//遍历打印流中的数据 } private static List<Author> getAuthors() { Author author1 = new Author(1L, "蒙多", 33, "一个从菜刀中明白哲理的祖安人", null); Author author2 = new Author(2L, "亚索", 15, "最美的不是下雨天", null); Author author3 = new Author(3L, "温", 14, "你知道我想要的只是在你身边", null); Author author4 = new Author(3L, "温", 14, "你知道我想要的只是在你身边", null); List<Book> books1 = new ArrayList<>(); List<Book> books2 = new ArrayList<>(); List<Book> books3 = new ArrayList<>(); books1.add(new Book(1L, "刀的两侧是光明与黑暗", "哲理, 爱情", 88, "用一把刀划分了爱恨")); books1.add(new Book(2L, "一个人不能倒在同一把刀下", "个人成长, 爱情", 99, "讲述如何从失败中明悟哲理")); books2.add(new Book(3L, "到有风的地方去", "哲学", 85, "带你用思维去领略世界的尽头")); books2.add(new Book(3L, "到有风的地方去", "哲学", 85, "带你用思维去领略世界的尽头")); books2.add(new Book(4L, "吹或者不吹", "爱情,个人传记", 56, "一个哲学家的恋爱观注定很难把他所在的时代理解")); books3.add(new Book(5L, "你的剑就是我的剑", "爱情", 56, "无法想象一个武者能对他的伴侣如此宽容")); books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎样的火花呢?")); books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎样的火花呢?")); author1.setBooks(books1); author2.setBooks(books2); author3.setBooks(books3); author4.setBooks(books3); //把多个作者存储到一个集合里 List<Author> authorList = new ArrayList<>(Arrays.asList(author1, author2, author3, author4)); return authorList; } public class Author { private Long id; private String name; private Integer age; private String info; private List<Book> books; } public class Book { private Long id; private String name; private String category; private Integer score; private String info; }常用操作创建流集合获取流对象集合引用名.stream()public static void main(String[] args) { //List<String> list = new ArrayList<>(); //Set<String> list = new Hashset<>(); List<String> list = new LinkedList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("c"); list.add("a"); //获取流对象 list.stream()//获取流对象 .distinct()//去重 .forEach(s -> System.out.println(s));//终结打印内容 }数组获取流对象Arrays.stream(数组)或`Stream`.of(数组)来创建public static void main(String[] args) { Integer[] nums = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; Arrays.stream(nums) .filter(value -> value%2 == 0) .forEach(v -> System.out.println(v)); Stream.of(nums) .filter(value -> value%2 == 0) .forEach(v -> System.out.println(v)); }Map获取流对象map.entrySet().stream()public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("a", 97); map.put("b", 98); map.put("c", 99); map.put("d", 100); map.put("e", 101); //获取map的流 map.entrySet().stream() .filter(entry -> entry.getValue() < 100) .forEach(v -> System.out.println(v)); }中间操作filter: filter是个方法。方法参数接收一个接口的实现类,用户可以在接口中定义的方法内实现自己的逻辑来过了流中的数据如以下示例过滤掉map集合中的value值大于100的数据Map<String, Integer> map = new HashMap<>(); map.put("a", 97); map.put("b", 98); map.put("c", 99); map.put("d", 100); map.put("e", 101); //获取map的流 map.entrySet().stream() .filter(entry -> entry.getValue() < 100) .forEach(v -> System.out.println(v));map:把一种流转换成另一种指定的流public static void main(String[] args) { //获取作家的集合,打印年龄小于18的作家的名字,并去重。 List<Author> authors = getAuthors(); authors.stream() .map(author -> author.getInfo()) .distinct() .forEach(value -> System.out.println(value)); }flatMap:把一种流转换成另一种指定的流。它和map函数的区别是authors.stream() .flatMap(author -> author.getBooks().stream()) .distinct() .forEach(v -> System.out.println(v));distinct: 去除重复元素,如果是自定义的类,那么该类需要重写equals和hashcode方法。否则会去重失败。List<Author> authors = getAuthors(); authors.stream() .distinct() .forEach(v -> System.out.println(v));sort : 给指定元素排序List<Author> authors = getAuthors(); authors.stream() .sorted()//使用实现Comparable接口重写的compareTo方法进行自定义排序 .forEach(v -> System.out.println(v.getName() + " " + v.getAge())); List<Author> authors = getAuthors(); authors.stream() .sorted((o1, o2) -> o1.getName().compareTo(o2.getName()))//按照Comparetor排序 .forEach(v -> System.out.println(v.getName() + " " + v.getAge()));limit : 限制输出几条数据authors.stream() .flatMap(author -> author.getBooks().stream()) .limit(5) .forEach(v -> System.out.println(v));skip : 跳过指定数量的数据条数,比如传入参数为3,那么就会忽略掉前三条数据。List<Author> authors = getAuthors(); authors.stream() .flatMap(author -> author.getBooks().stream()) .skip(5) .forEach(v -> System.out.println(v));终结操作forEach : 遍历打印count : 流里的数据经过一系列函数处理后,最终想要获取一共有多少条数据,就可以使用该函数List<Author> authors = getAuthors(); long count = authors.stream() .flatMap(author -> author.getBooks().stream()) .count(); System.out.println(count);max&min : 获取流中的最大最小值List<Author> authors = getAuthors(); Optional<Integer> max = authors.stream() .map(author -> author.getAge()) .max((o1, o2) -> o1 - o2); System.out.println(max.get()); Optional<Integer> min = authors.stream() .map(author -> author.getAge()) .min((param1, param2) -> param1 - param2); System.out.println(min.get());collect把流转成集合List<Author> authors = getAuthors(); List<Book> bookList = authors.stream() .flatMap(author -> author.getBooks().stream()) .collect(Collectors.toList()); System.out.println(bookList); //转Set authors.stream() .map(author -> author.getAge()) .collect(Collectors.toSet()) .forEach(v -> System.out.println(v)); //转map Map collect = authors.stream() .distinct() .collect(Collectors.toMap(author -> author.getName(), author -> author.getInfo())); //collect转map需要传入两个Function,第一个是求出map的key,第二个是求出value System.out.println(collect);查找与匹配anyMatch查看流中是否有指定数据,有则返回true,否则返回false。List<Author> authors = getAuthors(); boolean isMatch = authors.stream() .anyMatch(author -> author.getName().contains("多")); System.out.println(isMatch);allMatch流里所有的数据都要包含指定的内容,如下示例,所有作家名字里都要包含温才会返回true。List<Author> authors = getAuthors(); boolean isMatch = authors.stream() .allMatch(author -> author.getName().contains("温")); System.out.println(isMatch);findAny返回流中任意一个元素Optional<Author> any = authors.stream() .findAny(); System.out.println(any.get());findFirst返回流中第一个元素Optional<Author> any = authors.stream() .findFirst(); System.out.println(any.get());reduce归并/** * T result = identity; * * for (T element : this stream) * * result = accumulator.apply(result, element) * * return result; */ Integer[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Integer result = Stream.of(nums) .reduce(0, ((sum, index) -> sum += index)); System.out.println(result); //sum : 代表累加后的结果 //index : 表示数组下标 List<Author> authors = getAuthors(); Integer reduce = authors.stream() .map(author -> author.getAge()) .reduce(0, ((sum1, i) -> sum1 += i)); System.out.println(reduce);Optional概述我们在写代码的时候,经常会有出现空指针异常,这时候我们需要写if作非空判断。例如:如果一个对象的属性过多,那么就要写大量的if,这时候代码就会变得很臃肿。所以JDK8开始引入了Optional,就可以避免这种做非空判断的逻辑代码,使编写的代码更简洁。使用创建对象Optinal原理:可以把我们具体数据封装到Optional对象内部。然后使用Optional中封装好的方法去操作封装进去的数据,就可以优雅的避免空指针异常Optional常使用的方法是ofNullable,这是一个静态方法,无论传入的参数是否为null都不会出现问题。Author author = getAuthor(); Optional<Author> optional = Optional.ofNullable(author);这种方式初看觉得复杂,但是如果我们在getAuthor()方法内部就把对象封装为Optinal返回,使用时就会方便许多。在实际开发中我们的数据几乎都是从数据库获取,Mybatis从3.5版本开始支持Optional。可以直接把dao层方法的返回值类型定义为Optional,Mybatis会自动把数据封装成Optional返回。封装的过程不需要我们写代码操作。如果确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象。Author author = new Author(); Optional<Author> optional = Optional.of(author);注意:使用of时传入的参数必须不能为null。如果一个方法的返回值类型为Optional。但我们经过判断发型某次计算的结果却是null,这时候就需要把null封装成Optional对象。这时候可以使用Optional的静态方法empty来进行封装。Optional.empty();消费值获取到一个Optional后肯定需要对数据进行使用。这时可以使用ifPresent。该方法会判断内部数据是否空。不空才会使用,这种方式使代码更安全。public static void main(String[] args) { Optional<List<Author>> authors1 = Optional.ofNullable(getAuthors()); authors1.ifPresent(authors2 -> System.out.println(authors2.get(1))); }获取值get(): 不推荐,不安全orElseGet() :在使用一个引用前,该引用可能返回的值会是null,那此时我们可以使用该方法给该引用一个默认值如下代码,如果getAuthor()返回null,那么此时可以使用Lambda表达式里的:new Author(2L, "亚索", 15, "最美的不是下雨天", null)对象getAuthor().orElseGet(() -> new Author(2L, "亚索", 15, "最美的不是下雨天", null));orElseThrow()try { getAuthor().orElseThrow((Supplier<Throwable>) () -> new RuntimeException("该对象为空")); } catch (Throwable throwable) { throwable.printStackTrace(); }过滤filter()和Stream流里一样的使用方式判断isPresent()Optional<Author> optional = getAuthor(); optional.ifPresent(author -> System.out.println(author.getName())); if(optional.isPresent()) { System.out.println(optional.get()); }数据转换map()和Stream流里一样的使用方式函数式接口概述Jdk8提供的新功能,接口中只有一个抽象方法,并且有注解@FunctionalInterface常见函数式接口Consumer、Predicate、Runnable自定义函数式接口@FunctionalInterface public interface MyFunctionalInterface { public abstract int sum(int min, int max); default void sum1() { System.out.println("sum1()"); } } public static void main(String[] args) { getSum((min, max) -> min + max); } private static void getSum(MyFunctionalInterface function) { int sum = function.sum(1, 100); System.out.println(sum); }方法引用概述如果在使用Lambda表达式的时候,方法体中只有一个方法(包括构造方法)调用时,可以使用方法引用,让Lambda表达式的写法看起来更加简洁。基本语法格式:类名或对象名::方法名引用静态方法使用前提1:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,我们把重写的抽象方法中所有的参数都按照顺序传入了这个静态方法,这时候就可以引用类的静态方法。使用前提2:重写方法时,方法中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个方法中,这时候就可以使用方法引用。格式:类名::方法名引用对象的实例方法使用前提:重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,我们把重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这时候就可以引用对象的实例方法。格式:对象名::方法名构造方法引用使用前提:重写方法时,只有一行代码,并且这行代码调用了某个类的构造方法,且我们把要重写的抽象方法中的所有参数都按照顺序传入了这个构造方法中,这时候我们就可以引用构造方法。格式:类名::newList<Author> authors = getAuthors(); authors.stream() .map(author -> author.getName()) .map(String::new).count();
2023年01月12日
28 阅读
0 评论
0 点赞
1
...
12
13
14
...
19