在学习类和对象前,我们可以抛出一个问题,什么是面向过程?什么是面向对象?
在学习到类和对象这里时,我们就能java与c语言的区别了,一个是面向对象,一个是面向过程。比如我们在买手机时,我们会关注手机的cpu、内存、电池容量等信息,这样将手机拆分成多个部分去分析,就是面向过程;当你然你朋友去帮你买手机时,你只是说你需要一部手机,而如何挑选手机,是交给你的朋友去完成的,你只是关注于买手机,这就是面向对象。
一、类的定义和使用
1.类的定义
class 类名{
//1.字段/属性/成员变量
//2.方法
}
2.类的使用
<1>类中成员变量以及成员方法的定义
我们以定义一个洗衣机类为例:
class WashMachine{
//字段,属性,成员变量
public String brand; // 品牌
public String type; // 型号
public double weight; // 重量
public double length; // 长
public double width; // 宽
public double height; // 高
public String color; // 颜色
//行为,成员方法
public void washClothes(){ // 洗衣服
System.out.println("洗衣功能");
}
public void dryClothes(){ // 脱水
System.out.println("脱水功能");
}
public void setTime() { // 定时
System.out.println("定时功能");
}
}
需要了解的是,类也属于一个类型,它是一个自定义类型,那么,它也可以创建一个变量(对象),代码如下:
class WashMachine{
int size;
}
public class Main{
public static void main(String[]args){
//通过类名创建了一个变量(对象)
WashMachine washMachine;
}
}
为了有一个良好的代码习惯,我们一般要做到一个java文件只有一个类,但是这里为了方便,在一个java文件中有多个类。
上面我们知道了如何定义一个类,以及类里面可以有上面内容,那么我们如何使用这个类?
<2>类的使用(类的实例化)
由类生成对象的过程叫做实例化,要使用类里面定义的变量或者方法,就是通过类实例化一个对象来完成的。实例化对象需要使用new关键字,使用方法如下:
class WashMachine{
int size;
}
public class Main{
public static void main(String[]args){
//实例化一个对象的方法
WashMachine washMachine=new WashMachine();
}
}
想在我们已经实例化了一个对下,现在我们可以通过这个对象去访问类里面的变量或者方法了,访问的方法是通过对象.变量名或者对象.方法名(),在这之前,我们还需要了解我们在创建对象的时候,内存中是如何体现的:
可以看到,引用指向了数据,因此我们可以上述方法使用成员变量和成员方法,下面我们通过一段代码来了解
class Dog{
String name;
int age;
void bark(){
System.out.println("汪汪");
}
}
public class Main{
public static void main(String[]args){
//通过类名创建了一个变量(对象)
Dog dog=new Dog();
//对成员变量赋值
dog.age=18;
dog.name="花花";
//调用成员方法
dog.bark();
System.out.println("名字"+dog.name+"年龄"+dog.age);
}
}
运行结果如下:
3.类和对象的说明
二、this引用
1.this关键字的引入
我们先看一段代码:
public class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d) {
year = y;
month = m;
day = d;
}
public void printDate() {
System.out.println(year + "/" + month + "/" + day);
}
public static void main(String[] args) {
// 构造三个日期类型的对象 d1 d2 d3
Date d1 = new Date();
Date d2 = new Date();
Date d3 = new Date();
// 对d1,d2,d3的日期设置
d1.setDay(2020, 9, 15);
d2.setDay(2020, 9, 16);
d3.setDay(2020, 9, 17);
// 打印日期中的内容
d1.printDate();
d2.printDate();
d3.printDate();
}
}
可以看到,在上述代码中创建了三个对象,每个对象都调用了setday方法,没有任何问题,但是有两个疑问,
<1>.当形参名与成员变量名相同时
public void setDay(int year, int month, int day){
year = year;
month = month;
day = day;//究竟是谁对谁赋值?
}
<2>.三个对象都在调用setDate和printDate函数,但是这两个函数中没有任何有关对象的说明,那这两个方法如何确定打印的是哪个对象的数据呢?
为了避免以上两种情况,我们可以使用this关键字解决
2.this关键字的使用
this关键字有一个重要内涵,它表示的是当前对象的引用,前面我们知道,访问成员变量和成员方法可以使用对象.成员变量/成员方法()的方式,this也是引用,那么它的使用方法也是类似,即this.成员变量/成员方法()。即:
public void setDay(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
现在我们通过this来访问成员方法和成员变量,它表示该变量是当前对象的成员变量/成员方法。
3. 如何知道this表示的是当前对象的引用
其实非常简单,我们只需要通过调试,看一看this和当前所创建的对象是否相同即可!
我们可以看到,this的值和当前我们创建的对d1的哈希值相同,这就证明this表示的就是当前对象的引用,因此也就同样可以使用this.成员变量/成员方法()来访问。
4.成员方法中隐藏的this
前面我们说到了不使用关键字this是存在的另一个问题:三个对象都在调用setDate和printDate函数,但是这两个函数中没有任何有关对象的说明,那这两个方法如何确定打印的是哪个对象的数据呢? 其实答案很简单,每一个成员方法的形参列表中都包含了一个隐藏的this,用来接收对象的地址。我们可以通过下面的代码来观察:
5.通过this访问当前对象的其它构造方法
6.this引用的特性
三、对象的构造及初始化
1.对象的初始化
我们知道,对于局部变量,必须在创建的时候初始化,否则,编译器会报错,如
public static void main(String[] args) {
int a;
System.out.println(a);
}
// Error局部变量没有初始化
那对于一个对象呢?
public static void main(String[] args) {
Date d = new Date();
d.printDate();
d.setDate(2021,6,9);
d.printDate();
}
//可以通过编译
当我们要对对象初始化时,需要调用setDate方法,但是我们会发现
2.构造方法
1.概念
构造方法是一种特殊的成员方法,名字必须与类名相同,在创建对象时编译器会自动调用构造方法,并且在整个对象的生命周期中只调用一次。
public class Date {
public int year;
public int month;
public int day;
// 构造方法:
// 名字与类名相同,没有返回值类型,设置为void也不行
// 一般情况下使用public修饰
// 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
System.out.println("Date(int,int,int)方法被调用了");
}
public void printDate() {
System.out.println(year + "-" + month + "-" + day);
}
public static void main(String[] args) {
// 此处创建了一个Date类型的对象,并没有显式调用构造方法
Date d = new Date(2021, 6, 9); // 输出Date(int,int,int)方法被调用了
d.printDate(); // 2021-6-9
}
}
!!!构造方法只是给对象初始化,并不给对象开辟空间
2.构造方法的特性
public class Date {
public int year;
public int month;
public int day;
// 无参构造方法
public Date(){
this.year = 1900;
this.month = 1;
this.day = 1;
}
// 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(year + "-" + month + "-" + day);
}
public static void main(String[] args) {
Date d = new Date();
d.printDate();
}
}
在上面的两个构造方法中,方法名相同,返回值相同(没有),参数列表不同,构成了方法的重载。
!!!如果没有定义构造方法,那么系统会自动提供一个无参的构造方法。
3.通过this简化构造方法(同目录 二、5.)
public class Date {
public int year;
public int month;
public int day;
// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(1900,1,1);必须是构造方法中第一条语句
public Date(){
//System.out.println(year); 注释取消掉,编译会失败
this(1900, 1, 1);
//this.year = 1900;
//this.month = 1;
//this.day = 1;
}
// 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
注意:在使用this简化构造方法时,不可以成环,即在一个构造方法中使用this调用另一个构造方法,又在另一个构造方法中通过this调用上一个构造方法。如:
public Date(){
this(2021,1,1);
}
public Date(int year, int month, int day) {
this();
this.year = year;
this.month = month;
this.day = day;
}
}
this在当前对象中调用其它构造方法时,必须放在构造方法的第一句!!!
3.默认初始化
我们回到上文的问题,为什么局部变量需要初始化,而成员变量可以不初始化呢?
public class Date {
public int year;
public int month;
public int day;
public Date(int year, int month, int day) {
// 成员变量在定义时,并没有给初始值, 为什么就可以使用呢?
System.out.println(this.year);
System.out.println(this.month);
System.out.println(this.day);
}
public static void main(String[] args) {
// 此处a没有初始化,编译时报错:
// Error:(24, 28) java: 可能尚未初始化变量a
// int a;
// System.out.println(a);
Date d = new Date(2021,6,9);
}
要搞清楚这个问题,我们需要了解我们在通过new关键字创建对象时,发生的一些事情:
![](https://img-blog.csdnimg.cn/direct/a897e28a55fe4792a648488798bdcde9.png)
四、封装
1.封装的概念
面向对象的程序有三大特性:封装、继承和多态。在类和对象中,研究的就是封装。引入一个生活上的例子,当我们购买一台电脑时,我们能看到的是整台电脑,它的电池、内存条、cpu等我们是无法看到的,也就是说,把这些零件封装起来,就形成了一台电脑,但是,电脑也给我们留下了一些接口可以用来访问电脑内部(如usb接口等)。
对于Java,我们需要先了解一些访问权限修饰符:
在java中,是通过类来实现封装的,我们使用private关键字对类的成员变量或者成员方法进行修饰,把它们打包成一个类,再提供一些公开的接口,就实现了对类的封装。
简单来说,封装就是对外隐藏类内部的实现细节。
public class Computer {
private String cpu; // cpu
private String memory; // 内存
public String screen; // 屏幕
String brand; // 品牌---->default属性
//公开的接口,可以用来访问private修饰的成员变量
public Computer(String brand, String cpu, String memory, String screen) {
this.brand = brand;
this.cpu = cpu;
this.memory = memory;
this.screen = screen;
}
public void Boot(){
System.out.println("开机~~~");
}
public void PowerOff(){
System.out.println("关机~~~");
}
public void SurfInternet(){
System.out.println("上网~~~");
}
}
public class TestComputer {
public static void main(String[] args) {
Computer p = new Computer("HW", "i7", "8G", "13*14");
System.out.println(p.brand); // default属性:只能被本包中类访问
System.out.println(p.screen); // public属性: 可以任何其他类访问
// System.out.println(p.cpu); // private属性:只能在Computer类中访问,不能被其他类访问
}
}
2.封装扩展— —包
1.包的概念
2.导入包中的类
java中提供了很多现成的类给我们使用,比如导入Date类,可以使用java.util.Date导入java.util这个包中的Date类:
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是这个写法非常麻烦,如果我们后面要实例化多个Data类的话,就要写很多次重复的java.util.Date,对此,我们可以使用import语句导入一个包。如
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
如果需要导入java.util的其他类,可以使用java.util.*(通配符)(这样就可以不用写具体要导入的类了)
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
但是,不建议使用通配符,还是建议导入一个具体的类,否则会导致一些问题,比如要导入两个包,但是这两个包中都有相同的类,这个时候我们在使用这个类时,编译器会报错
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
java.util和java.sql两个包中都有Data类,这就会导致我们在实例化Data时编译器不知道我们导入的是哪个包中的类而出现报错。
我们还可以使用import static来导入一个包,这样的优点是可以不用实例化一个对象,可以直接使用包中的方法或者字段。如
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
3.自定义包
![](https://img-blog.csdnimg.cn/direct/b8cade1d1e9643c195e0e23b2172d9a8.png)
在输入框中写入自定义包的名称即可创建一个包。
五、static成员
1.static关键字的引入
我们以学生类为例(同一个班级):
public class Date{
public static void main(String [] args){
Student student1 = new Student(12,"小米","123","1班");
Student student2 = new Student(12,"小黄","134","1班");
Student student3 = new Student(13,"小刘","167","1班");
}
}
class Student{
int age;
String name;
String number;
String className;
public Student(int age, String name, String number, String className) {
this.age = age;
this.name = name;
this.number = number;
this.className = className;
}
}
通过观察,上述代码并没有什么问题,但是仔细看看我们发现,既然student1,student2,studnt3都是同一个班的学生,那我们是否可以使用某种方法可以让className只赋值一次,并且在后面创建的对象都能直接访问它呢?
2.static修饰成员变量
对于上面Student类的问题,我们引入关键字static可以解决,我们可以使用static修饰成员变量和成员方法,被修饰的成员变量或者成员方法就不在需要通过对象来访问了,可以直接通过类名来访问(在同一个类中则可以直接访问),并且static修饰的成员变量或成员方法只有一份,存在方法区中。
public class Date{
public static void main(String [] args){
Student student1 = new Student(12,"小米","123");
Student student2 = new Student(12,"小黄","134");
Student student3 = new Student(13,"小刘","167");
student3.className="1班";//可以通过对象来访问,但是不建议,因为className属于类,已经不依赖于对象来访问了
Student.className="1班";//这是最准确的访问方式,通过类名来访问
}
}
class Student{
int age;
String name;
String number;
//static修饰className
static String className="1班";
public Student(int age, String name, String number) {
this.age = age;
this.name = name;
this.number = number;
}
}
3.static修饰成员方法
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "Bit306";
// ...
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.classRoom);
}
}
//编译失败:Error:(10, 35) java: classRoom 在 extend01.Student 中是 private 访问控制
public class Student{
// ...
private static String classRoom = "Bit306";
// ...
public static String getClassRoom(){
return classRoom;
}
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.getClassRoom());
}
}
4.使用static修饰成员方法的注意事项
3. 不能在静态方法中访问任何非静态成员变量
public static String getClassRoom(){
System.out.println(this);
return classRoom;
}
// 编译失败:Error:(35, 28) java: 无法从静态上下文中引用非静态 变量 this
public static String getClassRoom(){
age += 1;
return classRoom;
}
// 编译失败:Error:(35, 9) java: 无法从静态上下文中引非静态变量age
其实理解起来非常简单,因为静态的是不依赖于对象的,如果在静态方法中使用了任何非静态的成员变量或者成员方法,都违背了不依赖于对象这一特性,因为一旦使用了非静态成员变量或成员方法,都需要通过实例化一个对象来访问。
因此,在非静态方法中可以调用静态方法,在静态方法中不能使用任何非静态的变量或方法,如果一定要使用,只能通过对象.成员变量/成员方法() 来使用。
5.static成员变量的初始化
1.就地初始化
即在类中创建静态成员变量是顺带赋值
2.通过get/set方法初始化
3.通过构造方法初始化
4.静态代码块初始化(下面提到)
6.静态代码块
静态代码块即static修饰的代码块,即
static{
}
静态代码块一般用来初始化静态成员变量。
public class Test{
public static void main(String[] args){
Student student=new Student(18,"小","5班");
student.age=18;
Student.className="1bang";
}
}
class Student{
int age;
String name;
static String className;
String number;
//构造代码块,又叫示例代码块,在创建对象时才会调用
{
System.out.println("实例代码块被调用了。。。。");
}
//static代码块,当类加载的时候调用,并且只会被调用一次
static{
System.out.println("静态代码块被调用了。。。。。");
}
public Student(int age, String name, String number) {
this.age = age;
this.name = name;
this.number = number;
System.out.println("构造方法被调用了......");
}
}
可以看到,它们执行的先后顺序。
六、对象的打印
public class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public static void main(String[] args) {
Person person = new Person("Jim","男", 18);
System.out.println(person);
}
}
// 打印结果:day20210829.Person@1b6d3586
由于对象名是一个引用,所以打印的时一个引用,那么如何通过对象名打印对象中的属性呢?只需要重写toString方法即可。
public class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//对toString方法进行重写,打印结果为对象的成员
@Override
public String toString() {
return "[" + name + "," + gender + "," + age + "]";
}
public static void main(String[] args) {
Person person = new Person("Jim", "男", 18);
System.out.println(person);
}
}
class Test{
public static void main(String[] args){
Person person=new Person("小于","哈哈",18);
System.out.println(person);//直接用对象名打印,打印结果为哈希值
}
}
为什么重写之后就可以打印对象中的成员了?看下图:
可以看到,println方法里有一个valueof方法,valueof方法里又有一个toString方法,因此我们对toString方法重写可以达到打印对象成员的目的。