1、
在OOP的术语中,我们把Person
称为超类(super class),父类(parent class),基类(base class),把Student
称为子类(subclass),扩展类(extended class)。
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。
继承有个特点,就是子类无法访问父类的private
字段或者private
方法。用protected
修饰的字段可以被子类访问。protected
关键字可以把字段和方法的访问权限控制在继承树内部,一个protected
字段和方法可以被其子类,以及子类的子类所访问。
在Java中,任何class
的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
如果父类没有默认的构造方法,子类就必须显式调用super()
并给出参数以便让编译器定位到父类的一个合适的构造方法。
即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
instanceof
实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null
,那么对任何instanceof
的判断都为false
继承是is关系,组合是has关系
-
继承是面向对象编程的一种强大的代码复用方式;
-
Java只允许单继承,所有类最终的根类是
Object
; -
protected
允许子类访问父类的字段和方法; -
子类的构造方法可以通过
super()
调用父类的构造方法; -
可以安全地向上转型为更抽象的类型;
-
可以强制向下转型,最好借助
instanceof
判断; -
子类和父类的关系是is,has关系不能用继承。
2、
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
多态的特性就是,运行期才能动态决定调用的子类方法。
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
。用final
修饰的方法不能被Override
对于一个类的实例字段,同样可以用final
修饰。用final
修饰的字段在初始化后不能被修改。
-
子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;
-
Java的方法调用总是作用于运行期对象的实际类型,这种行为称为多态;
-
final
修饰符有多种作用:-
final
修饰的方法可以阻止被覆写; -
final
修饰的class可以阻止被继承; -
final
修饰的field必须在创建对象时初始化,随后不可修改。
-
3、
如果一个class
定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract
修饰。
因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。
无法实例化的抽象类有什么用?因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。
尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质就是:
-
上层代码只定义规范(例如:
abstract class Person
); -
不需要子类就可以实现业务逻辑(正常编译);
-
具体的业务逻辑由不同的子类实现,调用者并不关心
-
通过
abstract
定义的方法是抽象方法,它只有定义,没有实现。抽象方法定义了子类必须实现的接口规范; -
定义了抽象方法的class必须被定义为抽象类,从抽象类继承的子类必须实现抽象方法;
-
如果不实现抽象方法,则该子类仍是一个抽象类;
-
面向抽象编程使得调用者只关心抽象方法的定义,不关心子类的具体实现。
4、
如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口:interface
。
所谓interface
,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract
的,所以这两个修饰符不需要写出来(写不写效果都一样)。
当一个具体的class
去实现一个interface
时,需要使用implements
关键字。
在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
Java的接口特指interface
的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。
抽象类和接口的对比如下:
abstract class | interface | |
---|---|---|
继承 | 只能extends一个class | 可以implements多个interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象(Iterable it =(
Collection coll =(
new ArrayList()
)
)
)
实现类可以不必覆写default
方法。default
方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default
方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法(调用时有实现)。
default
方法和抽象类的普通方法是有所不同的。因为interface
没有字段,default
方法无法访问字段,而抽象类的普通方法可以访问实例字段
Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;
接口也是数据类型,适用于向上转型和向下转型;
接口的所有方法都是抽象方法,接口不能定义实例字段;
接口可以定义default
方法(JDK>=1.8)。
5、
Java定义了一种名字空间,称之为包:package
。
在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。
包可以是多层结构,用.
隔开。例如:java.util
。包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。
位于同一个包的类,可以访问包作用域的字段和方法。不用public
、protected
、private
修饰的字段和方法就是包作用域。
import.
*
,表示把这个包下面的所有class
都导入进来(但不包括子包的class
)
import static
的语法,它可以导入可以导入一个类的静态字段和静态方法
Java编译器最终编译出的.class
文件只使用完整类名,因此,在代码中,当编译器遇到一个class
名称时:
-
-
如果是完整类名,就直接根据完整类名查找这个
class
; -
如果是简单类名,按下面的顺序依次查找:
-
查找当前
package
是否存在这个class
; -
查找
import
的包是否包含这个class
; -
查找
java.lang
包是否包含这个class
。
-
-
如果按照上面的规则还无法确定类名,则编译报错。
编写class的时候,编译器会自动帮我们做两个import动作:
-
-
默认自动
import
当前package
的其他class
; -
默认自动
import java.lang.*
。
-
Java内建的package
机制是为了避免class
命名冲突;
JDK的核心类使用java.lang
包,编译器会自动导入;
JDK的其它常用类定义在java.util.*
,java.math.*
,java.text.*
,……;
包名推荐使用倒置的域名,例如org.apache
。
Java内建的访问权限包括public
、protected
、private
和package
权限;
Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
final
修饰符不是访问权限,它可以修饰class
、field
和method
;
一个.java
文件只能包含一个public
类,但可以包含多个非public
类。
6、
classpath
是JVM用到的一个环境变量,它用来指示JVM如何搜索class
。classpath
就是一组目录的集合,它设置的搜索路径与操作系统相关。
在Windows系统上,用;
分隔,带空格的目录用""
括起来;在Linux系统上,用:
分隔。
classpath
的设定方法有两种:
在系统环境变量中设置classpath
环境变量,不推荐;
在启动JVM时设置classpath
变量,推荐。
JVM通过环境变量classpath
决定搜索class
的路径和顺序;
不推荐设置系统环境变量classpath
,始终建议通过-cp
命令传入;
jar包相当于目录,可以包含很多.class
文件,方便下载和使用;
MANIFEST.MF
文件可以提供jar包的信息,如Main-Class
,这样可以直接运行jar包。
7、
.class
文件是JVM看到的最小可执行文件,jar
文件就是class
文件的容器。.jmod模块,jre是Java运行时环境。
如果外部代码想要访问我们的模块中的类,我们必须将其导出(exports 类
)。
JVM自带的标准库rt.jar,从Java 9开始,原有的Java标准库已经由一个单一巨大的rt.jar
分拆成了几十个模块,这些模块以.jmod
扩展名标识。
Java 9引入的模块目的是为了管理依赖;
使用模块可以按需打包JRE;
使用模块对类的访问权限有了进一步限制。
8、
两个字符串比较,必须总是使用equals()
方法。
要忽略大小写比较,使用equalsIgnoreCase()
方法。
contains()
方法的参数是CharSequence
而不是String
,因为CharSequence
是String
的父类。
trim()
方法可以移除字符串首尾空白字符。空白字符包括空格,\t
,\r
,\n
strip()
方法也可以移除字符串首尾空白字符和中文的空格字符\u3000
isEmpty()
和isBlank()
来判断字符串是否为空和空白字符串
join()
用指定的字符串连接字符串数组
静态方法valueOf()要把任意基本类型或引用类型转换为字符串
Integer.parseInt、
Boolean.parseBoolean、
toCharArray()
Integer
有个getInteger(String)
方法,它不是将字符串转换为int
,而是把该字符串对应的系统变量转换为
Integer
new String(char[])
创建新的String
实例时,它并不会直接引用传入的char[]
数组,而是会复制一份,所以,修改外部的char[]
数组不会影响String
实例内部的char[]
数组,因为这是两个不同的数组。
从String
的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用。
Java的String
和char
在内存中总是以Unicode编码表示。
-
-
Java字符串
String
是不可变对象; -
字符串操作不改变原字符串内容,而是返回新字符串;
-
常用的字符串操作:提取子串、查找、替换、大小写转换等;
-
Java使用Unicode编码表示
String
和char
; -
转换编码就是将
String
和byte[]
转换,需要指定编码; -
转换为
byte[]
时,始终优先考虑UTF-8
编码
-
StringBuilder
是可变对象,用来高效拼接字符串;
StringBuilder
可以支持链式操作,实现链式操作的关键是返回实例本身;
StringBuffer
是StringBuilder
的线程安全版本,现在很少使用。
用指定分隔符拼接字符串数组时,使用StringJoiner
或者String.join()
更方便;
用StringJoiner
拼接字符串时,还可以额外附加一个“开头”和“结尾”。
9、
Java核心库提供的包装类型可以把基本类型包装为class,包装类型就是基本类型可以为null
;
自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);
装箱和拆箱会影响执行效率,且拆箱时可能发生NullPointerException
;
包装类型的比较必须使用equals()
;
整数和浮点数的包装类型都继承自Number
;
包装类型提供了大量实用方法。
10、
在Java中,有很多class
的定义都符合这样的规范,称为JavaBean类:
-
- 若干
private
实例字段; - 通过
public
方法来读写实例字段。
- 若干
public Type getXyz()
public void setXyz(Type value)
JavaBean是一种符合命名规范的class
,它通过getter
和setter
来定义属性;
属性是一种通用的叫法,并非Java语法规定;
可以利用IDE快速生成getter
和setter
;
使用Introspector.getBeanInfo()
可以获取属性列表。
11、
enum
类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==
比较
name()返回常量名 判断枚举常量的名字,要始终使用name()方法,绝不能调用toString()
ordinal() 返回定义的常量的顺序,从0开始计数;改变枚举常量定义的顺序就会导致ordinal()
返回值发生变化
枚举类的字段也可以是非final类型,即可以在运行期修改,但是不推荐这样做!
MON(1, "星期一"),给枚举添加常量字段和重写toString()方法
Java使用enum
定义枚举类型,它被编译器编译为final class Xxx extends Enum { … }
;
通过name()
获取常量定义的字符串,注意不要使用toString()
;
通过ordinal()
返回常量定义的顺序(无实质意义);
可以为enum
编写构造方法、字段和方法
enum
的构造方法要声明为private
,字段强烈建议声明为final
;
enum
适合用在switch
语句中。
12、
在Java中,由CPU原生提供的整型最大范围是64位long
型整数。使用long
型整数可以直接通过CPU指令进行计算,速度非常快。
java.math.BigInteger
就是用来表示任意大小的整数。BigInteger
内部用一个int[]
数组来模拟一个非常大的整数(用软件来模拟一个超过long的大整数),缺点是速度比较慢。
BigInteger
用于表示任意大小的整数;
BigInteger
是不变类,并且继承自Number
;
将BigInteger
转换成基本类型时可使用longValueExact()
等方法保证结果准确。
超过了基本类型的范围,转换时将丢失高位信息,结果不准确;使用ValueExact()
等方法超过抛出ArithmeticException异常Infinity
BigDecimal
用于表示精确的小数,常用于财务计算;
比较BigDecimal
的值是否相等,必须使用compareTo()
而不能使用equals()
。
Math
会尽量针对平台优化计算速度
StrictMath解决由于浮点数计算存在误差,不同的平台(例如x86和ARM)计算的结果可能不一致(指误差不同)
Random
实例时,如果不给定种子,就使用系统当前时间戳作为种子,因此每次运行时,种子不同,得到的伪随机数序列就不同
SecureRandom
的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。