函数式接口:
在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);
};
}
提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:[email protected]联系笔者删除。
笔者:苦海