{"id":19297595,"url":"https://github.com/limen/phper-learn-java","last_synced_at":"2026-05-16T02:41:36.090Z","repository":{"id":41014828,"uuid":"255254555","full_name":"limen/PHPer-Learn-Java","owner":"limen","description":"帮助PHP开发者快速入门Java","archived":false,"fork":false,"pushed_at":"2022-06-21T03:12:13.000Z","size":68,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-24T01:47:08.473Z","etag":null,"topics":["java","php","phper"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/limen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-13T06:59:15.000Z","updated_at":"2024-03-13T06:08:30.000Z","dependencies_parsed_at":"2022-08-31T20:01:19.243Z","dependency_job_id":null,"html_url":"https://github.com/limen/PHPer-Learn-Java","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/limen/PHPer-Learn-Java","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limen%2FPHPer-Learn-Java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limen%2FPHPer-Learn-Java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limen%2FPHPer-Learn-Java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limen%2FPHPer-Learn-Java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/limen","download_url":"https://codeload.github.com/limen/PHPer-Learn-Java/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limen%2FPHPer-Learn-Java/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284807785,"owners_count":27066464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-17T02:00:06.431Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","php","phper"],"created_at":"2024-11-09T23:05:26.668Z","updated_at":"2025-11-17T02:01:41.074Z","avatar_url":"https://github.com/limen.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PHPer learn Java\n\n# 概述\n\n本文面向需要学习Java的PHP开发者。\n\n本文从一个helloworld程序开始，紧接着一个数据库操作demo（这有别于从易到难的风格），然后回归基础，难度逐步加大。把数据库操作demo放在前面，是希望大家快速对一个Java项目形成感性的认识，请不要深究技术细节。\n\n难度最大的部分在于多线程和线程安全，这对很多PHP开发者来说是全新的领域。\n\n最后部分讲解如何用SpingBoot实现RESTful API。\n\n由于作者水平有限，不准确之处在所难免，敬请指正。\n\n\n# 准备工作\n环境依赖\n\n- JDK 1.8\n- IDE Intelij IDEA\n- Maven\n\n\n\n# Hello, World!\n按照惯例先写一个 helloword 程序。\n新建文件 HelloWorld.java\n```java\npublic class HelloWorld {\n    public static void main(String[] args) {\n        System.out.println(\"Hello, World!\");\n    }\n}\n```\n\n\n编译源文件，生成同名的.class文件\n```bash\n$ javac HelloWorld.java\n```\n\n\n执行class文件，无需后缀\n```bash\n$ java HelloWorld\nHello, World!\n```\n\n\nmain 方法是Java类的入口方法，方法名称和格式是约定俗成的。\n\n\n**作业**\n\n- 写一个PHP版的helloword，对比一下两者的区别。\n\n\n\n# 难度提升\n这一节用Java做点有难度的事：操作数据库。这一节的目标是对Java项目建立感性的认识。\n\n相关知识点\n\n- 依赖管理\n- Mybatis\n- XML\n\nmaven是Java项目依赖管理工具，类似于PHP的composer。\n\nmaven使用pom.xml配置依赖，类似于composer.json。\n\n引入两个依赖：mybatis和mysql connector，pom.xml文件内容如下\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n    \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n\n    \u003cgroupId\u003ecom.limengxiang\u003c/groupId\u003e\n    \u003cartifactId\u003ebasics\u003c/artifactId\u003e\n    \u003cversion\u003e1.0-SNAPSHOT\u003c/version\u003e\n\n    \u003c!-- 使用阿里云中心仓库 --\u003e\n    \u003crepositories\u003e\n        \u003crepository\u003e\n            \u003cid\u003ealiyun\u003c/id\u003e\n            \u003cname\u003ealiyun\u003c/name\u003e\n            \u003curl\u003ehttp://maven.aliyun.com/nexus/content/groups/public\u003c/url\u003e\n        \u003c/repository\u003e\n    \u003c/repositories\u003e\n    \u003cpluginRepositories\u003e\n        \u003cpluginRepository\u003e\n            \u003cid\u003ealiyun\u003c/id\u003e\n            \u003cname\u003ealiyun\u003c/name\u003e\n            \u003curl\u003ehttp://maven.aliyun.com/nexus/content/groups/public\u003c/url\u003e\n        \u003c/pluginRepository\u003e\n    \u003c/pluginRepositories\u003e\n\n    \u003cdependencies\u003e\n        \u003c!-- mybatis --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.mybatis.spring.boot\u003c/groupId\u003e\n            \u003cartifactId\u003emybatis-spring-boot-starter\u003c/artifactId\u003e\n            \u003cversion\u003e1.3.2\u003c/version\u003e\n        \u003c/dependency\u003e\n        \u003c!-- MySQl连接器 --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003emysql\u003c/groupId\u003e\n            \u003cartifactId\u003emysql-connector-java\u003c/artifactId\u003e\n            \u003cversion\u003e8.0.15\u003c/version\u003e\n            \u003cscope\u003eruntime\u003c/scope\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n\n\u003c/project\u003e\n```\n\n\n项目的目录结构如下\n\n![选区_081.png](https://cdn.nlark.com/yuque/0/2020/png/514450/1586431803443-3b9b8245-f746-4fa6-a17f-38e7872cdd12.png#align=left\u0026display=inline\u0026height=536\u0026name=%E9%80%89%E5%8C%BA_081.png\u0026originHeight=536\u0026originWidth=394\u0026size=26865\u0026status=done\u0026style=none\u0026width=394)\n\n\n- UserDAO.java：定义CRUD方法\n- UserModel.java：定义数据结构\n- UserMapper.xml：定义UserDAO方法对应的SQL\n\n表结构\n\n\n```sql\nCREATE TABLE `users` (\n  `id` int(11) NOT NULL AUTO_INCREMENT,\n  `username` varchar(30) NOT NULL,\n  `mobile` varchar(11) NOT NULL,\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4\n```\n\n\nUserDAO.java\n\n```java\npackage com.limengxiang.basics.dao;\n\nimport com.limengxiang.basics.model.UserModel;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\n@Mapper\npublic interface UserDAO {\n\n    public Integer insert(@Param(\"username\") String username, @Param(\"mobile\") String mobile);\n\n    public UserModel selectOne(@Param(\"tableName\") String tableName, @Param(\"id\") Integer id);\n\n    public List\u003cUserModel\u003e fuzzySearch(@Param(\"username\") String username, @Param(\"mobile\") String mobile);\n\n    public List\u003cUserModel\u003e searchByUsernameOrMobile(@Param(\"username\") String username, @Param(\"mobile\") String mobile);\n}\n```\n\n\nUserModel.java\n\n```java\npackage com.limengxiang.basics.model;\n\npublic class UserModel {\n    private String username;\n    private String mobile;\n\n    public String toString() {\n        return \"username:\" + username +\n                \", mobile:\" + mobile;\n    }\n}\n```\n\n\nUserMapper.xml\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\" ?\u003e\n\u003c!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\"\u003e\n\u003c!-- mapper:根标签，namespace：命名空间，也就是DAO类 --\u003e\n\u003cmapper namespace=\"com.limengxiang.basics.dao.UserDAO\"\u003e\n    \u003c!-- 插入数据 --\u003e\n    \u003cinsert id=\"insert\" parameterType=\"com.limengxiang.basics.model.UserModel\"\u003e\n        INSERT INTO users (username,mobile) VALUES (#{username}, #{mobile})\n    \u003c/insert\u003e\n    \u003c!-- 按主键ID查询 --\u003e\n    \u003cselect id=\"selectOne\" resultType=\"com.limengxiang.basics.model.UserModel\"\u003e\n        SELECT * FROM ${tableName} WHERE id = #{id}\n    \u003c/select\u003e\n    \u003c!-- 动态SQL，当参数非空时模糊匹配 --\u003e\n    \u003cselect id=\"fuzzySearch\" resultType=\"com.limengxiang.basics.model.UserModel\"\u003e\n        SELECT * FROM users\n        \u003c!-- 不用担心多余的AND --\u003e\n        \u003cwhere\u003e\n            \u003cif test=\"username!=null and username.trim()!=''\"\u003eAND username LIKE #{username}\u003c/if\u003e\n            \u003cif test=\"mobile!=null and mobile.trim()!=''\"\u003eAND mobile LIKE #{mobile}\u003c/if\u003e\n        \u003c/where\u003e\n    \u003c/select\u003e\n    \u003c!-- 动态SQL，类似switch语句 --\u003e\n    \u003cselect id=\"searchByUsernameOrMobile\" resultType=\"com.limengxiang.basics.model.UserModel\"\u003e\n        SELECT * FROM users\n        \u003cwhere\u003e\n            \u003c!-- 不用担心多余的AND --\u003e\n            \u003cchoose\u003e\n                \u003cwhen test=\"username!=null and username.trim()!=''\"\u003e\n                    AND username LIKE #{username}\n                \u003c/when\u003e\n                \u003cwhen test=\"mobile!=null and mobile.trim()!=''\"\u003e\n                    AND mobile LIKE #{mobile}\n                \u003c/when\u003e\n            \u003c/choose\u003e\n        \u003c/where\u003e\n    \u003c/select\u003e\n\u003c/mapper\u003e\n```\n\n\n以上三个文件帮助我们定义好了CRUD的业务逻辑。仔细看一下文件内容，试着发现一些规律。\n\n在开始操作数据库之前，还需要配置连接信息，并加载UserMapper.xml文件。\n\nmybatis-config.xml \n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\" ?\u003e\n\u003c!DOCTYPE configuration\n        PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-config.dtd\"\u003e\n\u003c!-- 根标签 --\u003e\n\u003cconfiguration\u003e\n    \u003cproperties\u003e\n        \u003cproperty name=\"driver\" value=\"com.mysql.cj.jdbc.Driver\"/\u003e\n    \u003c/properties\u003e\n\n    \u003c!-- 环境，可以配置多个，default：指定采用哪个环境 --\u003e\n    \u003cenvironments default=\"development\"\u003e\n        \u003c!-- id：唯一标识 --\u003e\n        \u003cenvironment id=\"development\"\u003e\n            \u003c!-- 事务管理器，JDBC类型的事务管理器 --\u003e\n            \u003ctransactionManager type=\"JDBC\" /\u003e\n            \u003c!-- 数据源，池类型的数据源 --\u003e\n            \u003cdataSource type=\"POOLED\"\u003e\n                \u003cproperty name=\"driver\" value=\"${driver}\" /\u003e \u003c!-- 配置了properties，所以可以直接引用 --\u003e\n                \u003cproperty name=\"url\" value=\"jdbc:mysql://172.168.0.92:3306/mybatis_demo\"/\u003e\n                \u003cproperty name=\"username\" value=\"root\"/\u003e\n                \u003cproperty name=\"password\" value=\"123456\"/\u003e\n            \u003c/dataSource\u003e\n        \u003c/environment\u003e\n    \u003c/environments\u003e\n\n    \u003cmappers\u003e\n        \u003cmapper resource=\"mapper/UserMapper.xml\" /\u003e\n    \u003c/mappers\u003e\n\u003c/configuration\u003e\n```\n\n\nMybatisDemo.java\n```java\npackage com.limengxiang.basics;\n\nimport com.limengxiang.basics.dao.UserDAO;\nimport com.limengxiang.basics.model.UserModel;\nimport org.apache.ibatis.io.Resources;\nimport org.apache.ibatis.session.SqlSession;\nimport org.apache.ibatis.session.SqlSessionFactoryBuilder;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\n\npublic class MybatisDemo {\n    public static void main(String[] args) {\n        try {\n            InputStream stream = Resources.getResourceAsStream(\"mybatis-config.xml\");\n            SqlSession sqlSession = new SqlSessionFactoryBuilder().build(stream).openSession(true);\n            UserDAO userDAO = sqlSession.getMapper(UserDAO.class);\n            // 插入数据\n            Integer inserted = userDAO.insert(\"allen\", \"13356986723\");\n            System.out.println(\"Insert result:\" + inserted);\n            inserted = userDAO.insert(\"alfred\", \"13856986723\");\n            System.out.println(\"Insert result:\" + inserted);\n\n            UserModel user = userDAO.selectOne(\"users\", 1);\n            System.out.println(\"Select one:\" + user);\n\n            List\u003cUserModel\u003e users = userDAO.fuzzySearch(\"all%\", \"\");\n            System.out.println(\"Fuzzy search by username:\" + users);\n\n            users = userDAO.fuzzySearch(\"\", \"133%\");\n            System.out.println(\"Fuzzy search by mobile:\" + users);\n\n\n            users = userDAO.fuzzySearch(\"all%\", \"133%\");\n            System.out.println(\"Fuzzy search by username and mobile:\" + users);\n\n            users = userDAO.searchByUsernameOrMobile(\"all%\", \"135%\");\n            System.out.println(\"Search by username:\" + users);\n\n            users = userDAO.searchByUsernameOrMobile(\"\", \"135%\");\n            System.out.println(\"Search by mobile:\" + users);\n\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n\n    }\n}\n```\n\n\n运行结果如下，重点关注打印出的SQL，和UserMapper.xml定义的SQL进行对比，试着发现一些规律。\n\n```basic\n19:45:14.943 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.\n19:45:15.094 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.\n19:45:15.094 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.\n19:45:15.094 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.\n19:45:15.094 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.\n19:45:15.171 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection\n19:45:15.590 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 2085002312.\n19:45:15.592 [main] DEBUG com.limengxiang.basics.dao.UserDAO.insert - ==\u003e  Preparing: INSERT INTO users (username,mobile) VALUES (?, ?) \n19:45:15.630 [main] DEBUG com.limengxiang.basics.dao.UserDAO.insert - ==\u003e Parameters: allen(String), 13356986723(String)\n19:45:15.638 [main] DEBUG com.limengxiang.basics.dao.UserDAO.insert - \u003c==    Updates: 1\nInsert result:1\n19:45:15.639 [main] DEBUG com.limengxiang.basics.dao.UserDAO.insert - ==\u003e  Preparing: INSERT INTO users (username,mobile) VALUES (?, ?) \n19:45:15.639 [main] DEBUG com.limengxiang.basics.dao.UserDAO.insert - ==\u003e Parameters: alfred(String), 13856986723(String)\n19:45:15.644 [main] DEBUG com.limengxiang.basics.dao.UserDAO.insert - \u003c==    Updates: 1\nInsert result:1\n19:45:15.662 [main] DEBUG com.limengxiang.basics.dao.UserDAO.selectOne - ==\u003e  Preparing: SELECT * FROM users WHERE id = ? \n19:45:15.662 [main] DEBUG com.limengxiang.basics.dao.UserDAO.selectOne - ==\u003e Parameters: 1(Integer)\n19:45:15.691 [main] DEBUG com.limengxiang.basics.dao.UserDAO.selectOne - \u003c==      Total: 0\nSelect one:null\n19:45:15.705 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - ==\u003e  Preparing: SELECT * FROM users WHERE username LIKE ? \n19:45:15.705 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - ==\u003e Parameters: all%(String)\n19:45:15.708 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - \u003c==      Total: 1\nFuzzy search by username:[username:allen, mobile:13356986723]\n19:45:15.709 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - ==\u003e  Preparing: SELECT * FROM users WHERE mobile LIKE ? \n19:45:15.709 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - ==\u003e Parameters: 133%(String)\n19:45:15.710 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - \u003c==      Total: 1\nFuzzy search by mobile:[username:allen, mobile:13356986723]\n19:45:15.710 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - ==\u003e  Preparing: SELECT * FROM users WHERE username LIKE ? AND mobile LIKE ? \n19:45:15.711 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - ==\u003e Parameters: all%(String), 133%(String)\n19:45:15.712 [main] DEBUG com.limengxiang.basics.dao.UserDAO.fuzzySearch - \u003c==      Total: 1\nFuzzy search by username and mobile:[username:allen, mobile:13356986723]\n19:45:15.713 [main] DEBUG com.limengxiang.basics.dao.UserDAO.searchByUsernameOrMobile - ==\u003e  Preparing: SELECT * FROM users WHERE username LIKE ? \n19:45:15.713 [main] DEBUG com.limengxiang.basics.dao.UserDAO.searchByUsernameOrMobile - ==\u003e Parameters: all%(String)\n19:45:15.714 [main] DEBUG com.limengxiang.basics.dao.UserDAO.searchByUsernameOrMobile - \u003c==      Total: 1\nSearch by username:[username:allen, mobile:13356986723]\n19:45:15.715 [main] DEBUG com.limengxiang.basics.dao.UserDAO.searchByUsernameOrMobile - ==\u003e  Preparing: SELECT * FROM users WHERE mobile LIKE ? \n19:45:15.715 [main] DEBUG com.limengxiang.basics.dao.UserDAO.searchByUsernameOrMobile - ==\u003e Parameters: 135%(String)\n19:45:15.716 [main] DEBUG com.limengxiang.basics.dao.UserDAO.searchByUsernameOrMobile - \u003c==      Total: 0\nSearch by mobile:[]\n```\n\n\n# 回归基础\n\n我们继续从基础开始学习，打好基础才能走得更远。\n\n## 静态与动态，强类型与弱类型\n\nJava是静态语言，PHP是动态语言；Java是强类型语言，PHP是弱类型语言。\n\n静态的含义是在编译期就能确定变量的数据类型，动态则不能。这也属于强弱类型的范畴。\n\nJava语言在声明变量时需要指定数据类型，这确保了“静态”，PHP则相反。\n\n## 数据类型\n\n基本数据类型\n\n- short\n- int\n- long\n- float\n- double\n- char\n- byte\n- boolean\n\n包装器类型\n\n- Short：short\n- Integer：int\n- Long：long\n- Float：float\n- Double：double\n- Character：char\n- Byte：byte\n- Boolean：boolean\n\n包装器类封装了基本类型常用的一些API，例如将字符串解析为整数。\n\n除基本数据类型外，一切皆对象！\n\n\n**思考**\n\n- PHP有哪些数据类型？跟Java对比有什么区别？\n\n## 类与继承\n\nPHP和Java都是面向对象的编程语言。\n\nPHP的面向对象借鉴了Java的设计，例如\n\n- 继承关键词：extends，implements\n- 不允许多继承，但可以实现多个接口\n- 抽象类、抽象方法\n\n熟悉PHP面向对象的开发者，学习Java面向对象不会有难度。这里不赘述了。\n\n\n## 包与命名空间\n\n在Java文件的顶部可以看到 package ***\n这声明了Java类所属的包，和PHP的命名空间（namespace）类似。\n\nJava对类的命名要求更严格一些，例如类名（public类）必须和文件名保持一致。\n\n\n# 难度Level 2\n\n## 方法签名\n\n先看一个例子\n\n\n```java\npublic class HelloWorld {\n\n    public void hello(String name) {\n        System.out.println(\"Hello, \" + name);\n    }\n\n    public void hello(int age) {\n        if (age \u003c 20) {\n            System.out.println(\"Hello son\");\n        } else if (age \u003e= 20 \u0026\u0026 age \u003c 40) {\n            System.out.println(\"Hello buddy\");\n        } else if (age \u003e= 40) {\n            System.out.println(\"Hello old man\");\n        } else {\n            System.out.println(\"Hello, you are \" + age + \" years old\");\n        }\n    }\n\n    /**\n     * 声明该方法会导致编译时报错\n     * error: method hello(int) is already defined in class HelloWorld\n     * 返回值类型不是签名的组成部分\n     */\n//    public int hello(int age) {\n//        return age;\n//    }\n\n    public static void main(String[] args) {\n        System.out.println(\"Hello, World!\");\n        HelloWorld hw = new HelloWorld();\n        hw.hello(\"Tom\");\n        hw.hello(60);\n    }\n}\n输出结果\nHello, World!\nHello, Tom\nHello old man\n\n```\n\n\n可以看到HelloWorld类定义了两个public void hello方法，这在PHP中是不允许的。\n\n这涉及到“方法签名”的概念，在Java类中，相同名称但签名不同的方法是可以共存的。\n\n方法名和方法参数类型列表构成了一个方法的签名，上面例子中两个hello方法的签名分别是\n\n- hello(String)\n- hello(int)\n\n\n\n**作业**\n\n- 编写一个Java类，实现多个构造方法。\n\n\n\n## 内部类\n\n看以下栗子。\n\n咦！HelloWorld类里面怎么多了一个Person类！不要惊慌，这就是接下来要说的“内部类”。\n```java\npublic class HelloWorld {\n\n    class Person {\n        private String name;\n        private int age;\n\n        public Person(String name, int age) {\n            this.name = name;\n            this.age = age;\n        }\n\n        public String toString() {\n            return name + \", \" + age + \" years old\";\n        }\n    }\n\n    public void hello(Person person) {\n        System.out.println(\"Hello, \" + person);\n    }\n\n    public void helloPerson(String name, int age) {\n        Person person = new Person(name, age);\n        hello(person);\n    }\n\n    public static void main(String[] args) {\n        System.out.println(\"Hello, World!\");\n        HelloWorld hw = new HelloWorld();\n        hw.helloPerson(\"Tom\", 25);\n    }\n}\n输出结果\nHello, World!\nHello, Tom, 25 years old\n```\n\n\n在一个Java类中定义另一个类，就形成了内部类。\n\n- 内部类是**依赖**外部类的对象存在的\n- 内部类可以**无条件访问**外部类的属性和方法\n\n内部类允许我们更好地封装业务代码，当这些封装仅限于内部使用时。PHP则不支持内部类。\n\n\n**作业**\n\n- 尝试从外部类访问内部类的属性和方法\n- 尝试在main方法中生成Person对象\n\n\n\n## 注解与反射\n注解的英文名称是 annotation，顾名思义，就是给代码加个标记。\n\n举个例子，当你收到一封秘密信件，信封上写着“请务必亲手交给xxx”，你就知道了，这个信件是要转交给别人的，自己不能打开。注解的作用大致也是如此。\n框架管理一个对象时，会检查对象的注解，例如上文中的UserDAO类，被@Mapper注解修饰，框架就知道这里定义的是CRUD方法，需要去xml配置文件中查找对应的SQL。\n\n注解的检查工作是通过“反射”机制实现的，PHP也有反射机制，这里不再赘述。\n\n\n# 难度Level 3\n## 线程与多线程\n直接上代码\n```java\nimport java.util.ArrayList;\n\nclass ThreadDemo {\n    class MyThread extends Thread {\n\n        private int id;\n\n        MyThread(int id) {\n            this.id = id;\n        }\n\n        /**\n         * 线程的业务逻辑\n         */\n        @Override\n        public void run() {\n            System.out.println(\"Thread #\" + id);\n        }\n    }\n\n    public void run(int num) {\n        // 声明一个MyThread数组\n        ArrayList\u003cMyThread\u003e threads = new ArrayList\u003cMyThread\u003e();\n        for (int i=0; i\u003cnum; i++) {\n            threads.add(new MyThread(i));\n        }\n        // Java版 foreach\n        for (MyThread th : threads) {\n            th.start();\n        }\n    }\n\n    public static void main(String[] args) {\n        ThreadDemo demo = new ThreadDemo();\n        // 启动10个线程\n        demo.run(10);\n    }\n}\n输出\nThread #1\nThread #7\nThread #6\nThread #9\nThread #5\nThread #4\nThread #3\nThread #0\nThread #2\nThread #8\n```\n\n\n在main方法中启动10个MyThread线程。\n\n从输出结果可以看出，多个线程的执行顺序是不固定的，这依赖于系统调度。\n\n\n**多线程对于一些PHP开发者来说是一个全新的概念。PHP是单线程的，准确的说是一个进程内只包含一个线程。**\n**对于Java来说，一个进程内可以运行多个线程。**\n**可以把进程想象成“房屋”，线程就是房屋里的“住户”。多个人同住一个房屋时，就可能出现资源抢占的问题。例如大家需要排队用洗手间，排队用洗衣机等。**\n\n\n\n\n## 线程安全\n想象一下，大家在排队用洗衣机时，如果有人不守规则，把脏衣服混到别人正在洗的衣服里，那最后洗出来的衣服肯定是不干净的。用专业术语来说，就是产生了“脏数据”。\n\n上代码\n```java\nimport java.util.ArrayList;\n\nclass ThreadDemo1 {\n\n    private int counter;\n\n    public ThreadDemo1(int cnt) {\n        counter = cnt;\n    }\n\n    public int getCounter() {\n        return counter;\n    }\n\n    public void setCounter(int counter) {\n        this.counter = counter;\n    }\n\n    class MyThread extends Thread {\n\n        private int id;\n\n        MyThread(int id) {\n            this.id = id;\n        }\n\n        /**\n         * 线程的业务逻辑\n         */\n        @Override\n        public void run() {\n            System.out.println(\"Thread #\" + id);\n            setCounter(getCounter() + 1);\n        }\n    }\n\n    public void run(int num) {\n        ArrayList\u003cMyThread\u003e threads = new ArrayList\u003cMyThread\u003e();\n        for (int i=0; i\u003cnum; i++) {\n            threads.add(new MyThread(i));\n        }\n        // Java版 foreach\n        for (MyThread th : threads) {\n            th.start();\n        }\n        // join使主线程等待线程结束\n        for (MyThread th : threads) {\n            try {\n                th.join();\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public static void main(String[] args) {\n        ThreadDemo1 demo1 = new ThreadDemo1(0);\n        // 启动多个线程，计数器的值应等于线程数量，否则就有写冲突\n        demo1.run(150);\n        System.out.println(\"Counter final:\" + demo1.getCounter());\n    }\n}\n输出\n... ...\nCounter final:149\n```\n多试几次，总会出现计数器小于150的情况[捂脸]。如果你的电脑CPU只有一个核心，就不要试了[捂脸]。\n\n\n**思考**\n\n- 每个线程给计数器加1，为什么计数器最终的值会小于线程数量？\n\n\n\n## 如何防止并发问题\nPHP开发者也经常需要处理并发问题，但处理的往往是“进程级”的并发。\n\n下面演示如何用Java语言的锁机制防止线程并发问题。\n\n```java\nclass ThreadDemo1 {\n\n    private int counter;\n    private ReentrantLock lock;\n\n    public ThreadDemo1(int cnt) {\n        counter = cnt;\n        lock = new ReentrantLock();\n    }\n\n    public int getCounter() {\n        return counter;\n    }\n\n    public void setCounter(int counter) {\n        this.counter = counter;\n    }\n\n    class MyThread extends Thread {\n\n        private int id;\n\n        MyThread(int id) {\n            this.id = id;\n        }\n\n        /**\n         * 线程的业务逻辑\n         */\n        @Override\n        public void run() {\n            // 加锁\n            lock.lock();\n            System.out.println(\"Thread #\" + id);\n            setCounter(getCounter() + 1);\n            // 解锁\n            lock.unlock();\n        }\n    }\n    // 以下代码与上面的demo相同，省略\n}\n```\n\n\nReentrantLock对象的lock方法是阻塞的，当别的线程已经抢占了锁资源时，当前线程进入等待状态。\n\nlock确保同一时间只能有一个线程对计数器加1，这就避免了并发写入的问题，确保计数器的值等于线程数量。\n\n\n# 实践\n## SpringBoot上手\n\n使用Java做web开发，始终绕不开Spring框架。不像PHP有那么多web框架，像Laravel、Yii、ThinkPHP等等，Java开发者好像很容易达成一致，学Spring就对了。这也和Spring的本身就足够经典有关。\n\nSpringBoot是Spring的增强版，降低了初学者学习Srping的成本。Spring本身需要的配置很多，SpringBoot可以认为是配置好的Spring。下面就开始上手吧。\n\n\n首先需要引入依赖，配置文件如下（新增部分）\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n    \u003cparent\u003e\n        \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n        \u003cartifactId\u003espring-boot-starter-parent\u003c/artifactId\u003e\n        \u003cversion\u003e2.2.0.RELEASE\u003c/version\u003e\n        \u003crelativePath/\u003e \u003c!-- lookup parent from repository --\u003e\n    \u003c/parent\u003e\n    \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n\n    \u003cgroupId\u003ecom.limengxiang\u003c/groupId\u003e\n    \u003cartifactId\u003ebasics\u003c/artifactId\u003e\n    \u003cversion\u003e1.0-SNAPSHOT\u003c/version\u003e\n    ... ... \n\n    \u003cdependencies\u003e\n        \u003c!-- springboot 相关依赖--\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-web\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-test\u003c/artifactId\u003e\n            \u003cscope\u003etest\u003c/scope\u003e\n            \u003cexclusions\u003e\n                \u003cexclusion\u003e\n                    \u003cgroupId\u003eorg.junit.vintage\u003c/groupId\u003e\n                    \u003cartifactId\u003ejunit-vintage-engine\u003c/artifactId\u003e\n                \u003c/exclusion\u003e\n            \u003c/exclusions\u003e\n        \u003c/dependency\u003e\n\t\t\t\t... ...\n        \n    \u003c/dependencies\u003e\n\n\u003c/project\u003e\n```\n\n\n创建应用启动文件，MyApplication.java\n```java\npackage com.limengxiang.basics;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.ComponentScan;\n\n/**\n * SpringBootApplication注解告知框架这是启动类\n * ComponentScan注解告知框架需要扫描的组件位置\n * 这里告知框架扫描controller包下面的所有组件，确保HelloController被扫描\n */\n\n@SpringBootApplication\n@ComponentScan(\"com.limengxiang.basics.controller\")\npublic class MyApplication {\n    public static void main(String[] args) {\n        SpringApplication.run(MyApplication.class);\n    }\n}\n```\n\n\n创建Controller，HelloController.java\n```java\npackage com.limengxiang.basics.controller;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.Map;\n\n/**\n * RestController注解告知框架这是一个提供Restful API的组件\n */\n@RestController\npublic class HelloController {\n\n    /**\n     * 框架自动获取请求参数传给方法\n     * name参数必传\n     * RequestMapping是路由映射注解，可以指定请求路径和请求方法\n     *\n     * @param name\n     * @return\n     */\n    @RequestMapping(value = \"/hello\", method = RequestMethod.GET)\n    public String hello(@RequestParam(\"name\") String name) {\n        return \"Hello, \" + name;\n    }\n\n    /**\n     * 手动从request对象中获取参数\n     * 框架自动解决依赖，注入request对象\n     *\n     * @param request\n     * @return\n     */\n    @RequestMapping(value = \"/params\", method = RequestMethod.POST)\n    public Map params(HttpServletRequest request) {\n        // 获取一个参数\n        String name = request.getParameter(\"name\");\n        System.out.println(\"param name:\" + name);\n        // 获取所有参数\n        Map paramMap = request.getParameterMap();\n        System.out.println(\"param map:\" + paramMap);\n\n        return paramMap;\n    }\n}\n```\n\n\n创建应用配置文件 application.yml，我们使用了mybatis，需要在这里配置连接信息。\n\n这里可以随便写，只要有这几项就OK。\n```yaml\nspring:\n  datasource:\n    url: jdbc:mysql://localhost:3306/read_data?useUnicode=true\u0026characterEncoding=UTF-8\u0026useSSL=false\n    username: root\n    password: 123456\n    driver-class-name: com.mysql.cj.jdbc.Driver\n```\n\n\n打开启动文件，运行MyApplication\n```\n2020-04-13 15:30:52.815  INFO 13600 --- [           main] com.limengxiang.basics.MyApplication     : Starting MyApplication on limengxiang-ubt with PID 13600 (/home/limengxiang/workplace/java/basics/target/classes started by limengxiang in /home/limengxiang/workplace/java/basics)\n2020-04-13 15:30:52.820  INFO 13600 --- [           main] com.limengxiang.basics.MyApplication     : No active profile set, falling back to default profiles: default\n2020-04-13 15:30:55.181  INFO 13600 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2020-04-13 15:30:55.229  INFO 13600 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2020-04-13 15:30:55.229  INFO 13600 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.27]\n2020-04-13 15:30:55.441  INFO 13600 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2020-04-13 15:30:55.441  INFO 13600 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2548 ms\n2020-04-13 15:30:55.623  INFO 13600 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-04-13 15:30:55.996  INFO 13600 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2020-04-13 15:30:55.998  INFO 13600 --- [           main] com.limengxiang.basics.MyApplication     : Started MyApplication in 5.192 seconds (JVM running for 6.485)\n```\n\n\n用浏览器访问 [http://127.0.0.1:8080/hello?name=Tom](http://127.0.0.1:8080/hello?name=Tom)\n\n看到“Hello, Tom”就说明成功了。\n\n\n用curl模拟POST请求，curl -X POST -d 'name=Tom\u0026age=22' 'http://127.0.0.1:8080/params'，返回\n\n```\n{\"name\":[\"Tom\"],\"age\":[\"22\"]}\n```\n\n\n**作业**\n\n- 结合mybatis demo，尝试在API中操作数据库\n\n\n\n## 单元测试\nPHP和Java都需要写单元测试，这是个好习惯。我们以UserDAO为例。\n\n打开UserDAO.java，光标定位到类名，按下Alt + Enter键，在弹出的选项中选择“Create test”，勾选需要测试的方法。\n\n![image.png](https://cdn.nlark.com/yuque/0/2020/png/514450/1586764551457-046fded5-1646-4716-b744-cca0e1ed3e4e.png#align=left\u0026display=inline\u0026height=304\u0026name=image.png\u0026originHeight=607\u0026originWidth=994\u0026size=77812\u0026status=done\u0026style=none\u0026width=497)\n\n\n点“OK”，自动生成测试文件模板，自己填充测试逻辑即可。\n```java\npackage com.limengxiang.basics.dao;\n\nimport com.limengxiang.basics.model.UserModel;\nimport org.apache.ibatis.io.Resources;\nimport org.apache.ibatis.session.SqlSession;\nimport org.apache.ibatis.session.SqlSessionFactoryBuilder;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass UserDAOTest {\n\n    private UserDAO dao;\n\n    @BeforeEach\n    void setUp() throws IOException {\n        if (dao == null) {\n            InputStream stream = Resources.getResourceAsStream(\"mybatis-config.xml\");\n            SqlSession session = new SqlSessionFactoryBuilder().build(stream).openSession(true);\n            dao = session.getMapper(UserDAO.class);\n        }\n    }\n\n    @Test\n    void insert() {\n        // 插入数据\n        Integer inserted = dao.insert(\"allen\", \"13356986723\");\n        assertEquals(inserted, 1);\n    }\n\n    @Test\n    void selectOne() {\n        UserModel user = dao.selectOne(\"users\", 1);\n        System.out.println(\"Select one:\" + user);\n    }\n\n    @Test\n    void fuzzySearch() {\n        List\u003cUserModel\u003e users = dao.fuzzySearch(\"all%\", \"\");\n        System.out.println(\"Fuzzy search by username:\" + users);\n    }\n\n    @Test\n    void searchByUsernameOrMobile() {\n        List\u003cUserModel\u003e users;\n        users = dao.searchByUsernameOrMobile(\"all%\", \"135%\");\n        System.out.println(\"Search by username:\" + users);\n\n        users = dao.searchByUsernameOrMobile(\"\", \"135%\");\n        System.out.println(\"Search by mobile:\" + users);\n    }\n}\n```\n\n\n运行单元测试\n\n![image.png](https://cdn.nlark.com/yuque/0/2020/png/514450/1586764720930-76c4bb0b-3255-4c88-a531-da6bd7f05a31.png#align=left\u0026display=inline\u0026height=138\u0026name=image.png\u0026originHeight=276\u0026originWidth=978\u0026size=45298\u0026status=done\u0026style=none\u0026width=489)\n\n\n# 总结\n恭喜你成为了Java入门级开发者。\n由于作者水平有限，只能引领你走到这里。以后的路就靠你自己了。加油。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flimen%2Fphper-learn-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flimen%2Fphper-learn-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flimen%2Fphper-learn-java/lists"}