1. 类和对象的初步认识
在学习java语言的时候,经常听说java是一门面向对象的语言;在java里面也有一切皆对象这样的说法。那么到底什么是类,什么又是对象呢?
1.1面向对象与面向过程
以洗衣服为例,给大家介绍面向对象和面向过程
1.1.1面向过程
我们传统的洗衣服得经历一下步骤:
注重的是洗衣服的过程
1.1.2面向对象
现在如果我们有了一个洗衣机,那么洗衣服就简单很多了,只需要四个对象就能够完成。人、衣服、洗衣粉、洗衣机,至于具体的细节,比如洗衣机是怎么洗衣服的我们并不需要考虑,只需要对象之间进行交互即可。
1.2 对象
对象也就是生活中的实体,比如:洗衣机。但是计算机并不认识洗衣机,需要开发人员通过代码对洗衣机进行描述——类。
1.3 类定义和使用
类也就是对对象的描述。但其实,在类与C语言里面的结构体非常像,在初学时,你可以把类当做结构体来学习,定义了一个新的类,也就是定义了一个新的类型,也可以像 int a; 一样创建一个变量。
同时我们也要想一想,要描述一个实体(对象)肯定要描述它的属性(颜色、大小、品牌啥的)、它的功能或行为(比如能洗衣服等),这也就是我们类需要做的事。
1.3.1 类的构成
类主要由成员变量和成员方法构成
成员方法又分为:
- 普通成员变量
- 静态成员变量(被static修饰)
成员方法又分为:
- 普通成员方法
- 静态成员方法(被static修饰)
1.3.2 类的定义格式
java中定义类时,需要用到class关键字,具体语法如下:
// 创建类
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("定时功能");
}
//这些就叫做成员方法
}
注意事项
- 类名注意采用大驼峰定义------------------点这里了解>>>>>大驼峰和小驼峰的定义和区别
- 所有成员前面暂时都用public关键字,并且不带static关键字,后面会进一步阐述
- public修饰的类必须要和文件名相同
1.3.3 类的实例化
定义了一个类,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型(是不是感觉越来越像结构体了)
用类类型创建对象的过程(也就是创建一个结构体变量),称为类的实例化,在java中采用new关键字,配合类名来实例化对象。
直接上代码:
class Studnet{
public String name;
public int age;
public void sleep(){
System.out.println("我要睡觉了");
}
}
// 类和类不能够嵌套,相互独立
public class test{
public static void main(String[] args) {
Student s1= new Student();
s1.name="zhangsan"; //和结构体一样,通过**.**进行访问成员
s1.age=18;
s1.sleep();
}
}
注意事项
- new关键字用于创建一个对象的示例
- 使用 . 来访问对象中的属性和方法.
- 同一个类可以创建多个实例(变量名不同就行)
1.3.4 this功能1——this引用
在类和对象里面将会介绍this的两个功能,这里先介绍第一个功能——this引用。
首先为什么会有this引用,我们先见以下的代码
class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d){
year = year;
month = month;
day = day;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
}
// 这里创建了一个Date类
public class Test{
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();
}
}
对于上述代码一共有两个问题:
public void setDay(int y, int m, int d){
year = year;
month = month;
day = day;
}
- 正如上面的代码,在成员方法里面形参名不小心与成员变量名的名字相同了,那函数体中到底是谁给谁赋值?成员变量给成员变量?参数给参数?参数给成员变量?成员变量参数?估计自己都搞不清楚了。
d1.printDate();
d2.printDate();
d3.printDate();
//这是打印日期
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
} //可是我们的这方法里面打印并没有任何关于对象的说明,那么printDate函数如何知道打印的是
//哪个对象的数据呢?
- 正像上面说的那样,明明我们的类方法里面明明没有任何关于对象的描述,可是为什么计算机就能准确的打印出d1的日期、d2的日期呢?我们知道肯定与前缀 d1. 和 d2. 有关可原理到底是为什么呢,这就与this有关
其实在我们所有类的成员方法中都省略了一个参数,也就是this
也就是说我们的类方法应该是这样的
public class Date {
public int year;
public int month;
public int day;
public void setDay(Date this,int y, int m, int d){ // 隐藏了第一个参数
year = year;
month = month;
day = day;
}
public void printDate(Date this){ // 同样隐藏了第一个参数
System.out.println(year + "/" + month + "/" + day);
}
}
this引用指向当前对象,谁调用了成员方法,this就指向谁(比如d1.printDate(),那么this指向的就是d1这个对象),所以我们在所有的成员方法的成员变量之前最好加上this.
public class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d){
this.year = year;
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(this.year + "/" + this.month + "/" + this.day);
} //Date this 可以不用写,编译器会自动完成
}
以上这个代码才是最标准的代码!!!!
注意事项
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- this只能在"成员方法"中使用
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收
1.4 对象的初始化
通过之前的学习我们知道,在java里面若是局部变量不进行初始化,那么编译器会直接报错的(这个与C语言不同,C语言若是不初始化会赋一个随机值)
对象的初始化一共有三种方式
- 默认初始化
- 就地初始化
- 构造方法初始化
1.4.1默认初始化
默认初始化是指在创建一个新对象时,对象的成员变量会被自动赋予默认值。
public class Date {
public int year;
public int month;
public int day;
public void show() {
System.out.println(this.year);
System.out.println(this.month);
System.out.println(this.day);
}
public static void main(String[] args) {
Date d = new Date();
d.show();
//此时成员变量并没有进行初始化,但是仍然可以使用,编译器并不会报错
}
}
默认值是根据成员变量的类型决定的。在Java中,以下是一些常见类型的默认初始化值:
数据类型 | 默认值 |
---|---|
byte | 0 |
char | ‘\u0000’ |
short | 0 |
int | 0 |
long | 0L |
boolean | false |
float | 0.0f |
double | 0.0 |
reference(引用类型) | null |
要了解这是为什么就需要知道 new 关键字背后所发生的事情:
Date d = new Date( );
1. 检测对象对应的类是否加载了,如果没有加载则加载
2. 为对象分配内存空间
3. 处理并发安全问题,比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
4. 初始化所分配的空间
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值(如上表)
5. 设置对象头信息(关于对象内存模型后面会介绍)
6. 调用构造方法,给对象中各个成员赋值
当然我们也可以在main方法里面,在创建了对象时候通过 “.” 操作符进行访问成员变量修改其内容。
1.4.2 就地初始化
就地初始化也就是就地初始化是指在成员变量声明的同时进行赋值操作,即在成员变量被创建的时候就给其赋初值。
public class Date {
public int year = 1900;
public int month = 1;
public int day = 1;
public static void main(String[] args) {
Date d1 = new Date(2021,6,9);
Date d2 = new Date();
}
}
但是这样的话,所有对象的这些属性就都是一样的,如果要修改的话,还是得在创建了对象时候改变其值,就没必要,反而麻烦了,除非这个是所有这个类的对象都满足的属性(其实这个后面就是后面的静态成员变量)
1.4.3 构造方法(重要)
构造方法就是用来给成员变量进行赋值的。构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
1.4.3.1 构造方法的结构
构造方法的特点
- 名字必须与类名相同,没有返回值类型,设置为void也不行
- 一般情况下用public修饰
- 在创建对象的时候编译器就会自动调用
见代码:
class student{
public String name;
public int age;
public void show(){
System.out.println("name "+this.name);
System.out.println("age "+this.age);
}
public student(String name,int age){
this.name=name;
this.age=age;
System.out.println("这是构造方法");
}
//这就是一个构造方法
}
public class Test3{
public static void main(String[] args) {
student s1=new student("lijunjie",18);
//这时候创建了对象就会自动调用构造方法
}
}
注意事项
- 若是在定义类的时候没有写构造方法,那么编译器会自动生成一个不带参数的构造方法
public student(){}
但是什么代码块里面什么都没有- 若是自己写了一个构造方法,并且这个构造方法有形参,那么在new一个对象的时候括号里面必须有实参
1.4.3.2 构造方法的重载
前面说了,构造方法也是一种方法,既然是方法就可以重载(改变形参的类型或者数量)
点击可以了解方法重载->>>>>>>>>>>>>>>方法重载
class student{
public String name;
public int age;
public void show(){
System.out.println("name "+this.name);
System.out.println("age "+this.age);
}
public student(String name,int age){
this.name=name;
this.age=age;
System.out.println("这是构造方法");
}
// 两个参数的构造方法
public student(){
this.age=18;
this.name="lijunjie";
}
// 无参数的构造方法
public student(int age) {
this.age = age;
}
// 一个参数的构造方法
}
public class Test3{
public static void main(String[] args) {
student s1=new student("lijunjie",18);
student s2=new student(18);
student s3=new student();
//正如前面的方法重载,java会自动根据你输入的参数类型判断你用的是哪一个方法;
}
}
1.4.3.3 this功能2——简化构造方法
即通过this调用其他的构造方法,一般是把含有参数的构造方法放到无参的构造构造方法里面(其实我在学习到这里的时候感觉有“亿”点点多余,明明有一个含有参数的构造方法就行了,非要写两个构造方法,但是先学习知识点吧,可能后面会了解到这样的好处)
this调用其他构造方法需要注意的点
- 首先是格式是this(括号里面是实参)
- 其次this()必须是第一条语句
- 不能够成环,也就是我调用了你,你又调用了我,这样就会陷入死循环
class student{
public String name;
public int age;
public void show(){
System.out.println("name "+this.name);
System.out.println("age "+this.age);
//System.out.println(classroom);
}
public student(){
this("zhangsan",10); //这里用了this,括号里面是实参
System.out.println("执行无参构造方法");
}
public student(String name,int age){
this.name=name;
this.age=age;
System.out.println("这是构造方法");
}
}
public class Test3{
public static void main(String[] args) {
student s1=new student();
}
}
这里是运行结果
注意一下这里的调用顺序:先是调用了无参的构造方法,然后是调用了含有参数的构造方法
1.4.3.4 构造方法的快捷键
- 首先右键鼠标,点击这个
- 然后点击这个
- 最后会出现这个,你可以选一个变量,也可以“ctrl”同时选择多个
这个就是结果:
2. 封装
面向对象程序三大特性:封装、继承、多态而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
这里并不多讲封装,因为学习的还是太少了,暂时不能够理解封装的真正作用,只介绍一些知识。
2.1 访问限定符
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。
Java中提供了四种访问限定符:
- private
- default(这个就是“默认”,不需要特别注明)
- protected
- public
- protected主要是用在继承中,继承部分详细介绍
- default权限指:什么都不写时的默认权限
- 访问权限除了可以限定类中成员的可见性,也可以控制类的可见性
- 一般情况下成员变量设置为private,成员方法设置为public(这个就与封装有关,但是这里学的还是太浅了,所以只需要记住,以后学多了自然会明白)
2.2 包
2.2.1 包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。 有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可
2.2.2 导入包中的类
导包就有点像C语言中包含一个头文件,正如我的前面一篇博客里面有一些关于数组的常见的方法(数组常用的方法),里面就是通过导了一个包
import java.util.Arrays
我们就得到了一些处理数组的方法
- 最简单粗暴的方法
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
- 使用 import语句导入包
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
- 还可以使用import 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) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
- 可以使用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);
}
}
2.2.3 自定义包
基本规则
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1)
- 若是新建了一个包,则代码的最上面一定会有一个 package 语句指定该代码在哪个包中
- 创建com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码
- 如果一个类没有 package 语句, 则该类被放到一个默认包中(也就是src)
操作步骤
- 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包
- 在弹出的对话框中输入包名
- 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可
- 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了
- 同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句
2.2.4 包的访问权限控制举例
Computer类位于com.bit.demo1包中,TestComputer位于com.bit.demo2包中:
package com.bit.demo1;
public class Computer {
private String cpu; // cpu
private String memory; // 内存
public String screen; // 屏幕
String brand; // 品牌
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("上网~~~");
}
}
///
package com.bite.demo2;
import com.bite.demo1.Computer;
public class TestComputer {
public static void main(String[] args) {
Computer p = new Computer("HW", "i7", "8G", "13*14");
System.out.println(p.screen);
// System.out.println(p.cpu); // 报错:cup是私有的,不允许被其他类访问
// System.out.println(p.brand); // 报错:brand是default,不允许被其他包中的类访问
}
}
// 注意:如果去掉Computer类之前的public修饰符,代码也会编译失败
2.2.5 常见的包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包。
这个后面学多了自然会了解
3.static
3.1 static修饰成员变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是这个类所有对象所共享的
- 静态成员变量一般直接通过类名+“.”进行访问
class Student {
public String name;
public int age;
public static String classroom = "B407"; //静态成员变量
public Student(){
this("zhangsan",10);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test3{
public static void main(String[] args) {
Student s1=new Student();
System.out.println(Student.classroom); // 通过类名进行访问
}
}
static当然也可以修饰方法,这个时候方法同样是类的方法,所有这个类的对象所共有的
但是注意
- 不可以在在静态方法中访问任何非静态成员变量
- 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用
3.2 静态成员变量的初始化
这个其实和之前的普通成员变量初始化并没有什么区别
- 默认初始化
- 就地初始化
- 构造方法初始化
4.代码块
代码块总共有四种类型:
- 普通代码块(就是普通的代码)
- 构造块
- 静态块
- 同步代码块(后续讲解多线程部分再谈)
4.1 构造代码块(在类定义里面,不再类的方法里)
结构:不加修饰符,啥也没有
功能::实例代码块。构造代码块一般用于初始化实例成员变量。(其实学到这里还是有一些疑问的,因为初始化的方法有很多,为什么还要这个,而且这个就和就地初始化没什么太大的区别)
class Student {
public String name;
public int age;
public static String classroom= "B407" ;
{
this.name="lijunjie";
this.age=18;
System.out.println("这是一个构造代码块");
} //这个就是构造块
public Student(){
this("zhangsan",10);
System.out.println("这是无参数的构造方法");
}
public Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("这是有参数的构造方法");
}
}
public class Test3{
public static void main(String[] args) {
Student s1=new Student();
}
}
由这个结果可以得出,构造块比构造方法先执行
只有在创建对象的时候构造块才会执行(这个与后面的静态块不一样)
4.2 静态代码块
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。
class Student {
public String name;
public int age;
public static String classroom ;
static{
classroom= "B407";
System.out.println("这是一个静态代码块");
} //静态代码块
{
this.name="lijunjie";
this.age=18;
System.out.println("这是一个构造代码块");
} // 构造代码块
public Student(){
this("zhangsan",10);
System.out.println("这是无参数的构造方法");
}
public Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("这是有参数的构造方法");
}
}
public class Test3{
public static void main(String[] args) {
System.out.println(Student.classroom);
}
}
可以看到我们并没有创建对象的时候
- 构造代码块并没有运行
- 静态代码块运行了
再试试在main里面加上这块代码
Student s1= new Student();
Student s2= new Student();
由这个运行结果可以看到:
- 静态代码块试运行一次
- 构造代码块在每一次创建对象的时候都会运行一次
4.3 构造代码块和静态代码块的调用总结
- 构造代码块在每一次创建对象的时候都会调用一次
- 静态代码块是只要加载了类,就会被调用,并且只调用一次
- 静态代码块在构造代码块之前先调用
5.对象的打印
对象的打印在java里面有一种快捷的方式
- 在类中右键鼠标
- 点击toString()
- 想打印什么就选什么(静态成员变量无法打印)
6.总结
在学习java的类和对象的时候,尽管和C语言的结构体有一点类似,但是还是学习了很多新的东西,而且类和对象的确是一种很抽象的东西,但是总算是写了一天的代码,总算把它总结完了