合作公司做网站,网站开发用什么字体一般,开发一个游戏的过程,个人网站搭建详细流程1.什么是 Java#xff1f;
Java 是一门面向对象的编程语言#xff0c;不仅吸收了 C语言的各种优点#xff0c;还摒弃了 C里难以理解的多继承、指针等概念#xff0c;因此 Java 语言具有功能强大和简单易用两个特征。Java 语言作为静态面向对象编程语言的优秀代表#xff…1.什么是 Java
Java 是一门面向对象的编程语言不仅吸收了 C语言的各种优点还摒弃了 C里难以理解的多继承、指针等概念因此 Java 语言具有功能强大和简单易用两个特征。Java 语言作为静态面向对象编程语言的优秀代表极好地实现了面向对象理论允许程序员以优雅的思维方式进行复杂的编程 。
2.Java 语言有哪些特点 面向对象封装继承多态平台无关性平台无关性的具体表现在于Java 是“一次编写到处运行Write OnceRun any Where”的语言因此采用 Java 语言编写的程序具有很好的可移植性而保证这一点的正是 Java 的虚拟机机制。在引入虚拟机之后Java 语言在不同的平台上运行不需要重新编译。支持多线程。C 语言没有内置的多线程机制因此必须调用操作系统的多线程功能来进行多线程程序设计而 Java 语言却提供了多线程支持编译与解释并存
3.JVM、JDK 和 JRE 有什么区别
JVMJava Virtual MachineJava 虚拟机Java 程序运行在 Java 虚拟机上。针对不同系统的实现WindowsLinuxmacOS不同的 JVM因此 Java 语言可以实现跨平台。
JRE Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合包括 Java 虚拟机JVMJava 类库Java 命令和其他的⼀些基础构件。但是它不能⽤于创建新程序。
JDK: Java Development Kit它是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切还有编译器javac和⼯具如 javadoc 和 jdb。它能够创建和编译程序。
简单来说JDK 包含 JREJRE 包含 JVM。 4.说说什么是跨平台性原理是什么
所谓跨平台性是指 Java 语言编写的程序一次编译后可以在多个系统平台上运行。
实现原理Java 程序是通过 Java 虚拟机在系统平台上运行的只要该系统可以安装相应的 Java 虚拟机该系统就可以运行 java 程序。
5.什么是字节码采用字节码的好处是什么?
所谓的字节码就是 Java 程序经过编译之类产生的.class 文件字节码能够被虚拟机识别从而实现 Java 程序的跨平台性。
Java 程序从源代码到运行主要有三步
编译将我们的代码.java编译成虚拟机可以识别理解的字节码(.class)解释虚拟机执行 Java 字节码将字节码翻译成机器能识别的机器码执行对应的机器执行二进制机器码
只需要把 Java 程序编译成 Java 虚拟机能识别的 Java 字节码不同的平台安装对应的 Java 虚拟机这样就可以可以实现 Java 语言的平台无关性。
6.为什么说 Java 语言“编译与解释并存”
高级编程语言按照程序的执行方式分为编译型和解释型两种。
简单来说编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。
比如你想读一本外国的小说你可以找一个翻译人员帮助你翻译有两种选择方式你可以先等翻译人员将全本的小说也就是源码都翻译成汉语再去阅读也可以让翻译人员翻译一段你在旁边阅读一段慢慢把书读完。
Java 语言既具有编译型语言的特征也具有解释型语言的特征因为 Java 程序要经过先编译后解释两个步骤由 Java 编写的程序需要先经过编译步骤生成字节码\*.class 文件这种字节码必须再经过 JVM解释成操作系统能识别的机器码在由操作系统执行。因此我们可以认为 Java 语言编译与解释并存。 7.Java 有哪些数据类型
**定义**Java 语言是强类型语言对于每一种数据都定义了明确的具体的数据类型在内存中分配了不同大小的内存空间。
Java 语言数据类型分为两种基本数据类型和引用数据类型。
基本数据类型
数值型 整数类型byte、short、int、long浮点类型float、double 字符型char布尔型boolean
Java 基本数据类型范围和默认值
基本类型位数字节默认值int3240short1620long6480Lbyte810char162‘u0000’float3240fdouble6480dboolean1false
引用数据类型
类class接口interface数组([])
8.自动类型转换、强制类型转换看看这几行代码
Java 所有的数值型变量可以相互转换当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时可以进行自动类型转换反之需要强制转换。
Java自动类型转换方向
这就好像小杯里的水倒进大杯没问题但大杯的水倒进小杯就不行了可能会溢出。 float f3.4对吗 不正确。3.4 是双精度数将双精度型double赋值给浮点型float属于下转型down-casting也称为窄化会造成精度损失因此需要强制类型转换float f (float)3.4;或者写成float f 3.4F short s1 1; s1 s1 1对吗short s1 1; s1 1;对吗 对于 short s1 1; s1 s1 1;编译出错由于 1 是 int 类型因此 s11 运算结果也是 int 型需要强制转换类型才能赋值给 short 型。
而 short s1 1; s1 1;可以正确编译因为 s1 1;相当于 s1 (short(s1 1);其中有隐含的强制类型转换。
9.什么是自动拆箱/封箱
装箱将基本类型用它们对应的引用类型包装起来拆箱将包装类型转换为基本数据类型
Java 可以自动对基本数据类型和它们的包装类进行装箱和拆箱。
装箱和拆箱
举例
Integer i 10; //装箱
int n i; //拆箱10.和有什么区别
运算符有两种用法短路与、逻辑与。
运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。
之所以称为短路运算是因为如果左边的表达式的值是 false右边的表达式会被直接短路掉不会进行运算。很多时候我们可能都需要用而不是。
例如在验证用户登录时判定用户名不是 null 而且不是空字符串应当写为username ! null !username.equals()二者的顺序不能交换更不能用运算符因为第一个条件如果不成立根本不能进行字符串的 equals 比较否则会产生 NullPointerException 异常。
注意逻辑或运算符|和短路或运算符||的差别也是如此。
11.switch 是否能作用在 byte/long/String 上
Java5 以前 switch(expr)中expr 只能是 byte、short、char、int。
从 Java 5 开始Java 中引入了枚举类型 expr 也可以是 enum 类型。
从 Java 7 开始expr 还可以是字符串(String)但是长整型(long)在目前所有的版本中都是不可以的。
12.break ,continue ,return 的区别及作用
break 跳出整个循环不再执行循环(结束当前的循环体)continue 跳出本次循环继续执行下次循环(结束正在执行的循环 进入下一个循环条件)return 程序返回不再执行下面的代码(结束当前的方法 直接返回) 13.用最有效率的方法计算 2 乘以 8
2 3。位运算数字的二进制位左移三位相当于乘以 2 的三次方。
14.说说自增自减运算看下这几个代码运行结果
在写代码的过程中常见的一种情况是需要某个整数类型变量增加 1 或减少 1Java 提供了一种特殊的运算符用于这种表达式叫做自增运算符)和自减运算符–。
和–运算符可以放在变量之前也可以放在变量之后。
当运算符放在变量之前时(前缀)先自增/减再赋值当运算符放在变量之后时(后缀)先赋值再自增/减。
例如当 b a 时先自增自己增加 1再赋值赋值给 b当 b a 时先赋值(赋值给 b)再自增自己增加 1。也就是a 输出的是 a1 的值a输出的是 a 值。
用一句口诀就是“符号在前就先加/减符号在后就后加/减”。 看一下这段代码运行结果 int i 1;
i i;
System.out.println(i);答案是 1。有点离谱对不对。
对于 JVM 而言它对自增运算的处理是会先定义一个临时变量来接收 i 的值然后进行自增运算最后又将临时变量赋给了值为 2 的 i所以最后的结果为 1。
相当于这样的代码
int i 1
int temp i;
i
i temp;
System.out.println(i);这段代码会输出什么 int count 0;
for(int i 0;i 100;i)
{count count;
}
System.out.println(count count);答案是 0。
和上面的题目一样的道理同样是用了临时变量count 实际是等于临时变量的值。
int autoAdd(int count)
{int temp count;count coutn 1;return temp;
}15.⾯向对象和⾯向过程的区别?
⾯向过程 面向过程就是分析出解决问题所需要的步骤然后用函数把这些步骤一步一步实现使用的时候再一个一个的一次调用就可以。⾯向对象 面向对象把构成问题的事务分解成各个对象而建立对象的目的也不是为了完成一个个步骤而是为了描述某个事件在解决整个问题的过程所发生的行为。 目的是为了写出通用的代码加强代码的重用屏蔽差异性。
用一个比喻面向过程是编年体面向对象是纪传体 16.面向对象有哪些特性
面向对象三大特征 封装 封装把⼀个对象的属性私有化同时提供⼀些可以被外界访问的属性的⽅法。 继承 继承是使⽤已存在的类的定义作为基础创建新的类新类的定义可以增加新的属性或新的方法也可以继承父类的属性和方法。通过继承可以很方便地进行代码复用。 关于继承有以下三个要点 ⼦类拥有⽗类对象所有的属性和⽅法包括私有属性和私有⽅法但是⽗类中的私有属性和⽅法⼦类是⽆法访问只是拥有。⼦类可以拥有⾃⼰属性和⽅法即⼦类可以对⽗类进⾏扩展。⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。 多态 所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编程时并不确定⽽是在程序运⾏期间才确定即⼀个引⽤变量到底会指向哪个类的实例对象该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法必须在由程序运⾏期间才能决定。 在 Java 中有两种形式可以实现多态继承多个⼦类对同⼀⽅法的重写和接⼝实现接⼝并覆盖接⼝中同⼀⽅法。
17.重载overload和重写override的区别
方法的重载和重写都是实现多态的方式区别在于前者实现的是编译时的多态性而后者实现的是运行时的多态性。
重载发生在一个类中同名的方法如果有不同的参数列表参数类型不同、参数个数不同或者二者都不同则视为重载重写发生在子类与父类之间重写要求子类被重写方法与父类被重写方法有相同的返回类型比父类被重写方法更好访问不能比父类被重写方法声明更多的异常里氏代换原则。
方法重载的规则
方法名一致参数列表中参数的顺序类型个数不同。重载与方法的返回值无关存在于父类和子类同类中。可以抛出不同的异常可以有不同修饰符。
18.访问修饰符 public、private、protected、以及不写默认时的区别
Java 中可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
default (即默认什么也不写: 在同一包内可见不使用任何修饰符。可以修饰在类、接口、变量、方法。private : 在同一类内可见。可以修饰变量、方法。注意不能修饰类外部类public : 对所有类可见。可以修饰类、接口、变量、方法protected : 对同一包内的类和所有子类可见。可以修饰变量、方法。注意不能修饰类外部类。 19.this 关键字有什么作用
this 是自身的一个对象代表对象本身可以理解为指向对象本身的一个指针。
this 的用法在 Java 中大体可以分为 3 种
普通的直接引用this 相当于是指向当前对象本身形参与成员变量名字重名用 this 来区分
public Person(String name,int age){this.namename;this.ageage;
}3.引用本类的构造函数
20.抽象类(abstract class)和接口(interface)有什么区别
接⼝的⽅法默认是 public 所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现⽽抽象类可以有⾮抽象的⽅法。接⼝中除了 static 、 final 变量不能有其他变量⽽抽象类中则不⼀定。⼀个类可以实现多个接⼝但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。接⼝⽅法默认修饰符是 public 抽象⽅法可以有 public 、 protected 和 default 这些修饰符抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰。从设计层⾯来说抽象是对类的抽象是⼀种模板设计⽽接⼝是对⾏为的抽象是⼀种⾏为的规范。 在 JDK8 中接⼝也可以定义静态⽅法可以直接⽤接⼝名调⽤。实现类和实现是不可以调⽤的。如果同时实现两个接⼝接⼝中定义了⼀样的默认⽅法则必须重写不然会报错。jdk9 的接⼝被允许定义私有⽅法 。 总结⼀下 jdk7~jdk9 Java 中接⼝的变化
在 jdk 7 或更早版本中接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实现接⼝的类实现。jdk 8 的时候接⼝可以有默认⽅法和静态⽅法功能。jdk 9 在接⼝中引⼊了私有⽅法和私有静态⽅法。
21.成员变量与局部变量的区别有哪些
从语法形式上看成员变量是属于类的⽽局部变量是在⽅法中定义的变量或是⽅法的参数成员变量可以被 public , private , static 等修饰符所修饰⽽局部变量不能被访问控制修饰符及 static 所修饰但是成员变量和局部变量都能被 final 所修饰。从变量在内存中的存储⽅式来看如果成员变量是使⽤ static 修饰的那么这个成员变量是属于类的如果没有使⽤ static 修饰这个成员变量是属于实例的。对象存于堆内存如果局部变量类型为基本数据类型那么存储在栈内存如果为引⽤数据类型那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。从变量在内存中的⽣存时间上看成员变量是对象的⼀部分它随着对象的创建⽽存在⽽局部变量随着⽅法的调⽤⽽⾃动消失。成员变量如果没有被赋初值则会⾃动以类型的默认值⽽赋值⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值⽽局部变量则不会⾃动赋值。
22.静态变量和实例变量的区别静态方法、实例方法呢 静态变量和实例变量的区别 静态变量: 是被 static 修饰符修饰的变量也称为类变量它属于类不属于类的任何一个对象一个类不管创建多少个对象静态变量在内存中有且仅有一个副本。
实例变量: 必须依存于某一实例需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。 静态⽅法和实例⽅法有何不同? 类似地。
静态方法static 修饰的方法也被称为类方法。在外部调⽤静态⽅法时可以使⽤类名.⽅法名的⽅式也可以使⽤对象名.⽅法名的⽅式。静态方法里不能访问类的非静态成员变量和方法。
实例⽅法依存于类的实例需要使用对象名.⽅法名的⽅式调用可以访问类的所有成员变量和方法。
24.final 关键字有什么作用
final 表示不可变的意思可用于修饰类、属性和方法 被 final 修饰的类不可以被继承 被 final 修饰的方法不可以被重写 被 final 修饰的变量不可变被 final 修饰的变量必须被显式第指定初始值还得注意的是这里的不可变指的是变量的引用不可变不是引用指向的内容的不可变。 例如
final StringBuilder sb new StringBuilder(abc);
sb.append(d);
System.out.println(sb); //abcd一张图说明 25.final、finally、finalize 的区别 final 用于修饰变量、方法和类final 修饰的类不可被继承修饰的方法不可被重写修饰的变量不可变。 finally 作为异常处理的一部分它只能在 try/catch 语句中并且附带一个语句块表示这段语句最终一定被执行无论是否抛出异常经常被用在需要释放资源的情况下System.exit (0) 可以阻断 finally 执行。 finalize 是在 java.lang.Object 里定义的方法也就是说每一个对象都有这么个方法这个方法在 gc 启动该对象被回收的时候被调用。 一个对象的 finalize 方法只会被调用一次finalize 被调用不一定会立即回收该对象所以有可能调用 finalize 后该对象又不需要被回收了然后到了真正要被回收的时候因为前面调用过一次所以不会再次调用 finalize 了进而产生问题因此不推荐使用 finalize 方法。
26.和 equals 的区别 : 它的作⽤是判断两个对象的地址是不是相等。即判断两个对象是不是同⼀个对象(基本数据类型 比较的是值引⽤数据类型 比较的是内存地址)。
equals() : 它的作⽤也是判断两个对象是否相等。但是这个“相等”一般也分两种情况
默认情况类没有覆盖 equals() ⽅法。则通过 equals() 比较该类的两个对象时等价于通过“ ”比较这两个对象还是相当于比较内存地址。自定义情况类覆盖了 equals() ⽅法。我们平时覆盖的 equals()方法一般是比较两个对象的内容是否相同自定义了一个相等的标准也就是两个对象的值是否相等。
举个例⼦Person我们认为两个人的编号和姓名相同就是一个人
public class Person {private String no;private String name;Overridepublic boolean equals(Object o) {if (this o) return true;if (!(o instanceof Person)) return false;Person person (Person) o;return Objects.equals(no, person.no) Objects.equals(name, person.name);}Overridepublic int hashCode() {return Objects.hash(no, name);}
}27.hashCode 与 equals?
这个也是面试常问——“你重写过 hashcode 和 equals 么为什么重写 equals 时必须重写 hashCode ⽅法” 什么是 HashCode hashCode() 的作⽤是获取哈希码也称为散列码它实际上是返回⼀个 int 整数定义在 Object 类中 是一个本地⽅法这个⽅法通常⽤来将对象的内存地址转换为整数之后返回。
public native int hashCode();哈希码主要在哈希表这类集合映射的时候用到哈希表存储的是键值对(key-value)它的特点是能根据“键”快速的映射到对应的“值”。这其中就利⽤到了哈希码 为什么要有 hashCode 上面已经讲了主要是在哈希表这种结构中用的到。
例如 HashMap 怎么把 key 映射到对应的 value 上呢用的就是哈希取余法也就是拿哈希码和存储元素的数组的长度取余获取 key 对应的 value 所在的下标位置。 为什么重写 quals 时必须重写 hashCode ⽅法 如果两个对象相等则 hashcode ⼀定也是相同的。两个对象相等对两个对象分别调⽤ equals ⽅法都返回 true。反之两个对象有相同的 hashcode 值它们也不⼀定是相等的 。因此equals ⽅法被覆盖过则 hashCode ⽅法也必须被覆盖。
hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode() 则该 class 的两个对象⽆论如何都不会相等即使这两个对象指向相同的数据 为什么两个对象有相同的 hashcode 值它们也不⼀定是相等的 因为可能会碰撞 hashCode() 所使⽤的散列算法也许刚好会让多个对象传回相同的散列值。越糟糕的散列算法越容易碰撞但这也与数据值域分布的特性有关所谓碰撞也就是指的是不同的对象得到相同的 hashCode 。
28.Java 是值传递还是引用传递
Java 语言是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变但对对象引用的改变是不会影响到调用者的。
JVM 的内存分为堆和栈其中栈中存储了基本数据类型和引用数据类型实例的地址也就是对象地址。
而对象所占的空间是在堆中开辟的所以传递的时候可以理解为把变量存储的对象地址给传递过去因此引用类型也是值传递。 29.深拷贝和浅拷贝?
浅拷贝仅拷贝被拷贝对象的成员变量的值也就是基本数据类型变量的值和引用数据类型变量的地址值而对于引用类型变量指向的堆中的对象不会拷贝。深拷贝完全拷贝一个对象拷贝被拷贝对象的成员变量的值堆中的对象也会拷贝一份。
例如现在有一个 order 对象里面有一个 products 列表它的浅拷贝和深拷贝的示意图
浅拷贝和深拷贝示意图
因此深拷贝是安全的浅拷贝的话如果有引用类型那么拷贝后对象引用类型变量修改会影响原对象。 浅拷贝如何实现呢 Object 类提供的 clone()方法可以非常简单地实现对象的浅拷贝。 深拷贝如何实现呢 重写克隆方法重写克隆方法引用类型变量单独克隆这里可能会涉及多层递归。序列化可以先将原对象序列化再反序列化成拷贝对象。
30.Java 创建对象有哪几种方式
Java 中有以下四种创建对象的方式:
Java创建对象的四种方式
new 创建新对象通过反射机制采用 clone 机制通过序列化机制
前两者都需要显式地调用构造方法。对于 clone 机制,需要注意浅拷贝和深拷贝的区别对于序列化机制需要明确其实现原理在 Java 中序列化可以通过实现 Externalizable 或者 Serializable 来实现。
31.String 是 Java 基本数据类型吗可以被继承吗 String 是 Java 基本数据类型吗 不是。Java 中的基本数据类型只有 8 个byte、short、int、long、float、double、char、boolean除了基本类型primitive type剩下的都是引用类型reference type。
String 是一个比较特殊的引用数据类型。 String 类可以继承吗 不行。String 类使用 final 修饰是所谓的不可变类无法被继承。
32.String 和 StringBuilder、StringBuffer 的区别
StringString 的值被创建后不能修改任何对 String 的修改都会引发新的 String 对象的生成。StringBuffer跟 String 类似但是值可以被修改使用 synchronized 来保证线程安全。StringBuilderStringBuffer 的非线程安全版本性能上更高一些。
33.String str1 new String(“abc”)和 String str2 “abc” 和 区别
两个语句都会去字符串常量池中检查是否已经存在 “abc”如果有则直接使用如果没有则会在常量池中创建 “abc” 对象。
堆与常量池中的String
但是不同的是String str1 new String(“abc”) 还会通过 new String() 在堆里创建一个 “abc” 字符串对象实例。所以后者可以理解为被前者包含。 String s new String(“abc”)创建了几个对象 很明显一个或两个。如果字符串常量池已经有“abc”则是一个否则两个。
当字符创常量池没有 “abc”此时会创建如下两个对象
一个是字符串字面量 “abc” 所对应的、字符串常量池中的实例另一个是通过 new String() 创建并初始化的内容与abc相同的实例在堆中。
34.String 不是不可变类吗字符串拼接是如何实现的
String 的确是不可变的“”的拼接操作其实是会生成新的对象。
例如
String a hello ;
String b world!;
String ab a b;在jdk1.8 之前a 和 b 初始化时位于字符串常量池ab 拼接后的对象位于堆中。经过拼接新生成了 String 对象。如果拼接多次那么会生成多个中间对象。
内存如下
jdk1.8之前的字符串拼接
在Java8 时JDK 对“”号拼接进行了优化上面所写的拼接方式会被优化为基于 StringBuilder 的 append 方法进行处理。Java 会在编译期对“”号进行处理。
下面是通过 javap -verbose 命令反编译字节码的结果很显然可以看到 StringBuilder 的创建和 append 方法的调用。
stack2, locals4, args_size10: ldc #2 // String hello2: astore_13: ldc #3 // String world!5: astore_26: new #4 // class java/lang/StringBuilder9: dup10: invokespecial #5 // Method java/lang/StringBuilder.init:()V13: aload_114: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;17: aload_218: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;24: astore_325: return也就是说其实上面的代码其实相当于
String a hello ;
String b world!;
StringBuilder sb new StringBuilder();
sb.append(a);
sb.append(b);
String ab sb.toString();此时如果再笼统的回答通过加号拼接字符串会创建多个 String 对象因此性能比 StringBuilder 差就是错误的了。因为本质上加号拼接的效果最终经过编译器处理之后和 StringBuilder 是一致的。
当然循环里拼接还是建议用 StringBuilder为什么因为循环一次就会创建一个新的 StringBuilder 对象大家可以自行实验。
35.intern 方法有什么作用
JDK 源码里已经对这个方法进行了说明 * p* When the intern method is invoked, if the pool already contains a* string equal to this {code String} object as determined by* the {link #equals(Object)} method, then the string from the pool is* returned. Otherwise, this {code String} object is added to the* pool and a reference to this {code String} object is returned.* p意思也很好懂
如果当前字符串内容存在于字符串常量池即 equals()方法为 true也就是内容一样直接返回字符串常量池中的字符串否则将此 String 对象添加到池中并返回 String 对象的引用
36.Integer a 127Integer b 127Integer c 128Integer d 128相等吗?
答案是 a 和 b 相等c 和 d 不相等。
对于基本数据类型比较的值对于引用数据类型比较的是地址
Integer a 127 这种赋值是用到了 Integer 自动装箱的机制。自动装箱的时候会去缓存池里取 Integer 对象没有取到才会创建新的对象。
如果整型字面量的值在-128 到 127 之间那么自动装箱时不会 new 新的 Integer 对象而是直接引用缓存池中的 Integer 对象超过范围 a1b1 的结果是 false public static void main(String[] args) {Integer a 127;Integer b 127;Integer b1 new Integer(127);System.out.println(a b); //trueSystem.out.println(bb1); //falseInteger c 128;Integer d 128;System.out.println(c d); //false}什么是 Integer 缓存 因为根据实践发现大部分的数据操作都集中在值比较小的范围因此 Integer 搞了个缓存池默认范围是 -128 到 127可以根据通过设置JVM-XX:AutoBoxCacheMax来修改缓存的最大值最小值改不了。
实现的原理是 int 在自动装箱的时候会调用 Integer.valueOf进而用到了 IntegerCache。
Integer.valueOf
很简单就是判断下值是否在缓存范围之内如果是的话去 IntegerCache 中取不是的话就创建一个新的 Integer 对象。
IntegerCache 是一个静态内部类 在静态块中会初始化好缓存值。 private static class IntegerCache {……static {//创建Integer对象存储for(int k 0; k cache.length; k)cache[k] new Integer(j);……}}37.String 怎么转成 Integer 的原理
PS:这道题印象中在一些面经中出场过几次。
String 转成 Integer主要有两个方法
Integer.parseInt(String s)Integer.valueOf(String s)
不管哪一种最终还是会调用 Integer 类内中的parseInt(String s, int radix)方法。
抛去一些边界之类的看看核心代码
public static int parseInt(String s, int radix)throws NumberFormatException{int result 0;//是否是负数boolean negative false;//char字符数组下标和长度int i 0, len s.length();……int digit;//判断字符长度是否大于0否则抛出异常if (len 0) {……while (i len) {// Accumulating negatively avoids surprises near MAX_VALUE//返回指定基数中字符表示的数值。此处是十进制数值digit Character.digit(s.charAt(i),radix);//进制位乘以数值result * radix;result - digit;}}//根据上面得到的是否负数返回相应的值return negative ? result : -result;}去掉枝枝蔓蔓当然这些枝枝蔓蔓可以去看看源码 cover 了很多情况其实剩下的就是一个简单的字符串遍历计算不过计算方式有点反常规是用负的值累减。
parseInt示意图
38.Object 类的常见方法?
Object 类是一个特殊的类是所有类的父类也就是说所有类都可以调用它的方法。它主要提供了以下 11 个方法大概可以分为六类
Object类的方法
对象比较
public native int hashCode() native 方法用于返回对象的哈希码主要使用在哈希表中比如 JDK 中的 HashMap。public boolean equals(Object obj)用于比较 2 个对象的内存地址是否相等String 类对该方法进行了重写用户比较字符串的值是否相等。
对象拷贝
protected native Object clone() throws CloneNotSupportedExceptionnaitive 方法用于创建并返回当前对象的一份拷贝。一般情况下对于任何对象 x表达式 x.clone() ! x 为 truex.clone().getClass() x.getClass() 为 true。Object 本身没有实现 Cloneable 接口所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException 异常。
对象转字符串
public String toString()返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
多线程调度
public final native void notify()native 方法并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。public final native void notifyAll()native 方法并且不能重写。跟 notify 一样唯一的区别就是会唤醒在此对象监视器上等待的所有线程而不是一个线程。public final native void wait(long timeout) throws InterruptedExceptionnative 方法并且不能重写。暂停线程的执行。注意sleep 方法没有释放锁而 wait 方法释放了锁 。timeout 是等待时间。public final void wait(long timeout, int nanos) throws InterruptedException多了 nanos 参数这个参数表示额外时间以毫微秒为单位范围是 0-999999。 所以超时的时间还需要加上 nanos 毫秒。public final void wait() throws InterruptedException跟之前的 2 个 wait 方法一样只不过该方法一直等待没有超时时间这个概念
反射
public final native Class? getClass()native 方法用于返回当前运行时对象的 Class 对象使用了 final 关键字修饰故不允许子类重写。
垃圾回收
protected void finalize() throws Throwable 通知垃圾收集器回收对象。
39.Java 中异常处理体系?
Java 的异常体系是分为多层的。
Java异常体系
Throwable是 Java 语言中所有错误或异常的基类。 Throwable 又分为Error和Exception其中 Error 是系统内部错误比如虚拟机异常是程序无法处理的。Exception是程序问题导致的异常又分为两种
CheckedException 受检异常编译器会强制检查并要求处理的异常。RuntimeException 运行时异常程序运行中出现异常比如我们熟悉的空指针、数组下标越界等等
40.异常的处理方式
针对异常的处理主要有两种方式
异常处理
遇到异常不进行具体处理而是继续抛给调用者 throwthrows
抛出异常有三种形式一是 throw,一个 throws还有一种系统自动抛异常。
throws 用在方法上后面跟的是异常类可以跟多个而 throw 用在方法内后面跟的是异常对象。
try catch 捕获异常
在 catch 语句块中补货发生的异常并进行处理。 try {//包含可能会出现异常的代码以及声明异常的方法}catch(Exception e) {//捕获异常并进行处理}finally { }//可选必执行的代码}try-catch 捕获异常的时候还可以选择加上 finally 语句块finally 语句块不管程序是否正常执行最终它都会必然执行。
41.三道经典异常处理代码题 题目 1 public class TryDemo {public static void main(String[] args) {System.out.println(test());}public static int test() {try {return 1;} catch (Exception e) {return 2;} finally {System.out.print(3);}}
}执行结果31。
try、catch。finally 的基础用法在 return 前会先执行 finally 语句块所以是先输出 finally 里的 3再输出 return 的 1。 题目 2 public class TryDemo {public static void main(String[] args) {System.out.println(test1());}public static int test1() {try {return 2;} finally {return 3;}}
}执行结果3。
try 返回前先执行 finally结果 finally 里不按套路出牌直接 return 了自然也就走不到 try 里面的 return 了。
finally 里面使用 return 仅存在于面试题中实际开发这么写要挨吊的。 题目 3 public class TryDemo {public static void main(String[] args) {System.out.println(test1());}public static int test1() {int i 0;try {i 2;return i;} finally {i 3;}}
}执行结果2。
大家可能会以为结果应该是 3因为在 return 前会执行 finally而 i 在 finally 中被修改为 3 了那最终返回 i 不是应该为 3 吗
但其实在执行 finally 之前JVM 会先将 i 的结果暂存起来然后 finally 执行完毕后会返回之前暂存的结果而不是返回 i所以即使 i 已经被修改为 3最终返回的还是之前暂存起来的结果 2。
42.Java 中 IO 流分为几种?
流按照不同的特点有很多种划分方式。
按照流的流向分可以分为输入流和输出流按照操作单元划分可以划分为字节流和字符流按照流的角色划分为节点流和处理流
Java Io 流共涉及 40 多个类看上去杂乱其实都存在一定的关联 Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
InputStream/Reader: 所有的输入流的基类前者是字节输入流后者是字符输入流。OutputStream/Writer: 所有输出流的基类前者是字节输出流后者是字符输出流。
IO-操作方式分类-图片来源参考[2] IO 流用到了什么设计模式 其实Java 的 IO 流体系还用到了一个设计模式——装饰器模式。
InputStream 相关的部分类图如下篇幅有限装饰器模式就不展开说了。
Java IO流用到装饰器
43.既然有了字节流,为什么还要有字符流?
其实字符流是由 Java 虚拟机将字节转换得到的问题就出在这个过程还比较耗时并且如果我们不知道编码类型就很容易出现乱码问题。
所以 I/O 流就干脆提供了一个直接操作字符的接口方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好如果涉及到字符的话使用字符流比较好
44.BIO、NIO、AIO
BIO、NIO、AIO
BIO(blocking I/O) 就是传统的 IO同步阻塞服务器实现模式为一个连接一个线程即客户端有连接请求时服务器端就需要启动一个线程进行处理如果这个连接不做任何事情会造成不必要的线程开销可以通过连接池机制改善(实现多个客户连接服务器)。
BIO、NIO、AIO
BIO 方式适用于连接数目比较小且固定的架构这种方式对服务器资源要求比较高并发局限于应用中JDK1.4 以前的唯一选择程序简单易理解。
NIO 全称 java non-blocking IO是指 JDK 提供的新 API。从 JDK1.4 开始Java 提供了一系列改进的输入/输出的新特性被统称为 NIO(即 New IO)。
NIO 是同步非阻塞的服务器端用一个线程处理多个连接客户端发送的连接请求会注册到多路复用器上多路复用器轮询到连接有 IO 请求就进行处理
NIO线程
NIO 的数据是面向缓冲区 Buffer的必须从 Buffer 中读取或写入。
所以完整的 NIO 示意图
NIO完整示意图
可以看出NIO 的运行机制
每个 Channel 对应一个 Buffer。Selector 对应一个线程一个线程对应多个 Channel。Selector 会根据不同的事件在各个通道上切换。Buffer 是内存块底层是数据。
AIOJDK 7 引入了 Asynchronous I/O是异步不阻塞的 IO。在进行 I/O 编程中常用到两种模式Reactor 和 Proactor。Java 的 NIO 就是 Reactor当有事件触发时服务器端得到通知进行相应的处理完成后才通知服务端程序启动线程去处理一般适用于连接数较多且连接时间较长的应用。
45.什么是序列化什么是反序列化
什么是序列化序列化就是把 Java 对象转为二进制流方便存储和传输。
所以反序列化就是把二进制流恢复成对象。
序列化和反序列化
类比我们生活中一些大件物品的运输运输的时候把它拆了打包用的时候再拆包组装。 Serializable 接口有什么用 这个接口只是一个标记没有具体的作用但是如果不实现这个接口在有些序列化场景会报错所以一般建议创建的 JavaBean 类都实现 Serializable。 serialVersionUID 又有什么用 serialVersionUID 就是起验证作用。
private static final long serialVersionUID 1L;我们经常会看到这样的代码这个 ID 其实就是用来验证序列化的对象和反序列化对应的对象 ID 是否一致。
这个 ID 的数字其实不重要无论是 1L 还是 IDE 自动生成的只要序列化时候对象的 serialVersionUID 和反序列化时候对象的 serialVersionUID 一致的话就行。
如果没有显示指定 serialVersionUID 则编译器会根据类的相关信息自动生成一个可以认为是一个指纹。
所以如果你没有定义一个 serialVersionUID 结果序列化一个对象之后在反序列化之前把对象的类的结构改了比如增加了一个成员变量则此时的反序列化会失败。
因为类的结构变了所以 serialVersionUID 就不一致。 Java 序列化不包含静态变量 序列化的时候是不包含静态变量的。 如果有些变量不想序列化怎么办 对于不想进行序列化的变量使用transient关键字修饰。
transient 关键字的作用是阻止实例中那些用此关键字修饰的的变量序列化当对象被反序列化时被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量不能修饰类和方法。
46.说说有几种序列化方式
Java 序列化方式有很多常见的有三种
Java常见序列化方式
Java 对象序列化 Java 原生序列化方法即通过 Java 原生流(InputStream 和 OutputStream 之间的转化)的方式进行转化一般是对象输出流 ObjectOutputStream和对象输入流ObjectInputStream。Json 序列化这个可能是我们最常用的序列化方式Json 序列化的选择很多一般会使用 jackson 包通过 ObjectMapper 类来进行一些操作比如将对象转化为 byte 数组或者将 json 串转化为对象。ProtoBuff 序列化ProtocolBuffer 是一种轻便高效的结构化数据存储格式ProtoBuff 序列化对象可以很大程度上将其压缩可以大大减少数据传输大小提高系统性能。
47.Java 泛型了解么什么是类型擦除介绍一下常用的通配符 什么是泛型 Java 泛型generics是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型也就是说所操作的数据类型被指定为一个参数。
ListInteger list new ArrayList();list.add(12);
//这里直接添加会报错
list.add(a);
Class? extends List clazz list.getClass();
Method add clazz.getDeclaredMethod(add, Object.class);
//但是通过反射添加是可以的
add.invoke(list, kl);System.out.println(list);泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
泛型类、泛型接口、泛型方法
1.泛型类
//此处T可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时必须指定T的具体类型
public class GenericT{private T key;public Generic(T key) {this.key key;}public T getKey(){return key;}
}如何实例化泛型类
GenericInteger genericInteger new GenericInteger(123456);2.泛型接口
public interface GeneratorT {public T method();
}实现泛型接口指定类型
class GeneratorImplT implements GeneratorString{Overridepublic String method() {return hello;}
}3.泛型方法 public static E void printArray( E[] inputArray ){for ( E element : inputArray ){System.out.printf( %s , element );}System.out.println();}使用
// 创建不同类型数组 Integer, Double 和 Character
Integer[] intArray { 1, 2, 3 };
String[] stringArray { Hello, World };
printArray( intArray );
printArray( stringArray );泛型常用的通配符有哪些 常用的通配符为 TEKV 表示不确定的 java 类型T (type) 表示具体的一个 java 类型K V (key value) 分别代表 java 键值中的 Key ValueE (element) 代表 Element 什么是泛型擦除 所谓的泛型擦除官方名叫“类型擦除”。
Java 的泛型是伪泛型这是因为 Java 在编译期间所有的类型信息都会被擦掉。
也就是说在运行的时候是没有泛型的。
例如这段代码往一群猫里放条狗
LinkedListCat cats new LinkedListCat();
LinkedList list cats; // 注意我在这里把范型去掉了但是list和cats是同一个链表
list.add(new Dog()); // 完全没问题因为 Java 的范型只存在于源码里编译的时候给你静态地检查一下范型类型是否正确而到了运行时就不检查了。上面这段代码在 JREJava运行环境看来和下面这段没区别
LinkedList cats new LinkedList(); // 注意没有范型
LinkedList list cats;
list.add(new Dog());为什么要类型擦除呢
主要是为了向下兼容因为 JDK5 之前是没有泛型的为了让 JVM 保持向下兼容就出了类型擦除这个策略。
48.说一下你对注解的理解
Java 注解本质上是一个标记可以理解成生活中的一个人的一些小装扮比如戴什么什么帽子戴什么眼镜。
Java注解和帽子
注解可以标记在类上、方法上、属性上等标记自身也可以设置一些值比如帽子颜色是绿色。
有了标记之后我们就可以在编译或者运行阶段去识别这些标记然后搞一些事情这就是注解的用处。
例如我们常见的 AOP使用注解作为切点就是运行期注解的应用比如 lombok就是注解在编译期的运行。
注解生命周期有三大类分别是
RetentionPolicy.SOURCE给编译器用的不会写入 class 文件RetentionPolicy.CLASS会写入 class 文件在类加载阶段丢弃也就是运行的时候就没这个信息了RetentionPolicy.RUNTIME会写入 class 文件永久保存可以通过反射获取注解信息
所以我上文写的是解析的时候没写具体是解析啥因为不同的生命周期的解析动作是不同的。
像常见的
Override注解
就是给编译器用的编译器编译的时候检查没问题就 over 了class 文件里面不会有 Override 这个标记。
再比如 Spring 常见的 Autowired 就是 RUNTIME 的所以在运行的时候可以通过反射得到注解的信息还能拿到标记的值 required 。 49.什么是反射应用原理 什么是反射 我们通常都是利用new方式来创建对象实例这可以说就是一种“正射”这种方式在编译时候就确定了类型信息。
而如果我们想在时候动态地获取类信息、创建类实例、调用类方法这时候就要用到反射。
通过反射你可以获取任意一个类的所有属性和方法你还可以调用这些方法和属性。
反射最核心的四个类
Java反射相关类 反射的应用场景 一般我们平时都是在在写业务代码很少会接触到直接使用反射机制的场景。
但是这并不代表反射没有用。相反正是因为反射你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
像 Spring 里的很多 注解 它真正的功能实现就是利用反射。
就像为什么我们使用 Spring 的时候 一个Component注解就声明了一个类为 Spring Bean 呢为什么通过一个 Value注解就读取到配置文件中的值呢究竟是怎么起作用的呢
这些都是因为我们可以基于反射操作类然后获取到类/属性/方法/方法的参数上的注解注解这里就有两个作用一是标记我们对注解标记的类/属性/方法进行对应的处理二是注解本身有一些信息可以参与到处理的逻辑中。 反射的原理 我们都知道 Java 程序的执行分为编译和运行两步编译之后会生成字节码(.class)文件JVM 进行类加载的时候会加载字节码文件将类型相关的所有信息加载进方法区反射就是去获取这些信息然后进行各种操作。
50.JDK1.8 都有哪些新特性
JDK1.8 有不少新特性我们经常接触到的新特性如下
JDK1.8主要新特性 接口默认方法Java 8 允许我们给接口添加一个非抽象的方法实现只需要使用 default 关键字修饰即可 Lambda 表达式和函数式接口Lambda 表达式本质上是一段匿名内部类也可以是一段可以传递的代码。Lambda 允许把函数作为一个方法的参数函数作为参数传递到方法中使用 Lambda 表达式使代码更加简洁但是也不要滥用否则会有可读性等问题《Effective Java》作者 Josh Bloch 建议使用 Lambda 表达式最好不要超过 3 行。 Stream API用函数式编程方式在集合类上进行复杂操作的工具配合 Lambda 表达式可以方便的对集合进行处理。 Java8 中处理集合的关键抽象概念它可以指定你希望对集合进行的操作可以执行非常复杂的查找、过滤和映射数据等操作。使用 Stream API 对集合数据进行操作就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。 简而言之Stream API 提供了一种高效且易于使用的处理数据的方式。 日期时间 APIJava 8 引入了新的日期时间 API 改进了日期时间的管理。 Optional 类用来解决空指针异常的问题。很久以前 Google Guava 项目引入了 Optional 作为解决空指针异常的一种方式不赞成代码被 null 检查的代码污染期望程序员写整洁的代码。受 Google Guava 的鼓励Optional 现在是 Java 8 库的一部分。
51.Lambda 表达式了解多少
Lambda 表达式本质上是一段匿名内部类也可以是一段可以传递的代码。
比如我们以前使用 Runnable 创建并运行线程 new Thread(new Runnable() {Overridepublic void run() {System.out.println(Thread is running before Java8!);}}).start();这是通过内部类的方式来重写 run 方法使用 Lambda 表达式还可以更加简洁
new Thread( () - System.out.println(Thread is running since Java8!) ).start();当然不是每个接口都可以缩写成 Lambda 表达式。只有那些函数式接口Functional Interface才能缩写成 Lambda 表示式。
所谓函数式接口Functional Interface就是只包含一个抽象方法的声明。针对该接口类型的所有 Lambda 表达式都会与这个抽象方法匹配。 Java8 有哪些内置函数式接口 JDK 1.8 API 包含了很多内置的函数式接口。其中就包括我们在老版本中经常见到的 Comparator 和 RunnableJava 8 为他们都添加了 FunctionalInterface 注解以用来支持 Lambda 表达式。
除了这两个之外还有 Callable、Predicate、Function、Supplier、Consumer 等等。
52.Optional 了解吗
Optional是用于防范NullPointerException。
可以将 Optional 看做是包装对象可能是 null, 也有可能非 null的容器。当我们定义了 一个方法这个方法返回的对象可能是空也有可能非空的时候我们就可以考虑用 Optional 来包装它这也是在 Java 8 被推荐使用的做法。
OptionalString optional Optional.of(bam);optional.isPresent(); // true
optional.get(); // bam
optional.orElse(fallback); // bamoptional.ifPresent((s) - System.out.println(s.charAt(0))); // b52.Optional 了解吗
Optional是用于防范NullPointerException。
可以将 Optional 看做是包装对象可能是 null, 也有可能非 null的容器。当我们定义了 一个方法这个方法返回的对象可能是空也有可能非空的时候我们就可以考虑用 Optional 来包装它这也是在 Java 8 被推荐使用的做法。
OptionalString optional Optional.of(bam);optional.isPresent(); // true
optional.get(); // bam
optional.orElse(fallback); // bamoptional.ifPresent((s) - System.out.println(s.charAt(0))); // b53.Stream 流用过吗
Stream 流简单来说使用 java.util.Stream 对一个包含一个或多个元素的集合做各种操作。这些操作可能是 中间操作 亦或是 终端操作。 终端操作会返回一个结果而中间操作会返回一个 Stream 流。
Stream 流一般用于集合我们对一个集合做几个常见操作
ListString 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);Filter 过滤
stringCollection.stream().filter((s) - s.startsWith(a)).forEach(System.out::println);// aaa2, aaa1Sorted 排序
stringCollection.stream().sorted().filter((s) - s.startsWith(a)).forEach(System.out::println);// aaa1, aaa2Map 转换
stringCollection.stream().map(String::toUpperCase).sorted((a, b) - b.compareTo(a)).forEach(System.out::println);// DDD2, DDD1, CCC, BBB3, BBB2, AAA2, AAA1Match 匹配
// 验证 list 中 string 是否有以 a 开头的, 匹配到第一个即返回 true
boolean anyStartsWithA stringCollection.stream().anyMatch((s) - s.startsWith(a));System.out.println(anyStartsWithA); // true// 验证 list 中 string 是否都是以 a 开头的
boolean allStartsWithA stringCollection.stream().allMatch((s) - s.startsWith(a));System.out.println(allStartsWithA); // false// 验证 list 中 string 是否都不是以 z 开头的,
boolean noneStartsWithZ stringCollection.stream().noneMatch((s) - s.startsWith(z));System.out.println(noneStartsWithZ); // trueCount 计数
count 是一个终端操作它能够统计 stream 流中的元素总数返回值是 long 类型。
// 先对 list 中字符串开头为 b 进行过滤让后统计数量
long startsWithB stringCollection.stream().filter((s) - s.startsWith(b)).count();System.out.println(startsWithB); // 3Reduce
Reduce 中文翻译为减少、缩小。通过入参的 Function我们能够将 list 归约成一个值。它的返回类型是 Optional 类型。
OptionalString reduced stringCollection.stream().sorted().reduce((s1, s2) - s1 # s2);reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2以上是常见的几种流式操作还有其它的一些流式操作可以帮助我们更便捷地处理集合数据。
DD1, “CCC”, “BBB3”, “BBB2”, “AAA2”, “AAA1” - **Match 匹配**java
// 验证 list 中 string 是否有以 a 开头的, 匹配到第一个即返回 true
boolean anyStartsWithA stringCollection.stream().anyMatch((s) - s.startsWith(a));System.out.println(anyStartsWithA); // true// 验证 list 中 string 是否都是以 a 开头的
boolean allStartsWithA stringCollection.stream().allMatch((s) - s.startsWith(a));System.out.println(allStartsWithA); // false// 验证 list 中 string 是否都不是以 z 开头的,
boolean noneStartsWithZ stringCollection.stream().noneMatch((s) - s.startsWith(z));System.out.println(noneStartsWithZ); // trueCount 计数
count 是一个终端操作它能够统计 stream 流中的元素总数返回值是 long 类型。
// 先对 list 中字符串开头为 b 进行过滤让后统计数量
long startsWithB stringCollection.stream().filter((s) - s.startsWith(b)).count();System.out.println(startsWithB); // 3Reduce
Reduce 中文翻译为减少、缩小。通过入参的 Function我们能够将 list 归约成一个值。它的返回类型是 Optional 类型。
OptionalString reduced stringCollection.stream().sorted().reduce((s1, s2) - s1 # s2);reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2以上是常见的几种流式操作还有其它的一些流式操作可以帮助我们更便捷地处理集合数据。