【java】基础知识要点
1.1 原始数据类型
1.1.1 数值比较
- 整型包装类比较
对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,所以:
-128 至 127区间内的 Integer 值用==可以判断相等;
但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,此时其地址不同,用==判断会不相等,正常建议都用equals判断。
2)浮点包装类比较
与0.0f或0.0d作算术比较(>或<判断)是允许的,但不能直接判断相等==。做大小比较时考虑使用Float或Double的compare (f1, f2)方法,或BigDecimal。
整数型包装类型应该使用equals方法做比较。浮点型包装类型不能用equals或者flt.compareTo(another) == 0进行相等的比较(可以进行compareTo(another)的大小比较)。
计算绝对值是否小于1e-6:(Math.abs(a -10.0) < 1e-6)
反例:for(double x = 0; x !=10; x += 0.1),导致死循环
浮点型数据默认为double类型。添加后缀f标识float
1.1.2 基本类型和不可变类型
【8个基本类型】
字符类型char
布尔类型boolean
数值类型byte、short、int、long、float、double
【不可变类】
创建该类的实例后,该实例的实例变量是不能改变的。Java提供的8个包装类、String类都是不可变类。如Interger、Long和String等。
不可变类设计原则:
1.class要用final修饰,保证类不能被继承;
2.成员变量需要用private String来修饰,保证成员变量均为私有变量;
3.类中不允许提供setter方法,保证成员变量不会被改变;
4.成员变量的getter方法中返回该对象的复制对象,保证原对象的引用不被改变;
注意:String对象不可变,其replace方法都是会新开一块内存,而不是原地替换。
1.1.3 BigInteger和BigDecimal
https://www.liaoxuefeng.com/wiki/1252599548343744/1279768011997217
这两个同样也是不可变类。注意要把结果值返回,它不会修改原值:
BigDecimal
用scale()
表示小数位数,用于精确计算!
另外对BigDecimal
进行比较必须使用compareTo()
方法来比较,它根据两个值的大小分别返回负数、正数和0
,分别表示小于、大于和等于。使用equals()
方法不但要求两个BigDecimal
的值相等,还要求它们的scale()
相等:
1.1.4 java中的大小端
存储量大于1字节,非char类型,如int,float等,要考虑字节的顺序问题了。java由于虚拟机的关系,屏蔽了大小端问题,需要知道的话可用 ByteOrder.nativeOrder() 查询。在操作ByteBuffer中,也可以使用 ByteBuffer.order() 进行设置
1.1.5 32位和64位
仅long型数据字节有差别。32位下为4字节,64位下为8字节。
1.1.6 字符串处理
StringBuffer是线程安全的,效率低
StringBuilder是线程不安全的,效率高
1.1.7 可变参数
- 1、只能出现在参数列表的最后;
- 2、... 位于变量类型和变量名之间,前后有无空格都可以;
- 3、调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
- 4、如果一个是两个方法是重载,一个是可变参数,一个是固定参数,然后我们调用方法如果固定长度的能满足,那么有优先调用固定长度的,
1.1.8 Java安全类库/安全管理器
1.1.8.1 SecurityManager
https://cloud.tencent.com/developer/article/1793295
在启用安全管理器的时候,配置遵循以下基本原则:
- 没有配置的权限表示没有。
- 只能配置有什么权限,不能配置禁止做什么。
- 同一种权限可多次配置,取并集。
- 统一资源的多种权限可用逗号分割。
1.1.8.2 启动安全管理器方式
1、启动参数方式
启动程序的时候通过附加参数启动安全管理器:
-Djava.security.manager
2、若要同时指定配置文件的位置那么示例如下:
-Djava.security.manager -Djava.security.policy="E:/java.policy"
3、编码方式启动
System.setSecurityManager(new SecurityManager());
没有这种方式System.setProperty(“java.security.manager”, xxx);
1.2 流程控制语句
1.2.1 break/continue label用法
break label用于跳出label后的语句块,用于跳出多重循环,(一般后面跟的就是一个循环语句)。
continue label用于继续执行外层循环。
注意:break label一定要保证在label语句块内,否则报语法错误。
label1:
while (true) {
for (int i = 0; i <= 10; i++) {
System.out.println("i=" + i);
if (i == 5) {
break label1;
}
}
}
1.2.2 switch支持的变量类型
char, byte, short, int
Character, Byte, Short, Integer
enum
String
没有boolean,long
1.3 类、接口、对象
1.3.1 Java的单继承和多继承
Java可以支持多层继承,即A->B->C->Object。
java类是单继承的。classB Extends classA
java接口可以多继承。Interface3 Extends Interface0, Interface1, interface……
不允许类多重继承的主要原因是,如果A同时继承B和C,而B和C同时有一个D方法,A如何决定该继承那一个呢?
但接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口。
此时如果B和C接口中同样的方法但返回值不同,会报编译错误:
有同样方法时,子类:先用自己方法实现——》继承父类方法实现——》继承接口方法实现。
1.3.2 类和成员可见性/作用域
【类可见性】
public 所有可见
private 仅对本类可见。
protected 本包和子类(包括外部包的子类)可见。
default(没有修饰符) 本包可见,外部包的子类都不行
子类可以覆盖父类的protected方法,并把它的可见性改为public,但是子类不能降低父类中定义的方法的可访问性。【子类可以扩大访问权限】
对于属性来说,不具备继承性。子类和父类的属性的是完全无关的,如果直接通过实例对象访问public属性,其值取决于声明时的变量类型
【类的成员可见性】
public:公共成员,其它类可见(包内,包外都可见),包内包外子类都可见;内部嵌套类可见;
protected:保护级,包内其它类可见,包内包外子类都可见;内部嵌套类可见;
无关键字:友好的,包内其它类可见,包外子类不可见;内部嵌套类可见;无继承性。
private:私有的,其它类不可访问,只对该类的内部类可见
1.3.3 函数重载overload与覆盖override
- 函数重载发生在同一个类的内部。这组函数具有相同的函数签名,但是参数列表不相同
- 函数覆盖发生在子类与父类之间。父类中定义了一个虚函数,在子类中重新实现了这个函数
【注意】名字隐藏:指的是父类中有一组重载函数,子类在继承父类的时候如果覆盖了这组重载函数中的任意一个,则其余没有被覆盖的同名函数在子类是不可见的。
【注意】函数签名:函数的名称及其参数类型组合在一起,就定义了一个唯一的特性,称为函数签名。(不包括返回类型和访问修饰符)。重载具有相同的函数签名。
1.3.4 变量与函数的运行时绑定规律
Java的引用变量有两种类型:编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。
引用变量的方法:在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。对象的属性则不具备多态性(此时如果用public直接访问属性,那属性的值取决于变量定义的类)。
前期绑定:在程序执行之前进行绑定(如果有的话,由编译器和连接器完成)。面向过程C语言的方法调用。
动态绑定或运行时绑定:在程序运行时根据对象的类型进行绑定。
如子类覆盖父类方法。属性不能被覆盖重写:
运行结果:Father'name(直接访问了public的属性)
注意:Java中除了static、final、private和构造方法之外,其他方法都是后期绑定。
1.3.5 变量初始化
java中域(成员属性)会被自动赋予初值,int/false/null,而局部变量中的则不会,如果使用未初始化的局部变量会导致错误!
1.3.6 Default函数(默认方法)
Java 8 引入了新的语言特性——默认方法(Default Methods)。
针对接口而言——在接口中的方法签名前加上了 default
关键字的实现方法。
目的:允许您添加新的功能到现有库的接口中,并能确保与采用旧版本接口编写的代码的二进制兼容性,如forEach方法。接口的实现类不需要去实现接口的所有方法。
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}
Default函数的继承:多继承存在冲突时,需要手动解决冲突,覆写存在歧义的方法,并可以使用 InterfaceName.super.methodName(); 的方式手动调用需要的接口默认方法。
1.3.7 函数式接口
https://segmentfault.com/a/1190000017433982
函数式接口:接口中只有一个抽象方法
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他静态方法内容
}
@FunctionalInterface注解
Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
函数接口,你可以理解为对一段行为的抽象,简单点说可以在方法就是将一段行为作为参数进行传递。函数接口就是对匿名内部类的优化。 如 Runnable类等
【四大基础函数接口】
一、功能性接口:Function 接收一个功能参数t,并返回一个功能结果R。
举例:可以简写为 Function<String,String> function = (str) -> "hello,"+str;
Function<String,String> function = (str) ->{
return "hello,"+str;
};
String str = function.apply("Tassel");
System.out.println(str);
【可以看出把一个lamda表达式(也可以是函数片段)赋给function,function调用自己的apply即可完成调用】
二、断言性接口:Predicate 主要用到test方法 其中参数t为输入参数,如果输入参数符合断言则返回true,否则false
Predicate<Integer> predOdd = integer -> integer % 2 == 1; System.out.println(predOdd.test(5));
三、供给性接口:Supplier 不接收任何参数 但有返回值
Supplier<String> getInstance = () -> "HelloWorld!"; System.out.println(getInstance.get());
四、消费性接口:Consumer 只接收一个参数t,但是没有返回值。
Consumer<String> printString = s -> System.out.println(s); printString.accept("helloWorld!")
1.3.8 ==,equal()和hashCode()
“==”:是运算符,直接比较的就是两个对象的地址;
equals():用于判断其他对象内容是否相同。是Object类的函数,默认和==相同都是比较地址,但是String、Math、Integer、Double等封装类中都对equals()进行了不同程度的重写,用于比较内容。
HashCode():hashCoed也是Object类里面的方法,返回值是一个对象的散列码。
Java对象的eqauls方法和hashCode方法是这样规定的:
1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
2、如果两个对象的hashCode相同,它们并不一定相同。
在集合查找时,使用hashcode无疑能大大降低对象比较次数,提高查找效率!
Java集合判断两个对象是否相等的规则是:
1.首先要判断两个对象的hashCode是否相等;
2.判断两个对象用equals()是否相等。
一般要求:当然重写其中一个方法.equals(),hashCode() 都需要重写另一个方法
他们是搭档起来就是判断对象是否相同(hashCode()在前,.equals()在后)。
1.3.9 Cloneable
Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现 Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。
深拷贝和浅拷贝。
要让对象可被拷贝:1)让该类实现java.lang.Cloneable接口;2)重写(Override)Object的clone()方法;
1.3.10 Serializable
Serializable也是一个空接口,它的目的只是简单的标识一个类的对象可以被序列化。只要对象实现了 java.io.Serializable 就可以进行序列化。
序列化:就是把对象转化成字节。
反序列化:把字节数据转换成对象。
目的:为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来,这是java中的提供的保存对象状态的机制—序列化。
序列化场景:1、对象网络传输;2、对象保存至文件中。
当某个字段被声明为transient后,默认序列化机制就会忽略该字段。
根据 serialVersionUID 值进行判断类是否修改过。
writeObject()方法序列化与readObject()方法反序列化
https://cloud.tencent.com/developer/article/1525299
1.3.11 finalize()
finalize()方法是Object类中提供的一个方法,在GC准备释放对象所占用的内存空间之前,它将首先调用finalize()方法。(类似于C++中的析构函数)。
在Java中,由于GC的自动回收机制,因而并不能保证finalize方法会被及时地执行(垃圾对象的回收时机具有不确定性),也不能保证它们会被执行(程序由始至终都未触发垃圾回收)。
finalize()方法中一般用于释放非Java 资源(如打开的文件资源、数据库连接等),或是调用非Java方法(native方法)时分配的内存(比如C语言的malloc()系列函数)。
【Java通用规范中已禁止调用该方法】由于finalize()方法的调用时机具有不确定性,不知道多久能回收资源甚至不回收,因而通常的做法是提供显示的close()方法供客户端手动调用。
finalize只能被调用一次;也就是说,覆盖了finalize方法的对象需要经过两个GC周期才能被清除。
【只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。】
1.4 集合
1.4.1 collection各类和接口继承结构
【Collection类的选择】
唯一吗?
是:Set
排序吗?
是:TreeSet或LinkedHashSet
否:HashSet
如果你知道是Set,但是不知道是哪个Set,就用HashSet。
否:List
要安全吗?
是:Vector
否:ArrayList或者LinkedList
查询多:ArrayList
增删多:LinkedList
如果你知道是List,但是不知道是哪个List,就用ArrayList。
Map中常用子类:HashMap、HashTable、TreeMap、ConcurrentHashMap。
HashMap:基础hash表,非线程安全,性能高。
TreeMap:有序键值对,按key排序
Hashtable:类似于HashMap,线程安全。性能低。
ConCurrentHashMap:HashMap + Hashtable。线程安全且性能较好。
HashMap基于数组和链表实现
ConCurrentHashMap
https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/
在Java1.7中采用分段锁,在Java1.8中采用了 CAS + synchronized
来保证并发安全性。
1.4.2 LinkedHashMap
LinkedHashMap是HashMap的子类,但是内部还有一个双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于双向链表中。LinkedHashMap支持两种顺序插入顺序 、 访问顺序(即遍历map时的顺序)
【插入顺序】:先添加的在前面,后添加的在后面。修改操作不影响顺序
【访问顺序】:所谓访问指的是get/put操作,对一个键执行get/put操作后,其对应的键值对会移动到链表末尾,所以最末尾的是最近访问的,最开始的是最久没有被访问的,这就是访问顺序。(头部结点为最不常访问结点)
LinkedHashMap有5个构造方法,其中4个都是按插入顺序,只有一个是可以指定按访问顺序:(可以用于实现LRU缓存)
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
其中参数accessOrder就是用来指定是否按访问顺序,如果为true,就是访问顺序。
public class LRUCache<K, V> extends LinkedHashMap<K, V>
1.4.3 集合初始化、大小和扩容机制
1.4.3.1 建议在集合初始化时指定集合容量大
如果没有设置大小,随着元素不断增加,需要不断resize重建hash表,影响性能。
ArrayList list = new ArrayList(20); // 指定初始容量
阿里开发手册建议:
1.4.3.2 Java集合的默认大小和扩容
ArrayList、Vector默认初始容量为10
HashSet、HashMap:默认初始容量为16
Vector:加载因子为1:即当 元素个数 超过 容量长度 时,进行扩容
扩容增量:原容量的 1倍,如 Vector的容量为10,一次扩容后是容量为20
ArrayList: 扩容增量:原容量的 0.5倍+1
HashSet:默认初始容量为16,加载因子为0.75 ,扩容增量:原容量的 1 倍
Map是一个双列集合
HashMap:默认初始容量为16,加载因子为0.75,扩容增量:原容量的 1 倍
1.4.3.3 ArrayList的动态扩容机制
使用无参构造器创建的ArrayList初始容量为0,第一次调用add()/addAll()方法时才会初始化数组的容量,初始容量为10
对集合添加若干个元素时,如果当前集合的容量满足需求,不扩容;如果当前集合容量不满足需求,则扩大为原来的1.5倍;如果扩大1.5倍依然不满足需求,则扩大为满足需求的最小容量。
1.4.3.4 初始化方法
Map和set的初始化
HashMap<String, String > h = new HashMap<String, String>() { {
put("a","b");
} };
List的初始化
List<String> numbers = new ArrayList<>(Arrays.asList("1", "2", "3"));
Arrays.asList得到的是一个长度和内容固定的列表,不能修改。
数组的初始化
【一维数组指定内容】int[] a2 = new int[] {1, 2, 3};
【二维数组指定大小】int [][] a4 = new int[4][4];
1.4.4 Collections和Arrays工具类
1.4.4.1 Collections常用方法
- max(Collection <? extends T> coll):根据元素的自然顺序,返回给定集合元素中的最大元素
- min(Collection <? extends T> coll):根据元素的自然顺序,返回给定集合元素中的最小元素
- Shuffle(element):洗牌方法,将当前集合内的数据进行随机打乱。
- Reverse(element):逆序排序,对当前集合的元素按照相反的顺序进行排序
- Sort(element):对当前集合进行升序排序,默认升序。
一是元素必须实现Comparable接口;
二是传入实现Comparator的参数。
- replaceAll替换批定元素为某元素,若要替换的值存在刚返回true,反之返回false
Collections.replaceAll(list, "aaa", "bbb")
- copy将集合n中的元素全部复制到m中,并且覆盖相应索引的元素
Collections.copy(m,n);
- swap交换集合中指定元素索引的位置
Collections.swap(m, 2, 3);
- fill用对象o替换集合list中的所有元素
Collections.fill(nums, 0);
1.4.4.2 Arrays常用方法
- asList(T… data) 注意:该方法返回的是Arrays内部静态类ArrayList。
- toString(int[] a)方法:返回一个指定数组的字符串表现形式
Arrays.toString(array1)); // 输出结果为[1, 2, 3, 4]
deepToString 用来打印多层次嵌套的数组元素。
- fill(int[] a, int value)方法:给指定数组的每个元素分配指定的值
- sort(int[] a):按升序对指定数组进行排序(和sort同样)
parallelSort() :java8新增的并行排序算法(稳定排序,数据量大时使用)
- binarySearch(int[] a, int value):使用二分搜索算法在指定的数组中搜索指定的值,并返回该值所在索引位置;若查询不到,则返回-1
- copyOf(T[] original, int newLength)拷贝数组,其内部调用了arraycopy() 方法,从下标0开始,如果超过原数组长度,会用null进行填充
- copyOfRange(T[] original, int from, int to)
- equals(Object[] array1, Object[] array2)判断两个数组是否相等,实际上比较的是两个数组的哈希值
1.4.5 WeakReference弱引用
在Java里, 当一个对象o被创建时, 它被放在Heap里. 当GC运行的时候, 如果发现没有任何引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1)没有任何引用指向它 2)GC被运行。
(可以是手动置空引用来回收对象内存 c= NULL)
对于简单对象, 当调用它的方法执行完毕后, 指向它的引用会被从stack中popup, 所以他就能在下一次GC执行时被回收了。
但是,当使用cache的时候, 由于cache的对象正是程序运行需要的, 那么只要程序正在运行, cache中的引用就不会被GC给回收。
Strong reference:
Object c = new Car(); //只要c还指向car object, car object就不会被回收
weak reference:
WeakReference<Car> weakCar = new WeakReference<Car>(car);
当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收
当要获得weak reference引用的object时, 首先需要判断它是否已经被回收:
if(weakCar.get()!=null)
【一般用weak reference引用的对象是有价值被cache, 而且很容易被重新被构建, 且很消耗内存的对象.】
另外, java提供了一个ReferenceQueue来保存这些所指向的对象已经被回收的reference.
soft reference和weak reference一样, 但被GC回收的时候需要多一个条件: 当系统内存不足时, soft reference指向的object才会被回收. 正因为有这个特性, soft reference比weak reference更加适合做cache objects的reference. 因为它可以尽可能的retain cached objects, 减少重建他们所需的时间和消耗.
1.4.6 for-each 循环优于传统for循环
for-each循环在简洁性和预防Bug方面有着传统的for循环无法比拟的优势,并且没有性能损失。尽可能多使用。在数组和list上均可采用(只要是实现了Iterable接口的对象)。
有三种情况下不可用 过滤删除指定元素、修改指定元素值、并行遍历多个集合。
1.4.7 HashMap中的equal和hashCode
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点
1.5 异常处理
1.5.1 异常种类和继承结构
Throwable是所有Error和Exception的超类,他们的区别是:
Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception )。
运行时异常:ArithmeticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。
受检查的异常,要么用try catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。
1.5.2 try catch finally中的return
https://www.cnblogs.com/cjeandailynotes/p/10565597.html
结论:
1、不管有没有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
1.5.3 try-with-resources语句
使用try-with-resources优雅的关闭资源。try-with-resources语句保证了每个声明了的资源在语句结束的时候都会被关闭。任何实现了java.lang.AutoCloseable接口的对象,和实现了java.io.Closeable接口的对象,都可以当做资源使用。
在try中创建好资源,在后面使用。又如:
try (Statement stmt = con.createStatement()) {
……
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
try-with-resources语句也可以像普通的try语句一样,有catch和finally代码块。在try-with-resources语句中,任何的catch和finally代码块都在所有被声明的资源被关闭后执行。
1.5.4 throw/throws
1)throw是语句抛出一个异常。(明确了这个地方要抛出这个异常)
throw e;
2)throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)
public void doA(int a) throws Exception1,Exception3{......}
1.5.5 线程和线程池的异常处理
https://blog.csdn.net/qq_32166627/article/details/118968373
在普通的单线程程序中,捕获异常只需要通过try catch finally代码块就可以了。在并发情况下,比如在父线程中启动了子线程,如何在父线程中捕获来自子线程的异常,从而进行相应的处理呢?
让我们回忆一下Runnable接口的run方法的完整签名,因为没有标识throws语句,所以方法是不会抛出checked异常的。至于RuntimeException这样的unchecked异常,由于新线程由JVM进行调度执行,如果发生了异常,也不会通知到父线程。
public abstract void run()
【线程的异常处理】
方法一:子线程中try... catch...
方法二:为线程设置“未捕获异常处理器”UncaughtExceptionHandler
为线程设置异常处理器。具体做法可以是以下几种:
(1)Thread.setUncaughtExceptionHandler设置当前线程的异常处理器;(线程默认没有异常处理器)
(2)Thread.setDefaultUncaughtExceptionHandler为整个程序设置默认的异常处理器;
使用时顺序:优先使用当前线程的UncaughtExceptionHandler——》使用线程组的UncaughtExceptionHandler——》使用全局默认DefaultUncaughtExceptionHandler
注意:子线程中发生了异常,如果没有任何类来接手处理的话,是会直接退出的,而不会记录任何日志。
所以,如果什么都不做的话,是会出现子线程任务既没执行成功,也没有任何日志提示的“诡异”现象的。
public static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.out.println(String.format("handle exception in child thread. %s", e));
}
}
【线程池的异常处理】
方法三:通过Future的get方法捕获异常(推荐)
使用线程池提交一个能获取到返回信息的方法,也就是 ExecutorService executorService = Executors.newFixedThreadPool(8);
Future future = executorService.submit(new ChildThread());
try {
future.get();
} catch (InterruptedException e) {
System.out.println(String.format("handle exception in child thread. %s", e));
} catch (ExecutionException e) {
System.out.println(String.format("handle exception in child thread. %s", e));
} finally {
if (executorService != null) {
executorService.shutdown();
}
}
1)通过execute提交的任务,才能将它抛出的异常交给UncaughtExceptionHandler。
2)通过submit提交的任务,无论是抛出的未检测异常还是已检查异常,都将被认为是任务返回状态的一部分。这个异常将被Future.get封装在ExecutionException中重新抛出
1.5.6 异常泄露敏感信息
敏感异常包装在非敏感异常抛出,不能防止敏感信息泄露
1.6 反射
反射应该是 JVM读取相应类的 字节码文件。
https://www.jianshu.com/p/9be58ee20dee
1.6.1 Java反射和相关类/方法
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。它把Java类中的各个成分映射成一个个的Java对象
与Java反射相关的类如下:
类名 |
用途 |
Class类 |
代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 |
代表类的成员变量(成员变量也称为类的属性) |
Method类 |
代表类的方法 |
Constructor类 |
代表类的构造方法 |
- 获得类相关的方法
方法 |
用途 |
asSubclass(Class<U> clazz) |
把传递的类的对象转换成代表其子类的对象 |
Cast |
把对象转换成代表类或是接口的对象 |
getClassLoader() |
获得类的加载器 |
getClasses() |
返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() |
返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) |
根据类名返回类的对象 |
getName() |
获得类的完整路径名字 |
newInstance() |
创建类的实例 |
getPackage() |
获得类的包 |
getSimpleName() |
获得类的名字 |
getSuperclass() |
获得当前类继承的父类的名字 |
getInterfaces() |
获得当前类实现的类或是接口 |
- 获得类中属性(Field)、注解Annotation、构造器Constructor、方法Method相关的方法【都是这四个接口,仅名字差异】
方法 |
用途 |
getField(String name) |
获得某个公有的属性对象 |
getFields() |
获得所有公有的属性对象 |
getDeclaredField(String name) |
获得某个属性对象 |
getDeclaredFields() |
获得所有属性对象 |
- 获得类中注解相关的方法
- 获得类中方法相关的方法
方法 |
用途 |
getMethod(String name, Class...<?> parameterTypes) |
获得该类某个公有的方法 |
getMethods() |
获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) |
获得该类某个方法 |
getDeclaredMethods() |
- 类中其他重要的方法
方法 |
用途 |
isAnnotation() |
如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) |
如果是指定类型注解类型则返回true |
isAnonymousClass() |
如果是匿名类则返回true |
isArray() |
如果是一个数组类则返回true |
isEnum() |
如果是枚举类则返回true |
isInstance(Object obj) |
如果obj是该类的实例则返回true |
isInterface() |
如果是接口类则返回true |
isLocalClass() |
如果是局部类则返回true |
isMemberClass() |
如果是内部类则返回true |
Field代表类的成员变量(成员变量也称为类的属性)。
方法 |
用途 |
equals(Object obj) |
属性与obj相等则返回true |
get(Object obj) |
获得obj中对应的属性值 |
set(Object obj, Object value) |
设置obj中对应属性值 |
Method代表类的方法。
方法 |
用途 |
invoke(Object obj, Object... args) |
传递object对象及参数调用该对象对应的方法 |
Constructor代表类的构造方法。
方法 |
用途 |
newInstance(Object... initargs) |
根据传递的参数创建类的对象 |
1.6.2 代码示例
1)代码中想要在基础模块channel中引用上层模块ap定义的一个类
2)使用反射创建对象
3)反射私有的构造方法
4)获取objectBook中的私有属性TAG值
5)使用invoke执行私有方法,如1例子。
1.6.3 Java中的Class对象
https://blog.csdn.net/dufufd/article/details/80537638
从面向对象的角度上来看,类也是对象,它们是类这个类对象,听起来有些抽象,但是在java中的实现就是所有的加载进来的类在虚拟机中都是一个java.lang.Class类的对象,而“类名.class”就是获得这个类的对象(在同一个ClassLoader中,类对象都是单例的)。
在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象和Class对象。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。
一个类被加载到内存并供我们使用需要经历如下三个阶段:
1)加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。
2)链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。
3)初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。
1.6.3.1 如何获得Class对象
有三种获得Class对象的方式:
- forName(“类的全限定名”)
- 实例对象.getClass()
- 类名.class (类字面常量)
用.class来创建对Class对象的引用时,不会自动地初始化该Class对象(这点和Class.forName方法不同)
如果一个字段被static final修饰,我们称为”编译时常量“,就像Dog的s1字段那样,那么在调用这个字段的时候是不会对Dog类进行初始化的。因为被static和final修饰的字段,在编译期就把结果放入了常量池中了。但是,如果只是将一个域设置为static 或final的,还不足以确保这种行为。
一旦类被加载了到了内存中,那么不论通过哪种方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class引用。jvm不会创建两个相同类型的Class对象。
其实对于任意一个Class对象,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性,也就是说,即使两个Class对象来源于同一个Class文件,只要加载它们的类加载器不同,那这两个Class对象就必定不相等。
1.6.3.2 基本数据类型的Class对象和包装类的Class对象不同
Class c1 = Integer.class;
Class c2 = int.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c1 == c2); // return false
1.6.3.3 在包装类中的字段TYPE
TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示,左右两边相互等价:
1.6.4 Reflect中的Modifier
Java中的Class、Field、Constructor等类的时候,都可以看到这样一个方法getModifiers,返回类、接口、变量、方法等以整数编码的Java语言修饰符;java.lang.reflect.Modifier类的静态方法和常量对这些编码进行了解析。
针对这个int编码,Modifier类提供了解码方法:
Modifier类一共提供了12中修饰符的编码表示常量
classModifiers、constructorModifiers、methodModifiers、fieldModifiers,它们的作用是返回一个int值将可以应用于一个类的源语言修饰符组合在一起。
1.7 输入输出流
1.7.1 常用的输入输出流
字节流
InputStream
FileInputStream 文件流 能处理二进制文件也能处理文本
BufferedInputStream缓冲流 能处理二进制文件也能处理文本
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(file1))
OutputStream
FileOutputStream 文件流 能处理二进制文件也能处理文本
BufferedOutputStream 缓冲流 能处理二进制文件也能处理文本
BufferedOutputStream bis=new BufferedOutputStream(new FileOutputStream(file2))
字符流
Reader
FileReader文件流 只能处理文本文件
BufferedReader 缓冲流 只能处理文本文件
BufferedReader br=new BufferedReader(new FileReader(name))
readLine()方法
Writer
FileWriter 文件流 只能处理文本文件
BufferedWriter 缓冲流 只能处理文本文件
1.7.2 IO流继承关系图
在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。
File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
其他的就是针对字节IO的InputStream/OutputStream和字符IO的Reader/Writer。如下图:
1.7.3 NIO
NIO(Non-Blocking I/O,java中,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,是解决高并发、I/O处理问题的有效方式。
这个是从Java1.4开始提供的一个新的IO包:java.nio
https://www.jianshu.com/p/d30893c4d6bb
1.7.4 用NIO多线程往同一文件写入数据
注意:Java普通的IO写操作不是线程安全的。java.nio.channels.FileChannel提供线程安全的写操作。
实现方案:https://blog.csdn.net/tianyeshiye/article/details/84746727
利用 RandomAccessFile 访问文件部分内容
利用 FileChannel 对线程独占的文件快需要加锁
利用 MappedByteBuffer 对文件进行并发写入
1.8 线程
线程一共有六种状态,分别为New、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,同一时刻只有一种状态,通过线程的getState方法可以获取线程的状态。
1.8.1 Java的Thread类方法
1.8.1.1 run和start()
一般线程要处理的代码放到run()方法中,start()方法启动线程将自动调用run()方法。
直接执行Run()方法就是在当前线程中开始执行了。
1.8.1.2 sleep()
使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。
枚举TimeUnit,其对sleep做了很好的封装:
//休眠一天
TimeUnit.DAYS.sleep(1);
//休眠一小时
TimeUnit.HOURS.sleep(1);
//休眠一分钟
TimeUnit.MINUTES.sleep(1);
//休眠一秒
TimeUnit.SECONDS.sleep(1);
//休眠一毫秒
TimeUnit.MILLISECONDS.sleep(1);
所以sleep()和wait()方法的最大区别是:
sleep()睡眠时,保持对象锁,仍然占有该锁;
而wait()睡眠时,释放对象锁。
wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。
1.8.1.3 interrupt相关方法
- interrupt()方法:在线程内部的interrupt flag标识,如果一个线程调用了interrupt方法,flag会被设置。但是如果当前线程正处于阻塞状态时,线程将会中断阻塞,并且会抛出InterruptedException异常,并且flag会被清除。
- isInterrupted()方法:此方法是Thread的实例方法,主要判断当前线程是否被中断。
- interrupted()方法:此方法是Thread中的一个静态方法,也是主要用于判断当前线程是否被中断,但是它和isInterrupted()方法有个区别就是该方法会直接清除掉该线程的interrupt标识。
调用线程类的interrupt() 方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。
join()方法等待该方法的线程执行完毕后再往下继续执行。如:
1.8.1.4 wait()、notify()、notifyAll()
wait()暂停当前线程并释放锁
notify()任意唤醒一个等待池中的线程,放入锁标志等待池,准备获取锁标志;
notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用(因为会对对象的“锁标志”进行操作)
Java中的synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取
1.8.1.5 其他方法
Yield():使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行。
getId() 获取线程的唯一ID,线程的ID在整个JVM进程中都是唯一的。
setPriority()&getPriority() 线程优先级
对volatile修饰的变量的修改,其它线程立即可见。
不能替代synchronized关键字
常用在一写多读场景
1.8.2 线程创建方式
方法一、继承Thread类,实现其中的run方法,start()调用
方法二、实现Runnable接口 Thread(Runnable target, String name)
方法三、实现Callable接口
注意:实现Callable接口方式,只能运行一次FutureTask,上面的例子最终只会输出:
线程一,a=1
1
【Runnable和Callable的区别】
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)
- call方法可以抛出异常,run方法不可以
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
- 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。
1.8.3 线程退出方式
- 正常退出(执行完线程的run方法)
- 使用interrupt中断。
1.8.4 共享数据和Synchronized
Runnable中默认是共享数据,需要增加synchronized关键字来设置互斥区,保护共享数据:
java中synchronized 主要有两种使用形式:同步方法和同步代码块。 synchronized 可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。Java 中每一个对象都可以作为锁,这是 synchronized 实现同步的基础:
(1)普通同步方法,锁是当前实例对象
(2)静态同步方法,锁是当前类的class对象
(3)同步方法块,锁是括号里面的对象接下来
Count可以按顺序递减:
1.8.5 AtomicInteger
AtomicInteger,AtomicLong,AtomicLongArray,
AtomicReference等原子类的类,主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理.
在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
1.8.6 线程同步机制
通信是指线程之间以何种机制来交换信息,主要有两种:共享内存和消息传递。
什么是共享变量?在java程序中所有的实例域、静态域和数组元素都是放在堆内存中(所有线程均可访问到,是可以共享的),而局部变量、方法定义参数和异常处理器参数不会在线程间共享。共享数据会出现线程安全问题,而非共享数据不会出现线程安全问题。
Java内存模型(JMM)
CPU的处理速度和主存的读写速度不是一个量级的,为了平衡这种巨大的差距,每个CPU都会有缓存。因此,共享变量会先放在主存中,每个线程都有属于自己的工作内存,并且会把位于主存中的共享变量拷贝到自己的工作内存,之后的读写操作均使用位于工作内存的变量副本,并在某个时刻将工作内存的变量副本写回到主存中去。
并且JMM决定了一个线程对共享变量的写入何时对其他线程是可见的。
在Java多线程编程中,经常会需要我们控制并发流程,等其他线程执行完毕,或者分阶段执行。Java在1.5的juc中引入了CountDownLatch
和CyclicBarrier
,1.7中又引入了Phaser
。
Exchanger 可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据,当线程A调用Exchange对象的exchange()方法后,他会陷入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行
Semaphore: 可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可; 比如多个窗口叫号,窗口都忙时,顾客等待,有空闲时,最新等待的被通知
1.8.7 守护线程
-线程运行结果与执行顺序无关
线程的调度是由CPU决定,CPU执行子任务时间具有不确定性。
1.8.8 Java的线程池体系结构
在 java.util.concurrent 包中已经提供为大多数使用场景的内置线程池:(Executors工具类提供的static方法)
Executors.newSingleThreadExecutor() # 单条线程
Executors.newFixedThreadPool(int n) # 固定数目线程的线程池
Executors.newCachedThreadPool() # 创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
Executors.newScheduledThreadPool(int n) # 支持定时及周期性的任务执行的线程池,多数情况下可用来替代 Timer 类。
1.9 JDBC
1.9.1 基本的JDBC连接
https://www.cnblogs.com/progor/p/9096463.html
JDBC(Java DataBase Connection),即java连接数据库技术。java连接数据库的通用标准,这套标准由一系列的接口(Connection,Statement,ResultSet等)构成,旨在屏蔽不同数据库之间的差异,使得开发者无需关注不同数据库系统之间的差异,简化java连接数据库的步骤。
通过驱动包来连接不同的数据库:
- 加载驱动
- 获取连接
jdbc协议:数据库子协议:主机[:端口][/连接的数据库] 【[]代表可选的】
例如:jdbc:mysql://localhost:3306/test
- 执行SQL语句
- 使用
Statement
执行语句
对于查询类的sql语句使用:executeQuery(sql),sql是一个字符串sql语句,返回结果是一个结果集
对于更新类(插入、修改、删除、更新)的语句使用:executeUpdate(sql),sql是一个字符串sql语句,返回结果是一个整数(受影响的行数)
- 使用PreparedStatement执行语句
PreparedStatement可以使用占位符,它是由占位符标识需要输入数据的位置,然后再逐一填入数据。
查询类的sql语句使用:executeQuery(sql)
更新类(插入、修改、删除、更新)的语句使用:executeUpdate(sql)
- 使用
CallableStatement
执行语句
CallableStatement
主要用来调用存储过程
- 获取返回
回结果是一个结果集,它有一个光标指向结果的每一行,最开始它不指向结果,第一次执行next()后,它指向第一行结果,继续执行next(),他会继续指向下一行。next的返回结果是布尔值,它可以用来判断是否有下一行。
- 关闭连接
后开启的需要先关闭,Statement、Connection、ResultSet都是需要关闭的
1.9.2 参数化查询
使用PreparedStatement来试下参数化查询:(?作为占位符, set方法设置参数)。(仅仅只是用PreparedStatement,但是sql还是自行拼接的话不算是参数化查询,有SQL注入攻击风险)
1.9.3 数据库连接池DBCP
DBCP是apache开源的连接池。
首先,使用DBCP连接池需要导入包:commons-dbcp.jar和commons-pool.jar
DBCP根据配置方式,下面给出两种使用方法:
1.手动配置法:【目前OMP代码中就是用该方式来逐个set的】
1)创建BasicDataSource对象: BasicDataSource dataSource = new BasicDataSource();
2)配置BasicDataSource对象:(少用)调用对应函数配置,例如dataSource.setDriverClassName("com.mysql.jdbc.Driver");
3)得到连接对象:Connection conn = ds.getConnection();
4)操作数据库。
2.配置文件配置法:(还是推荐这种方式)
1)创建BasicDataSourceFactory对象:BasicDataSourceFactory factory = new BasicDataSourceFactory();
2)利用BasicDataSourceFactory对象的createDataSource函数读取配置文件配置DataSource对象:DataSource dataSource = factory.createDataSource(properties);
3)得到连接对象:Connection conn = ds.getConnection();
4)操作数据库。
1.9.4 DbUtils
- DbUtils是apache旗下的一个操作数据库的工具
- DbUtils可以简化我们对数据库的CRUD操作,一个常用功能是能把查询到的数据自动封装起来,而不再需要我们操作ResultSet。
1.9.5 FetchSize设置每次缓存读取大小
(一次性从服务器读取的记录数)
- oracle中默认每次读取10行,JDBC默认每执行一次检索,会从游标中提取10行记录(不设置,结果集比较大的时候性能会很差)
- mysql驱动默认的行为是需要把整个结果全部读取到内存中才开始允许应用读取结果。所以mysql可能会有OOM问题。
需要在获得检索结果集之前,设置fetch size,否则就是无效。建议执行SQL语句之前设置,即ps.executeQuery();之前使用setFetchSize()函数设置。
1.10 Java8
1.10.1 lamda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)——有点像函数式参数Runnable的作用。
Java8中引入了一个新的操作符 "->" 该操作符称为箭头操作符或 Lambda 操作符。
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体
语法格式一:无参数,无返回值
Runnable runnable = ()->System.out.println("线程启动了");
runnable.run();
语法格式二:有一个参数,并且无返回值 (此时参数的小括号可以不写)
(x) -> System.out.println(x)
语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
即:Comparator com = (x, y) -> Integer.compare(x, y);
语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型。
备注:其实lamda表达式一般都是赋给函数式接口来实现代码功能的传递的。关于函数式接口参考1.3.7
Collection<BssStatsInfo> infoCollection=bssStatsInfo.values();
if(!CollectionUtils.isEmpty(infoCollection)){
infoCollection.forEach(i->{
if(null!=i){
i.setDate(date);
}
});
}
【lambda表达式对值封闭】
Java8中的lambda表达式,并不是完全闭包,lambda表达式对值封闭,不对变量封闭。简单点来说就是局部变量在lambda表达式中如果要使用,必须是声明final类型或者是隐式的final例如
int num = 123;
Consumer<Integer> print = (s) -> System.out.println(num);
就是可以的,虽然num没有被声明为final,但从整体来看,他和final类型的变量的表现是一致的,可如果是这样的代码
int num = 123;
num ++;
Consumer<Integer> print = (s) -> System.out.println(num);
则无法通过编译器,这就是对值封闭(也就是栈上的变量封闭)
如果上文中的num是实例变量或者是静态变量就没有这个限制。
1.10.2 Stream
https://blog.csdn.net/y_k_y/article/details/84633001
Stream 是 Java8 中处理集合的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
【特点】
1 . 不是数据结构,不会保存数据。
- 不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。(保留意见:毕竟peek方法可以修改流中元素)
- 惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
【分类】
无状态:指元素的处理不受之前元素的影响;
有状态:指该操作只有拿到所有元素之后才能继续下去。
非短路操作:指必须处理所有元素才能得到最终结果;
短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
【流的创建方法】
1.1 使用Collection下的 stream() 和 parallelStream() 方法
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
1.2使用Arrays 中的 stream() 方法,将数组转成流
1.3 使用Stream中的静态方法:of()、iterate()、generate()
1.4 使用 BufferedReader.lines() 方法,将每行内容转成流
1.5 使用 Pattern.splitAsStream() 方法,将字符串分隔成流
【流的中间操作】
2.1 筛选与切片
filter:过滤流中的某些元素
limit(n):获取n个元素
skip(n):跳过n元素,配合limit(n)可实现分页
distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
如:
映射:
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
排序:
sorted():自然排序,流中元素需实现Comparable接口
消费:
peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
【流的终止操作】
3.1 匹配、聚合操作
allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:返回流中第一个元素
findAny:返回流中的任意元素
count:返回流中元素的总个数
max:返回流中元素最大值
min:返回流中元素最小值
1.10.3 Optional
Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException)
https://www.cnblogs.com/wenbochang/p/10426171.html
https://www.cnblogs.com/zhangboyu/p/7580262.html
Optional其中有三个static方法。并且Optional的构造方法是private。所以Optional对象都是用三个static方法来创建:
static <T> Optional<T> empty() 返回空的 Optional 实例。
static <T> Optional<T> of(T value) 返回一个指定非null值的Optional。
static <T> Optional<T> ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
其他接口:
boolean isPresent() 如果值存在则方法会返回true,否则返回 false。
T get() 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
T orElse(T other) 如果存在该值,返回值, 否则返回 other。
- 创建实例——使用of 或ofNullable。
用of的时候需要自己保证传入的value非空,如果传入为空,就还是会抛出异常NullPointerException,所以用不用区别不大。
不知道value是不是空时,用ofNullable,这样如果传入为空,返回的是空的Optional对象。
- 访问Optional对象的值
T get() //返回值,如果为空抛出异常:NoSuchElementException
boolean isPresent() // 检查值是否存在
void ifPresent(Consumer<? super T> consumer) //该方法除了执行检查,还接受一个Consumer(消费者) 参数, 如果值存在则使用该值调用 consumer , 否则不做任何事情。
- 返回默认值
orElse(),它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值
orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果
- 返回异常
orElseThrow() API —— 它会在对象为空的时候抛出自己定义的异常
- 值转换
map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链试调用的操作成为可能
【Optional的链式调用】
1.10.4 MetaSpace
https://blog.csdn.net/kingmax54212008/article/details/104165185
持久代PermGen space
即内存的永久保存区域,说说为什么会内存溢出(java.lang.OutOfMemoryError: PermGen space ):这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。
JVM 种类有很多,比如 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘宝好样的!)。需要注意的是,PermGen space是Oracle-Sun Hotspot才有
【持久代中包含了虚拟机中所有可通过反射获取到的数据,比如Class和Method对象。即前面1.6中介绍的Class对象】
JDK8 HotSpot JVM 将移除上面的永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace,一种新的内存空间的诞生)。(不再有永久区内存不足问题)
这部分内存空间将全部移除。JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。
大部分类元数据都在本地内存中分配。
默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。
一些杂项数据已经移到Java堆空间中。升级到JDK8之后,会发现Java堆 空间有所增长。
1.11 泛型
泛型,即“参数化类型”。即将类型作为参数传入(实际的类型实参,?等类型形参)。
泛型有三种常用的使用方式:泛型类,泛型接口和泛型方法。
https://www.cnblogs.com/coprince/p/8603492.html
1.11.1 泛型类、接口、方法
- 泛型的类型参数只能是类类型,不能是简单类型。
- 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
if(ex_num instanceof Generic<Number>){ }
泛型类:
泛型接口:
泛型方法(根据传入的参数动态判断类型):
1.11.2 只在编译期有效
能够打印出日志,说明两个的Class对象时一样的,即都是ArrayList类。
1.11.3 泛型容器之间没有继承关系
- 苹果IS-A 水果
- 装苹果的盘子NOT-IS-A 装水果的盘子
所以,就算容器里装的东西之间有继承关系,但容器之间是没有继承关系的。所以我们不可以把Plate<Apple>的引用传递给Plate<Fruit>。此时就需要泛型通配符
1.11.4 泛型通配符
无边界通配符 <?> 标识可以是任意类型
上界通配符 Plate <? extends Fruit>
为泛型添加上边界,即传入的类型实参必须是指定类型的子类型
<? extends Fruit>会使往盘子里放东西的set()方法失效。但取东西get()方法还有效。
下界通配符 Plate<? super Fruit>
为泛型添加下边界,即传入的类型实参必须是指定类型的父类型
<? super Fruit>会使从盘子里取东西的get( )方法部分失效,只能存放到Object对象里。set( )方法正常。
通配符<?>和类型参数<T>的区别就在于:
1)对编译器来说所有的T都代表同一种类型。比如下面这个泛型方法里,三个T都指代同一个类型,要么都是String,要么都是Integer。
public <T> List<T> fill(T... t);
<?>是针对变量的:List<?> alist,标识alist这个变量里元素可以是任意类型。
2) 通配符只能用于填充泛型变量T,不能用于定义变量
PECS(Producer Extends Consumer Super)原则:
- 频繁往外读取内容的,适合用上界Extends。
- 经常往里插入的,适合用下界Super。
1.12 类初始化过程
https://www.cnblogs.com/fly-piglet/p/8766226.html
普通类:(静态变量和静态代码块只和出现顺序有关,普通变量和普通代码块也之和出现顺序有关)
- 静态变量
- 静态代码块
- 普通变量
- 普通代码块
- 构造函数
继承的子类:(静态——父类——子类)
- 父类静态变量
- 父类静态代码块
- 子类静态变量
- 子类静态代码块
- 父类普通变量
- 父类普通代码块
- 父类构造函数
- 子类普通变量
- 子类普通代码块
- 子类构造函数
接口:
- 声明的变量都是静态变量并且是final的,所以子类无法修改,并且是固定值不会因为实例而变化
- 接口中能有静态方法,不能有普通方法,普通方法需要用defalut添加默认实现
- 接口中的变量必须实例化
1.13 GC
https://blog.csdn.net/laomo_bible/article/details/83112622
Java垃圾回收机制
GC的分代收集分为:年轻代、老年代、永久代。(方法区是被当做永久代的,不过JDK1.6后将被取消掉了)
1.13.1 需要GC的区域
java 堆和方法区中
1.13.2 需要GC的对象
需要进行回收的对象就是已经没有存活的对象,判断一个对象是否存活常用的有两种办法:引用计数和可达分析。
可达性分析,通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,可作为GC Roots:
- 本地变量表(即栈)中引用的对象
- 方法区中静态变量引用的对象
- 方法区中常量引用的对象
- Native方法引用的对象
1.13.3 什么时候触发GC
(1)程序调用System.gc时可以触发
(2)系统自身来决定GC触发的时机(根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程)
GC又分为 minor GC 和 Full GC (也称为 Major GC )
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件
a.调用System.gc时,系统建议执行Full GC,但是不必然执行
b.老年代空间不足
c.方法去空间不足
d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存
e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
1.13.4 GC做了哪些事情
1.年轻代的GC(存放实例化的对象)
年轻代分为三个区:Eden和两个存活区(Survivor0和Survivor1),分别占内存的80%、10%、10% 使用“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中)
当Eden区满时,就执行一次MinorGC,并将剩余存活的对象都添加到Surivivor0,回收Eden中的没有存活的对象。
当Surivivor0页都满了的时候,就将仍然存活的存到Surivivor1中,回收Surivivor0中的对象Surivivor0和Surivivor1依次去存,当两个存活区切换了几次后(HotSpot默认是15次),将仍然存活的对象复制到老年代
2.老年代的GC(存放较大的实例化的对象和在年轻代中存活了足够久的对象)
老年代GC用的是标记-整理算法,即标记存活的对象,向一端移动,保证内存的完整性,然后将未标记的清掉
当老年代不够用时,也会执行Major GC,即Full GC
注意:如果永久代代存放的常量和类过大,无法全部放入永久代,也会触发永久代的GC,将一部分放入老年代
3.永久代的GC(存放常量、类)
在JDK1.6版本之后,永久代就要被取消掉了,只留下年轻代和老年代
年轻代的GC是必须的,但是老年代和永久代并不是必须的,可以通过设置参数来决定是否对类进行回收
1.13.5 System.gc()
执行System.gc()函数的作用只是提醒或告诉虚拟机,希望进行一次垃圾回收。
至于什么时候进行回收还是取决于虚拟机,而且也不能保证一定进行回收(如果-XX:+DisableExplicitGC设置成true,则不会进行回收)
java.lang.OutOfMemoryError: java heap space 可能会导致所有用户线程暂停,不可以通过try/catch解决。
java.lang.StackOverflowError: 线程栈空间不足
java.lang.OutOfMemoryError: PermGen space 是指方法区(永久代)内存溢出
1.14 JDK工具
https://blog.csdn.net/qq_27607965/article/details/79982519
1.14.1 标准工具
这些工具都是JDK提供的,通常都是长期支持的工具,JDK承诺这些工具比较好用。
基础
jar 创建和管理Jar文件
java Java运行工具,用于运行.class字节码文件或.jar文件
javac 用于Java编程语言的编译器
javadoc API文档生成器
javah C头文件和stub函数生成器,用于编写native方法
javap 类文件反汇编器,主要用于根据Java字节码文件反汇编为Java源代码文件
jdb Java调试器(Java Debugger)
安全
keytool 管理密钥库和证书。主要用于获取或缓存Kerberos协议的票据授权票据。允许用户查看本地凭据缓存和密钥表中的条目(用于Kerberos协议)。Kerberos密钥表管理工具,允许用户管理存储于本地密钥表中的主要名称和服务密钥。
jarsigner 生成并验证JAR签名
故障排查:
Jcmd JVM诊断命令工具,将诊断命令请求发送到正在运行的Java虚拟机。
jconsole 用于监控Java虚拟机的使用JMX规范的图形工具。它可以监控本地和远程JVM。它还可以监控和管理应用程序。
1.14.2 实验性工具
监控:
jps JVM进程状态工具(JVM Process Status Tool),在目标系统上列出HotSpot Java虚拟机进程的描述信息
jstat JVM统计监控工具(JVM Statistics Monitoring Tool),根据参数指定的方式收集和记录指定的jvm进程的性能统计信息。
故障排查:
jinfo Java的配置信息工具(Java Configuration Information),用于打印指定Java进程、核心文件或远程调试服务器的配置信息。
jhat Java堆分析工具(Java Heap Analysis Tool),用于分析Java堆内存中的对象信息。
jmap Java内存映射工具(Java Memory Map),主要用于打印指定Java进程、核心文件或远程调试服务器的共享对象内存映射或堆内存细节。
jsadebugd 适用于Java的可维护性代理调试守护程序(Java Serviceability Agent Debug Daemon),主要用于附加到指定的Java进程、核心文件,或充当一个调试服务器。
jstack Java的堆栈跟踪工具,主要用于打印指定Java进程、核心文件或远程调试服务器的Java线程的堆栈跟踪信息。
1.15 正则表达式
https://blog.csdn.net/mynamepg/article/details/83110538
1.15.1 Java正则表达式类
java正则表达式通过java.util.regex包下的Pattern类与Matcher类实现
Pattern类用于创建一个正则表达式,
split(CharSequence input)实例方法,用于分隔字符串,并返回一个String[]
静态方法:
Pattern.matcher(String regex,CharSequence input)是一个静态方法,用于快速匹配字符串,该方法适合用于只匹配一次,且匹配全部字符串.
Patther的实例方法:
matcher(CharSequence input) //返回一个Matcher类对象
【基本用法】
1.15.2 正则表达式语法
代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
^ 匹配字符串的开始
$ 匹配字符串的结束
\b 匹配字符串的结束
代码/语法 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
中括号”[]“:只有方括号里面指定的字符才参与匹配,也只能匹配单个字符。
”|“ 符号。相当与“或”,可以匹配指定的字符,但是也只能选择其中一项进行匹配。
分组:
重复单个字符直接在字符后面加上限定符就行了,但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),
重复的语法默认采用贪婪匹配(即匹配尽可能多的字符):
a.*b 匹配整个 aabab
【在上面的重复语法后加上?就是懒惰匹配,匹配尽可能少的字符】
语法 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
1.16 网络socket
Socket哪些操作可能会进入阻塞状态:
1、 serversocket的accpet()监听客户端连接
2、 执行socket的输出流写数据
3、 执行socket的输入流读取数据
4、 Socket的getOutputStream(), getInputStream()
- 点赞
- 收藏
- 关注作者
评论(0)