1.9对集合添加的优化
java 9添加了几种集合工厂的方式,更方便创建少量元素的集合,map实例/新的list,set,Map的静态工厂方法可以更方便的创建集合的不可变实例.
list接口 set接口 map接口 增加一个静态方法 of 可以给集合一次性添加多个元素
使用的前提:当集合中储存的元素的个数已经确定了,不在改变时使用.
注意:
1.of方法只适用于list set map 接口 不适用与接口的实现类
2.of方法的返回值是一个不能改变的集合,集合不能再使用add put 方法添加元素,会抛出异常.
3.set与map接口在调用of方法的时候,不能有重复的元素 ,否则抛出异常.
例子
public class Gather {
public static void main(String[] args) {
List<String> list = List.of("1","11","111");
// list.add("11");//UnsupportedOperationException 不支持操作异常
System.out.println(list);
// Set.of("2", "1", "3","3");//IllegalArgumentException 参数异常
Set<String> strings = Set.of("2", "1", "3");
//strings.add("11");//UnsupportedOperationException 不支持操作异常
System.out.println(strings);
//Map.of(1, "2", 1, "3");//IllegalArgumentException 参数异常
Map<Integer, String> integerStringMap = Map.of(1, "2", 2, "3");
// integerStringMap.put(3,"3");//UnsupportedOperationException 不支持操作异常
System.out.println(integerStringMap);
//map也可以这样添加 同样是不可变集合
Map<Integer, String> map = Map.ofEntries(Map.entry(1, "2"),Map.entry(2,"3"));
System.out.println(map);
//map.put(3,"200");
//扩展 在java8之前的版本也可以这样创建不可变集合
//List
List<String> list1 = Arrays.asList("1", "2");
// list1.add("1");报错
List<String> list2=new ArrayList<>();
list2.add("1");
List<String> list3 = Collections.unmodifiableList(list2);//将集合变成不可变集合
//System.out.println(list3);
//list3.add("1");报错
//Set
Set<String> stringSet=new HashSet<>();
stringSet.add("2");
Set<String> stringSet1 = Collections.unmodifiableSet(stringSet);
//stringSet1.add("1");报错
//map
Map<String,Integer> map1=new HashMap<>();
map1.put("1",1);
Map<String, Integer> map2 = Collections.unmodifiableMap(map1);
//map2.put("2",2);报错
}
}
详情代码E:\后端修炼之路\jdk新特性\jdk\src\jdk\nine\Gather.java
idea的debug调试
f8:逐行执行程序
f7:进入到方法中
shift+f8:跳出方法
f9:跳到下一个断点,如果没有下一个断点,那么就结束程序.
ctrl+f2:退出debug模式,停止程序
console:切换到控制台
LinkedHashMap集合
示例:
public class TestLinkedHashMap {
public static void main(String[] args) {
HashMap<String,String> map=new HashMap<>();
map.put("t","t");
map.put("b","b");
map.put("s","s");
map.put("c","c");
map.put("d","d");
System.out.println(map);//key不允许重复,无序
LinkedHashMap<String,String> hashMap=new LinkedHashMap();
hashMap.put("t","t");
hashMap.put("b","b");
hashMap.put("s","s");
hashMap.put("c","c");
hashMap.put("d","d");
System.out.println(hashMap);//key不允许重复有序 与放进去的循序一样
}
}
1.8中的Lambda表达式
比如实现接口线程的方式
使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程一");
}
}).start();
一方面,匿名内部类可以帮我们省去实现类的定义;另一方面 匿名内部类的语法 确实太复杂了
同样的例子在 Lambda中 更加简单
例子
new Thread(()->{
System.out.println("线程二");
}).start();
lambda表达式的标准格式:
由三部分组成:
a.一些参数
b.一个箭头
c.一段代码
格式:
(参数列表)->{一些重写方法的代码}
解释说明格式:
():接口中抽象方法的参数,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔.
->:传递的意思,把参数传递给方法体{}
{}:重写接口的抽象方法的方法体
例子
无参数无返回值
public class TestLamdba {
public static void main(String[] args) {
tempWrite(new Temp() {
@Override
public void write() {
System.out.println("使用内部类重写");
}
});
tempWrite(()->{
System.out.println("使用lamdba重写");
});
}
public static void tempWrite(Temp temp){
temp.write();
}
}
interface Temp{
void write();
}
有参数有返回值
class Person{
public int age;
public String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public class TestLamdba {
public static void main(String[] args) {
Person[] persons={
new Person(18,"张三"),new Person(13,"小妹妹"),new Person(14,"小弟弟")};
Arrays.sort(persons, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age-o2.age;
}
});//使用内部类的方式进行重写 接口 制定比较规则
Arrays.sort(persons,(Person o1, Person o2)->{
return o1.age-o2.age;
});//使用lamdba重写
for (Person ps:persons) {
System.out.println(ps);
}
}
}
lambda表达式的简略写法
lambda表达式是可推导,可以省略
凡是根据上下文推导出来的内容,都可以省略不写
可以省略的内容:
1.(参数列表):括号中参数列表的数据类型,可以省略不写
2.(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
3.{一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{} return 分号必须一起省略
什么叫做推导?
就比如说jdk 1.7之前 创建集合对象必须把前后的泛型都写上
ArrayList temp=new ArrayList();
jdk1.7之后,=号后边的泛型可以省略.后边的泛型可以根据前边的泛型推导出来
例子
Arrays.sort(persons,( o1, o2)->
o1.age-o2.age
);//使用lamdba简化写法重写
//参照上面的例子
public class TestLamdba {
public static void main(String[] args) {
tempWrite(1, a->{
//只有一个参数 省略() 类型
System.out.println("11");
});
public static void tempWrite(int a,Temp temp){
temp.write(a);
}
}
interface Temp{
void write(int a);
}
lambda的使用前提
1.使用lamdba必须具有接口 且要求接口中有且仅有一个抽象方法.(不然它不知道你重写的哪个方法)
无论是jdk内置的Runnable Comparator接口还是自定义的接口 只有当接口中的抽象方法存在且唯一时才可以使用lambda
2.使用Lambda必须具有上下文推断
也就是方法的参数或局部变量类型必须为lamdba对应的接口类型 才能使用lamdba作为该接口的实例.
有且仅有一个抽象方法的接口 ,称为"函数式接口"
如果使用内部类的方式进行重写方法 那么就会多一个class 文件 但是如果使用lamdba进行的话以后加载内存的时候就会少一个class,使用lamdba的效率要比内部类好的多!!
使用lamdba进行延迟加载
有些场景的代码执行后,结果不一定要使用,从而造成性能浪费,而Lambda表示是延迟执行的,这正好可以作为解决方案,提升性能.
性能浪费的日志案例
public class Demo{
private static void log(int level,String msg){
if(level==1){
System.out.println(msg);
}
}
public static void main(String[] args){
String msg1="Hello";
String msg2="World";
String msg3="java";
log(2,msg1+msg2+msg3);
}
}
以上代码的性能浪费
调用log方法,传递的第二个参数是一个拼接的后的字符串
先把字符串拼接好,然后在调用log方法,log方法中如果传递的日志等级不是1级那么就不会使用拼接后的字符串 所以感觉字符串就白拼接,存在了浪费.
使用lambda
interface MessageBuilder{
String builderMessage();
}
public class Demo{
public static void log(int level,MessageBuilder mb){
if(level==1){
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args){
String msg1="Hello";
String msg2="World";
String msg3="java";
log(2,()->{
return msg1+msg2+msg3;
});
}
}
使用lambda表达式作为参数传递,仅仅是把参数传递到log方法中
只有满足条件,日志的等级是1级
才会调用接口MessageBuilder中的方法builderMessage
才会进行字符串的拼接
如果条件不满足,日志的等级不是1级
那么MessageBuilder中的方法builderMessage也不会执行
所以拼接字符串的代码也不会执行
所以不会浪费性能
1.7与1.9对流的异常处理
代码
public class Try7And9 {
public static void main(String[] args)throws FileNotFoundException {
// writeDataJdk7();
writeDataJdk9();
}
/*JDK7处理IO流异常特性
* 格式 try(
* 定义流对象A;
* 定义流对象B;
* ...........
* )catch(){
* 处理异常
* }
* 注:此方法不需要释放,出了try会自动释放*/
public static void writeDataJdk7(){
try(
FileInputStream fis = new FileInputStream("C:\\Users\\asus\\Desktop\\小会同志.txt");
FileOutputStream fos = new FileOutputStream("C:\\Users\\asus\\Desktop\\小会同1.txt");
){
// fis=null;此时的fis是常量不能修改
int len = 0;
while((len = fis.read())!=-1){
fos.write(len);
}
}catch(IOException e){
System.out.println(e);
}
}
/*JDK 9处理IO异常新特性
* 格式:
* 定义对象A
* 定义对象B
* try(A;B){
* 内容
* }catch(){
* 输出异常
* 可以直接引用对象 同样也是自动关闭流
* }*/
public static void writeDataJdk9()throws FileNotFoundException {
FileInputStream fis = new FileInputStream("C:\\Users\\asus\\Desktop\\小会同志.txt");
FileOutputStream fos = new FileOutputStream("C:\\Users\\asus\\Desktop\\小会同志1.txt");
try(fis;fos){
// fis=null;此时的fis是常量不能修改
int len = 0;
while((len = fis.read())!=-1){
fos.write(len);
}
}catch(IOException e){
System.out.println(e);
}
}
}
详情代码E:\后端修炼之路\jdk新特性\jdk\src\jdk\nine\Try7And9.java
Properties集合
java.util.Properties集合extends Hashtable<k,v>implements Map<k,v>
Properties类表示了一个持久的属性集。Properties可保存在流中或者从流中加载。
Properties集合是唯一和IO流相结合的集合。
可以使用Properties集合中的方法store,把集合中的零时数据,持久化写入到硬盘中存储。
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用。
void load(InputStream inStream)
void load(Reader reader )
参数
InputStream inStream——字节输入流,不能读取含有中文的键值对。
Reader reader——————字符输入流,能读取含有中文的键值对。
使用步骤
1.创建Properties集合对象。
2.使用Properties集合对象中的方法load读取保存键值对的文件。
3.遍历Properties集合
注意
1.存储键值对的文件中,健与值默认的连接符号可以使用=,空格(其他符号)。
2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取。
3.存储键值对的文件中,健与值默认都是字符串,不用再加引号。
属性值列表中每个间及其对应值都是一个字符串。
Properties集合是一个双列集合,key和value默认都是字符串。
Properties集合中有一些操作字符串的特有方法。
Object setProperty(String key,String value)——调用Hashtable的方法put。
String getProperty(String key)———————通过key找到value值,此方法相当于Map集合中的get(key)方法。
setStringPropertyNames()—————放回此属性列表中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的keyset方法。
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储。
void store(OutputStream out,String comments)
void store(Writer writer,String comments)
参数
OutputStream out——字节输出流,不可以写中文
Writer writer————字符输出流,可以写中文
String comments——注释,用来解释说明保存的文件是做什么用的。
不能使用中文,会产生乱码,默认是Unicode编码
一般使用“空字符串”
使用步骤
1.创建Properties集合对象,添加数据。
2.创建字节输出流/字符输出流,构造方法中绑定要输出的目的地。
3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储。
4.释放资源。
代码:
public static void main(String[] args) {
Properties pr=new Properties();
pr.put("name", "张三");
pr.put("age", "18");
try {
FileWriter fw=new FileWriter(new File("src/text.properties"));
pr.store(fw,"这是一段注释");
} catch (IOException e) {
e.printStackTrace();
}
}//写入文件
public static void main(String[] args) {
//读取文件
//从指定文件读取
try {
Properties pr=new Properties();
FileInputStream fis= new FileInputStream(new File("src/text.properties"));
//使用"utf-8"编码
InputStreamReader isr=new InputStreamReader(fis,"utf-8");
pr.load(isr);
String name=pr.getProperty("name");
String age=pr.getProperty("age");
System.out.println(name+"---"+age);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
1.8对接口方法的改变
首先可以声明static 与 default 的方法
default 它的出现使得接口 解决了 接口升级的问题 就接口中新添加了一个方法 实现类不用必须去重写它 也可以重写覆盖
public class DefultClass {
public static void main(String[] args) {
new Testson().text();//输出父类中的方法
}
}
interface TestFather{
default void text(){
System.out.println("父类中的方法");
}
default void text1(){
System.out.println("可以写多个default");
}
void write();
}
class Testson implements TestFather{
@Override
public void write() {
System.out.println("重写方法");
}
}
但是下面的例子 如果2个父接口中相同的default方法那么就必须重写了
public class DefultClass {
public static void main(String[] args) {
new Testson().text();
}
}
interface TestFather{
default void text(){
System.out.println("父类中的方法");
}
void write();
}
interface TestFather2{
default void text(){
System.out.println("父类中的方法2");
}
}
class Testson implements TestFather,TestFather2{
@Override
public void text() {
//因为2个父接口中的默认方法相同 编译器不知道调用那个所以就必须要重写
}
@Override
public void write() {
System.out.println("重写方法");
}
}
static关键字
public class StaticClass {
public static void main(String[] args) {
Test.write();//根据接口名调用 同样子类不能调用 因为静态方法只属于这个类
}
}
interface Test{
static void write(){
System.out.println("我是接口的静态方法");
}
}
1.9接口私有方法
目的就是为了解决接口中代码相同部分
public class PrivateClass {
public static void main(String[] args) {
Test2.Test2();
}
}
interface Test{
private void write(){
System.out.println("方法中相同的代码");
}
default void Test1(){
//default方法默认就是public
write();
System.out.println("实现功能1");
}
default void Test2(){
write();
System.out.println("实现功能2");
}
}
interface Test2{
private static void write(){
System.out.println("方法中相同的代码");
}
static void Test1(){
//static方法默认就是public
write();
System.out.println("实现功能1");
}
static void Test2(){
write();
System.out.println("实现功能2");
}
}
1.8对函数接口的注解
首先什么是函数接口?
就是接口中只有一个抽象方法
函数接口跟普通接口其实没什么区别 只是实现的方式多了一个lambda方式
使用@FunctionalInterface注解限制
如果接口中没有一个抽象方法 编译错误 或者 有多个 编译错误 总的来说就一个接口只有一个抽象方法
@FunctionalInterface
interface myClass{
void write();
// void fun();
}
1.8中的常用函数接口
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。
Supplier接口
下面是最简单的Supplier接口及使用示例。
// Supplier接口源码
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。如:
import java.util.function.Supplier;
public class Demo01Supplier {
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "World ";
System.out.println(
getString(
() -> msgA + msgB
)
);
}
private static String getString(Supplier<String> stringSupplier) {
return stringSupplier.get();
}
}
练习求数组最大元素
使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。接口的泛型使用 java.lang.Integer 类。
import java.util.function.Supplier;
public class DemoNumberMax {
public static void main(String[] args) {
int[] numbers = {
100, 200, 300, 400, 500, -600, -700, -800, -900, -1000};
int numberMax = arrayMax(
() -> {
int max = numbers[0];
for (int number : numbers) {
if (max < number) {
max = number;
}
}
return max;
}
);
System.out.println("数组中的最大值为:" + numberMax);
}
/**
* 获取一个泛型参数指定类型的对象数据
* @param integerSupplier 方法的参数为Supplier,泛型使用Integer
* @return 指定类型的对象数据
*/
public static Integer arrayMax(Supplier<Integer> integerSupplier) {
return integerSupplier.get();
}
此接口指定什么泛型就返回什么类型
Consumer接口
源码
@FunctionalInterface
public interface Consumer<T> {
/**
* 对给定参数执行消费操作。
*
* @param t 输入参数
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
accept(t); after.accept(t); };
}
}
java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定。
Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:
代码:
import java.util.function.Consumer;
public class MyFuntion {
public static void main(String[] args) {
write("tam"