《Java8编程官方参考教程》笔记

第一章Java的历史和演变

1.1Java是解释型语言还是编译型语言

都是。编译型语言是指编译为字节码,字节码是高度优化的指令集合,由Java虚拟机运行。解释型语言是指字节码通过jvm来运行。早起的jvm被设计为字节码解释器。

第二章Java综述

2.1两种范式

程序有两部分构成:代码和数据。所以程序分为关注正在发生什么(面向过程)和会影响谁(面向对象)两种。

2.2oop三原则

封装:讲代码及其操作的数据绑定到一起的机制,保证代码和数据不受外部干扰,也不会被误用。

2.3标识符

标识符用于命名事物,从Java8开始,不建议使用下划线。

第三章数据类型、变量和数组

3.1Java的自动类型转换

两种类型是兼容的或者目标类型大于源类型。

3.2表达式中自动类型提示

在计算中,会把byte、short或char类型提升为int;还有如果有一个参数为long,则提升为long,如果有float或double类似。

第五章

5.1for-each

for(type itr-var:collection) statement-block
说明for-each只能遍历集合。迭代变量是只读的,所以无法通过在循环中改变迭代变量的目的来改变集合

第七章

7.1嵌套类和内部类

在内的内部定义一个类,这种嵌套类的作用域被限制在包含它的类的内部。嵌套类可以访问包含它的类的成员,包括私有成员。但是包含类不能访问嵌套类的成员。
嵌套类分为静态和非静态的。静态嵌套类不能直接访问包含类的非静态成员,所以很少使用。
内部类是非静态的嵌套类。可以访问外部类的所有变量和方法,且可以直接引用他们。

7.2可变参数

可变参数在内部使用的时候当做数组来用,可变参数可用为空,所以防止可变参数重载时出错
vaTest(int… v){
for(int x:v){}
}

第八章继承

8.1构造函数初始化顺序

按继承关系初始化,如果需要调用自己的构造函数,子类构造函数第一条一定要写super。

8.2动态方法调度

超类引用变量可以指向子类对象。Animal dog = new Dog();Animal cat = new Cat();说明:
Animal引用变量dog指向子类Dog对象,dog可以引用Animal中声明的方法,如果Dog中覆盖了父类方法,则使用子类的方法。变量不能够被覆盖。
动态调用就是指当通过超类引用调用重写的方法时,Java根据在调用时所引用对象的类型来判定调用哪个版本的方法。

8.3抽象类

包含抽象方法的类必须为抽象类。因为不是完整的类,所以不能有构造方法,不能有静态域。

8.4final的作用

1、命名常量;2、用在类上禁止继承;3、方法上禁止重写。

第九章包和接口

9.1包查找和Classpath

包查找:先从运行时系统使用工作目录开始;从classpath环境变量开始;根据之赐你个的-classpath选项开始。

9.2接口

一个接口可以被人以数量的类实现;一个类可以实现任意数量的接口,多个接口用逗号分隔。声明接口时不需要声明权限,但是实现接口时,一定要是public型。接口的动态查询功能是有消耗的,对性能要求苛刻的代码,请小心使用。
部分实现接口时,类必须声明为abstract。
在类中或接口中可以声明成员或者嵌套接口。
接口也可以继承几口。

9.3默认接口方法

默认方法的主要动机是提供一种扩展接口的方法,而不破坏现有代码。还要一种是希望在接口中指定本质上可以选。例如以前定义了一个remove()方法,不管实现类是否需要都得实现该方法,现在提供了默认实现,已有代码不需要改动可以使用接口提供的默认方法,新的实现类可以对默认方法提供新实现。
主要优点:1、优雅的随时间演化接口;2、实现接口不需要提供功能的占位符。

9.4类和接口的区别

类可以提供状态信息,而接口不行,接口只是一种声明。接口中不能声明变量,但是可以声明一个赋值的变量,在实现类中当常量使用。
所以Java无法实现多继承,因为接口不提供状态信息,只能通过默认方法来实现继承行为。

9.5在接口中使用静态方法

Java8提供了一种新功能,在接口中定义静态方法。在调用静态方法时不需要实现接口,也不需要接口的实例。** 实现接口的类或子类接口不会继承接口中的静态方法。 **

第十章 异常

10.1显式抛出异常

throw ThrowableInstance;
如果方法不处理异常,则用throws抛出。
try{}catch(){}finally{}的执行顺序和书写一致,先try,后catch,在finally(当三个一起发生的时候)。

10.2新功能

try-with-resource;多exception(AExcepiton|BException e);异常重新抛出(final Exception e)。异常重新抛出一般使用在你不确定抛出的什么异常,但是想通过父类捕获异常之后,可以更精确的处理。

第十一章多线程编程

11.1进程和线程

进程是指操作系统的不同应用可以同时进行;线程是同一程序的不同任务同时进行。

11.2线程优先级

Java为每个线程指定了优先级,线程优先级是一些整数。线程的优先级绝对值没有意义,优先级用于从一个线程切换到另外一个优先级,简称上下文切换。
上下文切换规则:1、线程自愿放弃控制。显式放弃控制、休眠或在I/o之前阻塞,然后检测优先级最高的线程。2、被更高优先级的线程取代。如果是同等优先级的线程,Windows采用循环方式,其他为等线程自愿向其他线程放弃控制权,否则其他线程就不能运行。

11.3同步

引入了异步方法,就需要一个在需要时可以强制同步的方法。Java采用监视器来实现进程间同步。每个对象都有隐式的监视器。一个线程位于一个同步方法中,其他线程就不能调用同一对象的其他任何方法。

11.4Thread类和Runnable接口

Java类的多线程是基于Thread类、Thread类的方法和Runnable接口而构建的。Thread类封装了线程的执行,Thread实例是线程的代理。正在运行的线程细微状态不能直接获取,所以需要通过代理获取。
表11-1 Thread类定义的一些方法

方法含义
getName()获取线程的名称
getPriority()获取线程的优先级
isAlive()确定线程是否任然在运行
join()等待线程终止
run()线程的入口点
sleep()挂起线程一段时间
start()通过调用线程的run方法启动线程
static currentThread()获取对调用他线程的引用

11.4.1主线程

Java程序启动时,会立即运行一个线程,这个线程就是主线程。1、其他线程都是从主线程产生的;2、通常主线程必须最后结束,因为他要关闭其他线程。

11.5 创建线程

线程都是通过Thread类、Thread的方法和实现Runnable接口,所以一般创建线程就有两种方式,1、实现Runnable接口;2、扩展Thread类本身。

15.1 实现Runnable接口

创建线程最简单的方式就是实现Runnable接口。主要是实现run方法,为线程执行提供一个入口。通过Thread t=new Thread(Runnable threadOb,Stirng threadName)创建对该线程的代理,然后通过t.start()来启动线程。

15.2扩展Thread类

扩展Thread类是创建线程的第二个方式,扩展类必须重写run方法且必须调用start方法以开始线程。因为start方法就是父类的方法,所以不需要创建Thread对象来调用,直接调用即可。

15.3选择哪种方式

一般的逻辑是当需要对类做扩展的时候,才继承。而继承Thread类的时候,只有run方法必须重写。所以如果只是简单的线程,推荐使用Runnable接口。

15.4同步

当多个线程需要访问同一个共享资源时,需要确保只有一个线程在使用资源,这一目的的过程称为同步。
监视器是同步的关键,并且只有一个线程可以获得监视器——当线程获得锁的时候,就进入了监视器。
同步的方式有两种,都要用到synchronized关键字。

15.4.1使用同步方法

synchronized type method()来实现方法的同步。当一个线程进入某个对象的同步方法是,其他线程就不能进入相同对象的任何同步方法

15.4.2synchronized语句

虽然同步方法是一种比较方便的同步手段,但是在有时候却不能满足要求。假设你调用第三方的类的方法,且该类的方法没有对同步做设置,那么就无法控制同步了。这时候就需要同步语句起作用的时候了。

synchronized(objRef){
    //statements to be synchronized
}

其中,objRef是对被同步对象的引用。synchronized确保对objRef对象成员方法的调用,只能在当前线程进入objRef对象的监视器(对象的隐式监视器监控线程)之后发生。

15.5进程间通信

前面的都是无条件锁住其他线程对特定方法的异步访问,Java对象的隐式监视器可以控制进程间通信的细微级别控制。
多线程是通过把任务分隔成独立的逻辑单元来替换事件循环编程,消除了轮询检测。Java通过wait()、notify()以及notifyAll()方法,来实现进程间通信,避免轮询检测。notify()只随机唤醒一个线程,notifyAll()唤醒全部且让他们争夺线程权限。notify()可能会导致死锁。
简单来说,把对象的监视器看做是一个办公厅,当对象给某个线程发送wait()消息时,这个线程就不会被领入办公厅,而是领入休息室里面睡觉;当同一个对象的其他线程发送notify()时,随机从休息室里面唤醒一个线程;当使用notifyAll()时,把休息室里面的所有睡觉的线程都叫醒,让他们竞争。

11.6获取线程状态

Thread.State getState()来获取线程状态。
getStats()方法的返回值

状态
BLOCKED线程正在等待需要的锁而挂起执行
NEW线程还没有开始运行
RUNNABLE线程要么当前正在执行,要么在获得CPU的访问权之后执行
TEMINATED线程已经执行完
TIMED_WAITING线程挂起执行一段指定的时间,
WAITING线程因为某些动作而挂起执行

《《Java8编程官方参考教程》笔记》 Paste_Image.png

第十二章 枚举、自动装箱与注解(元数据)

12.1枚举

12.1.1枚举的常识

1、每个枚举常量被隐式声明为枚举类的共有、静态final成员。所以,枚举不能再运行中创建,它不仅是常量还是静态final常量。
2、可以使用==判断两个枚举常量的相等性(因为是static final类型的)。
3、在switch中使用时,case的枚举类型必须和表达式的一样,并且不需要枚举类名称。

Apple ap;
switch(ap){
    case WINESAP://不需要枚举类型名称限定。因为switch表达式中的枚举类型已经隐式指定了case常量的枚举类型。如果试图做,会编译出错
}

12.1.2values()和valueOf(String str)方法

values()返回枚举常量列表的数组;valueOf()方法返回与传递到参数str相对应的枚举常量,大小写也要完全一致,如果找不到会报错。

12.1.3Java枚举是类类型

Java枚举是不能使用new实例化的类类型,所以他可以有自己的成员变量、成员方法、构造函数。每一个枚举常量都是枚举类的对象,所以每个枚举常量都有枚举类型域的副本。
除了枚举不能通过new来实例化对象外,枚举不能继承其他类;也不能是超类,意味着枚举不能扩展。

12.2类型封装器、自动装箱和自动拆箱

类型封装器是把基本类型封装为对象类型;自动装箱时把基本类型自动变成对象类型;自动拆箱是把对象类型中的值抽取为基本类型的值,不需要显式转化。在计算过程中,请更多的使用基本类型。

12.3注解

Java支持在源文件中嵌入补充信息,这类信息成为注解(annotation)。注解不会改变程序的动作,因此也不会改变程序的语义。

12.3.1注解的基础知识

注解是通过基于接口的机制而创建的。因为基于接口,所以不能声明变量(接口声明的变量,实现类会变成常量来使用),注解只包含方法声明,且不能提供实现方法体,但是这些方法的行为更接近域变量,所有注解都不能包含 extends子句,但是他是自动扩展了Annotation接口。

@interface Myanno{
    String str();
    int val();
}

在JDK8之前,注解只能用于声明(JDK8添加了使用注解类型的功能)。所有类型的声明都可以有与之关联的注解,例如类、方法、域变量、参数以及枚举常量甚至注解本身也可以被注解。
引用注解时,需要为注解的成员提供值

@Myaano(str="first annotation",val=10)
public static void myMeth(){}

注解的名称以@作为前缀,后面跟成员初始化列表,初始化方式看起来更像域变量。

12.3.2指定保留策略

保留策略确定了什么位置丢弃注解。Java定义了三种策略,被封装到了java.lang.annotation.RetentionPolicy枚举中。分别是:

  • source。只在源文件中保留,在编译期间被抛弃。
  • class。默认策略。在编译时被存储到class文件中,但是在运行中jvm不会得到这些注解。
  • runtime。编译时存在class文件中,运行时jvm可以获取这些注解。
    注意:局部变量声明的注解不能存储在.class文件中。
    保留策略是通过Java内置注解——@Retention指定的。一般形式为
    @Retention(retention-policy)

12.3.3在运行是使用反射获取注解

注解的目的一般在于用于其他的开发和部署工具,但是策略指定为runtime,那么任何程序在运行时都可以使用反射来查询注解。反射是能够在运行时获取类相关信息的特性。
使用反射的第一步是获取Class对象,表示希望获取其中注解的类。获得class对象后,可以使用其方法获取与类声明中各个条目相关的信息,包括注解。例如Class提供了getMethod()、getField()以及getConstructor()方法,这些方法分别获取与方法、域变量和构造对象相关的信息,这些方法返回Method、Field以及Constructor类型的对象。
Method getMethod(String methName,Class<?>… paramTypes)
对Class、Method、Field以及Constructor对象调用getAnnotation()方法,就可以获得与对象关联的特定注解。
<A extends Annotation> getAnnotation(Class<A> annoType)
简单来说,基本流程就是先获取class对象,如果是方法、域或者构造方法级别的注解,先获取对应的对象,在通过对象调用getAnnotation来获取注解对象,然后就可以获取值了。
如果注解不存在,则返回null。
getAnnotations()获取所有注解。

12.3.4标记注解

标记注解就是为了标记存在,所有没有成员。只要通过isAnnotationPresent(Class<A> a)即可

12.3.5单成员注解

单成员注解可以不写成员名称。

12.3.5内置注解

Java8增加了functionInterface,用于接口,表示是一个函数式接口。

12.4 类型注解

前面的注解主要用在声明上,但是Java8增加了类型注解,在能够使用注解的地方大多数都可以指定注解。
类型注解是指在可以使用类型的大多数地方,都可以使用注解。
类型注解必须包含ElementType.TYPE_USE(@Target指定注解使用范围)
Java8开始,可以显式的将this声明为方法的第一个参数,在这中声明中,this的类型必须是其类的类型,例如

class SomeClass{
    int myMeth(SomeClass this,int i,int j){}
}

除非要注解this的类型,否则没有必要声明this,并且显式声明this没有改变方法签名,因为隐式也会声明this。

第十三章 I/O

13.1I/O基础知识

13.1.1流

Java程序通过流执行I/O。流是一种抽象,要么产生信息,要么使用信息。流通过Java的I/O系统连接到物理设备。

13.1.2字节流和字符流

字节流为处理字节的输入和输出提供了方法,字符流同样。
1、字节流
字节流是两个抽象类InputStream和OutputStream,定义了其他流实现的一些关键方法,其中最重要的是read()、write()。
2、字符流
字符流是通过两个顶级抽象类,Reader和Writer。

13.2读取控制台输入

在Java中,控制台输入是通过从System.in读取完成的。为了获取与控制台关联的基于字符的流,可以再BufferdReader对象中封装System.in。

BufferedReader(Reader inputReader)

其中,inputReader是与即将创建的BufferedReader实例链接的流。Reader是抽象类,InputStreamRader是它的一个具体子类,该类将字节转换成字符。为了获得与System.in链接的InputStreamReaderd对象,使用下面的构造函数

InputStreamReader(InputStream inputStream)

13.3 transient和volatile修饰符

transient表示存储时不需要长久保存。volatile可以被其他部分随意修改。

13.4 assert关键字

一般用于在测试中判断,如果为TRUE,则不发生其他动作,否则抛出AssertionError错误。

13.5静态导入

import static java.lang.Math.sqrt;

13.6通过this()调用重载的构造函数

当执行this()调用时,首相执行与构造方法签名一致的构造函数,必须为第一句。不能同时使用supper()和this(),并且this()会增加开销。

第十四章泛型

通过使用泛型,可以以创建以类型安全的方式使用各种类型的类、接口以及方法。就本质而言,泛型就是参数化类型。参数化类型很重要,因为使用该特性创建的类,接口以及方法,可以作为参数指定所操作数据的类型。在泛型出来之前,使用Object,但是这种缺少类型安全性。

14.1什么是泛型

14.1.1泛型只使用引用类型

当声明反省类的实例时,传递过来的类型参数必须是引用类型。不能使用基本类型,会报错。

14.1.2基于不同类型参数的泛型类型是不同的

如果Gen<T>类,创建两个对象,Gen<Integer> inta和Gen<String> intb不是系统类型。

14.1.3泛型提升类型安全性的原理

泛型明确限定了参数类型,不需要强制转换等。

14.1.4带两个类型的泛型类

在泛型中可以声明多个类型参数,为了指定两个或多个类型参数,只需要用逗号分开即可。

14.2泛型类的一般形式

class class-name<type-param-list> {}, class-name<type-arg-list> var-name = new Class-name

14.3有界类型

泛型类的参数可以用任何类来替换,但是也有局限性。例如对某些只限定某类类型的就不适用,所以需要有界类型,使用下面的方式
<T extends superclass>,这样就指定了只能被superclass或者其子类替代,因此superclass定义了上限。

14.4使用通配符参数

前面介绍的泛型很好,但是有个问题,因为基于不同参数创建的泛型对象是不想同的。例如:

boolean sameAvg(Stats<T> ob){
  if(average()==ob.average()){
    return true;
  }
}
T average(){
  //todo 
}

这种情况下,sameAvg方法判断就会出错,因为类型不一致无法比较。例如Stats<Integer> 类型和Stats<Double>类型的平均值不能一起比较。
此时就有一种通配符参数,使用“?”之赐你个,例如改为:

boolean sameAvg(Stats<?> ob){
   if(average()==ob.average()){
    return true;
  }
}

此时,stats<?>和所有的Stats对象匹配,允许两个Stats对象比较他们的平均值。
和有界泛型一样,也肯定有有界通配符。

14.5创建泛型方法

泛型类里面使用泛型定义的参数来创建泛型方法,也可以在普通类里面创建泛型方法。

<T extends Number> boolean isTrue(T o){}

上面是在普通类里面创建泛型方法的示例,从上面可以看出,泛型方法的参数在返回值之前定义,其他和泛型类的泛型方法一样。类型参数列表在返回类型之前,公式:

<type-param-list> ret-type meth-name(param-list)

同理,构造方法也就类似了。

14.6泛型接口

泛型接口与泛型类基本没有差别。

14.7使用泛型的一些限制

14.7.1不能实例化类型参数

class Gen<T>{
 T ob;
Gen(){
ob=new T()
}
}

这种报错是很好理解的,因为编译器不知道创建那种类型的对象,T只是一种占位符。

14.7.2对静态成员的一些限制

静态成员不能使用在类中声明的类型参数,不能声明静态的泛型变量。因为静态是在编译时确定,而泛型是在运行时确定。所以不能声明。

14.7.3对泛型数组的一些限制

不能实例化类型为参数类型的数据;不能创建特定类型的泛型引用数组。因为Java泛型是通过擦除实现,所以可以用通配符来创建。Gen<?> gens[] = new Gen<?>[10]。

14.7.3对泛型异常的限制

泛型类不能扩展Throwalbe,这意味着不能创建泛型异常类。

14.8擦除

影响泛型以何种方式添加到Java中的一个重要约束是:需要与以前的Java版本兼容。为了满足这个需求,Java采用擦除实现泛型。
擦除的工作原理如下:编译Java代码时,所有泛型信息被移除(擦除)。意味着使用泛型的界定类型(如果没有显式指定,那么肯定就为Object类型)替换类型参数。

第十五章 lambda表达式

15.1lambda表达式简介

lambda表达式本质上就是一个匿名方法。但是这个方法不是独立执行的,而是用于实现函数式接口定义的另一个方法。因此,lambda表达式会导致产生一个匿名类,因此lambda表达式也叫做闭包。
函数式接口是仅包含一个方法的接口(Java的方法在C语言里面叫函数,所以函数式接口就是只包含一个函数的接口)。一般来说,这个方法指明了接口的目标用途。因此,函数式接口通常表示单个动作。函数式接口也被叫做SAM类型,Signle Abstract Method(单抽象方法)。
注意:lambda表达式只能用于其目标类型一倍指定的上下文中。虽然函数式接口里面只有一个方法,但是可以指定Object的任何共有方法。因为Object的共有方法被视为接口的隐式成员。

15.1.1 lambda表达式的基础知识

lambda是Java新引入的语法元素,所以有新的操作符->,叫做lambda操作符或者箭头操作符。左边是表达式的所有参数,右边是lambda的体既动作。可以把“->”表述为“成了“或者”进入“。lambda示例如下:

()->123

这是一个计算结果为常量的表达式。类似于 int myMeth(){return 123};没有参数,返回常量。

(n)->(n%2)==0

当n为偶数时,返回真。

15.1.2函数式接口

在Java8之前,接口方法可以看做是隐式的抽象方法,但是Java8开始,接口有了默认方法,所以只有在当没有默认方法时才是抽象方法。

interface MyNumber{
  double getValue();
}

其中,getValue()方法为隐式的抽象方法,并且是MyNumber定义的唯一方法,所以MyNumber为函数式接口,getValue()为其定义的功能。

MyNumber myNum;
myNum = ()->1234;

当目标类型上下文中出现lambda表达式时,会自动创建实现了函数式接口的一个类的实现,函数式接口声明的抽象方法的行为有lambda表示定义。当通过该目标调用该方法时,就会执行lambda表达式。因此,lambda表达式提供了一种将代码片段转换为对象的方法。

System.out.println(myNum.getValue());

同样,带参数的lambda为:

interface MyNumber{
  double getValue(int a,int b);
}
MyNumber myNum;
myNum = (a,b)->(a+b);
System.out.println(myNum.getValue(1,2));

因此,lambda表达式就是对函数式接口的实现。通过创建lambda表达式的过程就是对接口的实现过程。声明了lambda表达式之后就可以调用方法了。
注意:lambda表达式的实现是随时变动的(类的实现和lambda表达式有关,同一个接口声明的方法可能返回相反的值,因为lambda表达式随时可以变动)。可以再lambda表达式的左边带参数的类型,但是一般没必要。如果只是一个参数的,括号可以省略。例如n->n/2。

15.2 块lambda表达式

前面的lambda体只包含表达式,被称重表达式体,具有表达实体的lambda表达式有时候被称为表达式lambda。当然还有表达式块被称为lambda表达式块。
只要使用{}包围lambda体即可。其他和lambda体一致,但是必须使用return语句来返回值,必须这么做,因为块lambda体代表的不是一个表达式。

15.3 泛型函数式接口

lambda表达式自身不能指定类型参数,因此lambda表达式不能是泛型。但是与lambda表达式关联的函数式接口可以是泛型。因此,lambda表达式的目标声明部分由声明函数式接口引用时指定的参数类型决定。

interface SomFunc<T>{
  T func(T t);
}

class a{
  public static void main(String args[]){
    SomeFuc<String> reverse = (str)->{
      //todo
    }
    
  }  
}

通过这种方式就可以实现泛型函数式接口的lambda表达式实现。

15.4 作为参数传递lambda表达式

lambda表示可以用在任何提供了目标类型的上下文中。lambda表达式可以作为参数传递。

15.5 lambda表达式与异常

lambda表达式可以抛出异常,但是必须和接口方法的throws子句兼容。

15.6 lambda表达式和变量捕获

在lambda表示中,可以访问其外层作用域内定义的变量,但是只能访问外层作用域中的实质上final变量。实质final变量实质在第一次复制以后,值不再发生变化的变量。没有必要显式的将这种变量声明为final。lambda表达式不能修改外层作用域内的局部变量,如果修改局部变量就相当于移除其实质上的final状态,从而使得改变量变得不合法。

15.7 方法引用

方法引用提供了一种引用而不执行方法的方式。这个特性与lambda表达式有关,因为它也需要有兼容的函数式接口构成的目标类型上下文。方法引用有多种。

15.7.1 静态方法的方法引用

静态方法引用的一般语法如下:

ClassName::methodName

::是java8引进专门引进来做方法引用的,在于目标类型兼容的任何地方,都可以使用这个方法引用。

    原文作者:cleaner_wei
    原文地址: https://www.jianshu.com/p/de2e878e0ff1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞