Java 跟 C# 有些地方很相似,要注意串联。
1 关于概念
Java 程序实现的具体流程:
- JVM(Java Virtual Machine),即 java虚拟机,是java平台无关性实现的关键。
- JDK(Java Development Kit),即Java 语言的 软件开发工具包,开发阶段软件的编译与执行全依赖它了,其中有两个重要的组件(命令):
javac
:编译器(.java
=>.class
)java
:运行编译后的 Java 程序 (.class
)
- 且JDK 包含:
- JRE
- 开发工具集(如
Javac
编译工具)
- JRE(Java Runtime Environment),即 java 运行环境,其内容包含:
- JVM
- Java 核心类库和支持文件(JavaSE 标准类库)
JRE 与 JDK 的区别,前者面向使用者,后者面向开发者
Java平台有三个,分别是:
JavaSE
:java 标准版,多用于桌面程序JavaEE
:java 企业版,包含JavaSE
,多用于 Web 程序JavaME
:java 微型版,多用于移动设备
2 关于数据类型
Java 的数据类型分为 基本数据类型 和 引用数据类型。
其中含有8种基本数据类型,它们又分为:
- 数值型,数值型又分为:
- 整数类型(包含正负):
byte
(内存长度 8 bit)short
(内存长度 16 bit)int
(内存长度 32 bit)long
(内存长度 64 bit)
- 浮点类型(包含正负):
float
(内存长度 32 bit)double
(内存长度 64 bit)
- 整数类型(包含正负):
- 字符型(
char
):使用 单引号,内存长度 8 bit - 布尔型 (
boolean
):内存长度 8 bit
整数类型的 八进制 描述方式是以 0
开头的,十六进制 描述方式是以 0X
或 0x
开头的。
因为计算机并不能实际表示某些浮点数(如
0.1
),所以浮点数之间的直接比较是不靠谱的,它们的比较应该利用其差值小于某个临界值来判断,如Math.abs(x-0.1) < 0.00001
2.1 关于字面量
当赋值字面量的结尾添加 l
或 L
字母的时候,该数值表示一个 long
类型,此时如果赋值给一个 int
类型的变量就需要强转换,如
int int1 = (int) 123L;
即声明 long
类型时,最好在字面量后添加后缀字母,同理的还有 float
类型字面量结尾添加 f
或 F
,double
类型字面量结尾添加 d
或 D
。
不过此时要注意的是赋值时,浮点类型的字面量默认为 double
,即 float float1 = 1.2;
这是会报错的,因为上述代码的意思是将一个 float
类型的变量地址指向 double
类型的,所以此时就要显式的转换类型,或者直接修改字面量,如 float float1 = 1.2f;
2.2 关于数值的比较及转化
有时候整数尽管可以隐式转化为浮点数,但依旧可能会发生 精度缺失 ,比如:
long long1 = 12345678972555555l;
double double1 = long1;
与不同的整数类型比较一样,浮点数与整数进行比较,只要 数值相同,就会返回 true
,比如:
float f = 5;
long l = 5;
System.out.println(f == l);
关于引用类型的比较,通常会重写 equals
方法,如 String
类型,而当判断两个 String
类型的变量是否相同时,还需要检测调用 equals
方法的变量是否为 null
,此时可以使用更简洁的方法 Objects.equals
:
Objects.equals(String a, String b);
3 关于对象
3.1 关于对象的创建
一个对象的创建包括 声明对象 和 实例化对象。
声明对象:在内存 栈空间 里开辟一个区域,但还没指向实际内容
实例化对象:在内存的 堆空间 里开辟一个区域,在将 栈空间 的内存地址指向这个区域。
3.2 关于修饰符
3.2.1 关于访问修饰符
public
:最开放protected
:在同包的任意类中都可以访问,在不同包就只能是其继承类可以访问- 默认值:类、字段或方法在声明的时候并没有使用修饰符,此时为包作用域,即只能在同包中访问
private
:只能在本类中访问
不确定是否需要声明
public
时,就不声明public
,应该尽量地减少暴露对外的方法
3.2.2 关于其它修饰符
final
修饰符:如同名字的意思一样,当 final
修饰符修饰方法内的局部变量时,此时就像声明一个常量,无法修改,当该变量是一个引用类型时,该变量的内存指向地址无法改变。假如静态属性使用了 final
修饰,此时不在声明中赋值或者在 **静态代码块**中赋值就会报错
3.3 关于构造函数
- 先初始化字段,再执行构造函数
- 构造函数没有返回类型(即
void
也不能添加),只要类声明了一个构造函数,无论是否带参,系统默认的无参构造函数就会自动取消 - 重载构造函数之间的相互调用可以使用
this
关键字,而且该语句必须放置在该方法块中的第一行 - 可以使用
super
调用父类指定构造函数,而且该语句必须放置在该方法块中的第一行,所以super
以及this
是不能共存的,同时super
在子类的方法中调用时指明的是一个父类实例。 - 当构造函数的修饰符是
private
的时候,说明只能在本类中使用该构造函数来创建对象,在其它类中无法调用得了该构造函数
3.4 关于构造代码块
在类的内部可以添加 花括号{}
,来创建 构造代码块,使用 static
修饰花括号即可创建 静态代码块,其中在一个对象的创建过程中,静态代码块的执行顺序最早,然后到构造代码块,最后是构造函数。
假如静态属性使用了
final
修饰,此时不在声明中赋值或者在 **静态代码块**中赋值就会报错
3.5 关于继承
继承对象的初始化顺序是:
父类静态构造方法块 =>
子类静态构造代码块 =>
父类构造方法块 =>
父类构造函数 =>
子类构造代码块 =>
子类构造函数
子类的创建必须调用父类的一个构造函数,默认不显式声明的状况下(使用 super
)是调用父类的无参构造函数,如果此时父类没有无参构造函数,而且子类的构造函数中有没有显式地调用父类的其它构造函数,就会报错。
3.6 关于重写与重载
方法重载条件:
- 同一个类中
- 方法名相同,参数列表不同(顺序,个数,类型)
- 与返回值、访问修饰符,参数名称无关
原则上,重载的方法应该完成相同的功能,尽量不要依靠参数顺序的不同实现重载,而且重载的方法的返回值尽量相同
方法重写条件:
- 在继承的子类中
- 方法名,参数列表相同,返回值相同或是子类
- 访问修饰符范围不能比父类小
- 与参数名无关
3.7 关于向上转型与向下转型
- 向上转型(隐式转型,自动转型):父类引用指向子类实例,此时该父类变量可以调用子类重写父类的方法以及父类的派生的方法,无法调用子类的特有方法。
- 向下转型(强制转型,自动转型):子类引用指向父类实例,必须要显式转换,此时可以调用子类特有的方法,要注意想要 实现向下转型的前提是先向上转型,也就是说该父类实例是由向上转型之后得出的结果,正常地声明一个父类实例,是不能实现向下转型的(不然如果父类Fruit实例出来的对象是Orange,Orange当然不能强制转成Apple,所以说父类只有该子类对应的实例才能强转)。
- 判断一个能否向下转型,最简单的方法就是使用
instanceof
运算符(注意是运算符,返回是布尔值)
引用变量的声明类型与其实际类型可能不同,实际对象的方法调用总是指向实际类型,因为 Java 的方法调用是 基于运行时 的动态调用;
而多态是指针对某个类型方法的调用,其真正执行的方法取决于运行时的实际类型的方法
3.8 关于抽象(abstract
)
面向抽象编程的本质是:
- 上层代码只负责定义规范
- 不需要子类也可以实现业务逻辑
- 具体的业务逻辑由不同的子类实现,调用者并不关心
使用了 abstract
修饰符的类叫做抽象类,抽象类 不能直接使用 new
的方式实例化,可以先让子类重写了它的抽象方法后实例化子类,在进行向上转型,从而达到实例化的效果。
抽象方法不允许有方法体,子类必须重写该方法,否则子类也要同时声明为抽象类。
3.9 接口
当一个抽象内全是抽象方法的时候,就可以把这个抽象类转换为接口,接口定义纯抽象规范,它定义了某一批类所需要遵守的规范,默认访问修饰符 public
。
在JDK1.8中,接口可以包含常量,静态方法,使用 defualt
关键字还可以使接口带上方法体,当接口的默认方法与实现类的方法同名时使用实现类的方法,但如果是属性值同名就会报错了。
接口与抽象类的区别
接口 | 抽象类 | |
---|---|---|
继承 | 可以 implements 多个接口 | 只能继承一个接口 |
字段 | 可以声明实例字段,默认自带 final 修饰符,因此有时会在常量类中内部定义接口,并声明一些字段,其好处具体可以理解成一个常量类中的进一步逻辑细分 | 不能声明实例字段,只能声明 final 常量,默认声明字段带有 final 修饰符 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以使用 defualt 修饰符定义抽象方法,以达到类似定义非抽象方法的效果 |
3.10 总结代码
写了一下测试代码总结一下:
父类:
/**
*
* 测试对象初始化的顺序
* 关于继承对象的重写,编译器只会检查 final 修饰的方法,静态字段和 final 修饰的字段尽管重写不了但不会报错
* 假如在子类中重新声明一个一模一样的字段,则重新命名的字段与父类的字段再无关系,如果此时向上转型,访问得到的将是父类声明的字段
* 即引用变量的声明类型与其实际类型可能不同,实际对象的方法调用总是实际类型,所以 Java 的实例方法调用是基于运行时的动态调用,这种动态调用叫做 动态
* 多态是指针对某个类型方法的调用,其真正执行的方法取决于运行时的实际类型的方法
*
*/
public class AboutExtendsDemo {
public static void main(String[] args) {
System.out.println("输出继承对象的初始化顺序:");
// 输出:父类静态构造方法块->子类静态构造方法块->父类构造方法块->父类构造函数->子类构造方法块->子类构造函数
Son son = new Son();
Father sonToFather = son; // 向上转型
System.out.println(" ");
System.out.println("====================================================================================");
System.out.println(" ");
System.out.println("关于字段与方法的重写:");
System.out.println(" ");
System.out.println("子类的静态字段:" + Son.staticData); // 子类静态字段
System.out.println("子类的实例字段(在子类重新声明过):" + son.instanceData); // 子类实例字段
System.out.println("子类的实例字段(没在子类重新声明过):" + son.instanceData2); // 子类实例字段
System.out.println("子类的 final 静态字段:" + Son.finalStaticData); // 子类静态 final 字段
System.out.println("子类的静态方法:" + Son.staticMethod()); // 子类静态方法
System.out.println("子类的实例方法:" + son.instanceMethod()); // 子类实例方法
System.out.println("子类的 final 静态方法:" + Son.finalStaticMethod()); // 父类 final 静态方法
System.out.println("子类的 final 实例方法:" + son.finalInstanceMethod()); // 父类 final 实例方法
System.out.println(" ");
System.out.println("====================================================================================");
System.out.println(" ");
System.out.println("向上转型后");
System.out.println("sonToFather的静态字段:" + sonToFather.staticData); // 父类静态字段
System.out.println("sonToFather的实例字段(在子类重新声明过):" + sonToFather.instanceData); // 父类类实例字段
System.out.println("sonToFather的实例字段(没在子类重新声明过):" + sonToFather.instanceData2); // 子类类实例字段
System.out.println("sonToFather的 final 静态字段:" + sonToFather.finalStaticData); // 父类静态 final 字段
System.out.println("sonToFather的静态方法:" + sonToFather.staticMethod()); // 父类静态方法
System.out.println("sonToFather的实例方法:" + son.instanceMethod()); // 子类实例方法
System.out.println("sonToFather的 final 静态方法:" + sonToFather.finalStaticMethod()); // 父类 final 静态方法
System.out.println("sonToFather的 final 实例方法:" + sonToFather.finalInstanceMethod()); // 父类 final 实例方法
System.out.println(" ");
System.out.println("====================================================================================");
System.out.println(" ");
sonToFather.instanceData = "修改过的实例字段";
System.out.println("修改 sonToFather 的 instanceData 为 123,此时的 son 变量的 instanceData 是:" + son.instanceData);
// 子类实例字段
sonToFather.instanceData2 = "修改过的实例字段";
System.out.println("修改 sonToFather 的 instanceData2 为 456,此时的 son 变量的 instanceData 是:" + son.instanceData2);
// 修改过的实例字段
System.out.println(" ");
System.out.println("====================================================================================");
System.out.println(" ");
if (sonToFather instanceof Son) {
son = (Son) sonToFather;
System.out.println("向下转型后调用" + son.sonMethod());
}
}
}
class Father {
{
System.out.print("父类构造方法块->");
}
static {
System.out.print("父类静态构造方法块->");
}
Father() {
System.out.print("父类构造函数->");
}
// 父类静态字段,可以不赋值
static String staticData = "父类静态字段";
// 子类实例字段,可以不赋值,在子类中重新声明该字段
String instanceData = "父类实例字段";
// 子类实例字段,可以不赋值,不在在子类中重新声明该字段
String instanceData2 = "父类实例字段";
// 使用了final修饰的静态字段,假如不在声明中赋值或者在静态代码块中赋值就会报错
final static String finalStaticData = "父类静态 final 字段";
// 使用了final修饰的实例字段,假如不在声明中赋值或者在父类构造方法块或构造函数中赋值就会报错
final String finalInstanceData = "父类实例 final 字段";
// 父类实例方法
static String staticMethod() {
return "父类静态方法";
}
// 父类实例方法
String instanceMethod() {
return "父类实例方法";
}
// 父类 final 修饰的方法,final 修饰符比 返回类型 要靠前
final String finalInstanceMethod() {
return "父类 final 实例方法";
}
// 父类 final 修饰的静态方法,final 修饰符比 返回类型 要靠前
static final String finalStaticMethod() {
return "父类 final 静态方法";
}
}
class Son extends Father {
{
System.out.print("子类构造方法块->");
}
static {
System.out.print("子类静态构造方法块->");
}
Son() {
instanceData2 = "子类实例字段"; // 在子类没有重新声明,而是在构造函数中修改
System.out.print("子类构造函数");
}
static String staticData = "子类静态字段";
String instanceData = "子类实例字段";
final static String finalStaticData = "子类静态 final 字段";
final String finalInstanceData = "子类实例 final 字段";
static String staticMethod() {
return "子类静态方法";
}
String instanceMethod() {
return "子类实例方法";
}
String sonMethod() {
return "子类独有的方法";
}
// final void finalInstanceMethod() {
// System.out.println("子类 final 实例方法");
// }
// static final void finalStaticMethod() {
// System.out.println("子类 final 静态方法");
// }
}
杂
-
&&
运算符与&
运算符的区别,&&
是短路运算符 -
数组是对象,对象的属性拥有默认值(局部变量没有默认值),如整型默认值为
0
,对象默认值为null
,String
类型也是对象所以默认值也是null
,浮点默认值0.0
,布尔型默认值false