这篇博客全篇都是java的知识点,边看《Java编程思想》边写的,一天两天之内可能无法完成,但是我也肯定会在最近这几天内看完,然后再开始看一些网络知识了。当然我不能照着书全部重新敲一遍,我只是整理我觉得混淆的和以前并不清楚的东西。不全面也是正常了。
目录结构如下:
- 对象
- 操作符
- 控制执行流程
- 初始化与清理
- 访问控制权限控制
- 复用类
- 多态
- 接口
- 内部类
- 持有对象
- 通过异常处理处理错误
- 字符串
- 类型信息
- 泛型
- 数组
- 容器的深入研究
- Java I/O系统
- 枚举类型
- 注解
- 并发
- 组件
对象
对象:将问题空间中的元素及其在解空间中的表示称为对象。也就是说:程序可以通过添加新类型的对象使自身适用于某个特定的问题。对象具有状态,行为和标识。
类:描述了具有相同特性(数据元素)和行为(功能)的对象集合,所以一个类实际上就是一个数据类型。
接口确定了对某一特定对象所能发出的请求。
继承在只重写基类方法与需要添加新方法都是可行,但要根据真实的情况来,区分还是很明显的。
Java完全采用动态分配内存方式,编译器可以确定对象存活的时间,并且可以自动销毁它。这样可以避免暗藏的内存泄漏问题。
在程序中,彼此独立运行的部分称之为线程。
String中每个字符都是16位或者2个字节。null在Java中表示这个引用还没指向某个对象。
当声明一个事物static时,就表示这个域或这个方法不会与包含它的那个类的任何对象实例关联在一起。未创建实例时,也可以直接调用其static的域或方法,而非static关键字的域和方法都必须通过实例化才能引用。多个实例化指向的其实也只是一个static域。
javadoc只能为public,protect成员进行注释。private和包内可访问的注释会被忽略掉。javadoc注释支持嵌入HTML
类名的首字母大写,几个单词组合时不要用下划线隔开,其中每个内部单词的首字母都要大写。
用eclipse导出javadoc为html文件,可以导出整个项目,步骤为:项目右键—>export—>java—>javadoc—>javadoc command为jdk的bin文件的javadoc.exe这个文件,然后选择保存的路径—>finish 就成功了,自动概览整个项目,可以生成目录等。
操作符
不能把任何东西赋值给一个常数。
obj1 = obj2;这种叫做别名现象,指向的是同一个引用,改变的值那些都将会相等。方法调用时也是产生了一个别名,改变了方法中的对象,其实改变的是调用方法传入的对象。
前缀递增或者递减都是先执行运算,再进行赋值。而后缀递减或者后缀递增都是先赋值再运算。
在for(int i = 0;i<10;i++)这个语句中,最先初始化i=0,然后判断i<10,执行完之后再进行i++,再判断i<10是否成立来决定是否还要执行循环。
==和!=适用于所有的基本数据类型,也适用于所有对象,它们比较的是对象的引用是否是同一个。 equals()方法默认的也是比较引用,只有自己覆盖重写之后才会是想要的比较。
短路现象:一旦能够准确无误的确定整个表达式的值,就不会计算表达式的剩余部分了。这种方法可以获得潜在性能的提升。
float类型的常量一定要加f,不然会被认定为double类型,然后就会报错。例如:float aa = 1.2f;(此处不加f时就会报错)
按位操作符用来操作整数基本数据类型中的单个bit,即二进制位。(&,|,^:异或:某一个是1,但不全是1时会输出1,~),&,|,^可与赋值符号连用。
移位操作符的运算对象也是二进制,<<左移,>>(有符号,符号为正高位补0符号为负高位补1),>>>(java特有无符号,无论正负都在高位补0)右移,左移低位补0,
计算机显示二进制是显示的补码,整数就是原码,负数是将数字的反码(如果是正数,则表示方法和原码一样;如果是负数,则保留符号位1,然后将这个数字的原码按照每位取反,则得到这个数字的反码表示形式)加1。
假如a = -1;a是八位的数据。原码为1000 0001,但是计算机中存的是补码,也就是-1 的反码:1111 1111,那么计算机中表示的二进制为:1111 1111。
将a>>>3,就是说以无符号数来右移3位,得到的数为:1111 1,就是说无论有没有符号,将这个补码右移三位就行。
三元操作符:boolean-exp?value0:value1;
- Math.round();四舍五入取整,参数是float或者double
- Math.ceil();向上取整
- Math.floor();向下取整
控制执行流程
while与do-while的区别就是:do-while至少会执行一次。
逗号操作符运用的位置:函数的参数列表用逗号隔开,和for语句中控制表达式for(int j = 0,i = 1=;j<10;j++,i++),用逗号可以定义多个变量,但必须具有相同的类型。
for(int i:arr)是以foreach的方式遍历数组。
初始化与清理
为了让方法名相同而形式参数不用的构造器同时存在,所以必须用到方法重载。也可应用于其他方法,且方法同样方便。根据返回值来重载方法是行不通的。在导出类中也可以对基类方法进行重载。
重载接收较小的基本类型(例如参数是int,传入long),如果传入的参数较大,就得通过类型转换来执行窄化转换。如果不这样做就会报错。
this只能在方法内部使用,表示调用那个方法的对象。需要返回对当前对象的引用时,可以直接使用:return this;
通过this对构造函数的调用,只能在构造函数内能调用,调用语句必须是第一句,并且一个构造器内只能调用一个构造函数。
static方法内部不能调用非静态的方法,但是反过来可以。调用static方法时,由于存在this(不需要实例化就可以调用),所以不是通过“向对象发送消息”。
垃圾回收器基本原理就是虚拟机每隔一段时间就会运行一次垃圾回收或是在虚拟机认为需要的时候。主动调用是System.gc()。垃圾回收器运行了当然会收回内在。
两个最基本的java回收算法:复制算法和标记清理算法
- 复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
- 标记清理:一块区域,标记要回收的对象,然后回收,一定会出现碎片,那么引出标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
两个概念:新生代和年老代
- 新生代:初始对象,生命周期短的
- 永久代:长时间存在的对象
整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收。
P.S:
- Serial New收集器是针对新生代的收集器,采用的是复制算法
- Parallel New(并行)收集器,新生代采用复制算法,老年代采用标记整理
- Parallel Scavenge(并行)收集器,针对新生代,采用复制收集算法
- Serial Old(串行)收集器,新生代采用复制,老年代采用标记清理
- Parallel Old(并行)收集器,针对老年代,标记整理
- CMS收集器,基于标记清理
- G1收集器:整体上是基于标记清理,局部采用复制
综上:新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理。
使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize方法),他们也必须同内存及其回收有关。如果JVM并未面临内存耗尽的情形,它是不会花费时间去执行垃圾回收以恢复内存的。
最好是除了内存之外,不能依赖垃圾回收器做任何事情。如需清理,最好编写自己的清理方法,不要使用finalize()方法
当调用对象太多(我试了调用十万次)时,存储空间满了,会自动调用系统垃圾回收,强制进行终结的代码如下:
12 | System.gc(); |
java的所有对象(基本类型除外)都是在堆上分配的方式。每个对象都含有一个引用计数器,当引用的计数值变为0时,立即释放对象。
在类的内部,变量定义的先后顺序决定了初始化的先后顺序,但是变量初始化先于任何方法调用。静态初始化只有在必要的时候才会进行。构造器实际上也是静态方法。
123456789101112131415161718192021222324252627282930313233 | class { public (int maker) { // TODO Auto-generated constructor stub System.out.println("Cup("+maker+")"); } void f(int maker){ System.out.println("f("+maker+")"); }}class Cups{ static Cup cup1; static Cup cup2; static Cup cup4 = new Cup(4); Cup cup3; static{ //这个块只调用一次 cup1 = new Cup(1); cup2 = new Cup(2); //cup3不能放到静态块中,因为它不是静态的变量 System.out.println("--------"); }}public class YiWei { public static void main(String[] args) { Cups.cup1.f(1); }} |
java中指定可变参数实质上是编译器为程序员将参数列表填充为数组。
枚举类型初体验
123456789101112 | public class YiWei { public static void main(String[] args) { Spiciness howHot = Spiciness.NOT; swtch(howHot){ //可以直接将枚举类型当作常数处理 } } public enum Spiciness{ NOT,MILD,MEDIUM,HOT,FLAMING }} |
访问控制权限控制
java包的命名规则是全部使用小写字母,包括中间的字也是如此。
类既不可以是private的,也不可以是protect。所以类权限只可以是public和包权限。如果不希望其他任何人对类有访问权限,可以对构造器设置权限为private.
Java中,关键字package,包的命名模式和关键字import,可以使你对名称完全控制,从而避免命名冲突。
复用类
复用类的方法有二:1 组合,2 继承。
toString();方法的调用机制:当编译器需要一个String而你却只有一个对象时,toString()方法就会被调用。
12345678910111213141516171819202122232425262728 | class { public (int maker) { // TODO Auto-generated constructor stub System.out.println("Cup("+maker+")"); } void f(int maker){ System.out.println("f("+maker+")"); }}class Cups extends { public Cups() { // TODO Auto-generated constructor stub super(0); System.out.println("Cups"); }}public class YiWei extends Cups{ public static void main(String[] args) { YiWei yiWei = new YiWei(); }}//输出:Cup(0)// Cups |
从上面的输出结果来看,在实例化导出类的构建导出类的对象的时候,在那之前,基类的初始化已经完成了。在继承基类的时候,若是基类没有无参构造器,那么导出类会被编译器强制要求显式调用一个父类的一个构造器。若父类有无参构造器的话,则不会强制要求调用父类构造器,应该编译器隐式调用那么无参构造器。
@override可以防止在不想重载的时候进行了重载。
final修饰的变量必须赋值(在域的定义处或者每个构造器中用表达式对final进行赋值)。final对于基本数值,表示数值恒定不变;对于对象,表示引用恒定不变。无法指向下一块空间。但是对象本身是可以改变的。Java并未提供使对象恒定不变的效果。
带有恒定初始值的final static基本类型全用大写字母命名,并且单词与单词之间用下划线隔开。
final允许在参数列表中使用,表示无法在方法中更改参数引用所指向的对象。
final方法使用的原因有二:一:把方法锁定,不允许任何继承修改它的含义;二是因为效率(已经不需要)。类中所有的private方法都隐式的指定为final,如果试图覆盖一个private方法(隐式指定final),似乎是奏效的,而且编译器不会报错,但显示的final方法会报错。
final类表示不允许继承。
多态
向上转型:把对象的引用视为对其基本类型的引用的做法被称作向上转型。
java是通过后期绑定(也叫动态绑定、运行时绑定)的方式,以便在运行的时候能判断对象的类型。
封装通过合并特征和行为来创建新的数据类型。实现隐藏则是通过将细节“私有化”把接口实现分离开来。而多态的作用是消除类型之间的耦合关系,将改变的事物与未变的事物分离开来。
非private,final方法才可以实现覆盖调用,而且只有方法可以是多态。如果一个方法是静态的,那它的行为也不具有多态性。构造方法是隐式的static方法。
构造器中唯一能安全调用的方法是基类的final方法,这些方法不会被覆盖。
接口
包含抽象方法的类叫做抽象类。
c++参数列表入栈顺序从右往左,Java的顺序正好相反。
内部类
匿名内部类
嵌套内部类
静态内部类
内部类的使用复杂,而且极其容易忘记,内部类可以完成很多不可思议的需求。
持有对象
Collection:Map,set,List
Set中相同元素只能存储一次,但又不像Map那样以键值对的形式存储。HashSet是最快的元素获取方式,如果强调顺序则可以使用TreeSet(将元素升序存储)或者LinkedHashSet(添加的顺序存储)。TreeSet没有实现Colloction中的所有接口。
Map也是三种Map:HashMap查找最快,TreeMap升序,LinkedHashMap按插入的顺序存。
List分为ArrayList和LinkedList。两者在某些操作的性能不同,而且LinkedList包含的操作也多于ArrayList。
ArrayList:长于随机访问元素,但是在中间进行插入和删除元素比较缓慢。
LinkedList:通过较低的代价在List中间进行插入和删除操作。但是在随机访问方面相对较慢。添加了可以使其用做栈,队列,或双端队列的方法。[element() = getFirst(),offer():将一个元素插到队尾,peek():获取栈顶元素,poll(),remove() = removeFirst()]
Stack:LinkedList具有能够直接实现栈的所有功能的方法,所以可以直接将LinkedList转换为Stack。[pop():移除并返回栈顶元素,peek(),push(),empty()]
Map可以返回他的键的Set,他是值的Collection,或者他的键值对的Set.Map可以通过entrySet()和values()产生Colloction.
Queue:队列是一个典型的先进先出的容器。在不需要Collection的方法的情况下,就可以拥有一个可用的Queue.它声明弹出的是等待时间最长的元素。
PriorityQueue:优先队列,它声明下一个弹出的是最需要的元素(最高的优先级)。comparator对象来改变排序(如果想在优先队列中使用自己的类,那么使用者就必须提供自己的Comparator)。
迭代器:遍历选择序列中的对象。通常被称为轻量级对象,创建他的代价小。Java中的Iterator只能单向移动。
ListIterator只能用于List的访问,可以双向移动。
通过异常处理处理错误
123456789101112131415161718192021222324252627282930313233343536 | public class Test {public static void main(String[] args) { String i = null; /*try { if (i == null) { throw new MyException("string is null"); } } catch (MyException e) { // TODO: handle exception e.printStackTrace(System.out);//以控制台输出的方式输出异常信息// for(StackTraceElement element:e.getStackTrace())//输出方法名这些// System.out.println(element.getClassName()+"."+element.getMethodName()+"."+element.getLineNumber()); }*/ f(); System.out.println(4); }static void f(){ try { System.out.println(1); throw new MyException("2"); } catch (MyException e) { // TODO: handle exception System.out.println(3); }finally { System.out.println(5); }}}输出:1354//在catch中再次抛出异常不能在外面捕获,应该也有作用域相关的问题。 |
当覆盖方法的时候,只能抛出基类方法的异常说明列出的那些异常。但这个异常限制对构造方法无用。派生构造器不能捕获基类构造器抛出的异常。一个出现在基类方法说明中的异常,不一定会出现在派生类中。不能通过异常说明来重载方法。
异常抛出之后,异常处理机制将搜寻参数与异常类型匹配的第一个处理程序,然后就不再查找。RuntimeException类型的异常属于错误,将被自动捕获,也称为不受检查异常。
新写一个异常时,抛出这个异常,通过捕获它的父类依然可以捕获到这个异常。catch Exception可以捕获到所有的异常。
所谓的把异常抛到更高层,就是在catch字句中,再次抛出。
12345678910 | try { System.out.println(1); throw new MyException("2"); } catch (MyException e) { // TODO: handle exception System.out.println(3); throw new NullPointerException(); } |
字符串
String对象是不可变的,String中每一个看起来会修改String值的方法,实际上都是创建了一个新的String。
字符串想加时,编译器自动引入了StringBuilder类。因为它更高效。
append(a+”:”+c)这样的句子,编译器会另外创建一个StringBuilder来处理括号里的字符串操作。
StringBuilder是线程不安全的,StringBuffer线程安全,因此开销也更大。
正则表达式:
-?:要找一个数字,可能有个-号在最前面;
d:描述整数,\:插入一个普通的斜杠;
+:一个或多个之前表达式。eg:-?d+:最前面可能有一个负号,后面跟着一位或者多位数字;
[abc]= a|b|c 包含a,b,c的任意字符
[a-zA-Z]
[abc[ijk]] = a|b|c|i|j|k
s:空白符
S:非空白符
d = [0-9]
D = [^0-9]
w = [a-zA-Z0-9]
W = [^a-zA-Z0-9]
XY:X跟在Y的后面
X|Y:X或者Y
(X):捕获组
G:一个匹配的结束
Split():将字符串从正则表达式匹配的地方切开。
类型信息
事实证明,复习是十分重要的,可以发现很多被忽略掉的事情,例如:昨晚考了一道题,只包含0和1的数,按以前的习惯来讲吧,把数转换为String之后,循环判断这位是不是0或者1,其实还挺麻烦的,但是我想到昨天才看的正则表达式,我觉得这样是不是更简单一点,然后弄了个正则表达式(第一次写不是很熟练,尝试了几下才写出来),少了一层循环(时间复杂度减少,我很自豪,哈哈哈)。
罢了,言归正传。泛型这一大难题又在我眼前。
泛型最常用到是在容器类里面,就是Map,List,Set这样的类里面。例如:
12 | List<Integer> list = new ArrayList<>(); |
我们也用在自己写的类中:
1
123456789101112131415161718192021 | public class Me <T>{ T t; public void set(T t) { this.t = t; } public void print(){ System.out.println(t); }}//调用public class PuTong { public static void main(String[] args) { Me<String> me = new Me<>(); me.set("we are Chinese."); me.print(); }} |
当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。泛型方法可以存在在不是泛型类中:
123456789101112 | public class PuTong { public static <T> void print(T t) { System.out.println(t); } public static void main(String[] args) { print(1); print("str"); print(0.25f); }} |
泛型擦除:在使用泛型时,任何具体的类型信息都被擦除了,唯一知道就是在使用一个对象,在泛型代码内部,无法获得任何有关泛型参数类型的信息。List
边界:
泛型类型参数将擦除到它的第一个边界。
如果是普通的泛型就相当于将它擦除为Object,若是