精华内容
下载资源
问答
  • 对象的理解不正确的是
    千次阅读
    2021-03-11 11:52:16

    数据库应用技术复习 求解1下面关于JAVA语言的说法,错误的是(  )。A.JAVA语言是一种面向对象的程序设计语言。B

    数据库应用技术复习 求解

    1下面关于JAVA语言的说法,错误的是(  )。

    A.JAVA语言是一种面向对象的程序设计语言。

    B.JAVA语言是一种跨平台的语言。

    C.JAVA语言具有垃圾自动回收的机制。

    D.JAVA语言是计算机可以直接识别的一种语言。

    2、在java语言中,语句使用(  )号结束。

    A. , B. . C.; D.:

    6、转移字符 ‘n’的作用是:(  )。

    A.换行 B.回车 C.退格键 D. 制表符

    C. 取得字符串的长度 D. String类中没有这个方法

    8、表达式 ((1>2)||(3>2))&&(4>3)的执行结果是:(  )。

    A. true B.false C.无法确定 D.4

    9、下列哪个关键字用于实现接口(  )。

    A.implements B.implement C. import D. imports

    1、下列修饰符中与访问控制无关的是 (  )。

    A.private    B.public

    C.protected D.final。

    2、以下有关类的继承的说明中,正确的是:(  )。

    A.子类能直接继承父类所有的非私有属性,也可通过接口继承父类的私有属性

    B.子类只能继承父类的方法,不能继承父类的属性

    C.子类只能继承父类的非私有属性,不能继承父类的方法

    D.子类不能继承父类的私有属性。

    3、方法头中static修饰符的含义:(  )。

    A.方法体为空 B.定义的方法没有形参

    C.该方法属于类的方法 D.方法的返回值不能参加算术运算

    4、下面的变量名不正确的是(  )。

    A. studentName B. _ studentName C. $studentName D. 2studentName

    5、能从循环语句的循环体中跳出的语句是(  )。

    A. for 语句 B. break 语句 C. while 语句 D. continue语句

    更多相关内容
  • JSON是什么?如何正确理解

    万次阅读 多人点赞 2018-08-04 20:47:26
    JSON (JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 ...

    1.背景介绍

    什么是JSON

    JSON (JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。——百度百科

    数据传输是我们在敲代码时,经常遇到的一个场景,前后端交互。给数据一个统一的格式有利于我们编写和解析数据。

    json,是一种数据格式,在与后端的数据交互中有较为广泛的应用。

    JSON的诞生

    JSON是 (JavaScript Object Notation, JS 对象标记),它是一种数据交换格式。在JSON出现之前,大家一直用XML来传递数据。因为XML是一种纯文本格式,所以它适合在网络上交换数据。XML本身不算复杂,但是,加上DTD、XSD、XPath、XSLT等一大堆复杂的规范以后,任何正常的软件开发人员碰到XML都会感觉头大了,最后大家发现,即使你努力钻研几个月,也未必搞得清楚XML的规范。

    终于,在2002年的一天,道格拉斯·克罗克福特(DouglasCrockford)同学为了拯救深陷水深火热同时又被某几个巨型软件企业长期愚弄的软件工程师,发明了JSON这种超轻量级的数据交换格式。

    由于JSON非常简单,很快就风靡Web世界,并且成为ECMA标准。几乎所有编程语言都有解析JSON的库,而在JavaScript中,我们可以直接使用JSON,因为JavaScript内置了JSON的解析。把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串,这样才能够通过网络传递给其他计算机。如果我们收到一个JSON格式的字符串,只需要把它反序列化成一个JavaScript对象,就可以在JavaScript中直接使用这个对象了。

    转义

    我们在调用 jsonp 接口或者调用js文件的时候,由于文件编码不同会导致出现乱码的问题。 如果你的文件出现了非英文字符,如果调用时文件编码不一致,同样会出现乱码情况。

    这也就是为什么要数据统一格式的原因。

    JSON 是适用于 Ajax 应用程序的一种有效格式,原因是它使 JavaScript 对象和字符串值之间得以快速转换 JSON是一种传递对象的语法

    JSON是一个提供了stringify和parse方法的内置对象

    stringify将js对象转化为符合json标准的字符串

    parse将符合json标准的字符串转化为js对象


    2.知识剖析

    JSON对值的类型和格式有严格的规定
    复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
    简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinity和undefined)。
    字符串必须使用双引号表示,不能使用单引号。
    对象的键名必须放在双引号里面。
    数组或对象最后一个成员的后面,不能加逗号。
      PS: 需要注意的是,空数组和空对象都是合格的JSON值,null本身也是一个合格的JSON值。

     

    JSON 的语法规则
    JSON 的语法规则十分简单,可称得上“优雅完美”,总结起来有:
    数组(Array)用方括号(“[]”)表示。
    对象(Object)用大括号(”{}”)表示。
    名称/值对(name/value)组合成数组和对象。
    名称(name)置于双引号中,值(value)有字符串、数值、布尔值、null、对象和数组。
    并列的数据之间用逗号(“,”)分隔

    实例

    JSON 数据的书写格式是:名称/值对。

    名称/值对组合中的名称写在前面(在双引号中),值对写在后面,中间用冒号隔开,

    其中 值 可以是:数字(整数或浮点数)、字符串(在双引号中)、布尔值(true或false)、数组(在方括号中)、对象(在花括号中)、null

    varjson= {"password":123456,"name":"myname","Booleans":true,"Array":[x,y,z],"object":{}}

    或者是嵌套使用

     

    转义概述

    为什么需要转义?在js中我们使用的js对象进行处理,但是在与后端数据交换的时候,我们发送规定的json格式的字符串,所以在给后端发送或接受数据的时候,需要转义

    {name:"myname",password:123456}

    "{"name":"myname","password":123456}"

    其中json字符串转js对象,调用parse方法:

    js对象 = JSON.parse(json字符串);

    js对象转json字符串,调用stringify方法:

    json字符串 = JSON.stringify(js对象);


    3.常见问题

    在json字符串转换成对象,还有eval_r('('+json字符串+')')这个方法,但是在对目标数据进行读取时,可能会出现一些意外的错误:

     


    4.解决方案

    原因:eval_r获取的json对象的值中,如果有执行代码,也将照样执行!所以若不能保证数据的安全性,不要使用eval_r方法进行转义。

     


    5.编码实战

    下面是一个简单的json数据发送应用,有兴趣可以阅读一下:

     

     

    6.扩展思考

    我们常看到

    {name:"myname",password:123456}

    {"name":"myname","password":123456}

    这样两种格式,即js对象和json,然而js对象和json有什么不一样的地方?

    很多人搞不清楚 JSON 和 Js 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:

    JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。

    var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的

    var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串

     


    7.参考文献

    参考一:JSON官方文网

    参考二:Json对象和Json字符串的区别

    参考三:json属性名必须加引号的讨论

    参考四:json怎么理解


    8.更多讨论

    1、XML是什么?

    XML与Access,Oracle和SQL Server等数据库不同,数据库提供了更强有力的数据存储和分析能力,例如:数据索引、排序、查找、相关一致性等,XML的宗旨传输数据的,而与其同属标准通用标记语言的HTML主要用于显示数据。事实上XML与其他数据表现形式最大的不同是:他极其简单。这是一个看上去有点琐细的优点,但正是这点使XML与众不同。

    2、向后台传输数据要用什么格式?

    json字符串格式,如果是一个对象要先用JSON.stringify()转义,转义成为字符串才能将数据传给后端。

    3、后端传过来的字符串数据要怎样才能转义成为一个对象?

    用JSON.parse()方法转义。

    展开全文
  • 深入理解Java类型信息(Class对象)与反射机制

    万次阅读 多人点赞 2017-05-01 23:19:19
    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解

    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
    http://blog.csdn.net/javazejian/article/details/70768369
    出自【zejian的博客】

    关联文章:

    深入理解Java类型信息(Class对象)与反射机制

    深入理解Java枚举类型(enum)

    深入理解Java注解类型(@Annotation)

    深入理解Java并发之synchronized实现原理

    深入理解Java内存模型(JMM)及volatile关键字

    深入理解Java类加载器(ClassLoader)

    本篇主要是深入对Java中的Class对象进行分析,这对后续深入理解反射技术非常重要,主要内容如下:

    深入理解Class对象

    RRTI的概念以及Class对象作用

    认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中,其部分源码如下:

    public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement {
        private static final int ANNOTATION= 0x00002000;
        private static final int ENUM      = 0x00004000;
        private static final int SYNTHETIC = 0x00001000;
    
        private static native void registerNatives();
        static {
            registerNatives();
        }
    
        /*
         * Private constructor. Only the Java Virtual Machine creates Class objects.(私有构造,只能由JVM创建该类)
         * This constructor is not used and prevents the default constructor being
         * generated.
         */
        private Class(ClassLoader loader) {
            // Initialize final field for classLoader.  The initialization value of non-null
            // prevents future JIT optimizations from assuming this final field is null.
            classLoader = loader;
        }

    Class类被创建后的对象就是Class对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,挺拗口,通过下图理解(内存中的简易现象图):

    到这我们也就可以得出以下几点信息:

    • Class类也是类的一种,与class关键字是不一样的。

    • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。

    • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。

    • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载

    • Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。

    Class对象的加载及其获取方式

    Class对象的加载

    前面我们已提到过,Class对象是由JVM加载的,那么其加载时机是?实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件),注意,使用new操作符创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法),由此看来Java程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象创建时依据Class对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。下面通过一个简单例子来说明Class对象被加载的时机问题(例子引用自Thinking in Java):

    package com.zejian;
    
    class Candy {
      static {   System.out.println("Loading Candy"); }
    }
    
    class Gum {
      static {   System.out.println("Loading Gum"); }
    }
    
    class Cookie {
      static {   System.out.println("Loading Cookie"); }
    }
    
    public class SweetShop {
      public static void print(Object obj) {
        System.out.println(obj);
      }
      public static void main(String[] args) {  
        print("inside main");
        new Candy();
        print("After creating Candy");
        try {
          Class.forName("com.zejian.Gum");
        } catch(ClassNotFoundException e) {
          print("Couldn't find Gum");
        }
        print("After Class.forName(\"com.zejian.Gum\")");
        new Cookie();
        print("After creating Cookie");
      }
    }

    在上述代码中,每个类Candy、Gum、Cookie都存在一个static语句,这个语句会在类第一次被加载时执行,这个语句的作用就是告诉我们该类在什么时候被加载,执行结果:

    inside main
    Loading Candy
    After creating Candy
    Loading Gum
    After Class.forName("com.zejian.Gum")
    Loading Cookie
    After creating Cookie
    
    Process finished with exit code 0

    从结果来看,new一个Candy对象和Cookie对象,构造函数将被调用,属于静态方法的引用,Candy类的Class对象和Cookie的Class对象肯定会被加载,毕竟Candy实例对象的创建依据其Class对象。比较有意思的是

    Class.forName("com.zejian.Gum");

    其中forName方法是Class类的一个static成员方法,记住所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。这里通过forName方法,我们可以获取到Gum类对应的Class对象引用。从打印结果来看,调用forName方法将会导致Gum类被加载(前提是Gum类从来没有被加载过)。

    Class.forName方法

    通过上述的案例,我们也就知道Class.forName()方法的调用将会返回一个对应类的Class对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象,如下的第2种方式是通过一个实例对象获取一个类的Class对象,其中的getClass()是从顶级类Object继承而来的,它将返回表示该对象的实际类型的Class对象引用。

    public static void main(String[] args) {
    
        try{
          //通过Class.forName获取Gum类的Class对象
          Class clazz=Class.forName("com.zejian.Gum");
          System.out.println("forName=clazz:"+clazz.getName());
        }catch (ClassNotFoundException e){
          e.printStackTrace();
        }
    
        //通过实例对象获取Gum的Class对象
        Gum gum = new Gum();
        Class clazz2=gum.getClass();
        System.out.println("new=clazz2:"+clazz2.getName());
    
      }

    注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。

    Class字面常量

    在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:

    //字面常量的方式获取Class对象
    Class clazz = Gum.class;

    这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助,关于反射技术稍后会分析,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下,一般情况下更倾向使用.class的形式,这样可以保持与普通类的形式统一。

    boolean.class = Boolean.TYPE;
    char.class = Character.TYPE;
    byte.class = Byte.TYPE;
    short.class = Short.TYPE;
    int.class = Integer.TYPE;
    long.class = Long.TYPE;
    float.class = Float.TYPE;
    double.class = Double.TYPE;
    void.class = Void.TYPE;

    前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,这里我们可能需要简单了解一下类加载的过程,如下:

    • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

    • 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。

    • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

    由此可知,我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。下面通过小例子来验证这个过程:

    import java.util.*;
    
    class Initable {
      //编译期静态常量
      static final int staticFinal = 47;
      //非编期静态常量
      static final int staticFinal2 =
        ClassInitialization.rand.nextInt(1000);
      static {
        System.out.println("Initializing Initable");
      }
    }
    
    class Initable2 {
      //静态成员变量
      static int staticNonFinal = 147;
      static {
        System.out.println("Initializing Initable2");
      }
    }
    
    class Initable3 {
      //静态成员变量
      static int staticNonFinal = 74;
      static {
        System.out.println("Initializing Initable3");
      }
    }
    
    public class ClassInitialization {
      public static Random rand = new Random(47);
      public static void main(String[] args) throws Exception {
        //字面常量获取方式获取Class对象
        Class initable = Initable.class;
        System.out.println("After creating Initable ref");
        //不触发类初始化
        System.out.println(Initable.staticFinal);
        //会触发类初始化
        System.out.println(Initable.staticFinal2);
        //会触发类初始化
        System.out.println(Initable2.staticNonFinal);
        //forName方法获取Class对象
        Class initable3 = Class.forName("Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);
      }
    }

    执行结果:

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74

    从输出结果来看,可以发现,通过字面常量获取方式获取Initable类的Class对象并没有触发Initable类的初始化,这点也验证了前面的分析,同时发现调用Initable.staticFinal变量时也没有触发初始化,这是因为staticFinal属于编译期静态常量,在编译阶段通过常量传播优化的方式将Initable类的常量staticFinal存储到了一个称为NotInitialization类的常量池中,在以后对Initable类常量staticFinal的引用实际都转化为对NotInitialization类对自身常量池的引用,所以在编译期后,对编译期常量的引用都将在NotInitialization类的常量池获取,这也就是引用编译期静态常量不会触发Initable类初始化的重要原因。但在之后调用了Initable.staticFinal2变量后就触发了Initable类的初始化,注意staticFinal2虽然被static和final修饰,但其值在编译期并不能确定,因此staticFinal2并不是编译期常量,使用该变量必须先初始化Initable类。Initable2和Initable3类中都是静态成员变量并非编译期常量,引用都会触发初始化。至于forName方法获取Class对象,肯定会触发初始化,这点在前面已分析过。到这几种获取Class对象的方式也都分析完,ok~,到此这里可以得出小结论:

    • 获取Class对象引用的方式3种,通过继承自Object类的getClass方法,Class类的静态方法forName以及字面常量的方式”.class”。

    • 其中实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段,而字面常量获取Class对象的方式则不会触发初始化。

    • 初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的Java程序代码或者字节码。

    关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化

    • 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。

    • 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。

    • 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。

    • 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类

    • 当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化(这点看不懂就算了,这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的,这是一个比较大点的话题,这里暂且打住)

    理解泛化的Class对象引用

    由于Class的引用总数指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。在Java SE5引入泛型后,使用我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型。如下:

    /**
     * Created by zejian on 2017/4/30.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ClazzDemo {
    
        public static void main(String[] args){
            //没有泛型
            Class intClass = int.class;
    
            //带泛型的Class对象
            Class<Integer> integerClass = int.class;
    
            integerClass = Integer.class;
    
            //没有泛型的约束,可以随意赋值
            intClass= double.class;
    
            //编译期错误,无法编译通过
            //integerClass = double.class
        }
    }
    

    从代码可以看出,声明普通的Class对象,在编译器并不会检查Class对象的确切类型是否符合要求,如果存在错误只有在运行时才得以暴露出来。但是通过泛型声明指明类型的Class对象,编译器在编译期将对带泛型的类进行额外的类型检查,确保在编译期就能保证类型的正确性,实际上Integer.class就是一个Class<Integer>类的对象。面对下述语句,确实可能令人困惑,但该语句确实是无法编译通过的。

    //编译无法通过
    Class<Number> numberClass=Integer.class;

    我们或许会想Integer不就是Number的子类吗?然而事实并非这般简单,毕竟Integer的Class对象并非Number的Class对象的子类,前面提到过,所有的Class对象都只来源于Class类,看来事实确实如此。当然我们可以利用通配符“?”来解决问题:

    Class<?> intClass = int.class;
    intClass = double.class;

    这样的语句并没有什么问题,毕竟通配符指明所有类型都适用,那么为什么不直接使用Class还要使用Class<?>呢?这样做的好处是告诉编译器,我们是确实是采用任意类型的泛型,而非忘记使用泛型约束,因此Class<?>总是优于直接使用Class,至少前者在编译器检查时不会产生警告信息。当然我们还可以使用extends关键字告诉编译器接收某个类型的子类,如解决前面Number与Integer的问题:

    //编译通过!
    Class<? extends Number> clazz = Integer.class;
    //赋予其他类型
    clazz = double.class;
    clazz = Number.class;

    上述的代码是行得通的,extends关键字的作用是告诉编译器,只要是Number的子类都可以赋值。这点与前面直接使用Class<Number>是不一样的。实际上,应该时刻记住向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。

    关于类型转换的问题

    在许多需要强制类型转换的场景,我们更多的做法是直接强制转换类型:

    package com.zejian;
    
    /**
     * Created by zejian on 2017/4/30.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ClassCast {
    
     public void cast(){
    
         Animal animal= new Dog();
         //强制转换
         Dog dog = (Dog) animal;
     }
    }
    
    interface Animal{ }
    
    class Dog implements  Animal{ }

    之所可以强制转换,这得归功于RRTI,要知道在Java中,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常。除了强制转换外,在Java SE5中新增一种使用Class对象进行类型转换的方式,如下:

    Animal animal= new Dog();
    //这两句等同于Dog dog = (Dog) animal;
    Class<Dog> dogType = Dog.class;
    Dog dog = dogType.cast(animal)

    利用Class对象的cast方法,其参数接收一个参数对象并将其转换为Class引用的类型。这种方式似乎比之前的强制转换更麻烦些,确实如此,而且当类型不能正确转换时,仍然会抛出ClassCastException异常。源码如下:

    public T cast(Object obj) {
        if (obj != null && !isInstance(obj))
             throw new ClassCastException(cannotCastMsg(obj));
         return (T) obj;
      }

    instanceof 关键字与isInstance方法

    关于instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例。如下,在强制转换前利用instanceof检测obj是不是Animal类型的实例对象,如果返回true再进行类型转换,这样可以避免抛出类型转换的异常(ClassCastException)

    public void cast2(Object obj){
        if(obj instanceof Animal){
              Animal animal= (Animal) obj;
          }
    }

    而isInstance方法则是Class类中的一个Native方法,也是用于判断对象类型的,看个简单例子:

    public void cast2(Object obj){
            //instanceof关键字
            if(obj instanceof Animal){
                Animal animal= (Animal) obj;
            }
    
            //isInstance方法
            if(Animal.class.isInstance(obj)){
                Animal animal= (Animal) obj;
            }
      }

    事实上instanceOf 与isInstance方法产生的结果是相同的。对于instanceOf是关键字只被用于对象引用变量,检查左边对象是不是右边类或接口的实例化。如果被测对象是null值,则测试结果总是false。一般形式:

    //判断这个对象是不是这种类型
    obj.instanceof(class)

    而isInstance方法则是Class类的Native方法,其中obj是被测试的对象或者变量,如果obj是调用这个方法的class或接口的实例,则返回true。如果被检测的对象是null或者基本类型,那么返回值是false;一般形式如下:

    //判断这个对象能不能被转化为这个类
    class.inInstance(obj)

    最后这里给出一个简单实例,验证isInstance方法与instanceof等价性:

    class A {}
    
    class B extends A {}
    
    public class C {
      static void test(Object x) {
        print("Testing x of type " + x.getClass());
        print("x instanceof A " + (x instanceof A));
        print("x instanceof B "+ (x instanceof B));
        print("A.isInstance(x) "+ A.class.isInstance(x));
        print("B.isInstance(x) " +
          B.class.isInstance(x));
        print("x.getClass() == A.class " +
          (x.getClass() == A.class));
        print("x.getClass() == B.class " +
          (x.getClass() == B.class));
        print("x.getClass().equals(A.class)) "+
          (x.getClass().equals(A.class)));
        print("x.getClass().equals(B.class)) " +
          (x.getClass().equals(B.class)));
      }
      public static void main(String[] args) {
        test(new A());
        test(new B());
      } 
    }

    执行结果:

    Testing x of type class com.zejian.A
    x instanceof A true
    x instanceof B false //父类不一定是子类的某个类型
    A.isInstance(x) true
    B.isInstance(x) false
    x.getClass() == A.class true
    x.getClass() == B.class false
    x.getClass().equals(A.class)) true
    x.getClass().equals(B.class)) false
    ---------------------------------------------
    Testing x of type class com.zejian.B
    x instanceof A true
    x instanceof B true
    A.isInstance(x) true
    B.isInstance(x) true
    x.getClass() == A.class false
    x.getClass() == B.class true
    x.getClass().equals(A.class)) false
    x.getClass().equals(B.class)) true

    到此关于Class对象相关的知识点都分析完了,下面将结合Class对象的知识点分析反射技术。

    理解反射技术

    反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

    Constructor类及其用法

    Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:

    方法返回值方法名称方法说明
    static Class<?>forName(String className)返回与带有给定字符串名的类或接口相关联的 Class 对象。
    Constructor<T>getConstructor(Class<?>... parameterTypes)返回指定参数类型、具有public访问权限的构造函数对象
    Constructor<?>[]getConstructors()返回所有具有public访问权限的构造函数的Constructor对象数组
    Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)返回指定参数类型、所有声明的(包括private)构造函数对象
    Constructor<?>[]getDeclaredConstructor()返回所有声明的(包括private)构造函数对象
    TnewInstance()创建此 Class 对象所表示的类的一个新实例。

    下面看一个简单例子来了解Constructor对象的使用:

    package reflect;
    
    import java.io.Serializable;
    import java.lang.reflect.Constructor;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectDemo implements Serializable{
        public static void main(String[] args) throws Exception {
    
            Class<?> clazz = null;
    
            //获取Class对象的引用
            clazz = Class.forName("reflect.User");
    
            //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
            User user = (User) clazz.newInstance();
            user.setAge(20);
            user.setName("Rollen");
            System.out.println(user);
    
            System.out.println("--------------------------------------------");
    
            //获取带String参数的public构造函数
            Constructor cs1 =clazz.getConstructor(String.class);
            //创建User
            User user1= (User) cs1.newInstance("xiaolong");
            user1.setAge(22);
            System.out.println("user1:"+user1.toString());
    
            System.out.println("--------------------------------------------");
    
            //取得指定带int和String参数构造函数,该方法是私有构造private
            Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
            //由于是private必须设置可访问
            cs2.setAccessible(true);
            //创建user对象
            User user2= (User) cs2.newInstance(25,"lidakang");
            System.out.println("user2:"+user2.toString());
    
            System.out.println("--------------------------------------------");
    
            //获取所有构造包含private
            Constructor<?> cons[] = clazz.getDeclaredConstructors();
            // 查看每个构造方法需要的参数
            for (int i = 0; i < cons.length; i++) {
                //获取构造函数参数类型
                Class<?> clazzs[] = cons[i].getParameterTypes();
                System.out.println("构造函数["+i+"]:"+cons[i].toString() );
                System.out.print("参数类型["+i+"]:(");
                for (int j = 0; j < clazzs.length; j++) {
                    if (j == clazzs.length - 1)
                        System.out.print(clazzs[j].getName());
                    else
                        System.out.print(clazzs[j].getName() + ",");
                }
                System.out.println(")");
            }
        }
    }
    
    
    class User {
        private int age;
        private String name;
        public User() {
            super();
        }
        public User(String name) {
            super();
            this.name = name;
        }
    
        /**
         * 私有构造
         * @param age
         * @param name
         */
        private User(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }
    
      //..........省略set 和 get方法
    }

    运行结果:

    User [age=20, name=Rollen]
    --------------------------------------------
    user1:User [age=22, name=xiaolong]
    --------------------------------------------
    user2:User [age=25, name=lidakang]
    --------------------------------------------
    构造函数[0]:private reflect.User(int,java.lang.String)
    参数类型[0]:(int,java.lang.String)
    构造函数[1]:public reflect.User(java.lang.String)
    参数类型[1]:(java.lang.String)
    构造函数[2]:public reflect.User()
    参数类型[2]:()

    关于Constructor类本身一些常用方法如下(仅部分,其他可查API),

    方法返回值方法名称方法说明
    Class<T>getDeclaringClass()返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
    Type[]getGenericParameterTypes()按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。
    StringgetName()以字符串形式返回此构造方法的名称。
    Class<?>[]getParameterTypes()按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型
    TnewInstance(Object... initargs)使用此 Constructor对象表示的构造函数来创建新实例
    StringtoGenericString()返回描述此 Constructor 的字符串,其中包括类型参数。

    代码演示如下:

    Constructor cs3=clazz.getDeclaredConstructor(int.class,String.class);
    
    System.out.println("-----getDeclaringClass-----");
    Class uclazz=cs3.getDeclaringClass();
    //Constructor对象表示的构造方法的类
    System.out.println("构造方法的类:"+uclazz.getName());
    
    System.out.println("-----getGenericParameterTypes-----");
    //对象表示此 Constructor 对象所表示的方法的形参类型
    Type[] tps=cs3.getGenericParameterTypes();
    for (Type tp:tps) {
        System.out.println("参数名称tp:"+tp);
    }
    System.out.println("-----getParameterTypes-----");
    //获取构造函数参数类型
    Class<?> clazzs[] = cs3.getParameterTypes();
    for (Class claz:clazzs) {
        System.out.println("参数名称:"+claz.getName());
    }
    System.out.println("-----getName-----");
    //以字符串形式返回此构造方法的名称
    System.out.println("getName:"+cs3.getName());
    
    System.out.println("-----getoGenericString-----");
    //返回描述此 Constructor 的字符串,其中包括类型参数。
    System.out.println("getoGenericString():"+cs3.toGenericString());
    /**
     输出结果:
     -----getDeclaringClass-----
     构造方法的类:reflect.User
     -----getGenericParameterTypes-----
     参数名称tp:int
     参数名称tp:class java.lang.String
     -----getParameterTypes-----
     参数名称:int
     参数名称:java.lang.String
     -----getName-----
     getName:reflect.User
     -----getoGenericString-----
     getoGenericString():private reflect.User(int,java.lang.String)
     */

    其中关于Type类型这里简单说明一下,Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。getGenericParameterTypesgetParameterTypes 都是获取构成函数的参数类型,前者返回的是Type类型,后者返回的是Class类型,由于Type顶级接口,Class也实现了该接口,因此Class类是Type的子类,Type 表示的全部类型而每个Class对象表示一个具体类型的实例,如String.class仅代表String类型。由此看来Type与 Class 表示类型几乎是相同的,只不过 Type表示的范围比Class要广得多而已。当然Type还有其他子类,如:

    • TypeVariable:表示类型参数,可以有上界,比如:T extends Number

    • ParameterizedType:表示参数化的类型,有原始类型和具体的类型参数,比如:List<String>

    • WildcardType:表示通配符类型,比如:?, ? extends Number, ? super Integer

    通过以上的分析,对于Constructor类已有比较清晰的理解,利用好Class类和Constructor类,我们可以在运行时动态创建任意对象,从而突破必须在编译期知道确切类型的障碍。

    Field类及其用法

    Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:

    方法返回值方法名称方法说明
    FieldgetDeclaredField(String name)获取指定name名称的(包含private修饰的)字段,不包括继承的字段
    Field[]getDeclaredField()获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
    FieldgetField(String name)获取指定name名称、具有public修饰的字段,包含继承字段
    Field[]getField()获取修饰符为public的字段,包含继承字段

     
    下面的代码演示了上述方法的使用过程

    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectField {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class<?> clazz = Class.forName("reflect.Student");
            //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
            // 否则抛NoSuchFieldException
            Field field = clazz.getField("age");
            System.out.println("field:"+field);
    
            //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
            Field fields[] = clazz.getFields();
            for (Field f:fields) {
                System.out.println("f:"+f.getDeclaringClass());
            }
    
            System.out.println("================getDeclaredFields====================");
            //获取当前类所字段(包含private字段),注意不包含父类的字段
            Field fields2[] = clazz.getDeclaredFields();
            for (Field f:fields2) {
                System.out.println("f2:"+f.getDeclaringClass());
            }
            //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
            Field field2 = clazz.getDeclaredField("desc");
            System.out.println("field2:"+field2);
        }
        /**
          输出结果: 
         field:public int reflect.Person.age
         f:public java.lang.String reflect.Student.desc
         f:public int reflect.Person.age
         f:public java.lang.String reflect.Person.name
    
         ================getDeclaredFields====================
         f2:public java.lang.String reflect.Student.desc
         f2:private int reflect.Student.score
         field2:public java.lang.String reflect.Student.desc
         */
    }
    
    class Person{
        public int age;
        public String name;
        //省略set和get方法
    }
    
    class Student extends Person{
        public String desc;
        private int score;
        //省略set和get方法
    }

    上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。下面将通过Field类本身的方法对指定类属性赋值,代码演示如下:

    //获取Class对象引用
    Class<?> clazz = Class.forName("reflect.Student");
    
    Student st= (Student) clazz.newInstance();
    //获取父类public字段并赋值
    Field ageField = clazz.getField("age");
    ageField.set(st,18);
    Field nameField = clazz.getField("name");
    nameField.set(st,"Lily");
    
    //只获取当前类的字段,不获取父类的字段
    Field descField = clazz.getDeclaredField("desc");
    descField.set(st,"I am student");
    Field scoreField = clazz.getDeclaredField("score");
    //设置可访问,score是private的
    scoreField.setAccessible(true);
    scoreField.set(st,88);
    System.out.println(st.toString());
    
    //输出结果:Student{age=18, name='Lily ,desc='I am student', score=88} 
    
    //获取字段值
    System.out.println(scoreField.get(st));
    // 88

    其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,当然关于Field类还有其他常用的方法如下:

    方法返回值方法名称方法说明
    voidset(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
    Objectget(Object obj)返回指定对象上此 Field 表示的字段的值
    Class<?>getType()返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
    booleanisEnumConstant()如果此字段表示枚举类型的元素则返回 true;否则返回 false
    StringtoGenericString()返回一个描述此 Field(包括其一般类型)的字符串
    StringgetName()返回此 Field 对象表示的字段的名称
    Class<?>getDeclaringClass()返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
    voidsetAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性

     
    上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。

    Method类及其用法

    Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。下面是Class类获取Method对象相关的方法:

    方法返回值方法名称方法说明
    MethodgetDeclaredMethod(String name, Class<?>... parameterTypes)返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
    Method[]getDeclaredMethod()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
    MethodgetMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
    Method[]getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。

    同样通过案例演示上述方法:

    import java.lang.reflect.Method;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectMethod  {
    
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
    
            Class clazz = Class.forName("reflect.Circle");
    
            //根据参数获取public的Method,包含继承自父类的方法
            Method method = clazz.getMethod("draw",int.class,String.class);
    
            System.out.println("method:"+method);
    
            //获取所有public的方法:
            Method[] methods =clazz.getMethods();
            for (Method m:methods){
                System.out.println("m::"+m);
            }
    
            System.out.println("=========================================");
    
            //获取当前类的方法包含private,该方法无法获取继承自父类的method
            Method method1 = clazz.getDeclaredMethod("drawCircle");
            System.out.println("method1::"+method1);
            //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
            Method[] methods1=clazz.getDeclaredMethods();
            for (Method m:methods1){
                System.out.println("m1::"+m);
            }
        }
    
    /**
         输出结果:
         method:public void reflect.Shape.draw(int,java.lang.String)
    
         m::public int reflect.Circle.getAllCount()
         m::public void reflect.Shape.draw()
         m::public void reflect.Shape.draw(int,java.lang.String)
         m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
         m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
         m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
         m::public boolean java.lang.Object.equals(java.lang.Object)
         m::public java.lang.String java.lang.Object.toString()
         m::public native int java.lang.Object.hashCode()
         m::public final native java.lang.Class java.lang.Object.getClass()
         m::public final native void java.lang.Object.notify()
         m::public final native void java.lang.Object.notifyAll()
    
         =========================================
         method1::private void reflect.Circle.drawCircle()
    
         m1::public int reflect.Circle.getAllCount()
         m1::private void reflect.Circle.drawCircle()
         */
    }
    
    class Shape {
        public void draw(){
            System.out.println("draw");
        }
    
        public void draw(int count , String name){
            System.out.println("draw "+ name +",count="+count);
        }
    
    }
    class Circle extends Shape{
    
        private void drawCircle(){
            System.out.println("drawCircle");
        }
        public int getAllCount(){
            return 100;
        }
    }

    在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:

    Class clazz = Class.forName("reflect.Circle");
    //创建对象
    Circle circle = (Circle) clazz.newInstance();
    
    //获取指定参数的方法对象Method
    Method method = clazz.getMethod("draw",int.class,String.class);
    
    //通过Method对象的invoke(Object obj,Object... args)方法调用
    method.invoke(circle,15,"圈圈");
    
    //对私有无参方法的操作
    Method method1 = clazz.getDeclaredMethod("drawCircle");
    //修改私有方法的访问标识
    method1.setAccessible(true);
    method1.invoke(circle);
    
    //对有返回值得方法操作
    Method method2 =clazz.getDeclaredMethod("getAllCount");
    Integer count = (Integer) method2.invoke(circle);
    System.out.println("count:"+count);
    
    /**
        输出结果:
        draw 圈圈,count=15
        drawCircle
        count:100
    */

    在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。

    方法返回值方法名称方法说明
    Objectinvoke(Object obj, Object... args)对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
    Class<?>getReturnType()返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型
    TypegetGenericReturnType()返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。
    Class<?>[]getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组
    Type[]getGenericParameterTypes()按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型
    StringgetName()以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
    booleanisVarArgs()判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
    StringtoGenericString()返回描述此 Method 的字符串,包括类型参数。

     
    getReturnType方法/getGenericReturnType方法都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息

    public interface Type {
        //1.8新增
        default String getTypeName() {
            return toString();
        }
    }

    而getParameterTypes/getGenericParameterTypes也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的。

    反射包中的Array类

    在Java的java.lang.reflect包中存在着一个可以动态操作数组的类,Array,它提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作进行取值和赋值。在Class类中与数组关联的方法是:

    方法返回值方法名称方法说明
    Class<?>getComponentType()返回表示数组元素类型的 Class,即数组的类型
    booleanisArray()判定此 Class 对象是否表示一个数组类。

    java.lang.reflect.Array中的常用静态方法如下:

    方法返回值方法名称方法说明
    static Objectset(Object array, int index)返回指定数组对象中索引组件的值。
    static intgetLength(Object array)以 int 形式返回指定数组对象的长度
    static objectnewInstance(Class<?> componentType, int... dimensions)创建一个具有指定类型和维度的新数组。
    static ObjectnewInstance(Class<?> componentType, int length)创建一个具有指定的组件类型和长度的新数组。
    static voidset(Object array, int index, Object value)将指定数组对象中索引组件的值设置为指定的新值。

    下面通过一个简单例子来演示这些方法

    package reflect;
    
    import java.lang.reflect.Array;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectArray {
    
        public static void main(String[] args) throws ClassNotFoundException {
            int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            //获取数组类型的Class 即int.class
            Class<?> clazz = array.getClass().getComponentType();
            //创建一个具有指定的组件类型和长度的新数组。
            //第一个参数:数组的类型,第二个参数:数组的长度
            Object newArr = Array.newInstance(clazz, 15);
            //获取原数组的长度
            int co = Array.getLength(array);
            //赋值原数组到新数组
            System.arraycopy(array, 0, newArr, 0, co);
            for (int i:(int[]) newArr) {
                System.out.print(i+",");
            }
    
            //创建了一个长度为10 的字符串数组,
            //接着把索引位置为6 的元素设为"hello world!",然后再读取索引位置为6 的元素的值
            Class clazz2 = Class.forName("java.lang.String");
    
            //创建一个长度为10的字符串数组,在Java中数组也可以作为Object对象
            Object array2 = Array.newInstance(clazz2, 10);
    
            //把字符串数组对象的索引位置为6的元素设置为"hello"
            Array.set(array2, 6, "hello world!");
    
            //获得字符串数组对象的索引位置为5的元素的值
            String str = (String)Array.get(array2, 6);
            System.out.println();
            System.out.println(str);//hello
        }
        /**
         输出结果:
         1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
         hello world!
         */
    }

    通过上述代码演示,确实可以利用Array类和反射相结合动态创建数组,也可以在运行时动态获取和设置数组中元素的值,其实除了上的set/get外Array还专门为8种基本数据类型提供特有的方法,如setInt/getInt、setBoolean/getBoolean,其他依次类推,需要使用是可以查看API文档即可。除了上述动态修改数组长度或者动态创建数组或动态获取值或设置值外,可以利用泛型动态创建泛型数组如下:

    /**
      * 接收一个泛型数组,然后创建一个长度与接收的数组长度一样的泛型数组,
      * 并把接收的数组的元素复制到新创建的数组中,
      * 最后找出新数组中的最小元素,并打印出来
      * @param a
      * @param <T>
      */
     public  <T extends Comparable<T>> void min(T[] a) {
         //通过反射创建相同类型的数组
         T[] b = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length);
         for (int i = 0; i < a.length; i++) {
             b[i] = a[i];
         }
         T min = null;
         boolean flag = true;
         for (int i = 0; i < b.length; i++) {
             if (flag) {
                 min = b[i];
                 flag = false;
             }
             if (b[i].compareTo(min) < 0) {
                 min = b[i];
             }
         }
         System.out.println(min);
     }

    毕竟我们无法直接创建泛型数组,有了Array的动态创建数组的方式这个问题也就迎刃而解了。

    //无效语句,编译不通
    T[] a = new T[];

    ok~,到这反射中几个重要并且常用的类我们都基本介绍完了,但更重要是,我们应该认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只会简单地检查这个对象,判断该对象属于那种类型,同时也应该知道,在使用反射机制创建对象前,必须确保已加载了这个类的Class对象,当然这点完全不必由我们操作,毕竟只能JVM加载,但必须确保该类的”.class”文件已存在并且JVM能够正确找到。关于Class类的方法在前面我们只是分析了主要的一些方法,其实Class类的API方法挺多的,建议查看一下API文档,浏览一遍,有个印象也是不错的选择,这里仅列出前面没有介绍过又可能用到的API:

     /** 
      *    修饰符、父类、实现的接口、注解相关 
      */
    
    //获取修饰符,返回值可通过Modifier类进行解读
    public native int getModifiers();
    //获取父类,如果为Object,父类为null
    public native Class<? super T> getSuperclass();
    //对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类间接继承来的
    public native Class<?>[] getInterfaces();
    //自己声明的注解
    public Annotation[] getDeclaredAnnotations();
    //所有的注解,包括继承得到的
    public Annotation[] getAnnotations();
    //获取或检查指定类型的注解,包括继承得到的
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
    
    /** 
      *   内部类相关
      */
    //获取所有的public的内部类和接口,包括从父类继承得到的
    public Class<?>[] getClasses();
    //获取自己声明的所有的内部类和接口
    public Class<?>[] getDeclaredClasses();
    //如果当前Class为内部类,获取声明该类的最外部的Class对象
    public Class<?> getDeclaringClass();
    //如果当前Class为内部类,获取直接包含该类的类
    public Class<?> getEnclosingClass();
    //如果当前Class为本地类或匿名内部类,返回包含它的方法
    public Method getEnclosingMethod();
    
    /** 
      *    Class对象类型判断相关
      */
    //是否是数组
    public native boolean isArray();  
    //是否是基本类型
    public native boolean isPrimitive();
    //是否是接口
    public native boolean isInterface();
    //是否是枚举
    public boolean isEnum();
    //是否是注解
    public boolean isAnnotation();
    //是否是匿名内部类
    public boolean isAnonymousClass();
    //是否是成员类
    public boolean isMemberClass();
    //是否是本地类
    public boolean isLocalClass(); 

    ok~,本篇到此完结。


    展开全文
  • 理解什么是线程安全性、原子性

    万次阅读 2019-12-29 11:56:30
    提到多线程这里要说两个概念,就是串行和并行,搞清楚这个我们才能更好的理解多线程。所谓串行其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子,我们下载多个文件,在串行中它是按照一定的...

    目录

    •写在前面

    •原子性

    加锁机制


    •写在前面

    进程想要执行任务需要依赖线程,换句话说就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。提到多线程这里要说两个概念,就是串行和并行,搞清楚这个我们才能更好的理解多线程。所谓串行其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子,我们下载多个文件,在串行中它是按照一定的顺序去进行下载的,也就是说必须等下载完A之后,才能开始下载B,它们在时间上是不可能发生重叠的。

    要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。要是的对象是线程安全的,需要采用同步机制来协同对对象可变状态的访问,如果无法实现协同,那么可能会导致数据破坏以及其他不该出现的结果。java中的同步机制是关键字synchronized,它提供了独占式的加锁方式,不仅如此还包括volatile类型变量,显式锁Lock以及原子变量。

    单线程近似定义为“所见即所知”,那么定义线程安全性,则当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。一个类在单线程中运行都不正确,那么它肯定是不会是线程安全的。如果正确的实现了某个对象,那么在任何操作中(包括调用对象的公有方法或者对其公有域进行读写操作)都不会违背不变性条件或后验条件。在线程安全类的对象实例上执行的任何串行或并行操作都不会处于无效状态。

    无状态的对象一定是安全的,啥是无状态?这个类既不包括任何域,也不包括任何对其他类中域的引用。举个例子,大多数Servlet都是无状态的,从而极大的降低了在实现Servlet线程安全性时的复杂性,只有当Servlet在处理请求时需要保存一些信息,线程安全性才会成为一个问题。

    •原子性

    其实原子性就是一个不可分割的,我们怎么理解呢?举个例子,当我们在无状态对象中增加一个状态时,会发生什么情况?比我们要计算一个类调用了多少次(这个类可以是Servlet),我们定义一个私有的Long类型的域(命名为count),在方法中没调用一次就将这个值加一(即count++),如下。

       Integer count = 0;
       public void getCount() {
           count ++;
           System.out.println(count);
       }

    由于我们没有对这个类进行任何的同步机制操作,所以这个类是非线程安全的,虽然在单线程环境中能正确运行,但在多线程情况下就会出现问题。我们注意到count++这个操作,这个操作看上去是一个操作,其实这个操作并非原子性的,因为它并不会作为一个不可分割的操作来执行,实际上count++ 包含了三个独立的操作,分为读取count的值,将值加1,然后计算结构写入count中,这是一个“读取-修改-写入”的操作序列,并且其结果依赖于之前的状态。在并发下就会出现问题,我再多线程下跑了上面那段代码,结果如下

    我们可以看到,这里出现了两个26,为什么会出现这种情况,出现这种情况显然表明我们这个方法根本就不是线程安全的,出现这种问题的原因有很多,我们说最常见的一种,就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的。

    在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,它有一个正式的名字,叫“竞态条件”。竞态条件是啥?当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件,最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观察结果来决定下一步的动作。比如首先观察到某个条件为真(例如文件X不存在),然后根据这个观察结果采用相应的动作(创建文件X),但事实上,在你观察到这个结果以及开始创建文件之间,观察结果可能变得无效(另一个线程在这个期间创建了文件X),从而导致各种问题(未预期的异常、数据覆盖、文件被破坏等)。

    与大多数并发错误一样,竞态条件并不总是会产生错误,还需要某种不恰当的执行时序。原子操作是对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作。而我们前面提到的类似count++的这种操作,叫做复合操作,即包含了一组必须以原子方式执行的操作以确保线程安全性。在实际情况中,应该尽可能的使用现有的线程安全对象来管理类的状态,与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易,从而也更容易维护和验证线程安全性。这里值得一提的是,java中给我们弄好了很多原子操作的类型,在这个包下java.util.concurrent.atomic。

    加锁机制

    上面我们说了,我们可以对单个类进行原子性操作,这样可以保证我们程序的安全性,但是,我们想一想,如果当多个原子性的操作同时进行时,而且各个原子性操作之间都存在相互依赖的关系,这种情况下,我们怎么保证程序运行正确(线程安全)?如果还是使用原子性操作的方法,那么我们要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

    说到这里,我们就不得不提一下java提供的一种内置的锁机制来支持原子性,即同步代码块(synchronized block),同步代码快包括两部分,一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。每个Java对象都可以用作一个实现同步的锁,这些所被称为内置锁或监视器锁。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,这里要说,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。值得一提的是,内置锁相当于一种互斥锁,意味着最多只有一个线程能持有这种锁。要注意了,任何一个执行同步代码块的线程,都不能看到有其他线程正在执行由同一锁保护的同步代码块(这里涉及到可见性的问题)。synchronized直接加在方法上,虽然能很方便的解决线程安全的问题,但是也会带来性能低下的问题,所以synchronized怎么使用,也是值得学习的。

    可重入函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。

    当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会被阻塞,然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是线程,而不是调用。重入的一种实现方式是,为每一个锁关联一个获取计数值和一个所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次请求获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应的递减,当计数值为0时,这个锁将被释放。

    到此我们就可以将多个复合操作封装到一个同步代码块中,但是这样是不够的,如果用同步代码块来协调对某个变量的访问,那么在访问这个变量的所有位置上都需要使用同步。而且,当使用锁来协调对某个变量的访问是,在访问变量的所有位置上都要使用同一个锁。而且,并不是只有在写入共享变量时才需要使用同步,对于可能被多个线程同时访问的可变状态变量,在访问它时都需要有同一个锁,这种情况下,我们成状态变量时由这个锁保护的。

    在这里值得一提的是,当使用锁时,你应该清楚代码块中实现的功能,以及在执行该代码块时是否需要很长的时间。无论是执行计算密集的操作,还是在执行某个可能阻塞的操作,如果持有锁的时间过长,那么都会带来活跃性或性能的问题。当执行时间较长的计算或者可能无法快速完成的操作时(例如网络IO或控制台IO),一定不要持有锁。

    展开全文
  • 可变对象可变对象

    万次阅读 2018-08-12 22:22:48
    ”中,因为在调用方法时与方法内部,输出id是相同,所以理解官方的那句话 Remember that arguments are passed by assignment in Python.&nbsp; 要记住,Python 里的参数是通过赋值传递的。  之后...
  • 创建后状态能被修改...可变性的理解: 无论是Java语言规范还是Java存储模型都没有对可变性做出正式的定义。可变性并不是将域简单地等于将对象的所有变量都声明为final类型,所有域都是final类型的对象仍然可以
  • 静态成员变量是属于类的,一旦赋值则可改变, 所有对象共享这一个值. (平时使用需谨慎) =========================分割线 非常感谢指点, 发现我原来对静态变量理解的是错误的. 贴上最新的理解  @...
  • javascript测试题

    千次阅读 2021-06-18 13:10:40
    一、单选题(30分)1.以下说法正确的是:()BA.DOM的全称为Document Object ...下面关于javascript理解正确的:()AA.javascript是由核心语法,五大核心对象,DOM,BOM组成B.document.write()向文档中写入内容,可以写在函...
  • httpSession的正确理解

    万次阅读 多人点赞 2018-09-06 09:47:51
    下面说说我的理解吧: 一个session就是一系列某用户和服务器间的通讯。服务器有能力分辨出不同的用户。一个session的建立是从一个用户向服务器发第一个请求开始,而以用户显式结束或session超时为结束。 其工作原理...
  • 深入理解Class对象 RRTI的概念以及Class对象作用 认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于...
  • C++中引用能再更改绑定的对象理解

    千次阅读 多人点赞 2018-04-25 16:08:05
    转自:https://blog.csdn.net/gao1440156051/article/details/51073433C++Primer教材上说引用能再改变绑定搞得对象,也就是引用再第二次赋值。可是下面的程序能正常运行,不会出错。这里怎么出现了引用赋值语句呢...
  • 全方位深入理解JavaScript面向对象

    万次阅读 多人点赞 2018-03-30 10:13:16
    JavaScript面向对象...理解JavaScript中的函数对象与普通对象 理解prototype和proto 理解原型和原型链 详解原型链相关的Object方法 了解如何用ES5模拟类,以及各种方式的优缺点 了解如何用ES6实现面向对象 目录...
  • 5 理解对象

    2020-08-20 19:11:23
    下面是几个示例: 代码冰箱贴 下面的代码被胡乱地贴在冰箱上,请利用你的对象创建和句点表示法技能,将它们放在正确的位置。请注意,其中有些冰箱贴可能是多余的! var dog = { name: _________ _________: 20.2 age...
  • 面向对象设计的七大设计原则详解

    万次阅读 多人点赞 2018-10-03 12:32:21
    文章目录面向对象的七大设计原则简述七大原则之间的关系一、开闭原则(The Open-Closed Principle ,OCP)概念理解系统设计需要遵循开闭原则的原因开闭原则的实现方法一个符合开闭原则的设计开闭原则的相对性二、 ...
  • 通过AbstractCoroutineContextElement更加深入理解Kotlin伴生对象 ​ 最近疫情居家办公,没事摸鱼在看《深入理解Kotlin协程》这本书,发现Kotlin协程上下文需要添加元素时会用到AbstractCoroutineContextElement这...
  • 了解并发编程中对象的共享

    万次阅读 2019-12-30 11:59:56
    我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且希望确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。如果没有同步,那么这种情况就无法实现,所以我们可以通过显示...
  • 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对可信的进行信息隐藏。 继承 面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它...
  • 对于js的原型和原型链的理解

    万次阅读 多人点赞 2019-06-23 22:20:23
    原型模式是用于创建重复的对象,同时又能保证性能,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。原型模式的目的是用原型实例...
  • 其实后来查出原理我都懂,还是挂在自己理解不深刻,脑海里没有一套完整的知识体系吧。 如果当时面试官把问题分成两个来问我 一:Vue的双向绑定原理 答:利用数据劫持和发布者-订阅者模式监听数据值的变化,并且...
  • 由一道关于sleep()和wait()方法的题目...- B sleep释放对象锁,wait放弃对象锁 - C sleep暂停线程、但监控状态仍然保持,结束后会自动恢复 - D wait后进入等待锁定池,只有针对此对象发出notify方法后获得对象锁进...
  • python中关于self的理解

    千次阅读 2022-05-16 07:27:00
    可以理解为抽象设计图 类名:类的名字。 查看/实现方式 :print(nameMain)或者print(self.class) 类的实例:即类创建的对象,类每创建一个实例就开辟新的内存地址指向这个创建的实例。抽象设计图具体实现的东西 查看...
  • 原型链的理解(全面)

    千次阅读 多人点赞 2020-11-25 16:31:01
    创建一个实例 使用构造函数创建一个实例 function Star(age,name) { this.age = age this.name = name ...理解构造函数里面的内容,构造函数里面是有静态成员和实例成员的 实例成员就是构造函数内部通过thi
  • 开源对象存储MinIO技术白皮书

    万次阅读 多人点赞 2019-09-27 13:38:36
    ,因为一旦在设计上引入主节点,那么如果主节点宕机,整个锁管理器机制即将失效,这对MinIO对象存储系统影响非常严重,是可接受的。 • 系统必须是弹性的 ,即使存在多个失效的节点,只要它们的个数小于n/2, ...
  • 深入理解Java类型信息(Class对象)

    千次阅读 多人点赞 2019-05-19 19:36:01
    RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息。这里分两种: ...
  • 通过构造函数实例化出来的对象,并具有prototype属性,其默认有一个__proto__属性,__proto__的值指向构造函数的原型。在原型对象上添加或修改的属性,在所有实例化出的对象上都可共享。 /*代码基本架构 1.每个...
  • 面向对象编程基本概念

    千次阅读 2018-04-18 15:41:53
    OOP专注于模块化,改变容忍度,代码重用,易于理解和分布式开发。今天大多数项目都使用面向对象的概念。由于C ++引入了OOP,因此用户体验已经产生了关于如何最好地利用OOP的更多知识。有很多关于面向对象的协议和更...
  • JavaScript 进阶教程(1)--面向对象编程

    万次阅读 多人点赞 2020-08-16 18:13:37
    理解面向对象开发思想 掌握 JavaScript 面向对象开发相关模式 2 面向对象介绍 2.1什么是对象 Everything is object (一切皆对象) 我们可以从两个层次来理解对象: (1) 对象是单个事物的抽象。...
  • python 面向对象全面详解

    万次阅读 多人点赞 2019-01-12 14:25:23
    一、对面向对象理解 1、面向对象的编程---object oriented programming 2、python面向对象的重要术语: 3、函数和面向对象编程的区别 二、封装、继承、多态 1、封装(Encapsulation) 2、继承(Inheritance...
  • 如何正确的创建和销毁Java对象

    万次阅读 2017-06-15 18:24:16
    Java是一门强大的高级语言。在学习了其基础知识后,我们仍需要理解其深刻的内涵。接下来,我们会以《Effective Java》一书做为Java进阶学习的载体,对Java进行一个系统的、全新的认识。 第一章:创建和销毁对象
  • set无序,list有序的正确理解

    千次阅读 2019-03-22 17:44:43
    Set特点:元素无放入顺序,元素可重复。注意,元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的。 2、其实,有序或无序是指是否按照其添加的顺序来存储对象。List 是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 582,758
精华内容 233,103
热门标签
关键字:

对象的理解不正确的是