读书笔记 --《 java核心技术卷一》

2023-05-16

 

Java核心技术卷 1

第一部分(java基本介绍)

java特性

简单性

面向对象

分布式

健壮性

安全性

体系结构中立

编译器生成一个体系结构中立的目标文件格式,这是一种编译过的代码, 只要有 Java 运行时系统, 这些编译后的代码可以在许多处理器上运行。Java 编译器通过生成 与特定的计算机体系结构无关的字节码指令来实现这一特性。 精心设计的字节码不仅 可以很容易地在任何机器上解释执行,而且还可以动态地翻译成本地机器代码,这也是java能跨平台的原因。

可移植性

解释型

对于“Java 是解释执行” 这个说法其实不太正确,我们开发的源代码,首先通过javac编译成字节码(bytecode) ,然后在运行时通过java虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。但是常见的JVM,比如我们大多数情况下使用的Oracle JDK 提供的Hotspot JVM 都提供了JIT(Just-In-Time)编译器,也就是我们所说的动态编译器,JIT能够在运行时将热点代码编译成为机械码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。

高性能

为了实现跨平台,java是边解释边执行的机制,这种机制注定性能上要逊色于编译执行的语音,当java在其性能方面做了各种各样的优化,使其性能方面的劣势经可能被忽略。例如对执行方式进行优化,对热点代码实行编译执行,而非边解释执行。例如对代码结构进行优化,从代码的执行效率上提升其执行性能,例如对方法进行方法内联优化,即通过把方法体替换方法调用,以此来减少调用方法带来的额外开支, 如方法栈帧的生成、参数字段的压入、栈帧的弹出、还有指令执行地址的跳转。

多线程

动态性

java开发环境搭建

1.安装jdk

2.配置环境变量

3.运行第一个java程序

第三部分(类与对象)

在类之间, 最常见的关系有•依赖( “ uses-a”) :依赖(dependence), 即“ uses-a” 关系, 是一种最明显的、最常见的关系。例如,Order 类使用 Account 类是因为 Order 对象需要访问 Account 对象查看信用状态。但是 Item类不依 赖于 Account 类, 这是因为 Item 对象与客户账户无关。因此, 如果一个类的方法操纵另一个 类的对象,我们就说一个类依赖于另一个类。 应该尽可能地将相互依赖的类减至最少。如果类 A 不知道 B 的存在, 它就不会关心 B 的任何改变(这意味着 B 的改变不会导致 A 产生任何bug)。用软件工程的术语来说,就是 让类之间的耦合度最小。 •聚合( “ has-a”):聚合(aggregation), 即“ has-a” 关系, 是一种具体且易于理解的关系。例如, 一个 Order 对象包含一些Item 对象。聚合关系意味着类 A 的对象包含类 B 的对象。•继承( “ is-a”):继承(inheritance), 即“ is-a” 关系, 是一种用于表示特殊与一般关系的。例如,Rush Ordei•类由 Order 类继承而来。在《编程思想》中,认为聚合要比继承更能做到解耦和,让代码可以更加健壮而易于扩展。

隐式参数与显式参数

方法用于操作对象以及存取它们的实例域。例如,方法: public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } 将调用这个方法的对象的 salary 实例域设置为新值。看看下面这个调用: number007.raiseSalary(5); 它的结果将 number007.salary 域的值增加 5%。具体地说,这个调用将执行下列指令: double raise = nuaber007.salary * 5 / 100; nuiber007.salary += raise; raiseSalary方法有两个参数。 第一个参数称为隐式( implicit) 参数, 是出现在方法名前的 Employee 类对象。第二个参数位于方法名后面括号中的数值,这是一个显式( explicit) 参 数 (有些人把隐式参数称为方法调用的目标或接收者。 ) 可以看到,显式参数是明显地列在方法声明中的, 例如 double byPercent。隐式参数没有 出现在方法声明中。 在每一个方法中, 关键字 this 表示隐式参数。 如果需要的话,可以用下列方式编写 raiseSalary 方法: public void raiseSalary(double byPercent) { double raise = this.salary * byPercent / 100; this.salary += raise; }

封装的规范与优点

封装规范:确认其属性(变量)和行为(方法)具有某一共同特性,应当归属于一个类中。在有些时候,需要获得或设置实例域的值。因此,应该提供下面三项内容: •一 私有的数据域; •一 公有的域访问器方法; •一个公有的域更改器方法。 这样做要比提供一个简单的公有数据域复杂些, 但是却有着下列明显的好处: 首先, 可以改变内部实现,除了该类的方法之外,不会影响其他代码。而且通过方法来访问或更改数据,我们便可以对其过程进行控制,例如更改时,若接受值为null,便拒接更改。而如果直接将数据公开为public,则对数据的读取和更改就完全不可控了。严格控制方法变量的访问权限:记住一点,凡是不应该被外部调用的,都应该设置为private ,在将任何一个方法或变量设置为public前,都应该慎重考虑是否必须这样做,尽可能严苛的控制访问权限,能够避免很多错误的出现。对于私有方法, 如果改用其他方法实现相应的操作, 则不必保留原有的方法。如果数据的表达方式发生了变化,这个方法可能会变得难以实现, 或者不再需要。然而,只要方法是私有的,类的设计者就可以确信:它不会被外部的其他类操作调用,可以将其删去。如果方法是公有的, 就不能将其删去,因为其他的代码很可能依赖它。 final 实例域:final表示不可变的,该修饰可以更加严格的控制一个对象中的变量或方法是否可变,这对于很多只做展示的数据类,也就是bean类很有帮助,因为这些数据类只是存放数据使用,它们应该拒绝一切对原本数据的修改操作,一旦实例化,对象内部属性就不再被更改,而一个内容不可变的对象,是不存在竞争态的,也就从根本上解决了数据的安全问题,与线程安全问题,不会被修改,自然也就不会发生数据先后不符的情况。所以在设计一个类时,应该对该类,类中的每一个对象,类中的每一个方法都思考一下,该类是否应该被继承,该属性是否需要被修改,该方法是否需要被复写,若不需要,则按照权限最低原则,应设置为final。

静态域

如果将域定义为 static, 每个类中只有一个这样的域。而每一个对象对于所有的实例域 却都有自己的一份拷贝。即被定义为static,便属于类,而不属于对象,随类的加载而被生成。静态方法:静态方法是一种不能向对象实施操作的方法。静态方法中没有隐式参数,也就不能用this来得到隐式参数,而在一个非静态方法中,this指代其隐式参数,一般指调用的对象。

方法参数

Java 程序设计语言对对象采用的不是引用调用,实际上, 对象引用是按值传递的。 •一个方法不能修改一个基本数据类型的参数(即数值型或布尔型) 。 •一个方法可以改变一个对象参数的状态。•一个方法不能让对象参数引用一个新的对象。 

类的初始化

如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值: 数值为 0、 布尔值为 false、 对象引用为null。然而,只有缺少程序设计经验的人才会这样做。确实, 如 果不明确地对域进行初始化,就会影响程序代码的可读性。本书推荐,即使成员变量会被默认赋予一个初始值,然而还是推荐程序员手动写好赋值操作,这样可以增加代码的可读性。

类被回收

可以为任何一个类添加 finalize 方法。finalize 方法将在垃圾回收器清除对象之前调用。 在实际应用中,不要依赖于使用 finalize 方法回收任何短缺的资源, 这是因为很难知道这个 方法什么时候才能够调用。 注释: 有个名为 System.mnFinalizersOnExit(true) 的方法能够确保 finalizer 方法在 Java 关 闭前被调用。不过,这个方法并不安全,也不鼓励大家使用。有一种代替的方法是使用方法 Runtime.addShutdownHook 添加“ 关闭钓” (shutdown hook)

包的导入:java.time.* 的语法比较简单,对代码的大小也没有任何负面影响。当然,如果能够明确 地指出所导人的类,将会使代码的读者更加准确地知道加载了哪些类。但是, 需要注意的是, 只能使用星号(*) 导入一个包, 而不能使用 import java.* 或 importjava.*.*导入以 java 为前缀的所有包。  

注释

类的设计技巧

1. 一定要保证数据私有 。这是最重要的;绝对不要破坏封装性。有时候, 需要编写一个访问器方法或更改器方法, 但是最好还是保持实例域的私有性。很多惨痛的经验告诉我们, 数据的表示形式很可能会改 变, 但它们的使用方式却不会经常发生变化。当数据保持私有时, 它们的表示形式的变化不 会对类的使用者产生影响, 即使出现 bug 也易于检测。 2. 一定要对数据初始化 Java 不对局部变量进行初始化, 但是会对对象的实例域进行初始化。最好不要依赖于系 统的默认值, 而是应该显式地初始化所有的数据, 具体的初始化方式可以是提供默认值, 也 可以是在所有构造器中设置默认值。3. 不要在类中使用过多的基本类型 。就是说,用其他的类代替多个相关的基本类型的使用。这样会使类更加易于理解且易于 修改。4. 不是所有的域都需要独立的域访问器和域更改器 5.将职责过多的类进行分解(但也不要矫枉过正)6. 类名和方法名要能够体现它们的职责7.优先使用不可变的类 

第五部分(接口)

在 Java 程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。在java8后,接口可以实现方法体,即接口中不再只能有抽象方法,而是可以有已实现的方法。关键词: default接口与抽象类的区别:接口可以多实现,而抽象类是类,只能单继承。

默认方法

默认方法的实现:public interface Comparable<T> { default int compareTo(T other) { return 0; } } 默认方法冲突:1 ) 超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。(即接口和父类中有先相同方法时,父类为准)2 ) 接口冲突。 如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且 参数类型(不论是否是默认参数)相同的方法, 必须覆盖这个方法来解决冲突。

内部类

内部类( inner class) 是定义在另一个类中的类。为什么需要使用内部类呢? 其主要原因有以下三点:•内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。•内部类可以对同一个包中的其他类隐藏起来。•当想要定义一个回调函数且不想编写大量代码时,使用匿名 (anonymous) 内部类比较便捷。一般的内部类会隐式持有一个对外部类的引用,而静态内部类则不会持有这个引用,所以,出于对内存泄漏提防,若内部类没有需要用到外部类的相关域,则应该被设置为static,即静态内部类。在内部类不需要访问外围类对象的时候, 应该使用静态内部类。 有些程序员用嵌 套类 (nested class) 表示静态内部类。 与常规内部类不同,静态内部类可以有静态域和方法。 声明在接口中的内部类自动成为 static 和 public 类。

静态内部类

匿名内部类

内部类特殊语法

下面的技巧称为“ 双括号初始化” (double brace initialization), 这里利用了内部类 语法。假设你想构造一个数组列表,并将它传递到一个方法: ArrayList<String> friends = new ArrayListoO; friends,add("Harry"); friends,add("Tony"); invite(friends); 如果不再需要这个数组列表,最好让它作为一个匿名列表。不过作为一个匿名列表, 该如何为它添加元素呢? 方法如下: invite(new ArrayList<String>0 {{ add("Harry"); add("Tony"); }}); 注意这里的双括号。外层括号建立了 ArrayList 的一个匿名子类。内层括号则是一个对象构造块。

代理

利用代理可以在运行时创建一个实现了一组给定接口的新类 : 这种功能只有在编译时无法确定需要实现哪个接口时才冇必要使用。关键类:Proxy 类

静态代理

public interface ISinger { 2     void sing(); 3 } 4 5 /** 6  *  目标对象实现了某一接口 7  */ 8 public class Singer implements ISinger{ 9     public void sing(){ 10         System.out.println("唱一首歌"); 11     }   12 } 13 14 /** 15  *  代理对象和目标对象实现相同的接口 16  */ 17 public class SingerProxy implements ISinger{ 18     // 接收目标对象,以便调用sing方法 19     private ISinger target; 20     public UserDaoProxy(ISinger target){ 21         this.target=target; 22     } 23     // 对目标对象的sing方法进行功能扩展 24     public void sing() { 25         System.out.println("向观众问好"); 26         target.sing(); 27         System.out.println("谢谢大家"); 28     } 29 } /** 2  * 测试类 3  */ 4 public class Test { 5     public static void main(String[] args) { 6         //目标对象 7         ISinger target = new Singer(); 8         //代理对象 9         ISinger proxy = new SingerProxy(target); 10         //执行的是代理的方法 11         proxy.sing(); 12     } 13 } 总结:其实这里做的事情无非就是,创建一个代理类SingerProxy,继承了ISinger接口并实现了其中的方法。只不过这种实现特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含是关键。缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。

动态代理

关键类:Proxy1 public interface ISinger { 2     void sing(); 3 } 4 5 /** 6  *  目标对象实现了某一接口 7  */ 8 public class Singer implements ISinger{ 9     public void sing(){ 10         System.out.println("唱一首歌"); 11     }   12 } 这回直接上测试,由于java底层封装了实现细节(之后会详细讲),所以代码非常简单,格式也基本上固定。调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )接收的三个参数依次为:ClassLoader loader:指定当前目标对象使用类加载器,写法固定Class<?>[] interfaces:目标对象实现的接口的类型,写法固定InvocationHandler h:事件处理接口,需传入一个实现类,一般直接使用匿名内部类测试代码:1 public class Test{ 2  public static void main(String[] args) { 3  Singer target = new Singer(); 4  ISinger proxy  = (ISinger) Proxy.newProxyInstance( 5                 target.getClass().getClassLoader(), 6                 target.getClass().getInterfaces(), 7                 new InvocationHandler() { 8                     @Override 9     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 10                         System.out.println("向观众问好"); 11                         //执行目标对象方法 12      Object returnValue = method.invoke(target, args); 13                         System.out.println("谢谢大家"); 14                         return returnValue; 15                     } 16                 }); 17         proxy.sing(); 18     } 19 } 总结:以上代码只有标黄的部分是需要自己写出,其余部分全都是固定代码。由于java封装了newProxyInstance这个方法的实现细节,所以使用起来才能这么方便,具体的底层原理将会在下一小节说明。缺点:可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,加入没有,则可以使用Cglib代理。

第八部分(集合)

迭代器

Iterator 接口包含 4 个方法: public interface Iterator<E> { E next(); boolean hasNextO; void remove0; default void forEachRemaining(Consumer<? super E> action); } 通过反复调用 next 方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末 尾,next 方法将抛出一个 NoSuchElementException。 因此,需要在调用 next 之前调用 hasNext 方法。如果迭代器对象还有多个供访问的元素, 这个方法就返回 true。如果想要査看集合中的 所有元素,就请求一个迭代器,并在 hasNext 返回 true 时反复地调用 next 方法。例如: Collection<String> c = . . .; Iterator<String> iter = c.iteratorO; while (iter.hasNextO) { String element = iter.next0; do something with element } 用“ foreach” 循环可以更加简练地表示同样的循环操作: for (String element : c) { do something with element } 编译器简单地将“ foreach” 循环翻译为带有迭代器的循环。 “ for each” 循环可以与任何实现了 Iterable 接口的对象一起工作, 这个接口只包含一个抽象方法: public interface Iterable<E> Iterator<E> iteratorO; } Collection 接口扩展了 Iterable 接口。因此, 对于标准类库中的任何集合都可以使用“ for each” 循环。 在 Java SE 8中,甚至不用写循环。可以调用 forEachRemaining 方法并提供一 lambda 表达式(它会处理一个元素)。 将对迭代器的每一个元素调用这个 lambda 表达式,直到再没 有元素为止。 iterator.forEachRemaining(el ement -> do something with element);

测试一个集合是否支持高效的随机访问

注释:为了避免对链表完成随机访问操作, Java SE 1.4 引入了一个标记接口 RandomAccess。 这个接口不包含任何方法, 不过可以用它来测试一个特定的集合是否支持高效的随机访问: if (c instanceof RandomAccess) { use random access algorithm else { use sequential access algorithm }

具体的集合

ArrayList          一种可以动态增长和缩减的索引序列 LinkedList       一种可以在任何位置进行高效地插人和删除操作的有序序列 ArrayDeque 一种用循环数组实现的双端队列 HashSet 一种没有重复元素的无序集合 TreeSet —种有序集 EnumSet 一种包含枚举类型值的集 LinkedHashSet 一种可以记住元素插人次序的集 PriorityQueue 一种允许高效删除最小元素的集合 HashMap 一种存储键 / 值关联的数据结构 TreeMap —种键值有序排列的映射表 EnumMap 一种键值属于枚举类型的映射表 LinkedHashMap 一种可以记住键 / 值项添加次序的映射表 WeakHashMap 一种其值无用武之地后可以被垃圾回收器回收的映射表 IdentityHashMap 一种用 =而不是用 equals 比较键值的映射表

数据结构

链表

代表集合:LinkedList解决了数组插入,删除性能低下的问题,但是相对的,对它进行随机访问的效率是十分低下的,因为每次都必须从头或者尾部开始遍历,一直到要访问的位置。

数组

代表集合:ArrayList(线程不安全), Vector(线程安全)随机访问效率很高,但是插入或删除效率低下

散列集

代表集合:HashSet链表和数组可以按照人们的意愿排列元素的次序。但是,如果想要査看某个指定的元 素, 却又忘记了它的位置, 就需要访问所有元素, 直到找到为止。如果集合中包含的元 素很多, 将会消耗很多时间。如果不在意元素的顺序,可以有几种能够快速査找元素的数 据结构。其缺点是无法控制元素出现的次序。它们将按照有利于其操作目的的原则组织数据。散列集迭代器将依次访问所有的桶。 由于散列将元素分散在表的各个位置上,所以访问 它们的顺序几乎是随机的。只有不关心集合中元素的顺序时才应该使用 HashSet。

树集

代表集合:TreeSet TreeSet 类与散列集十分类似, 不过, 它比散列集有所改进。树集是一个有序集合 ( sorted collection) o 可以以任意顺序将元素插入到集合中。在对集合进行遍历时,每个值将 自动地按照排序后的顺序呈现。将一个元素添加到树中要比添加到散列表中慢, 参见表 9-3中的比较,但是,与检查数 组或链表中的重复元素相比还是快很多。如果树中包含 n 个元素, 査找新元素的正确位置平 均需要 l0g2 n 次比较。例如, 如果一棵树包含了 1000 个元素,添加一个新元素大约需要比较10 次。 

队列

代表集合:ArrayDeque  ,  LinkedList 前面已经讨论过, 队列可以让人们有效地在尾部添加一个元素, 在头部删除一个元 素。有两个端头的队列, 即双端队列,可以让人们有效地在头部和尾部同时添加或删除元 素。不支持在队列中间添加元素。

优先级队列

代表集合:PriorityQueue优先级队列(priority queue) 中的元素可以按照任意的顺序插人,却总是按照排序的顺序 进行检索。也就是说,无论何时调用 remove 方法,总会获得当前优先级队列中最小的元素。 然而,优先级队列并没有对所有的元素进行排序。如果用迭代的方式处理这些元素,并不需 要对它们进行排序。优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)。堆是一 个可以自我调整的二叉树,对树执行添加( add) 和删除(remore) 操作, 可以让最小的元素 移动到根,而不必花费时间对元素进行排序。

映射

集是一个集合,它可以快速地查找现有的元素。但是,要查看一个元素, 需要有要查找 元素的精确副本。这不是一种非常通用的査找方式。通常, 我们知道某些键的信息,并想要 查找与之对应的元素。 映射(map) 数据结构就是为此设计的。映射用来存放键 / 值对。如 果提供了键, 就能够查找到值。例如, 有一张关于员工信息的记录表, 键为员工 ID,值为 Employee 对象。Java 类库为映射提供了两个通用的实现:HashMap 和 TreeMap。这两个类都实现了 Map 接口。 散列映射对键进行散列, 树映射用键的整体顺序对元素进行排序, 并将其组织成搜索 树。散列或比较函数只能作用于键。与键关联的值不能进行散列或比较。 应该选择散列映射还是树映射呢? 与集一样, 散列稍微快一些, 如果不需要按照排列顺序访问键, 就最好选择散列。要迭代处理映射的键和值, 最容易的方法是使用 forEach 方法。可以提供一个接收键和 值的 lambda 表达式。映射中的每一项会依序调用这个表达式。 scores•forEach((k, v) -> System.out.println("key=" + k + ", value:" + v));

弱引用映射

代表映射:WeakHashMap当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键 / 值对。下面是这种机制的内部运行情况。WeakHashMap 使用弱引用 ( weak references) 保存键。 WeakReference 对象将引用保存到另外一个对象中,在这里,就是散列键。对于这种类型的 对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象 已经没有他人引用了,就将其回收。然而, 如果某个对象只WeakReference 引用, 垃圾 回收器仍然回收它,但要将引用这个对象的弱引用放人队列中。WeakHashMap 将周期性地检 查队列, 以便找出新添加的弱引用。一个弱引用进人队列意味着这个键不再被他人使用, 并 且已经被收集起来。于是, WeakHashMap 将删除对应的条目。

算法

Collections 类:在 Collections 类中包含了几个简单且很有用的算法。前面介绍的查找集合中最大元素的 示例就在其中。另外还包括:将一个列表中的元素复制到另外一个列表中;用一个常量值填 充容器;逆置一个列表的元素顺序。

排序

Collections 类中的 sort 方法可以对实现了 List 接口的集合进行排序。人们可能会对 sort 方法所采用的排序手段感到好奇。通常,在翻阅有关算法书籍中的排 序算法时,会发觉介绍的都是有关数组的排序算法, 而且使用的是随机访问方式。但是,对 列表进行随机访问的效率很低。实际上, 可以使用归并排序对列表进行高效的排序。然而, Java 程序设计语言并不是这样实现的。它直接将所有元素转人一个数组,对数组进行排序,然后,再将排序后的序列复制回列表。集合类库中使用的排序算法比快速排序要慢一些,快速排序是通用排序算法的传统选择。但是,归并排序有一个主要的优点:稳定,即不需要交换相同的元素。为什么要关注相 同元素的顺序呢? 下面是一种常见的情况。假设有一个已经按照姓名排列的员工列表。现在,要按照工资再进行排序。如果两个雇员的工资相等发生什么情况呢? 如果采用稳定的排 序算法,将会保留按名字排列的顺序。换句话说,排序的结果将会产生这样一个列表, 首先按照工资排序,工资相同者再按照姓名排序。

二分查找

要想在数组中査找一个对象, 通常要依次访问数组中的每个元素,直到找到匹配的元素为止。然而, 如果数组是有序的,就可以直接査看位于数组中间的元素, 看一看是否大于要查找的元素。如果是, 用同样的方法在数组的前半部分继续查找;否则, 用同样的方法在数组的后半部分继续查找。这样就可以将查找范围缩减一半。一直用这种方式査找下去。例如,如果数组中有 1024 个元素, 可以在 10 次比较后定位所匹配的元素(或者可以确认在数组中不存在这样的元素,) 而使用线性查找,如果元素存在,平均需要 512 次比较;如果元素不存在, 需要 1024 次比较才可以确认。 Collections 类的 binarySearch方法实现了这个算法。注意, 集合必须是排好序的, 否则算法将返回错误的答案。

集合与数组的转化

如果需要把一个数组转换为集合,Arrays.asList 包装器可以达到这个目的。例如: String□ values = . . HashSet<String> staff = new HashSeto(Arrays.asList(values)); 从集合得到数组会更困难一些。当然,可以使用 toArray 方法: Object □ values = staff.toArray0; 不过,这样做的结果是一个对象数组。尽管你知道集合中包含一个特定类型的对象,但 不能使用强制类型转换: StringQ values = (StringQ) staff.toArray0;// Error! toArray方法返回的数组是一个 Object[] 数组, 不能改变它的类型。实际上,必须使用toArray 方法的一个变体形式,提供一个所需类型而且长度为 0 的数组。这样一来, 返回的数 组就会创建为相同的数组类型: String[] values = staff.toArray(new StringtO]); 如果愿意,可以构造一个指定大小的数组: staff.toArray(new String[staff•si ze()]); 在这种情况下,不会创建新数组。

第九部分(并发)

中断线程

当线程的 run 方法执行方法体中最后一条语句后, 并经由执行 return 语句返冋时,或出现了在方法中没有捕获的异常时,线程将终止。在 Java 的早期版本中, 还有一个 stop 方法, 其他线程可以调用它终止线程。但是,这个方法现在已经被弃用了。

线程状态

线程可以有如下 6 种状态:•New (新创建) •Runnable (可运行) •Blocked (被阻塞) •Waiting (等待) •Timed waiting (计时等待) •Terminated (被终止) 要确定一个线程的当前状态, 可调用 getState 方法。 •新创建线程 当用 new 操作符创建一个新线程时,如 newThread(r), 该线程还没有开始运行。这意味着它的状态是 new。当一个线程处于新创建状态时,程序还没有开始运行线程中的代码。在线程运行之前还有一些基础工作要做。•可运行线程 一旦调用 start 方法,线程处于 runnable 状态。一个可运行的线桿可能正在运行也可能没有运行, 这取决于操作系统给线程提供运行的时间。Java 的规范说明没有将它作为一个单独状态。一个正在运行中的线程仍然处于可运行状态。在任何给定时刻,二个可运行的线程可能正在运行也可能没有运行(这就是为什 么将这个状态称为可运行而不是运行。•被阻塞线程和等待线程当线程处于被阻塞或等待状态时,它暂时不活动。它不运行任何代码且消耗最少的资源。直到线程调度器重新激活它。细节取决于它是怎样达到非活动状态的。 •被终止的线程线程因如下两个原因之一而被终止: •因为 run 方法正常退出而自然死亡。•因为一个没有捕获的异常终止了 nm 方法而意外死亡。 特别是, 可以调用线程的 stop 方法杀死一个线程。 该方法抛出 ThreadDeath 错误对象, 由此杀死线程。但是,stop 方法已过时, 不要在自己的代码中调用这个方法。 

线程属性

线程优先级

Thread:• void setPriority(int newPriority) 设置线程的优先级。• static void yield( ) 导致当前执行线程处于让步状态。如果有其他的可运行线程具有至少与此线程同样高 的优先级,那么这些线程接下来会被调度。注意,这是一个静态方法。

守护线程

可以通过调用 t.setDaemon(true); //标识该线程为守护线程或用户线程。这一方法必须在线程启动之前调用。 将线程转换为守护线程(daemon thread。) 这样一个线程没有什么神奇。守护线程的唯一用途是为其他线程提供服务。计时线程就是一个例子,它定时地发送“ 计时器嘀嗒” 信号给其他线程或清空过时的高速缓存项的线程。当只剩下守护线程时, 虚拟机就退出了,由于如果只剩下守护线程, 就没必要继续运行程序了。 守护线程有时会被初学者错误地使用, 他们不打算考虑关机( shutdown) 动作。但是,这是很危险的。守护线程应该永远不去访问固有资源, 如文件、 数据库,因为它会在任何时 候甚至在一个操作的中间发生中断。 

未捕获异常处理器

线程的 run方法不能抛出任何受查异常, 但是,非受査异常会导致线程终止。在这种情 况下,线程就死亡了。 但是,不需要任何 catch子句来处理可以被传播的异常。相反,就在线程死亡之前, 异常被传递到一个用于未捕获异常的处理器。 该处理器必须属于一个实现 Thread.UncaughtExceptionHandler 接口的类。这个接口只有 —个方法。 void uncaughtException(Thread t, Throwable e) 可以用 setUncaughtExceptionHandler 方法为任何线程安装一个处理器。也可以用 Thread 类的静态方法 setDefaultUncaughtExceptionHandler 为所有线程安装一个默认的处理器。替换 处理器可以使用日志 API 发送未捕获异常的报告到日志文件。 如果不安装默认的处理器, 默认的处理器为空。但是, 如果不为独立的线程安装处理器,此时的处理器就是该线程的 ThreadGroup 对象。 

同步

锁对象

如果能使用synchronized实现,经可能用synchronized实现,因为其代码简介,封装完善。

synchronized

public synchronized void methodO { method body } 等价于 public void methodQ { this.intrinsidock.1ock(); try { method body } finally { this.intrinsicLock.unlockO; } } 内部对象锁只有一个相关条件。wait 方法添加一个线程到等待集中,notifyAU /notify方法解除等待线程的阻塞状态。换句话说,调用 wait 或 notityAll 等价于 intrinsicCondition.await(); intrinsicCondition.signalAll();例子:class Bank { private double口 accounts; public synchronized void transfer(int from,int to, int amount) throws InterruptedException { while (accounts[from] < amount) wait(); // wait on intrinsic object lock's single condition accounts[from] -= amount ; accounts[to] += amount ; notifyAll(); // notify all threads waiting on the condition } public synchronized double getTotalBalanceO { . . . } }

ReentrantLock

private Lock myLock = new ReentrantLock; myLock.lockO; // a ReentrantLock object try { critical section } finally { myLock.unlockO; // make sure the lock is unlocked even if an exception is thrown } 这一结构确保任何时刻只有一个线程进人临界区。一旦一个线程封锁了锁对象, 其他任何线程都无法通过 lock 语句。当其他线程调用 lock 时,它们被阻塞,直到第一个线程释放锁对象。  把解锁操作括在 finally 子句之内是至关重要的。如果在临界区的代码抛出异常, 锁必须被释放。否则, 其他线程将永远阻塞。 

条件对象

Condition 现在,当账户中没有足够的余额时, 应该做什么呢? 等待直到另一个线程向账户中注入了资金。但是,这一线程刚刚获得了对 bankLock 的排它性访问, 因此别的线程没有进行存款操作的机会。这就是为什么我们需要条件对象的原因。 一个锁对象可以有一个或多个相关的条件对象。你可以用 newCondition 方法获得一个条件对象。习惯上给每一个条件对象命名为可以反映它所表达的条件的名字。例如,在此设置 一个条件对象来表达“ 余额充足” 条件。如果 transfer 方法发现余额不足,它调用 sufficientFunds.await(); 当前线程现在被阻塞了,并放弃了锁。我们希望这样可以使得另一个线程可以进行增加账户余额的操作。当另一个线程转账时, 它应该调用 sufficientFunds,signalAll(); 这一调用重新激活因为这一条件而等待的所有线程。

监视器

•监视器是只包含私有域的类。 •每个监视器类的对象有一个相关的锁。 •使用该锁对所有的方法进行加锁。换句话说,如果客户端调用 obj.meth0d(), 那 么 obj 对象的锁是在方法调用开始时自动获得, 并且当方法返回时自动释放该锁。因为所有的域是私有的,这样的安排可以确保一个线程在对对象操作时, 没有其他线程能访问该域。 •该锁可以有任意多个相关条件。

Volatile 域

有时,仅仅为了读写一个或两个实例域就使用同步,显得开销过大了。这时候就可以使用到volatile。volatile 关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为 volatile , 那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。

final变量

还有一种情况可以安全地访问一个共享域, 即这个域声明为 final 时。考虑以下声明: final Map<String, Double〉accounts = new HashKap<>0; 其他线程会在构造函数完成构造之后才看到这个 accounts 变量。 如果不使用 final,就不能保证其他线程看到的是 accounts 更新后的值,它们可能都只是 看到 null , 而不是新构造的HashMap。 当然,对这个映射表的操作并不是线程安全的。如果多个线程在读写这个映射表,仍然 需要进行同步。

原子性

若操作是不可拆分的,便称之为原子性操作,若一个操作是原子性操作,则不会出现线程安全问题,因为不可能有线程会中途介入,也无法中途介入,也就从根本上解决了线程安全问题。java.util.concurrent.atomic 包中有很多类使用了很高效的机器级指令(而不是使用锁) 来保证其他操作的原子性。 例如, Atomiclnteger 类提供了方法 incrementAndGet 和 decrementAndGet, 它们分别以原子方式将一个整数自增或自减。例如,可以安全地生成一个数值序列,如下所示: public static AtomicLong nextNumber = new AtomicLongO ; // In some thread... long id = nextNumber.increinentAndGetO: incrementAndGet 方法以原子方式将 AtomicLong 自增, 并返回自增后的值。也就是说,获得值、 增 1 并设置然后生成新值的操作不会中断。可以保证即使是多个线程并发地访问同一个实例,也会计算并返回正确的值。

死锁

即线程因为自己的条件无法继续执行任务,可是又没有释放锁,就导致其他线程也无法执行任务,最终导致程序挂起。

线程局部变量

前面几节中, 我们讨论了在线程间共享变量的风险。有时可能要避免共享变量, 使用ThreadLocal 辅助类为各个线程提供各自的实例。要为每个线程构造一个实例,可以使用以下代码: public static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); 要访问具体的格式化方法,可以调用: String dateStamp = dateFormat.get().format(new DateO); 在一个给定线程中首次调用 get 时, 会调用 initialValue 方法。在此之后, get 方法会返回属于当前线程的那个实例。

读写锁

java.util.concurrent.locks 包 定 义 了 两 个 锁 类, 我 们 已 经 讨 论 的 ReentrantLock 类 和 ReentrantReadWriteLock 类。 如果很多线程从一个数据结构读取数据而很少线程修改其中数 据的话, 后者是十分有用的。在这种情况下, 允许对读者线程共享访问是合适的。当然,写者线程依然必须是互斥访问的。 下面是使用读 / 写锁的必要步骤: 1 ) 构 造 一 个 ReentrantReadWriteLock 对象: private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(): 2 ) 抽取读锁和写锁: private Lock readLock = rwl . readLock(); private Lock writeLock = rwl .writeLock(); 3 ) 对所有的获取方法加读锁: public double getTotalBalanceO { readLock.lockO; try { . . . } finally { readLock.unlock(); } } 4 ) 对所有的修改方法加写锁: public void transfer(. . .) { writeLock.lockO; try { . . . } finally { writeLock.unlock(); } } 

阻塞队列

java.util.concurrent 包提供了阻塞队列的几个变种。 默认情况下,LinkedBlockingQueue 的容量是没有上边界的,但是,也可以选择指定最大容量。LinkedBlockingDeque 是一个双端 的版本。ArrayBlockingQueue 在构造时需要指定容量,并且有一个可选的参数来指定是否需 要公平性。若设置了公平参数, 则那么等待了最长时间的线程会优先得到处理。通常,公平 性会降低性能,只有在确实非常需要时才使用它。 PriorityBlockingQueue 是一个带优先级的队列, 而不是先进先出队列。元素按照它们的 优先级顺序被移出。该队列是没有容量上限,但是,如果队列是空的, 取元素的操作会阻塞。

线程安全的集合

java.util.concurrent 包提供了映射、 有序集和队列的高效实现:ConcurrentHashMap、 ConcurrentSkipListMap >ConcurrentSkipListSet 和 ConcurrentLinkedQueue。 这些集合使用复杂的算法,通过允许并发地访问数据结构的不同部分来使竞争极小化。集合返回弱一致性( weakly consistent) 的迭代器。这意味着迭代器不一定能反映出它们被构造之后的所有的修改,但是,它们不会将同一个值返回两次,也不会拋出 Concurrent ModificationException 异常。

执行器

线程池

Executors构建一个新的线程是有一定代价的, 因为涉及与操作系统的交互。如果程序中创建了大 量的生命期很短的线程,应该使用线程池( thread pool)。一个线程池中包含许多准备运行的 空闲线程。将 Runnable 对象交给线程池, 就会有一个线程调用 run 方法。 当 run 方法退出 时,线程不会死亡,而是在池中准备为下一个请求提供服务。 另一个使用线程池的理由是减少并发线程的数目。创建大量线程会大大降低性能甚至使 虚拟机崩溃。如果有一个会创建许多线程的算法, 应该使用一个线程数“ 固定的” 线程池以 限制并发线程的总数。

同步器

栅栏(CyclicBarrier)

CyclicBarrierCyclicBarrier 类实现了一个集结点(rendezvous) 称为障栅( barrier)。考虑大量线程运行在一次计算的不同部分的情形。当所有部分都准备好时,需要把结果组合在一起。当一个线程完成了它的那部分任务后, 我们让它运行到障栅处。一旦所有的线程都到达了这个障栅,障栅就撤销, 线程就可以继续运行。障栅被称为是循环的( cyclic), 因为可以在所有等待线程被释放后被重用。在这一点上,有别于 CountDownLatch, CountDownLatch 只能被使用一次。

倒计时门拴(CountDownLatch)

CountDownLatch一个倒计时门栓( CountDownLatch) 让一个线程集等待直到计数变为 0。倒计时门栓是 一次性的。一旦计数为 0, 就不能再重用了。 一个有用的特例是计数值为 1 的门栓。实现一个只能通过一次的门。线程在门外等候直到另一个线程将计数器值置为0。 举例来讲, 假定一个线程集需要一些初始的数据来完成工作。工作器线程被启动并在门外等候。另一个线程准备数据。当数据准备好的时候, 调用 cmmtDown, 所有工作器线程就可以继续运行了。 然后,可以使用第二个门栓检査什么时候所有工作器线程完成工作。用线程数初始化门栓。每个工作器线程在结束前将门栓计数减 1。另一个获取工作结果的线程在门外等待,一旦所有工作器线程终止该线程继续运行。

交换器(Exchanger)

Exchanger当两个线程在同一个数据缓冲区的两个实例上工作的时候, 就可以使用交换器( Exchanger) 典型的情况是, 一个线程向缓冲区填人数据, 另一个线程消耗这些数据。当它们都完成以后,相互交换缓冲区。

同步队列(SynchronousQueue)

SynchronousQueue 同步队列是一种将生产者与消费者线程配对的机制。当一个线程调用 SynchronousQueue的 put 方法时,它会阻塞直到另一个线程调用 take 方法为止,反之亦然。与 Exchanger 的情况不同, 数据仅仅沿一个方向传递,从生产者到消费者。 即使 SynchronousQueue 类实现了 BlockingQueue 接口, 概念上讲,它依然不是一个队列。它没有包含任何元素,它的 size方法总是返回 0。

第二部分(java基本语法)

基本数据类型

整型

byte : 1 字节 int:     4 字节 short: 2 字节 long:  8 字节 

浮点型

float : 4 字节double : 8字节2.0 - 1.1 问题:在使用double类型运算该公式时,结果为:0.899999在使用float类型运算该公式时,结果为:0.9

布尔型

char 类型

我们强烈建议不要在程序中使用 char 类型,除非确实需要处理 UTF-16 代码单元。最好 将字符串作为抽象数据类型处理

变量

变量

局部变量

局部变量就是本地变量,在方法、构造器或者块中使用,在方法、构造器或者块进入时被创建,一旦退出该变量就会被销毁局部变量没有默认值,因此本地变量必须被声明并且在第一次使用前要给它赋值

成员变量

成员变量可以分为:    实例变量 (不用static修饰)       随着实例属性的存在而存在   类变量 (static修饰)       随着类的存在而存在 成员变量无需显式初始化,系统会自动对其进行默认初始化

常量

运算符

一般运算

+、-、 *、/ :表示加、减、乘、除运算。 % :整数的求余操作(有时称为取模) 用 % 表示。例如,15/2 等于 7,15%2 等于 1, 15.0/2 等于 7.50 自增: i++  ;  ++ i    区别只在于先加再使用还是先使用再加,单独使用时无区别。自减: i--    ;   -- i   区别只在于先减再使用还是先使用再减,单独使用时无区别。Math类中有大量计算方法,例如三角函数方法,幂运算等。

类型转换

不同类型之间可以相互转换,但是在大类型转化为小类型时,要注意精度丢失问题。如double 转float 。不同类型直接进行运算时,结果为大类型。例如double 类型变量加float 类型变量,结果为double 类型。

位运算

很少用,通过对二进制数的各个位直接进行操作计算而得出结果。就效率而言是计算手段中最高的。

字符串

从概念上讲, Java 字符串就是 Unicode 字符序列。 例如, 串“ Java\u2122” 由 5 个 Unicode 字符 J、a、v、a 和™。Java 没有内置的字符串类型, 而是在标准 Java类库中提供了 一个预定义类,很自然地叫做 String。每个用双引号括起来的字符串都是 String类的一个实例。String是不可变的。由于String是不可变的,所以我们在进行一系列的字符串拼接操作时,其实会产生大量的中间字符串,而这些字符串并不是我们要的,而他们又实实在在的占用着内存,需要等待垃圾回收,所以在需要频繁进行字符串拼接操作时,可以使用StringBuilder 或者StringBuffer,他们的区别在于,StringBuilder 是线程不安全的,而StringBuffer是线程安全的

输入输出

输入

输出

我们可以使用printf方法格式化输出内容:在 printf中,可以使用多个参数, 例如: System.out.printf("Hello, %s. Next year, you'll be SSd", name, age); 每一个以 %字符开始的格式说明符都用相应的参数替换。格式说明符尾部的转换符将指示被 格式化的数值类型:f 表示浮点数,s 表示字符串,d 表示十进制整数

控制流程

条件判断

if

switch

循环

for

while

中断流程控制语句

break

continue

return

大数值

如果基本的整数和浮点数精度不能够满足需求, 那么可以使用jaVa.math 包中的两个 很有用的类:Biglnteger 和 BigDecimaL 这两个类可以处理包含任意长度数字序列的数值。 Biglnteger类实现了任意精度的整数运算, BigDecimal 实现了任意精度的浮点数运算。 使用静态的 valueOf方法可以将普通的数值转换为大数值: Biglnteger a = Biglnteger.valueOf(100); 不能使用人们熟悉的算术运算符(如:+ 和 *) 处理大数值。 而需要使用大数 值类中的 add 和 multiply 方法。 Biglnteger c = a.add(b);   // c = a + b Biglnteger d = c.nultipiy(b.add(Biglnteger.valueOf(2))); // d = c * (b + 2)

Biglnteger(实现了任意精度的整数运算)

BigDecimaL(实现了任意精度的浮点数运算)

数组

数组是一种数据结构, 用来存储同一类型值的集合。通过一个整型下标可以访问数组中 的每一个值。例如, 如果 a 是一个整型数组, a[i] 就是数组中下标为 i 的整数。 在声明数组变量时, 需要指出数组类型 (数据元素类型紧跟 []) 和数组变量的名字。下 面声明了整型数组 a: int[] a; 不过, 这条语句只声明了变量 a, 并没有将 a 初始化为一个真正的数组。应该使用 new 运算 符创建数组。int[ ] a = new int[100];这条语句创建了一个可以存储 100 个整数的数组。数组长度不要求是常量: newint[n] 会创建 一个长度为 n 的数组。数组的大小是在初始化的时候就固定的,后期无法进行扩大或缩小,就如同String一样,你想改变,只能通过实例化一个新的数组然后改变引用指向。

for each循环

Java 有一种功能很强的循环结构, 可以用来依次处理数组中的每个元素(其他类型的元 素集合亦可)而不必为指定下标值而分心。 这种增强的 for 循环的语句格式为:for (variable:collection) {} 定义一个变量用于暂存集合中的每一个元素, 并执行相应的语句(当然,也可以是语句块)。 collection 这一集合表达式必须是一个数组或者是一个实现了 Iterable 接口的类对象(例如 ArrayList)循环ArrayList时,普通for循环比foreach循环花费的时间要少一点;循环LinkList时,普通for循环比foreach循环花费的时间要多很多。需要循环数组结构的数据时,建议使用普通for循环,因为for循环采用下标访问,对于数组结构的数据来说,采用下标访问比较好。需要循环链表结构的数据时,一定不要使用普通for循环,这种做法很糟糕,数据量大的时候有可能会导致系统崩溃

数组排序(Arrays.sort)

要想对数值型数组进行排序, 可以使用 Arrays类中的 sort 方法:  int[] a = new int[10000]; Arrays.sort(a) 这个方法使用了优化的快速排序算法。快速排序算法对于大多数数据集合来说都是效率比较高的

多维数组

网上有各种理解方法,例如横纵图标理解,而我个人比较喜欢一种粗暴的理解方法,即数组存数组  或  数组的数组。二维数组:即是一个数组的每个下标处存放一个数组的数组三维数组:即是一个数组的每个下标处存放一个数组,而存放的那个数组的每个下标处又存放着一个数组的数组以此类推例如:二维数组中的[3][7] ,即表示数组下标为3处那个数组的下标为7的值。

第四部分(继承)

超类和子类

关键词 : extends 覆盖方法:方法的签名: 方法名与方法参数方法的返回值不属于方法签名,所以方法的重载不能只改变方法的返回值,而方法的覆盖可以修改方法的返回值。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。构造方法:子类构造器若调用手动调用父类的构造器,则构造语句必须位于子类构造器的第一行。由于子类的构造器不能访问父类的私有域, 所以必须利用 父类的构造器对这部分私有域进行初始化,我们可以通过 super 实现对超类构造器的调用。使用 super 调用构造器的语句必须是子类构造器的第一条语句。 如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数) 的构造器。 注释: 有些人认为 super 与 this 引用是类似的概念, 实际上,这样比较并不太恰当。这是 因为 super 不是一个对象的引用, 不能将 super 赋给另一个对象变量, 它只是一个指示编 译器调用超类方法的特殊关键字。 在覆盖一个方法的时候,子类方法不能低于超类方法的可见性

多态

有一个用来判断是否应该设计为继承 关系的简单规则, 这就是“ is-a” 规则, 它 表明子类的每个对象也是超类的对象。“ is-a” 规则的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以 用子类对象置换。 在 Java程序设计语言中,对象变量是多态的。 一个 父类变量既可以引用一个 父类对象, 也可以引用一个 父类的任何一个子类的对象。充分利用java的多态特性,能写出耦合性低且健壮,简洁的代码。

方法的调用

1 ) 编译器査看对象的声明类型和方法名。假设调用 x.f(param),且隐式参数 x声明为 C 类的对象。需要注意的是:有可能存在多个名字为 f, 但参数类型不一样的方法。例如,可 能存在方法 f(im) 和方法 String)。编译器将会一一列举所有 C 类中名为 f 的方法和其超类中 访问属性为 public 且名为 f 的方法(超类的私有方法不可访问) 。 至此, 编译器已获得所有可能被调用的候选方法。 2 ) 接下来,编译器将査看调用方法时提供的参数类型。如果在所有名为 f 的方法中存在 一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重栽解析(overloading resolution)。例如,对于调用 x.f(“ Hello” )来说, 编译器将会挑选 f(String),而不是 f(int)。由于允许类型转换(int 可以转换成double, Manager 可以转换成 Employee, 等等), 所以这 个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法, 或者发现经过类型转换后 有多个方法与之匹配, 就会报告一个错误。 至此, 编译器已获得需要调用的方法名字和参数类型。 3 ) 如果是 private 方法、 static 方法、final 方法(有关 final 修饰符的含义将在下一节讲 述)或者构造器, 那么编译器将可以准确地知道应该调用哪个方法, 我们将这种调用方式称 为静态绑定(static binding)。与此对应的是,调用的方法依赖于隐式参数的实际类型, 并且 在运行时实现动态绑定。在我们列举的示例中, 编译器采用动态绑定的方式生成一条调用 f (String) 的指令。 4 ) 当程序运行,并且采用动态绑定调用方法时, 虚拟机一定调用与 x 所引用对象的实 际类型最合适的那个类的方法。假设 x 的实际类型是 D,它是 C类的子类。如果 D类定义了 方法 f(String),就直接调用它;否则,将在 D类的超类中寻找 f(String), 以此类推。 每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个 方法表(method table), 其中列出了所有方法的签名和实际调用的方法。这样一来,在真正 调用方法的时候, 虚拟机仅查找这个表就行了。在前面的例子中, 虚拟机搜索 D 类的方法 表, 以便寻找与调用 f(Sting) 相K配的方法。这个方法既有可能是 D.f(String), 也有可能是 X.f(String), 这里的 X 是 D 的超类。这里需要提醒一点,如果调用 super.f(param), 编译器将 对隐式参数超类的方法表进行搜索。1 ) 首先,虚拟机提取 e 的实际类型的方法表。既可能是 Employee、Manager 的方法表, 也可能是 Employee类的其他子类的方法表。2 ) 接下来, 虚拟机搜索定义 getSalary 签名的类。此时, 虚拟机已经知道应该调用哪个 方法。3) 最后,虚拟机调用方法。 动态绑定有一个非常重要的特性: 无需对现存的代码进行修改,就可以对程序进行扩展。 假设增加一个新类 Executive, 并且变量 e 有可能引用这个类的对象, 我们不需要对包含调用 e.getSalary() 的代码进行重新编译。如果 e 恰好引用一个 Executive 类的对象,就会自动地调 用 Executive.getSalaryO 方法。 

抽象类

Object(所有类的超类)

equals

写一个完美的 equals方法的建议:1 ) 显式参数命名为 otherObject, 稍后需要将它转换成另一个叫做 other 的变量。2 ) 检测 this 与 otherObject 是否引用同一个对象:if (this = otherObject) return true; 这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一 个一个地比较类中的域所付出的代价小得多。3 ) 检测 otherObject 是否为 null, 如果为 null, 返 回 false。这项检测是很必要的。if (otherObject = null) return false; 4 ) 比较 this 与 otherObject 是否属于同一个类。如果 equals 的语义在每个子类中有所改 变,就使用 getClass 检测: if (getClass() != otherObject.getCIassO) return false; 如果所有的子类都拥有统一的语义,就使用 instanceof检测: if (!(otherObject instanceof ClassName)) return false; 5 ) 将 otherObject 转换为相应的类类型变量:ClassName other = (ClassName) otherObject 6 ) 现在开始对所有需要比较的域进行比较了。使用 = 比较基本类型域,使用 equals 比 较对象域。如果所有的域都匹配, 就返回 true; 否 则 返 回 false。 

hashCode

散列码( hash code) 是由对象导出的一个整型值。散列码是没有规律的。如果 x 和 y 是 两个不同的对象, x.hashCode( ) 与 y.hashCode( ) 基本上不会相同。 如果重新定义 equals方法,就必须重新定义 hashCode方法, 以便用户可以将对象插人 到散列表中hashCode 方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码, 以便能够让各个不同的对象产生的散列码更加均匀。例如, 下面是 Employee 类的 hashCode方法。 public int hashCode() { return 7 * name.hashCode0 + 11* new Double(salary).hashCode0 + 13 * hireDay.hashCode(); } 不过,还可以做得更好。首先, 最好使用 null 安全的方法 Objects.hashCode。如果其参 数为 null,这个方法会返回 0, 否则返回对参数调用 hashCode 的结果。 另外,使用静态方法 Double.hashCode 来避免创建 Double 对象: public int hashCode() { return 7 * Objects.hashCode(name) + 11 * Double.hashCode(salary) + 13 * Objects.hashCode(hireDay); } 还有更好的做法,需要组合多个散列值时,可以调用 ObjeCtS.hash 并提供多个参数。这 个方法会对各个参数调用 Objects.hashCode, 并组合这些散列值。这样 Employee.hashCode方 法可以简单地写为: public int hashCodeO { return Objects,hash(name, salary, hireDay); } Equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true, 那么 x.hashCode( ) 就必 须与 y.hashCode( ) 具有相同的值。例如, 如果用定义的 Employee.equals 比较雇员的 ID,那 么 hashCode 方法就需要散列 ID,而不是雇员的姓名或存储地址。  

toString

用于返回表示对象值的字符串。绝大多数(但不是全部)的 toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值。下面是 Employee类中的 toString方法的实现: public String toStringO { return "Employee[ name=" + name + ",salary: " + salary + ",hireDay=" + hireDay +"]" ;} 实际上,还可以设计得更好一些。最好通过调用getClaSS( ).getName( ) 获得类名的字符 串,而不要将类名硬加到 toString方法中。 public String toStringO { return getClassO.getNameO + "[name=" + name +'salary: " + salary + ",hireDay=" + hireDay + "]"; } toString方法也可以供子类调用。 当然,设计子类的程序员也应该定义自己的 toString方法,并将子类域的描述添加进去。 如果超类使用了 getClass( ).getName( ), 那么子类只要调用 super.toString( )就可以了。例如, 下面是 Manager 类中的 toString方法: public class Manager extends Employee public String toStringO { return super.toStringO + "[bonus=" + bonus + "]"; }

反射

java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。

获取对象实例

Class类中的方法:• static Cl ass forName(String className) 返回描述类名为 className 的 Class 对象。 • Object newlnstance() 返回这个类的一个新实例。 只能调用无参的构造方法。Constructor类中的方法:• Object newlnstance(Object[] args) 构造一个这个构造器所属类的新实例。 可以添加调用有参数的构造方法。

获取类的属性方法

调用对象方法

调用无参方法:package org.lxh.demo15.invokedemo ;import java.lang.reflect.Method ;public class InvokeSayChinaDemo{ public static void main(String args[]){ Class<?> c1 = null ; try{ c1 = Class.forName("org.lxh.demo15.Person") ; // 实例化Class对象 }catch(Exception e){} try{ Method  met = c1.getMethod("sayChina") ; // 找到sayChina()方法 met.invoke(c1.newInstance()) ; // 调用方法 }catch(Exception e){ e.printStackTrace() ; } }};

第六部分(异常,断言,日志)

人们在遇到错误时会感觉不爽。如果一个用户在运行程序期间,由于程序的错误或一些 外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了, 为了避免这类事 情的发生, 至少应该做到以下几点: •向用户通告错误; •保存所有的工作结果; •允许用户以妥善的形式退出程序。

异常

与执行简单的测试相比, 捕获异常所花费的时间大大超过了前者, 因此使用 异常的基本规则是:只在异常情况下使用异常机制。 

异常基本分层

需要注意的是,所有的异常都是由 Throwable 继承而来,但在下一层立即分解为两个分 支:Error 和 Exception:' Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。 应用程序不应该 抛出这种类型的对象。 如果出现了这样的内部错误, 除了通告给用户,并尽力使程序安全地 终止之外, 再也无能为力了。这种情况很少出现。 在设计 Java 程序时, 需要关注 Exception 层次结构。 这个层次结构又分解为两个分支: 一个分支派生于 RuntimeException ; 另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于 RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类 问题导致的异常属于其他异常 IOException: 派生于 RuntimeException 的异常包含下面几种情况: •错误的类型转换。 •数组访问越界 。•访问 null 指针 。不是派生于 RuntimeException 的异常包括: •试图在文件尾部后面读取数据。 •试图打开一个不存在的文件。 •试图根据给定的字符串查找 Class 对象, 而这个字符串表示的类并不存在,, “ 如果出现 RuntimeException 异常, 那么就一定是你的问题” 是一条相当有道理的规则。 应该通过检测数组下标是否越界来避免 ArraylndexOutOfBoundsException 异常;应该通过在 使用变量之前检测是否为 null 来杜绝 NullPointerException 异常的发生:

抛出异常

throws在自己编写方法时, 不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法 中用 throws 子句声明异常, 什么异常必须使用 throws 子句声明, 需要记住在遇到下面 4 种 情况时应该抛出异常: 1 ) 调用一个抛出受査异常的方法, 例如, FilelnputStream 构造器。 2 ) 程序运行过程中发现错误, 并且利用 throw语句抛出一个受查异常。3 ) 程序出现错误, 例如,a[-l]=0 会抛出一个 ArraylndexOutOffloundsException 这样的 非受查异常。4 ) Java 虚拟机和运行时库出现的内部错误。 如果出现前两种情况之一, 则必须告诉调用这个方法的程序员有可能抛出异常。 为什么? 因为任何一个抛出异常的方法都有可能是一个死亡陷阱。 如果没有处理器捕获这个异 常,当前执行的线程就会结束。 对于那些可能被他人使用的 Java 方法, 应该根据异常规范( exception specification), 在 方法的首部声明这个方法可能抛出的异常。 不需要声明 Java 的内部错误,即从 Error 继承的错误。任何程序代码都具有抛出那些 异常的潜能, 而我们对其没有任何控制能力。不应该声明从 RuntimeException 继承的那些非受查异常,因为这些异常是我们可以控制解决的,我们应该解决它,而不是抛出这个异常。

再次抛出异常与异常链

在 catch 子句中可以抛出一个异常,这样做的目的是改变异常的类型,: 如果开发了一个 供其他程序员使用的子系统, 那么,用于表示子系统故障的异常类型可能会产生多种解释。 ServletException 就是这样一个异常类型的例子。执行 servlet 的代码可能不想知道发生错误的 细节原因, 但希望明确地知道 servlet 是否有问题。 下面给出了捕获异常并将它再次抛出的基本方法(不推荐使用): try { access the database } catch (SQLException e) { throw new ServletException("database error: " + e.getMessageO); } 这里,ServleException 用带有异常信息文本的构造器来构造。 不过,可以有一种更好的处理方法,并且将原始异常设置为新异常的“ 原因”: try { access the database }catch (SQLException e) { Throwable se = new ServletException ("database error"); se.ini tCause(e); } 当捕获到异常时, 就可以使用下面这条语句重新得到原始异常:  Throwable e = se.getCauseO ; 强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。

捕获异常

不管是否有异常被捕获,finally 子句中的代码都被执行。在下面的示例中, 程序将在所 有情况下关闭文件。 InputStream in = new FileInputStream(. . .); try{ //code that might throwexceptions   } catch (IOException e) { // 3 //showerror message // 4 } finally { // 5 in.doseO; } 在上面这段代码中,有下列 3 种情况会执行 finally 子句: 1 ) 代码没有抛出异常。 在这种情况下, 程序首先执行 try 语句块中的全部代码,然后执 行 finally 子句中的代码t 随后, 继续执行 try 语句块之后的第一条语句。也就是说,执行标 注的 1、 2、 5、 6 处。 2 ) 抛出一个在 catch 子句中捕获的异常。在上面的示例中就是 IOException 异常。在这种 情况下,程序将执行 try语句块中的所有代码,直到发生异常为止。此时,将跳过 try语句块中 的剩余代码,转去执行与该异常匹配的 catch 子句中的代码, 最后执行 finally 子句中的代码。 如果 catch 子句没有抛出异常,程序将执行 try 语句块之后的第一条语句。在这里,执行 标注 1、 3、 4、5、 6 处的语句。 如果 catch 子句抛出了一个异常, 异常将被抛回这个方法的调用者。在这里, 执行标注 1、 3、 5 处的语句。 3 ) 代码抛出了一个异常, 但这个异常不是由 catch 子句捕获的。在这种情况下,程序将 执行 try 语句块中的所有语句,直到有异常被抛出为止。此时, 将跳过 try 语句块中的剩余代 码, 然后执行 finally 子句中的语句, 并将异常抛给这个方法的调用者。在这里, 执行标注 1、 5 处的语句。

推荐写法

强烈建议解搞合 try/catch 和 try/finally 语句块。这样可以提高代码的清晰 度。例如: InputStrean in = . . .; try { try { code that might throwexceptions } finally { in.doseO; } } catch (IOException e) { show error message } 内层的 try语句块只有一个职责, 就是确保关闭输入流。外层的 try 语句块也只有一个职 责, 就是确保报告出现的错误。这种设计方式不仅清楚,而且还具有一个功能,就是将会报告 finally 子句中出现的错误。

自定义异常

我们需要做的只是定义一个派生于 Exception 的类,或者派生于 Exception 子类的类。例如, 定义一个派生于 IOException 的类。 习惯上, 定义的类应该包含两个构造器, 一个是默认的构造器;另一个是带有详细描述信息 的构造器(超类 Throwable 的 toString 方法将会打印出这些详细信息, 这在调试中非常有用)。 class FileFormatException extends IOException { public FileFormatExceptionO {} public FileFormatException(String gripe) { super(gripe); } } javaJang.Throwabie :•Throwable( ) 构造一个新的 Throwabie 对象, 这个对象没有详细的描述信息。 •Throwable(String message ) 构造一个新的 throwabie 对象, 这个对象带有特定的详细描述信息。习惯上,所有派 生的异常类都支持一个默认的构造器和一个带有详细描述信息的构造器。 •getMessage( ) 获得 Throwabie 对象的详细描述信息。

finallly

当 finally 子句包含 return 语句时, 将会出现一种意想不到的结果„ 假设利用 return 语句从 try语句块中退出。在方法返回前, finally 子句的内容将被执行。如果 finally 子句中 也有一个 return 语句,这个返回值将会覆盖原始的返回值。finally 语句中也有可能抛出异常,若finally抛出异常,原始的异常将会丢失,所以往往需要在finally也嵌套对应的try catch,不过在java7之后,这种情况得到了解决:带资源的 try 语句(try-with-resources) 的最简形式为: try (Resource res = . . .) { work with res } try块退出时,会自动调用 res.doseO。下面给出一个典型的例子, 这里要读取一个文件 中的所有单词: try (Scanner in = new Scanner(new FileInputStream(7usr/share/dict/words")), "UTF-8") { while (in.hasNextO) System.out.pri ntl n(i n.next()); } 这个块正常退出时, 或者存在一个异常时, 都会调用 inxloseO 方法, 就好像使用了 finally块一样。 还可以指定多个资源:  try (Scanner in = new Scanne(new FileInputStream('7usr/share/dict/words"). "UTF-8"); PrintWriter out = new PrintWriter("out.txt")) { while (in.hasNextO) out.println(in.next().toUpperCaseO); } 不论这个块如何退出, in 和 out 都会关闭。如果你用常规方式手动编程,就需要两个嵌 套的 try/finally语句。 上一节已经看到,如果 try 块抛出一个异常, 而且 close 方法也抛出一个异常,这就会带 来一个难题,. 带资源的 try 语句可以很好地处理这种情况。原来的异常会重新抛出,而 close 方法抛出的异常会“ 被抑制% 这些异常将自动捕获,并由 addSuppressed 方法增加到原来的 异常。 如果对这些异常感兴趣, 可以调用 getSuppressed 方法,它会得到从 close 方法抛出并 被抑制的异常列表。 你肯定不想采用这种常规方式编程。只要需要关闭资源, 就要尽可能使用带资源的 try 语句。 

断言

关键词: assert若在代码中添加大量的测试语句用于检测程序bug,这些语句会一直存在,影响程序运行效率,而断言则不会。断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测语句将会被自动地移走。实际上基本不使用断言机制。

日志

Logger基本日志 : Logger.getClobal 0,info("File->Open menu item selected");高级日志 : private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp"):

第七部分(泛型程序设计)

泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。缺点:不能用基本类型实例化类型参数运行时类型查询只适用于原始类型不能创建参数化类型的数组泛型类的静态上下文中类型变量无效不能抛出或捕获泛型类的实例可以消除对受查异常的检查注意擦除后的冲突:public class Pair<T> { public boolean equals(T value) { return first,equals(value) && second,equals(value); } } 考虑一个 Pair<String>。从概念上讲, 它有两个 equals 方法:boolean equals(String) // defined in Pai r<T> boolean equals(Object) // inherited from Object 但是,直觉把我们引入歧途。方法擦除 boolean equals(T) 就是 boolean equals(Object) 与 Object.equals 方法发生冲突。 当然,补救的办法是重新命名引发错误的方法。

命名习惯

类型变量使用大写形式,且比较短, 这是很常见的。在 Java 库中, 使用变量 E 表示集合的元素类型, K 和 V 分别表示表的关键字与值的类型。T ( 需要时还可以用临近的 字母 U 和 S) 表示“ 任意类型”。

泛型类

public class Pair<T> { private T first; private T second; public Pair() { first = null ; second = null ; } public PairfT first, T second) { this, first = first; this.second = second; } public T getFirstO { return first; } public T getSecondO { return second; } public void setFirst(T newValue) { first = newValue; } public void setSecond(T newValue) { second = newValue; } }

泛型方法

class ArrayAlg { public static <T> T getMiddle(T... a) { return a[a.length / 2]; } }

泛型限定

<T extends BoundingType〉表示 T 应该是绑定类型的子类型 (subtype)。 T 和绑定类型可以是类,也可以是接口。一个类型变量或通配符可以有多个限定, 例如:T extends Comparable & Serializable 限定类型用“ &” 分隔,而逗号用来分隔类型变量。在 Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用 一个类作为限定,它必须是限定列表中的第一个。<T super BoundingType〉

泛型擦除

java编译后,是没有泛型类这种东西的,所有的泛型代码都会被编译为使用时的具体类型,编译成为一个普通的类。

通配符

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

读书笔记 --《 java核心技术卷一》 的相关文章

  • 有效地将三个字母的货币名称转换为符号名称(例如 20 美元到 20 美元)

    我有一个格式化的字符串 它等于USD 20 我想把它转换成 20 我怎样才能高效地做到这一点 我应该使用正则表达式来执行此操作 但由于区域设置发生变化 国家 地区 ISOCode 也会发生变化 你需要的是这个 import java uti
  • 仅运行相应源代码已更改的单元测试?

    我正在 Jenkins CI 服务器中运行单元测试和 Selenium 测试 众所周知 在大型项目中测试需要很长时间才能运行 Java 是否有一个工具 框架只能触发其源代码已更改的测试 这是因为并非每次对 SCM 的提交都会影响源代码的所有
  • 合并 2 个 .jks 信任库文件

    我正在使用启用了 SSL 的 Tomcat 并使用信任库进行客户端身份验证 我有两个 jks trustore 文件 第一个 我将其用于 PROD 环境 另一个用于 TEST 环境客户端证书 我在 Tomcat 上部署了 Web 应用程序
  • 使用 s:select 标签在下拉菜单中使用 i18n [重复]

    这个问题在这里已经有答案了 我的 JSP 页面中有一个下拉菜单 它是通过
  • import java 导入错误:没有名为 java 的模块

    我似乎遇到了障碍 根本无法解决这个问题 任何人都可以帮我弄清楚为什么我无法导入 java 模块吗 Error Traceback most recent call last File datasource config py line 3
  • 如何将参数传递给Workmanager DoWork方法

    我想安排任务在 24 小时后从数据库中删除 public class WorkManager extends Worker public WorkManager NonNull Context context NonNull WorkerP
  • 如何从球衣服务端点发送实体列表?

    我正在从球衣服务器发送实体列表 在客户端 我试图获取这些实体列表 但它给了元帅例外 为什么它在元素名末尾添加 s 即 emps 而不是 emp XmlRootElement public class Emp Server side code
  • Java:Swing:设置JButton的位置

    我想实现以下布局 OK
  • 简单的Java程序插入USB热点后速度慢100倍

    我有以下Java程序 class Main public static void main String args throws java io IOException long start System nanoTime java io
  • 覆盖乔达一周的第一天?

    是否有可能覆盖乔达弱的第一天sunday 因为 Joda 使用Monday作为一周的第一天 如果有办法的话 谁能解释一下 我在 SOF 中提到了以下主题 乔达时间 一周的第一天 https stackoverflow com questio
  • 相对重力

    我最近开始使用jMonkey引擎 这非常好 但我在尝试实现相对重力时陷入了困境 我想让行星彼此围绕轨道运行 不一定是完美的圆形轨道 取决于速度 所以每个对象都应该影响其他对象 我现在拥有的 关闭全球重力 bulletAppState get
  • 在休眠搜索中使用现有分析器AnalyzerDiscriminator

    Entity Indexed AnalyzerDefs AnalyzerDef name en tokenizer TokenizerDef factory StandardTokenizerFactory class filters To
  • String.intern() 线程安全吗

    我想在Java中使用 String intern 来节省内存 对具有相同内容的字符串使用内部池 我从不同的线程调用这个方法 这是个问题吗 对你的问题的简短回答是肯定的 它是线程安全的 但是 您可能需要重新考虑使用此工具来减少内存消耗 原因是
  • SFTP Java - 管道关闭 Jsch 异常

    我正在研究一种 java 方法 将文件从一个位置复制到另一个远程位置 我的代码如下 我尝试使用jsch 0 1 42 0 1 50 0 1 54 public static void processFiles ArrayList
  • Java 日期和 MySQL 时间戳时区

    我正在编辑一段代码 其基本功能是 timestamp new Date 然后坚持下去timestamp中的变量TIMESTAMPMySQL 表列 然而 通过调试我看到Date显示在正确时区的对象 GMT 1 当持久化在数据库上时 它是GMT
  • Java泛型类型参数中的问号是什么意思? [复制]

    这个问题在这里已经有答案了 这是取自斯坦福解析器附带的一些示例的一小段代码 我已经用 Java 进行了大约 4 年的开发 但从未对这种风格的代码应该表示什么有非常深入的理解 List
  • 使用 ProcessBuilder 启动 CMD

    我尝试使用以下代码在 Windows 中启动 CMD 应用程序 但它无法按预期工作 来自不同网站的几个示例表明 cmd 作为 ProcessBuilder 构造中的参数应该有效 我需要做什么才能让我的 Java 应用程序在 Windows
  • 不鼓励在Web应用程序中使用线程吗?

    我们与同事就在 Java 的 Web 应用程序中使用线程进行了激烈的讨论 他们的观点是 不建议在 Java Web 应用程序中使用线程 因为它们不受容器管理 一般来说 我对此表示同意 因为线程可能会干扰容器 但是 如果它不是 Java EE
  • 原子整数的compareandexchange()与compareandset()

    在研究 AtomicInteger 时 我发现这个 API 提供了两种方法 比较和交换 如果当前值被引用 则自动将该值设置为 newValue to 作为见证值 预期值 记忆效应为 由指定VarHandle compareAndExchan
  • 如何将元素添加到通用集合

    我想知道如何将专用对象添加到通用集合中 我正在使用以下代码 Collection

随机推荐

  • 直流电机和步进电机-第1季第12部分-朱有鹏-专题视频课程

    直流电机和步进电机 第1季第12部分 1966人已学习 课程介绍 本课程是 朱有鹏老师单片机完全学习系列课程 第1季第12个课程 xff0c 主要讲解了直流电机和步进电机 xff0c 其中步进电机是关键 xff0c 通过学习让大家初步掌握步
  • 无需后台接入?带你玩转VasSonic 2.0里的Local Server

    腾讯手Q增值团队于今年8月份正式开源了VasSonic xff0c 一个轻量级高性能的Hybrid框架 VasSonic框架使用并行加载 动态缓存 增量更新等手段 xff0c 实现了终端H5页面的秒开 xff0c 对用户体验的优化做的非常极
  • gcc中的-w -W和-Wall选项

    今天在看一个makefile时看到了gcc W Wall 这句 xff0c 不明其理 xff0c 专门查看了gcc的使用手册 w的意思是关闭编译时的警告 xff0c 也就是编译后不显示任何warning xff0c 因为有时在编译之后编译器
  • VNC Connect使用参数填充VNC配置文件

    VNC Server xff0c VNC Viewer和支持程序由参数控制 xff0c 为大多数用户提供了合适的默认值 您可以通过为参数指定新值来配置程序 xff1a 1 在程序启动之前 2 在启动时在命令行上 3 程序运行时 xff0c
  • arXiv Journal 2021-01-11

    想来想去 xff0c 觉得还是把每次在arXiv上扫过的文章简单记录下来 2021 01 11 hep ph 2 papershep th 2 papershep lat 1 paper hep ph 2 papers Title QCD
  • HJ28 素数伴侣

    描述 题目描述 若两个正整数的和为素数 xff0c 则这两个正整数称之为 素数伴侣 xff0c 如2和5 6和13 xff0c 它们能应用于通信加密 现在密码学会请你设计一个程序 xff0c 从已有的 N xff08 N 为偶数 xff09
  • ZRAM SWAP

    1 ZRAM 1 1 zram的理解 ZRAM xff08 压缩内存 xff09 的意思是说在内存中开辟一块区域压缩数据 就是说假设原来150MB的可用内存现在可以放下180MB的东西 本身不会提高内存容量和运行速度 只是让后台程序更少被系
  • 最简单的神经网络--BP神经网络介绍

    今天从网上看到一篇介绍BP神经网络的文章 xff0c 感觉非常好 xff0c 转载保存 转载地址 xff1a https blog csdn net weixin 40432828 article details 82192709
  • 【没有哪个港口是永远的停留~ 论文解读】SphereFace

    论文 xff1a SphereFace Deep Hypersphere Embedding for Face Recognition 代码 xff1a at https github com wy1iu sphereface 摘要 本文讨
  • 【没有哪个港口是永远的停留~ 论文解读】AM - softmax

    论文 xff1a Additive Margin Softmax for Face Verification 代码 xff1a https github com happynear AMSoftm 相似论文 xff1a CosFace La
  • 串口通信和RS485-第1季第13部分-朱有鹏-专题视频课程

    串口通信和RS485 第1季第13部分 5373人已学习 课程介绍 本课程是 朱有鹏老师单片机完全学习系列课程 第1季第13个课程 xff0c 主要讲解了串行通信UART及其扩展RS485 本课程很重要 xff0c 因为串口通信是我们接触的
  • 每天一分钟玩转golang:基础类型之浮点型(二)

    大家好 xff0c 我是加摩斯 xff0c 觉得文章有帮助的小伙伴 xff0c 记得一键三连哟 xff5e 申明 xff1a 本系列两天更新一篇 xff0c 纯原创 xff0c 转载前请与我沟通 Go使用两种浮点型变量来存储小数 xff0c
  • Linux Deploy踩坑指南之二:开启zram块设备

    参考 xff1a https sleeplessbeastie eu 2021 03 17 how to use compressed ram based block devices 当android设备有相对充足的ram xff0c 就可
  • 一文彻底搞懂webpack devtool

    为什么需要Source Map 首先根据谷歌开发者文档的介绍 xff0c Source Map一般与下列类型的预处理器搭配使用 xff1a 转译器 xff08 Babel xff09 编译器 xff08 TypeScript xff09 M
  • DOCKER默认虚拟网卡IP地址与局域网冲突解决

    一 背景 docker启动时默认会创建一个docker0网桥 xff0c 它在内核层连通了其他的物理或虚拟网卡 xff0c 相当于将所有容器和其主机都放到同一个网络 但是部署在内网中的IP段存在有此网段的IP时 xff0c 会导致冲突 xf
  • 学习Hadoop MapReduce与WordCount例子分析

    MapReduce框架一直围绕着key value这样的数据结构 xff0c 下面以官方自带的WordCount为例子 xff0c 自己分析MapReduce的工作机制 MapReduce可以分为Map和Reduce过程 xff0c 代码实
  • mysql GTID主从复制 跳过复制错误

    在mysqlGTID下 xff0c 使用 span class hljs operator span class hljs keyword SET span span class hljs keyword GLOBAL span SQL S
  • 在Ubuntu/Debian Linux系统上检查已经安装软件包的版本

    如果要在Ubuntu Debian Linux操作系统上检查已经安装软件包的版本 xff08 知道Ubuntu Debian系统上可用的软件包版本 xff09 xff0c 可使用apt apt cache aptitude和apt show
  • Ubuntu16.04网络连接正常但浏览器无网络

    ubuntu xff0c google浏览器突然不能上网了 xff0c 经过一番折腾 xff0c 终于解决了问题 xff0c 在此记录一下 开始如下图 xff1a 然而 ping 一下百度 xff0c 是可以 ping 通的 xff0c 说
  • 读书笔记 --《 java核心技术卷一》

    Java核心技术卷 1 第一部分 xff08 java基本介绍 xff09 java特性 简单性 面向对象 分布式 健壮性 安全性 体系结构中立 编译器生成一个体系结构中立的目标文件格式 xff0c 这是一种编译过的代码 xff0c 只要有