Appearance
Java 核心技术学习笔记
Java 核心技术 I
2022.10.13
1.2
Java语言健壮性
Java和C++最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能性。
3.5.1
计算精度问题
由于Java中的double类型使用64位存储一个数值,而有些处理器使用80位的浮点寄存器,但最终结果都要被截断位64位,这就可能会导致不同处理器运行浮点计算出现不一致的计算结果或者导致溢出的情况。针对这种问题,可以使用strictfp 关键字修饰类或方法,这样可以使被修饰的方法使用严格的浮点计算,这样便可以在不同平台上获得相同的结果。
3.5.2
余数问题
计算时钟时使用 ( a + b ) % 12 这个式子,当b小于0时,运算的结果可能为负数,这个时候我们可能会使用
( ( a + b ) % 12 + 12 ) % 12 这种方式,十分的麻烦。这种情况可以使用Math类中的 floorMod 方法,解决方法就变为了 floorMod( a + b, 12) 。(当除数为负数时,floorMod会返回负数结果,但是结合实际场景,这种情况很少出现)
计算抛出异常
一般情况下,Java进行数学运算时,如果出现溢出等异常,使用运算符进行运算只会返回一个错误的结果而不做任何提醒。而使用Math类中的addExact、subtractExact、multiplyExact等方法,可以将溢出等异常进行捕获并抛出,使得整数具有更好的运算安全性。
位运算
Java中可以使用 >>> 运算符来进行右移操作,并用0填充高位,即逻辑运算。
码点
码点是信息原子的单元
代码单元是一个用来存储编码码点的一部分的单元
单独的一个代码单元可能代表一个完整的码点,也可能是一个码点的一部分
java
public class C366 {
public static void main(String[] args) {
String s = "hello🍺";
System.out.println(s);
System.out.println(s.codePointCount(0, s.length()));
System.out.println(s.codePointAt(5));
System.out.println(s.codePointAt(6));
System.out.println(s.codePoints().count());
// 判断字符是否是补充字符
System.out.println(Character.isSupplementaryCodePoint(s.codePointAt(5)));
}
}子串
substring方法的第二个参数是不想复制的第一个位置
判断字符串是否相等
equals可以检测两个字符串是否相等==只能判断两个字符串是否在同一个位置上,若字符串在同一个位置上,便可以使用==来判断只有字符串字面量是共享的,而+或substring等操作得到的字符串并不共享。
3.10.6
Arrays.binarySearch,二分查找,能找到便返回下标,找不到便返回一个负数r,而-r-1便是所查找的数应该插入的位置。
java
import java.util.Arrays;
public class C3106 {
public static void main(String[] args) {
int[] a = new int[]{1, 8, 3, 6, 5};
Arrays.sort(a);
System.out.println(Arrays.toString(a));// 多维数组要用deepToString方法
System.out.println(Arrays.binarySearch(a, 3));
System.out.println(Arrays.binarySearch(a, 7));
}
}
4.5
Java对对象采用的是值传递,不是引用传递
方法参数
- 方法不能修改基本数据类型的参数
- 方法可以改变对象的参数状态
- 方法不能让一个参数对象引用另一个新的对象
测试代码:
java
package com.gettler.lessson4;
public class C45 {
public static void main(String[] args) {
System.out.println("测试基本类型参数");
int a = 1, b = 2;
System.out.println("a:" + a + " b:" + b);
System.out.println("调用方法后");
swapValue(a, b);
System.out.println("a:" + a + " b:" + b);
System.out.println("———————————————————————————————————————————————————————————————");
System.out.println("测试对象");
Student s1 = new Student(1, "lyl");
Student s2 = new Student(2, "yj");
System.out.println("s1:" + s1 + " s2:" + s2);
swapObj(s1, s2);
System.out.println("调用方法后");
System.out.println("s1:" + s1 + " s2:" + s2);
System.out.println("———————————————————————————————————————————————————————————————");
System.out.println("测试修改对象参数属性");
System.out.println("s1:" + s1);
moltifyObj(s1);
System.out.println("调用方法后");
System.out.println("s1:" + s1);
System.out.println("———————————————————————————————————————————————————————————————");
}
/**
* 测试修改对象属性
*
* @param s1
*/
private static void moltifyObj(Student s1) {
s1.setName("jyp");
}
/**
* 测试对象参数
*
* @param s1
* @param s2
*/
private static void swapObj(Student s1, Student s2) {
Student t = s1;
s1 = s2;
s2 = t;
}
/**
* 测试基本类型参数
*
* @param a
* @param b
*/
static void swapValue(int a, int b) {
int t = a;
a = b;
b = t;
}
}
class Student {
int id;
String name;
@Override
public String toString() {
return "Student{" + "id=" + id + ", name='" + name + '\'' + '}';
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}运行结果:

继承
Java中只有公共继承,没有私有继承和保护继承
多态
C++中如果希望实现动态绑定,需要将成员函数声明为virtual,而在Java中,动态绑定是默认的行为。
若不希望一个方法是虚拟的,可以将其标记为final
强制类型转换
- 只能在继承层次内进行强制类型转换
- 在将超类强制转换位子类之前,应该使用
instanceof进行检查
尽量少用强制类型转换,如果转换失败会抛出ClassCastException异常,而没有捕获ClassCastException异常的话,会使程序终止
重写equals方法的建议
- 参数命名为otherObject。
- 检测this和otherObject是否相等(只是优化)
java
if(this==otherObject)return true;- 检测otherObject是否为null
java
if(otherObject==null)return false;比较this与otherObject的类
若equals语义在子类中改变,则使用getClass检测。
java
if(getClass()!=otherObject.getClass())return false;- 若在所有子类中equals语义相同,则使用instanceof检测。
java
if(!(otherObject instanceof ClassName))return false;- 将otherObject强制转化为相应类类型的变量
java
ClassName other=(ClassName)otherObject;- 根据相等性概念要求来比较
java
return field1==other.field1&&field2==other.field2&&......;String getName() 可以返回这个类的名字
Class getSuperclass() 可以返回这个类的超类
Class.forName(className) 获取类名对应的Class对象
ArrayList
java
import java.util.ArrayList;
public class C531 {
public static void main(String[] args) {
// 设置数组初始容量
// 创建ArrayList时,若能估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法
// 或者将初始容量直接传入构造器。这样前100次调用不会因为数组扩容带来额外的时空开销。
ArrayList<String> arrayList1 = new ArrayList<>();
arrayList1.ensureCapacity(100);
ArrayList<String> arrayList2 = new ArrayList<>(100);
// 添加元素
arrayList1.add("hi");
arrayList1.add(1, "study?"); // 将元素插入到指定位置,并后移其他元素
System.out.println(arrayList1);
// 访问元素
System.out.println(arrayList1.get(0));
// 修改元素
System.out.println(arrayList1.set(0, "hi what hi")); // 会返回之前的元素内容
arrayList1.set(1, "study!");
System.out.println(arrayList1);
// 删除元素
arrayList1.remove(0);// 将后面的元素前移
}
}枚举
static Enum valueOf (Class enumClass, String name) 返回给定类中有指定名字的枚举常量
String toString() 返回枚举常量名
int ordinal() 返回枚举常量在enum声明中的位置,位置从0开始计数
int compareTo(E other) 枚举常量出现在other之前便返回负数,相等则返回0,否则返回整数。
java
public enum C56 {
SMALL("s"), MEDIUM("M"), LARGE("L");
private String size;
// 构造函数必须为private
private C56(String s) {
this.size = s;
}
public String getSize() {
return size;
}
}反射
反射机制可以用来:
- 在运行时分析类的能力
- 在运行时检查对象(编写一个适用于所有类的toString方法)
- 实现泛型数组操作代码
- 利用Method对象(类似C++中的函数指针)
java
import java.lang.reflect.*;
public class C571 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
String className = "com.gettler.lesson5.Student";
Class cl = Class.forName(className);
Object obj = cl.getConstructor().newInstance();
System.out.println("使用反射机制创建对象:" + obj);
// newInstance(Object... params) 可以传参
System.out.println(className + "类的修饰符:" + Modifier.toString(cl.getModifiers()));
System.out.println(className + "类的公共字段:");
// getDeclaredFields是获取该类声明的所有字段
// getFields是获取该类的所有公共字段(public)
// Methods 和 Construtors 同理
// System.out.println(Arrays.toString(cl.getFields()));
// System.out.println(Arrays.toString(cl.getDeclaredFields()));
Field[] fields = cl.getDeclaredFields();
for (Field f : fields) {
System.out.print(f.getType().getName() + " " + f.getName() + "\n");
}
System.out.println(className + "类的方法:");
Method[] methods = cl.getDeclaredMethods();
for (Method m : methods) {
// 修饰符
String modifiers = Modifier.toString(m.getModifiers());
// 返回类型
Class returnType = m.getReturnType();
// 方法名
String name = m.getName();
// 参数
Class[] params = m.getParameterTypes();
System.out.print(modifiers + " " + returnType + " " + name + "(");
for (int i = 0; i < params.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(params[i].getName());
}
System.out.print(")\n");
}
System.out.println(className + "类的构造器:");
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c : constructors) {
String modifier = Modifier.toString(c.getModifiers());
String name = c.getName();
System.out.print(modifier + " " + name + "( ");
Class[] params = c.getParameterTypes();
for (int i = 0; i < params.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(params[i].getName());
}
System.out.print(")\n");
}
}
}用于测试的Student类
java
package com.gettler.lesson5;
import java.util.Objects;
public class Student {
String name;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return name.equals(student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student() {
name = "jyp";
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + '}';
}
}运行结果

使用反射在运行时分析对象
java
import java.lang.reflect.Field;
public class C575 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Student s = new Student();
Class cl = s.getClass();
Field f = cl.getDeclaredField("name");
// 获取 s 中 f 表示的字段
Object o = f.get(s);
System.out.println(o);
// 将 s 中 f 表示的字段设置为新值
f.set(s, "lyl");
System.out.println(f.get(s));
}
}使用java.lang.reflect.Array类
java
import java.lang.reflect.Array;
import java.util.Arrays;
public class C576 {
public static void main(String[] args) {
String[] arrays = new String[]{"123", "456", "789", "jyp", "lyl", "yj"};
System.out.println(Array.get(arrays, 0));
Array.set(arrays, 0, "Gettler");
System.out.println(Array.get(arrays, 0));
System.out.println(Array.getLength(arrays));
System.out.println(Arrays.toString((Object[]) Array.newInstance(String.class, 5)));
System.out.println(Arrays.deepToString((Object[]) Array.newInstance(String.class, new int[]{5, 6, 7})));
}
}运行结果

clone()方法
使用clone()方法必须要实现Cloneable接口
默认的clone()只能对对象的所有字段进行逐个拷贝,对于这个对象的基本数据类型,可以进行正常拷贝,而对于对象中包含其他对象的引用,拷贝字段只会得到子对象的另一个引用(浅拷贝)。
针对这种情况,我们如果还想要使用clone()方法,便需要令子对象也实现Cloneable接口,并实现其自己的clone方法。
lamdba
自由变量:非参数且不在代码中定义的变量。
类似这样
java
public static void repeatMessage(String text,int delay){
ActionListener listener=event->{
System.out.println(text);
Toolkit.getDefaultToolkit().beep();
};
new Timer(delay,listener).start();
}这里的代码表明,lambda表达式可以捕获外层作用域中的变量的值。而我们要确保所捕获的值是明确定义的,并且只能引用之不会改变的变量。
示例代码
java
import java.util.function.IntConsumer;
public class C627 {
public static void main(String[] args) {
repeat_Runnable(10, () -> {
System.out.println("jyp不学习");
});
repeat_IntConsumer(10, i -> System.out.println("lyl不起床" + i));
}
public static void repeat_Runnable(int n, Runnable action) {
for (int i = 0; i < n; i++) {
action.run();
}
}
public static void repeat_IntConsumer(int n, IntConsumer action) {
for (int i = 0; i < n; i++) {
action.accept(i);
}
}
}内部类
Java 核心技术 II
Java 流
流与集合的区别
- 流不存储其元素
- 流的操作不会修改其数据源(不能在流中对集合进行添加、修改、删除等操作)
- 流的操作是尽可能惰性执行,直到需要其结果时,操作才会执行
流的创建
根据集合创建流
java
Array.stream(array,form,to)根据数组创建流
java
Stream.of(1,2,3,4)创建无限流
generate方法
java
Stream<Double> randoms=Stream.generate(Math::random);0.2960344001169929, 0.6186724740604096, 0.8428301917406658, 0.8697418646866307, ...
java
Stream<String> echos=Stream.generate(()->"Echo");Echo, Echo, Echo, Echo, Echo, Echo, Echo, Echo, Echo, Echo, ...
iterate方法
java
Stream<BigInteger> integers=Stream.iterate(BigInteger.ONE,n->n.add(BigInteger.ONE));1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...
使用 iterate 方法生成有限序列
java
Stream<BigInteger> integers=Stream.iterate(BigInteger.ONE,
n->n.compareTo(BigInteger.valueOf(5))< 0,
n->n.add(BigInteger.ONE));使用 empty 方法创建不含元素的流
java
Stream.empty();流的转换
都是生成新的流,而不是对原来的流进行修改
filter 方法
对元素进行匹配过滤
java
Stream.iterate(1,n->n<=10,n->n+1).filter(i->i%2==0)
.forEach(i->System.out.print(i+", "));
map 方法
对元素执行某方法
java
Stream.iterate(1,n->n<=10,n->n+1).filter(i->i%2==0).map(i->i+10)
.forEach(i->System.out.print(i+", "));
flatMap 方法
将流进行map操作后每个元素产生的结果流连接到一起
java
Stream.iterate(1,n->n<=10,n->n+1).filter(i->i%2==0)
.flatMap(i->Stream.iterate(0,j->j<i, j ->j+1)).forEach(i->System.out.print(i+", "));
distinct方法
去重
sorted方法
排序
peek方法
多用于输出元素,作用于记录日志、调试代码
peek与map、foreach的区别
- peek的参数是Consumer,没有返回值,而map的参数是Function,有返回值
- peek不会中断流,而foreach会中断流,无法进行后续操作
抽取子流和组合流
limit(n)方法
只保留前n个元素
skip(n)方法
保留除了前n个元素的其他元素
dropWhile方法
排除满足条件元素,直到不满足条件为止
java
Stream.iterate(1,n->n<=10,n->n+1).dropWhile(i->i< 5)
.forEach(i->System.out.print(i+", "));takeWhile方法
保留满足条件元素,直到不满足条件为止
java
Stream.iterate(1,n->n<=10,n->n+1).takeWhile(i->i< 5)
.forEach(i->System.out.print(i+", "));流的约简
count方法
返回流中元素数量
max方法
返回流中元素的最大值
min方法
返回流中元素的最小值
findAny方法
返回任意一个元素,若流为空,返回一个Optional对象
findFirst方法
返回第一个元素,若流为空,返回一个Optional对象
anyMatch方法
任意元素满足条件时返回true
allMatch方法
所有元素都满足条件时返回true
noneMatch方法
没有满足条件的元素时返回true
Optional类
orElse(T other)方法
Optional为空时,返回other
orElseGet(Supplier other)方法
Optional为空时,返回other的调用结果
orElseThrow(Supplier exception)方法
Optional为空时,抛出异常exception的调用结果
empty()方法
返回一个空的Optional
java
Optional<String> optionalString=Optional.empty();
try{
String r=optionalString.orElseThrow(IllegalStateException::new);
System.out.println(r);
}catch(Throwable t){
t.printStackTrace();
}
System.out.println(optionalString.orElse("1111"));
System.out.println(optionalString.orElseGet(()->"123"+"456"));
ifPresent( Consumer action)方法
不为空则将其值传递给action
ifPersentOrElse(Consumer action, Runnable emptyAction)方法
若不为空,则传参给action,否则执行emptyAction
isPresent()
若不为空,返回true 不推荐使用
get()方法
取值 不推荐使用
Optional的使用场景
丢弃不存在的用户
java
Stream<User>users=ids.flatMap(id->Stream.ofNullable(User.classicLookup(id)));从流中收集结果
toList方法
转换为列表
toSet方法
转换为集合
toCollection(Supplier collectionFactory)方法
转换为任意一个集合,参数为类似TreeSet::new的构造器
场景实践
判定字符是否唯一
实现一个算法,确定一个字符串 s 的所有字符是否全都不同。
示例 1:
java
输入:s="leetcode"
输出:false示例 2:
java
输入:s="abc"
输出:true限制:
0 <= len(s) <= 100s[i]仅包含小写字母- 如果你不使用额外的数据结构,会很加分。
java
class Solution {
public boolean isUnique(String astr) {
return astr.chars().distinct().count() == astr.length();
}
}