一、泛型
泛型是 Java 中另一个使用非常广泛的特性,泛型中的「泛」指的是参数类型不固定, 也就是说什么数据类型都可以,它还有另一个名字,就是「参数化类型」——也就是说不仅 数据本身是参数,数据类型也可以被指定为参数——类、接口或者方法可以被不同类型的参数所重用。你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。
1、泛型类
通过泛型可以完成对一组类的操作对外开放相同的接口
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
//可以传入类型不同的参数
Generic generic = new Generic("1");
Generic generic1 = new Generic(1);
Generic generic2 = new Generic(1.11);
Generic generic3 = new Generic(false);
2、泛型接口
未传入泛型实参时,在声明类的时候,需将泛型的声明也一起加到类中。
public interface Generator<T> {
public T next();
}
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
传入实参时:
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
3、泛型方法
//可变参数
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型测试","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
4、通配符
- ?:表示通配符,它无法声明变量,但在泛型中却非常重要,它表示可以持有任何类型, 例如:List<?> list,表示 List 集合中可以存放任何数据类型(虽然它本来就可以这么做)。
- <? extends T>是上界通配符,表示只允许 T 及 T 的子类调用,所以如果传入的类型不 是 T 或 T 的子类,编译会报错。
- <? super T>是下界通配符,表示只允许 T 及 T 的父类调用,所以如果传入的类型不是 T 或 T 的父类,编译会报错。
练习1: 上界通配符有一个特性:只能获取,不能添加,请通过搜索引擎了解这句话的含义并 用代码实现它
答:上界只取不放是指当操作一个泛型参数为上界通配符<? extends T>的引用时,只能操作其返回泛型参数相关的方法(返回值为T的方法)而无法操作设置泛型参数相关(修改T)的方法。
练习2: 下界通配符有一个特性:只能添加,不能获取,请通过搜索引擎了解这句话的含义并 用代码实现它。
答:而下界只存不取则是指当操作一个泛型参数为下届通配符<? super T>的引用时,只能操作设置泛型参数相关(修改T)而无法操作返回泛型参数相关的方法(返回值为T的方法)。
package com.company;
public class Task08_01 {
public static void main(String[] args){
/*
* 上界<? extends T>:不能往里存,只能往外取
* <? extends Fruit>会使盘子里放东西的set()方法失效,但是get()方法还有效
* */
Plate<? extends Fruit> p1 = new Plate<Apple>(new Apple());
/*
* p.set(new Fruit()); 报错
* p.set(new Fruit());报错
*/
//读取出来的东西只能存放在Fruit或者它的基类里
Fruit newFruit1 = p1.get();
Object newFruit2 = p1.get();
//Apple newFruit3 = p.get();报错
/*
* 下界<? super T>:不影响往里存,但是往外取的只能放在Object对象里
* <? super Fruit>:get()方法获得的对象只能放在Object对象里,set()方法正常
* */
Plate<? super Fruit> p2 = new Plate<Fruit>(new Fruit());
//存入元素正常
p2.set(new Fruit());
p2.set(new Apple());
//取出来的东西只能放在Object对象中
Object newFruit4 = p2.get();
}
static class Fruit{
}
static class Apple extends Fruit{
}
static class Plate<T>{
private T item;
public Plate(T t){
item = t;
}
public void set(T t){
item = t;
}
public T get(){
return item;
}
}
}
总结:
- 频繁往外读取内容的,适合用上界Extends。
- 经常往里插入的,适合用下界Super。
二、反射
Java 有一种特性,能够在程序运行时获取某个类对象的所有的属性、方法、包信息和 注解等等,并且还能改变对象的属性值、调用对象方法、验证注解信息,这种动态获取信息、 动态改变属性值和动态调用方法的能力,就是反射(Reflection)。反。反射是将类中各个成分映射成各个对象。
1、获取Class对象
- 通过类名获取:类名.class
- 通过对象获取:对象名.getClass();
- 通过全类名获取:Class.forName(类的全名),这里的类的全名指的是包括包名在内的 对象名称,例如:net.csdn.java.User,就是 User 类的全名
2、获取构造方法
- public Constructor[] getConstructors():获取所有公有的构造方法
- public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
- public Constructor getConstructor(Class… parameterTypes):获取单个的"公有的"构造方法
- public Constructor getDeclaredConstructor(Class… parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有
3、获取成员变量
- Field[] getFields():获取所有的"公有字段"
- Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有
- public Field getField(String fieldName):获取某个"公有的"字段
- public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
- Field --> public void set(Object obj,Object value):设置字段的值
**练习:**使用反射解析某个你创建的类,把类中所有的属性、方法、接口都提取出来。要求: 通过三种不同的方式获取 Class 对象; 修改解析出来的类的属性值,然后再给类添加新的属性; 调用类方法(包括私有方法、静态方法和构造方法),执行后打印结果; 解析出该类的父类对象,并同样修改其父类的属性值并调用父类的方法。
package com.company;
import java.awt.datatransfer.FlavorEvent;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Task09_反射 {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//第一种方式获得Class对象
Student student1 = new Student();
Class stuClass = student1.getClass();
//第二种方式获得Class对象
Class stuClass2 = Student.class;
//第三种方式获得Class对象
try{
Class stuClass3 = Class.forName("com.company.Student");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
//获取Student类的所有属性
System.out.println("获取所有属性:");
Field[] fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray)
System.out.println(f);
//为属性设置值
System.out.println("为属性设置值:");
Field f = stuClass.getField("name");
Object obj = stuClass.getConstructor().newInstance();//产生Student对象
f.set(obj,"FXW");
Student stu = (Student)obj;
System.out.println("验证名字"+stu.name);
//调用构造方法
System.out.println("调用构造方法");
Constructor con = stuClass.getConstructor(null);
System.out.println(con);
Object obj2 = con.newInstance();
//调用私有方法
System.out.println("调用私有方法");
Method m = stuClass.getDeclaredMethod("show",int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj,20);
System.out.println(result);
}
}
class Student{
public String name;
protected int age;
char sex;
private String number;
public Student(){
System.out.println("共有无参构造方法");
}
public Student(String name,int age){
System.out.println("共有有参构造方法");
}
private String show(int age){
System.out.println("私有方法");
return "a";
}
}
三、注解
1、代码注解:作用在代码上
- @Override:检查该方法是否是重写方法,如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误
- @Deprecated:标记过时方法,如果使用该方法,会报编译警告
- @SuppressWarnings:指示编译器去忽略注解中声明的警告
2、元注解:作用在其他注解上
- @Retention:标识注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在 运行时可以通过反射访问
- @Documented:标记注解是否包含在用户文档中;
- @Target:标记注解应该是哪种 Java 成员
- @Inherited:标记注解是继承于哪个注解类(默认注解并没有继承于任何子类)
- @SafeVarargs:Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数 调用产生的警告
- @FunctionalInterface:Java 8 开始支持,标识一个函数式接口
- @Repeatable:Java 8 开始支持,标识注解可以在同一个声明上使用多次
练习1: 自定义一个 ElementType 类别为 METHOD,RetentionPolicy 类别为 RUNTIME 的 注解,任务要求:创建一个类,类的方法使用自定义的注解; 通过反射解析类注解,判断该方法是否有注解,如果有则打印出注解的内容。
注:
- RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
- RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
- RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
package com.company;
import java.lang.annotation.*;
import java.lang.reflect.Method;
public class Task10_01 {
public static void main(String[] args) throws NoSuchMethodException {
//获取类
Class stuClass = Student.class;
//获取方法
Method method = stuClass.getMethod("show");
//获取注解对象
M m = method.getAnnotation(M.class);
System.out.println(m.value());
}
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
public @interface M {
String value() default "这是默认注释内容";
}
class Student{
@M
public void show(){
System.out.println("a");
}
}
}
练习2: 自定义一个 ElementType 类别为 FIELD,RetentionPolicy 类别为 CLASS 的注解, 任务要求: 创建一个类,给类的某个字段使用自定义的注解; 通过反射解析类注解,判断该方法是否有注解,如果有则打印出注解的内容。
package com.company;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
public class Task10_02 {
public static void main(String[] args) throws NoSuchFieldException {
Class stuClass = Student.class;
Field f = stuClass.getDeclaredField("name");
M m = f.getAnnotation(M.class);
System.out.println(m.value());
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface M {
String value() default "这是默认注释内容";
}
public class Student{
@M
public String name;
}
}
总结:
@Target(ElementType.TYPE) //接口、类、枚举
@Target(ElementType.FIELD) //字段、枚举的常量
练习3: 模仿上面的嵌套注解,除了可以字段类型的注解外,再加上字段长度和是否可以为空 的注解,并加入到@TableColumn 注解中去
package com.company;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class Task10_03 {
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnLength{
int value() default 0;
}
public @interface ColumnEmptyOrNot{
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnLength{
boolean value() default true;
}
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableColumn{
ColumnLength columnlength() default @ColumnLength;
ColumnEmptyOrNot columnEmptyOrNot() default @ColumnEmptyOrNot;
}
}
练习4: 自定义嵌套注解,用来描述用户类中的「岗位」字段。
第一层注解定义岗位所在的部门
第二层注解定义岗位名称和描述。
//岗位名称
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PostName {
String postName() default "";
}
//岗位描述
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PostDescription {
String postDescription() default "";
}
//岗位所在部门
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Department {
String departmentName() default "";
PostName postName() default @PostName;
PostDescription postDescription() default @PostDescription;
}
public class Position {
@Department(postName = @PostName , postDescription = @PostDescription)
private String postName;
private String postDescription;
}