李刚老师的《疯狂Java讲义》(第三版)是我的启蒙作品,前前后后一共看了3遍,每次都有新的认识。
接下来的一段时间就将一些经典的知识记录下来。
1.计算机高级语言的执行方式分为编译型和解释型,前者运行高效但不能跨平台(C,C++,Object-C),后台可以跨平台但效率不高(Ruby,Python)。Java比较特殊,先编译生成.class,再在JVM中解释。
2.垃圾回收机制:依靠垃圾回收算法,何时回收对Java程序员而言具有透明性,因此要养成良好的习惯——对于不需要的对象,不要引用他们。(在堆中进行回收)
3.Java是强类型语言:所有类变量必须先声明后使用,指定类型的变量只能接受与类型相匹配的值。
4.Java支持两种类型:
- 基本类型:布尔类型、数值类型
- 引用类型:类、接口、数组。
5.强制类型转化:造成溢出时,之前一直觉得19.745会变成19(实在汗颜),其实是转换成二进制后再进行截取。
6.常量池:在编译期被确定,并已被保存在.class文件中的一些数据。包括类、方法、接口中的常量,也包括字符串常量。
7.switch语句:控制表达式的数据类型只能是byte,short,char,int,枚举,String.
8.break和continue可以通过标签跳到指定的循环层
public class BreakTest2 { public static void main(String[] args) { outer: for (int i = 0 ; i < 5 ; i++ ) { for (int j = 0; j < 3 ; j++ ) { System.out.println("i的值为:" + i + " j的值为:" + j); if (j == 1) { break outer; } } } } }
9.栈和堆:
- 当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自动销毁。因此,所有在方法中定义的局部变量都是放在栈内存中;
- 当我们在程序中创建一个对象是,这个对象将被保存到运行是数据区中,以便反复利用,这个运行是数据区就是堆内存。堆内存中的对象不会因方法结束而销毁,就算方法结束后,这个对象还可能被另一个变量所引用,则这个对象不会销毁,只有当一个变量没有任何引用变量引用它,那么系统的垃圾回收器才会在合适的时候回收它;
10.Arrays:Java8增加的工具类,在java.util包下,支持并发编程
// 定义一个a数组 int[] a = new int[]{3, 4 , 5, 6}; // 定义一个a2数组 int[] a2 = new int[]{3, 4 , 5, 6}; // a数组和a2数组的长度相等,每个元素依次相等,将输出true System.out.println("a数组和a2数组是否相等:" + Arrays.equals(a , a2)); // 通过复制a数组,生成一个新的b数组 int[] b = Arrays.copyOf(a, 6); System.out.println("a数组和b数组是否相等:" + Arrays.equals(a , b)); // 输出b数组的元素,将输出[3, 4, 5, 6, 0, 0] System.out.println("b数组的元素为:" + Arrays.toString(b)); // 将b数组的第3个元素(包括)到第5个元素(不包括)赋为1 Arrays.fill(b , 2, 4 , 1); // 输出b数组的元素,将输出[3, 4, 1, 1, 0, 0] System.out.println("b数组的元素为:" + Arrays.toString(b)); // 对b数组进行排序 Arrays.sort(b); // 输出b数组的元素,将输出[0, 0, 1, 1, 3, 4] System.out.println("b数组的元素为:" + Arrays.toString(b)); }
11.Java语言通过new关键字调用构造器。
12.this关键字:
- 让类中的一个方法,访问该类里的另一个方法或者实例变量。
- this所代表的对象是不确定的,但他的类是确定的。
- 在构造器中,this代表该构造器正在初始化的对象。
- 如果在某个方法中把this作为返回值,则可以多次连续调用同一个方法。
public class ReturnThis { public int age; public ReturnThis grow() { age++; // return this返回调用该方法的对象 return this; } public static void main(String[] args) { ReturnThis rt = new ReturnThis(); // 可以连续调用同一个方法 rt.grow() .grow() .grow(); System.out.println("rt的age成员变量值是:" + rt.age); } }
13.static:不要使用对象去调用static修饰的成员变量,要使用类去调用。
14.值传递:将实际参数的副本传入方法内,参数本身不会受到任何影响。
15.形参可变的方法:定义方法时,在最后一个形参的类型后增加三个点(...)。多个参数值被当成数组传入。与传入数组相比,形参可变更加简洁,但一个方法只能有一个可变形参。
public class Varargs { // 定义了形参个数可变的方法 public static void test(int a , String... books) { // books被当成数组处理 for (String tmp : books) { System.out.println(tmp); } // 输出整数变量a的值 System.out.println(a); } public static void main(String[] args) { // 调用test方法 test(5 , "疯狂Java讲义" , "轻量级Java EE企业应用实战"); } }
16.递归算法:一个方法调用自身,一定要向已知方向递归。
17.局部变量:形参(方法签名中定义的变量),方法局部变量(在方法内定义),代码块局部变量(在代码块内定义),局部变量不属于任何类或者实例,它总是保存在其所在方法的栈内存中。
18.封装:类的成员变量不直接暴露,而是通过方法实现操作和访问,以便于在方法中添加一些限制条件
19.高内聚:尽可能把模块的内部数据、功能实现细节隐藏在模块内部独立完成,不允许外部直接干预;低耦合:仅暴露少量的方法给外部使用。
20.静态导入:JDK1.5增加的导包方法,用于导入包内的静态成员 import static package...
21.构造器:
- 构造器是创建Java对象的重要途径,但这个对象并不是完全由构造器负责创建的。
- 子类的构造器必定会调用其父类的构造器(没有super和this时默认调用无参的构造器)。
22.方法重写(覆盖)原则:
- 两同:方法名、参数列表相同
- 两小:子类方法返回值<=父类方法返回值
- 一大:子类方法访问权限>=父类方法访问权限
23.当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存。
24.引用变量类型:
- 编译时类型:由声明该变量时使用的类型决定
- 运行时类型:由实际赋给该变量的对象决定
- 编译时类型和运行时类型不同时,就出现了所谓的多态
- 引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法(可用强制类型转换解决问题)。
25.向上转型:把一个子类对象直接赋值给父类引用变量;强制类型转换:把一个父类对象赋给子类引用变量
26多态:
- 相同类型的变量,调用同一个方法时呈现出多种不同的行为特征叫做多态
- 成员变量不存在多态,总是调用父类的值
27.instanceof:判断前面的对象是否是后面的类,或者其子类、实现类的实例
String str="str"; //true System.out.println(str instanceof Object); Object obj=new Object(); //false System.out.println(obj instanceof String);
28.继承与组合:
- 继承代表"is a",组合代表"has a"
- 创建子类对象,系统会为其父类所定义的实例变量分配内存空间,因此继承和组合在系统开销上没有太大的区别
29.初始化块:
- 初始化块在构造器之前执行(编译后初始化块的内容会还原到构造器中)
- 静态初始化块在普通初始化块之前执行
30.包装类:包装类的实例可以与数值类型直接比较
31.toString:自定义类时,尽量重写类的toString方法,便于输出实例的值
32.==与equals:
- 对于引用类型,只有二者指向同一个对象,==才会等于true
- String类型情况如下,编译时确定的数据在常量池中,运行时生成的数据在堆内存中
// s1直接引用常量池中的"疯狂Java" String s1 = "疯狂Java"; String s2 = "疯狂"; String s3 = "Java"; // s4后面的字符串值可以在编译时就确定下来 // s4直接引用常量池中的"疯狂Java" String s4 = "疯狂" + "Java"; // s5后面的字符串值可以在编译时就确定下来 // s5直接引用常量池中的"疯狂Java" String s5 = "疯" + "狂" + "Java"; // s6后面的字符串值不能在编译时就确定下来, // 不能引用常量池中的字符串 String s6 = s2 + s3; // 使用new调用构造器将会创建一个新的String对象, // s7引用堆内存中新创建的String对象 String s7 = new String("疯狂Java"); System.out.println(s1 == s4); // 输出true System.out.println(s1 == s5); // 输出true System.out.println(s1 == s6); // 输出false System.out.println(s1 == s7); // 输出false
33.重写equals的条件:
- 自反性:x.equals(x)=true
- 对称性:若x.equals(y)=true,则y.equals(x)=true
- 传递性:若x.equals(y)=true,y.equals(z)=true,则x.equals(z)=true
- 一致性:只要x.equals(y)=true且x,y不变,无论调用多少次结果都不变
- 对任何不适Null的x,x.equals(null)=false 6.equals相同,则hashcode相同
34.null类型的实例可以访问类的静态方法和静态变量,在底层是通过该实例的类去访问的
35.final:
- final变量不是不能被赋值,而是不能被改变
- final变量必须由程序员显示的指定初始值
- final修饰引用变量时,引用地址不可以改变,对象可以改变
- final修饰的方式不能被重写,但是可以被重载
36.宏变量:定义final变量时就为该变量指定了初始值,而且可以在编译时就确定下来,编译器会把程序中所有用到改变量的地方直接替换成该变量的值(进入常量池)
37.不可变类:
- private final修饰所有成员变量
- 只有getter没有setter
38.缓存池:先进先出缓存实例,重写了equals和hsahcode
class CacheImmutale { private static int MAX_SIZE = 10; // 使用数组来缓存已有的实例 private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE]; // 记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例 private static int pos = 0; private final String name; private CacheImmutale(String name) { this.name = name; } public String getName() { return name; } public static CacheImmutale valueOf(String name) { // 遍历已缓存的对象, for (int i = 0 ; i < MAX_SIZE; i++) { // 如果已有相同实例,直接返回该缓存的实例 if (cache[i] != null && cache[i].getName().equals(name)) { return cache[i]; } } // 如果缓存池已满 if (pos == MAX_SIZE) { // 把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置。 cache[0] = new CacheImmutale(name); // 把pos设为1 pos = 1; } else { // 把新创建的对象缓存起来,pos加1 cache[pos++] = new CacheImmutale(name); } return cache[pos - 1]; } public boolean equals(Object obj) { if(this == obj) { return true; } if (obj != null && obj.getClass() == CacheImmutale.class) { CacheImmutale ci = (CacheImmutale)obj; return name.equals(ci.getName()); } return false; } public int hashCode() { return name.hashCode(); } } public class CacheImmutaleTest { public static void main(String[] args) { CacheImmutale c1 = CacheImmutale.valueOf("hello"); CacheImmutale c2 = CacheImmutale.valueOf("hello"); // 下面代码将输出true System.out.println(c1 == c2); } }
39.默认方法:
- JDK1.8后新增的方法,在接口中一共有三种方法,抽象方法(abstract),类方法(static),默认方法(default),后两者必须有方法实现
- 使用接口的实例来调用默认方法
40.抽象类:抽象类作为多个子类的抽象父类,可以被当成系统实现过程的中间产品,这个中间产品已经实现了系统的部分功能,但这个产品依然不能当成最终产品,必须由进一步的完善,
41.内部类:
- 提供更好的封装
- 内部类成员可以直接访问外部类的私有数据
- 匿名内部类适用于创建只需要一次使用的类
- 局部内部类和匿名内部类不是类成员
- 包含内部类的类被称为外部类
42.非静态内部类:
- 非静态内部类对象里保存了一个外部类的引用
- 外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在
- 不允许在非静态内部类定义静态成员
43.静态内部类:
- 静态内部类可以包含静态成员和非静态成员
- 静态内部类是外部类的类相关,静态内部类对象寄生在外部类的类本身中,只持有外部类的类引用,没有外部类的对象引用
44.内部类的使用:
- 尽量使用静态内部类(调用简单)
- 局部内部类没有什么卵用
- 非静态内部类的构造器必须由其外部类的对象来调用
public class SubClass extends Out.In { //显示定义SubClass的构造器 public SubClass(Out out) { //通过传入的Out对象显式调用In的构造器 out.super("hello"); } }
45.匿名内部类:
- 必须继承一个父类或者实现一个接口
- 不能是抽象类
- 不能定义构造器
- JDK1.8以前被匿名内部类访问的局部变量必须使用final修饰,1.8以后会自动加上final修饰
46.枚举类:可以使用枚举类来替代静态final常量
public class Enum { public enum Season{ spring,summer,fall,winter; } public static void main(String[] args) { Season season=Season.spring; System.out.println(season); switch (season){ case spring: System.out.println("spring"); break; case summer: System.out.println("summer"); break; case fall: System.out.println("fall"); break; case winter: System.out.println("winter"); break; } }
47.垃圾回收:
- 只负责回收对象,不负责物理资源
- 程序无法精确控制垃圾回收的运行
- 在回收对象前,会调用对象的finalize方法尝试让其获得新的引用以复活
- finalize方法何时被调用具有不确定性
public class FinalizeTest { private static FinalizeTest ft = null; public void info() { System.out.println("测试资源清理的finalize方法"); } public static void main(String[] args) throws Exception { // 创建FinalizeTest对象立即进入可恢复状态 new FinalizeTest(); // 通知系统进行资源回收 // System.gc(); //① // 强制垃圾回收机制调用可恢复对象的finalize()方法 // Runtime.getRuntime().runFinalization(); //② System.runFinalization(); //③ ft.info(); } public void finalize() { // 让tf引用到试图回收的可恢复对象,即可恢复对象重新变成可达 ft = this; } }
48.引用类型:
- 强引用(最常见的引用)
- 软引用(当系统内存空间不够时会被回收)
- 弱引用(比软引用级别更低,当系统垃圾回收机制运行时,无论内存够不够都会被回收)
- 虚引用(没什么用)