java薄弱基础知识记录(一)
所有内容为对“廖雪峰java”的整理
总结了我认为需做笔记的地方
- 用final修饰的类不能被继承
- 对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。
- 到底什么是方法签名呢? 通过指定方法的访问级别(例如 public 或private)、可选修饰符(例如abstract 或sealed)、返回值、名称和任何方法参数,可以在类或 结构中声明方法。 这些部分统称为方法的“签名”。 为进行方法重载,方法的返回类型不是方法签名的一部分。
基础知识
- java程序基础:
- 变量、数据类型:整形(int、long、short、byte)
浮点型(double、float)
字符型(char)
布尔型(bool)
引用型(除以上基本类型外,都是引用类型:引用类型的变量类似于C语言的指针,它内部存储一个“地址”,指向某个对象在内存的位置) - 常量:变量的时候,如果加上final修饰符,这个变量就变成了常量
- var变量:使用var定义变量,仅仅是少写了变量类型而已,编译器会自动推断变量类型。例如:
var sb = new StringBuilder();
- 类型的概念:
- 数据类型是和数据结构密切相关的,它是:值的集合和定义在这个值集上的一组操作的总称。分为原子类型和结构类型。
- 转义字符:
" 表示字符"
’ 表示字符’
\ 表示字符
\n 表示换行符
\r 表示回车符
\t 表示Tab
\u#### 表示一个Unicode编码的字符
- 变量、数据类型:整形(int、long、short、byte)
- 面向对象程序设计:
- 方法:固定参数、可变参数、参数绑定
- 构造方法:可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分。
- 方法重载:目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
- 继承:继承树、super、向上转型、向下转型(同类型,利用instanceof,在向下转型前可以先判断)、区分继承和组合
- 多态:在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。用final修饰的方法不能被Override。
- 抽象类:abstract
- 接口:interface、default方法
- 静态字段和静态方法
- 包
- 作用域:全局变量、局部变量、public、private、protected、final、一个java文件只能有一个public签名(修饰符)
- 内部类:Inner Class、Anonymous(匿名) Class、Static Nested Class
- classPath:classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class
- jar:强烈不推荐在系统环境变量中设置classpath,那样会污染整个系统环境。在启动JVM时设置classpath才是推荐的做法。实际上就是给java命令传入-classpath或-cp参数。
/META-INF/MANIFEST.MF(存放class信息,就不用JVM命令行参数了) - class版本:
javac --source 9 --target 11 Main.java
- 模块:自带“依赖关系”的class容器就是模块(jmods);
编写模块(项目中必须有module-info.java);
运行模块(java --module-path hello.jar --module hello.world
);
打包jre(在当前目录下,我们可以找到jre目录,这是一个完整的并且带有我们自己hello.jmod模块的JRE);
访问权限:class的这些访问权限只在一个模块内有效,模块和模块之间,例如,a模块要访问b模块的某个class,必要条件是b模块明确地导出了可以访问的包(module-info.java中编写如下代码)。module java.xml { exports java.xml; exports javax.xml.catalog; exports javax.xml.datatype; ... }
- java核心类
- 字符串string:两个字符串比较,必须总是使用equals()方法。
System.out.println(s1.equals(s2));
- 字符串声明方式及原理:java字符串声明方式区别1 java中堆栈和常量池详解|区别2
java常量池和堆空间的区别 堆和池 java虚拟机讲区别 - 比较:想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()方法而不能用==;要忽略大小写比较,使用equalsIgnoreCase()方法。
- 以下详见:
- 是否包含字符串/提取子串
- 字符索引
- 去除首尾空白字符trim()、strip()
- 替换字符串replace
- 分割字符串splice
- 拼接字符串join
- 格式化字符串formatted()、format()
- 类型转换:String.valueOf(123); Integer.parseInt(“123”); Boolean.parseBoolean(“true”); “Hello”.toCharArray();
- 字符编码:
byte[] b1 = "Hello".getBytes(); // 按系统默认编码转换,不推荐 byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换 byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换 byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换
- 字符串声明方式及原理:java字符串声明方式区别1 java中堆栈和常量池详解|区别2
- stringbuilder:为了能高效拼接字符串,Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象。append()、delete()方法。
- StringJoiner:add方法 String.join()(在内部使用了StringJoiner来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()更方便)
- 包装类型:想要把int基本类型变成一个引用类型,我们可以定义一个Integer类,它只包含一个实例字段int,这样,Integer类就可以视为int的包装类(Wrapper Class)。
直接把int变为Integer的赋值写法,称为自动装箱(Auto Boxing),反过来,把Integer变为int的赋值写法,称为自动拆箱(Auto Unboxing)
所有的包装类型都是不变类(final)。
进制转换:
System.out.println(Integer.toString(100)); // "100",表示为10进制 System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制 System.out.println(Integer.toHexString(100)); // "64",表示为16进制 System.out.println(Integer.toOctalString(100)); // "144",表示为8进制 System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制
整数和浮点数的包装类型都继承自Number - javaBean:有私有属性的字段和读getter写setter方法的类。
作用:JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,主要用在图形界面的可视化设计中。
枚举javaBean属性:使用Introspector.getBeanInfo()可以获取属性列表。 - 枚举类:java枚举类型是一种基本数据类型而不是构造数据类型,而在C语言等计算机编程语言中,它是一种构造数据类型。 枚举类型用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。 定义:是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。
enum:为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类。其次,不可能引用到非枚举的值,因为无法通过编译。最后,不同类型的枚举不能互相比较或者赋值,因为类型不符。
enum比较:使用enum定义的枚举类是一种引用类型(类)。因为enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较。
enum类型:
定义的enum类型总是继承自java.lang.Enum,且无法被继承;
只能定义出enum的实例,而无法通过new操作符创建enum的实例;
定义的每个实例都是引用类型的唯一实例;
可以将enum类型用于switch语句。
name():返回枚举的名称常量名
ordinal():返回定义的常量的顺序,从0开始计数。要编写健壮的代码,就不要依靠ordinal()的返回值。因为enum本身是class,所以我们可以定义private的构造方法,并且,给每个枚举常量添加字段
switch:因为枚举类天生具有类型信息和有限个枚举常量,所以比int、String类型更适合用在switch语句中 - 记录类:使用record(通常用于定义数据类)定义的是不变类(类和字段final)。构造方法、可以编写Compact Constructor对参数进行验证、可编写静态方法of()。和enum类似,我们自己不能直接从Record派生,只能通过record关键字由编译器实现继承
- BigInteger:BigInteger用于表示任意大小的整数;BigInteger是不变类,并且继承自Number;将BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确。
- BigDecimal:BigDecimal用于表示精确的小数,常用于财务计算;比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()
- 常用工具类:Math:数学计算、Random:生成伪随机数、SecureRandom:生成安全的随机数
- 字符串string:两个字符串比较,必须总是使用equals()方法。
面向抽象编程
- 当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例:这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
- 面向抽象编程的本质就是:
- 上层代码只定义规范(例如:abstract class Person);
- 不需要子类就可以实现业务逻辑(正常编译);
- 具体的业务逻辑由不同的子类实现,调用者并不关心。
- 位于同一个包的类,可以访问包作用域的字段和方法。不用public、protected、private修饰的字段和方法就是包作用域。
异常处理
- 捕获异常try catch finally 捕获多种异常
- 抛出异常:创建某个Exception的实例;用throw语句抛出。调用printStackTrace()可以打印异常的传播栈,对于调试非常有用。通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。
- 屏蔽异常:这说明finally抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。
- 自定义异常
- NullPointerException空指针异常:
- NullPointerException是Java代码常见的逻辑错误,应当早暴露,早修复;
- 可以启用Java 14的增强异常信息来查看NullPointerException的详细错误信息。
- 断言(assert):断言是一种调试方式,断言失败会抛出AssertionError,只能在开发和测试阶段启用断言;对可恢复的错误不能使用断言,而应该抛出异常;断言很少被使用,更好的方法是编写单元测试。
- TS类型断言定义:把两种能有重叠关系的数据类型进行相互转换的一种 TS 语法,把其中的一种数据类型转换成另外一种数据类型。
- JDK logging:
- 日志是为了替代System.out.println(),可以定义格式,重定向到文件等;
- 日志可以存档,便于追踪问题;
- 日志记录可以按级别分类,便于打开或关闭某些级别;
- 可以根据配置文件调整日志,无需修改代码;
- Java标准库提供了java.util.logging来实现日志功能。
- Commons Logging:
- Commons Logging是一个第三方日志库,它是由Apache创建的日志模块。Commons Logging的特色是,它可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。
- Commons Logging可以自动检测并使用其他日志模块。
- Commons Logging日志级别:FATAL、ERROR、WARNING、INFO、DEBUG、TRACE
- log4j:Log4j是一个组件化设计的日志系统,它的架构大致如下:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
├──>│ Appender │───>│ Filter │───>│ Layout │───>│ Console │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
├──>│ Appender │───>│ Filter │───>│ Layout │───>│ File │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
└──>│ Appender │───>│ Filter │───>│ Layout │───>│ Socket(通过网络输出到远程计算机)
└──────────┘ └──────────┘ └──────────┘ └──────────┘
当我们使用Log4j输出一条日志时,Log4j自动通过不同的Appender把同一条日志输出到不同的目的地。 - SLF4J和LogBack:其实SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现。始终使用SLF4J的接口写入日志,使用Logback只需要配置,不需要修改代码。
反射
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
- Class类:由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)。
- 动态加载:当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。
- 访问字段:
Field getField(name):根据字段名获取某个public的field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类) - 调用方法:
Method getMethod(name, Class…):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) - 调用构造方法:
getConstructor(Class…):获取某个public的Constructor;
getDeclaredConstructor(Class…):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。 - 获取继承关系:
Class getSuperclass():获取父类类型;
Class[] getInterfaces():获取当前类实现的所有接口。 - 动态代理:动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
注解Annotation
- 第一类是由编译器使用的注解,这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。
- 第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
- 第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。
- 注解可以配置参数
- 定义注解public @interface Report
- 元注解:有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)
- 使用注解:使用反射API读取Annotation;注解的作用(个人认为):检查
泛型
- T是任意一种类
- 泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型
- 注意泛型的继承关系:可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。
- 使用、编写:多个泛型类型
- 擦拭法:Java语言的泛型实现方式是擦拭法(Type Erasure)。所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
- first = new T();
last = new T();
擦拭后实际上变成了:
first = new Object();
last = new Object(); - 局限一:不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型
- 局限二:无法取得带泛型的Class。
- 局限三:无法判断带泛型的类型
- 局限四:不能实例化T类型;
- 要实例化T类型,我们必须借助额外的Class参数。
- 一个类可以继承自一个泛型类。例如:父类的类型是Pair,子类的类型是IntPair,可以这么继承
- first = new T();
- extends通配符:限制T
- super通配符:扩展
- 反射和泛型:Java的部分反射API也是泛型。例如:Class就是泛型。。
- 谨慎使用泛型可变参数:static T[] asArray(T… objs)
集合
- List:在集合类中,List是最基础的一种集合:它是一种有序列表。List是一种有序链表:List内部按照放入元素的先后顺序存放,并且每个元素都可以通过索引确定自己的位置。
- equals():在List中查找元素时,List的实现类通过元素的equals()方法比较两个元素是否相等,因此,放入的元素必须正确覆写equals()方法,Java标准库提供的String、Integer等已经覆写了equals()方法;
- Map:Map是一种映射表,可以通过key快速查找value
- 编写equals、hashcode:list、map必须正确覆写equals()方法。
- 思考一下HashMap为什么能通过key直接计算出value存储的索引。相同的key对象(使用equals()判断时返回true)必须要计算出相同的索引,否则,相同的key每次取出的value就不一定对。
- 通过key计算索引的方式就是调用key对象的hashCode()方法,它返回一个int整数。HashMap正是通过这个方法直接定位key对应的value的索引,继而直接返回value。
- enumMap:如果作为key的对象是enum类型,那么,还可以使用Java集合库提供的一种EnumMap,它在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。
- 使用EnumMap的时候,根据面向抽象编程的原则,应持有Map接口。
- TreeMap:还有一种Map,它在内部会对Key进行排序,这种Map就是SortedMap。注意到SortedMap是接口,它的实现类是TreeMap。
- Properties:Properties设计的目的是存储String类型的key-value,但Properties实际上是从Hashtable派生的,它的设计实际上是有问题的,但是为了保持兼容性,现在已经没法修改了。除了getProperty()和setProperty()方法外,还有从Hashtable继承下来的get()和put()方法,这些方法的参数签名是Object,我们在使用Properties的时候,不要去调用这些从Hashtable继承下来的方法。
- 用于程序中配置文件的实时编写,或配置文件单独编写
- Set:如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set。
- Quenue
- PriorityQueue优先级队列:
- PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。
- PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。
- Deque:允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue),学名Deque。
- Stack
- Iterator:使用迭代器的好处在于,调用方总是以统一的方式遍历各种集合类型,而不必关心它们内部的存储结构。
- collections:Collections是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合。
I/O
文件处理(练练编程就可掌握)
时间与日期
单元测试
- Junit测试:单元测试可以确保单个方法按照正确预期运行,如果修改了某个方法的代码,只需确保其对应的单元测试通过,即可认为改动正确。此外,测试代码本身就可以作为示例代码,用来演示如何调用该方法。
- fixTure:JUnit提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture。
- 异常测试
- 条件测试:@Disabled
- 参数化测试
正则表达式
匹配规则、复杂匹配规则、分组匹配、非贪婪匹配、搜索和替换
安全和加密
- 编码算法:
- URL编码和Base64编码都是编码算法,它们不是加密算法;
- URL编码的目的是把任意文本数据编码为%前缀表示的文本,便于浏览器和服务器处理;
- Base64编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加1/3。
- 哈希算法:哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
- 碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足
- 哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
- 数据库管理员能够看到用户明文口令;
- 数据库数据一旦泄漏,黑客即可获取用户明文口令。
- BouncyCastle:BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方库。
- Hmac算法:Hmac算法就是一种基于密钥的消息认证码算法,它的全称是Hash-based Message Authentication Code,是一种更安全的消息摘要算法。
- 对称加密算法:对称加密算法就是传统的用一个密码进行加密和解密。例如,我们常用的WinZIP和WinRAR对压缩包的加密和解密,就是使用对称加密算法
- 口令加密算法、密钥交换算法、非对称加密算法、签名算法(公知于大众)、数字证书(一层一层检验)
多线程(参考OS)
maven基础
- 常用maven phase:经常用到的phase其实只有几个:
clean:清理
compile:编译
test:运行测试
package:打包-
执行一个phase又会触发一个或多个goal:
执行的phase 对应的goal compile compiler:compile test compiler:testCompile ^ surefire:test
-
- 使用插件:
- 插件
插件名称 对应执行的phase clean clean compiler compile surefire test jar package - 自定义插件:定义plugin/configuration。
- 插件
- 多模块管理:中央仓库 私有仓库 本地仓库
- munw:mvnw是Maven Wrapper的缩写。因为我们安装Maven时,默认情况下,系统所有项目都会使用全局安装的这个Maven版本。但是,对于某些项目来说,它可能必须使用某个特定的Maven版本,这个时候,就可以使用Maven Wrapper,它可以负责给这个特定的项目安装指定版本的Maven,而其他项目不受影响。简单地说,Maven Wrapper就是给一个项目提供一个独立的,指定版本的Maven给它使用。
- 发布Artifact:
- 可以发布到本地,然后推送到远程Git库,由静态服务器提供基于网页的repo服务,使用方必须声明repo地址;
- 可以发布到central.sonatype.org,并自动同步到Maven中央仓库,需要前期申请账号以及本地配置;
- 可以发布到GitHub Packages作为私有仓库使用,必须提供Token以及正确的权限才能发布和使用。