精华内容
下载资源
问答
  • jdk1.8新特性面试题
    万次阅读 多人点赞
    2018-08-16 00:31:25

    这几天面试中,问到JDK的基本都会问到有关JDK1.8的新特性,基于这点,自己简单总结了下;

    看到网上有个人总结了这样一句话,感觉比较通俗易懂;

    1. 速度更快 – 红黑树
    2. 代码更少 – Lambda
    3. 强大的Stream API – Stream
    4. 便于并行 – Parallel
    5. 最大化减少空指针异常 – Optional

    1:HashMap

    有人会在问你HashMap的时候会问你JDK1.7和1.8有什么变化;
    主要还是HashMap中链长度大于8时采取红黑树的结构存储。(1.7的时候是链表结构)
    红黑树,除了添加,效率高于链表结构。


    2:ConcurrentHashMap

    Jdk1.7时隔壁级别CocnurrentLevel(锁分段机制)默认为16。

    JDK1.8采取了CAS算法
    CAS原理主要涉及的有:锁机制、CAS 操作;具体可以参考CAS原理分析

    Jdk1.8没有永久区,取而代之的是MetaSpace元空间,用的是物理内存。
    (当你跟面试官聊到JVM的时候可以聊到1.8后用就去是被取代了,这回事一个加分项)


    3、Lambda表达式

    1、Lambda表达式的基础语法:Java8引入了一个新的操作符“->”,该操作符成为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分

    左侧:Lambda表达式的参数列表
    右侧:Lambda表达式中所需执行的功能,即Lambda体。

    语法格式一:无参数,无返回值

    Runnable r2 = () -> System.out.println("hello lambda");
            r2.run();
    1
    2
    

    语法格式二:有一个参数,并且无返回值

    (x) -> System.out.print(x);
    

    语法格式三:若只有一个参数,小括号可以省略不写

    x -> System.out.print(x);
    

    语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句

            Comparator<Integer> c1 = (x, y) -> {
                System.out.print(Integer.compare(x, y)+"函数式接口");
                return Integer.compare(x, y);
            }  ;
            c1.compare(1, 2);
    1
    2
    3
    4
    5
    

    语法格式五:若Lambda体中只有一条语句,return和大括号都可以省略不写

    Comparator c1 = (x,y) -> Integer.compare(x,y);

    语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文进行类型推断出数据类型,既“类型推断”。(Integer x,Integer y) -> Integre.compare(x,y);
    结论:

    左右遇一括号省,左侧推断类型省, 能省则省。


    4、并行流

    Fork/Join框架:
    在必要的情况下,将一个大任务进行必要的拆分Fork成若干个小任务,再将小任务的运算结果进行Join汇总。

    Fork/Join框架和传统线程池的区别:
    采用“工作窃取”模式(Working-stealing),即当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

    相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程等待的时间,提高了性能。

    并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
    Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel() 与sequential() 在并行流与顺序流之间进行切换。
    自己实现的ForkJoin

    /**
     * JDK8的并行流实现。
     */
    @Test
    public void test3(){
        Instant start = Instant.now();
    
        long sum = LongStream.rangeClosed(0, 1000000000L)
                .parallel()//并行流
                //.sequential()//串行流
                .reduce(0, Long::sum);
    
        Instant end = Instant.now();
    
        System.out.println(Duration.between(start,end).toMillis());
    }
    //并行流将会充分使用多核的优势,多线程并行执行,基数越大,效果越明显。其底层还是Fork/Join框架。只不过SUN公司优化的更好,比自己实现更高效
    

    5、Optional类

    Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。

    常用方法:

    • Optional.of(T t) : 创建一个Optional 实例
    • Optional.empty() : 创建一个空的Optional 实例
    • Optional.ofNullable(T t):若t 不为null,创建Optional 实例,否则创建空实例
    • isPresent() : 判断是否包含值
    • orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
    • orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回s 获取的值
    • map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
    • flatMap(Function mapper):与map 类似,要求返回值必须是Optional
    更多相关内容
  • 问题:ArrayList和LinkedList的区别 难度:★★★★ 工资提升度:★★☆ 博主精选答案: 一、接口的默认方法 ...Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展...

    问题:ArrayList和LinkedList的区别

    难度:★★★★

    工资提升度:★★☆

    博主精选答案:

    一、接口的默认方法


    Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法,示例如下:
    代码如下:
    interface Formula { double calculate(int a);
    default double sqrt(int a) { return Math.sqrt(a); } }
    Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
    代码如下:
    Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100);}};formula.calculate(100);1/ 100.0 formula.sqrt(16);//4.0
    文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算sqrt(a *100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。
    译者注:在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8的这个特新在编译器实现的角度上来说更加接近Scala的trait。在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和ava 8的这个在语义上有差别

    二、Lambda表达式

    首先看看在老版本的Java中是如何排列字符串的:代码如下:
    List<string> names = Arrays.asList(""peterF"", "anna", "mike", ""xenia");
    Collections.sort(names, new Comparator<string>(){ @override public int compare(String a, String b){return b.compareTo(a); } 3);
    只需要给静态方法Collections.sort传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。
    在Java 8中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:代码如下:
    Collections.sort(names,(String a, String b)-> { return b.compareTo(a); });看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:代码如下:
    Collections.sort(names, (String a, String b)-> b.compareTo(a));
    对于函数体只有一行代码的,你可以去掉大括号(以及return关键字,但是你还可以写得更短点:代码如下:
    Collections.sort(names, (a, b)-> b.compareTo(a));
    Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。

    三、函数式接口


    Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而"函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
    我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加@Functionallnterface注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
    示例如下:代码如下:
    @Functionallnterface interface Converter<F,T> { T convert(F from); } Converter<String, Integer> converter=(from) -> Integer.valueOf(from); Integer converted = converter.convert("123"");
    System.out.printIn(converted); ll 123
    需要注意如果@Functionallnterface如果没有指定,上面的代码也是对的。
    译者注将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如RhinoJavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有org.w3c.dom.events.EventTarget的
    addEventListener第二个参数EventListener。

    四、方法与构造函数引用


    前一节中的代码还可以通过静态方法引用来表示:代码如下:
    Converter<String, Integer> converter = Integer:valueOf; Integer converted = converter.convert("123");System.out.printIn(converted); 1/ 123
    Java 8允许你使用:关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:
    代码如下:
    converter = something:startsWith; String converted = converter.convert("Java");System.out.printIn(converted); 11 "”
    接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:代码如下:
    class Person { String firstName; String lastName;Person()0
    person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }接下来我们指定一个用来创建Person对象的对象工厂接口:
    代码如下:
    interface PersonFactory<P extends Person> { P create(String firstName, String lastName); }
    这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
    代码如下:
    PersonFactory<Person> personFactory = Person:new; Person person = personFactory.create("Peter""Parker");
    我们只需要使用Person:new来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
     

    五、Lambda 作用域


    在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。


    六、访问局部变量


    我们可以直接在lambda表达式中访问外层的局部变量:代码如下:
    final int num = 1; Converter<Integer, String stringConverter = (from) -> String.valueOf(from + num);stringConverter.convert(2); 1/ 3
    但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:代码如下:
    int num = 1; Converter<Integer, String stringConverter = (from) -> String.valueOf(from + num);stringConverter.convert(2); 1l 3
    不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:代码如下:
    int num = 1;Converter<Integer, String> stringConverter =(from)->String.valueOffrom + num); num = 3;在lambda表达式中试图修改num同样是不允许的。

    七、访问对象字段与静态变量


    和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:代码如下:
    class Lambda4 { static int outerStaticNum; int outerNum;
    void testScopes() { Converter<Integer, String> stringConverter1 =(from) -> { outerNum = 23; returnString.valueOf(from); };
    Converter<Integer, String> stringConverter2 = (from)-> { outerStaticNum = 72; returnstring.valueOf(from); 1;}}


    八、访问接口的默认方法


    还记得第一节中的formula例子么,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。Lambda表达式中是无法访问到默认方法的,以下代码将无法编译:
    代码如下:
    Formula formula = (a) -> sqrt( a * 100); Built-in Functional Interfaces
    JDK 1.8API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@Functionallnterface注解以便能用在lambda上。Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

    九、新的接口和类(不是很重要)

    predicate接口
    Predicate接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):
    代码如下:
    Predicate<string> predicate =(s)-> s.length() > 0;
    predicate.test(""foo""); ll true predicate.negate().test(""foo"); ll false
    Predicate<Boolean> nonNull = Objects:nonNull; Predicate<Boolean> isNull = Objects:isNull;Predicate<string> isEmpty = String:isEmpty; Predicate<string> isNotEmpty = isEmpty.negate();

    Function接口
    Function接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose,andThen) :
    代码如下:
    Function<String, Integer> toInteger = Integer::valueOf; Function<String, String backToString =tolnteger.andThen(String::valueOf);
    backToString.apply("123");11 "123"

    Supplier接口
    Supplier接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数

    代码如下:
    Supplier<Person> personSupplier = Person:new; personSupplier.get(); /l/ new Person

    Consumer接口Consumer接口表示执行在单个参数上的操作。
    代码如下:
    Consumer<Person> greeter=(p)-> System.out.printIn("Hello, " + p.firstName); greeter.accept(newPerson("Luke", "Skywalker"));


    Comparator接口Comparator是老Java中的经典接口,Java 8在此之上添加了多种默认方法:

    代码如下:
    Comparator<Person> comparator =(p1, p2)-> p1.firstName.compareTo(p2.firstName);Person p1 = new Person("John" , "Doe"); Person p2 = new Person("Alice" ,""Wonderland";comparator.compare(p1, p2);/l > 0 comparator.reversed().compare(p1, p2);ll<O
     

    optional接口
    Optional不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:
    Optional被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java8中,不推荐你返回null而是返回Optional。
    代码如下:
    Optional<string> optional = Optional.of("bam");
    optional.isPresent(); /l true optional.get(); ll "bam" optional.orElse("fallback"); // ""bam"
    optional.ifPresent((s)-> System.out.println(s.charAt(O))); 1/""b"

    Stream接口
    java.util.Stream表示能应用在一组元素上一次执行的操作序列。Stream操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。
    Stream的创建需要指定一个数据源,比如java.util.Collection的子类,List或者Set,Map不支持。Stream的操作可以串行执行或者并行执行。
    首先看看Stream是怎么用,首先创建实例代码的用到的数据List:代码如下:
    List<string> stringCollection = new ArrayList<>(); stringCollection.add("ddd2");
    stringCollection.add("aaa2"); stringCollection.add(""bbb1"); stringCollection.add("aaa1");stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2");stringCollection.add("ddd1");
    Java 8扩展了集合类,可以通过Collection.stream()或者Collection.parallelStream()来创建一个Stream。:


    Filter过滤
    过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。
    代码如下:
    stringCollection .stream() .filter((s) -> s.startsWith("a"7)) .forEach(System.out:println);//"aaa2", "aaa1"


    Sort排序
    排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
    代码如下:
    stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")).forEach(System.outprintIn);// "aaa1", "aaa2"
    需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
    代码如下:
    System.out.printIn(stringCollection); //ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1


    Map映射中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
    代码如下;
    stringCollection .stream() .map(String:toUpperCase) .sorted((a, b)-> b.compareTo(a)).forEach(System.out::println);
    1/"DDD2"",""DDD1","CCC","BBB3""""BBB2""""AAA2""""AAA1"

    Match匹配
    Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。
    代码如下:
    boolean anyStartsWithA = stringCollection .stream() .anyMatch((s)-> s.startsWith(" a"));System.out.println(anyStartsWithA); ll true
    boolean allStartsWithA = stringCollection .stream() .allMatch((s)-> s.startsWith("a"));System.out.println(allStartsWithA); // false
    boolean noneStartsWithZ= stringCollection .stream() .noneMatch((s) -> s.startsWith("z"));System.out.println(noneStartsWithz); // true
    count计数计数是一个最终操作,返回Stream中元素的个数,返回值类型是long.代码如下:
    long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith(""b")) .count();System.out.println(startsWithB); 1l 3
    Reduce规约
    这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:
    代码如下:
    Optional<string> reduced = stringCollection .stream().sorted() .reduce((s1, s2)->s1+ "#"+s2);reduced.ifPresent(System.out:printIn); /ll "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

    并行**Streams**
    前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
    下面的例子展示了是如何通过并行Stream来提升性能:首先我们创建一个没有重复元素的大表:
    代码如下:
    int max = 1000000; List<string> values = new ArrayList<>(max); for (int i = 0; i < max; i++) ( UUID uuid =UUID.randomUUID(); values.add(uuid.toString0));}
    然后我们计算一下排序这个Stream要耗时多久,串行排序:代码如下:
    long to = System.nanoTime();
    long count = values.stream().sorted().count(); System.out.printIn(count);long t1 = System.nanoTime();
    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.printIn(String.format("sequential sorttook: %d ms", millis));
    //串行耗时: 899 ms并行排序:
    代码如下:
    long to = System.nanoTime();
    long count = values.parallelStream().sorted().count(); System.out.printIn(count);long t1 = System.nanoTime();
    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.printIn(String.format("parallel sort took:%d ms", millis));
    //并行排序耗时:472ms上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream().
    Map
    前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。代码如下:
    Map<lnteger, String> map = new HashMap<>();
    for (int i = o; i<10; i++) { map.putlfAbsent(i, "val" + i);}
    map.forEach((id, val) -> System.out.printIn(val);以上代码很容易理解, putfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。
    下面的例子展示了map上的其他有用的函数:
    代码如下:

    Map<Integer, String> map = new HashMap<>();
    for (int i = 0; i< 10; i++) { map.putlfAbsent(i, "val" + i);}
    map.forEach((id, val)-> System.out.printIn(val);以上代码很容易理解,putifAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。
    下面的例子展示了map上的其他有用的函数:
    代码如下:
    map.computelfPresent(3, (num, val) -> val + num); map.get(3); // val33map.computelfPresent(9,(num, val) -> null); map.containsKey(9); // falsemap.computelfAbsent(23, num -> "val" + num); map.containsKey(23); ll truemap.computelfAbsent(3, num -> "bam"); map.get(3); // val33
    接下来展示如何在Map里删除一个键值全都匹配的项:
    代码如下:
    map.remove(3, "val3"); map.get(3); // val33map.remove(3, "val33"); map.get(3); ll null另外一个有用的方法:
    代码如下:
    map.getOrDefault(42, "not found""); // not found对Map的元素做合并也变得很容易了:
    代码如下:
    map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9);// val9
    map.merge(9, "concat", (value, newValue)-> value.concat(newValue)); map.get(9); // val9concatMerge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。九、Date API
    Java 8在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分:
    Clock时钟
    Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代System.currentTimeMilis()来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象

    代码如下:
    Clock clock = Clock.systemDefaultZone(); long millis = clock.millis);
    Instant instant = clock.instant(; Date legacyDate = Date.from(instant);// legacy java.util.Date

    Timezones时区
    在新API中时区使用Zoneld来表示。时区可以很方便的使用静态方法of来获取到。时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。
    代码如下:
    System.out.println(Zoneld.getAvailableZonelds(); //prints all available timezone idsZoneld zone1 =Zoneld.of("Europe/Berlin"); Zoneld zone2 = Zoneld.of(""Brazil/East");System.out.printIn(zone1.getRules()); System.out.printIn(zone2.getRules());
     ZoneRules[currentStandardOffset=+01:00]//ZoneRules[currentStandardOffset=-03:00]

    LocalTime本地时间
    LocalTime定义了一个没有时区信息的时间,例如晚上10点,或者17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
    代码如下:
    LocalTime now1 =LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2);

    System.out.printIn(now1.isBefore(now2)); // false
    long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween =ChronoUnit.MINUTES.between(now1, now2);
    System.out.printIn(hoursBetween); // -3 System.out.printIn(minutesBetween);// -239

    LocalTime提供了多种工厂方法来简化对象的创建,包括解析时间字符串。


    LocalTime本地时间
    LocalTime定义了一个没有时区信息的时间,例如晚上10点,或者17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
    代码如下:
    LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2);System.out.println(now1.isBefore(now2)); ll false
    long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween =ChronoUnit.MINUTES.between(now1 , now2);
    System.out.printIn(hoursBetween); // -3 System.out.printIn(minutesBetween); // -239LocalTime提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
    代码如下:
    LocalTime late = LocalTime.of(23,59,59); System.out.println(late); // 23:59:59
    DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.GERMAN);
    LocalTime leetTime = LocalTime.parse("13:37" , germanFormatter); System.out.printIn(leetTime);// 13:37

    LocalDateTime本地日期时间
    LocalDateTime同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。
    代码如下:
    LocalDateTime sylvester = LocalDateTime.of(2014,Month.DECEMBER,31,23,59,59);
    DayOfWeek dayOfWeek = sylvester.getDayOfWeek); System.out.printin(dayOfWeek);// WEDNESDAYMonth month = sylvester.getMonth(); System.out.printIn(month);// DECEMBER
    long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.printIn(minuteOfDay); //1439
    只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
    代码如下:
    Instant instant = sylvester .atZone(Zoneld.systemDefault()) .tolnstant();
    Date legacyDate = Date.from(instant); System.out.printIn(legacyDate);/l Wed Dec 3123:59:59 CET 2014格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:代码如下:
    DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yy - HH:mm");
    LocalDateTime parsed =LocalDateTime.parse("Nov 03,2014-07:13", formatter); String string =formatter.format(parsed); System.out.printIn(string); // Nov 03,2014-07:13
    和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。

    十、 Annotation注解


    在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。首先定义一个包装类Hints注解用来放置一组具体的Hint注解:
    代码如下:
    interface Hints { Hintvalue(); }
    @Repeatable(Hints.class)@interface Hint { String value(); }
    Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。例1:使用包装类当容器来存多个注解(老方法)
    代码如下:
    @Hints({@Hint(hint1"),@Hint("hint2"))) class Person 0例2:使用多重注解(新方法)
    代码如下:
    @Hint("hint1") @Hint("hint2") class Person0
    第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:代码如下:

    Hint hint = Person.class.getAnnotation(Hint.class); System.out.printIn(hint);// null
    Hints hints1 = Person.class.getAnnotation(Hints.class);System.out.printIn(hints1.value().length);//2

    Hint[]hints2 = Person.class.getAnnotationsByType(Hint.class);

    System.out.printIn(hints2.length); // 2

    即便我们没有在Person类上定义@Hints注解,我们还是可以通过getAnnotation(Hints.class)来获取@Hints注解,更加方便的方法是使用getAnnotationsByType可以直接获取到所有的@Hint注解。另外Java 8的注解还增加到两种新的target上了:
    代码如下:
    @Target((ElementType.TYPE_PARAMETER, ElementType.TYPE_USE))@interface MyAnnotation (关于Java 8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如Arrays.parallelSort, StampedLock和CompletableFuture等等。











     

    展开全文
  • 2、用匿名内部类Runnable接口作为参数开启一个线程,可以精简 3、小结,黑马JDK8新特性的b站视频 二、新增四个函数式接口,一开始觉得没啥用,后来想应该是解耦合以及实现代码复用 Supplier:生产者,有一个get方法 ...

    一、lambda表达式

           格式:()-> { 方法体 }

            方法的参数或局部变量类型是函数式接口(有且仅有一个抽象方法),

            @FunctionalInterface 函数式接口检验

            

    1、Collections.sort(List list,Comparato c),根据上面的规则

            

    2、用匿名内部类Runnable接口作为参数开启一个线程,可以精简

            

    3、小结,黑马JDK8新特性的b站视频

        

    二、新增四个函数式接口,一开始觉得没啥用,后来想应该是解耦合以及实现代码复用

    1. Supplier:生产者,有一个get方法
    2. Consumer:消费者,有accept方法和andThen()
    3. Function:apply方法,传一个类型的值,返回另一个类型的值,也有andThen,compose 方法(应该是执行先后的问题,源码里一个before,一个after),还有identity方 法(返回本身??)
    4. Predicate:test方法,做判断用,返回boolean类型  

    三、接口新增default方法 static 方法

    1. A、因为一个接口有很多实现类,比如Map,要是新增一个方法,所有实现类都会 报错,都需要重写这个方法,所以新增默认方法,方便方法的扩展。

            B、方式一:实现类可以直接调用

                  方式二:实现类可以重写   

    1. static方法不能用实现类的实例调用,直接 接口名.method()调用,也可以给接口 其他方法使用
    2. 静态方法和默认方法的区别 + 小结

             

    四、集合的Stream流式操作

            1、回顾 map 的四种遍历方式

    public void test(){
            Map<String,Integer> map = new HashMap<>();
            map.put("张三",14);
            map.put("张1",15);
            map.put("张2",17);
            map.put("张3",13);
    
            Set<String> set = map.keySet();
            set.stream().forEach(s-> System.out.println(s));
    
            Collection<Integer> collection = map.values();
            collection.stream().forEach(s-> System.out.println(s));
           
    
            Set<Map.Entry<String, Integer>> set1 = map.entrySet();
            set1.stream().forEach(s-> System.out.println(s));
    
            for (Map.Entry<String,Integer> me : map.entrySet()){
                System.out.println(me.getKey()+"  "+me.getValue());
            }
        }

            2、 a、获取流的方式一:集合对象 . stream();map 的话按上面获取集合形式

                   b、获取流的方式二:基本数据类型和引用数据类型数组有区别

                    可以看到,基本数据类型数组是作为一个元素进行操作

                    

             3、小结,常用方法和注意事项

                    

                    方法补充:

                            a、distinct方法可以直接去重,面试当时问到还不会

                            b、match(allMatch anyMatch nonematch , 全满足,任一满足,全部满足)返回  boolea类型

                            c、find(findFirst findAny)一样的查找第一个

                            d、sort 空参:默认排序,可以里面加 new Comparator接口自定义排序

                            e、max 和 min 方法:查找最大值 \ 最小值,需要加Comparator自定义排序,查找  出来是Optional类型的值,直接输出 String.format("Optional[%s]", value),不想  要可以用max.get()

                            f、reduce:传两个参数,返回一个值,能获取上一次的结果并进行操作

                            g、map 和 reduce 组合使用,可以将一个类型的转换类型并操作,比如对象转为    对象的属性然后对属性进行操作

                                  examp:比如统计一串字符中某个字符出现的次数,可以用filter过滤然后          count;或者用map将等于的变为1,不等的变为0,然后reduce相加

                            h、mapToInt,因为泛型里都是Integer包装类,操作会涉及装箱和拆箱,转为int      类型的话,会节省内存,用mapToInt(Integer::intValue),类名::方法名

                            i、静态 Stream.concat 合并两个流,注意,合并后原先的流不能进行操作,不同 同类型的也可以

            4、将结果收集成集合或者数组

                    a、集合:stream.collect(Collectors.toCollection(ArrayList(或别的)::new))如果是set、map类型,会自动去重,否则是List或Set(Collectors.toList(Set))

                    b、数组:stream.toArray(String[](或别的类型,否则是Object[])::new);

    后面还有好多,以后感兴趣再看...............................

    展开全文
  • 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 ...在1.8中,两者之间的数据结构是一样的,但是它们的底层代码却有一些很大的不同,让我们来看看ConcurrentHashMap是如何实现线程安全的! 一


    前言

    我们都知道HashMap是线程不安全的,而ConcurrentHashMap是线程安全的。在1.8中,两者之间的数据结构是一样的,但是它们的底层代码却有一些很大的不同,让我们来看看ConcurrentHashMap是如何实现线程安全的!

    一、ConcurrentHashMap存储结构

    DK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的Node数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS + synchronized实现更加细粒度的锁。
    在这里插入图片描述

    二、存储元素

    1、第一步:初始化数组长度

    问题:多线程并发的情况,可能会导致多个线程同时进行初始化数组操作

    解决:CAS乐观锁,这个办法,就让一个线程在初始化的时候,其他线程如果进来了,就在外面等着。

    当tab也就是原来的数组为空的时候,就要进行初始化数组过程,调用initTable()方法。
    在这里插入图片描述
    初始化数组initTable()

    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0) //SIZECTL<0时,说明已经有线程在初始化数组,那么就让线程等待
                Thread.yield(); //线程让步
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {  //当SIZECTL等于sc时,将SIZECTL值设为-1。成功返回true,失败返回false
                try {
                //初始化
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;  //sc<0的时候取一个默认容量16
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; //创建一个初始化长度为16的数组
                        table = tab = nt;  //赋值给tab和table
                        sc = n - (n >>> 2);  //12,相当于扩容阈值
                    }
                } finally {
                    sizeCtl = sc; //12
                }
                break;
            }
        }
        return tab;
    }
    ------------------------------
    sizeCtl三个值代表的意思
    sizeCtl=0  :  未初始化
    sizeCtl=-1  :  有线程正在初始化
    sizeCtl=12  :  预扩容大小
    

    SIZECTL其实就是内存中sizeCtl值
    在这里插入图片描述
    CAS – compareAndSwapInt(Object obj, long offset, int expect, int update)实际上就是在比较某个对象(obj)的某个字段在内存的值(offset)和期望(expect),如果offset==expect,就更新(update)。

    总结:iniTable()通过CAS机制,将内存中sizeCtl(通过SIZECTL得到)值,与sc进行比较,如果相等返回true并替换SIZECTL=-1,接着就执行初始化数组(16)的过程了。当下一个线程进来的时候,因为sizeCtl已经是-1了,就会执行Thread.yield()让步,此刻保证初始化过程中的线程安全。

    2、第二步:索引位为空时存储元素

    初始化过程结束了之后,要继续进行判断索引位置上是否有节点,没有节点的话就创建一个节点,和HashMap类似,但是在存储的过程中也要考虑线程安全。

    问题:多线程并发的情况,可能会存在多个线程同时在一个位置上放元素情况。比如说一个null的地方,线程t1和线程t2同时进来得到null信息,也就会同时创建新节点。

    解决:采用CAS,判断i和null是否相等,是则替换i的值不为空,这样下一个并发线程进来的时候就无法重复put。

    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
        if (casTabAt(tab, i, null,
                     new Node<K,V>(hash, key, value, null)))
            break;                   // no lock when adding to empty bin
    }
    

    比HashMap多了一个步骤就是casTabAt(),也是CAS机制:在这个tab中,判断i和null是否相等,如果相等才可以创建新节点。

    3、第三步:索引位不为空时存储元素

    依旧是为了保证多线程并发时的线程安全,之前说过1.7采用的是Segment 分段锁,而1.8则采用CAS + synchronized。
    下面的过程就体现了synchronized的作用,以数组上的每个索引,也就是头结点/根节点为一把锁,实现互斥访问。
    在这里插入图片描述

    else {
        V oldVal = null;
        synchronized (f) { //f是数组上的索引,加锁确保线程安全
            if (tabAt(tab, i) == f) { //如果i位置是索引也就是头结点
                /*
                * 第一段:如果是链表,就遍历链表,找合适位置放元素
                * */
                if (fh >= 0) { //fh是索引位置元素的hash值,大于等于0说明是链表
                    binCount = 1;
                    for (Node<K,V> e = f;; ++binCount) {
                        K ek;
                        if (e.hash == hash &&
                            ((ek = e.key) == key ||
                             (ek != null && key.equals(ek)))) {
                            oldVal = e.val;
                            if (!onlyIfAbsent)
                                e.val = value;
                            break;
                        }
                        Node<K,V> pred = e;
                        if ((e = e.next) == null) {
                            pred.next = new Node<K,V>(hash, key,
                                                      value, null);
                            break;
                        }
                    }
                }
                /*
                * 第二段:如果是树节点,就按照putTreeVal方式放元素
                * */
                else if (f instanceof TreeBin) { //判断头结点是否是树节点
                    Node<K,V> p;
                    binCount = 2;
                    if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                   value)) != null) {
                        oldVal = p.val;
                        if (!onlyIfAbsent)
                            p.val = value;
                    }
                }
            }
        }
        if (binCount != 0) {
            if (binCount >= TREEIFY_THRESHOLD)
                treeifyBin(tab, i);
            if (oldVal != null)
                return oldVal;
            break;
        }
    }
    

    这段主要是synchronized用法,其他代码和HashMap1.8的底层原理类似

    4、第四步:并发扩容

    问题:当有线程在进行扩容操作的时候,其他线程如果put元素,不清楚是否会往老数组中放元素还是新数组中放元素

    解决:在扩容的时候,暂不允许put元素,空闲线程则去帮忙扩容

    在putval源码中这段代码就是解决方法

    else if ((fh = f.hash) == MOVED)
    	tab = helpTransfer(tab, f);
    

    在扩容的时候会把头节点的hash置为MOVED(-1),当线程运行到这段代码的时候,就知道正在扩容,于是会帮忙扩容转移元素 - - - - helpTransfer()

    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }
    

    具体实现transfer

    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }
    

    三、ConcurrentHashMap常见面试题

    1、ConcurrentHashMap使用什么技术来保证线程安全?

    jdk1.7:Segment+HashEntry来进行实现的;

    jdk1.8:放弃了Segment臃肿的设计,采用Node+CAS+Synchronized来保证线程安全;

    2、HashMap 和 ConcurrentHashMap 的区别

    1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
    2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许

    3、ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

    JDK1.7

    首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

    在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:

    一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

    在这里插入图片描述

    1. 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
    2. Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

    JDK1.8

    在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

    结构如下:
    在这里插入图片描述

    4、ConcurrentHashMap默认初始容量是多少?

    从下面ConcurrentHashMap类的静态变量可以看出它的初始容量为16

    5、ConCurrentHashmap 的key,value是否可以为null?为什么?

    不行 如果key或者value为null会抛出空指针异常

    ConrrentHashMap 是一个用于多线程并发场景下的并发容器(Map),也就是在多线程环境下执行增删改查方法要保证线程安全性,

    为什么不能为null?这里涉及到二义性问题,所以当我们用get方法获取到一个value为null的时候,这里会产生二义性:

    • 可能没有这个key
    • 可能有这个key,只不过value为null

    HashMap如何解决二义性问题

    containsKey方法的结果一个为false一个为true,可以通过这个方法来区分上面说道的二义性问题

    public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
    }

    • 如果存在key为null的元素(key=null对应的hash值=0),getNode获取到值不为null;
    • 如果不存在key为null的元素,此时hash值=0对应的下标元素为null,即getNode获取到的值为null;

    ConcurrentHashMap为什么不能解决二义性问题

    因为ConcurrentHashMap是一个用在多线程并发的Map容器,不能put null是因为无法分辨是key没找到的null,还是有key值为null。这在多线程里面是没发保证会不会有其他线程修改为null键和null值的情况,所以不让put null。

    6、ConCurrentHashmap 每次扩容是原来容量的几倍

    2倍 在transfer方法里面会创建一个原数组的俩倍的node数组来存放原数据

    7、ConCurrentHashmap的数据结构是怎么样的?

    在java1.8中,它是一个数组+链表+红黑树的数据结构。

    8、ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?

    弱一致性,HashMap强一致性。

    ConcurrentHashMap可以支持在迭代过程中,向map添加新元素,而HashMap则抛出了ConcurrentModificationException,因为HashMap包含一个修改计数器,当你调用他的next()方法来获取下一个元素时,迭代器将会用到这个计数器。

    ConcurrentHashMap面试题参考链接
    https://blog.csdn.net/QGhurt/article/details/107323702
    https://blog.csdn.net/xt199711/article/details/114339022
    https://blog.csdn.net/afreon/article/details/120397899

    展开全文
  • 书到用时方恨少,由于前几日在对接...在java集合中,通过forEach方法遍历集合就用到了jdk1.8新特性:Lambda表达式与函数式接口 先看forEach方法的源码: default void forEach(Consumer<? super T> action) {
  • 在不使用JDK1.8新特性的情况下,通常的做法是 import java.util.ArrayList; import java.util.List; public class Test_Arr{ public static void main(String[] args) { List<String> arr= new ArrayList(); list....
  • JDK8新特性面试

    千次阅读 2021-03-08 19:04:30
    作为替代方式,接口可以提供一个默认的方法实现,所有这个接口的 实现类都会通过继承得倒这个方法(如果有需要也可以重写这个方法) Java 8 的另外一个新特性是接口里可以声明静态方法,并且可以实现 1 private ...
  • 点击上方“Java精选”,选择“设为星标”别问别人为什么,多问自己凭什么!下方留言必回,有问必答!每天08:35更新文章,每天进步一点点...Java8的新特性主要是Lambda表达式...
  • 键位置是唯一的,底层的数据结构控制键的 4.jdk1.8前数据结构是:链表 + 数组 jdk1.8之后是 : 链表 + 数组 + 红黑树 5.阈值(边界值) > 8 并且数组长度大于64,才将链表转换为红黑树,变为红黑树的目的是为了高效的...
  • 面试jdk 1.7和1.8的区别

    2022-03-11 14:26:36
    jdk1.8 当每个链表长度 >8 ,并且数组元素个数 ≥64时,会调整成红黑树,目的是提高效率 jdk1.8 当链表长度 <6 时 调整成链表 jdk1.8 以前,链表时头插入,之后为尾插入 原因:头插法在并发情况下会出现链表...
  • 面试JDK1.8新特性

    千次阅读 2020-04-20 12:06:27
    先都摆上,自己会不定期修改和完善 嘎嘎 ①引入了lambda表达式,可以简化匿名内部类的代码,允许将方法作为参数。 ②方法引用,可以进一步简化lambda表达式的书写,可以引用类的构造方法,静态方法,特定类的...
  • Jdk1.7 与 jdk1.8的区别,最新的特征有哪些(美团,360,京东面试题目)在jdk7的新特性方面主要有下面几方面的增强:1.1二进制变量的表示,支持将整数类型用二进制来表示,用0b开头。 所有整数int、short、long、byte...
  • jdk1.8新特性Stream流操作的学习笔记 今天学习了jdk1.8新特性Stream的操作,这种写法是专门针对集合的操作,通过Stream流里面封装的逻辑,程序员可以写出非常简洁,干净的代码而完成高效复杂的逻辑,它的操作类似...
  • 一旦被创建其状态则不可被改变,任何对它的修改都会创建一个的对象。 3.可以创建一个包含可变对象的不可变对象 Static关键字相关:https://zhuanlan.zhihu.com/p/71631414 创建对象的几种方式 1. 采用new关键字 2...
  • HashMap jdk1.8版本新特性讲解

    千次阅读 2018-06-07 12:24:05
    今天给大家分享一下,HashMap jdk1.8特性讲解.1.背景介绍什么是HASHMAP?HashMap是基于哈希表的Map接口的实现,存储的是键值对,并允许使用null键和null值.HashMap是非Synchronized,即是线程不安全的.如何要满足线程...
  • 不知道大家有没有遇到这样的情况:虽然JDK1.9已经出来了,但是去公司面试的时候,仍然经常会被问到JDK1.8新特性方面的问题。 需要更多教程,微信扫码即可 ???????????? 别忘了扫码领资料哦【高清Java学习路线...
  • 面试中经常被问到的list如何去重,一般是口述,不需要代码体现,这个时候,思维一定要清晰,可以罗列出集中去重的方法,以展现你对list数据结构,以及相关方法的掌握,体现你的java基础学的是否牢固 下面,我就将五种方法...
  • 首先简单说说他的特性。 concurrentHashMap跟Hashtable具有相同的功能方法。可以看作是Hashtable的升级版,HashMap的线程安全版。 跟Hashtable相同,它的键或值不允许是null。 ConcurrentHashMap和HashMap一样...
  • 为什么 JDK 1.8 之前的时间与日期 API 不好用? 1、java.util.Date 是从 JDK 1.0 开始提供,易用性差 默认是中欧时区(Central Europe Time) 起始年份是 1900 年 起始月份从 0 开始 对象创建之后...
  • HashMap面试题(jdk1.8)

    2021-11-16 14:14:57
    文章目录1、HashMap的特性?2、HashMap底层原理? 1、HashMap的特性? (1)HashMap底层是Node数组+链表+红黑树,键值对允许均允许null且线程不安全; (2)HashMap默认容量为16、默认阈值12、默认负载因子0.75f、树化...
  • Java 8 新特性 及 常见 面试题

    万次阅读 多人点赞 2018-09-22 21:34:44
    Java 8 新特性简介: 1. 代码更少(增加了语法:Lambda 表达式) 2. 强大的 Stream API(集合数据的操作) 3. 最大化的减少空指针 异常:Optional 类 的使用 4. 接口的新特性 5. 注解的新特性 6. 集合的底层 源码...
  • 史上最详细的 JDK 1.8 HashMap 源码解析

    万次阅读 多人点赞 2018-01-07 18:00:41
    JDK1.8 的实现中,还优化了高位运算的算法,将 hashCode 的高 16 位与 hashCode 进行异或运算,主要是为了在 table 的 length 较小的时候,让高位也参与运算,并且不会有太大的开销。 下图是一个简单的例子: 当...
  • jdk1.8新特性 java语言有哪些优点? 同一个.java文件中是否可以有多个main方法 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 如何在main方法执行前输出”hello world” java程序的初始化顺序 ...
  • HashMap集合简介 HashMap 基于哈希表的Map接口实现,是以 key-value 存储形式存在,即主要用来存放键值对。...JDK1.8之前 HashMap 由 数组+链表 组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突(...
  • HashMap的结构:数组 + 链表,数组 + 链表 + 红黑树,何时转为红黑树、何时转回链表 拉链法 类图、成员变量、构造函数 常见方法:get、put、remove、containsKey、扩容 常见面试题(hashCode和equals的关系) ...
  • 作者:依本多情blog.csdn.net/qq_36520235/article/details/82417949一、真实面试题之:Hashmap的结构...
  • INITIAL_CAPACITYMAXIMUM_CAPACITYDEFAULT_LOAD_FACTORTREEIFY_THRESHOLDMIN_TREEIFY_CAPACITYUNTREEIFY_THRESHOLDHashMap部分源码put()hash()putVal()HashMap面试题1.说一下Ha...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,991
精华内容 3,596
关键字:

jdk1.8新特性面试题

友情链接: giepou_v86.zip