谈谈Java8的新特性一

谈谈Java8的新特性

虽然java8已经发布了很久,但是很多开发人员还是继续使用着原来java语法编写代码,很少会用到java8的一些新的语法。这一批人,包含了有多年的开发人员,大部分有多年开发经验的人员,对于一些新的技术,还是比较缺乏主动学习的热情。

对于一个技术开发人员,对于一些新的技术,咋们还是得不断的学习,不断的接收新的技术,不然就会跟不上当今流行的技术。有句话说得比较好:“落后就会被挨打”,大家都不愿意被打吧?

哈哈,鉴于此,所以今天我们来谈谈Java8的一些新特性

我们就从以下这些新特性来展开说说:

  1. 接口的新特性
  2. 注解的新特性
  3. 集合的底层源码实现
  4. 新日期时间的API
  5. Optional类的使用 (最大化减少空指针异常)
  6. Lambda 表达式(Lambda Expressions) (代码更少)
  7. Stream API (便于并行)

1. 接口的新特性

Java 8中,你可以为接口添加静态方法默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。

静态方法: 使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。

默认方法: 默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。

  • 例如:以下的例子
public interface TestA {
    double PI = 3.14;

    //接口,可以定义“默认方法”方法
    public default void method() {
        System.out.println("广东");
    }

    default String method1() {
        return "湖南";
    }
    
    //接口,可以定义“静态方法”方法
    public static void method2() {
        System.out.println(“hello lambda!");
    }
}

复制代码
  • 接口中的默认方法

接口默认方法的“类优先”原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时

  • 选择父类中的方法。 如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。

  • 接口冲突。 如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么实现类必须覆盖该方法来解决冲突

    扫描二维码关注公众号,回复: 13757182 查看本文章

接口冲突的解决方式:

interface MyDemo1{
    default String getName(){
        return "hello java8 demo1!";
    }
}

interface MyDemo2{
    default String getName(){
        return "hello java8 demo2!";
    }
}

//MyDemo类,实现了Mydemo1和MyDemo2接口
//MyDemo1和MyDemo2接口,都有默认方法getName()
//那么MyDemo类必须重写覆盖getName()方法
class MyDemo implements MyDemo1,MyDemo2{
    
    //实现类必须覆盖该方法来解决冲突
    public String getName(){
        return MyDemo1.super.getName();
    }
}
复制代码

2. 注解的新特性

Java 8 中关于注解的修改

Java 8对注解处理提供了两点改进:可重复的注解可用于类型的注解。此外,反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
	String[] value();
}


@Target({TYPE, FIELD, METHOD, PARAMETER,PACKAGE, CONSTRUCTOR, LOCAL_VARIABLE,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class) //表示可重复
public @interface MyAnnotation {
	String[] value();
}

//应用场景:
public Person(@MyAnnotation(value="notnull")String name){
	this.name = name;
}
复制代码

3. 集合的底层源码实现

ArrayList、LinkedList、Vector区别?

List子接口:存储序的、可重复的数据、"动态"数组

  • ArrayList:作为List的主要实现类;线程不安全的,效率高;底层使用数组实现

(Collections中定义了synchronizedList(List list)将此ArrayList转化为线程安全的)

  • LinkedList:对于频繁的插入、删除操作,我们建议使用此类,因为效率高;内存消耗较ArrayList大;底层使用双向链表实现

  • Vector:List的古老实现类;线程安全的,效率低;底层使用数组实现

补充:你不可不知的数据结构

image.png

image.png

然后可以分析一下ArrayList和LinkedList的底层源码实现

ArrayList 源码分析

jdk7:

ArrayList list = new ArrayList();//初始化一个长度为10的Object[] elementData
sysout(list.size());//返回存储的元素的个数:0
list.add(123);
list.add(345);
...
复制代码

当添加第11个元素时,需要扩容,默认扩容为原来的1.5倍。还需要将原有数组中的数据复制到新的数组中。

删除操作:如果删除某一个数组位置的元素,需要其后面的元素依次前移。

remove(Object obj) / remove(int index)
复制代码

jdk8:

ArrayList list = new ArrayList();//初始化一个长度为0的Object[] elementData
sysout(list.size());//返回存储的元素的个数:0
list.add(123);//此时才创建一个长度为10的Object[] elementData
list.add(345);
...
复制代码

当添加第11个元素时,需要扩容,默认扩容为原来的1.5倍。还需要将原有数组中的数据复制到新的数组中。

开发时的启示:

  1. 建议使用:ArrayList list = new ArrayList(int length);
  2. jdk8延迟了底层数组的创建:内存的使用率;对象的创建更快

LinkedList 源码分析

LinkedList:底层使用双向链表存储添加的元素

void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
复制代码

内部类体现:

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
}
复制代码

HashMap和Hashtable的对比

HashMap:Map的主要实现类;线程不安全的,效率高;可以存储null的key和value (存储结构:jdk7.0 数组+链表; jdk8.0 数组+链表+红黑树)

Hashtable:Map的古老实现类;线程安全的,效率低;不可以存储null的key和value

HashMap的底层实现原理

HashMap map = new HashMap();//底层创建了长度为16的Entry数组 向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i.如果位置i上没有元素,则entry1直接添加成功。如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry是否equals.如果返回值为true.则使用entry1的value去替换equals为true的entry的value.如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。

默认情况下,如果添加元素的长度 >= DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR (临界值threshold默认值为12)且新要添加的数组位置不为null的情况下,就进行扩容。默认扩容为原有长度的2倍。将原有的数据复制到新的数组中。

jdk 8.0 :

1.HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组。

2.当首次调用map.put()时,再创建长度为16的数组

3.当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有entry使用红黑树进行存储。而jdk 7 中没有红黑树结构

4.新添加的元素如果与现有的元素以链表方式存储的时候:“七上八下”:jdk7:新添加的当链表头,jdk8:新添加的当链表尾
复制代码

HashMap的存储结构

JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)

JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。

jdk7: image.png

jdk8: image.png

  • 负载因子值的大小,对HashMap有什么影响

负载因子的大小决定了HashMap的数据密度。

负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。

负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。

  • 拓展1:LinkedHashMap 源码分析

LinkedHashMap继承于HashMap,在HashMap底层结构的基础上额外添加了一对链表:

static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
}
复制代码
  • 拓展2:HashSet / LinkedHashSet 等底层源码有变化吗?

image.png

public static void main(String[] args) {
	List list = new ArrayList();
	list.add(1):
	list.add(2);
	list.add(3);
	updateList(list);
	System.out.println(list);
}

private static void updateList(List list) {
	list.remove(2); 
}
复制代码
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1);
System.out.println(set);
set.add(new Person(1001,"CC"));
System.out.println(set);
set.add(new Person(1001,"AA"));
System.out.println(set);

其中,其中Person类中重写了hashCode()和equal()方法
复制代码

好了,今天就分享到这里了,剩下的几个新特性,下次再分享了。

哈哈,敬请期待!!!

猜你喜欢

转载自juejin.im/post/7082578833135632398