一、toString方法

返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。

建议所有子类都重写此方法。

  1. 最好通过getClass().getName()获得类名的字符串
  2. 如果超类(被继承的类)使用了getClass().getName(), 那么子类只要调用super.toString()就可以

    二、泛型

    泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

三、反射

反射的基本运用

一、获得Class对象

  1. 使用Class类的forName静态方法
    1. public static Class<?> forName(String className)


比如在JDBC中加载数据库驱动:

  1. Class.forNamedriver
  1. 直接获取某一个对象的class

    1. Class<?> class = int.class;
    2. Class<?> classInt = Integer.TYPE;
  2. 调用某个对象的getClass()方法

    1. StringBuilder str = new StringBuilder("123")
    2. Class<?> class = str.getClass();

二、判断是否为某个类的实例

用instanceof关键字来判断是否为某个类的实例。同时我们可以借助反射中 Class 对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法

  1. public native boolean isInstance(Object obj);

三、创建实例

  1. 使用Class对象的newInstance()方法来创建Class对象对应类的实例

    1. Class<?> c = String.class;
    2. Object str = c.newInstance();
  2. 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例:

    1. //获取String所对应的Class对象
    2. Class<?> c = String.class;
    3. //获取String类带一个String参数的构造器
    4. Constructor constructor = c.getConstructor(String.class);
    5. //根据构造器创建实例
    6. Object obj = constructor.newInstance("23333");

四、获取方法

  1. getDeclaredMethods方法 返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

    1. public Method[] getDeclaredMethods()
  2. getMethods方法 返回某个类的所有公用(public)方法,包括其继承类的公用方法

    1. public Method][] getMethods()
  3. getMethod方法 返回一个特定的方法,其中第一个参数为方法的名称,后面的参数为方法的参数对应Class的对象

    1. public Method getMethod(String name, Class<?>...parameterTypes)

五、获取构造器信息

通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例

  1. public T newInstance (Object...initargs)
  2. //此方法可以根据传入的参数来调用对应的Constructor创建对象实例

六、获取类的成员变量信息

getFiled:访问公有的成员变量

getDeclaredField:所有已声明的成员变量,但不能得到起父类的成员变量

七、调用方法

当从类中获取一个方法后,可以用 invoke() 方法来调用这个方法

  1. public Object invoke(Object obj, Object... args)

八、获取类名

getName() 返回类的全限定名(包括包名)

  1. Class aClass = ... //获取Class对象
  2. String className = aClass.getName();

如果仅仅只是想获取类的名字(不包含包名),那么使用 getSimpleName()方法

  1. Class aClass = ... //获取Class对象
  2. String simpleClassName = aClass.getSimpleName();

九、获取修饰符

用getModifiers()获取类的修饰符,返回是一个int类型数据

  1. Class aClass = ... //获取Class对象,具体方式可见Class对象小节
  2. int modifiers = aClass.getModifiers();

四、序列化和反序列化

序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等,在网络传输过程中,可以是字节或者XML等格式;

而字节或者XML格式的可以还原成完全相等的对象,这个相反的过程又称为反序列化

理解:序列化就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得。

为什么要序列化:现在几乎所有应用程序都涉及网络IO和磁盘IO,而数据传输时都是以字节为单位,所以所有的数据都必须能够序列化为字节

作用:序列化是为了解决在流中的问题时触发该对象上读取和写入操作,便于网络传输和持久存储。

序列化的实现:将需要被序列化的类实现Serializable接口,该方法不需要实现这个接口,实现了Serializable只是为了标注该对象被序列化,然后使用一个输出流(例如:文件输出流)来构造的ObjectOutputStream(对象流)对象,然后使用对象输出对象的writeObject(对象obj)方法可以将一个对象obj参数写入(即保存其状态),如果你想恢复的输入流。

注意:

  1. 在java中 ,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化(实现Serializable只是为了标注该对象被序列化)
  2. 通过ObjectOutputStream和ObjectInputStream对 对象进行序列化及反序列化
  3. 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,重要的是两个类的序列化ID是否一致
  4. 想要父类对象也序列化,就要让父类也实现Serializable接口
  5. 序列化并不保存静态变量(声明为static类型的成员变量也不可以被序列化,因为static代表类的状态
  6. Transient关键字的作用是控制变量的序列化(声明为transient类型的成员数据不能被序列化,因为transient代表对象的临时数据
  7. 如果某个类能够被序列化,其子类也可以被序列化

将java对象序列化及反序列化有以下的接口和类:

  • java.io.Serializable
  • java.io.Externalizable
  • ObjectOutput
  • ObjectInput
  • ObjectOutputStream
  • ObjectInputStream

Serialization

Java类通过实现java.io.Serializable接口来启用序列化功能,未实现此接口的类将无法将其任何状态或者信息进行序列化或者反序列化。序列化接口没有方法或者字段,仅用于标识可序列化的语义

如果要序列化的类有父类,要想将在父类中定义过的变量序列化下来,那么父类也应该实现java.io.Serialization接口。

Externalizable

Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()

当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法,否则最后的结果为null

注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器创建一个新的对象,然后再将被保存对象的字段的值分别填充新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器

Externalizable和Serialization的区别

实现Externalizable接口序列化然后再进行反序列化之后对象的属性都恢复成了默认值,也就是说之前的那个对象的状态并没有被持久化下来

静态变量的序列化

静态变量属于类的状态,因此 序列化并不保存静态变量

Transient关键字使用

Transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型是0,对象型是null

注意:

  • transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的
  • 被transient关键字修饰的变量不再能被序列化
  • 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化

五、RandomAccess接口

这是一个标记接口(Marker),它没有任何方法,这个接口被List的实现类(子类)使用。

如果List子类实现了RandomAccess接口,那就表示它能够快速随机访问存储的元素。

意义:在对列表进行随机或顺序访问的时候,访问算法能够选择性能最佳方式。

六、Java8新特性

1. Lambda表达式

主要用来定义行内执行的方法类型接口,即只有一个单一的方法接口。
java8支持不用接口直接通过lambda表达式传入参数。

语法

  • 可选类型声明 —— 无需声明参数的类型。编译器可以从该参数的值推断出来
  • 可选圆括号参数 —— 无需在括号中声明参数。但,对于多个参数,括号是必须的
  • 可选大括号 —— 如果主体中含有一个单独的语句, 表达式主体没有必要使用大括号
  • 可选return关键字 —— 编译器会自动返回值。花括号是必需的,以表明表达式返回一个值

表达式形式:参数,箭头(—>) 以及一个表达式,如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在 { } 中,并包含显式的return语句。

其余注意要点:

  • lambda表达式没有参数,仍然要提供空括号,就像无参数方法一样
  • 如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至可以省略小括号
  • 无需指定lambda表达式的返回类型
  • 如果一个lambda表达式只在某些分支返回一个值,而在另外一些分支不返回值,这是不合法的
    • 如:(int x) -> { if (x>=0) return 1; }

变量作用域

lambda表达式只能引用标记了final的外层局部变量。也就是说,不能在lambda内部修改定义在域外的局部变量!

  • lambda表达式中,只能引用值不会改变的变量(因为如果在lambda表达式中改变变量,并发执行多个动作时就会不安全)
  • lambda表达式中捕获的变量必须实际上是最终变量。 最终变量:初始化之后就不会再为它赋新值
  • lambda表达式中声明与一个局部变量同名的参数或局部变量不合法

2.函数式接口

什么是函数式接口:有且必须只能有一个抽象方法的接口,默认方法个数没有限制(任何有一个抽象方法的接口都是函数式接口)

函数式接口可以被隐式转换为lambda表达式
可以使用Lambda表达式来表示该接口的一个实现(Java8之前一般是用匿名类实现的)
lambda表达式就是一个函数式接口的实例

注意:

  • 函数式接口必须有一个抽象方法
  • 不能把lambda表达式赋给类型为Object的变量,Object不是一个函数式接口
  • 函数式接口里允许定义默认方法(因为默认方法不是抽象方法)
  • 函数式接口里允许定义静态方法(静态方法不是抽象方法,是一个已经实现了的方法)
  • 函数式接口里允许定义java.lang.Object里的public方法(虽然他们是抽象方法,但是不被当成是抽象方法。因为任何一个函数式接口的实现,默认都继承Object类,包含了来自java.lang.Object 里对这些抽象方法的实现)

函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,当你写的接口不符合函数式接口的定义,编译器会报错。(加不加@FunctionalInterface对于接口是不是函数式接口没有影响

3.方法引用

  1. 用 :: 操作符分隔方法名与对象或类名
  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod
    前面两种情况,方法引用等价于提供方法参数的lambda表达式 System.out::println等价于x ->System.out,println(x)
    第三种情况,第一个参数会成为方法的目标
  1. 可以在方法引用中使用this参数,使用super也是合法的。使用this作为目标,会调用给定方法的超类版本
  2. 方法引用可以使语言的构造更紧凑简洁,减少冗余代码

①构造器引用

语法:Class::new 或者 Class::new 注意构造器没有参数

  1. 与方法引用类似,不过方法名为new 例如:Person::new是Person构造器的一个引用
  2. 数组类型建立构造器引用,有一个参数的话即数组的长度 例如:int[]::new / 有参数 x->new int[x]
  3. 无法构造泛型类型T的数组

    ②静态方法引用

    语法:Class::static_method 接受一个Class类型的参数

    ③特定类的任意对象的方法引用

    语法:Class::method 这个方法没有参数

④特定对象的方法引用

语法:instance::method 这个方法接受一个instance对应的Class类型的参数

4.Stream

流,可以让你以一种声明的方式处理数据
使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对java集合运算和表达的高级抽象

什么是流

Stream是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。(java中的Stream并不会存储元素)
  • 数据源 流的来源。可以是 集合、数组、I/O channel、产生器generator 等
  • 聚合操作 类似SQL语句一样的操作,比如 filter\map\reduce\find\match\sorted等

Stream操作两个基础的特征:

  • Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格,这样做可以对操作进行优化,比如延迟执行和短路。
  • 内部迭代:Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现。(以前对集合遍历都是通过Iterator或者For-Each的方式,显式的在集合外部进行迭代)

流操作
分为:中间操作(可以连接起来的流操作)和终端操作(关闭流的操作)
流的使用一般包括三件事:

  1. 一个数据源(如集合)来执行一个查询
  2. 一个中间操作链,形成一条流的流水线
  3. 一个终端操作,执行流水线,并能生成结果

生成流

集合接口有两个方法来生成流:

  • stream() - 为集合创建串行流
  • parallelStream() - 为集合创建并行流

所有的流操作都可以串行执行或者并行执行。除非显示地创建并行流,否则Java库中创建的都是串行流。

forEach

Stream提供forEach方法来迭代流中的每个数据
e.g; 输出10个随机数

  1. Random random = new Random();
  2. random.ints().limit(10).forEach(System.out::println);

map

map方法用于映射每个元素到对应的结果

filter

filter方法用于通过设置的条件过滤出元素

limit

limit方法用于获取指定数量的流

sorted

sorted方法用于对流进行排序(从小到大)

并行流(parallel)

parallelStream是流并行处理程序的代替方法。
其实就是一个并行执行的流,可能提高多线程任务的速度。
作用:Stream就有平行处理能力,处理的过程会将一个大任务分为多个小任务,每一个任务都是一个操作

Collector

Collector类实现了很多归约操作,比如将流转换成集合和聚合元素。Collector可用于返回列表或字符串
Collect也是收集器,是Stream一种通用的、从流生成复杂值的结构。Collectors工具库封装了相应的转换方法

5.接口增强

  • 在接口中可以添加使用default关键字修饰的非抽象方法。即:默认方法(或拓展方法)
  • 接口里可以声明静态方法,并且可以实现

(1) 默认方法

默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法
实现方法:在方法名前面加个default关键字
语法格式如下:

  1. public interface Vehicle {
  2. default void print(){
  3. System.out.println("我是一辆车!");
  4. }
  5. }
  • 默认方法不能够重写Object中的方法,却可以重载Object中的方法
  • 默认方法允许我们在接口里添加新的方法,而不会破坏实现这个接口的已有类的兼容性,也就是不会强迫实现接口的类 实现默认方法

默认方法和抽象方法的区别:抽象方法必须要被实现,默认方法不用。

接口可以提供一个默认的方法实现,所有这个接口的实现类都会通过继承得到这个方法(如果有需要也可以重写这个方法)
e.g:

  1. interface Defaulable {
  2. //使用default关键字声明了一个默认方法
  3. @SuppressLint("NewApi")
  4. default String myDefalutMethod() {
  5. return "Default implementation";
  6. }
  7. }
  8. class DefaultableImpl implements Defaulable {
  9. //DefaultableImpl实现了Defaulable接口,没有对默认方法做任何修改
  10. }
  11. class OverridableImpl implements Defaulable {
  12. //OverridableImpl实现了Defaulable接口重写接口的默认实现,提供了自己的实现方法。
  13. @Override
  14. public String myDefalutMethod() {
  15. return "Overridden implementation";
  16. }
  17. }

多个默认方法
一个接口有默认方法,一个类实现了多个接口,而且这些接口有相同的默认方法,解决方法如下:
第一种:创建自己的默认方法,来覆盖重写接口的默认方法

  1. public class car implements Vehicle, FourWheeler {
  2. default void print(){
  3. System.out.println("我是一辆四轮汽车!");
  4. }
  5. }

第二种:使用super来调用指定接口的默认方法

  1. public class Car implements Vehicle, FourWheeler {
  2. public voi dprint(){
  3. Vehicle.super.print();
  4. }
  5. }

默认方法只能通过接口实现类的对象来调用
e.g:

  1. public class Main{
  2. public static void main(String[] args){
  3. new JDK8InterfaceImpl().defaultMethod();
  4. }
  5. }

总结:默认方法给予我们修改接口而不破坏原来的实现类的结构提供了便利
**

(2) 静态方法

接口里可以声明静态方法,并且可以实现

  1. public interface JDK8Interface {
  2. // static修饰符定义静态方法
  3. static void staticMethod() {
  4. System.out.println("接口中的静态方法");
  5. }

静态方法只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用。
e.g:

  1. public class Main{
  2. public static void main(String[] args){
  3. JDK8Interface.staticMethod();
  4. }
  5. }

(3) java8中抽象类与接口的异同

相同点:

  1. 都是抽象类型;
  2. 都可以有实现方法(java8之前的不行)
  3. 都可以不需要实现类或者继承者去实现所有方法(现在接口中的默认方法不需要实现类实现)、

不同点:

  1. 抽象类不可以多重继承,接口可以
  2. 接口中定义的变量默认是public static final型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值;抽象类中的变量默认是friendly型,其值可以在子类中重新定义,也可以重新赋值。

friendly型:如果一个类、类属变量及方法不以public,protected,private这三种修饰符来修饰,它就是friendly类型的。包内的任何类都可以访问它,而包外的任何类都不能访问它。(对包内的其他类是友好的,对包外的其他类是关闭的)

6.Optional类

  • Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
  • Optional是个容器:可以保存类型T的值,或者仅仅保存null

意义:解决空指针异常

类声明

java.util.Optional类的声明

  1. public final class Optional<T> extend Object

类方法

image.png
这些方法都是从java.lang.Object类继承来的

常见方法

of
为非NULL值创建一个Optional
通过工厂方法创建Optional实例,需要注意的是传入的参数不能为空,否则会抛出空指针异常
ofNullable
为指定的值创建一个Optional。如果指定的值为null,则返回一个空的Optional。可为空的Optional。
isPresent
如果值存在返回true,否则返回false
get
如果Optional有值则将其返回,否则抛出NoSuchElementException
ifPresent
java8支持不用接口直接通过lambda表达式传入参数,如果Optional实例有值,调用ifPresent()可以接受接口端lambda表达式

  1. //ifPresent方法接受lambda表达式作为参数
  2. //lambda表达式对Optional的值调用consumer进行处理
  3. username.ifPresent((value)->{
  4. System.out.println(vaule.length());
  5. });

orElse
如果有值则将其返回,否则返回指定的其他值
如果Optional实例有值则将其返回,否则返回orElse方法传入的参数
orElseGet
orElseGet与orElse方法类似,区别在于得到的默认值。
orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值