Java 面试知识点解析(四)——版本特性篇(1)_java

  • 前言:

在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部分内容参照自这一篇文章,有一些自己补充的,也算是重新学习一下 Java 吧。

前序文章链接:

Java 面试知识点解析(一)——基础知识篇

Java 面试知识点解析(二)——高并发编程篇

Java 面试知识点解析(三)——JVM篇


对于 Java 各个版本的特性,特别是 Java 8 的新知识点,我们都应该有所了解。
前排申明和好文推荐:闪烁之狐 » Java5新特性及使用 » Java6新特性及使用 » Java7新特性及使用 » Java8新特性及使用(一)» Java8新特性及使用(二)

(一)Java 5 相关知识点

参考文章:jdk 1.5新特性

1)增强型 for 循环:

答:增强 for 循环:foreach 语句,foreach 简化了迭代器。

格式:// 增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器

for( 元素类型 变量名 : Collection集合 & 数组 ) {
    …
}

语法:
for ( type 变量名:集合变量名 ) { … }

注意事项:

  • 迭代变量必须在( )中定义!

  • 集合变量可以是数组或实现了Iterable接口的集合类。

高级for循环和传统for循环的区别:

高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合,在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。

如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。比起普通的for循环,高级for循环还有性能优势,因为它对数组索引的边界值只计算一次(摘自《Effective Java》第46条)。

高级for循环可以遍历map集合吗?

答:原则上map集合是无法使用增强for循环来迭代的,因为增强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证一定有iterator方法,java有这样的限定是因为增强for循环内部还是用迭代器实现的,而实际上,我们可以通过某种方式来使用增强for循环。

for(Object obj : map.entrySet()) {
    Map.Entry entry = (Entry) obj;  // obj 依次表示Entry
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

总之,for-each 循环在简洁性和预防 Bug 方面有着传统 for 循环无法比拟的优势,并且没有性能损失。应该尽可能地使用 for-each 循环。遗憾的是,有三种常见的情况是无法使用 for-each 循环的:

  1. 过滤——如果需要遍历集合,并删除选定的元素,就需要使用显式地迭代器,以便可以调用它的 remove 方法。

  2. 转换——如果需要遍历列表或者数组,并取代它部分或者全部的元素值(增删、或对元素进行赋值),就需要列表迭代器或者数组索引,以便设定元素的值

  3. 平行迭代——如果需要并行地遍历多个集合,就需要显式地控制迭代器或者所因变量以便所有迭代器或者索引变量都可以得到同步前移

2)可变参数:

解析:什么意思呢?举个例子:在 JDK 1.5 之前,当我们要为一个传递多个类型相同的参数时,我们有两种方法解决,1.直接传递一个数组过去,2.有多少个参数就传递多少个参数。

例如:

public void printColor(String red,String green,String yellow){
}// 或者public void printColor(String[] colors){
}

这样编写方法参数虽然能够实现我们想要的效果,但是,这样是不是有点麻烦呢?再者,如果参数个数不确定,我们怎么办呢?Java JDK1.5为我们提供的可变参数就能够完美的解决这个问题

答:

可变参数(...):用到函数的参数上,当要操作的同一个类型元素个数不确定的时候,可是用这个方式,这个参数可以接受任意个数的同一类型的数据。

和以前接收数组不一样的是:

以前定义数组类型,需要先创建一个数组对象,再将这个数组对象作为参数传递给函数。现在,直接将数组中的元素作为参数传递即可。底层其实是将这些元素进行数组的封装,而这个封装动作,是在底层完成的,被隐藏了。所以简化了用户的书写,少了调用者定义数组的动作。

如果在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,否则编译会失败。)。

如果要获取多个int数的和呢?可以使用将多个int数封装到数组中,直接对数组求和即可。

可变参数的特点:

  • ① 只能出现在参数列表的最后;

  • ② “...” 位于变量类型和变量名之间,前后有无空格都可以;

  • ③ 调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。

Public int add(int  x, int... args){//也可以直接(int..args)就是说传不传都可以
        Int sum = x;        For(int i = 0; i<=args.lengrth;i++){
                Sum+=args[i];
        }        return sum;
}

实例:

public class VariableParameter {      public static void main(String[] args) {
            System. out.println(add(1, 2));
            System. out.println(add(1, 2, 3));
      }      
       public static int add(int x, int... args){            int sum = x;            for(int i = 0; i < args.length; i++){
                sum += args[i];
            }            return sum;
      }
}

3)枚举

解析:关键字 enum

答:

问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个;

解决办法:

1)在 setGrade 方法中做判断,不符合格式要求就抛出异常;
2)直接限定用户的选择,通过自定义类模拟枚举的方式来限定用户的输入,写一个 Grade 类,私有构造函数,对外提供 5 个静态的常量表示类的实例;
3)jdk5 中新定义了枚举类型,专门用于解决此类问题;
4)枚举就是一个特殊的java类,可以定义属性、方法、构造函数、实现接口、继承类;

为什么要有枚举?

问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。

枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。

用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能。

1、私有的构造方法。
2、每个元素分别用一个公有的静态成员变量表示。

可以有若干公有方法或抽象方法。采用抽象方法定义nextDay就将大量的if.else语句转移成了一个个独立的类

示例:定义一个Weekday的类来模拟枚举功能。

public class WeekDay {      
       private WeekDay(){}      
       public static final WeekDay SUN = new WeekDay();       public static final WeekDay MON = new WeekDay();      
       public WeekDay nextDay(){             if(this == SUN){                   return MON ;
            } else{                   return SUN ;
            }
      }      
       public String toString(){             return this == SUN? "SUN":"MON" ;
      }
}public class EnumTest {       public static void main(String[] args) {
            WeekDay day = WeekDay.MON;
            System. out.println(day.nextDay());            //结果:SUN
      }
}

使用枚举类实现

public class EnumTest {       public static void main(String[] args) {
            WeekDay day = WeekDay.FRI;
            System.out.println(day);             //结果:FRI
            System.out.println(day.name());             //结果:FRI
            System.out.println(day.ordinal());             //结果:5
            System.out.println(WeekDay. valueOf("SUN"));             //结果:SUN
            System.out.println(WeekDay. values().length);             //结果:7
      }      
      public enum WeekDay{
             SUN,MON ,TUE,WED, THI,FRI ,SAT;
      }
}

总结: 枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象,例如可以调用WeekDay.SUN.getClass().getName 和 WeekDay.class.getName()。

注意: 最后一个枚举元素后面可以加分号,也可以不加分号。

实现带有构造方法的枚举

  • 枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。

  • 枚举元素必须位于枚举体中的最开始部分,枚举元素列表的最后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器会报告错误。

  • 带构造方法的枚举:
    构造方法必须定义成私有的
    如果有多个构造方法,将根据枚举元素创建时所带的参数决定选择哪个构造方法创建对象。
    枚举元素 MON 和 MON() 的效果一样,都是调用默认的构造方法。

示例:

public class EnumTest {       public static void main(String[] args) {
            WeekDay day = WeekDay.FRI;
      }      
      public enum WeekDay{             SUN(1),MON (),TUE, WED,THI ,FRI,SAT;            
             private WeekDay(){
                  System. out.println("first" );
            }            
             private WeekDay(int value){
                  System. out.println("second" );
            }             //结果:
             //second
             //first
             //first
             //first
             //first
             //first
             //first
      }
}

实现带有抽象方法的枚举

定义枚举TrafficLamp,实现抽象的nextTrafficLamp方法:每个元素分别是由枚举类的子类来生成的实例对象,这些子类采用类似内部类的方式进行定义。增加上表示时间的构造方法。

public class EnumTest {    public static void main(String[] args) {
        TrafficLamp lamp = TrafficLamp.RED;
        System.out.println(lamp.nextLamp());        //结果:GREEN
    }    public enum TrafficLamp {        RED(30) {            public TrafficLamp nextLamp() {                return GREEN;
            }
        }, GREEN(45) {            public TrafficLamp nextLamp() {                return YELLOW;
            }
        }, YELLOW(5) {            public TrafficLamp nextLamp() {                return RED;
            }
        };        private int time;        private TrafficLamp(int time) {            this.time = time;
        }        public abstract TrafficLamp nextLamp();
    }
}

注意:
1、枚举只有一个成员时,就可以作为一种单例的实现方式。
2、查看生成的class文件,可以看到内部类对应的class文件。

4)自动拆装箱

答:在 Java 中数据类型分为两种:基本数据类型、引用数据类型(对象)

自动装箱:把基本类型变成包装器类型,本质是调用包装器类型的valueOf()方法

注意:基本数据类型的数组与包装器类型数组不能互换

在 java程序中所有的数据都需要当做对象来处理,针对8种基本数据类型提供了包装类,如下:


int → Integer
byte → Byte
short → Short
long → Long
char → Character
double → Double
float → Float
boolean → Boolean


在 jdk 1.5 以前基本数据类型和包装类之间需要相互转换:

基本---引用 Integer x = new Integer(x);
引用---基本 int num = x.intValue();

1)Integer x = 1; x = x + 1; 经历了什么过程?装箱→拆箱→装箱
2)为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小为 -128~127 一个字节的大小。String池:Java 为了优化字符串操作也提供了一个缓冲池;

→ 享元模式(Flyweight Pattern):享元模式的特点是,复用我们内存中已经存在的对象,降低系统创建对象实例。

自动装箱:

Integer num1 = 12;

自动拆箱:

System.out.println(num1 + 12);

基本数据类型的对象缓存:

Integer num1 = 12;
Integer num2 = 12;
System.out.println(num1 == num2);//ture
Integer num3 = 129;
Integer num4 = 129;
System.out.println(num3 == num4);//false
Integer num5 = Integer.valueOf(12);
Integer num6 = Integer.valueOf(12);
System.out.println(num5 == num6);//true

示例:

public class AutoBox {       public static void main(String[] args) {             //装箱
            Integer iObj = 3;            
             //拆箱
            System. out.println(iObj + 12);             //结果:15
            
            Integer i1 = 13;
            Integer i2 = 13;
            System. out.println(i1 == i2);             //结果:true
            
            i1 = 137;
            i2 = 137;
            System. out.println(i1 == i2);             //结果:false
      }
}

注意:
如果有很多很小的对象,并且他们有相同的东西,那就可以把他们作为一个对象。
如果还有很多不同的东西,那就可以作为外部的东西,作为参数传入。
这就是享元设计模式(flyweight)。

例如示例中的Integer对象,在-128~127范围内的Integer对象,用的频率比较高,就会作为同一个对象,因此结果为true。超出这个范围的就不是同一个对象,因此结果为false。

5)泛型 Generics

答:引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处。Parameterized Type作为参数和返回值,Generic是vararg、annotation、enumeration、collection的基石。

泛型可以带来如下的好处总结如下:

  1. 类型安全:抛弃List、Map,使用List、Map给它们添加元素或者使用Iterator遍历时,编译期就可以给你检查出类型错误

  2. 方法参数和返回值加上了Type: 抛弃List、Map,使用List、Map

  3. 不需要类型转换:List list = new ArrayList();

  4. 类型通配符“?”: 假设一个打印List中元素的方法printList,我们希望任何类型T的List都可以被打印

6)静态导入

答:静态导入:导入了类中的所有静态成员,简化静态成员的书写。
import语句可以导入一个类或某个包中的所有类
import static语句导入一个类中的某个静态方法或所有静态方法

import static java.util.Collections.*;  //导入了Collections类中的所有静态成员

静态导入可以导入静态方法,这样就不必写类名而可以直接调用静态方法了。

例子:

原来的:

public class Demo12 {    public static void main(String[] args) {
        System.out.println(Math.max(12, 15));
         System. out.println(Math.abs(3-6));
    }
}

使用静态导入的:

import static java.lang.Math.max ;import static java.lang.Math.abs ;public class Demo12 {    public static void main(String[] args) {
        System.out.println(max(12, 15));
        System. out.println(abs(3-6));
    }
}

注意:

1、也可以通过import static java.lang.Math.*;导入Math类下所有的静态方法。
2、如果将javac设置为了Java5以下,那么静态导入等jdk1.5的特性都会报告错误。

7)新的线程模型和并发库Thread Framework(重要)

答: 最主要的就是引入了 java.util.concurrent 包,这个都是需要重点掌握的。

HashMap 的替代者 ConcurrentHashMap 和 ArrayList 的替代者 CopyOnWriteArrayList 在大并发量读取时采用 java.util.concurrent 包里的一些类会让大家满意 BlockingQueue、Callable、Executor、Semaphore

8)内省(Introspector)

答:是 Java 语言对 Bean 类属性、事件的一种缺省处理方法。例如类 A 中有属性 name , 那我们通过 getName,setName 来得到其值或者设置新的值。通过 getName/setName 来访问name属性,这就是默认的规则。Java 中提供了一套 API 用来访问某个属性的 getter /setter 方法,通过这些 API 可以使你不需要了解这个规则(但你最好还是要搞清楚),这些 API 存放于包 java.beans 中。

一般的做法是通过类 Introspector 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器 (PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后我们就可以通过反射机制来 调用这些方法。

扩展阅读:java Introspector(内省) 的介绍

9)注解(Annotations)

答:

注解(Annotation)是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符,它是一种由JSR-175标准选择用来描述元数据的一种工具。Java从Java5开始引入了注解。在注解出现之前,程序的元数据只是通过java注释和javadoc,但是注解提供的功能要远远超过这些。注解不仅包含了元数据,它还可以作用于程序运行过程中、注解解释器可以通过注解决定程序的执行顺序。

比如,下面这段代码:

@Overridepublic String toString() {  
    return "This is String.";
}

上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我们不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。

为什么要引入注解?

使用注解之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用注解会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是注解定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记接口,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像注解这种标准的方式。

目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。

参考文章(更多注解戳这里):Java注解的理解和应用

10)新增 ProcessBuilder 类

答:

ProcessBuilder 类是 Java5 在 java.lang 包中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。在此之前,都是由 Process 类处来实现进程的控制管理。每个 ProcessBuilder 实例管理一个进程属性集。它的 start() 方法利用这些属性创建一个新的 Process 实例。start() 方法可以从同一实例重复调用,以利用相同的或相关的属性创建新的子进程。

ProcessBuilder 是一个 final 类,有两个带参数的构造方法,你可以通过构造方法来直接创建 ProcessBuilder 的对象。而 Process 是一个抽象类,一般都通过 Runtime.exec() 和 ProcessBuilder.start() 来间接创建其实例。ProcessBuilder 为进程提供了更多的控制,例如,可以设置当前工作目录,还可以改变环境参数。而 Process 类的功能相对来说简单的多。ProcessBuilder 类不是同步的。如果多个线程同时访问一个 ProcessBuilder,而其中至少一个线程从结构上修改了其中一个属性,它必须保持外部同步。

若要使用 ProcessBuilder 创建一个进程,只需要创建 ProcessBuilder 的一个实例,指定该进程的名称和所需参数。要执行此程序,调用该实例上的 start() 即可。下面是一个执行打开 Windows 记事本的例子。注意它将要编辑的文件名指定为一个参数。

class PBDemo {    public static void main(String args[]) {        try {
            ProcessBuilder proc = new ProcessBuilder("notepad.exe", "testfile");
            proc.start();
        } catch (Exception e) {
            System.out.println("Error executing notepad.");
        }
    }

}

参考文章:Java5新特性及使用

11)新增Formatter格式化器(Formatter)

Formatter 类是Java5中新增的 printf-style 格式化字符串的解释器,它提供对布局和对齐的支持,提供了对数字,字符串和日期/时间数据的常用格式以及特定于语言环境的输出。常见的 Java 类型,如 bytejava.math.BigDecimal 和 java.util.Calendar 都支持。 通过 java.util.Formattable 接口提供了针对任意用户类型的有限格式定制。

更详细的介绍见这里。主要使用方法的代码示例如下:

import java.io.BufferedReader;  import java.io.FileReader;  import java.text.MessageFormat;  import java.text.SimpleDateFormat;  import java.util.*;import org.slf4j.Logger;  import org.slf4j.LoggerFactory;/** * 格式化测试使用的示例类. * * @author blinkfox on 2017-11-28. */public class FormatTester {    private static final Logger log = LoggerFactory.getLogger(FormatTester.class);    /**     * 格式化.     */
    private static void formatter() {
        StringBuilder sb = new StringBuilder();
        Formatter formatter = new Formatter(sb, Locale.US);        // 可重新排序输出.
        formatter.format("%n%4$2s %3$2s %2$2s %1$2s %n", "a", "b", "c", "d"); // -> " d  c  b  a"
        formatter.format(Locale.FRANCE, "e = %+10.4f", Math.E); // -> "e =    +2,7183"
        formatter.format("%nAmount gained or lost since last statement: $ %(,.2f", 6217.58);        // -> "Amount gained or lost since last statement: $ 6,217.58"

        log.info("打印出格式化后的字符串:{}", formatter);
        formatter.close();
    }    /**     * printf打印.     */
    private static void printf() {
        String filename = "testfile";        try (FileReader fileReader = new FileReader(filename)) {
            BufferedReader reader = new BufferedReader(fileReader);
            String line;            int i = 1;            while ((line = reader.readLine()) != null) {
                System.out.printf("Line %d: %s%n", i++, line);
            }
        } catch (Exception e) {
            System.err.printf("Unable to open file named '%s': %s", filename, e.getMessage());
        }
    }    /**     * stringFormat使用.     */
    private static void stringFormat() {        // 格式化日期.
        Calendar c = new GregorianCalendar(1995, Calendar.MAY, 23);
        String s = String.format("Duke's Birthday: %1$tm %1$te,%1$tY", c);        // -> s == "Duke's Birthday: May 23, 1995"
        log.info(s);
    }    /**     * 格式化消息.     */
    private static void messageFormat() {
        String msg = "欢迎光临,当前({0})等待的业务受理的顾客有{1}位,请排号办理业务!";
        MessageFormat mf = new MessageFormat(msg);
        String fmsg = mf.format(new Object[]{new Date(), 35});
        log.info(fmsg);
    }    /**     * 格式化日期.     */
    private static void dateFormat() {
        String str = "2010-1-10 17:39:21";
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");        try {
            log.info("格式化后的日期:{}", format.format(format.parse(str)));
        } catch (Exception e) {
            log.error("日期格式化出错!", e);
        }
    }    public static void main(String[] args) {        formatter();        stringFormat();        messageFormat();        dateFormat();        printf();
    }

}

参考文章:Java5新特性及使用

12)新增 Scanner 类(Scanner)

java.util.Scanner 是 Java5 的新特征,主要功能是简化文本扫描,但这个类最实用的地方还是在获取控制台输入。

(1).Scanner概述

可以从字符串(Readable)、输入流、文件、Channel等来直接构造Scanner对象,有了Scanner了,就可以逐段(根据正则分隔式)来扫描整个文本,并对扫描后的结果做想要的处理。

Scanner 默认使用空格作为分割符来分隔文本,但允许你使用 useDelimiter(Pattern pattern) 或 useDelimiter(String pattern) 方法来指定新的分隔符。

主要API如下:

  • delimiter(): 返回此 Scanner 当前正在用于匹配分隔符的 Pattern

  • hasNext(): 判断扫描器中当前扫描位置后是否还存在下一段。

  • hasNextLine(): 如果在此扫描器的输入中存在另一行,则返回true。

  • next(): 查找并返回来自此扫描器的下一个完整标记。

  • nextLine(): 此扫描器执行当前行,并返回跳过的输入信息。

(2).扫描控制台输入

当通过 new Scanner(System.in) 创建了一个 Scanner 实例时,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给 Scanner,作为扫描对象。如果要获取输入的内容,则只需要调用 Scanner 的 nextLine() 方法即可。

/*** 扫描控制台输入.** @author blinkfox 2017-11-28*/public class ScannerTest {    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.println("请输入字符串:");        while (true) {
            String line = s.nextLine();            if (line.equals("exit")) break;
            System.out.println(">>>" + line);
        }
    }

}

(3).其它示例

该示例中会从 myNumbers 文件中读取长整型 long 的数据。

Scanner sc = new Scanner(new File("myNumbers"));  while (sc.hasNextLong()) {  
    long aLong = sc.nextLong();
}

以下示例可以使用除空格之外的分隔符来从一个字符串中读取几个条目:

String input = "1 fish 2 fish red fish blue fish";  
Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");  
System.out.println(s.nextInt());  
System.out.println(s.nextInt());  
System.out.println(s.next());  
System.out.println(s.next());  
s.close();

将输出:

1  2  red  
blue

参考文章:Java5新特性及使用

13)StringBuilder

StringBuilder 也是 Java5 中新增的类,主要用来代替 + 号和 StringBuffer 来更加高效的拼接字符串。StringBuffer 与 StringBuilder 都是继承于 AbstractStringBuilder,主要的区别就是 StringBuffer 的函数上都有 synchronized 关键字,保证线程安全。

关于 StringBuilder 的使用这里就不再详细介绍了,网上文章也有很多。总之,对于动态字符串的拼接推荐使用 StringBuilder。静态字符串的拼接直接使用 + 号或者字符串的 concat(String str) 方法,甚至也使用 StringBuilder 亦可。

参考文章:Java5新特性及使用


(二)Java 6 相关知识点

关于 JDK 1.6 的新特性,了解一下就可以了...如果有兴趣深入研究的童鞋,右转这里:Java6新特性及使用

1)Desktop 类和 SystemTray 类:

答:

在JDK6中 ,AWT新增加了两个类:Desktop 和 SystemTray 。

前者可以用来打开系统默认浏览器浏览指定的 URL,打开系统默认邮件客户端给指定的邮箱发邮件,用默认应用程序打开或编辑文件(比如,用记事本打开以txt为后缀名的文件),用系统默认的打印机打印文档;

后者可以用来在系统托盘区创建一个托盘程序.

2)使用 JAXB2 来实现对象与 XML 之间的映射

答:

JAXB是Java Architecture for XML Binding的缩写,可以将一个Java对象转变成为XML格式,反之亦然。

我们把对象与关系数据库之间的映射称为ORM, 其实也可以把对象与XML之间的映射称为OXM(Object XML Mapping). 原来JAXB是Java EE的一部分,在JDK6中,SUN将其放到了Java SE中,这也是SUN的一贯做法。

JDK6中自带的这个JAXB版本是2.0, 比起1.0(JSR 31)来,JAXB2(JSR 222)用JDK5的新特性Annotation来标识要作绑定的类和属性等,这就极大简化了开发的工作量。

实际上,在Java EE 5.0中,EJB和Web Services也通过Annotation来简化开发工作。另外,JAXB2在底层是用StAX(JSR 173)来处理XML文档。除了JAXB之外,我们还可以通过XMLBeans和Castor等来实现同样的功能。

3)理解StAX

答:

StAX(JSR 173)是JDK6.0中除了DOM和SAX之外的又一种处理XML文档的API。

StAX 的来历 :在JAXP1.3(JSR 206)有两种处理XML文档的方法:DOM(Document Object Model)和SAX(Simple API for XML).

由 于JDK6.0中的JAXB2(JSR 222)和JAX-WS 2.0(JSR 224)都会用到StAX,所以Sun决定把StAX加入到JAXP家族当中来,并将JAXP的版本升级到1.4(JAXP1.4是JAXP1.3的维护版本). JDK6里面JAXP的版本就是1.4. 。

StAX是The Streaming API for XML的缩写,一种利用拉模式解析(pull-parsing)XML文档的API.StAX通过提供一种基于事件迭代器(Iterator)的API让 程序员去控制xml文档解析过程,程序遍历这个事件迭代器去处理每一个解析事件,解析事件可以看做是程序拉出来的,也就是程序促使解析器产生一个解析事件,然后处理该事件,之后又促使解析器产生下一个解析事件,如此循环直到碰到文档结束符;

SAX也是基于事件处理xml文档,但却是用推模式解析,解析器解析完整个xml文档后,才产生解析事件,然后推给程序去处理这些事件;DOM 采用的方式是将整个xml文档映射到一颗内存树,这样就可以很容易地得到父节点和子结点以及兄弟节点的数据,但如果文档很大,将会严重影响性能。

4)使用Compiler API

答:

现在我们可以用JDK6 的Compiler API(JSR 199)去动态编译Java源文件,Compiler API结合反射功能就可以实现动态的产生Java代码并编译执行这些代码,有点动态语言的特征。

这个特性对于某些需要用到动态编译的应用程序相当有用,比如JSP Web Server,当我们手动修改JSP后,是不希望需要重启Web Server才可以看到效果的,这时候我们就可以用Compiler API来实现动态编译JSP文件,当然,现在的JSP Web Server也是支持JSP热部署的,现在的JSP Web Server通过在运行期间通过Runtime.exec或ProcessBuilder来调用javac来编译代码,这种方式需要我们产生另一个进程去 做编译工作,不够优雅而且容易使代码依赖与特定的操作系统;Compiler API通过一套易用的标准的API提供了更加丰富的方式去做动态编译,而且是跨平台的。

5)轻量级Http Server API

答:

JDK6 提供了一个简单的Http Server API,据此我们可以构建自己的嵌入式Http Server,它支持Http和Https协议,提供了HTTP1.1的部分实现,没有被实现的那部分可以通过扩展已有的Http Server API来实现,程序员必须自己实现HttpHandler接口,HttpServer会调用HttpHandler实现类的回调方法来处理客户端请求,在 这里,我们把一个Http请求和它的响应称为一个交换,包装成HttpExchange类,HttpServer负责将HttpExchange传给 HttpHandler实现类的回调方法.

6)插入式注解处理API(Pluggable Annotation Processing API)

答:

插入式注解处理API(JSR 269)提供一套标准API来处理Annotations(JSR 175)

实 际上JSR 269不仅仅用来处理Annotation,我觉得更强大的功能是它建立了Java 语言本身的一个模型,它把method, package, constructor, type, variable, enum, annotation等Java语言元素映射为Types和Elements(两者有什么区别?), 从而将Java语言的语义映射成为对象, 我们可以在javax.lang.model包下面可以看到这些类. 所以我们可以利用JSR 269提供的API来构建一个功能丰富的元编程(metaprogramming)环境.

JSR 269用Annotation Processor在编译期间而不是运行期间处理Annotation, Annotation Processor相当于编译器的一个插件,所以称为插入式注解处理.如果Annotation Processor处理Annotation时(执行process方法)产生了新的Java代码,编译器会再调用一次Annotation Processor,如果第二次处理还有新代码产生,就会接着调用Annotation Processor,直到没有新代码产生为止.每执行一次process()方法被称为一个"round",这样整个Annotation processing过程可以看作是一个round的序列.

JSR 269主要被设计成为针对Tools或者容器的API. 举个例子,我们想建立一套基于Annotation的单元测试框架(如TestNG),在测试类里面用Annotation来标识测试期间需要执行的测试方法。

7)用Console开发控制台程序

JDK6 中提供了java.io.Console 类专用来访问基于字符的控制台设备. 你的程序如果要与Windows下的cmd或者Linux下的Terminal交互,就可以用Console类代劳. 但我们不总是能得到可用的Console, 一个JVM是否有可用的Console依赖于底层平台和JVM如何被调用. 如果JVM是在交互式命令行(比如Windows的cmd)中启动的,并且输入输出没有重定向到另外的地方,那么就可以得到一个可用的Console实例.

8)对脚本语言的支持

如: ruby, groovy, javascript.

9)Common annotations

Common annotations 原本是Java EE 5.0(JSR 244)规范的一部分,现在SUN把它的一部分放到了Java SE 6.0中.随着Annotation元数据功能(JSR 175)加入到Java SE 5.0里面,很多Java 技术(比如EJB,Web Services)都会用Annotation部分代替XML文件来配置运行参数(或者说是支持声明式编程,如EJB的声明式事务), 如果这些技术为通用目的都单独定义了自己的Annotations,显然有点重复建设, 所以,为其他相关的Java技术定义一套公共的Annotation是有价值的,可以避免重复建设的同时,也保证Java SE和Java EE 各种技术的一致性。

10)Java DB(Derby)

从 JDK6 开始,JDK 目录中新增了一个名为 db 的目录。这便是 Java 6 的新成员:Java DB。这是一个纯 Java 实现、开源的数据库管理系统(DBMS),源于 Apache 软件基金会(ASF)名下的项目 Derby。它只有 2MB 大小,对比动辄上 G 的数据库来说可谓袖珍。但这并不妨碍 Derby 功能齐备,支持几乎大部分的数据库应用所需要的特性。JDK6.0里面带的这个Derby的版本是10.2.1.7,支持存储过程和触发器;有两种运行模式,一种是作为嵌入式数据库,另一种是作为网络数据库。前者的数据库服务器和客户端都在同一个JVM里面运行,后者允许数据库服务器端和客户端不在同一个JVM里面,而且允许这两者在不同的物理机器上。值得注意的是JDK6里面的这个Derby支持JDK6的新特性 JDBC 4.0 规范(JSR 221)。

11)JDBC 4.0

在 Java SE 6 所提供的诸多新特性和改进中,值得一提的是为 Java 程序提供数据库访问机制的 JDBC 版本升级到了 4.0, 这个以 JSR-221 为代号的版本,提供了更加便利的代码编写机制及柔性,并且支持更多的数据类型。JDBC4.0 主要有以下改进和新特性。

  • 自动加载 java.sql.Driver,而不需要再调用 class.forName

  • 添加了 java.sql.RowId 数据类型用来可以访问 sql rowid ;

  • 添加了 National Character Set 的支持;

  • 增强了 BLOB 和 CLOB 的支持功能;

  • SQL/XML 和 XML 支持;

  • Wrapper Pattern

  • SQLException 增强;

  • Connection 和 Statement 接口增强;

  • New Scalar Funtions

  • JDBC API changes


(三)JAVA 7 相关知识点

之前已经写过一篇详细介绍 Java 7 特性的文章了,这里就直接黏了:Java 7新特性

1)Diamond Operator

类型判断是一个人特殊的烦恼,入下面的代码:

Map<String,List<String>> anagrams = new HashMap<String,List<String>>();

通过类型推断后变成:

Map<String,List<String>> anagrams = new HashMap<>();

注:这个<>被叫做diamond(钻石)运算符,Java 7后这个运算符从引用的声明中推断类型。

2)在switch语句中使用字符串

switch语句可以使用原始类型或枚举类型。Java引入了另一种类型,我们可以在switch语句中使用:字符串类型。

说我们有一个根据其地位来处理贸易的要求。直到现在,我们使用if-其他语句来完成这个任务。

private voidprocessTrade(Trade t){

            String status = t.getStatus();            if(status.equalsIgnoreCase(NEW)) {                  newTrade(t);

            } else if(status.equalsIgnoreCase(EXECUTE)) {                  executeTrade(t);

            } else if(status.equalsIgnoreCase(PENDING)) {                  pendingTrade(t);

            }

}

这种处理字符串的方法是粗糙的。在Java中,我们可以使用增强的switch语句来改进程序,该语句以String类型作为参数。

public voidprocessTrade(Trade t) {
    String status = t.getStatus();    switch(status) {
        caseNEW:            newTrade(t);            break;
        caseEXECUTE:            executeTrade(t);            break;
        casePENDING:            pendingTrade(t);            break;         default:            break;
    }
}

在上面的程序中,状态字段总是通过使用 String.equals() 与案例标签来进行比较。

3)自动资源管理

Java中有一些资源需要手动关闭,例如Connections,Files,Input/OutStreams等。通常我们使用 try-finally 来关闭资源:

public voidoldTry() {    try{

        fos= newFileOutputStream("movies.txt");

        dos= newDataOutputStream(fos);

        dos.writeUTF("Java 7 Block Buster");

    } catch(IOException e) {

        e.printStackTrace();

    } finally{        try{

            fos.close();

            dos.close();

        } catch(IOException e) {            // log the exception

        }

    }

}

然而,在Java 7中引入了另一个很酷的特性,可以自动管理资源。它的操作也很简单,我们所要做的就是在 try 块中申明资源如下:

try(resources_to_be_cleant){   // your code}

以上方法与旧的 try-finally 能最终写成下面的代码:

public voidnewTry() {    try(FileOutputStream fos = newFileOutputStream("movies.txt");

        DataOutputStream dos = newDataOutputStream(fos)) {

        dos.writeUTF("Java 7 Block Buster");

    } catch(IOException e) {        // log the exception

    }

}

上面的代码也代表了这个特性的另一个方面:处理多个资源。FileOutputStream 和 DataOutputStream 在try语句中一个接一个地含在语句中,每一个都用分号(;)分隔符分隔开。我们不必手动取消或关闭流,因为当空间存在try块时,它们将自动关闭。

在后台,应该自动关闭的资源必须试验 java.lang.AutoCloseable 接口。

任何实现 AutoCloseable 接口的资源都可以作为自动资源管理的候选。AutoCloseable 是 java.io.Closeable 接口的父类,JVM会在程序退出try块后调用一个方法 close()

4)带下划线的数字文本

数字文字绝对是对眼睛的一种考验。我相信,如果你给了一个数字,比如说,十个零,你就会像我一样数零。如果不计算从右到左的位置,识别一个文字的话,就很容易出错,而且很麻烦。Not anymore。Java在识别位置时引入了下划线。例如,您可以声明1000,如下所示:

int thousand =  1_000;

或1000000(一百万)如下:

int million  =  1_000_000

请注意,这个版本中也引入了二进制文字-例如“0b1”-因此开发人员不必再将它们转换为十六进制。

5)改进的异常处理

在异常处理区域有几处改进。Java引入了多个catch功能,以使用单个抓到块捕获多个异常类型。

假设您有一个方法,它抛出三个异常。在当前状态下,您将分别处理它们,如下所示:

public voidoldMultiCatch() {    try{        methodThatThrowsThreeExceptions();

    } catch(ExceptionOne e) {        // log and deal with ExceptionOne

    } catch(ExceptionTwo e) {        // log and deal with ExceptionTwo

    } catch(ExceptionThree e) {        // log and deal with ExceptionThree

    }

}

在一个catch块中逐个捕获一个连续的异常,看起来很混乱。我还看到了捕获十几个异常的代码。这是非常低效和容易出错的。Java为解决这只丑小鸭带来了新的语言变化。请参阅下面的方法oldMultiCatch方法的改进版本:

public voidnewMultiCatch() {    try{        methodThatThrowsThreeExceptions();

    } catch(ExceptionOne | ExceptionTwo | ExceptionThree e) {        // log and deal with all Exceptions

    }

}

多个异常通过使用 “|” 操作符在一个catch块中捕获。这样,您不必编写数十个异常捕获。但是,如果您有许多属于不同类型的异常,那么您也可以使用“多个catch块”块。下面的代码片段说明了这一点:

public voidnewMultiMultiCatch() {    try{        methodThatThrowsThreeExceptions();

    } catch(ExceptionOne e) {        // log and deal with ExceptionOne
        
    } catch(ExceptionTwo | ExceptionThree e) {        // log and deal with ExceptionTwo and ExceptionThree

    }
    
}

在上面的例子中,在和ExceptionThree属于不同的层次结构,因此您希望以不同的方式处理它们,但使用一个抓到块。

6)New file system API(NIO 2.0)

那些使用Java的人可能还记得框架引起的头痛。在操作系统或多文件系统之间无缝地工作从来都不是一件容易的事情.。有些方法,例如删除或重命名,在大多数情况下都是出乎意料的。使用符号链接是另一个问题。实质上API需要大修。

为了解决上述问题,Java引入了一个新的API,并在许多情况下引入了新的api。

在NIO2.0提出了许多增强功能。在处理多个文件系统时,它还引入了新的类来简化开发人员的生活。

Working With Path(使用路径):

新的 java.nio.file 由包和接口组成例如:Path,Paths,FileSystem,FileSystems等等。

路径只是对文件路径的简单引用。它与java.io.File等价(并具有更多的特性)。下面的代码段显示了如何获取对“临时”文件夹的路径引用:

public voidpathInfo() {

    Path path= Paths.get("c:\Temp\temp");

    System.out.println("Number of Nodes:"+ path.getNameCount());

    System.out.println("File Name:"+ path.getFileName());

    System.out.println("File Root:"+ path.getRoot());

    System.out.println("File Parent:"+ path.getParent());

}

最终控制台的输出将是:

Number of Nodes:2File Name:temp.txtFile Root:c:

File Parent:c:Temp

删除文件或目录就像在文件中调用delete方法(注意复数)一样简单。在类公开两个删除方法,一个抛出NoSuchFileException,另一个不抛。

下面的delete方法调用抛出NoSuchFileException,因此您必须处理它:

Files.delete(path);

Where as Files.deleteIfExists(path) does not throw exception (as expected) if the file/directory does not exist.

使用 Files.deteleIfExists(path) 则不会抛出异常。

您可以使用其他实用程序方法,例如Files.copy(.)和Files.move(.)来有效地对文件系统执行操作。类似地,使用 createSymbolicLink(..) 方法使用代码创建符号链接。

文件更改通知:

JDK 7中最好的改善算是File change notifications(文件更改通知)了。这是一个长期等待的特性,它最终被刻在NIO 2.0中。WatchService API 允许您在对主题(目录或文件)进行更改时接收通知事件。

具体的创建步骤就不给了,总之它的功能就跟它的名字一般,当文件发生更改的时候,能及时作出反馈。

7)Fork and Join(Fork/Join框架)

在一个 Java 程序中有效地使用并行内核一直是一个挑战。很少有国内开发的框架将工作分配到多个核心,然后加入它们来返回结果集。Java已经将这个特性作为Fork/Join框架结合了起来。

基本上,在把手头的任务变成了小任务,直到小任务简单到可以不进一步分手的情况下解决。这就像一个分而治之的算法.。在这个框架中需要注意的一个重要概念是,理想情况下,没有工作线程是空闲的。他们实现了一个 work-stealing 算法,在空闲的工人“偷”工作从那些工人谁是忙。

支持Fork-Join机制的核心类是 ForkJoinPool和ForkJoinTask。

什么是Fork/Join框架:

Java7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

Fork/Join的运行流程图如下:

Java 面试知识点解析(四)——版本特性篇(1)_java_02

工作窃取算法:

工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。工作窃取的运行流程图如下:

Java 面试知识点解析(四)——版本特性篇(1)_java_03

工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。

Fork/Join框架使用示例:

让我们通过一个简单的需求来使用下 Fork/Join 框架,需求是:计算1 + 2 + 3 + 4的结果。

使用 Fork/Join 框架首先要考虑到的是如何分割任务,如果我们希望每个子任务最多执行两个数的相加,那么我们设置分割的阈值是2,由于是4个数字相加,所以 Fork/Join 框架会把这个任务 fork 成两个子任务,子任务一负责计算1 + 2,子任务二负责计算3 + 4,然后再 join 两个子任务的结果。

因为是有结果的任务,所以必须继承 RecursiveTask ,实现代码如下:

import java.util.concurrent.ExecutionException;  import java.util.concurrent.ForkJoinPool;  import java.util.concurrent.Future;  import java.util.concurrent.RecursiveTask;/** * CountTask. * * @author blinkfox on 2018-01-03. */public class CountTask extends RecursiveTask<Integer> {    /** 阈值. */
    public static final int THRESHOLD = 2;    /** 计算的开始值. */
    private int start;    /** 计算的结束值. */
    private int end;    /**     * 构造方法.     *     * @param start 计算的开始值     * @param end 计算的结束值     */
    public CountTask(int start, int end) {        this.start = start;        this.end = end;
    }    /**     * 执行计算的方法.     *     * @return int型结果     */
    @Override
    protected Integer compute() {        int sum = 0;        // 如果任务足够小就计算任务.
        if ((end - start) <= THRESHOLD) {            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {            // 如果任务大于阈值,就分裂成两个子任务来计算.
            int middle = (start + end) / 2;
            CountTask leftTask = new CountTask(start, middle);
            CountTask rightTask = new CountTask(middle + 1, end);            // 等待子任务执行完,并得到结果,再合并执行结果.
            leftTask.fork();
            rightTask.fork();
            sum = leftTask.join() + rightTask.join();
        }        return sum;
    }    /**     * main方法.     *     * @param args 数组参数     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool fkPool = new ForkJoinPool();
        CountTask task = new CountTask(1, 4);
        Future<Integer> result = fkPool.submit(task);
        System.out.println("result:" + result.get());
    }

}

参考文章:Java7新特性及使用
这里是Java 7的新特性一览表:http://www.oschina.net/news/20119/new-features-of-java-7

https://mp.weixin.qq.com/s/JUBzYRFXBNZB5XQ5DJoMPA