本篇 Java 的学习路线是参考韩顺平老师的教程
教程:零基础 30 天学会 Java
https://www.bilibili.com/video/BV1fh411y7R8

# 面向对象

假如某宠物机构,有 200 只猫和 200 只狗,它们有不同的名字、年龄、毛发颜色、体重等等,需要通过程序输入它们的名字,自动把它们的属性打印出来

如果用之前的知识,我们只能通过单独的定义变量或者使用数组解决这个问题

讲道理,这道题如果用前面的方法去做,那真的是要崩溃了,由此可见传统的方法并不利于数据的批量创建和管理,而且效率很低

因此,Java 的作者引入了 类与对象 (OOP),根本原因就是现有的技术不能完美的解决新的需求

# 类与对象

类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起

类一般预先定义好属性,属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型 (对象或数组)

  1. 类是对对象的抽象,不占用内存

  2. 类是一种抽象的数据类型

  3. 类是对象的模板

  • 对象
  1. 对象是类的实例,占用存储空间

  2. 对象是具有类类型的变量

类与对象的区别:

  1. 类是抽象的,概念的,代表一类事物,比如人类、动物类.. 即它是数据类型

  2. 对象是具体的,实际的,代表一个具体事物,比如艾蕾是一个对象

  3. 类是对象的木板,对象是类的一个个体,对应一个实例

  • Code
a
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;	// 颜色
}
  • 运行结果

upload successful

  • 对象在内存中的存在方式

upload successful

# 成员方法

类除了包含一些数据类型意外,还可以定义一些方法,这样当我们调用类的时候,也可以使用这些方法

  • 成员方法定义

访问修饰符 返回数据类型 方法名 (形参列表...){

代码块;
return 返回值;

}

  • Exercise
a
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;
        }
}
  • 运行结果

upload successful

# overload

  • 注意事项和细节
  1. 方法名必须相同

  2. 形参列表必须不同 (参数个数或者顺序至少有一个不同)

  3. 返回类型没有要求

  • Code
a
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;
        }
}
  • 运行结果

upload successful

# 可变参数

  • 注意事项和细节
  1. 可变参数的实参可以为 0 个或者多个

  2. 可变参数的实参可以是数组

  3. 可变参数的本质就是数组

  4. 可变参数可以和普通类型的参数一起放在形参列表,但是必须保证可变参数在最后

  5. 一个形参列表中只能出现一个可变参数

  • Code
a
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;
        }
}

# 构造器

  • 注意事项和细节
  1. 一个类可以定义多个不同的构造器,即构造器重载

  2. 构造器名和类名必须相同

  3. 构造器没有返回值

  4. 构造器是完成对象的初始化,并不是创建对象

  5. 在创建对象时,系统自动调用该类的构造方法

  6. 如果没有定义构造器,系统会自动给类生成一个默认无参构造器 (默认构造器),比如 Person (){}, 使用 javap 指令反编译看看

  7. 一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能再使用默认的无参构造器,除非重新定义如下:Person (){}

  • Code
a
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;
        }
}
  • 运行结果

upload successful

# this

this 主要是解决形参变量名的问题,java 虚拟机会给每个对象分配 this,代表当前对象的引用

  • 注意事项和细节
  1. this 关键字可以用来访问本类的属性、方法、构造器

  2. this 用于区分当前类的属性和局部变量

  3. 访问成员方法的语法: this. 方法名 (参数列表);

  4. 访问构造器语法: this (参数列表); 只能在构造器中访问另一个构造器,this 必须放在第一条语句

  5. this 不能在类定义的外部使用,只能在类定义的方法中使用

a
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. 公司名。项目名。业务模块名

a
com.bob.oa.counts		// 计算模块
com.bob.oa.controller	// 控制模块
com.bob.crm.user	// 用户模块
com.bob.crm.order	// 订单模块
com.bob.crm.utils	// 工具类
  • 注意事项和细节
  1. package 的作用是声明当前类所在的包,需要放在类的最上面,一个类中只能有一条 package

  2. import 位置放在 package 的下方,在类定义前面,可以有多条且没有顺序

# 访问修饰符

1 访问级别 访问控制修饰符 同类 同包 子类 不同包
2 公开 public
3 受保护 protected ×
4 默认 没有修饰符 × ×
5 私有 private × × ×
  • 注意事项
  1. 修饰符可以用来修饰类中的属性,成员方法以及类

  2. 只有默认的和 public 才能修饰类,并且遵循上述访问权限的特点

  3. 子类中的访问权限

  4. 成员方法的访问规则和属性完全一样

# 封装

encapsulation 就是把抽象出来的数据 [属性] 和对数据的操作 [方法] 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作 [方法] 才能对数据进行操作

encapsulation 的好处是可以隐藏细节,可以对数据进行验证,保证安全合理

  • 封装步骤
  1. 将属性进行私有化 private

  2. 提供一个公共的 (public) set 方法,对属性判断并赋值

  3. 提供一个公共的 (public) get 方法,用于获取属性的值

# 继承

如果一个类的成员属性和成员方法有多处相同,可以是使用继承提高代码复用率,而不必写重复的代码,并且代码的扩展性和维护性也提高了

继承可以解决代码复用问题,让我们的编程更加接近人类思维,当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需通过 extends 来声明继承父类即可

  • 基本语法
    class subclass extands fatherclass {}
  1. 子类会自动拥有父类定义的属性和方法

  2. 父类又叫超类、基类

  3. 子类又叫派生类

upload successful

a
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{}
  • 注意事项和细节
  1. 子类继承了父类所有的属性和方法,但是私有属性不能呢个在子类直接访问,要通过公共的方法去访问

  2. 子类必须调用父类的构造器,完成父类初始化

  3. 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中使用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译不通过

  4. 如果希望制定去调用父类的某个构造器,则定义 super (parameter)

  5. super () 和 this () 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器

  6. 父类构造器的调用不限于直接父类,将一直往上追溯到 Object 类,java 所有类都是 Object 类的子类,Object 是所有类的父类

  7. java 是单继承机制,子类最多只能直接继承一个父类

upload successful

# 多态

多态是方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上,重写和重载就体现多态

  1. 一个对象的编译类型和运行类型可以不一致

  2. 编译类型在定义对象时就确定了,不能改变

  3. 运行类型时可以改变的

  4. 编译类型为 = 左边, 运行类型为 = 右边

  5. 编译状态由编译器 javac 决定哪些方法和属性可以调用,运行状态由 java 决定哪些方法和属性可以调用

  6. 多态向上转型, 父类类型 引用名 = new 子类类型

  7. 多态向下转型, 子类类型 引用名 = new (子类类型) 父类引用,只能强转父类引用对象

a
//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 代表父类的引用,用于访问父类的属性、方法、构造器

  1. 访问父类的属性,但是不能访问父类的 private 属性

  2. 访问父类的方法,但是不能访问父类的 private 方法

  3. 访问父类的构造器,super (参数列表),只能放在构造器的第一句

# overwrite

子类重写父类方法

a
@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
A a1 = new A();
A a2 = new A();
A a3 = a1;
a1 == a3 //true
a1 == a2 //false
a2 == a3 // false
  • toString

默认返回 全类名 +@+ 哈希值的十六进制,用于返回对象的属性信息

a
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

当直接输出一个对象时,toString 方法默认被调用,print (A); 默认输出 print (A.toString);

  • finalize

当某个对象没有任何引用时,JVM 则认为这个对象是一个垃圾对象,由对象的垃圾器调用此方法销毁对象,也可以通过 System.gc () 主动触发垃圾回收机制,子类可以重写该方法释放资源