再谈String
字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来进行查找的数据结构
- 字符串常量池
反射
- 定义
Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任
意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信
息以及动态调用对象方法的功能称为java语言的反射(reflection)机制
- 用途
- 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
- 反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无
论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的
就是类的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类。
- 反射基本信息
Java程序中许多对象在运行时会出现两种类型:运行时类型**(RTTI)**和编译时类型,例如Person p = new
Student();这句代码中p在编译时类型为Person,运行时类型为Student。程序需要在运行时发现对象和类的真实
信息。而通过使用反射程序就能判断出该对象和类属于哪些类。
-
反射相关的类
-
常用获得类相关的方法
-
常用获得类中属性相关的方法(以下方法返回值为Field相关)
-
获得类中注解相关的方法
-
获得类中构造器相关的方法(以下方法返回值为Constructor相关)
-
获得类中方法相关的方法(以下方法返回值为Method相关)
获得类对象的3种方式
1.使用 Class.forName(“类的全路径名”); 静态方法。前提:已明确类的全路径
2.使用 .class 方法。说明:仅适合在编译前就已经明确要操作的 Class
3.使用类对象的 getClass() 方法
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)");
}
private void eat(){
System.out.println("i am eat");
}
public void sleep(){
System.out.println("i am pig");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> c1 = Class.forName("Student");
Class<?> c2 = Student.class;
Student student = new Student();
Class<?> c3 = student.getClass();
System.out.println(c1==c2);//true
System.out.println(c3==c2);//true
System.out.println(c1==c3);//true
//得出结论:一个类只能拥有一个class对象
}
}
public class ReflectClassDemo {
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("Student");
Student student = (Student) c1.newInstance();
System.out.println(student);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
reflectNewInstance();
}
}
运行结果:因为调用的是无参构造方法,所以name和age都是默认的
那么如果要调用私有的有参构造函数也是可以的,这就是反射
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("Student");
Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);//因为是私有的使用,需要程序猿确认
Student student = (Student)constructor.newInstance("xiaoming",19);
System.out.println(student);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
运行结果:调用了私有的有参构造函数
也可以获取到私有成员变量
public static void reflectPrivateField() {
try {
Class<?> c1 = Class.forName("Student");
Field field = c1.getDeclaredField("name");
field.setAccessible(true);
Student student = (Student)c1.newInstance();
field.set(student,"lisi");
System.out.println(student);
} catch (ClassNotFoundException | NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
运行结果:
也可以对私有的方法进行调用
public static void reflectPrivateMethod() {
try {
Class<?> c1 = Class.forName("Student");
Method method = c1.getDeclaredMethod("function", String.class);
//这里的function是私有方法的名字,String是成员变量
method.setAccessible(true);
Student student = (Student)c1.newInstance();
method.invoke(student,"调用私有方法");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
运行结果:
- 反射的优缺点
优点:1. 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意 一个方法
2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力
3. 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等
缺点:1. 使用反射会有效率问题。会导致程序效率降低
- 反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂
枚举
- Enum 类的常用方法
方法 | 描述 |
---|---|
values() | 以数组形式返回枚举类型的所有成员 |
ordinal() | 获取枚举成员的索引位置 |
valueOf() | 将普通字符串转换为枚举实例 |
compareTo() | 比较两个枚举成员在定义时的顺序 |
public enum TestEnum {
RED,BLACK,GREEN;//0 1 2
public static void main(String[] args) {
TestEnum[] testEnums = TestEnum.values();
for (int i = 0; i < testEnums.length; i++) {
System.out.println(testEnums[i] + " 索引: " + testEnums[i].ordinal());
}
TestEnum testEnum = TestEnum.valueOf("GREEN");
System.out.println(testEnum);
System.out.println(RED.compareTo(GREEN));
}
}
其中,values方法在枚举类没有,因为我们自己写的枚举类都默认继承枚举类Enum,而这个Enum类是抽象类
枚举和反射的结合
public enum TestEnum {
RED(1,"red"),
BLACK(2,"black"),
GREEN(3,"green");
private String color;
private int ordinal;
TestEnum(int ordinal,String color) {
this.color = color;
this.ordinal = ordinal;
}
}
TestEnum不能是public的,默认是private的,因此可以省略private
由于是私有的,那么我们要访问这个私有的构造方法就需要用到反射
import java.lang.reflect.Constructor;
public class TestReflectEnum {
public static void reflectPrivateConstructor() {
try {
Class<?> classStudent = Class.forName("testenmu.TestEnum");//包里面的路径
Constructor<?>[] constructors = classStudent.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println("构造方法打印:"+constructors[i]);
}
//注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类是提供了两个参数分别是String和int。
Constructor<?> declaredConstructorStudent =
classStudent.getDeclaredConstructor(String.class,int.class,
int.class,String.class);//这里这样写下面做出了解释
//设置为true后可修改访问权限
declaredConstructorStudent.setAccessible(true);
Object objectStudent = declaredConstructorStudent.newInstance("父类参数",111,666,"绿色");
TestEnum testEnum = (TestEnum) objectStudent;
System.out.println("获得枚举的私有构造函数:"+testEnum);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
reflectPrivateConstructor();
}
}
Constructor<?> declaredConstructorStudent =
classStudent.getDeclaredConstructor(String.class,int.class,
int.class,String.class);
这样写是因为我们自己写的枚举类默认继承了Enum类,Enum又是一个抽象类
所以我们在写自己的构造方法时,一个类继承了另外一个类,要帮助这个类进行初始化,这里需要传4个参数来反射过去,参数列表先写父的再写子的
可是运行结果还是报错,不过这个报错是正确的:无法创建枚举对象
如果是枚举就抛异常,说明枚举通过反射在类外根本拿不到枚举对象,所以枚举是非常安全的!
lambda
函数式接口
要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法
注意:
- 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
- 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的
定义方式:
@FunctionalInterface
interface NoParameterNoReturn {
//注意:只能有一个抽象方法
void test();
//也可以有这些方法:
/* static void test2() {
}
default void test3() {
}*/
}
如果我们正常的调用NoParameterNoReturn里面的test方法:
public static void main1(String[] args) {
NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn(){
@Override
public void test() {
System.out.println("hello");
}
};
noParameterNoReturn.test();
}
如果我们用到lambda表达式的话:
public static void main2(String[] args) {
NoParameterNoReturn noParameterNoReturn = ()->System.out.println("hello");
noParameterNoReturn.test();
}
- 用法都总结为:
- 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
- 参数的小括号里面只有一个参数,那么小括号可以省略
- 如果方法体当中只有一句代码,那么大括号可以省略
- 如果方法体中只有一条语句,且是return语句,那么大括号可以省略,且去掉return关键字
变量捕获
Lambda 表达式中存在变量捕获 ,了解了变量捕获之后,我们才能更好的理解Lambda 表达式的作用域 。Java当中的匿名类中,会存在变量捕获
在上述代码当中的变量a就是,捕获的变量。这个变量要么是被final修饰,如果不是被final修饰的 你要保证在使用之前,没有修改。
- Lambda在集合当中的使用
注意:Collection的forEach()方法是从接口 java.lang.Iterable 拿过来的
总结
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作
缺点:- 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试