Java高级特性

String

String是Java中的字符串类型,字符串类型在内存中是一个不可变的对象。如果要对字符串对象进行修改,如果是较少的修改可以使用+运算符,Java会自动进行优化,但如果是频繁的修改就最好使用StringBuilder类,这个类可以方便高效地进行字符串修改操作。StringBuilder是线程不安全的,StringBuffer是线程安全的,但是安全的效率低
调用Object.toString()方法时,应注意方法体内的隐式数据转换,如果将本对象转换为String对象(也就是调用toString()方法),就会带来递归调用的问题。

格式化输出

  • System.out.printf()的格式化
    可以采用与c相同的格式化,如%d,%f等。
  • System.out.format()
    与printf一样。
  • Formatter类
    传递一个System.out的输出流给Formatter,比如PrintSteam等,然后使用format方法进行和printf一样的格式输出
  • String.format()提供一个返回String对象的格式化字符串输出方法

具体格式:
%[argument_index$][flags][width][.precision]conversion
argument_index:参数序号
flags:默认右对齐,-为左对齐。
width:域最小尺寸
precision:域最大尺寸,当为String时表示字符数量,当为浮点数时表示小数位数

正则表达式

正则表达式,就是基于某一种制定的规则,对字符串进行匹配。

基础

.表示任意字符,^表示一行的起始,$表示一行的结束,\b表示词的边界,\B表示非词的边界,\G表示前一个匹配的结束。
\d表示数字字符;\w表示单词字符;\s表示空白符;\W表示非单词字符,其他大写同理表示非XX。
Java的正则表达式中,\\表示反斜线,其后的字符将会被转义,其他转义字符与ASCII转义字符一样。
?表示0或1个字符,+表示一个或多个之前的表达式,A-B表示从A到B的范围(A,B为字符或数字)。
|表示前后两个表达式的“或”,^表示对表达式的否定,&&表示表达式的连接,[]中包括的子表达式是或的关系。
表达式可以用()包起来表示一个整体,

Java中正则表达式的使用

可以使用String的matches()方法,对正则表达式进行匹配,返回是否匹配的布尔值结果。
使用String的split()方法可以使用参数中的正则表达式,对字符串进行切分。
String的replaceFirst()replaceAll()方法可以根据正则表达式进行匹配替换

在Java中,还提供了更高效的java.util.regex包对正则表达式进行支持,具体的使用方法见文档:Java SE 1.8参考文档
其核心类为Pattern类,可以对提供的正则表达式进行compile()编译,加快匹配速度,匹配规则见类文档。compile()接受编译标记参数。
Pattern类提供了matcher()方法,该方法会返回一个Matcher对象,即匹配结果对象,该对象有find(),group(),split()等方法,对Matcher对象进行操作即可达到匹配然后处理的目的。

Java中正则表达式的“量词”

量词描述正则表达式吸收输入文本的方式,分为贪婪型,懒惰型和占有型。
贪婪型为尽可能多地匹配字符,懒惰型为匹配到结果即可结束,占有型为匹配时不保留中间状态,可以加快匹配的进行。

使用Scanner对Readable对象进行输入

Java中有Scanner类,可以对File,InputSteam,String或者是其他具有Readable接口的对象进行处理,从中按照需求,使用nextLine();nextInt();nextDouble()等方法自动获取流中的某种特定对象。
Scanner可以使用useDelimiter()方法自定义定界符,对输入流进行划分。
Scanner可以使用next()方法,输入正则表达式,进行自定义匹配。

枚举类型深入

基本enum特性

枚举类型可以通过values()方法获取所有元素,使用ordinal()方法返回某个元素的次序,使用compareTo()方法返回前后次序差值,还有equals(),==和hashCode()可以使用。

向enum添加新方法

enum可以视为一个类,除了不能继承以外,还可以向enum类中添加新方法,甚至是静态方法。可以对enum类的toString()等方法进行覆盖。

switch中的enum

在switch中只能使用整数值,而枚举类型具有整数值的次序,所以可以在switch中使用枚举类型。在case语句中,不必使用enum类型名来限定元素范围。
编译器会自动在创建的enum方法中添加values()方法。enum类继承自Enum类,如果将enum向上转型,那么将无法使用values()方法,只能通过getEnumConstants()方法获取所有元素。
enum虽然不能再继承其他类(因为已经继承了Enum),但是可以继承并实现其他接口,通过实现接口,可以扩展枚举类的功能并完成向上转型的抽象。enum还可以实现嵌套。

EnumSet

使用EnumSet,可以替代基于int的标志位的功能,表示某种开关信息。
EnumSet可以大量的of()方法,具体的方法都可以在JDK中获得信息,这是追求性能带来的现象。

EnumMap

EnumMap是一种特殊的Map,它的键来自enum,其速度很快,而且可以使用enum实例对在EnumMap中进行查找。enum中的每个键在EnumMap中都存在,如果没有使用put()方法存入值,那么对应的值就是null。

常量相关方法

Java允许开发成为enum实例编写方法,也就是针对于enum中的每一个元素,定义一个或多个方法,通过同样的接口为其赋予不同的行为。

基于enum的功能,可以便利地实现职责链,状态机等功能。还可以实现多路分发等功能。

类型信息

RTTI

运行时类型识别可以帮你在丢失一些信息后把信息重新找回来。
现在有一个Shape类,派生出circle/Square/Triangle三个子类,然后分别创建了一个对象,放进List中,此时就会发生向上转型(这时进行了RTTI),子类的额外信息仿佛消失了,当从List中拿回来的时候,编译器自动帮你把Object类型转换为Shape类型,但是更具体的类型就难以再找回来了,这时候RTTI就再次派上用场了。

Class对象

每一个类都有一个Class对象,包含了这个类的所有信息,存放在这个类的.class文件中。Java中类是动态加载到jvm中的,也就是用到了再加载,在加载过程中,Java虚拟机就会调用类加载器,检查这个类的class对象是否加载,如果没有加载,就会对其进行加载,然后使用Class对象创建类的所有对象。
static对象在类加载时进行初始化。
所有的Class对象都属于Class类,Class类具有一些静态方法,其中forName(String)方法可以根据类名,返回该类的Class对象的引用。
通过类的class对象,可以获取class对象(forName()),类名(getName()),接口等类信息(getInterfaces())和类的继承结构(getSuperclass())等。
通过Class对象的newInstance()方法,可以获得该类的一个对象,该类必须有默认构造器。
还可以通过类名.class获取类的Class对象(此时只是创建引用,没有进行初始化,初始化被惰性延迟到具体使用的时候),基本类型的包装器类属性和基本数据类型的Class对象是一样的。
Class对象是具体的类的对象,泛型类引用之间并不通用,并不是因为都是Class类的对象而可以互相指向。泛型类Class引用是具体的,但可以使用Class<?>来摆脱这种限制,或者是与extends关键词何用,达到Class<? extends Number>这种宽松的限制效果。
对于Class对象,可以使用Type.cast()方法进行转型。

类型转换前的检查

instanceof关键词可以在类型转换前对类型进行检查,判断对象是否是特定类型的实例,返回布尔类型值。isInstance()也可以达到同样的效果,并且可以使代码更加简洁。
BaseType.isAssignableFrom(cls)可以对一个类是否为本类的超类或接口进行判断。
instanceof和isInstance()对一个类是否为另一个类的子类进行了判断,将子类也视为父类的一种,而equals()和==仅考虑这个类是否严格地是另一个类。

通过类型信息和类型转换,用户可以绕开类开发者使用接口进行的解耦合的限制,调用本来不开放给用户的方法。

反射

反射可以动态地获得某个类的信息和类的方法,是运行时的类型检查。
使用Class对象的getMethods()和getConstructors()方法可以获得类的方法数组和构造器方法数组及其详细信息。
通过反射,你可以通过一个类的名称,获得类的属性、构造器和方法列表,使用构造器创建对象,使用get/set方法修改属性,使用invoke()方法调用方法。
使用反射实现代理模式,让类继承InvocationHandler接口,通过代理管理实际对象,可以将对被代理方法的调用交由代理进行转发,进而可以进行一些过滤和其他处理。

通过反射甚至可以突破Java对访问控制的限制,超权限地访问private对象。

空对象

空对象可以表示一种占位,在某处有一个空位,将来要被放置一个对象,但现在是空的。
在Java中,通过代理模式和DynamicProxy类,可以自动创建空对象。

泛型

泛型,就是不指定确切的类型,可以适应于许多类型,可以极大地扩展代码的适用性和灵活性,或者说就是“泛型”这种类型。

简单泛型

用<A,B,C>的方式,在需要指明类型的地方暂不指明类型,在真正使用该类对象时将类型的判断交由jvm,就是泛型最简单的使用。
泛型可以用于类/接口/内部类/匿名类和方法。

擦除

泛型会编译器自动进行类型推断,但是只对赋值操作有效,如果对将泛型方法调用返回的结果作为参数传递,编译器会将其视为Object然后报错,除非用<type>显式的声明该方法返回的类型。
在泛型代码内部,无法获得任何有关泛型参数类型的信息,泛型擦除了实际放入的实际类型的所有信息,只作为一个Object进行处理。

擦除的代价和补偿

在尖括号内部使用extends关键词,可以对泛型类型进行限制,只能在使用继承了关键词后的类的类作为泛型类型。
使用泛型创建数组时最好使用Array.newInstance()方法。使用类的isInstance()方法并传入一个类型的Class对象,可以对该类是否为一个类的对象进行判断。
可以用工厂方法创建泛型对象类型实例,通过一个工厂类,传递一个Class对象,使用newInstance()创建这个类型的新对象。
不可以创建泛型数组,只能通过ArrayList实现泛型数组的功能。因为数组关心其成员的具体类型,成员的类型是在创建时确定的,而泛型时运行时确定的。

边界

泛型的边界,就是通过extends关键词,使泛型被限定为某些类的继承类,如<T extends A&B&C>
对于数组,不会发生向上转型,只能储存数组设定的类型,而对于泛型容器来说,容器是可以持有泛型类型的对象的,但是如果确定了一个向上转型的持有关系,那这是一种“僵化”的关系,将没有对象可以同时满足两种类型检查进而放进容器,但是可以从容器中拿出来。
List<? extends A>表明具有任何从A继承的类型的列表。List<? super A>是超类型通配符,即表示泛型接受类A导出的所有类。<?>表示特定的任何类(虽然当前不知道是什么类),含义为当前泛型参数可以接受任何类型。通过?通配符捕获的泛型对象,将被转化为确切类型。

泛型的各种问题

  • 基本类型不能作为类型参数,只能使用其包装类(Java会自动装包拆包,但自动包装机制不能用于数组)。
  • 因为泛型的擦除机制,所以一个类不能实现同一个泛型接口的两种变体,因为他们实际上被擦除成了同一个接口,而一个接口不能在同一个类中被实现两次。
  • 将带有泛型类型的参数转型或者是instanceof将不会有任何效果,而如果出现了必须转型的对象无法正常转型,那就是必须得使用泛型类来进行转型,也不能直接转型到实际类型。
  • 当两个相同的参数列表全为泛型时,由于擦除,实际上他们是相同的,因此而不能产生重载,只能通过方法名进行区分。
  • 如果一个泛型接口实际上相当于对基类的方法进行了收窄,那么这将不能工作,收窄行为带来了冲突。基类劫持了接口。

自限定类型

像一个镜子一样,创建一个新类,这个类以继承自一个以自己的名字为参数的泛型类。也就是可以达到一个目的:基类用导出类代替其参数。
然后在继承关系中,就会产生一个要求:正在定义的类将被作为参数传递给基类。如果其基类与派生类没有保持同样的参数,没有同样被自限定,就会出错。

参数协变

自限定可以产生协变参数类型,也就是方法参数类型会随子类而变化。
如果在父类中使用自限定类型并返回了一个泛型,那么在子类中,随之继承而来的方法将自适应地变化为子类所描述的类型,也就是说继承自父类的方法在子类中因为自限定的原因而变得灵活地重写,而不是普通继承造成的重载。

泛型异常

在将异常应用于泛型时,必须将实际的异常类应用于泛型参数,否则将无法运行。

混型

混型实现了一种方法的按需组装,比组合更加灵活,在Java中可以使用动态代理的实现思路去实现混型,就是使用泛型构建代理类,然后使用反射的机制,实现动态代理。

潜在类型机制(弱类型?)

在Java中,难以实现弱类型的功能,但是可以使用反射的机制绕开类型的限制,将类型检查转移到运行时,也就是说可以尝试进行这样的操作:在运行时发现类的一个方法,可以用来做一个事,那么这件事就能做。
在实际的代码中,可以使用适配器模式,在现有接口上增加新的接口功能。

泛型和容器的搭配,解决了部分容器中放置错误类型的问题,但是它更广阔的应用在于能编写出更加泛化的代码,单个的代码可以适用于更多的被允许的类型中。

05-12 11:29