Java编程思想拾遗(2) 基本数据类型

313 阅读6分钟

基本变量

所有new出来的对象存储在堆中,而对于小而简单的变量,用new往往不是很有效,不如创建一个并非是引用的“自动”变量,这个变量直接存储“值”,并置于堆栈中。

基本类型具有的包装器类,使得可以在堆中创建一个对象,用来表示对应的基本类型。

数组

Java确保数组会被初始化(零值或者null),而且不能在它的范围之外被访问,否则抛出运行时异常。比如当创建一个数组对象时,实际上就是创建了一个引用数组,每个引用初始为null,编译器也能为基本数据类型的数组所占的内存全部清零。

无论使用哪种类型的数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,这个数组对象用以保存指向其他对象的引用,对象数组和基本类型数组在使用上几乎是相同的,唯一的区别就是对象数组保存的是引用,基本类型数组直接保存基本类型的值。只读成员length是数组对象唯一一个可以访问的字段,表示此数组对象可以存储多少元素,注意不是实际保存的元素个数。

多维数组本质上是嵌套数组,高维元素维护低微数组,低微数组的个数和内存自行分配管理

在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速,但是为这种速度所付出的代价是数组对象的大小被固定,并且在其生命周期中不可改变。

随着自动包装机制的出现,容器已经可以与数组几乎一样方便地用于基本类型中了,数组硕果仅存的优点就是效率,然而如果要解决更一般化的问题,数组就可能会受到过多的限制,因此在这些情形下你还是会使用容器。

字符串

String对象是不可变的,String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容,而最初的String对象则纹丝不动。

每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象一直待在单一的物理位置上,从未动过。局部引用的生命周期只在方法内,参数是为方法提供信息的,而不是想让该方法改变自己的

如果需要在for循环拼装String,请务必手动使用StringBuilder,否则会有冗余的StringBuilder对象在for循环中被产生。StringBuffer是线程安全的实现。

正则表达式

  • 除了\n和\t这种操作系统级,单\是不合法的,\\才能进入Java的正则解析控制域,代表转义,能转哪些由Java定,正向转还是逆向转都可能,比如\\d代表数字,\\+代表普通字符+,\\\\代表普通反斜杠字符(前两个解析后两个)
  • 竖直线分隔表示"或",比如"abc".matches("((abc)|(def))+")
  • [abc]表示包含a、b、c的任意一个字符,[^abc]表示除了a、b、c的任意一个字符
  • matcher.find()是可以循环调用的,每一次find从目标字符串匹配出一次表达式并无视其它无关字符,而目标字符串可能可以匹配多次表达式,以表达式而不是目标字符串为中心;matches()则需要完全匹配。组的层次在这之下
  • 组序号以左括号为准,A(B(C))D中,组0是ABCD,组1是BC,组2是C
  • ?:匹配但不捕获,比如(?:abc|def|ig),可以匹配abc或def或ig但group不计入

文本替换,appendReplacement(StringBuffer sbuf,String replacement)执行渐进式替换,不像replaceFirst和replaceAll用固定字符串替换,这个允许自定义编程生成replacement。

String s = ”abd eaa“;
StringBuffer sbuf = new StringBuffer();
Patter p = Pattern.compile("[aeious]");
Matcher m = p.matcher(s):
while(m.find()) {
    m.appendReplacement(sbuf, m.group().toUpperCase());
}
m.appendTail(sbuf);

枚举

在你创建enum时,这些enum都继承自Enum类,编译器会自动添加一些有用的特性。例如,它会创建toString()方法以便你可以很方便地显示某个enum实例的名字,编译器还会创建ordinary()方法用来表示某个特定enum常量的声明顺序,以及static values()方法用来按照enum常量的声明顺序,产生由这些常量值构成的数组。

下面的内容对于目前稍微有点超纲,因为笔者是在后面写完再过来的...

public abstract class Enum<E extends Enum<E>>

enum Explore { HERE, THERE }

// compile auto generate
final class Explore extends java.lang.Enum {
    // that's why we use Explore.HERE
    public static final Explore HERE;
    public static final Explore THERE;
    public static final Explore[] values();
    public static Explore valueOf(java.lang.String);
    static {};
}

可以通过Class对象获取所有enum实例,非枚举类调用则会报错。

enum Search { HITHER, YON }

public class UpcastEnum {
    public static void main(String[] args) {
        Search[] vals = Search.values();
        Enum e = Search.HITHER; // Upcast
        // e.values(); // No values() in Enum
        for(Enum en : e.getClass().getEnumConstants()) {
            System.out.println(en); // output: HITHER YON
        }
    }
}

所以enum无法继承其他类,但实现接口多重继承还是没问题的。

 enum CartoonCharater implements Generator<CartoonCharater> {
     SPLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
     
     private Random rand = new Random(47);
     
     public CartoonCharater next() {
         return values()[rand.nextInt(values().length)];
     }
 }
 
 public class EnumImplementation {
     public static <T> void printNext(Generator<T> rg) {
         System.out.print(rg.next() + ". ");
     }
     public staic void main(String[] args) {
         CartoonCharater cc = CartoonCharater.BOB;
         for(int i = 0; i< 10; i++) {
             printNext(cc);
         }
     }
 }
 

Java允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为,要实现常量相关的方法,你需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象方法。可以理解为枚举类为父类,枚举实例覆盖或实现父类方法。(笔者之前忘了这个特性,是通过在枚举中添加一个Function成员字段实现类似的效果,不过相对而言还是用继承更合适)

public enum ConstantSpecificMethod {
    DATE_TIME {
        String getinfo() {
            return DateFormat.getDateInstance().format(new Date));
        }
    }
    
    abstract String getInfo();
    
    public static void main(String[] args) {
        for(ConstantSpecificMethod csm: values()) {
            System.out.println(csm.getInfo()):
        }
    }
}

Java引入了EnumSet,其内部使用long存储了Enum的ordinary(),一个成员占据一个bit。此外还提供了EnumMap,其内部使用数组实现,数组下标即为Enum的ordinary()。

注解

注解可以用来生成描述符文件,甚至或是新的类定义,并且有助于减轻编写样板代码的负担,每当你创建描述符性质的类或接口时,一旦其中包含了重复性的工作,那就可以考虑使用注解来简化与自动化该过程。

注解把元数据与源代码文件结合在一起,并利用Annotation API(AnnotatedElement反射接口,依托于Class对象所以并没有太多神秘色彩)为自己的注解构造处理工具,而不是保存在外部文档中,注解是真正的语言级的概念,一旦构造出来,就享有编译期的类型检查保护,程序员也拥有对源代码以及字节码检查强大的检查与操作能力。

注解的定义看起来很像接口的定义,事实上,和其他任何Java接口一样,注解也将会编译成class文件。注解的元素看起来就像是接口的方法,唯一的区别是你可以为其制定默认值。