JDK8&9新特性
接口的新特性
概述
jdk8之前接口是规则的集合体,方法只有抽象方法。 jdk8版本开始不光光有抽象方法同时增加了实体方法。
增加内容
jdk8: 默认方法 静态方法 jdk9: 私有方法
默认方法
概述
被关键字 default 修饰的方法就是默认方法,是在jdk8版本才出现的方法,独属于接口所有。
语法格式:
修饰符 default 返回值类型 方法名 (参数列表){方法体}
使用规则:
1、直接被实现类对象调用使用 2、可以被实现类重写后调用使用 3、实现类的其他方法中想要调用接口的默认方法: 直接使用接口的实现类对象调用使用【前提:实现类没有重写接口的默认方法】 可以直接调用【调用的肯定是默认方法原有的方法体】 调用格式: 接口名.super.默认方法名(实参) 前提:方法所在的类必须是接口的实现类
影响:
1、使接口拥有了具体的方法从而越来越接近抽象类 2、因为接口是实现的关系,一个类可以实现多个接口,比抽象类更加容易使用,降低了耦合性加强了扩展 性,未来有取代抽象类的趋势。
代码示例:
接口: package com.tyhxzy.demo; public interface InterfaceA { void show();// 抽象方法 // 默认方法 public default void work(){ System.out.println("我是新来的默认方法 请多多关照,有事您说话"); } } 实现类: package com.tyhxzy.demo; public class ClassA implements InterfaceA { @Override public void show() { System.out.println("实现类给接口擦的屁股 "); } @Override public void work() { // 保留接口中的原有功能 调用接口中的work方法 InterfaceA.super.work(); System.out.println("看着新来的默认方法有点不爽,给你增加点伙计"); } // 实现类独有的方法 中调用接口中的默认方法 public void run() { // 调用接口中的work方法 InterfaceA.super.work(); // 执行的永远都是 默认方法的方法体 // 使用接口对象 调用默认方法 new ClassA().work();// 没有重写 :执行的就是 默认方法的方法体 重写:执行重写后的方法体 } } 测试类: package com.tyhxzy.demo; public class TestClassA { public static void main(String[] args) { // 想要使用接口中的方法 --- 获取接口的实现类对象 ClassA classA = new ClassA(); // 实现类对象调用接口中有的方法 classA.show();// 调用抽象方法 执行结果是实现类中重写后的方法体的结果 // 直接调用接口中的方法 classA.work();// 结果是接口中默认方法的方法体的结果 // 直接调用发现接口的默认方法太low了 我要扩展功能【重写方法】 // 调用run方法 classA.run(); } }
静态方法
概述:
静态方法独属于接口本身,实现类对象没有访问的权利,只能接口名调用方法
使用:
1、直接使用接口名调用 2、不同的接口中可以定义相同方法声明的静态方法,互不影响
结论
1、接口中的静态方法只能接口名调用,实现类对象没有权利使用 2、静态方法提供给接口的默认方法使用
代码示例:
接口A: package com.tyhxzy.demo; public interface InterfaceA { void show();// 抽象方法 // 默认方法 public default void work(){ System.out.println("我是新来的默认方法 请多多关照,有事您说话"); } // 静态方法 public static void get(){ System.out.println("我是接口A中的静态方法get"); } } 接口B: package com.tyhxzy.demo; public interface InterfaceB { // 静态方法 public static void get(){ System.out.println("我是接口B中的静态方法get"); } } 实现类: package com.tyhxzy.demo; public class ClassA implements InterfaceA { @Override public void show() { System.out.println("实现类给接口擦的屁股 "); } @Override public void work() { // 保留接口中的原有功能 调用接口中的work方法 InterfaceA.super.work(); System.out.println("看着新来的默认方法有点不爽,给你增加点伙计"); } // 实现类独有的方法 中调用接口中的默认方法 public void run() { // 调用接口中的work方法 InterfaceA.super.work(); // 执行的永远都是 默认方法的方法体 // 使用接口对象 调用默认方法 new ClassA().work();// 没有重写 :执行的就是 默认方法的方法体 重写:执行重写后的方法体 } // 实现类重写不了接口中的静态方法 } 测试类: package com.tyhxzy.demo; public class TestClassA { public static void main(String[] args) { // 想要使用接口中的方法 --- 获取接口的实现类对象 ClassA classA = new ClassA(); // 实现类对象调用接口中有的方法 classA.show();// 调用抽象方法 执行结果是实现类中重写后的方法体的结果 // 直接调用接口中的方法 classA.work();// 结果是接口中默认方法的方法体的结果 // 直接调用发现接口的默认方法太low了 我要扩展功能【重写方法】 // 调用run方法 classA.run(); // 实现类对象 调用不了接口的静态方法 // classA.get(); InterfaceA.get(); InterfaceB.get(); } }
私有方法
概述
是jdk9版本增加的一个实体方法,主要是用来进一步封装代码,提升相关代码安全性的手段。私有化之后方法不能被实现类直接调用使用或重写修改,只能提供给接口的静态方法和默认方法使用。
使用
普通私有方法:只能提供给默认方法调用使用 静态私有方法:默认方法和静态方法都可以调用
代码示例
接口: package com.tyhxzy.demo; public interface InterfaceA { void show();// 抽象方法 // 默认方法 public default void work(){ System.out.println("我是新来的默认方法 请多多关照,有事您说话"); get(); // 默认方法调用所有的私有方法 start(); end(); } // 静态方法 public static void get(){ System.out.println("我是接口A中的静态方法get"); // 静态方法只能静态的私有方法 end(); } // 普通的私有方法 private void start(){ System.out.println("接口的普通私有方法"); } // 静态的私有方法 private static void end(){ System.out.println("接口的静态私有方法"); } // private default void end(){ // System.out.println("接口的静态私有方法"); // } } 实现类: package com.tyhxzy.demo; public class ClassA implements InterfaceA { @Override public void show() { System.out.println("实现类给接口擦的屁股 "); } // 实现类中不可以重写接口私有方法 } 测试类: package com.tyhxzy.demo; public class TestClassA { public static void main(String[] args) { // 想要使用接口中的方法 --- 获取接口的实现类对象 ClassA classA = new ClassA(); // 实现类对象不能直接调用私有方法 可以借助默认方法调用 // classA.start(); // classA.end(); } }
Lambda表达式
概述:
Lambda表达式是java对数学函数表达式的一种体现形式,本质是一个值,在java中主要是体现在对特殊的匿名内部类对象的一种表示,代表匿名内部类的对象。 也可以理解为Lambda表达式是匿名内部类格式的一种简化,但是本质不一样。 都是匿名内部类对象 但是一个是进行单独编译,创建的对象 Lambdavia表达式不会单独编译 本质是值 值表示 了匿名内部类的对象
前提:【记住】
1、有且只有一个抽象方法的接口 2、必须要有上下文推断 也就是方法的参数或局部变星类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
格式:【记住】
标准格式:【三个一:一个小括号 一个箭头 一个花括号】 (参数列表)->{执行代码段} 格式解释: (参数列表):是匿名内部类中重写的抽象方法的方法参数列表 【就是接口抽象方法的参数列表】 ->:原封不动 他是lambda表达式的标志 {执行代码段}:lambda表达式对接口抽象方法的重写
结论:
lambda表示式就是得到接口实现类对象的另一种方式【1、定义实现类创建对象 2、 匿名内部类 3、lambda表达式】
代码示例:
package com.tyhxzy.demo; public class LambdaDemo { public static void main(String[] args) { // 直接使用Runnable的实现类对象 // 传统的方式【匿名内部类】 Runnable r = new Runnable() { @Override public void run() { System.out.println("这是匿名内部类重写抽象方法的方法体"); } }; new Runnable() { @Override public void run() { System.out.println("这是匿名内部类重写抽象方法的方法体"); } }; // 上面的匿名内部类符合lambda表示的使用前提 之一 Runnable r1 = ()->{ System.out.println("这是匿名内部类重写抽象方法的方法体");}; // 不能单独存在这是一个值 // ()->{ System.out.println("这是匿名内部类重写抽象方法的方法体");}; // 使用:匿名内部类课直接调用方法 lambda表达式也可以直接调用方法 效果上:都是接口的实现类对象 // 本质: 匿名内部类就是一个对象 lambda本质是一个值 java中这个值就是 对象 // 结论:lambda表示式就是得到接口实现类对象的另一种方式【1、定义实现类创建对象 2、 匿名内部类 3、lambda表达式】 r.run(); r1.run();
// 开辟新线程做事情 new Thread(()->{ // 线程要干的事 System.out.println("run方法要干的事"); }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("run方法要干的事"); } }).start(); }
}
Lambda表达式的省略原则
概述:
在符合一定条件下Lambda表达式的格式可以进行简化的
具体原则:
1、小括号参数的参数类型可以直接省略 2、小括号的参数有且只有一个的时候,小括号可以省略 3、花括号中有且只有一个执行语句的时候,可以同时省略花括号 分号 和 return关键字 【要省略三者同时省略,否则一个都不要省略】 4、小括号和花括号的省略原则互不干扰,各省略各的
代码示例
package com.tyhxzy.demo; public class TestInterfaceC { public static void main(String[] args) { // 使用Lambda表达式获取接口InterfaceC的实现类对象 // 标准格式 InterfaceC c = (int num)->{ // System.out.println(num); return num *2; }; // 简化 :小括号符合省略原则 InterfaceC c1 = num ->{ // System.out.println(num); return num *2; }; // 简化 :花括号符合省略原则 [要么省略全部省略 要么一个都不要省略] // InterfaceC c2 = num -> System.out.println(num); InterfaceC c2 = (num) -> num *2; } }
方法引用
概述:
方法引用是对函数式接口的实现类对象获取的另一种方式,他一般是lambda表达式的栾生兄弟 都是用来体现特殊接口的实现类读写的新方式。 方法引用对 有且只有一个抽象方法接口中的重写方法的方法逻辑在其他类的方法中已经实现过的接口 可以使用方法引用
特殊条件:
Lambda表达式对接口抽象方法重写的方法体,在其他类中有具体的方法已经书写实现过的情况就可以使用方法引用表示接口的实现类对象 使用范围比lambda更小了。 lambda表达式使用所有的有且只有一个抽象方法的接口 方法引用只能使用于有且只有一个抽象方法的接口的部分接口
使用前提:
1、有且只有一个抽象方法的接口 2、必须有上下文推断
方法引用的格式:
静态方法引用: 类名 ::静态方法名 普通方法引用: 对象名 ::普通方法名 构造方法的引用: 类名 ::new
代码示例:
接口 package com.tyhxzy.demo; public interface InterfaceE { void work(String str,int num); } F接口 package com.tyhxzy.demo; public interface InterfaceF { Demo get(int num); } Demo类 package com.tyhxzy.demo; public class Demo { int name ; public void work(String str){ // 方法的逻辑和方法的声明和接口中抽象方法重写是相同的 System.out.println(str); } public static void show(String str ,int num){ // 方法的逻辑和方法的声明和接口中抽象方法重写是相同的 String s = str + num ; System.out.println(s); } public Demo(int name) { this.name = name; } public Demo() { } } 静态方法引用 package com.tyhxzy.demo; public class TestInterfaceE { public static void main(String[] args) { // 想要获取E接口的实现类对象 // 匿名内部类 InterfaceE e1 = new InterfaceE() { @Override public void work(String str, int num) { System.out.println(str + num); } }; // lambda表达式方式 InterfaceE e2 = (String str, int num)-> {System.out.println(str + num);}; // 方法引用【静态方法的引用】 InterfaceE e3 = Demo :: show; e1.work("123",456); e2.work("123",456); e3.work("123",456); } } 构造方法引用 package com.tyhxzy.demo; public class TestInterfaceF { public static void main(String[] args) { // 获取F接口的实现类对象 调用get方法得到Demo对象 // 匿名内部类 InterfaceF f1 = new InterfaceF() { @Override public Demo get(int num) { return new Demo(); } }; Demo demo = f1.get(200); System.out.println(demo.name);// 0 // Lambda 表达式 InterfaceF f2 = (num) ->{ return new Demo(num);// 调用Demo类的空参构造 }; Demo demo2 = f2.get(300); System.out.println(demo2.name);// 0 // 方法引用 [构造的引用有局限性 ] InterfaceF f3 = Demo :: new;// 引用所有的构造 具体执行按照上下文推断 Demo demo3 = f3.get(500); System.out.println(demo3.name);// 0 } }
函数式接口
概述:
有且只有一个抽象方法的接口就是函数式接口 比如:jdk中熟悉的函数式接口 :Runnable Callable
注解:
@FunctionalInterface 作用:定义函数接口的时候需要在接口上面增加这个注解,方便编译的时候判断该接口是不是函数式接口
jdk内置常用函数式接口
1、Consumer:消费型接口 有一个可以消费任意数据类型的数据的方法 2、Supplier:供给型接口 有一个可以提供任意数据类型数据的方法 3、Function:函数型接口 有一个可以把任意数据乐行转换为其他任意数据类型的方法 4、Predicate:断言型接口 有一个可以对任意数据进行判断的方法 指定定义了功能,没有定义具体的实现原则,可以使用这样的接口对象定义对应的具体规则,对象采用Lambda表达式来体现,规则在对象中 对象又是Lambda表达式,表达式又是一个值 就可以传递这个值,相当于实现把规则进行传递了
Consumer<T>
【消费性接口】
翻译过来就被称为消费者类型的接口。
那什么叫消费者类型呢?
消费者是啥,花钱的呗
给你一个东西,你就消费掉了
这就是消费型接口,所以抽象方法呢?猜都猜得出来了吧?来看看:
看到了吧,也是since1.8,并且也是FunctionalInterface,可以用Lambda的接口。抽象方法就是那一个accept。
accept就是接受一个泛型类型的参数,然后就,,就吃掉了,什么都不返回。
这不就是完美的消费者逻辑吗?所以就叫消费型接口。
至于下面那个andThen的默认方法,老哥都说过了,JDK的默认方法是1.8以后的新特性,现在先不管。
所以我们开始写Lambda表达式,创建几个Consumer对象试试:
看到了吧,也是since1.8,并且也是FunctionalInterface,可以用Lambda的接口。抽象方法就是那一个accept。 accept就是接受一个泛型类型的参数,然后就,,就吃掉了,什么都不返回。 这不就是完美的消费者逻辑吗?所以就叫消费型接口。 至于下面那个andThen的默认方法,老哥都说过了,JDK的默认方法是1.8以后的新特性,现在先不管。 所以我们开始写Lambda表达式,创建几个Consumer对象试试:
其实不难,知道了Lambda表达式用处场景和简化写法以后,Consumer接口的对象,其实就是这么简单。 那有没有在源码中有体现呢?其实JDK8对于集合Collection体系提供了一个新的迭代方法。 以前不是写for循环或者for-each增强循环码,JDK8更直接,对于Collection体系下面,直接给了你一个forEach方法:
这个方法属于Iterable接口,像是List,Set等等,都可以用这个方法。看入参,不就是需要你传入一个Consumer吗?然后你发现,他其实内部也很简单,就是内部手动给你for-each增强循环了一次,然后对于每一个元素都执行了Consumer的消费逻辑,至于逻辑是啥,我们自己规定,比如System.out.println每一个元素,就可以是我们的消费逻辑之一。
意思是,以后我们迭代集合会更丝滑:
Supplier【供给型接口】
待这消费型接口Consumer都说了,那我们就说说生产者类型接口Suppiler。
其实学了Consumer接口以后,这个Suppiler都没啥说的了。
消费者接口的抽象方法不是accept吗?给你一个泛型类型的对象【类型你自己指定】,然后就消费了,没有返回了。这就是消费型接口的抽象呗。确实,这也就是抽象的一种消费的理念。
那么生产呢?
很简单呗,猜都能猜出来,那不就是什么都不用给我,我给你搞个对象出来,对象类型你泛型指定,这不就是生产了?
来看看:
完全没技术难度,那我们来写几个生产者接口的实现对象,用Lambda试试:
生产者的抽象逻辑就是:什么都不用入参,返给外界一个生产出来的对象,和消费者的逻辑是完全相反的。这就是生产者逻辑【当然,不用想的那么离谱,比如还要原料才能生产啥的,这样钻牛角尖去理解,也没那么必要】
Function<T,R>【函数型接口】
这个类在java.util包下面,since 1.8也表示在JDK8以后才有这个玩意儿。Functional Interface也表示他只有一个抽象方法等待实现,可以用Lambda表达式——这个方法就是apply。
入参和出参类型,由我们用泛型动态指定。apply的具体逻辑就相当于是入参转化为出参的具体逻辑。也就相当于是y = f(x)这个里面的,映射法则f。具体逻辑需要我们用匿名内部类或者Lambda,写方法体来具体指定。
其实也就这个意思。这个接口,就代表一个y = f(x)的具体映射法则f的抽象。所以这个接口的名字也是非常的见名之意:Function,函数的意思
看出来了吗?
f1其实就是对于传入的String,获取长度的一个方法
f2其实就是对于传入的int数字加1的方法
f3其实就是对于传入的Object调用toString转化为字符串输出的方法
Java把这些映射规则,也就是y = f(x)中的【f】抽象成了这个Function接口的apply逻辑。然后x和y,自变量和因变量,也就是入参出参,Java使用了扩展性更强的泛型参数类型,而不是固定Object入参出参。因为固定Object的话还要涉及到类型转换,还有可能报ClassCast异常,很麻烦
Function接口,或者说下面的四大类函数式接口,在后面的Stream API中用的特别多,一定要掌握!
Predicate<T>
【断言型接口】
功能
boolean test(T t):抽象方法 判断对给定的数据是否符合某个条件 Predicate and (Predicate p): 把两个断言型接口的判断规则判断的结果进行逻辑且【与】的处理 Predicate or (Predicate p): 把两个断言型接口的判断规则判断的结果进行逻辑或的处理 Predicate negate(): 取判断结果的反向结果
我们发现,他下面除了一些很多默认方法以外,只提供了一个抽象方法,test方法。这个方法就是传入一个参数,泛型类型自己制定,然后经过一段逻辑【这个由我们实现类指定,不管是匿名内部类还是Lambda都行】,最后返回true或者false的布尔值。
这不就是对于一个任意类型的入参,根据我们写的逻辑,然后进行一个判断返回true或者false的抽象吗?
那这个就叫对于一个元素的断言呗。给我一个元素,我给你断言到底你是真还是假。至于我判断的逻辑,交给本接口的实现类的书写即可,至于咋书写?匿名内部类或者Lambda呀。
匿名内部类语法对于这种单抽象方法的接口来说太low了,果断选Lambda表达式。
那么还是老样子,写几个实现类来看看:
这个能看懂的话,其实就没啥问题了,挺简单的。 可能有些靓仔们对于Lambda表达式这种写法还感到很,怎么说呢?可能感到有点没法接受。没事,慢慢来,至少匿名内部类到Lambda的转换要会,然后写着写着,其实你就熟练了,也就会发现Lambda表达式很好用了。 其实Lambda表达式说白了就是:不关注单个抽象方法的权限,返回值,签名,只关注于该方法的入参和方法体即可。但是仅限于一个抽象方法的接口的匿名内部类的简化情况。 其实学到这里,不知道各位靓仔们有没有一个感觉,好像就是,你讲的这些四大函数式接口,我们会Lambda也能听懂啊,但是有啥用呢?
Stream流
- 概述
概述: Stream是jdk8增加的一个接口,该接口提供了一些对容器数据进行操作的规则,有了这些规则就可以不 通过遍历容器就可以以完成对相关数据的操作处理。 特点:不能存放任何的数据,只有对数据的修改的能力
- 理解
当需要对多个元素所在的容器进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。这个模型其实就是Stream
Stream可以理解为像生产流水线,操作只能往前走不能回退,经过一道工序操作的数据就会发生变化上一道工序的对象就消失不可再次使用.
- 作用:
不用遍历容器下对容器数据进行操作比较快捷方便
- 注意:
Stream虽然可以操作数据但是本身不能够存储任何数据
Steam对象的获取:
Collection的Stream获取
集合中提供了方法: stream():返回一个集合对应的Stream流对象
Map的Stream的获取
不能直接获取Stream对象,只能变为单列集合再去获取对应的stream对象 功能: keySet:获取所有key的set集合 entrySet:获取所有键值对对象的set集合 values:获取所有value的collectioon集合 单列集合调用stream方法获取对应的stream对象
数组的Stream获取:
使用Stream接口的静态方法:of(T t)
代码示例:
package com.tyhxzy.demo; import java.util.*; import java.util.stream.Stream; public class StreamDemo { public static void main(String[] args) { // 单列集合对象获取操作Stream的对象 List<String> list = new ArrayList(); Stream<String> s1 = list.stream(); // 双列集合不能直接得到Stream流 双列变单列 在获取Stream HashMap<String, String> map = new HashMap<>(); Set<String> set = map.keySet(); Set<Map.Entry<String, String>> entries = map.entrySet(); Collection<String> values = map.values(); Stream<String> s2 = set.stream(); Stream<Map.Entry<String, String>> s3 = entries.stream(); Stream<String> s4 = values.stream(); // 数组 String[] arr = new String[1024]; Stream<String> s5 = Stream.of(arr); } }
结论:
集合:单列集合中的stream方法
数组:Stream接口中的静态方法of(T t)
Stream的方法
方法
Stream filter(Predicate p): 按照指定的条件对stream中数据进行过滤【满足保留】 Stream limit(int num): 只获取Stream中前num个元素 Stream skip(int num): 跳过前num个数据获取之后的数据 Stream map(Function f): 映射功能【把Stream中的数据映射为另一种数据】 Stream concat(Stream s1,Stream s2):这是一个静态方法 拼接两个stream流为一个stream流 toArray(): 把stream流中的数据收集到数组中 collect(Collector c): 把stream流中的数据收集到指定的集合中 Collector :参数的类型 是一个接口获取可以通过工具类Collectors的方法获取 常用: 获取List集合:Collectors.toList() 获取Set集合: Collectors.toSet() forEach(Consumer c): 用来使用stream流中的数据的 int count(): 返回stream流中元素的个数
代码示例:
package com.tyhxzy.demo; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class StreamMethodDemo { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("王宝强"); list.add("宝宝"); list.add("蓉蓉"); list.add("马蓉"); list.add("宋小宝"); list.add("小黑娃"); list.add("吉克隽逸"); list.add("黑葫芦娃"); list.add("古力娜扎.汉娜.玉山"); Stream<String> s1 = list.stream(); // 过滤数据 Stream<String> s2 = s1.filter((str) -> { return str.length() < 4; }); // foreach // s2.forEach(System.out :: println); // 取前几位元素 Stream<String> s3 = s2.limit(4); // s3.forEach(System.out :: println); // Stream<String> s3 = s1.filter((str) -> { // return str.length() == 4; // }); // 跳过前几个数据 Stream<String> s4 = s3.skip(2); // s4.forEach(System.out :: println); // 映射Stream中的数据为另一种数据 Stream<char[]> s5 = s4.map((name) -> { return name.toCharArray(); }); //s5.forEach(System.out :: println); Stream<char[]> s6 = Stream.of("nihao".toCharArray()); // 合并两个Stream流数据为一个流数据 Stream<char[]> s7 = Stream.concat(s5, s6); // 消费Stream中的数据【输出】 // s7.forEach(System.out :: println);
// 获取Stream的操作元素的个数 // long count = s7.count(); // System.out.println(count); // 获取Stream元素到数组【Object数组】
// Object[] obj = s7.toArray();
//
// for (Object o : obj) {
// System.out.println(o);
// }// 收集Stream中的数据到集合中 List<char[]> list1 = s7.collect(Collectors.toList()); for (char[] chars : list1) { System.out.println(chars); } }
}
方法总结
概述
延迟方法:返回值类型还是Stream类型的方法就是延迟方法 可以继续链式编程【Stream的功能】 终结方法:返回值不是Stream类型是其他的数据类型是终结方法
详情
方法名 方法作用 方法种类 是否支持链式调用 count 统计个数 终结 否 forEach 逐一处理 终结 否 toArray 收集数据到数组 终结 否 collect 收集数据到指定集合 终结 否 filter 过滤 延迟方法 是 limit 取用前几个 延迟方法 是 skip 跳过前几个 延迟方法 是 concat 组合 延迟方法 是 map 映射 延迟方法 是
Stream综合案例
需求
有两个 Arraylist 集合,存储队伍中的多个成员姓名,使用 Stream 方式,对以下步骤进行操作 1、第一个队伍只要名字为 3 个字的成员姓名 2、第一个队伍只要筛选之后的前三个人 3、第二个队伍只要姓张的 4、第二个队伍筛选之后不要前两个人 5、将两个队伍合并成一个队伍 6、合并之后的队伍中的所有人名字变为Person(自定义类型)对象,并将对象存储到一个List集合中 队伍 1:宫本武藏、宋公明、苏有朋、石头人、时传祥、李耳、庄子、洪七公 队伍 2:帕瓦罗蒂、张三疯、赵薇薇、张自忠、孛儿只斤铁木真、张天爱、张翠花
代码示例
传统的方式: package com.tyhxzy.demo; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class StreamText { public static void main(String[] args) { String[] arr1 = "宫本武藏、宋公明、苏有朋、石头人、时传祥、李耳、庄子、洪七公".split("、"); // Stream.of(arr1).map() ArrayList<String> list1 = new ArrayList<>(); for (String s : arr1) { list1.add(s); } String[] arr2 = "帕瓦罗蒂、张三疯、赵薇薇、张自忠、孛儿只斤铁木真、张天爱、张翠花".split("、"); // Stream.of(arr1).map() ArrayList<String> list2 = new ArrayList<>(); for (String s : arr2) { list2.add(s); } // StreamMethod(list1, list2); // 传统方法方式 // 1、第一个队伍只要名字为 3 个字的成员姓名 // 2、第一个队伍只要筛选之后的前三个人 for (int i = 0; i < list1.size(); i++) { if (list1.get(i).length() != 3){ list1.remove(list1.get(i)); i--; } } list1.remove(list1.size()-1); list1.remove(list1.size()-1); // 3、第二个队伍只要姓张的 // 4、第二个队伍筛选之后不要前两个人 for (int i = 0; i < list2.size(); i++) { if (!list2.get(i).startsWith("张")){ list2.remove(list2.get(i)); i--; } } list2.remove(0); list2.remove(0); // 合并两个集合数据为一个集合 list1.addAll(list2); ArrayList<Person> list = new ArrayList<>(); for (String name : list1) { Person person = new Person(name); list.add(person); } System.out.println(list); } }
Stream方式: package com.tyhxzy.demo; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class StreamText { public static void main(String[] args) { String[] arr1 = "宫本武藏、宋公明、苏有朋、石头人、时传祥、李耳、庄子、洪七公".split("、"); // Stream.of(arr1).map() ArrayList<String> list1 = new ArrayList<>(); for (String s : arr1) { list1.add(s); } String[] arr2 = "帕瓦罗蒂、张三疯、赵薇薇、张自忠、孛儿只斤铁木真、张天爱、张翠花".split("、"); // Stream.of(arr1).map() ArrayList<String> list2 = new ArrayList<>(); for (String s : arr2) { list2.add(s); } Stream<String> stream1 = list1.stream(); Stream<String> stream2 = list2.stream(); // 1、第一个队伍只要名字为 3 个字的成员姓名 // 2、第一个队伍只要筛选之后的前三个人 Stream<String> s1 = stream1.filter((name) -> { return name.length() == 3; }).limit(3); // 3、第二个队伍只要姓张的 // 4、第二个队伍筛选之后不要前两个人 Stream<String> s2 = stream2.filter((name) -> { return name.startsWith("张"); }).skip(2); // 5、将两个队伍合并成一个队伍 Stream<String> newStream = Stream.concat(s1, s2); // 6、合并之后的队伍中的所有人名字变为Person(自定义类型)对象, Stream<Person> stream = newStream.map((name) -> { return new Person(name); }); // 并将对象存储到一个List集合中 List<Person> list = stream.collect(Collectors.toList()); System.out.println(list); } }
Person类: package com.tyhxzy.demo; public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } }
注解
概述
概述
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用
1、编写文档:通过代码里标识的注解生成文档【例如,生成文档doc文档 @author @version】 2、代码分析:通过代码里标识的注解对代码进行分析【例如,注解的反射 】 3、编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】
常见注解
1. @author:用来标识作者名 2. @version:用于标识对象的版本号,适用范围:文件、类、方法。 3. @Override : 用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。 4、@Webservlet:
自定义注解
- 定义格式
1、元注解 2、public @interface 注解名称{ 3、 属性列表; }
- 注解的元注解
元注解:用来描述注解的注解 分类: @Target:描述注解能够作用的位置 value值:是一个ElementType[] 常用取值: 1、ElementType.TYPE ==== 使用在类上 2、ElementType.METHOD ==== 使用在方法上 3、ElementType.FILED ==== 使用在属性上 @Retention:保留注解被保留的阶段 value值:是一个RetentionPolicy枚举 取值: 1、RetentionPolicy.SOURCE---【在源代码中不会到字节码文件中】 2、RetentionPolicy.CLASS---【编译到字节码文件中但是运行时jvm不会读取】 3、RetentionPolicy.RUNTIME--【编译到字节码文件中运行时jvm会读取】 @Documented :描述注解是否被抽取到API文档中 @Inherited:注解是否被子类继承
注解的属性
- 属性的作用
可以让用户在使用注解时传递参数,让注解的功能更加强大。
- 属性的格式
- 格式1:数据类型 属性名(); - 格式2:数据类型 属性名() default 默认值;
- 属性适用的数据类型
1、八种基本数据类型(int,float,boolean,byte,double,char,long,short) 2、 String类型,Class类型,枚举类型,注解类型 3、 以上所有类型的一维数组
注解本质上就是一个接口,该接口默认继承Annotation接口
代码示例
package com.tyhxzy.demo; import javax.lang.model.element.NestingKind; import java.lang.annotation.*; import java.util.EnumMap; // 1、元注解=== 就是定义好的注解 直接拿来用 @Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) //@Documented @Inherited public @interface People { // 2、属性[1、注解的本质是接口 2、属性其实就是接口的抽象方法另类表现形式] String name(); int age() default 20; byte num(); short num1(); long num2(); float num3(); double num4(); char num5(); boolean boo(); Class clazz(); // enum num12{}; // @interface inter{}; int[] arr(); //Person[] arr1(); }
注解的解析
步骤:
在程序中使用(解析)注解的步骤(获取注解中定义的属性值): 1. 获取注解定义的位置的反射元素对象 (Class,Method,Field) 2. 获取指定的注解 getAnnotation(Class) 3. 调用注解中的抽象方法获取配置的属性值 使用格式: @注解名(属性名=属性值,属性名=属性值,属性名=属性值...)
代码示例
package com.tyhxzy.demo; // 使用注解的时候 属性需要赋值的 没有默认值一定要赋值 有默认值可以不赋值【使用的默认值】 @People(name = "金莲",age = 25,arr={15,20}) public class TestPeople { @People(name = "大郎",age = 28,arr={100,20000}) public int num; @People(name = "庆庆",arr={10,200000}) public void show(){} }
package com.tyhxzy.demo;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;public class TestGetValue {
public static void main(String[] args) throws Exception {
// 1、解析那个位置的注解 :获取解析的注解位置的对象 【反射对象】
Classclazz = TestPeople.class;
// 2、获取该位置的注解对象【反射对象 === 成员方法对象 Method类对象】
People annotation = clazz.getAnnotation(People.class);
// 3、注解对象调用对应的属性 【对应抽象方法】
int age = annotation.age();
String name = annotation.name();
int[] arr = annotation.arr();
System.out.println(name);// 金莲
System.out.println(age);// 25
System.out.println(Arrays.toString(arr));// 15 20// 解析属性num上的注解 // 1、先获取属性的对象 Field num = clazz.getField("num"); // 2、获取该位置的注解对象 People annotation1 = num.getAnnotation(People.class); // 3、获取注解对象中的属性的值 int age1 = annotation1.age(); String name1 = annotation1.name(); int[] arr1 = annotation1.arr(); System.out.println(name1);// 大郎 System.out.println(age1);// 28 System.out.println(Arrays.toString(arr1));// 100 20000 // 获取方法的注解数据 // 1、获取位置方法的反射对象 Method show = TestPeople.class.getMethod("show"); // 2、获取该位置的注解对象 People annotation2 = show.getAnnotation(People.class); int age2 = annotation2.age(); String name2 = annotation2.name(); int[] arr2 = annotation2.arr(); System.out.println(name2);// 庆庆 System.out.println(age2);// 20 System.out.println(Arrays.toString(arr2));// 10 200000 }
}
注解练习
需求:
定义一个注解:Book - 包含属性:String value() 书名 - 包含属性:double price() 价格,默认值为 200 - 包含属性:String[] authors() 多位作者
代码实现
package com.tyhxzy.demo; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface Book { // - 包含属性: 书名 String value(); //// - 包含属性:double price() 价格,默认值为 200 double price() default 200; //// - 包含属性:String[] authors() 多位作者 String[] authors(); }
使用注解
- 定义类在成员方法上使用Book注解并解析数据
package com.tyhxzy.demo; import java.lang.reflect.Method; public class TestBook { @Book(value = "神墓",authors = {"辰东","辰南"}) public void show(){ // 解析注解 try { // 1、获取注解所在位置的反射对象 【show方法】 Method show = TestBook.class.getMethod("show"); // 2、获取该位置的注解对象 Book annotation = show.getAnnotation(Book.class); // 3、调用注解属性获取对应的值 String value = annotation.value(); double price = annotation.price(); String[] authors = annotation.authors(); // 4、使用解析得到的注解数据 System.out.println(authors[0]+"和"+authors[1] +"合力创作的"+value+"售卖"+price);
} catch (NoSuchMethodException e) { e.printStackTrace(); } } public static void main(String[] args) { new TestBook().show(); }
}
- **使用注意事项** ```java - 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。 - 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
注解的特殊属性value
当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。
package com.tyhxzy.demo; import java.lang.annotation.*; @Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) //@Documented @Inherited public @interface ABC { // int value(); int[] value(); } 使用: package com.tyhxzy.demo; @ABC(/*value =*/ {123,456}) public class TestABC { }
如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value属性名不能省略。
package com.tyhxzy.demo; import java.lang.annotation.*; @Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) //@Documented @Inherited public @interface ABC { // int value(); int[] value(); String name() default "baobao"; int money(); } package com.tyhxzy.demo; @ABC(/*value =*/ value = {123,456},money = 123) public class TestABC { }