一. 反射
1. 反射的概述
2. 反射的使用
反射常用的类
通过反射获取Class对象
获得Class类相关的方法
使用反射创建实例对象
使用反射获取实例对象中的构造方法
通过反射获取实例对象的属性
通过反射获取实例对象的方法
3. 反射的优缺点
二. 枚举
1. 枚举的概述
2. 什么是枚举
枚举类
3.为枚举添加方法
4.枚举的优缺点
三. Lambda表达式
1. 什么是Lambda表达式?
2. 函数式接口
3. Lambda表达式的基本使用
4. Lambda在集合当中的使用
5.总结
红色标注内容则为重点内容!!!
1. 反射的概述
(1)什么是反射:
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查。被private封装的资源只能类内部访问,外部是不行的,但反射能直接操作类私有属性。反射可以在运行时获取一个类的所有信息,(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
(2)反射的基本信息
Java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型,例如Person p = new Student();这句代码中p在编译时类型为Person,运行时类型为Student。程序需要在运行时发现对象和类的真实。而通过使用反射程序就能判断出该对象和类属于哪些类。
(3)反射的用途
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类。
2.反射的使用
反射常用的类:
通过反射获取class对象:
有三种方式:(面试也会问道,重点中的重点)
- 通过Class类中的通过forName方法。
- 通过类名.class获取。
- 通过使用实例对象调用getclass方法获取。
通过下面一个小案例给大家看下这三种方式通过反射获取class对象:
Student类定义:
class Student{
//私有属性name
private String name = "fan";
//公有属性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)");
}
private void eat(){
System.out.println("i like eating");
}
public void sleep(){
System.out.println("i like sleeping");
}
private void function(String str) {
System.out.println("私有方法function被调用:"+str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
获取对应类的Class对象:
public static void main(String[] args) {
//有3种方式可以获取Class对象
//1.通过对象的getClass()方法
Student student = new Student();
Class<?> class1 = student.getClass();
//2、通过类名.class获取
Class<?> class2 = Student.class;
//3. forName(“路径”)
Class<?> clas3s = null;
try {
class3 = Class.forName("Student");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
System.out.println(class1.equals(class2));
System.out.println(class1.equals(class3));
System.out.println(class2.equals(class3));
}
通过判断结果都为true,三种方式获取class对象都是同一个。
获得Class类的方法
使用反射创建实例对象:
我们在使用newInstance()方法返回值是泛型,所以在获取时要进行强转。
public static void main(String[] args) {
//获取相关类的Class对象
Class<?> c = Student.class;
//使用newInstance方法创建实例
try {
//需要进行强转
Student student = (Student) c.newInstance();
System.out.println(student);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
执行结果就为:student();student{name='fan',age=18}
使用反射获取实例对象中的构造方法
使用反射获取实例对象中构造方法然后创建实例对象:
- 获取Class对象。
- 通过上述的方法获取构造器。
- 如果获取的是私有的构造方法,则需要记得通过构造器的
setAccessible
方法将访问权限开启。 - 调用构造器中的
newInstance
方法获取对象
public static void main(String[] args) throws ClassNotFoundException {
//1.获取Clas对象
Class<?> c = Class.forName("Student");
//2.获取指定参数列表的构造器,演示获取Student中的一个私有构造器,参数传形参列表类型
try {
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
//获取的私有构造方法,需要打开访问权限,默认关闭
constructor.setAccessible(true);
//3.根据获取到的构造器获取实例对象,使用newInstance方法,需要传入构造器需要的参数
Student student = (Student) constructor.newInstance("fanfan", 21);
System.out.println(student);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
通过反射获取实例对象的属性
通过如下过程修改一个对象的私有属性:
获取Class对象。
创建或通过反射实例化一个需要修改其私有字段的类。
通过属性名,调用上述getDeclaredField方法获取对应的属性对象。
通过setAccessible方法设置为访问私有属性开权限。
通过Field对象的set方法,修改传入对象中的对应属性。
public static void main(String[] args) {
//1.获取Class对象
Class<?> c = Student.class;
try {
//2.通过反射创建实例对象
Student student = (Student) c.newInstance();
//3.获取私有属性name
Field field = c.getDeclaredField("name");
//4.给该私有属性开权限
field.setAccessible(true);
//5.修改该私有属性
field.set(student, "被反射修改的私有属性");
System.out.println(student);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
通过反射获取实例对象的方法
通过如下过程获取Student对象中的私有方法function:
获取相关Student类的Class对象。
创建或通过反射实例化一个Student。
通过class对象获取到实例对象中的方法对象,参数为方法名,形参类型列表。
为获取的私有方法开访问权限。
通过invork方法调用方法。
public static void main(String[] args) {
try {
//1.获取Class对象
Class<?> c = Class.forName("Student");
//2.获取Student的一个实例对象
Student student = (Student) c.newInstance();
//3.通过class对象获取实例的方法对象,参数为方法名,以及形参列表
Method method = c.getDeclaredMethod("function", String.class);
//4.为私有方法开访问权限
method.setAccessible(true);
//5.通过invork方法调用方法
method.invoke(student, "传入私有方法参数");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
反射的优缺点:
优点
反射机制极大的提高了我们代码的灵活性和扩展性,降低了模块的耦合性,提高了自身的适应能力.通过反射机制可以让程序创建和控制任何类的对象无需提前硬编码目标类.使用反射机制能够在运行时构造一个类的对象,判断一个类所具有的成员变量和方法,调用一个对象方法.反射机制时构建框架技术的基础,使用反射可以避免将代码写死在框架中
缺点
当然使用反射也有缺点凡是事情都有正反两面,如果一个功能可以不用反射完成,那就最好不用反射.
性能开销:反射涉及了动态类型的解析,所以jvm无法对这些代码进行优化.因此反射操作的效率要比正常操作效率低很多
安全限制:使用反射技术通常要在一个没有安全限制的程序运行.
内部暴露:由于反射技术允许一些在正常情况下不允许执行的操作比如:一些私有的属性或方法,这可能导致代码的功能失调并破坏了代码的可移植性
二. 枚举
什么是枚举?
枚举是在JDK1.5后引入的,enum 全称 enumeration。使用枚举可以将常量组织起来,统一管理。
声明枚举:
声明枚举时必须使用 enum 关键字,然后定义枚举的名称、可访问性、基础类型和成员等。枚举声明的语法如下:
enum-modifiers enum enumname:enum-base {
enum-body,
}
其中,enum-modifiers 表示枚举的修饰符主要包括 public、private 和 internal;enumname 表示声明的枚举名称;enum-base 表示基础类型;enum-body 表示枚举的成员,它是枚举类型的命名常数。
任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔。
提示:如果没有显式地声明基础类型的枚举,那么意味着它所对应的基础类型是 int。
下面代码定义了一个表示性别的枚举类型 SexEnum 和一个表示颜色的枚举类型 Color。
public enum SexEnum {
male,female;
}
public enum Color {
red,blue,green,black;
}
定义完后可以通过枚举类型名直接引用常量,如 SexEnum.male、Color.red
使用枚举还可以使 switch 语句的可读性更强,例如以下示例代码:
enum Signal {
// 定义一个枚举类型
green,yellow,red
}
public class TrafficLight {
Signal color = Signal.red;
public void change() {
switch(color) {
case RED:
color = Signal.green;
break;
case YELLOW:
color = Signal.red;
break;
case GREEN:
color = Signal.yellow;
break;
}
}
}
枚举类:
Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。
所有枚举实例都可以调用 Enum 类的方法
枚举成员:green
枚举成员:yellow
枚举成员:red
通过调用枚举类型实例的 values()方法
可以将枚举的所有成员以数组形式返回,也可以通过该方法获取枚举类型的成员。
下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 values() 方法输出这些成员。
enum Signal {
// 定义一个枚举类型
green,yellow,red;
}
public static void main(String[] args) {
for(int i = 0;i < Signal.values().length;i++) {
System.out.println("枚举成员:"+Signal.values()[i]);
}
}
创建一个示例,调用valueOf()方法获取枚举的一个成员,再调用 compareTo() 方法进行比较,并输出结果。具体实现代码如下:
public class TestEnum {
public enum Sex {
// 定义一个枚举
male,female;
}
public static void main(String[] args) {
compare(Sex.valueOf("male")); // 比较
}
public static void compare(Sex s) {
for(int i = 0;i < Sex.values().length;i++) {
System.out.println(s + "与" + Sex.values()[i] + "的比较结果是:" + s.compareTo(Sex.values()[i]));
}
}
}
通过调用枚举类型实例的ordinal()方法
可以获取一个成员在枚举中的索引位置。下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 ordinal() 方法输出成员及对应索引位置。
public class TestEnum1 {
enum Signal {
// 定义一个枚举类型
GREEN,YELLOW,RED;
}
public static void main(String[] args) {
for(int i = 0;i < Signal.values().length;i++) {
System.out.println("索引" + Signal.values()[i].ordinal()+",值:" + Signal.values()[i]);
}
}
}
为枚举添加方法:
Java 为枚举类型提供了一些内置的方法,同时枚举常量也可以有自己的方法。此时要注意必须在枚举实例的最后一个成员后添加分号,而且必须先定义枚举实例。
下面的代码创建了一个枚举类型 WeekDay,而且在该类型中添加了自定义的方法。
enum WeekDay {
Mon("Monday"),Tue("Tuesday"),Wed("Wednesday"),Thu("Thursday"),Fri("Friday"),Sat("Saturday"),Sun("Sunday");
// 以上是枚举的成员,必须先定义,而且使用分号结束
private final String day;
private WeekDay(String day) {
this.day = day;
}
public static void printDay(int i) {
switch(i) {
case 1:
System.out.println(WeekDay.Mon);
break;
case 2:
System.out.println(WeekDay.Tue);
break;
case 3:
System.out.println(WeekDay.Wed);
break;
case 4:
System.out.println(WeekDay.Thu);
break;
case 5:
System.out.println(WeekDay.Fri);
break;
case 6:
System.out.println(WeekDay.Sat);
break;
case 7:
System.out.println(WeekDay.Sun);
break;
default:
System.out.println("wrong number!");
}
}
public String getDay() {
return day;
}
}
上面代码创建了 WeekDay 枚举类型,下面遍历该枚举中的所有成员,并调用 printDay() 方法。示例代码如下:
public static void main(String[] args) {
for(WeekDay day : WeekDay.values()) {
System.out.println(day+"====>" + day.getDay());
}
WeekDay.printDay(5);
}
Java 中的 enum 还可以跟 Class 类一样覆盖基类的方法。下面示例代码创建的 Color 枚举类型覆盖了 toString() 方法。
public class Test {
public enum Color {
RED("红色",1),GREEN("绿色",2),WHITE("白色",3),YELLOW("黄色",4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name,int index) {
this.name = name;
this.index = index;
}
// 覆盖方法
@Override
public String toString() {
return this.index + "-" + this.name;
}
}
public static void main(String[] args) {
System.out.println(Color.RED.toString()); // 输出:1-红色
}
}
EnumMap 与 EnumSet
为了更好地支持枚举类型,java.util 中添加了两个新类:EnumMap 和 EnumSet
。使用它们可以更高效地操作枚举类型。
EnumMap 类
EnumMap 是专门为枚举类型量身定做的 Map 实现。虽然使用其他的 Map(如 HashMap)实现也能完成枚举类型实例到值的映射,但是使用 EnumMap 会更加高效。
HashMap 只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值,使得 EnumMap 的效率非常高。
下面是使用 EnumMap 的一个代码示例。枚举类型 DataBaseType 里存放了现在支持的所有数据库类型。针对不同的数据库,一些数据库相关的方法需要返回不一样的值,例如示例中 getURL() 方法。
// 定义数据库类型枚举
public enum DataBaseType {
MYSQUORACLE,DB2,SQLSERVER
}
// 某类中定义的获取数据库URL的方法以及EnumMap的声明
private EnumMap<DataBaseType,String>urls = new EnumMap<DataBaseType,String>(DataBaseType.class);
public DataBaseInfo() {
urls.put(DataBaseType.DB2,"jdbc:db2://localhost:5000/sample");
urls.put(DataBaseType.MYSQL,"jdbc:mysql://localhost/mydb");
urls.put(DataBaseType.ORACLE,"jdbc:oracle:thin:@localhost:1521:sample");
urls.put(DataBaseType.SQLSERVER,"jdbc:microsoft:sqlserver://sql:1433;Database=mydb");
}
//根据不同的数据库类型,返回对应的URL
// @param type DataBaseType 枚举类新实例
// @return
public String getURL(DataBaseType type) {
return this.urls.get(type);
}
枚举的优缺点:
优点:
1 增强代码可读性:
枚举可以让你代码看起来很舒服,而且常量统一管理起来,当项目很大的时候很容易管理。
2 传递参数错误:
可以减少参数传递的错误性
3 去除equals两者判断
由于常量值地址唯一,使用枚举可以直接通过“==”进行两个值之间的对比,性能会有所提高。
4 编译优势(与常量类相比)
常量类编译时,常量被直接编译进二进制代码中,常量值在升级中变化后,需要重新编译引用常量的类,因为二进制代码中存放的是旧值。
举类编译时,没有把常量值编译到代码中,即使常量值发生改变,也不会影响引用常量的类。
5 修改优势(与常量类相比)
枚举类编译后默认final class,不允许继承可防止被子类修改。常量类可被继承修改、增加字段等,易导致父类不兼容。
6 枚举型可直接与数据库交互
7 Switch语句优势
使用int、String类型switch时,当出现参数不确定的情况,偶尔会出现越界的现象,这样我们就需要做容错操作(if条件筛选等),使用枚举,编译期间限定类型,不允许发生越界。
缺点:
不可以继承,无法扩展
三.Lambda表达式
什么是Lambda表达式?
Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。 Lambda 表达式(Lambda expression)可以看作是一个匿名函数,基于数学中的λ演算得名,也可称为闭包(Closure)
Lambda表达式的语法
(parameters) -> expression 或 (parameters) ->{ statements; }
函数式接口:
要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法
- 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
- 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。
定义方式:
@FunctionalInterface
interface NoParameterNoReturn {
//注意:只能有一个方法
void test();
}
@FunctionalInterface
interface NoParameterNoReturn {
void test();
default void test2() {
System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
}
}
Lambda表达式的基本使用:
1.无返回值函数式接口:
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
void test(int a);
}
//无返回值两个参数
@FunctionalInterface
interface MoreParameterNoReturn {
void test(int a,int b);
}
public class TestDemo {
public static void main(String[] args) {
NoParameterNoReturn n = ()->{
System.out.println("无参数无返回值");
};
n.test();
OneParameterNoReturn o = (a)-> {
System.out.println("无返回值一个参数"+a);
};
o.test(666);
MoreParameterNoReturn m = (int a,int b)->{
System.out.println("无返回值两个参数"+a+" "+b);
};
m.test(666,999);
}
}
2.有返回值函数接口
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
int test(int a);
}
//有返回值多个参数
@FunctionalInterface
interface MoreParameterReturn {
int test(int a,int b);
}
public class TestDemo {
public static void main(String[] args) {
NoParameterReturn n = ()->{
return 666;
};
int ret1 = n.test();
System.out.println(ret1);
System.out.println("================");
OneParameterReturn o = (int a)->{
return a;
};
int ret2 = o.test(999);
System.out.println(ret2);
System.out.println("================");
MoreParameterReturn m = (int a,int b)-> {
return a+b;
};
int ret3 = m.test(10,90);
System.out.println(ret3);
}
}
3.语法简写特点
Lambda表达式的语法还可以精简,显得非常有逼格,但是可读性就非常差。
- 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
- 参数的小括号里面只有一个参数,那么小括号可以省略
- 如果方法体当中只有一句代码,那么大括号可以省略
- 如果方法体中只有一条语句,其是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);
}
Lambda在集合当中的使用
为了能够让Lambda和Java的集合类集更好的一起使用,集合当中,也新增了部分接口,以便与Lambda表达式对接。要用Lambda遍历集合就一定要看懂源码。
1.List和forEach、sort
//forEach演示
import java.util.*;
import java.util.function.Consumer;
public class TestDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
System.out.println("=================");
list.forEach(a-> System.out.println(a));
}
}
//sort演示
import java.util.function.Consumer;
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("bit");
list.add("hello");
list.add("lambda");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
System.out.println(list);
System.out.println("=============");
//Lambda方法
list.sort((o1,o2)->o2.compareTo(o1));
System.out.println(list);
}
2.HashMap和forEach
import java.util.function.Consumer;
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "hello");
map.put(2, "bit");
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, "bit");
map.put(3, "hello");
map.put(4, "lambda");
map.forEach((k,v)-> System.out.println("key = "+k+" vak = "+v));
}
总结:
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作
缺点:
- 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试