知识屋:更实用的电脑技术知识网站
所在位置:首页 > 教育

Java基础知识点面试手册

发表时间:2022-03-25来源:网络


前言

本文快速回顾了Java中最基础的知识点,用作面试复习,事半功倍。

知乎阅读体验不好,可以移步Java基础知识点面试手册 - Rude3Knife的博客 - CSDN博客查看该文章,获得更好的阅读体验。

说明

整合以下文章,并进行补充:

微信文章:精华:Java 开发岗面试知识点解析
http://how2j.cn/k/j2se-interview/j2se-interview-java/624.html
https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%9F%BA%E7%A1%80.md

基础知识点

面向对象的特性

答:封装、继承和多态。

多态分为编译时多态和运行时多态。

编译时多态:方法的重载运行时多态:指程序中定义的对象引用所指向的具体类型在运行期间才确定。

运行时多态有三个条件

多态的存在有三个前提: - 要有继承关系 - 子类要重写父类的方法 - 父类引用指向子类对,

父类Animal

class Animal { int num = 10; static int age = 20; public void eat() { System.out.println("动物吃饭"); } public static void sleep() { System.out.println("动物在睡觉"); } public void run(){ System.out.println("动物在奔跑"); } }

子类Cat

class Cat extends Animal { int num = 80; static int age = 90; String name = "tomCat"; public void eat() { System.out.println("猫吃饭"); } public static void sleep() { System.out.println("猫在睡觉"); } public void catchMouse() { System.out.println("猫在抓老鼠"); } }

测试类Demo_Test1

class Demo_Test1 { public static void main(String[] args) { Animal am = new Cat(); am.eat(); am.sleep(); am.run(); //am.catchMouse();这里先注释掉,等会会说明 //System.out.println(am.name);//这里先注释,待会说明 System.out.println(am.num); System.out.println(am.age); } }

以上的三段代码充分体现了多态的三个前提,即:

1、存在继承关系 Cat类继承了Animal类 2、子类要重写父类的方法 子类重写(override)了父类的两个成员方法eat(),sleep()。其中eat()是非静态的,sleep()是静态的(static)。 3、父类数据类型的引用指向子类对象。

如果再深究一点呢,我们可以看看上面测试类的输出结果,或许对多态会有更深层次的认识。猜一猜上面 的结果是什么。

可以看出来

子类Cat重写了父类Animal的非静态成员方法am.eat();的输出结果为:猫吃饭。

子类重写了父类(Animal)的静态成员方法am.sleep();的输出结果为:动物在睡觉

未被子类(Cat)重写的父类(Animal)方法am.run()输出结果为:动物在奔跑

那么我们可以根据以上情况总结出多态成员访问的特点:

成员变量 - 编译看左边(父类),运行看左边(父类) 成员方法 - 编译看左边(父类),运行看右边(子类)。动态绑定 静态方法 - 编译看左边(父类),运行看左边(父类)。 (静态和类相关,算不上重写,所以,访问还是左边的) 只有非静态的成员方法,编译看左边,运行看右边

那么多态有什么弊端呢?

不能使用子类特有的成员属性和子类特有的成员方法。

参考:https://www.zhihu.com//30082151

很明显,执行强转语句Cat ct = (Cat)am;之后,ct就指向最开始在堆内存中创建的那个Cat类型的对象了。 这就是多态的魅力吧,虽然它有缺点,但是它确实十分灵活,减少多余对象的创建,不用说为了使用子类的某个方法又去重新再堆内存中开辟一个新的子类对象。

数据类型

String是类类型,不是基本类型。

基本类型 有八种:整型 (4种)字符型 (1种)浮点型 (2种)布尔型(1种)





缓存池

public class Main_1 { public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d);//true,缓存池 System.out.println(e == f);//false,不在缓存池 System.out.println(c == (a + b));//true System.out.println(c.equals(a + b));//true System.out.println(g == (a + b));//true System.out.println(g.equals(a + b));//false System.out.println(g.equals(a + h));//true }

使用==的情况:

如果比较Integer变量,默认比较的是地址值。特例:如果比较的某一边有操作表达式(例如a+b),那么比较的是具体数值

使用equals()的情况:

无论是Integer还是Long中的equals()默认比较的是数值特例:Long的equals()方法,JDK的默认实现:会判断是否是Long类型

new Integer(123) 与 Integer.valueOf(123) 的区别在于:

new Integer(123) 每次都会新建一个对象Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。

valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

public static Integer valueOf(int i) { if (i >= IntegerCache.low && i


3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

Program Creek : Why String is immutable in Java?

String, StringBuffer and StringBuilder

1. 可变性

String 不可变StringBuffer 和 StringBuilder 可变

2. 线程安全

String 不可变,因此是线程安全的StringBuilder 不是线程安全的StringBuffer 是线程安全的,内部使用 synchronized 进行同步

对于三者使用的总结:

1.如果要操作少量的数据用 String

2.单线程操作字符串缓冲区下操作大量数据 StringBuilder

3.多线程操作字符串缓冲区下操作大量数据 StringBuffer

String 和StringBuffer的区别?

String是immutable的,其内容一旦创建好之后,就不可以发生改变。

StringBuffer 是可以变长的,内容也可以发生改变 改变的原理是StringBuffer内部采用了字符数组存放数据,在需要增加长度的时候,创建新的数组,并且把原来的数据复制到新的数组这样的办法来实现。

https://blog.csdn.net/yeweiyang16/article/details/51755552

初始化:可以指定给对象的实体的初始容量为参数字符串s的长度额外再加16个字符

扩容:尝试将新容量扩为大小变成原容量的1倍+2,然后if判断一下 容量如果不够,直接扩充到需要的容量大小。

StackOverflow : String, StringBuffer, and StringBuilder

String Pool

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。

当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。

String s1 = new String("aaa"); String s2 = new String("aaa"); System.out.println(s1 == s2); // false String s3 = s1.intern(); String s4 = s1.intern(); System.out.println(s3 == s4); // true

如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

String s5 = "bbb"; String s6 = "bbb"; System.out.println(s5 == s6); // true

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

StackOverflow : What is String interning?深入解析 String#intern

new String("abc")

使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。

"abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量;而使用 new 的方式会在堆中创建一个字符串对象。

创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。

public class NewStringTest { public static void main(String[] args) { String s = new String("abc"); } }

使用 javap -verbose 进行反编译,得到以下内容:

// ... Constant pool: // ... #2 = Class #18 // java/lang/String #3 = String #19 // abc // ... #18 = Utf8 java/lang/String #19 = Utf8 abc // ... public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: new #2 // class java/lang/String 3: dup 4: ldc #3 // String abc 6: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V 9: astore_1 // ...

在 Constant Pool 中,#19 存储这字符串字面量 "abc",#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。

以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。

public String(String original) { this.value = original.value; this.hash = original.hash; }

运算

参数传递

参数传递

Java 的参数是以值传递的形式传入方法中,而不是引用传递。

以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,在一方改变其所指向对象的内容时对另一方没有影响。

public class Dog { String name; Dog(String name) { this.name = name; } String getName() { return this.name; } void setName(String name) { this.name = name; } String getObjectAddress() { return super.toString(); } } public class PassByValueExample { public static void main(String[] args) { Dog dog = new Dog("A"); System.out.println(dog.getObjectAddress()); // Dog@4554617c func(dog); System.out.println(dog.getObjectAddress()); // Dog@4554617c System.out.println(dog.getName()); // A } private static void func(Dog dog) { System.out.println(dog.getObjectAddress()); // Dog@4554617c dog = new Dog("B"); System.out.println(dog.getObjectAddress()); // Dog@74a14482 System.out.println(dog.getName()); // B } }

如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。

class PassByValueExample { public static void main(String[] args) { Dog dog = new Dog("A"); func(dog); System.out.println(dog.getName()); // B } private static void func(Dog dog) { dog.setName("B"); } }

StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?

float 与 double

Java 不能隐式执行向下转型,因为这会使得精度降低。

1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。Java 不能隐式执行向下转型,因为这会使得精度降低。

// float f = 1.1;

1.1f 字面量才是 float 类型。

float f = 1.1f;

隐式类型转换

因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。

short s1 = 1; // s1 = s1 + 1;

但是使用 += 运算符可以执行隐式类型转换。

s1 += 1;

上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:

s1 = (short) (s1 + 1);

StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?

switch

从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。

String s = "a"; switch (s) { case "a": System.out.println("aaa"); break; case "b": System.out.println("bbb"); break; }

switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。

继承

访问权限

Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。

可以对类或类中的成员(字段以及方法)加上访问修饰符。

类可见表示其它类可以用这个类创建实例对象。成员可见表示其它类可以用这个类的实例对象访问到该成员;

protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别:这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。

字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。

例如下面的例子中,AccessExample 拥有 id 共有字段,如果在某个时刻,我们想要使用 int 去存储 id 字段,那么就需要去修改所有的客户端代码。

public class AccessExample { public String id; }

可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。

public class AccessExample { private int id; public String getId() { return id + ""; } public void setId(String id) { this.id = Integer.valueOf(id); } }

但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。

public class AccessWithInnerClassExample { private class InnerClass { int x; } private InnerClass innerClass; public AccessWithInnerClassExample() { innerClass = new InnerClass(); } public int getValue() { return innerClass.x; // 直接访问 } }

抽象类与接口

1. 抽象类

抽象类和普通类最大的区别是:

抽象类不能被实例化,需要继承抽象类才能实例化其子类

public abstract class AbstractClassExample { protected int x; private int y; public abstract void func1(); public void func2() { System.out.println("func2"); } } public class AbstractExtendClassExample extends AbstractClassExample { @Override public void func1() { System.out.println("func1"); } }

2. 接口

接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。

在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。

接口的字段默认都是 static 和 final 的。

public interface InterfaceExample { void func1(); default void func2(){ System.out.println("func2"); } int x = 123; // int y; // Variable 'y' might not have been initialized public int z = 0; // Modifier 'public' is redundant for interface fields // private int k = 0; // Modifier 'private' not allowed here // protected int l = 0; // Modifier 'protected' not allowed here // private void fun3(); // Modifier 'private' not allowed here } public class InterfaceImplementExample implements InterfaceExample { @Override public void func1() { System.out.println("func1"); } }

3. 比较

从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;需要使用多重继承。

使用抽象类:

需要在几个相关的类中共享代码。需要能控制继承来的成员的访问权限,而不是都为 public。需要继承非静态static和非常量final字段。

在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

深入理解 abstract class 和 interfaceWhen to Use Abstract Class and Interface

super

访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。访问父类的成员:如果子类重写了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。

Using the Keyword super

继承相关小问题

接口是否可继承接口?

可以,比如List 就继承了接口Collection

抽象类是否可实现(implements)接口?

可以,比如 MouseAdapter鼠标监听适配器 是一个抽象类,并且实现了MouseListener接口

抽象类是否可继承实体类(concrete class)?

可以,所有抽象类,都继承了Object

Object 通用方法

经典:

https://fangjian0423.github.io/2016/03/12/java-Object-method/

概览

public native int hashCode() public boolean equals(Object obj) protected native Object clone() throws CloneNotSupportedException public String toString() public final native Class getClass() protected void finalize() throws Throwable {} public final native void notify() public final native void notifyAll() public final native void wait(long timeout) throws InterruptedException public final void wait(long timeout, int nanos) throws InterruptedException public final void wait() throws InterruptedExceptiongetClass方法返回当前运行时对象的Class对象


hashCode方法该方法返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。(文章中有对hashcode的详细解释)equals方法如果重写了equals方法,通常有必要重写hashCode方法,这点已经在hashCode方法中说明了。


clone方法创建并返回当前对象的一份拷贝。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。toString方法Object对象的默认实现,即输出类的名字@实例的哈希码的16进制。


notify方法:唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。notifyAll方法跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。


wait(long timeout) throws InterruptedException方法wait方法会让当前线程等待直到另外一个线程调用对象的notify或notifyAll方法,或者超过参数设置的timeout超时时间。wait(long timeout, int nanos) throws InterruptedException方法跟wait(long timeout)方法类似,多了一个nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。


wait() throws InterruptedException方法
跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念。

需要注意的是 wait(0, 0)和wait(0)效果是一样的,即一直等待。

finalize方法
该方法的作用是实例被垃圾回收器回收的时候触发的操作,就好比 “死前的最后一波挣扎”。

补充:什么是Native Method

简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。

equals()

1. 等价关系

Ⅰ 自反性

x.equals(x); // true

Ⅱ 对称性

x.equals(y) == y.equals(x); // true

Ⅲ 传递性

if (x.equals(y) && y.equals(z)) x.equals(z); // true;

Ⅳ 一致性

多次调用 equals() 方法结果不变

x.equals(y) == x.equals(y); // true

Ⅴ 与 null 的比较

对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false

x.equals(null); // false;

2. 等价与相等

对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。Integer x = new Integer(1); Integer y = new Integer(1); System.out.println(x.equals(y)); // true System.out.println(x == y); // false

3. 实现

检查是否为同一个对象的引用,如果是直接返回 true;检查是否是同一个类型,如果不是,直接返回 false;将 Object 对象进行转型;判断每个关键域是否相等。public class E { private int x; private int y; private int z; public E(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; E that = (E) o; if (x != that.x) return false; if (y != that.y) return false; return z == that.z; } }

4. 两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

因为hashCode()方法和equals()方法都可以通过自定义类重写,是可以做到equals相同,但是hashCode不同的

但是,在Object类的equals()方法中有这么一段话

翻译如下:

通常来讲,在重写这个方法的时候,也需要对hashCode方法进行重写, 以此来保证这两个方法的一致性—— 当equals返回true的时候,这两个对象一定有相同的hashcode.

hashCode()

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。

在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。

理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。

一个数与 31 相乘可以转换成移位和减法:31*x == (x e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> { int result = e1.compareTo( e2 ); return result; } );

函数式接口

指的是只有一个函数的接口,java.lang.Runnable 和 java.util.concurrent.Callable 就是函数式接口的例子;

只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,java8 提供了一个特殊的注解 @Functionallnterface 来标明该接口是一个函数式接口。

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } }

接口的默认方法和静态方法

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写。(换句话说,可以直接继承,也可以覆写该默认方法

静态方法:见上方链接

由于JVM上的默认方法的实现在字节码层面提供了支持因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。

尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档。

方法引用

不甚理解,见上方链接

引入重复注解

注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。

在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。

更好的类型推断

Java 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。

注解的使用场景拓宽

注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。

Java编译器的新特性

参数名称

为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer liberary。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。

Java官方库的新特性

HashMap/CurrentHashMap等变化

HashMap 是数组 + 链表 + 红黑树(JDK1.8 增加了红黑树部分)实现。

Optional

在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。

接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:

Optional< String > fullName = Optional.ofNullable( null ); System.out.println( "Full Name is set? " + fullName.isPresent() ); System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。

Streams

新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。

// Calculate total points of all active tasks using sum() final long totalPointsOfOpenTasks = tasks .stream() .filter( task -> task.getStatus() == Status.OPEN ) .mapToInt( Task::getPoints ) .sum(); System.out.println( "Total points: " + totalPointsOfOpenTasks );

Date/Time API

包含了所有关于日期、时间、时区、持续时间和时钟操作的类。(Java 8 的日期与时间问题解决方案)

新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。

这些类都是不可变的、线程安全的。

Base64

对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码

并行数组

Java8版本新增了很多新的方法,用于支持并行数组处理。

最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。

并发性

基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作

另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作

JVM的新特性

JVM 内存管理方面,由元空间代替了永久代。

其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

区别: 1. 元空间并不在虚拟机中,而是使用本地内存; 2. 默认情况下,元空间的大小仅受本地内存限制; 3. 也可以通过 -XX:MetaspaceSize 指定元空间大小。

为什么要做这个转换?所以,最后给大家总结以下几点原因:

字符串存在永久代中,容易出现性能问题和内存溢出。类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。Oracle 可能会将HotSpot 与 JRockit 合二为一。

Java 7和8的新特性(英文)

New highlights in Java SE 8

Lambda ExpressionsPipelines and StreamsDate and Time APIDefault MethodsType AnnotationsNashhorn JavaScript EngineConcurrent AccumulatorsParallel operationsPermGen Error Removed

New highlights in Java SE 7

Strings in Switch StatementType Inference for Generic Instance CreationMultiple Exception HandlingSupport for Dynamic LanguagesTry with ResourcesJava nio PackageBinary Literals, Underscore in literals Diamond Syntax
Difference between Java 1.8 and Java 1.7?
Java 8 特性

Java 与 C++ 的区别

Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。Java 支持自动垃圾回收,而 C++ 需要手动回收。Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。Java 不支持操作符重载,虽然可以对两个 String 对象支持加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。

What are the main differences between Java and C++?


关注我

本人目前为后台开发工程师,主要关注Python爬虫,后台开发等相关技术。

原创博客主要内容:

笔试面试复习知识点手册Leetcode算法题解析(前150题)剑指offer算法题解析Python爬虫相关实战后台开发相关实战

同步更新以下几大博客:

Csdn:
http://blog.csdn.net/
拥有专栏:Leetcode题解(Java/Python)、Python爬虫开发知乎:
https://www.zhihu.com/people/yang-zhen-dong-1/
拥有专栏:码农面试助攻手册掘金:
https://juejin.im/user/5b48015ce51d45191462ba55简书:
https://www.jianshu.com/u/b5f225ca2376个人公众号:Rude3Knife

收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜