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",(name)->{
System.out.println(name);
});
}
public static void write(String name, Consumer<String> consumer){
consumer.accept(name);
}
}
如果想进行组合消费那么可以使用方法而这个方法就是 Consumer 接口中的default方法 andThen 。
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
//备注: java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出
//NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组 合的情况:
import java.util.function.Consumer;
public class Demo02Consumer {
public static void main(String[] args) {
consumerString(
// toUpperCase()方法,将字符串转换为大写
s -> System.out.println(s.toUpperCase()),
// toLowerCase()方法,将字符串转换为小写
s -> System.out.println(s.toLowerCase())
);
}
private static void consumerString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
}
运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的组合。
比如可以这么写
public class MyFuntion {
public static void main(String[] args) {
consumerString(
// toUpperCase()方法,将字符串转换为大写
s -> System.out.println(s.toUpperCase()),
// toLowerCase()方法,将字符串转换为小写
s -> System.out.println(s.toLowerCase()),
s -> System.out.println(s+"1111")
);
}
private static void consumerString(Consumer<String> one, Consumer<String> two,Consumer<String> Three) {
one.andThen(two).andThen(Three).accept("Hello");
}
}
HELLP->hello->hello1111
Predicate接口
Predicate函数式接口的主要作用就是提供一个test方法,接受一个参数返回一个布尔类型,Predicate在stream api中进行一些判断的时候非常常用。
源码
@FunctionalInterface
public interface Predicate<T> {
/**
* 具体过滤操作 需要被子类实现.
* 用来处理参数T是否满足要求,可以理解为 条件A
*/
boolean test(T t);
/**
* 调用当前Predicate的test方法之后再去调用other的test方法,相当于进行两次判断
* 可理解为 条件A && 条件B
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* 对当前判断进行"!"操作,即取非操作,可理解为 ! 条件A
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* 对当前判断进行"||"操作,即取或操作,可以理解为 条件A ||条件B
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* 对当前操作进行"="操作,即取等操作,可以理解为 A == B
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
使用方式
package cn.dali5.code05;
import java.util.function.Predicate;
/*Predicate<T>接口
* 作用:对某种数据进行判断,返回boolean。(比如判断字符串的长度)
*
* 抽象方法: boolean test(T t)
* 默认方法:and 判断两个条件是否同时符合,同时符合返回true,反之返回false
* or 或
* negate 取反*/
public class PredicateClass {
public static void main(String[] args) {
System.out.println(method("山下美月",(String name)-> name.length()>4)//false
);//一个条件检验
System.out.println(method02("abcde",(String name)->name.length()>4,(String name)->name.contains("a")));//true
System.out.println(method03("abcde",(String name)->name.length()>4,(String name)->name.contains("z")));//true
System.out.println(method04("abcde",(String name)->name.length()>4));//false
}
public static boolean method(String name, Predicate<String> pd){
return pd.test(name);
}
public static boolean method02(String name,Predicate<String> pd1,Predicate<String> pd2){
return pd1.and(pd2).test(name);
}//and方法使用
public static boolean method03(String name,Predicate<String> pd1,Predicate<String> pd2){
return pd1.or(pd2).test(name);
}// or方法使用
public static boolean method04(String name,Predicate<String> pd1){
return pd1.negate().test(name);
}//negate方法使用
}
Funtciton接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
......
}
Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。 使用的场景例如:将 String 类型转换为 Integer 类型。
import java.util.function.Function;
public class DemoFunctionApply {
public static void main(String[] args) {
method(s -> Integer.parseInt(s));
}
private static void method(Function<String, Integer> function) {
int num = function.apply("10");
System.out.println(num + 20);
}
}
//输出30
注意是转换成R然后在进行返回
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。JDK源代码如:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:
import java.util.function.Function;
public class DemoFunctionAndThen {
public static void main(String[] args) {
method(
str -> Integer.parseInt(str)+10,
i -> i *= 10
);
}
private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
int num = one.andThen(two).apply("10");
System.out.println(num + 20);
}
}
第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一 起。
练习
请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
String str = “赵丽颖,20”;
- 将字符串截取数字年龄部分,得到字符串;
- 将上一步的字符串转换成为int类型的数字;
- 将上一步的int数字累加100,得到结果int数字。
import java.util.function.Function;
public class DemoFunction {
public static void main(String[] args) {
String str = "赵丽颖,20";
int age = getAgeNum(
str,
s -> s.split(",")[1],
s -> Integer.parseInt(s),
n -> n += 100
);
System.out.println(age);
}
private static int getAgeNum(String str,
Function<String, String> one,
Function<String, Integer> two,
Function<Integer, Integer> three) {
return one.andThen(two).andThen(three).apply(str);
}
}
运行程序,控制台输出:120
1.8中的Stream
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作 。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性.
以前我们处理复杂的数据只能通过各种for循环,不仅不美观,而且时间长了以后可能自己都看不太明白以前的代码了,但有Stream以后,通过filter,map,limit等等方法就可以使代码更加简洁并且更加语义化。
下面看一个例子
public class Student {
int no;
String name;
String sex;
float height;
public Student(int no, String name, String sex, float height) {
this.no = no;
this.name = name;
this.sex = sex;
this.height = height;
}
****
}
Student stuA = new Student(1, "A", "M", 184);
Student stuB = new Student(2, "B", "G", 163);
Student stuC = new Student(3, "C", "M", 175);
Student stuD = new Student(4, "D", "G", 158);
Student stuE = new Student(5, "E", "M", 170);
List<Student> list = new ArrayList<>();
list.add(stuA);
list.add(stuB);
list.add(stuC);
list.add(stuD);
list.add(stuE);
现有一个List list里面有5个Studeng对象,假如我们想获取Sex=“G”的Student,并打印出来。如果按照我们原来的处理模式,必然会想到一个for循环就搞定了,而在for循环其实是一个封装了迭代的语法块。在这里,我们采用Iterator进行迭代:
Iterator<Student> iterator = list.iterator();
while(iterator.hasNext()) {
Student stu = iterator.next();
if (stu.getSex().equals("G")) {
System.out.println(stu.toString());
}
}
整个迭代过程是这样的:首先调用iterator方法,产生一个新的Iterator对象,进而控制整
个迭代过程,这就是外部迭代 迭代过程通过显式调用Iterator对象的hasNext和next方法完成迭代
而在Java 8中,我们可以采用聚合操作:
list.stream()
.filter(student -> student.getSex().equals("G"))
.forEach(student -> System.out.println(student.toString()));
首先,通过stream方法创建Stream,然后再通过filter方法对源数据进行过滤,最后通过foeEach方法进行迭代。在聚合操作中,与Labda表达式一起使用,显得代码更加的简洁。这里值得注意的是,我们首先是stream方法的调用,其与iterator作用一样的作用一样,该方法不是返回一个控制迭代的 Iterator 对象,而是返回内部迭代中的相应接口: Stream,其一系列的操作都是在操作Stream,直到feach时才会操作结果,这种迭代方式称为内部迭代。
外部迭代和内部迭代(聚合操作)都是对集合的迭代,但是在机制上还是有一定的差异:
- 迭代器提供next()、hasNext()等方法,开发者可以自行控制对元素的处理,以及处理方式,但是只能顺序处理;
- stream()方法返回的数据集无next()等方法,开发者无法控制对元素的迭代,迭代方式是系统内部实现的,同时系统内的迭代也不一定是顺序的,还可以并行,如parallelStream()方法。并行的方式在一些情况下,可以大幅提升处理的效率。
如何使用Stream?
聚合操作是Java 8针对集合类,使编程更为便利的方式,可以与Lambda表达式一起使用,达到更加简洁的目的。
前面例子中,对聚合操作的使用可以归结为3个部分:
- 创建Stream:通过stream()方法,取得集合对象的数据集。
- Intermediate:通过一系列中间(Intermediate)方法,对数据集进行过滤、检索等数据集的再次处理。如上例中,使用filter()方法来对数据集进行过滤。
- Terminal通过最终(terminal)方法完成对数据集中元素的处理。如上例中,使用forEach()完成对过滤后元素的打印。
在一次聚合操作中,可以有多个Intermediate,但是有且只有一个Terminal。也就是说,在对一个Stream可以进行多次转换操作,并不是每次都对Stream的每个元素执行转换。并不像for循环中,循环N次,其时间复杂度就是N。转换操作是lazy(惰性求值)的,只有在Terminal操作执行时,才会一次性执行。可以这么认为,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
Stream的操作分类
刚才提到的Stream的操作有Intermediate、Terminal和Short-circuiting:
Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered
Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator
Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
惰性求值和及早求值方法
像filter这样只描述Stream,最终不产生新集合的方法叫作惰性求值方法;而像count这样最终会从Stream产生值的方法叫作及早求值方法。
long count = allArtists.stream()
.filter(artist -> {
System.out.println(artist.getName());
return artist.isFrom("London");
})
.count();
如何判断一个操作是惰性求值还是及早求值,其实很简单,只需要看其返回值即可:如果返回值是Stream,那么就是惰性求值;如果返回值不是Stream或者是void,那么就是及早求值。上面的示例中,只是包含两步:一个惰性求值-filter和一个及早求值-count。
前面,已经说过,在一个Stream操作中,可以有多次惰性求值,但有且仅有一次及早求值。
其实就是说 filter map skip 都是在对函数模型进行操纵 集合元素并没有真正的被处理 只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作 而这得益于Lambda的延迟执行特性 简单的说就是 终结方法执行 过滤 的哪些方法才会执行.
创建Stream
我们有多种方式生成Stream:
Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);
Collection接口和数组的默认方法(默认方法,也是Java的新特性之一),把一个Collection对象转换成Stream
创建无限流
通过数组
//通过集合中的新增方法获取
ArrayList<String> temp=new ArrayList();
temp.add("谭炜");
temp.add("周姿会");
temp.add("谭炜1");
temp.add("周姿会1");
temp.add("周姿会2");
Stream<String> stream = temp.stream();//获取ArrayList 的集合流
Stream<String> stringStream1 = temp.parallelStream();//创建并行流 流中有线程 取出来的循序可能不一样
stringStream1.forEach(System.out::println);
Set<String> stringSet=new HashSet<>();
Stream<String> setStream = stringSet.stream();//获取Set 的集合流
Map<Integer,String> map=new HashMap<>();//但是map集合不是Collection接口中 所以可以用以下方式获取
Stream<Integer> stream1 = map.keySet().stream();
Stream<String> stream2 = map.values().stream();
Stream<Map.Entry<Integer, String>> stream3 = map.entrySet().stream();
//通过静态方法获取
Stream<String> stringStream = Stream.of("1", "2", "4", "3");
String[] tempString={"1","2"};
Stream.of(tempString);
//数组
Stream<String> stream4 = Arrays.stream(tempString);
//创建无限流
Stream<Integer> iterate = Stream.iterate(0, (t) -> {
return t + 2;
});//从0开始 一直返回加2的数据 无限循环 这里只取前10个数据
iterate.limit(10).forEach(System.out::println);
Stream<Double> generate = Stream.generate(() -> {
return Math.random();
});//一直返回 随机数 无限循环
generate.limit(10).forEach(System.out::println);
备注:"Stream流"其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不储存任何元素(或其地址值) 只是负责计算数据.
forEach 方法
遍历数据流中的元素
源码
void forEach(Consumer<? super T> action);
ArrayList<String> temp=new ArrayList();
temp.add("谭炜");
temp.add("周姿会");
temp.add("谭炜1");
temp.add("周姿会1");
temp.add("周姿会2");
Stream<String> stream = temp.stream();//获取ArrayList 的集合流
stream.forEach((name)->{
System.out.println(name);
});
// stream.forEach((nme)->{
//
// });// Stream 流 只能被消费一次 就是只能调用一次方法 要么就没有返回值 要么就返回新的Stream流
map方法
源码
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
可以看到参数是 funciton 也就是将什么转换成什么
代码
public static void main(String[] args) {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> integerStream1 = integerStream.map((name) -> name + 1);//将他们各自加上一个1
integerStream1.forEach((name)->System.out.println(name));//2 3 4 5 6 7
Stream<Integer> integerStream3 = Stream.of(1, 2, 3, 4, 5, 6);
integerStream3.map((name)->name.toString()+"tt").forEach((name)->System.out.println(name));//链式写法先将数据转换成字符串然后在加上 tt 然后在遍历
}
Count方法
源码
long count();
跟集合的size方法 差不多
ArrayList<String> temp=new ArrayList();
temp.add("谭炜");
temp.add("周姿会");
temp.add("谭炜1");
temp.add("周姿会1");
temp.add("周姿会2");
Stream<String> stream = temp.stream();//获取ArrayList 的集合流
System.out.println(stream.count());//输出5
limit方法
源码
Stream<T> limit(long maxSize);
跟mysql差不多 返回的是前几行的数据
ArrayList<String> temp=new ArrayList();
temp.add("谭炜");
temp.add("周姿会");
temp.add("谭炜1");
temp.add("周姿会1");
temp.add("周姿会2");
Stream<String> stream = temp.stream();//获取ArrayList 的集合流
Stream<String> limit = stream.limit(3);//取前三个 如果小于0 报错 如果大于元素长度返回全部
limit.forEach((name)->System.out.println(name));
skip方法
跳过前几行的数据
ArrayList<String> temp=new ArrayList();
temp.add("谭炜");
temp.add("周姿会");
temp.add("谭炜1");
temp.add("周姿会1");
temp.add("周姿会2");
Stream<String> stream = temp.stream();//获取ArrayList 的集合流
stream.skip(3).forEach((name)->System.out.println(name));//跳过前三个 如果参数小于0 报错 如果参数大于长度 返回一个空的流
concat方法
此方法是Stream的一个静态方法
相当于是将2个流的数据合并 反回一个新的流
源码
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
例子
ArrayList<String> temp=new ArrayList();
temp.add("谭炜");
temp.add("周姿会");
temp.add("谭炜1");
temp.add("周姿会1");
temp.add("周姿会2");
Stream<String> stream = temp.stream();//获取ArrayList 的集合流
ArrayList<String> temp2=new ArrayList();
temp.add("周姿会是猪1");
temp.add("周姿会是猪2");
temp.add("周姿会是猪3");
temp.add("周姿会是猪4");
temp.add("周姿会是猪5");
Stream<String> stream1 = temp2.stream();
Stream<String> concat = Stream.concat(stream, stream1);
concat.forEach((name)->System.out.println(name));//输出2个集合的全部数据
filter方法 :过滤
其实就是判断 不符合条件的就删除
源码
Stream<T> filter(Predicate<? super T> predicate);
参数为Predicate接口 合法就返回true 不合法就false
ArrayList<String> temp=new ArrayList();
temp.add("谭炜");
temp.add("周姿会");
temp.add("谭炜1");
temp.add("周姿会1");
temp.add("周姿会2");
Stream<String> stream = temp.stream();//获取ArrayList 的集合流
Stream<String> stringStream = stream.filter((name) -> name.length() > 3);//判断长度是否大于3 为true 留下 为false 删除
distinct方法
通过流所生成元素的hashcode()和equals()方法 去除重复元素
public class ChongClass {
public static void main(String[] args) {
List<Dog> temp=new ArrayList<>();
temp.add(new Dog(18,"华龙"));
temp.add(new Dog(18,"华龙"));
temp.add(new Dog(18,"华龙"));
temp.add(new Dog(18,"华龙"));
Stream<Dog> stream = temp.stream().distinct();
stream.forEach(System.out::println);//只有一条数据
}
}
class Dog{
private int age;
private String name;
@Override
public String toString() {
return "Dog{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dog dog = (Dog) o;
return age == dog.age &&
name.equals(dog.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dog() {
}
public Dog(int age, String name) {
this.age = age;
this.name = name;
}
}
map方法
map(Function f)–接收一个函数作为参数 将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素.
List<String> list = Arrays.asList("t", "a", "n");
Stream<String> stream1 = list.stream();
Stream<String> stringStream = stream1.map((s) -> {
return s.toUpperCase();
});//转换为大写
stringStream.forEach(System.out::println);
flatMap方法
flatMap(Function f)—接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流.
public static void main(String[] args) {
List<String> list = Arrays.asList("tan", "wei", "zhou");
Stream<String> stream = list.stream();
Stream<Stream<Character>> streamStream = stream.map(falgMapClass::fromStringToStrream);//使用map 不会将 子Stream 转换成 数据
streamStream.forEach((s)->{
s.forEach(System.out::println);
});
System.out.println("--");
//使用flatMap方法
Stream<Character> characterStream = list.stream().flatMap(falgMapClass::fromStringToStrream);//将子Stream转换成数据
characterStream.forEach(System.out::println);
}
//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStrream(String str){
ArrayList<Character> list=new ArrayList<>();
for(Character c:str.toCharArray()){
list.add(c);
}
return list.stream();
}
}
sorted方法
排序
下面代码以自然序排序一个list
list.stream().sorted()
自然序逆序元素,使用Comparator 提供的reverseOrder() 方法
list.stream().sorted(Comparator.reverseOrder())
使用Comparator 来排序一个list
list.stream().sorted(Comparator.comparing(Student::getAge))
把上面的元素逆序
list.stream().sorted(Comparator.comparing(Student::getAge).reversed())
例子
/**
* 1.排序
* sorted() 产生一个新流,其中按自然顺序排序
* sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序
*
* 2.自然排序和定制排序
* 2.1.自然排序:按照对象的Comparable-compareTo去排序
* 2.2.定制排序:按照自己定义的Comparator-compare去排序
*/
// 1.sorted():自然排序
public class PaiClass {
public static void main(String[] args) {
List<Integer> temp= Arrays.asList(5,6,1,2,8,9,3);
temp.stream().sorted().forEach(System.out::println);//1 2 3 4 5 6 8 9
System.out.println("---------");
temp.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);//9 8 6 5 4 3 2 1
List<Dog> temp1=new ArrayList<>();
temp1.add(new Dog(18,"华龙"));
temp1.add(new Dog(8,"华龙1"));
temp1.add(new Dog(21,"黑熊"));
temp1.add(new Dog(18,"华龙2"));
Stream<Dog> stream = temp1.stream();
Stream<Dog> stream = temp1.stream();
//temp1.stream().sorted().forEach(System.out::println);如果没有实现Comparable接口 按照自然排序 就会报错
stream.sorted((s,s1)->{//自定义排序
return s.getAge()-s1.getAge();
}).forEach(System.out::println);
}
}
终结方法
1.匹配与查找
public class TestZhongJie {
public static void main(String[] args) {
List<Dog> temp=new ArrayList<>();
temp.add(new Dog(18,"华龙"));
temp.add(new Dog(8,"华龙1"));
temp.add(new Dog(21,"黑熊"));
temp.add(new Dog(18,"华龙2"));
Stream<Dog> stream = temp.stream();
//allMatch(Predicate p)-检查是否匹配所有元素
//是否所有的狗的年龄都大于18
boolean b = stream.allMatch((e) -> {
return e.getAge() > 18;
});
System.out.println(b);//false
//anyMatch(Predicate p)-检查是否至少匹配一个元素
//是否存在狗的年龄大于20
boolean b1 = temp.stream().anyMatch((e) -> {
return e.getAge() > 20;
});
System.out.println(b1);//true
//noneMatch(Predicate p)-检查是否没有匹配的元素.练习:是否存在狗的名字"黑"
boolean b2 = temp.stream().noneMatch((e) -> {//如果有 以黑开头 的就是false 如果没有 就是 true
return e.getName().startsWith("黑");
});
System.out.println(b2);
//findFirst-返回第一个元素
Optional<Dog> first = temp.stream().findFirst();
System.out.println(first);
//finAny-返回当前流中的任意元素
Optional<Dog> any = temp.parallelStream().findAny();//使用循序流 老是 是 第一个
System.out.println(any);
//count-返回流中元素的总个数 介绍过了
//max(Comparator c)-返回流中最大值
//练习返回最高的年龄
// Optional<Dog> max = temp.stream().max((s, s1) -> {
// return Integer.compare(s.getAge(), s1.getAge());
// });返回的是一个对象
Optional<Integer> max1 = temp.stream().map(e->e.getAge()).max(Integer::compare);
System.out.println("-----"+max1);//输出21
//min(Comparator c)-返回流中最小值
//返回最小的年龄的狗
Optional<Dog> min = temp.stream().min((e, e1) -> {
return Integer.compare(e.getAge(), e1.getAge());
});
System.out.println(min);
//forEach 方法 介绍过了
}
}
详情代码
E:\后端修炼之路\jdk新特性\jdk\src\jdk\eight\TestZhongJie.java
2.归约
public class TestReduce {
public static void main(String[] args) {
//reduce(T identity,BinaryOperator)-可用将流中的元素反复结合起来,得到一个值,返回T
//练习1:计算1-10的自然数的和
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = integers.stream().reduce(0, Integer::sum);//0是起始值
System.out.println(reduce);
//reduce(BinaryOperator)-可以将流中元素反复结合起来,得到一个值,返回Optional<T>
//练习2:计算所有狗年龄的总和
List<Dog> temp=new ArrayList<>();
temp.add(new Dog(18,"华龙"));
temp.add(new Dog(8,"华龙1"));
temp.add(new Dog(21,"黑熊"));
temp.add(new Dog(18,"华龙2"));
Stream<Integer> integerStream = temp.stream().map((s) -> s.getAge());
Optional<Integer> reduce1 = integerStream.reduce((e, e1) -> {
return e + e1;
});
// Optional<Integer> reduce2=temp.stream().reduce((e,e1)->{
// return e.getAge()+e1.getAge();
// }); 这个 地方 不能这样 写 因为 规定 三个类型必须一样 Dog int int 是不合理的
}
}
3.收集
collect方法 其实就是将流转换为集合
下面的将流转换成集合 第一种与第二种 都是收集操作
将流转换成集合
1 使用 Collectors.toList 方法 ***
stream.collect(Collectors.toList());
ArrayList<String> temp=new ArrayList(); Stream<String> stringStream = temp.stream();//获取ArrayList 的集合流 ArrayList<String> temp1= (ArrayList<String>) stringStream.collect(Collectors.toList()); System.out.println(temp1);//返回一个ArrayList集合 Set<String> temp=new HashSet<>(); temp.add("22221"); Stream<String> stream = temp.stream(); Stream<String> stringStream = stream.filter((name) -> name.length() > 3); Set<String> collect = stringStream.collect(Collectors.toSet()); System.out.println(collect);//返回Set集合 Map<String,String> temp=new HashMap<>(); temp.put("1","2"); temp.put("11","22"); Set<Map.Entry<String, String>> entries = temp.entrySet(); Stream<Map.Entry<String, String>> stream = entries.stream(); Stream<Map.Entry<String, String>> entryStream = stream.filter(entry -> entry.getKey().equals("11"));//过滤掉 1:2 Map<String, String> collect = entryStream.collect(Collectors.toMap(name -> name.getKey(), name -> name.getValue())); System.out.println(collect);//返回一个map集合
2 使用 toCollection() 方法 **
stream.collect(Collectors.toCollection(ArrayList::new))
stream.collect(Collectors.toCollection(返回什么集合就写什么集合)) 参数是Supplier接口 但必须继承Collection
ArrayList<String> temp=new ArrayList(); temp.add("谭炜"); temp.add("周姿会"); temp.add("谭炜1"); temp.add("周姿会1"); temp.add("周姿会2"); Stream<String> stream = temp.stream();//获取ArrayList 的集合流 Stream<String> stringStream = stream.filter((name) -> name.length() > 3); // ArrayList<String> temp1= stringStream.collect(Collectors.toCollection(()->new ArrayList<>())); HashSet<String> set= stringStream.collect(Collectors.toCollection(()->new HashSet<>())); // System.out.println(temp1);//返回一个集合 System.out.println(set);
3 forEach() 方法 *
List newList = new ArrayList<>();
stream.forEach(newList::add);
stream.forEachOrdered(newList::add); //或者4 toArray() 方法
Integer[] arrayOfInteger = streamOfInteger.toArray(Integer[]::new);
List newList = Arrays.asList(arrayOfInteger);代码:
ArrayList<Integer> temp=new ArrayList(); temp.add(1); temp.add(2); temp.add(3); temp.add(4); temp.add(5); Stream<Integer> stream = temp.stream();//获取ArrayList 的集合流 Integer[] integers = stream.toArray(Integer[]::new);//此处只能传入方法引用 List<Integer> integers1 = Arrays.asList(integers);//转换为list集合 System.out.println(integers1);
最后注意的是Collection接口实现了forEach()方法
List<Integer> collect = integerStream.collect(Collectors.toList());
collect.forEach(System.out::println);
1.8中的新日期时间
新日期时间出现的背景
如果我们可以跟别人说:“我在1502643933071见面,别晚了!” 那么就再简单不过了,但是我们希望时间与昼夜和四季有关,于是事件就变复杂了,jdk 1.0中包含了一个java.util.Date类, 但是它的大多数方法已经在jdk1.1引入Calendar之后被弃用了,而Claendar并不比Date好多少,它们面临的问题是:
可变性:像日期和时间这样的类应该是不可变.
偏移性:Date中的年份是从1900开始的,而月份都是从0开始的
Date data=new Date(2020-1900,10-1,1) ;//定义2020年10月 1号
格式化:格式化只对Date有用,Calendar则不行.
此外,它们也不是线程安全的,不能处理闰秒等.
总结:对日期和时间的操作一直是java程序员最痛苦的地方之一
第三次引入的Apl是成功的,并且java 8中引入的java.time.ApI 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务.
java 8 吸收了 Joda-Time的精华 ,以一个新的开始为Java创建优秀的ApI.
新的java.time中包含了所有关于本地日期 (LocalDate),本地时间(LocalTime),本地日期时间(LocalDateTime),时区(ZoneDateTime)和持续时间(Duration)的类.历史悠久的Date类新增了toInstant()方法.用于把Date转换成新的表达形式.这些新增的本地化时间日期API大大简化日期时间和本地化的管理.
java-time 包含值对象的基础包
java-time-chrono 提供对不同的日历系统的访问
java.time.format-格式化和解析时间和日期
java.time.temporal 包括底层框架和扩展特性
java.time.zone 包含时区支持的类
使用时间类型
- LocalDate:本地日期,不包含具体时间 例如:2014-01-14 可以用来记录生日、纪念日、加盟日等。
- LocalTime:本地时间,不包含日期。
- LocalDateTime:组合了日期和时间,但不包含时差和时区信息。
- ZonedDateTime:最完整的日期时间,包含时区和相对UTC或格林威治的时差。
代码
public class MyDate {
public static void main(String[] args) {
LocalDate localDate=LocalDate.now();//获取当前时间
LocalDateTime localDateTime=LocalDateTime.now();
LocalTime localTime=LocalTime.now();
System.out.println(localDate);//显示 年 月 日
System.out.println(localDateTime);//显示 年 月 日 时 分 秒 ..
System.out.println(localTime);//显示 时 分 秒 ..
//自定义时间
LocalDate localDate1=LocalDate.of(2020,10,1);//不需要要偏移
System.out.println(localDate1);//2020-10-1
System.out.println("-------------------------");
//获取 时间 年 月等
LocalDateTime localDateTime1=LocalDateTime.now();
System.out.println(localDateTime1.getDayOfMonth());//返回这个月的第几天
System.out.println(localDateTime1.getDayOfWeek());//返回这个星期的星期几
System.out.println(localDateTime1.getMonth());// 返回当前月的英文
System.out.println(localDateTime1.getMonthValue());//返回int 类型的 当前月
System.out.println(localDateTime1.getMinute());//返回当前分钟 比如现在是13:09 返回 9
//体现了不可变性 如果是Calendar修改 修改的是当前时间对象 不会重新返回新对象
LocalDateTime localDateTime2 = localDateTime1.withDayOfMonth(10);//更改当前月
System.out.println(localDateTime2);
// 还比如修改 小时 年等 withXXX
//加减时间
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);//给月份加上 3个月 本来是4月 加上就是 7月
System.out.println(localDateTime3);
//加 小时 年 plusXXXX
LocalDateTime localDateTime4 = localDateTime.minusMonths(3);//减少3个月
System.out.println(localDateTime4+"111");
//减 小时 年 minusXXXX
//总结 LocalDate LocalDateTime LocalTime 都类似 唯一不同的是 操作的范围不一样 LocalDateTime 就是范围最大的 可以操作年 月 日 时 分 秒
}
}
Instant
所谓的 Instant 类代表的是某个时间(有点像 java.util.Date),它是精确到纳秒的(而不是象旧版本的Date精确到毫秒)。如果使用纳秒去表示一个时间则原来使用一位Long类型是不够的,需要占用更多一点的存储空间,实际上其内部是由两个Long字段组成,第一个部分保存的是自标准Java计算时代(就是1970年1月1日开始)到现在的秒数,第二部分保存的是纳秒数(永远不会超过999,999,999)。我们看下一个具体的例子:
public class MyDate2 {
public static void main(String[] args) {
Instant instant=Instant.now();//获取本初子午线的标准时间
System.out.println(instant);//输出的是子午线的时间 我们的时间在东八区 所以要偏移8个小时
//偏移时间
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));//返回偏移后的时间
System.out.println(offsetDateTime);
//返回1970年1月1日00:00:00到现在的时间毫秒数
System.out.println(instant.toEpochMilli());
//返回1970年1月1日00:00:00 基础上加上指定毫秒的 Instant对象
Instant instant1 = Instant.ofEpochMilli(1587017331831L);
System.out.println(instant1);//输出2020-04-16T06:08:51.831Z
}
}
Instant 是可比较的,这意味着可以对两个 Instant 进行比较。它提供了 isAfter() 和 isBefore() 两个方法进行比较 相关可以查看api
DateTimeFormatter
使用旧的Date
对象时,我们用SimpleDateFormat
进行格式化显示。使用新的LocalDateTime
或ZonedLocalDateTime
时,我们要进行格式化显示,就要使用DateTimeFormatter
。
和SimpleDateFormat
不同的是,DateTimeFormatter
不但是不变对象,它还是线程安全的。线程的概念我们会在后面涉及到。现在我们只需要记住:因为SimpleDateFormat
不是线程安全的,使用的时候,只能在方法内部创建新的局部变量。而DateTimeFormatter
可以只创建一个实例,到处引用。
代码:
public class MyDate3 {
/*
* 时间格式化:java.time.format.DateTimeFormatter
* 1.默认提供
* 2.自定义格式
* 格式化步骤:
* 1.新建一个DateTimeFormatter对象(有静态对象,也可以自己创建[自己创建是调用ofPattern方法])。
* 2.调用该对象的format方法,参数是需要格式化的LocalDateTime对象。
* 解析步骤:
* 1.新建一个时间的字符串对象。
* 2.新建一个DateTimeFormatter对象(有静态对象,也可以自己创建[自己创建是调用ofPattern方法])。
* 3.调用DateTimeFormatter对象的parse方法,进行解析,返回一个TemporalAccessor对象。
* 4.调用LocalDateTime的静态方法form,将TemporalAccessor转换成LocalDateTime对象。
*/
public static void main(String[] args) {
//方式一:预定义的标准格式.
DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTime localDateTime=LocalDateTime.now();
String str1=dateTimeFormatter.format(localDateTime);//格式化返回一个字符串
System.out.println(str1);//标准格式2020-04-16T15:02:44.9113166
//解析:字符串-->日期
TemporalAccessor parse = dateTimeFormatter.parse("2020-04-16T15:02:44.9113166");
LocalDateTime localDateTime1=LocalDateTime.from(parse);//转换成 LocalDateTime对象
System.out.println(localDateTime1);
//方式二:
//本地化相关的格式化 如:ofLocalizedDateTime()
// FormatStyle.MEDIUM FormatStyle.SHORT:适用于LocalDateTime
LocalDateTime localDateTime2=LocalDateTime.now();
DateTimeFormatter formatter= DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
String format = formatter.format(localDateTime2);
System.out.println(format);
TemporalAccessor parse1 = formatter.parse(format);
LocalDate from = LocalDate.from(parse1);//转换
//本地相关的格式化 如:ofLocalizedDate
//FormatStyle.SHORT FormatStyle.MEDIUM FormatStyle.FULL FormatStyle.LONG 适用于 LocalDate
LocalDate localDate=LocalDate.now();
DateTimeFormatter formatter1= DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
String format1 = formatter1.format(localDate);
System.out.println(format1);
//方式三 自定义 如:ofPattern("yyyy-MM-dd hh:mm:ss E");
DateTimeFormatter formatter3= DateTimeFormatter.ofPattern("yyyy-MM-dd");
String format2 = formatter3.format(LocalDateTime.now());
System.out.println(format2);
TemporalAccessor parse2 = formatter3.parse(format2);
LocalDate from1 = LocalDate.from(parse2);
System.out.println(from1);
}
}
详情代码查看E:\后端修炼之路\jdk新特性\jdk\src\jdk\eight\MyDate3.java
总结:最后的从字符串转换成 日期对象 其实都是一样步骤
1.8中的注解
可重复注解
**一、什么是重复注解
**允许在同一申明类型(类,属性,或方法)的多次使用同一个注解
@Repeatable
**二、一个简单的例子
**
java 8之前也有重复使用注解的解决方案,但可读性不是很好,比如下面的代码:
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseOldVersion {
@Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
public void doSomeThing(){
}
}
由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解,我们再来看看java 8里面的做法:
@Repeatable(Authorities.class)
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseNewVersion {
@Authority(role="Admin")
@Authority(role="Manager")
public void doSomeThing(){ }
}
其实不过就是找另一个注解来进行储存
但是需要注意的是结构需要一样 就比如 注解能够在方法上 属性上 那么扩展注解也必须要 而且生命周期也要一样 就比如 注解生命是运行时 那么扩展注解也是运行时
类型注解
在Java 8以前,注解只能在定义程序元素的时候使用。从Java 8开始,类型注解可以在任何使用类型的地方使用。例如如下位置 :
创建对象
类型转换
使用implements实现接口
使用throws 声明抛出异常
上面这些位置都会使用到类型,因此可以使用类型注解来修饰。
TYPE_PARAMETER
直译为类型变量,被该值所修饰,意味着声明的注解可以作用在泛型类,泛型接口,泛型方法上。
//声明注解
// 声明注解
@Target({ ElementType.TYPE_PARAMETER})
public @interface NotNull {}
//测试注解
public class TestNotNull<@NotNull T> {
public <@NotNull T> void testT(T x) {}
}
public interface A<@NotNull T> {}
TYPE_USE
ElementType.TYPE_USE(Use of a type) 能标注任何类型名称
// 声明类型注解
@Target(ElementType.TYPE_USE)
public @interface NotNull {}
//使用类型注解
@NotNull // 声明类使用类型注解,在继承和实现的时候都使用类型注解
public class TestNotNull<@NotNull T> extends @NotNull HashMap<String, String> implements @NotNull Serializable {
@NotNull
private T t;
// 声明成员变量使用类型注解
@NotNull
private String test;
// 方法中使用类型注解,抛异常使用类型注解
public static void main(@NotNull String[] args) throws @NotNull Exception {
// 声明局部变量使用类型注解
@NotNull
String string;
// 创建对象使用类型注解
string = new @NotNull String("小钻风");
// 强制类型转换使用类型注解
Object object = (@NotNull Object) string;
System.out.println(object);
}
@NotNull // 声明方法使用类型注解, 泛型使用类型注解
public String test(List<@NotNull String> lists) {
return "";
}
// @NotNull 返回值为空意味着没有类型返回,所以不能够使用类型注解
public void test2() {}
public <@NotNull T> void testT(@NotNull T xddf) {
}
}
1.8HashMap的优化
1.new HashMap();底层没有创建一个长度为16的数组
2.jdk 8底层的数组是:Node[] , 而非Entry[]
3.首次调用put()方法时 底层创建长度为16的数组
4.jdk7底层结构只有:数组+链表 . jdk 8 中底层结构 :数组+链表+红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8 且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储.
1.8针对Lambda的方法引用
此种写法是Java8 Lambda表达式
双冒号运算就是Java中的方法引用 method references
[方法引用]的格式是 类名::方法名
举例:
1.表达式:
person -> person.getName();
可以替换成:
Person::getName
2.表达式:
() -> new HashMap<>();
可以替换成:
HashMap::new
算冒号::为引用运算符,而它所在的表达式被称为方法引用.如果lamdba要表达的函数方案已经存在某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lamdba的替代者.
简单示例
代码:
public class MethodYin {
public static void main(String[] args) {
tempWrite("谭炜同志",(s)->{
System.out.println(s);
});//使用lamdba
tempWrite("谭炜同志",System.out::println);//参数可以省略 使用方法引用
}
public static void tempWrite(String s,temp temp){
temp.write(s);
}
}
interface temp{
void write(String s);
}
注:lambda中传递的参数一定是方法引用中的那个方法可以接受的类型否则会抛出异常
就拿上面的例子说 println 是可以接受 String类型的参数的
引用对象的中的成员方法
public class MethodYin2 {
public static void main(String[] args) {
//使用lamdba
write("tan",(s)->{
new MethodStringUp().StringUp(s);
});
//通过方法引用
write("tan",new MethodStringUp()::StringUp);
}
public static void write(String s,MethodInter methodInter){
methodInter.write(s);
Comparator<String> comparator=(s,s1)->{
return s.compareTo(s1);
};
int compare = comparator.compare("abc", "abs");
System.out.println(compare);
Comparator<String> stringComparator=comparator::compare;//引用上面已经实现了的方法
}
}
interface MethodInter{
void write(String s);
}
class MethodStringUp{
public void StringUp(String s){
System.out.println(s);
}
}
引用类中的静态方法
public class MethodYin3 {
public static void main(String[] args) {
unTempInt(-10,(i)->{
return Math.abs(i);
});//使用lamdba表达式
unTempInt(-10,Math::abs);//使用方法引用
}
public static int unTempInt(int i,unTemp temp){
return temp.AMethod(i);
}
}
interface unTemp{
int AMethod(int i);
}
使用super关键字引用父类的方法
public class MethodYin4 extends FatherMethod{
public static void main(String[] args) {
new MethodYin4().show();
}
public void write(){
System.out.println("我是子类的方法");
}
public void method(InterFather interFather){
interFather.InterWrite();
}
public void show(){
method(()->{
new FatherMethod().write();//调用父类方法 使用lambda
});
//使用方法引用
method(super::write);
}
}
class FatherMethod{
public void write(){
System.out.println("我是父类中的方法");
}
}
interface InterFather{
void InterWrite();
}
使用构造器引用
public class MethodYin5 {
public static void main(String[] args) {
writeStudent("谭炜",(name)->{
return new Student(name);
});//lamdba
writeStudent("周姿会",Student::new);//使用方法引用 自动调用 Student的有参数构造 然后返回 跟lamdbda的方式一样
MyStudent student=Student::new;//也可以这样写只不过没有name参数 是一个空的student对象
}
public static void writeStudent(String name,MyStudent myStudent){
Student student = myStudent.writeStudent(name);
System.out.println(student.getName());
}
}
interface MyStudent{
Student writeStudent(String name);
}
class Student{
private String name;
public Student(String name){
this.name=name;
}
public Student(){};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用数组引用
public class MethodYin6 {
public static void main(String[] args) {
GetInt(10,(len)->{
return new int[len];
});//使用lambda方式
GetInt(10,int[]::new);//使用方法引用 其实跟lambda是一样的原理
}
public static int[] GetInt(int len,MethodFang methodFang){
return methodFang.write(len);
}
}
interface MethodFang{
int[] write(int len);
}
原理(自己理解)
原理 拿 此例子 来讲
首先将 -10传入 unTempInt方法 然后将 -10传入 AMethod方法 然后 进行 Math::abs 操作 如果说接口方法有返回值 就 会 这样重写 retrun Math.abs(-10); 如果说没有返回值 就会 Math.abs(10); 简单说就是自动适应有无返回值
public static int unTempInt(int i,unTemp temp){
return temp.AMethod(i);
}
unTempInt(-10,Math::abs);
interface unTemp{
int AMethod(int i);
}
1.8中的Optional类
Optional基本介绍
从 Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都非常了解的异常。
本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional 的意义显然不止于此。
我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
在这个小示例中,如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
你看到了,这很容易就变得冗长,难以维护。
为了简化这个过程,我们来看看用 Optional 类是怎么做的。从创建和验证实例,到使用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证 Optional 奇迹的时刻。
创建Optional实例
public static void main(String[] args) {
Optional<Object> empty = Optional.empty();//创建一个空的Optional
Optional<String> s = Optional.of(new String("1"));//创建一个Optional实例,参数必须非空
// Optional.of(null);抛异常
Optional<Object> o = Optional.ofNullable(null);//创建一个实例 参数可以为null
}
Optional方法
方法 | 说明 |
---|---|
OptionalflatMap(Function<? super T,Optional> mapper) | 如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional |
T get() | 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException |
void ifPresent(Consumer<? super T> consumer) | 如果值存在则使用该值调用 consumer , 否则不做任何事情。 |
boolean isPresent() | 如果值存在则方法会返回true,否则返回 false。 |
Optionalmap(Function<? super T,? extends U> mapper) | 如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。 |
T orElse(T other) | 如果存在该值,返回值, 否则返回 other。 |
T orElseGet(Supplier<? extends T> other) | 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。 |
T orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常 |
Optional filter(Predicate<? super predicate) | 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。 |
例子
import java.util.Optional;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8Tester = new Java8Tester();
Integer value1 = null;
Integer value2 = new Integer(10);
// Optional.ofNullable - 允许传递为 null 参数
Optional<Integer> a = Optional.ofNullable(value1);
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
Optional<Integer> b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b){
// Optional.isPresent - 判断值是否存在
System.out.println("第一个参数值存在: " + a.isPresent());
System.out.println("第二个参数值存在: " + b.isPresent());
// Optional.orElse - 如果值存在,返回它,否则返回默认值
Integer value1 = a.orElse(new Integer(0));
//Optional.get - 获取值,值需要存在
Integer value2 = b.get();
return value1 + value2;
}
}
例子
public class TestOptional {
public static void main(String[] args) {
//getDogName(new Boy(new MyDog("周姿会")));
ifGetDogName(null);
}
public static void getDogName(Boy boy){
//boy.getMyDog().getName();
String name="notNull";
if(boy!=null){
MyDog myDog = boy.getMyDog();
if(myDog!=null){
name = myDog.getName();
}
}
System.out.println(name);
}
public static void ifGetDogName(Boy boy){
Optional<Boy> boy1 = Optional.ofNullable(boy);//创建一个实例
Boy orElse = boy1.orElse(new Boy(new MyDog("周姿会2号")));//如果不为null 就返回本身的值 如果是null 就拿参数里面的值
MyDog myDog = orElse.getMyDog();
Optional<MyDog> myDog1 = Optional.ofNullable(myDog);
MyDog dog = myDog1.orElse(new MyDog("周姿会3号"));//如果不为null 就返回本身的值 如果是null 就拿参数里面的值
System.out.println(dog.getName());
}
}
class Boy{
private MyDog myDog;
public MyDog getMyDog() {
return myDog;
}
public void setMyDog(MyDog myDog) {
this.myDog = myDog;
}
public Boy() {
}
public Boy(MyDog myDog) {
this.myDog = myDog;
}
}
class MyDog{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MyDog() {
}
public MyDog(String name) {
this.name = name;
}
}
详情代码
E:\后端修炼之路\jdk新特性\jdk\src\jdk\eight\TestOptional.java
返回原来的值 如果不包含值就抛出异常
Optional<String> s = Optional.ofNullable(new String("111"));
String s1 = s.get();
1.8中Nashorn简单了解
Nashorn 一个 javascript 引擎。
从JDK 1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的 invokedynamic,将JavaScript编译成Java字节码。
与先前的Rhino实现相比,这带来了2到10倍的性能提升。
首先创建一个JavaScript文件名为fun.js
function f(){
return 1;
};
print(f()+1);
然后在JavaScript当前文件下 指向cmd命令 也可以cd 切换到 JavaScript文件的目录 然后执行jjs命令
jjs fun.js
输出2
不过jjs工具计划从未来的JDK版本中删除
静态代理与动态代理
代理是英文 Proxy 翻译过来的。我们在生活中见到过的代理,大概最常见的就是朋友圈中卖面膜的同学了。
她们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人
按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。
所以,代理就有一种中间人的味道。
接下来,我们说说软件中的代理模式。
静态代理
我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。
现在用代码来进行模拟。
首先得有一个接口,通用的接口是代理模式实现的基础。
package com.frank.test;
public interface Movie {
void play();
}
然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。
package com.frank.test;
public class RealMovie implements Movie {
@Override
public void play() {
// TODO Auto-generated method stub
System.out.println("您正在观看电影 《肖申克的救赎》");
}
}
这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。那么 Proxy 代理呢?
package com.frank.test;
public class Cinema implements Movie {
RealMovie movie;
public Cinema(RealMovie movie) {
super();
this.movie = movie;
}
@Override
public void play() {
guanggao(true);
movie.play();
guanggao(false);
}
public void guanggao(boolean isStart){
if ( isStart ) {
System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
} else {
System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
}
}
}
Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,我们编写测试代码。
package com.frank.test;
public class ProxyTest {
public static void main(String[] args) {
RealMovie realmovie = new RealMovie();
Movie movie = new Cinema(realmovie);
movie.play();
}
}
然后观察结果:
电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
您正在观看电影 《肖申克的救赎》
电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。
静态代理 的缺点
1.代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展
2.每一个代理类只能为一个借口服务,这样一来程序开发中必然产生过多的代理
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
基于接口的动态代理
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
package jdk.eight;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TrendsClass {
public static void main(String[] args) {
SuperMan superMan=new SuperMan();
Human method = (Human) getMethod(superMan);//创建代理类对象
System.out.println(method.dream());//当执行代理类的方法时 就会相应的执行被代理类的方法
method.eat("辣条");
}
public static Object getMethod(Object o){
//第一个参数是 类的加载器 也就是 被代理类的加载器 第二个参数 获取 被代理类的全部接口 第三个参数 绑定方法
Object o1 = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new ProxyClass(o));
return o1;
}
}
class ProxyClass implements InvocationHandler {
private Object o;//被代理对象
public ProxyClass(){
}
public ProxyClass(Object o) {
this.o = o;
}
//如果执行代理类中的方法 就会自动执行下面的invoke方法
@Override//proxy 是代理的对象 method是被代理类的方法 args被代理类的方法参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
Object invoke = method.invoke(o, args);
return invoke;
}
}
interface Human{
public void eat(String name);
public String dream();
}
//被代理类
class SuperMan implements Human{
@Override
public void eat(String name) {
System.out.println("I like eat"+name);
}
@Override
public String dream() {
return "我要成为IT大佬";
}
}
详情代码E:\后端修炼之路\jdk新特性\jdk\src\jdk\eight\TrendsClass.java
基于子类的动态代理
首先引入jar
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
不过此时我们并没有使用maven来进行jar包管理
所以出现了问题
很多java字节码操作和分析的第三方类库都引用了asm.jar文件,由于工程不是Maven管理的,无法解决以来传递问题,所以要手动引入asm.jar文件。把asm.jar文件添加到项目路径类,运行,然后就正常了。
生产者
package proxy;
/**
* 一个生产者
*/
public class Producer {
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
消费者
package proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行北地阿里对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.5f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
详情代码
E:\后端修炼之路\jdk新特性\jdk\src\proxy
1.9新特性之目录结构
包含jdk8及以前的jdk版本,所有目录结构以及目录含义如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJCqVYYf-1622431367500)(\img\jdk8.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhBanW41-1622431367503)(\img\jdk81.png)]
jdk9之后,目录结构发生变化如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FeTeAovG-1622431367505)(\img\jdk9.png)]
1.9新特性之模块化
一个大型的项目,比如淘宝商城等,都会包含多个模块,比如订单模块,前台模块,后台管理模块,广告位模块,会员模块…等等,各个模块之间会相互调用,不过这种情况下会很少,只针对特殊情况,如果一个项目有30个模块系统进行开发,但是只要某个单独模块运行时,都会带动所有的模块,这样对于jvm来说在内存和性能上会很低,所以,java9提供了这一个特性,某一个模块运行的时候,jvm只会启动和它有依赖的模块,并不会加载所有的模块到内存中,这样性能大大的提高了。写法上如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7VmLgxLp-1622431367507)(img/java9模块.png)]
一个项目中的两个模块,模块之间通过module-info.java来关联,在src 下IDEA编辑器右键创建module-info.java
这个两个模块java9Demo和java9Test中,java9demo编写一个实体类Person,在java9Test调用这样一个过程
这个是java9Demo 将 java9Test 模块需要的文件导出 exports 把它所在的包导出
module java9Demo{
exports com.mdxl.layer_cj.entity;
}
然后在java9Test模块中创建一个package-info.java,引入java9Demo模块导出包名
module java9Test{
requires java9Demo;
}
这样就可以直接在java9Test中引入Person实体类了,这只是一个简单的例子。exports 控制着那些包可以被模块访问,所以不被导出的包不能被其他模块访问
package-info.java值只是包说明文件而已
参考E:\后端修炼之路\jdk新特性\modularity
1.9JShell工具
怎么理解,怎么用呢?这个只是针对于java9来说,相当于cmd工具,你可以和cmd一样,直接写方法等等,不过我认为只是适用于初学者做一些最简单的运算和写一些方法:
在cmd中打开这个工具:
输入jshell
然后进入工具后可以进行一些简单的java操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJ6AZngJ-1622431367508)(img/javajshell.png)]
也可以定义类与方法 写法一样
可以使用/open调用 但是 文件要求是命令行形式的
没有受检异常 就是没有编译时异常
1.9砖石操作符升级
在1.9之前这样写是会报错
Comparable<Object> stringComparable=new Comparable<>() {
@Override
public int compareTo(Object o) {
return 0;
}
};
所以在1.9之前只能这样写
Comparable<Object> stringComparable=new Comparable<Object>() {//必须加上 Object
@Override
public int compareTo(Object o) {
return 0;
}
};
1.9之后就可以有此写法new Comparable<>()
1.9String底层存储结构的变更
java8之前 String的底层结构类型都是 char[] ,但是java9 就替换成 byte[] 这样来讲,更节省了空间和提高了性能
比如String a=“a”;这里其实一个byte就能存储下 按照Ios-8859-1/Latin-1取
当然1.9的String会动态的调整 就比如 String a=“谭” 在底层还是byte数组 只不过存储2个 按照utf-16取 这样就不会乱码
同理,StringBuilder StringBuffer也更换了底层数据结构
1.9InputStream的新方法:tranferTo()
public class InputNewMethod {
public static void main(String[] args) {
try (
InputStream inputStream=new FileInputStream("C:\\Users\\asus\\Desktop\\小会同志.txt");
OutputStream outputStream=new FileOutputStream("C:\\Users\\asus\\Desktop\\小会同志1.txt");
){
inputStream.transferTo(outputStream);//把输入流的所有数据直接自动地复制到输出流中
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.9中新增的Stream方法
takeWhile方法
1.takeWhile():用于从 Stream 中获取一部分数据,接收一个Predicate 来进行选择。在有序的Stream 中,takeWhile 返回从开头开始的尽量多的元素。
List<Integer> integers = Arrays.asList(23, 43, 45, 55, 61, 54, 32, 2, 45, 89, 7);
integers.stream().takeWhile(x->x<60).forEach(System.out::println);//23, 43, 45, 55 到了61发现 不满足条件返回61之前的元素
dropWhile方法
dropWhile 的行为与takeWhile 相反,返回剩余的元素
List<Integer> integers = Arrays.asList(23, 43, 45, 55, 61, 54, 32, 2, 45, 89, 7);
integers.stream().dropWhile(x->x<60).forEach(System.out::println);//61, 54, 32, 2, 45, 89, 7
ofNullable方法
ofNullable():Java 8 中 Stream 不能完全为null,否则会报空指针异常。而 Java 9 中的ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。
//在1.8中的of方法
Stream<Integer> stream=Stream.of(1,2,3,4,null);//of()参数中的多个元素,可以包含null值
//stream.forEach(System.out::println);
System.out.println(stream.count());//输出5
// Stream<Object> objectStream = Stream.of(null);//of()参数不能储存单个null值 但是可以储存2个或者多个
//objectStream.forEach(System.out::println);
//1.9解决of不能储存单个的null的问题
Integer i=null;
Stream<Integer> i1 = Stream.ofNullable(i);//输出0 会自动过滤掉 null
System.out.println(i1.count());
iterator方法
重载的使用
Stream.iterate(0,x->x+1).limit(10).forEach(System.out::println);//以前1.8的时候创建无限流的时候 只能使用过滤的方法 来取前10个值
//1.9解决了此问题可以使用iterate的重载方法
Stream.iterate(0,x->x<10,x->x+1).forEach(System.out::println);//加了一个判断
详情代码E:\后端修炼之路\jdk新特性\jdk\src\jdk\nine\StreamNewMethod.java
1.9 Optional的stream
代码
List<String> list=new ArrayList<>();
list.add("t");
list.add("j");
list.add("t");
Optional<List<String>> list1 = Optional.ofNullable(list);
Stream<List<String>> stream1 = list1.stream();
Stream<String> stringStream = stream1.flatMap(x -> x.stream());//Optional此时是一个流了 不是只能储存一个数据的集合 所以flatMap也能将子流的数据进行合并
stringStream.forEach(System.out::println);
// Stream<Stream<String>> streamStream = stream1.map(x -> x.stream());
// streamStream.forEach(x->x.forEach(System.out::println));
Java10 局部类型推断
为什么会出现局部类型推断?
就比如
string s="11";
List<String> list = Arrays.asList("a", "b", "c");
LinkedHashSet<Integer> set=new LinkedHashSet<>();
是否感觉到string 跟list 与LinkedHashSet有些多余
作为java开发者,在声明一个变量时,我们总是习惯了敲2此变量类型,第一次用于声明变量类型,第二次用于构造器
还比如
返回值类型含复杂泛型结构
Stream<List<String>> stream1 = list1.stream();
我们也经常声明一种变量,他只会被使用一次,而且是用在下一行代码中,比如:
Stream<List<String>> stream1 = list1.stream();
stream1.forEach(System.out::println);
太多类型的声明只会分散注意力,不会带来额外的好处
使用var
既然叫局部变量类型推断,以只能用在局部变量中
var javastack = "javastack";//等价于String javastack = "javastack";
//1、字面量定义局部变量
private static void testVar() {
var javastack = "javastack";
System.out.println(javastack);
}
//2.接收方法返回值定义局部变量
private static void testMethod() {
var javastack = getJavastack();
System.out.println(javastack);
}
public static String getJavastack() {
return "javastack";
}
//3、循环中定义局部变量
private static void testLoop() {
for (var i = 0; i < 3; i++) {
for (var m = 10; m < 15; m++) {
System.out.println(i + m);
}
}
}
//4、泛型结合局部变量
private static void testGeneric() {
// 表达式1
List<String> list1 = new ArrayList<>();
list1.add("javastack");
// 表达式2
var list2 = new ArrayList<>();
list2.add(2018);
// 表达式3
var list3 = new ArrayList<String>();
list3.add("javastack");
}
表达式1后面 <>
里面 jdk 1.7+开始是不用带具体类型的,在接口中指明就行了。
表达式2中如果使用 var
的话,<>
里面默认会是 Object
的,所以可以添加任意类型。
表达式3中在 <>
强制使用了 String 来指定泛型。
局部变量类型推断不能用在以下场景
1、类成员变量类型
// 编译报错
private var javastack = "Java技术栈";
为什么?如果var 可以是 类成员变量那么它就可以有默认值 因为比如说 int String 都会有默认值 而编译器无法确定 var的默认值是什么
2、方法返回类型
/**
* 编译报错
* @return
*/
public static var getJavastack(){
return "Java技术栈";
}
无法确定返回值类型
3、Lambda 表达式
private static void testLambda() {
Runnable runnable = () -> System.out.println("javastack");
// 编译报错
// var runnable = () -> System.out.println("javastack");
}
函数式接口有很多 如果不在左边指明 谁知道
其他
public class VarMyClass {
public static void main(String[] args) {
var i=new int[]{1,2,3,4};//可以通过 new int[] 推断类型
//var i1={1,2,3,4};//错误无法推断
//方法引用
Consumer consumer=System.out::println;
// var consumer=System.out::println; 谁知道你重写的哪个接口
// var t;//var 本身就是推断的 如果没有类型让他推断就会报错
//var t=null;
// try {
//
// }catch (var e){//无法推断异常类型报错
// e.printStackTrace();
// }
}
// public void method(var i){//没有类型推断 报错 构造方法同理
//
// }
}
工作原理
在处理var时,编译器先是查看表示右边部分,并根据右边变量值的类型进行推断,作为左边变量的类型,然后将该类型写入字节码当中.
总结 如果根据var 推断 自己先推断一下 如果可以那么var 也可以推断
Java10集合新增方法
public class CopyOfNew {
//集合list set map中新增的copyOf(),用于创建一个只读的集合
public static void main(String[] args) {
//示例1:
var list1= List.of("java","python","c");
var list = List.copyOf(list1);
System.out.println(list==list1);//true
//list.add("1");
//示例2:
var list2=new ArrayList<String>();
var copy=List.copyOf(list2);
System.out.println(list2==copy);//false
}
//示例1和2代码基本一致 但是结果不相同
//copyOf(Xxx coll)方法 如果参数coll本身就是一个只读集合,则copyOf返回值即为当前的coll
//如果不是一个只读集合,则copyOf返回一个新的集合 这个集合是只读的
}
Java11String新增方法
repeat()
repeat()重复字符串内容。
返回一个字符串,其内容是字符串重复n次后的结果。
String temp="神经病".repeat(2);
System.out.println(temp);//神经病神经病
如果对空字符串或长度为0的字符串做操作,会返回空字符串。
strip()
strip()将字符串头和尾的空格去除后的字符串。
String temp1=" 神经病abs ".strip()+"111";
System.out.println(temp1);//神经病abs111
还提供了stripLeading()和stripTrailing(),可以分别去掉头部或尾部的空格。
strip()和trim()的区别
strip系列方法判断是否空格基于Character.isWhiteSpace()。换句话说,它关注的是Unicodewhitespace字符。
trim方法判断的标准是小于等于Unicode空格字符(U+0020)。如果对上面的示例采用trim():
String temp2="\u2005谭".strip();
System.out.println(temp2);//谭
String temp3="\u2005谭".trim();
System.out.println(temp3);// 谭
可以看到,trim()可以去掉普通的空白。但是trim()方法不能识别Unicode的空白字符,不认为’\u2005’是一个空白字符。
isBlank()
如果字符串为空或只包含空格,则返回true。否则返回false。
String temp4="\n\t\u2005 ";
System.out.println(temp4.isBlank());//true
isBlank也会识别Unicode空白字符。
lines()
从字符串返回按行分割的Stream。
String temp5="\n111\n222\r ";
System.out.println(temp5.lines().count());//输出4
行分割福包括:\n \r \r\n。
stream包含了按顺序分割的行,行分隔符被移除了。
这个方法会类似split(),但性能更好。
Java11 Optional类新增方法补充 java 9 java10
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W6st1Si9-1622431367509)(img/op新.png)]
public static void main(String[] args) {
Optional<Object> empty = Optional.empty();
System.out.println(empty.isEmpty());//判断内部的value是否存在 true
System.out.println(empty.isPresent());//判断内部的value是否为空 false
Optional<String> s = Optional.of("1");
//orElseThrow():value非空,返回value:否则报异常NoSuchElementException
String s1 = s.orElseThrow();
System.out.println(s1);//1
Optional<String> h = Optional.of("H");
//or:value非空,返回对应的Optional:value为空,返回形参封装的Optional
Optional<String> or = h.or(() -> Optional.of("1"));
System.out.println(or);//H
}
Java11局部变量类型推断的升级
//错误的形式:必须要有类型 可以加上var
// Consumer<String> con1=(@Deprecated t)->System.out.println(t);
//正确的形式:
Consumer<String> con1=(@Deprecated var t)->System.out.println(t);//在java11之前此写法是错误的
Java11 HttpClient
java.net.http.HttpClient 是 jdk11 中正式启用的一个 http 工具类(其实早在 jdk9 的时候就已经存在了,只是处于孵化期),官方寓意为想要取代 HttpURLConnection 和 Apache HttpClient 等比较古老的开发工具。
新增的 HttpClient 截止到目前(2019年3月)为止其实网络资料还比较少,笔者只是根据一些博文和官方 Demo 自己摸索了一下,做了下总结。
由于是 jdk11 中才正式使用的工具类,距离开发者还很遥远,所以对于源码笔者暂不打算深挖,浅浅的理解怎么使用就行
现有的HttpURLConnection API存在许多问题:
基本的URLConnection被设计为支持多种协议,很多协议已经过时不用了。该API早于HTTP/1.1发布,而且过于抽象。使用困难,包含很多没有文档说明的行为。只支持阻塞模式。维护困难。
HTTP Client API的主要类包括:
java.net.http.HttpClient
java.net.http.HttpRequest
java.net.http.HttpResponse
java.net.http.WebSocket
HTTP Client API特性:
对大多数场景提供简单易用的阻塞模型。
通过异步机制支持事件通知。
完整支持HTTP协议的特性。
易于建立WebSocket握手。
支持HTTP/2,包括协议升级(Upgrade)和服务端推送(server push)。
支持 HTTPS/TLS。
和现有的其他实现类库相比,性能相当或有提升,内存占用少
java11的简化的编译运行程序
增强java启动器支持运行单个java源代码文件的程序.
注意点 :
- 执行源文件中的第一个类, 第一个类必须包含主方法
- 并且不可以使用别源文件中的自定义类, 本文件中的自定义类是可以使用的.
一个命令编译运行源代码
看下面的代码。
// 编译
javac Javastack.java
// 运行
java Javastack
在我们的认知里面,要运行一个 Java 源代码必须先编译,再运行,两步执行动作。而在未来的 Java 11 版本中,通过一个 java 命令就直接搞定了,如以下所示。
java Javastack.java