1 Unstar Star 1 Fork 0

梁昊 / lqhao

Create your Gitee Account
Explore and code with more than 5 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Without author's permission, this code is only for learning and cannot be used for other purposes.
Clone or download
content.json 251.34 KB
Copy Edit Web IDE Raw Blame History
梁昊 authored 2020-07-25 19:47 . Site updated: 2020-07-25 19:46:50
{"meta":{"title":"不蒸馒头争口气","subtitle":"一个有心的车主","description":"万丈高楼平地起,一切承担靠地基","author":"laden","url":"http://lqhao.gitee.io","root":"/"},"pages":[{"title":"分类","date":"2018-01-04T16:00:00.000Z","updated":"2020-07-06T09:47:30.081Z","comments":true,"path":"categories/index.html","permalink":"http://lqhao.gitee.io/categories/index.html","excerpt":"","text":""},{"title":"友情链接","date":"2020-07-07T14:17:49.000Z","updated":"2020-07-06T09:47:52.560Z","comments":true,"path":"link/index.html","permalink":"http://lqhao.gitee.io/link/index.html","excerpt":"","text":""},{"title":"about","date":"2020-07-07T03:41:19.000Z","updated":"2020-07-07T09:23:59.270Z","comments":true,"path":"about/index.html","permalink":"http://lqhao.gitee.io/about/index.html","excerpt":"","text":"Name: ladenAge: 23Gender: maleAddress: Shan XiBlog: https://lqhao.gitee.io/ 此生如若能自己打通任督二脉,或生命中存在贵人能帮持一把,那将是三生之幸。若能够找到生活的真正意义,能够随时随地无忧无虑的干自己想干的事,不会为金钱之类的事情所困扰,那初期受的所有苦难都是值得的。 人生如戏,吃奶一年,滚爬两年,懵懂四年,义务教育9年,社会走一遭10年,还是没钱!娶妻生子天天想,在过10年有车也有房,闯一闯敢不敢,不敢!不敢!母亲还在家中做饭!只需父母健在,管它恋爱不恋爱!祝愿在外漂泊的姐妹同胞!多给家里一个问候。 眼看天下路,何故执笔人!道道通我心,一笔写人生。"},{"title":"标签","date":"2020-07-04T16:00:00.000Z","updated":"2020-07-06T09:48:40.816Z","comments":true,"path":"tags/index.html","permalink":"http://lqhao.gitee.io/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"Java-JDBC连接池、JDBCTemplate","slug":"Java-JDBC连接池、JDBCTemplate","date":"2020-07-25T11:41:52.836Z","updated":"2020-07-25T11:44:06.115Z","comments":true,"path":"2020/07/25/2/","link":"","permalink":"http://lqhao.gitee.io/2020/07/25/2/","excerpt":"","text":"1. 数据库连接池 2. Spring JDBC : JDBC Template数据库连接池1. 概念:其实就是一个容器(集合),存放数据库连接的容器。 当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。 2. 好处: 1. 节约资源 2. 用户访问高效 3. 实现: 1. 标准接口:DataSource javax.sql包下的 1. 方法: * 获取连接:getConnection() * 归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接 2. 一般我们不去实现它,有数据库厂商来实现 1. C3P0:数据库连接池技术 2. Druid:数据库连接池实现技术,由阿里巴巴提供的 4. C3P0:数据库连接池技术 * 步骤: 1. 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar , * 不要忘记导入数据库驱动jar包 2. 定义配置文件: * 名称: c3p0.properties 或者 c3p0-config.xml * 路径:直接将文件放在src目录下即可。 3. 创建核心对象 数据库连接池对象 ComboPooledDataSource 4. 获取连接: getConnection * 代码: //1.创建数据库连接池对象 DataSource ds = new ComboPooledDataSource(); //2. 获取连接对象 Connection conn = ds.getConnection(); 5. Druid:数据库连接池实现技术,由阿里巴巴提供的 1. 步骤: 1. 导入jar包 druid-1.0.9.jar 2. 定义配置文件: * 是properties形式的 * 可以叫任意名称,可以放在任意目录下 3. 加载配置文件。Properties 4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory 5. 获取连接:getConnection * 代码: //3.加载配置文件 Properties pro = new Properties(); InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); //4.获取连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(pro); //5.获取连接 Connection conn = ds.getConnection(); 2. 定义工具类 1. 定义一个类 JDBCUtils 2. 提供静态代码块加载配置文件,初始化连接池对象 3. 提供方法 1. 获取连接方法:通过数据库连接池获取连接 2. 释放资源 3. 获取连接池的方法 * 代码: public class JDBCUtils { //1.定义成员变量 DataSource private static DataSource ds ; static{ try { //1.加载配置文件 Properties pro = new Properties(); pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties")); //2.获取DataSource ds = DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 获取连接 */ public static Connection getConnection() throws SQLException { return ds.getConnection(); } /** * 释放资源 */ public static void close(Statement stmt,Connection conn){ /* if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } }*/ close(null,stmt,conn); }​ public static void close(ResultSet rs , Statement stmt, Connection conn){ ​ if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } ​ if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } } } /** * 获取连接池方法 */ public static DataSource getDataSource(){ return ds; } }Spring JDBC* Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发 * 步骤: 1. 导入jar包 2. 创建JdbcTemplate对象。依赖于数据源DataSource * JdbcTemplate template = new JdbcTemplate(ds); 3. 调用JdbcTemplate的方法来完成CRUD的操作 * update():执行DML语句。增、删、改语句 * queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合 * 注意:这个方法查询的结果集长度只能是1 * queryForList():查询结果将结果集封装为list集合 * 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中 * query():查询结果,将结果封装为JavaBean对象 * query的参数:RowMapper * 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装 * new BeanPropertyRowMapper<类型>(类型.class) * queryForObject:查询结果,将结果封装为对象 * 一般用于聚合函数的查询 4. 练习: * 需求: 1. 修改1号数据的 salary 为 10000 2. 添加一条记录 3. 删除刚才添加的记录 4. 查询id为1的记录,将其封装为Map集合 5. 查询所有记录,将其封装为List 6. 查询所有记录,将其封装为Emp对象的List集合 7. 查询总记录数 * 代码: import cn.itcast.domain.Emp; import cn.itcast.utils.JDBCUtils; import org.junit.Test; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import java.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Map; public class JdbcTemplateDemo2 { //Junit单元测试,可以让方法独立执行​ //1. 获取JDBCTemplate对象 private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); /** * 1. 修改1号数据的 salary 为 10000 */ @Test public void test1(){ //2. 定义sql String sql = "update emp set salary = 10000 where id = 1001"; //3. 执行sql int count = template.update(sql); System.out.println(count); } /** * 2. 添加一条记录 */ @Test public void test2(){ String sql = "insert into emp(id,ename,dept_id) values(?,?,?)"; int count = template.update(sql, 1015, "郭靖", 10); System.out.println(count); } /** * 3.删除刚才添加的记录 */ @Test public void test3(){ String sql = "delete from emp where id = ?"; int count = template.update(sql, 1015); System.out.println(count); } /** * 4.查询id为1001的记录,将其封装为Map集合 * 注意:这个方法查询的结果集长度只能是1 */ @Test public void test4(){ String sql = "select * from emp where id = ? or id = ?"; Map<String, Object> map = template.queryForMap(sql, 1001,1002); System.out.println(map); //{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20} } /** * 5. 查询所有记录,将其封装为List */ @Test public void test5(){ String sql = "select * from emp"; List<Map<String, Object>> list = template.queryForList(sql); for (Map<String, Object> stringObjectMap : list) { System.out.println(stringObjectMap); } } /** * 6. 查询所有记录,将其封装为Emp对象的List集合 */ @Test public void test6(){ String sql = "select * from emp"; List<Emp> list = template.query(sql, new RowMapper<Emp>() { @Override public Emp mapRow(ResultSet rs, int i) throws SQLException { Emp emp = new Emp(); int id = rs.getInt("id"); String ename = rs.getString("ename"); int job_id = rs.getInt("job_id"); int mgr = rs.getInt("mgr"); Date joindate = rs.getDate("joindate"); double salary = rs.getDouble("salary"); double bonus = rs.getDouble("bonus"); int dept_id = rs.getInt("dept_id"); emp.setId(id); emp.setEname(ename); emp.setJob_id(job_id); emp.setMgr(mgr); emp.setJoindate(joindate); emp.setSalary(salary); emp.setBonus(bonus); emp.setDept_id(dept_id); return emp; } });​ for (Emp emp : list) { System.out.println(emp); } } /** * 6. 查询所有记录,将其封装为Emp对象的List集合 */ @Test public void test6_2(){ String sql = "select * from emp"; List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class)); for (Emp emp : list) { System.out.println(emp); } } /** * 7. 查询总记录数 */ @Test public void test7(){ String sql = "select count(id) from emp"; Long total = template.queryForObject(sql, Long.class); System.out.println(total); } }","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"连接池","slug":"连接池","permalink":"http://lqhao.gitee.io/tags/%E8%BF%9E%E6%8E%A5%E6%B1%A0/"},{"name":"Template","slug":"Template","permalink":"http://lqhao.gitee.io/tags/Template/"}]},{"title":"Java-JDBC","slug":"Java-JDBC","date":"2020-07-25T11:40:18.389Z","updated":"2020-07-25T11:41:32.945Z","comments":true,"path":"2020/07/25/ckd1l6zch0000y4ndh8csb75o/","link":"","permalink":"http://lqhao.gitee.io/2020/07/25/ckd1l6zch0000y4ndh8csb75o/","excerpt":"","text":"title: Java-JDBCdata: 2020-07-25 11:30:00tags: [Java,JDBC]id: 1category: Java 2. 1. JDBC基本概念 2. 快速入门 3. 对JDBC中各个接口和类详解JDBC:1. 概念:Java DataBase Connectivity Java 数据库连接, Java语言操作数据库 * JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。 2. 快速入门: * 步骤: 1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar 1.复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下 2.右键-->Add As Library 2. 注册驱动 3. 获取数据库连接对象 Connection 4. 定义sql 5. 获取执行sql语句的对象 Statement 6. 执行sql,接受返回结果 7. 处理结果 8. 释放资源 * 代码实现: //1. 导入驱动jar包 //2.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //3.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root"); //4.定义sql语句 String sql = "update account set balance = 500 where id = 1"; //5.获取执行sql的对象 Statement Statement stmt = conn.createStatement(); //6.执行sql int count = stmt.executeUpdate(sql); //7.处理结果 System.out.println(count); //8.释放资源 stmt.close(); conn.close(); 3. 详解各个对象: 1. DriverManager:驱动管理对象 * 功能: 1. 注册驱动:告诉程序该使用哪一个数据库驱动jar static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。 写代码使用: Class.forName("com.mysql.jdbc.Driver"); 通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块 static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } 注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。 2. 获取数据库连接: * 方法:static Connection getConnection(String url, String user, String password) * 参数: * url:指定连接的路径 * 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称 * 例子:jdbc:mysql://localhost:3306/db3 * 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称 * user:用户名 * password:密码 2. Connection:数据库连接对象 1. 功能: 1. 获取执行sql 的对象 * Statement createStatement() * PreparedStatement prepareStatement(String sql) 2. 管理事务: * 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务 * 提交事务:commit() * 回滚事务:rollback() 3. Statement:执行sql的对象 1. 执行sql 1. boolean execute(String sql) :可以执行任意的sql 了解 2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句 * 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。 3. ResultSet executeQuery(String sql) :执行DQL(select)语句 2. 练习: 1. account表 添加一条记录 2. account表 修改记录 3. account表 删除一条记录 代码: Statement stmt = null; Connection conn = null; try { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 定义sql String sql = "insert into account values(null,'王五',3000)"; //3.获取Connection对象 conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root"); //4.获取执行sql的对象 Statement stmt = conn.createStatement(); //5.执行sql int count = stmt.executeUpdate(sql);//影响的行数 //6.处理结果 System.out.println(count); if(count > 0){ System.out.println("添加成功!"); }else{ System.out.println("添加失败!"); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally { //stmt.close(); //7. 释放资源 //避免空指针异常 if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } 4. ResultSet:结果集对象,封装查询结果 * boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true * getXxx(参数):获取数据 * Xxx:代表数据类型 如: int getInt() , String getString() * 参数: 1. int:代表列的编号,从1开始 如: getString(1) 2. String:代表列名称。 如: getDouble("balance") * 注意: * 使用步骤: 1. 游标向下移动一行 2. 判断是否有数据 3. 获取数据 //循环判断游标是否是最后一行末尾。 while(rs.next()){ //获取数据 //6.2 获取数据 int id = rs.getInt(1); String name = rs.getString("name"); double balance = rs.getDouble(3); System.out.println(id + "---" + name + "---" + balance); } * 练习: * 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。 1. 定义Emp类 2. 定义方法 public List<Emp> findAll(){} 3. 实现方法 select * from emp; 5. PreparedStatement:执行sql的对象 1. SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题 1. 输入用户随便,输入密码:a' or 'a' = 'a 2. sql:select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a' 2. 解决sql注入问题:使用PreparedStatement对象来解决 3. 预编译的SQL:参数使用?作为占位符 4. 步骤: 1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar 2. 注册驱动 3. 获取数据库连接对象 Connection 4. 定义sql * 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?; 5. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql) 6. 给?赋值: * 方法: setXxx(参数1,参数2) * 参数1:?的位置编号 从1 开始 * 参数2:?的值 7. 执行sql,接受返回结果,不需要传递sql语句 8. 处理结果 9. 释放资源 5. 注意:后期都会使用PreparedStatement来完成增删改查的所有操作 1. 可以防止SQL注入 2. 效率更高抽取JDBC工具类 : JDBCUtils* 目的:简化书写 * 分析: 1. 注册驱动也抽取 2. 抽取一个方法获取连接对象 * 需求:不想传递参数(麻烦),还得保证工具类的通用性。 * 解决:配置文件 jdbc.properties url= user= password= 3. 抽取一个方法释放资源 * 代码实现: public class JDBCUtils { private static String url; private static String user; private static String password; private static String driver; /** * 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块 */ static{ //读取资源文件,获取值。 try { //1. 创建Properties集合类。 Properties pro = new Properties(); //获取src路径下的文件的方式--->ClassLoader 类加载器 ClassLoader classLoader = JDBCUtils.class.getClassLoader(); URL res = classLoader.getResource("jdbc.properties"); String path = res.getPath(); System.out.println(path);///D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties //2. 加载文件 // pro.load(new FileReader("D:\\\\IdeaProjects\\\\itcast\\\\day04_jdbc\\\\src\\\\jdbc.properties")); pro.load(new FileReader(path)); //3. 获取数据,赋值 url = pro.getProperty("url"); user = pro.getProperty("user"); password = pro.getProperty("password"); driver = pro.getProperty("driver"); //4. 注册驱动 Class.forName(driver); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }​​ /**​ * 获取连接​ * @return 连接对象​ */​ public static Connection getConnection() throws SQLException {​ return DriverManager.getConnection(url, user, password); } /** * 释放资源 * @param stmt * @param conn */ public static void close(Statement stmt,Connection conn){ if( stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if( conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }​​ /**​ * 释放资源​ * @param stmt​ * @param conn​ */​ public static void close(ResultSet rs,Statement stmt, Connection conn){​ if( rs != null){​ try {​ rs.close();​ } catch (SQLException e) {​ e.printStackTrace();​ }​ }​ if( stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if( conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } * 练习: * 需求: 1. 通过键盘录入用户名和密码 2. 判断用户是否登录成功 * select * from user where username = "" and password = ""; * 如果这个sql有查询结果,则成功,反之,则失败 * 步骤: 1. 创建数据库表 user CREATE TABLE USER( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32), PASSWORD VARCHAR(32) ); INSERT INTO USER VALUES(NULL,'zhangsan','123'); INSERT INTO USER VALUES(NULL,'lisi','234'); 2. 代码实现: public class JDBCDemo9 { public static void main(String[] args) { //1.键盘录入,接受用户名和密码 Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String username = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); //2.调用方法 boolean flag = new JDBCDemo9().login(username, password); //3.判断结果,输出不同语句 if(flag){ //登录成功 System.out.println("登录成功!"); }else{ System.out.println("用户名或密码错误!"); }​​ } ​​​ /*​ * 登录方法​ */​ public boolean login(String username ,String password){​ if(username == null || password == null){​ return false;​ }​ //连接数据库判断是否登录成功​ Connection conn = null;​ Statement stmt = null;​ ResultSet rs = null;​ //1.获取连接​ try {​ conn = JDBCUtils.getConnection();​ //2.定义sql​ String sql = “select * from user where username = ‘“+username+”‘ and password = ‘“+password+”‘ “;​ //3.获取执行sql的对象​ stmt = conn.createStatement();​ //4.执行查询​ rs = stmt.executeQuery(sql);​ //5.判断​ / if(rs.next()){//如果有下一行,则返回true​ return true;​ }else{​ return false;​ }*/​ return rs.next();//如果有下一行,则返回true​ } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtils.close(rs,stmt,conn); } ​​ return false;​ }​ } JDBC控制事务:1. 事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。 2. 操作: 1. 开启事务 2. 提交事务 3. 回滚事务 3. 使用Connection对象来管理事务 * 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务 * 在执行sql之前开启事务 * 提交事务:commit() * 当所有sql都执行完提交事务 * 回滚事务:rollback() * 在catch中回滚事务 4. 代码: public class JDBCDemo10 { public static void main(String[] args) { Connection conn = null; PreparedStatement pstmt1 = null; PreparedStatement pstmt2 = null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //开启事务 conn.setAutoCommit(false); //2.定义sql //2.1 张三 - 500 String sql1 = "update account set balance = balance - ? where id = ?"; //2.2 李四 + 500 String sql2 = "update account set balance = balance + ? where id = ?"; //3.获取执行sql对象 pstmt1 = conn.prepareStatement(sql1); pstmt2 = conn.prepareStatement(sql2); //4. 设置参数 pstmt1.setDouble(1,500); pstmt1.setInt(2,1); pstmt2.setDouble(1,500); pstmt2.setInt(2,2); //5.执行sql pstmt1.executeUpdate(); // 手动制造异常 int i = 3/0; pstmt2.executeUpdate(); //提交事务 conn.commit(); } catch (Exception e) { //事务回滚 try { if(conn != null) { conn.rollback(); } } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { JDBCUtils.close(pstmt1,conn); JDBCUtils.close(pstmt2,null); }​​ }​ } ​","categories":[],"tags":[]},{"title":"MySQL-约束","slug":"MySQL-约束","date":"2020-07-23T08:54:40.814Z","updated":"2020-07-23T08:56:10.402Z","comments":true,"path":"2020/07/23/2/","link":"","permalink":"http://lqhao.gitee.io/2020/07/23/2/","excerpt":"","text":"1. DQL:查询语句 1. 排序查询 2. 聚合函数 3. 分组查询 4. 分页查询 2. 约束 3. 多表之间的关系 4. 范式 5. 数据库的备份和还原DQL:查询语句1. 排序查询 * 语法:order by 子句 * order by 排序字段1 排序方式1 , 排序字段2 排序方式2... * 排序方式: * ASC:升序,默认的。 * DESC:降序。 * 注意: * 如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件。 2. 聚合函数:将一列数据作为一个整体,进行纵向的计算。 1. count:计算个数 1. 一般选择非空的列:主键 2. count(*) 2. max:计算最大值 3. min:计算最小值 4. sum:计算和 5. avg:计算平均值 * 注意:聚合函数的计算,排除null值。 解决方案: 1. 选择不包含非空的列进行计算 2. IFNULL函数 3. 分组查询: 1. 语法:group by 分组字段; 2. 注意: 1. 分组之后查询的字段:分组字段、聚合函数 2. where 和 having 的区别? 1. where 在分组之前进行限定,如果不满足条件,则不参与分组。having在分组之后进行限定,如果不满足结果,则不会被查询出来 2. where 后不可以跟聚合函数,having可以进行聚合函数的判断。 -- 按照性别分组。分别查询男、女同学的平均分 SELECT sex , AVG(math) FROM student GROUP BY sex; -- 按照性别分组。分别查询男、女同学的平均分,人数 SELECT sex , AVG(math),COUNT(id) FROM student GROUP BY sex; -- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组 SELECT sex , AVG(math),COUNT(id) FROM student WHERE math > 70 GROUP BY sex; -- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组,分组之后。人数要大于2个人 SELECT sex , AVG(math),COUNT(id) FROM student WHERE math > 70 GROUP BY sex HAVING COUNT(id) > 2; SELECT sex , AVG(math),COUNT(id) 人数 FROM student WHERE math > 70 GROUP BY sex HAVING 人数 > 2;​​ 4. 分页查询​ 1. 语法:limit 开始的索引,每页查询的条数;​ 2. 公式:开始的索引 = (当前的页码 - 1) * 每页显示的条数​ – 每页显示3条记录​​ SELECT * FROM student LIMIT 0,3; – 第1页​ SELECT * FROM student LIMIT 3,3; – 第2页 SELECT * FROM student LIMIT 6,3; -- 第3页 3. limit 是一个MySQL"方言"约束* 概念: 对表中的数据进行限定,保证数据的正确性、有效性和完整性。 * 分类: 1. 主键约束:primary key 2. 非空约束:not null 3. 唯一约束:unique 4. 外键约束:foreign key * 非空约束:not null,值不能为null 1. 创建表时添加约束 CREATE TABLE stu( id INT, NAME VARCHAR(20) NOT NULL -- name为非空 ); 2. 创建表完后,添加非空约束 ALTER TABLE stu MODIFY NAME VARCHAR(20) NOT NULL; 3. 删除name的非空约束 ALTER TABLE stu MODIFY NAME VARCHAR(20);​​ * 唯一约束:unique,值不能重复​ 1. 创建表时,添加唯一约束​ CREATE TABLE stu(​ id INT,​ phone_number VARCHAR(20) UNIQUE – 添加了唯一约束​​ );​ * 注意mysql中,唯一约束限定的列的值可以有多个null ​​ 2. 删除唯一约束​​ ALTER TABLE stu DROP INDEX phone_number;​ 3. 在创建表后,添加唯一约束 ALTER TABLE stu MODIFY phone_number VARCHAR(20) UNIQUE; * 主键约束:primary key。 1. 注意: 1. 含义:非空且唯一 2. 一张表只能有一个字段为主键 3. 主键就是表中记录的唯一标识 2. 在创建表时,添加主键约束 create table stu( id int primary key,-- 给id添加主键约束 name varchar(20) ); 3. 删除主键 -- 错误 alter table stu modify id int ; ALTER TABLE stu DROP PRIMARY KEY; 4. 创建完表后,添加主键 ALTER TABLE stu MODIFY id INT PRIMARY KEY; 5. 自动增长: 1. 概念:如果某一列是数值类型的,使用 auto_increment 可以来完成值得自动增长 2. 在创建表时,添加主键约束,并且完成主键自增长 create table stu( id int primary key auto_increment,-- 给id添加主键约束 name varchar(20) );​​ 3. 删除自动增长​ ALTER TABLE stu MODIFY id INT;​ 4. 添加自动增长​ ALTER TABLE stu MODIFY id INT AUTO_INCREMENT; * 外键约束:foreign key,让表于表产生关系,从而保证数据的正确性。 1. 在创建表时,可以添加外键 * 语法: create table 表名( .... 外键列 constraint 外键名称 foreign key (外键列名称) references 主表名称(主表列名称) ); 2. 删除外键 ALTER TABLE 表名 DROP FOREIGN KEY 外键名称; 3. 创建表之后,添加外键 ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称);​​ 4. 级联操作​ 1. 添加级联操作​ 语法:ALTER TABLE 表名 ADD CONSTRAINT 外键名称​ FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称) ON UPDATE CASCADE ON DELETE CASCADE ;​ 2. 分类:​ 1. 级联更新:ON UPDATE CASCADE​ 2. 级联删除:ON DELETE CASCADE 数据库的设计1. 多表之间的关系 1. 分类: 1. 一对一(了解): * 如:人和身份证 * 分析:一个人只有一个身份证,一个身份证只能对应一个人 2. 一对多(多对一): * 如:部门和员工 * 分析:一个部门有多个员工,一个员工只能对应一个部门 3. 多对多: * 如:学生和课程 * 分析:一个学生可以选择很多门课程,一个课程也可以被很多学生选择 2. 实现关系: 1. 一对多(多对一): * 如:部门和员工 * 实现方式:在多的一方建立外键,指向一的一方的主键。 2. 多对多: * 如:学生和课程 * 实现方式:多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键 3. 一对一(了解): * 如:人和身份证 * 实现方式:一对一关系实现,可以在任意一方添加唯一外键指向另一方的主键。 3. 案例 -- 创建旅游线路分类表 tab_category -- cid 旅游线路分类主键,自动增长 -- cname 旅游线路分类名称非空,唯一,字符串 100 CREATE TABLE tab_category ( cid INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(100) NOT NULL UNIQUE ); -- 创建旅游线路表 tab_route /* rid 旅游线路主键,自动增长 rname 旅游线路名称非空,唯一,字符串 100 price 价格 rdate 上架时间,日期类型 cid 外键,所属分类 */ CREATE TABLE tab_route( rid INT PRIMARY KEY AUTO_INCREMENT, rname VARCHAR(100) NOT NULL UNIQUE, price DOUBLE, rdate DATE, cid INT, FOREIGN KEY (cid) REFERENCES tab_category(cid) ); /*创建用户表 tab_user uid 用户主键,自增长 username 用户名长度 100,唯一,非空 password 密码长度 30,非空 name 真实姓名长度 100 birthday 生日 sex 性别,定长字符串 1 telephone 手机号,字符串 11 email 邮箱,字符串长度 100 */ CREATE TABLE tab_user ( uid INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(100) UNIQUE NOT NULL, PASSWORD VARCHAR(30) NOT NULL, NAME VARCHAR(100), birthday DATE, sex CHAR(1) DEFAULT '男', telephone VARCHAR(11), email VARCHAR(100) ); /* 创建收藏表 tab_favorite rid 旅游线路 id,外键 date 收藏时间 uid 用户 id,外键 rid 和 uid 不能重复,设置复合主键,同一个用户不能收藏同一个线路两次 */ CREATE TABLE tab_favorite ( rid INT, -- 线路id DATE DATETIME, uid INT, -- 用户id -- 创建复合主键 PRIMARY KEY(rid,uid), -- 联合主键 FOREIGN KEY (rid) REFERENCES tab_route(rid), FOREIGN KEY(uid) REFERENCES tab_user(uid) );​​ 2. 数据库设计的范式​ * 概念:设计数据库时,需要遵循的一些规范。要遵循后边的范式要求,必须先遵循前边的所有范式要求​​ 设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。​ 目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。​ * 分类: 1. 第一范式(1NF):每一列都是不可分割的原子数据项 2. 第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于码(在1NF基础上消除非主属性对主码的部分函数依赖) * 几个概念: 1. 函数依赖:A–>B,如果通过A属性(属性组)的值,可以确定唯一B属性的值。则称B依赖于A 例如:学号–>姓名。 (学号,课程名称) –> 分数 2. 完全函数依赖:A–>B, 如果A是一个属性组,则B属性值得确定需要依赖于A属性组中所有的属性值。 例如:(学号,课程名称) –> 分数 3. 部分函数依赖:A–>B, 如果A是一个属性组,则B属性值得确定只需要依赖于A属性组中某一些值即可。 例如:(学号,课程名称) – > 姓名 4. 传递函数依赖:A–>B, B – >C . 如果通过A属性(属性组)的值,可以确定唯一B属性的值,在通过B属性(属性组)的值可以确定唯一C属性的值,则称 C 传递函数依赖于A 例如:学号–>系名,系名–>系主任 5. 码:如果在一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性组)为该表的码 例如:该表中码为:(学号,课程名称) * 主属性:码属性组中的所有属性 * 非主属性:除过码属性组的属性 3. 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)数据库的备份和还原1. 命令行: * 语法: * 备份: mysqldump -u用户名 -p密码 数据库名称 > 保存的路径 * 还原: 1. 登录数据库 2. 创建数据库 3. 使用数据库 4. 执行文件。source 文件路径 2. 图形化工具:","categories":[{"name":"MySQL","slug":"MySQL","permalink":"http://lqhao.gitee.io/categories/MySQL/"}],"tags":[{"name":"MySQL","slug":"MySQL","permalink":"http://lqhao.gitee.io/tags/MySQL/"}]},{"title":"MySQL-基础","slug":"MySQL-基础","date":"2020-07-23T03:22:21.092Z","updated":"2020-07-23T03:23:54.734Z","comments":true,"path":"2020/07/23/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/23/1/","excerpt":"","text":"数据库 数据库的基本概念 MySQL数据库软件 安装 卸载 配置 SQL 数据库的基本概念1. 数据库的英文单词: DataBase 简称 : DB 2. 什么数据库? * 用于存储和管理数据的仓库。 3. 数据库的特点: 1. 持久化存储数据的。其实数据库就是一个文件系统 2. 方便存储和管理数据 3. 使用了统一的方式操作数据库 -- SQL​​ 4. 常见的数据库软件​ * 参见《MySQL基础.pdf》 MySQL数据库软件1. 安装 * 参见《MySQL基础.pdf》 2. 卸载 1. 去mysql的安装目录找到my.ini文件 * 复制 datadir="C:/ProgramData/MySQL/MySQL Server 5.5/Data/" 2. 卸载MySQL 3. 删除C:/ProgramData目录下的MySQL文件夹。 3. 配置 * MySQL服务启动 1. 手动。 2. cmd--> services.msc 打开服务的窗口 3. 使用管理员打开cmd * net start mysql : 启动mysql的服务 * net stop mysql:关闭mysql服务 * MySQL登录 1. mysql -uroot -p密码 2. mysql -hip -uroot -p连接目标的密码 3. mysql --host=ip --user=root --password=连接目标的密码 * MySQL退出 1. exit 2. quit * MySQL目录结构 1. MySQL安装目录:basedir="D:/develop/MySQL/" * 配置文件 my.ini 2. MySQL数据目录:datadir="C:/ProgramData/MySQL/MySQL Server 5.5/Data/" * 几个概念 * 数据库:文件夹 * 表:文件 * 数据:数据SQL1.什么是SQL? Structured Query Language:结构化查询语言 其实就是定义了操作所有关系型数据库的规则。每一种数据库操作的方式存在不一样的地方,称为“方言”。 2.SQL通用语法 1. SQL 语句可以单行或多行书写,以分号结尾。 2. 可使用空格和缩进来增强语句的可读性。 3. MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写。 4. 3 种注释 * 单行注释: -- 注释内容 或 # 注释内容(mysql 特有) * 多行注释: /* 注释 */ 3. SQL分类 1) DDL(Data Definition Language)数据定义语言 用来定义数据库对象:数据库,表,列等。关键字:create, drop,alter 等 2) DML(Data Manipulation Language)数据操作语言 用来对数据库中表的数据进行增删改。关键字:insert, delete, update 等 3) DQL(Data Query Language)数据查询语言 用来查询数据库中表的记录(数据)。关键字:select, where 等 4) DCL(Data Control Language)数据控制语言(了解) 用来定义数据库的访问权限和安全级别,及创建用户。关键字:GRANT, REVOKE 等DDL:操作数据库、表1. 操作数据库:CRUD 1. C(Create):创建 * 创建数据库: * create database 数据库名称; * 创建数据库,判断不存在,再创建: * create database if not exists 数据库名称; * 创建数据库,并指定字符集 * create database 数据库名称 character set 字符集名; * 练习: 创建db4数据库,判断是否存在,并制定字符集为gbk * create database if not exists db4 character set gbk; 2. R(Retrieve):查询 * 查询所有数据库的名称: * show databases; * 查询某个数据库的字符集:查询某个数据库的创建语句 * show create database 数据库名称; 3. U(Update):修改 * 修改数据库的字符集 * alter database 数据库名称 character set 字符集名称; 4. D(Delete):删除 * 删除数据库 * drop database 数据库名称; * 判断数据库存在,存在再删除 * drop database if exists 数据库名称; 5. 使用数据库 * 查询当前正在使用的数据库名称 * select database(); * 使用数据库 * use 数据库名称; 2. 操作表 1. C(Create):创建 1. 语法: create table 表名( 列名1 数据类型1, 列名2 数据类型2, .... 列名n 数据类型n ); * 注意:最后一列,不需要加逗号(,) * 数据库类型: 1. int:整数类型 * age int, 2. double:小数类型 * score double(5,2) 3. date:日期,只包含年月日,yyyy-MM-dd 4. datetime:日期,包含年月日时分秒 yyyy-MM-dd HH:mm:ss 5. timestamp:时间错类型 包含年月日时分秒 yyyy-MM-dd HH:mm:ss * 如果将来不给这个字段赋值,或赋值为null,则默认使用当前的系统时间,来自动赋值 6. varchar:字符串 * name varchar(20):姓名最大20个字符 * zhangsan 8个字符 张三 2个字符 * 创建表 create table student( id int, name varchar(32), age int , score double(4,1), birthday date, insert_time timestamp ); * 复制表: * create table 表名 like 被复制的表名; 2. R(Retrieve):查询 * 查询某个数据库中所有的表名称 * show tables; * 查询表结构 * desc 表名; 3. U(Update):修改 1. 修改表名 alter table 表名 rename to 新的表名; 2. 修改表的字符集 alter table 表名 character set 字符集名称; 3. 添加一列 alter table 表名 add 列名 数据类型; 4. 修改列名称 类型 alter table 表名 change 列名 新列别 新数据类型; alter table 表名 modify 列名 新数据类型; 5. 删除列 alter table 表名 drop 列名; 4. D(Delete):删除 * drop table 表名; * drop table if exists 表名 ; 客户端图形化工具:SQLYog DML:增删改表中数据1. 添加数据: * 语法: * insert into 表名(列名1,列名2,...列名n) values(值1,值2,...值n); * 注意: 1. 列名和值要一一对应。 2. 如果表名后,不定义列名,则默认给所有列添加值 insert into 表名 values(值1,值2,...值n); 3. 除了数字类型,其他类型需要使用引号(单双都可以)引起来 2. 删除数据: * 语法: * delete from 表名 [where 条件] * 注意: 1. 如果不加条件,则删除表中所有记录。 2. 如果要删除所有记录 1. delete from 表名; -- 不推荐使用。有多少条记录就会执行多少次删除操作 2. TRUNCATE TABLE 表名; -- 推荐使用,效率更高 先删除表,然后再创建一张一样的表。 3. 修改数据: * 语法: * update 表名 set 列名1 = 值1, 列名2 = 值2,... [where 条件]; * 注意: 1. 如果不加任何条件,则会将表中所有记录全部修改。DQL:查询表中的记录* select * from 表名; 1. 语法: select 字段列表 from 表名列表 where 条件列表 group by 分组字段 having 分组之后的条件 order by 排序 limit 分页限定 2. 基础查询 1. 多个字段的查询 select 字段名1,字段名2... from 表名; * 注意: * 如果查询所有字段,则可以使用*来替代字段列表。 2. 去除重复: * distinct 3. 计算列 * 一般可以使用四则运算计算一些列的值。(一般只会进行数值型的计算) * ifnull(表达式1,表达式2):null参与的运算,计算结果都为null * 表达式1:哪个字段需要判断是否为null * 如果该字段为null后的替换值。 4. 起别名: * as:as也可以省略 3. 条件查询 1. where子句后跟条件 2. 运算符 * > 、< 、<= 、>= 、= 、<> * BETWEEN...AND * IN( 集合) * LIKE:模糊查询 * 占位符: * _:单个任意字符 * %:多个任意字符 * IS NULL * and 或 && * or 或 || * not 或 ! -- 查询年龄大于20岁 SELECT * FROM student WHERE age > 20; SELECT * FROM student WHERE age >= 20; -- 查询年龄等于20岁 SELECT * FROM student WHERE age = 20; -- 查询年龄不等于20岁 SELECT * FROM student WHERE age != 20; SELECT * FROM student WHERE age <> 20; -- 查询年龄大于等于20 小于等于30 SELECT * FROM student WHERE age >= 20 && age <=30; SELECT * FROM student WHERE age >= 20 AND age <=30; SELECT * FROM student WHERE age BETWEEN 20 AND 30; -- 查询年龄22岁,18岁,25岁的信息 SELECT * FROM student WHERE age = 22 OR age = 18 OR age = 25 SELECT * FROM student WHERE age IN (22,18,25); -- 查询英语成绩为null SELECT * FROM student WHERE english = NULL; -- 不对的。null值不能使用 = (!=) 判断 SELECT * FROM student WHERE english IS NULL; -- 查询英语成绩不为null SELECT * FROM student WHERE english IS NOT NULL; -- 查询姓马的有哪些? like SELECT * FROM student WHERE NAME LIKE '马%'; -- 查询姓名第二个字是化的人 SELECT * FROM student WHERE NAME LIKE "_化%"; -- 查询姓名是3个字的人 SELECT * FROM student WHERE NAME LIKE '___'; -- 查询姓名中包含德的人 SELECT * FROM student WHERE NAME LIKE '%德%';","categories":[{"name":"MySQL","slug":"MySQL","permalink":"http://lqhao.gitee.io/categories/MySQL/"}],"tags":[{"name":"MySQL","slug":"MySQL","permalink":"http://lqhao.gitee.io/tags/MySQL/"}]},{"title":"Java-反射、注解","slug":"Java-反射、注解","date":"2020-07-22T12:08:41.178Z","updated":"2020-07-22T13:41:04.936Z","comments":true,"path":"2020/07/22/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/22/1/","excerpt":"","text":"Junit测试: 测试分类 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值 白盒测试:需要写代码.关注程序具体的执行流程 Junit使用: 白盒测试 步骤 定义一个测试类(测试用例) 建议: 测试类名使用被测试类+Test 包名:xxx.xxx.xxx.test 定义测试方法: 可以独立运行 建议: 方法名: test+测试方法名 返回值: void 参数列表: 空参 给方法加注解@Test 点击小灯泡导入Jnuit环境依赖 判定结果 红色: 失败 绿色: 成功 一般使用断言操作处理结果,Assert类中的assertXXX方法 assertEquals(期望的结果,程序运算的结果),若两个数不同显示红色 注解@Before 被修饰的方法会在测试方法之前被自动执行,常用于初始化资源 注解@After 被修饰的方法会在测试方法之后被自动执行,常用于释放资源 反射框架设计的灵魂 反射:将类的各个部分封装成其他对象,这就是反射机制 好处: 可以在程序的运行过程中,操作这些对象 可以解耦,提高程序的可扩展性 获取class类对象的三种方式 Class.forName(“全类名”) : 将字节码文件加载进内存,返回class 多用于配置文件,将类名可以定义在配置文件中,来读取文件加载类 通过类名的属性class来获取 多用于参数的传递 对象.getclass() : 这个方法在Object类中定义的 多由于获取对象的字节码的方式 123456789101112131415public static void main(String[] args) throws Exception { //1.Class.forName(\"全类名\") : 将字节码文件加载进内存,返回class Class cls1 = Class.forName(\"domain.Person\"); System.out.println(cls1); //2.通过类名的属性class来获取 Class<Person> cls2 = Person.class; System.out.println(cls2); //对象.getclass() Person p = new Person(); Class cls3 = p.getClass(); System.out.println(cls3); //==比较三个对象 System.out.println(cls1==cls2);//true System.out.println(cls2==cls3);//true} 结论: 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪种方式获取的Class对象,都是同一个 Class对象的方法 获取的功能 获取成员变量们 Filed[] getFileds() 获取所有public修饰的成员变量 Field: 成员变量 对指定obj对象设置value值 void set(Object obj, Object value) 对指定obj对象获取值 Object get(Object obj) 忽略访问权限修饰符安全的检查 setAccessible(true) 暴力反射 Field getField(String name) 获取指定名称的public修饰的成员变量 Field[] getDeclaredFields() 获取所有成员变量,不考虑修饰符,且能设置所有成员变量 Field getDeclaredFields(String name) 获取单个成员变量,不考虑修饰符,且能设置该成员变量的值 获取构造方法们 Constructor getConstructor(Class<?>… parameterTypes) Constructor:构造方法: T newInstance(Object… initargs) 用来创建对应的对象 如果构造使用空参构造来构造对象,操作可以简化:使用Class中的newInstance()方法来创建对象 Constructor<?>[] getConstructors() Constructor getDeclaredConstructor(Class<?>… parameterTypes) Constructor<?>[] getDeclaredConstructors() 获取成员方法们 Method getMethod(String name, Class<?>… parameterTypes) Method:方法对象 Object invoke(Object obj, Object… args) 执行方法 String getName() 获取方法名 Method[] getMethods() Method getDeclaredMethod(String name, Class<?>… parameterTypes) Method[] getDeclaredMethods() 获取类名 String getName() 123456789101112131415161718192021222324252627//获取成员变量们public static void main(String[] args) throws Exception { //获取Person的Class对象 Class personClass = Person.class; //获取成员变量们 Field[] fields = personClass.getFields(); for (Field field : fields) { System.out.println(field); } Field a = personClass.getField(\"a\"); Person p = new Person(); //用set()给p对象的a设置变量 a.set(p,\"123\"); //用get()获取p对象的a值 Object value = a.get(p); System.out.println(value); Field[] declaredFields = personClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } //可以获取私有成员变量,且设置成员变量 Field d = personClass.getDeclaredField(\"d\"); //忽略访问权限修饰符安全的检查 d.setAccessible(true); d.set(p,\"aaa\"); System.out.println(p); } 12345678910111213141516//获取构造方法们public static void main(String[] args) throws Exception { //获取Person的Class对象 Class personClass = Person.class; //Constructor<T> getConstructor(Class<?>... parameterTypes) Constructor constructor = personClass.getConstructor(String.class, int.class); //使用newInstance(Object... initargs) 用来构造带参的对象 Object objperson = constructor.newInstance(\"zhangsan\", 23); System.out.println(objperson); //使用newInstance(Object... initargs) 用来构造空参的对象 Object o1 = constructor.newInstance(); System.out.println(o1); //直接使用Class对象构造空参对象 Object o = personClass.newInstance(); System.out.println(o);} 12345678910111213141516171819202122232425//获取成员方法们public static void main(String[] args) throws Exception { //获取Person的Class对象 Class personClass = Person.class; //获取空参成员方法 Method eat_method = personClass.getMethod(\"eat\"); //创建一个对象 Person p =new Person(); //执行对象中的eat方法 eat_method.invoke(p); //获取带参成员方法 Method eat_method2 = personClass.getMethod(\"eat\", String.class); //执行带参成员方法 eat_method2.invoke(p,\"\"); //获取所有public修饰方法 Method[] methods = personClass.getMethods(); for (Method method : methods) { System.out.println(method); } //获取类名 String className = personClass.getName(); //包名.类名 System.out.println(className);} 案例: 需求: 写一个”框架”,不改变该类的任何代码的情况下,可以帮我们创建任意类的对象,并且执行任意的方法 实现: 配置文件 反射 步骤 将需要创建的对象的全类名和需要执行的方法定义载配置文件中 在程序中加载读取配置文件 使用反射技术来加载类文件进内存 创建对象 执行方法 12345678910111213141516171819202122232425/*框架类 */public class ReflectTest { public static void main(String[] args) throws Exception { /* 不能改变该类的任何代码,可以创建任意类的对象,可以执行任意方法 */ //创建Properties对象 Properties pro = new Properties(); //加载配置文件,转换为集合 //获取文件class目录下的配置文件 ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream(\"properties\"); pro.load(is); //获取配置文件中定义的数据 String className = pro.getProperty(\"className\"); String methodName = pro.getProperty(\"methodName\"); //加载该类进内存 Class<?> cls = Class.forName(className); Object obj = cls.newInstance(); Method method = cls.getMethod(methodName); method.invoke(obj); }} 注解 概念描述: 说明程序的.给计算机看的 JDK1.5之后的新特性 使用注解: @注解名称 作用分类 编写文档: 通过代码里标识的注解生成文档[生成doc文档] 代码分析: 通过代码里标识的元数据对代码进行分析[使用反射] 编译检查: 通过代码里标识的元数据让编译器能实现基本的编译检查[Override] JDK中预定义的一些注解 @Override: 检查被改注解标注的方法是否是继承自父类/接口的 @Deprecated: 该注解标注的内容,已过时 @SuppressWamings(“all”): 压制所有警告,一般传递参数all 自定义注解 格式: 1234元注解public @interface 注解名{ //属性列表} 本质 123//注解本质上就是一个接口,该接口默认继承Annotation接口public interface MyAnno extends java.lang.annotation.Annotation {} 属性:接口中的抽象方法 123456789101112131415161718192021222324252627282930313233343536public @interface MyAnno { //称这个抽象方法为注解的属性 public abstract String show();}要求: 1.属性的返回值类型可以有 基本数据类型,String,枚举,注解,以上类型的数组,其他均不行包括void public @interface MyAnno { int age();//返回值为基本数据类型 String name();//返回值为String String name() default \"张三\";//返回值为String,默认赋值为张三 Test_enum show3();//返回值为枚举类型 MyAnno2 show4();//返回值为注解 String[] show5();//返回值为以上类型数组 } public enum Test_enum{ p1,p2; } public @interface MyAnno2{ } 2.定义了属性,在使用时需要给属性赋值 @MyAnno(age = 12,name=\"张三\") public class Test { } 3.定义属性时,使用default关键字给属性默认初始化,则取用默认值 4.如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接写值即可 public @interface MyAnno { int value();//属性名称为value,使用时直接写值即可 } @MyAnno(12) public class Test { } 5.枚举数组赋值,注解类型赋值,数组类型赋值(数组中只有一个值,则{}可以省略) @MyAnno(show3=Test_enum.p1,show4=@MyAnno2,show5={\"aa\",\"bb\",\"cc\"}) public class Test { } 元注解: 用于描述注解的注解 @Target: 描述注解能够作用的位置 ElementType的取值 TYPE: 表示注解只能作用于类上 METHOD: 表示该注解只能作用于方法上 FIELD: 表示该注解只能作用于成员变量上 @Retention: 描述注解被保留的阶段 @Retention(RetentionPolicy.RUNTIME):表示当前被描述的注解会保留到class字节码文件中,并被JVM读取到 @Documented: 描述注解是否被抽取到api文档中 @Inherited: 描述注解是否被子类继承,加表示继承 12345678910//表示该MyAnno3注解可以作用于类上,方法上,成员变量上@Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//表示当前被描述的注解会保留到class字节码文件中,并被JVM读取到@Retention(RetentionPolicy.RUNTIME)//表示该注解可以被保留到doc文档中@Documented//表示该注解会自动被子类继承@Inheritedpublic @interface MyAnno3 {} 在程序中使用(解析)注解:获取注解中定义的属性值 获取注解使用位置的对象 (Class,Method,Filed) 获取指定的注解 getAnnotation(Class) 调用注解中的抽象方法获取配置的属性值 12345678910111213141516171819202122232425262728293031@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Pro { String className(); String methodName();}@Pro(className = \"domain.Person\",methodName = \"eat\")public class ReflectTest { public static void main(String[] args) throws Exception { /* 不能改变该类的任何代码,可以创建任意类的对象,可以执行任意方法 */ //1.解析注解 //获取该类的字节码文件对象 Class<ReflectTest> reflectTestClass = ReflectTest.class; //2.获取上边的注解对象 // 其实就是在内存中生成了一个该注解接口的子类实现对象 Pro an = reflectTestClass.getAnnotation(Pro.class); //3.调用注解对象中定义的抽象方法,获取返回值 String className = an.className(); String methodName = an.methodName(); //加载该类进内存 Class cls = Class.forName(className); Object obj = cls.newInstance(); Method method = cls.getMethod(methodName); method.invoke(obj); }} 小结: 大多数时候,会使用注解,而不是自定义注解 注解给编译器,给解析程序用 注解并不是程序的一部分,可以理解为注解就是一个标签","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"注解","slug":"注解","permalink":"http://lqhao.gitee.io/tags/%E6%B3%A8%E8%A7%A3/"},{"name":"反射","slug":"反射","permalink":"http://lqhao.gitee.io/tags/%E5%8F%8D%E5%B0%84/"}]},{"title":"Java-Stream流","slug":"Java-Stream流","date":"2020-07-20T07:50:13.103Z","updated":"2020-07-22T14:28:22.676Z","comments":true,"path":"2020/07/20/2/","link":"","permalink":"http://lqhao.gitee.io/2020/07/20/2/","excerpt":"","text":"Stream流用于解决已有集合类库既有的弊端,用来对集合和数组进行简化操作 当使用一个流的时候,通常包含三个基本步骤:获取一个数据源(source)->数据转换->执行操作获取想要的结果->每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道 12345678910111213141516171819202122/* * Stream是1.8之后出现的,关注的是做什么,而不是怎么做 * 过滤集合,只要张开头,长度为3的 * */public class TestStream { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add(\"张无忌\"); list.add(\"周芷若\"); list.add(\"赵敏\"); list.add(\"张强\"); list.add(\"张三丰\"); //只要张开头, //长度为3的 list.stream() .filter(name -> name.startsWith(\"\")) .filter(name -> name.length() == 3) .forEach((name)-> System.out.println(name)); }} 获取流 所有的Collection集合都可以通过stream默认方法获取流 Stream接口的静态方法of可以获取数组对应的流,of方法的参数是一个可变参数,可以传递数组 1234567891011121314151617181920212223public static void main(String[] args) { //list集合->stream流 List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream(); //set集合->stream流 Set<String> set = new HashSet<>(); Stream<String> s = set.stream(); //map集合间接->stream流 Map<String,String> map = new HashMap<>(); //先获取键集合 Set<String> keySet = map.keySet(); Stream<String> stream3 = keySet.stream(); //获取值集合 Collection<String> values = map.values(); Stream<String> stream4 = values.stream(); //map集合获取键值对(键值映射关系) Set<Map.Entry<String, String>> entries = map.entrySet(); Stream<Map.Entry<String, String>> stream5 = entries.stream(); //数组->stream流 Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5, 7); int[] arr = {1,2,3,4,5}; Stream<int[]> stream7 = Stream.of(arr);} 常用方法 延迟方法:返回值类型任然是Stream接口自身类型的方法,支持链式调用 filter()方法 将一个流转换成另一个流 map()映射方法 可以将当前流中类型数据转换称为另一种类型的数据 limit()方法 截取集合元素的前几个,返回一个新的流 skip()方法 跳过前几个元素,返回一个新的流,若长度大于流长,则返回一个空流 终结方法:返回值类型不再是是Stream接口自身类型的方法 count() 来统计流中元素的个数 forEach() 用来遍历流中的数据 静态方法 concat() 将两个同类型的流合并成为一个流 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061/* forEach()方法 void forEach(Consumer<? super T> action); 用来遍历流中的数据,是一个终结方法 该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理 Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据 filter()方法 Stream<T> filter(Predicate<? super T> Predicate) 将一个流转换成另一个流 该方法接收一个Predicate接口函数,会将每一个流元素交给该函数进行判断 stream属于管道流,只能消费使用一次,第一个stream流调用完毕方法,数据就会流转到下一个stream,流 而这是第一个stream流已经关闭,不能再使用方法 map()映射方法 <R> Stream map(Function<? super T,? extends R> mapper); 可以将当前流中的T类型数据转换称为另一种R类型的数据 count()方法 stream提供count方法用来统计数一数其中元素的个数,是一个终结方法,返回值为long类型 limit()方法 Stream<T> limit(long maxsize) 延迟方法,截取集合元素的前几个,返回一个新的流 skip()方法 Stream<T> skip(long n); 跳过前几个元素,返回一个新的流,若长度大于流长,则返回一个空流 静态方法concat() Static<T> Stream<T> concat(Stream<? extends T> a, Stream<? extends ?> b); 将两个同类型的流合并成为一个流 */public static void main(String[] args){ //获取一个stream流 Stream<String> stream1 = Stream.of(\"123\", \"111\", \"222\",\"789\",\"147\",\"1158\",\"1369\"); //使用filter对stream流中的元素进行过滤,只保留1开头数据,返回一个新的流 Stream<String> stream2 = stream1.filter(name -> name.startsWith(\"1\")); //使用map方法对stream流中的元素进行转换String整数转换(映射)为int类型 Stream<Integer> stream3 = stream2.map(name -> Integer.parseInt(name)); //使用limit()方法对stream流中的元素进行截取,截取前三个元素 Stream<Integer> stream4 = stream3.limit(3); //使用skip()方法对stream流中的元素进行跳过,跳过前两个 Stream<Integer> stream5 = stream4.skip(2); //统计stream中元素的个数,是终结方法,不能与forEach同用 //System.out.println(stream3.count()); //对流中的数据进行遍历 stream5.forEach(name-> System.out.println(name)); //stream1.forEach(name-> System.out.println(name));//报异常,stream1已经关闭 Stream<String> stream6 = Stream.of(\"张无忌\"); Stream<String> stream7 = Stream.of(\"赵敏\"); //使用静态方法concat()将两个流合为一个流 Stream<String> concat = Stream.concat(stream6, stream7); concat.forEach(name-> System.out.println(name));} 综合练习 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364/* 练习:集合元素处理(Stream流方式) 现有两个ArrayList集合队伍当中有多个成员,按要求进行筛选 1.第一个队伍只要名字为3个字的成员 2.第一个队伍筛选后的前三个人 3.第二个队伍只要张姓成员 4.第二个队伍筛选之后不要前两个人 5.将两个队伍合并为一个队伍 6.根据姓名创建Person对象,存储到新的集合中 7.打印队伍信息 */public static void main(String[] args) { ArrayList<String> one = new ArrayList<>(); one.add(\"迪丽热巴\"); one.add(\"宋远桥\"); one.add(\"苏星河\"); one.add(\"石破天\"); one.add(\"石中玉\"); one.add(\"老子\"); one.add(\"庄子\"); one.add(\"洪七公\"); //1.第一个队伍只要名字为3个字的成员 //2.第一个队伍筛选后的前三个人 Stream<String> stream1 = one.stream(). filter(name -> name.length() == 3). limit(3); ArrayList<String> two = new ArrayList<>(); two.add(\"古力娜扎\"); two.add(\"张无忌\"); two.add(\"赵丽颖\"); two.add(\"张三丰\"); two.add(\"尼古拉斯赵四\"); two.add(\"张天爱\"); two.add(\"张二狗\"); //3.第二个队伍只要张姓成员 //4.第二个队伍筛选之后不要前两个人 Stream<String> stream2 = two.stream(). filter(name -> name.startsWith(\"\")). skip(2); //5.将两个队伍合并为一个队伍 //6.根据姓名创建Person对象 //7.打印队伍信息 Stream.concat(stream1, stream2). map(name -> new Person(name)). forEach(p -> System.out.println(p));}class Person { String name; public Person(String name) { this.name = name; } @Override public String toString() { return \"Person{\" + \"name='\" + name + '\\'' + '}'; }} 方法引用对Lambda表达式的优化 method(s-> System.out.println(s));–>method(System.out::println); 前者语义,拿到参数后经Lambda之手,继而传递给System.out.println方法去处理 后者语义,直接让System.out中的println方法来取代lambda,两种写法执行的效果完全一样,而第二种引用的写法复用了已有方案,更加简洁 引用的方式 通过对象名来引用成员方法 对象名::对象的成员方法 通过类名引用类的静态方法 类名::类的静态方法名 通过super引用父类的成员方法 super::父类的成员方法 通过this引用本类的成员方法 this::本类成员方法 类的构造器(构造方法)引用 类名::new 数组的构造器引用 int[ ]::new 1234567891011121314151617181920212223242526272829303132333435/*通过对象名引用成员方法 使用前提:对象名已经存在,成员方法也已经存在 */public class MethodRerObj { //定义一个成员方法,传递字符串,按大写输出 public static void printstr(Printable p){ p.println(\"hello\"); } public static void main(String[] args) { printstr((s)->{ Method me = new Method(); me.printupper(s); }); //优化 //对象存在,成员方法存在,可以使用引用方法 Method me = new Method(); printstr(me::printupper); printstr((s)->Method.printlower(s)); //类已经存在,静态方法也存在 //通过类名调用引用静态方法, printstr(Method::printlower); }}class Method{ public void printupper(String s){ System.out.println(s.toUpperCase()); } public static void printlower(String s){ System.out.println(s.toLowerCase()); }}","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"Stream","slug":"Stream","permalink":"http://lqhao.gitee.io/tags/Stream/"}]},{"title":"Java-函数式接口","slug":"函数式接口","date":"2020-07-19T13:30:17.000Z","updated":"2020-07-22T13:33:24.620Z","comments":true,"path":"2020/07/19/2/","link":"","permalink":"http://lqhao.gitee.io/2020/07/19/2/","excerpt":"","text":"函数式接口有且仅有一个抽象方法的接口,叫做函数式接口即适用于函数式编程场景的接口,也就是使用Lambda编程 12345格式: 修饰符 interface 接口名称{ public abstract 返回值类型 方法名称(可选参数信息); //其他非抽象方法内容,默认方法,静态方法,私有方法 } 语法糖:指使用更加方便,但是原理不变的代码语法,例如使用for-each语法,其实底层实现的原理仍然使迭代器,这便是”语法糖”,从应用层面来讲,Java的Lambda可以被当作使匿名内部类的”语法糖”,但使二者在原理上是不同的,匿名内部类会创建一个.class文件,而使用Lambda表达式就不会 12345678910/** 注解:检测所写代码是否符合要求,若不符合,则编译报错* @FunctionalInterface:检测接口是否为一个函数式接口* 是:编译成功* 否:编译失败(抽象方法的个数不为一个)* */@FunctionalInterfacepublic interface Function { public abstract void method();} Lambda的延迟执行 12345678910111213141516171819202122232425262728293031323334353637/**使用Lambda优化内存浪费* Lambda特点:延迟加载* 使用前提,必须存在函数式接口** */ @FunctionalInterfaceinterface Message{ //定义一个拼接字符串的抽象方法 public abstract String msgBuilder();}public class DemoLambda { //定义一个显示日志的方法,参数传递日志等级和Message接口 public static void showLog(int level, Message msg){ if(level==1){ //调用msg接口方法 System.out.println(msg.msgBuilder()); } } public static void main(String[] args) { String s1 = \"hello\"; String s2 = \"world\"; //参数是函数式接口,可以传递lambda表达式,返回拼接字符串 showLog(1, ()->s1+s2); /* 使用lambda作为参数传递,仅仅是吧参数传递到showLog方法中 只有满足条件,日志等级是1 才会调用接口中的方法,进行字符串拼接 如果条件不满足,日志等级非1 那么接口中的方法就不会调用,拼接字符串也不会执行 所以不会存在性能浪费 */ }} 使用lambda作为返回值 1234567891011121314151617181920212223public class demoComparator { //定义一个方法,方法的返回值类型使用函数式接口Comparator public static Comparator<String> getComparator(){ //方法的返回值是一个接口,那么可以返回这个接口的匿名内部类 /*return new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.length()-o1.length(); } };*/ //优化写法 return (o1,o2)->o2.length()-o1.length(); } public static void main(String[] args) { //调用自定义方法 String[] strings = {\"aaa\",\"c\",\"ccccc\"}; //使用指定的方法进行排序 Arrays.sort(strings,getComparator()); System.out.println(Arrays.toString(strings)); }} 常用的函数式接口 Supplier接口,传什么类型,get()获取什么类型 1234567891011121314151617181920212223242526272829303132333435363738394041/** 常用的函数式接口* Supplier<T>接口:* 被称之为生产型接口,指定接口是泛型是什么类型,接口的get()就会生产什么类型的数据* 用来获取一个泛型参数指定类型的对象数据* */public class TestSupplier { //方法的参数传递Supplier<T>接口,泛型执行String,get就会返回一个String public static String getString(Supplier<String> sup){ return sup.get(); } public static void main1(String[] args) { String s = getString(() -> { //生产一个字符串,并返回 return \"周润发\"; }); System.out.println(s); } //求数组元素的最大值 //定义一个方法用于获取int类型数组的最大值,方法参数Supplier<Integer> public static int getMax(Supplier<Integer> sup){ return sup.get(); } public static void main(String[] args) { int[] arr = {1,2,5,7,4,20}; //调用getMax int maxvalue = getMax(() -> { //获取数组的最大值并返回 int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (max < arr[i]) { max = arr[i]; } } return max; }); System.out.println(\"数组中元素最大值\"+maxvalue); }} Consumer接口 传什么类型,消费什么类型 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748/* * Consumer<T>接口:是一个消费型接口,泛型执行什么类型,就可以使用accept(t)消费什么类型 * 至于消费怎么使用,需要自定义(输出,计算) * Consumer<T>接口中的默认方法andThen(),可以把两个Consumer<T>对象组合组合到一起,再对数据进行消费 * 例如: Consumer<String> con1 Consumer<String> con2 * String s = \"hello\" * con1.accept(s);con2.accept(s);//消费了两次 * 可以使用con1.andThen(con2).accept(s)//消费一次与上面等效, * 谁写前面谁先消费 * * */public class TestConsumer { //方法的参数传递Consumer<T>接口,泛型执行String,accept就会消费一个String public static void method(String name, Consumer<String> Consumer) { Consumer.accept(name); } public static void main1(String[] args) { //调用method方法 method(\"周星驰\", (name) -> { //将字符串进行反转输出 String rename = new StringBuilder(name).reverse().toString(); System.out.println(rename); } ); } //定义一个方法,传递两个Consumer接口,泛型使用字符串 public static void method(String name,Consumer<String> con1,Consumer<String> con2){ //使用andThen con1.andThen(con2).accept(name);//谁写前先执行谁 //等效于 //con1.accept(name); // con2.accept(name); } public static void main(String[] args) { method(\"Angela baby\", (name)->{ //消费方式,将字符串转为大写输出 System.out.println(name.toUpperCase()); }, (name)->{ //消费方式,将字符串转为小写输出 System.out.println(name.toLowerCase()); }); }} Predicate接口: 对某种类型的数据进行判断,从而得到一个boolean值结果 抽象方法 boolean test(T t) 用于条件判断的场景 默认方法 and() or() negate() 1234567891011121314151617181920212223242526272829303132333435363738394041424344/** Predicate* 作用:对某种数据类型的数据进行判断,结果返回boolean* abstract boolean test(T t);用来对指定数据类型进行判断是否符合条件* 逻辑表达式:可以连接多个判断的条件* 默认方法:and与or或negate非* 使用:判断字符串的长度是否大于5 判断字符串中是否包含a,两个条件必须同时满足使用and()方法* or(),negate()使用相同* */public class TestPredicate { //定义方法,参数传递一个字符串,一个Predicate对象 //使用Predicate中的test对字符串进行判断 public static boolean checkString(String name, Predicate<String> pre){ return pre.test(name); } public static void main1(String[] args) { String s = \"abcdef\"; //调用checkString方法 boolean b = checkString(s, (name) -> { //对传递的字符串进行判断 return name.length() > 5; }); System.out.println(b); } //定义一个方法方法参数传递一个字符串,传递两个Predicate //一个判断字符串长度是否大于5,一个判断字符串是否包含a,条件需同时满足 public static boolean checkString2(String name,Predicate<String> pre1,Predicate<String> pre2){ //return pre1.test(name)&&pre2.test(name);相同 return pre1.and(pre2).test(name); } public static void main(String[] args) { String s = \"abcdef\"; boolean b = checkString2(s, (name) -> { return name.length()>5; }, (name) -> { return name.contains(\"a\"); }); System.out.println(b); }} Function<T,R>接口 根据一个类型的数据得到另一个类型的数据,前者T称为前置条件,后者R称为后置条件 abstract R apply(T,t) 将T类型转换为R类型 默认方法 andThen()方法,使用与Consumer相似 123456789101112131415161718192021222324252627282930313233343536373839public class TestFunction { //定义一个方法,参数出入一个字符串类型的整数,一个Function接口泛型<String,Integer> //使用接口中的apply()方法,将字符串类型的整数转换为Integer类型整数 public static void change(String s,Function<String,Integer> fun){ Integer apply = fun.apply(s); System.out.println(apply); } public static void main1(String[] args) { //定义一个字符串整数 String s = \"1234\"; change(s,(str)->{ //将字符串类型的整数转换为Integer类型整数 return Integer.parseInt(str); }); } //Function中的默认andThen()方法,将两个Function接口组合在一起 //将String类型的\"123\"转换为Integer+10,再将+10后的整数转换为String public static String change1(String s, Function<String,Integer> fun1, Function<Integer,String> fun2){ return fun1.andThen(fun2).apply(s); } public static void main(String[] args) { String s = \"1234\"; String s1 = change1(s, //String->Integer (str) -> { int i = Integer.parseInt(str)+10; return i; }, //Integer->String (intel) -> { return intel.toString(); }); System.out.println(s1); }}","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"Lambda","slug":"Lambda","permalink":"http://lqhao.gitee.io/tags/Lambda/"},{"name":"函数式接口","slug":"函数式接口","permalink":"http://lqhao.gitee.io/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E6%8E%A5%E5%8F%A3/"}]},{"title":"Java-网络编程","slug":"Java-网络编程","date":"2020-07-19T04:30:17.000Z","updated":"2020-07-22T13:28:18.557Z","comments":true,"path":"2020/07/19/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/19/1/","excerpt":"","text":"网络编程UDP 用户数据报协议(User Datagram Protocol),UDP是无连接协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接 耗资小,通信效率高,通常用于音频,视频,和普通数据的传输,因为这种情况偶尔丢失一两个数据包,也不会对接收结果产生太大的影响,但是使用UDP不保证数据的完整性. 特点:数据被限制在64KB以内,超出这个范围就不能发送了 TCP 传输控制协议(Transmission Control Protocol) TCP是面向连接的通信协议,传输数据之前.在发送端和接收端会建立逻辑连接,可靠无差错的数据传输 三次握手 第一次握手 客户端向服务器端发出连接请求,等待服务器确认 第二次握手 服务器端向客户端回送一个响应,通知客户端接收到了连接请求 第三次握手 客户端再次向服务器端发送确认信息,确认连接 服务器端 12345678910111213141516171819202122232425262728293031323334/**TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据* 表示服务器的类:* java.net.ServerSocket 此类实现服务器套接字。* 构造方法:* ServerSocket(int port) 创建绑定到特定端口的服务器套接字。* 服务器端必须知道是哪个客户端请求的服务器* 所以可以使用accept方法获取到请求的客户端对象Socket* 成员方法:* Socket accept() 侦听并接受到此套接字的连接。* 实现步骤:* 1.创建ServerSocket对象,和系统要指定的端口号* 2.使用ServerSocket对象的accept()方法,获取到客户端的对象* 3.使用Socket对象中的方法getInputStream()获取网络字节输出流InputStream* 4.使用InputStream中的read方法读取客户端发送的数据* 5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象* 6.使用网络字节输出流OutputStream对象中的方法write,给客户端发送数据* 7.释放资源,Socket,ServerSocket都要释放* */public class TCPServer { public static void main(String[] args) throws IOException{ ServerSocket server = new ServerSocket(8888); Socket socket = server.accept(); InputStream is = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println(new String(bytes,0,len)); OutputStream os = socket.getOutputStream(); os.write(\"客户端你也好\".getBytes()); socket.close(); server.close(); }} 客户端 1234567891011121314151617181920212223242526272829303132333435/** TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据* java.net.Socket:此类实现客户端套接字* 构造方法:* Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号。* host:服务器主机的名称/也可以是ip地址 port:服务器的端口好* 成员方法:* OutputStream getOutputStream() 返回此套接字的输出流。* InputStream getInputStream() 返回此套接字的输入流。* void close() 关闭此套接字。* 实现步骤:* 1.创建一个客户端对象Socket,构造方法绑定服务器的ip和端口* 2.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象* 3.使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据* 4.使用Socket对象中的方法getInputStream()获取网络字节输出流InputStream* 5.使用InputStream中的read方法读取服务器回写的数据* 6.释放资源,关socket即可* 注意:* 1.客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用字节创建的流对象* 2.当我们创建客户端对象Socket的时候,就会去请求服务器,和服务器经过3此握手,建立连接通信,这时若服务器未启动,会抛出异常,若服务器已经启动,就可以进行交互了** */public class TCPClient { public static void main(String[] args) throws IOException { Socket socket = new Socket(\"127.0.0.1\",8888); OutputStream os = socket.getOutputStream(); os.write(\"服务器你好\".getBytes()); InputStream is = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println(new String(bytes,0,len)); socket.close(); }} 综合案例,文件上传案例 read的阻塞问题 public int read() 从此输入流中读取一个数据字节。如果没有输入可用,则此方法将阻塞。 fis.read(bytes) 作用: 读取本地文件,结束标记读取到-1结束, 但while循环不会读取-1进文件,那么也不会把结束标记给服务器,服务器就永远读取不到文件的结束标记,服务器读取不到,就会进入死循环,阻塞状态,之后的代码就不会执行到,服务器就不会给客户端回写消息,客户端读取不到消息,也会进入阻塞状态 解决方案: 上传完文件,给服务器写一个结束标记使用Socket对象的shutdownOutput()方法该方法可以禁用此套接字的输出流,也就是上传结束 优化 服务器端可以自定义一个命名规则,防止被覆盖,例:域名+毫秒值+随机数 使服务器可以一直处于监听状态,将accept使用一个死循环,有一个客户端上传一个文件,就保存一个文件,此时一直监听,服务器就不用关闭了 使用多线程提高效率,有一个客户端上传文件,就开启一个线程,完成文件的上传创建一个Thread对象,重写run方法(run方法父类没有抛出异常所以重写的异常只能try…catch…) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104/* 客户端 * 读取本地文件上传到服务器,再读取服务器回写的数据 * 实现步骤: * 1.创建一个本地输入流FileInputStream,构造方法绑定源地 * 2.创建一个客户端Socket对象,构造方法中绑定服务器ip端口 * 3.使用Socket中的方法getOutputStream()获取网络字节输出流 * 4.使用本地输入流FileInputStream对象中的方法read()读取本地文件 * 5.使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器 * 6.使用Socket中的方法getInputStream(),获取网络字节输入流 * 7.使用网络字节输入流中的方法read,读取服务器写回的数据 * 8.释放资源(FileInputStream,Socket) * * */public class TCPFileUpLoadClient { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream(\"F:\\\\FileTest\\\\a.txt\"); Socket socket = new Socket(\"127.0.0.1\", 8888); BufferedInputStream bis = new BufferedInputStream(fis); OutputStream os = socket.getOutputStream(); int len = 0; byte[] bytes = new byte[1024]; while ((len = bis.read(bytes)) != -1) { os.write(bytes, 0, len); } //为防止服务器端read()发生阻塞,需要写一个结束标记 //Socket中的shutdownOutput()方法:禁用此套接字的输出流 socket.shutdownOutput(); InputStream is = socket.getInputStream(); while ((len = is.read(bytes)) != -1) { System.out.println(new String(bytes, 0, len)); } bis.close(); socket.close(); }}/*服务器端 * 文件上传案例的服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写\"上传成功\" * 实现步骤: * 1.创建一个服务器ServerSocket对象,跟系统要指定的端口 * 2.使用ServerSocket对象中的accept()方法,获取到请求的客户端Socket对象 * 3.使用Socket中的方法getInputStream(),获取网络字节输入流 * 4.判断upload文件夹是否存在,不存在则创建一个 * 5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定目的地 * 6.使用网络字节输入流中的read()方法读取客户端上传的文件 * 7.使用本地字节输出流FileOutputStream对象中的write()方法,把读取到的文件写入本地硬盘 * 8.使用Socket中的方法getOutputStream()获取网络字节输出流 * 9.使用网络字节输出流中的方法write()给客户端回写\"上传成功\" * 10.释放资源(FileOutputStream,socket,ServerSocket) * */public class TCPFileUpLoadServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); /* 使用死循环让服务器一直处于监听状态 */ while (true) { Socket accept = serverSocket.accept(); /* 使用多线程技术提高程序效率 有一个客户端上传文件,就开启一个线程,完成文件的上传 创建一个Thread对象,重写run方法 */ new Thread(new Runnable() { @Override public void run() { try { InputStream is = accept.getInputStream(); /* 服务器端可以自定义一个命名规则,防止被覆盖,例:域名+毫秒值+随机数 */ String filename = \"lqh\" + System.currentTimeMillis() + new Random().nextInt(9999) + \".txt\"; File file = new File(\"D:\\\\Desktop\\\\123\"); if (!file.exists()) { file.mkdir(); } FileOutputStream fos = new FileOutputStream(file + \"\\\\\" + filename); int len = 0; byte[] bytes = new byte[1024]; while ((len = is.read(bytes)) != -1) { fos.write(bytes, 0, len);//此处read会进入死循环,因为客户端不会发送结束标记-1 } OutputStream os = accept.getOutputStream(); os.write(\"文件上传成功\".getBytes()); fos.close(); accept.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } //一直监听,服务器就不用关闭了 //serverSocket.close(); } } B/S版本的TCP服务器 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/* * 创建B/S版本的TCP服务器 * */public class TCPServer { public static void main(String[] args) throws IOException { //创建一个ServerSocket,和系统要指定的端口号 ServerSocket serverSocket = new ServerSocket(8888); while (true) { //使用accept()方法创建客户端socket Socket socket = serverSocket.accept(); //建立线程 new Thread(new Runnable() { @Override public void run() { try { //获取网络字节输入流,接收请求 InputStream is = socket.getInputStream(); //使用BufferedReader中的readLine()方法读取一行 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String s = br.readLine(); //将读取路径到的按空格进行分割 String[] s1 = s.split(\" \"); String substring = s1[1].substring(1); //将解析出的路径用FileInputStream读取出来 FileInputStream fis = new FileInputStream(substring); //获取socket的字节输出流 OutputStream os = socket.getOutputStream(); //http请求头部的通用格式 os.write(\"HTTP/1.1 200 OK\\r\\n\".getBytes()); os.write(\"Content-Type:Type/html\\r\\n\".getBytes()); os.write(\"\\r\\n\".getBytes()); //用socket字节输出流将读取到的本地文件会写到http请求 byte[] bytes1 = new byte[1024]; int length = 0; while ((length = fis.read(bytes1)) != -1) { os.write(bytes1, 0, length); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } }}","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"Socket","slug":"Socket","permalink":"http://lqhao.gitee.io/tags/Socket/"}]},{"title":"Java-File、IO流","slug":"Java-File、IO","date":"2020-07-18T02:30:50.961Z","updated":"2020-07-22T13:24:01.709Z","comments":true,"path":"2020/07/18/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/18/1/","excerpt":"","text":"File类java.io.File类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作,File是一个与系统无关的类,任何的操作系统都可以使用类中的方法, 记住三个词: file:文件 directory :文件夹/目录 path :路径 静态变量 static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。 windows 为”;” Linux 为”:” static String separator 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。 文件名称分隔符 windows 为”\\“ Linux 为”/“ 构造方法 File(File parent, String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 File(String parent, String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 File(URI uri) 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。 常用的获取方法 String getAbsolutePath(): 返回此File的绝对路径名字字符串 String getPath() : 返回此File转换为路径名字符串,toString()调用的就是这个方法 String getName(): 返回由此File表示的文件或目录的名称 long length() : 返回由此File表示的文件的长度,以字节为单位 常用判断功能的方法 boolean exists(): 此文件或目录是否存在 boolean isDirectory(): 此File表示的是否为目录 boolean isFile(): 此File表示的是否为文件 常用创建删除功能的方法 boolean createNewFile() : 当且仅当具有该名称的文件尚不存在时,创建一个新的空文件 boolean delete(): 删除由此File表示的文件或目录 boolean mkdir() : 创建由此File表示的目录 boolean mkdirs() : 创建由此File表示的目录,包括任何必需但不存在的父目录 ,船创建多级文件 常用目录的遍历 String[ ] list(): 返回String数组,表示该File目录中的所有子文件或目录, 路径不存在或给出的不是路径会抛出异常 File[ ] listfiles(): 返回File数组, 表示该File目录中的所有的子文件或目录 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102/* *File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 *参数: * String pathname:字符串的路径名称 * 路径可以是以文件结尾,也可以是以文件夹结尾 * 路径可以是相对路径,也可以是绝对路径 * 路径可以是存在的,也可以是不存在的 * 创建File对象只是把字符串路径封装,不考虑真假 * */private static void show01() { File file1 = new File(\"F:\\\\2020_7_JavaStudy\\\\stage_2\\\\a.txt\"); System.out.println(file1);}/* * File(String parent, String child) * 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 * 参数: * String parent, String child * 把路径分成了两部分,父路径,子路径, * 好处:两个路径可以单独书写,使用灵活,两个路径都可以变化 * */private static void show02(String parent, String child) { File file = new File(parent, child); System.out.println(file);}/* * File(File parent, String child) * 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 * 参数:File parent, String child * 父路径为file类型,可以使用file类的方法,对路径进行操作,再创建对象 * */private static void show03() { File parent = new File(\"F:\\\\\"); File file = new File(parent, \"hello.java\"); System.out.println(file);}/*- String getAbsolutePath(): 返回此File的绝对路径名字字符串- String getPath() : 返回此File转换为路径名字符串- String getName(): 返回由此File表示的文件或目录的名称- long length() : 返回由此File表示的文件的长度*/ private static void show01() { File file = new File(\"a.txt\"); System.out.println(\"绝对路径:\" + file.getAbsolutePath()); System.out.println(\"字符串路径:\" + file.getPath()); System.out.println(\"获取结尾名称:\" + file.getName()); System.out.println(\"获取文件大小:\" + file.length()); }/*- boolean exists(): 此文件或目录是否存在- boolean isDirectory(): 此File表示的是否为目录,目录不存在也返回false- boolean isFile(): 此File表示的是否为文件,文件不存在也返回false*/ private static void show02() { File file = new File(\"F:\\\\2020_7_JavaStudy\\\\stage_2\\\\a.txt\"); System.out.println(\"此目录是否存在:\"+file.exists()); System.out.println(\"此File是否为目录\"+file.isDirectory()); System.out.println(\"此File是否为文件\"+file.isFile()); }/*- boolean createNewFile() : 当且仅当具有该名称的文件尚不存在时,创建一个新的空文件,路径为构造方法给出- boolean delete(): 删除由此File表示的文件或目录- boolean mkdir(): 创建由此File表示的目录- boolean mkdirs(): 创建由此File表示的目录,包括任何必需但不存在的父目录 ,船创建多级文件*/private static void show03() throws IOException { File file = new File(\"F:\\\\FileTest\\\\a.txt\"); //创建a.txt file.createNewFile();//若目录不存在,会有空指针异常 File file1 = new File(\"F:\\\\FileTest\\\\789\"); //创建单级文件夹 file1.mkdir(); File file2 = new File(\"F:\\\\FileTest\\\\123\\\\456\"); //既可以创建单级文件夹,也创建多级文件夹 file2.mkdirs(); //既可以构造方法中的删除文件也可以删除文件夹,直接从硬盘删除 file.delete(); file2.delete();}/*- String[ ] list():返回String数组,表示该File目录中的所有子文件或目录,路径不存在或给出的不是路径会抛出异常- File[ ] listfiles(): 返回File数组, 表示该File目录中的所有的子文件或目录*/private static void show04() { File file = new File(\"F:\\\\FileTest\"); String[] list = file.list(); for (String filename : list) { System.out.println(filename); } File[] files = file.listFiles(); for (File file1 : files) { System.out.println(file1); }} 使用递归遍历多级目录文件案例 1234567891011121314151617181920212223public class PrintFile { public static void main(String[] args) { File file = new File(\"F:\\\\FileTest\"); getAllFile(file); } /* * 定义一个方法: 参数传递File类型的目录 * 方法中对目录进行遍历 * */ public static void getAllFile(File dir) { File[] files = dir.listFiles(); //遍历目录 for (File file : files) { //输出目录文件或文件夹名称 System.out.println(file); //若为文件夹,继续递归遍历 if (file.isDirectory()) { getAllFile(file); } } }} 文件搜索案例 12345678910111213141516171819202122232425262728293031//遍历文件只有.java结尾的文件public class Recurison { public static void main(String[] args) { File file = new File(\"F:\\\\FileTest\"); getAllFile(file); } /* * 定义一个方法: 参数传递File类型的目录 * 方法中对目录进行遍历 * */ public static void getAllFile(File dir) { File[] files = dir.listFiles(); //遍历目录 for (File file : files) { //输出目录文件或文件夹名称,后缀转换为小写,忽略大小写 //file.getName().endsWith(\".java\") //file.getPath().endsWith(\".java\") if (file.toString().toLowerCase().endsWith(\".java\")) { //输出目录文件或文件夹名称 System.out.println(file); } //若为文件夹,继续递归遍历 if (file.isDirectory()) { getAllFile(file); } } }} 文件过滤器优化文件搜索 将listFiles()方法传入过滤器,即FileFilter接口的实现类对象,或者FilenameFilter接口的实现类对象. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475/* * 可以使用过滤器来实现指定类型文件的筛选 * 在File类中有两个和ListFiles重载的方法,方法的参数传递的就是过滤器 * File[] listFiles() * 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 * File[] listFiles(FileFilter filter) * 返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 * File[] listFiles(FilenameFilter filter) * 返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 *java.io.FileFilter接口:用于抽象路径名(File)的过滤器,过滤文件 * 抽象方法:用来过滤文件 * boolean accept(File pathname) 试指定抽象路径名是否应该包含在某个路径名列表中。 * 参数 pathname: 使用ListFiles方法遍历目录得到的每一个文件对象 *java.io.FilenameFilter接口:实现此接口的类实例可用于过滤器文件名。 * 抽象方法: * boolean accept(File dir, String name) 测试指定文件是否应该包含在某一文件列表中。 * 参数 dir被遍历的目录 * name:使用listFiles方法遍历目录,获取的每一个文件/文件夹的名称 * 注意: * 两个过滤器接口没有实现类,需要自己写实现类,重写过滤的方法accept(),在方法中自己定义规则 * */public class BestRecurison { public static void main(String[] args) { File file = new File(\"F:\\\\FileTest\"); getAllFile(file); } /* * 定义一个方法: 参数传递File类型的目录 * 方法中对目录进行遍历 * */ public static void getAllFile(File dir) { /* 使用匿名内部类,设置过滤器 此处也可使用Lambda表达式(pathname)-> return (pathname.toString().toLowerCase().endsWith(\".java\")||pathname.isDirectory()) */ //FileFilter的方式 /*File[] files = dir.listFiles(new FileFilter(){ @Override public boolean accept(File pathname) { //若结尾为.java或者是文件夹则保留返回true if (pathname.toString().toLowerCase().endsWith(\".java\")||pathname.isDirectory()){ return true; } return false; } });*/ //FilenameFilter接口方式 File[] files = dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return new File(dir, name).isDirectory() || name.toLowerCase().endsWith(\".java\"); } }); //遍历目录 for (File file : files) { //若为文件夹,继续递归遍历 if (file.isDirectory()) { getAllFile(file); } else { System.out.println(file); } } }} IO流 字符输入/出流, 字节输入/出流, 出入的对象是内存和硬盘,内存是主体.出:内存->硬盘 输入流 输出流 字节流 字节输入流 InputStream 字节输出流 OuputStream 字符流 字符输入流 Reader 字符输出流 Writer 一切皆为字节,字节流可以读写任意的文件 字节流 OuputStream字节输出流方法 void close() 关闭此输出流并释放与此流有关的所有系统资源。 void flush() 刷新此输出流并强制写出所有缓冲的输出字节。 void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。 void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 abstract void write(int b) 将指定的字节写入此输出流。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859/* * OutputStream 此抽象类是表示输出字节流的所有类的超类 * 他的子类: FileOutputStream *构造方法: * FileOutputStream(File file) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 * FileOutputStream(String name) 创建一个向具有指定名称的文件中写入数据的输出文件流。 * FileOutputStream(String name, boolean append) 创建一个向具有指定 name 的文件中写入数据的输出文件流。 * FileOutputStream(File file, boolean append) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流 * 参数: file name: 写入数据的目的地 * append : 追加写 true:追加写 false:创建新文件,覆盖写 * 作用: * 1.创建一个FileOutputStream对象 * 2.会根据构造方法中传递的文件/文件路径,创建一个空的文件 * 3.会把FileOutputStream对象指向创建好的文件 * 写入数据的原理(内存->硬盘) * java程序->JVM(java虚拟机)-OS(操作系统)->OS调用写数据的方法,把数据写入文件 * 字节输出流的使用步骤: * 1.创建一个FileOutputStream对象,构造方法传入写入数据目的地 * 2.调用FileOutputStream对象中的write,把数据写入导文件中 * 3.释放资源(流会占用一定的内存,使用完毕要把内存清空,提高程序的效率) * */public class DemoOutputStream { public static void main(String[] args) throws IOException { //创建一个FileOutputStream对象 FileOutputStream fos = new FileOutputStream(\"F:\\\\FileTest\\\\a.txt\"); //一次写一个字节调用write(),十进制整数转换为二进制整数 fos.write(97);//写入一个字节'a' //关闭流 fos.close(); FileOutputStream fos1 = new FileOutputStream(new File(\"F:\\\\FileTest\\\\a.txt\")); //一次写多字节调用write() byte[] a = {-65, -66, 67, 68, 69}; fos1.write(a);//传入字节数组 //把字节数组的一部分写入到文件中 fos1.write(a, 2, 3); //写入字符串的方法,可以使用String类中的方法,将String转换为字节数组 byte[] bytes = \"你好\".getBytes(); System.out.println(Arrays.toString(bytes)); fos1.write(bytes); fos1.close(); //追加写 FileOutputStream fos3 = new FileOutputStream(\"F:\\\\FileTest\\\\a.txt\", true); //追加写 fos3.write(\"你好\".getBytes()); //换行写 /*换行符号 * windows: \\r\\n * linux: /n * mac: /r * */ for (int i = 0; i < 10; i++) { fos3.write(\"你好\".getBytes()); fos3.write(\"\\r\\n\".getBytes()); } fos3.close(); }} InputStream字节输入流 void close() 关闭此输入流并释放与该流关联的所有系统资源。 abstract int read() 从输入流中读取数据的下一个字节。 int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/* * InputStream字节输入流:定义了所有子类共性的方法 * 使用实现类:FilterInputStream extends InputStream * 作用:把硬盘文件中的数据读取到内存中 * 构造方法: * FilterInputStream(String name) * FilterInputStream(File file) *读取数据原理: (硬盘->内存) * java程序->JVM->OS->OS调用读取数据的方法,读取文件 * 使用步骤: * 1.创建FilterInputStream对象,构造方法中绑定要读取的数据源 * 2.使用read()方法,读取文件 * 3.释放资源 * */public class demoInputStream { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("F:\\\\FileTest\\\\a.txt"); //读取文件的一个字节并返回,若末尾返回-1 int read = fis.read(); System.out.println((char) read);//a //每读一次指针向后移动一位 read = fis.read(); System.out.println((char)read);//b //循环读 int len = 0; //(len=fis.read())!=-1的巧妙使用 while((len=fis.read())!=-1){ System.out.println(len); } fis.close(); /*一次读取多个字节 *int read(byte[] b) * 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 *int read(byte[] b, int off, int len) * 将输入流中最多 len 个数据字节读入 byte 数组。 *参数: b:缓冲作用,设定一个数组来接收每次读取到的值,数组的长度一班定义为1024的整数倍 *返回值: int为每次读取到的有效字节个数 * */ FileInputStream fis1 = new FileInputStream("F:\\\\FileTest\\\\b.txt"); //设定一个数组来接收读取到的值 byte[] bytes = new byte[10]; //返回int为读取到的字节的个数 int read1 = fis1.read(bytes,3,3); System.out.println(Arrays.toString(bytes)); //String的构造方法,传入bytes数组,从0开始,到指定长度转换为字符串 System.out.println(new String(bytes,0,read1)); System.out.println(read1); }} 文件的复制 12345678910111213141516171819202122232425public static void main(String[] args) throws IOException { //创建一个字节输入流 FileInputStream fis = new FileInputStream(\"F:\\\\FileTest\\\\a.txt\"); //创建一个字节输出流 FileOutputStream fos = new FileOutputStream(\"F:\\\\FileTest\\\\m.txt\"); int len = 0; //循环读出,读出的不是-1就一直读 /* while ((len = fis.read()) != -1) { //写入 fos.write(len); } */ //使用数组缓冲来一次读取多个字节,写入多个字节 byte[] bytes = new byte[1024]; //每次读出指定数组大小的内容 while ((len = fis.read(bytes)) != -1) { //每次从0开始写入指定长度的字节数 fos.write(bytes, 0, len); } //先关闭写流再关闭写流,写完了,就一定读完了 fos.close(); fis.close();} 注意:字节流读取到中文字符时,可能不会显示完整的字符,因为一个中文字符可能占用多个字节存储,所以Java提供了一些字符流,以字符为单位,专门处理文本文件 GBK 2字节 UTF-8 3个字节 字符流 字符输入流 Reader colse() 关闭并释放与此流相关的任何系统资源 int read() 从输入流读入一个字符,并返回 int read(char[] cbuf) 从输入流读取一些字符,并将它们存储到字符数组cbuf中. 12345678910111213141516171819202122232425262728293031323334/* * Reader:字符输入流最顶层的父类,是一个抽象类 * 需要使用他的子类:FileReader extends InputStreamReader extends Reader * 作用:把硬盘中的内容以字符的方式读取到内存中 * FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader。 * FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader * 参数:读取文件的数据源 * 作用:创建一个FileReader对象,把该对象指向要读取的文件 * 使用步骤: * 1.创建一个FileReader对象,构造方法中绑定读取的数据源 * 2.使用FileReader中的方法读取文件 * 3.关闭流 * */public class DemoReader { public static void main(String[] args) throws IOException { FileReader fr = new FileReader(\"F:\\\\FileTest\\\\a.txt\"); //每次读取单个字符 /*int read = 0; while ((read = fr.read()) != -1) { System.out.print((char) read); }*/ //每次读取一个数组 char[] cs = new char[1024];//存储读取到的多个字符 int len = 0;//存读每次读取的有效字符个数,若为-1读结束 while ((len = fr.read(cs)) != -1) { //String(char[] value, int offset, int count) //将字符数组转为字符串 System.out.print(new String(cs, 0, len)); } fr.close(); }} 字符输入流 Writer write(int c) 写入单个字符 write(char[] cbuf) 写入字符数组 abstract void write(char[] bufer, int off, int len) 写入字符数组的某一部分,off为开始索引,len为写入的字符个数 write(String str) 写入字符串,将数据写入到内存缓存区当中(字符转换为字节的过程) write(String str, int off, int len) 写入字符串的某一部分,off为开始索引,len为写入的字符个数 flush() 将内存缓冲区中的内容刷新到硬盘文件中,刷新缓冲区,流对象可以继续使用 close() 关闭流, 先刷新缓冲区,通知系统释放资源,流对象不可以再使用了 123456789101112131415161718192021222324252627282930313233343536373839404142434445/* * Writer抽象类:是所有字符输出流的父类 * 所用的子类:FileWriter extends OutputStreamWriter extendsWriter * 作用:把内存中的字符数据写入到文件自 * 构造方法: * FileWriter(File file) * 根据给定的 File 对象构造一个 FileWriter 对象。 * FileWriter(String fileName) * 根据给定的文件名构造一个 FileWriter 对象。 * 参数:文件目的地 * 作用:创建一个FileWriter对象,创建一个 * 1.创建一个FileWriter对象,构造方法中绑定写入的数据源 * 2.使用FileWriter中的write()将数据写入到内存缓存区当中(字符转换为字节的过程) * 3.使用flush()将内存缓冲区中的内容刷新到文件中 * 4.释放资源(会把内存缓冲区中的数据刷新到文件中) * */public class DemoWriter { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter(\"F:\\\\FileTest\\\\c.txt\"); //单个字符写 fw.write(97);//将数据写入到内存缓存区当中(字符->字节) fw.flush();//将缓冲中的数据存入硬盘 //一次写一个字符数组 char[] cs = {'a', 'b', 'c', 'd', 'e'}; fw.write(cs); //一次写字符数组的一部分 fw.write(cs, 0, 4); //一次写一个字符串 fw.write(\"你好哦哈哈哈\"); //一次写字符串的一部分 fw.write(\"你好哦哈哈哈\", 2, 3); fw.flush(); fw.close(); //续写和换行,使用两个参数的构造方法,第二个参数为续写开关 FileWriter fw1 = new FileWriter(\"F:\\\\FileTest\\\\c.txt\", true); //换行写入 windows:\\r\\n linux:/n mac:/r for (int i = 0; i < 5; i++) { fw1.write(\"梁清豪\\r\\n\"); } fw1.flush(); fw1.close(); }} IO异常的处理 在jdk1.7之前,可以使用try..catch..finally处理流中的异常 1234567891011121314151617181920212223242526272829//1.7之前需要用到两次try...catch...和判空语句public static void main(String[] args) { //将变量改为作用域改为全局,让变量能在finally中使用 //若new的时候执行失败,则fw会没有值,fw.close()会报错,需将fw赋一个初值 FileWriter fw = null; try { //若执行失败,则fw会没有值,fw.close()会报错 fw = new FileWriter(\"F:\\\\FileTest\\\\c.txt\", true); for (int i = 0; i < 5; i++) { fw.write(\"输入字符\\r\\n\"); } fw.flush(); fw.close(); } catch (IOException e) { System.out.println(e); } finally { //close本身也有异常,故需要继续try..catch.. try { //fw.close声明抛出了io异常,所以需要人为处理 //若创建对象失败了,那么fw的默认值将是null,fw.close()会有空指针异常 //需要加一条判断,若不为空,才执行fw.close() if (fw != null) { fw.close();//fw1变量出了大括号,作用域限制找不到fw1 } } catch (IOException e) { e.printStackTrace(); } }} jdk1.7新特性,可以省去finally中复杂的操作 1234567891011121314151617181920212223242526272829/* * JDk7的新特性在try的后边可以增加一个(),在括号中定义流对象 * 那么这个流对象的作用域就在try中有效 * try中的代码执行完毕,会自动把流对象释放,不用写finally * 格式try(定义流对象;可定义多个){ * 会出异常的代码 * }catch{ * 异常处理的逻辑 * } * */public class IOTryCatch7 { public static void main(String[] args) { try ( //定义流对象;可定义多个,使用完毕会自动释放流对象 FileInputStream fis = new FileInputStream(\"F:\\\\FileTest\\\\a.txt\"); FileOutputStream fos = new FileOutputStream(\"F:\\\\FileTest\\\\m.txt\")) { //会出异常的代码 int len = 0; while ((len = fis.read()) != -1) { //写入 fos.write(len); } } catch (IOException e) { System.out.println(e); } }} jdk1.9新特性,对1.7特性进行了改变,但用起来不如1.7,因为既得throws还得try…catch… 1234567891011121314151617181920212223242526272829303132/* * JDK9的新特性, * 在try的前边可以定义流对象 * 在try的后边()中可以直接引入流对象的名称(变量名) * 在try代码执行完毕后,流对象也可以释放掉,不用写finally * 格式: * A a = new a(); * B b = new b(); * try(a,b){ * 会有异常的代码 * }catch{ * 异常处理逻辑 * } * */public class IOTryCatch9 { public static void main(String[] args) throws FileNotFoundException { //在try外定义流对象,但需要将此处异常抛出 FileInputStream fis = new FileInputStream(\"F:\\\\FileTest\\\\a.txt\"); FileOutputStream fos = new FileOutputStream(\"F:\\\\FileTest\\\\m.txt\"); try ( //传入流对象;可定是多个,使用完毕会自动释放流对象 fis,fos) { //会出异常的代码 int len = 0; while ((len = fis.read()) != -1) { //写入 fos.write(len); } } catch (IOException e) { System.out.println(e); } }} 属性集 Properties Properties extends Hashtable<k,v> 是唯一与IO流相结合的集合 Properties 类表示了一个持久的属性集。可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。 方法 void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对).字符输入流,能读取含有中文的中文键值对 void load(InputStream inStream) 从输入流中读取属性列表(键和元素对).字节输入流不能读取含有中文的键值对 void store(OutputStream out, String comments) 以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。字节输出流不能写入中文. comments用来解释说明保存的文件是做什么用的,不能使用中文,默认是unicode编码,一般使用空字符串 void store(Writer writer, String comments) 以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。 字符输出流能写入中文 Object setProperty(String key, String value) 调用 Hashtable 的方法 put。 String getProperty(String key) 通过key找到value值,相当于Map中的get(key) Set<String> stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。 相当于map中的keySet() 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162/** Properties作用:* 可以使用Properties集合中的方法store,把集合中的临时数据持久化写入到硬盘* 可以使用Properties集合中的方法load,把硬盘中的数据读取到内存* 属性列表中每个键及其对应值都是一个字符串: 是一个双列集合,key和value默认都是字符串* */public class DemoProperties { public static void main(String[] args) throws IOException { /* * 使用Properties存储数据,遍历取出Properties中的数据 * Properties集合中有一些操作字符串的特有方法 * */ //创建一个Properties集合对象 Properties prot = new Properties(); //使用setProperty()往集合中添加数据 prot.setProperty(\"赵丽颖\",\"168\"); prot.setProperty(\"迪丽热巴\",\"167\"); prot.setProperty(\"古力娜扎\",\"160\"); //使用stringPropertyNames(),将集合中的键值取出,返回到set集合中 Set<String> set = prot.stringPropertyNames(); for (String s : set) { //通过key值获取value值 System.out.println(s+\"->\"+prot.getProperty(s)); } /* * store()持久化使用步骤: * 1.创建Properties集合对象,添加数据 * 2.创建字节/字符输出流,其构造方法中绑定输出的目的地 * 3.使用store()方法持久化到硬盘 * 4.关闭流 * */ //创建字符输出流, FileWriter fw = new FileWriter(\"F:\\\\FileTest\\\\a.txt\"); //store()方法持久化到硬盘,传入FileWriter对象和注释 prot.store(fw,\"save Data\"); fw.flush(); fw.close(); //使用字节流,匿名对象会自动关闭流,不用手动关闭 prot.store(new FileOutputStream(\"F:\\\\FileTest\\\\a.txt\",true),\"save\"); /* * load()读取数据使用步骤: * 1.创建Properties集合对象,添加数据 * 2.创建字节/字符输出流,其构造方法中绑定输入的源地 * 3.使用load()方法读取数据到内存 * 4.关闭流 * 注意: * 1.文件中键值对默认的连接符号可以使用等号\"=\"或者空格\" \",或其他符号也可 * 2.存储键值对的文件中,可以使用\"#\"注释,被注释的键值对不会再被读取 * 3.存储键值对的文件中,默认都是字符串,不用再加引号 * */ FileReader fw1 = new FileReader(\"F:\\\\FileTest\\\\a.txt\"); prot.load(fw1); //再次提取key值 Set<String> set1 = prot.stringPropertyNames(); for (String s : set1) { System.out.println(s+\"->\"+prot.getProperty(s)); } fw1.close(); }} 缓冲流Buffer对基本的流进行一种增强 字节缓冲流: BufferedInputStream BufferedOutputStream 字符缓冲流: BufferedReader BufferedWriter 字节缓冲输入输出流 BufferedInputStream BufferedOutputStream 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162/** BufferedOutputStream extends OutputStream* 有继承自父类的共性成员方法* 构造方法:* BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。 参数:OutputStream out 字节输出流,可以传递FileOutputStream,缓冲流会给传递的对象增加一个缓冲区,* int size 指定缓冲流内部缓冲区大小,不指定为默认大小* 使用步骤:* 1.创建一个FileOutputStream对象,构造方法绑定目的地* 2.创建BufferedOutputStream对象,构造方法传递FileOutputStream对象* 3.使用BufferedOutputStream对象中的write方法,把数据缓冲到内存缓冲区* 4.使用flush(),把内部缓冲区数据输出到目的地* 5.释放资源,调用close()方法* */public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream(\"F:\\\\FileTest\\\\a.txt\"); BufferedOutputStream bos = new BufferedOutputStream(fos); bos.write(\"我把数据写入到内部缓冲区中\".getBytes()); bos.flush(); bos.close();//关闭缓冲流会自动关闭基本的字节流不用fos.close()}/** 与BufferedOutputStream基本相同* BufferedInputStream extends InputStream* 有继承自父类的共性成员方法* 构造方法:* BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 BufferedInputStream(InputStream in, int size) 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 参数:InputStream in 字节输入流,可以传递FileInputStream,缓冲流会给传递的对象增加一个缓冲区* int size 指定缓冲流内部缓冲区大小,不指定为默认大小* 使用步骤:* 1.创建一个FileInputStream对象,构造方法绑定目的地* 2.创建BufferedInputStream对象,构造方法传递FileInputStream对象* 3.使用BufferedInputStream对象中的read方法,把数据缓冲到内部缓冲区* 4.释放资源,调用close()方法* */public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream(\"F:\\\\FileTest\\\\a.txt\"); BufferedInputStream bis = new BufferedInputStream(fis); /* int read = 0; while((read=bis.read())!=-1) { System.out.println((char)read); } */ byte[] bytes = new byte[1024];//存储每次读取的数据 int read =0;//记录每次读取的有效字节个数 while((read=bis.read(bytes))!=-1){ System.out.println(new String(bytes,0,read)); } bis.close();} 字符缓冲输入输出流: BufferedReader BufferedWriter 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859/** BufferWriter extends Writer* 有继承自父类的共性成员方法* 构造方法:* BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。 BufferedWriter(Writer out, int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 参数:Writer out 字符输出流,可以传递FileWriter,缓冲流会给传递的对象增加一个缓冲区* int sz 指定缓冲流内部缓冲区大小,不指定为默认大小* 特有的成员方法:void newLine() 写入一个行分隔符。会根据不同的操作系统获取不同的行分割符* 使用步骤:* 1.创建一个FileWriter对象,构造方法绑定目的地* 2.创建BufferedWriter对象,构造方法传递FileWriter对象* 3.使用BufferedWriter对象中的write方法,把数据缓冲到内存缓冲区* 4.使用flush(),把内存缓冲区数据输出到目的地* 5.释放资源,调用close()方法* */public static void main(String[] args) throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter(\"F:\\\\FIleTest\\\\a.txt\")); for (int i = 0; i < 10; i++) { bw.write(\"数据缓冲到内存\"+i); //换行方法 bw.newLine(); } bw.flush(); bw.close(); method();}/** 与BufferReader基本相同* BufferReader extends Reader* 有继承自父类的共性成员方法* 构造方法:* BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。 BufferedReader(Reader in, int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流。 参数:Reader in 字符输入流,可以传递FileReader,缓冲流会给传递的对象增加一个缓冲区 int size 指定缓冲流内存缓冲区大小,不指定为默认大小* 特有的成员方法:* 使用步骤: String readLine() 读取一行数据,不包含终止符号,达到流的末尾返回null* 行的终止符号 \\r \\n 或回车之后跟着换行** 1.创建一个FileReader对象,构造方法绑定目的地* 2.创建BufferedReader对象,构造方法传递FileReader对象* 3.使用BufferedReader对象中的read/readLine方法,把数据缓冲到内存缓冲区* 4.释放资源,调用close()方法* */public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new FileReader(\"F:\\\\FileTest\\\\a.txt\")); String s = \"\"; //循环读取每一个整行,结束条件为读取到null的时候 while((s=br.readLine())!=null){ System.out.println(s); } br.close();} 字符缓冲练习,文本排序 12345678910111213141516171819202122232425262728293031323334/* * 1.使用HashMap key存出每行文本的序号,value存储每行文版 * 2.创建字符缓冲输入流,构造方法中绑定字符输入流 * 3.创建字符缓冲输出流,构造方法中绑定字符输出流 * 4.使用字符缓冲输入流中的readLine()逐行读取文本 * 5.对读取到的文本进行切割,获取行序号和文本内容 * 6.把读取到的序号(key)和文本内容(value)存储到HashMap集合中,会按照序号自动排序 * 7.遍历HashMap集合,获取每一个键值对 * 8.把每一个键值对拼接为一个文本行 * 9.把拼接好的每一个文本使用字符缓冲输出流中的write,写入到文件中 * 10.释放资源 * * */public static void main(String[] args) throws IOException { HashMap<String, String> map = new HashMap<>(); BufferedReader br = new BufferedReader(new FileReader(\"F:\\\\FileTest\\\\a.txt\")); BufferedWriter bw = new BufferedWriter(new FileWriter(\"F:\\\\FileTest\\\\b.txt\")); String line; //将文件中内容读出,按照\".\"分割,并存入HashMap中 while ((line = br.readLine()) != null) { String[] arr = line.split(\"\\\\.\"); map.put(arr[0], arr[1]); } //将HashMap中的东西读出来并写入新文件 for (String key : map.keySet()) { String value = map.get(key); line = key + \".\" + value; bw.write(line); //写换行 bw.newLine(); } bw.close(); br.close();} 转换流 InputStreamReader OutputStreamWriter可以指定编码表,读取/写入任意格式的文件 编码: 按照某种规则,将字符存储到计算机中,称之为编码 解码: 将计算机中的二进制数按照某种规则,转换为字符,称为解码 字符集: 也叫编码表,是一个系统支持的所有字符的集合,包含各个国家文字,标点符号,图形符号,数字等,是一个生活中的文字和计算机二进制的对应规则 FileReader只能读取IDE默认编码格式的文件,若读取其他编码格式文件,会出现乱码 InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/** OutputStreamWriter extends Writer* 字符通向字节的桥梁,可以指定编码表* 有继承自父类的共性方法* 构造方法:* OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter。* 参数:* OutputStream out 字节输出流,用来将转换之后的字节输出到文件* String charsetName 编码表名称,不区分大小写,不知道默认使用IED的编码* 使用步骤:* 1.创建OutputStreamWriter,构造方法中传入字节输出流对象和指定的编码表名称* 2.使用OutputStreamWriter中的方法writer(),把字符转换为字节存储到内存中* 3.使用OutputStreamWriter中的方法flush(),将内存中数据刷新到文件中* 4.释放资源* */ public static void main(String[] args) throws IOException { //第二个参数指定编码格式 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(\"F:\\\\FileTest\\\\utf_8.txt\"), \"utf-32\"); osw.write(\"你好\"); osw.flush(); osw.close(); }/** InputStreamReader extends Reader* 字符通向字节的桥梁,可以指定编码表* 有继承自父类的共性方法* 构造方法:* InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。 InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader。* 参数:* InputStream in 字节输入流,用来读取文件中保存的字节到内存* String charsetName 编码表名称,不区分大小写,不知道默认使用IED的编码* 使用步骤:* 1.创建InputStreamReader,构造方法中传入字节输入流对象和指定的编码表名称* 2.使用InputStreamReader中的方法read(),把字符转换为字节存储到从文件中读取到内存* 4.释放资源* 注意:构造方法中,指定的编码格式要与文件编码相同,否则乱码* */ private static void main(String[] args) throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(\"F:\\\\FileTest\\\\utf_8.txt\"), \"utf-8\"); int read = 0; while ((read = isr.read()) != -1) { System.out.println((char) read); } isr.close(); } 练习:转换文件编码 将GBK编码的文本文件,转换为UTF-8文本文件 12345678910111213141516171819/* * 1.创建InputStreamReader对象,构造方法传递字节输入流,读取格式为\"GBK\" * 2.创建OutputStreamWriter对象,构造方法传递字节输出流,写格式为\"UTF-8\" * 3.将read()读的数据用writer()写入文件 * 4.释放资源 * */public static void main(String[] args) throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(\"F:\\\\FileTest\\\\a.txt\"), \"GBK\"); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(\"F:\\\\FileTest\\\\d.txt\"), \"UTF-8\"); //准备一个字符数组将读取到的字符存入其中 char[] read = new char[1024]; int Length;//接收读取到数据的有效长度,-1结束 while ((Length = isr.read(read)) != -1) { osw.write(read, 0, Length); } osw.flush(); osw.close(); isr.close();} 序列化对象的序列化: 把对象以流的形式,写入到文件中保存,叫写对象,也叫对象的序列化,对象中包含的不仅仅式字符,故使用字节流. ObjectOutputStream: 对象的序列化流 使用writeObject(对象名)方法 对象的反序列化: 把文件中保存的对象,以流的方式读取出来,叫做读对象,也叫反序列化,读取的文件保存的都是字节,故使用字节流. ObjectInputStream: 对象的反序列化流 使用readObject(对象名)方法 ObjectOutputStream 序列化流 ObjectInputStream 反序列化流 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960/** ObjectOutputStream extends OutputStream* 把对象以流的形式写入到文件中* 注意:* 对象序列化和反序列化时必须实现Serializable接口,以启动序列化,会给类添加一个标记,当序列化时,就会检测类中是否有这个标记,如果有就会正常进行,若没有就会有异常* 构造方法:* ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。* 参数: OutputStream out 传入一个字节输出流*特有的成员方法:* void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。* 使用步骤:* 1.创建ObjectOutputStream对象,构造方法中传递字节输出流* 2.使用writeObject()将对象写入文件* 3.释放资源* */public static void main(String[] args) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"F:\\\\FileTest\\\\d.txt\")); Person p = new Person(\"赵丽颖\", 18); oos.writeObject(p);//NotSerializableException未序列化异常 oos.close();} /** ObjectInputStream extends InputStream* 把文件中的对象读入到内存* 注意:* 反序列化时必须实现Serializable接口,以启动序列化,会给类添加一个标记,当序列化时,就会检测类中是否有这个标记,如果有就会正常进行,若没有就会有异常* 对象的反序列化还有类找不到异常,必须声明处理ClassNotFoundException异常* 构造方法:* ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream。* 参数: InputStream in 传入一个字节输入流*特有的成员方法:* Object readObject() 从 ObjectInputStream 读取对象。* 该方法会有类找不到异常,必须有返回的类才能被读取* 使用步骤:* 1.创建ObjectInputStream对象,构造方法中传递字节输入流* 2.使用readObject()将对象读入内存* 3.释放资源* 4.使用读取处理的对象,例如直接打印** */public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(\"F:\\\\FileTest\\\\d.txt\")); Object o = ois.readObject();//会有ClassNotFoundException类找不到异常 //可以强转为Person类型 Person p = (Person) o; ois.close(); System.out.println(p);}//直接实现Serializable接口即可,该接口只是标记,无须重写任何方法class Person implements Serializable{ private String name; private int age; static final long serialVersionUID = 42L;//序列号问题 //需写构造方法和重写toString() 此处省略} transient 瞬态关键字 static 静态优先于非静态加载到内存中,被static修饰的成员变量时不能被序列化的,序列化的都是对象,而静态不属于对象,是属于类的,序列化时就不会将静态的变量序列化 transient 被transient 修饰的成员变量同样也不能被序列化,同时被transient修饰的关键字也不是静态的. 反序列化可能存在的问题 序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 “serialVersionUID” 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:static final long serialVersionUID = 42L; 练习:序列化集合,往文件中保存多个对象 123456789101112131415161718192021222324252627282930313233343536373839404142/* * 练习:序列化集合 * 当我们想在文件中保存多个对象的时候 * 可以把多个对象存储到一个集合中 * 对集合进行序列化和反序列化 * 分析: * 1.定义一个存储Person对象的ArrayList集合 * 2.往ArrayList集合中存储Person对象 * 3.创建一个序列化流ObjectOutputStream * 4.ObjectOutputStream中的writeObject()对集合进行序列化 * 5.创建ObjectInputStream * 6.使用ObjectInputStream中的readObject()对集合进行反序列化 * 7.把Object类型的集合转换为ArrayList集合 * 8.遍历ArrayList集合 * 9.释放资源 * */public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException { //1.定义一个存储Person对象的ArrayList集合 ArrayList<Person> list = new ArrayList<>(); //2.往ArrayList集合中存储Person对象 list.add(new Person(\"赵丽颖\", 18)); list.add(new Person(\"杨颖\", 19)); list.add(new Person(\"迪丽热巴\", 20)); //3.创建一个序列化流ObjectOutputStream ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"F:\\\\FileTest\\\\list.txt\")); //4.ObjectOutputStream中的writeObject()对集合进行序列化 oos.writeObject(list); //6.使用ObjectInputStream中的readObject()对集合进行反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(\"F:\\\\FileTest\\\\list.txt\")); Object o = ois.readObject(); //7.把读出的Object类型的集合转换为ArrayList集合 ArrayList<Person> list1 = (ArrayList<Person>) o; //8.遍历ArrayList集合 for (Person person : list1) { System.out.println(person); } //9.释放资源 ois.close(); oos.close(); }} 打印流 System.out.println() out其实就是一个打印流, println()是打印流里面的方法 /* * java.io.PrintStream: 打印流 * PrintStream extends OutputStream 有继承自父类的成员方法 * PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。 * 特定: * 1.只负责数据的输出,不负责数据的读入 * 2.与其他输出流不同,PrintStream 永远不会抛出 IOException * 3.有特有的方法print,println 输出任意类型的值 * 构造方法:输出的目的地可以是文件,字节输出流,文件路径 * PrintStream(File file) PrintStream(OutputStream out) PrintStream(String fileName) * 注意: * 如果使用继承自父类的writer()方法写数据,那么查看数据的时候会查询编码表 97->a * 如果使用特有方法print()写数据,写的数据原样输出 97->97 * */ public class DemoPrintStream { public static void main(String[] args) throws FileNotFoundException { //创建一个打印流,构造方法中绑定目的地,抛出的是文件找不到异常,不会有IO异常 PrintStream ps = new PrintStream(\"F:\\\\FileTest\\\\print.txt\"); //97->a ps.write(97); //97->97,可以传递任意数据类型的值 ps.println(97); //使用System中的static void setOut(PrintStream out)方法 //改变输出语句打印流的目的地为参数 System.out.println(\"我是在控制台输出的\"); //将输出语句的目的地改为打印流的目的地 System.setOut(ps); System.out.println(\"此时我实在指定的目的地输出的\"); ps.close(); } }","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"FIle","slug":"FIle","permalink":"http://lqhao.gitee.io/tags/FIle/"},{"name":"IO","slug":"IO","permalink":"http://lqhao.gitee.io/tags/IO/"}]},{"title":"Java-Lambda表达式","slug":"Lambda","date":"2020-07-15T11:53:08.687Z","updated":"2020-07-22T13:32:31.680Z","comments":true,"path":"2020/07/15/3/","link":"","permalink":"http://lqhao.gitee.io/2020/07/15/3/","excerpt":"","text":"lambda表达式 jdk8发布 12345678910111213141516171819202122232425262728293031/*lambda的标准格式 由三部分组成 一些参数 一个箭头 一段代码 (参数列表) -> {一些重写方法的代码};*/public static void main(String[] args) { //普通多线程方式 new Thread(new Runnable() { @Override public void run() { System.out.println(\"线程创建\"); } }).start(); //使用Lambda表达式实现多线程 //大括号就是重写run方法的大括号 new Thread(() -> { System.out.println(\"lambda实现多线程\"); System.out.println(\"lambda实现多线程\"); });}/*语义分析() -> System.out.println(\"lambda实现多线程\"); 前面的一对小括号即run方法的参数(无),代表不需要任何条件 中间的箭头代表将前面的鹅参数传递给后面的代码 后面的鹅输入语句即业务逻辑代码 */ 匿名内部类好处和弊端: 好处: 省去了定义实现类 弊端: 语法确实复杂 Lambda表达式练习 使用Lambda的标准格式(无参数无返回) 12//答案:使用lambda表达式简化匿名内部类的书写invokeCook(() -> System.out.println(\"吃饭啦!\")); Lambda的参数和返回值 1234567891011121314151617181920212223242526272829303132 class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; }}public static void main(String[] args) { Person[] arr = { new Person(\"柳岩\", 38), new Person(\"赵丽颖\", 18), new Person(\"关晓彤\", 19), }; //使用匿名内部类 //对person对象按照年龄升序排序 Arrays.sort(arr, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.age-o2.age; } }); //使用有参数lambda表达式 //对person对象按照年龄升序排序 Arrays.sort(arr,(Person o1, Person o2)-> { return o1.age-o2.age; }); for (Person person : arr) { System.out.println(person.name); } }} 123456789101112131415161718public class TestLambda2 { public static void main(String[] args) { //使用lambda表达式 int a =method(1,2,(int c,int b)-> { return c + b; }); System.out.println(a); } //自定义一个调用接口的方法 public static int method(int a,int b,Calculator calculator){ return calculator.calc(a,b); }}interface Calculator { //定义一个计算两个int整数和的方法,并返回结果 public abstract int calc(int a, int b);} Lambda表达式: 可推导可省略的 凡是根据上下文,推导出来的内容,都可以省略书写 可以省略的内容: (参数列表) :括号中的草书列表的数据.可以省略不写 (参数列表): 括号中的参数如果只有一个,那么类型和()都可以省略 (一些代码): 如果{ }中的代码只有一行,无论是否有返回值, { } ,return , 分号都可以省略(要省略,他们三个必须一起省略) JDK1.7版本之前,创建集合对象必须把前后的泛型都写上,JDK1.7之后,等号右边的泛型省略,可以根据右边的泛型推导出来 Lambda表达式使用前提 使用Lambda必须具有接口,且接口内有且只有一个抽象方法 无论是JDK的Runnable 、Compareator接口,还是自定义的方法,当只有接口中的抽象方法存在且唯一时,才可以使用Lambda 使用Lambda必须具有上下文推断 也就是方法的参数或局部变量必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例 备注: 有且仅有一个抽象方法的接口,称为“函数式接口”","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"Lambda","slug":"Lambda","permalink":"http://lqhao.gitee.io/tags/Lambda/"}]},{"title":"Java-异常、线程","slug":"Java-异常、进程","date":"2020-07-15T09:17:27.111Z","updated":"2020-07-22T13:30:42.959Z","comments":true,"path":"2020/07/15/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/15/1/","excerpt":"","text":"Debug调试程序 可以让代码逐行执行,查看代码执行的过程,调试程序出现的Debug 使用方式 可以在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug,添加到哪里) 右键选择debug执行程序,程序就学会停留在添加的第一个断点处 执行程序 F8: 逐行执行程序 F7: 进入到方法中 shift+F8 跳出方法 F9 : 跳到下一个断点,如果没有下一个断电,那么就结束程序 ctrl+F2 : 退出debug模式,停止程序 异常异常体系 Throwable 类是所有错误或异常的超类 Exception:编译器异常 ,进行编译(写代码),Java程序出现的问题 RuntimeException :运行期异常,Java程序运行过程中出现的问题 Error: 错误,系统内部的错误,必须修改代码,程序才能继续执行 12345try{ //可能出现异常的代码} catch(Exception e){ //处理异常} 异常的处理过程: jvm检测到异常,会做两件事 JVM会根据异常产生的原因,创建一个异常对象,这个异常对象包含了异常产生的内容、原因、位置 例:数组中索引最大为2,却要访问3,会有数组越界异常 new ArrayIndexOutIfBoundsException(“3”) 在所调用的方法中没有异常的处理逻辑,JVM就会把异常抛出给异常的调用者main方法,来处理这个异常,main接收到这个异常对象,但main方法也没有处理措施,main继续把对象抛出给main方法的调用者JVM处理,JVM接受到这个异常,又会做两件事1. 把异常对象(内容、原因、位置)以红色的字体打印在控制台 2. JVM会终止当前正在执行的java程序-->中断处理 throw关键字 可以使用throw关键字在指定的方法中抛出指定的异常 12//使用格式throw new xxxException(\"异常产生的原因\") 注意: throw关键字必须写在方法的内部 throw关键字后边的new的对象必须是Exception或者是Exception的子类对象 throw关键字抛出指定的异常对象,我们就必须处理这个异常对象 throw关键字后面创建的是RuntimeException或者是RuntimeException 的子类对象,我们可以不处理,默认交给JVM处理(打印异常,中断程序) throw关键字后面创建的是编译异常(写代码时报错),我们就必须处理这个异常,要么throws要么try…catch… 实际中方法接收参数后必须对方法进行合法性检测,若不合法,需抛出异常,告知使用者 123456789101112131415public static int getElement(int[] arr, int index){ /* 我们需对传递过来的参数数组进行合法性校验 如果arr的值是null,那么就需要抛出空指针异常,告知方法的调用者,传递的数组值是null 空指针异常是一个运行期异常,默认交给JVM处理 */ if (arr == null){//如果arr的值是null,抛出空指针异常 throw new NullPointerException(\"传递的数组值为null\"); } //如果index不在数组索引范围内,则抛出索引越界异常,告知方法调用者,传递的索引有误 if(index < 0 || index > arr.length()-1){ throw new ArrayIndexOutIfBoundsException(\"索引越界了\"); }} Objecs非空判断 123456789101112public static void method(Object obj){ //对传递过来的参数进行合法性判断,判断是否为null /* if(obj == null){ throw new NullPointerException(\"传递的对象值为null\"); } */ //使用Objects的requireNonNull静态方法,可简化上述代码 Objects.requireNull(obj); //重载的requireNonNull方法,可以输出提示语 Objects.requireNull(obj,\"传递的对象值为null\");} 异常处理throws 12345678910111213141516171819202122232425262728293031323334353637/**throws关键字: 异常处理的第一种方式,交给别人处理*使用格式:在方法声明时使用* 修饰符 返回值类型 方法名(参数列表) throws AAAException, throw new BBBException{* throw new AAAException(\"产生原因\");* throw new BBBException(\"产生原因\");* ...* }* 注意:* 1. throws关键字必须写在方法的声明出* 2. throws关键字后边声明的异常必须是Exception或者是Exception的子类* 3. 方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常* 如果抛出的多个异常对象又子父类关系,那么直接声明父类即可* 4.调用了一个声明抛出异常的方法,就必须声明处理的异常,要么继续使用throws* 声明抛出,交给方法的调用者处理,最终交给JVM.要么try...catch...* */public class DemoThrows { //FileNotFoundException extends IOException 可不声明 public static void main(String[] args) throws Exception {//此处继续抛出,交给JVM处理 readFile(\"C:\\\\a.txt\");//会有编译异常,必须处理 } /* 定义方法判断文件路径的合法性,若不是C:\\\\a.txt抛出文件未发现异常,不是.txt结尾抛出io异常 * */ //public static void readFile(String filename) throws FileNotFoundException,IOException { //public static void readFile(String filename) throws IOException { public static void readFile(String filename) throws Exception { if(!filename.equals(\"C:\\\\a.txt\")){ throw new FileNotFoundException(\"传递文件类型错误\");//此处为编译异常,必须处理 } if(!filename.endsWith(\".txt\")){ throw new IOException(\"文件后缀出错\"); } System.out.println(\"路径正确\"); }} try{…}catch{…} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 /* * try...catch... : 自己处理异常 * 格式: * try{ * 可能出现异常的代码 * } * catch(定义异常异常变量,用来接收try的异常对象){ * 异常的处理逻辑,有异常对象之后,怎么处理异常 * 一般会把异常信息记录到一个日志中 * } * ...此处可包含多个catch * catch(定义异常异常变量,用来接收try的异常对象){} * finally{ * 无论是否出现异常都会执行 * finally不能单独使用,必须与try一起使用, * 一般用于资源释放 * } * 执行完try..catch..后会继续执行之后的代码 * */ //测试输入的文件名是否符合要求public class DemoTryCatch { public static void main(String[] args) { try { readFile(\"C:\\\\a.tx\"); //try抛出什么异常,catch定义什么异常对象,用来接收异常 } catch (Exception e) { //System.out.println(\"文件后缀出错\"); /* ThrowAble类 定义了三个方法 String getMessage() 返回此 throwable 的简短描述。 String toString() 返回此 throwable 的详细消息字符串。 void printStackTrace() JVM打印异常默认调用此方法,异常信息最全面 */ System.out.println(e.getMessage()); System.out.println(e.toString()); e.printStackTrace(); } finally { //\"无论是否出现异常都会执行\" System.out.println(\"资源释放\"); } System.out.println(\"后续代码\");//有try..catch..此处不会中断,会执行 } //文件名合法性检测函数,若不符合要求,抛出异常 public static void readFile(String filename) throws Exception { if (!filename.endsWith(\".txt\")) { throw new IOException(\"文件后缀出错\"); } System.out.println(\"路径正确\"); } } 注意事项 多个异常的捕获 多个异常分别处理 多个异常一次捕获,多次处理(通常使用) 注意事项: catch里边定义的异常变量,如果有子父类关系,那么子类的异常catch必须定义在上面,否则就会报错 多个异常一次捕获,一次处理 运行时异常,可以既不捕获,也不声明 如果finally有return语句,将会永远返回finally的结果,应当避免finally中写return 父类异常是什么样,子类方法就什么样 如果父类出现了多个异常,子类重写父类方法时,可抛出和父类相同的异常或者是父类异常的子类,或者不抛出异常 若父类方法没有抛出异常,子类重写父类该方法时也不能抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出 在try/catch后可以追加finally代码块,其中的代码就一定会执行,通常用于资源回收 12345678910111213141516171819202122232425262728293031323334/** 父类异常是什么样,子类方法就什么样 - 如果父类出现了多个异常,子类重写父类方法时,可抛出和父类相同的异常或者是父类异常的子类,或者不抛出异常 - 若父类方法没有抛出异常,子类重写父类该方法时也不能抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出*/public class Fu { public void show01() throws NullPointerException, ClassCastException { } public void show02() throws IndexOutOfBoundsException { } public void show03() throws IndexOutOfBoundsException { } public void show04() { }}class Zi extends Fu { //子类重写父类方法时,可抛出和父类相同的异常 public void show01() throws NullPointerException, ClassCastException { } //子类重写父类方法时,可抛出父类异常的子类 public void show02() throws ArrayIndexOutOfBoundsException { } //子类重写父类方法时,可不抛出异常 public void show03() { } //父类方法没有抛出异常,子类重写父类该方法时也不能抛出异常,只能捕获异常 public void show04() /*throws Exception 不允许,只能处理异常*/ { //throw new Exception();//报错 //只能try/catch try { throw new Exception(\"编译器异常\"); }catch (Exception e){ //在此处处理异常 e.printStackTrace(); } }} 自定义异常 12345678910111213141516171819202122232425/* * Java提供的异常不够我们使用时,需要自定义异常类 * 格式: * public class XXXException extends Exception / RuntimeException{ } * 需要添加一个空参构造方法,一个带异常信息的构造方法 * 注意: * 1.自定义异常类一把都是以Exception结尾,说明该类是一个异常类 * 2.自定义异常类,必须继承: * Exception(编译器异常),如果方法内部抛出异常,必须throws或try/catch * 或者RuntimeException(运行期异常),那么自定义异常无需处理,交给JVM处理 * */public class RegisterException extends Exception { //添加一个空参构造方法 public RegisterException() { super(); } //添加一个带异常信息的构造方法 //查看源码发现,所有的异常类的构造方法都会调用父类,让父类来处理异常 public RegisterException(String message) { super(message); }} 1234567891011121314151617181920212223242526272829303132333435/*用户存在抛出异常 * 分析: * 1.使用数组保存已经注册的用户名 * 2.使用Scanner让用户输入注册的用户名 * 3.定义一个方法对用户输入注册的用户,进行判断是否重复 * 变量用户名数组,比较用户是否存在. * 若存在抛出RegisterException,告知用户用户名已被注册 * 若不存在继续遍历比较,提示用户,注册成功 * */public class TestRegisterException { String[] usernames = {\"张三\", \"李四\", \"王五\"}; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println(\"请输入用户名\"); String name = scanner.next(); try { checkUsername(name); } catch (RegisterException e) { e.printStackTrace(); } } //用户名合法性自定义方法 public static void checkUsername(String name) throws RegisterException { for (String username : usernames) { if (name.equals(username)) { throw new RegisterException(\"该用户已被注册\"); } } System.out.println(\"恭喜注册成功\"); }} 多线程 并发与并行 并发:指两个或多个事件在同一时间段内完成 并行:指两个或多个事件在同一时刻发生(同时发生) 线程与进程 进程: 一个内存中运行的应用程序,画图,qq,浏览器 线程: 是进程的一个执行单元,一个进程中可以有多个线程,多线程间互不影响 主线程:执行main方法的程序 Thread 创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。 创建线程的另一种方法是声明实现 Runnable 接口的类。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/** 创建多线程的第一种方式 创建Thread的子类* 实现步骤:* 1.创建Thread类的子类* 2.在Thread类的子类中重写子类中的方法,设置线程任务(做什么)* 3.创建Thread类的子类对象* 4.调用Thread类中的start()方法,开启新的线程,JVM调用该线程的run方法* 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。* 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。* Java程序属于抢占式调度,优先级高先执行* *//** 获取线程的名称* 1.使用Thread类中的getName()返回线程名称* String getName() 返回该线程的名称。* 2.可以先获取到当前正在执行的线程,使用线程中的方法getName()返回线程名称* static Thread currentThread() 返回对当前正在执行的线程对象的引用。* 主线名: main* 新线程名:Thread-0 Thread-1...* 设置线程的名称* 1.void setName(String name) 改变线程名称,使之与参数 name 相同。* 2.创建一个带参数的构造方法,参数传递线程名称,super()父类的构造方法,* 把线程名称传递给父类,让Thread给子线程起名字* */public class MyThread extends Thread{ String name; ////第二种方式设置线程名,传入线程名,可传参修改线程名 public MyThread(String name, String name1) { super(name); this.name = name1; } @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(\"run\"+i); } }}//main中使用:public static void main(String[] args) { MyThread p1 = new MyThread(); p1.start();//会去调用run方法,与后续的循环并行执行 //第一种方式设置线程名 p1.setName(\"小强\"); //第一种方式获取当前线程的名称 String name = p1.getName(); //会暂停一秒,再打印输出 Thread.sleep(1000); System.out.println(name);//thread-0 //第二种获取当前线程的名称,此处为main主线程名称 Thread t = Thread.currentThread(); System.out.println(t); System.out.println(t.getName()); //第二种方式设置线程名 MyThread p2 = new MyThread(\"新线程名\",\"小强\"); //后续的 for (int i = 0; i < 20; i++) { System.out.println(\"main\"+i); }} Thread的方法 String getName() 返回该线程的名称。 void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 void run() 线程要执行的任务在此处定义 static void sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停,有异常 static Thread currentThread() 返回对当前正在执行的线程对象的引用 1234567891011121314151617181920212223242526272829303132/** 创建多线程的第二种方式,实现Runnable接口* Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法* Thread的构造方法可以传递Runnable的实现类对象* 实现步骤:* 1.创建Runnable接口的实现类* 2.在实现类中重写Runnable接口的run方法,设置线程任务* 3.创建Runnable实现类对象,构造方法中传递Runnable接口的实现类对象* 4.创建Thread类对象,构造方法中传递Runnable接口的实现类* 5.调用Thread类中的start()方法,开启新线程的run()方法* *///创建Runnable接口的实现类public class PrimeRun implements Runnable{ //设置线程任务 @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+\" \"+i); } }}//main中使用private static void main(String[] args) { //创建Runnable实现类对象 PrimeRun primeRun = new PrimeRun(); //创建Thread类对象,构造方法中传递Runnable接口的实现类 Thread t = new Thread(primeRun); //调用Thread类中的start()方法,开启多线程 t.start(); System.out.println(\"后续代码\");} Thread和Runnable的区别 其实就是实现Runnable接口创建多线程程序的好处 避免了单继承的局限性, 一个类只能继承一个类,类继承了Thread类就不能继承其他的类,实现了Runnable接口,还可以继承其他的类,实现其他的接口 增强了程序的扩展性,降低了程序的耦合性(解耦),实现了Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦),实现类中,重写了run()方法,用来设置线程任务,创建Thread类对象,调用start()方法,设置线程任务 使用过程中尽量使用Runnable接口 匿名内部类实现线程的创建 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748/*匿名内部类实现线程的创建 * 匿名: 没有名字 * 内部类: 写在其他类内部的类 * 匿名内部类: 简化代码 * 格式: * new 父类/接口(){ * 重写父类/接口中的方法 * }; * */public static void main(String[] args) { //new MyThread.start(); new Thread() { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + \" \" + i); } } }.start(); //线程的接口Runnable Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + \"程序员\" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; new Thread(r).start(); //简易写法 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + \"农学人\" + i); } } }).start();} 线程安全 概述 实现买票 123456789101112131415161718192021222324252627282930313233public class RunnableImpl implements Runnable { private int ticket = 100; //设置买票任务 @Override public void run() { //判断票是否存在 while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + \"正在卖第\" + ticket + \"张票\"); ticket--; } } }}//main中使用public static void main(String[] args) { //创建三个线程,同时开启,对共享的票出售 //创建Runnable即接口的实现类对象 RunnableImpl run = new RunnableImpl(); //创建Thread类对象,构造方法中传递Runnable接口实现类对象 Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); //调用start开启多线程 t1.start(); t2.start(); t3.start();}/*线程安全是不允许产生的,我们可以让一个线程访问共享数据的时候,无论是否失去了CPU的执行权,让其他线程只能等待,等待当前线程买完票,其他线程再进行买票,保证始终一个线程在买票,此时就需要线程同步*/ 线程同步 同步代码块 同步方法 锁机制 1234567891011121314151617181920212223242526272829303132333435363738394041同步代码块格式:synchronized(同步锁){ 需要同步的代码块或者访问了共享数据的代码}/* * 解决线程安全的第一种方法:使用同步代码块* 买票案例出现了线程安全问题* 卖出了不存在的票和重复的票* 注意:* 1.同步代码块中的锁对象可以是任意的对象* 2.但是必须保证多个线程使用的锁对象是同一个* 3.锁对象作用:* 把同步代码块锁住,只让一个线程在同步代码块中* */public class RunnableImpl implements Runnable { //一共的票数 private int ticket = 100; //创建一个锁对象 Object obj = new Object(); //设置买票任务 @Override public void run() { //判断票是否存在 while (true) { //同步代码块 synchronized (obj) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + \"正在卖第\" + ticket + \"张票\"); ticket--; } } } }} 同步技术的原理: 使用了一个锁对象,这个锁对象叫同步锁,也叫对象监视器, 三个线程一起抢夺CPU的执行权,谁抢到了谁执行run()方法进行买票,t0抢到了CPU的执行权,执行run()方法,遇到synchronized代码块,这时t0会检查synchronized代码块 是否有锁对象,发现有,就会获取到锁对象,进入到同步执行, t1抢到了cpu的执行权,执行run()方法,遇到synchronized代码块,这时t1也会检查synchronized代码块是否有锁对象, 发现没有,t1就会进入到阻塞状态,一直等待t0线程归还锁对象,一直到t0线程执行完同步中的同步代码,t0就会把锁对象归还给同步代码块,这时候,t1才能获取到锁对象,进入同步代码块执行 一句话: 同步中的线程没有执行完毕,不会释放锁. 同步外的线程,没有锁,不会进入线程 同步保证了只能有一个线程在同步中执行共享数据,保证了安全,但是,频繁的判断锁,获取锁,释放锁,程序的效率就会有所降低 同步方法 12345678910111213141516171819202122232425262728293031323334353637383940同步方法格式:public synchronized void method(){ 可能会产生线程安全的代码}public class RunnableImpl implements Runnable { private int ticket = 100; //创建一个锁对象 Object obj = new Object(); /* * 解决线程安全的第二种方法:使用同步方法 * 使用步骤: * 1.把访问了共享数据的代码块抽取出来,放到一个方法中 * 2.在方法上添加一个synchronized修饰符 * */ //定义同步方法 public synchronized void method(){ //放需要同步的代码 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + \"正在卖第\" + ticket + \"张票\"); ticket--; } } //设置买票任务 @Override public void run() { //判断票是否存在 while (true) { //调用同步方法 method(); } }} 同步方法也会把方法内部的代码锁住,只让一个线程执行,同步方法的锁对象其实就是实现类对象,也就是this 也可将同步方法设为静态同步方法,同样能够保证安全,静态同步方法的锁对象不能是this,this是创建对象产生的,而静态方法的创建优先于对象,静态同步方法的锁对象是本类的class属性–>class文件对象 synchronized(RunnableImpl.class) Lock锁方法 (java.util.concurrent.locks)包,是一个接口需要用它的实现类ReentrantLock Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 方法 void lock() 获取锁 void unlock 释放锁 1234567891011121314151617181920212223242526272829303132333435public class RunnableImpl implements Runnable { private int ticket = 100; /* * 解决线程安全的第三种方法:使用Lock锁 * 使用步骤: * 1.在成员位置创建一个ReentrantLock对象 * 2.在可能出现安全问题的代码前调用Lock接口中的方法lock()获取锁 * 3.在可能出现安全问题的代码后调用Lock接口中的方法unlock获取锁 * */ //在成员位置创建一个ReentrantLock对象 Lock lock = new ReentrantLock(); //设置买票任务 @Override public void run() { while (true) { //调用前获取锁 lock.lock(); if (ticket > 0) { try { Thread.sleep(100); System.out.println(Thread.currentThread().getName() + \"正在卖第\" + ticket + \"张票\"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); } finally { //将释放锁放到finally中,无论程序是否异常,都释放锁 //可以提供程序效率 lock.unlock(); } } } }} 线程六种状态 NEW 至今尚未启动的线程处于这种状态。 RUNNABLE 正在 Java 虚拟机中执行的线程处于这种状态。 BLOCKED 受阻塞并等待某个监视器锁的线程处于这种状态。 WAITING 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。 TIMED_WAITING 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。 TERMINATED 已退出的线程处于这种状态。 等待唤醒案例:线程之间的通信 多个线程在处理同一个资源,但是处理的动作(线程的任务)却不同 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586/** 创建一个消费者线程,告知生产者所需资源种类与数量,调用wait()方法,进入无限等待状态* 创建一个生成者线程,花了5秒生成资源,生成之后,调用notify()方法,唤醒消费者* 注意:* 1.生产者和消费者必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行* 2.同步使用的锁对象必须唯一* 3.只有锁对象才能调用wait()和notify()方法* Object中的方法* void wait()* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。进入wait set中,不会浪费CPU资源,线程状态时WAITING* void notify()* 唤醒在此对象监视器上等待的单个线程。 唤醒之后会继续执行wait之后的代码,线程后被唤醒,继续检测锁的状态,如果能获取到锁,就进入就绪态,如果获取不到,变为阻塞状态* *//** 进入TimeWaiting(计时等待)两种方式* 1.使用sleep(long m)方法,在线程睡醒后进入Runnable或Block* 2.使用wait(long m)方法,如果在毫秒值结束后还未被notify(),就会自动醒来** 唤醒的方法:* 1.notify()唤醒单个线程* 2.notify()All唤醒所有线程* */public static void main(String[] args) { //创建锁对象,保证唯一 Object obj = new Object(); //创建消费者1进程 new Thread() { @Override public void run() { //保证等待和唤醒只能有一个执行,使用同步技术 synchronized (obj) { System.out.println(\"消费者1告知生产者资源种类和数量\"); //用锁对象调用wait方法进入无限等待状态 try { obj.wait(5000);//5秒后还未被notify(),就会自动醒来. } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println(\"消费者1唤醒之后执行的代码\"); } } }.start(); //创建消费者2进程 new Thread() { @Override public void run() { //保证等待和唤醒只能有一个执行,使用同步技术 synchronized (obj) { System.out.println(\"消费者2告知生产者资源种类和数量\"); //用锁对象调用wait方法进入无限等待状态 try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println(\"消费者2唤醒之后执行的代码\"); } } }.start(); //创建生产者进程 new Thread(){ @Override public void run() { //生产者花五秒钟生成资源,睡眠5秒 try { Thread.sleep(5000);//花五秒钟生成资源, } catch (InterruptedException e) { e.printStackTrace(); } //保证等待和唤醒只能有一个执行,使用同步技术 synchronized (obj){ System.out.println(\"5秒之后生成资源,告知消费者\"); //obj.notify();//唤醒单个进程 obj.notify();//两个消费者进程都唤醒 } } }.start();} 等待唤醒类(生产者消费者类)实现 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157/* * 包子类 * 属性: 皮 馅 * 包子状态: 有 true 没有false * */public class BaoZi { String pi; String xian; Boolean flage = false;}/* * 生产者(包子铺)类: 是一个线程类,可以继承Thread * 设置线程任务(run):生产包子 * 对包子状态进行判断: * true: 有包子 * 包子铺调用wait()方法等待顾客 * false: 没包子 * 包子铺交替生产两种包子(i%2==0) * 包子铺生产好包子,修改包子状态为true * 唤醒吃货线程 * 注意: * 包子铺线程和吃货线程关系-->通信(互斥) * 必须使用同步技术保证两个线程只有一个在执行 * 锁对象必须保证唯一,可以使用包子作为锁对象 * 包子铺类和吃货类需要把包子对象作为参数使用 * 1.在成员位置创建一个包子变量 * 2.使用带参构造方法,为包子赋值 * */public class BaoZiPu extends Thread { //在成员位置创建一个包子变量 private BaoZi baoZi; //使用带参构造方法,为包子赋值 public BaoZiPu(BaoZi baoZi) { this.baoZi = baoZi; } //使用同步技术保证两个线程只有一个在执行 @Override public void run() { //设置一个变量记录生产何种包子 int count = 0; //让包子铺一直生产包子 while (true) { //使用同步技术 synchronized (baoZi) { //如果有包子铺有包子则等待 if (baoZi.flage == true) { try { baoZi.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //若没有包子 //被唤醒后执行,生产包子 //交替生产两种包子(i%2==0) if (count % 2 == 0) { //生产薄皮三鲜馅包子 baoZi.pi = \"薄皮\"; baoZi.xian = \"三鲜馅\"; } else { //生产冰皮,牛肉大葱陷 baoZi.pi = \"冰皮\"; baoZi.xian = \"牛肉大葱陷\"; } count++; System.out.println(\"包子铺正在生产\" + baoZi.pi + baoZi.xian + \"包子\"); //生产包子需要3秒 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //生产好包子,修改包子状态为true baoZi.flage = true; //唤醒吃货进程 baoZi.notify(); System.out.println(\"包子生产好了\" + baoZi.pi + baoZi.xian + \"包子,可以开吃了\"); } } }}/* * 消费者(吃货)类:是一个线程类,可以继承Thread * 设置线程任务(run):吃包子 * 对包子状态进行判断: * true: 没包子 * 包子铺调用wait()方法等待做包子 * false: 有包子 * 吃包子,吃货吃完包子 * 修改包子的状态为false没有 * 唤醒包子铺线程,生产包子 * */public class ChiHuo extends Thread { //在成员位置创建一个包子变量 private BaoZi baozi; //使用带参构造方法,为包子赋值 public ChiHuo(BaoZi baoZi) { this.baozi = baoZi; } @Override public void run() { //一直让吃货吃包子 while (true) { //使用同步技术 synchronized (baozi) { //若没有包子则等待 if (baozi.flage == false) { try { baozi.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //包子好了 //被唤醒之后 System.out.println(\"正在吃包子,\" + baozi.pi + baozi.xian + \",真香\"); //吃包子要两秒 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //吃完包子设置状态为没包子了 baozi.flage = false; //唤醒包子铺线程 baozi.notify(); System.out.println(\"吃完了,让包子铺开始生产\"); System.out.println(\"=======================\"); } } }}/* * 包含main方法启动程序 * 创建包子对象 * 创建包子铺线程,做包子 * 创建吃货线程,吃包子 * */public class DemoTest { public static void main(String[] args) { BaoZi baoZi = new BaoZi(); new BaoZiPu(baoZi).start(); new ChiHuo(baoZi).start(); }} 线程池 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这种频繁创建线程就会影响系统的效率,因为频繁创建线程和销毁线程也需要时间. 线程池: 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源 线程容器–>集合(ArrayList HashSet LinkedList<Thread> HashMap) 用LinkedList<Thread>最好 当程序第一次启动的时候,可以创建多个线程保存到集合中, 当要使用线程的时候,就可以从集合中取出线程来使用 使用Thread t = list.remove(0);返回被移除的第一个元素(线程只能被一个任务使用) 或Thread t = linked.removeFirst(); 返回被移除的第一个元素 当使用完毕线程 ,需要把线程归还给线程池 list.addd(0); 或 linked.addLast(); 在jdk1.5之后jdk内置了线程池,可以直接使用,不用自己创建 好处: 降低资源消耗 提高响应速度 提高线程的可观理性 线程池的使用 Executors 生产线程池的工厂类其中有一个静态方法返回一个线程池 static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。参数nThreads: 创建的线程池中的线程数 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849/* * 线程池: jdk1.5之后 * java.util.concurrent.Executors: 线程池的工厂类,用来生产线程池 * 方法: static ExecutorService newFixedThreadPool(int nThreads) * 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。 * 参数: * 创建线程池中线程数 * 返回值: * ExecutorService接口类型,返回的ExecutorService的实现类对象,可以使ExecutorService来接收(面向接口编程) *java.util.concurrent.ExecutorService:线程池接口 * 从线程池中获取线程,调用start方法,执行线程任务: * Future<?> submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。 //返回值Future<?>可以不管 * 关闭/销毁线程池的方法: * void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务 * 使用步骤: * 1.使用线程池的工厂类Executors里边的静态方法newFixedThreadPool生产一个指定数量的线程池 * 2.创建一个类,实现Runnable接口,重写run方法,设置线程任务 * 3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 * 4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行) * *///实现Runnable接口,重写run方法,设置线程任务public class RunnableImpl implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + \"创建了一个新线程\"); }}//main中使用线程池public static void main(String[] args) { //线程池的工厂类Executors里边的静态方法newFixedThreadPool生产一个有2个线程的线程池 ExecutorService es = Executors.newFixedThreadPool(2); //调用ExecutorService中的方法submit,传递线程任务(实现类),自动开启线程,执行run方法 es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新线程 //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用 es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新线程 es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新线程 //调用ExecutorService中的方法shutdown销毁线程池(不建议执行) es.shutdown(); es.submit(new RunnableImpl());//出错,会有异常,线程池已销毁,不能获取线程了}","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"Exception","slug":"Exception","permalink":"http://lqhao.gitee.io/tags/Exception/"},{"name":"Thread","slug":"Thread","permalink":"http://lqhao.gitee.io/tags/Thread/"}]},{"title":"Java-日期、System类、Collection集合","slug":"Java-日期-集合","date":"2020-07-13T02:42:23.188Z","updated":"2020-07-22T13:27:38.274Z","comments":true,"path":"2020/07/13/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/13/1/","excerpt":"","text":"Object 类Object是类层次结构的父类 每个类都使用Object作为超类,包括自己定义的类 toString()方法:打印的只是对象的地址值,使用时需重写。 直接打印实例,其实就是调用了toString()方法 equals()方法:默认是“==”用来比较对象的地址值,纯比较地址来说没有意义,所以需要重写,比较两个对象的属性(name,age) == 基本数据类型比较的是值 引用数据类型比较的是地址值 日期时间类 Date:(java.util包)表示日期和时间的类 1000毫秒=1秒 毫秒值的作用:可以对时间和日期进行计算 时间原点 1970年1月1日 00:00:00 英国格林尼治时间 System.currentTimeMillis();计算当前系统时间到时间原点经历了多少毫秒 注意:中国属于东八区,会把时间增加8个小时 1970年1月1日 08:00:00 构造方法 空参Date() :获取系统的日期时间 带参Date(long date): 传递毫秒值,把毫秒值转换为日期 成员方法 getTime():返回从原点时间到当前时间的毫秒值,相当于System.currentTimeMillis(); DateFormat类:(java.text包)日期格式化类,是一个抽象类,不能直接使用,要用他的子类SimpleDateFormat类 成员方法 String format(Date date)将一个 Date 格式化为日期/时间字符串。 Date parse(String source) 把符合构造方法中模式的字符串,解析生成一个日期。 会有 ParseException 异常,如果字符串跟构造方法中不一样,程序就会抛出异常。 SimpleDateFormat类 构造方法 SimpleDateFormat(String pattern) 用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。y 年 M 月 d 日 H 时 m 分 s 秒 “yyyy-MM-dd HH:mm:ss” 模式中的字母不能改,连接模式的符号可以改 Calendar (java.util包)日历类,抽象类,但是提供了getInstance()静态方法,该方法直接返回对象Calender类的子类对象 Calendar calendar = Calendar.getInstance(); //多态 提供了很多操作日历字段的方法 YEAR MOUTH DAY public static final int YERA public static final int MONTH (西方月份0-11 东方月份1-12) 成员方法 int get (int field) :返回给定日历字段的值 field指日历类的字段可以使用静态变量来获取 void set(int field , int value):将给定的日历字段设置为给定值 value给指定字段设置的值 absteact void add(int field , int amount):根据日历的规则,为指定的日历字段添加或减去指定的时间量 Date gettime() :返回一个表示Calendar时间值(从历元到现在的毫秒偏移量)的Date对象 综合练习题1234567891011121314151617181920212223242526/* 计算一个人出生了多少天 * Scanner获取出生日期 * 使用DateFormat类中的方法parse,把字符串的出生日期,转换为Date格式的出生日期 * 把Date格式的出生日期转换为毫秒值 * 获取当前的日期转换为毫秒值 * 两值相减得到出生了多少毫秒值 * 把毫秒差值转换为天 (s/100/60/24) * */public class Test { public static void main(String[] args) throws ParseException { Scanner scanner = new Scanner(System.in); System.out.println(\"格式 yyyy-mm-dd\"); String birthdayString = scanner.next(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd\"); Date birthdayDate = simpleDateFormat.parse(birthdayString);//此处需抛异常 System.out.println(\"出生日期\" + birthdayDate); long birthdayss = birthdayDate.getTime(); Date today = new Date(); System.out.println(\"现在时间\" + today); long now = today.getTime(); long ss = now - birthdayss; System.out.println(\"你已经活了\" + ss / 1000 / 60 / 60 / 24 + \"\"); }} System类 (java.lang包)可以获取系统相关的信息或系统级操作,其中的方法都是静态的,可以通过类名直接使用 常用方法 long System.currentTimeMillis(); 返回以毫秒为单位的当前时间,可以用于测试程序的运行时间 void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。 src - 源数组。 srcPos - 源数组中的起始位置。 dest - 目标数组。 destPos - 目标数据中的起始位置。 length - 要复制的数组元素的数量。 12345678private static main(String[] args) { // [1,2,3,4,5] [6,7,8,9,10] ==> [1,2,3,4,5] [1,2,3,9,10] int[] a = {1,2,3,4,5}; int[] b = {6,7,8,9,10}; System.out.println(Arrays.toString(a)+\" \"+ Arrays.toString(b)); System.arraycopy(a,0,b,0,3); System.out.println(Arrays.toString(a)+\" \"+ Arrays.toString(b)); } StringBulider String类 字符串是常量;它们的值在创建之后不能更改,字符串的底层时一个被final修饰的数组,不能改变,是一个常量 进行字符串的相加,内存在就会有多个字符串,占用空间多,效率低下。 String s = “a” + “b” + “c”= “abc” 会产生五个字符串 a b c ab abc StringBulider 字符串缓冲区,可以提高操作效率(看成一个长度可变的字符串) StringBulider 底层也是一个数组,但没有被final修饰,可以改变长度 StringBulider 在内存中始终是单个数组占用空间少,效率高,如果超出了StringBulider 的容量,会自动扩容 构造方法 StringBulider () 构造一个不带任何字符的字符串生成器,其初始容量为 16 个字符。 StringBuilder(String str) 构造一个字符串生成器,并初始化为指定的字符串内容。 成员方法 StringBulider append() 将任意类型的数据追加到序列,并返回this,调用方法的对象本身,使用append方法无需接受返回值 链式编程 :方法返回值是一个对象,可以继续调用方法 strBu.append(1).append(8.8).append(“abc”).append(“中”); String toString() 实现StringBulider和String的相互转换 String=>StringBuilder 用构造方法就可以 StringBulider=>String 用toString()方法 1234567private static main(String[] args) { String str =\"hello\"; StringBuilder stringBuilder = new StringBuilder(str); stringBuilder.append(\"123\").append(true).append(\"中华\"); String str1 = stringBuilder.toString(); System.out.println(str1);} 包装类 基本数据类型,使用起来非常方便,但是没有对应的方法来操作这些基本类型的数据,可以使用一个类,吧基本类型的数据装起来,在类中定义一些方法,这些类叫做包装类,我们可以使用类中的方法来操作这些基本类型的数据 装箱与拆箱 12345678910111213141516171819202122232425/*装箱:把基本类型的数据,包装到包装类中(基本数据类型->包装类)以Integer举例构造方法: Integer(int value) 构造一个新分配的 Integer 对象,它表示指定的 int 值。 Integer(String s) 构造一个新分配的 Integer 对象,它表示 String 参数所指示的 int 值。 传递的字符串必须是基本类型的数据 \"100\"正确 \"a\"错误静态方法: Integer(int value) 构造一个新分配的 Integer 对象,它表示指定的 int 值。 Integer(String s) 构造一个新分配的 Integer 对象,它表示 String 参数所指示的 int 值。 拆箱:在包装类中取出基本类型的数据(包装类->基本数据类型)成员方法: int intValue() 以 int 类型返回该 Integer 的值。 */private static main(String[] args) { //装箱 Integer integer = new Integer(\"100\"); Integer integer1 = new Integer(100); Integer integer2 = Integer.valueOf(1); Integer integer3 = Integer.valueOf(\"2\"); System.out.println(integer);//100 //拆箱 int a = integer.intValue(); System.out.println(a);} 自动装箱与自动拆箱 1234567891011121314/** 自动装箱与拆箱:基本数据类型与包装类之间可以自动相互转换* jdk1.5之后的新特性*/ private static main(String[] args) { //自动装箱 直接把int类型的数据赋值给包装类 Integer integer = 1; //自动拆箱:integer是包装类,无法直接参与运算,可以自动转换为基本数据类型,再进行计算 //相当于 integer = integer.intValue()+2; integer = integer + 2; ArrayList<Integer> list = new ArrayList<>(); list.add(1);//这个方法本来需要存放integer类型的数据,但是此处自动装箱了 int a = list.get(0);//本需要拆箱的,此处自动拆箱了 } 基本类型与字符串类型直接的相互转换 基本类型变为字符串 基本类型的值+”” (最简单的方法) 包装类的静态方法toString(参数), 不是Object的toString(). static String toString(int i) 返回一个表示指定整数的 String 对象。 String类的静态方法valueOf(参数) static String valueOf(int i) 返回 int 参数的字符串表示形式 字符串变为基本类型 使用包装类的静态方法parseXXX(“数值类型的字符串”) Integer类 : static int parseInt (String s) 123456789private static main(String[] args) { //基本类型->字符串 int i = 100; String s1 = i + \"\"; String s2 = Integer.toString(i); String s3 = String.valueOf(i); //字符串->基本类型 int j = Integer.parseInt(s1);} Collection集合 集合数组区别 数组的长度是固定的,集合的长度是可变的 数组中存储的是同一类型的元素,可以存储基本数据类型值.集合存储的都是对象.而且对象的类型可以不一致,在开发中一般当对象多的时候,使用集合进行存储. 集合框架学习方式 学习顶层:学习顶层接口/抽象类中共性的方法,所有的子类都可以使用 使用底层:顶层不是接口就是抽象类,无法创建对象使用,需使用底层的子类创建对象使用 collection常用功能 boolean add(E e); 把给定的对象添加到当前集合中 void clear(); 清空集合中所有元素 boolean remove(E e); 把给定的对象在当前集合中删除 boolean contains(E e); 判断当前集合中是否包含给定的对象 boolean isEmpty(); 判断当前集合是否为空 int size(); 返回集合中元素的个数 Object[ ] toArray(); 把集合中的元素, 存储到数组中 1234567891011121314151617181920212223242526public static void main(String[] args) { //创建集合对象,可以使用多态 Collection<String> coll = new ArrayList<>(); System.out.println(coll); //collection重写了toString() [] //boolean add(E e); 一般返回都是true,所以可以不一接受,添加元素 coll.add(\"赵丽颖\"); coll.add(\"杨幂\"); coll.add(\"张三\"); System.out.println(coll);//输出所有内容 //boolean remove(E e); 返回值是布尔值,若元素存在,删除元素,返回true coll.remove(\"张三\"); System.out.println(coll); //boolean contains(E e); boolean bool = coll.contains(\"李四\");//返回false //boolean isEmpty(); boolean bool2 = coll.isEmpty();//为空返回true 不为空返回false //int size(); int size = coll.size();//返回集合长度 //Object[] toArray(); 把集合中的元素,存储到数组中 Object[] array = coll.toArray();//返回一个集合 for (int i = 0; i < array.length; i++) {//遍历集合 System.out.println(array[i]); } //void clear(); 清空集合中所有元素,但是不删除集合 coll.clear();//集合此时为 []} Iterator<>接口 (迭代器) : 通用的取出元素的方式 方法 boolean hasNext() 如果仍有元素可以迭代,则返回 true。 E next() 返回迭代的下一个元素。 void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 12345678910111213141516171819202122232425262728293031 private static void main(String[] args) { /* boolean hasNext() 如果仍有元素可以迭代,则返回 true。 E next() 取出集合的下一个元素。 Iterator是一个接口,我们无法使用,需要使用Iterator接口的实现类对象,获取实现类的方法比较特殊 Collection中有一个方法,叫iterator(),这个方返回的就是迭代器的实现类对象 迭代器使用步骤(重点) 1.使用计划中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收()多态 2.使用Iterator接口中的hasNext()判断还有没有下一个元素 3.使用Iterator接口中的方法next取出集合中的下一个元素 Iterator也是有泛型的,集合是什么泛型,迭代器就是什么泛型 */ //创建集合对象 Collection<String> coll = new ArrayList<>(); coll.add(\"赵丽颖\"); coll.add(\"杨幂\"); coll.add(\"迪丽热巴\"); coll.add(\"刘亦菲\"); System.out.println(coll); //多态 接口 实现类对象 Iterator<String> it = coll.iterator(); while (it.hasNext()){//判断集合中是否有元素 String str = it.next();//取出每个元素 System.out.println(str); } //使用for循环不推荐// for (Iterator<String> it2 = coll.iterator();it2.hasNext();){// String str = it.next();//取出每个元素// System.out.println(str);// } String str1 = it.next();//此时已没有元素了,抛 NosuchElementException 异常 } 增强for循环(foreach):专门用来遍历集合跟数组 底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写,是jdk1.5之后的新特性 接口 Collection extends Iterable 所有的单列集合都可以使用”foreach” 格式 : for(集合/数组的数据类型 变量名: 集合名/数组名){ sout(变量名) } 12345678910111213141516private static main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add(\"赵丽颖\"); list.add(\"杨幂\"); list.add(\"迪丽热巴\"); for (String s : list) { //对集合遍历 System.out.println(s); }} private main(String[] args) { int[] array = {1,2,3,4,5}; for (int i : array){//对数组遍历 System.out.println(i); }} 泛型 泛型可以看做一种未知的数据类型,当不知道使用什么数据类型的时候,可以使用泛型 E e :Element 元素 T t : Type 类型 ArrayList 定义的时候不确定使用什么类型,所以使用了泛型 创建集合对象的时候,就会确定泛型的数据类型 ArrayList list = new ArrayList<>(); 创建集合对象不使用泛型 好处: 集合不使用泛型,默认的类型就是Object类型,可以存储任意类型的数据 弊端: 不安全,会引发类型装换异常异常 12345678910111213private static main(String[] args) { ArrayList list = new ArrayList();//存放的是Object类型数据 list.add(\"赵丽颖\"); list.add(1); Iterator iterator = list.iterator(); iterator.hasNext(); Object object = iterator.next();//获取赵丽颖 String str = (String) object; System.out.println(str);//字符串转字符串正常 Object object1 =iterator.next();//获取1 String str1 = (String) object1;//int类型转字符串,会有ClassCastException类型转换异常 System.out.println(str1); } 创建集合对象使用泛型 好处: 避免了类型转换的麻烦,存储什么类型,取出就是什么类型, 把运行期异常提升到了编译期 弊端: 泛型是什么类型,只能存储什么类型的数据 12345private static main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add(\"赵丽颖\"); list.add(1);//存储非字符串类型,报错} 自定义含有泛型的类 1234567891011121314151617181920212223//传入的参数设置为泛型,可以传入任意泛型的参数public class GenericClass<E> { private E name ; public E getName() { return name; } public void setName(E name) { this.name = name; }}//使用public static void main(String[] args) { //不写泛型默认为Object类型 GenericClass genericClass = new GenericClass(); genericClass.setName(\"此处为Object类型\"); //创建GenericClass对象,泛型为Integer GenericClass<Integer> gc= new GenericClass<>(); gc.setName(\"赵丽颖\");//类型错误,会报错 gc.setName(111);//类型正确 } 含有泛型的方法 123456789101112131415161718192021222324//带有泛型public class GenericMethod { //泛型的普通方法 public <M> void method(M m) { System.out.println(m); } //泛型的静态方法 public static <S> void method1(S m) { System.out.println(m); }} public void main(String[] args){ GenericMethod genericMethod = new GenericMethod(); //使用泛型方法 genericMethod.method(10);//可以传入任意类型,会自动装箱 genericMethod.method(\"赵丽颖\"); genericMethod.method(true); //调用泛型静态方法 GenericMethod.method1(321);//类名.方法名调用 GenericMethod.method1(\"杨幂\"); } 含有泛型的接口 123456789101112131415161718192021222324252627282930313233343536373839//定义一个泛型接口public interface GenericInterface<I> { public abstract void method(I i);}//第一种实现类指定泛型类型//1.含有泛型接口的实现类,需指定接口的泛型//Scanner类实现了Iterator接口,并指定接口的泛型为String,所以重新的next方法泛型默认就是String//public final class Scanner implements Iterator<String>{// }public class GenericInterfaceImpl implements GenericInterface<String>{ @Override public void method(String s) { System.out.println(s); }}//第二种实例化是指定泛型类型//2.含有泛型接口的第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走// 就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型public class GenericInterfaceImpl1<I> implements GenericInterface<I> { @Override public void method(I i) { System.out.println(i); }}//测试含有泛型接口的实现类//第一种实现类指定泛型类型,第二种实例化是指定泛型类型private static void demo03() { //第一张泛型实现调用 GenericInterfaceImpl genericInterface = new GenericInterfaceImpl(); genericInterface.method(\"实现类指定的是字符串只能是字符串\"); //第二种泛型实现调用,需手动指定泛型 GenericInterfaceImpl1<Integer> genericInterfaceImpl1 = new GenericInterfaceImpl1(); genericInterfaceImpl1.method(123);//调用时指定的是Inerger只能是Integer} 泛型的通配符 <?> : 泛型不确定是可以使用通配符 ,但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用 不知道用什么类型来接受的时候,此时可以使用?,?表示未知通配符,此时只能接受数据,不能存储数据 123456789101112131415161718192021222324252627282930313233/* * 泛型的通配符: * ? 代表任意的数据类型 * 使用方式: * 不能创建对象使用 * 只能作为方法的参数使用 * 注意: * 定义时不能使用<?> ArrayList<?> list = new ArrayList<>();为错误写法 * */public class Generic { public static void main(String[] args) { ArrayList<Integer> list01 = new ArrayList<>(); list01.add(1); list01.add(2); ArrayList<String> list02 = new ArrayList<>(); list02.add(\"赵丽颖\"); list02.add(\"杨幂\"); //调用自定义方法,此时可以打印任意类集合数据 printArray(list01); printArray(list02); } //定义一个遍历ArrayList方法,此时不知道遍历的数据类型是什么,可以使用泛型通配符 public static void printArray(ArrayList<?> list) { Iterator<?> it = list.iterator(); while (it.hasNext()) { //Objiet类型可以接收任意类型数据 Object obj = it.next(); System.out.println(obj); } }} 通配符高级使用–受限泛型 泛型的上限 格式: 类型名称<? extends 类> 对象名称 意义: 只接收该类型的及其子类 泛型的下限 格式: 类型名称<? super 类> 对象名称 意义: 只能接收该类型及其父类型 比如 现已知Object类, String类, Number类, Integer类, 其中Number类是Intege的父类 1234567891011121314151617181920212223242526272829public class Generic1 { public static void main(String[] args) { //Integer extends Number extends Object //String extends Object Collection<Integer> list1 = new ArrayList<Integer>(); Collection<String> list2 = new ArrayList<String>(); Collection<Number> list3 = new ArrayList<Number>(); Collection<Object> list4 = new ArrayList<Object>(); getElement1(list1); getElement1(list2); //报错 getElement1(list3); getElement1(list4); //报错 getElement2(list1); //报错 getElement2(list2); //报错 getElement2(list3); getElement2(list4); } //泛型的上限:此时的泛型?,必须是Number类型,或者是Number的子类 public static void getElement1(Collection<? extends Number> coll) { } //泛型的下限:此时的泛型?,必须是Number类型,或者是Number的父类 public static void getElement2(Collection<? super Number> coll) { }} collection综合案例(斗地主) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758public class DouDiZhu { public static void main(String[] args) { //1.准备牌 //定义一个存储54张牌的集合,泛型使用字符串 ArrayList<String> poker = new ArrayList<>(); //定义两个数组,一个存序号,一存花色 String[] colors = {\"\", \"\", \"\", \"\"}; String[] numbers = {\"2\", \"A\", \"K\", \"Q\", \"J\", \"10\", \"9\", \"8\", \"7\", \"6\", \"5\", \"4\", \"3\",}; //先存大小王 poker.add(\"大王\"); poker.add(\"小王\"); for (String number : numbers) { for (String color : colors) { //将组装好的牌放到集合中 poker.add(color + number); } } //2.洗牌,使用Collections中静态方法 Collections.shuffle(poker); System.out.println(poker); //3.发牌 //定义四个集合存储玩家牌和底牌 ArrayList<String> play01 = new ArrayList<>(); ArrayList<String> play02 = new ArrayList<>(); ArrayList<String> play03 = new ArrayList<>(); ArrayList<String> diPai = new ArrayList<>(); //遍历poker集合获取每一张牌 //使用poker集合的索引,给玩家轮流发牌,剩余三张给底牌,i>51 就发完了 for (int i = 0; i < poker.size(); i++) { String p = poker.get(i); if (i >= 51) { diPai.add(p); } else if (i % 3 == 0) { //给玩家1发牌 play01.add(p); } else if (i % 3 == 1) { //给玩家2发牌 play02.add(p); } else if (i % 3 == 2) { //给玩家3发牌 play03.add(p); } } //4.看牌 System.out.println(\"刘德华:\" + play01); System.out.println(\"周润发:\" + play02); System.out.println(\"周星驰:\" + play03); System.out.println(\"底牌:\" + diPai); }}输出结果:刘德华:[♦7, ♦J, 小王, ♦A, ♣4, ♠9, ♦8, ♠6, ♣10, ♠4, ♣7, ♥K, ♥5, ♦9, ♥10, ♠7, ♥9]周润发:[♠J, ♠10, ♥8, ♦5, ♣3, ♣8, ♠5, ♥3, ♠3, ♠8, ♦K, ♦Q, ♣Q, ♦3, 大王, ♥Q, ♥J]周星驰:[♣6, ♥4, ♠A, ♥6, ♣K, ♣9, ♠2, ♣A, ♠K, ♣5, ♦6, ♥7, ♣2, ♥2, ♣J, ♥A, ♦2]底牌:[♠Q, ♦10, ♦4] List集合接口 extends Collection 特点 是一个有序的集合,存取元素和取出元素的顺序是一致的 有索引,包含了一些带索引的方法 运行存储重复元素 方法 E get (int index) 返回集合中指定位置元素 E remove(int index) 删除集合中指定位置元素 E set (int index, E e) 用指定元素替换集合中指定位置元素 12345678910111213141516171819202122232425262728//使用索引时应注意索引越界异常 public static void main(String[] args) { List<String> list= new ArrayList<>(); list.add(\"赵丽颖\"); list.add(\"周润发\"); list.add(\"刘德华\"); System.out.println(list); //在1号位置添加迪丽热巴 list.add(1,\"迪丽热巴\"); //移除2号位置元素,并返回移除元素 String removeE = list.remove(2); //替换0位置元素,并返回被替换元素 String setE = list.set(0,\"杨幂\"); //list集合遍历有三种方式 //1.普通for遍历 for (int i = 0; i < list.size(); i++) { String string = list.get(i); } //2.迭代器 Iterator<String> it = list.iterator(); while (it.hasNext()){ String str = it.next(); } //3.增强for循环 for (String s : list) { System.out.println(s); } } List集合的实现类 ArrayList集合: 底层是一个数组结构,增删时,会创建一个新的数组,影响效率需谨慎使用,异步的 LinkedList集合: 底层是一个链表结构, 查询时,会从头查找,影响效率,异步的 有大量的关于首尾元素的操作 12345678910111213141516171819202122232425262728private static void main(String[] args) { /* * LinkedList implements List接口 * 底层是一个链表结构,查询慢,增删快,使用特有方法不能使用多态 * */ LinkedList<String> linked = new LinkedList<>(); linked.add(\"a\"); linked.add(\"b\"); linked.add(\"c\"); //void addFirst(E e)将指定元素插入开头 linked.addFirst(\"www\"); //void push(E e) 等效于addFirst linked.push(\"http\"); //void addLast(E e)将指定元素插入结尾 linked.addLast(\"com\"); //E getFirst() 返回第一个元素 String str = linked.getFirst(); //E getLast() 返回最后一个元素 String str1 = linked.getLast(); //boolean isEmpty() 若空返回true boolean b = linked.isEmpty(); //E removeFirst() 删除并返回此列表的第一个 String removeStr = linked.removeFirst(); //E removeLast() 移除并返回此列表的最后一个 String removestr1 = linked.removeLast(); //E pop() 效果同removeLast一样 String removeStr2 = linked.pop(); } Vector 集合(JDK1.1版本最早期的集合,了解) 可以实现可增长的对象数组 同步的,已被ArrayList取代 Set集合接口 extends Collection 特点 不允许存储重复元素 没有索引,没有带索引的方法,也不能使用普通的for循环遍历, HashSet 实现类 不允许存储重复元素 HashSet implements Set 存储和取出元素的顺可能不一致 底层是一个哈希表结构(查询速度非常的快) 哈希值:是一个十进制的整数,由系统随机给出(就是对象地址,是一个逻辑地址,不是数据实际存储的物理地址,在Object的int hashCode()方法可以获取对象的哈希值) 方法源码: public native int hashCode(); native : 代表该方法调用的时本地操作系统方法 可以直接使用System.out.println(对象名.hashCode());输出对象的哈希值 ` public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }` Object的toString()方法 返回的其实就是一个哈希值 String 类的哈希值 String 重写了Object 的hashCode()方法 1234String s1 = new String (\"abc\");String s2 = new String (\"abc\");s1.hashCode();s2.hashCode();//这两个哈希值一样 哈希表 特点: 速度快 1.7之前 哈希表 = 数组+链表 1.8之后 哈希表 = 数组+链表 , 哈希表 = 数组+红黑树(提高查询速度) 初始容量16个 Set集合不允许存储重复元素的原理 Set集合在调用add方法时,add方法会调用元素的hashCode方法和equals方法,判断元素是否重复 使用: 要想元素不重复, 需重写类的 equals()和hashCode() 方法 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354/* * set保存元素唯一只能是其中一种{ Integer, String , Person...} * 要求: * 同名和同龄的人,视为同一个人,只能存储一次 * */public class Person { private String name; private int age;//getter setter暂时省略 public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() {//重写toString,直接打印对象时,输出不再是地址值 return \"Person{\" + \"name='\" + name + '\\'' + \", age=\" + age + '}'; } @Override public boolean equals(Object o) {//重写equals,比较各个元素是否相同 if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { //重写hashCode返回哈希值 return Objects.hash(name, age); } }main中使用: private static void main(String[] args) { //创建HashSet集合存储person HashSet<Person> set = new HashSet<>(); Person p1 = new Person(\"赵丽颖\",18); Person p2 = new Person(\"赵丽颖\",19); Person p3 = new Person(\"赵丽颖\",18); set.add(p1); set.add(p2); set.add(p3);//不会添加,重写了hashCode和equals System.out.println(set);//不会有重复元素 } LinkedHashset集合 extends HashSet 具有可预知迭代顺序的 Set 接口的哈希表和链接列表的实现,与HashSet不同之处在于,LinkedHashSet维护这一个运行于所有条目的双重链接列表. 底层是一个Hash表(数组+链表/红黑树)+链表, 多了一条链表用来记录元素的存储顺序,保证元素有序 123456789private static void main(String[] args) { //HashSet无序,LinkedHashSet有序 //均不允许重复 LinkedHashSet<String> linked = new LinkedHashSet<>(); linked.add(\"www\"); linked.add(\"baidu\"); linked.add(\"com\");//此处顺序不会改变 System.out.println(linked);} 可变参数 JDK1.5之后出现的新特性 使用前提: 当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数 使用格式: 定义方法时使用, 修饰符 返回值类型 方法名(数据类型... 变量名){} 原理: 底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数,传递的参数可以是 0个(不传递),1,2,3,…多个 12345678910111213141516171819202122232425 public static void main(String[] args) { int[] a = {1,2,3,4}; System.out.println(demo04(a)); }//使用可变参数 int... arr/*注意: 一个方法的参数列表,只能有一个可变参数 错误写法: method(int... a, String... str) 只能有一个可变参数 如果方法的参数由多个,那么可变参数必须写在参数列表的末尾 如 : method(int i, String str, int... arr) 可变参数的特殊写法(终极写法) Object... obj*/ private static int demo04(int... arr) { /* * 定义一个个方法计算0个到n个int整数的和 * */ System.out.println(arr);//打印数组的地址 System.out.println(arr.length);//参数的长度由传入指定 int sum = 0; for (int i : arr) { //遍历arr求和 sum+=i; } return sum; } Collections集合 是一个集合工具类,用来的对集合进行操作 部分方法: static bollean addAll(Collection c, T… element):往集合中添加一些元素 static void shuffle(List<?> list): 打乱集合顺序 static void sort(List list) 将集合中元素按照默认规则排序 使用前提: 被排序的元素,必须实现Comparable,重写接口中的方法compareTo()定义排序规则 Comparable排序规则: . 自己(this)-参数 升序 参数-自己(this) 降序 static void sort(List list, Comparator<? super T>) 将集合中元素按照指定规则排序 Comparable 与 Comparator区别 Comparabel : 自己(this) 和别人(参数)比较,自己需要实现Compareable接口,重写比较规则的compareTo方法 Comparator : 相当于给个第三方的裁判,比较两个元素 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657//对谁排序接口Comparable<>泛型写谁public class Person implements Comparable<Person> { private String name; private int age;//getter setter暂时省略 //主要学习排序,其他省略 @Override //重写Comparable的排序规则 public int compareTo(Person person){ /*return 0;//默认返回0,认为元素都是相同的 自定义排序规则,比较两个人的年龄(this,参数 Person) Comparable排序规则: 自己(this)-参数 升序 参数-自己(this) 降序 */ return this.getAge()-person.getAge(); }}//主方法使用private static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, \"c\", \"b\", \"a\", \"d\");//静态方法,,添加多个元素 Collections.shuffle(list);//静态方法,打乱集合顺序 Collections.sort(list); //静态方法,对集合默认排序 //对自定义类型排序 Person person = new Person(\"赵丽颖\",18); Person person1 = new Person(\"杨幂\",20); Person person2 = new Person(\"a杨颖\",19); Person person3 = new Person(\"b杨颖\",19); ArrayList<Person> people = new ArrayList<>(); Collections.addAll(people,person,person1,person2,person3); Collections.sort(people);//若没实现compareTo方法,会报错 System.out.println(people);//按年龄排好序的结果 /* 扩展:了解 Comparator中compare方法需重写自定义比较规则,此处用匿名类的方式重写 参数:传一个集合,传一个比较器 排序规则: o1-o2 升序 */ Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { //按照年龄升序排序 int result = o1.getAge()-o2.getAge(); //如果两人年龄相同,则按照姓名第一个字母排序 if (result == 0){ //按首字母升序 return o1.getName().charAt(0)-o2.getName().charAt(0); } //年龄不同按年龄排序 return result; } }); System.out.println(people);//年龄不同,按年龄排序,年龄相同,} Map集合K-V键值对, 将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 双列集合,Collection是单列集合 特点 是一个双列集合,一个元素包含两个值(一个KEY,一个VALUE) Map集合中元素,key和value可以相同,也可以不同 Map集合中的元素,key是不允许重复的,value是运行重复的 Map集合中的元素,key和value是一一对应的 常用的实现类 HashMap<k,v> implements Map<k,v>接口 特点: 底层是哈希表,查询速度特变快 JDK1.8之前 数组+单向链表 JDK1.8之后 数组+单向链表/红黑树(链表长度超过8):题告查询速度 HashMap集合是一个无序的集合,存储元素和取出元素可能不一样 多线程,不安全,速度快,不同步 LinkedHashMap<k,v> 集合 extends HashMap<k,v> 集合 特点 LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序) LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的 Map集合中常见的方法 boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。 V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作).若key不存在,返回null,若key存在,替换value,返回旧value V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回null V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 Set keySet() 返回此映射中包含的键的 Set 视图。 把Map中所有的key取出来放到Set集合中 Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。 Entry Map的内部接口,映射项(键-值对)。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960//测试Map中的方法 private static void main(String[] args) { // V put(K key, V value) key不重复返回v是null,如存在,替换value 返回旧value Map<String,String> map = new HashMap<>(); String v1 = map.put(\"李晨\",\"范冰冰\");//返回v1为null System.out.println(v1); String v2 = map.put(\"李晨\",\"赵丽颖\");//返回v2为范冰冰 System.out.println(v2); map.put(\"冷锋\",\"龙小云\");//一般情况不需要接收返回值 map.put(\"杨过\",\"小龙女\"); //V remove(Object key) key存在返回被删除的value,不存在返回null String v3 = map.remove(\"冷锋\");//删除\"冷锋\"返回v3为龙小云 System.out.println(map); //V get(Object key) key存在返回对应的value,不存在返回null String v4 = map.get(\"杨过过\");//不存在返回null //boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。 boolean bool = map.containsKey(\"赵丽颖\");//key不存在返回false /* 第一种方式遍历元素:使用keySet()方法,通过键找值的方式 实现步骤: 1.使用Map集合中的方法keySet()所有的键取出来放到Set集合中 2.遍历Set集合,获取Map()集合中的每一个key 3.通过Map集合中的方法get(key),通过key找到value */ Set<String> set = map.keySet(); //使用迭代器 Iterator<String> it = set.iterator(); while (it.hasNext()){ String key = it.next(); String value = map.get(key); System.out.println(\"key:\"+key+\"value:\"+value); } //使用增强for for (String key : map.keySet()) { String value = map.get(key); System.out.println(\"key:\"+key+\"value:\"+value); } /* 第二种方式遍历元素:使用entrySet()方法 实现步骤: 1.使用Map集合中的方法entrySet()所有的entry对象取出来放到Set集合中 2.遍历Set集合,获取集合中的每一个entry对象 3.通过entry中的的方法getKey()获取key,getValue()获取值 */ Set<Map.Entry<String, String>> set1 = map.entrySet(); //使用迭代器遍历 Iterator<Map.Entry<String, String>> it1 = set1.iterator(); while (it1.hasNext()){ Map.Entry<String, String> entry = it1.next(); String key = entry.getKey(); String value = entry.getValue(); System.out.println(\"key:\"+key+\"value:\"+value); } //使用增强for循环 for (Map.Entry<String, String> entry : set1) { String key1 = entry.getKey(); String value1 = entry.getValue(); System.out.println(\"key:\" + key1 + \"value:\" + value1); }} HashMap存储自定义类型得键值 Map集合保证key是唯一的 作为key的元素,必须重写hashCode()方法和equals()方法,保证key唯一 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364//定义一个Person类public class Person { private String name; private int age;//Getter Setter方法不是重点,暂时省略 public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return \"Person{\" + \"name='\" + name + '\\'' + \", age=\" + age + '}'; } //Map中需要用到equals()和hashCode()方法比较key是否相同 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); }}main中使用//自定义类型作为value可以重复private static void show02() { HashMap<Integer, Person> map = new HashMap<>(); map.put(1,new Person(\"张三\",18)); map.put(2,new Person(\"李四\",19)); map.put(3,new Person(\"王五\",20)); map.put(1,new Person(\"赵六\",18));//key重复,会替代 //使用keyset遍历集合 for (Integer key : map.keySet()) { Person value = map.get(key); System.out.println(\"key:\"+key+\"-->value:\"+value); }}//自定义类型作为key,必须重写equals()和hashCode()方法private static void show03() { //key使用自定义类,因为key唯一,必须重写hashCode()和equals方法; HashMap<Person, Integer> map = new HashMap<>(); map.put(new Person(\"张三\",18),1); map.put(new Person(\"李四\",19),2); map.put(new Person(\"王五\",20),3); //若不重写重写hashCode()和equals方法,重复也会添加 map.put(new Person(\"张三\",18),4); //使用entrySet()遍历 for (Map.Entry<Person, Integer> entry : map.entrySet()) { Person key = entry.getKey(); Integer value = entry.getValue(); System.out.println(\"key:\" + key + \"value:\" + value); }} LinkedHashMap<k,v>集合 extends HashMap<k,v> 有序Map集合 底层原理 哈希表+链表(用来记录元素的顺序) HashTable<k,v> implements Map<k,v> 接口 键值都不允许存储空值 底层也是一个哈希表 早期的双链集合JDK1.0就有,已被(HashMap)取代,但是Hashtable的子类Properties依然活跃于历史舞台,Properties是唯一一个和IO流相结合的集合 同步的,单线程,速度慢,安全 Map集合练习:计算一个字符串每个字符出现的次数 1234567891011121314151617181920212223242526272829303132/** 1.用户输入字符串* 2.转换字符串为字符数组* 3.创建HashMap集合存放[字符]与[对应个数]* 4.判断key是否存在,若存在,取出value并++,再将value放回,若不存在,新建一个key,并将value设为1* 5.遍历Map集合* */public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println(\"请输入一个字符串\"); String str = scanner.next(); char[] chars = str.toCharArray(); HashMap<Character, Integer> map = new HashMap<>(); for (char c : chars) { //判断是否存在 if(map.containsKey(c)){ //对value操作 Integer value = map.get(c); value++; map.put(c,value); }else { //不存在创建key map.put(c, 1); } } for (Map.Entry<Character, Integer> entry : map.entrySet()) { char key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key+\"\"+value+\"\"); }} JDK9集合添加的新特性 Lsit接口,Set接口,Map接口:里边增加了一个静态的of()方法,可以给集合一次性添加多个元素 使用前提:当集合中存储的元素个数已经确定了,不在使用时改变 of()方法只适用于Lsit接口,Set接口,Map接口三个接口,不适用于接口的实现类 of()方法的返回值是一个不能改变的集合,集合不能再使用add,put方法,使用会抛出异常 Set接口和Map接口在调用of方法时,不能有重复元素,否则会抛出异常 12345678public static void main(String[] args){ List<String> list = List.of(\"a\",\"b\",\"c\",\"d\"); System.out.println(lsit); list.add(\"e\");//不能使用,此处会抛不支持操作异常 Set<String> set = Set.of(\"a\",\"b\",\"c\",\"d\");//错误,\"a\"重复,会抛非法参数异常,不允许元素重复 Map<String, Integer> = Map.of(\"张三\",18,\"李四\",19,\"王五\",20);//正确 map.put(\"赵六\",80);//错误,不能添加} 斗地主案例2:要求实现所发的牌已排好序 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283/*发牌有序版 * 1.准备牌 * 2.洗牌 * 3.发牌 * 4.排序 * 5.发牌 * */public class DouDizhu { public static void main(String[] args) { //准备牌 //创建Map集合,存储牌的索引和组装好的牌 HashMap<Integer, String> poker = new HashMap<>(); //创建一个list集合,存储牌的索引 ArrayList<Integer> pokerIndex = new ArrayList<>(); String[] colors = {\"\", \"\", \"\", \"\"}; String[] numbers = {\"2\", \"A\", \"K\", \"Q\", \"J\", \"10\", \"9\", \"8\", \"7\", \"6\", \"5\", \"4\", \"3\",}; //设置一个索引值,添加大小王 int index = 0; poker.put(index, \"大王\"); pokerIndex.add(index); index++; poker.put(index, \"小王\"); pokerIndex.add(index); index++; //向poker中添加元素 for (String number : numbers) { for (String color : colors) { poker.put(index, color + number); pokerIndex.add(index); index++; } } //查看牌与牌索引 System.out.println(poker); System.out.println(pokerIndex); //打乱牌 Collections.shuffle(pokerIndex); //发牌 四个集合存玩家和底牌 ArrayList<Integer> play01 = new ArrayList<>(); ArrayList<Integer> play02 = new ArrayList<>(); ArrayList<Integer> play03 = new ArrayList<>(); ArrayList<Integer> dipai = new ArrayList<>(); //遍历索引获取牌,向玩家集合添加牌 for (int i = 0; i < pokerIndex.size(); i++) { Integer p = pokerIndex.get(i); if (i >= 51) { dipai.add(p); } else if (i % 3 == 0) { //给玩家1发牌 play01.add(p); } else if (i % 3 == 1) { //给玩家2发牌 play02.add(p); } else if (i % 3 == 2) { //给玩家3发牌 play03.add(p); } } Collections.sort(play01); Collections.sort(play02); Collections.sort(play03); Collections.sort(dipai); lookPoker(\"刘德华\", poker, play01); lookPoker(\"周润发\", poker, play02); lookPoker(\"周星驰\", poker, play03); lookPoker(\"底牌\", poker, dipai); } //代码重用,定义一个看牌的方法 public static void lookPoker(String name, HashMap<Integer, String> poker, ArrayList<Integer> list) { System.out.print(name + \": \"); for (Integer key : list) { String value = poker.get(key); System.out.print(value + \" \"); } System.out.println(); }}","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"Collection","slug":"Collection","permalink":"http://lqhao.gitee.io/tags/Collection/"}]},{"title":"Java-基本引用类型的使用","slug":"基础引用类型的使用","date":"2020-07-10T11:48:44.759Z","updated":"2020-07-22T13:33:45.449Z","comments":true,"path":"2020/07/10/2/","link":"","permalink":"http://lqhao.gitee.io/2020/07/10/2/","excerpt":"","text":"Randomr.nextInt(); 随机数范围为整个int r.nextInt(3); 指定随机范围 ,左闭右开区间[0,3) 0,1,2 r.nextInt(10)+1 指定范围为 [1,10 ] ArrayList数组长度不可变 ,ArrayList长度可变 为泛型,也就是装在集合当中的所有元素,全都是统一的什么类型,泛型只能是引用类型,不能是基本类型,泛型只能是引用类型,不能是基本类型,如果想要存放基本类型,必须使用基本类型的包装类 直接打印得到的不是地址值,是空[ ] ArrayList 常用方法 public Boolean add(E e): 向集合中追加元素 public E get(int index): 从集合中获取指定位置元素 public E remove(int index): 从集合中删除指定位置元素 public int size(): 获取集合的尺寸长度 String 字符串一旦被创建,内容永不可变 正因为字符串不可变,所以字符串可以共享使用 字符串效果上相当于char[ ] 字符数组,但底层原理是byte[ ]字节数组 三种构造方法 public String():创建一个空白字符串 public String(char[ ] array): 根据字符数组的内童,来创建对应的字符串 public String(byte[ ] array):根据字节数组的内容,来创建对应的字符串 byte[ ] array = {97,98,99} String str = new String(array); 直接创建 String str = “hello”; 字符串常量池:只有程序当中直接写上的双引号字符串,就在字符串常量池中 对于基本类型来说, ==是进行【数值】的比较 对于引用类型来说, ==是【地址值】的比较 public boolean equals (Object obj) 参数可以是任何对象,只有参数是一个字符串且内容相同才会返回true,否则为false 注意事项 任何对象都能用Object进行接收 推荐把字符串写在变量前 即 “abc”.equals(str) public boolean equalsIgnoreCase(String str), 忽略大小写进行比较 String相关常用方法 public int length()获取字符串字符个数 public String concat(String str) 将当前字符串和参数字符串拼接成为返回值新的字符串,地址与之前字符串不同 public char charAt(int index) 获取指定索引位置的单个字符(索引从0开始) public int indexOf(String str)查找参数字符串在本字符串中首次出现的索引位置,如果没有返回-1 字符串截取 public String subString(int index)截取某一位置到结束,返回新字符串 public String subString (int begin , int end)截取从begin开始,一直到end结束 [begin, end) 转换相关方法 public char[ ] toCharArray() : 将当前字符串拆分成字符数组作为返回值 public byte[ ] getBytes(): 获得当前字符串底层的字节数组 public String replace (CharSequence old String, CharSequence newString):将所有出现的老字符串的指定内容替换成新内容,返回替换之后的结果新字符串。 CharSeqence 可以接收字符串类型 分割字符串 public String[ ] split (String regex) 按照参数的规则,将字符串切分成若干部分,返回字符串数组 regex其实是一个正则表达式 static关键字 如果一个成员变量使用了static关键字,那么这个变量不再属于对象自己,而是属于所在类。多个对象共享同一份数据。 一旦用static修饰成员方法,那么这个方法就成了静态方法,静态方法不属于实例,只属于类。 调用 对于本类当中的静态方法可直接调用 方法调用 成员方法 成员方法可以访问静态变量 静态方法 静态方法可以访问静态变量,但不能访问成员变量,因为内存中【先】有的静态内容,【后】有的非静态内容 静态方法不能使用this 静态变量使用的内存图 静态代码块 格式 12345public class 类名称{ staiic{ //静态代码块内容 } } 特点:当第一次用到本类时,静态代码块执行唯一的一次 用途,用来一次性地对静态成员变量进行赋值 Arrays 是一个与数组相关的工具类,里面提供了大量的静态方法,用来实现数组常见的操作 public static String toString (数组): 将参数数组变成字符串(按照默认格式:[元素1,元素2,元素3]) public static void sort(数组): 按照默认升序,对数组元素进行排序 数字,字母升序 如果是自定义类型,那么定义的类需要有Comparable或者Comparator接口 Math public static double abs(double num):获取绝对值 public static double ceil(double num):向上取整 public static double floor(double num):向下取整 public static long round(double num):四舍五入","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"ArrayList","slug":"ArrayList","permalink":"http://lqhao.gitee.io/tags/ArrayList/"},{"name":"String","slug":"String","permalink":"http://lqhao.gitee.io/tags/String/"}]},{"title":"Java-面向对象笔记","slug":"面向对象三大特性","date":"2020-07-10T11:47:59.747Z","updated":"2020-07-22T13:37:10.051Z","comments":true,"path":"2020/07/10/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/10/1/","excerpt":"","text":"面向对象三大特征:封装、继承、多态封装性封装就是将一些细节信息隐藏起来,对于外界不可见 1.方法就是一种封装 2.关键字private也是一种封装 ​ 一旦使用private进行修饰,本类之中仍可正常访问,类外将不能直接访问 ​ 间接访问成员变量就是使用例setAge()、getAge()方法,即Getter Setter 方法 ​ * 注意 当返回值是Boolean类型时他的Getter方法必须使用isXxx()方法 一个标准的类四部分组成​ 1.所有成员变量都要使用private关键词修饰 ​ 2.为每一个变量编写一对Getter/Setter方法 ​ 3.编写一个无参构造方法 ​ 4.编写一个全参构造方法 匿名对象 如果确定一个对象只需要使用唯一的一次,可使用匿名对象 直接 new Person();新建实例 使用成员变量 new Person().name = “XXX”; 匿名对象只能使用唯一的一次,下次使用必须重新new 使用匿名对象 eg: Scanner int num = new Scanner(System.in).nextInt(); 使用匿名对象传参 method(new Scanner(Systen.in)); 匿名对象也可当返回值 return new Scanner(System.in); 继承性 继承是多态的前提,如果没有继承,就没有多态 继承主要解决的问题就是:共性抽取 父类:也称为基类、超类 子类:也可以叫做派生类 继承关系当中的特点: 子类可以拥有父类授权的“内容” 子类还可以拥有自己专有的内容 Java的extends是单继承,一个类的直接父类只能有一个 Java可以多级继承,子类有父类,父类也可以有父类 在继承关系中,“子类就是一个父类” 列如:父类是员工,子类是讲师,那么“讲师就是一个员工” 在父子类的继承关系中,若成员变量重名,则创建子类对象时,访问有两种方式: 直接通过子类对象访问成员变量 等号左边是谁就优先用谁,没有则向上找 间接通过成员方法访问成员变量 该方法属于谁,就优先用谁,没有则向上找 局部变量,子类成员变量,父类成员变量均重名,访问父类成员变量用super关键字 父子类的继承关系中,访问重名成员方法访问规则 创建的对象是谁,就优先用谁,若没有则向上找 重写(关键字:@Override 是一个可选的安全检测手段,可写可不写) 在继承关系当中,方法的名称一样,参数列表也一样 重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写。 重载(Overload):方法的名称一样,参数列表【不一样】 特点 创建的是子类,则优先用子类方法 子类方法的返回值必须【小于等于】父类方法的返回值范围 子类方法的权限,必须【大于等于】父类方法的权限修饰符 public > protected > (default) > private default不是关键字default,而是留空,例如:int a; 子类中,使用super.方法名,把父类的该方法的内容重复利用 继承关系中父子类构造方法的关系 子类构造方法当中有一个隐含的super();调用,所以一定先调用父类构造,后执行子类构造。 子类构造可以用过super(参数)关键字来调用父类重载构造。 super()的父类构造方法调用,必须是子类构造方法的第一个语句,子类成员方法不可调用,不能一个子类构造调用多次super()构造。 子类必须调用父类构造方法,不写则赠送super(),写了则用写的指定的super()调用,super()只能有一个,还必须是第一句。 super总结(用来访问父类内容) 在子类的成员的方法中访问父类的成员变量。super.a 在子类的成员方法中,继承父类的成员方法。super.method() 在子类的构造方法中,继承父类的构造方法。super(参数) this总结(用了访问本类内容) 在本类的成员方法中,访问本类的成员变量。this.a 在本类的成员方法中,访问本类的另一个成员方法。this.method() 在本类的构造方法中,访问本类的另一个构造方法。this(参数)必须是第一个语句,也是唯一个 构造方法super,this注意 构造方法中super()或this()必须是第一个语句,也是唯一一个 构造方法中super()和this()关键字不能同时使用 抽象 如果父类当中的方法不确定{…}中内容如何实现,那么这样的方法就叫做抽象方法 加上abstract关键字,去掉大括号就是抽象方法 public abstract void eat(); 有抽象方法的是抽象类,需在class前写abstract关键字,public abstract class Animal{…} 不能直接创建抽象类对象,只能用一个子类继承抽象父类 子类必须覆盖重写父抽象类的所有抽象方法,并去掉abstract关键字 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类使用的,子类的构造方法中有默认的super()方法调用父类构造方法 抽象类中不一定有抽象方法 接口类 接口就是一种公共的规范标准,只要符合规范标准,就可以大家通用 接口是一种引用数据类型,最重要的内容就是其中的抽象方法 接口中可以包含的: Java7 常量 抽象方法 Java8(super Java7) 默认方法 静态方法 Java9(super Java8) 私有方法 1234567891011121314151617181920212223242526272829303132333435public interface DemoIntterface{ //抽象方法,关键词使用public abstract,这两个关键字可以选择性的省略,以下都属于抽象方法 public abstract void methodAbs(); abstract void methodAbs2(); public void methodAbs3(); void methodAbs4(); //默认方法定义格式,默认方法可以解决接口升级的问题,如想在接口中添加新的方法,但又不能影响已经继承过该接口的类的使用。在继承过该接口类的实例中,可以调用接口类中后添加的默认方法,默认方法类似于普通继承,默认方法也可被实现类覆盖重写 //接口的默认方法也可拼接函数模型 //public关键字可以省略 public default 返回值类型 方法名称(参数列表){ //... } /* 接口中的静态方法 通过接口类的名称,直接调用其中的静态方法:接口类.静态方法(),不能使用实例名.方法名调用 public关键字可以省略 */ public static 返回值类型 方法名称(参数列表){ //... } /*接口的私有方法 若接口类中需要抽取一个公共方法,用来解决两个默认方法之间代码重复的问题。但这个共有方法不应该让实现类使用,应该是私有化的 Java9开始允许定义私有方法 1.普通私有方法:解决多个默认方法之间代码重复问题 2.静态私有方法:解决多个静态方法之间代码重复问题 */ private 返回值类型 方法名称(参数列表){ //... } private static 返回值类型 方法名称(参数列表){ //... } } 接口使用步骤 接口不能直接使用,必须有一个实现类来实现接口 123public class 实现类名称 implements 接口名称{ //...} 接口的实现必须覆盖重写(实现)接口中的所有抽象方法。实现:去掉abstract关键字,加上方法体大括号 如果实现类并没有覆盖重写接口中的所有抽象方法,那么这个实现类自己就必须是抽象类 接口当中也可以定义“成员变量”,但必须使用[public] [static] [final] 三个关键字进行修饰,这三个关键字也可省略,变量名使用大写用下划线分割,接口中的常量必须进行赋值,一旦赋值不可修改。 1234public interface A{ public static final int NUM = 10; //因关键字可省略,完全等效于 int NUM = 20; } 使用接口时需要注意 接口没有静态代码块或构造方法,因为接口不能new 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口 如果实现类所有实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可 如果实现类没有覆盖重写所有接口类的所有抽象方法,那实现类必须是抽象类 如果实现类的多个接口中,存在重复的默认方法,那么实现类一定要对该默认方法重写 一个类如果直接父类中的方法,和接口中的方法产生冲突,优先使用父类中的方法 1234567891011121314151617181920public interface A{ public abstract void methodA(); public abstract void methodC(); public default void methodDefault(){} }public interface B{ public abstract void methodB(); public abstract void methodC(); public default void methodDefault(){}}public class C implements A,B{ //需要写两接口的所有抽象方法,或者该类是抽象类 public void methodA(){...} public void methodB(){...} //若继承的抽象方法中有重名的抽象方法,重写一个即可 public void methodC(){...} //若继承的抽象方法中有重名的默认方法,必须重写该默认方法 public void methodDefault(){}} 123456789public class A{ public void methodDefault(){}}public interface B{ public default void methodDefault(){} }public class C extends A implements B{ //此处的methodDefault优先使用父类的} 继承总结 类与类之间是单继承的,直接父类只能有一个 类与接口之间是多实现的,一个类可以实现多个接口 接口与接口之间是多继承的 注意事项: 多个父接口当中的抽象方法如果重复,没关系,因为抽象方法没有实现,只是抽象而已 多个父接口当中的默认方法如果重复,那么子接口必须对该默认方法进行重写,而且带有default关键字 1234567891011121314//接口与接口之间可以是多继承的public interface A{ public abstract void methodA(); public default void methodDefault(){...}}public interface B{ public abstract void methodB(); public default void methodDefault(){...}}public interface C extends A,B{ public abstract void methodC(); //多个父接口当中的默认方法如果重复,那么子接口必须对默认方法进行重写 public default void methodDefault(){...}} ​ 多态性 (Polymorphism) extends继承或者implements实现,是多态性的前提 代码中体现多态性,其实就是一句话,父类引用指向子类对象 父类名称 对象名 = new 子类名称() 接口名称 对象名 = new 实现类名称() 右侧子类对象当作父类使用 访问成员变量规则(同继承一样) 直接通过子类对象访问成员变量 等号左边是谁就优先用谁,没有则向上找 间接通过成员方法访问成员变量 该方法属于谁,就优先用谁,没有则向上找 123456789101112131415161718192021222324public class A{ int num = 10; public void showNum(){ System.out.println(num) }}public class B extends A { int num = 20; int age = 18; @Override public void showNum(){ System.out.println(num) }}public class Main { public static void main(String[] args){ A obj = new B(); //等号左边是A父类 System.out.println(obj.num) //访问的是等号左边的A类的num System.out.println(obj.age) //obj中无age属性,错误写法,报错 //子类没有覆盖重写,就使用父 //此处子类该方法被重写,属于B,优先使用B obj.showNum(); }} 访问成员方法规则(同继承也一样) 看new的是谁,就优先用谁,没有则向上找 口诀 成员变量:编译看左边,运行还看左边 成员方法:编译看左边,运行看右边 12345678910111213141516171819202122232425public class A{ public void method(){ } public void metoodFu(){ }}public class B extends A { @Override public void method(){ } public void metoodZi(){ }}public class Main { public static void main(String[] args){ A obj = new B(); //等号左边是A父类 // obj.method();//父子都有,new的是子,优先用子 obj.methodFu();//子类没有,父类有,向上找到父类 //编译看左边,左边是父类,父类中没有methodZi()方法,所以编译报错 obj.methodZi();//编译不通过,不能访问,错误写法 }} 多态的好处 可以使无论右边new的时候换成哪个子类对象,等号左边调用方法都不会变化 多态的上下转型 对象的向上转型 格式:父类名称 对象名 = new 子类名称() 含义:创建一个子类对象,把他当作父类来看待使用。父类引用指向子类对象。 向上转型一定是安全的,因为从子类的小范围转向了父类的大范围。 一旦向上转型位父类,那么就无法调用子类原有的特性 对象的向下转型 其实是一个还原的动作 子类名称 对象名 = (子类名称)父类对象; 含义,将父类对象【还原】为本来的鹅子类对象 12345678Animal animal = new Cat();//本来是猫,向上转型成动物Cat cat = (Cat)animal;//本来是猫,已经被当成动物了,还原成为本来的猫注意事项: a.必须保证对象本来创建的时候,就是猫,才能向下转型成为猫。 b.如果对象创建的时候本来不是猫,非要向下转型为猫,就会报错 Animal animal = new Dog();本来是狗,向上转型成动物 Cat cat = (Cat)animal;//本来是猫,向下转型为狗,报错,ClassCastException 类转换错误 如何才能知道一个父类引用的对象,本来是什么子类 格式: 对象 instanceof 类名称 将会得到一个Boolean值结果,也就是判断前面的的对象能不能当作后面类型的实例 Animal animal = new Cat();//本来是猫 animal.eat();//猫吃鱼 //判断父类引用本来是不是Dog if (animal instanceof Dog){ Dog dog = (Dog) animal; dog.watchHouse(); } //判断animal本来是不是Cat if (animal instanceof Cat){ Cat cat = (Cat) animal; cat.catchMouse(); } <!--9--> final 关键字 代表最终,不可改变的 可以用来修饰一个类 public final class 类名称{ //... } 含义:当前这个类不能有任何的子类 可以有父类 注意:一个类是final的,那么其中所有的成员方法都不能被重写,因为没有子类 可以用来修饰一个方法 修饰符 final 返回值类型 方法名称(参数列表){//...} 含义:当前方法是最终方法,不能被子类覆盖重写 对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾, abstract 中的方法必须被【覆盖重写】 final 中的方法必须【不被覆盖重写】 可以用于修饰一个局部变量 final 类型 变量 = 固定值; 一旦使用final来修饰局部变量,那么这个变量就不能进行更改,”一次赋值,终身不变“ 对于基本类型来说,不可变说的是变量当中的数据不可变 对于引用类型来说,不可变说的是变量当中的地址值不可变 1234final int num = 3; //基本类型正确赋值final int num2;num2 = 5; //基本类型正确赋值,只要保证唯一一次赋值即可final Student stu = new Student(\"赵丽颖\");//引用类型,此stu实例不可重行赋值,final修饰后,实例地址不可变 可以用来修饰一个成员变量 对于成员变量,如果使用final关键字修饰,那么这个变量也照样是不可变的。 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。 对于final的成员变量,要么直接赋值,要么通过构造方法赋值(要保证类当中所有重载构造方法都对 final 成员变量进行赋值) 12345678910public class Test{ final int age = 18; //1.没有默认值,必须进行赋值。 final String name;//2.通过构造方法赋值,必须进行赋值。 public Test(){ this.name = \"赵丽颖\"; } public Test(String name ){ this.name = name; }} 四种权限修饰符 public > protected > (default 啥都不写) > private 同一个类 (我自己) YES YES YES YES 同一个包(我领居) YES YES YES NO 不同包子类 (我儿子) YES YES NO NO 不同包非子类 (陌生人) YES NO NO NO 内部类 如果一个事物的内部包含另一个事物,那么这就是一个内部包含另一个类,例如:身体和心脏的关系,又如:汽车和发动机的关系 分类 成员内部类 定义格式 123456修饰符 class 外部类名称 { //... 修饰符 class 内部类名称 { //... }} 内用外,随意访问,外用内,一定需要借助内部类对象 生成的.class文件 成员内部类使用 直接方式:再外部类的方法中,创建内部类实例,然后main只是调用外部类 123456789101112131415161718192021public class Body { //外部类 private String name; public class Heart {//成员内部类 public void beat() { System.out.println(\"内部类方法\"); System.out.println(name);//正确写法,能访问name } } public void methodBody() { System.out.println(\"外部类方法\"); new Heart().beat();//外部类中直接使用内部类 }}main中使用: Body body = new Body(); body.methodBody();//能够间接访问内部类 直接方式:公式:外.内 格式 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称(); 123main中使用: Body.Heart heart new Body().new heart();//直接创建内部类对象 heart.beat();//直接调用内部类 如果出现了重名现象,那么格式是,外部类名称.this.变量名称 123456789101112131415public class Outer { int num = 10;//外部类成员变量 public class Inner { int num = 30;//内部类成员变量 public void methodInner() { int num = 30;//内部类方法的局部变量 System.out.println(num);//30 System.out.println(this.num);//内部类的成员变量20 System.out.println(Outer.this.num);//10内部类访问外部类成员变量 } }} 局部内部类(包含匿名内部类) 如果一个类是定义再一个方法内部的,那么这就是一个局部内部类,只有当前所属的方法才能使用它,出了这个方法就不能用了。 12345678//定义格式修饰符 class 外部类名称{ 修饰符 返回值类型 外部类方法名称(参数列表){ class 局部内部类名称{ //... } }} 使用 12345678910111213141516171819public class Outer { //外部类名称 public void methodOuter() { //外部类方法 class Inner { //局部内部类 int num = 10; public void methodInner() {//局部内部类的方法 System.out.println(num); } } Inner inner =new Inner();//局部内部类使用 inner.methodInner(); }}main中使用: Outer obj = new Outer(); obj.methodOuter(); 注意事项:局部内部类,如果希望访问所在的方法的局部变量,那么这个局部变量必须是【有效的final】关键字定义的,从java8+开始,只要局部变量事实不变,则final关键字可以省略。 原因 new出来的对象在堆内存当中 局部变量是跟着方法走的,在栈内存中 方法运行结束后,立刻出栈,局部变量就会立刻消失 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。 inner对象活的时间久 12345678910111213141516171819public class Outer { public void methodOuter() { final int num1 = 10; int num2 = 20; int num3 = 30; num3 = 40;//报错 class Inner {//此局部内部类属于methodMOuter()方法 public void methodInner() {//此方法属于局部内部类 System.out.println(num1);//此处可以访问num1 System.out.println(num2);//正确访问,num2没变化,java8+特性 System.out.println(num3);//出错,num3变化了 } } Inner inner =new Inner(); inner.methodInner(); }} 匿名内部类(重点) 如果接口的实现类(或者是父类的子类),只需要使用唯一的一次,那么这种情况下就可以省略该类的定义,而改为使用【匿名内部类】 12345678910111213141516171819//匿名内部类格式接口名称 对象名 = new 接口名称(){ //覆盖重写所有的抽象方法};//示例:public interface MyInterface {//定义接口类 public abstract void method();}main中使用(重点): //main方法中使用匿名内部类实现该接口的抽象方法 MyInterface obj = new MyInterface() { @Override public void method() { System.out.println(\"接口实现\"); } }; obj.method(); - 格式解析: “new 接口名称(){...};” - new 代表创建对象的动作 - 接口名称就是匿名内部类需要实现哪个接口 - {...} 这才是匿名内部类的内容 - 注意事项 - 使用场景:只对接口进行唯一一次实现 - 匿名内部类在【创建对象】的时候,**只能使用一次**,如果希望多次创建对象,而且类的内容一样的话,那就就得尽量使用单独定义的实现类了 - 匿名对象,在【调用方法】的时候,只能调用唯一一次。如果希望同一个对象,调用多次方法,那么必须给对象起个名字 - 匿名内部类是省略了【实现类/子类对象】,但是你梦对象是省略了【对象名称】,强调,你梦内部类和匿名对象不是一回事!!! 12345678//使用了匿名内部类,而且还使用了匿名对象new MyInterface() { @Override public void method() { System.out.println(\"接口实现\"); } }.obj.method();//此处使用了匿名对象调用方法//若还要调用其他方法,还需要重写创建匿名内部类。类作为成员变量类型 12345678public class A{ //...}public class B{ private String name; private A a; //...} 接口作为成员变量类型 123456789101112131415161718192021222324252627public interface A{ void method();}public class B{ private A a; public B(A a){ this.a=a; }}main中使用匿名内部类实现: A a1= new A(){//使用匿名内部类实现 @Override public void method(){ //... } }; B b = new B(); b.a = a1;或者同时使用匿名对象和匿名内部类: B b = new B(new A(){//同时使用匿名对象和匿名内部类,B的构造方法传入的参数是A类的对象 @Override public void method(){ //... } }); 接口作为方法的参数或返回值 类的权限修饰符 定义一个类的时候,权限修饰符规则 外部类 只能使用 public 或者(default 不写) 成员内部类 public / protected / (default) / private 局部内部类 什么都不能写,但效果也不同于不写default)","categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"}]},{"title":"Java-API-说明文档推荐","slug":"Java-API-doc","date":"2020-07-08T00:23:07.046Z","updated":"2020-07-10T06:56:23.197Z","comments":true,"path":"2020/07/08/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/08/1/","excerpt":"","text":"Jave的JDK API说明文档 工欲善其事,必先利其器,学习Java同样如此。 我这个Java小白,无意中发现一款JDK的API中文版说明文档,关于JDK说明文档,在1.6之前sun公司未被oracle公司收购,出的API说明文档都有中文版,对国内童鞋是十分的友好,但1.6之后,便不再提供中文版,所以只能有1.6的中文文档,有了这个说明文档,可以当作工具来使用,此文档是官方翻译,有不懂的类可以随时查看。我们要明白,知识没有新旧,不能因为有了更新的版本,旧的版本就一定有问题。对于小白的我,旧的版本也足够用。但由于版本更新实际存在,其中内容与新版必有出入,需自行断定 文档下载地址: 下载:https://liangqinghao.lanzous.com/irZNOeedqwj 密码:8qiu 因网盘原因,限制上传.chm类型文件,但又考虑到下载速度问题,故我将后缀改为了.zip才能上传,下载后需手动将.zip改为.chm方可使用 如有内容不显示现象: 1. 右键单击该 CHM 文件,然后单击“属性”。2. 单击“取消阻止”或者“解除锁定”。3. 双击此 .chm 文件以打开此文件。 使用方法 图中已经说明,看图操作即可,更多操作,自行研究。","categories":[{"name":"API文档","slug":"API文档","permalink":"http://lqhao.gitee.io/categories/API%E6%96%87%E6%A1%A3/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"API","slug":"API","permalink":"http://lqhao.gitee.io/tags/API/"}]},{"title":"hexo","slug":"hexo_deploy","date":"2020-07-06T03:50:17.000Z","updated":"2020-07-11T23:43:22.198Z","comments":true,"path":"2020/07/06/1/","link":"","permalink":"http://lqhao.gitee.io/2020/07/06/1/","excerpt":"环境部署安装三个环境 安装nodejs apt-get -y install node node -v 安装git,设置用户名、邮箱 apt-get -y install git git config –global user.email “邮箱” git config –global user.name “用户名” 安装hexo,因国内原因,需要安装cnpm提高速度 npm install -g cnpm –registry=https://registry.npm.taobao.org cnpm install hexo-cli -g hexo -v","text":"环境部署安装三个环境 安装nodejs apt-get -y install node node -v 安装git,设置用户名、邮箱 apt-get -y install git git config –global user.email “邮箱” git config –global user.name “用户名” 安装hexo,因国内原因,需要安装cnpm提高速度 npm install -g cnpm –registry=https://registry.npm.taobao.org cnpm install hexo-cli -g hexo -v 部署hexo 找个地方新建一个空文件夹,存放博客内容例: mkdir /blog (若配置过程出配错,可直接将此文件夹删除,重新创建) 进入blog目录,生成博客 cd /blog hexo init 可以ls看一下此时目录下生成的基础框架内容 启动博客 hexo s (server) 用浏览器访问提示的地址localhost:4000,有初始内容即为成功 新建文章 hexo new ”文章名字“ cd 到提示的路径/blog/source/_posts/ 查看新的文章 用 vim 编辑文章 退回/blog,清理缓存,重新生成博客 cd ../.. hexo cleam hexo g ( generate) 重新启动 hexo s 此时访问即可看到新文章 部署博客到github 登录github新建一个仓库 昵称必须一样创建完成的点击确定 回到主机,安装git部署插件 cnpm install –save hexo-deployer-git 修改配置文件,修改部署项 repo地址为github页创建的那个https地址 vim /blog/_config.yml 部署到远端,中途需输入用户名密码 hexo d (deploy) 稍等片刻访问仓库名 xx.github.io 可以访问到 换主题 访问 https://github.com/litten/hexo-theme-yilia 查看这款主题 git clone https://github.com/litten/hexo-theme-yilia /blog/theme/yilia 修改配置文件设置为指定主题 vim /blog/_config.yml 重新清理,重新生成,重新启动 hexo clean hexo g hexo s 访问后可重新推到远端","categories":[{"name":"hexo","slug":"hexo","permalink":"http://lqhao.gitee.io/categories/hexo/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://lqhao.gitee.io/tags/linux/"},{"name":"hexo","slug":"hexo","permalink":"http://lqhao.gitee.io/tags/hexo/"}]}],"categories":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/categories/Java/"},{"name":"MySQL","slug":"MySQL","permalink":"http://lqhao.gitee.io/categories/MySQL/"},{"name":"API文档","slug":"API文档","permalink":"http://lqhao.gitee.io/categories/API%E6%96%87%E6%A1%A3/"},{"name":"hexo","slug":"hexo","permalink":"http://lqhao.gitee.io/categories/hexo/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://lqhao.gitee.io/tags/Java/"},{"name":"连接池","slug":"连接池","permalink":"http://lqhao.gitee.io/tags/%E8%BF%9E%E6%8E%A5%E6%B1%A0/"},{"name":"Template","slug":"Template","permalink":"http://lqhao.gitee.io/tags/Template/"},{"name":"MySQL","slug":"MySQL","permalink":"http://lqhao.gitee.io/tags/MySQL/"},{"name":"注解","slug":"注解","permalink":"http://lqhao.gitee.io/tags/%E6%B3%A8%E8%A7%A3/"},{"name":"反射","slug":"反射","permalink":"http://lqhao.gitee.io/tags/%E5%8F%8D%E5%B0%84/"},{"name":"Stream","slug":"Stream","permalink":"http://lqhao.gitee.io/tags/Stream/"},{"name":"Lambda","slug":"Lambda","permalink":"http://lqhao.gitee.io/tags/Lambda/"},{"name":"函数式接口","slug":"函数式接口","permalink":"http://lqhao.gitee.io/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E6%8E%A5%E5%8F%A3/"},{"name":"Socket","slug":"Socket","permalink":"http://lqhao.gitee.io/tags/Socket/"},{"name":"FIle","slug":"FIle","permalink":"http://lqhao.gitee.io/tags/FIle/"},{"name":"IO","slug":"IO","permalink":"http://lqhao.gitee.io/tags/IO/"},{"name":"Exception","slug":"Exception","permalink":"http://lqhao.gitee.io/tags/Exception/"},{"name":"Thread","slug":"Thread","permalink":"http://lqhao.gitee.io/tags/Thread/"},{"name":"Collection","slug":"Collection","permalink":"http://lqhao.gitee.io/tags/Collection/"},{"name":"ArrayList","slug":"ArrayList","permalink":"http://lqhao.gitee.io/tags/ArrayList/"},{"name":"String","slug":"String","permalink":"http://lqhao.gitee.io/tags/String/"},{"name":"API","slug":"API","permalink":"http://lqhao.gitee.io/tags/API/"},{"name":"linux","slug":"linux","permalink":"http://lqhao.gitee.io/tags/linux/"},{"name":"hexo","slug":"hexo","permalink":"http://lqhao.gitee.io/tags/hexo/"}]}

Comment ( 0 )

Sign in for post a comment

NodeJS
1
https://gitee.com/lqhao/lqhao.git
git@gitee.com:lqhao/lqhao.git
lqhao
lqhao
lqhao
master

Search

132457 8cb2edc1 1899542 131848 70c8d3a4 1899542