Java易遗漏知识点(再看Java编程思想梳理)
一、零散
1、java允许我们把任何基本数据类型转换成别的基本数据类型,布尔型除外。(基本数据类型:boolean, char, byte, short, int, long, float, double), 把一个short类型数据和int类型数据进行计算,结果的数据类型是int,通常表达式中出现的最大数据类型就是表达式最终结果的数据类型。
2、new一个对象是存储在堆里的,而基本类型的实例则是存储在堆栈中的,是直接存储的实际值而非引用,所以更加高效,同时复制一个基本类型变量时是直接把值复制了一份,而不像其他对象那样只是复制一个引用;
3、几乎所有的操作符(+-*/等)都只能操作基本类型,除了=,==,!=,还有一个就是可以使用+,+=操作String(java对此操作符进行了重载);
4、基本数据类型变量在创建时都会给定一个初始值,如果我们没主动指定,编译器则会给定默认的(number的是0,boolean的是false, char的是[]),如 int i ; i没有给定初始值,则会自动给定为0。
5、java包名命名规则为全部使用小写字母,类名首字母大写,变量使用驼峰命名法,常量使用全部大写字母(使用下划线_分隔单词),
6、 /*
* 注释
*/
这种类型的注释有的会习惯性的在每一行以*号开头,但中间的*并没有什么用,编译器只取开头的/*和结尾的*/,中间的全都认定为注释内容。不过以/**开头的注释可以被javadoc工具提取出来自动生成文档,javadoc只会提取public和protected的成员注释。
7、短路现象:一旦能够明确无误的确定整个逻辑表达式的值,就不会在运行剩余的表达式了。如A&&B,如果A是false,则不会再运行B表达式了,因为已经可以确定A&&B的结果是false了;
8、实例化long类型的值时一般使用L(如321L),小写的l容易被误认为1,float使用F/f,double使用D/d,十六进制整数类型使用0x或0X开头,八进制以0开头
9、指数的写法:1.39E-43f (数学为1.39*10(-43次方))
10、static方法就是没有this的方法,在static方法内部不能调用非static方法,但非static方法可以调用static方法。静态变量/方法只有在首次被调用时才会进行初始化,静态方法所在类时不会初始化静态方法,同样初始化静态方法时也不是初始化其所在的类。
11、在实例化类时,会先实例所有的变量,无论变量在方法前还是方法后,都会先把所有的变量提取出来进行初始化。
12、finalize()方法是在对象被回收前执行的方法,但并是不是在被回收的同时执行的,当垃圾回收器准备回收对象时,会首先执行其finalize()方法,在下一次执行垃圾回收动作时才会真正的回收此对象。但垃圾回收并不会确定总会发生,只要程序没有濒临存储空间用完的那一刻,则不会进行垃圾回收,那么对象空间也不会被释放,因为垃圾回收本身也有计算开销。finalize()方法主要用于必须自己手动实施清理的东西。
13、数组定义时int[] a和 int a[]的效果是一样的。
14、 final关键字
final作用于基本类型时使其数值恒定不变,作用于其他对象时则是使对象的引用(内存地地)恒定不变,如果同时加上static关键字则强调只有一份数据;
final作用于方法时使方法锁定,以防任何继承类修改它的含义。类中private方法都是隐式的指定为final的。
final作用于类则禁止继承类, final类中所有的方法都隐式指定为final。
15、assert关键字需要在运行时候显式开启才能生效,应避免使用。assert关键字本意上是为测试调试程序时使用的。
16、RuntimeException被称作“不受检查异常”,因为系统会自动捕获,不需要我们手动来try/catch了。
17、finally里的代码,即使是在之前已经执行了return语句了,仍然会运行。所以一个方法运行了return后并不一定终结,如果是在try/catch里return里,还有可能在最后运行finally里的代码。
18、String对象是不可变的,每一个看起来会修改String值的方法实际上都是创建了一个全新的String对象。
19、扫描文件可考虑使用Scanner对象,它是在SE5中新增,可以接受包括File,String,InputSream等任何输入对象,使用它的next()方法可以按行、字等各种方式依次取出内容。next()可传入正则参数。
20、System.out.printf()可打印出格式化的字符串,如:System.out.printf("你好 %s", "aaa");效果同format()方法;
21、常用formatter类型转换:d 整数,c Unicode字符, b boolean值,s 字符串,f 浮点数,e 浮点科学计数,x 十六进制整数,h 十六进制散列, % 字符%;
22、需要对齐时的格式语法: %[argument_index$][flags][width][.precision][conversion]
flags 即-号,如果加上则会左对齐,不加则默认右对齐
width 最小宽度;
.precision 对于String来说是最大字符数,对于浮点数则表示小数部分位数(默认是6位),多了就舍,少了就补0
示例: %-15.5s 表示在15格宽度内以左对齐方式显示最长5位字符串。
System.out.printf("你好%-15.5s %.3f", "aaabbb",1.1);
结果:你好aaabb 1.100
23、split()和replaceAll(),replaceFirst()方法的参数可以接收正则表达式。是String类自带的几个可使用正则表达式的方法。
二、类、接口
1、接口中的任何域都自动是public static final的,所以接口中的变量都是常量。接口中的方法也不用使用public关键字,因为已经隐式指定为public了
2、新建类时,如果没有构造器,则编译器会自动帮忙创建一个默认构造器(无参),但如果已经定义了一个构造器(无论有参无参),编译器就不会再帮助创建默认构造器了。
3、自定义的类,如果没有重写equals()方法,那么调用equals进行比较时默认比较的是两个对象的引用是否相同;
在重写equals()方法时总是注意把hashCode()也重写是一个良好的习惯。否则会使用Object的hashCode()方法,即使用对象的存储地址计算散列码(哈希码)。
4、构造器实际上是隐式的static方法
5、接口与类的使用中,恰当的原则应该是:优先选择使用类而非接口,如果接口的必需性变得非常明确再添加接口。接口是一种重要的工具,但不能舍近求远的滥用。
6、使用instanceof来判断是否是某个类,如果是目标类的父类,也返回true.
7、Java对象序列化。
如果对象实现了Serializable接口,java运行中会将这些对象转换成一个字节序列(一般是经过流就会被序列化),并能够在以重启程序后将这个字节序列完全恢复为原来的对象。序列化能够我自动弥补不同操作系统之间的差异。
如果只想部分序列化,可使用Externalizabler接口。或者也可以使用transient关键字来修饰属性,以让被修饰的属性不被序列化。
即使是同一个序列化对象,只要是经过了两个不同的流再取出来,它们就会变成两个不同的对象(内存地址是两个不同的了)。完全的深度复制。
8、enum的实质还是一个类,所以在enum里可以进行类的大部分行为。enum创建里其实已经自动继承了java.lang.Enum类了,所以新建enum时不能继承类,也不能继承其他enum,但可以实现一个或多个接口。
9、什么是反射?
看得最多的一句就是:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,这种动态获取、调用对象方法的功能称为java语言的反射机制。
说的是个什么玩意儿?能不能说人话?
其实反射机制解决的问题就是:在编译阶段类的信息不可知,或者就是这个类根本就没写出来,在java程序已经在运行状态了,突然给你来一段原来没有的代码,让你把这段代码运行起来。这段代码里的方法我也想调用,给我整好了。出于这个目的,我们就希望在运行时能获取到一个类的所有属性和方法等类相关的信息,然后就是能调用这些方法。这就是反射机制干的事,反射完成的东西其实没有什么神奇之处,它只是把编译期间干的事让在运行过程中也可以干罢了。
反射带了更加动态的编程风格,同时反射也给了我们极大的权限,让我们可以调用那些非公共访问权限的方法。
动态代理、AOP中就有反射的使用(此处可有代理的相关内容)。
三、数组、容器
1、Arrays.asList()方法输出结果的底层是一个数组,尺寸是不能变化的,所以不能进行增加和删除元素的操作。
2、HashSet是最快速获取元素的set存储方式,TreeSet和LinkedHashSet是有序的,LinkedHashSet按照被添加的顺序保存对象,TreeSet按照比较结果的升序保存对象。同样类似的还有HashMap,TreeMap和LinkedHashMap。
3、ArryaList长于随机访问元素,但在中间插入和移除元素时较慢。LinkedList在中间进行插入和删除操作的代价较低,但在随机访问上相对比较慢。同时LinkedList是先进先出(栈)队列Queue的一种实现。
4、ArrayList为什么比LinkedList查询速度快?
ArrayList从原理上就是数据结构中的数组,也就是内存中一片连续的空间,这意味着,当我get(index)的时候,我可以根据数组的(首地址+偏移量),直接计算出我想访问的第index个元素在内存中的位置。
LinkedList可以简单理解为数据结构中的链表(其实是双向循环链表),在内存中开辟的不是一段连续的空间,而是每个元素有一个[元素|下一元素地址]这样的内存结构。当get(index)时,只能从首元素开始,依次获得下一个元素的地址。
5、在List指定位置添加元素,使用add(index, object),传入两个参数即可,第一个是要插入的位置。
6、迭代器是一个对象,它的作用是遍历并选择序列中的对象,同时将遍历序列操作与序列底层的结构分享,统一了对容器的访问方式。Java序列常用的Iterator只能单向移动,next()获取序列中的下一元素,hasNext()检查序列中是否还有元素,remove()将迭代器新近返回的元素删除。实例:Iterator<String> iterator = new ArrayList<String>().iterator();
7、HashTable线程安全,键和值都不允许为null,计算效率相对较低; HashMap线程不安全,键和值都可以为null,但键的null只能存在一个,HashMap使用效率更高(因为不用考虑线程);
8、应尽量避免使用Vector,Hashtable,Stack等过时的容器。
图:简单的容器分类https://blog.csdn.net/anjayxc/article/details/6068091
9、所有的Collection子类型都有一个接收另一个Collection对象的构造器,用所接收的元素来填充新的容器
10、List、Set、Quene等取交集可用Collection的方法retainAll();
11、使用Set时,要想确保元素的唯一性,如果是自定义对象,需要定义equals()方法,不然可能会达不到去重的效果。
12、java.util.Collections提供的一些比较有用的工具方法
1 public static <T extends Comparable<? super T>> void sort(List<T> list) 排序,目标需要重写Comparable方法 2 public static <T> void sort(List<T> list, Comparator<? super T> c) 排序,并指定比较方法 3 private static <T> T get(ListIterator<? extends T> i, int index) 按索引获取 4 public static void reverse(List<?> list) 逆序 5 public static void shuffle(List<?> list) 随机打乱顺序 6 public static void shuffle(List<?> list, Random rnd) 随机打乱顺序,自定义规则 7 public static void swap(List<?> list, int i, int j) 交换List中两个元素的位置 8 private static void swap(Object[] arr, int i, int j) 交换数组中两个元素的位置 9 public static <T> void fill(List<? super T> list, T obj) 用obj填充list里所有元素 10 public static <T> void copy(List<? super T> dest, List<? extends T> src) 复制 11 public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) 获取最小值,目标需要重写Comparable方法 12 public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp 获取最小值,并指定比较方法 13 public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 获取最大值,目标需要重写Comparable方法 14 public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) 获取最大值,并指定比较方法 15 public static void rotate(List<?> list, int distance) 所有元素向后移distance个位置 16 public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) 使用新值替换所有的旧值 17 public static int indexOfSubList(List<?> source, List<?> target) 从头开始查找第一个目标索引,没找到则返回-1 18 public static int lastIndexOfSubList(List<?> source, List<?> target) 从最后开始查找第一个目标索引,没找到则返回-1 19 public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) 返回一个不可改变容器 20 public static boolean disjoint(Collection<?> c1, Collection<?> c2) 当两个集合没有交集时返回true
13、迭代器
使用迭代器实现List的简单原理:
1 public class MyIterator<T> { 2 private T value; 3 private MyIterator<T> next; 4 private boolean hasNext; 5 6 MyIterator(T t){this.value = t;} 7 8 public T getValue(){return this.value;} 9 10 public void setNext(MyIterator<T> next){this.next = next;} 11 public MyIterator<T> next(){return this.next;} 12 13 public void setHasNext(){this.hasNext = true;} 14 public boolean hasNext(){return this.hasNext;} 15 } 16 public class MyList<T>{ 17 private int size; 18 private MyIterator<T> iterator; 19 public int size(){return this.size; } 20 21 public void add(T t){ 22 MyIterator<T> newIt = new MyIterator(t); 23 if (this.size > 0){ 24 newIt.setHasNext(); 25 newIt.setNext(this.iterator); 26 } 27 this.iterator = newIt; 28 this.size ++; 29 } 30 public String toString(){ 31 StringBuilder builder = new StringBuilder(); 32 builder.append("["); 33 MyIterator<T> current = this.iterator; 34 boolean hasNext = false; 35 if (size > 0){ 36 do { 37 builder.append(current.getValue().toString()).append(","); 38 hasNext = current.hasNext(); 39 current = current.next(); 40 }while (hasNext); 41 } 42 builder.append("]"); 43 return builder.toString(); 44 } 45 46 public static void main(String[] args) { 47 MyList<String> myList = new MyList<String>(); 48 myList.add("obb"); 49 myList.add("test"); 50 myList.add("lisa"); 51 System.out.println(myList.size); 52 System.out.println(myList.toString()); 53 } 54 }
结果:
3
[lisa,test,obb,]
14、散列
减少线性查询,用存储元素最快的数据结构数组来存储键信息。
如下是简单的HashMap实现原理:
1 public class MyHashMap<K,V> { 2 static final int SIZE = 997; 3 //散列分装桶 4 List<MapEntry<K,V>>[] buckets = new List[SIZE]; 5 6 public void put(K key, V value){ 7 boolean found = false; 8 int index = Math.abs(key.hashCode()) % SIZE; 9 //如果桶位还是空的则新建一个桶 10 if (buckets[index] == null){ 11 buckets[index] = new ArrayList<MapEntry<K, V>>(); 12 } 13 List<MapEntry<K,V>> bucket = buckets[index]; 14 for (MapEntry<K,V> entry : bucket){ 15 if (entry.getKey().equals(key)){ 16 entry.setValue(value); 17 found = true; 18 break; 19 } 20 } 21 if (!found){ 22 MapEntry<K,V> newEntry = new MapEntry<K,V>(key, value); 23 buckets[index].add(newEntry); 24 } 25 } 26 public V get(Object key){ 27 int index = Math.abs(key.hashCode()) % SIZE; 28 if (buckets[index] == null) 29 return null; 30 for (MapEntry<K,V> entry : buckets[index]){ 31 if (entry.getKey().equals(key)){ 32 return entry.getValue(); 33 } 34 } 35 return null; 36 } 37 }
将Map根据哈希值分储于997个桶里,在查询时再去相应的桶里取出来,查询速度能够比线性查询显著提高,各桶的存储数量如果能分散得比较平均是最好的状态
*桶的数量使用质数是一个早期的选择,因为取余数也是一个较大的计算开销。现在Java的散列函数都使用2的整数次方,用掩码代替除法。
>指定HashMap、HashSet的性能因子,我们可以通过指定初始容量和负载因子来满足我们特定需求,以提高性能。
new HashMap(初始容量, 负载因子);
不指定时的默认初始容量为16,负载因子为0.75;
负载=当前存储总量/当前容量 (空表的负载为0)
当负载达到负载因子水平时,容器会自动增加一倍左右的容量(桶位数),并重新将现有的对象分布到新的桶集中,这也被称为再散列。
负载轻的表产生冲突的可能性小,对插入/查找比较理想,但会减慢使用迭代器进行遍历的过程。所以高的负载因子可以减少存储空间,但会增加查找代价。
四、正则表达式
正则表达式的匹配结果Groups对应取值,group(0)为整个表达式,1为第一对括号包含的组,依次类推。
如A(B(C))D,group(0)是ABCD, group(1)是BC,group(2)是C。
正则部分示例:
1 Pattern.compile("a+", Pattern.CANON_EQ);//两个字符串当且仅当它们的完全规范分解相匹配时才匹配,如a\u030A就会匹配字符串? 2 Pattern.compile("a+", Pattern.CASE_INSENSITIVE);//忽略大小写,只对US-ASCII字符集中的字符有效; 3 Pattern.compile("a+", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);//忽略大小写,对所有Unicode字符都有效; 4 Pattern.compile("a+", Pattern.COMMENTS);//忽略空格,以#号开头行会被当作注释忽略掉; 5 Pattern.compile("a+", Pattern.DOTALL); //.可以匹配所有字符,包括行终结符;效果类似于\s\S之类的了 6 Pattern.compile("a+", Pattern.MULTILINE); //如果有多行^和$会匹配每一行的开始和结束,而不是默认的整个字符串的开头和结尾。 7 Pattern.compile("a+", Pattern.UNIX_LINES); //在.、^和$行为中,只识别行终结符\n。 8 9 Matcher matcher = Pattern.compile("a+").matcher("aaaac"); 10 matcher.find();//可以在输入的任意位置定位正则表达式,不输入则从第一位开始; 11 matcher.lookingAt(); //只有在正则表达式与输入的最开始处就开始匹配才会成功; 12 matcher.matches(); //只有在整个输入都匹配正则表达式时才会成功; 13 matcher.reset("bbcd");//将现有matcher应用于一个新的字符串,如果不传参则是将对象重新设置到当前字符序列的起始位置。
五、内部类
六、注解
七、泛型
八、I/O系统
九、代理
十、多线程
其他参考: