目录
为什么正规代码的for循环都是++count不是count++
静态(static):
static可以用来修饰变量或方法以及部分类
静态变量:
- 被直接储存在类中,在类被初始化的时候就创建一块内存来保存这个变量。
- 所有类的实例(对象)公用这块内存。
- 静态变量可以直接通过类名来引用,不需要新建对象再引用。比如说输出语句System.out.println()中的out就是System类的一个静态变量。我们每次都是直接引用而非创建一个类的对象再引用
静态方法:
- 静态方法中只可以引用静态的变量或方法。比如说如果有一个普通的int变量被定义在类中,那么这个变量将无法被静态方法引用
静态内部类:
- 内部类可以被static修饰。(主类或辅助类不行)。静态内部类可以借助主类在其他类中直接被调用。语法就是 主类名.内部类名 m = new 主类名.内部类名()
- 静态内部类可以有非静态的变量或方法。这些变量或方法和普通的变量或方法是一样的。在内部类中可以直接非静态方法中用。在内部类外需要先创建对象再用
- 静态内部类不能调用主类的非静态方法或变量
总得来说,静态的东西和这个类本身共存亡,普通的东西和实例(对象)共存亡
java在使用一个类的时候会经历几个过程:加载,链接,初始化,使用,卸载。在链接阶段,java虚拟机(JVM)中会开辟一些内存来储存这些静态方法或属性或私有方法。在初始化阶段才会真正实现new对象的过程(具体的类的加载过程后面会总结)
内部类与辅助类
一个java文件中可以定义不止一个类,根据他们被定义的位置不同将他们称为内部类和辅助类
内部类
- 被定义在主类中(就是内个和文件同名的类中)。
- 内部类可以被public、private等修饰
- 内部类不是一个独立的类,在主类以外的类中想要调用这个内部类的话需要借助外部类来调用
- 如果这个内部类是静态的,那么 主类名.内部类名 来定义这个内部类。如果内部类不是静态的,那么需要通过主类的实例(对象)来定义这类,此时语法比较麻烦
- 内部类可以被主类直接引用,他的可被调用范围和主类是一样的。
- 非静态内部类可以直接访问外部类的所有成员变量和方法,包括私有成员。(因为非静态内部类持有主类的隐式引用)
- 但外部类不能直接引用内部类的方法或变量,需要创建实例对象
辅助类
- 被定义在主类外。
- 辅助类不能被public、private修饰。
- 辅助类是一个独立的类,可以直接在当前包中被其他类使用
- 主类使用辅助类时需要指明使用的辅助类是在哪个包下的,定义语法见下面
- 由于没有被public修饰,因此只能在当前包(文件夹)中被引用。在其他包中引用不了
我们这里定义了两个wuhu,一个内部类一个辅助类。可以看到new辅助类的对象时需要标明他是在thread_lock包中的
package thread_lock;
public class test {
public static void main(String[] args) {
test test = new test();
test.test1();
}
public void test1(){
wuhu neibuwuhu = new wuhu();
neibuwuhu.qifei();
thread_lock.wuhu waibuwuhu = new thread_lock.wuhu();
waibuwuhu.qifei();
}
// 内部类
public class wuhu{
public void qifei(){
System.out.println("内部起飞");
}
}
}
// 辅助类。
class wuhu{
public void qifei(){
System.out.println("辅助起飞");
}
}
库和包的区别
- 库(library)以.jar的形式出现,是我们可以导入调用的内容
- 包(package)以文件夹的形式出现。一个项目(project)中可以有很多包,每个包中的.java文件需要在开头声明自己在哪个包中。如果.java文件直接被定义在src(source)文件夹下,则不需要声明自己在哪个包中。库中的文件夹也可以被称为包,比如说long包
package thread_lock;
表示当前文件在thread_lock包下
java9引入了模块了来更好地管理库中文件夹。与之前单纯地用文件夹(包)相比,多了一个module-info.java
文件用于定义包之间的关系
jar包
jar文件本质上是一个zip文件,并且其中通常包含一个清单文件META-INF/MANIFEST.MF,描述这个jar文件的内容和属性(清单文件)
try catch
代码会执行try块中的语句,如果发生错误则立即中止并执行相应(根据catch的参数来确定)catch中的语句。如果没有其他问题就继续执行try catch后的语句
抛出异常
如果一个异常没有被捕获,那么他会沿着调用栈逐层向上传递,如果这个异常一直没有被捕获,那么他最终会被JVM接管,通常JVM会打印异常栈跟踪然后终止抛出这个异常的线程
config
configuration的简称,翻译为配置
字符和字节
字符(character)是“,”等,是人类可读文字的最小单元
字节(byte)是一个八位的二级制数,如01100001,一个字节有八个比特(bit)。是二进制数据的基本单位(并非内存的最小单位,内存的最小单位是bit)
字符可以通过某些常用的编码方案转化为一个或多个字节(如ASCII就是一种编码方案)
'\r'和'\n'
'\r'是回车符,将指针移动至当前行的首部。ASCII值为13
'\n'是换行符,将光标移动到下一行。ASCII值为10
在windows系统中,需要'\r''\n'一起使用来代表完成换行行为(和MAC等系统略有不同)
创建接口的实现
在看书过程中经常会看到这句话。实际上这是“创建一个实现了这个接口的类的实例”的省略。
注意,如果是创建接口的实现,那么这个变量只能调用接口中有的属性或方法
加public,不加public和加private的区别
这里只考虑写代码上的区别,不考虑在多线程中泄露,发布等内容。
加public是整个项目都可以调用;不加public就是默认访问权限,只能在当前包(文件夹)中使用;加private就是只能在当前类中使用。
lambda表达式
在java8中出现
lambda表达式实际上就是重写接口中方法的一种简单形式。其语法为() ->{}括号中为接口方法中的输入参数,->为分隔符,{}为重写的方法内容。下面是一个具体的例子
new Thread(() -> {
int a = 1
});
这样子就避免了再写一个新的匿名内部类的麻烦。需要注意的是,为了简便,如果输入参数只有一个的话可以不写括号,如果没有输入参数的话可以只写括号。如果重写的方法只有一行语句并且不涉及新建局部变量的话可以只写一行并且不写大括号(就像if语句一样)。
重载
重载指的是在同一个类中对同一个方法名可以有很多不同的参数,这样对同一个方法名可以有很多不同的方法。最典型的就是构造方法,一般我们会有一个无参数的方法,然后会有一些有参数的方法,但这些都是方法名和类名一样的构造方法。需要注意的是重载与重写不同。重载是同一类中的事情,重写是子类重写父类的。
&运算(作为“与”)
a和b两个数做&运算(a&b或b&a),会先将两个数转为二进制。然后将两个数通过高位补0的方式对齐。如果两个数的二进制值在某一位同时为1,则结果取1,有一个为0就取0。比如对二进制数1001和0011,做&运算的结果为0001。这里的两个数要求为整数,即int,byte,short和long以及相应的封装类(Integer等)
&运算(作为布尔运算)
奖两个布尔类型转化为一个。作为结果来说和&&是一样的,但&&只有当第一个位真时才去判断第二个(短路运算符)。&会将两个都判断再输出。
接口中的default关键字
在java8中引入的关键字。在接口中,原本是不允许方法有方法体的。但是用default修饰之后就可以有方法体,并且不需要被重写。一个接口中可以有多个default方法
^运算:异或和
对于两个数a和b,求异或和就是先将他们两个转化为二进制,对于相应位置的数,相同取0不同取1,得到新的二进制数,然后将他转化为整数。
泛型
泛型是java中的一种特性。我们可以在接口和类的名字后跟<>,在<>中定义泛型 T。或者是泛型T作为方法的返回值或参数出现。理论上讲T可以被替换为任意的字母。在应用中除了T t外,还可能有另外两种写法:<? extends class>这种形式,这样表示这个泛型(我们用的是"?")一定要继承某一个类class。即这个泛型是class的子类(class本身也可以)。这里class是要具体给定的,比如Map、Object等。或者是<? super class>这种,这种表示泛型一定要是class的父类(class本身也可以)。或者直接用<?>,但这种只能在方法中作为参数用,比如 method(List<?> list)。但不能再类名中用。
泛型在编译的时候会被类型擦除,他的类型会被替换为他的上界。比如<? extends class>的上界就是class。如果是<T>形式定义的,那么他的上界默认为Object。此外,使用泛型还有一定的规则,比如不能实例化,不能创建泛型数组,基本数据类型不能用作泛型,比如List<int> list 是非法的,应该使用List<Integer> list。
为什么正规代码的for循环都是++count不是count++
++i是后置自增,i++是前置自增。一般来讲++i会更规范一点,一是符合习惯,而是可以避免一些不必要的开销(i++可能会生成额外的临时对象)
句柄
(Handle)其实就是引用的另一种说法,比如对于以下语句来说。myDog就是一个句柄,他其实只是一个变量,指向了Dog()对象对应的地址。java中由于不主动操作内存,因此其实并没有严格的句柄的概念,这个概念更多出现在C和C++中
Dog myDog = new Dog();
线程生命周期
关于多线程的内容见java并发编程实战-初阅读-持续更新
从大分类来讲线程有六种状态:new、Runnable(Running)、Blocked、Waiting、Timed Waiting和Terminated。
其中Blocked也可以叫Synchronized Blocked。Runnable是一个宏观状态,包括了Running和Ready状态。我认为从宏观来讲Blocked、Waiting和Timed Waiting可以归为一类,因为此时线程都处于某种意义上的暂停。
我们依次来看。
当一个线程被新建,他就进入了new状态,此时线程既没有启动也没有被分配CPU资源
如果调用了线程的start方法,此时线程进入Runnable状态,此时线程没有被分配CPU资源,也就是Runnable细分的Ready状态
当CPU分配给这个线程资源,线程就进入Running状态并且开始执行run方法。Running状态实际上就Runnable状态的一部分。
如果线程没有被阻塞,那么run方法中的内容被执行完之后线程就进入Terminated状态。此时线程不再占用CPU资源,但仍占用内存资源。不出意外的话他将等待被垃圾回收器回收
如果线程被阻塞的话,线程将进入“暂停”状态。如果线程需要等待其他线程来将他启动,那么是Waiting状态;如果需要等待一定的时间再启动,那么是Timed Waiting状态;如果他在等待别的程序释放锁,那么是Synchronized Blocked状态。
上面的暂停是我自己的理解,他包括Blocked、Waiting和Timed Waiting三种。
偷了一张图来标明各个状态的转换
对于上图,一个线程不能从Terminated回到Runnable或从Runnable回到new。线程池中的线程如果没有任务的话将会处于Waiting或Timed Waiting状态,具体是哪个状态取决于饱和策略。
将异常添加到方法签名
关于异常,我们要首先明确一点,异常可以分为受检异常和非受检异常。受检异常指内些会被编译器发现的异常,比如IOException或SQLException等,这些异常来自于外部原因;非受检异常不会被编译器发现,指运行时异常(RuntimeException及其子类)或错误(Error及其子类),这些异常来自于代码本身逻辑存在问题。一般认为非受检异常是程序员的问题,而非实际使用上可能出现的异常。换句话说,受检异常即使是大佬来了也不能保证避免,但如果发生非受检异常,那就是程序员纯菜
如果代码中有受检异常既没有被添加到方法签名也没有被捕获,将无法通过编译。
如果代码中有非受检异常,那么将可以通过编译,但运行时会出错。对于非受检异常,有没有添加到方法签名来说没有区别,都会在返回给调用他的函数。在方法签名中添加非受检异常唯一的好处就是增加代码可读性
abstract关键字
abstract用来修饰一个类或一个方法。当他修饰方法时,这个方法和接口中的方法一样,没有方法体。当他修饰类的时候,这个类称为抽象类。
抽象类可以和普通类一样有普通属性和方法,也可以有和接口中方法一样的抽象方法。这里需要区分一下这种抽象类和有default方法的接口。类用于建立is-a关系,接口用于建立can-do关系。一个类只能继承一个类(不管他是不是抽象的,都只能继承一个)但可以实现多个接口。
StringBuilder和StringBuffer,String的区别联系
这三个都用于创建字符串对象,我们从简单到复杂依次来说。
String是最基本的字符串对象,我们初学的时候认识到的就是它。String对象是不可变的,对String对象的内容进行任何改变都会导致系统新建一个String对象。因此如果对String对象有频繁的删改会导致性能浪费。但也正因为它固定,所以他天生是线程安全的。
StringBuilder是可变字符串对象,当对原对象进行操作时可以直接进行增添和改动,不会增加新的对象。但StringBuilder不是线程安全的,也就是说,如果有多个线程同时操作一个StringBuilder对象可能会出现问题。
StringBuffer也是可变字符串对象,他的API和StringBuilder几乎一。但他的方法都被Synchronized修饰了,也就是说他的方法都被加锁了,因此是线程安全的方法。但相比于StringBuilder也增加了性能开销
String底层是由一个被final修饰的char[]数组实现的,并且String类本身也是final的,因此它不能被修改或继承。而StringBuilder和StringBuffer是由char[]数组实现的,当字符数量超出后会进行扩容(通常是1.5倍)
ICMP和ARP
这两个都是互联网的协议,一个是Internet Control Message Protocol,一个是Address Resolution Protocol。他们都是OSI五层模型的网络层的协议。一个负责报告传输错误,一个用于获取MAC地址。两个协议的具体内容见计算机网络-入门版-持续更新
拷贝
拷贝分为浅拷贝和深拷贝。
浅拷贝和深拷贝都是新建一个变量,指向一个内存地址。为了方便起见我们假设现在有一个类A,其中有成员变量int a和Class b。a是基本数据类型,b是引用类型字段(比如对象,数组)。
如果对A进行浅拷贝得到B,那么B将a的值复制一份作为B的a属性,同时获取b的引用,将这个引用拿过来作为B的b。如果是浅拷贝的话,在A中改变b,B中的b也会改变。(A中改变b的话变得是b本身,而不是b指向的内存,但A中b和B中b是完全一样的,所以A中b变,B中b也会变)
如果对A进行深拷贝得到C,那么C将不止复制基本数据的值,也会新建一个变量指向b的内存(建立一份拷贝)。此时A中对b进行什么操作,都不会影响C中b。要注意的是,深拷贝是每一层都要拷贝,也就是说,如果Class b也还有一个对象属性Class c,那么对b进行拷贝的时候也要是深拷贝。
Object类自带一个浅拷贝方法(clone),这个拷贝方法是本地方法,即由c代码实现。如果要执行浅拷贝,直接调用这个方法就行。
如果要进行深拷贝的话则要重写这个clone方法,对其中的对象属性也进行拷贝。并且要求这个对象的clone方法也要重写为深拷贝方法
String的intern方法
当一个字符串调用intern方法时,首先会检查这个字符串的内容在字符串常量池中有没有出现过(java7之后字符串产量池在堆中)。如果存在的话,返回这个内容在常量池中的引用,如果不存在的话,会把这个对象的引用放到字符串常量池中(并不是在字符串常量池中创建一个新的对象!)。
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
在java7及之后的版本运行这段代码,第一个会返回true,第二个会返回false。“计算机软件”这个字符串在常量池中没有,因此调用intern时会把他放到常量池中并返回引用。而“java”这个字符串在创建虚拟机的时候就已经被新建到常量池中,因此intern会返回“java”在常量池中的引用,原本的str2则是java堆中的一个String对象,因此结果是false。
RFC文档
RFC是计算机网络领域中的一个概念。他是一个文档,规定了很多不同的互联网协议,比如HTTP,TCP,IP等等
Protect关键字
Protect是一个修饰访问范围的关键字,可以修饰属性或方法或一个类的内部类。但不能修饰一个普通的类,一个普通的类只能是没有修饰或是被public修饰。
被protect关键字修饰的变量或方法可以在以下两种情况种被访问:1、同一个包中的类。2、不同一个包中,如果一个类作为子类继承了这个类,那么这个子类也可以访问这个被protect修饰的内容。
finalize方法
在设计之初,finalize方法是为了帮助垃圾回收的一个方法,作为一个空方法被定义在Object类中,但是在实际中发现这个方法存在很多问题,因此从java9之后就被标记为不被建议使用。建议使用try-with-resources和AutoCloseable作为替代。