对于在internet上下载java字节码文件,并在本地运行(在网页中运行的java程序)称为applet,每次访问都能得到最新的applet,刚开始诞生的时候感觉会很流行,但是网景公司的浏览器和微软的ie浏览器因为使用的java版本不同,有些版本已经过时,难以将最新的Java版本开发的applet应用,随着后来adobe的falsh出场,和java的安全问题(就是访问一个applet,浏览器和java浏览器插件的限制变得越来越多),所以现在要想在浏览器中使用applet不仅要一定的水平而且要付出很大的努力,applet逐渐淘汰
这张图片是不同的jdk版本出现的功能。
Java程序 Java图形程序 Javaapplet程序
java提供了固定得数据范围,不像c++那样为了在特定平台得到更好得性能而采用不同范围,这样就会在移植平台的时候可能出现溢出的情况,而java提供的固定范围,可以很好的实现跨平台
java提供了八种基本数据类型
四种整数类型(int,short,long,byte):长整型后面可以添加一个L或者l,十六进制有一个前缀0x或者0X,八进制有0,二进制0b或者0B(从java7开始,可以用_下划线分割,这样更易读,编译器会将下划线去掉)
两种浮点型(float,double): float有一个F或者f的后缀,double有D或者d的后缀,因为float精度很小,大多数情况都是double,所以一个小数不加任何后缀的情况下是double类型的。(double还有十六进制的表示方式例如0.125=2^-3,就可以表示为0x1.0p-3,这里的1.0是十六进制数,-3是十进制但是基数是2,所以表示就是1* 2的-3次方,还有常规的aeb表示形式) 浮点型数据不适合表示四舍五入的情况,因为java采用二进制系统保存,不能精确的还原二进制数,所以会和预想的结果可能不太一样,最好使用BigDecimal类。
一种表示unicode编码的char: 可以表示为十六进制值,范围从\u0000到\uffff,\u这个转义字符还可以出现在引号之外,其他的转义字符则只能在引号中间 unicode和char char描述的是一个utf-16的代码单元(16位)
一种表示真值的Boolean 注意:java没有提供unsigned的修饰的类型 所有数值都遵守IEEE 754规范,Double.positive_infinity正无穷大,Double.negative_infinity负无穷大,Double.NaN不是数字
java变量可以用字母和数字定义,但是必须由字母开头,java的数字和字母范围更加大,字母包括原来的字母还有_,$ ,以及本地语言的字母,在项目开发中因该避开使用$ ,因为这是留给一些工具产生的,或者编译器产生的
使用未初始化变量,编译器就会报错
在java中,使用final代表常量 final修饰的常量只能被赋值一次, 而且不能修改,不然就会报错,习惯上把常量大写
除法运算/,当两边都是整数时,结果也会是整数,否则就会是浮点型(double)类型,整数除以0会报异常,浮点型除以0则会是正无穷大,或者是NaN 对于实现跨平台,想要实现浮点型的结果一致是有难度的,double是64位的存储一个数值,而 有些处理器是使用80位的寄存器,这些寄存器会进行中间结果的计算精度,这个结果会和64位的结果可能会不一样,所以java一开始是始终进行截断操作的,但是后来受到了反对,所以修改了,默认的话是不进行截断的,只有加了strictfp关键字的方法才会进行截断操作
double Math.sqrt(double a)计算a的平方根 double Math.pow(double a,double b)计算a的b次方 Math.floorMod(int a,int b) 三角函数: Math.sin Math.cos Math.tan Math.atan Math.atan2
Math.log Math.log10 Math.exp Math.round(float) 四舍五入 常量π和E Math.PI Math.E 为确保在所有平台上都有相同的数值,可以使用StrictMath类
如果一种位数多的类型向位数少的类型转换就会造成精度的丢失,有大类型的都会向大类型的转换,如int+double,double大一些,就会向double转换 虚线表示精度会有丢失,实线表示不会造成精度丢失 当超过转换的目标的范围,就会高位截断,例如(byte)300会转换成44(低八位)
除了上面的自动类型转换,还有强制的,有些时候需要将大类型转换为小类型就需要在类型前面(目标类型),就可以强制类型转换
相当于两个赋值操作,如果最终赋值类型不等于左边的就会进行强制类型转换,例如,int x; x+=1.4;等价于x = (int)(x+1.4)
跟C语言一样,提供自增自减的运算符,有前缀和后缀的区别 注意:在表达式中尽量不要使用++运算符,可读性不高
用于检测相等或不等,值为true或者false
&& || ! 前两个有短路的效果 !后面只能接boolean值
& | ^ ~ 按位与,按位或,按位异或 << >> >>> 左移位,右移位,无符号右移位 左移位会在右边补0 右移位会在高位补符号位 无符号右移位会在高位补0
移位运算符会在达到位数范围后从头开始,例如1<<35,相当于1<<3,这里的1是整数类型,相当于35模32
由于>>不能确定是进行了算术运算(拓展符号位)还是进行了逻辑运算(补0)意味c++对负数产生的结果可能依赖具体的实现,但是java消除了这种不确定性
enum关键字 包括有限个命名的值
从概念上将,字符串就是unicode序列,java没有内置的字符串类型,但是基础类库提供了一个字符串的预定义类,每一个用双引号括起来的都是String的一个实例
String实例提供了一个方法,substring(int a,int b)不包括b的位置,如果其中任何一个位置越界就会报错
String类提供了一个静态方法join可以将多个字符串拼接在一起,还可以直接字符串之间的分隔符
String类没有提供修改字符串的方法,所以一旦一个字符串创建好了之后就不能改变,但是这是否会降低程序的性能,是也不完全是,因为不可变的话就可以让编译器提供共享。 可以理解为java的字符串是c语言里面的字符指针 char* a = “abc”,这样的,而不是char a[3] = “abc”,如果对字符串变量重新赋值的话,是重新创建一片内存空间,而原来没有使用的字符串将会被jvm回收。 小提示:c++的字符串是可以进行修改的,也会进行垃圾回收
使用String实例的equals方法,a.equals(b),这里的a可以直接用双引号换成字符串也可以 如果想要不区分大小可以使用equalsIgnoreCase方法 compareTo(String otherString) compareToIgonoreCase(String str) 上面两个方法返回值都是整形,0表示相等,1表示不相等
但是使用equals看起来清晰一些 注意:== 运算符只能比较两个位置是否一致,完全有可能将内容相同的多个字符串放置在不同的位置
解释:如果jvm始终将字符串共享,则用==比较就是相等的,但是事实上,使用 +或者substring等 产生的字符串是不共享的
空串是长度为0,内容为空的字符串,有自己的长度,内容 null串是一个Java对象,表示没有任何对象和该对象有关联
java字符串是由char值序列组成的,而char类型又是由utf-16编码表示unicode码点的代码单元 大多数的字符是一个代码单元就可以表示,辅助字符是两个代码单元 length()方法返回的是代码单元的数量 如果想要实际的码点数量则用codePointCount(int beginIndex,int endIndex)方法 charAt()返回的是指定代码单元 offsetByCodePoint(int index,ine codePointOffset)方法,返回从index,偏移codePointOffset后的码点的索引 codePointAt(int index)方法,返回指定码点的十进制Unicode编码
String StringBuffer StringBuilder 第一个String类是不可变类,每一次拼接都会产生大量的副本,占用内存降低效率 StringBuilder可以改变,线程不安全(不支持并发访问),效率高 StringBuffer可以改变,线程安全,效率低
可以看出StringBuilder线程不安全,造成数据的混乱,相反StringBuffer大多数方法都是synchronized修饰的,则不会造成混乱。
Scanner类,指定构造器System.in则可以接受控制台的输入 next下一个单词 nextInt下一个整数 nextDouble下一个浮点型 如果输入的类型不一致就会报错 Console类,比Scanner安全,输入密码时显示不出,但是Scanner比较方便,大多数情况还是使用Scanner
System.out.printLn java还提供了和C语言一样的格式控制输出 printf() 这种通过格式输出控制的时间格式已经过时
就是用一对括号括起来的语句,(方法体)块作用域内可以嵌套定义块,但是不能像C语言那样定义重名的变量,这样会增加编程的出错率,所以不能重复定义
if else
while do while
for 对于for循环,如果控制变量是浮点类型可能会造成死循环,如for(double i=1.02;i!=10;i+=0.1)由于浮点型不能精确的表示0.1,所以可能会跳过10造成死循环
case的取值可以是 char byte short int 的常量表达式 枚举类型 从jdk7开始可以使用字符串
有break和continue,用法和C语言是一样的 区别在于java的提供了带标签的break和continue,可以用于跳出多重循环而不会像goto语句那样破坏程序结构,如果在多重的循环中跳出来,则会执行标签的下一条语句
由于java采用的是二进制系统,从而不能精确的表示0.1之类的浮点数,所以会造成数据的误差,应当使用BigDecimal类进行操作 BigDecimal.valueOf(long a)返回一个转换后的BigDecimal BigDecimal.valueOf(long a,int t)返回a/(10^t)的BigDecimal 加减乘除 add(BigDecimal other) subtract(BigDecimal other) multiply(BigDecimal other) divide(BigDecimal other)如果是无限循环小数会报错,可以使用下面方法 divide(BigDecimal other,RoundingMode mode)RoundingMode.HALF_UP是四舍五入 compareTo(BigDecimal other)相等0,小于负数,大于正数
BigInteger也是用于整数的大数运算,不限制位数可以构造的时候传递字符串,方法和BigDecimal差不多
存储同一数据类型的集合,如果只是声明,这就不是真正的数组 可以直接赋值,例如int[] a = {1,2}是一种简写new的写法 还有一种就是初始化同时赋值的写法int[] a = new int[]{1,2}这里注意不能直接写出数组的大小。 数组变量可以重新初始化或者将其他数组变量赋值给这个变量 有一种小技巧,数组是可以定义0长度的,但是和null又不一样,在结果刚好为空的时候这样很有用,不会造成空指针异常 int[] a = new int[0] int[] a = new int[]{} 两种方法都可以
下标是从0开始的
创建一个数字数组时,所有元素初始化为0boolean数组,所有元素默认初始化为false创建对象数组的,所有元素初始化默认为null它定义了一个变量暂存集合中的每一个元素,不用担心数组下标越界的问题 Arrays.toString()方法提供了得到数组元素的字符串
使用Arrays.copyOf(被拷贝的数组,新数组长度) 如果长度小于被拷贝的数组,则只拷贝指定的区域 如果大于原来的长度,则赋值是默认的(0,false,null) 注意:java数组和c++的数组很大不同,例如c++的int a[100]不同于java的int[] a = new int[100],而相当于int * a = new int[100],由于java没有指针运算,所以不能通过a+1得到下一个元素
类名后面的第一个参数是args[0]
Arrays.sort() 使用了优化后的快排,效率很高
Math.random() 可以生成0~1之间的浮点数(包括0但是不包括1) 用生成的浮点数*n,就可以的到0~n-1范围内的数
binarySearch(a,v) 在数组a中查找v binarySearch(a,start,end,v) 在指定范围查找v(不包括end),吐过查找到了返回对应的下标,如果没有找到则返回一个负数,是负的a长度 fill(a,v) 将数组a的元素都填充为v equals(a,b) 如果两个数组大小相等且对应元素相等则为true
deepToString()多维数组转换成字符串 foreach循环不能自动处理多维数组中的每一个元素,应该用一个多重foreach循环 定义的时候可以只指定行 int[][] a = new int[3][]; 指定了一个整形数组,里面有三个元素,元素又是一个数组,但是还没有分配。 这里的二维数组也不同于c++的数组 int** a = new int*[] 二级指针,一级指针则指向真正的数组
object-oriented programming OOP是当今主流的程序设计方式,取代了20世纪70年代“结构化”或者过程化编程技术 算法+数据结构=程序 在面向过程的程序设计方式中:算法即处理某一功能的过程,再对数据采用适当的存储结构,第一步是过程,第二步才是数据 而在面向对象的程序设计方式中:将数据排在第一,过程排在第二
总的来说面向对象将一个规模大的系统划分为完成特定功能的一些模块(对象)如果某一功能的数据发生错误,这将比在面向过程中笼统的大模块中查找容易的多。
类是构造对象的模板或者蓝图,可以将类想象成一个模具,将对象想象成小甜品,将材料放入模具制造甜品的过程称作创建类的实例 封装(encapsulation 也成数据隐藏):是处理对象的重要概念,将数据和方法封装到同一个包中,并对对象的使用者隐藏具体的实现方式,每一个类的实例都有一组特定的实例字段值,这些值的集合称为这个对象当前的状态,,只要调用这个对象的方法,他的状态就有可能会改变 封装的关键:只能通过对象的方法对对象数据进行操作,而不能在其他类中访问到当前类的实例字段,封装给对象带来了“黑盒”特征,这是提高重用性和可靠性的关键 在扩展一个已有的类时,新类具有父类的全部属性和方法,只需要在新类中提供适用于这个新类的新方法和数据字段(继承inheritance)
要想使用OOP,就得了解对象的三个主要特征
对象的行为对象的状态对象的标识 1表示对象提供的方法,完成哪些操作 2表示对象的描述信息(属性),随着时间不同,状态也有可能发生改变,例如调用了某个对象方法 3表示同一状态的两个对象的区分方式,每个对象都有唯一的标识。并不是所有的类都由面向对象的特征,例如Math类,可以直接通过Math.random(),不用关系他是怎么实现的,这正是封装的关键,但是Math类之封装了功能,没有隐藏数据,因为没有数据
要想使用对象,就必须构造对象,并指定其初始状态,然后对对象应用方法 对象变量并没有实际的包含一个对象,而是引用了一个对象。 new操作符返回的是一个引用。 注意:所有的java对象都存储在堆中,当一个对象包含另外一个对象变量时,只是包含着另一个堆对象的指针
Date类的实例有一个状态,就是特定的时间点 时间是距离一个固定时间点的毫秒数来表示的(纪元)UTC(Coordinated Universal time国际协调时间)时间1970年1月1日00:00:00 和GMT(Greenwich Mean Time格林尼治时间)一样,是实用的科学标准时间 但是Date类对于人类记录日期不是特别有用,Date的日期可以描述大多数地区的阳历,但是对于中国或者希伯来的描述就不一样了 类库的设计者决定将时间和时间点的命名分开,所以标准的java类库由两个类,一个是用于表示时间点的Date类,另外一个就是日历表示方法的LocalDate类。 localdate类应该使用工厂方法now()获取对象实例,这个实例会代表当前的日期
now过去当前日期的对象 of构造一个给定日期的对象 getyear getmonthvalue getdayofMonth 获得年月日 getdayOfweek获得当前日期是星期几返回一个DayOfWeek对象调用getvalue可以获得对应的整数值 plusDays minusDays 得到之后或者之前几天
可以改变对象装填的方法叫更改其方法 通过调用方法而不改变对象状态然后生成一个新对象返回,这个方法叫访问器方法
如果一个A类中使用了另一个B类 当Javac 编译A类时,会自动编译B类,如果B类已有更新就会重新编译B类
隐式参数是调用对象方法的那个对象,可以在方法中通过this来表示这个隐式参数 显式参数式方法括号后面接受的参数
封装易于检查出错误 注意:对于可变类的返回要注意,因为返回的是同一个对象,这样对对象进行修改之后就破坏了封装的完整性,不易于检查出错误,如果一定要返回可变类,应该对他进行克隆clone
一个类的类方法可以对他的所有对象进行操作,而不仅限于隐式参数 (个人理解)对象只有属性,然而类方法是提取出来的,可以操作有的对象属性,当然私有属性是不能访问的。
对于final修饰的变量,表示这个变量一旦给了他指向的目标,他就不会再指向别的内容了。
静态域也叫类域
使用final修饰的变量是不允许再对其进行赋值的 然而System类里面有一个setOut方法,可以将System.out流进行重新设置,这是因为setOut方法不是由java语言实现的,他是一个本地方法,本地方法可以绕过java语言的存取控制机制。
静态方法不能访问实例对象,但是可以在实例对象中访问静态方法(但是通常不建议这么做)
静态方法还有另外一种用途 就是通过工厂方法来构造对象,返回不同的构造对象
main方法不对任何对象进行操作,在启动程序时还没有任何一个对象,而main方法就会执行并创建程序所需对象。
按值调用 按引用调用
overlording 如果一个方法有相同的名字,不同的参数,便产生了重载 重载解析: 编译器挑选出具体执行哪一个方法,通过各个方法给出的参数列表和特定方法的参数进行对比,如果找不到对应的参数列表就会编译错误 签名: 提供方法名和参数列表,例如String类的 indexOf(int) indexOf(int,int) 这就是签名
如果在构造器中没有对域进行初始化,就会赋值上默认值。这会影响程序的可读性。 如果没有提供构造方法,那么系统会提供一个空参的构造方法,将实例域都初始化为默认值,0,false,null 如果提供了一个带参数的构造方法,则无参的构造方法就不会自动创建,从而不能使用。
在定义实例域的时候给予初始值(这会在构造器之前执行)
调用当前类的另外构造器可以使用this(参数列表)来进行另外构造器的调用,不能进行构造器的递归调用 这样 对公共的构造器代码部分只用写一次即可
对象初始化块 用一对大括号包起来的 每当new一个对象时,都会自动调用这个代码块 构造器会在执行了这个代码块之后再执行 静态初始化块 静态代码块会在第一类加载时执行。
从图片中可以看出代码块的执行顺序 在java8之前是可以没有主方法就通过静态代码块打印输出一个Helloworld然后再输出没有主方法,在8及其之后程序就会先检查有无主方法了 java.util.Random 构造一个新的随机数生成器 random返回0-n-1的随机数
在c++中有专门释放对象或者资源的析构器,但是由于java有垃圾回收期,所以就没有析构器。 如果使用了内存之外的资源,比如硬盘文件或者系统资源的另外一个句柄,这种情况下就需要回收了。 可以通过重写finalize方法,finalize方法会在清除对象之前进行调用,但是有弊端,你不知道什么时候回进行对象回收和清除,finalize方法很难知道什么时候调用。 有个System.runFinalizersOnExit(true)可以确保在jvm关闭时进行finalize的调用,但是这个静态方法不安全,可以使用替换的方法,Runtime.getRuntime().addShutdownHook(Thread a),这个方法可以在jvm关闭之前执行初始化但是未执行的线程a的线程体,从而确保资源的释放 从图片中可以看出System那个方法是已经弃用的,在运行的时候我发现有两次是没有执行的finalize方法的,所以可能官方定义的不安全可能在这里吧。
java允许使用包将类组织起来 所有的标准java包都处于java,javax包层次中 为了确保包的唯一性,sun公司建议将域名逆序书写,因为域名是独一无二的, 从编译器的角度来说java.util和java.util.jar包是没有任何关系的,这两个包都拥有独立的类集合
一个类可以使用所属包下的所有类,以及其他包下面的公有类(public class如果在这个类文件写一个不是public修饰的class其他包就访问不到)
在包中定位是编译器的工作
import和#include还是不同的,c++不能看到其他文件的内部,除了#include引用进来的和自身文件,但是java不同,java可以看到所有文件的内部,只需要告诉他到哪里去看就行了,例如java.util.LocalDate,使用import的好处就是简洁,不用每次都写 在c++中类似的包机制是namespace,java的package和import类似c++的using和namespace
可以通过package static导入其他类中的静态方法或者域
只需要将包名放入源文件的开头,如果没有将包名放到开头,则这个类会被放到一个默认包中(default package),默认包是一个没有名字的包。 javac后面跟的是路径名加上具体需要编译的类名.java javac com/lzb/User.java 调用jvm加载类使用java命令后面跟具体的包名和类名 java com.lzb.User javac是对文件进行操作,所以使用的是文件分隔符 java是虚拟机对类进行操作,所用使用的是点运算符
注意:如果在文件开头导入了package指定包,而且没有依赖其他的类,即使不在指定的目录下也可以进行编译,但是使用java命令运行的时候jvm就会找不到具体的类,就会报错 可以看出是可以正常编译的,但是运行就会有问题
public protected private 第一种可以被其他包中的类访问到 第二种只能被同一个包中的类访问到 第三种则只能在当前类中访问
类文件可以存储在文件系统之外,还可以存储在jar(java archive java归档)文件中,这样改善性能还可以节省空间。 为了能让jvm找到这些类,还需要配置类路径 点表示当前路径,windows中用分号进行分割。
jvm要找类文件,会先搜索jre/lib和jre/lib/ext目录下放的系统类文件,找不到的话就会从设置的classpath中搜索。
如果这个类依赖于别的类文件,编译器就会去定位,编译器定位比虚拟机定位会复杂很多,编译器会查找包含这个类的包,并查询所有的import指定,确定其中导入了被引用的类,例如查找User.java import java.util.* import com.lzb.* 他会首先去java.lang.User(默认包含这个包)然后去java.util.User,然后去com.lzb.User进行逐一查看,如果找到了两个以上的类,就会产生编译错误,因为类要是唯一的,多个的话会冲突,编译器的任务还不止这些,他还要去看原文件是否比编译好的文件新,如果新的话还要重新进行编译。
最好用-classpath或者-cp
提示:set命令用法
javadoc从以下几个特性中抽取信息
包公共类和接口公有的受保护的构造器和方法共有的和受保护的域 在每个文档注释之后都会紧跟自由文本(可以是html修饰符),标记由@开头放在import语句之后和类之前
除了通用标记之外,还可以使用下面几个标记 @param 变量描述 @return 返回值描述 @throws 方法可能抛出的异常描述
只需要对公有域进行描述(静态变量)
下面这些标记可以用再类文档注释 @author @version 这两个默认是省略的,javadoc -version -author才会出来 @since 从哪个版本开始的新特性 @deprecated 对类和方法或者变量添加不再使用的表示,给出取代的意见 @see可以用于类也可用于方法,添加一个超链接
javadoc -d 生成目标位置 包名(可以多个,用空格分开) 创建package.html文件,会抽取body内的信息 p143
反射:程序运行期间发现更多的类及其属性的能力 java的继承都是公有继承,而没有c++的私有继承和保护继承
已存在的类:超类,基类,父类 新类:子类,派生类,孩子类
通过超类扩展子类的时候,需要指出子类和超类的不同,因此在设计类的时候,应该讲通用的方法放到超类里面,而讲具有特殊用途的方法放到子类中
子类会继承父类的所有方法,和所有的域(包括初始化块),但是不能访问父类那些私有的域和方法,需要借助父类的方法才能进行访问。 如果子类和父类的那个方法相同,则不能通过方法名再调用,这样会递归调用这个重写的方法,直到程序崩溃,所以要使用关键字super调用父类的方法。 注意:super和this不是一个相同的概念,不能将super赋值给一个对象变量,他只是一个指示编译器调用超类方法的特殊关键字。 继承只能增加或者重写方法或者域不能将其删除。
由于子类不能访问父类的私有域,所以必须借用父类的构造器对其进行初始化,可以在子类构造器的第一句通过super(参数列表)的形式调用父类的构造器。
如果子类的构造器没有显示调用父类的构造器,系统会自动调用父类空参的构造器。如果父类中没有空参的构造器,且没有显示的调用其他的构造器,则编译器会报错。
一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism),在运行时能够自动选择调用哪个方法的现象被称为动态绑定(dynamic binding)
在java中,不需要将方法声明为虚拟方法,动态绑定是默认的处理方式。如果不希望一个方法有虚拟特征,则加上final关键字即可
this
引用隐式参数访问该类的其他构造器super 3. 调用超类方法 4. 调用超类构造器。 注意:调用构造器的语句应该放在第一句
由一个超类派生出的所有子类的集合叫做继承层次(inheritance hierarchy),从某个特定的类到其祖先的路径被称为继承链(inheritance chain)
有一个用来判断是否应该设计为继承关系的简单规则就是is-a原则,表明每个子类对象也是超类的对象。 is-a的另一种表述规则就是置换原则,可以将子类元素赋值给父类变量 注意:java的子类数组可以赋值给父类数组,但是运行的时候数组只能兼容自己的类型,否则会有ArrayStoreException异常 在这里做个小笔记:java中允许的继承体系对于赋值的时候会产生向上转型和向下转型的情况,向上转型总是安全的,即将子类对象赋值给父类对象,但是在一些情况下向下转型就会产生错误,即将父类对象赋值给子类对象,例如两个没有关系的父子关系转换就会产生错误
你可以这样理解,A是经理,B是员工,如果你将经理赋给员工,显然经理能胜任员工的职位(大多数情况是这样),所以可以兼容,但是将员工赋值给经理,显然他干不来经理的活,所以不能兼容。
数组也是同样的道理,将一个没有关系的(也就是把经理位置给了一个员工)赋值给子类,显然不能正确转换,所以会报数组元素异常。
例如现在调用x.flag(param)方法
编译器找到隐式参数x对应的方法flag,但是可能会有方法重名,会列出所有的flag方法(包括父类中获取到的public修饰的方法)编译器参看调用方法的参数类型,如果存在一个名称相同且类型相同的方法就会选择这个方法,这个过程叫做重载解析。由于类型可以自动转换,例如int可以自动转换成double,参数中传递的是int类型,但是方法只有double的,依旧可以解析,同理,如果传参是子类,方法只有父类,一样可以解析,反之则不行 如果发现解析后有多个方法与之匹配就会报错。方法的名字和参数列表叫做方法的签名,如果子类中定义了和父类相同签名的方法,那么就会覆盖父类的方法,由于返回值类型不是签名的一部分,一定要确定返回值类型的兼容性,将重写的方法返回值类型定义为原来的子类型,这样的叫做可协变的返回值类型
如果是private,static,final方法修饰的方法或者构造器,编译器知道具体调用哪个方法,这样的调用方式称为静态绑定, 与此相反的 调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定
采用动态调用的方式,总会在对象中挑选最合适的那个方法,(总是在最低子类开始找,然后往上),这样会造成很大的时间开销,因此,jvm为每个类预先建立了一张方法表,列出所有方法的签名和实际调用的方法,在真正调用方法时仅查表就可以了。(如果使用super关键字,就会在父类表中搜索)
如果调用e.getSalary()
jvm首先会根据e的具体类型的方法表进行提取,可能是自身类也可能是子类的方法表。然后jvm对定义getSalary签名的类进行搜索,然后就知道调用哪一个方法。最后调用这个方法】动态绑定有一个重要特性:就是不需要修改现存代码,就可以对程序进行扩展(多态)一般使用接口进行中间转换,将子模块作为参数,这样修改的时候只需要将旧的模块替换就可以了。
在重写一个方法的时候,子类方法不能低于父类方法的可见性(需要>=)因为private在子类中不可见,虽然被继承了,但是看不到,所以也没法重写。
如果希望一个类被禁止继承,就用final修饰他(他其中的方法都自动是final修饰的。但是域则不是final修饰可以进行多次修改),当然如果希望一个方法不能被重写,同样的可以使用final修饰它,这样他就不能被重写了。 为了解决多态性带来的系统开销,早起的java使用final来修饰,使得不能产生多态,现在即时编译器的产生,如果一个方法没有被重写并且很短且经常被调用,那么就可以进行优化,这个过程叫做内联(inlining),例如调用e.getA,就会被转换成e.a域,这会很大程度的改善性能,但是如果被重写,编译器就不知道会做何等复杂操作,所以覆盖的方法不能进行内联
对于基本类型的自动转化和向上转型,java提供的强制类型转换可以向下转型,但是对于继承链中从下而下的转换,如果并无关系则会报ClassCaseException,这时可以用instanceof关键字进行检测,该关键字可以检测出某对象是不是后面类的实例(子类也行) 如果类型转换失败后,java不会产生一个null对象,而是抛出一个异常。
在java中提供protected来描述受保护的方法或者域,他允许子类和同一个包下的类使用该域。但是这有一个弊端,如果这个类进行修改,那么他就要通知所有使用这个类的程序员进行改动,这违背了oop的设计思想,Object.clone就是这样的一个方法
private–>仅对本类可见 public—>对所有类可见 protected–>对子类和同一个包的可见 default–>默认,仅对同一个包中的可见。
这是所有类的超类 使用Object类型可以引用任何类型的对象
在java中只有基本类型不是对象所有的数组类型,不管是对象数组还是基本类型类型数组都扩展了Object类。Object中的equals方法只会比较两个对象是否是同一个,这并没什么意义。一般进行重写比较状态是否相等。 为了防止隐式参数为空的情况,可以调用Objects.equals(object a,object b)方法,如果两个都是空返回true,否则调用String.equals
getClass方法返回一个对象所属的类
父类 instanceof 子类返回的是false,也就是说父类不是子类的实例。 如果equals比较的不是同一个类,则java规范要求equals方法具有以下特点
自反性:x.equals(x)返回true对称性:x.equals(y)和y.equals(x)应该返回相同结果传递性:x.equals(y)和y.equals(z)应该和x.equals(z)是相同结果一致性:对于反复测试x.equals(y)结果应该是一样的对于非空引用x,则x.equals(null)返回false对于一个良好的equals方法应该是下面这样 6. 显示参数命名为otherObject 7. 检测this == otherObject(优化语句,这样比计较域付出的代价小很多) 8. 检测otherObject 是否为null(返回false) 9. 比较this和otherObject是否是同一个类(如果equals在每个子类中都有所改变就用getClass比较)getClass!=otherObject.getClass 返回false,如果在子类中都拥有统一的语义,那么久使用instanceof检测 10.将otherObject转化成other(强转)。 10. 比较域
注意:如果重写方法时,参数不一样,就不是重写,可以用@Override检测
String类重写了hashCode方法 默认的hashcode方法返回的是对象存储的地址(native底层实现) 注意:如果重写了equals方法,那就必须重新定义hashcode方法,已便将对象插入到散列表中
Objects.hashCode方法提供安全的hashCode方法,如果参数为null则会返回0,否则就会调用hashCode方法 Objects.hash(…)可以组合多个散列值
如果存在数组类型的域,就可以使用Arrays.hashCode计算散列码,这个散列码是由数组元素的散列码组成的。
使用getClass().getName()可以使得子类也能调用父类的toString方法。 只要让对象名加上一个字符串就能自动调用他的toString方法 Object类的toString方法是类名加上@和十六进制的散列码
java.lang.getClass() java.lang.getSuperClass()
ArrayList类似数组,但是在删除和添加的时候能够自动调整数组容量。
将new 和列表结合起来,赋值给一个变量,编译器会检查这个变量,并且将类型放到<>里面。 数组列表中管理着一个对象引用数组,如果这个数组的空间用完了,数组列表会自动创建一个更多的数组,并将所有的对象从较小数组拷贝到大数组中 add(E obj)总是返回true,在末尾添加元素obj size返回数组列表元素个数 ensureCapacity(int capacity)确保数组列表在不重新分配内部存储数组的情况下有足够的容量存储给定数量的元素。 trimToSize()将列表存储容量消减到元素个数。
使用get方法获取到第i个元素 set(i,ele) 用lel替换i位置
add(int i,E ele)后移元素,将i位置放为ele E remove(int index)删除指定位置元素,返回被删除元素
可以将自动装箱和拆箱应用到算术运算符,他会先进行拆箱,然后就行计算,再进行装箱。 数值类的包装类都继承自Number类。 自动装箱规范要求boolean,byte,char<=127,介于-128到127之间的short和int被包装到固定的对象,如果用==比较他们一定会相等。 装箱和拆箱是编译器进行的操作,在编译时会将这些操作变成实际的方法。
程序运行时,像查看一个对象字段是很容易的事。而利用反射机制可以查看再编译时还不知道的对象字段。 通过一个类获取到字段,再将指定的字段给get方法,将返回这个字段的值。 而使用set方法,则可以将指定对象的值给覆盖 如果是访问私有字段,会抛出一个非法访问异常。 解决方法是使用setAccessible方法(注释的内容)。 这个方法是AccessibleObject类中的一个方法,他是Field,Method,Constructor类的超类,这个特性是为调试,持久存储和类似机制提供的。如果不允许访问,就会抛出上述异常。 访问可以被模块系统,安全管理器拒绝,但是安全管理器并不常用。
数组在创建的时候会记住存的类型,当存储一个不兼容的内容就会报错。 可以使用反射包提供的Array类动态复制 Array.newInstance(类型,长度)返回一个Object类型,就可以进行一些其他的操作了,比如用System.copyOf进行复制。 这样就会动态创建一个10元素的String数组。 Object类型是可以指向数组的
Class.getCoponentType可以得到Class的类型.
这是创建了一个二维的数组。
java的设计者说过:方法指针是很危险的,所以没有c++中的函数指针,取而代之的是接口和lamda表达式 我们可以使用Method类中的Invoke方法执行指定的方法。 由于方法可以重载,所以Class.getMethod方法可以指定参数 得到了指定的方法Method后可以调用Method的Invoke方法执行,里面有两个参数,实际对象和参数。 若访问的是静态方法,第一个参数可以为null. 提示:由于Invode方法参数和返回值都是Object,编译器就不会检查那么严格,而且这比直接调用方法慢得多,设计者推荐使用接口或者lamda表达式。
公共操作和字段放到超类中
java中,接口不是类,而是对洗完符合这个接口的类的一组需求。 接口中声明的所有方法,都是public方法。 接口没有实例,所以没有实例字段 可以定义常量(自动是public static final),而定义的常量会被实现的子类所获得。
使用接口的原因:java是一种强类型语言,在调用方法时,编译器要检查这个方法是否存在,若实现了某个接口,那么久一定实现了这个方法,而不用担心这个方法调用出错。
在使用Arrays.sort(Object[] a)方法进行排序时,传入的类一定是实现了Compareble接口,不然会报错 若某个类实现了Cloneable就可以使用Object中的clone方法,为类对象创建一个准确副本。
区别:一个类只能继承一个抽象类,但是可以继承多个接口,在编程角度来说,接口弥补了java单继承的缺点,降低了程序的复杂性。
java8中的方法只能是共有的,不能是private,但是在8之后可以使用private,但是必须实现方法体。
default关键字可以在接口中进行方法实现,但是子类会重写这个方法。子类不会强制实现这个方法。
若一个接口有默认方法,子类实现接口,那么调用子类对象.默认方法,将调用接口.默认方法
若两个接口提供了相同的默认方法,编译器会强制要求类实现这个默认方法,。。 若一个接口中有默认方法而另一个接口只是声明了方法,同样会强制实现。
如果不是实现两个接口,只是一个类一个接口,那么会采用类优先的原则。因为这可以和java7有更好的兼容性
callback. 当发生某一时间时候执行某一动作叫做回调 JOptionPane.showMessageDialog(Component,ActionListener) Timer(int Interval,ActionListener) Tookit.getDefaultToolkit.beep()
String类的compareTo方法可以被Arrays.sort方法调用,但是当我们不想用String类写的compareTo按字典排序,而是希望按长度排序,我们就可以使用Arrays.sort的第二种形式sort(Object[],Comparator),这里Comparator是一个接口,需要我们自己实现compare方法,这样就可以按照长度进行排序了。
protected clone方法默认是进行浅拷贝(只是复制这个对象的基本类型,对于对象的引用只是拷贝过来其他对象的引用) 子类只能调用受保护的clone方法来克隆它自己的对象。 Cloneable接口是java提供的少数标记接口之一,也称为记号接口,标记接口不包含任何方法,唯一的作用是允许在类型查询中使用instaceof