文章目录
类和对象的初步认识
在讲类和对象之前,我们我们需要先真正理解什么是面向对象和面向过程。
我们以洗衣服举例:
面向过程,如上图,你洗衣服关注的是每一步我应该干什么,有严格的顺序性。
面向对象,如上图,洗衣服是“人”、“衣服”、“洗衣机”、“洗衣粉”四个对象交互完成的,你不用关心衣服扔进洗衣机然后洗衣机怎么洗,你只需要把洗衣粉、衣服给洗衣机即可,然后洗衣机开始洗衣服。
面向对象你只需要把握以下三点即可编程
1.找对象
2.创建对象
3.使用对象
那对象是怎么来呢?对象是由类而来的
比如说:我们是人嘛,每个人都有共同的属性——姓名、性别、年龄。。。
也有很多共同的行为——吃饭、睡觉、上学、玩游戏。。。
那由这些共同的属性和行为我们可以放到一个模板(我们可以称这个模板为人)里,由这个模板可以产生很多的对象,而这个模板也就是一个类。
以大白话说就是:人类是一个类(它不是一个实体),单独到个人(实体)是一个对象。
创建对象是通过new关键字——把类实例化
(图片来自比特就业课)
提示:以下是本篇文章正文内容,下面案例可供参考
一、类和对象的实例化
基本语法:
//创建类
Class 类名称{
field;//成员属性
method;//成员方法
}
//实例化对象
类名称 对象名 =new 类名称();
实战举例:
class Person{//一个类由属性和字段组成
//属性(也叫字段、成员变量)
//成员变量又分1.普通成员变量,2.静态成员变量(后面说)
public String name;//普通成员变量
public int age=18;//属性是可以赋值的,但不建议
//因为后面创建对象是单独的个体,每个个体属性不一定全一样
public int num;
//方法(行为)
//方法也分1.普通成员方法,2.静态成员方法
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
public class test {
public static void main(String[] args) {
int a =1;
Person xiaoHei=null;
//和上面int a =1一样,Person是一种类型,xiaoMing是一个变量,然后我们可以给它赋初值
//注意:由类定义的变量是一个引用变量,初值可以给null
Person xiaoMing=new Person();//由Person这个类实例化一个对象
//成员变量age怎么访问?
System.out.println(xiaoMing.age);//打印18
//我们前面说过xiaoMing是一个引用变量,引用了Person这个对象
//对象里包含了name、age等等成员变量
//我们可以通过xiaoMing.age来访问age
System.out.println(xiaoMing.name);//如果没有赋值直接打印是打印null
System.out.println(xiaoMing.num);//如果没有赋值直接打印是打印0
//解释:
//整形:byte、short、int、long默认值0
//浮点型:double、float默认值为0.0
//字符型:char默认值"\u0000"
//布尔型:boolean默认值false
//引用数据类型:数组、类、接口默认值null
//既然可以访问这些成员变量,我们就可以给它赋值
xiaoMing.num=1000;
System.out.println(xiaoMing.num);//打印1000
xiaoHei=new Person();
System.out.println(xiaoHei.num);
//打印0,同一个类的创建的不同对象是不影响的,
// 比如你给小明一个号码牌,不代表所有人类都有这样一个号码牌
//其他人(对象)如果没有给num赋值,他们的num依旧默认是0
//调用方法
xiaoMing.eat();//引用变量名.方法() 即可
}
}
普通成员变量需要
二、类的成员
2.1字段/属性/成员变量
代码如下(示例):
class Person{//一个类由属性和字段组成
//属性(也叫字段、成员变量)
//成员变量又分1.普通成员变量,2.静态成员变量(后面说)
public String name;//普通成员变量
public int age=18;//属性是可以赋值的,但不建议
//因为后面创建对象是单独的个体,每个个体属性不一定全一样
public int num;
}
public class test {
public static void main(String[] args) {
int a =1;
Person xiaoHei=null;
//和上面int a =1一样,Person是一种类型,xiaoMing是一个变量,然后我们可以给它赋初值
//注意:由类定义的变量是一个引用变量,初值可以给null
Person xiaoMing=new Person();//由Person这个类实例化一个对象
//成员变量age怎么访问?
System.out.println(xiaoMing.age);//打印18
//我们前面说过xiaoMing是一个引用变量,引用了Person这个对象
//对象里包含了name、age等等成员变量
//我们可以通过xiaoMing.age来访问age
System.out.println(xiaoMing.name);//如果没有赋值直接打印是打印null
System.out.println(xiaoMing.num);//如果没有赋值直接打印是打印0
//解释:
//整形:byte、short、int、long默认值0
//浮点型:double、float默认值为0.0
//字符型:char默认值"\u0000"
//布尔型:boolean默认值false
//引用数据类型:数组、类、接口默认值null
//既然可以访问这些成员变量,我们就可以给它赋值
xiaoMing.num=1000;
System.out.println(xiaoMing.num);//打印1000
xiaoHei=new Person();
System.out.println(xiaoHei.num);
//打印0,同一个类的创建的不同对象是不影响的,
// 比如你给小明一个号码牌,不代表所有人类都有这样一个号码牌
//其他人(对象)如果没有给num赋值,他们的num依旧默认是0
}
}
2.2方法
class Person{
//方法(行为)
//方法也分1.普通成员方法,2.静态成员方法
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
public class test {
public static void main(String[] args) {
Person xiaoMing=new Person();//由Person这个类实例化一个对象
//调用方法
xiaoMing.eat();//引用变量名.方法() 即可
}
}
2.3static关键字
static修饰的静态成员变量
import java.util.Arrays;
class Person{
public int age;
public static int count;//静态成员变量(也是成员变量)
}
public class test {
public static void main(String[] args) {
Person p1=new Person();
Person p2=new Person();
p1.count++;
p1.age++;
System.out.println(p1.count);//打印1
System.out.println(p1.age);//打印1
p2.count++;
p2.age++;
System.out.println(p2.count);//打印2
System.out.println(p2.age);//打印1
}
}
我们new了两个对象p1和p2,我们age++和count++由前面的知识可知道,不赋值的整形应该是默认为0,那我们打印的age和count应该都是1,而不同对象的age和count也应该是单独的、互补影响的,所以4个打印都应该是1啊,同样验证,普通成员变量age符合我们上面所说的要求,p1.age和p2.age打印的都是1。但是问题出现了,p2的count(静态成员变量)打印的是2
count默认是0,却打印了2,就好像p1.count和p2.count进行++操作的时候是对同一个内容进行操作一样,这不符合逻辑啊?但事实就是这样哈哈哈
静态成员变量又称类变量,被static修饰过后,count是被放到方法区中(不是堆区!)
ps:方法区会存储对应类的字节码文件(.class文件),还会存储静态的成员变量
静态成员变量不是对象的,它是类的,且在方法区中只有一个,不像对象,每一个对象一个对应的成员变量。
到这里也解释了,为什么我们用p1.count++和p2.count++会是对同一空间同一内容进行操作(count只有一个)。而p1.age,和p2.age是两个不同空间的不同的内容。
我们上面代码new p1 和new p2来调用count都是没有意义的,你可以直接使用类名Person来调用count,也间接的节约了创建对象所需的空间
static修饰的静态成员方法
直接用类就可以调用,其他的和普通成员方法没区别
class Person{
public static void xiao() {
System.out.println("haha");
}
}
public static void main(String[] args) {
Person.xiao();//打印haha,静态成员方法不需要new对象
}
注意:静态的成员变量是不可以在普通方法中定义的
public static void m() {
static int a=1;//这里会报错
}
普通方法里能调用静态方法
public static void hello() {
System.out.println("你好");
}
public void print() {
hello();//这里不会报错,可以正常调用
}
静态方法里不能调用普通方法
class Person{
public String name;
public int age;
public static void staticFunc() {
print();//这里会报错,静态方法内不允许调用普通方法
}
public void print() {
System.out.println("姓名"+name+"年龄"+age);
}
}
原因很简单,静态方法是不依赖对象的,我们本是可以通过类名进行调用,但是普通成员变量是依赖对象的,你如果调用了staticFunc里的print,print里面的name和age哪里来的对象呢?
当然了,如果你非要抬杠,我就要在静态方法里调用普通方法怎么办,如下
class Person{
public String name;
public int age;
public static void staticFunc() {
Person p=new Person();
//你在静态方法里new一个对象
person.print();
}
public void print() {
System.out.println("姓名"+name+"年龄"+age);
}
}
再举一个相似的例子:
public class TestDemo {
public static void func1() {
}
public void func2() {
}
public static void main(String[] args) {
func1();//静态方法可以直接调用
//func2();//普通方法直接调用会报错
//普通方法的 正确调用方式如下
TestDemo test=new TestDemo();
test.func2();
}
}
综上:静态方法可以直接通过类名调用,普通方法则需要new一个对象
final修饰的常量/静态常量
class Test{
public int a;
public static int count;
public final int size=1;//对象里
//被final修饰的叫常量,也属于对象,后续不可更改,类似C语言const
public final static int c=99;//在方法区
//静态的常量,属于类本身,有且只有一份,被final修饰后续不可更改
}
一个对象存储在哪里和final无关,还是取决于是否有static
最后:注意——static定义的东西只能在类里面定义,不可以在方法里定义
小知识:main函数是不是静态static的都可以,取决于JVM的设计逻辑
2.4小结
1.静态成员变量的访问方式是通过类名.静态成员属性/方法,只要是静态的就是不依赖于对象的,因为你普通成员变量需要通过访问对象来实现,但是静态的直接通过类就可以进行访问了
2.普通成员方法调用需要对象的引用,但是static的静态成员方法可以直接通过类名来调用
3.静态的成员变量是不可以在方法中定义的(因为它是类变量)
4.普通方法里可以调用静态方法
5.静态方法里不可以调用普通方法
6.静态方法可以直接通过类名调用,普通方法则需要new一个对象
三、封装
3.1private实现封装
private/public两个关键字表示“访问权限控制”
1.被public修饰的成员变量或成员方法,可以直接被类的调用者使用。
2.被private修饰的成员变量或成员方法,不能被类的调用者使用
class Test{
public String name;
}
class Name{
private String name;
//一旦属性或方法被private修饰,则该属性或方法就被封装起来了
//封装的效果就是:被封装的属性或方法只能在当前的类中使用
}
public class TestDemo {
public static void main(String[] args) {
Test t=new Test();
t.name="abc";//没有被private修饰可以用
Name n=new Name();
n.name="abc";//被private修饰了,你再用就会报错
}
}
被类Name中的name被private修饰后的,只能在当前的类中进行访问,打个不恰当的比方,现在疫情封城了,你只能在你所在的城市里活动,不允许出城。private就相当于封城令,而被修饰的属性/方法就是我们人,当前类就是我们当前的城市。
那说了这么多,我们为什么要用private呢?
1.我们用了private后,它修饰的属性或方法(你不想让别人拿到修改的东西)就会更加安全。
2.如果不用private,比如我之前的类Name中我们是用public String name;
如果哪天有人给我把类里面的name改成了myname,那后面很多程序用的时候是用的对象名.name,你现在name没了啊,难道还要一个一个去往下改吗?这显然不现实。用了private之后用户只需要调用你公开的方法(3.2的getter和setter方法)即可,你在类里面把name改成什么都可以。
注意事项:
1.private不光能修饰字段,也能修饰方法
2.通常情况下我们会把字段设为private属性,但是方法是否需要设计为public,就需要视情况而定,一般我们希望一个类只提供“必要的”public方法,而不是所有方法都无脑设为public
3.2getter和setter方法
我们3.1用private封装了属性或方法,那这个时候肯定会有小伙伴问,那如果我真的想要使用那个被封装的属性或方法呢?也很简单,代码如下:
class Name{
private String name;
//一旦属性或方法被private修饰,则该属性或方法就被封装起来了
//封装的效果就是:被封装的属性或方法只能在当前的类中使用
public String getName() {//获取Name的方式
return name;
}
public void setName(String myName){
name=myName;
}
}
public class TestDemo {
public static void main(String[] args) {
Test t=new Test();
t.name="abc";//没有被private修饰可以用
Name n=new Name();
//n.name="abc";//被private修饰了,你再用就会报错
n.setName("xiaoMing");//外部给name赋值
String x=n.getName();//外部接收name
System.out.println(x);//打印xiaoMing
}
}
打印效果如下:
注意事项:
1.getName即为getter方法,表示获取这个成员的值。
2.setName即为setter方法,表示设置这个成员的值。
四、构造方法
4.1基本语法
构造方法是一种特殊方法,方法名和类名是相同的,且没有返回值,它使用关键字new实例化新对象时会被自动调用,用于完成初始化操作。
new执行过程:
1.为对象分配空间
2.调用对象的合适的构造方法
(合适就意味着构造方法不唯一)
语法规则:
1.方法名称必须与类名称相同
2.构造方法没有返回值类型声明
3.每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)
示例如下:
class people{
public people(){
System.out.println("people()不带参数的构造方法");
}
}
那我们怎么调用这个构造方法呢?
public static void main(String[] args) {
people p=new people();
}
new一个对象直接执行代码即可,打印效果如下:
再来看看带参数的构造方法及其使用方式
class people{
public String name;
public people(){
System.out.println("people()不带参数的构造方法");
}
public people(String name){
this.name=name;
System.out.println("people(String)带String参数的构造方法");
}
}
public class TestDemo {
public static void main(String[] args) {
people p=new people("xiaoHei");
}
}
new对象的时候把相应构造方法需要的参数放进去即可完成调用,打印效果如下:
冷知识:如果你原先的类里没有任何构造方法,那么编译器会默认生成一个不带有参数的构造函数。:
class people{
}
public class TestDemo {
public static void main(String[] args) {
people p=new people();
}
}
你执行上面的代码也不会报错,系统会默认为你生成如下代码
class people{
public people(){
}
}
注意事项:
1.如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数。
2.若类中定义了构造方法,则默认的无参构造将不再生成。
3.构造方法支持重载,规则和普通方法的重载一致。
4.2this关键字
我们来回顾一下3.1的代码
class Name{
private String name;
//一旦属性或方法被private修饰,则该属性或方法就被封装起来了
//封装的效果就是:被封装的属性或方法只能在当前的类中使用
public void setName(String myName){
name=myName;//赋值方法一
}
}
有时候我们可能不小心把形参写成了成员名一样
class Name{
private String name;
//一旦属性或方法被private修饰,则该属性或方法就被封装起来了
//封装的效果就是:被封装的属性或方法只能在当前的类中使用
public void setName(String name){
name=name;//局部变量优先使用
}
}
但上述的赋值方法是默认形参赋给形参,你并没有真正实现成员name的赋值,那如果我们写的函数形参就是和成员变量名一样呢?我们用this
class Name{
private String name;
//一旦属性或方法被private修饰,则该属性或方法就被封装起来了
//封装的效果就是:被封装的属性或方法只能在当前的类中使用
public void setName(String name){
this.name=name;
//this表示当前对象的引用
}
}
注意事项:
1.this.data表示调用当前对象的属性
2.this.func()表示调用当前对象的方法
3.this()调用当前对象的其他构造方法——this()只能存在于构造方法中
关于3,解释如下:
class people{
public String name;
public people(){
this("hh");
System.out.println("people()不带参数的构造方法");
}
public people(String name){
this.name=name;
System.out.println("people(String)带String参数的构造方法");
}
}
public class TestDemo {
public static void main(String[] args) {
people p=new people();
}
}
我们从main函数进去,new了一个对象,开始调用不带参数的构造方法,不带参数的构造方法里有一个this(),this()表示调用当前对象的其他构造方法,也就说这里的this(“hh”)=people(“hh”),也就是进入了另一个构造方法 public people(String name),然后打印"people(String)带String参数的构造方法",出 public people(String name),回到public people(),打印"people()不带参数的构造方法"
一般的,我们会在当前对象属性/方法前加this.因为你加上一定没错,不加可能当前对象属性/方法会与形参相同导致错误。简言之,加上一定没错,不加可能有错。
五、认识代码块
字段的初始化方法有:
1.就地初始化
2.使用构造方法初始化
3.使用代码块初始化
前面两种我们已经学习过了,接下来我们介绍第三种方式,使用代码块初始化
5.1什么是代码块
使用{ }定义的一段代码
根据代码块定义的位置及关键字,又可分为以下四种:
1.普通代码块
2.构造代码块
3.静态块
4.同步代码块(后续讲解多线程部分继续学习)
5.2普通代码块
定义在方法中的代码块,直接用{ }定义,示例如下:
{
代码内容
}
5.3构造代码块
也叫实例代码块,示例如下:
class person{
private String name;//实例成员变量
private int age;
private String sex;
}
{//实例代码块
this.name="bit";
this.age=12;
this.sex="m";
}
实例代码块优先于构造函数运行。
5.4静态代码块
使用static定义的代码块,一般用于初始化静态成员属性。 示例如下:
static{
count=10;//只能访问静态数据成员
}
5.5引申
class people{
public String name;
public people(){
System.out.println("people()不带参数的构造方法");
}
public people(String name){
this.name=name;
System.out.println("people(String)带String参数的构造方法");
}
{
System.out.println("实例代码块");
}
static{
System.out.println("静态代码块");
}
}
public class TestDemo {
public static void main(String[] args) {
people p=new people();
}
}
我们由构造方法那块知识知道,我们new了一个不带参数的对象然后直接运行,一定会得到不带参数的构造方法里面的一些东西,然后我们people这个类里面代码块的顺序是构造方法->实例代码块->静态代码块,但我们上面这段代码实际运行情况并不是这样
我们是优先运行了静态代码块,然后是实例代码块和构造方法。不管静态、实例、构造代码块在类中的顺序,静态代码块一定是先被打印,其次是实例代码块,最后是构造方法,如果大家都是相同类型(比如同是静态)的,这时才看的是代码顺序。
我们再细化看一下实例代码块和静态代码块的区别
class people{
public String name;
public people(){
System.out.println("people()不带参数的构造方法");
}
public people(String name){
this.name=name;
System.out.println("people(String)带String参数的构造方法");
}
{
System.out.println("实例代码块");
}
static{
System.out.println("静态代码块");
}
}
public class TestDemo {
public static void main(String[] args) {
people p1=new people();
System.out.println("===============");
people p2=new people();
}
}
我们new了两个对象,但我们静态代码块永远只执行一次,剩下的实例代码块和构造方法均执行两次。
我们再来测试一下,不new对象的运行效果
class people{
public static int count;
public String name;
public people(){
System.out.println("people()不带参数的构造方法");
}
public people(String name){
this.name=name;
System.out.println("people(String)带String参数的构造方法");
}
{
System.out.println("实例代码块");
}
static{
System.out.println("静态代码块");
}
}
public class TestDemo {
public static void main(String[] args) {
System.out.println(people.count);
System.out.println(people.count);
}
}
静态代码块不需要实例化对象也可以运行,但是不管你执行几次,静态代码块只会执行一次。
静态代码块本质上来说:是初始化静态的东西的,它是不依赖于对象的
六、一些补充
6.1匿名对象
匿名只是表示没有名字的对象。
1.没有引用的对象成为匿名对象
2.匿名对象只能在创建对象时使用
3.如果一个对象只是用一次,后面不需要用了,可以考虑使用匿名对象。
class people{
public void eat(){
System.out.println("正在吃东西");
}
public void drink(){
System.out.println("正在喝东西");
}
}
public class TestDemo {
public static void main(String[] args) {
new people().eat();
new people().drink();
}
}
上述这个代码,我们new了一个对象却没有把它赋给任何值,再用这个new出来的对象去调用eat函数。这种操作方法我们不需要名字,可以直接使用,就叫作匿名对象。但是有个缺点,就是你如果用匿名对象,每用一个需要对象的函数都要new一个匿名对象,比如我们上面调用两个函数eat和drink就new了两个对象的空间,比较浪费空间。
七、小结
1.一个类可以产生无数的对象,类就是模板,对象就是具体的实例。
2.类中定义的属性,大概可分为几类:类属性,对象属性。其中被static修饰的数据类型称为类属性,static修饰的方法称为类方法,特点是不依赖于对象,我们只需要通过类名就可以调用其属性或者方法。
3.静态代码块优先实例代码块执行,实例代码块优先构造函数执行。
4.this关键字代表的是当前对象的引用,不是当前对象。