面向对象
类
可以把类看作函数的容器,或者表示自定义的数据类型。
修饰符
public:可以修饰类、类方法、类变量、实例变量、实例方法、构造方法,表示可以被外部访问。
private:可以修饰类、类方法、类变量、实例变量、实例方法、构造方法,表示不可以被外部访问,只能在类的内部使用
static:修饰类变量和类方法,也可以修饰内部类
final:修饰类变量,实例变量,表示只能被赋值一次。
protect:可以被同一个包内和子类访问。 包
private < 默认 < protected < public 字符编码
构造方法
构造方法体现了多态
1 | public Point() { //构造器A |
构造方法可以有多个,但是和其他方法不同,构造方法特殊的地方:
名称是固定的,与类名相同。
没有返回值,也不能有返回值。构造方法隐含的返回值是实例本身。
构造器A调用了构造器B,其中this必须放在第一行。构造方法是用于初始化对象,如果要调用别的构造方法,就先调用别的,然后再根据自己需求进行更改,而如果自己先初始化了一部分,再调别的, 自己的修改将可能会被覆盖。
1 | this(0, 0); |
默认构造方法
每一个类至少都要有一个构造方法,java编译器会自动生成一个默认的构造方法,也没有具体操作。但是一旦定义了新的构造方法,java就不会再自动生成默认的构造方法。例:如果只声明上述demo中的B构造方法,那么下面语句
1 | Point p = new Point(); |
就会报错,因为找不到无参构造器。
私有构造方法
构造方法可以用private修饰,用于以下几种场景:
不能创建类对象,类只能被静态访问,如Math和Array类的构造方法就是私有的。
能创建类的对象,但只能被类的静态方法调用,在一些单例场景下,对象是通过静态方法获取的,静态方法来调用私有构造器创建对象,方法中会进行判断,如果已经创建过该对象,就重用这个对象。
只是用来被其他多个构造器调用,减少重复代码。类似于将重复代码提出成为一个方法。
super
子类可以通过super调用父类的构造方法
1 | public class Base { |
包
任何语言编译时都有一个相同的问题就是命名冲突,包就是用来解决这一问题。比如String类的完全限定名称就是java.lang.String
java API中所以类和接口都位于java包货javax下,java是标准包,javax是扩展包。
定义类的时候,应该先用关键字package声明其包名,如下:
1 | package org.service; |
jar包
为了方便使用第三方代码,也是为了方便我们写的代码给其他人使用,程序大多都有打包的概念,将多个编译后的文件打包未一个文件,方便其他程序调用。
在java中,编译后的多个包的java class文件可以打包成一个文件,然后运行一下命令:
1 | jar -cvf<包名>.jar <最上层包名> |
例如对前面的类打包,如果inde.class位于E:\bin\org\service\index.class,则可以到目录E:\bin下然后运行:
1 | jar -cvf index.jar org |
index.jar就是jar包,其实就是一个压缩文件。
封装继承多态
封装
封装就是隐藏实现细节,提供简化接口。封装是程序设计的第一原则。
继承
使用继承一方面可以复用代码,公共的属性和方法可以放在父类中,子类只关心子类所特有的就可以了;另一方面,不同子类的对象可以更为方便的统一处理。一个类只能继承一个父类,但是可以实现多个接口。接口和抽象类
根父类Object
任何类都有一个隐藏父类object,Object没有定义属性,但是定义了一些方法。如toString(),但我们一般会对此方法重写。
重写
在子类中对父类的方法进行重写,调用子类此方法时,是调用子类新重写的方法体,而不是父类的。
1 |
|
重写和重载
参数签名:参数个数、类型、顺序。
重写 | 重载 |
---|---|
函数名相同 | 函数名相同 |
子类重写父类的方法 | 同一个类内 |
参数签名要相同 | 参数签名不同 |
父子类型转换
向上转型:子类型的对象赋值给父类型的引用变量。
向下转型:父类型的变量赋值给子类型的引用变量。
instanceof可以检测给定的父类变量是不是某子类对象。
1 | Base b = new Child(); //向上转型 |
继承权限
修饰符中,protected表示可以被同一个包中的其他类访问,也可以被子类访问。
子类重写父类方法时,不能降低父类方法的可见性。即父类是public的时候子类只能是public;父类如果是protected,子类可以是protected或者public,即子类只能升级父类方法的可见性。
继承反应的是”is-a”的关系,子类是属于父类的,子类必须能支持父类所有对外的行为。子类减少了父类的可见性,破坏了”is-a”的关系,子类可以增加父类的行为,所以可以提升可见性。
修饰符final可以防止继承。
继承破坏封装
子类和父类直接是细节依赖,子类扩展父类,仅仅知道父类能做什么是不够的,还要知道父类是怎么做的,而且父类的实现细节也不能随意修改,否则可能会影响子类。
如何避免?
避免使用继承
正确使用继承
避免使用继承
使用final关键字
优先使用组合而非继承。组合即是Child类不继承Base,在类中声明Base调用其方法。
使用接口和抽象类
多态
父类Shape 子类Circle、Line 子类ArrowLine
变量shape可以引用任何Shape子类类型的对象,这叫多态,即一种类型的变量可以引用多种实际类型的对象。
对于shape变量,他有两个类型:类型Shape称为静态类型;类型Circle、Line、ArrowLine成为动态类型。调用方法的时候,会进行动态绑定。
多态和动态绑定时计算机程序的一种重要思维方式,使得操作对象的程序不需要关注对象的实际类型,从而可以统一处理不同对象,但又能实现对每个对象的特有行为。
静态绑定
若Child类和Parent类都有某同名静态方法和静态变量,那么类外变量访问时要看访问变量的静态类型:静态类型是父类,则访问父类的静态方法和静态变量;如果静态类型是子类,则访问子类的静态方法和变量。看一个例子,这是父类代码:
1 | public class Base { |
定义了一个public静态变量s,一个public实例变量m,一个静态方法test。这是子类代码:
1 | public class Child extends Base { |
子类定义了和父类重名的方法和变量。对于一个子类对象来说,他就有了两份变量和方法,在子类内部访问的时候,访问的都是子类的方法和变量,用super来访问父类的。以下是外部访问代码:
1 | public static void main(String[] args) { |
实例变量、静态变量、静态方法、private方法,都是静态绑定的。
重载
重载是同一个类中,声明了同名函数,但是函数的参数类型或者个数不同。当调用该函数时,会匹配传入参数于之相对于的函数主体。
动态绑定
动态绑定是指在”执行期间”判断所引用的实际类型类型,根据其实际类型调用其相应的方法,new的是谁就去找谁的方法,就是动态绑定。
注:当有多个重名函数的时候,在决定调用哪个函数的过程中,首先是按照参数类型进行匹配的,即在所有重载或重写函数的版本中找到最匹配的,然后才看变量的动态类型,进行动态绑定。
例:
1 | public class Base { |
父类声明了两个输入类型为int的函数,子类为两个long,若调用sum函数时的两个参数是int类型,输出为base_int_int。
父类声明输入类型为int a和long b,子类为两个long,调用函数输入为两个int类型时输出base_int_long。此时父类子类的两个方法类型都不匹配,但是调用的是父类的代码,因为父类的更匹配一些。
父类和子类都是int a和long b,调用函数仍然是输入两个int类型的参数,此时父子函数类型都是一样的,则进行动态绑定,调用子类的函数,输出child_int_long。
泛型
泛型将接口的概念进一步扩展,就是广泛的类型。
1 | public class Pair<T> { |
1 | public class Pair<U, V> { |
泛型参数类型到底是什么呢?其实在java编辑器将java源代码转化为.class文件时,就会将泛型代码转化为普通的非泛型代码,将T、U、和V类型擦除,替换成Object,强调,java泛型是通过擦除实现的。那为什么不直接用Object而选择泛型呢?
更好的安全性
更好的可读性
编译无误,运行报错
使用泛型是,在程序调用中有类型错误时编译器是会有提示,但是Object不会有提示
编译报错
1 | Pair pair = new Pair("老马" ,1); |
1 | Pair<String,Integer> pair = new Pair<>("老马" ,1); |
泛型讲数据结构和算法与数据类型相对分离,使得一套数据结构和算法可以用于各种数据类型,而且保证数据类型安全,提高可读性。
通配符
<?>
表示所有类型
<? extends E>
表示E和E的子类型
<? super E>
表示E和E的父类型
细节和局限性
使用泛型类、方法和接口
基本类型不能用于实例化类型参数
运行时类型信息不适用于泛型(不易理解)
类型擦除可能会引发一些冲突(不易理解)
定义泛型类、方法和接口
不能通过类型参数创造对象
泛型类类型参数不能用于静态变量和方法
泛型和数组
不能创建泛型数组,因为泛型数组类型可能会和复制内容泛型类型不匹配,引起报错。
接口和抽象类
接口
使用interface声明接口,修饰符一般都是public。接口内声明的方法都没有定义方法体,java8之前接口内不能实现方法。接口方法不需要加修饰符,加不加都相当于是public abstract
一个类可以实现多个接口
一个类可以继承类并实现接口
一个接口可以继承多个接口
1 | public class Test implements Interface1, Interface2 {} //一个类可以实现多个接口 |
接口中可以定义变量,修饰符是public static final,可写可不写都是。变量可以通过”接口名.变量名”调用,例Inferface1.a。
instanceof用来判断一个对象是否实现了某接口,p instanceof Inferface01 //返回值是Boolean类型值
使用接口:
1 | MyComparable p1 = new Point(2,3); |
Point类实现了MyComparable接口,所有可以声明p1和p2是MyComparable类型的变量,引用Point类型的对象。此时p1和p2都可以调用MyComparable 接口的方法也只能调用它的方法。
java8和java9中对接口做了一些增强。java8
抽象类
抽象类是相对于具体类而言的,一般来讲,具体类有直接对应的对象,而抽象类一般是用来继承。抽象类不让创建对象。
抽象类和抽象方法需要用abstract关键词声明。
1 | public abstract class Shape{ |
接口 | 抽象类 |
---|---|
不能用于创建对象 | 不能用于创建对象 |
都是抽象方法 | 可以是抽象方法,也可以写方法体 |
不能定义实例变量 | 可以定义实例变量 |
可以实现多个接口 | 只能继承一个抽象类 |
抽象类和接口是相互配合,而不是代替,他们经常一起使用,接口声明能力,抽象类实现默认实现,实现全部或者部分方法,一个接口经常有一个对应的抽象类。
枚举(了解)
枚举也是一种属性类型,他的取值是有限的可以枚举出来的,比如一年四季,一周七天。
1 | public enum Size { |
所有枚举类型都有name()
方法和toString()
方法返回内容一样。枚举的equals和==的返回值是一样的。
枚举类型有一个int ordinal()
方法,返回枚举值在声明时的顺序,从0开始。
compareTo方法可以比较两个枚举类型的位置之差。
常用基础类
包装类
基本数据类型 | byte | short | int | long | float | double | char | boolean |
---|---|---|---|---|---|---|---|---|
对应包装类 | Byte | Short | Integer | Long | Float | Double | Character | Boolean |
8个包装类都实现了Serializable , Comparable 接口
自动拆装箱
装箱:将基本类型转化为包装类型的过程叫装箱。
拆箱:将包装类型转化为基本类型的过程叫拆箱。
JDK1.5之前没有自动装箱
1 | Integer integer = new Integer(10); //手动装箱1 |
JDK1.5之后有自动装箱
1 | Integer one = 1; //自动装箱 |
其实自动拆装箱和手动拆装箱本质上是一样的。
基本用法
equals
包装类都重写了equals方法,他的比较运算结果和==的结果是一样的。
hashcode
hashcode返回一个对象的哈希值,是一个int类型的数,一个对象的哈希值不能改变,相同对象的哈希值必须一样。
hashcode和equals的联系:如果两个对象equals为true,则hashcode也必须一样。反之则不要求,如果equals返回值为false时,hashcode可以一样也可以不一样。
String的hashcode是每个字符*31的n-1次方相加,用31的(n-1)次方可能有两个原因:1.方便生成更分散的散列。2.31*h=32*h-h,按位计算和减法计算代替乘法,加快计算速率。
comparable
包装类都继承了comparable接口,有一个compareTo的方法,当比较式在小于、等于、大于参数是对应返回-1、0、1。注:0.1和0.1*0.1比较结果并不为0。
String、StringBuffer和StringBuilder
String | Stringbuffer | StringBuilder |
---|---|---|
不可变 | 可变 | 可变 |
线程安全 | 线程不安全 | |
多线程操作字符串 | 单线程操作字符串 |
String
String是被final修饰的,不能被继承,也不能被修改,JDK9之前是char[],JDK9之后是byte[],String创建之后不能修改,底层方法区维护了一个字符串常量池,实现共享。都是用unicode编码,一个字符占两个字节。
StringBuilder
StringBuilder是可变类和线程不安全字符操作类,字符串操作不会产生新的对象,每个StringBuilder对象都有一个缓冲区容量,超过该容量时会增加容量。效率高
StringBuffer
StringBuffer是可变类和线程安全字符操作类,字符串操作不会产生新的对象,每个StringBuffer对象都有一个缓冲区容量,超过该容量时会增加容量。效率低
可变类
有三个构造方法StringBuffer()``,``StringBuffer(int capacity)``,``StringBuffer(String ``str)
StringBuffer()
初始容量为16,StringBuffer(int capacity)
初始容量是capacity,StringBuffer(String ``str)
初始容量是str.length()+16
扩容:当容量满后,计划扩容2*n+2,如果追加的长度仍然超出,则扩容为n+count,count为追加长度。
总结
如果修改次数少用String
如果修改次数多,且单线程操作用StringBuilder
如果修改次数多,且多线程操作用StringBuffer
StringBuffer和StringBuilder的区别就在于StringBuffer的操作使用synchronized关键字加了锁,是线程安全的。
System类
exit退出程序
arraycopy复制数组
currentTimeMillens返回当期时间毫秒数
System.gc() gc运行垃圾回收机制 垃圾收集器
Math类
math的都是静态方法
abs | 返回绝对值 | pow(2,4) | 返回 2的4次方 |
---|---|---|---|
min/max | 返回最大值/最小值 | ceil | 向上取整double类型 |
random | [0,1)随机数 | floor | 向下取整 |
sqrt | 求开方 | round | 四舍五入 |
Array类
Arrays.toString(arr) | 显示数组 | Arrays.sort(arr) | 冒泡排序 |
---|---|---|---|
Array.copyOf(arr,arr.length) | 拷贝arr.length到新数组中,如果大于arr,则新数组加空 | fill(arr,22) | 用22替换所有元素 |
Arrays.toString(arr,new Comparator()) | 可重写排序,定制排序 | Array.asList(arr) | 转化为List集合 |
binarySearch(arr,1) | 二分法查找有序数组,如果不存在返回-(应该存在的位置+1) | equals | 重写了 |
大数据类
BigInteger
函数 | 描述 | 函数 | 描述 |
---|---|---|---|
BigInteger.add(a) | 加 | BigInteger.multiply(a) | 乘 |
BigInteger.subtract(a) | 减 | BigInteger.divide(a) | 除 |
BigDecimal
函数 | 描述 | 函数 | 描述 |
---|---|---|---|
BigDecimal.add(a) | 加 | BigDecimal..multiply(a) | 乘 |
BigDecimal.subtract(a) | 减 | BigDecimal..divide(a,BigDecimal.ROUND_CEILING) | 除(如果有无限循环,将保留分子精度) |
日期类
第一代日期类
java.util.Date
1 | Date d1 = new Date(); //获取当前时间 美国格式 |
第二代日期类
java.util.Calendar
Calendar是抽象类,构造器是私有的
通过getInstance()获取实例
Calendar的问题
可变性
偏移性:月份是0开始的
格式化:格式化只对Date有用,对Calendar没用
线程不安全,不能处理闰秒
第三代日期类
java.time.*
LocalDate包含日期,LocalTime包含时间,LocalDateTime包含日期+时间
格式化日期
1 | DateTimeFormat dtf= DateTimeFormatter.ofPattern(格式); |