Java 学习日记(4)
反射
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期间借助Reflection API取得任何类的内部信息,并能够直接操作任何对象的内部属性和方法。
加载完类之后,在堆内存的方法区种就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就相当于一面镜子,透过这个镜子看到类的结构,我们称之为反射。
正常方式: 引入需要的"包类"名称 -> 通过new实例化 -> 取得实例化对象
反射方式: 实例化对象->getClass()方法->得到完整的"包类"名称
获取Class类实例的几种方式
package com.an.java;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author an
* @create 2021-04-15-19:41
*/
public class ReflectionTest {
/**
* 反射之前的操作
*/
@Test
public void test1() {
//1、创建Person类的对象
Person p1 = new Person("Tom",12);
//2、通过对象调用其内部的属性和方法
p1.age = 10;
System.out.println(p1.toString());
p1.show();
// 在Person类的外部,是不可以通过Person类的对象调取其私有的结构
// 比如: name,showNation()以及私有的构造器
}
/**
* 反射之后,对于Person的操作
*/
@Test
public void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class c = Person.class;
// 1、通过反射可以创造Person类的对象
Constructor cons = c.getConstructor(String.class,int.class);
Object obj = cons.newInstance("tom",12);
Person p = (Person)obj;
System.out.println(p.toString());
// 2、通过反射调用指定的属性和方法
Field age = c.getDeclaredField("age");
age.set(p,10);
System.out.println(p.toString());
// 3、调用方法
Method show = c.getDeclaredMethod("show");
show.invoke(p);
// 4、通过反射,可以调用私有结构
Constructor cons1 = c.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person)cons1.newInstance("Jerry");
// 调用私有的属性
Field name = c.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"jazz");
System.out.println(p1);
// 调用私有方法
Method nation = c.getDeclaredMethod("showNation", String.class);
nation.setAccessible(true);
String na = (String)nation.invoke(p1,"中国");
System.out.println(na);
}
// 通过直接 new 的方式或反射的方式都可以调用公共的结构,开发中如何选择?
// 建议: 直接new的方式
// 什么时候使用反射,反射的方式,反射的特征: 动态性
// 反射机制与面向对象的封装性是否矛盾?
// 不矛盾, 封装性可以看作一个建议, 反射解决的是能不能的问题
/**
* 关于 java.lang.Class 类的理解
* 1、类的加载过程
* 程序在经过 javac.exe 命令后,会生成一个或多个字节码文件(.class结尾),
* 接着使用java.exe对某个字节码文件进行解释和运行,相当于把字节码文件加载
* 到内存中,此过程称为类的加载。加载到内存中的类,称为运行时类,此运行时类
* ,就作为Class的一个实例
* 2、换句话说,Class 的实例就对应着一个运行时类
* 3、加载到内存中的运行时类,会缓存一定时间,在此时间内,我们可以通过
* 不同的方式来获取运行时类
*/
// 获取Class实例的方式
@Test
public void test3() throws ClassNotFoundException {
// 方式1: 调用运行时类的属性
Class<Person> class1 = Person.class;
System.out.println(class1);
// 方式2: 通过运行时类的对象,调用getClass()方法
Person p1 = new Person();
Class class2 = p1.getClass();
// 方式3: 调用Class的静态方法: forName(String classPath)
Class class3 = Class.forName("com.an.java.Person");
System.out.println(class1 == class2);
System.out.println(class2 == class3);
// 方式4: 使用类的加载器
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class class4 = classLoader.loadClass("com.an.java");
}
/**
* 有Class对象的类型
* 1、class: 外部类,成员
* 2、interface: 接口
* 3、[]: 数组
* 4、enum: 枚举
* 5、annotation: 注解 @interface
* 6、primitive type: 基本数据类型
* 7、void
*/
}
ClassLoader
package com.an.java;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author an
* @create 2021-04-15-20:50
*/
public class ClassLoaderTest {
@Test
public void test1() {
// 对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
// 调用系统类加载器的getParent(): 获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
// 调用扩展类加载器的getParent(): 无法获取引导类加载器
// 引导类加载器主要负责加载java的核心类库,无法加载自定义类
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);
}
/**
* Properties: 用来读取配置文件
*/
@Test
public void test2() throws IOException {
Properties pros = new Properties();
// 默认在modules下
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user:"+user+" password:"+password);
// 默认在 src 下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
pros.load(is);
}
}
创建运行时类的对象
package com.an.java;
import org.junit.Test;
import java.util.Random;
/**
* 通过反射创建对应运行时类对象
*
* @author an
* @create 2021-04-16-10:16
*/
public class NewInstanceTest {
@Test
public void test1() throws IllegalAccessException, InstantiationException {
/*
newInstance(): 调用此方法,创建对应的运行时类的对象
内部调用了运行时类的空参构造器
要想此方法正常创建运行时类的对象,要求:
1、运行时类必须提供空参的构造器
2、空参的构造器的访问权限够,通常,设置为public
在 javabean 中要求提供一个 public 的空参构造器,原因:
1、便于通过反射,创建运行时类的对象
2、便于子类继承此运行时类,默认调用super()时,有父类构造器
*/
Class<Person> c = Person.class;
Object obj = c.newInstance();
}
// 体会反射的动态性
@Test
public void test2() {
int num = new Random().nextInt(3);
String classPath = "";
switch (num) {
case 0:
classPath = "java.util.Date"; break;
case 1:
classPath = "java.lang.Object"; break;
case 2:
classPath = "com.an.java.Person"; break;
}
Object obj=null;
try {
obj = getInstance(classPath);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
System.out.println(obj);
}
/*
创建一个指定类的对象
*/
public Object getInstance(String classPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class c = Class.forName(classPath);
return c.newInstance();
}
}
反射的应用: 动态代理
代理设计模式的原理:
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
之前涉及到的代理为静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发过程中必然产生过多代理。最好可以通过一个代理类完成全部的代理功能。
静态代理举例
package com.an.java;
/**
* 静态代理举例
* 代理类和被代理类在编译期间就已经确定下来了
*
*
* @author an
* @create 2021-04-16-10:39
*/
interface ClothFactory {
void produceCloth();
}
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory; // 用被代理类实例化
public ProxyClothFactory(ClothFactory factory) {
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("准备工作");
factory.produceCloth();
System.out.println("后续工作");
}
}
// 被代理类
class NikeClothFactory implements ClothFactory {
@Override
public void produceCloth() {
System.out.println("生产Nike");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
// 创建被代理类的对象
ClothFactory nike = new NikeClothFactory();
// 创建代理类的对象
ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
动态代理举例
package com.an.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理举例
* @author an
* @create 2021-04-16-10:50
*/
interface Human {
String getBelief();
void eat(String food);
}
// 被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
/**
* 要想实现动态代理,需要解决的问题
* 问题一: 如何根据加载到内存中的被代理类,动态创建一个代理类及对象
* 问题二:通过代理类的对象调用方法时,如何调用被代理类中对应的方法
*/
class ProxyFactory {
// 调用此方法,返回一个代理类的对象,解决问题一
public static Object getProxyInstance(Object obj) {
MyInvocationHander hander = new MyInvocationHander();
hander.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),hander);
}
}
class MyInvocationHander implements InvocationHandler {
private Object obj; // 赋值时,需要使用被代理对象赋值
public void bind(Object obj) {
this.obj = obj;
}
// 当我们通过代理类的对象,调用方法a时,就会自动调用如下的方法: invoke()
// 将被代理类要执行的方法a的功能声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method: 即为代理类调用的方法,此方法也成为被代理类调用方法
Object returnValue = method.invoke(obj, args);
// 上述方法的返回值就作为invoke()方法的返回值
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human)ProxyFactory.getProxyInstance(superMan);
// 当通过代理类对象调用方法时,会自动调用被代理类中同名的对象
proxyInstance.getBelief();
proxyInstance.eat("火锅");
}
}
Lambda 表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
package com.an.java;
import org.junit.Test;
import java.util.Comparator;
import java.util.function.Consumer;
/**
* Lambda 表达式的使用
*
* 1、举例: (o1,o2) -> Integer.compare(o1,o2);
*
* 2、格式
* ->: Lambda操作符 或 箭头操作符
* ->: 左边 Lambda 形参列表,其实是接口抽象方法的形参列表
* ->: 右边 Lambda 体(重写抽象方法的方法体)
*
* 3、重写,分为6种情况
*
* 4、Lambda 表达式的本质: 作为函数式接口的实例
*
* 5、如果一个接口只声明了一个方法,则这个接口可以称为函数式接口
*
* 6、我们可以在一个接口上使用 @FunctionalInterface 注解,这样可以
* 检查它是否是一个函数式接口。同时javadoc也会包含一个声明
*
*
* @author an
* @create 2021-04-16-14:57
*/
public class LambdaTest1 {
// 语法一: 无参,无返回值
@Test
public void test1() {
Runnable r1 = () -> System.out.println("Helloworld");
r1.run();
}
// 语法二: 需要一个参数,但是没有返回值
@Test
public void test2() {
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("c");
Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("b");
}
// 语法三: 数据类型可以省略,可以由编译器推断得出,称为"类型推断"
@Test
public void test3() {
Consumer<String> con1 = (s) -> {
System.out.println(s);
};
con1.accept("b");
}
// 语法四: Lambda 表达式只需要一个参数,小括号可以省略
@Test
public void test4() {
Consumer<String> con1 = s -> {
System.out.println(s);
};
con1.accept("b");
}
// 语法五: 需要两个或以上的参数,多条执行语句,可以有返回值
@Test
public void test5() {
Comparator<Integer> com2 = (o1, o2) -> {
return Integer.compare(o1,o2);
};
int compare2 = com2.compare(12,21);
System.out.println(compare2);
}
// 语法六: Lambda 体只有一条语句,return 与大括号都可以省略
@Test
public void test6() {
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1,o2);
int compare2 = com2.compare(12,21);
System.out.println(compare2);
}
}
方法引用与构造器引用
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。
格式: 使用操作符"::"将类或对象与方法名分隔开来。
如下三种主要使用情况:
-
对象::实例方法名
-
类::静态方法名
-
类::实例方法名
构造器引用:和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型。
String API
Stream API(java.util.stream)把真正的函数式编程风格引入到Java中。
Stream 是 Java8 中处理集合的关键抽象概念,可以指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。
Stream 和 Collection 集合的区别: Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者主要面向内存,存储在内存中,后者主要面向CPU,通过CPU实现计算。
①Stream 自己不会存储元素
②Stream 不会改变源对象,他们会返回一个有结果的Stream
③Stream 操作是延迟执行的,意味着他们会等到需要结果的时候才执行。
Stream 操作的三个步骤
1、创建 Stream
一个数据源(集合、数组),获取一个流。
2、中间操作
一个中间操作链,对数据源的数据进行处理。
3、终止操作
一旦执行终止操作,就执行中间操作链,并产生结果,之后,不会再被使用。
package com.an.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Stream API 实例化
* @author an
* @create 2021-04-16-15:33
*/
public class StreamAPITest {
@Test
public void test1() {
/*
通过集合
*/
// default Stream<E> stream(): 返回一个顺序流
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
Stream<Integer> stream = list.stream();
// default Stream<E> parallelStream(): 返回一个并行流
Stream<Integer> stream2 = list.parallelStream();
/*
通过数组
*/
// 调用 Arrays 类的 static <T> Stream<T> stream(T[] array);
int arr[] = new int[]{1,2,3,4};
IntStream stream3 = Arrays.stream(arr);
/*
通过Stream 类本身 of()
*/
Stream<Integer> stream4 = Stream.of(1, 2, 3, 4, 5);
/*
创建无限流
*/
// 迭代
// 遍历前10个偶数
Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);
Stream.generate(Math::random);
}
}