皑如山上雪
皎若云间月
简介:
什么是面向对象?
我们之前把所有的代码都写在 main() 方法中,这可不完全是面向对象,我们更喜欢称只之为面向过程。虽然我们确实使用了一些对象比如 String数组,不过并没有真正的开发我们的对象类型。所以面向对象是摆脱 main() 这个过程,建立属于我们的对象
定义类
我们在一开始写 java 程序时就已经接触过类了,如上图 Main 就是我们的一个 java 类
所以 class 就是我们 java 定义类时需要使用的标识符
public class Student { //成员变量 private String name; private int age; private String ID; //成员方法 public void Behavior(){ System.out.println("上学"); } }
<1> 像这样我们就已经定义了一个学生类,其中 class 为定义类的关键字,Student 为类的名字,{} 中为类的主体
<2> 类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类具有哪些功能,称为类的成员方法
这里提一下 Java 语言将 Student 类在计算机中定义完成,经过 javac 编译之后形成 .class 文件,在 JVM 的基础上计算机就可以识别了
命名规范:类名注意采用大驼峰定义(第一个字母大写其余小写)如 Main、Student
实例化类
类不是对象,不过可以构造对象
定义了一个类,就相当于在计算机中定义了一种新的类型,与 int,double 类似,只不过 int和 double 是 java 语言自带的内置类型,而类是用户自定义了一个新的类型,比如以上介绍的 Student 类。而我们用这些类型定义的变量如 int a,a 可以说成是 int 类创建的对象,同理 Student 张三 ,张三就是 Student 类创建的一个对象
创建你的第一个对象
创建和使用一个对象需要什么呢? -- 这个时候你就需要两个类
<1>一个是你想要使用的对象类型
<2>一个是测试这个程序的启动类,我们一般使用一个类嵌套 main() 方法,它的作用就是创建你的新类的对象,然后使用点(.)运算符访问那些新对象的方法和变量
而用类的类型创建对象的过程,称为类的实例化,在 java 中采用 new 关键字,配合类名来实例化对象
我们来实践一下:
class Dog{
public String name;
public String color;
public void bark(){
System.out.println("旺旺");
}
public void image(){
System.out.println(name);
System.out.println(color);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.name = "旺财";
d.color = "红";
d.image();
d.bark();
Dog a = new Dog();
a.name = "大黄";
a.color = "黄";
a.image();
a.bark();
}
}
总结:
<1> new 关键字用于创建一个对象的实例
<2> 使用(.)点运算符来访问对象中的属性和方法
<3> 同一个类可以创建多个实例
问答环节:
问:学过 C 的你可能会问,如果我需要全局变量和方法呢?既然一切都必须放在类里面那该怎么办呢?
答:在 java 程序中没有 “全局” 变量和方法的概念,如果你希望一个变量或者方法在其他地方也能使用,我们可以把一个变量或者方法标记为 public(公有)、static(静态),这会让它表现得更像是一个 “全局” 方法
问:java 程序是什么?你最后交付的到底是什么?
答:java 程序是一堆类(或者至少一个类)。在一个 java 应用中必须有一个类包含 main 方法来作为程序的启动。这些类就是你要交付的东西
this 引用
为什么会有 this 引用呢?
<1>解决命名冲突
我们还是以上面的例子为例,如果 形参名不小心与成员变量名相同 时,那函数体中到底是谁给谁赋值?成员变量给成员变量?参数给参数?参数给成员变量?成员变量参数?
public void setDog(String name,String color){
name = name;
color = color;
}
答案是:参数给参数,我们来看
关于为什么会优先使用局部变量的原因
方法中的变量为局部变量,存储在栈中,作用范围是方法内;我们想通过 构造器set 初始化的是成员变量存储在堆中,作用范围是本类内部
所以当成员变量局部变量重名时,优先使用局部变量
而最好的解决方法就是使用 this 指针,我们可以加上 this.成员变量名
this 的作用是引用当前对象在本类中的属性。什么是当前对象(因为我们可以创建很多个对象),如果出现多个对象共用一个属性的情况这样就乱套了
<2>解决方法冲突
class Dog{
public String name;
public String color;
public void setDog(String name,String color){
this.name = name;
this.color = color;
}
public void PrintDog(){
System.out.println(name+" : "+color);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.setDog("大黄","黄");
d.PrintDog();
System.out.println("-------------------------");
Dog q = new Dog();
q.setDog("旺财","红");
q.PrintDog();
}
}
根据以上的代码两个对象都在调用 setDog 和 printDog 函数,但是这两个函数中没有任何有关对象的说明,setDog 和 printDog 函数如何知道打印的是那个对象的数据呢?
这其中的功劳还是依靠于 this --- 引用当前对象
有细心的同学肯定会问为什么 printDog 没有写 this 还能分辨对象的数据呢?
其实 this 在方法中是 “隐藏” 属性:this 是 “成员方法” 第一个隐藏参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this 负责接收
所以说 this 可以显示写,也可以编译器帮我们写
当然我们最好都写上 this,比如像刚刚 srtDog 的例子就必须使用 this
总结:
this 的作用就是对应类类型引用,即哪个对象调用就是哪个对象的引用类型 |
this 只能在 "成员方法" 中使用 |
在 "成员方法" 中,this 只能引用当前对象,不能再引用其他对象 |
对象的初始化
在还没有学类与对象时,我们定义一个局部变量如果没有初始化就使用便会报错
然而在类中对象是怎么初始化的呢?-- 构造方法(我们将初始化对象的方法称为构造方法)
我们来看
系统默认生成的无参构造方法
class Dog{
public String name;
public String color;
public int age;
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.PrintDog();
}
}
根据以上代码,我们来看我们创建了一个对象但是我们并没有对其初始化,那么我们打印的值是什么呢?
我们发现系统已经默认帮我们初始化了,例如 String类型 初始化成 null,int类型 初始化成 0
以下是编译器默认类型的初始化值
有参的构造方法
构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的 ~
构造方法分为有参和无参构造,其实就是字面意思我们来看
注意:如果我们自己写了构造方法系统将不会自动生成默认的构造方法
class Dog{
public String name;
public String color;
public int age;
public Dog(String n, String c, int a){
this.name=n;
this.color=c;
this.age=a;
}
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog("大黄","黄",9);
d.PrintDog();
}
}
以上就是一个有参的构造,由于构造方法没有返回值所以不需要用 void 修饰
如果你不小心用了 void 或者其他的类型,那么抱歉编译器会将这个方法当成是普通的方法不会再将其看做构造方法了
构造方法的定义:
[修饰符] 类名(参数)
{
初始化属性
}
无参的构造方法
class Dog{
public String name;
public String color;
public int age;
public Dog(){
this.name="大黄";
this.color="黄";
this.age=9;
}
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.PrintDog();
}
}
无参的构造方法其实很简单,但是要注意的是无参构造和有参构造是可以同时存在的,它们构成方法 重载
我们开发当中我们一般都是有参和无参构造都是要写的,因为一旦我们写了自己的构造方法,系统都将不在生成默认的无参构造。而实际开发中我们创建一个对象并不会直接就 “扒拉扒拉” 传值直接初始化, 所以我们需要一个无参的构造方法。但是要是都是这么写,类的属性一多就很不现实
idea 早就考虑早这一点了,我们可以通过编译器快速生成构造方法:
鼠标右键点击生成
无参构造调用有参的构造方法
因为 this 引用的是当前对象,所以我们可以·通过在无参构造中借助 this 来调用有参构造来帮助我们初始化
class Dog{
public String name;
public String color;
public int age;
public Dog(int age, String color, String name) {
this.age = age;
this.color = color;
this.name = name;
}
public Dog()
{
this(9,"黄","大黄");
}
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.PrintDog();
}
}
我们无参的构造方法是可以调用有参的构造方法的,但是有一点还是需要注意:
这个时候我们有参的构造就不能调用无参的构造方法了,因为在这样调下去就无限套娃了
this 的调用不能形成环 !!!
就地初始化
就地初始化十分简单:在声明成员变量时,就直接给出了初始值
代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造函数中
class Dog{
public String name = "旺财";
public String color = "红";
public int age = 10;
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.PrintDog();
}
}
面向对象特性 -- 封装
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问
我们来看一下的例子:
class Dog{
public String name;
public String color;
public int age;
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.name = "旺财";
d.color = "红";
d.age = 9;
d.PrintDog();
}
}
我们在 main 方法中调用了该类的成员变量,如果是实际开发中你愿意让用户修改你设置的数值吗
那肯定不行是吧,所以我们需要把这个类封装一下:
我们可以通过 访问限定符 来对类进行封装 -- 一般是我们不想让用户看见的用 private 修饰,我们想让用户访问的用 public 修饰
像这样
class Dog{
private String name;
private String color;
private int age;
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
}
我们的成员变量就不会被类外直接访问了,那怎么间接访问呢?
java 专门给类的成员变量设计了 set(修改)、get(获取)方法
class Dog{
private String name;
private String color;
private int age;
public void PrintDog(){
System.out.println(name+" : "+color+" : "+age);
}
public void setName(String name) {
this.name = name;
}
public void setColor(String color) {
this.color = color;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
}
class DogTestDrive{
public static void main(String[] args) {
Dog d = new Dog();
d.setName("大黄");
System.out.println(d.getName());
}
}
一般情况下:成员变量使用 public、成员方法使用 private
访问限定符
Java 中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java 中提供了四种访问限定符
本章节只介绍两种:
public(公有)类外和类内都能访问,private(私有)类内能访问类外不能访问
当然访问限定符除了能修饰类的属性还能修饰类,由 public 修饰的类就说明这个类是公开的
不过这个类名需要要与文件名一致
包访问权限
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录
在我们实际开发中,一个项目可能有几百上千个类,难道我们要把它们都塞在一个目录吗?
-- 当然这是不现实的,java 包的作用就是将相关的类与接口放在同一个包中方便管理与使用
如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别
包的路径
我们在包的目录下创建一个 java类 便会自动生成一个包的路径
但是如果是在 src 目录下的 java类 不会生成路径,因为 java 会把 src 当成多目录下的起始目录
如果我们想要在当前包的类中访问其他包中的类,我们就需要用 import 进行导包
java 中已经提供了很多现成的类供我们使用,例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date 类
package com.DF;
import java.util.Date;
public class DogTestDrive{
public static void main(String[] args) {
Date da = new Date();
}
}
还这样写成这样:(这样就不需要导包了,但是会比较麻烦)
java.util.Date da = new java.util.Date();
如果你还需使用 util 包下的其他类,我们可以导入
import java.util.*;
(*)是一种通配符,有点类似于C++中的 #include<bits/stdc++.h> (包含所有头文件)
意思就是导入 util 目录下的所有类。这个时候肯定有同学会问,导入那么多类程序会不会对运行 java 程序有影响呢?
答案是 -- 当然不会,因为类只有被创建出对象(被使用)系统才会导入包,如果没有创建出对象不管你的代码写的有多大,系统都是不会导入包的