11反射

反射

1 反射概述

​ 简介

  • Reflection是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象称之为:反射
  • Java不是动态语言,但Java可以称之为“准动态语言”,我们可以利用反射机制,字节码操作获得类似动态语言的特性。

​ 反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

​ 反射相关的主要API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器

​ 反射的疑问

  • 通过直接new方式或反射方式都可以调用公共结构,应该用哪个?建议用new方式
  • 什么时候用反射?反射的特征:动态性
  • 反射机制和面向对象的封装性是不是矛盾,如何看待?不矛盾

2 Class类的理解

​ 概述

  • java.lang.Class此类是Java反射的源头,实际上所谓反射,从程序的运行结果来看也很好理解:即可以通过对象反射求出类的名称
  • 对象反射后可以得到的信息:某个类的属性,方法,构造器,某个类到底实现了哪些接口。
  • 对于每个类而言,JRE都为其保留了一个不变的Class类型的对象。一个Class包含了特定的某个结构的有关信息
  • Class本身也是一个类
  • Class对象只能有系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己由哪个Class实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载,运行的类,唯有先获得相应的Class对象

​ 类加载过程

  • 程序在经过javac.exe命令以后,会生成一个或多个字节码文件(.class)
  • 接着我们使用java.exe命令对某个字节码文件进行解释运行 相当于将某个字节码文件加载到内存中了,此过程称为类的加载 加载到内存中的类称为运行时类,此运行时类就作为Class的一个实例
  • 换句话说,Class的实例就对应着一个运行时的类
  • 加载到内存中的运行类,会缓存一定时间,在此时间可以通过不同的方式来获取此运行时类

​ 哪些类型可以有Class对象

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void

​ Class类常用方法

  • static Class forName(String name):返回指定类名name的Class对象
  • Object newInstance():调用缺省构造函数,返回该Class对象的一个实例
  • getName():返回此Class对象所表示的实体(类,接口,数组类,基本类型或void)名称
  • Class getSuperClass():返回当前Class对象的父类的Class对象
  • Class[] getInterfaces():获取当前Class对象的接口
  • ClassLoader getClassLoader():返回该类的类加载器
  • Constructor getConstructor(xxx.Class...):返回指定的构造器
  • Constructor[] getConstructors():返回一个包含某些Constructor对象的数组
  • Field getDeclaredField(String name):返回Field指定的属性
  • Field[] getDeclaredFields():返回Field所有属性的一个数组
  • Method getMethod(String name,Class...paramTypes):返回一个Method对象,此对象的形参类型为paramType
  • 属性.set(Class对象,属性值)
  • 方法.invoke(Class对象,方法形参对应的实参)
public class G67Reflection {

    //反射之前对于Person类的操作
    @Test
    public void test1(){
        //1创建person类的对象
        Person p1=new Person("fao",12);

        //2通过对象,调用其内部的属性,方法
        p1.age=10;
        System.out.println(p1.toString());//Person{name='fao', age=10}
        p1.show();//你好,我是一个人

        //在Person类外部,不可以调用其内部的私有结构
    }

    //反射之后
    @Test
    public void test2() throws Exception {

        //0获取对象.class
        Class clazz=Person.class;
        
        //1通过反射,创建Person类对象
        Constructor cons = clazz.getConstructor(String.class, int.class);
        Object obj = cons.newInstance("fao", 12);
        Person p=(Person)obj;
        System.out.println(p.toString());//Person{name='fao', age=12}

        //2通过反射 调用对象指定的属性,方法
        //调属性
        Field age = clazz.getDeclaredField("age");
        age.set(p,10);
        System.out.println(p.toString());//Person{name='fao', age=10}

        //调方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(p);//你好,我是一个人
        System.out.println();
        
        //3通过反射,可以调用私有结构:构造器,属性,方法
        //构造器
        Constructor con1 = clazz.getDeclaredConstructor(String.class);
        con1.setAccessible(true);//获取私有构造器属性或方法的权限
        Person p1=(Person) con1.newInstance("lala");
        System.out.println(p1);//Person{name='lala', age=0}

        //属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1,"meimei");
        System.out.println(p1);//Person{name='meimei', age=0}

        //方法
  Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        showNation.invoke(p1,"中国");//我的国籍是: 中国
    }

}


class Person{
    private String name;
    public int age;

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

    public Person() {
    }

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

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void show(){
        System.out.println("你好,我是一个人");
    }

    private String showNation(String nation){
        System.out.println("我的国籍是: "+nation);
        return nation;
    }
}

3 获取Class类的实例的四种方式(前三种方式需要掌握)

  1. 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。实例:Class clazz=String.class;
  2. 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象 实例:Class clazz=person1.getClass();
  3. 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException 实例:Class clazz =Class.forName("java.lang.String")
  4. 其他方式,实例:ClassLoader=this.getClass().getClassLoader(); Class clazz=cl.loadClass("类的全类名")
public class G68ClassLei { 
    @Test
    public void test1() throws ClassNotFoundException {

        //方式1 调用运行时类的属性 .class
        Class clazz1=Person.class;
        System.out.println(clazz1);//class Person

        //方式2 通过运行时类的对象 调用getClass方法
        Person P1=new Person();
        Class clazz2 = P1.getClass();
        System.out.println(clazz2);//class Person

        //方式3 调用Class的静态方法 forName(String classPath)(使用最多 更好体现多态性)
        Class clazz3 = Class.forName("Person");
        System.out.println(clazz3);//class Person

        System.out.println(clazz1==clazz2);//true
        System.out.println(clazz1==clazz3);//ture

        //方式四(了解)使用类的加载器 ClassLoader
        ClassLoader classLoader = G68ClassLei.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("Person");
        System.out.println(clazz4);//class Person
    }
}

4 类的加载与ClassLoader的理解(了解一下)

​ 4.1类的加载过程

  • 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:1类的加载load->2类的链接link->3类的初始化Initialize
  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与
  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。1验证:确保加载的类信息符合JVM规范,例如以cafe开头,没有安全方面的问题 2准备:正式为类变量分配内存并设置类变量默认初始化只 的阶段,这些内存都将在方法区中进行分配。3解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 初始化:执行类构造器()方法的过程。类构造器()方法是由编译期间自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。当初始化一个类的时候,如果发现其父类还没有就行初始化,则需要先触发器父类的初始化。

​ 4.2 了解ClassLoader

public class G69ClassLoader {
    @Test
    public void test1(){
        //对于自定义类使用系统类加载器进行
        ClassLoader classLoader = G69ClassLoader.class.getClassLoader();
        System.out.println(classLoader);//系统类加载器

        //获取扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);//扩展类加载器
        
        //无法获取引导类加载器
        //引导类加载器主要加载java核心类库
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);
    }

    /*
    Properties:用来读取配置文件 这个经常使用!!!
     */
    @Test
    public void test2() throws Exception {
        Properties pros=new Properties();
        //读取配置文件方式1
        //此时文件默认在当前module下
        FileInputStream fis=new FileInputStream("jdbc.properties");
        pros.load(fis);
        
        // 读取配置文件方式2
        //配置文件默认识别为当前module的src下
        ClassLoader classLoader = G69ClassLoader.class.getClassLoader();
     InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
        pros.load(is);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println(user);//fao
        System.out.println(password);//123
    }
}

5 创建运行时类的对象

  • 有了Class对象,可以做什么?创建类的对象:调用Class对象的newInstance()方法
  • 要求:1类必须有一个无参数的构造器 2类的构造器的访问权限需要足够
  • 没有无参构造器就不能创建对象了吗?不是,只要在操作的时候目前调用类中的构造器,并将参数传递进去之后,才可以实例化操作。步骤如下:
  • 通过Class类的getDeclaredConstructor(Class...parameterTyoes)取得本类的指形参类型的构造器
  • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
  • 通过Constructor实例化对象
public class G70NewInstance {
    @Test
    public void test1() throws InstantiationException, IllegalAccessException {

        Class clazz=Person.class;

     //newInstance():调用此方法,创建对应运行时类的对象,调用运行时类的空参构造器
     //要想此方法正常创建类的对象:1运行类必须提供空参结构器 2空参的构造器访问权限要够 通常public

        Object obj = clazz.newInstance();
        System.out.println(obj);//Person{name='null', age=0}
    }


    //体会反射的动态性
    @Test
    public void test2() throws Exception {

        String classPath="";
        int num = new Random().nextInt(3);//1,2,3
        switch (num){
            case 0:
                classPath="java.util.Date";
                break;
            case 1:
                classPath="java.lang.Object";
                break;
            case 2:
                classPath="Person";
                break;
        }
        Object obj = getInstance(classPath);//下面定义了该方法
        System.out.println(obj);
    }


    /*
    此方法创建一个指定类的对象
    classPath:指定类的全类名
     */
    public Object getInstance(String classPath) throws Exception{
        Class clazz = Class.forName(classPath);
        return clazz.getConstructor();//返回该构造器信息
    }
}

6 获取运行时类的完整结构

  • public Class[] getInterfaces():获取实现的全部接口
  • public Class getSuperClass():获取此类继承的父类
  • public Constructor[] getConstructors():获取此类的获取public构造方法
  • public Constructor[] getDeclaredConstructors:获取此类声明的所有构造方法
  • Constructor类中:1 public int getModifiers():取得修饰符 2 public String getName():取得方法名称 3 public Class[] getParameterTypes():取得参数的类型
  • public Method[] getMethods():获取所有public的方法
  • public Method[] getDeclaredMethods():获取所有的方法
  • Method类中 1 public Class getReturnType()获取全部返回值的类型 2 public int getModifiers():取得修饰符 3 public Class[] getParameterTypes():取得参数的类型 4public Class getExceptionTypes():获取异常信息
  • public Field[] getFields():获取所有public的属性
  • public Field[] getDeclaredFields():获取所有的属性
  • Filed类中:1 public int getModifiers():取得修饰符 2 public String getName():取得方法名称 3 public Class[] getTypes():取得属性的类型+
  • get Annotations/get DeclaredAnnotations:Annotation相关
  • Type getGenericSuperclass():获取父类泛型类型
  • ParameterizedType():泛型类型
  • getActualTypeArguments():获取实际的泛型类型参数数组
  • Package getPackage():类所在的包
//MyInterface
public interface MyInterface {
    void info();
}
//MyAnnotation
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.RUNTIME)//一定是runti,e
public @interface MyAnnotation {
    String value() default "hello";
}
//Creature
public class Creature<T>implements Serializable {

    private char gender;
    public double weight;

    private void breath(){
        System.out.println("生物呼吸");
    }

    public void eat(){
        System.out.println("生物吃东西");
    }
}

//Person
@MyAnnotation(value ="hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{

    private String name;
    int age;
    public int id;

    public Person(){
    }

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

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

    private void show(String nation){
        System.out.println("我的国籍是 "+nation );
    }

    public String display(String interest){
        return interest;
    }
    
    @Override
    public void info() {
        System.out.println("我是一个人");

    }

    @Override
    public int compareTo(String o) {
        return 0;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }

    private static void showDesc(){
        System.out.println("我是一个可爱的人");
    }
}
public class G71FieldTest {
   @Test
   public void test1(){
       Class clazz=Person.class;
       //获取属性结构
       //getFields 获取当前运行时类及其父类中声明为public访问权限的属性
       Field[] fields = clazz.getFields();
       for (Field f:fields){
           System.out.println(f);
       }
       //public int Person.id
       //public double Creature.weight
       System.out.println();
       
       //getDeclaredFields获取当前运行时类声明的所有属性(不包含父类)
       Field[] declaredFields = clazz.getDeclaredFields();
       for (Field f:declaredFields){
           System.out.println(f);
       }
       //private java.lang.String Person.name
       //int Person.age
       //public int Person.id
   }

   //权限修饰符 数据类型 变量名
   @Test
   public void test2(){
       Class clazz=Person.class;
       Field[] declaredFields = clazz.getDeclaredFields();

       for (Field f:declaredFields){
           //1权限修饰符(0是缺省,1是public,2是private)
           int modifiers = f.getModifiers();
           System.out.print(modifiers+ "\t");

           //2数据类型
           Class<?> type = f.getType();
           System.out.print(type+ "\t");

           //3变量名
           String name = f.getName();
           System.out.print(name);
           System.out.println();
       }
       //2	class java.lang.String	name
       //0	int	age
       //1	int	id
   }

   
   //获取运行时方法结构
   @Test
   public void test3(){
       Class clazz=Person.class;
       //getMethods 获取当前运行时类及其父类中声明为public访问权限的方法
       Method[] methods = clazz.getMethods();
       for (Method m:methods){
           System.out.println(m);
       }

       System.out.println();
       //getDeclaredMethods获取当前运行时类声明的所有方法(不包含父类)
       Method[] declaredMethods = clazz.getDeclaredMethods();
       for (Method m:declaredMethods){
           System.out.println(m);
       }
   }

   /*
   @xxx
   权限修饰符 返回值类型 方法名(参数类型1 形参名1 ..)trows xxxException
    */
   @Test
   public void test4(){

       Class clazz=Person.class;
       Method[] methods = clazz.getMethods();
       for (Method m:methods){

           //1获取方法声明的注解
//            Annotation[] annos = m.getAnnotations();
//            for (Annotation a:annos){
//                System.out.println(a);
//            }

           //权限修饰符
           int modifiers = m.getModifiers();
           System.out.print(Modifier.toString(modifiers)+"\t");

           //返回值类型
           System.out.print(m.getReturnType().getName()+"\t");

           //方法名
           System.out.print(m.getName());
           System.out.print("(");
           
           //形参列表
           Class<?>[] parameterTypes = m.getParameterTypes();
           if (!(parameterTypes==null && parameterTypes.length==0)){
               for (int i=0;i<parameterTypes.length;i++){
                 System.out.print(parameterTypes[i].getName()+"_args"+i);
               }
           }
           System.out.print(")");

           //抛出异常
           Class<?>[] exceptionTypes = m.getExceptionTypes();
           if(exceptionTypes.length>0){
               System.out.print("throws ");
               for (int i=0;i<exceptionTypes.length;i++){
                   System.out.print(exceptionTypes[i].getName());
               }
           }
           System.out.println();
       }
   }

   
   //获取构造器结构
   @Test
   public void test5(){
       Class clazz=Person.class;
       
       //getConstructors获取当前运行时类中声明为public的构造器
       Constructor[] constructors = clazz.getConstructors();
       for (Constructor c:constructors){
           System.out.println(c);
       }
       System.out.println();
       //public Person()
       
       //getDeclaredConstructors获取当前运行时类中所有构造器
       Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
       for (Constructor c:declaredConstructors){
           System.out.println(c);
       }
       //Person(java.lang.String,int)
       //private Person(java.lang.String)
       //public Person()
   }
}
public class G72OtherFieldTest {
    
    //获取运行时类的父类
    @Test
    public void test1(){
        Class clazz=Person.class;
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
		//class Creature
    }

    
    //获取运行时类带泛型的父类
    @Test
    public void test2(){
        Class clazz=Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
		//Creature<java.lang.String>
    }

     
    //获取运行时类带泛型的父类的泛型
    @Test
    public void test3(){
        Class clazz=Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType parameterizedType=(ParameterizedType)genericSuperclass;
        //获取泛型类型
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        
        System.out.println(actualTypeArguments[0].getTypeName());
		//java.lang.String
    }

   
    //获取运行时类实现的接口
    @Test
    public void test4(){
        Class clazz=Person.class;
        Class[] interfaces = clazz.getInterfaces();
        for (Class c:interfaces){
            System.out.println(c);
        }
        //interface java.lang.Comparable
        //interface MyInterface
        System.out.println();

        //获取运行时类父类的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for (Class c:interfaces1){
            System.out.println(c);
        }
		//interface java.io.Serializable
    }
    
      
    //获取运行时类所在的包
    @Test
    public void test5(){
        Class clazz=Person.class;
        Package pack = clazz.getPackage();
        System.out.println(pack);//package
    }

   
    //获取运行时类声明的注解
    @Test
    public void test6(){
        Class clazz=Person.class;
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation a:annotations){
            System.out.println(a);
        }
        //@MyAnnotation("hi")
    }
    
}

7 调用运行时类的指定结构

7.1调用指定方法

  • 先通过getMethod(String name,Class...parameterTypes)取得一个Method对象,并设置此方法操作时所需要的参数类型
  • 之后调用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息
  • Object invoke(Object obj, Object[] args)说明
  • Object对应原方法的返回值,若原方法无返回值可以为null
  • 若原方法为静态方法,此时形参Object obj可以为null
  • 若原方法形参列表为空,则Object[] args可以为null
  • 若方法声明为private则需要在调用此invoke前,显示调用方法对象的setAccessible(true)方法,将可访问private的方法

​ 7.2调用指定属性

  • 通过Field类操作类的属性,通过Field类提供的set()和get()方法完成设置和取得属性内容的操作
  • public Field getField(String name):返回指定的public的Field
  • public Field getDeclaredField(String name):返回指定的Field
  • 在Field中
  • public Object get(Object obj):取得指定对象obj上此属性的内容
  • public void set(Object obj,Object value):设置指定对象obj上属性的内容

​ 7.3 关于setAccessible方法使用

  • Method和Filed,Constructor对象都有setAccessible()方法
  • setAccessible 启动和禁用访问安全检查的开关
  • 参数值为true则指示反射的对象在使用时应该取消java语言访问检查,这样就能使得原本无法访问的私有成员也可以访问
public class G73ReflectionTest {

    //getField一般只能获取public的,不常用
    @Test
    public void test1() throws Exception{
        Class clazz = Person.class;
        
        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        //获取指定的属性:要求是public的
        Field id = clazz.getField("id");//直接改age不行
      
        //set()参数1指明哪个对象的属性 参数2设置为多少
        id.set(p,1001);

        //获取当前属性的值
        //get()参数1:获取哪个对象的当前属性值
        Object o = id.get(p);
        int pid=(int) o;
        System.out.println(pid);//1001
    }

  
    //getDeclaredField:获取运行时类的指定属性,私有也行--要掌握
    @Test
    public void test2() throws Exception{
        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        //1getDeclaredField获取指定的属性 私有也行
        Field name = clazz.getDeclaredField("name");

        //2保证当前属性是可以访问的
        name.setAccessible(true);

        //3获取,设置指定对象此属性
        name.set(p,"tom");
        
        System.out.println(name.get(p));//tom
    }

   
    //getDeclaredMethod:获取运行时类的指定方法--要掌握
    @Test
    public void test3() throws Exception{
        Class clazz=Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        //1获取指定的某个方法
        //getDeclaredMethod
        //参数1指明获取方法的名称
        //参数2指明获取方法的形参列表
        Method show = clazz.getDeclaredMethod("show", String.class);

        //2保持当前方法是可访问的
        show.setAccessible(true);

        //3调用Invoke方法
        //invoke参数1方法的调用者 参数2方法的实参
        //invoke方法返回值就是调用方法的返回值
        //如果没有返回值,就是null
        Object chn = show.invoke(p, "chn");//我的国籍是 chn
        System.out.println((String) chn);//null
        System.out.println();

        //静态方法private static void showDesc()
        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);

        //括号里面写啥都可以 写null也行 但一定要写
        showDesc.invoke(Person.class);//我是一个可爱的人
    }


    //getDeclaredConstructor:获取运行时类的指定构造器
    @Test
    public void test4() throws Exception{
            Class clazz=Person.class;

            //1获取指定构造器
            //getDeclaredConstructor 参数:指明构造器的参数列表
            Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);

            //2保证此构造器可访问
            declaredConstructor.setAccessible(true);

            //3调用此构造器 创建运行时类的对象
            Person p =(Person) declaredConstructor.newInstance("fao");
            System.out.println(p);
			//Person{name='fao', age=0, id=0}
        }
}
posted @ 2021-10-16 18:26  fao99  阅读(34)  评论(0)    收藏  举报