面向对象
补充:类加载过程
java文件经过编译成为class文件,class文件加载到内存中。
class文件在加载到内存的过程中分为:
加载 — 将 class 文件转换为二进制的字节码
校验 — 检查字节码的安全性
准备 — 将静态变量放到方法区并分配空间,然后标记一个默认值,标记的默认值可能会被舍弃
解析 — 常量符号引用的替换(暂时用不上)
初始化 — 执行静态变量的赋值以及静态代码块,这个没有特定的先后顺序,代码谁在上先执行谁。
- 情况一
class A{
// 先将静态变量i放入方法区,然后标记一个默认值0,检查变量i是否具有具体的初始化值.
// 有初始化值3,舍弃标记值将3作为初始化值设置进去。然后执行静态代码块,将i的值改为5
static int i = 3;
static {
i = 5;
}
}
- 情况二
class A{
// 在准备阶段,先将静态变量i放入方法区,并且标记默认值为0.到了初始化阶段,先执行静态代码块。将i的标记值由0改成5
// 然后执行i的赋值,发现有初始化值3,所以抛弃标记值5,将3作为最终的值设置进去。
static {
i = 5;
}
static int i = 3;
}
- 情况三
class A{
// 在准备阶段,先将静态变量i放入方法区,并且标记默认值为0.到了初始化阶段,先执行静态代码块。将i的标记值由0改成5
// 然后执行i的赋值,发现没有初始化值,把标记值5作为最终的值设置进去。
static {
i = 5;
}
static int i;
}
- 情况四
class A{
// 在准备阶段,先将静态变量i放入方法区,并且标记默认值为0.到了初始化阶段,发现i没有初始化值,然后把默认的标记值0赋值给i
// 然后执行 i += 5,结果是5
static int i;
static {
i += 5;
}
}
- 情况五
class A{
// 静态变量在没有执行赋值语句之前(标记值状态),不允许直接运算
// 这种情况下先执行静态代码块,i还是在标记值状态,所以不能运算,编译失败
static {
i += 5;
}
static int i;
}
练习:
class SDemo{
// 准备阶段:加载sd标记值为null,i标记值是0,j标记值是0.
// 初始化阶段: sd=new SDemo();就要执行构造方法,会将i的标记值变为1,j的标记值变为1.
// 然后执行int i = 5,舍弃i的标记值1,i赋值为5
// 然后执行 int j;由于j没有初始化值,所以将标记值1赋值给j。
// 所以结果是 5 1
static SDemo sd = new SDemo();
static int i = 5;
static int j;
public SDemo(){
i++;
j++;
}
}
1 final
final 是 java 中的一个关键字,可以修饰数据,方法和类
1.1 final修饰数据
final修饰的数据称之为常量,定义之后不能修改。final修饰基本数据类型,其值不能修改。final修饰引用数据类型,其地址不能修改。
- final作为常量不能修改
// i目前被final修饰,这个值就是常量,不能修改
final int i;
i = 8;
- final的常量可以作为参数传入方法中,在方法中可以修改
public static void main(String[] args) {
// i目前被final修饰,这个值就是常量,不能修改
final int i;
i = 8;
add(i);
System.out.println(i);
}
// final作为常量参数传入方法,这个参数可以改变。但是原来的实参的值并没有发生改变
public static void add(int i){
i++;
System.out.println(i);
}
- final修饰引用数据类型,地址不能改变
// final修饰的对象的地址不能改变
final int[] arr = {3,4,5};
System.out.println(arr);
// 数组里面的值是可以改变的
arr[0] = 8;
System.out.println(Arrays.toString(arr));
arr = new int[3];
- final修饰的引用数据类型可以作为参数传入方法中,在方法中可以改变地址。
// final修饰的对象的地址不能改变
final int[] arr = {3,4,5};
System.out.println(arr);
// arr[0] = 8;
// System.out.println(Arrays.toString(arr));
// arr = new int[3];
// 能不能扩容? 不能扩容,因为扩容也是创建一个新的数组,地址还是会发生改变
// arr = Arrays.copyOf(arr,arr.length * 2);
changeRef(arr);
System.out.println(arr);
}
public static void changeRef(int[] arr1){
arr1 = Arrays.copyOf(arr1,arr1.length * 2);
}
- final可以修饰形参,因为形参本质上就是局部变量,下面的写法会报错
// final修饰的对象的地址不能改变
final int[] arr = {3,4,5};
System.out.println(arr);
// arr[0] = 8;
// System.out.println(Arrays.toString(arr));
// arr = new int[3];
// 能不能扩容? 不能扩容,因为扩容也是创建一个新的数组,地址还是会发生改变
// arr = Arrays.copyOf(arr,arr.length * 2);
changeRef(arr);
System.out.println(arr);
}
public static void changeRef(final int[] arr1){
// 这里是要给final修饰的arr1的地址改变,所以不行
arr1 = Arrays.copyOf(arr1,arr1.length * 2);
}
- final修饰的成员变量需要在对象创建完成前赋值。
class Student{
// 成员变量在堆中,有默认值.如果编译成功,i的值永远都是0。
// final修饰的成员变量要求在对象创建完成之前给值
final int i;
int j;
// 只能在构造代码块或者构造方法中赋值,不能同时赋值。因为同时赋值相当于要修改final修饰的常量的值。
// {
// i = 7;
// }
public Student(){
this.i = 9;
}
}
- final修饰的静态变量称之为静态常量,要求在类加载完成之前赋值
class Person{
// 静态常量要求在类加载完成之前给值
// static final int i = 10;
static final int i;
static {
i = 6;
}
}
总结:final修饰的数据称之为常量,定义之后不能修改。对于基本数据类型而言指的是值不能改变,对于引用数据类型而言,指的是地址不能改变,其中的属性和元素是可以改变的。
1.2 final修饰方法
final修饰的方法称之为最终方法,不能被重写或者隐藏。
class Person{
// 静态常量要求在类加载完成之前给值
// static final int i = 10;
static final int i;
int age;
String name;
static {
i = 6;
}
public final void eat(){
System.out.println("人要吃饭~");
}
}
class Teacher extends Person{
// 这里会报错,因为final修饰的方法不能被重写
public void eat(){
}
}
- final修饰的方法可以被继承。
- final修饰的方法可以被重载。
public final void eat(){
System.out.println("人要吃饭~");
}
public final void eat(int a){
}
1.3 final修饰类
final修饰的类称之为最终类,不能被继承。
// 面试题:
- String能不能被继承?
不能,因为String是被final修饰的最终类,不能被继承。
// 这种写法编译不通过
class SubString extends String{
}
2 abstract
abstract是一个关键字,可以修饰方法,也可以修饰类。修饰的方法没有方法体,称之为抽象方法。抽象方法只能存在于抽象类中。
- 抽象方法只能存放在抽象类中(接口)。
public abstract void work();
- 定义抽象类
abstract class Proffession{}
- 抽象类不能直接创建对象,需要使用向上转型
Proffession proffession = new Teacher();
- 抽象类能不能被final修饰?
不行。抽象类中的抽象方法必须要有类来实现,final修饰的类是最终类,不能被继承。
- 抽象类中可以定义普通方法,也可以有构造方法
- 抽象方法可以被重载。子类需要实现所有的抽象方法。
- 抽象方法可以重载,必然要被继承和重写。那么不能用private/final/static修饰
- 抽象类也是一个类,受到java单继承的限制。
3 接口
3.1 接口概述
接口是功能的集合。同样可以看作一种引用数据类型,是比抽象类更抽象的"类"。
接口一般只描述所应该具备的方法,并没有具体的实现。具体的实现由接口的实现类(类似于接口的子类)来完成。这样将功能的定义与实现分离,优化程序设计。
在JDK1.8之前,接口中的所有的方法都是抽象方法,从JDK1.8之后开始允许接口中存在静态方法和默认方法。
请记住:一切事物均有功能,即一切事物均有接口。
3.2 接口的基本使用
定义接口Profession
public interface Profession {
// 接口中的抽象方法
public abstract void work();
public abstract double salary();
}
定义接口的实现类,实现类和接口之间使用implements关键字建立联系
public class Actor implements Profession{
@Override
public void work() {
System.out.println("跳舞~");
}
@Override
public double salary() {
return 10000.0;
}
}
测试
public static void main(String[] args) {
// 接口也是一种引用数据类型 使用多态的向上转型来创建
// 父接口 对象名 = new 接口的实现类();
Proffession proffession = new Actor();
proffession.work();
System.out.println(proffession.salary());
}
3.3 接口的特性
接口也是一个.java文件,编译后会产生.class文件.但是接口不是一个类。
-
接口中的抽象方法只能被public abstract修饰,可以省略,在编译后会自动加上public abstract
-
可以使用javap 这个指令来反编译,查看生成的.class文件
- 接口中无法定义普通成员变量,必须使用固定写法。可以省略修饰符,编译时会自动加上。
public static final 数据类型 变量名 = 值;
-
经常会定义一个接口,里面定义变量,实际上都是静态常量。
-
在接口中修饰符可以写也可以不写,对于结果是一样的。
-
一般接口的实现类命名都是 接口名+impl
-
接口不能实例化,也没有构造方法
-
需要使用向上转型来创建接口
父接口 接口名 = new 接口的实现类();
3.4 接口的多继承
在java中,接口和接口之间是多继承。多个接口之间用,隔开。
interface UserMapper{
void a();
void b();
}
interface AdminMapper{
void c();
}
interface BrandMapper{
void d();
}
// 接口的多继承 分别从接口中继承了 a b c d方法
interface MyMapper extends UserMapper,AdminMapper,BrandMapper{
}
// 需要实现四个方法
class MyClass implements MyMapper{
@Override
public void a() {
}
@Override
public void b() {
}
@Override
public void c() {
}
@Override
public void d() {
}
}
3.5 类和接口的多实现
一个类可以实现多个接口,用,隔开
class MyClassImpl implements UserMapper,AdminMapper{
@Override
public void a() {
}
@Override
public void b() {
}
@Override
public void c() {
}
}