一、JDK8 新特性
(1)lambda表达式
lambda表达式是一种特殊的匿名内部类,允许把函数作为一个方法的参数传递。
基本语法:<函数式接口><变量名> = (参数1,参数2...)->{
//方法体 };
举例:使用lambda表达式创建线程:
//lambda表达式
Runnable task03 = ()-> {
System.out.println("这是使用Lambda表达式完成的");
};
Thread t3=new Thread(task03);
t3.start();
(2)函数式接口
如果一个接口只有一个抽象方法,则该接口被称之为函数式接口。函数式接口可以使用lambda表达式。使用@FunctionalInterface注解可以检测接口是否为函数式接口。
常见的函数式接口有四种:Consumer、Supplier、Function、Predicate
Consumer<T>
有参数,无返回值。
public class Test {
public static void main(String[] args) {
Consumer<Double> c= t->{
System.out.println("洗脚花费了:"+t);
};
fun01(c,200);
}
public static void fun01(Consumer<Double> consumer,double money){
consumer.accept(money);
}
}
Supplier<T>
无参,想有返回结果的函数式接口T get();
public class Test02 {
public static void main(String[] args) {
//Supplier<Integer> supplier=()->new Random().nextInt(10);
fun(()->new Random().nextInt(10));
}
public static void fun(Supplier<Integer> supplier){
Integer result = supplier.get();
System.out.println("内容为:"+result);
}
}
Function<T,R>
有参,有返回 值。
T: 参数类型的泛型
R: 函数返回结果的泛型
public class Test03 {
public static void main(String[] args) {
fun((t)->{
return t.toUpperCase();
},"hello world");
}
//传入一个字符串,返回字符串的长度。
public static void fun(Function<String,String> function,String msg){
String s = function.apply(msg);
System.out.println("结果为:"+s);
}
}
Predicated<T>
当传入一个参数时,需要对该参数进行判断时,则需要这种函数。
public class Test04 {
public static void main(String[] args) {
fun(n->{
return n.length()>3?true:false;
},"栗毅");
}
public static void fun(Predicate<String> predicate,String name ){
boolean b = predicate.test(name);
System.out.println("该名称的长度是否长啊:"+b);
}
}
(3)Stream流
对集合的一些操作可以交给stream流进行,对比传统的集合操作模式,stream流可以直接将上一轮的输出作为下一轮的输入来使用,代码会变得更加的简洁。
stream流中常用的api
- 遍历 foreach
- 匹配 find、match
- 聚合 max、min、count
- 筛选 filter
- 映射 map
- 收集 collect
- 排序 sorted
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪", 18, "中国", 'F'));
personList.add(new Person("Tom", 24, "美国", 'M'));
personList.add(new Person("Harley", 22, "英国", 'F'));
personList.add(new Person("向天笑", 20, "中国", 'M'));
personList.add(new Person("李康", 22, "中国", 'M'));
personList.add(new Person("小梅", 20, "中国", 'F'));
personList.add(new Person("何雪", 21, "中国", 'F'));
personList.add(new Person("李康", 22, "中国", 'M'));
//对流中元素排序
personList.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.forEach(System.out::println);
//集合中每个元素只要名.map--->原来流中每个元素转换为另一种格式。
personList.stream()
.map(item -> {
Map<String, Object> m = new HashMap<>();
m.put("name", item.getName());
m.put("age", item.getAge());
return m;
})
.forEach(System.out::println);
}
(4) 新增日期时间类
LocalDate: 表示日期类。yyyy-MM-dd
LocalTime: 表示时间类。 HH:mm:ss
LocalDateTime: 表示日期时间类 yyyy-MM-dd t HH:mm:ss sss
DatetimeFormatter:日期时间格式转换类。
Instant: 时间戳类。
public class Test {
public static void main(String[] args) {
LocalDate now = LocalDate.now(); //获取当前日期
LocalDate date = LocalDate.of(2022, 8, 23);//指定日期
LocalTime now1 = LocalTime.now();//当前时间
LocalTime of = LocalTime.of(17, 30, 20, 600);
LocalDateTime now2 = LocalDateTime.now();//获取当前日期时间
LocalDateTime now3 = LocalDateTime.of(2022,6,20,17,45,20);
Duration between = Duration.between(now2, now3);
System.out.println(between.toHours());
DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate parse = LocalDate.parse("1999-12-12", dateTimeFormatter);//把字符串转换为日期格式
String format = parse.format(dateTimeFormatter);
}
}
二、Java集合
Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collection
接口,主要用于存放单一元素;另一个是 Map
接口,主要用于存放键值对。对于Collection
接口,下面又有三个主要的子接口:List
、Set
和 Queue
。
List, Set, Map 的区别
List
: 存储的元素是有序的、可重复的。Set
: 存储的元素不可重复的。Map
: 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
ArrayList的扩容机制
ArrayList 是一个数组结构的存储容器 ,默认情况下 ,数组的长度是 10.当然我们也可以在构建 ArrayList 对象的时候自己指定初始长度。随着在程序里面不断的往 ArrayList 中添加数据 ,当添加的数据达到 10 个的时候ArrayList 就没有多余容量可以存储后续的数据。这个时候 ArrayList 会自动触发扩容。 扩容的具体流程很简单
-
首先 ,创建一个新的数组 ,这个新数组的长度是原来数组长度的 1.5 倍。
-
然后使用 Arrays.copyOf 方法把老数组里面的数据拷贝到新的数组里面扩容完成后再把当前要添加的元素加入到新的数组里面 ,从而完成动态扩容的过程。
HashMap底层实现
JDK1.8 之前 HashMap
底层是 数组和链表 结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashcode
得到 hash 值,然后通过 (n - 1) & hash
判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
Hashmap扩容机制
① 创建时如果不指定容量初始值,HashMap
默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值, HashMap
会将其扩充为 2 的幂次方大小(HashMap
中的tableSizeFor()
方法保证)。也就是说 HashMap
总是使用 2 的幂作为哈希表的大小,
HashMap常见的遍历方式
-
使用迭代器(Iterator)EntrySet 的方式进行遍历;
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
-
使用迭代器(Iterator)KeySet 的方式进行遍历;
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key);
System.out.println(map.get(key));
}
-
使用 For Each EntrySet 的方式进行遍历;
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
-
使用 For Each KeySet 的方式进行遍历;
for (Integer key : map.keySet()) {
System.out.println(key);
System.out.println(map.get(key));
}
-
使用 Lambda 表达式的方式进行遍历;
map.forEach((key, value) -> {
System.out.println(key);
System.out.println(value);
});
-
使用 Streams API 单线程的方式进行遍历;
map.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
-
使用 Streams API 多线程的方式进行遍历。
map.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
三、反射
-
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
-
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
-
反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
反射的优缺点:
1、优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、缺点:
(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
class对象的获取方法:
public class TestMain {
public static void main(String[] args) {
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());
//第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("com.cn.test.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
通过反射生成对象的方法
(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class;
Object str = c.newInstance();
(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器构造类的实例。
//获取String的Class对象
Class<?> str = String.class;
//通过Class对象获取指定的Constructor构造器对象
Constructor constructor=c.getConstructor(String.class);
//根据构造器创建实例:
Object obj = constructor.newInstance(“hello reflection”);
通过反射获取构造方法:
public Constructor[] getConstructors(); // 所有"公有的"构造方法
public Constructor[] getDeclaredConstructors() // 获取所有的构造方法(包括私有、受保护、默认、公有)
通过反射获取成员变量
1).Field[] getFields():获取所有的"公有字段" *
2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
设置字段的值: * Field --> public void set(Object obj,Object value):
* 参数说明: * 1.obj:要设置的字段所在的对象; * 2.value:要为字段设置的值;
通过反射获取成员方法
public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类) * public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
调用方法: * Method --> public Object invoke(Object obj,Object... args):* 参数说明: * obj : 要调用方法的对象; * args: 调用方式时所传递的实参;
四、动态代理
(1)JDK动态代理
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
1.实现InvocationHandler接口,重写invoke()
2.使用Proxy.newProxyInstance()产生代理对象
3.被代理的对象必须要实现接口
1.设计一个接口UserService
,以及对应的实现类。
public interface UserService {
void hello();
}
public class UserServiceImpl implements UserService{
@Override
public void hello() {
System.out.println("****************hello*******************");
}
}
2.定义一个InvocationHandler
,构造函数中需要传入被代理的对象:
public class MyInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("==========before===========");
// 执行目标方法
Object res = method.invoke(target, args);
System.out.println("==========after===========");
return res;
}
// 获取目标对象的代理对象
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
}
}
3.Test
类测试:
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(userService);
// 代理对象
UserService proxy = (UserService) handler.getProxy();
// 通过代理对象执行对应函数
proxy.hello();
}
(2)Cglib动态代理
CGLib 必须依赖于CGLib的类库,需要满足以下要求:
1.实现MethodInterceptor接口,重写intercept()
2.使用Enhancer对象.create()产生代理对象
1.自定义一个拦截器,需要实现MethodInterceptor
(继承了Callback
接口):
public class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("==========before===========");
Object res = methodProxy.invokeSuper(obj, args);
System.out.println("==========after===========");
return res;
}
}
2.自定义一个类UserProcess
public class UserProcess {
public void hello(){
System.out.println("****************hello*******************");
}
}
3.Test
类测试:
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserProcess.class);
enhancer.setCallback(new MethodInterceptorImpl());
UserProcess o = (UserProcess) enhancer.create();
o.hello();
System.out.println(o);
}
}
二者的区别:
- jdk代理只能对实现了接口的类进行代理,而cglib代理可以对普通类进行代理;
- jdk代理是通过反射的方式来实现动态代理,而cglib则是通过为目标类生成一个子类的方式来实现动态代理;
- 由于cglib代理是为目标类生成了一个子类,并对父类方法进行增强,所以目标类不能用final修饰;
五、自定义注解
Annotation型定义为@interface
元注解:@Retention @Target @
@Target 表示该注解目标,可能的 ElemenetType 参数包括:
ElemenetType.CONSTRUCTOR 构造器声明
ElemenetType.FIELD 域声明(包括 enum 实例)
ElemenetType.LOCAL_VARIABLE 局部变量声明
ElemenetType.METHOD 方法声明
ElemenetType.PACKAGE 包声明
ElemenetType.PARAMETER 参数声明
ElemenetType.TYPE 类,接口(包括注解类型)或enum声明
@Retention 表示该注解的生命周期,可选的 RetentionPolicy 参数包括
RetentionPolicy.SOURCE 注解将被编译器丢弃
RetentionPolicy.CLASS 注解在class文件中可用,但会被JVM丢弃
RetentionPolicy.RUNTIME JVM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Documented 指示将此注解包含在 javadoc 中
@Inherited 指示允许子类继承父类中的注解