1. 泛型

  • 泛型是约束元素的类型
  • 泛型符号 T type E element K key V value,实际上泛型的符号可以任意定义
  • 繁星符号是一个占位符,先占着位置给引用类型
  • 泛型的符号、名称都没有明确的要求
  • 泛型符号可以应用在类、接口、方法上

1.2 泛型类

创建一个泛型类,代码如下:

// 在该处指定泛型的个数以及符号
public class Generic<A, B, C> {
    private A a;
    private B b;
    private C c;

    public Generic(){

    }

    public Generic(A a, B b, C c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public A method1(A a){
        return a;
    }
}

泛型类的指定需要在类名后指定,如上,指定了 <A,B,C> 后,就在这里进行了类型的占位,在该类中就可以将 A,B,C 当做已经存在的类型进行使用,如传递参数到方法上,指定方法的返回值类型等等。

注意:泛型类中的泛型不能定义在类中的静态方法中,静态方法不能使用泛型符号

静态方法与非静态方法区别如下:

静态方法:是使用 static 关键字修饰的方法,又叫类方法.属于类的,不属于对象, 在实例化对象之前就可以通过类名.方法名调用静态方法。 (静态属性,静态方法都是属于类的,可以直接通过类名调用)。

  1. 在静态方法中,可以调用静态方法,不能调用非静态方法。
  2. 在静态方法中,可以引用类变量(即,static修饰的变量),不能引用成员变量(即,没有static修饰的变量)。
  3. 在静态方法中,不能使用super和this关键字
  4. 静态方法的生命周期跟相应的类一样长,静态方法和静态变量会随着类的定义而被分配和装载入内存中。一直到线程结束,静态属性和方法才会被销毁。(也就是静态方法属于类)

**非静态方法:**是不含有static关键字修饰的普通方法,又称为实例方法,成员方法。属于对象的,不属于类的。(成员属性,成员方法是属于对象的,必须通过new关键字创建对象后,再通过对象调用)。

  1. 在普通方法中,可以调用普通方法,可以调用静态方法
  2. 在普通方法中,可以引用类变量和成员变量
  3. 在普通方法中,可以使用super和this关键字
  4. 非静态方法的生命周期和类的实例化对象一样长,只有当类实例化了一个对象,非静态方法才会被创建,而当这个对象被销毁时,非静态方法也马上被销毁。(也就是非静态方法属于对象)

测试如下:

public class Test1 {
    public static void main(String[] args) {
        // 指定泛型中的类型为具体类型
        Generic<String, Integer, Double> generic = new Generic<>();
        System.out.println(generic.method1("abcc"));

      	// 如果不对类型进行指定,那么泛型中定义的类型都是Object类型
        Generic generic1 = new Generic();

    }
}

1.2 泛型接口

接口中也可以定义泛型,接口中一旦定义了泛型后,就需要将其进行实现,如定义了以下的泛型接口,

public interface GenericInterface<A, B, C> {
  
    A method1(A a);
    
    void method2(B b);
    
    void method3(C c);
}

泛型接口的实现有以下几种方式。

1.2.1 实现类直接确定

直接在实现接口的时候就将其泛型的类型进行确定,代码如下,

public class Generic implements GenericInterface<String, Integer, Double> {

    @Override
    public String method1(String s) {
        return s;
    }

    @Override
    public void method2(Integer integer) {

    }

    @Override
    public void method3(Double aDouble) {

    }
}

在上述代码的第一行,实现了对接口泛型的指定,将泛型定义的 A,B,C 类型进行了指定,之后就可以进行正常使用了。

如果类在实现接口的时候不对其进行类型的指定,那么其就都是 Object 类型,如

public class Generic implements GenericInterface {

    @Override
    public Object method1(Object o) {
        return null;
    }

    @Override
    public void method2(Object o) {

    }

    @Override
    public void method3(Object o) {

    }
}

1.2.2 实现类同样带泛型

也可以在实现接口的时候将其泛型同样写到类上,这样,相当于就是一个泛型类了,示例如下,

public class Generic<A, B, C> implements GenericInterface<A, B, C> {

    @Override
    public A method1(A a) {
        return a;
    }

    @Override
    public void method2(B b) {

    }

    @Override
    public void method3(C c) {

    }
}

测试同泛型类的测试即可。

1.3 泛型方法

泛型方法需要在方法的 public 后面加上 <T> ,这里的 T 为传入的泛型的名称,有多少个就写多少个,如下所示,

public class Generic {

    public <T> void method1(T t){
        System.out.println(t);
    }
}

当然,也可以将返回值设为泛型,如下

public class Generic {

    public <T> T method1(T t){
        System.out.println(t);
      	return t;
    }
}

同时,静态方法也能够使用泛型,该泛型是传入的泛型,并不是类的泛型,静态方法中是不可以使用类的泛型的,

public class Generic {

    public static <T> T method1(T t){
        System.out.println(t);
      	return t;
    }
}

测试如下:

public class Test1 {
    public static void main(String[] args) {
        // 指定泛型中的类型为具体类型
        Generic generic = new Generic();
        generic.method1("12");
    }
}

1.4 泛型上下限

1.4.1 类型通配符

类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List,List 等所有 List<具体类型实参> 的父类,如下示例,

public class Test1 {
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        getData(name);
        getData(age);
        getData(number);

    }

  	// 使用类型通配符
    public static void getData(List<?> data) {
        System.out.println("data :" + data.get(0));
    }
}

1.4.2 泛型上下限

比如创建了一个抽象类 Animal 当做父类,有两个子类 Cat 以及 DogAnimal 类如下:

public abstract class Animal {
    public abstract void eat();
}

Dog 子类如下:

public class Dog extends Animal{

    @Override
    public void eat() {
        System.out.println("狗喜欢吃骨头");
    }
}

Cat 子类如下:

public class Cat extends Animal{

    @Override
    public void eat() {
        System.out.println("猫喜欢吃鱼");
    }
}

我们定义喂食的方法如下,

public class Generic {

    public void feed(List<Animal> animals){
        for(Animal animal: animals){
            animal.eat();
        }
    }
}

如果我们想要将一个狗的集合定义的时候传入到 feed 函数中,那么就会报错,如下,

public class Test1 {
    public static void main(String[] args) {
        List<Dog> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Dog());

        Generic generic = new Generic();
      	// 这里会报错
        generic.feed(animals);
    }

}

如果我们需要能够在泛型中定义后能够添加 Animal 类的子类到泛型中,那么能够怎么做呢?

如果将 feed 函数里面的形参 List<Animal> animals 改为 List<?> animals ,那么就是来者不拒了,什么类型都可以接收,就并非是我们的目的了。

这就涉及到了泛型的上下限了。

泛型的上限如下,

public class Generic {

  	// 设置泛型的上限,表示这个List里面的值是Animal及Animal的所有子类、孙类等
    public void feed(List<? extends Animal> animals){
        for(Animal animal: animals){
            animal.eat();
        }
    }
}

测试如下,

public class Test1 {
    public static void main(String[] args) {
        List<Animal> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Dog());
        animals.add(new Cat());

        Generic generic = new Generic();
        generic.feed(animals);
    }

}

也可以设置泛型的下限,

public class Generic {

  	// 设置泛型的下限,表示这个List里面的值是Animal及Animal的父类等
    public void feed(List<? super Animal> animals){
        for(Animal animal: animals){
            animal.eat();
        }
    }
}

2. 反射

反射就是加载类,并允许以编程的方式解剖类中的各个成分。

反射能够获取的东西:

  1. 加载类,获取类的字节码:Class对象
  2. 获取类的构造器:Constructor对象
  3. 获取类的成员变量:Field对象
  4. 获取类的成员方法:Method对象

我们创建一个 Student 类,类的代码如下:

public class Student {

    private String name;
    private int age;


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
  
  	@Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.1 加载类

反射的第一步就是加载类,获取类的字节码,将其加载为 Class 对象,加载类的方式有三种。

我们加载Student 类的方式有如下三种,

public class Test1 {
    public static void main(String[] args) throws Exception {

        // 加载类方式一
        Class c1 = Student.class;
        System.out.println("全类名:" + c1.getName());// 获取全类名
        System.out.println("类简名" + c1.getSimpleName());// 获取简名

        // 加载类方式二
        Class c2 = Class.forName("com.mr.study.Student");//这里必须是全类名
        System.out.println(c1 == c2);

        // 加载类方式三
        Student s = new Student();
        Class c3 = s.getClass();
        System.out.println(c2 == c3);
    }
}

2.2 获取构造器

2.2.1 获取构造器

获取构造器的方法如下:

方法名说明
Constructor<?>[] getConstructors()获取全部构造器(只有public部分)
Constructor<?>[] getDeclaredConstructors()获取全部构造器(只要存在就能获取)
Constructor<T> getConstructor(Class<?>...paramaterTypes)获取某个构造器(只有public部分)
Constructor<T> getDeclaredConstructor(Class<?>...paramaterTypes)获取某个构造器(只要存在就能获取)

我们以上述的 Student 类为加载类,获取其构造器,代码如下,

public class Test1 {
    public static void main(String[] args) throws Exception {

        // 1.加载类
        Class c1 = Student.class;
        // 2.获取类的全部构造器
        Constructor[] constructors = c1.getDeclaredConstructors();
        // 3.遍历数组中的每个构造器对象
        for (Constructor constructor: constructors){
            // 输出构造器名称和参数个数
            System.out.println(constructor.getName() + "---->" + constructor.getParameterCount());
        }

        //获取某个构造器
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
        System.out.println(constructor.getName() + "---->" + constructor.getParameterCount());
    }

}

2.2.2 使用构造器

获取了构造器后,我们便要使用构造器,使用构造器的方法如下

Constructor提供的方法说明
T newInstance(Object... initargs)调用构造器对象表示的构造器,并初始化对象
public void setAccessible(boolean flag)设为 true ,表示禁止检查访问控制(暴力反射)

使用构造器初始化一个对象代码如下,

public class Test1 {
    public static void main(String[] args) throws Exception {

        // 1.加载类
        Class c1 = Student.class;

        // 2.获取某个构造器
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);

        // 3.设置禁止访问权限,这样即是是private类型的构造器也能访问到,称为暴力反射
        constructor.setAccessible(true);

        // 4.使用构造器初始化
        Student student = (Student)constructor.newInstance("张三", 24);
        System.out.println(student);
    }
}

2.3 获取成员变量

2.3.1 获取成员变量

获取成员变量的方法如下:

方法名说明
public Field[] getFields()获取全部成员变量(只有public部分)
public Field[] getDeclaredFields()获取全部成员变量(只要存在就能获取)
public Field getField(String name)获取某个成员变量(只有public部分)
public Field getDeclaredField(String name)获取某个成员变量(只要存在就能获取)

我们以上述的 Student 类为加载类,获取其成员变量,代码如下,

public class Test1 {
    public static void main(String[] args) throws Exception {

        // 1.加载类
        Class c1 = Student.class;

        // 2.获取全部成员变量
        Field[] fileds = c1.getDeclaredFields();

        // 3.遍历全部成员变量
        for (Field filed : fileds) {
            System.out.println(filed.getName() + " : " + filed.getType());
        }
    }
}

2.3.2 使用成员变量

获取了成员变量后,我们便要使用成员变量,使用成员变量的方法如下

方法说明
void set(Object obj, Object value)赋值
Object get(Object obj)取值
public void setAccessible(boolean flag)设为 true ,表示禁止检查访问控制(暴力反射)

使用成员变量赋值并取值成员变量的代码如下,

public class Test1 {
    public static void main(String[] args) throws Exception {

        // 1.加载类
        Class c1 = Student.class;

        // 2.获取某个成员变量
        Field fname = c1.getDeclaredField("name");
        Field fage = c1.getDeclaredField("age");

        // 3.创建一个对象
        Student s = new Student("张三", 25);

        // 4.禁止访问权限
        fname.setAccessible(true);
        fage.setAccessible(true);

        // 5.使用get和set方法
        fname.set(s, "李四");//传入需要set的对象以及值

        String sName = (String) fname.get(s);
        int sAge = (Integer) fage.get(s);

        System.out.println("s.name: " + sName);
        System.out.println("s.age: " + sAge);
    }
}

2.4 获取成员方法

2.4.1 获取成员方法

获取成员方法的方法如下:

方法名说明
public Method[] getMethods()获取全部成员方法(只有public部分)
public Method[] getDeclaredMethods()获取全部成员方法(只要存在就能获取)
public Method getMethod(Class<?>...paramaterTypes)获取某个成员方法(只有public部分)
public Method getDeclaredMethod(Class<?>...paramaterTypes)获取某个成员方法(只要存在就能获取)

我们以上述的 Student 类为加载类,获取其成员方法,代码如下,

public class Test1 {
    public static void main(String[] args) throws Exception {

        // 1.加载类
        Class c1 = Student.class;

        // 2.获取全部成员变量
        Method[] methods = c1.getDeclaredMethods();

        // 3.遍历全部成员变量
        for (Method method : methods) {
            System.out.println(method.getName() + " : " + method.getParameterCount() + " : " + method.getReturnType());
        }
    }
}

2.4.2 使用成员方法

获取了成员方法后,我们便要使用成员方法,使用成员方法的方法如下

方法说明
public Object invoke(Object obj, Objecct... args)赋值
public void setAccessible(boolean flag)设为 true ,表示禁止检查访问控制(暴力反射)

使用构造器赋值并取值成员变量的代码如下,

public class Test1 {
    public static void main(String[] args) throws Exception {

        // 1.加载类
        Class c1 = Student.class;

        // 2.获取某个成员方法
        Method getAge = c1.getDeclaredMethod("getAge");
        Method setAge = c1.getDeclaredMethod("setAge", int.class);//方法名和传参类型

        // 3.创建一个对象
        Student s = new Student("张三", 25);

        // 4.禁止访问权限
        getAge.setAccessible(true);
        setAge.setAccessible(true);

        // 5.使用invoke方法
        int sAgeBefore = (Integer) getAge.invoke(s);
        Object a = setAge.invoke(s, 35);
        int sAgeAfter = (Integer) getAge.invoke(s);

        System.out.println("s.age Before: " + sAgeBefore);
        System.out.println("s.age After: " + sAgeAfter);
    }
}

更多推荐