本篇 Java 的学习路线是参考韩顺平老师的教程
教程:零基础 30 天学会 Java
https://www.bilibili.com/video/BV1fh411y7R8
# 面向对象
假如某宠物机构,有 200 只猫和 200 只狗,它们有不同的名字、年龄、毛发颜色、体重等等,需要通过程序输入它们的名字,自动把它们的属性打印出来
如果用之前的知识,我们只能通过单独的定义变量或者使用数组解决这个问题
讲道理,这道题如果用前面的方法去做,那真的是要崩溃了,由此可见传统的方法并不利于数据的批量创建和管理,而且效率很低
因此,Java 的作者引入了 类与对象 (OOP),根本原因就是现有的技术不能完美的解决新的需求
# 类与对象
- 类
类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起
类一般预先定义好属性,属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型 (对象或数组)
-
类是对对象的抽象,不占用内存
-
类是一种抽象的数据类型
-
类是对象的模板
- 对象
-
对象是类的实例,占用存储空间
-
对象是具有类类型的变量
类与对象的区别:
-
类是抽象的,概念的,代表一类事物,比如人类、动物类.. 即它是数据类型
-
对象是具体的,实际的,代表一个具体事物,比如艾蕾是一个对象
-
类是对象的木板,对象是类的一个个体,对应一个实例
- Code
import java.util.Scanner; | |
public class Class01{ | |
public static void main(String[] args){ | |
/** | |
* 题目: | |
* 张太太养了两只猫: | |
* 一只叫小白,今年 3 岁,白色 | |
* 另一只叫小花,今年 5 岁,黄色 | |
* 需求: | |
* 当用户输入小猫的名字,显示该猫的属性 | |
* 如果用户输入错误,则显示张老太没有这只猫猫 | |
*/ | |
Cat cat1 = new Cat(); // 用 new 创建一个 Cat 对象,开辟内存空间,定义一个对象名 cat1 指向对象所在的内存地址 | |
// 给 cat1 的属性赋值 | |
cat1.name = "小白"; | |
cat1.age = 3; | |
cat1.color = "白色"; | |
Cat cat2 = new Cat(); // 用 new 创建一个 Cat 对象,开辟内存空间,定义一个对象名 cat2 指向对象所在的内存地址 | |
// 给 cat2 的属性赋值 | |
cat2.name = "小花"; | |
cat2.age = 5; | |
cat2.color = "黄色"; | |
Scanner sc = new Scanner(System.in); | |
System.out.print("请输入你要查找的猫名字: "); | |
String catName = sc.next(); | |
if (catName.equals(cat1.name)){ | |
System.out.println(cat1.name + " 今年 " + cat1.age + " 岁,颜色是 " + cat1.color); | |
}else if(catName.equals(cat2.name)){ | |
System.out.println(cat2.name + " 今年 " + cat2.age + " 岁,颜色是 " + cat2.color); | |
}else{ | |
System.out.println("张老太没有" + catName + "这只猫猫"); | |
} | |
} | |
} | |
class Cat{ | |
// 属性、成员变量、field | |
String name; // 名字 | |
int age; // 年龄 | |
String color; // 颜色 | |
} |
- 运行结果
- 对象在内存中的存在方式
# 成员方法
类除了包含一些数据类型意外,还可以定义一些方法,这样当我们调用类的时候,也可以使用这些方法
- 成员方法定义
访问修饰符 返回数据类型 方法名 (形参列表...){
代码块;
return 返回值;
}
- Exercise
public class method01{ | |
public static void main(String[] args){ | |
Person p1 = new Person(); | |
// 给属性赋值 | |
p1.name = "Bob"; | |
p1.age = 32; | |
p1.result = 99.5; | |
System.out.println("name is " + p1.name + " age is " + p1.age + " result is " + p1.result); | |
// 调用成员方法 | |
p1.say(); | |
p1.cal01(); | |
p1.cal02(100); | |
int res = p1.getSum(20,5); | |
System.out.println("res is " + res); | |
} | |
} | |
// 定义一个 Person 类 | |
class Person{ | |
// 定义一些基本的数据类型属性 | |
String name; | |
int age; | |
double result; | |
// 定义一个 say 方法 | |
public void say(){ | |
System.out.println("hello,Bob!"); | |
} | |
// 定义一个 cal01 方法 | |
public void cal01(){ | |
int sum = 0; | |
for(int i = 1; i <= 1000; i++){ | |
sum += i; | |
} | |
System.out.println("cal01 sun is " + sum); | |
} | |
// 定义一个 cal02 方法 | |
public void cal02(int n){ | |
int res = 0; | |
for (int i = 1; i <= n; i++){ | |
res += i; | |
} | |
System.out.println("cal02 sum is " + res); | |
} | |
// 定义一个 getSum 方法 | |
public int getSum(int i, int j){ | |
int res = i + j; | |
return res; | |
} | |
} |
- 运行结果
# overload
- 注意事项和细节
-
方法名必须相同
-
形参列表必须不同 (参数个数或者顺序至少有一个不同)
-
返回类型没有要求
- Code
class Method{ | |
public void print(int n, double x){ | |
System.out.println(n + x); | |
} | |
public double print(double n, double x){ | |
return n + x; | |
} | |
public int print(int n, int x){ | |
return n * x; | |
} | |
} |
- 运行结果
# 可变参数
- 注意事项和细节
-
可变参数的实参可以为 0 个或者多个
-
可变参数的实参可以是数组
-
可变参数的本质就是数组
-
可变参数可以和普通类型的参数一起放在形参列表,但是必须保证可变参数在最后
-
一个形参列表中只能出现一个可变参数
- Code
public class VariableParameter{ | |
public static void main(String[] args){ | |
Parameter p = new Parameter(); | |
System.out.println(p.calcAdd(10, 20, 30, 40, 50)); | |
} | |
} | |
class Parameter{ | |
public int calcAdd(int... nums){ | |
int sum = 0; | |
for (int i = 0; i < nums.length; i++){ | |
sum+=nums[i]; | |
} | |
return sum; | |
} | |
} |
# 构造器
- 注意事项和细节
-
一个类可以定义多个不同的构造器,即构造器重载
-
构造器名和类名必须相同
-
构造器没有返回值
-
构造器是完成对象的初始化,并不是创建对象
-
在创建对象时,系统自动调用该类的构造方法
-
如果没有定义构造器,系统会自动给类生成一个默认无参构造器 (默认构造器),比如 Person (){}, 使用 javap 指令反编译看看
-
一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能再使用默认的无参构造器,除非重新定义如下:Person (){}
- Code
public class Constructor{ | |
public static void main(String[] args){ | |
Cons cons = new Cons("Bob", 32); | |
System.out.println("cons name is " + cons.name + ", age is " + cons.age); | |
Cons cons1 = new Cons("李蕾"); | |
System.out.println("cons1 name is " + cons1.name); | |
} | |
} | |
class Cons{ | |
String name; | |
int age; | |
public Cons(String names, int ages){ | |
name = names; | |
age =ages; | |
} | |
public Cons(String names){ | |
name = names; | |
} | |
} |
- 运行结果
# this
this 主要是解决形参变量名的问题,java 虚拟机会给每个对象分配 this,代表当前对象的引用
- 注意事项和细节
-
this 关键字可以用来访问本类的属性、方法、构造器
-
this 用于区分当前类的属性和局部变量
-
访问成员方法的语法: this. 方法名 (参数列表);
-
访问构造器语法: this (参数列表); 只能在构造器中访问另一个构造器,this 必须放在第一条语句
-
this 不能在类定义的外部使用,只能在类定义的方法中使用
class Constructor{ | |
public Constructor(){ | |
this("Bob", 32); | |
System.out.println("Constructor() 被调用"); | |
} | |
public Constructor(String name, int age){ | |
System.out.println("Constructor(String name, int age) 被调用"); | |
} | |
} |
# 包
- 常用的包
java.lang.* //lang 包是基本包,默认引入
java.util.* //util 包是系统提供的工具包
java.net.* //net 包是网络开发包
java.awt.* //awt 包是界面开发包,GUI
- 包的本质
包的本质实际上就是创建不同的文件夹 / 目录来保存类文件
- 命名规则
只能包含数字、字母、下划线、小圆点,但是不能用数字开头,不能用关键字和保留字
一般是小写字母 + 小圆点,例如: com. 公司名。项目名。业务模块名
com.bob.oa.counts // 计算模块 | |
com.bob.oa.controller // 控制模块 | |
com.bob.crm.user // 用户模块 | |
com.bob.crm.order // 订单模块 | |
com.bob.crm.utils // 工具类 |
- 注意事项和细节
-
package 的作用是声明当前类所在的包,需要放在类的最上面,一个类中只能有一条 package
-
import 位置放在 package 的下方,在类定义前面,可以有多条且没有顺序
# 访问修饰符
1 | 访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|---|
2 | 公开 | public | √ | √ | √ | √ |
3 | 受保护 | protected | √ | √ | √ | × |
4 | 默认 | 没有修饰符 | √ | √ | × | × |
5 | 私有 | private | √ | × | × | × |
- 注意事项
-
修饰符可以用来修饰类中的属性,成员方法以及类
-
只有默认的和 public 才能修饰类,并且遵循上述访问权限的特点
-
子类中的访问权限
-
成员方法的访问规则和属性完全一样
# 封装
encapsulation 就是把抽象出来的数据 [属性] 和对数据的操作 [方法] 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作 [方法] 才能对数据进行操作
encapsulation 的好处是可以隐藏细节,可以对数据进行验证,保证安全合理
- 封装步骤
-
将属性进行私有化 private
-
提供一个公共的 (public) set 方法,对属性判断并赋值
-
提供一个公共的 (public) get 方法,用于获取属性的值
# 继承
如果一个类的成员属性和成员方法有多处相同,可以是使用继承提高代码复用率,而不必写重复的代码,并且代码的扩展性和维护性也提高了
继承可以解决代码复用问题,让我们的编程更加接近人类思维,当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需通过 extends 来声明继承父类即可
- 基本语法
class subclass extands fatherclass {}
-
子类会自动拥有父类定义的属性和方法
-
父类又叫超类、基类
-
子类又叫派生类
public class A{ | |
public String name; | |
public int age; | |
private double score; | |
public void setScore(double score){ | |
this.score = score; | |
} | |
public void showInfo(){ | |
System.out.println("姓名: "name + " ,年龄: " + age + " ,成绩: " score); | |
} | |
} | |
public class B extands A{ | |
private void setAge(int age){ | |
this.age = age; | |
} | |
} | |
public class C extands B{} |
- 注意事项和细节
-
子类继承了父类所有的属性和方法,但是私有属性不能呢个在子类直接访问,要通过公共的方法去访问
-
子类必须调用父类的构造器,完成父类初始化
-
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中使用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译不通过
-
如果希望制定去调用父类的某个构造器,则定义 super (parameter)
-
super () 和 this () 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
-
父类构造器的调用不限于直接父类,将一直往上追溯到 Object 类,java 所有类都是 Object 类的子类,Object 是所有类的父类
-
java 是单继承机制,子类最多只能直接继承一个父类
# 多态
多态是方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上,重写和重载就体现多态
-
一个对象的编译类型和运行类型可以不一致
-
编译类型在定义对象时就确定了,不能改变
-
运行类型时可以改变的
-
编译类型为 = 左边, 运行类型为 = 右边
-
编译状态由编译器 javac 决定哪些方法和属性可以调用,运行状态由 java 决定哪些方法和属性可以调用
-
多态向上转型, 父类类型 引用名 = new 子类类型
-
多态向下转型, 子类类型 引用名 = new (子类类型) 父类引用,只能强转父类引用对象
//java 动态绑定机制: | |
// 当调用对象方法的时候,该方法会和对象的内存地址 / 运行类型绑定 | |
// 当调用对象属性时,没有动态绑定机制,哪里声明就哪里使用 | |
class A { | |
public int i = 10; | |
public int sum(){ | |
return geti() + 10; | |
} | |
public int sum1(){ | |
return i + 10; | |
} | |
public int geti(){ | |
return i; | |
} | |
} | |
class B extends A { | |
public int i = 20; | |
// public int sum(){ | |
// return i + 20; | |
// } | |
public int sum1(){ | |
return i + 10; | |
} | |
// public int geti(){ | |
// return i; | |
// } | |
} | |
public class Main { | |
public static void main(String[] args) { | |
A a = new B(); | |
System.out.println(a.sum()); //20 | |
System.out.println(a.sum1()); //30 | |
System.out.println(a.i); //10 | |
} | |
} |
# super
super 代表父类的引用,用于访问父类的属性、方法、构造器
-
访问父类的属性,但是不能访问父类的 private 属性
-
访问父类的方法,但是不能访问父类的 private 方法
-
访问父类的构造器,super (参数列表),只能放在构造器的第一句
# overwrite
子类重写父类方法
@Override | |
public boolean equals(Object obj){ | |
if(this == obj){ | |
return true; | |
} | |
if(obj instanceof Person){ | |
Person p = (Person)obj; | |
return this.age == p.age && this.name.equals(p.name) && this.salary == p.salary; | |
} | |
return false; | |
} |
# object 类详解
- hashCode
判断两个引用对象是否来自同一个对象
A a1 = new A(); | |
A a2 = new A(); | |
A a3 = a1; | |
a1 == a3 //true | |
a1 == a2 //false | |
a2 == a3 // false |
- toString
默认返回 全类名 +@+ 哈希值的十六进制,用于返回对象的属性信息
public String toString() { | |
return getClass().getName() + "@" + Integer.toHexString(hashCode()); | |
} |
当直接输出一个对象时,toString 方法默认被调用,print (A); 默认输出 print (A.toString);
- finalize
当某个对象没有任何引用时,JVM 则认为这个对象是一个垃圾对象,由对象的垃圾器调用此方法销毁对象,也可以通过 System.gc () 主动触发垃圾回收机制,子类可以重写该方法释放资源