免责声明:
    本文内容多来自网络文章,转载为个人收藏,分享知识,如有侵权,请联系博主进行删除。

基础篇#

1.1Java基础##

  • 面向对象的特征:继承、封装和多态

三大特性是:封装,继承,多态

所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

所谓继承是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

  • 常见到的runtime exception

算术异常类:ArithmeticExecption
空指针异常类:NullPointerException
类型强制转换异常:ClassCastException
数组越界异常:IndexOutOfBoundsException
操作数据库异常:SQLException
输入输出异常:IOException

  • int 和 Integer 有什么区别,Integer的值缓存范围
  • 包装类,装箱和拆箱

区别:
int 是基本数据类型,直接存数值,进行初始化时 int 类的变量初始值为 0 ;
Integer 是对象,用一个引用指向这个对象,Integer 的变量初始化值为 null 。

两种数据类型:
原始数据类型:分别:boolean、byte 、short、char、int、float、double、long;
引用数据类型,分为数组 和 接口。

为了编程开发的方便,Java 还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的 包装类型(wrapper class),int的包装类就是 Integer,从 Java5 开始引入了自动装箱和自动拆箱,使得二者可以相互转换。

原始数据类型:boolean 、char、byte、short、int、long 、float、double
封装类类型: Boolean、Character、Byte、Short、Integer、Long、Float、Double

自动装箱:将基本数据类重新转换为对象
自动拆箱:将对象重新转换为基本数据类型

缓存:
归结于Java对于Integer 与 int 的自动装箱和拆箱的设计,是一种模式:叫 享元模式(flyweight)。
加大对简单数字的重利用,Java 定义在自动装箱时对于值在 -128~127 之间的值,他们被装箱为Integer 对象后,会存在内存中被重用,始终只有一个对象。

参考:int 和 Integer

  • String、StringBuilder、StringBuffer

区别:
1、String是字符串常量,而StringBuffer和StringBuilder是字符串变量。由String创建的字符内容是不可改变的,而由StringBuffer和StringBuidler创建的字符内容是可以改变的。
2、StringBuffer是线程安全的,而StringBuilder是非线程安全的。StringBuilder是从JDK 5开始,为StringBuffer类补充的一个单线程的等价类。我们在使用时应优先考虑使用StringBuilder,因为它支持StringBuffer的所有操作,但是因为它不执行同步,不会有线程安全带来额外的系统消耗,所以速度更快。

为什么String不可变:
虽然String、StringBuffer和StringBuilder都是final类,它们生成的对象都是不可变的,而且它们内部也都是靠char数组实现的,但是不同之处在于,String类中定义的char数组是final的,而StringBuffer和StringBuilder都是继承自AbstractStringBuilder类,它们的内部实现都是靠这个父类完成的,而这个父类中定义的char数组只是一个普通是私有变量,可以用append追加。因为AbstractStringBuilder实现了Appendable接口。

String类不可变性的好处:
1.只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(译者注:String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
2.如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
3.因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
4.类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
5.因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

  • 重载和重写的区别

定义:

重载:
Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。
重写:
父类与子类之间的多态性,对父类的函数进行重新定义。
子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。

特点:

重载:
1、在使用重载时只能通过不同的参数样式(方法签名)。例如,不同的参数类型,不同的参数个数,不同的参数顺序。
2、不能通过访问权限、返回类型、抛出的异常进行重载。
3、方法的异常类型和数目不会对重载造成影响。
4、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。
重写:
1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果。
2、覆盖的方法的返回值必须和被覆盖的方法的返回一致。
3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类。
4、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
5、方法被定义为final不能被重写。

  • Java普通类、抽象类与接口的区别

抽象类和接口
抽象类与接口都是用于功能的抽象。
1、方法: 接口只能有抽象方法,抽象类可以有抽象方法和非抽象方法。Java8以后,接口可以直接定义default和static方法了。抽象类可以有protect和private方法。
2、成员变量:接口中的成员变量默认是static和final的,抽象类可以像常规的对象一样定义各种成员变量。可以是非static和final的。
3、实现:接口不能实现其他的接口,只能继承一个其它的接口。抽象类可以实现多个接口。
4、构造器:抽象类可以有构造器,接口不能有构造器。
5、main方法: 抽象类可以有main方法,并且我们可以运行它。接口不能有main方法。
6、速度:抽象类速度更快一些,接口需要时间寻找类中的实现方法。

普通类和抽象类
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

  • 说说反射的用途及实现

反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

Java反射框架主要提供以下功能:
1.在运行时判断任意一个对象所属的类;
2.在运行时构造任意一个类的对象;
3.在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
4.在运行时调用任意一个对象的方法。

反射的主要用途
1、反射最重要的用途就是开发各种通用框架。

基本反射功能的实现(反射相关的类一般都在java.lang.relfect包里):
1、获得Class对象
      使用Class类的forName静态方法
      直接获取某一个对象的class
     调用某个对象的getClass()方法
2、判断是否为某个类的实例
     用instanceof关键字来判断是否为某个类的实例
3、创建实例
      使用Class对象的newInstance()方法来创建Class对象对应类的实例。
      先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。
4、获取方法
    getDeclaredMethods()
getMethods()
5、获取构造器信息
     getDeclaredConstructors()
getConstructors()
6、获取类的成员变量(字段)信息
   getFiled: 访问公有的成员变量
     getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量
     getFileds和getDeclaredFields用法
7、调用方法
invoke()
8、利用反射创建数组
 Array.newInstance()

注意:
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

  • 说说自定义注解的场景及实现

  • HTTP请求的GET与POST方式的区别

1、
GET请求:
请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。URL的编码格式采用的是ASCII 编码,而不是uniclde,即是说所有的非ASCII字符都要编码之后再传输。
POST请求:POST请求会把请求的数据放置在HTTP请求包的包体中。上面的item=bandsaw就是实际的传输数 据。
因此,GET请求的数据会暴露在地址栏中,而POST请求则不会。
2、传输数据的大小
在HTTP规范中,没有对URL的长度和传输的数据大小进行限制。但是在实际开发过程中,对于GET,特定的 浏览器和服务器对URL的长度有限制。因此,在使用GET请求时,传输数据会受到URL长度的限制。
对于POST,由于不是URL传值,理论上是不会受限制的,但是实际上各个服务器会规定对POST提交数据大小进 行限制,Apache、IIS都有各自的配置。
3、安全性
POST的安全性比GET的高。这里的安全是指真正的安全,而不同于上面GET提到的安全方法中的安全,上面 提到的安全仅仅是不修改服务器的数据。比如,在进行登录操作,通过GET请求,用户名和密码都会暴露再URL 上,因为登录页面有可能被浏览器缓存以及其他人查看浏览器的历史记录的原因,此时的用户名和密码就很容易被 他人拿到了。除此之外,GET请求提交的数据还可能会造成Cross-site request frogery攻击
4、HTTP中的GET,POST,SOAP协议都是在HTTP上运行的

  • Session与Cookie区别

Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息。如何识别特定的客户呢?cookie就可以做到。每次HTTP请求时,客户端都会发送相应的Cookie信息到服务端。它的过期时间可以任意设置,如果你不主动清除它,在很长一段时间里面都可以保留着,即便这之间你把电脑关机了。

Session是在无状态的HTTP协议下,服务端记录用户状态时用于标识具体用户的机制。它是在服务端保存的用来跟踪用户的状态的数据结构,可以保存在文件、数据库或者集群中。在浏览器关闭后这次的Session就消失了,下次打开就不再拥有这个Session。其实并不是Session消失了,而是Session ID变了,服务器端可能还是存着你上次的Session ID及其Session 信息,只是他们是无主状态,也许一段时间后会被删除。
如果你的站点是多节点部署,使用Nginx做负载均衡,那么有可能会出现Session丢失的情况(比如,忽然就处于未登录状态)。这时可以使用IP负载均衡(IP绑定 ip_hash,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决Session的问题),或者将Session信息存储在集群中。在大型的网站中,一般会有专门的Session服务器集群,用来保存用户会话,这时可以使用缓存服务比如Memcached或者Redis之类的来存放Session。

区别:
1、Cookie 在客户端(浏览器),Session 在服务器端。
2、Cookie的安全性一般,他人可通过分析存放在本地的Cookie并进行Cookie欺骗。在安全性第一的前提下,选择Session更优。重要交互信息比如权限等就要放在Session中,一般的信息记录放Cookie就好了。
3、单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie。
4、Session 可以放在 文件、数据库或内存中,比如在使用Node时将Session保存在redis中。由于一定时间内它是保存在服务器上的,当访问增多时,会较大地占用服务器的性能。考虑到减轻服务器性能方面,应当适时使用Cookie。
5、Session 的运行依赖Session ID,而 Session ID 是存在 Cookie 中的,也就是说,如果浏览器禁用了 Cookie,Session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 Session ID)。
6、用户验证这种场合一般会用 Session。因此,维持一个会话的核心就是客户端的唯一标识,即Session ID。

注意:
HttpSession默认使用Cookie进行保存SessionID,当客户端禁用了Cookie之后,可以通过URL重写的方式进行实现。
可以通过response.encodeURL(url) 进行实现
API对encodeURL的结束为,当浏览器支持Cookie时,url不做任何处理;当浏览器不支持Cookie的时候,将会重写URL将SessionID拼接到访问地址后。

用途:
Cookies:
1、记住密码,下次自动登录。
2、购物车功能。
3、记录用户浏览数据,进行商品(广告)推荐。

Session:
1、判断用户是否登录。
2、购物车功能。

  • 列出自己常用的JDK包

java.lang: 这个是系统的基础类,比如String、Math、Integer、System和Thread, 提供常用功能。
java.io: 这里面是所有输入输出有关的类,比如文件操作,输入输出流等
java.net: 这里面是与网络有关的类,比如URL,URLConnection、URLEncoder等。
java.util : 这个是系统辅助类,特别是集合类Collection,List,Map,Set;Date、UUID、regex(正则)。
java.sql: 这个是数据库操作的类,Connection, Statememt,ResultSet等
java.math:数学相关,BigDecimal、
javax.persistence:JPA相关类
javax.validation:数据校验,传参、结果
javax.servlet:servlet开发
javax.servlet.http:为基于Http协议的servlet开发提供便利

  • MVC设计思想

MVC是一种软件架构的思想,将一个软件按照模型、视图、控制器进行划分。其中,模型用来封装业务逻辑,视图用来实现表示逻辑,控制器用来协调模型与视图
(视图要通过控制器来调用模型,模型返回的处理结果也要先交给控制器,由控制器来选择合适的视图来显示 处理结果)。
        1)模型: 业务逻辑包含了业务数据的加工与处理以及相应的基础服务(为了保证业务逻辑能够正常进行的事务、安全、权限、日志等等的功能模块)
        2)视图:展现模型处理的结果;另外,还要提供相应的操作界面,方便用户使用。
        3)控制器:视图发请求给控制器,由控制器来选择相应的模型来处理;模型返回的结果给控制器,由控制器选择合适的视图。

  • equals与==的区别

== 的作用:
  基本类型:比较的就是值是否相同
  引用类型:比较的就是地址值是否相同
equals 的作用:
  引用类型:默认情况下,比较的是地址值。
注:不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同

注意字符串缓冲池、Integer缓存池~

  • equals重写原则

自反性:对于任何非null的引用值x,x.equals(x)应返回true。
对称性:对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。
传递性:对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。
一致性:对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。
对于任何非空引用值x,x.equal(null)应返回false。

  • hashCode和equals方法的区别与联系

HashCode 只是在需要用到哈希算法的数据结构中才有用,比如 HashSet, HashMap 和 Hashtable

原则:
1、在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
2、如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
3、如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 不要求 一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

一般来讲,equals 这个方法是给用户调用的,而 hashcode 方法一般用户不会去调用 ;
当一个对象类型作为集合对象的元素时,那么这个对象应该拥有自己的equals()和hashCode()方法,保证不重复,而且要遵守前面所说的几个原则。

  • 什么是Java序列化(串行化)和反序列化(并行化),如何实现Java序列化?或者请解释Serializable 接口的作用

无论何种类型的数据,都是以二进制的形式在网络上传送,为了由一个进程把Java对象发送给另一个进程,需要把其转换为字节序列才能在网络上传送,把JAVA对象转换为字节序列的过程就称为对象的序列化;
将字节序列恢复成Java对象的过程称为对象的反序列化。

序列化的实现:将需要被序列化的类实现Serializable接口,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

jdk api 文档里面关于接口 Serializable 的描述:
类通过实现 java.io.Serializable 接口以启用其序列化功能。
未实现此接口的类将无法使其任何状态序列化或反序列化。
可序列化类的所有子类型本身都是可序列化的。因为实现接口也是间接的等同于继承。
序列化接口没有方法或字段,仅用于标识可序列化的语义。

关于 serialVersionUID 的描述:
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID。

序列化(串行化)特点:
(1)如果某个类能够被串行化,其子类也可以被串行化。
(2)声明为static和transient类型的成员数据不能被串行化。因为static代表类的状态, transient代表对象的临时数据;
(3)相关的类和接口:在java.io包中提供的涉及对象的串行化的类与接口有ObjectOutput接口、ObjectOutputStream类、ObjectInput接口、ObjectInputStream类。

  • Object类中常见的方法,为什么wait notify会放在Object里边?

toString():输出一个对象的地址字符串(哈希code码);可以通过重写toString方法,获取对象的属性!

equals():比较的是对象的引用是否指向同一块内存地址, 重写equals()方法比较两个对象的内容是否相同

Object() :默认构造方法

clone() :创建并返回此对象的一个副本。

finalize() :当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

getClass() :返回一个对象的运行时类。

hashCode() :返回该对象的哈希码值。

notify() :唤醒在此对象监视器上等待的单个线程。

notifyAll() : 唤醒在此对象监视器上等待的所有线程。

wait() : 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

wait(long timeout) : 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。

wait(long timeout, int nanos) : 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

为什么wait notify会放在Object里边?wait(),notify(),notifyAll()用来操作线程为什么定义在Object类中?
1、这些方法存在于同步中;
2、使用这些方法必须标识同步所属的锁;
3、锁可以是任意对象,所以任意对象调用方法一定定义在Object类中。

wait(),sleep()区别:
wait():释放资源,释放锁
sleep():释放资源,不释放锁

  • Java的平台无关性如何体现出来的

Java从四个方面支持了平台无关性:
1、最主要的是Java平台本身(jdk、jre、jvm)。Java平台扮演Java程序和所在的硬件与操作系统之间的缓冲角色。这样Java程序只需要与Java平台打交道,而不用管具体的操作系统。
2、Java语言保证了基本数据类型的值域和行为都是由语言自己定义的。而C/C++中,基本数据类是由它的占位宽度决定的,占位宽度由所在平台决定的。不同平台编译同一个C++程序会出现不同的行为。通过保证基本数据类型在所有平台的一致性,Java语言为平台无关性提供强有力的支持。
3、Java class文件。Java程序最终会被编译成二进制class文件。class文件可以在任何平台创建,也可以被任何平台的Java虚拟机装载运行。它的格式有着严格的定义,是平台无关的。
4、可伸缩性。Sun通过改变API的方式得到三个基础API集合,表现为Java平台不同的伸缩性:J2EE,J2SE,J2ME。

  • JDK、JRE和JVM的区别

Java 开发工具包 (JDK)
Java开发工具包是Java环境的核心组件,并提供编译、调试和运行一个Java程序所需的所有工具,可执行文件和二进制文件。JDK是一个平台特定的软件,有针对Windows,Mac和Unix系统的不同的安装包。可以说JDK是JRE的超集,它包含了JRE的Java编译器,调试器和核心类。

Java虚拟机(JVM)
JVM是Java编程语言的核心。当我们运行一个程序时,JVM负责将字节码转换为特定机器代码。JVM也是平台特定的,并提供核心的Java方法,例如内存管理、垃圾回收和安全机制等。JVM 是可定制化的,我们可以通过Java 选项(java options)定制它,比如配置JVM 内存的上下界。JVM之所以被称为虚拟的是因为它提供了一个不依赖于底层操作系统和机器硬件的接口。这种独立于硬件和操作系统的特性正是Java程序可以一次编写多处执行的原因。

Java运行时环境(JRE)
JRE是JVM的实施实现,它提供了运行Java程序的平台。JRE包含了JVM、Java二进制文件和其它成功执行程序的类文件。JRE不包含任何像Java编译器、调试器之类的开发工具。如果你只是想要执行Java程序,你只需安装JRE即可,没有安装JDK的必要。

JDK, JRE 和JVM的区别:
JDK是用于开发的而JRE是用于运行Java程序的。
JDK和JRE都包含了JVM,从而使得我们可以运行Java程序。
JVM是Java编程语言的核心并且具有平台独立性。

  • Java 8有哪些新特性

1、Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
2、方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
3、默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
4、新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
5、Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
6、Date Time API − 加强对日期与时间的处理。
7、Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

posted on 2019-03-13 19:07  theWayToWill  阅读(169)  评论(0编辑  收藏  举报