学习廖大神的Java基础教程:https://www.liaoxuefeng.com/wiki/1252599548343744
好记性不如烂笔头,记下要点
java笔记:
推荐书籍:Java编程思想;effective java;
1 Java规定,某个类定义的public static void main(String[] args) 是Java程序的固定入口方法,因此,Java程序总是从main方法开始执行。Java入口程序规定的方法必须是静态方法,方法名必须为main,括号内的参数必须是String数组。
2 一个Java源码只能定义一个public类型的class,并且class名称和文件名要完全一致;
使用javac可以将.java源码编译成.class字节码;
使用java可以运行一个已编译的Java程序,参数是类名。
3 在Java语言中,线程是一种特殊的对象, 它必须由Thread类或其子(孙)类来创建。通常有两种方法来创建线程:其一,使用型构为Thread(Runnable)的构造子类将一个实现了Runnable接口的对象包装成一个线程,其二,从Thread类派生出子类并重写run方法,使用该子类创建的对象即为线程。值得注意的是Thread类已经实现了Runnable接口,因此,任何一个线程均有它的run方法,而run方法中包含了线程所要运行的代码。线程的活动由一组方法来控制。Java语言支持多个线程的同时执行,并提供多线程之间的同步机制(关键字为synchronized)。
4 在方法内部,语句才是真正的执行代码。Java的每一行语句必须以分号结束;
Java有3种注释,第一种是单行注释,以双斜线开头,直到这一行的结尾结束:
// 这是注释…
而多行注释以/星号开头,以/结束,可以有多行:
/*
这是注释
blablabla…
这也是注释
/
还有一种特殊的多行注释,以/开头,以/结束,如果有多行,每行通常以星号开头:这种特殊的多行注释需要写在类和方法的定义处,可以用于自动创建文档
/
- 可以用来自动创建文档的注释
- @auther liaoxuefeng
*/
5 在Java中,变量必须先定义后使用,在定义变量的时候,可以给它一个初始值。
基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:
整数类型:byte,short,int,long
浮点数类型:float,double
字符类型:char
布尔类型:boolean
除了上述基本类型的变量,剩下的都是引用类型。例如,引用类型最常用的就是String字符串
定义变量的时候,如果加上final修饰符,这个变量就变成了常量:常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误;
计算机内存的最小存储单元是字节(byte),一个字节就是一个8位二进制数,即8个bit。一个字节是1byte,1024字节是1K,1024K是1M,1024M是1G,1024G是1T。
┌───┐
byte │ │
└───┘
┌───┬───┐
short │ │ │
└───┴───┘
┌───┬───┬───┬───┐
int │ │ │ │ │
└───┴───┴───┴───┘
┌───┬───┬───┬───┬───┬───┬───┬───┐
long │ │ │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┘
┌───┬───┬───┬───┐
float │ │ │ │ │
└───┴───┴───┴───┘
┌───┬───┬───┬───┬───┬───┬───┬───┐
double │ │ │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┘
┌───┬───┐
char │ │ │
└───┴───┘
6 var关键字
有些时候,类型的名字太长,写起来比较麻烦,这个时候,如果想省略变量类型,可以使用var关键字;编译器会根据赋值语句自动推断出变量的类型;
7 变量的作用范围
在Java中,多行语句用{ }括起来。很多控制语句,例如条件判断和循环,都以{ }作为它们自身的范围;在语句块中定义的变量,它有一个作用域,就是从定义处开始,到语句块结束。超出了作用域引用这些变量,编译器会报错。定义变量时,要遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域.
8 整数运算
Java的整数运算遵循四则运算规则,可以使用任意嵌套的小括号。四则运算规则和初等数学一致;整数的数值表示不但是精确的,而且整数运算永远是精确的,即使是除法也是精确的,因为两个整数相除只能得到结果的整数部分:
要特别注意,整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果
Java还提供了++运算和–运算,它们可以对一个整数进行加1和减1的操作:
9 位运算
位运算是按位进行与、或、非和异或的运算>>>>>> & | ~ ^
10 强制类型转换
强制转型使用(类型),例如,将int强制转型为short;
int i = 12345;
short s = (short) i; // 12345
11 三元运算符
Java还提供一个三元运算符b ? x : y,它根据第一个布尔表达式的结果,分别返回后续两个表达式之一的计算结果
12 Java的数组有几个特点:
数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false;
数组一旦创建后,大小就不可改变。
也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小
13 输出
println是print line的缩写,表示输出并换行。因此,如果输出后不想换行,可以用print()
14 基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。
15 由于构造方法是如此特殊, 所以构造方法的名称就是类名。 构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句。
可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分
16 继承是面向对象编程中非常强大的一种机制; Java使用extends关键字来实现继承;通过继承,子类自动获得了父类的所有字段,严禁定义与父类重名的字段!Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。继承有个特点,就是子类无法访问父类的private字段或者private方法;
这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问;因此,protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问;
super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName。如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
继承是is关系,组合是has关系。
17 多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法 一个实际类型为Student,引用类型为Person的变量,调用其run()方法,调用的是Person还是Student的run()方法?运行一下上面的代码就可以知道,实际上调用的方法是Student的run()方法。因此可得出结论:Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。
18 final
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override:
如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承
对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改;
19 如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:
class Person {
public abstract void run();
}
把一个方法声明为abstract,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的,所以,Person类也无法被实例化。编译器会告诉我们,无法编译Person类,因为它包含抽象方法。必须把Person类本身也声明为abstract,才能正确编译它。我们无法实例化一个抽象类。
因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。
20 注意区分术语:Java的接口特指interface的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。
21在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。还有一种字段,是用static修饰的字段,称为静态字段:static field。实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例:
推荐用类名来访问静态字段。可以把静态字段理解为描述class本身的字段.
静态方法
有静态字段,就有静态方法。用static修饰的方法称为静态方法。调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数
因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段
22 如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。
把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
23 jar包
如果有很多.class文件,散落在各层目录中,肯定不便于管理。如果能把目录打一个包,变成一个文件,就方便多了。
jar包就是用来干这个事的,它可以把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件,这样一来,无论是备份,还是发给客户,就简单多了。jar包实际上就是一个zip格式的压缩文件,而jar包相当于目录。如果我们要执行一个jar包的class,就可以把jar包放到classpath中:
那么问题来了:如何创建jar包?
因为jar包就是zip包,所以,直接在资源管理器中,找到正确的目录,点击右键,在弹出的快捷菜单中选择“发送到”,“压缩(zipped)文件夹”,就制作了一个zip文件。然后,把后缀从.zip改为.jar,一个jar包就创建成功。
24 程序入口
一般有@PostConstruct 可以全局搜索PostConstruct(快捷键 Ctrl+shift+F)找到各个服务的入口
(利用了注解,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解))
25 异常处理
捕获异常
捕获异常使用try…catch语句,把可能发生异常的代码放到try {…}中,然后使用catch捕获对应的Exception及其子类;
Java的try … catch机制还提供了finally语句,finally语句块保证有无错误都会执行。
所有异常都可以调用printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法。
捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息。
26 注解
注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”
Java的注解可以分为三类:
第一类是由编译器使用的注解,例如:
@Override:让编译器检查该方法是否正确地实现了覆写;
@SuppressWarnings:告诉编译器忽略此处代码产生的警告。
这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。
第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)
27 反射
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。
正常情况下,如果我们要调用一个对象的方法,或者访问一个对象的字段,通常会传入对象实例:
但是,如果不能获得Person类,只有一个Object实例,怎么办?所以,反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
除了int等基本类型外,Java的其他类型全部都是class(包括interface)。仔细思考,我们可以得出结论:class(包括interface)的本质是数据类型(Type)。无继承关系的数据类型无法赋值;
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。(大写的Class是一个类名,特殊的类名)
这种通过Class实例获取class信息的方法称为反射(Reflection)。
如何获取一个class的Class实例?有三个方法:
方法一:直接通过一个class的静态变量class获取:Class cls = String.class;
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String s = “Hello”;
Class cls = s.getClass();
方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class cls = Class.forName(“java.lang.String”);
JVM总是动态加载class,可以在运行期根据条件来控制加载class。
能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有Method信息;
对实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法;为了调用非public方法,我们通过Method.setAccessible(true)允许其调用
28 泛型
泛型是一种“代码模板”,可以用一套代码套用各种类型。
泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>:
ArrayList strList = new ArrayList();
T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList;
这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。
泛型还可以定义多种类型。例如ava标准库的Map<K, V>就是使用两种泛型类型的例子。它对Key使用一种类型,对Value使用另一种类型。
Java语言的泛型实现方式是擦拭法(Type Erasure)。
所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。