文章目录
基本概念
JRE:JAVA RUNTIME Environment,运行时环境,运行java程序必备,但只能运行不能开发和调试
JDK:Java Development Kit,开发工具 包,包含JRE、工具和基础类库
JVM
Write Once , Run Anywhere
JAVA是半编译半解释语言
- 编译型语言:开发完将所有代码编译成可执行程序(里面是机器码),这样就实现了"一次编译,无限次运行",只需要可执行程序,不需要编译环境,但是一般不能跨平台(例如exe不能在linux运行,linux和ndows的函数也不一样),比如c
- 解释型语言:用到哪些代码就转换哪些,无法脱离开发环境,必须给源码才能运行,“一次编写,到处运行”,解释器适配不同的平台,有了解释器这个中间层,代码就可以跨平台,比如python
- Java:先编译所有源码到中间文件内,然后将中间文件拿到虚拟机执行,兼顾了跨平台和执行效率
javac
- javac是一种编译器,可以将一种语言规范编译为另一种语言规范,编译器一般是将人好理解的语言编译为机器好理解的语言,因为有jvm的存在(识别二进制字节码转化为机器识别码,然后将项目运行在不同平台上),所以javac只需要将java类编译为二进制字节码也就是.class文件,只有jvm能识别,然后jvm负责解释.class文件变成本地可执行命令使机器执行
- 编译器步骤:
- 词法分析:分辨关键字
- 语法分析:是否符合java语言规范
- 语义分析:化繁为简,添加缺失,例如将foreach转为for
- 字节码生成
逻辑运算
数据类型
数据类型实质上是用来定义编程语言中相同类型的数据的存储形式,是编程语言中对内存的一种抽象表达方式,也就是决定了如何将代表这些值的 bit 存储到计算机的内存中
所以,数据在内存中的存储,是根据数据类型来划定存储形式和存储位置的。
基本数据类型
byte short int long boolean char float double
这些类型不是对象,没有hashCode方法,违背了Java面向对象,一切皆对象的初衷,无法放入list等对象存储结构中
引用类型
类、接口、数组(大小统一4字节,用于记录引用对象的地址)
对基本类型做了包装类以便面向对象
他们有hashCode,可以比equals更快的进行对象比较,在HashMap、HashSet等散列结构中,都是先通过hashCode查找对象的
包装类型
Java分为八大基本类型和class、interface等引用类型,基本类型是不能定义为null的,即不是一个对象,Java是一个面向对象的语言,Java基于对象进行操作,想要把基本类型封装成对象,就有了包装类,Java的核心库java.lang提供了每个基本类型的包装类
基本类型没法用toString()等一些基本方法,必须作为对象才能调用这些基本方法,且List<>这种泛型要求必须是Object或其子类,所以需要装拆箱,主要还有java定义为面向对象,所以一切比较皆对象
例如ArrayList就要求每个成员都是Object或其子类类型的,需要用包装类进行填充
自动装箱
Integer i = 100
int x = n
直接把int变为integer叫自动装箱,反过来叫自动拆箱
发生在编译阶段,目的是为了少写代码,但可能会影响代码效率,我认为还是手写显式的进行装拆箱比较好,Java在很多地方都做了自动装箱
计算机内存中只用二进制表示,例如int n = 100在内存中是01100100,但是print出来是十进制的100,这是程序设计的一个重要原则:数据的存储和显示要分离
https://www.zhihu.com/question/375456014
值传递和引用传递
https://blog.csdn.net/weixin_45454859/article/details/115682731
https://zhuanlan.zhihu.com/p/145248444?utm_source=wechat_session&utm_medium=social&s_r=0
形参和实参
- 实参:方法被调用时传入的实际值,,它在方法被调用时已经被初始化,并在调用时传入
- 形参:方法被调用时传入的参数,如:func(int a)中的a,只有在func被调用期间a才有意义,也就是会被分配内存空间;在方法func执行完成后,a就会被销毁释放空间,也就是不存在了
所以groovy之前他们不写return不写接收的只传过去形参,对于实际的参数属性没有改变
区别
Java是值传递
- 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
- 引用传递是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
或者是:
- 值传递:在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。
- 引用传递:”引用”也就是指向真实内容的地址值。在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向同一个内存地址,对形参的操作会影响真实内容。
https://blog.csdn.net/txlfreedom/article/details/113095863
JAVA中基本类型是值传递,引用类型是引用传递,参考上面链接,但是String却是值传递
foreach 中的使用
int[] nums = { 1, 2, 3, 4, 5 };
for (int i : nums) {
if (i == 4) {
i = 233;
System.out.println("已修改");
}
}
//i不变,还是4
Person[] persons = { new Person("张三", 12), new Person("李四", 33), new Person("王五", 90) };
for (Person person : persons) {
if (person.name.equals("张三")) {
person.age = 100;
System.out.println("已修改");
}
}
//张三的age已经改为100
为什么呢
int[] nums = { 1, 2, 3, 4, 5 };
for (int i : nums) {
if (i == 4) {
i = 233;
}
}
//相当于:
for(int j=0;j<nums.length;j++){
int i=nums[j];
if(i==4){
i=233;
}
//所以说修改对于原数组根本没有任何影响
}
子函数的引用指向了233这个对象,但是主函数还是指向了4,所以不变
而对于Person类相当于
for(int j=0;j<persons.length;j++) {
Person i=persons[j];
if (i.name.equals("张三")) {
i.age = 100; //这里改变的是persons[j].age,而不是persons[j]本身,所以能成功改变
}
}
子函数的引用指向了persons[j],修改了属性值,主函数也指向了persons[j],就可以拿到已经修改的属性值了
总而言之,实体类对象foreach可以修改属性,其他的还是老实的普通for循环即可
异常
分为 Exception 和 Error,继承自 Throwable,Exception分为编译时异常和运行时异常,编译时异常指在代码编译阶段程序就报出该异常,指明该代码块需要使用throw或者try catch,常见有IOException和SQLException等
运行时异常指代码跑着跑着出现的异常,如空指针、数组越界等。Error指程序无法处理的错误,如jvm堆栈问题、内存溢出、端口占用等。
异常能被程序本身处理,错误没法被程序处理。
捕获
看如下代码:
try{
//待捕获代码
}catch(Exception e){
System.out.println("catch is begin");
return 1 ;
}finally{
System.out.println("finally is begin");
return 2 ;
}
//catch is begin
//finally is begin
//2
//执行了finally后已经return了,所以catch里面的return不会被执行到。也就是说finally永远都会在catch的return前被执行
可以使用finally关闭资源,也可以使用try-with-resource语法进行资源的关闭,这是一个语法糖
try (BufferedReader br = new BufferedReader) {
} catch {
}
多个异常使用相同处理,可以
catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
System.out.println("Bad input");
}
e.getMessage(); 只会获得具体的异常名称. 比如说NullPoint 空指针,就告诉你说是空指针…
e.printStackTrace();会打出详细异常,异常名称,出错位置,便于调试用… 会比较长,慎用
抛出
某方法出了异常且没有捕获,异常就会抛到上层调用方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c6P89a9p-1652690986858)(https://note.youdao.com/yws/res/3281/WEBRESOURCE7ebf46bbf3624f6c12837d8d0f68a6c0)]
全局统一异常
后台手册 | RuoYi
NullPointerException
TODO
事务
引入了spring-boot-starter即可开启事务,通过@Transcational注解使用事务
- @Transcational注解只能作用于public方法上
坑点1
@Transactional
public int insertUser(User user) throws Exception
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
// 模拟抛出SQLException异常
boolean flag = true;
if (flag)
{
throw new SQLException("发生异常了..");
}
return rows;
}
Spring默认只检查RuntimeException和Error,其他I异常需要使用rollbackFor属性,@Transactional(rollbackFor = Exception.class)
坑点2
catch捕获异常,相当于把异常吃了,spring无法感知异常,自然不会回滚数据,需要将异常抛出,可以在方法上抛,也可以catch上抛
@Transactional
public int insertUser(User user) throws Exception
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
// 模拟抛出SQLException异常
boolean flag = true;
if (flag)
{
throw new RuntimeException("发生异常了..");
}
return rows;
}
或者
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("发生异常了..");
}
面向对象
面向过程不会考虑复用,就像我车轮子坏了,我是从头造个轮子用还是直接拿现成的用(面向对象考虑标准的模块化设计,核心的定标准规范,就像我的轮子必须和车同型号)
三大特性
- 继承
- 手机在通话的基础上进行功能延展,典型的继承
- 功能延续,代码复用
- 细化或者说明确功能,比如继承 HashMap,那子类一般就是做 map 进行返回的
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
- super:实现对父类成员的访问
- this:指向自己的引用
- final:类不能被继承,方法不能被重写
- 子类不继承父类的构造器,只能调用,如果父类的构造器有参数,子类的构造器中需要用super调用父类构造器并传参,如果父类是无参构造器则程序自动调用父类构造方法
- 封装
- 隐藏过程,内部操作不可见,保证了安全性
- 定规范进行模块化间的配合,就是使用接口进行通信和调用
- 典型的get/set方法,使用private定义实体类属性,只暴露get/set方法,保证了代码和数据的安全
- 多态
- 编译时多态和运行时多态,就是重载和重写
- 重写要加上@Override,重写可以在不修改父类的基础上扩展子类
- 面向对象需要用重写和对象造型
- 向上转型,
父类 对象名 = new 子类,对象只调用子类重写的父类方法,不能调用子类自己的方法,将调用拆分出来可以简化方法 - 多态的直观表现就是不同子类的接口实现
https://blog.csdn.net/hou_shiyu/article/details/99086741 - 在继承的基础上扩展
- final:
- 类不能被继承
- 方法不能被重写
- 属性不能被修改
OOP:面向对象编程
类与对象
- 类:对某一类事物的共性的抽象概念
- 对象:一个具体产物
类是模板,对象是可使用的实例
抽象类
使用abstract修饰的方法是抽象方法,包含抽象方法的类必须用abstract定义为抽象类
Amethod am = new Amethod()编译错误,抽象类无法被实例化
接口
- 没有字段
- 方法默认public abstract
- 一个类可以implements多个interface
- 与多态结合,典型的是
List list = new ArrayList()和List list = new Vector(),这是java面向对象的表现形式,依赖于抽象不依赖于实现,因为重写方法的返回值相同,所以调用者不用关心具体实现,我只需要改变new的具体实现类就可以完成不同的实现,改动小扩展好 - 可以有静态字段
静态字段与静态方法
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。
静态方法可以访问静态字段和其他静态方法
包
- jvm会根据包+类找到具体类进行编译
- 不用public等修饰的方法和字段就是包作用域,同包可访问
- 编译器默认import同包的类和java.lang.*
- 包没有父子和继承关系,java.util和java.util.zip是不同的包
构造方法
有参和无参,使用new来调用,可以用this对字段初始化
重载
方法名和返回相同,参数不同,为了功能类似的方法用同一个名字方便记住,比如说String的indexOf()方法
连接
连接 es、mysql,需要在创建和关闭连接时加锁
内部类
可以实现java的多继承,目前没用到
内部类拥有外部类的private访问权限
https://blog.csdn.net/yu422560654/article/details/7466260
反射
https://www.zhihu.com/question/377483107
当我的接口接收不同且未知的实体类对象时,我需要用Object接收,要知道接收对象的具体类型且用到其属性或方法,需要反射,在程序运行期,通过反射拿到具体对象,在编译器我是不知道会拿到什么对象的
Class 实例
JVM每加载一个class就会为其创建一个Class实例
以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来
Class cls = new Class(String);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o6IXYxHw-1652690986860)(https://note.youdao.com/yws/res/a/WEBRESOURCEe55739e6cddb93682dcc9099e37ba58a)]
如何获取一个class的Class实例:
- 方法一:直接通过一个class的静态变量class获取:
Class cls = String.class; - 方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String s = "Hello";
Class cls = s.getClass(); - 方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class cls = Class.forName("java.lang.String");
==精准比较两个class的数据类型,instanceof可以匹配指定类型的子类型
动态加载
JVM在执行java程序时,并不是一次性把所有class都加载到内存,是第一次用到该class才会加载,这是JVM的动态加载
访问字段
Class提供以下方法访问字段
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
Object p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"
一般实体类对象用反射
f.setAccessible(true);不管这个字段是不是public,一律允许访问。
动态代理
TODO
编写接口后,不需要实现类,通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler
注解
注释会被编译器直接忽略,注解则可以被打包放进class文件
Java的注解分两类:
- 由编译器使用的注解,不会进入.class文件,编译后会被扔掉
- @Override:让编译器检查是否正确覆写
- @SuppressWarnings:告诉编译器忽略代码产生的警告
- 程序运行期能读取的注解,常用此类
举例
- @Resource/@Autowired:都是用来注入依赖的,Resource根据名称注入,是JAVA自带的;Autowired根据类型注入,是Spring的
- @SpringBootApplication:springboot核心注解,标识springboot应用,用来开启boot的各项能力,实际上是
@Configuration、@EnableAutoConfiguration、@ComponentScan三个注解的合体 - @Configuration:定义配置类,相当于传统的xml配置文件,例如读取yml文件的配置并做处理或提供相应方法用于调用
- @Service:业务层标识
- @ResponseBody:该方法返回的数据填入HTTP Response body中
- @Component:泛指组件
- @Bean:产生一个bean
- @RequestMapping:地址映射
- @RequestParam:获取单个参数
- @PathVariable:获取url后面跟的参数
自定义注解
使用@interface定义注解,最常用参数应命名为value,使用default设定默认值
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
元注解
修饰其他注解的注解,在自定义注解上进行注解
- @Target:定义该注解的使用范围
- @Retention:定义该注解的生命周期
泛型
ArrayList,一种数据类型模板,不定义的话就是Object,需要强转很麻烦例如:
List li = new ArrayList();
li.add("我是Object");
String str = (String) li.get(0);
泛型就是一种模板,例如ArryList<String>,这样就规定了传参的类型(不定义泛型其实就是用Object)
集合
一个java对象,内部持有其他java对象,并对外提供访问接口
List
数组大小不可变且只能顺序存取,ArrayList使用数组来存储,例如list=[1,2,3,4,5],实际内部用数组存储了这些元素,但是其实际大小是X(X>5,内部预留了空闲位置),添加时自动移动空闲元素然后填充,没有空闲元素时会新建更大数组然后把旧数组赋值过去,它把对数组进行的操作进行了封装
一般情况下使用ArrayList,LinkedList使用链表实现,每个元素指向下一个,效率不如ArrayList
快速创建list
List<Integer> list = List.of(1, 2, 5);
数组静态初始化
int[] i = new int[]{1,2,3};
迭代器Iterator比普通for循环要高效,foreach本身是使用了迭代器
Map
containsKey(K key)判断是否存在key
map是无序的,不是put的顺序也不是key的顺序
遍历
- 遍历key
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
- 遍历key和value
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
IO
以内存为中心,input(外部读取到内存)和output(内存读取到外部)
代码是在内存中运行的,数据必须在内存中,最终表示为byte数组、字符串等
IO流是一种流式的数据输入/输出模型:
- 二进制数据以byte为最小单位在InputStream/OutputStream中单向流动;
- 字符数据以char为最小单位在Reader/Writer中单向流动。
- InputStream / OutputStream:两个抽象类
File
File对象既可以表示文件也可以表示目录,构建File对象时不会判断具体文件是否存在
有些时候,程序需要读写一些临时文件,File对象提供了createTempFile()来创建一个临时文件,以及deleteOnExit()在JVM退出时自动删除该文件。
如果需要对目录进行复杂的拼接、遍历等操作,使用Path对象更方便。
在计算机中,类似文件、网络端口这些资源,都是由操作系统统一管理的。应用程序在运行的过程中,如果打开了一个文件进行读写,完成后要及时地关闭,以便让操作系统把资源释放掉,否则,应用程序占用的资源会越来越多,不但白白占用内存,还会影响其他应用程序的运行。
//使用try--finally关闭资源占用,或者使用java7的try(resource)进行关闭,编译器会自动加上finally
try (InputStream input = new FileInputStream("src/readme.txt")) {}
SpringBoot
参数接收
Controller与前端表单交互接收参数有以下几种方式
- 通过标签的name直接接收
@RequestMapping("/login")
public void login(String email, String password){
System.out.println("email是"+email);
System.out.println("password是"+password);
}
- 通过@RequestParamer接收,可以自定义命名
@RequestMapping("/login")
public void login(@RequestParam("email") String e,@RequestParam("password") String p){
System.out.println(e);
System.out.println(p);
}
- 通过自定义model接收
- 通过@HttpServletRequest接收
@RequestMapping("/login")
public void login(HttpServletRequest request ){
String query = request.getQueryString();
String email = request.getParameter("email");
列表
Stream
Stream貌似不会跳出,需要break的情况还是普通for
list.stream().filter(dto-> 筛选条件).forEach(dto2-> dto2.setId("1"));
String
String.indexOf()是从0开始的!
- String的a+b操作会使用StringBuilder.append方法进行字符串拼接,然后使用toString方法进行返回,会造成内存消耗,这是语法糖
- String是用final修饰的不可变类,严格来说String是个字符串常量,所以每次对String操作都会在常量池产生新的对象或者指向已有的对象
- StringBuilder可变,底层是char[],创建时会给一定容量的数组,之后append或者其他操作都会在原对象里变化,例如[a] --> [a,b]
内存消耗
static方法会消耗更多的内存,LeetCode解题改掉一个static方法,内存少消耗了一半
语法糖
https://www.cnblogs.com/yanzige/p/10913919.html
- switch支持string枚举:其实是通过String的hashCode和equal方法实现的,比较的是哈希值
- 泛型:List在编译时会被擦除掉泛型, 编译器根本不认识泛型
- 自动拆装箱:还是通过valueOf实现
- 方法变长参数:通过(String… strs){}接收变长参数,实际还是通过数组
- try-with-resource:通过try(){}的方式进行资源关闭,其实还是通过各种if判断进行资源关闭
- foreach:底层用for和iterator