【反射 & 枚举 & Lambda表达式】反射,枚举,lambda的示例及优缺点

反射 & 枚举 & Lambda表达式

文章目录

  • 反射 & 枚举 & Lambda表达式
    • 1. 反射
      • 1.1 反射的基本信息
      • 1.2 反射相关的类
      • 1.3 Class类中的相关方法
      • 1.4 反射示例
      • 1.5 反射的优缺点
    • 2. 枚举
      • 2.1 枚举的定义
      • 2.2 使用
      • 2.3 常用方法
      • 2.4 枚举的优缺点
    • 3. Lambda表达式
      • 3.1 Lambda表达式的语法
      • 3.2 函数式接口
      • 3.3 Lambda表达式的基本使用
      • 3.4 语法精简
      • 3.5 变量捕获
      • 3.6 Lambda在集合中的使用
        • 3.61 Collection接口
        • 3.62 List接口
        • 3.63 Map接口
      • 3.7 Lambda表达式的优缺点

1. 反射

Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到,那么我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

1.1 反射的基本信息

Java程序中许多对象在运行时会出现两种类型:运行时类型和编译时类型,例如Person p = new Student(); 这句代码中p在编译时类型为Person,运行时类型为Student。程序需要在运行时发现对象和类的真实信息,通过使用反射程序就能判断出该对象和类属于那些类

1.2 反射相关的类

类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量/类的属性
Method类 代表类的方法
Constructor类 代表类的构造方法

1.3 Class类中的相关方法

常用获得类相关的方法

方法 用途
getClassLoader() 获得类的加载器
getDeclaredClasses() 返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的)
forName(String className) 根据类名返回类的对象
newInstance() 创建类的实例
getName() 获得类的完整路径名字

常用获得类中属性相关的方法

方法 用途
getField(String name) 获得某个公有的属性对象
getFields() 获得所有公有的属性对象
getDeclaredField(String name) 获得某个属性对象
getDeclaredFields() 获得所有属性对象

获得类中方法相关的方法

方法 用途
getMethod(String name,Class…parameterTypes) 获得该类某个公有的方法
getMethods() 获得该类所有公有的方法
getDeclaredMethod(String name,Class…parameterTypes) 获得该类某个方法
getDeclareMethods() 获得该类所有方法

1.4 反射示例

在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的,即:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息。

获得Class对象的三种方式

  1. 使用Class.forName(“类的全路径名”);静态方法。前提:已明确类的全路径名
  2. 使用.class方法 。仅适合在编译前就已经明确要操作的Class
  3. 使用类对象的getClass()方法

示例:

package reflect;
class Student{
    //私有属性name
    private String name = "zhangsan";
    //公有属性age
    public int age = 18;
    //不带参数的构造方法
    public Student(){
        System.out.println("Student()");
    }
    private Student(String name,int age){
        this.name = name;
        this.age = age;
        System.out.println("Student(String name)");
    }
    public void eat(){
        System.out.println(" i am eat");
    }
    public void sleep(){
        System.out.println("i am sleep");
    }
    public void function(String str){
        System.out.println(str);
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class TestDemo {
    public static void main(String[] args) {
        //通过getClass获取Class对象
        Student s1 = new Student();
        Class c1 = s1.getClass();
        //直接通过类名.class得到,这个方法最为安全可靠,程序性能更高
        //这说明任何一个类都有一个隐含的静态成员变量class
        Class c2 = Student.class;

        //3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
        //但可能抛出 ClassNotFoundException 异常
        Class c3 = null;
        try {
            //注意这里是类的全路径,如果有包需要加包的路径
            c3 = Class.forName("reflect.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的c1,c2,c3进行 equals 比较,发现都是true
        System.out.println(c1.equals(c2));
        System.out.println(c1.equals(c3));
        System.out.println(c2.equals(c3));
    }
}

反射的使用

接下来我们开始使用反射,我们依旧反射上面的Student类,把反射的逻辑写到另外的类当中进行理解

package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectClassDemo {

    public static void reflectNewInstance(){
        //创建对象
        Class<?> c3 = null;
        try {
            c3 = Class.forName("reflect.Student");
            Object o = c3.newInstance();
            Student student = (Student) o;
            System.out.println(student);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 反射私有的构造方法
    public static void reflectPrivateConstructor() {

        try {
            Class<?> c3 = Class.forName("reflect.Student");
            Constructor<?> constructor = c3.getDeclaredConstructor(String.class, int.class);
            //私有的属性或者方法需要设置
            constructor.setAccessible(true);
            Object stu = constructor.newInstance("xiaoqiang",15);
            Student student = (Student) stu;
            System.out.println(student);
        }catch (Exception e){

        }
    }
    // 反射私有属性
    public static void reflectPrivateField(){
        try {
            Class<?> c3 = Class.forName("reflect.Student");
            Field field = c3.getDeclaredField("name");
            field.setAccessible(true);
            Student student = (Student) c3.newInstance();
            //设置student这个字段值为bit
            field.set(student,"xxxxx");
            String name = (String) field.get(student);
            System.out.println("反射私有属性修改了name:"+name);
        }catch (Exception e){

        }
    }
    // 反射私有方法
    public static void reflectPrivateMethod() {
        try {
            Class<?> classStudent = Class.forName("reflect.Student");
            Method methodStudent = classStudent.getDeclaredMethod("function",String.class);
            System.out.println("私有方法的方法名为:"+methodStudent.getName());
            //私有的一般都要加
            methodStudent.setAccessible(true);
            Object objectStudent = classStudent.newInstance();
            Student student = (Student) objectStudent;
            //反射调用方法使用invoke 代表调用student这个对象methodStudent表示的方法,并且传参
            methodStudent.invoke(student,"我是给私有的function函数传的参数");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        reflectNewInstance();
        System.out.println("===========");
        reflectPrivateConstructor();
        System.out.println("===========");
        reflectPrivateField();
        System.out.println("===========");
        reflectPrivateMethod();
    }
}
//执行结果
Student()
Student{name='zhangsan', age=18}
===========
Student(String name)
Student{name='xiaoqiang', age=15}
===========
Student()
反射私有属性修改了name:xxxxx
===========
私有方法的方法名为:function
Student()
我是给私有的function函数传的参数

1.5 反射的优缺点

优点:

  1. 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法
  2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力
  3. 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。

缺点:

​ 反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂 。

2. 枚举

2.1 枚举的定义

主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式:

public static int final RED = 1;
public static int final GREEN = 2;
public static int final BLACK = 3;

但是常量举例有不好的地方,例如:可能碰巧有个数字1,但是他有可能误会为是RED,现在我们可以直接用枚举来进行组织,这样一来,就拥有了类型,枚举类型。而不是普通的整形1

public enum TestEnum{
    RED,BLACK,GREEN;
}

优点:将常量组织起来统一进行管理

场景:错误状态码,消息类型,颜色的划分,状态机等等…

本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了这个类。

2.2 使用

public enum TestEnum {
	RED,BLACK,GREEN,WHITE;
	public static void main(String[] args) {
		TestEnum testEnum2 = TestEnum.BLACK;
		switch (testEnum2) {
			case RED:
				System.out.println("red");
				break;
			case BLACK:
				System.out.println("black");
				break;
			case WHITE:
				System.out.println("WHITE");
				break;
			case GREEN:
				System.out.println("black");
				break;
			default:
				break;
		}
	}
}

2.3 常用方法

方法名称 描述
values() 以数组形式返回枚举类型的所有成员
ordinal() 获取枚举成员的索引位置
valueOf() 将普通字符串转换为枚举实例
compareTo() 比较两个枚举成员在定义时的顺序
public enum TestEnum {
    RED,BLACK,GREEN,WHITE;

    public static void main(String[] args) {
        TestEnum[] testEnum = TestEnum.values();//以数组形式返回枚举类型的所有成员
        for (int i = 0; i < testEnum.length; i++) {
            System.out.println(testEnum[i]+ " " + testEnum[i].ordinal());
            //获取枚举成员的索引位置
        }
        System.out.println(TestEnum.valueOf("GREEN"));//将普通字符串转换为枚举实例
        System.out.println(BLACK.compareTo(WHITE));//比较两个枚举成员在定义时的顺序
       
    }
}

在Java当中枚举实际上就是一个类。所以我们在定义枚举的时候,还可以这样定义和使用枚举:

枚举的构造方法默认是私有的

public enum TestEnum {
	RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
	private String name;
	private int ordinal;
	//枚举的构造方法默认是私有的
	private TestEnum (String name,int ordinal) {
	this.name = name;
	this.ordinal = ordinal;
	}
	public static TestEnum getEnumKey (int ordinal) {
	for (TestEnum t: TestEnum.values()) {
		if(t.ordinal == ordinal) {
			return t;
		}
	}
		return null;
	}
	public static void main(String[] args) {
		System.out.println(getEnumKey(2));
	}
}

注意:不能通过反射获取枚举类的实例!!

2.4 枚举的优缺点

优点:

  1. 枚举常量更简单安全 。
  2. 枚举具有内置方法 ,代码更优雅

缺点:

  1. 不可继承,无法扩展

3. Lambda表达式

lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。 Lambda 表达式可以看作是一个匿名函数

3.1 Lambda表达式的语法

基本语法: (参数列表) ->方法体 或 (参数列表) ->{ 方法体; }

Lambda表达式由三部分组成:

  1. 参数列表:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明,也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
  2. ->:可理解为“被用于”的意思
  3. 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。
// 1. 不需要参数,返回值为 2
() -> 2
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的和
(x, y) -> x + y
// 4. 接收2个int型整数,返回他们的乘积
(int x, int y) -> x * y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

3.2 函数式接口

要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法 。

注意

  1. 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
  2. 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。

定义方式:

@FunctionalInterface
interface NoParameterNoReturn {
	//注意:只能有一个方法
	void test();
}

@FunctionalInterface
interface NoParameterNoReturn {
	void test();
	default void test2() {
		System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
	}
}

3.3 Lambda表达式的基本使用

//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
	void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
	void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
	void test(int a,int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
	int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
	int test(int a);
}
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
	int test(int a,int b);
}


Lambda表达式本质是一个匿名函数,函数的方法是:返回值 方法名 参数列表 方法体。在,
Lambda表达式中我们只需要关心:参数列表 方法体。
具体使用见以下示例代码:

public class TestDemo {
	public static void main(String[] args) {
	NoParameterNoReturn noParameterNoReturn = ()->{
		System.out.println("无参数无返回值");
	};
	noParameterNoReturn.test();
        
        
	OneParameterNoReturn oneParameterNoReturn = (int a)->{
		System.out.println("无参数一个返回值:"+ a);
	};
	oneParameterNoReturn.test(10);
        
        
	MoreParameterNoReturn moreParameterNoReturn = (int a,int b)->{
		System.out.println("无返回值多个参数:"+a+" "+b);
	};
	moreParameterNoReturn.test(20,30);
        
        
	NoParameterReturn noParameterReturn = ()->{
		System.out.println("有返回值无参数!");
		return 40;
	};
    //接收函数的返回值
	int ret = noParameterReturn.test();
	System.out.println(ret);
        
        
	OneParameterReturn oneParameterReturn = (int a)->{
		System.out.println("有返回值有参数!");
		return a;
	};
	ret = oneParameterReturn.test(50);
	System.out.println(ret);
        
        
	MoreParameterReturn moreParameterReturn = (int a,int b)->{
		System.out.println("有返回值多个参数!");
		return a+b;
	};
	ret = moreParameterReturn.test(60,70);
        System.out.println(ret);
	}
}

3.4 语法精简

  1. 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
  2. 参数的小括号里面只有一个参数,那么小括号可以省略
  3. 如果方法体当中只有一句代码,那么大括号可以省略
  4. 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字。
public static void main(String[] args) {
	MoreParameterNoReturn moreParameterNoReturn = (a,b)->{
	System.out.println("无返回值多个参数,省略参数类型:"+a+" "+b);
	};
	moreParameterNoReturn.test(20,30);
    
    
	OneParameterNoReturn oneParameterNoReturn = a ->{
		System.out.println("无参数一个返回值,小括号可以省略:"+ a);
	};
	oneParameterNoReturn.test(10);
    
    
	NoParameterNoReturn noParameterNoReturn = ()->System.out.println("无参数无返回值,方法体中只有一行代码");
	noParameterNoReturn.test();
    
    
	//方法体中只有一条语句,且是return语句
	NoParameterReturn noParameterReturn = ()-> 40;
	int ret = noParameterReturn.test();	
	System.out.println(ret);
}

3.5 变量捕获

Lambda 表达式中存在变量捕获 ,了解了变量捕获之后,我们才能更好的理解Lambda 表达式的作用域 。Java当中的匿名类中,会存在变量捕获

匿名内部类的变量捕获

class Test {
	public void func(){
		System.out.println("func()");
	}
}
public class TestDemo {
	public static void main(String[] args) {
		int a = 100;
		new Test(){
			@Override
			public void func() {
				System.out.println("我是内部类,且重写了func这个方法!");
			System.out.println("我是捕获到变量 a == "+a+" 我是一个常量,或者是一个没有改变过值的变量!");
			}
		};
	}
}


在上述代码当中的变量a就是,捕获的变量。这个变量要么是被final修饰,如果不是被final修饰的 你要保证在使用之前,没有修改。

Lambda的变量捕获

@FunctionalInterface
interface NoParameterNoReturn {
	void test();
}
public static void main(String[] args) {
	int a = 10;
	NoParameterNoReturn noParameterNoReturn = ()->{
		// a = 99; error
	System.out.println("捕获变量:"+a);
	};
	noParameterNoReturn.test();
}

3.6 Lambda在集合中的使用

3.61 Collection接口

示例

public static void main(String[] args) {
	ArrayList<String> list = new ArrayList<>();
	list.add("Hello");
	list.add("World");
	list.add("hello");
	list.add("lambda");
	list.forEach(new Consumer<String>(){
		@Override
		public void accept(String str){
			//简单遍历集合中的元素。
			System.out.print(str+" ");
		}
	});
}

修改为lambda表达式:

public static void main(String[] args) {
	ArrayList<String> list = new ArrayList<>();
	list.add("Hello");
	list.add("World");
	list.add("hello");
	list.add("lambda");
	//表示调用一个,不带有参数的方法,其执行花括号内的语句,为原来的函数体内容。
	list.forEach(s -> {
		System.out.println(s);
	});
}
//执行结果
Hello
World
hello
lambda

3.62 List接口

示例:

public static void main(String[] args) {
	ArrayList<String> list = new ArrayList<>();
	list.add("Hello");
	list.add("World");
	list.add("hello");
	list.add("lambda");
	list.sort(new Comparator<String>() {
		@Override
		public int compare(String str1, String str2){
				//注意这里比较长度
				return str1.length()-str2.length();
			}
		});
		System.out.println(list);
}

修改为lambda表达式:

public static void main(String[] args) {
	ArrayList<String> list = new ArrayList<>();
	list.add("Hello");
	list.add("World");
	list.add("hello");
	list.add("lambda");
	//调用带有2个参数的方法,且返回长度的差值
	list.sort((str1,str2)-> str1.length()-str2.length());
	System.out.println(list);
}

3.63 Map接口

示例:

public static void main(String[] args) {
	HashMap<Integer, String> map = new HashMap<>();
	map.put(1, "hello");
	map.put(2, "World");
	map.put(3, "hello");
	map.put(4, "lambda");
	map.forEach(new BiConsumer<Integer, String>(){
		@Override
		public void accept(Integer k, String v){
			System.out.println(k + "=" + v);
		}
	});
}

修改为lambda表达式:

public static void main(String[] args) {
	HashMap<Integer, String> map = new HashMap<>();
	map.put(1, "hello");
	map.put(2, "World");
	map.put(3, "hello");
	map.put(4, "lambda");
	map.forEach((k,v)-> System.out.println(k + "=" + v));
}

3.7 Lambda表达式的优缺点

优点:

  1. 代码简洁,开发迅速
  2. 方便函数式编程
  3. 非常容易进行并行计算
  4. Java 引入 Lambda,改善了集合操作

缺点:

  1. 代码可读性变差
  2. 在非并行计算中,很多计算未必有传统的 for 性能要高
  3. 不容易进行调试

你可能感兴趣的:(数据结构,java)