跨平台原理
Java的跨平台基于编译器和JVM。编译器把源文件编译成与平台无关的字节码class文件,JVM把该文件解释成与平台有关的机器码指令,在平台上执行。
Java面向对象的4个特征
1 抽象
提取对象的共性,构成抽象类或接口,由继承抽象类的类或接口的实现类来重写抽象方法。
2 继承
子类继承父类(超类、基类),子类具有父类没有的属性或方法,super是离自己最近的父类对象的引用。
3 封装
绑定属性和方法,隐藏类的具体实现,提高对象的安全性。
4 多态
调用对象的方法会有不同的行为。分为编译时多态和运行时多态,编译时多态通过方法重载来实现,运行时多态通过方法重写来实现。
序列化和反序列化
序列化:把对象转成字节(类实现Serializable接口,添加serialVersionUID字段,确保反序列化时版本兼容)。
反序列化:把字节转成对象。
transient修饰字段后,默认序列化忽略该字段。
自定义序列化时需要自己写writeObject和readObject方法。
ArrayList序列化源码分析
把elementData中实际存储的数据序列化,减少存储空间。
集合框架
第一类Collection
List
ArrayList通过数组实现,随机访问比较快,增删操作比较慢。
LinkedList通过链表实现,增删操作比较快,随机访问比较慢。
Queue
LinkedList实现了Deque接口,具有双端队列的功能。
PriorityQueue内部数据结构是堆,每次出队列的元素总是当前队列中最大值(大顶堆)或是最小值(小顶堆)。
Set
Set与List的主要区别是Set不允许元素重复,List允许元素重复。
第二类Map
Map中最常用的是HashMap,考虑线程安全,一般使用ConcurrentHashMap。
static关键字
作用
1. 分配单一的存储空间。
2. 实现属性或方法与类关联。
4种使用情况
1. 静态属性
static不能修饰局部变量。
因为静态方法不依赖对象,所以静态方法里面不能使用this。
因为非静态方法和非静态属性依赖对象,所以静态方法里面不能使用非静态方法或者非静态属性。非静态方法里面可以使用静态方法或者静态属性。
因为静态方法属于类,所以静态方法不能被重写。
null对象可以调用静态方法。
public class NullInvoker {
public static void testNullInvoker() {
System.out.println("abc");
}
public static void main(String[] args) {
NullInvoker in = null;
in.testNullInvoker();
}
}
结果是abc。
2. 静态方法
静态成员变量被所有的对象共享,内存中只存一份,在加载类时初始化。
非静态成员变量是单个对象独有,在创建对象时初始化。
3. 静态代码块
仅在加载类时执行静态代码块,任何方法内部都不能使用静态代码块。
public class Test {
Person person = new Person("Test");
static{
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
static{
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static{
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
结果是
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
分析
加载类:按照Test类、MyClass类、Person类顺序来执行static代码。
执行main方法:按照加载类的顺序,先初始化成员变量,再调用构造器。
4. 静态成员类
final关键字
1. 变量名前加final
该变量的值初始化后不能再改变。
2. 方法名前加final
该方法不能被重写。final、static和private修饰的方法在编译期绑定,不存在多态,不能被重写。
3. 类名前加final
该类不能被继承。
throw和throws的区别
throw用于方法内部,后面只能跟一个异常对象。
throws用于方法声明上,后面可以声明多种异常类型,抛出的异常由调用者处理。
重载和重写
@Override方法重载
方法名相同,参数列表不同(不同的参数类型或者参数顺序或者参数个数),属于编译时多态。
重载的目的是方便程序员调用方法。例如,System.out.println用于输出,当输出整数时是用这个方法,当输出字符串时还是用这个方法。
@Override重写
子类重写父类方法从而发挥不同作用,属于运行时多态。
重写要求:
子类方法的返回值、方法名和参数列表与覆盖的父类方法一致。
子类方法抛出的异常存在限制。
覆盖的父类方法修饰符不能为private或final,否则子类只是定义了一个没有覆盖的方法。
重载与重写的区别
重载要求参数列表不同,重写要求参数列表相同。
重载是多个方法之间的关系,重写是一对方法之间的关系。
重载根据参数列表来确定方法签名,重写根据对象类型来确定方法签名。
重载属于编译时多态,在编译时确定方法签名;重写属于运行时多态,在运行时确定方法签名。
多线程方法区别
线程的5种状态
新建状态(New):当线程对象被创建(Thread t = new MyThread();)后,线程进入新建状态。
就绪状态(Runnable):当调用线程对象的start方法(t.start();)后,线程进入就绪状态,等待CPU调度执行。
运行状态(Running):当CPU调度处于就绪状态的线程时,线程进入运行状态。其中,就绪状态是进入运行状态的唯一入口。
阻塞状态(Blocked):处于运行状态的线程由于某种原因停止执行,进入阻塞状态。根据阻塞原因不同,阻塞状态可以分为三种:
1 等待阻塞:处于运行状态的线程调用wait方法,进入等待阻塞。
2 同步阻塞:线程在获取同步锁失败后进入同步阻塞状态。
3 其他阻塞:调用线程的sleep或join或执行I/O请求时,线程进入阻塞状态。
死亡状态(Dead):线程执行完任务或者因异常退出run方法,进入死亡状态,意味着结束生命周期。
sleep方法与wait方法的区别
1 原理不同
sleep方法是Thread类的静态方法,使线程暂停执行一段时间,等到计时结束,该线程会自动苏醒。
wait方法是Object类的非静态方法,用于线程间的通信,使线程等待,直到其他线程调用notify方法(或notifyAll方法)时才被唤醒。也可以通过计时即设置超时时间来自动唤醒该线程。
2 对锁的处理机制不同
sleep方法不涉及线程间通信,不会释放锁。
wait方法会使线程释放占有的锁。
3 使用区域不同
sleep方法可以放在任何地方,wait方法只能放在同步块中。
4 是否需要捕获异常不同
sleep方法必须捕获异常。在线程睡眠过程中,其他对象可能调用它的interrupt方法,抛出InterruptedException。
wait、notify和notifyAll这些方法不需要捕获异常。
综上所述,因为sleep方法不会释放锁,容易产生死锁问题,所以推荐使用wait方法。
sleep方法与yield方法的区别
1 是否考虑线程的优先级不同
sleep方法给其他线程运行机会时不考虑线程的优先级,也就是说,它会给低优先级的线程运行的机会。
yield方法只会给相同优先级或更高优先级的线程运行的机会。
2 重新执行时机不同
sleep方法会使当前线程阻塞,在睡眠时间内不会被执行。
yield方法只是使当前线程重新回到可执行状态,当前线程有可能在进入可执行状态后立刻又被执行。
3 是否抛出异常不同
sleep方法声明抛出InterruptedException,yield方法声明没有抛出任何异常。
4 可移植性不同
sleep方法比yield方法(与操作系统有关)具有更好的可移植性。
Java 8 Stream
生成流
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
forEach
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
映射每个元素到对应的结果
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
filter
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
limit
// 获取指定数量
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Collectors
// 返回列表或字符串
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);