一,java基本程序设计结构:html
1,在网页中运行的 Java 程序称为 applet。 要使用 applet ,须要启用 Java 的 Web 浏览器执行字节码。java
2,jdk安装目录下的 src.zip 文件中包含了全部公共类库的源代码。 要想得到更多的源代码 ( 例如 :编译器 、 虚拟机 、 本地方法以及私有辅助类 ),请访问网站 :http://jdk8.java.net。程序员
3, 浮点数值不适用于没法接受舍入偏差的金融计算中。例如,命令System.out.println(2.0-1.1)将打印出0.8999999999999999,而不是人们想象的0.9。这种舍入偏差的主要缘由是浮点数值采用二进制系统表示,而在二进制系统中没法精确地表示分数1/10。这就好像十进制没法精确地表示分数1/3—样。算法
4, 在Java中,-共有8种基本类型(primitivetype),其中有4种整型【byte 1个字节,short 2个字节,int 4个字节,long 8个字节】、2种浮点类型【float 4个字节,double 8个字节】、1种用于表示Unicode编码的字符单元的字符类型char和1种用于表示真值的boolean类型。基本类型和引用类型都保存在栈中,可是基本类型保存的是实际值,而引用类型保存的是一个对象的内存地址。基本类型是内建在Java语言中的特殊的数据类型,它们不是继承自Object对象,因此int等基本类型不属于Object 【参考1】【参考2:官方教程说明】。日常Object o = (int) 3;不会报错,这是用了自动装箱功能。可是泛型中类型参数不能为基本类型,由于编译器类型擦除时会把泛型类型参数(假设此类型参数没有边界)设置为Object,而Object不能用于存储基本类型的值(没有用自动装箱功能)。shell
4.1,float类型的有效位数(精度)为6~7位。double类型的有效位数为15位。json
5,码点(code point)表示 与一个编码表(如Unicode编码)中的某个字符对应的代码值。在Unicode编码表标准中,码点采用十六进制书写,并加上前缀U+,例如 U+0041 就是拉丁字母 A 的码点。Unicode的码点能够分为17个代码平面(code plane)。第一个代码平面,有时叫第零个代码平面,叫作 基本多语言平面(basic multimultilingual plane),码点从U+0000 到 U+FFFF。其他的16个平面从U+10000 到 U+10FFFF。 第一个平面里包含经典的Unicode代码,其他16个包括一些辅助字符。 UTF-16是Unicode的一种使用方式,UTF即Unicode Transfer Format,即把Unicode转换为某种格式的意思。UTF-16编码采用不一样长度的编码来表示全部的Unicode码点。在Unicode的基本多语言平面中,UTF-16将Unicode中的每一个字符用2个字节16位来表示,一般被称为 代码单元(code unit,又称码元)。而对于其余16个平面中的辅助字符,UTF-16采用一对连续的代码单元进行编码,即用2个(2字节的)码元表示。为了可以区分出某个码元是一个字符的编码(基本多语言平面中的字符,即单16位)仍是一个辅助字符(即双16位)的第一或第二部分,UTF-16编码规定以54开头(110110)的一个码元表示辅助字符的前16位即第一部分,以55开头(110111)的一个码元表示辅助字符的后16位,即第二部分。其余开头的码元则是单16位的表示字符的码元。因为第零平面的字符有0x0000-0xffff共65536个字符,恰好能够用16位表示完,如此确定也有以54开头的单16位编码。实际上,Unicode为了配合UTF-16规定了 以54开头的区间(即110110 开头的16位区间,范围从D800-DBFF,共1024个字符位置),和以55开头的区间(范围从DC00~DFFF共1024个字符位置)不容许分配任何字符。因此实际上Unicode第零平面表示的字符共65536-2048 个。参考文章:https://blog.csdn.net/wusj3/article/details/88641084。 Java中的char类型描述了UTF-16编码中的一个码元,一个码点可能包含一个码元也可能包含2个码元(例如: 𝕆 ,𠠦)。数组
5.1, Unicode字符编码表其实和计算机没有任何关系,它只是给每一个字符一个数字编号。如何在计算机中存储这些数字才是计算机的事情。有好多种实现方式,utf-8,utf-16等。其中,在Unicode的第零个平面中的字符(65536-2048个字符)其正常的二进制编码 和 这些字符使用 utf-16 编码后的结果是同样的。浏览器
6,const是Java保留的关键字,但目前并无使用。在Java中,必须使用final定义常量。缓存
7,整数被0除将会产生一个异常,而浮点数被0除将会获得无穷大或NaN结果。安全
8,在默认状况下,虚拟机设计者容许对中间计算结果采用扩展的精度。可是,对于使用 strictfp 关键字标记的方法必须使用严格的浮点计算(即中间结果要进行截断)。
9,在Math类中,为了达到最快的性能,全部的方法都使用计算机浮点单元中的例程..若是获得一个彻底可预测的结果比运行速度更重要的话,那么就应该使用StrictMath类,,它使用“自由发布的Math 库”(fdlibm)实现算法,以确保在全部平台上获得相同的结果
10,基本类型之间的转换:如图,
一,
int n=123456789; float f = n; // f=1.23456792E8。 float类型的精度是6-7位。123456789包含的位数比float的精度要多,因此会损失必定的精度。 二, 两个基本类型的数值进行二元运算时,java编译器会先将两个操做数转换为同一种类型,而后再进行计算。 若是有一个操做数为double,另外一个也会被转换为double, 不然,若是有一个为float,另外一个也会被转换为float, 不然,若是有一个为long,另外一个也会被转换为long. 不然,两个操做数都会被转换为int。 精度小于int类型的数值运算会被自动转换为int类型而后再运算。如, 两个short类型的数值进行运算时,会首先将short类型转换为int。因此,以下代码编译会报错: short s1 = 1; short s2 = 1; s1 = s1 + s2;// 报错:没法将int类型赋值给short类型! 必须使用强制类型转换(cast): s1 = (short) (s1 + s2); 可是 s1 += s2;不会报错,由于 += 运算符在运算后(s1+s2),若是获得的值的类型与左侧操做数(s1)的类型不一样,就会发生强制类型转换:
即s1+=s2最终其实是:s1 = (short) (s1+s2)。 三, 在必要的时候,int类型的值会自动的转换为double类型。有时候须要将double类型转为int类型(这种转换会损失精度),在Java中这种操做不会自动进行,
须要经过强制类型转换(cast)实现这个操做。如:double x = 9.997; int nx = (int) x;//nx = 9; int nx = (int) Math.round(x);// nx = 10;
11,Java没有内置的字符串类型,而是在标准Java类库中提供了一个预约义类,很天然地叫作String。每一个用双引号括起来的字符串都是String类的一个实例。因为不能修改Java字符串中的字符,因此在Java文档中将String类对象称为不可变字符串.不可变字符串却有一个优势:编译器可让字符串共享。为了弄清具体的工做方式,能够想象将各类字符串存放在公共的存储池中。字符串变量指向存储池中相应的位置。若是复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。
12, Java中比较字符串是否相等不能使用 ==。由于这个运算符只能肯定两个字符串是否放在同一个位置(这句话的含义实际是 == 比较字符串不只比较字面是否相同,还比较两个字符串的内存地址是否相同!)。若是java虚拟机始终将相同的字符串共享放在同一个内存地址中,那么就能够使用 == 检测字符串是否相等。可是实际上,只有字符串常量是共享的(即放在同一块内存中),而使用 + 或 substring等操做产生的结果并非共享的。因此千万不能使用 == 比较字符串是否相等。例如,String s = "Hello"; s.substring(0,2) == "He" 是错误的,二者并不 ==,可是倒是equals的。
12, String API
1,nothing to note
13,数组: 一旦建立了数组 ,就不能再改变它的大小( 尽管能够改变每个数组元素 )。
一,
- int[] arr = new int[10] ; arr[0] = 3;arr[1]=4;
- int[] arr = {1,2,3,4};
- int[] arr = new int[] {1,2,3,4}
二,
- String arrStr = Arrays.toString(arr);
- int[] arr1 = arr; // 两个变量引用同一个数组,一个改变会影响另外一个
- int[] arrCopy = Arrays.copyOf(arr, arr.length * 2); // 只拷贝值。若是数组元素是数值型,那么多余的元素将被赋值为0;若是数组元素是布尔型,则将赋值为false。相反,若是长度小于原始数组的长度,则只拷贝最前面的数据元素。
- int[] arrCopy = Arrays.copyOfRange(arr, startIndex, endIndex); // [start, end)
- void Arrays.sort(arr);
- boolean Arrays.equals(arr1, arr2);
- //若是两个数组长度相同,而且在对应的位置上数据元素也均相同,将返回true。数组的元素类型能够是Object、int、long、short、char、byte、boolean、float或double
- int[][] arrArr = new int[2][3];// {{1,2},{2,3}}
- String arrArrStr = Arrays.deepToString(arrArr);
三,
第四章,对象与类:
※,如下都以以下2个类为例子:Employee, Manager
class Employee
{
private String name; private double salary; private LocalDate hireDay; public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; hireDay = LocalDate.of(year, month, day); } public String getName() { return name; } public double getSalary() { return salary; } public LocalDate getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } }
class Manager extends Employee
{
private double bonus; public Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } }
14,能够显式地将对象变量设置为 null, 代表这个对象变量目前没有引用任何对象 。全部的Java对象都存储在堆中。当一个对象包含另外一个对象变量时,这个变量依然包含着指向另外一个堆对象的指针。
15,注意,在这个示例程序中包含两个类:Employee类和带有public访问修饰符的EmployeeTest类。EmployeeTest类包含了main方法。源文件名是EmployeeTest.java,这是由于文件名必须与public类的名字相匹配。在一个源文件中,只能有一个公有类,但能够有任意数目的非公有类。接下来,当编译这段源代码的时候,编译器将在目录下建立两个类文件:EmployeeTest.class和Employee.class将程序中包含main方法的类名提供给字节码解释器,以便启动这个程序:javaEmployeeTest字节码解释器开始运行EmployeeTest类的main方法中的代码。
16,多个源文件的使用
一个源文件能够包含了两个类。许多程序员习惯于将每个类存在一个单独的源文件中。例如,将Employee类存放在文件Employee.java中,将EmployeeTest类存放在文件EmployeeTest.java中。
若是喜欢这样组织文件,将能够有两种编译源程序的方法。一种是使用通配符调用Java编译器:
javac Employee*.java
因而,全部与通配符匹配的源文件都将被编译成类文件。或者键人下列命令:
javac EmployeeTest.java
读者可能会感到惊讶,使用第二种方式,并无显式地编译Employee.java,然而,当Java编译器发现EmployeeTest.java使用了Employee类时会查找名为Employee.class的文件。若是没有找到这个文件,就会自动地搜索Employee.java,而后,对它进行编译。更重要的是:若是Employee.java版本较已有的Employee.class文件版本新,Java编译器就会自动地从新编译这个文件。
17,p127: getter访问器方法注意不要返回 “可变对象”。由于对这个对象调用更改器方法会改变对象的私有状态,这是咱们不想要的。若是须要返回一个可变对象的引用,应该首先对它进行克隆,
18,p129: final 实例域。final关键字通常用于基本类型的域(即类的字段或称属性),或不可变类的域(若是类中的每一个方法都不会改变其对象,这种类就是不可变的类。例如,String类就是一个不可变的类)。final通常不用于可变的类,容易引发读者的理解混乱,例如:
private final StringBuilder evaluations ;
在 Employee 构造器中会初始化为
this.evaluations = new StringBuilder() ; final关键字只是表示存储在 evaluations 变量中的对象引用不会再指示其余 StringBuilder对象。不过这个对象能够更改: public void giveGoldStar() { evaluations . append ( LocalDate . now ( ) + " : Gold star ! \ n " ) ; }
19,静态域与静态方法:
※,静态域、静态方法属于类不属于对象(或称为实例),因此静态方法中不可调用实例域,也不可调用实例方法。可是反过来,实例(或实例方法)能够调用静态域,也能够调用静态方法,可是不提倡,见下条。
※,能够使用对象调用静态方法。例如,若是harry是一个Employee对象,能够用harry.getNextId()代替Employee.getNextId()。不过,这种方式很容易形成混淆,其缘由是getNextld方法计算的结果与harry毫无关系。咱们建议使用类名,而不是对象来调用静态方法。
※,在下面两种状况使用静态方法:
※,若是查看一下System类,就会发现有一个setOut方法,它能够将System.out设置为不一样的流。读者可能会感到奇怪,为何这个方法能够修改final变量的值。缘由在于,setOut方法是一个本地方法,而不是用Java语言实现的。本地方法能够绕过Java语言的存取控制机制。这是一种特殊的方法,在本身编写程序时,不该该这样处理。
System.setOut(new PrintStream(new File("xxxx\\a.txt")));
System.out.println("hello out");//往文件中打印了hello out
※,术语“static”有一段不寻常的历史。起初,C引入关键字static是为了表示退出一个块后依然存在的局部变量在这种状况下,术语“static”是有意义的:变量一直存在,当再次进入该块时仍然存在。随后,static在C中有了第二种含义,表示不能被其余文件访问的全局变量和函数。为了不引入一个新的关键字,关键字static被重用了。最后,C++第三次重用了这个关键字,与前面赋予的含义彻底不同,这里将其解释为:属于类且不属于类对象的变量和函数。这个含义与Java相同。
※,工厂方法:
20,方法参数:按值调用 和 按引用调用。
※,Java 程序设计语言老是采用按值调用。有些程序员(甚至本书的做者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的(P137)。Java方法能够改变对象参数的状态,但这种改变的原理并非引用传递,而是形参获得的是对象引用(即实参是对象的引用)的拷贝,对象引用及它的拷贝同时引用同一个对象。具体参考书中叙述图解。
※,(C语言资料,这段话对理解Java对对象的按值调用颇有帮助!)形参至关因而实参的“别名”,对形参的操做其实就是对实参的操做,在引用传递过程当中,被调函数的形式参数虽然也做为局部变量在栈中开辟了内存空间,可是这时存放的是由主调函数(本身理解:main函数)放进来的实参变量的地址。被调函数对形参的任何操做都被处理成间接寻址,即经过栈中存放的地址访问主调函数中的实参变量。正由于如此,被调函数对形参作的任何操做都影响了主调函数中的实参变量。
※,下面总结一下Java中方法参数的使用状况:
21,对象构造器
※,方法名和方法参数类型 在一块儿叫作方法签名。方法返回类型不是方法签名的一部分。方法重载(英文名实际上叫超载,是类的能力,超载的能力)须要方法的签名不一样。
※,域(即类的属性)与局部变量的主要不一样点:必须明确地初始化方法中的局部变量。可是,若是没有初始化类中的域,将会被自动初始化为默认值(数值为0、布尔值为false、对象引用为null,如String类型默认为null)。
※,若是类中没有任何一个构造器,那么系统会提供一个无参数构造器,这个构造器将全部的实例域设置为默认值。因而,实例域中的数值型数据设置为0、布尔型数据设置为false、全部对象变量将设置为null。注意只有类中没有任何构造器时系统才会提供一个默认的无参数构造器,若是类中至少有一个构造器,可是没有提供无参数构造器,则在构造对象时没有提供参数会被视为不合法。
※,显式域初始化:能够经过不一样重载的构造器设置类的实例域的初始状态。当一个类的全部构造器都但愿把相同的值赋予某个特定的实例域时,能够直接在类声明中将初始值赋给域。
域的初始值不必定是常量,例如:
class Employee {
private static int nextId = 1; private int id = assignId();// 初始化对象时执行 Employee e = new Employee() private String name; Employee (String name) { this.name = name; } public static int assignId() { int r = nextId; nextId++; return r; } }
※,调用另外一个构造器:类中的this指代类方法的隐式参数,java类中,this能够省略,但最好带上。this关键字还有另一个含义,即调用另外一个构造器。例如,
publicEmployee(doubles)
{
//calls Employee(String, double)
this("Employee#" + nextld, s);// 形如这样,表示调用另外一个构造器
nextld++; }
※,初始化块 ☆
1, 前面讲了java两种初始化数据域的方法:①在构造器中设置值。②在声明中赋值。实际上java还支持第三种机制:初始化块。初始化块中能够有多行代码,只要构造类的对象,这些块就会被执行。
2,初始化数据域的顺序:
- 全部数据域被初始化为默认值(0、false或null)。
- 按照在类声明中出现的次序,依次执行全部域初始化语句和初始化块。
- 执行构造器方法。
3,若是静态域的初始化代码比较复杂也能够使用静态初始化块。只要在代码放在一个块中,并标记关键词static便可。在类第一次加载时,全部的静态初始化语句以及静态初始化块都将依照类定义的顺序执行。
如将静态域nextId起始值赋予一个10000之内的随机整数:
static { Random generator = new Random(); nextId = generator.nextInt(10000); }
4,
※,对象析构与finalize方法: 因为Java有自动的垃圾回收器,不须要人工回收内存,因此Java不支持析构器。
22,包
※,java.lang包是被默认导入的。
※,全部标准的java包都处于java 和 javax 包层次中。
※,从编译器的角度来看,嵌套的包之间没有任何关系。例如,java.util包与java.util.jar包毫无关系。每个都拥有独立的类集合。
※,修饰符:public,package-private(即没有任何修饰符时的默认值),protected,private
修饰词 | 本类 | 同一个包的类 | 继承类 | 其余类 |
private | √ | × | × | × |
无(默认) | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
※,静态导入:import语句不只能够导入类,还增长了导入静态方法和静态域的功能。
import static java.lang.System.*; out.println("fuck U");//
※,若是没有在源文件中放置package语句,这个源文件中的类就被放置在一个默认包(default package中),默认包是一个没有名字的包。类的路径必须和包名一致。
javac ./com/learn/java/Test.java //编译器命令能够在任何目录下执行,只要能找到源代码文件(编译后的class文件叫类文件,java为后缀名的文件叫源文件)便可。
java com.learn.java.Test // 解释器命令必须在基目录下执行,即包含子目录com的目录。
※,若是没有指定public或private, 这个部分(类、方法或变量(域))能够被同一个包中的全部方法访问。
※,类路径(class path): 类路径就是全部包含类文件(即编译后的class文件)的路径的集合,即告诉编译器和虚拟机去哪儿找类文件。
※,JAR(Java Archive)文件:
Manifest-Version: 1.0
Name: Woozle.class Name: com/mycompany/mypkg/
※, 文档注释:JDK中包含一个颇有用的工具叫javadoc。Java的API文档就是经过对标准Java类库的源代码运行javadoc生成的。
※,类设计技巧
private String street;
private String city;
private String state;
※,
※,
第五章,继承(inheritance)
※,本章还阐述了反射(reflection)的概念。反射是指在程序运行期间发现更多的类及其属性的能力。这是一个功能强大的特性,使用起来也比较复杂。因为主要是开发软件工具的人员,而不是编写应用程序 的人员对这项功能感兴趣,所以对于这部份内容,能够先浏览一下,待往后再返回来学习。
※,关键字extends代表正在构造的新类派生于一个已存在的类。已存在的类称为超类(super class)、基类(base class)或父类(parent class);新类称为子类(sub class)、派生类(derived class)或孩子类(child class)。
※,Java是单一继承,即只容许继承一个类。
※,Java子类继承父类时,并无继承父类的私有方法。可是若是父类的公有方法或protected方法访问了父类的私有属性,那么子类对象也能够访问到父类的这些私有属性。官方文档说明以下:A subclass does not inherit the private
members of its parent class. However, if the superclass has public or protected methods for accessing its private fields, these can also be used by the subclass.【点我Java官方文档】。
※,方法重写(Override) 【点我查看官方文档】
class Super { private void get() {} } class Sub extends Super { /** * 子类没有继承父类的私有get()方法 * 所以能够签名相同可是返回值类型不一样 */ public String get() { return "到头来都是为他人做嫁衣裳"; } }
※,子类中使用super关键词调用超类的方法。有些人认为super和this引用是相似的概念,这是错误的。super不是一个对象的引用,不能将super赋给另外一个对象变量(而this是对象引用,能够赋值给另外一个变量)。super 只是一个指示编译器调用超类方法的的特殊关键字。
※,子类构造器:
※,多态(polymorphism):此书中多态是在继承章节中的一小节,多态是继承致使的,继承是多态的前提。
0,两个简单例子:
1, Parent p = new Child(); // 当用父类引用来接收一个子类类型的对象时,对象变量p被编译器视为是Parent类型的,可是调用父子类中都有的方法p.getName()时(注意此时若是Parent类中若是没有getName()方法,编译器会报错)
,实际调用的是子类Child中的getName方法。 p实际指向的是子类对象的引用。 2, Parent[] parents = new Parent[3] Child c = new Child(); parents[0] = c; parent[1] = new Parent(); parent[2] = new Parent(); for (Parent e : parents) { System.out.println(e.getName()); } // 尽管这里将e声明为Parent类型,但实际上e既能够引用Parent类型的对象,也能够引用Child类型的对象。 // 当e引用Parent对象时,e.getName()调用的是Parent类中的getName方法,当e引用Child对象时,e.getName()调用的是Child对象的方法。
像这种在运行时可以自动地选择调用哪一个方法的现象称为动态绑定(dynamic binding)1,一个对象变量能够指示多种实际类型的现象叫作多态。
2,多态存在的三个条件:
- 继承
- 重写(方法覆盖)
- 父类引用指向子类对象
3,当使用多态方式调用方法时,首先检查父类中是否含有该方法,若是没有则编译器报错;若是有再去调用子类的同签名方法(具体叙述见下)。
3.1, 注意:父类引用指向子类的对象时,父类引用只能调用父类已有的方法。若是子类没有重写父类的方法,就不存在多态,由于调用的仍是父类的(实际上,根据下面动态绑定的论述,本身推断以下:父类引用在动态绑定阶段,查看的是子类对象的方法表,没有重写的状况下就使用子类继承来的同签名方法。)。所谓的多态就是须要对同一个方法的调用产生不一样的状态,不重写也就没有多态(但也不会报错)。
4,静态绑定和动态绑定:(Java编译器将源码编译成class文件供Java虚拟机执行)
- 静态绑定(前期绑定)是指在程序运行前就已经知道方法是属于哪一个类的,在编译时就能够链接到类中,定位到这个方法。在Java中,final,static, private修饰的方法以及构造函数都是静态绑定的,不需程序运行,不需具体的实例对象就能够知道这个方法的具体内容。
- 动态绑定(后期绑定)是指在程序运行过程当中根据具体的实例对象才能具体肯定是哪一个方法。动态绑定是多态得以实现的重要因素。动态绑定经过方法表来实现:虚拟机预先为每一个类建立一个方法表(method table),在真正调用方法的时候虚拟机仅查找这个表就好了。方法表中记录了这个类中定义的方法的指针,每一个表项指向一个具体的方法代码。若是这个类重写了父类中的某个方法,则对应的表项指向新的代码实现处。从父类继承来的方法位于子类定义的方法的前面。
5,向上转型(upcasting) 和 向下转型(downcasting)
向上转型:通俗的讲向上转型就是将子类对象转为父类对象,此处父类对象能够为接口。向上转型不须要强制转换。
- 向下转型:将父类对象转为子类对象叫作向下转型。向下转型须要强制转换。且有可能出现编译经过但运行时错误的向下转型。
Parent p = new Child(); // 这个就叫作向上转型。无需强制转换。 Child c = (Child) p;// 这个就叫作向下转型。须要强制转换。此时编译和运行都不会报错,由于p实际指向是一个子类对象。
System.out.println(p instanceof Child);// true System.out.println(p instanceof Parent);// true Parent p1 = new Parent(); Child c1 = (Child) p1;// 这里编译器不会报错,可是运行时会报错(ClassCastException),由于p1实际指向的是父类的对象。 System.out.println(p1 instanceof Child);// false System.out.println(p1 instanceof Parent);// true
应该养成这样一个良好的程序设计习惯:在将超类转换成子类以前,应该使用 instanceof 进行检查是否可以转换成功。
null instanceof C ;// 始终为false。由于null没有引用任何对象,固然也不会是引用C类型的对象。- 向上转型的一个好处就是能够使代码变得简洁。好比:
Animal类有3个子类:Cat, Dog, Bird。每一个子类都重写了Animal类的 bark()方法,每种动物的叫声都不同。
现有一个Test类,其中有个方法是getBark(Animal animal)。此时参数只要是父类的Animal便可,
Test的实例调用getBark()方法时能够传入不一样的动物,如getBark(cat)等等,此方法能够根据传入的不一样的动物类型发出正确的叫声。
若是没有向上转型,那么getBark()这个方法就须要写多个,有几个子类动物就须要写几个。6,动态绑定的编译、运行原理:
- 编译阶段:向上转型时是用父类引用执行子类对象,并能够用父类引用调用子类中重写了的同签名方法。可是不能调用子类中有但父类中没有的方法。缘由在于在代码的编译阶段,编译器经过声明的对象的类型(即引用自己的类型)在方法区该类型的方法表中查找匹配的方法(最佳匹配法:参数类型最接近的被调用,好比int能够转成double),若是查找到了则编译经过。向上转型时,父类引用的类型是父类,因此编译器在父类的方法表中查找匹配的方法,因此子类中新增的方法是查不到的,若是查不到编译器就会报错。
- 运行阶段:当Parent p = new Child(); p.say();语句编译经过后,进入Java虚拟机执行阶段,执行Parent p = new Child()语句时,建立了一个Child实例对象,而后在p.say()调用方法时,JVM会把刚才建立的Child对象压入操做数栈,用它来进行调用,这个过程就是动态绑定:即用实例对象所属的类型去查找它的方法表,找到匹配的方法进行调用。子类的方法表包含从父类继承来的方法以及本身新增的方法,若是p.say()在子类中被重写了,那么JVM就会调用子类中的这个方法, 若是没有被重写,那么JVM就会调用父类的这个方法。以此类推。
7,子类覆盖父类的方法(即方法重写)时,子类方法不能下降父类方法的可见性。特别是,当父类方法是public时,子类方法必定要是public。
※,阻止继承:final类和方法
※,强制类型转换:
※,抽象类:
假设,Person是抽象类,Student和Employee是Person的非抽象子类。
1,Person p = new Student("晴雯"); 2, Person[] people = new Person[2]; people[0] = new Student("黛玉"); people[1] = new Employee("秦可卿");
※※※,Object 类: 全部类的超类
一,概述
二,equals()方法:
※,Object 类中的 equals()方法仅在两个对象具备相同的引用(即两个对象指向同一块存储区域)时才返回true,这是Object类的默认操做。可是对于多数类来说,这种判断并无什么意义。实际上,常常须要检测两个对象状态的相等性,若是两个对象状态相等(即某些域相等)就认为这两个对象是相等的。因此常常须要重写这个方法,以实现对象状态相等(即某些域相等)就能够返回true。
※,假设两个 Employee 对象,若是对象的姓名,薪水,和雇用日期都是相同的,就认为他们是相等的。如下为Employee类重写的equals()方法:
public boolean equals(Object otherObject) {
// 两个对象引用是否指向同一个对象,即彻底相同
if (this == otherObject) return true; if (null == otherObject) return false; // this.getClass(): 获取类的名称。这里用getClass()判断实际有点争议,见下文 if (getClass() != otherObject.getClass()) return false; // 如今otherObject确定是个非null的 Employee对象 Employee other = (Employee) otherObject; /* * 为防备name 或 hireDay可能为null的状况,这里使用了Objects.equals()方法而不是直接用name.equals(other.name). */ return Objects.equals(name, other.name)&& salary == other.salary && Objects.equals(hireDay, other.hireDay); }
※,在定义子类的equals()方法时,首先要调用超类的equals()方法,若是检测失败,对象就不能相等。若是超类中的域到相等,就须要比较子类中的实例域。
如下为Employee的子类Manager类重写的equal()方法
public boolean equals(Object otherObject) {
if (!super.equals(otherObject)) return false; // 经过了超类的检测,说明otherObject 和 this 属于同一个类:Manager Manager other = (Manager) otherObject; return bonus == other.bonus; }
※,继承中涉及到的equals()方法:
1,上面重写的equals()方法使用了对象的getClass()方法比较两个对象是否属于同一个类来做为是否equals的一个条件,这个条件有些争议。以下论述:
2,反过来说,若是使用 instanceof 做为判断两个对象equals的条件,也有不合适的地方。以下论述:
Java语言规范要求equals()方法必须具备以下特性:
假设e为Employee的一个对象,m为Manager的一个对象,若是使用instanceof做为判断两个对象equals的条件,根据上面的对称性规则,若是e.equasl(m)为true,那么m.equals(e)也必须返回true。这就使得Manager类受到了束缚:这个类的equals方法必须可以用本身与任何一个Employee对象进行比较,这样就会忽略Manager对象特有的信息。这样就会致使两个Manager对象没法比较是不是equals的, 只要m1和 m2 的name, salary, hireDay一致,那么两个对象就是equals的, 没法比较两个Manager对象的bonus是否相等。解决方法见下。
3,关因而使用getClass()方法仍是使用 instanceof 操做符来做为判断两个对象是否equals的条件,能够从如下两个角度看待:
※,关于equals()方法的一种常见的错误:
public boolean equals(Employee otherObject) {
// 两个对象引用是否指向同一个对象,即彻底相同
if (this == otherObject) return true; if (null == otherObject) return false; // this.getClass(): 获取类的名称 if (getClass() != otherObject.getClass()) return false; // 如今otherObject确定是个非null的 Employee对象 Employee other = (Employee) otherObject; /* * 为防备name 或 hireDay可能为null的状况,这里应该使用Objects.equals()方法优化一下 * Objects.equals(name, other.name); Objects.equals(hireDay, other.hireDay); */ return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay); }
// 错误的地方在于equals()方法的参数类型是Employee。其结果是这个equals()方法并无覆盖Object类的equals()方法,而是定义了一个彻底无关的方法。为了不发生类型错误,
能够使用@Override(比...更重要)对覆盖超类的方法进行标记。若是出现了错误而且正在定义一个新的方法,编译器就会给出错误报告。
※,java.util.Objects.equals(a, b)方法: 这个方法对null是安全的, 若是两个参数都为null,Objects.equals(a,b)调用将返回true;若是其中一个参数为null,则返回false;不然,
若是两个参数都不为null,则调用a.equals(b)
※,java.util.Arrays.equals(arr1, arr2): 对于两个数组类型,能够使用Arrays.equals()检测两个数组是否相等。若是两个数组以相同的顺序包含相同的元素,则他们是相等的,不然就是不相等的。
三,hashCode()方法:
※,int java.lang.Object.hashCode(): 返回对象的散列码。散列码能够是任意的整数,能够整数也能够负数。两个equals相等的对象要求返回相等的散列码。
※,散列码(hash code)是由对象导出的一个整形值。散列码是没有规律的,若是x和y是两个不一样的对象,那么x.hashCode()和y.hashCode()基本不会同样。
※,hashCode()方法定义在Object类中,所以每一个对象都有一个默认的散列码,这个散列码值是由对象的内存存储地址推导出来的。
※,若是从新定义equals()方法,就必须从新定义hashCode()方法,以便用户能够将对象插入到散列表中(散列表后面讲述).
※,hashCode()方法的定义:
@Override
public int hashCode() { /** * static int java.util.Objects.hashCode()方法是null安全的方法,若是参数为null,返回0. * 不然对参数调用hashCode()方法。好比name.hashCode(); hireDay.hashCode(); * 使用静态方法 static int java.lang.Double.hashCode()能够避免创造Double对象:new Double(salary).hashCode(); */ return 7 * Objects.hashCode(name) + 11 * Double.hashCode(salary) + 13 * Objects.hashCode(hireDay); }
@Override
public int hashCode() {
// static int java.util.Objects.hash(Object... objects) return Objects.hash(name, salary, hireDay); }
※,equals()方法与hashCode()方法的定义必须一致:若是x.equals(y)方法返回true,那么x.hashCode()就必须和y.hashCode()具备相同的值。不然就会出现问题(具体什么问题待研究)。也就是说,equals相等的两个对象的hashCode也要保证相等。可是反过来两个hashCode相等的对象不必定须要equals相等(这个好理解:假设两个不一样的类有相同的属性和hashCode规则,那么hashCode相等而不equals相等)。好比,若是用定义的Employee.equals()比较雇员的ID,那么hashCode()方法就须要散列ID而不是雇员的姓名或继承自Object的存储地址推导出来。
四:toString()方法:
※,Object类中还有一个很重要的方法toString(),它用于返回表示对象值的字符串。Object类默认的toString()方法返回值是:对象所属的类名和散列码。以下例
※,绝大多数重写了toString()方法的类都遵循这样的规则:类的名字,随后是一对方括号括起来的域值。
/**
* 不直接写Employee而是使用this.getClass().getName()获取类名更具普适性。
* 好比继承了此类的子类实现本身的toString()方法时能够直接调用super.toString()方法就能够获得子类本身的类名
*/
@Override
public String toString() { return this.getClass().getName() + "[name=" + this.name + ",salary=" + this.salary +",hireDay=" + this.hireDay +"]"; }
@Override
public String toString() { return super.toString() +"[bonus=" + this.bonus +"]"; }
※,在调用x.toString()方法的地方能够使用【""+x】替代,此时编译器会自动调用x.toString()。这种写法的好处是:若是x是基本类型,基本类型却没有toString()方法,这条语句照样能够执行。
※,API
24,泛型数组列表(Generic Array List)
※,出现数组列表ArrayList的背景
※,使用ArrayList类: java.util.ArrayList<E> SE1.2
※,动态改变大小的原理
分配数组列表,以下所示
new ArrayList<Employee>(100) // capacity is 100,but size now is 0.
它与为新数组分配空间有所不一样: new Employee[100] // size is 100 数组列表的容量与数组的大小有一个很是重要的区别。若是为数组分配100个元素的存储空间,数组就有100个空位置能够使用。而容量为100个元素的数组列表只是 拥有保存100个元素的潜力(实际上从新分配空间的话,将会超过100),可是在最初,甚至是完成初始化构造以后,数组列表根本就不含有任何元素。
---------------------------------------------------------------------------------------------------------------------------
Employee[] staff = new Employee[2];
System.out.println(Arrays.toString(staff));// 打印:[null, null]
System.out.println(staff.length);// 打印:2
----------
ArrayList<Employee> staff = new ArrayList<>(2);
System.out.println(staff);// 打印:[]
System.out.println(staff.size());//打印:0
※,访问ArrayList元素
ArrayList<Employee> employees = new ArrayList<>(100);//容量100,可是大小此时仍是0 employees.set(0, new Employee());// employees中还没有含有第0个元素,编译经过但运行时报错。
ArrayList<Employee> list = new ArrayList<>();
for (int i = 0; i < max; i++) { x = ... list.add(x) } Employee[] staff = new Employee[list.size()]; list.toArray(staff);//list中的元素从前日后依次添加到staff中。 /** * list.toArray()方法详解: * 1,此方法始终有返回值:Object[],即将list中的元素从前日后依次添加至返回值arr中 * 2,若是数组参数staff的大小与list的大小相同,那么除了添加至返回值arr中以外,list中的元素也会依次复制到staff中 * 3,若是staff的大小大于list的大小,arr和staff同样,除了list中的元素外,多余的元素用null填充 * 4,若是staff的大小小于list的大小,那么staff将保持不变,不会被填充。arr则会正常填充list中的全部元素。这种状况和 * 直接调用不带参数的toArray()效果相同。 * Object[] orr = list.toArray();效果等同于Object[] orr2 = list.toArray(new X[0]) */ Object[] arr = list.toArray(type[]
※,插入或删除ArrayList元素
25,对象包装器与自动装箱(Object Wrappers and AutoBoxing)
※,有时须要将基本类型转换为对象。全部的基本类型都有一个与之对应的类。这些类称为包装器(wrapper)。这些对象包装器类拥有很明显的名字:Byte, Short, Integer, Long, Float, Double(这六个类派生自公共的超类Number), Character, Void和Boolean。注意:对象包装器类是不可变的,即一旦构造了包装器,就不容许更改包装在其中的值。同时对象包装器类仍是final的,所以不能定义他们的子类。
※,数组列表ArrayList的泛型参数是不容许为基本类型的,即ArrayList<int> al = new ArrayList<>();是不合法的。此时就须要用到包装器类Integer。注意:因为每一个值分别包装在对象中,因此ArrayList<Integer>的效率远低于int[]数组。所以应该用它构造小型集合,其缘由是此时程序员操做的方便性要比执行效率更加剧要
※,自动装箱(autoboxing)与自动拆箱(unbox): ArrayList<Integer> list = new ArrayList<>();
※,关于包装器类的几个注意事项:
// 注意调用triple(n)方法并不能将n变成3倍。由于包装器类Integer是不可变的!
public static void triple(Integer x) {
x = x * 3; }
※,装箱和拆箱操做是编译器的行为,而不是Javad虚拟机的行为。编译器在生成类的字节码时插入必要的方法调用。虚拟机只是执行这些字节码。
※,数值对象包装器的另外一个好处是: Java设计者发现能够将某些基本方法放置在包装器类中,如 int x = Integer.parseInt(string);// parseInt()是一个静态方法,这与Integer对象毫无关系。可是Integer类是放置这个方法的好地方。
※,API---java.lang.Integer 1.0
26,参数数量可变的方法
※,在JavaSE5.0以前,每一个Java方法都是固定参数个数。以后有了变参方法。一个例子就是PrintStream System.out.printf()方法。这个方法能够传入任意个数的参数。这个方法的实现以下:
// ...语法表示这个方法能够接收任意数量的对象。
public PrintStream printf(String format, Object ... args) {
return format(format, args); } 1, 实际上,printf()方法接收2个参数,一个是格式化字符串,另外一个是Object[]数组,这个数组中保存着全部的参数(若是调用者提供的是整形数组或其余基本类型的值,自动装箱功能将把它们转换成对象)。 2,就是说,Object ... 和 Object[]彻底同样。
※,一个自定义的可变参数的方法:参数类型能够任意,甚至能够为基本类型。
//
public static double max(double... values) {
double largest = Double.NEGATIVE_INFINITY; for (double v : values) { f (v > largest) { largest = v; } } return largest; } 1, 调用方式: max(3.1, 4.5, -5); 2, 编译器将new double[] {3.1, 4.5, -5}传递给max方法。也能够直接这么调用max方法(即传入一个数组),可是注意要保持类型的一致。
好比:public static void f(Object...args){System.out.println(Arrays.toString(args));},若是以数组参数形式调用此方法,那么这个数组必须是Object[]才能保证一一对应。
假如传入的是int[]{1,2,4},那么这个int[]数组总体将被当作是Object[]中的一项。即打印出来的是【[[I@15db9742]】,而若是传入的是Object[]{1,2,4}那么打印出来的即是[1,2,4]。
※,在Java中已经存在且最后一个参数是数组的方法能够重定义为可变参数的方法而不会破坏任何已经存在的代码。好比大名鼎鼎的main方法就能够写为:
public static void main(String ... args) {...}
※,
※,
27,枚举类(Enumeration Classes)
※,JDK1.5以后出现了enum类型。能够单独成类,也能够定义在class或interface之中。
※,枚举的用法:
/** * 1,枚举enum是一个特殊的Java类。它继承自java.lang.Enum。枚举类是final类,不能被继承。 * 2,经过 cfr jar包反编译能够发现,其实enum就是一个语法糖。 * 3,RED, GREEN, BLUE(叫作枚举常量)实际上就是枚举类RGB的实例,外部没法再构造出RGB的实例。所以,再比较两个 * 枚举类型的值时,不须要调用equals()方法,直接使用“==”就能够了。 * 4,枚举的构造器只是在构造枚举常量的时候被调用,即RED,GREEN,BLUE各调用一次。因此,枚举常量的 * 形式要和构造函数保持一致。好比RED(1, "红色")须要对应RGB(int a, String b){}形式的构造函数。 * 能够存在多个构造函数,所以也能够存在多种形式的枚举常量,好比:RED(1,"红色"),GREEN, BLUE;枚举的
* 构造器只能用private修饰,若是没有修饰符,默认也是private。 * 5, 枚举常量要放在最前面,枚举的域和方法要放在枚举常量的后面。这个不明白为啥..
* 6, 能够在枚举类中覆盖超类Enum的一些方法,好比@Override public String toString(){return ...;} * */ enum RGB { RED(), GREEN, BLUE; } // 反编译后的代码以下: java -jar ../cfr_0_132.jar RGB.class --sugarenums false /* * Decompiled with CFR 0_132. */ public final class RGB extends Enum<RGB> { public static final /* enum */ RGB RED = new RGB(); public static final /* enum */ RGB GREEN = new RGB(); public static final /* enum */ RGB BLUE = new RGB(); private static final /* synthetic */ RGB[] $VALUES; public static RGB[] values() { return (RGB[]) $VALUES.clone(); } public static RGB valueOf(String string) { return Enum.valueOf(RGB.class, string); } private RGB() { super(string, n); } static { $VALUES = new RGB[] { RED, GREEN, BLUE }; } }
※,枚举类的一些方法:java.lang.Enum<E> 5.0 RGB r = RGB.RED;
Class<E>
getDeclaringClass();//返回枚举常量所在枚举类的类对象。r.getDeclaringClass();// class com.learn.java.RGB
static <T extends Enum<T>> T
valueOf(Class<T> enumClass, String name);// 返回指定枚举常量名指定枚举类型的枚举常量。RGB g = Enum.valueOf(RGB.class, "GREEN");
※,枚举与switch
RGB r = RGB.BLUE;
switch (r) { case RED://这里只能用RED,不能用RGB.RED System.out.println("rgb.red"); break; case BLUE: System.out.println("rgb.blue"); break; default: System.out.println("rgb..."); }
※,
28,反射(Reflection)
※,可以分析类能力的程序称为反射。也就是说,反射的代码研究的是类自己,有点元数据(meta-data)的感受。反射是一种功能强大且复杂的机制 。 使用它的主要人员是工具构造者, 而不是应用程序
员。
※,Class 类:描述类的一个类。
※,获取Class类的对象的三种方法:
Class类其实是泛型类。实际上应该写为Class<Employee> c = Employee.class;
※,API: java.lang.Class 1.0
※,java.lang.reflect包中有三个类Filed, Method, Constructor 分别用于描述类的域、方法和构造器。里面的一些方法有须要后面再研究。
※,
29,继承的设计技巧
※,将公共操做和域放在超类。
※,尽可能不要使用protected 域。
※,除非全部继承的方法都有意义,不然不要使用继承。
※,使用多态而非类型信息。使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展
※, 不要过多的使用反射。反射功能对于编写系统程序来讲极其实用, 可是一般不适于编写应用程序。
第六章: 接口 、 lamda表达式 与内部类
一,接口
1,Java中,接口不是类,而是对类的一组需求描述。
2,一个例子: java.util.Arrays. static void sort(Object[] a) ,能够对数组a中的元素进行排序。前提是数组中的元素必须属于实现了 Comparable 接口的类,而且元素间是可比较的。
// Comparable接口(是个泛型接口)源码以下:
public interface Comparable<T> {
public int compareTo(T o); }
// 语言标准规定: x.compareTo(y)和y.compareTo(x)一定是相反的。若是一个抛异常,另外一个也应该抛异常。因此涉及子类继承时,须要判断是否子类和超类能够比较分两种状况处理。 // 状况1,若是子类和超类没法对比,那么就加上getClass类型判断 class Employee implements Comparable<Employee> { public int compareTo(Employee e) { if (getClass() != e.getClass()) { throw new ClassCastException(); } /** * java.lang.Double/Integer * static void int compareTo(double x, double y) * x < y 返回负值,相等返回0,x > y返回正值 */ return Double.compare(salary, e.getSalary()); } } // 状况2,若是子类和超类能够比较,那么在超类中提供一个compareTo()方法并声明为final class Employee implements Comparable<Employee> { public final compareTo(Employee e) { return Double.compare(salary, e.getSalary()); } }
3,接口中不能含有实例域,由于接口不能实例化。在Java SE 8 以前,也不能在接口中实现方法。可是在Java SE 8中,能够实现静态方法和默认方法了。
/**
* 在Java SE 8中,能够为Path接口增长静态方法,这样一来,Paths实用工具类就再也不是必要的。
* 不过,整个Java库都以这种方式重构不太可能,可是在实现咱们本身的接口时,能够再也不提供一个伴随类了。
*/
public interface Path
{
public static Path of(URI uri) { . . . } public static Path of(String first, String... more) { . . . } . . . }
/**
* Java SE 8中能够为接口方法提供一个默认实现,实现体中能够调用任何其余方法。
* 有了默认方法,实现这个接口的类就能够没必要实现这个方法了,
* 若是没实现这个方法的类的实例对象调用了这个方法就会调用接口中定义的这个默认方法。
*/
public interface Collection
{
int size(); // an abstract method
default boolean isEmpty() { return size() == 0; } . . . }
4,更多例子:
※,接口与回调。
/**
* javax.swing包里的Timer类有一个定时器方法:new Timer(int delay, ActionListener listener)。
* 将对象传递给定时器,要求此对象必须实现java.awt.event包中的ActionListener接口。
*/
ActionListener listener = new TimePrinter(); Timer t = new Timer(1000, listener);//每隔1000ms调用一下listener中的actionPerformed方法。 t.start();
/**
* 在关闭提示框以前,每隔1s执行一下。若是没有下面这句代码,程序注册了事件以后就退出了,因此不会出现预期的间隔性效果。
* 除了下面这种方式外,还能够使用 java.lang. Thread.sleep(10000);阻止程序退出
*/ JOptionPane.showMessageDialog(null, "Quit Program?");class TimePrinter implements ActionListener { @Override public void actionPerformed(ActionEvent e) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(e.getWhen())); // System.out.println("At the tone, the time is " + new Date()); Toolkit.getDefaultToolkit().beep(); } }
javax.swing.JOptionPane 1.2
static void showMessageDialog(Component parent, Object message);// 显示一个包含一条消息和OK按钮的对话框。这个对话框将位于其parent组件的中央。若是parent为mill,对话框将显示在屏幕的中央
java.awt.Toolkit 1.0
※,Comparator 接口:(注意不是 上面的 Comparable 接口,而是 Comparator 接口)
String[] srr = new String[]{"helloWorld", "bcde","abc","fuck", "good", "fuckU"};
System.out.println(Arrays.toString(srr));//[helloWorld, bcde, abc, fuck, good, fuckU]
Comparator<String> lengthComparator = new LengthComparator(); Arrays.sort(srr, lengthComparator); System.out.println(Arrays.toString(srr));//[helloWorld, fuckU, bcde, fuck, good, abc] class LengthComparator implements Comparator<String> { @Override public int compare(String o1, String o2) { /** * 返回1或正数(true)表示按照这个规则须要调换o1和o2的位置,即o2排在o1的前面; * 返回-1或负数(false)表示不须要调换o1和o2的位置,即o1排在o2的前面。 * 0 表示不排序,同-1. */ return o1.length() - o2.length();// 升序 /** * 若是o1.length > o2.length,返回正数,表示须要调整o1,o2,即o2在o1前面,即升序. * 若是o1.length < o2.length,返回负数,表示不准调整o1,o2,即o1在o2前面,即升序. */ } }
※,对象克隆(Cloneable接口):有关克隆的细节技术性很强,克隆没有你想象中那么经常使用。标准库中只有不到5%的类实现了clone()方法。
Employee origin = new Employee();
Employee cloned = origin.clone(); 浅拷贝:即对象origin中若是有其余引用对象,则cloned对象中并无克隆这个内部的引用对象。即origin对象和cloned对象依然共享一些信息。
class Employee implements Cloneable {
@Override
public Employee clone() throws CloneNotSupportedException { return (Employee) super.clone(); } }
/**
* 抛出异常,之因此不捕获这个异常,是由于捕获异常很适合用于final类。
* 若是不是final类最好仍是保留throws符号。这样就容许子类不支持克隆时
* 能够选择抛出一个CloneNotSupportedException异常。
*/
@Override
public Employee clone() throws CloneNotSupportedException { //调用 Object.clone() Employee cloned = (Employee) super.clone(); // 克隆对象中的可变域 cloned.birthDay =(Date) this.birthDay.clone(); return cloned; }
int[] arrA = {2, 3, 5, 7, 11};
int[] cloned = arrA.clone(); System.out.println(Arrays.toString(arrA));//[2, 3, 5, 7, 11] cloned[0] = 33;//不会改变arrA数组 System.out.println(Arrays.toString(arrA));//[2, 3, 5, 7, 11] System.out.println(Arrays.toString(cloned));//[33, 3, 5, 7, 11]
二, lambda表达式:
5,语法格式:
//这是彻底体形式,下面有各类特殊形式
Arrays.sort(srr, (String o1, String o2) -> { return o1.length() - o2.length(); });
Arrays.sort(srr, (o1, o2) -> {
return o1.length() - o2.length(); });
6,函数式接口(Functional Interface)
public interface Predicate<T> {
boolean test(T t);
// additional default and static methods
} ArrayList<Integer> list = new ArrayList<>(); list.removeIf(e -> e == null);//将list中全部为null的元素删除掉。 等价于如下: list.removeIf(new RemoveCond()); class RemoveCond implements Predicate<Integer> { @Override public boolean test(Integer t) { return t == null; } }
7,方法引用(Method Reference)
Timer timer = new Timer(1000, event -> System.out.println(event));
/**
* object::instanceMethod
* 表达式System.out::println是一个方法引用,等价于 event -> System.out.println(event)
*/ Timer timer = new Timer(1000, System.out::println); String[] srr = {"Abc", "abbbbb", "good", "Goaaaa"}; //第二个参数是函数式接口 Comparator<String> 的一个实现,表示不考虑字母大小写对字符串数组排序 Arrays.sort(srr, (x, y) -> x.compareToIgnoreCase(y)); /** * Class.instanceMethod * String::compareToIgnoreCase是一个方法引用,等价于(x, y) -> x.compareToIgnoreCase(y) */ Arrays.sort(srr, String::compareToIgnoreCase); //关于方法引用 用“::”操做符分隔方法名 与 对象或类名。主要有三种状况(方法引用貌似均可以替换为lambda表达式): 1. object::instanceMethod,如System.out::println等价于 x->System.out.println(x); System.out是PrintStream类的一个对象 2. Class::staticMethod,如 Math::pow等价于Math.pow(x, y); 3. Class::instanceMethod,如String::compareToIgnoreCase等价于(x, y) -> x.compareToIgnoreCase(y);第一个参数会成为方法的隐式参数 //另外 1. 能够在方法引用中使用this。this::equals等价于x -> this.equals(x); 2. 也能够在方法引用中使用super。super::instanceMethod会使用this做为隐式参数,调用给定方法的超类版本。
8,构造器引用(Constructor Reference)
/**
* 构造器引用和方法引用很类似,只不过方法名为new。好比Employee::new是Employee的构造器的一个引用。
* 注意:每一个stream流只能用一次,不然会报错:stream has already been operated upon or closed
*/
//如今将一个字符串列表转换为Employee对象数组列表
List<String> names = Arrays.asList("Liverpool", "Kloop", "Chamberlain", "Mane", "Salah", "Firmino");
Stream<Employee> stream = names.stream().map(Employee::new);//map方法会为names中每一个元素调用Employee(String name)构造器
List<Employee> staff = stream.collect(Collectors.toList()); //能够使用数组类型创建构造器引用。例如int[]::new 是一个构造器引用,它有一个参数即数组的长度。等价于lambda表达式:x -> new int[x] Stream<Employee> stream1 = names.stream().map(Employee::new); Object[] staff1 = stream1.toArray(); Stream<Employee> stream2 = names.stream().map(Employee::new); Employee[] staff2 = stream2.toArray(Employee[]::new);
9,lambda表达式的变量做用域
public static void repeatMessage(String text, int delay) {
/**
* 1,下面的lambda表达式由3个部分组成:
* ①参数event
* ②代码块
* ③自由变量,指的是非参数event并且不在lambda代码块中定义的变量。这里便是text变量
*
*,2,表示lambda的数据结构必须存储自由变量的值,咱们说自由变量的值被lambda表达式捕获(captured)了。
* 代码块加上自由变量的值构成了一个闭包。在Java中,lambda表达式就是闭包。
*
*,3,lambda表达式中捕获的变量必须是常量或者其实是常量(final or effectively final)。所谓其实是常量
* 意思是,这个变量初始化以后就不会再为它赋值。因此下面两处给text再赋值的操做编译器都会报错。这个限制是有缘由的:
* 若是在lambda表达式中改变变量,并发执行多个动做时就会不安全。
*
*,4,lambda表达式嵌套在 repeatMessage 方法中,lambda表达式的做用域和嵌套块有相同的做用域。因此下面在嵌套块
* 中定义event变量就会和lambda表达式中的event变量冲突,编译器报错。
*
*,5,由上面4中所述可推知,lambda表达式中若是使用了this关键字,这个this和在嵌套快中的this彻底同样,指的就是
* 实例化的对象。
*/
//5,System.out.println(this.toString());// lambda表达式中如有this关键字,和此处的this含义相同。
//4, String event = "xxx";//lambda表达式的做用域和此处的event有相同的做用域,因此会报变量名已定义的编译错误。
//3 text = "从新赋值";
ActionListener listener = event -> { //3 text = "从新赋值"; System.out.println(text); Toolkit.getDefaultToolkit().beep(); }; new Timer(delay, listener).start(); }
10,经常使用的函数式接口:略
11,再谈Comparator接口:
三,内部类(Inner Class)
内容挺多,暂不记录了。之后再看一遍在记录
※,匿名内部类:
四,Service Loader(动态加载实现接口的全部类): 参见此篇文章
1,用法:只需将全部类文件打包到一个jar文件中,而后在jar文件中的 META-INF文件夹下新建一个services文件夹,services文件夹下新建一个文件,文件名为接口的全名(即带着包名,如com.learn.java.HelloInterface)。文件的内容是实现这个接口的全部类的全名。而后使用ServiceLoader类就能够今后文件中读取到全部实现该接口的类了。详细细节以下。
package com.learn.java; public interface HelloInterface { void sayName(); void sayAge(); }
package tong.huang.shan; import com.learn.java.HelloInterface; public class Dog implements HelloInterface { public static void main(String[] args) { System.out.println(String.format("%s: main方法", Dog.class.getName())); } @Override public void sayName() { System.out.println(String.format("%s: 汪汪", this.getClass())); } @Override public void sayAge() { System.out.println(String.format("%s: 3岁", this.getClass())); } }
package tong.huang.shan; import com.learn.java.HelloInterface; public class Sheep implements HelloInterface { @Override public void sayName() { System.out.println(String.format("%s: 咩咩", this.getClass())); } @Override public void sayAge() { System.out.println(String.format("%s: 10岁了", this.getClass())); } }
package tong.huang.shan; import java.util.ServiceLoader; import com.learn.java.HelloInterface; public class Study { public static void main(String[] args) { /** * 下面这行代码是在静态方法中获取类名的方法。因为getClass()方法须要this来调 * 用,可是静态方法中没有this,因此在静态方法中构造一个匿名内部类 * 【new Object(){},匿名内部类是这个类(此例中即Object)的子类】, * 获取此匿名内部类的class类(class tong.huang.shan.Study$1), * 而后再调用getEnclosingClass()方法获取包围这个内部类的类, * 即此静态方法的class类(class tong.huang.shan.Study)。 */ System.out.println(new Object(){}.getClass().getEnclosingClass() + ":"); ServiceLoader<HelloInterface> serviceLoader = ServiceLoader.load(HelloInterface.class); for (HelloInterface myServiceLoader : serviceLoader) { myServiceLoader.sayAge(); myServiceLoader.sayName(); } } }
tong.huang.shan.Dog
tong.huang.shan.Sheep
2, API
static <S> ServiceLoader<S> load(Class<S> service);//建立一个service loader,这个loader将会加载实现了给定接口的全部类。
3,
五,代理类(Proxy)
1,具体不是很理解,记录几个例子。
2, 利用代理能够在运行时建立一个实现了一组给定接口的新类。这种功能只有在编译时没法肯定须要实现哪一个接口时才有必要使用。
3,建立一个代理对象,须要使用 java.lang.reflect.Proxy 类的newProxyInstance() 方法:
4,一个例子: 使用代理类 和 调用处理器 跟踪方法调用。
package tong.huang.shan; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TraceHandler implements InvocationHandler { private Object target; public TraceHandler(Object t) { target = t; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.print(target); System.out.print("." + method.getName() + "("); if (null != args) { for (int i = 0; i < args.length; i++) { System.out.print(args[i]); if (i < args.length -1) { System.out.print(","); } } } System.out.println(")"); return method.invoke(target, args); } }
package tong.huang.shan; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Date; import java.util.Random; import com.learn.java.HelloInterface; public class Study { public static void main(String[] args) { // Object value = "红楼梦"; // InvocationHandler handler = new TraceHandler(value); // Class[] interfaces = new Class[] {Comparable.class}; // Object proxy = Proxy.newProxyInstance(null, interfaces, handler); // Class pc = Proxy.getProxyClass(null, interfaces); // System.out.println(proxy.getClass()); // System.out.println(value.getClass()); // System.out.println(Proxy.isProxyClass(proxy.getClass())); // System.out.println(Proxy.isProxyClass(value.getClass())); // proxy.equals("平儿"); // value = proxy; // System.out.println(value.equals("史湘云")); Object[] elements = new Object[1000]; for (int i = 0; i < elements.length; i++) { Integer value = i + 1; InvocationHandler handler = new TraceHandler(value); Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler); elements[i] = proxy; } Integer key = new Random().nextInt(elements.length) + 1; // key = 500;int result = Arrays.binarySearch(elements, key);// 这句会调用invoke()方法,打印出二分查找的全过程。 if (result > 0) { System.out.println(elements[result]); } } }
500.compareTo(159) 250.compareTo(159) 125.compareTo(159) 187.compareTo(159) 156.compareTo(159) 171.compareTo(159) 163.compareTo(159) 159.compareTo(159) 159.toString()//虽然toString()方法不属于Comparable接口,可是toString()方法也被代理了,这是由于Object类中的一部分方法都会被代理。
5,代理类的特性
6,API
Object invoke(Object proxy, Method method, Object[] args); //定义了代理对象调用方法时但愿执行的动做
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler);// 构造实现指定接口的代理类的一个新实例。全部方法会调用给定处理器对象的invoke方法。
7,
1,异常分类
2,抛出异常时,不须要抛出从Error继承的错误(Error类也算是异常的一种)。任何程序代码都具备抛出那些异常的潜能,而咱们对其没有任何控制能力。
3,子类抛出的异常不能比超类更通用(即子类抛出的异常要么和超类抛出的异常相同要么是超类抛出异常的子类)。特别是当超类没有抛出异常时,子类也不能抛出任何异常。
4,自定义异常类
class CustomException extends Exception { CustomException() { } CustomException(String msg) { super(msg); } }
5,
1,若是try子句中的某一行代码抛出了异常,那么程序将跳过try块中剩余的其他代码。
2,一个try语句块中能够捕获多个异常类型,连续多个catch。
3,Java SE7中,同一个catch子句中能够捕获多个异常类型,只有当捕获的异常类型彼此之间不存在子类关系时才须要这个特性。例如:
try { ....... } catch (FileNotFoundException | UnknowHostException e) { System.out.println(e.getClass());// 因为一旦抛出一个异常,代码就不继续往下运行了,因此catch时只能catch一个异常,因此用一个变量e。 }
4,finally子句:
/** * 内层的语句块只有一个职责,就是确保关闭输入流。 * 外层的try语句块也只有一个职责,就是确保报告出现的错误。 * 这种设计方式不只清楚,并且还具备一个功能, * 就是将会报告finally子句中出现的错误。 */ InputStream in = . . .; try { try { // code that might throw exceptions } finally { in.close(); } } catch (IOException e) { // show error message }
5, try-with-resources 语句
try (Scanner in = new Scanner(new FileInputStream("C:\\Users\\JoY-33\\Desktop\\1.txt"), "GBK"); PrintWriter out = new PrintWriter("C:\\Users\\JoY-33\\Desktop\\2.txt")) { while (in.hasNextLine()) { out.println(in.nextLine()); } }
6,分析堆栈轨迹元素
Throwable t = new Throwable(); t.printStackTrace(); // 打印结果以下 java.lang.Throwable at tong.huang.shan.Study.main(Study.java:15)
// 第二个例子
Throwable t = new Throwable();
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
String desc = out.toString();
System.out.println(desc);
private static long factorial(int n) { System.out.println("factorial(" + n + ")"); Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for (StackTraceElement frame : frames) { System.out.println(frame); } long r; if (n <= 1) { r = 1L; } else { r = n * factorial(n - 1); } System.out.println("return " + r); return r; } //调用 factorial(3)将打印以下堆栈轨迹: factorial(3) tong.huang.shan.Study.factorial(Study.java:53) tong.huang.shan.Study.main(Study.java:32) factorial(2) tong.huang.shan.Study.factorial(Study.java:53) tong.huang.shan.Study.factorial(Study.java:62) tong.huang.shan.Study.main(Study.java:32) factorial(1) tong.huang.shan.Study.factorial(Study.java:53) tong.huang.shan.Study.factorial(Study.java:62) tong.huang.shan.Study.factorial(Study.java:62) tong.huang.shan.Study.main(Study.java:32) return 1 return 2 return 6
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); for (Thread thread : map.keySet()) { StackTraceElement[] frames = map.get(thread); System.out.println("ThreadName: " + thread.getName()); System.out.println(Arrays.toString(frames)); }
7,API
※,java.lang.Throwable 1.0
※,java.lang.StackTraceElement 1.4
※,java.lang.StackWalker 9
※,java.lang.StackWalker.StackFrame 9
8,
1,能避免异常的尽可能避免异常,由于捕获异常很是耗时。例如用一些判断条件避免异常。例如对一个栈进行退栈操做能够经过判断是否为空规避EmptyStackException。
//使用条件规避异常 if (!s.empty()) { s.pop(); } /**
* 强行退栈并捕获异常很是耗时!!! * 在测试的机器上,调用isEmpty的版本运行时间为646毫秒。 * 捕获EmptyStackException的版本运行时间为21739毫秒。 */ try { s.pop(); } catch (EmptyStackException e) { }
2,尽可能避免for循环内使用try-catch。而应该try整个for循环。
3,早抛出,晚捕获。
4,
0,不一样的IDE中开启断言(或其余jvm选项)的方法
"configurations": [ { "type": "java", "name": "CodeLens (Launch) - Study", "request": "launch", "mainClass": "tong.huang.shan.Study", "projectName": "java-study_adaad70d", "vmArgs": "-ea",//虚拟机参数 },
1,在默认状况下,断言机制是被禁用的。能够在运行程序时经过 -enableassertions 或 -ea 选项开启断言,如:java -ea myApp。在启用或禁用断言时没必要从新编译程序。启用或禁用断言是类加载器(class loader)的功能。当断言被禁用时,类加载器将跳过断言代码。
2,语法格式:断言有两种格式。若是启用断言,这两种格式都会对条件进行检测,若是为false则抛出一个AssertionError异常。
3,说明:
4,API:
java.lang.ClassLoader 1.0
void setDefaultAssertionStatus ( boolean b ) 1.4
对于经过类加载器加载的全部类来讲,若是没有显式地说明类或包的断言状态,就启用或禁用断言。
void setCIassAssertionStatus ( String className , boolean b ) 1.4
对于给定的类和它的内部类,启用或禁用断言 。
void setPackageAssertionStatus ( String packageName , bool ean b ) 1.4
对于给定包和其子包中的全部类,启用或禁用断言。
void clearAssertionStatus () 1.4
移去全部类和包的显式断言状态设置 ,并禁用全部经过这个类加载器加载的类的断言。
5,
0,System.out 和 System.err 的区别
1,全局日志记录器:
2,自定义日志记录器
3,修改日志管理器配置
4,日志处理器 (P293)
5,日志过滤器
6,日志格式化器
public static void main(String[] args) { Logger logger = Logger.getLogger("tong.huang.shan"); logger.setLevel(Level.FINE);
/**
* 日志记录器会将日志发送到父处理器中,最终的父处理器是一个叫作【""】的处理器,它也有一个ConsoleHandler。若是不设置false则控制台会打印两次
* Logger.getLogger("")能够获取到这个最终的父处理器。
*/ logger.setUseParentHandlers(false); Handler handler = new ConsoleHandler(); handler.setLevel(Level.FINE); handler.setFilter((t)->t.getMessage().contains("33"));//java.log.logging.Filter是一个函数式接口。 handler.setFormatter(new MyFormatter());// java.log.logging.Formatter是一个抽象类。抽象类和接口的区别在于抽象类只能单继承,接口能够多实现。 logger.addHandler(handler); logger.fine("hello world33"); } class MyFormatter extends Formatter { @Override public String format(LogRecord logRecord) { return logRecord.getMessage().replace("33", "55"); } }
7,书中有一个自定义处理器(程序清单7.2 p298),经过扩展Handler类或StreamHandler类 实如今窗口中显示日志记录的功能。
8, API
1,日志代理
3,堆栈轨迹
//不带参数的printStackTrace()函数实际的实现以下:
public void printStackTrace() { printStackTrace(System.err); }
PrintWriter pw = new PrintWriter("C:\\Users\\JoY-33\\Desktop\\1.txt"); new Throwable().printStackTrace(pw); /* * 注意:使用PrintWriter往文件写入时须要使用flush()或close()将缓冲区刷新,不然不会写入文件中。 */ // pw.flush();//仅仅刷新缓冲区,刷新以后流对象还能够继续使用 pw.close();// 关闭流对象,在关闭以前会先刷新一次缓冲区。关闭以后,流对象不能够继续使用了。
StringWriter out = new StringWriter(); PrintWriter pw = new PrintWriter(out); new Throwable().printStackTrace(pw); System.out.println(out);
4,
5,
Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { //save information in log file }; });
6,要想观察类的加载过程,能够使用-verbose 参数启动Java虚拟机。有时候这种方法有助于诊断因为类路径引起的问题。
7,属于“lint” 最初是指一种定位C程序中潜在问题的工具。如今泛指 查找可疑可是并不违背语法规则代码的 工具。Java编译器的 -Xlint 选项使用方法以下:
8,Java虚拟机选项中的 -X 选项并无被正式支持,有些JDK选项中并无这些选项。能够使用 java -X 获得全部非标准选项的列表。 javac -X 也同样。
9,jconsole的使用:
10,jmap 的使用:
11,Java Mission Controller是一个相似于jconsole的专业的分析和诊断Java进程的工具。具体参考文档: https://docs.oracle.com/javacomponents/index.html
12,
1,Java SE5.0新增了泛型机制。
2,类型参数(type parameters): 通常 E表示集合元素,K , V表示键值,T, U, S等表示任意类型。
1,泛型类:Pair<T>
package tong.huang.shan;
// Pair<T>是一个泛型类,也能够说是一个泛型类型(Java中的类也表示一个类型)。在编译器中,这个泛型类被翻译为一个普通的Pair类。 public class Pair<T> { private T first; private T second; Pair() { first = null; second = null; } Pair(T first, T second) { this.first = first; this.second = second; } @Override public String toString() { return "Pair<" + this.first + "," + this.second + ">"; } public T getFirst() { return this.first; } public T getSecond() { return this.second; } public void setFirst(T first) { this.first = first; } public void setSecond(T second) { this.second = second; } }
2,泛型方法 和 类型参数的限定类型
package tong.huang.shan; import java.time.LocalDate; public class Study { public static void main(String[] args) { String[] arr = { "mary", "had", "a", "little", "lamb" }; Pair<String> minmax = ArrayAlg.minmax(arr); System.out.println(minmax); String middle = ArrayAlg.<String>getMiddle("John", "Q", "public"); System.out.println(middle); /** * 下面这个方法调用会编译报错。 * 编译器自动打包参数为一个Double和两个Integer对象,而后寻找这些类的公共超类。 * 事实上Double类和Integer类的共同超类有两个:Number类和Comparator接口。 * 因此这意味着ArrayAlg的这个方法返回值能够赋值给Number类型或Comparator类型。 * 固然也能够将参数都改成double类型。(3.14,111d, 0d) */ // double mid = ArrayAlg.getMiddle(3.14, 111, 0); // System.out.println(mid); LocalDate[] birthdays = { LocalDate.of(1906, 12, 9), // G. Hopper LocalDate.of(1815, 12, 10), // A. Lovelace LocalDate.of(1903, 12, 3), // J. von Neumann LocalDate.of(1910, 6, 22), // K. Zuse }; Pair<LocalDate> mm = ArrayAlg.minmaxG(birthdays); System.out.println("min = " + mm.getFirst()); System.out.println("max = " + mm.getSecond()); } } class ArrayAlg { public static Pair<String> minmax(String[] a) { if (null == a || a.length == 0) { return null; } String min = a[0]; String max = a[0]; for (int i = 1; i < a.length; i++) { if (min.compareTo(a[i]) > 0) min = a[i]; if (max.compareTo(a[i]) < 0) max = a[i]; } return new Pair<String>(min, max); } /** * 泛型版本的 minmax 方法 */ public static <T extends Comparable> Pair<T> minmaxG(T[] a) { if (null == a || a.length == 0) { return null; } T min = a[0]; T max = a[0]; for (int i = 1; i < a.length; i++) { if (min.compareTo(a[i]) > 0) { min = a[i]; } if (max.compareTo(a[i]) < 0) { max = a[i]; } } return new Pair<T>(min, max); } // 泛型方法能够在普通类中定义,泛型参数放在修饰符和返回类型之间 public static <T> T getMiddle(T... a) { return a[a.length / 2]; } /** * 类型变量的限定类型(bounding types,或译为界限类型)能够有多个,用&符号分隔:<T extend Comparable & Serializable> * 限定类型能够有多个接口,可是只能有一个类(类只能单继承),若是限定类型中有类,它必须是 限定列表中的第一个。 */ public static <T extends Comparable> T min(T[] a) { if (a == null || a.length == 0) return null; T smallest = a[0]; for (int i = 1; i < a.length; i++) if (smallest.compareTo(a[i]) > 0) smallest = a[i]; return smallest; } } interface A { } interface B { } // 接口能够继承接口,能够继承多个接口 interface C extends A, B { } class AA { } class BB { } // 类只能继承一个类,能够实现多个接口 class CC extends AA implements A, B { }
3,
1,Java 虚拟机中没有泛型类型对象,全部的对象都属于普通类。
2,类型擦除
//例子一:原来的泛型类型 public class Pair<T> { private T first; } //类型擦除后为: public class Pair { private Object first; } //例子二:原来的泛型变量 public class Pair<T extends Comparable & Serializable> { private T first; } // 类型擦除后为: public class Pair { private Comparable first; } // 注:Serializable 接口是标签接口(tagging interface:即没有任何方法的接口,只是用来instanceof用的),为了提升效率应将标签接口放在限定类型列表的末尾。
3,使用Java泛型时的约束与局限性: 大多数限制都是由类型擦除引发的。
if (a instanceof Pair<String>) // Error if (a instanceof Pair<T>) //Error //强制类型转换编译器会警告 Pair<String> p = (Pair<String>) a; // getClass()方法老是返回原始类型。 Pair<String> stringPair = ... Pair<Employee> employeePair = ... if (stringPair.getClass() == employeePair.getClass()) // true:都返回Pair.class
//Error,不容许建立参数化类型的数组 Pair<String>[] table = Pair<String>[][3]; /* 注意:只是不能建立参数化类型的数组,可是声明类型为Pair<String>[]变量还是合法的, * 只是不能用new Pair<String>[3]初始化这个变量. * 能够声明通配类型的数组而后进行类型转换。可是结果是不安全的。 */ Pair<String>[] t = (Pair<String>[])new Pair<?>[3];
/** * 可变参数ts其实是一个数组。若是T是一个泛型类型,好比Pair<String>, * 那么Java虚拟机就必须创建一个Pair<String>数组,这就违反了不能建立泛型数组的规则。 * 可是,对于这种状况,规则有所放松,只会有一个警告而不是错误。 * 能够有两种方法抑制这个警告: * 方法一:为addAll方法增长注解@SuppressWarnings("unchecked") * 方法二:自Java SE7开始,还能够使用@SafeVarargs注解标注addAll方法。 */ // @SafeVarargs public static <T> void addAll(Collection<T> coll, T... ts) { for (T t: ts) {coll.add(t);} }
4, 泛型类型的继承原则
LocalDate date = LocalDate.of(1111, 11, 11); /* * Java 泛型 */ Manager m1 = new Manager("name", 3d, date); Manager m2 = new Manager("name1", 4d, date); Pair<Manager> managerPair = new Pair<>(m1, m2); //编译没法经过:Type mismatch: cannot convert from Pair<Manager> to Pair<Employee> Pair<Employee> employeePair = managerPair; /* * Java 数组 */ Manager[] managers = {m1, m2}; Employee[] employees = managers; employees[0] = new Employee();//编译能够经过,可是运行时错误:java.lang.ArrayStoreException System.out.println(Arrays.toString(employees));
5,
1,通配符类型容许类型参数变化。注意:通配符不是在定义泛型类时使用的,而是在使用泛型类时才能用到通配符。如定义Pair类时,只能是public class Pair<T>{}不能是public class Pair<? extends Employee>
2,通配符捕获: 用于通配符不是一个类型,所以不能再编码中使用 ? 做为一种类型。假设,要交换Pair<?>的first和second属性,如下代码是错误的
? t = p.getFirst(); // Error p.setFirst(p.getSecond()); p.setSecond(t);
解决方法是再写一个辅助方法swapHelper,以下
// swapHelper是泛型方法 public static <T> void swapHelper(Pair<T> p) { T t = p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); } / ** * swap方法能够调用泛型方法swapHelper,注意swap并非泛型方法,swap的参数是 * Pair<?>类型的。 swap方法中static和void之间并无泛型类型。 */ public static void swap(Pair<?> p) { swapHelper(p); } 此时,swapHelper方法的参数T捕获了通配符。通配符捕获只有在许多有限制的状况下才是合法的。编译器必须可以确信通配符表达的是单个、肯定的类型。
例如:ArrayList<Pair<T>>中的T永远不能捕获ArrayList<Pair<?>>中的通配符,由于数组列表ArrayList中能够保存两个?不一样的 Pair<?>。
3,
1,
2,
第九章:集合
1,集合的接口与实现分离
※,接口与实现分离虚拟例子:队列接口:public interface Queue<E>{} ,队列的实现方法一般有两种:
一是使用循环数组(circular array)public class CircularArrayQueue<E> implements Queue<E>{},
二是使用链表(linked list)。
※,
2,collection接口 和 Iterator 迭代器接口
※,Java类库中,集合的基本接口是Collection接口。Collection接口有两个基本方法:
public interface Collection<E> { boolean add(E element); Iterator<E> iterator();//迭代器 ....//还有一些其余方法 }
※,Iterator接口包含4个方法:
public interface Iterator<E> { E next(); boolean hasNext(); void remove(); default void forEachRemaining(Consumer<? super E> action); }
※,for each循环是迭代器循环 while (iterator.hasNext()) {next = iterator.next();...}的简化 形式。 for each循环能够与任何实现了Iterable接口的对象一块儿工做。Iterable接口只包含一个抽象方法:
public interface Iterable<E> { Iterator<E> iterator(); }
Collection 接口扩展(继承)了Iterable接口,所以对于标准类库中的任何集合均可以使用 for each 循环。
※,在Java SE8中,甚至能够不用写循环。调用forEachRemaining方法并提供一个lamda表达式(它会处理一个元素). 例子以下:
Collection<Integer> collection = new ArrayList(); collection.add(3); collection.add(4); Iterator iterator = collection.iterator(); List<Integer> list = new ArrayList<>(); //e是Object类型 iterator.forEachRemaining(e-> { list.add((Integer) e + 3); }); System.out.println(list); //[6, 7]
※, 迭代时 元素被访问的顺序取决于集合类型。
※,实际上,JDK1.2中出现的Iterator接口中的next() 和 hasNext()方法与 JDK1.0中的Enumeration接口中的nextElement()和 hasMoreElement() 方法的做用是同样的。Java类库的设计者本能够选择使用Enumeration接口,可是他们不喜欢这个冗长的名字,因而引入了具备较短方法名的新接口。
※,Iterator.next() 与 InputStream.read() 能够看做是等效的。
※,
3,泛型实用方法
4,集合框架中的接口
1,链表
2,数组列表
3,散列集
4,树集
5,队列与双端队列
6,优先级队列
1,
2,
1,
2,
1,
2,
1,
2,
1,
2,
1,
2,