Java

入门

基础

1: jdk/bin:

2:Java程序总是从main方法开始执行。Java入口程序规定的方法必须是静态方法,方法名必须为main,括号内的参数必须是String数组。

3:一个Java源码只能定义一个public类型的class,并且class名称和文件名要完全一致;

4:注释

5: 基本数据类型

6:引用类型

7:常量

final修饰,全大写。一般在构造方法中初始化final字段

8:自动推断

var sb = new StringBuilder();

9:移位

10:转型后超过了整型能表示的最大范围,将返回整型的最大值: 2147483647

四舍五入:对浮点数加上0.5再强制转型

11:三元运算b ? x : y会首先计算b,如果btrue,则只计算x,否则,只计算y。此外,xy的类型必须相同,因为返回值不是boolean,而是xy之一。

12: Java在内存中总是使用Unicode表示字符,占用两个字节。(Java 11及以后拉丁文一个字符使用一个字节存储,非拉丁文才用两个及以上字节存储)

13:字符串可以用"""..."""表示多行字符串(Text Blocks),总是以最短的行首空格为基准,去掉多行字符串前面共同的空格。

14:字符串不可变

15:数组变量可以指向不同对象,数组本身是引用类型:

int [] x=new int[3]; x=new int [5];原先数组并未改变,只是指向了另一个数组。

16: 字符串数组

相较于基本数据类型数组,修改字符串数组元素的值并非修改内存中的值,而是新开辟一个字符串对象,并将索引指向新的字符串对象。

17:数组元素可以是值类型(如int)或引用类型(如String),但数组本身是引用类型;

18:格式化输出

占位符说明
%d格式化输出整数
%x格式化输出十六进制整数
%f格式化输出浮点数
%e格式化输出科学计数法表示的浮点数
%s格式化字符串

格式化输入:nextInt等不读取回车,会把回车留给下一个输入。

19:应用类型判等

要避免NullPointerException错误,利用短路运算符&&if (s1 != null && s1.equals("hello")),可以把一定不是null的对象"hello"放到前面:例如:if ("hello".equals(s))

流程控制

1:串联if else,大于判断从大到小,小于判断从小到大。

2:switch

等价于多个串联判等if else,计算结果必须是整型、字符串或枚举类型;switch语句是比较“内容相等”,调用的是equals。

数组

1:Arrays

Arrays.sort():对数组排序实际上修改了数组本身,对于基本类型数组,改变元素的存储顺序,对于引用类型数组,不改变存储顺序,改变的是索引指向的对象。排序对值类型排序改变的是实际数据,对引用类型排序改变的是索引与元素间的对应关系。

2:多维数组的每个数组元素长度都不要求相同;打印多维数组可以使用Arrays.deepToString()

OOP

方法

1:隐含的变量this,它始终指向当前实例,当前实例是指调用该方法的对象。

2:可变参数实际就是一个数组

3:基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。

引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方。

注意传递的变量是直接引用string还是间接引用string

直接传递一个string 变量:外界重新赋值是重新创建一个对象,并把变量指针指向新的对象,并不改变原先对象的值,所以修改外部string值不会改变对象内string值,因为对象内string仍然指向旧的对象。

间接传递一个string变量:如传递一个string 数组,一个包含string字段的对象,通过函数传递的是数组或者对象的引用,外界重新赋值是重新创建一个对象,并把变量索引指向新的对象,此时外部和内部的索引都发生改变,string值同步变化。

直接传递一个基本类型参数,是将值复制后传递给函数,内外独立变化。

间接传递一个基本类型变量:如传递一个int 数组,一个包含int字段的对象,通过函数传递的是数组或者对象的引用,外界重新赋值是直接修改对象值,此时外部和内部的同步发生改变。

构造方法

1:构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。

2:创建对象实例的时候,按照如下顺序进行初始化:

构造方法的代码由于后运行,字段值最终由构造方法的代码确定

3:一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)

super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName

方法重载

1:参数列表不同(参数个数,类型,顺序),重载方法返回值类型应该相同

继承

1:子类无法访问父类的private字段或者private方法,可以自动获得了父类的所有protected、public字段和方法,父类private变量的初始化可以通过子类构造方法里面使用super(…)实现,严禁定义与父类重名的字段。

2:任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

3:protected关键字可以把字段和方法的访问权限控制在继承树内部。

4:要某个class被final修饰,那么任何类都不可以从该class继承。final class Person{}

Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称

5:把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting),向上转型实际上是把一个子类型安全地变为更加抽象的父类型,因为子继承自父类,因此它拥有父类的全部功能。该对象只能调用父类已有方法,最终执行的是子类方法。Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

6:instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类.(p instanceof Student)

7,向上转型:Person p = new Student(),(Persion)student,因为子类含有父类全部非private字段和方法,可以正常转换。转换后p只能调用persion类的方法,无法调用Student特有方法,并且调用父类方法时实际是执行子类重写方法,Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。向上转型,屏蔽各个子类实现差异,便用调用。

8,向下转型:把一个父类类型强制转型为子类类型,Person p = new Student();(Student)p,只有在父类变量所指的实例为子类实例时才可向下转型。否则无法转换,因为子类功能比父类多,多的功能无法凭空变出来。

9:继承是is关系,组合是has关系。

因为StudentPerson的一种,它们是is关系,而Student并不是Book。实际上StudentBook的关系是has关系。具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例。

10:

18:重载:方法名相同,方法参数必须不相同,方法返回值可同可不同。虽然方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。不可构建只有返回值不同的重载方法。

多态

1:多态:

2:final修饰符有多种作用:

3:面向抽象编程的本质就是:

接口

1:interface,就是比抽象类还要抽象的纯抽象接口,接口的字段只能是public static final类型。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样),抽象方法就是为了被重写所以不能使用private关键字修饰!接口也是数据类型,适用于向上转型和向下转型。

抽象类和接口的对比如下:

 abstract classinterface
继承只能extends一个class可以implements多个interface
字段可以定义实例字段不能定义实例字段,可以有静态字段
抽象方法可以定义抽象方法可以定义抽象方法
非抽象方法可以定义非抽象方法可以定义default方法
构造器有构造器没有构造器
权限管理public、protected、默认、privatepublic、private

一个interface可以继承自另一个interfaceinterface继承自interface使用extends,它相当于扩展了接口的方法。

2:一般来说,公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象。

3:defailt方法可以有方法体,

实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。不推荐过度使用,如果你大量使用default方法在你的应用接口中,将很快意识到他没有真正精简代码。因为不能在接口中提炼default里重复的代码到一个新的普通方法,这与以精简代码为目的的default关键字相冲突。只能由实现类的实例访问。

default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。抽象方法就是为了被重写所以不能使用private关键字修饰。它永远不能是静态的或私有的。

4,“类优先” 原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时,如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略,当该类未重写该方法,将会调用父类中的方法。

如果一个接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),将产生接口冲突,必须在当前实现类中Override该方法,否则编译器会因为不知道应该调用哪一个接口中的default方法而报错,重写接口中default方法后,编译器会执行重写后的方法。如果都是默认方法,也可以手动指定调用哪个默认方法:InterfaceName.super.methodName();

5,静态方法

实现者不能继承和重写它们,适合于提供实用方法,有助于避免实现类中的糟糕实现带来的不希望的结果。Java 接口静态方法仅对接口方法可见,但是和其他静态方法一样,静态方法是接口的一部分,只能在Interface类上调用静态方法,而不能在实现此Interface的类上调用静态方法:InterfaceName.staticMethod();✔,ImplementClass.staticMethod();

接口的静态方法和实现类自身定义的同名静态方法并不冲突,可以通过接口名或类名调用他们,互不影响。

6,私有方法/私有静态方法

可以让多个默认方法/静态方法调用共享一个私有方法/私有静态方法,改善接口内部的代码可重用性,私有接口方法将允许它们共享代码,并且避免把这个私有方法暴露给该接口的实现类。私有方法和私有静态方法不可被实现该接口的类或继承该接口的接口调用或重写。私有方法(private method)和私有静态方法(private static method)的提出都是对jdk8提出的default和public static method的补充。

7,引入默认和静态方法扩展了接口的功能,jdk8开始引入的Lambda语法以及Stream API,都是在接口层实现的。

继承关系

1,Java不支持多继承,主要是为了避免多继承会让语言本身变得复杂(像C++),效率也会降低。而接口可以提供多继承的大多数好处,同时避免多重继承的复杂性和低效性。

2,菱形继承关系,即两个派生类继承同一个基类,同时两个派生类又作为基本继承给同一个派生类。这种继承形如菱形,故又称为菱形继承。菱形继承主要有数据冗余和二义性的问题。

 

由于最底层的派生类继承了两个基类,同时这两个基类有继承的是一个基类,故而会造成最顶部基类的两次调用,会造成数据冗余及二义性问题。在Assistant的对象中Person成员会有两份。getName()方法无法知道该调用谁的name,执行出错。

3,继承与组合

4,避免菱形问题

使用接口(Interface)实现多重继承的功能,同时避免菱形问题。可以通过指定某个类必须实现哪些方法,但不需要在接口中定义这些方法的具体内容。一个类无论实现几个接口,即使各个接口中有同名方法,最终调用的是实现类覆写的方法,不用纠结具体使用哪个接口中的方法,因为接口中根本没有定义方法的实体,只定义了规范。

Java是不允许“实现多继承”,不允许继承多个类。但是Java支持“声明多继承”,语序实现多个接口。

5,Python实现了多继承,通过方法解析顺序(MRO)解决多继承中的同名冲突问题。MRO是采用C3算法(不同情况下,可表现为广度优先,也可表现为深度优先),搜寻到的第一个方法,或者变量进行调用。

静态字段和静态方法

1:实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段,对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例,在Java程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段只是因为编译器可以根据实例类型自动转换为类名.静态字段来访问静态对象。推荐用类名来访问静态字段。可以把静态字段理解为描述class本身的字段,构造方法可以正常访问静态变量。

2:用static修饰的方法称为静态方法。调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数,静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,可以访问静态字段和其他静态方法,通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。静态方法常用于工具类和辅助方法,如Math.random()、Arrays.sort()

3:因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型。int MALE = 1;(默认:public static final)

4:包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系,导包最终指向类 ,java.util会导入util下的类,不会导入zip包下的类。没有定义包名的class,它使用的是默认包,非常容易引起名字冲突,因此,不推荐不写包名的做法,编译后的.class文件也需要按照包结构存放。import时,可以使用*,表示把这个包下面的所有class都导入进来(但不包括子包的class),import static的语法,它可以导入可以导入一个类的静态字段和静态方法。

5:位于同一个包的类,可以访问包作用域的字段和方法。不用publicprotectedprivate修饰的字段和方法就是包作用域。

6:Java编译器最终编译出的.class文件只使用完整类名,因此,在代码中,当编译器遇到一个class名称时:

如果按照上面的规则还无法确定类名,则编译报错。

编译器会自动帮我们做两个import动作:

作用域

1:

2:使用局部变量时,应该尽可能把局部变量的作用域缩小,尽可能延后声明局部变量。一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同

3:

 publicprotecteddefaultprivate
类内部(直接访问成员)
类内部(通过对象访问成员)
本包(通过对像访问成员)
子类(和父类同包,直接访问成员)
子类(和父类异包,直接访问成员)
子类(和父类同包,通过父类对象访问成员)
子类(和父类异包,通过父类对象访问成员)
外部包(通过对像访问成员)

两层权限控制:类(public、default)+字段/方法(public 、protected 、default 、private)

内部类

1:成员内部类、局部内部类、匿名内部类和静态内部类

classpath和jar

1:classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class

假设classpath.;C:\work\project1\bin;C:\shared,当JVM在加载abc.xyz.Hello这个类时,会依次查找:

如果JVM在某个路径下找到了对应的class文件,就不再往后继续搜索。如果所有路径下都没有找到,就报错。没有设置系统环境变量,也没有传入-cp参数,那么JVM默认的classpath.,即当前目录,在IDE中运行Java程序,IDE自动传入的-cp参数是当前工程的out目录和引入的jar包。

2:jar可以把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件,jar文件本质为一个.zip文件,jar包结构要和导包的路径相一致,不能加入多余上层路径。jar包里的第一层目录,不能是out,而应该是pkg0pkg1。原因是pkg0.Class0必须按pkg0/Class0.class存放,而不是out/production/pkg0.Class0.class

jar包还可以包含一个特殊的/META-INF/MANIFEST.MF文件,MANIFEST.MF是纯文本,可以指定Main-Class和其它信息。JVM会自动读取这个MANIFEST.MF文件,如果存在Main-Class,我们就不必在命令行指定启动的类名:java -jar hello.jar

模块

1:把一堆class封装为jar仅仅是一个打包的过程,而把一堆class封装为模块则不但需要打包,还需要写入依赖关系,并且还可以包含二进制代码(通常是JNI扩展)。此外,模块支持多版本,即在同一个模块中可以为不同的JVM提供不同的版本

2:

module-info.java:写入用到的包

 

创建模块

按需定制打包jre

JRE自身的标准库已经分拆成了模块,只需要带上程序用到的模块,其他的模块就可以被裁剪掉

权限管理

class的这些访问权限只在一个模块内有效,模块和模块之间,例如,a模块要访问b模块的某个class,必要条件是b模块明确地导出了可以访问的包。

核心类

字符串和编码

1:Unicode编码需要两个或者更多字节表示,UTF-8编码,它是一种变长编码,用来把固定长度的Unicode编码变成1~4字节的变长编码,Java的Stringchar在内存中总是以Unicode编码表示。

如果传入函数的对象有可能改变,我们需要复制而不是直接引用。如果直接使用参数的引用,当外部代码不可信,这就会造成安全隐患。

字符串常量池

1,字符串不变性

2,加入常量池

3,StringTableintern方法与Java中的HashMap的实现相似,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。

JDK7后常量池被重新定位到堆中。这意味着不再受限于单独的固定大小内存区域。并且如果常量不再被引用,那么JVM是可以回收它们来节省内存,因此常量池放在堆区可以更方便和堆区的其他对象一起被JVM进行垃圾收集管理。

4,拼接:常量池可使用javac Main.java;javap -verbose Main验证

5,使用 intern 方法后可以节约堆内存,但为了在常量池中匹配字符串,在时间上有了一些增长,属于用时间换空间。

StringBuilder

1,StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象,对于普通的字符串+操作,并不需要我们将其改写为StringBuilder,因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory的操作。在运行期,StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。

2,进行链式操作的关键是,定义的方法会返回this,这样,就可以不断调用自身的其他方法。

3,String、StringBuffer、StringBuilder

String是字符串常量,而StringBuffer和StringBuilder是字符串变量。由String创建的字符内容是不可改变的,而由StringBuffer和StringBuidler创建的字符内容是可以改变的。StringBuffer是线程安全的,而StringBuilder、和String是非线程安全的。

虽然String、StringBuffer和StringBuilder都是final类,它们生成的对象都是不可变的,而且它们内部也都是靠byte实现的,但是不同之处在于,String类中定义的byte数组是final的,而StringBuffer和StringBuilder都是继承自AbstractStringBuilder类,它们的内部实现都是靠这个父类完成的,而这个父类中定义的byte[组只是一个普通是私有变量,可以用append追加。

String要设计成不可变:字符串常量池的需要,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象,假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象。允许String对象缓存HashCode:Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中,字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存。安全性:String被许多的Java类(库)用来当做参数, 假若String不是固定不变的,将会引起各种安全隐患。

包装类

1:

所有的包装类型都是不变类:public final class Integer {}

因为Integer是引用类型,必须使用equals()比较

数据的存储和显示要分离。

JavaBean

1,private实例字段,public方法来读写实例字段。读写方法名分别getXyz()setXyz(),但boolean字段的读方法一般命名为isXyz()

JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输

把一组对应的读方法(getter)和写方法(setter)称为属性(property):只读属性,只写属性

gettersetter也是一种数据封装的方法。

枚举

1,让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,

2,enum常量本身带有类型信息,不可能引用到非枚举的值,因为无法通过编译,不同类型的枚举不能互相比较或者赋值,因为类型不符。

3,使用enum定义的枚举类是一种引用类型,但是enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较

4,编译后的enum类和普通class并没有任何区别,它被编译器编译为final class Xxx extends Enum { … }

记录类

1,自动创建private final变量、构造方法、与变量名同名的get方法、覆写toString()equals()hashCode()方法。

 

BigInteger和BigDecimal

1,对BigInteger做运算的时候,只能使用实例方法:

new BigInteger("12").add(new BigInteger("1234"))

2,可使用longValueExact()等方法保证结果准确:new BigInteger("12").longValueExact()

3,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。

工具类

异常处理

异常

1:Error表示严重的错误,程序对此一般无能为力,Exception则是运行时的错误,它可以被捕获并处理

Exception又分为两大类:RuntimeException以及它的子类;非RuntimeException(包括IOExceptionReflectiveOperationException等等)

捕获异常

1,Java规定:

2:只要是方法声明的Checked Exception,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在main()方法中捕获,不会出现漏写try的情况。这是由编译器保证的。main()方法也是最后捕获Exception的机会

3:如果没有发生异常,就正常执行try { ... }语句块,然后执行finally。如果发生了异常,就中断执行try { ... }语句块,然后跳转执行匹配的catch语句块,最后执行finally。某些情况下,因为方法声明了可能抛出的异常,所以可以不写catch,只使用try ... finally结构。不推荐捕获了异常但不进行任何处理。

4: 合并处理异常,也可以匹配多个非继承关系的异常。:catch (IOException | NumberFormatException e)

抛出异常

1: 如果当前方法可能抛出checked Exception,则必须在方法内捕获,或者在方法头声明上抛;如果是runtime exception则不强制要求捕获或者上抛。

2:如果一个方法捕获了某个异常后,又在catch子句中抛出新的异常,就相当于把抛出的异常类型“转换”了,为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息:

 

3:在catch中抛出异常,不会影响finally的执行。JVM会先执行catch抛出异常前的代码-> finally内容-> catch 中抛出异常,所以如果finally会抛出异常,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常

自定义异常:

1,自定义一个从RuntimeException派生的BaseException作为“根异常”,然后,派生出各种业务类型的异常。

其他业务类型的异常就可以从BaseException派生,自定义的BaseException应该提供多个不同参数类型的构造方法:

2,baseexception的子类抛出异常的时候,就可以选择父类合适的构造方法:super(String msg),super(Throwable cause)

NullPointerException

1:遇到NullPointerException,遵循原则是早暴露,早修复,严禁使用catch来隐藏这种编码错误,成员变量在定义时初始化,使用空字符串"",空数组而不是null可避免很多NullPointerException,VM可以给出详细的信息告诉我们null对象到底是谁:添加启动参数-XX:+ShowCodeDetailsInExceptionMessages

断言

1:断言条件预期为true。如果计算结果为false,则断言失败,抛出AssertionError,导致程序结束退出,因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段,对于可恢复的程序错误,不应该使用断言。使用assert语句时,还可以添加一个可选的断言消息:assert x >= 0 : "x must >= 0";断言失败的时候,AssertionError会带上消息x must >= 0,更加便于调试。

2,要执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)参数启用断言

3,assert condition : "msg";断言条件预期为true。如果计算结果为false,则断言失败,带上消息抛出AssertionError,导致程序结束退出。

Commons Logging

1:Commons Logging:三层接口

定义了6个日志级别:

2:在实例方法中引用Logfinal Log log = LogFactory.getLog(getClass());,子类可以直接使用该log实例

3:如果在静态方法中引用Logstatic final Log log = LogFactory.getLog(Main.class);

log4j

1: log4j:底层实现

通过不同的Appender把同一条日志输出到不同的目的地(Console,File,Socket,jdbc),通过Filter来过滤哪些log需要被输出,哪些log不需要被输出,通过Layout来格式化日志信息

log4j2.xml

反射

Class类

1:反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

2:除了int等基本类型外,Java的其他类型全部都是class(包括interface),class(包括interface)的本质是数据类型(Type),JVM同时为每一种基本类型如int也创建了Class,通过int.class访问

3:JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例(包括class,interface,基本数据类型。不是只有引用类型可以被Class类实例化,基本数据类型也可以被Class类实例化),并关联起来,JVM持有的每个Class实例都指向一个数据类型(classinterface),一个Class实例包含了该class的所有完整信息,包括类名、包名、父类、实现的接口、所有方法、字段等,通过Class实例获取class信息的方法称为反射(Reflection)

4:用instanceof不但匹配指定类型,还匹配指定类型的子类,而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。通常情况下,我们应该用instanceof判断数据类型,因为面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个class的时候,我们才使用==判断class实例。

5:JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载,能在运行期根据条件加载不同的实现类。

获取字段

1:Class类提供了以下几个方法来获取字段:

调用方法

1:Class类提供了以下几个方法来获取Method

2,使用反射调用方法时,仍然遵循多态原则:即总是调用作为参数的实际类型的覆写方法(如果存在,如果不存在覆写,仍然调用父类方法)

构造方法

1,通过Class实例获取Constructor的方法如下:

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

调用非publicConstructor时,必须首先通过setAccessible(true)设置允许访问。

获取继承关系

1,父类

2,返回当前类直接实现的的interface,并不包括其父类实现的接口

对所有interfaceClass调用getSuperclass()返回的是null(接口不存在父类,只有实现的接口),获取该接口的父接口要用getInterfaces()

3,两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()

动态代理

1,可以在运行期动态创建某个interface的实例,没有实现类但是在运行期动态创建了一个接口对象的方式,动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,并不存在可以直接实例化接口的黑魔法。

注解

1:注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”,注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定,注解的本质是加入额外的功能,使用注解可以大大减少重复代码,比如进行字段检查,要在多个重载的构造方法和set方法里面进行检查,对于不同类中同类型的字段也要进行相同的检测,代码冗余,服用度低,并且检查的规则被写死,灵活度低。如果使用注解,直接在字段定义处加上注解,即可实现字段检查,并且检查的标准可作为参数传入注解,灵活可变。

使用注解

1:Java的注解可以分为三类:

第一类是由编译器使用的注解,例如:@Override:让编译器检查该方法是否正确地实现了覆写;这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。主要由编译器使用

第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能,旨在加载时起作用。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。仅保存在class文件中,它们不会被加载进JVM,主要由底层工具库使用

第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,一直起作用,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。

定义注解

1:定义一个注解时,还可以定义配置参数,配置参数必须是常量。

配置参数可以包括:

注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值

2:使用@interface语法来定义注解(Annotation),它的格式如下:

3:元注解:可以修饰其他注解:

使用@Target可以定义Annotation能够被应用于源码的哪些位置,@Target(ElementType.XXXX)

@Retention定义Annotation的生命周期,如果@Retention不存在,则该Annotation默认为CLASS,通常我们自定义的Annotation都是RUNTIME,所以务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解:

使用@Inherited定义被该注解修饰的类的子类是否可继承父类的Annotation@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效。

4:定义

处理注解

1,注解定义后也是一种class,读取注解,需要使用反射API

2,判断某个注解是否存在于ClassFieldMethodConstructor

3,使用反射API读取Annotation:

4,获得变量值:

int type = report.type();

5 :读取注解

 

6 : 使用注解

检查逻辑完全是我们自己编写的,JVM不会自动给注解添加任何额外的逻辑。