泛型与反射
也是重新温习了下泛型与反射,反射基本就是一些api理解即可,不过需要注意类加载器原理,而泛型则需要理解其设计思想,可以代替Object,更加灵活,可读性强。
泛型
泛型如果指定后,编译阶段就会检查,不让乱输其他类型,必须是引用类型; 如果不指定就默认Object
// 如果指定泛型, 就必须存指定的类型 Iterator<String> iterator = arrayList.iterator(); List<String> arrayList = new ArrayList<>(); /*** new 集合 如果没有指定泛型* 存放的类型是为Object类型*/ ArrayList arrayList = new ArrayList(); // Iterator iterator = arrayList.iterator(); //如果想要精确获取,则String str = (String)iterator.next; 需要强转,不过因为存放的不是一种类型,所以还要判断(instanceof),否则可能转换异常
1.Java 泛型 (generics) 是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 2. 早期的时候,使用 Object 来代表任意类型。但是这样在向上转型的是没有问题的,但是在向下转型的时候存在类型转换的问题,这样的程序其实是不安全的。所以 Java 在 JDK5 之后提供了泛型来解决这个问题 3. 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 4. 泛型是一种把类型的明确工作推迟到创建对象或者调用方法的时候才去明确的特殊类型。 注意:类型参数只能代表引用型类型,不能是原始类型 (像 int,double,char 的等)。 泛型可以使用在 方法、接口、类 分别称作为:泛型类、泛型方法、泛型接口。
泛型类
泛型类定义的格式: 格式:修饰符 class 类名 <类型>{} 范例:public class Student<T>{} 此处 T 可以随便写为任意标识,T、E、K、V 等型式的参数常用于表示泛型;
/*** 泛型类优化*/ public class Student<T> {private T number;public T show(T t) {return t;} }
缺点就是: 如果想要传递不同类型的方法则需要创建指明多个对象; (Object强转就不考虑了,因为需要先instanceof判断类型)
泛型方法
可以优化掉方法重载
格式:修饰符 <类型> 返回值类型 方法名 (类型 变量名){...} 范例:public<T> void show(T t){...}
public class Student {public <T> T show(T t) {return t;} }
优点: 可以只new一个对象,然后直接传递不同类型的方法,此时会正常获取,而且我们也不用手动<指定类型>,ctrl alt v 可以正常解析获取
泛型接口
格式:修饰符 interface 接口名 <类型>{...} 范例:public interface MayiktInterface <T>{...}
public interface Student<T> {T show(T t) {return t;} // 默认public } // 如果直接实现,还是Object public class StudentImpl<T> implements Student<T> {@Overridepublic T show(T t) {return t; // 而且此时可以发现,创建后传参,前方会隐式显示参数的名字,t} }
public class Test01 {public static void main(String[] args) {Student<String> stringMayikt = new StudentImpl<>();String show = stringMayikt.show("36");System.out.println(show);} }
方法:
public interface Mayikt<T> {<M> T show(T t, M m); // 如果写成<T> T show (T t); 则这个T对应泛型方法,而不是类上的T,所以下方如果实现则变成T1好区分 } public class MayiktImpl<T> implements MayiktInterface<T> {@Override public <M> T show(T t, M m) {System.out.println(m);return t;} }
泛型通配符
类型通配符 一般用于接受使用,不能够做添加。
List : 表示元素类型未知的 list,它的元素可以匹配任何类型。
带通配符的 List 仅表示它是各种泛型 List 的父类,并不能把元素添加到其中。 (而且也是需要判断类型的,否则此时获得的是Object)
也是可以遍历的,但是不能添加,比如 .add方法
类型通配符上限:<? extends 类型> List<? extends MayiktParent>: 它表示的类型是 MayiktParent 或者子类型。
类型通配符下限:<? super 类型> List<? super MayiktParent>: 它表示的类型是 MayiktParent 或者其父类型
/*** 定义的 printList方法 明确知道 接受具体 list泛型 是什么类型* List<?> 只能够用于接受 ?------- 可以接受所有的泛型类型 不能够用于添加* 是可以做get操作 获取到类型是为Object类型** @param stringList*/ public static void printList(List<?> stringList) { // List<? extends People> stringList 也可以这样传参Object o = stringList.get(0); // 直接获取到的是Object类型Iterator<?> iterator = stringList.iterator(); // 拿到的也是?while (iterator.hasNext()){System.out.println(iterator.next()); // 如果想要强转iterator.next()为指定的对象,则instanceof判断一下} }
可变参数
可变参数 又称 参数个数可变,用作方法的形参出现,那么方法参数个数就是 可变 的了。 (底层基于数组实现)
书写格式: 2.1 格式:修饰符 返回值类型 方法名 (数据类型... 量名){} 2.2 范例:public static int sum (int... a) {}
可变参数 注意事项: 这里的 可变参数变量 其实是一个数组。 如果一个方法 有多个参数,包含可变参数,可变参数要放在最后
public static int sum(int... a) {int sum = 0;for (int i = 0; i < a.length; i++) {sum+=a[i];}return sum; }
ArrayList中的asList就用到了可变参数 T...
public class Test04 {public static void main(String[] args) {/*** 使用 Arrays.asList 定义好的 元素个数 是不能够发生变化的*/List<String> strings = Arrays.asList("mayikt", "meite", "wangmazi");// 注意 如果使用Arrays.asList 方法 创建的 集合 不能够添加和删除strings.set(0, "6666"); // 可以修改,不能add和removeSystem.out.println(strings);} }
擦除机制(底层)
说明:将一个 List 集合 泛型赋值给一个没有使用到泛型 List 集合 直接去除泛型 --- 擦除机制
反编译后可以发现运行阶段没有泛型
List<String> strs = new ArrayList<String>(); strs.add("mayikt"); //说明:将一个List集合 泛型赋值给一个未使用泛型的集合会直接去除掉 List list = strs; // 此时仍然可以添加Object类型变量
反射
1.Java 反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是 JVM 得到 class 对象之后,再通过 class 对象进行反编译,从而获取对象的各种信息。
2.Java 属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到 JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
Java 反射机制可以动态方式获取到 class 相关信息 class 中成员方法、属性,反射技术灵活调用方法 或者给我们成员属性赋值,class.forName 初始化对象(创建我们的对象)
类加载器
xxx.getClass().getClassLoader().loadClass(); 类加载器的主要作用: 类加载器(ClassLoader)是 Java 虚拟机中负责加载类的组件,它的核心职责是将类的字节码文件(.class文件)加载到 JVM 中,并生成对应的Class对象,以便 JVM 可以使用这些类进行实例化、方法调用等操作。具体过程如下: 加载:在这一阶段,类加载器会根据类的全限定名(包名 + 类名)找到对应的.class文件,通过 IO 操作读取其字节码数据,然后在 JVM 内存中创建一个对应的Class对象 。这就好比从磁盘这个 “仓库” 中把类的 “蓝图” 搬运到 JVM 的 “工作区”,并整理成 JVM 能识别的格式。 链接:包括验证(确保字节码符合 JVM 规范,如文件格式正确、字节码指令合法等)、准备(为类的静态变量分配内存并设置初始值,比如static int num = 10; 在准备阶段num会被初始化为 0 )、解析(将符号引用转换为直接引用,让 JVM 能准确找到要访问的类、方法、变量等)。 初始化:对类的静态变量进行赋值和执行静态代码块,使类进入可使用状态。 对于同一个类,类加载器不一定只会执行一次,这要分情况来看: 同一个类加载器:在 Java 中,为了提高性能和避免重复加载,JVM 会维护一个已加载类的缓存。当使用同一个类加载器尝试加载一个已经加载过的类时,类加载器会直接从缓存中返回已存在的Class对象,而不会再次执行完整的加载流程。 例如,在一个普通的 Java 应用中,自定义一个类加载器加载某个类,后续再次请求加载该类时,不会重复加载。 不同类加载器:如果使用不同的类加载器去加载同一个类(比如自定义了多个不同的类加载器,或者应用程序中同时存在系统类加载器和自定义类加载器 ),由于类加载器的命名空间相互隔离,每个类加载器都有自己独立的已加载类缓存,那么就会出现多次加载同一个类的情况,且不同类加载器加载出来的类,在 JVM 中被视为不同的类。例如,通过自定义类加载器加载一个User类,再通过系统类加载器加载User类,这两个User类的Class对象在 JVM 中是不同的,它们之间不能进行类型转换等操作。 不过,对于 Java 核心类库中的类(由启动类加载器加载),在 JVM 启动过程中会被加载一次,之后在整个应用运行期间,不会再重复加载 。但对于自定义类或其他非核心类,类加载器的加载次数取决于类加载器的使用方式和 JVM 的运行逻辑。
类加载器: 当我们new出自定义对象时,如果发现没有加载到程序内存中,此时就会开始执行类加载器将该对象加载,而项目刚开始运行的时候应该底层依赖了其他类,所以其他先进行加载
class不管什么方式获取到的,都是唯一的
public class Test13 {public static void main(String[] args) throws ClassNotFoundException {// 1.获取class方式 直接类名称.Class<MayiktUserEntity> mayiktUserEntityClass = MayiktUserEntity.class;// 2.new 对象 通过对象获取class springioc容器 根据class获取对象MayiktUserEntity mayiktUserEntity = new MayiktUserEntity();Class<? extends MayiktUserEntity> aClass = mayiktUserEntity.getClass();// 类的完整路径地址 包的名称+类名称组合 第三种,企业使用最多,不过因为怕路径写错,所以也要抛异常Class<?> aClass1 = Class.forName("com.mayikt.entity.MayiktUserEntity");} }
注意 : 上述三种获取到的class都是完全相同的,因为运行期间,一个类只会产生一个class
反射应用的几个常见场景: 1.JDBC 中 Class.forName ("com.mysql.jdbc.Driver")---- 反射技术加载 mysql 驱动 2.Spring 底层基于反射初始化对象 3.(写一套自己)第三方框架扩展功能 代理设计模式
初始化对象
注意: 上边俩个创建对象代码都是只能获取公有的,如果想获取全部方法,那么就Declared即可
(1)批量获取的方法: public Constructor[] getConstructors():所有"公有的"构造方法 public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有) (2)单个获取的方法,并调用: public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法: public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法" (3) 调用构造方法: Constructor-->newInstance(Object... initargs) newInstance是 Constructor类的方法(管理构造函数的类) api的解释为: newInstance(Object... initargs) ,使用此 Constructor 对象表示的构造方法来创建 它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象,并为之调用。
获取成员属性
获取成员变量并调用:
批量的 1.1 Field[] getFields():获取所有的"公有字段" 1.2 Field[] getDeclaredFields():获取所有字段,包括: 私有、受保护、默认、公有;
获取单个的: 2.1.public Field getField(String fieldName):获取某个"公有的"字段; 2.2.public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
设置字段的值 需要注意权限问题: 3.1.Field --> public void set(Object obj,Object value): 3.2.参数说明: 3.3.obj:要设置的字段所在的对象;
public class Test15 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {// 获取class成员属性(反射给成员属性赋值、反射调用我们方法)//1.java反射技术 创建对象Class<?> aClass = Class.forName("com.mayikt.entity.MayiktUserEntity");//Field[] fields = aClass.getFields();) // 获取所有的"公有字段"Field[] fields = aClass.getDeclaredFields(); // 获取所有字段//2.获取单个的:for (int i = 0; i < fields.length; i++) {System.out.println(fields[i]);}// 如何给成员属性赋值呢?MayiktUserEntity mayiktUserEntity = (MayiktUserEntity) aClass.newInstance();Field userNameField = aClass.getDeclaredField("userName");// 反射技术给私有成员属性赋值 参数1:传递对象 参数2:赋值的内容// 如果通过反射技术给私有成员属性赋值的情况下 设置下访问的权限userNameField.setAccessible(true); // 先设置权限才可赋值userNameField.set(mayiktUserEntity, "mayikt666"); // 这个也是获取到对象,然后再修改System.out.println(mayiktUserEntity.getUserName());} }
调用方法
所有的方法: 1.1.public Method [] getMethods (): 获取所有 "公有方法";(包含了父类的方法也包含 Object 类) 1.2.public Method [] getDeclaredMethods (): 获取所有的成员方法,包括私有的 (不包括继承的)
获取单个的方法: 2.1.public Method getMethod (String name,Class... parameterTypes): 参数: name:方法名; Class ...:形参的Class类型对象 public Method getDeclaredMethod(String name,Class... parameterTypes)
调用方法: Method --> public Object invoke (Object obj,Object... args): 参数说明: obj: 要调用方法的对象;
注意 此时获取到的公有方法还包含了Object,这是和其他不同的地方
Method addUserMethod = aClass.getDeclaredMethod("addUser", String.class, Integer.class); MayiktUserEntity mayiktUserEntity = (MayiktUserEntity) aClass.newInstance(); addUserMethod.setAccessible(true); String result = (String) addUserMethod.invoke(mayiktUserEntity, "mayikt", 22); System.out.println(result); //看自己编写的方法有没有返回值可获取
也是调用修改方法或属性强,如果是私有设置一下权限即可