Java基础笔记(持续更新)

基本概念

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

版权声明:本文为qq_42544599原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。