java中函数式接口、Stream流、方法引用、junit单元测试、反射、注解

函数式接口:

在java中有且仅有一个抽象方法的接口称为函数式接口,但是可以包含其它的默认的或静态的方法。

格式:

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数);
    // 其他非抽象方法
}

函数式接口:

// 1.函数式接口:有且仅有一个抽象方法的接口,当然接口中可以包含其他默认、静态、私有方法
// 2.为了确保接口是函数式接口,可以写注解:@Functional Interface,它可以自动检测是否为函数式接口,当有多个抽象方法时就会报错。
@FunctionalInterface
public interface MethodsInterFace {
    public abstract void sayHi();
    // void eat();
}

接口实现类:

public class MethodsInterFaceTest implements MethodsInterFace {
    @Override
    public void sayHi(){
        System.out.println("重写了抽象方法sayHi");
    };
}

测试使用函数式接口(函数式接口作为参数使用):

public class Demo {
    // 1.函数式接口的使用:一般可以作为方法的参数和返回值类型:
    public static void testMethodsInterFace(MethodsInterFace mi){
        mi.sayHi();
    };
    public static void main(String[] args){
        // 创建一个接口实现类对象传给testMethodsInterFace方法使用:
        MethodsInterFaceTest mit = new MethodsInterFaceTest();
        testMethodsInterFace(mit);
        // 2.可以在方法调用时直接传递接口的匿名内部类:
        testMethodsInterFace(new MethodsInterFace(){
            @Override
            public void sayHi(){
                System.out.println("匿名内部类重写了抽象方法sayHi");
            };
        });
        // 3.方法的参数是一个函数式接口时,可以使用Lambda表达式:Lambda表达式可以较匿名内部类节省内存,但是原理是不太一样的,Lambda有延迟
        testMethodsInterFace(()->{
            System.out.println("使用Lambda表达式重写了接口的抽象方法");
        });
        // 简化Lambda表达式:
        testMethodsInterFace(()->System.out.println("使用简化Lambda表达式重写了接口的抽象方法"));
    };
}

函数式接口做饭返回值使用: 如果一个方法的返回值类型是一个函数式表达式,那么就可以直接返回一个Lambda表达式

import java.util.Arrays;
import java.util.Comparator;

public class ComparatorDemo {
    // 1.实现一个方法,该方法返回java.util.Comparator接口类型作为字符串排序时使用(Comparator不仅仅可以用来做排序,它是一个比较器,比较灵活)
    public static Comparator<String> getSortResult(){
        //return new Comparator<String>(){
        //    @Override
        //    public int compare(String s1,String s2){
        //        // 按照字符串长度降序排序:
        //        return s1.length() - s2.length();
        //    };
        //};
        // 方法返回一个函数式接口,可以使用Lambda简化:
        return (s1,s2)-> s1.length() - s2.length();
    };
    public static void main(String[] args){
        String[] arr = {"123","0000","0"};
        System.out.println("1排序前的顺序:" + Arrays.toString(arr)); // 1排序前的顺序:[123, 0000, 0]
        Arrays.sort(arr,getSortResult());
        System.out.println("2排序后的顺序:" + Arrays.toString(arr)); // 2排序后的顺序:[0, 123, 0000]
    };
}

常用函数式接口简介: JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。

Supplier接口: 该接口包含一个无参数方法get,get返回一个前面泛型指定类型的数据 ,被称为生产型接口,前面泛型指定什么数据类型,就会返回什么类型数据。

import java.util.function.Supplier;

public class SupplierDemo {
    // 1.定义一个方法,方法参数传递一个Supplier<T>接口,泛型执行String,get方法就会返回一个String
    public static String getString(Supplier<String> sp){
        return sp.get();
    };
    // 2.练习:使用Supplier求数组元素中最大值:
    public static int getMaxNum(Supplier<Integer> sp){
        return sp.get();
    };


    public static void main(String[] args){
        // 1-1:方法的参数是一个函数式接口,可以使用lambda表达式:
        String str = getString(()-> "一个字符串");
        System.out.println(str); // 一个字符串

        // 2-1:定义一个int类型的数组:
        int[] arr = {1,5,2,3};
        int maxValue = getMaxNum(() -> {
            int maxTemp = arr[0];
            for (int i : arr) {
                if (maxTemp<i){
                    maxTemp = i;
                };
            }
            return maxTemp;
        });
        System.out.println("数组中最大值:" + maxValue); // 数组中最大值:5
    };
}

Consumer接口: Consumer接口刚好与Supplier接口相反,Supplier接口接口用于生产一个数据,而Consumer用于消费一个数据,给一个指定类型的数据将这个数据使用掉。

import java.util.Locale;
import java.util.function.Consumer;

public class ConsumerDemo {
    // 1.Consumer接口用于消费一个指定类型的数据,泛型指定什么类型,accept方法就消费什么类型的数据,具体怎么消费,需要自定义(打印,输出,计算等)
    // 定义一个方法:方法的参数1传递一个字符串的姓名,方法的参数2传递Consumer接口消费字符串的姓名:
    public static void useName(String names, Consumer<String> cn){
        cn.accept(names);
    };
    // 2.Consumer接口的默认方法:andThen,andThen将多个Consumer组合起来再对数据进行消费:
    // 定义一个方法,方法传递自个字符串和两个Consumer接口,接口泛型使用字符串:
    public static void useAndThen(String names, Consumer<String> cn1, Consumer<String> cn2){
        // cn1.accept(names);
        // cn2.accept(names);
        // 使用andThen代替上面方法:
        cn1.andThen(cn2).accept(names);
    };
    public static void main(String[] args){
        // 1.测试useName
        useName("kuhai123",cn -> {
            System.out.println(cn); // kuhai123
            // 翻转字符串:链式编程多次调用
            String reNames = new StringBuffer(cn).reverse().toString();
            System.out.println(reNames); // 321iahuk
        });
        // 2.测试useAndThen
        useAndThen("kuHai",cn1 -> System.out.println(cn1.toUpperCase()), cn2 -> System.out.println(cn2.toLowerCase()));
    };
}

Predicate接口:有时候需要对某种数据类型进行判断,从而得到一个boolean值结果,这时候可以使用Predicate接口。

import java.util.function.Predicate;

public class PredicateDemo {
    // 1.Predicate接口用于判断数据是否满足某个条件,返回布尔值,其中包含一个方法test做判断:
    // 定义一个方法:参数传递一个字符串和一个Predicate接口,接口的泛型使用String,使用接口中的方法test对字符串进行判断,并返回判断结果:
    public static boolean isString(String str, Predicate<String> ps){
        return ps.test(str);
    };

    // 2.Predicate接口中有一个and方法,表示并且的意思:
    // 定义一个方法接收两个Predicate接口和一个字符串,接口泛型指定为字符串,对字符串使用两个接口做判断,并返回判断结果:
    public static boolean isAllSatisfy(String s, Predicate<String> p1, Predicate<String> p2){
        // return p1.test(s) && p2.test(s);
        return p1.and(p2).test(s);
    };

    // 3.Predicate接口中有一个or方法,表示或者的意思:
    // 定义一个方法接收两个Predicate接口和一个字符串,接口泛型指定为字符串,对字符串使用两个接口做判断,并返回判断结果:
    public static boolean isSomeSatisfy(String s, Predicate<String> p1, Predicate<String> p2){
        // return p1.test(s) || p2.test(s);
        return p1.or(p2).test(s);
    };

    // 4.Predicate接口中有一个negate方法,表示取反的意思:
    // 定义一个方法:参数传递一个字符串和一个Predicate接口,接口的泛型使用String,使用接口中的方法test对字符串进行判断,并返回判断结果:
    public static boolean isEmptyStr(String s, Predicate<String> p1){
        // return !p1.test(s);
        return p1.negate().test(s);
    };

    public static void main(String[] args){
        // 1.测试1:判断字符串长度是否大于0:
        String tempStr = "abcde";
        // 调用方法做校验:
        boolean rs = isString(tempStr,(s)->s.length() > 0);
        System.out.println(rs); // true

        // 2.测试2: 判断字符串长度是否大于2并且小于5:
        String str2 = "123456";
        boolean s2 = isAllSatisfy(str2,(s)->s.length() > 2,(s)->s.length() < 5);
        System.out.println(s2); // false

        // 3.测试3: 判断字符串长度是否小于5或大于8:
        String str3 = "1234";
        boolean s3 = isSomeSatisfy(str3,(s)->s.length() < 5,(s)->s.length() > 8);
        System.out.println(s3); // true

        // 4.测试4: 判断字符串长是否为空字符串
        String str4 = "";
        boolean s4 = isEmptyStr(str4,(s)->s != "");
        System.out.println(s4); // true
    };
}

Function接口:用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

import java.util.function.Function;

public class FunctionDemo {
    // 1.Function接口用来根据一个类型的数据得到另一个类型的数据,其中主要方法apply:
    // 定义一个方法将字符串转换为Integer类型:
    public static void toNumber(String s, Function<String,Integer> f){
        // Integer n = f.apply(s);
        int n = f.apply(s); // 自动拆箱
        System.out.println(n); // 123
    };

    // 2.andThen方法用来进行组和操作:
    // 定义一个方法将字符串转换为数字类型后加10后再转换为字符串:
    public static void addTen(String str, Function<String,Integer> f1, Function<Integer,String> f2){
        String st = f1.andThen(f2).apply(str);
        System.out.println(st); // 20
    };
    public static void main(String[] args){
        // 1.测试:将字符串转换为数字类型:
        String str = "123";
        toNumber(str,(String strs)->Integer.parseInt(strs));

        // 2.测试:将字符串加10后再返回:
        String s2 = "10";
        addTen(s2, st -> Integer.parseInt(st) + 10, n -> n + "");
    };
}
Stream流:

Strema流和io流是完全不一样的两种概念。Stream流用于对数组和集合做简化操作。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

// Stream流式模型:当对一个数组或集合的多个元素进行操作时,可以先拼一个模型:filter过滤 -> 映射map -> 跳过skip -> 统计count
public class StreamDemo {
    // 1.对集合中的元素进行过滤处理,常用的方法就是遍历处理,有的时候可能需要多个条件,此时有可能需要遍历多次,这样就会有点麻烦,此时可以使用Stream简化处理:
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("张一");
        list.add("张二二");
        list.add("李四");
        list.add("王五");
        // 2.集合中有一个方法stream,可以将集合转换为Stream流:Stream流有个filter方法,找到满足提交的数据,可以接收一个Lambda表达式,支持链式调用:
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 2).forEach(s -> System.out.println(s)); // 张一

        // 3.获取流的方式:1.所有的Collection集合都可以通过stream默认方法获取 2.Stream接口的静态方法of获取,of方法中接收一个数组
        Stream<Integer> ist = Stream.of(1,2,3,4);

        // 4.流模型的操作很丰富,常用api可分两类:1.延迟方法(返回值类型仍然是Stream接口自身类型的api,支持链式调用)2.终结方法(返回值类型不再是Stream接口类型的api,调用终结方法后不再支持链式调用,终结方法常用的有:count/forEach)

        // 5.Stream流中forEach方法:该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理,Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,进行消费数据;简单记忆:forEach遍历流中的每一个元素,对每个元素进行处理,调用了forEach方法后就不能在调用Stream的其他方法了
        Stream<String> slist = Stream.of("赵丽颖","迪丽热巴","霍元甲");
        slist.forEach(item -> System.out.println(item));

        // 6.Stream流中filter方法:filter方法将一个流转换为另一个子集流,该方法接收一个Predicate函数式接口做为筛选条件,可对元素进行筛选:
        Stream<Integer> ilist = Stream.of(1,2,3,4);
        Stream<Integer> list2 = ilist.filter(item -> item > 3);
        list2.forEach(item -> System.out.println(item)); // 4

        // 7.Stream流的特点:Stream流属于管道流,只能被消费一次,使用一次就不能被使用了,第一个Stream流调用完毕后就会流到下一个Stream上,而此时第一个Stream流已经使用完毕了,就会被关闭,再使用就会报错:
        // list2.forEach(item -> System.out.println(item)); // 抛出了异常

        // 8.Stream流中map方法:map将一个流中的元素映射到另一个流中,该方法接收一个Function接口,使用Function接口可将某个类型转换为另一个类型,做依依映射:
        Stream<Integer> isst = Stream.of(1,2,3,4);
        Stream<String> isst2 = isst.map(item -> item.toString());
        isst2.forEach(item -> System.out.println(item));

        // 9.Stream流中提供了count方法:count用于统计流中元素的个数,类似Collection当中的size,返回值类型为long类型,该方法是一个终结方法:
        Stream<Integer> listl = Stream.of(1,2,3,4);
        System.out.println(listl.count()); // 4
        // listl.forEach(item -> System.out.println(item)); // 使用过了,再使用会抛异常

        // 10.Stream流中的limit方法:limit方法用于截取前n个元素,n类型是long,返回的是新的流,支持链式调用:
        String[] arrs = {"元素1","元素2","元素3","元素4","元素5",};
        Stream<String> streamlist = Stream.of(arrs);
        Stream<String> streamlist2 = streamlist.limit(2);
        streamlist2.forEach(item -> System.out.println(item)); // 元素1 元素2

        System.out.println("-------------------");

        // 11.Stream流中的skip方法:skip方法用于跳过前n个元素返回剩下的元素,n类型是long,返回的是新的流,支持链式调用:(当传入的参数大于元素的个数时会得到一个长度为0的空流)
        String[] arrs2 = {"元素1","元素2","元素3","元素4","元素5",};
        Stream<String> ster = Stream.of(arrs2);
        Stream<String> ster2 = ster.skip(2);
        ster2.forEach(item -> System.out.println(item)); // 元素3 元素4 元素5

        // 12.Stream流中的concat静态方法:concat方法用于将两个流合并成一个流:
        Stream<String> l1 = Stream.of("1","2");
        Stream<String> l2 = Stream.of("3","4");
        Stream<String> l3 = Stream.concat(l1,l2);
        l3.forEach(item -> System.out.println(item)); // 1 2 3 4
    };
}
方法引用:

方法引用实际是对Lambda的优化,如:

Printable接口:

// 1.定义一个打印的函数式接口:
@FunctionalInterface
public interface Printable {
    // 打印字符串的抽象方法:
    void print(String s);
}

public class PrintDemo {
    // 2.定义一个方法传递Printable接口,对字符串进行打印:
    public static void printString(Printable p){
        p.print("打印内容:");
    };
    public static void main(String[] args){
        // 3.调用printString方法:
        printString(s -> System.out.println(s)); // 打印内容:

        // 3-1方法引入调用方法:Lambda表达式的目的,打印参数传递的字符串,把参数s传递给了System.out对象,调用out对象中的方法println对字符串输出,注意:1.System.out对象已经存在 2.println方法也已经存在,所以可以使用方法引入优化Lambda表达式(可以使用System.out直接引入方法println),如:
        printString(System.out::println); // 打印内容:
        // ::被称为引用运算符,而它所在的表达式被称为方法引用,如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么可以使用双冒号来引用该方法作为Lambda的代替:
    };
}

通过对象名引用成员方法:

定义一个包含成员方法的类:

public class MethodsRerObject {
    // 1.定义一个成员方法,传递字符串,把字符串按照大写输出:
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    };
}

通过对象名引用方法测试:

public class MethodsReferenceDemo {
    // 2.通过对象名引用成员方法:使用前提对象名是已经存在的,成员方法也是已经存在的,就可以是使用对象名来引用成员方法:
    // 定义一个方法,方法的参数传递Printable接口:
    public static void printString(Printable p){
        p.print("asda");
    };

    public static void main(String[] args){
        printString((s) -> {
            // 3.创建一个MethodsRerObject对象:
            MethodsRerObject obj = new MethodsRerObject();
            // 4.调用对象中成员方法:按照大写输出
            obj.printUpperCaseString(s); // ASDA
        });
        // 方法引用优化:
        MethodsRerObject obj = new MethodsRerObject();
        printString(obj::printUpperCaseString); // ASDA
    };
}

通过类名称引用静态方法:

@FunctionalInterface
public interface Calcable {
    // 1.定义一个抽象方法:传递一个整数,对整数进行绝对值计算:
    int calsAbs(int n);
}

public class StaticClassMethodsReferenceDemo {
    // 2.通过类名称引用静态成员方法:前提类已存在,静态方法已经存在
    // 定义一个方法,方法的参数传递要计算绝对值的整数和Calcable接口
    public static int methodsabs(int num, Calcable c){
        return c.calsAbs(num);
    };
    public static void main(String[] args){
        // 3.调用methodsabs方法:
        int rs = methodsabs(-5,cn -> Math.abs(cn));
        System.out.println(rs); // 5

        // 优化:通过类引用静态方法:
        int rs2 = methodsabs(-5,Math::abs);
        System.out.println(rs2); // 5
    };
}

通过super引用成员方法: 如果在继承关系中,当Lambda中需要出现super调用时,也可以使用方法引用进行代替,如:

@FunctionalInterface
public interface Geetable {
    void greet();
}
// 定义一个父类:
public class Human {
    // 定义一个方法:
    public void sayHai(){
        System.out.println("hi,我是human");
    };
}
// 定义子类,继承Human:
public class Man extends Human {
    // 子类重写sayHai方法:
    @Override
    public void sayHai(){
        System.out.println("hi,我是Man");
    };
    // 定义一个方法,参数是Greetable接口:
    public void method(Geetable g){
        g.greet();
    };
    // 定义一个show方法:在show方法中调用method接口:
    public void show(){
        // method(() -> {
        //     // 创建父类对象:
        //     Human hm = new Human();
        //     // 调用父类的syaHi方法:
        //     hm.sayHai(); // hi,我是human
        // });
        // 优化:因为有子父类关系,所以存在一个关键字super,代表父类,所以我们可以直接使用super调用父类的成员方法:
        // method(() -> super.sayHai());
        method(super::sayHai);
    };
    public static void main(String[] args){
        // 创建Man对象:并调用show方法:
        new Man().show();
    };
}

通过this引用成员方法: this代表当前对象,如果需要引用的方法,

// 定义一个富有的函数接口:
public interface Richable {
    // 购买的方法:
    void buy();
}

public class Husband {
    // 定义一个买房子的方法:
    public void buyHouse(){
        System.out.println("买房");
    };
    // 定义一个买车的方法,参数传递Richable接口:
    public void buyCar(Richable r){
        r.buy();
        //  System.out.println("买车");
    };
    // 定义一个非常高兴的方法:
    public void soHappy(){
        // 调用买车的方法:
        // buyCar(() -> {
        //     this.buyHouse();
        // });
        // 优化:
        buyCar(this::buyHouse);
    };

    public static void main(String[] args) {
        new Husband().soHappy();
    }
}

类的构造器引用: 由于构造器的名称和类名称完全一样,并不固定,所以构造器引用使用类名称::new 的格式表示,如:

// 定义一个类:
public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

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

// 定义一创建Person对象的函数式接口:
@FunctionalInterface
public interface PersonBuilder {
    // 定义一个方法,根据传递的姓名,创建Person对象:
    Person builderPerson(String name);
}

// 定义一个测试demo
public class Demo {
    // 定义一个方法,传递姓名和PersonBuilder接口,方法中通过姓名创建对象:
    public static void printName(String name,PersonBuilder p){
        Person ps = p.builderPerson(name);
        System.out.println(ps);
    };

    public static void main(String[] args) {
        // 调用printName方法:
        // printName("苦海123", (String name) -> {
        //     return new Person(name);
        // });
        // 优化:使用方法引用:
        printName("苦海123", Person::new);
    }
}

数组构造器引用:

数组也是Object的子类对象,所以同样具有构造器,只是语法对应到Lambda的使用场景中时,需要一个函数式接口:

// 定义一个创建数组的函数式接口:
@FunctionalInterface
public interface ArrayBuilder {
    // 定义创建int类型的数组的方法,参数传递数组的长度,返回创建好int类型的数组:
    int[] builderArray(int length);
}

public class ArrayBuildDemo {
    // 定义一个方法,方法的参数传递创建数组长度和ArrayBuilder接口,方法内部根据传递的长度使用ArrayBuild中的方法创建数组并返回:
    public static int[] createArray(int length,ArrayBuilder ab){
        return ab.builderArray(length);
    };

    public static void main(String[] args) {
        // 调用createArray方法:
        // int[] arr1 = createArray(5,(len) -> {
        //     return new int[len];
        // });
        // 优化:使用数组构造器引用:
        int[] arr1 = createArray(5,int[]::new);
        System.out.println(arr1.length);
    }
}

junit单元测试:

测试大概可以分为两类:

黑盒测试:不需要写代码,给输入值,看程序最终能否输出想要的结果。

白盒测试:需要写代码,关注程序的具体执行流程。

junit使用步骤:1.定义一个测试类(测试用例,类名推荐XXXTest,放在XXX.XXX.test包)2.定义测试方法:可独立运行,方法名推荐testXXX 3.给方法加@Test注解

import org.junit.After;
import org.junit.Before;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class CalculateTest {
    // 1.加测试注解
    @Test
    public void testAddnum(){
        // 2.创建需要测试累的对象:
        Calculate cs = new Calculate();
        // 3.调用对象的方法:
        int result = cs.addNum(5,3);
        System.out.println(result);
        // 点击编辑器左侧箭头即可执行此方法,无需写main函数,点哪个箭头,对应的函数会执行,如果方法有异常抛出,那么控制台会有爆红警告,报红表示测试失败

        // 4.断言:上面爆红只是语法上的错误,真要测试一个结果是否正确,那么需要借助Assert下的相关方法对输出的结果和想要的结果进行比较:
        Assert.assertEquals(8,result); // 当输出的结果和想要的结果不一样时,这里也会报红
    };

    // 2.初始化方法:用于申请资源等,加@Befoure注解,执行于@test前
    @Before
    public void init(){
        System.out.println("Before注解方法执行");
    };

    // 3.释放资源方法:所有测试方法都执行完后会自动执行的方法,在前面加@After注解即可,执行于@test后
    @After
    public void destory(){
        System.out.println("After注解方法执行");
    };
}
反射:

将类的各个组成部分封装为其他对象,这就是反射机制,反射的好处:可以在程序运行过程中操作这些对象,可以解耦,降低程序的耦合性,提高程序的可扩展性。

Person类文件:

public class Person {
    private String name;
    private int age;
    public String grad;

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

    public Person() {
    }

    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;
    }

    public String getGrad() {
        return grad;
    }

    public void setGrad(String grad) {
        this.grad = grad;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", grad='" + grad + '\'' +
                '}';
    }
    public void eat(){
        System.out.println("吃...");
    };
    public void eat(String foot){
        System.out.println("吃..." + foot);
    };
}

pro.properties配置文件:

# 1.配置文件:
# 定义一个类名:这里的类是项目中存在的Class文件
className=Person
# 定义一个方法名:这里的方法也是Class类文件中对应出现的方法
methodName=eat

测试反射技术:

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectDemo1 {
    // 获取class对象的方式:1.Class.forName("全类名"),将字节码文件加载进内存,返回class对象 2.类名.class,通过类名的属性class获取 3.对象.getClass(),通过Object类中定义的方法getClass获取
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IOException {
        // 1.Class.forName("全类名"),将字节码文件加载进内存,返回class对象,多用于配置文件
        Class cls1 = Class.forName("Person");
        System.out.println(cls1); // class Person
        // 2.类名.class,通过类名的属性class获取,多用于参数传递
        Class cls2 = Person.class;
        System.out.println(cls2); // class Person
        // 3.对象.getClass(),通过Object类中定义的方法getClass获取,多用于对象获取字节码的方式
        Person p1 = new Person();
        Class cls3 = p1.getClass();
        System.out.println(cls3); // class Person
        System.out.println(cls1 == cls2 && cls2 == cls3); // true,同一个.class字节码文件在一次程序运行过程中只被加载进内存一次。


        System.out.println("-----------------------");


        // class对象功能:1.获取所有的成员变量 2.获取所有的构造方法 3.获取所有成员方法 4.获取类名
        // 获取Person的class对象:
        Class classObj = Class.forName("Person");
        // 1-1.获取所有被public修饰的成员变量: getFields用于获取public修饰的成员变量
        Field[] flist = classObj.getFields();
        for (Field item : flist) {
            System.out.println(item); // 这里获取到的结果:public java.lang.String Person.grad
        }
        // 1-2.获取指定被public修饰的成员变量: getField用于获取指定名称的被public修饰的成员变量
        Field fie = classObj.getField("grad");
        System.out.println(fie); // 这里获取到的结果:public java.lang.String Person.grad
        // 1-3.获取成员变量的作用:可以获取和设置成员变量对应的值:get用于获取成员变量的值,get方法接收一个对象实例 set用于设置成员对象的值,接收两个变量,一是对象实例,二是成员变量对应要设置的值
        Person p2 = new Person();
        fie.set(p2,"10班");
        Object p = fie.get(p2);
        System.out.println(p); // null,初识化为null值,前面加了set,所以加set后的值为10班
        // 1.4获取所有的成员变量:getDeclaredFields()获取所有的成员变量,不被修饰符限制
        Field[] flists = classObj.getDeclaredFields();
        for (Field item : flists) {
            System.out.println(item); // 这里获取到的结果:private java.lang.String Person.name 、 private int Person.age 、 public java.lang.String Person.grad
        }
        // 1-5.获取指定的成员变量:getDeclaredField()获取指定名称的成员变量,不被修饰符锁限制,即使是私有的也是可以被操作的,但是会抛异常,只要使用setAccessible忽略异常就可以正常运行
        Field fits = classObj.getDeclaredField("name");
        fits.setAccessible(true); // 获取访问权限修饰符的安全检查:true为忽略,false为不忽略
        Person p3 = new Person("苦海",18,"11班");
        fits.set(p3,"kuhai123");
        System.out.println(fits.get(p3)); // 原本是:苦海,但是前面重新设置了kuhai123,所以这里打印:kuhai123

        // 2-1.获取对象构造器:getConstructor用来获取构造器函数,根据可变参数获取对应的构造器:
        Constructor cn = classObj.getConstructor(String.class, int.class,String.class);
        System.out.println(cn);
        // 构造方法的作用:用来创建对象
        Object p4 = cn.newInstance("苦海123",16,"6班");
        System.out.println(p4.toString()); // Person{name='苦海123', age=16, grad='6班'}

        // 3-1.获取方法:getMethod用来获取指定名称的方法:
        Method eats = classObj.getMethod("eat",String.class); // 第一个参数为方法名,后面可接收对应类型重载方法
        // 获取方法的作用:使用invoke调用方法:
        Person p5 = new Person();
        eats.invoke(p5,"苹果"); // 吃...被打印了,如果获取方法时传递了对应参数的重载方法,则第二个参数开始为重载方法所需的参数,传递了参数的值:吃...苹果
        // 3-2.获取所有public修饰的方法:
        Method[] methods = classObj.getMethods();
        for (Method item : methods) {
            System.out.println(item); // 这里获取到的结果:这里除了对象自身的方法外,还有继承于Object的一些方法,getName()可获取方法名称:
            System.out.println(item.getName());
        }
        // 方法获取到也是可以执行的,通过invoke(接收多个对象)

        System.out.println("------------******--------------");
        // 案例:实现一个可以定义任意类和执行该类的任意方法的框架:
        // 实现步骤:1.将要创建的对象的全类名和所需要执行的方法定义在配置文件中 2.在程序中加载读取配置文件 3.使用反射技术来加载类文件进内存 4.执行方法
        // 1.在项目包文件夹下定义一个:pro.properties配置文件,文件名可以自定义,但是后缀是:.properties结尾,定义好配置文件后,可以通过以下方法加载文件:
        // 1-1.创建Properties对象
        Properties pro = new Properties();
        // 1-2.调用pro的load方法加载配置文件到内存,并将其转换为一个双链集合:
        // 1-2-1.通过getClassLoader()获取类字节码文件的加载器:
        ClassLoader classLoader = ReflectDemo1.class.getClassLoader();
        // 1-2-2.可以借助ClassLoader里面的getResourceAsStream方法获取资源对应的字节流,这里和使用io流是一个意思,但是io获取就比较麻烦了
        InputStream isStrem = classLoader.getResourceAsStream("pro.properties");
        // 1-2-3.通过load方法加载字节流:
        pro.load(isStrem);
        // 2.获取配置文件中定义的数据:
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        // 3.加载该类进内存,返回一个Class对象:
        Class cls = Class.forName(className);
        // 4.创建对象:newInstance方法已被启用
        Object objs = cls.newInstance();
        // 5.获取方法对象:
        Method methObj = cls.getMethod(methodName);
        // 6.执行方法:
        methObj.invoke(objs);

        // 以上创建对象并执行对象方法的优势:可以不用改代码,只是修改配置文件就可以让一个新的类创建对象并调用其中的方法,不用再去测试代码是否有bug,项目庞大时,可扩展性强
    }
}
注解:

注解也叫元数据,它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。通过代码里标识的元数据让编译器能够实现基本的编译检查,如Override;通过代码里的注解生成doc文档;对代码进行分析等。说白了就是在写代码的时候加上特殊的注释,可以通过java相关命令自动生成开发文档以及代码的自校验等

生成开发文档:通过命令:javadoc 类文件名 ,可以生成html说明文档,如果报错,则修改编码为GBK即可

JDK预定义的一些注解:

@Override:检测被该注释标注的方法是否是继承自父类的。

@Deprecated:该注解表示已过时,表示某个内容已经过时,但是可以使用。

@SuppressWarnings:压制警告。

自定义注解:

自定义注解分两部分:1.元注解,就是写给自己的注释,可以不写 2.写个程序的注解,真正生效的注解,其格式:public @interface 注解名称 {}

注解本质:public interface MyAnno extends java.lang.annotation.Annotation {},可以看出实际就是一个接口,此接口中可以定义属性(接口中的抽象方法,必须有返回值,返回值类型:基本类型、枚举、注解)

元注解定义的一些注释:

@Target:描述注解所作用的位置

@Retention:描述注解被保留的阶段

@Documented:描述注解是否被抽取到api文档中

@Inherited:注解是否被子类继承

定义一个注解:

// 1.定义一个自定义注解MyAnno:(通过反编译可以查询自定义注解的源码,反编译步骤:1.先javac编译注解java文件 2.javap编译注解class字节码文件),通过反编译得到的结果:public interface MyAnno extends java.lang.annotation.Annotation {},注解本质就是一个接口
// 元注解定义:

import java.lang.annotation.*;
@Target(value={ElementType.METHOD,ElementType.TYPE}) // ElementType的值有:TYPE表示MyAnno注解只能注解在类上、METHOD可以作用域方法上、FIELD可以作用于成员变量上,多个类型可以同时添加,只需要用逗号隔开即可。
@Retention(RetentionPolicy.RUNTIME) // 这里也是一个枚举,但是一般自己定义的注解一般使用RUNTIME即可,表示当前秒数的注解会保留到class字节码文件中并被jvm读取到、 如果设置为CLASS则表示注释会保留到class字节码文件中,但不会被jvm读取到
@Documented // 加此注解表示当前的注解会被加载到api文档中
@Inherited // 加此注解表示子类继承该注解
public @interface MyAnno {
    public String show(); // 这里的抽象方法也可以叫做属性,方法的返回值是有要求的:基本类型、枚举、注解
     int show1() default 0; // 使用default可以给默认值,如果不给默认值,在使用注解时就要给值
}

在Person类中使用自定义类:

public class Person {
    public String name;
    // 2.使用自己定义的注解:后面加括号可以传值给注解,多个值用逗号隔开,如果不想赋值,那么定义的时候就要给默认值:
    @MyAnno(show = "hello", show1 = 8) // 如果注解只有一个抽象方法,并且抽象方法名称为value时,这里使用注解时可以直接传值,如:@MyAnno(8)
    public void sayHi(){
        System.out.println("hi...");
    };
}

使用注解实现利用反射自动创建对象并调用对象的方法:

定义一个注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 定义一个描述执行的类名和方法名的注解demo:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}

定义一个类:

public class DemoClass {
    public void show(){
        System.out.println("show...");
    };
}

实现过程:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 用注解代实现框架创建对象案例:
@Pro(className="DemoClass",methodName="show")
public class ReflectDemo {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException {
        // 1.解析注释:
        // 1-1.获取该类字节码文件对象:
        Class<ReflectDemo> refcls = ReflectDemo.class;
        // 1-2.获取上边的注释对象:
        Pro ans = refcls.getAnnotation(Pro.class); // 在内存中生成了该注释接口的子类实现类对象
        // 1-3.调用注解对象中定义的方法获取返回值:
        String className = ans.className();
        String methodName = ans.methodName();

        // 2.加载该类进内存,返回一个Class对象:
        Class cls = Class.forName(className);
        // 3.创建对象:newInstance方法已被启用
        Object objs = cls.newInstance();
        // 4.获取方法对象:
        Method methObj = cls.getMethod(methodName);
        // 5.执行方法:
        methObj.invoke(objs);
    };
}

提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:810665436@qq.com联系笔者删除。
笔者:苦海

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苦海123

快乐每一天!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值