Java 核心技术卷摘录
final关键词
final常量,只能被赋值一次,赋值后不能被更改,习惯使用大写命名。在对象构建时必须初始化final常量。
final类,不允许扩展的类,也就是阻止其他人在这个类上定义子类。
final方法,不允许覆盖的方法。
NaN表示不是一个数字,0/0或者负数的平方根为NaN。
位运算:&(与)、|(或)、^(异或)、!(非)。比如二进制从右数第四位为1,int fourthBittFromRight = ( n & ( 1 << 3 )) >> 3;
Math:
1.常量 Math.PI、Math.E。
2.三角函数 Math.sin、Math.cos、Math.tan。
3.运算 Math.pow、Math.sqrt、Math.round(四舍五入返回long)、Math.abs。
4.随机数 Math.random(),返回一个0.0-1.0之间的double。
String:
- int compareTo(String other)
按照字典顺序比较 - boolean endsWith(String suffix), boolean startsWith(String prefix)
判断是否以某字符串开头或结尾 - boolean equals(String other), boolean equalsIgnoreCase(String other)
是否相等,后者不区分大小写 - int length()
获取字符串长度 - String replace(CharSequence oldString, CharSequence newString)
用新字符串替换原始字符串中的所有旧字符串,可用String或者StringBuilder作为参数 - String substring(int beginIndex, int endIndex)
返回新的子字符串 - String toLowerCase(), String toUpperCase()
都改成小写或者大写 - String trim()
返回一个新的删除前后空格的字符串
StringBuilder
由于每次使用字符串连接(+)都会创建一个新的字符串,效率很低,所以引入了一个StringBuilder对象,使用方法如下:
StringBuilder builder = new StringBuilder();
builder.append(ch); //在末尾插入字符
builder.append(str); //在末尾插入字符串
int le = builder.length(); //求长度
String res = builder.toString(); //转化为字符串
格式化输出
沿用c语言的格式化风格
System.out.printf("%8.2f", x); //小数点前8位后2位
System.out.printf("Hello, %s. Next year you will be %d.", name, age); //多参数打印
String message = String.format("Hello, %s. Next year you will be %d.", name, age); //格式化创建字符串,不打印
大数值
java.math包中有两个处理任意长度数字序列的对象。
BigInteger:表示大整数
BigDecimal:表示大实数
这两个对象不能用+、-、*、/运算,其使用方法如下:
//分别表示加,减,乘,除,余数
BigInteger add(BigInteger other);
BigInteger subtract(BigInteger other);
BigInteger multiply(BigInteger other);
BigInteger divide(BigInteger other);
BigInteger mod(BigInteger other); //BigDecimal没有mod方法
//比较
int compareTo(BigInteger other);
//赋值
static BigInteger valueOf(long x);
static BigDecimal valueOf(long x, int scale); // x/10的scale次方
数组
int[] smallPrimes = {2,3,4,5,6,7}; //数组初始化
//初始化一个匿名数组
smallPrimes2 = new int[] {2,3,4,5,6,8};
//允许数组长度为0,来表示结果为空
int res = new int[0];
For-each(增强for)
for(variable : collection) statement
其中collection集合表达式必须是一个数组或者实现了Iterable接口的类对象比如ArrayList。
命令行参数
每一个java应用程序都接受一个字符串数组,可在命令行中输入参数。
Arrays
Arrays.toString(a); //返回包含a中数据元素的字符串,被括号包裹,用逗号隔开
//返回与a类型相同的数组
static type copyOf(type[] a, int start, int end);
//为数组a进行优化的快速排序
static void sort(type[] a);
//二分搜索查找值,返回下标
static int binarySearch(type[] a, int start, int end, type v);
//将所有数组元素设置为v
static void fill(type[] a, type v);
//比较两个数组是否相等
static boolean equals(type[] a, type[] b);
隐式参数与显式参数
对于下列Employee对象的raiseSalary方法:
class Employee {
private salary;
...
public void raiseSalary(double byPercent) {
double raise = this.salary * byPercent / 100;
this.salary += raise
}
...
}
Employee number007 = new Employee(...);
number007.raiseSalay(5);
在调用过程中,number007为隐式参数,5为显式参数。在raiseSalary方法里可以直接调用关键字this作为隐式参数来获得当前对象的属性,this也可以省略。
静态域与静态方法
每一个Employee对象都有一个id域(属性),但是所有实例共享一个静态域nextId。即使没有实例,静态域也存在,它属于类而不属于任何实例。
静态常量(private static final double PI;)也是一样,所有对象共享,而不用每个对象都拷贝一份。
静态方法
比如Math.pow(a,b),是一种不能向对象实施的方法,也就是不使用任何对象(没有隐式参数),就能运行。这表示这个方法不能访问对象的状态和非静态域(属性)。
所以一般在函数不需要访问对象的状态,或者只访问静态域的情况下才使用静态方法。
工厂方法是静态方法常用的一种场景;
main方法也是一种静态方法,多用于单元测试,在命令行输入java Employee,会执行Employee的main方法。
方法参数
java方法得到的是所有参数值的拷贝!
方法参数总共分为2类:1.基本数据类型;2.对象引用;
方法不能修改基本数据类型的参数。 对象引用的拷贝与原引用共同指向一个对象,所以方法可以通过对象引用来改变该对象的内部状态(属性)。 特殊的当方法对两个对象引用的拷贝进行交换时,并不会影响原引用,即不能让对象参数引用其他的对象。
通过静态方法和静态域来实现非常数类初始化
关键字this
除了作为隐式参数调用对象的实例域以外,还可与用来在构造器中调用另一个构造器:
super关键词
问题:子类不能直接访问超类(父类)的私有域,需要通过公用的接口,如何在子类中调用同名的超类接口呢?
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
在上述例子中,如果不使用super关键词,就会无限迭代调用本函数进入死循环。super关键词是指示编译器调用超类方法的特有关键词。
abstract关键词
抽象类中的抽象方法不需要实现,充当占位的作用,抽象类不能被实例化。但是可以定义一个抽象类的对象,来引用非抽象子类对象,如:
Map<Integer,Integer> map = new HashMap<>(); //map可以使用HashMap中定义的函数
在接口(interface)中会用到更多的抽象方法。
== 和 equals !!!(重点)
= =在处理基本数据类型时是比较值是否相等,而处理对象引用数据类型时比较是否指向同一个对象。
equals不能作用于基本的数据类型,它是Object类里面的一个函数,源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
用来判断两个引用是否为同一对象。
一般我们在定义使用equals时都会重载equals来帮助我们判断两个引用的值是否相同。
比如String对象已经重载了equals函数,其内部重载源码和使用过程如下:
//String类重载equals
public boolean equals(Object anObject) {
//如果两个引用为同一对象直接返回true
if (this == anObject) {
return true;
}
if (anObject instanceof String) { //这里用instanceof来判断比较的对象是否为String
String anotherString = (String)anObject; //强转为String
int n = value.length; //this.value.length
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//测试
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,非同一对象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
HashCode
比如字符串的equals被重载为判断字符串内容是否相同,所以两个具有相同内容的字符串其hashCode是相同的。
equals,hashCode,toString都是Object里面定义的函数。
ArrayList
是一个采用类型参数的泛型类
//初始化
ArrayList<Employee> staff = new ArrayList<>();
//各类方法
int size(); //返回当前元素数量
void trimToSize(); //将存储容器大小减小到当前尺寸
void toArray(T[] a); //将ArrayList中的元素拷贝到数组a中,方便使用下标[]存取;
void set(int index, T obj); //设置下标为index的值
T get(int index); //获得指定位置的元素
void add(int index, T obj); //在指定位置插入元素
void remove(int index); //删除指定位置元素,后面的元素都往前移动(在太多删除操作情况下效率会很低)
Class类
所有对象都有一个类型标识,保存这些信息的类被称为Class
Employee e;
Class cl = e.getClass();
String s = cl.getName(); //s=="Employee"
//调用静态方法forName获得类名对应的Class类
String classname = "java.util.Date";
Class cl = Class.forName(classname);
//newInstance可以快速创建一个相同类的实例,调用默认的构造方法,若没有默认则抛出异常
Emoloyee e2 = e.getClass().newInstance();
//结合forName和newInstance根据字符串创建类实例
String classname = "java.util.Date";
Date date = Class.forName(classname).newInstance();
反射 (!!!重点)
核心技术卷1 P201~218
有点难看不懂,之后结合博客再仔细看一看(听说是面试提问的重点)。
接口
- 接口不是类,是类的一组需求描述。
- 定义接口用interface;实现接口用implements;
- 比如实现了Cloneable接口,可以使用Object类的clone方法进行深拷贝。Arrays的sort方法可以对数组中的对象进行排序,前提是对象实现了Comparable接口。下面接口的定义以及让Employee对象实现Comparable接口,来完成排序的例子:
//接口定义
public interface Comparable<T> {
//不必申明是pubilc,默认就是public
int compareTo(T other);
}
//让对象实现接口
class Employee implements Comparable<Employee> {
public int compareTo(Employee other) {
if(this.salary < other.salary) return -1;
if(this.salary > other.salary) return 1;
return 0;
}
...
}
//创建employee数组
Employee[] staff = new Employee[3];
staff[0] = ...;
...
//排序
Arrays.sort(staff);
- 检查一个类是否实现类某接口可以用关键词instance(检查对象是否属于一个类用instanceof)
if(anObject instance Comparable) {...}
- 接口与抽象类的区别(为什么不直接使用抽象类代替接口?):因为java不支持多继承,每个类只能扩展一个类,而对于接口来说每个类可以实现多个接口,这样就可以对不同的需求进行区分。
对象克隆
- clone方法是Object类的一个proteced方法,不能直接调用。
- Object.clone()方法默认进行浅拷贝。对于如String这类不可变的实例域来说,使用浅拷贝是可行的,但是对于可变实例域需要重新定义clone方法。
- 实现Cloneable接口来重新定义clone方法需要注意三点:1.需要重定义clone方法为public;2.需要声明异常throws CloneNotSupportedException;3.首先调用父类的克隆方法,然后再克隆所有可变实例域。
class Employee implements Cloneable {
public Employee clone() throws CloneNotSupportedException {
Employee cloned = (Employee) super.clone(); //父类克隆
cloned.hireDay = (Date) this.clone(hireDay); //可变实例域克隆
return cloned;
}
}
一种简单有效的深拷贝方式
new ArrayList(List<> list);
回调、内部类、代理 P230~P254
内部类和代理有点难
之后结合博客看
七、八、九、十章跳过
异常
- 异常分类
其中Error描述系统内部错误和资源耗尽错误;RuntimeRxception包括:数组越界、错误类型转换、访问空指针;其他异常包括:在文件尾部读取数据、打开错误格式URL、根据字符串查找Class对象不存在; - 方法声明抛出异常
一个方法必须声明所有可能抛出的已检查异常,后续系统就会搜索异常处理器。方法一旦抛出异常,就不需要为返回值和代码而担忧。
String readData(Scanner in) throws EOFException {
...
while(){
if(!in.hasNext()){
if(n<len){
throw new EOFException;
}
...
}
}
}
- 捕获异常
用 try/catch/finally 语句块
集合接口Collection
- 集合的接口与实现是分离的。比如队列接口(Queue)可以用循环数组或者链表来实现。
Queue<Customer> expressLane = new CircularArrayQueue<Customer>();
Queue<Customer> expressLane = new LinkedListQueue<Customer>();
注意!!!在构建集合对象时(new)必须使用具体的类才有意义;声明变量时使用接口的优点在于可以轻松的修改构造器来更换另外一种实现。
- 除Map结尾的接口外,所有集合类的基本接口是Collection接口,实现了以下几种有用的方法:
Iterator<E> iterator(); //返回迭代器
int size(); //返回集合元素个数
boolean isEmpty(); //判断是否为空
boolean contains(Object obj); //是否包含元素obj
boolean containsAll(Collection<?> other); //是否包含other里所有元素
boolean add(Object obj); //添加元素,成功返回true
boolean addAll(Collection<?> other); //添加集合里的所有元素
boolean remove(Object obj); //删除元素
boolean removeAll(Collection<?> other); //删除集合里的所有元素
void clean();
boolean retainAll(Collection<?> other); //删除不同于other里的所有元素
Object[] toArray(); //返回数组对象
迭代器接口的方法,其中remove和next需要配套使用,迭代器更像是在两个元素之间,为了防止越界,在每次调用next方法之前需要通过hasNext来判断是否存在:
boolean hasNext(); //下一个元素是否存在
E next(); //返回访问的下一个元素
void remove(); //删除上次访问的对象
链表List、LinkedList
散列集HashSet
Set<String> word = new HashSet<String>();
如果不在意元素的次序,想要达到快速查找的目的,可以使用散列集数据结构。
散列表为每个对象计算一个散列码,由String类的hashCode方法生成。对于自定义类则需要实现这个类的hashCode方法,这个方法必须与equals方法兼容,即对每个值相同的对象(a.equals(b)==true),则散列码也相同(a.hashCode()==b.hashCode()).
Java 的散列表用链表数组来实现,每个链表称为一个“桶”,桶数量一般在元素个数的0.75~1.5倍,并且为素数。(散列冲突、再散列、装填因子在数据结构中有更详细的讲解)
树集TreeSet
SortSet<String> sorter = new TreeSet<String>();
是一个有序集合,遍历时将以插入顺序呈现。
内部使用 树 (红黑树)结构来完成排序。
插入速度比散列表慢,但是也还是比链表或数组快很多。
注意对象必须要实现Comparable接口,来实现对象的比较。也可以使用Comparator<>接口。
双端队列Deque
Deque接口由ArrayDeque和LinkedList类实现
其中新增的offer()、poll()、peek()方法与原来的add()、remove()、element()方法的区别在于前者在操作失败时返回null,而后者直接报错。
优先级队列PriorityQueue
内部使用一个可以自我调整的二叉树——堆来实现,与TreeSet同样需要实现比较接口。
在remove操作时总是会移除最小元素。
映射表Map
Map接口有两个实现类HashMap<>()和TreeMap<>()。
散列作用于键,每个键有一个值对应。键必须是唯一的,put相同的键,第二次的value会覆盖第一次。
可以使用以下方法获取映射表Map的三个视图,注意对视图进行删除,则原映射表也会删除,且不允许添加:
集合抽象类
AbstractCollection、AbstractList、AbstractSet、AbstractQueue、AbstractMap等
集合接口提供了大量的方法,而集合抽象类则为这些方法进行了实例实现。 真正用到的实例类都是继承于这些抽象类。
Collection.sort()方法实现排序
List<String> staff = new LinkedList<>();
Collection.sort(staff); //默认为升序
Collection.sort(staff,Collection.reverseOrder()); //降序排序
sort内部使用归并排序,其速度比快排慢,但是归并排序有稳定的优点。
Collection.binarySearch()方法实现二分查找
前提是集合必须是有序的,才能进行二分查找
其他简单算法
创建线程
- 将任务代码写到一个实现了Runnable接口的类中,覆盖其run()方法;
Class MyRunnable implements Runnable {
public void run() { //必须覆盖
...
}
}
- 创建一个Runnable对象,并由Runnable对象创建一个Thread对象,随后启动线程
Runnable r = new MuRunnable();
Thread t = new Thread(r); //构造线程用于后续调用r的run()方法
t.start(); //启动线程,引发run()方法
interrupt()、interrupted()、isInterrupted()的区别
线程状态
- New(新生):创建后未启动
- Runnable(可运行):调用了start方法,可能在运行或者等待分配时间
- Blocked(被阻塞):在获取内部锁而被其他线程持有时,进入阻塞状态;允许持有锁后才恢复为非阻塞状态
- Waiting(无限期等待):在等待另一个线程通知某个条件(显示唤醒)
- Time waiting(计时等待):在无限期等待的基础上增加了一个超时参数,在超时期满之后就可以重新激活
- Terminated(被终止):run方法跑完后自然死亡;或者因为一个没有捕捉的异常而意外死亡。
后面关于多线程的知识过了一遍,具体可以结合《深入理解JVM》的最后一章来看,更深层的补充在《Java并发编程之美》和《Java高并发程序设计》这两本书中。