对 《Java核心技术·卷I》 一些知识点的摘录。
数据类型
数字型
-
Java没有任何无符号(Unsigned)类型的int、long、short、byte等类型;
-
Math类中有更安全的int、long运算,包括
addExact(),subtractExact(),multiplyExact(),incrementExact(),decrementExact(),negateExact(),如果溢出会抛出ArithmeticException,原理可以参考zhuanlan.zhihu.com/p/80016248; -
Math.round()方法可以对浮点数进行舍入,返回值是long类型; -
Java对布尔值的运算中,关系运算符&&与||按短路求值计算,而位运算符&与|不采用短路求值;
-
赋值运算符=、二元赋值、三元运算符为右结合,从右向左计算;
-
java.math.BigInteger与BigDecimal可以实现任意精度的整数与浮点数运算,但不支持算术运算符,常用的方法有:static BigInteger valueof(long x); // 转换为BigInteger或BigDecimal int compareTo(BigInteger other); // <返回负数、=返回0、>返回正数 BigInteger add(BigInteger other); BigInteger subtract(BigInteger other); BigInteger multiply(BigInteger other); BigInteger divide(BigInteger other); BigInteger mod(BigInteger other); BigInteger sqrt();
字符型
-
java.lang.String对象是不可变的(immutable),只能让引用这个字符串的变量引用其他的字符串; -
要用
equals()检测字符串的相等性,==运算符会检测两个字符串是否在同一个位置上,相当于Python的is; -
空串是长度为0的字符串,而
str == null表示没有对象与str关联,要分开检查;if(str != null && str.length() != 0) -
常用的字符串String操作:
int compareTo(String strCmp); //按字典序比较,<返回负数、=返回0、>返回正数 boolean empty(); boolean equals(String strCmp); boolean equalsIgnoreCase(String str_cmp); String replace(String strOld, String strNew); //查找所有old并用new代替 String substring(int begIndex, int endIndex); String repeat(int repCount); static String join(String separator, String[] strs); -
关于Java中UTF-16与码点的一些特别之处:
- 注释中的Windows路径C:\users会误以为是\u转义(Unicode转义)而产生错误,因为Unicode转义会在解析代码前进行替换;
- Java中char默认使用UTF-16编码,建议不要在程序中使用char类型;
length()与charAt()计算的是代码单元,codePointCount()与codePointAt()计算的是码点,比如emoji是1个码点,占2个代码单元,用charAt()就不能正确显示;
-
StringBuilder类用于动态构建字符串:
int length(); this append(String str); this insert(int offset, String str); this delete(int begIndex, int endIndex); String toString();
输入输出
输入
-
构造一个
java.util.Scanner对象,并与System.in关联,即可从控制台读取输入;Scanner in = new Scanner(System.in); -
构造一个
java.util.Scanner对象,并与文件名关联,即可从文件中读取输入;Scanner in = new Scanner(Path.of("myFile.txt"), StandardCharsets.UTF_8); -
如果不用
Path.of()则Scanner会将字符串解析为数据而不是文件名; -
Scanner类的常用方法:
String nextLine(); // 回车分隔 String next(); // 空格分隔 int nextInt(); double nextDouble(); boolean hasNext(); boolean hasNextInt(); boolean hasNextDouble();
输出
-
Java 5 沿用了C语言的printf方法,即
System.out.printf(); -
用于printf格式化输出的转换符:
- d:十进制整数
- x:十六进制整数
- f:定点浮点数
- e:指数浮点数
- s:字符串
-
用于printf格式化输出的标志:
- +:打印正负号
- 空格:在数字前补空格
- 0:在数字前补0
- -:左对齐
- #:强制f格式包含小数点,或x格式添加前缀0x
- $:指定参数索引
-
构造一个
java.io.PrintWriter对象,并与文件名关联,即可输出到文件中;PrintWriter out = new PrintWriter("myFile.txt", StandardCharsets.UTF_8);
控制流程
-
Java不能在嵌套的两个块中声明同名的变量;
-
switch语句case的标签可以是:
- char、byte、short、int的常量表达式
- 枚举常量
- 字符串字面量(Java7及以上)
-
Java支持带标签的break语句,标签必须放在希望跳出的最外层循环之前,并且紧跟一个冒号:
label: { if(condition) break label; }
数组
-
数组变量的声明与初始化
int[] a = {1, 2, 3}; // 声明的同时分配空间,不可分开 int[] a = new int[100]; // 声明与分配空间是分开的,用默认值(数字-0、布尔-false、对象-null)初始化 int[] a = new int[]{1, 2, 3}; // 声明与分配空间是分开的,用指定值初始化 -
数组拷贝
int[] newArray = oldArray; // 浅拷贝 int[] newArray = Arrays.copyOf(oldArray, oldArray.length); // 深拷贝 -
java.util.Arrays的常用方法:static String toString(T[] a); // 将数组转为字符串 static String deepToString(T[][] a); // 将二维数组转为字符串 static T[] copyOf(T[] a, int end); static T[] copyOfRange(T[] a, int beg, int end); static void sort(T[] a); static int binarySearch(T[] a, T v); // 找到返回下标,否则返回-(插入位置+1) static int binarySearch(T[] a, int beg, int end, T v); static void fill(T[] a, T v); static boolean equals(T[] a, T[] b); // 下标相同的元素都对应相等返回true
对象与类
-
Java中的对象变量可以看作C++中的对象指针,所有Java对象变量都储存在堆中,构造器总是结合new操作符一起使用;
-
Java总是按值调用,但是由于对象变量相当于指针,所以方法得到的变量副本与原变量指向同一个对象;
-
不要编写返回可变对象引用的访问器方法,会破坏封装性,应该使用clone返回可变对象的副本;
-
Java中的final效果相当于C++中的顶层const;
-
构造器中使用
this(params)调用另一个构造器; -
Java的包机制类似于C++中的命名空间,package相当于namespace,import相当于using;
-
如果类、变量、方法不指定public或private,则可以被包内所有方法访问;
-
java.time.LocalDate常用方法static LocalData now(); // 构造一个表示当前日期的对象 static LocalData of(int year, int month, int day); // 构造一个指定年月日的对象 int getYear()/getMonthValue()/getDayOfMonth(); // 获取年/月/日 int getDayOfWeek().getValue(); // 获取星期几 LocalDate plusDays(int n)/minusDays(int n); // 生成日期之前/之后n天的日期 -
java.util.Objects用于处理null引用的方法(Java 9)// 如果对象为null则抛出带有msg消息的NullPointerException异常 static <T> void requireNonNull(T obj, String msg); // 如果对象为null则返回默认对象,否则返回对象本身 static <T> T requireNonNullElse(T obj, T defaultObj);
继承
继承
-
在Java中,所有的继承都是公共继承,没有私有继承与保护继承;
-
super不是一个对象的引用,它只是指示编辑器调用超类方法的特殊关键字; -
使用
super调用构造器的语句必须是子类构造器的第一句(必须先构造超类); -
在Java中,动态绑定是默认的行为,如果不希望一个方法是
virtual的,可以声明为final; -
虚拟机预先为每个类计算了一个方法表,列出了所有方法的签名和要实际调用的方法;
-
abstract关键字用于声明抽象类或抽象方法; -
抽象类不能实例化,但可以定义抽象类的对象变量来引用自类的实例;
Object类
-
可以使用
Object类引用所有类型的对象; -
基本类型的数组都扩展了
Object类; -
x.toString()可以用""+x代替,这样x也可以是基本类型或null; -
变参
method(Object… params)相当于method(Object[] params),调用相当于method(new Object[]{arg1, arg2, …}); -
java.lang.Object提供的通用方法:boolean equals(Object otherObject); String toString(); -
java.util.Objects的常用方法static boolean equals(Object a, Object b); // 允许null的相等性判断
泛型数组列表
-
java.util.ArrayList<E>的常用方法boolean add(E obj); // 增 void add(int index, E obj) //插 E set(int index, E obj); // 改 E get(int index); // 查 E remove(int index); // 删 int size(); void ensureCapacity(int capacity); // 指定容量 void trimToSize(); // 削减空位置 -
java.util.Arrays的常用方法static boolean equals(xxx[] a, xxx[] b); //数组长度相同且对应元素相同才会true
装箱与拆箱
-
包装器都是
java.lang.Number类的子类 -
自动装箱:当基本类型赋值给一个包装器对象的时候,会自动调用包装器的静态方法
valueOf(基本类型) -
自动拆箱:当一个包装器对象赋值给一个基本类型的时候,会自动调用
包装器对象.基本类型Value() -
注意==用于包装器对象的时候判断的是它们的地址而非值,应该用
equals()方法 -
java.lang.包装器的常用方法,以Integer为例int intValue(); // 自动或手动拆箱所用的方法 static String toString(int i); static int parseInt(String s); // 返回字符串s表示的整数 static Integer valueOf(String s); // 返回用字符串s表示的整数初始化的Integer对象
枚举类
-
枚举类是
java.lang.Enum类的子类,常用的公共方法有static Enum valueOf(Class enumClass, String name); // 返回给定类中名为name的枚举常量 String toString(); int ordinal(); // 返回枚举常量的声明位置,从0开始计数 int compareTo(Enum otherEnum); -
public enum Gender{ MALE,FEMALE }的意思是声明一个枚举类Gender,它有且只有MALE和FEMALE两个实例 -
所以枚举类中可以与普通类一样声明属性、构造函数与方法;
接口、Lambda表达式与内部类
接口
-
接口不是类,而是对希望符合这个接口的类的一组需求;
-
抽象类偏向于抽象类别或者说事物本质,只能扩展一个抽象类;
-
而接口偏向于抽象功能或者说事物操作,可以扩展多个接口;
-
接口可以提供多继承的大多数好处,同时避免了多继承的复杂与低效;
-
接口中的方法总是
public,也必须是public,所以不必再提供访问修饰符了; -
接口中的字段总是
public static final,同样无需提供多余的关键字了; -
可以为接口方法提供一个默认实现,如果实现这个接口的类无需实现这个方法的时候,或者需要兼容老版本接口的时候;
-
instanceof可以检查一个对象是否实现了某个接口; -
在标准库中,有成对出现的接口与实用工具类,比如
Collection/Collections或Path/Paths; -
在Java8中允许在接口中提供静态方法,这样一来实现接口的时候伴随的实用工具类就不是必须的了;
-
关于比较接口
Comparablepublic interface Comparable<T>{ int compareTo(T other); } // java.util.Arrays的sort(Object[] a)方法要求数组元素必须为实现了Comparable接口的类; // 如果compareTo将A-B作为整数排序依据的话,运算可能会溢出,可以调用静态方法Interger.compare(A, B);
Lambda表达式
-
lambda表达式是一个可传递的代码块;
-
lambda表达式的书写技巧:
- 即使lambda表达式没有参数,仍然要提供空括号:
() -> {for(int i; i < 10; ++i) System.out.println(i);}- 如果可以推导出lambda表达式的参数类型,那么可以忽略其类型:
Comparator<String> comp = (first, second) -> first.length() - second.length();- 如果lambda表达式只有一个参数,并且类型可以推导出,那么可以忽略小括号:
ActionListener listener = event -> System.out.println(Instant.ofEpochMilli(event.getWhen)); -
在Java中对lambda表达式所做的只有将其转换为函数式接口(只有一个抽象方法的接口,比如
Comparator<T>); -
lambda表达式中只能引用值不会改变的变量(事实最终变量,初始化后不会再赋值);
-
java.util.Function中定义了很多非常通用的函数式接口:Predicate接口,ArrayList的removeIf()的参数就是Predicate;
public interface Predicate<T>{ boolean test(T t); }Supplier接口,只有调用时才会返回一个T类型的值,用于实现懒计算;
public interface Supplier<T>{ T get(); }
方法引用
-
方法引用指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法;
-
只有当lambda表达式的代码块只有方法调用时才能重写为方法引用;
-
方法引用有三种传递方式:
-
对象::实例方法
object::instanceMethod例如
System.out::println等价于x -> System.out.println(x) -
类::实例方法
Class::instanceMethod例如
String::compareToIgnoreCase等价于(x,y) -> x.compareToIgnoreCase(y) -
类::静态方法
Class::staticMethod例如
Math::pow等价于(x,y) -> Math.pow(x,y)
-
内部类
-
内部类可以对同一个包中的其他类隐藏;
-
内部类方法可以访问外部类作用域中的数据,包括原本私有的数据;
-
内部类的对象会有一个隐式引用
OuterClass.this,指向实例化这个对象的外部类对象; -
局部内部类是不带访问说明符的内部类,作用域被限定在声明局部内部类的块中;
-
局部内部类对外界完全隐藏,但是它可以访问外部类的字段与局部变量(事实最终变量);
-
匿名内部类是不指定类名的局部内部类,只需创建对象时可以使用:
// 继承自SuperType类的匿名内部类 var anonymousInnerClass = new SuperType(construction parameters){ method and data } // 实现了InterfaceType接口的匿名内部类 var anonymousInnerClass = new InterfaceType(){ method and data } -
利用匿名内部类支持初始化块的特性,可以使用“双括号初始化”:
// 外层括号建立一个ArrayList的匿名子类,内括号完成了对象的初始化 invite(new ArrayList<String>(){{add("Henry"); add("Tony");}}); // 不过更常见的是利用List.of()方法 invite(List.of("Henry", "Tony")); -
生成日志或调试信息时往往会用到当前类的名称,利用匿名子类可以获得静态方法的类名:
// new Object(){}建立一个匿名内部类对象,而getEnclosingClass()方法可以得到其外部类的信息 new Object(){}.getClass().getEnclosingClass();