Java小知识&高级
2021-02-28 16:52:11 1 举报
AI智能生成
java的一些小知识
作者其他创作
大纲/内容
易错点
转型
基本数据类型除布尔类型可以互相转型,但引用类型不行,引用类型只能向上转型,没有关系的引用类型之间不能互相转型
三目表达式
三目表达式是一个表达式,它最后只能有一种数据类型,当两个类型不一样时会自动向上转型,而if-else不受此限制
数字的非常规写法
二进制
0bxxxx
0是数字,不是字母,下别的八进制,十六进制都是数字0
八进制
0xxxx
十六进制
0Xxxxx
e(E)
字母e(E)的前面是一个浮点数(整型会隐式转化为double),后面是一个整数。E之前的浮点数是系数
,而E之后的整数就表示10的多少次方,这个整数可以是负数。
,而E之后的整数就表示10的多少次方,这个整数可以是负数。
数字末尾加f(F)
表示转化为float型(java默认为doublt),如2.3E3F将double转化为float
数字末尾加l(L)
用大写L,因为小写L(l)和数字1容易混淆
表示为long类型,比如给long直接赋一个超出int范围的值在数字末尾加L(java默认为int)
数字末尾加d(D)
表示为doublt类型
java中的分结号: 下划线_
如1_000_000_000
从JDK1.7开始引入
下划线出现的位置随意,并非一定要每3位数字才能出现一个下划线
无论浮点数还是整数,数字当中都可以出现下划线
字符的非常规写法
'\uxxxx'
表示一个16进制数字 这种格式是unicode码的写法表示一个char字符
转义字符(escape character)
字母前面加上反斜杠(back slash)"\"来表示常见的那些不能显示的ASCII字符.称为转义字符
常见的转义字符
\b
退格(BS)
\f
换页(FF)
\n
换行(LF Line Feed):将当前位置移到下一行开头(line feed字面意思为移到下一行当前位置)
\r
回车(CR Carriage Return):移到本行开头
\t
水平制表(HT)
\v
垂直制表(VT)
\\ \' \''
\ ' "
\0
空字符(NULL)
\ddd
1到3位八进制数所代表的任意字符
\uhhhh
四位十六进制所代表的任意字符
ASCII码对照表
斜杠:"/" 与 反斜杠:"\" ,此处不可互换
扩展
① / 斜线, slash或又称为forward slash (前斜线), 原本是标点符号。 起源于古罗马,中世纪时用作逗号(而当时的双斜线//是被当作连接号的,后来演化成等号=)。
a) 英语里的斜线, 最常用的即是替代字符“or”表选择, 比如: Yes/No; 还有就是避免名字的冲突,比如美国的人口普查表中有"Assyrian/Chaldean/Syriac", 就是为了避免因Syriac名字不同叫法而产生的冲突或歧义,其实也是or的含义;
b) 算术; 英文里面称over, 比如: 123/456
c) 金钱表示。 $50/- 表示50美元上限, 后面没有了,防止有人添加修改。
d) 日期的表示
② \ backslash, 反斜线, 主要用于电脑中, 也正是对普通/斜线的借鉴。 操作系统(xNix), 电脑语言(C/C++, Perl),编码(部分Unicode)等都使用它。
a) 英语里的斜线, 最常用的即是替代字符“or”表选择, 比如: Yes/No; 还有就是避免名字的冲突,比如美国的人口普查表中有"Assyrian/Chaldean/Syriac", 就是为了避免因Syriac名字不同叫法而产生的冲突或歧义,其实也是or的含义;
b) 算术; 英文里面称over, 比如: 123/456
c) 金钱表示。 $50/- 表示50美元上限, 后面没有了,防止有人添加修改。
d) 日期的表示
② \ backslash, 反斜线, 主要用于电脑中, 也正是对普通/斜线的借鉴。 操作系统(xNix), 电脑语言(C/C++, Perl),编码(部分Unicode)等都使用它。
标识符
概念
为方法、变量或其他用户定义项所定义的名称,标识符可以有一个或多个字符
构成规则
标识符由数字(0~9)和字母(A~Z 和 a~z)、美元符号($)、下划线(_)以及 Unicode 字符集中符号大于 0xC0 的所有符号组合构成(各符号之间没有空格)
标识符的第一个符号为字母、下划线和美元符号,后面可以是任何字母、数字、美元符号或下划线
标识符命名时,切记不能以数字开头,也不能使用任何 Java 关键字作为标识符,而且不能赋予标识符任何标准的方法名。
占位符
移位运算
<<
所有位左移,低位补0
a << b 等价于 a * 2^b(注意数字溢出)
>>
所有位右移,最高位补符号位(及原来最高位是什么现在就补什么)
当a为正数时
a >> b 等价于 a / 2^b(小数部分舍去)
当a为负数时不一定
如: -5 >> 1 = -3
>>>
所有位右移,高位补零(及与符号无关)
java.util.Arrays.binarySearch:这里用(low + high) >>> 1代替(low + high) /2是非常正确的,首先是因为数组下标肯定不会是负数,另一方面如果low + high大于int最大值(溢出变为负数了)时,只有>>>1能保证结果正确。
注:虽然乘除可以用位移运算代替但位移运算效率并不高于乘除
关键字
protected
基类的 protected 成员是包内可见的,并且对子类可见(及父类的实例在子类不可见,子类继承的可见如下);
若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。
及不在一个包时,只有继承了的子类的内部的自己的实例可访问
序列化(serialize)
序列化:将对象写入到IO流中
反序列化:从IO流中恢复对象
意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
使用场景:所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。通常建议:程序创建的每个JavaBean类都实现Serializeable接口。
反序列化:从IO流中恢复对象
意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
使用场景:所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。通常建议:程序创建的每个JavaBean类都实现Serializeable接口。
Serializable接口是一个标记接口,没有任何方法。一旦实现了此接口,该类的对象就是可序列化的。
类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法。
ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:
public final void writeObject(Object x) throws IOException
上面的方法序列化一个对象,并将它发送到输出流。相似的 ObjectInputStream 类包含如下反序列化一个对象的方法:
public final Object readObject() throws IOException, ClassNotFoundException
ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:
public final void writeObject(Object x) throws IOException
上面的方法序列化一个对象,并将它发送到输出流。相似的 ObjectInputStream 类包含如下反序列化一个对象的方法:
public final Object readObject() throws IOException, ClassNotFoundException
transient关键字
被transient修饰的属性,在序列化对象的时候,这个属性不会序列化到指定的目的地中
Java 的标准约定是给序列化文件一个 .ser 扩展名
Exception&Error
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
比如说,你的代码少了一个分号,那么运行出来结果是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
异常发生的原因有很多,通常包含以下几大类:
比如说,你的代码少了一个分号,那么运行出来结果是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
异常发生的原因有很多,通常包含以下几大类:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
分为
Exception
检查性异常(Checked Exception)
最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略及编译器会提醒捕获
如:
- NoSuchFieldException
- NoSuchMethodException
- ClassNotFoundException
运行时异常
运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略,如ArithmeticException。
如:
- ArithmeticException
- ArrayIndexOutOfBoundsException
- IllegalArgumentException
错误
错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
Throwable
Error
Exception
IOException
RuntimeException
Java 程序通常不捕获错误。错误一般发生在严重故障时,出现Error的情况会造成程序直接无法运行(都无法运行了捕获没有任何意义),它们在Java程序处理的范畴之外。
Error 用来指示运行时环境发生的错误。
例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
Error 用来指示运行时环境发生的错误。
例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
使用try,catch,finally结构来捕获异常并执行对异常的操作;
一个方法抛出异常使用throws在方法签名和声明,在方法体内部使用throw,如throw new Remotexception();
一个方法抛出异常使用throws在方法签名和声明,在方法体内部使用throw,如throw new Remotexception();
Java中定义了两种类型的异常和错误
1)JVM(Java虚拟机) 异常:由 JVM 抛出的异常或错误。例如:NullPointerException 类,ArrayIndexOutOfBoundsException 类,ClassCastException 类。
2)程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException 类,IllegalStateException 类。
2)程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException 类,IllegalStateException 类。
Java注释
单行注释
//
文本注释
/* */
说明注释
说明注释允许你在程序中嵌入关于程序的信息。你可以使用 javadoc 工具软件来生成信息,并输出到HTML文件中。
说明注释,使你更加方便的记录你的程序信息。
说明注释,使你更加方便的记录你的程序信息。
以 /** 开始,以 */结束
javadoc 标签
文档注释
在开始的 /** 之后,第一行或几行是关于类、变量和方法的主要描述。
之后,你可以包含一个或多个各种各样的 @ 标签。每一个 @ 标签必须在一个新行的开始或者在一行的开始紧跟星号(*).
多个相同类型的标签应该放成一组。例如,如果你有三个 @see 标签,可以将它们一个接一个的放在一起。
之后,你可以包含一个或多个各种各样的 @ 标签。每一个 @ 标签必须在一个新行的开始或者在一行的开始紧跟星号(*).
多个相同类型的标签应该放成一组。例如,如果你有三个 @see 标签,可以将它们一个接一个的放在一起。
javadoc 输出什么
javadoc 工具将你 Java 程序的源代码作为输入,输出一些包含你程序注释的HTML文件。
每一个类的信息将在独自的HTML文件里。javadoc 也可以输出继承的树形结构和索引。
由于 javadoc 的实现不同,工作也可能不同,你需要检查你的 Java 开发系统的版本等细节,选择合适的 Javadoc 版本。
每一个类的信息将在独自的HTML文件里。javadoc 也可以输出继承的树形结构和索引。
由于 javadoc 的实现不同,工作也可能不同,你需要检查你的 Java 开发系统的版本等细节,选择合适的 Javadoc 版本。
Class对象
RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中
Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里;
当我们new一个新对象或者引用静态成员变量时,类加载器首先会检查这个类的Class对象是否已被加载,如果还没有加载,默认的类加载器就会先根据类名查找.class文件,然后经java的安全机制检测确保其没有被破坏并且不包含不良Java代码,完全没有问题后就会被动态加载到内存中,最后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。
当我们new一个新对象或者引用静态成员变量时,类加载器首先会检查这个类的Class对象是否已被加载,如果还没有加载,默认的类加载器就会先根据类名查找.class文件,然后经java的安全机制检测确保其没有被破坏并且不包含不良Java代码,完全没有问题后就会被动态加载到内存中,最后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。
类的加载过程
加载(Loading)--> 验证(verification)--> 准备(preparation)--> 解析(resolution)-->初始化(initialization)
|<- - - - - - - - - - - - - - - - 链接(Linking) - - - - - - - - - - - - - ->|
|<- - - - - - - - - - - - - - - - 链接(Linking) - - - - - - - - - - - - - ->|
加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件或动态代理,网络获取等方式创建一个Class对象
链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。
初始化:类加载最后阶段,若该类具有超类(super),则对其进行初始化,执行静态初始化器和静态初始化成员变量。
静态常量存储到了一个称为NotInitialization类的常量池中,在以后对静态常量的引用实际都转化为对NotInitialization类的自身常量池的引用,这也就是引用编译期静态常量不会触发类初始化的重要原因
链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。
初始化:类加载最后阶段,若该类具有超类(super),则对其进行初始化,执行静态初始化器和静态初始化成员变量。
静态常量存储到了一个称为NotInitialization类的常量池中,在以后对静态常量的引用实际都转化为对NotInitialization类的自身常量池的引用,这也就是引用编译期静态常量不会触发类初始化的重要原因
关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化:
使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含静态常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。
使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。
当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类
当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化
使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含静态常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。
使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。
当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类
当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化
Class.forName方法
Class.forName()方法的调用将会返回一个对应类的Class对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象(及Object.getClass())
Class字面常量
Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量如下:
Class clazz = Gum.class;
Class clazz = Gum.class;
这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用于接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助
基本数据类型的包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下:
- boolean.class = Boolean.TYPE;
- char.class = Character.TYPE;
- byte.class = Byte.TYPE;
- short.class = Short.TYPE;
- int.class = Integer.TYPE;
- long.class = Long.TYPE;
- float.class = Float.TYPE;
- double.class = Double.TYPE;
- void.class = Void.TYPE;
前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,我们获取字面常量的Class引用时,触发的是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。
泛化的Class对象
由于Class的引用总是指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。在Java SE5引入泛型后,使用我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但在编译期足以确保我们使用正确的对象类型。
注:向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期
类型转换
直接强制转换
Java SE5中新增一种使用Class对象进行类型转换的方式,调用Class里的cast方法
instanceof运算符与isInstance方法
instanceof是一个运算符,isInstance是Class类里的方法,他们等价都是检查左边对象是不是右边类或接口的实例化
反射(Reflection)
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。
Constructor类
反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的
Field类
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象
Method类
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法),同理,通过Class类中的方法获取Method对象
Array类
提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作进行取值和赋值
Class类中获取Constructor的主要方法
只能获取public的构造方法
getConstructor
getConstructors
可以获取任意访问权限的构造方法
getDeclaredConstructor
getDeclaredConstructors
Constructor类的主要方法
getDeclaringClass
getName
getGenericParameterTypes
getParameterTypes
toGenericString
getGenericParameterTypes 与 getParameterTypes 都是获取构成函数的参数类型,前者返回的是Type类型,后者返回的是Class类型,由于Type顶级接口,Class也实现了该接口,因此Class类是Type的子类,Type与 Class 表示类型几乎是相同的,只不过 Type表示的范围比Class要广得多而已
利用好Class类和Constructor类,我们可以在运行时动态创建任意对象,从而突破必须在编译期知道确切类型的障碍。
Java 5 新特征
泛型(Generics)
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型,及提供类型安全性
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。
比如:一个方法只要Number的子类,没有范型就需要通过if (instanceof) else if ......来转化成对应的Integer,Double等对应的类型,这时如果输入了一个非Number子类,如输入了String,就会达不到预期的效果,执行最后的else里的操作,如else里 throws一个RuntimeException,就会影响程序后续的运行,而范型不会出现这种情况,及下边的安全隐患。
对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。
那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
比如:一个方法只要Number的子类,没有范型就需要通过if (instanceof) else if ......来转化成对应的Integer,Double等对应的类型,这时如果输入了一个非Number子类,如输入了String,就会达不到预期的效果,执行最后的else里的操作,如else里 throws一个RuntimeException,就会影响程序后续的运行,而范型不会出现这种情况,及下边的安全隐患。
对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。
那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
泛型方法
该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
下面是定义泛型方法的规则:
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前。
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
有界的类型参数
限制那些被允许传递到一个类型参数的类型种类范围
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界 或者 跟super关键字,最后紧跟下界,有多个上界或下界用&连接
泛型类
在类名后面添加了类型参数声明部分,和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
类型通配符
类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List<String>,List<Integer> 等所有List<具体类型实参>的父类。
同上,要声明一个有界的类型参数,后跟extends关键字或super。
对于有范型声明的,如果要采用任意类型的范型使用<?>,因为不用<?>会丢失类型安全性
编程规约如下:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(在Java中,Type指: 类,接口,枚举,包括注解)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 无界通配符(表示不确定的java类型)
S、U、V - 2nd、3rd、4th types
Java 注解(Annotation)
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。注解其实是接口的另一种写法,当然在编译后也会生成.class文件
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。注解其实是接口的另一种写法,当然在编译后也会生成.class文件
内置的注解
作用在其他注解的注解(或者说 元注解)是:
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
- @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
作用在其他注解的注解(或者说 元注解)是:
- @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
- @Documented - 标记这些注解是否包含在用户文档中。
- @Target - 标记这个注解应该是哪种 Java 成员。
- @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类
从 Java 7 开始,额外添加了 3 个注解:
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
元注解
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
Annotation 的作用
Annotation 是一个辅助类,它在 Junit、Struts、Spring 等工具框架中被广泛使用。
我们在编程中经常会使用到的 Annotation 作用有:
1)编译检查
Annotation 具有"让编译器进行编译检查的作用"。
例如,@SuppressWarnings, @Deprecated 和 @Override 都具有编译检查作用。
例如,@SuppressWarnings, @Deprecated 和 @Override 都具有编译检查作用。
2) 在反射中使用 Annotation
在反射的 Class, Method, Field 等函数中,有许多于 Annotation 相关的接口。
这也意味着,我们可以在反射中解析并使用 Annotation。
这也意味着,我们可以在反射中解析并使用 Annotation。
3) 根据 Annotation 生成帮助文档
通过给 Annotation 注解加上 @Documented 标签,能使该 Annotation 标签出现在 javadoc 中。
4) 能够帮忙查看查看代码
通过 @Override, @Deprecated,@FunctionalInterface等,我们能很方便的了解程序的大致结构。
另外,我们也可以通过自定义 Annotation 来实现一些功能。
另外,我们也可以通过自定义 Annotation 来实现一些功能。
主干类
Annotation
Annotation 就是个接口。
"每 1 个 Annotation" 都与 "1 个 RetentionPolicy" 关联,并且与 "1~n 个 ElementType" 关联。可以通俗的理解为:每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n 个。
"每 1 个 Annotation" 都与 "1 个 RetentionPolicy" 关联,并且与 "1~n 个 ElementType" 关联。可以通俗的理解为:每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n 个。
ElementType
ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。
"每 1 个 Annotation" 都与 "1~n 个 ElementType" 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途。例如,若一个 Annotation 对象是 METHOD 类型,则该 Annotation 只能用来修饰方法。
"每 1 个 Annotation" 都与 "1~n 个 ElementType" 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途。例如,若一个 Annotation 对象是 METHOD 类型,则该 Annotation 只能用来修饰方法。
RetentionPolicy
RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。
"每 1 个 Annotation" 都与 "1 个 RetentionPolicy" 关联。
a) 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
b) 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
c) 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由JVM读入。
"每 1 个 Annotation" 都与 "1 个 RetentionPolicy" 关联。
a) 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
b) 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
c) 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由JVM读入。
语法
声明语法
注解不支持继承,不能使用关键字extends但声明注解必须使用@interface
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {}
上面的作用是定义一个 Annotation,它的名字是 MyAnnotation1。定义了 MyAnnotation1 之后,我们可以在代码中通过 "@MyAnnotation1" 来使用它。 其它的,@Documented, @Target, @Retention, @interface 都是来修饰 MyAnnotation1 的。下面分别说说它们的含义:
@interface
使用 @interface 定义注解时,意味着它继承了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
定义 Annotation 时,@interface 是必须的。
注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
定义 Annotation 时,@interface 是必须的。
注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
@Documented
类和方法的 Annotation 是不出现在 javadoc 中的。如果使用 @Documented 修饰该 Annotation,则表示它可以出现在 javadoc 中。
定义 Annotation 时,@Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc 中。
定义 Annotation 时,@Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc 中。
@Target(ElementType.TYPE)
ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定 Annotation 的类型属性。
@Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味着,MyAnnotation1 是来修饰"类、接口(包括注释类型)或枚举声明"的注解。
定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。
@Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味着,MyAnnotation1 是来修饰"类、接口(包括注释类型)或枚举声明"的注解。
定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。
@Retention(RetentionPolicy.RUNTIME)
RetentionPolicy 是 Annotation 的策略属性,而 @Retention 的作用,就是指定 Annotation 的策略属性。
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被虚拟机读取。
定义 Annotation 时,@Retention 可有可无。若没有 @Retention,则默认是 RetentionPolicy.CLASS。
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被虚拟机读取。
定义 Annotation 时,@Retention 可有可无。若没有 @Retention,则默认是 RetentionPolicy.CLASS。
内部语法
内部没有任何元素的注解称为标记注解(marker annotation)
public @interface DBTable {
String name() default "";
}
String name() default "";
}
声明一个String类型的name元素,其默认值为空字符,但是必须注意到对应任何元素的声明应采用抽象方法的声明方式(其实本质也就是抽象方法),同时可选择使用default提供默认值
注解支持的元素数据类型:基本数据类型,String,Class(特指Class类不是任意的引用类型),enum,Annotation,和上边类型的数组
动态参数(Varargs)
Varargs: variable number of arguments
到J2SE 1.4为止,一直无法在Java程序里定义实参个数可变的方法——因为Java要求实参(Arguments)和形参(Parameters)的数量和类 型都必须逐一匹配,而形参的数目是在定义方法时就已经固定下来了。尽管可以通过重载机制,为同一个方法提供带有不同数量的形参的版本,但是这仍然不能达到 让实参数量任意变化的目的。
有些方法的语义要求它们必须能接受个数可变的实参——例如著名的main方法,就需要能接受所有的命令行参数为实参,而命令行参数的数目,事先根本无法确定下来。
对于这个问题,传统上一般是采用“利用一个数组来包裹要传递的实参”的做法来应付(如main方法),这种做法可以有效的达到“让方法可以接受个数可变的参数”的目的,只是调用时的形式不够简单,varargs简化了调用时的传参形式
动态参数的实例:Java5以后主函数的形参可以写成String... args。
有些方法的语义要求它们必须能接受个数可变的实参——例如著名的main方法,就需要能接受所有的命令行参数为实参,而命令行参数的数目,事先根本无法确定下来。
对于这个问题,传统上一般是采用“利用一个数组来包裹要传递的实参”的做法来应付(如main方法),这种做法可以有效的达到“让方法可以接受个数可变的参数”的目的,只是调用时的形式不够简单,varargs简化了调用时的传参形式
动态参数的实例:Java5以后主函数的形参可以写成String... args。
调用时可以传入零个或多个实参
使用方法
只要在一个形参的“类型”后加上三个连续的“.”(即“...”,英文里的句中省略号),就可以让它和不确定个实参相匹配。
如:Integer... a
如:Integer... a
只有最后一个形参才能被定义成动态参数。因此,一个方法里只能有一个这样的形参。如果这个方法还有其它的形参,要把它们放到前面的位置上。
编译器会在编译时把这最后一个形参转化为一个数组形参,并在编译出的class文件里作上一个记号,表明这是个实参个数可变的方法,因此
这种写法等价于数组(方法体里可直接当数组对待,调用时也可传入数组),所以不能再为这个类定义一个和转化后的方法签名一致的方法如下方编译器会报错。
void sum (int... a){}
void sum (int[] a){}
这种写法等价于数组(方法体里可直接当数组对待,调用时也可传入数组),所以不能再为这个类定义一个和转化后的方法签名一致的方法如下方编译器会报错。
void sum (int... a){}
void sum (int[] a){}
@SafeVarargs
Java7加入的注解,在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。
Java 7 新特征
try-with-resources
旧的代码风格用Try-Catch-Finally管理资源吗,try中使用的资源需要被明确地关闭,这个体验有点繁琐。
Java7可在try的圆括号中申请资源,try括号内的资源会在try语句结束后自动释放(资源类必须实现java.lang.AutoCloseable接口,一般的文件、数据库连接等均已实现该接口,close方法将被自己主动调用)
Java 8 新特征
lambda表达式(闭包)
允许把函数作为一个方法的参数(函数作为参数传递进方法中)
特征
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
lambda转换的目标类型必须是接口
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量(或不声明final但隐性的具有 final 的语义),这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
在 Lambda 表达式当中不允许声明一个与外层局部变量同名的参数或者局部变量。
方法引用
They are compact, easy-to-read lambda expressions for methods that already have a name.
语法
构造器引用:Class::new,或者更一般的Class< T >::new
静态方法引用:Class::static_method
特定类的任意对象的方法引用:Class::method
特定对象的方法引用:instance::method
Stream
以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象
过程
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合(groupingBy)等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
转化为代码为:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
什么是Stream
Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等
聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。
Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
生成流
在 Java 8 中, 集合接口有两个方法来生成流:
stream() − 为集合创建串行流。
parallelStream() − 为集合创建并行流。
可执行的部分操作
forEach
map
filter
distinct
limit
sorted
collect
Collectors:
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串
count
统计
例:IntSummaryStatistics stats = numbers.stream().mapToInt(x -> x).summaryStatistics();
接口
default关键字
接口可以有实现方法,而且不需要实现类去实现其方法
之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
一个类实现了多个接口,且这些接口有相同的默认
方法,以下实例说明了这种情况的解决方法:
方法,以下实例说明了这种情况的解决方法:
1.创建自己的默认方法,来覆盖重写接口的默认方法
2.使用 super 来调用指定接口的默认方法
可以声明(并且可以提供实现)静态方法
函数式接口(Functional Interface)
有且仅有一个抽象方法,但是可以有多个非抽象方法的接口(并不是只有一个抽象方法的接口都是函数式接口(无注解但编译器treat it like functionInterface)如:comparable<T>,Iterable<T>)。
In literal that some of interface incidentally have only one abstract methodhowever they don't ment to be a function interface. Such
as comparable: it would be silly use lambda for comparable rather than Comparator The point of Comparable is to implement it inside the object being compared.
In literal that some of interface incidentally have only one abstract methodhowever they don't ment to be a function interface. Such
as comparable: it would be silly use lambda for comparable rather than Comparator The point of Comparable is to implement it inside the object being compared.
函数式接口可以被隐式转换为 lambda 表达式。
Java8新增function 函数式接口包
0 条评论
下一页