Java反射及新特性
Java
反射
1、单例模式的饿汉式和懒汉式中,即便私有化构造器,通过反射,也可以创建单例模式中类的多个对象。
2、通过反射,可以调用类中私有结构,而面向对象的封装性体现的是是否建议调用内部api,如果是private声明的结构,意味着不建议调用,因为内部封装了对应的更全面的结构,推荐调用此公开结构。
而反射指的是能否调用,表示只要类的结构都调用到内存中,我们都有能力进行调用。
3、Class类的理解
运行时类:加载到内存中的类
4、获取Class实例的三种方式
public void test() throws ClassNotFoundException {
//运行类在内存中会缓存起来,在整个执行期间,只会加载一次 所以下面的==都为true
//方式1. 调用运行时类的静态属性:class
Class clazz1=User.class;
System.out.println(clazz1);
//方式2. 调用运行时类的对象的getClass()
User u1=new User();
Class clazz2 = u1.getClass();
System.out.println(clazz2==clazz1);
//方式3. 调用Class的静态方法forName(String className)
String name="com.atguigu02._class.User";//全类名
Class clazz3 = Class.forName(name);
System.out.println(clazz1==clazz3);
}
实例指向的可以是java的所有数据类型,包括void,Class本身等
只要元素类型和维度相同,则获得的Class实例相等。
补充知识:(对象是抽象的说法,你可以把它看作一个符合它自身定义的所有实例的代表,而实例则是对象的现实体现。你可以说一个实例是一个对象,但你不能说一个对象是一个实例。因为定义对象时只是把自身的规则和逻辑定义好,实例可以通过输入不同的数据使得自己和其他实例不同。
比如你可以定义一个Wheel.java 它是对象:而Test.java 中可以定义两个wheel的实例。)
5、类的加载器
启动类装载器/引导类装载器、扩展类装载器和应用程序装载器是层次关系,没有父子关系。
6、使用类的加载器获取流
public void test2() throws IOException {
Properties pro=new Properties();
//通过类加载器读取文件的默认路径为:当前module下的src下
//用File读取文件的相对默认路径为:当前module下
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info.properties");
pro.load(is);
String name = pro.getProperty("name");
String password = pro.getProperty("password");
System.out.println(name+":"+password);
}
7、当使用构造器创建实例,结果没有相应的类初始化构造器,则会报InstantiationException;
当使用的构造器为私有构造器时,会报ILLegalAccessException异常。
8、为什么一定要有一个显示的无参构造器?
9、int.class 和Integer.class是两个完全不同的类型,只有值才能自动装箱。
10、注解可以参照SuppressWarnings来写
11、调用指定的结构
12、反射的好处主要体现在动态性,让数据和代码分开
13、使用Class.forName()执行了类构造器中的clinit方法
新特性
Lambda表达式
1、常见的函数式接口:Comparator\Runable\java.util.function下定义的丰富接口
2、方法引用、构造器引用,数组引用使用的条件:就是在写抽象方法时调用的是其它抽象方法,且满足一些特征(其它抽象方法参数和原抽象方法函数之间的个数相等或少一个,返回的数据类型相同等),才可以进行替换。
3、接口中的方法全是抽象方法,要想使用Lambda表达式,该接口必须有且只有一个抽象方法
方法引用
public class MethodRefTest {
//情况一:对象::实例方法
//Consumer中的void accept(T t)
@Test
public void test1(){
Consumer<String> con1=new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
//Lambda
Consumer<String> con2=s-> System.out.println(s);
//方法引用
Consumer<String> con3=System.out::println;
}
//Supplier中的T get()
@Test
public void test2(){
Employee emp=new Employee("ma",22);
Supplier<String> sup1=new Supplier<String>() {
@Override
public String get() {
return emp.getName();
}
};
//Lambda
Supplier<String> sup2=()->emp.getName();
//方法引用
Supplier<String> sup3=emp::getName;
}
//Function中的R apply(T t)
@Test
public void test3(){
Function<Double,Long> fun1=new Function<Double, Long>() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
//Lambda
Function<Double,Long> fun2=adouble->Math.round(adouble);
//方法引用
Function<Double,Long> fun3=Math::round;
}
@Test
public void test4(){
BiPredicate<String,String> bipre1=new BiPredicate<String, String>() {
@Override
public boolean test(String s, String s2) {
return s.equals(s2);
}
};
//Lambda
BiPredicate<String,String> bipre2=(s1,s2)->s1.equals(s2);
//方法引用
BiPredicate<String,String> bipre3=String::equals;
}
}
构造器引用
@Test
public void test3(){
//判断函数式接口 有两个形参并返回一个类型的值
BiFunction<String,Integer,Employee> bif1=new BiFunction<String, Integer, Employee>() {
@Override
public Employee apply(String s, Integer integer) {
return new Employee(s,integer);
}
};
System.out.println(bif1.apply("Tom",12));
//构造器引用
BiFunction<String,Integer,Employee> bif2=Employee::new;
System.out.println(bif2.apply("Jack",20));
}
4、函数式接口:接口中只声明一个抽象方法
StreamAPI
1、stream不会对源数据进行影响。
2、stream提供的新方法
3、创建Stream的三种方式
public class StreamAPITest {
//创建Stream的方式1:通过集合
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
Stream<Employee> stream = list.stream();
}
//方式2:通过数组 调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
@Test
public void test2(){
Integer[] arr=new Integer[]{1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
}
//方式3:通过Stream的of()
@Test
public void test3(){
Stream<String> stream = Stream.of("AA", "BB", "CC");
}
}
JDK8之后的新特性
1、使用record不能在声明类中定义实例字段,类不能声明为abstract(因为目的就是为了声明一个类),不能声明显式的父类(因为它已经有一个父类了,是Record,类的继承是单一继承,即只能有一个直接父类),不能设置实例变量属性等。
2、JDK8的新特性(相较于JDK7),最可以谈的有lambda表达式和Stream API
和JDK7的对比:包括元空间、HashMap、新的日期时间API等
3、HotSpot虚拟机来说 jdk7:方法区的落地体现:永久代
jdk8:元空间。
解释:永久代和元空间都属于方法区,都是方法区的实现。
元空间:使用本地内存,它不属于JVM内存,不受JVM垃圾回收的控制。最大大小取决于系统内存,而不是堆大小
永久代:使用JVM的内存。
使用元空间的原因:避免OOM(OutOFMemory),使用永久代需要默认设置永久代的初始大小和最大值,但并不是总能知道设置的值应该是多少,所以就会报OOM错误。而元空间使用本地内存,本地内存有多大就可以用多少,默认为无限。