目录
一、初步认识类和对象
二、类的成员
1.字段
2.方法
3.static 关键字
三、封装
1.什么是封装
2.private实现封装
3.getter和setter方法
四、构造方法
五、代码块
六、补充
1.toString方法:
2.匿名对象
一、初步认识类和对象
1.C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
JAVA 是 基于面向对象 的, 关注 的是 对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。面向过程注重的是过程,在整个过程中所涉及的行为,就是功能。面向对象 注重的是对象,也就是参与过程所涉及到的主体。是通过逻辑将一个个功能实现连接起来 打个比方:以洗衣服为例 面向过程:人将衣服放进洗衣机,倒入洗衣粉,启动洗衣机,洗衣机就会完成洗衣过程并且甩干 面向对象:人、衣服、洗衣粉、洗衣机四个对象之间交互完成,人不需要关注洗衣机具体是如何洗衣服的,如何甩干的面向对象是思考问题的一种思考方式,是一种思想。比如:概念与实例。理论与实践。名和实等等。它的好处是将复杂的事情变简单了,只要面对一个对象就行。面向对象设计把握一个重要的经验:谁拥有数据,谁对外提供操作这些数据(私有)的方法(被动的一方是数据的拥有者,主动的一方是执行者)。开发时:找对象,建对象,用对象,并维护对象之间的关系
2. 类就是一类对象的统称。对象就是这一类具体化的一个实例。类相当于一个模板,对象是由模板产生的样本。一个类,可以产生无数的对象。声明一个类就是创建一个新的数据类型,而类在 Java 中属于 引用类型 , Java 使用关键字 class 来声明类。 声明一个类的基本语法:// 创建类class
class Person { public int age;//成员属性 实例变量 public String name; public String sex; public void eat() {//成员方法 System.out.println("吃饭!"); } public void sleep() { System.out.println("睡觉!"); }}public class Main{ public static void main(String[] args) { Person person = new Person();//通过new实例化对象 Person person2 = new Person(); //如果改为Person person2 = person;那么它的意思是person2这个引用指向person这个引用所指向的对象,并不是“引用指向引用” person.eat();//成员方法调用需要通过对象的引用调用 person.sleep(); }}
编译并运行该代码,输出如下:
吃饭!
睡觉!
内存布局如图所示:
看这张图,我们感觉好像引用都在栈上,其实引用不一定在栈上,如:
class Person { public int age; public String name; public String sex;}public class Test{ Person person = new Person(); public static void main(String[] args) { Test test = new Test(); }}
其内存布局是这样的:
二、类的成员类的成员可以包含以下:字段、方法、代码块、内部类和接口等。但是本笔记重点总结前3种
1.字段 在类中 , 但是方法外部定义的变量 、 这样的变量我们称为 " 字段" 或 "属性" 或 "成员变量 "( 三种称呼都可以 , 一般不会严格区分),成员变量又分为普通成员变量(普通成员变量都属于对象)和静态成员变量,以下类中的是普通成员,笔记的后面会遇到静态成员变量。 我们有时也会看见实例变量这个词,实例变量指在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
class Person { public String name; // 字段/属性/成员变量 public int age; }class Test { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name); System.out.println(person.age); }}
编译并运行该代码,输出如下:
null
0
(1)使用 、访问对象的字段,"访问" 既包含读, 也包含写
(2)对于一个对象的字段如果没有显式设置初始值 , 那么会被设置一个默认的初值。 默认规则: ① 对于各种数字类型, 默认值为 0 ②对于 boolean 类型, 默认值为 false ③ 对于引用类型(String, Array, 以及自定制类), 默认值为 null 很多时候我们不希望字段使用默认值 , 而是需要我们显式设定初值:(字段就地初始化)class Person { public String name = "张三"; public int age = 18; }class Test { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name); System.out.println(person.age); }}
我们并不建议这么初始化,因为当我们new多个对象时,不见得每个对象都叫张三,都是18岁。我们可以这样初始化:
class Person { public String name; public int age; }class Test { public static void main(String[] args) { Person person = new Person(); person.name = "张三"; person.age = 18; System.out.println(person.name); System.out.println(person.age); }}
2.方法编译并运行该代码,输出如下:
张三
18
用于描述一个对象的行为
方法又分为普通方法和静态方法,以下类中的是普通方法,笔记的后面会遇到静态方法
class Person { public int age; public String name; public void show() { System.out.println("我叫" + name + ", 今年" + age + "岁"); }}class Test { public static void main(String[] args) { Person person1 = new Person(); Person person2 = new Person(); person1.age = 18; person1.name = "张三"; person2.age = 19; person2.name = "李四"; person1.show(); person2.show(); }}
此处的 show 方法 , 表示 Person 这个对象具有一个 " 展示自我 " 的行为 、这样的 show 方法是和 person 实例相关联的 、 如果创建了其他实例 , 那么 show 的行为就会发生变化 方法中还有一种特殊的方法称为 构造方法,这个在笔记的后面再总结。编译运行该代码,输出如下:
我叫张三,今年18岁
我叫李四,今年19岁
3.static 关键字
它可以修饰属性,修饰方法,修饰代码块,修饰类
(1)修饰属性(Java静态属性和类相关, 和具体的实例无关、换句话说, 同一个类的不同实例共用同一个静态属性)
class Test{ public int a;//普通成员变量 实例变量 存放在对象中 public static int count;//类变量也叫静态成员变量 public final SIZE;//被final修饰的叫常量,也属于对象。 被final修饰,后续不可更改 public static final int COUNT = 99;//静态的常量,属于类本身,只有一份 被final修饰,后续不可更改}public class Main{ public static void main(String[] args) { Test t1 = new Test(); t1.a++; Test.count++; System.out.println(t1.a); System.out.println(Test.count); System.out.println("---------------"); Test t2 = new Test(); t2.a++; Test.count++; System.out.println(t2.a); System.out.println(Test.count); }}
编译并运行该代码,输出如下:
1
1
---------------
1
2
注意:count被static所修饰,所有类共享。且不属于对象,访问方式为:类名 、属性
内存解析如下:
(2)修饰方法
如果在任何方法上应用 static 关键字,此方法称为静态方法 ①静态方法属于类,而不属于类的对象 ②可以直接调用静态方法,而无需创建类的实例 ③静态方法可以访问静态数据成员,并可以更改静态数据成员的值 注意: (i) 静态方法和实例无关 , 而是和类相关 、因此这导致了两个情况:a.静态方法不能直接使用非静态数据成员或调用非静态方法 ( 非静态数据成员和方法都是和实例相关的)b. this 和 super 两个关键字不能在静态上下文中使用 (this 是当前实例的引用 , super 是当前实例父类实例的引用 , 也 是和当前实例相关 ) (ii)一个方法具体要不要带 static, 都需要是情形而定 (iii)main是不是静态方法都可以,这取决于我们的JVMclass TestDemo{ public int a; public static int count; public static void change() { count = 100; //a = 10; error 不可以访问非静态数据成员 }}public class Main{ public static void main(String[] args) { TestDemo.change();//无需创建实例对象 就可以调用 System.out.println(TestDemo.count); }}
编译并运行该代码,输出如下:
100
三、封装 1.什么是封装 在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者、封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了、这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度 2.private实现封装 private/ public 这两个关键字表示 " 访问权限控制 " (1)被 public 修饰的成员变量或者成员方法 , 可以直接被类的调用者使用 (2)被 private 修饰的成员变量或者成员方法 , 不能被类的调用者使用( 类的使用者根本不需要知道, 也不需要关注一个类都有哪些 private 的成员、从而让类调用者以更低的成本来使用类)
2.private实现封装 private/ public 这两个关键字表示 " 访问权限控制 " (1)被 public 修饰的成员变量或者成员方法 , 可以直接被类的调用者使用 (2)被 private 修饰的成员变量或者成员方法 , 不能被类的调用者使用( 类的使用者根本不需要知道, 也不需要关注一个类都有哪些 private 的成员、从而让类调用者以更低的成本来使用类)
class Person { private String name = "张三"; private int age = 18; public void show() { System.out.println("我叫" + name + ", 今年" + age + "岁"); } } class Test { public static void main(String[] args) { Person person = new Person(); //person.name = "张三";//error person.show(); } }
如果类的实现者修改了字段的名字 , 类的调用者不需要做出任何修改 ( 类的调用者根本访问不到 name, age这样的字段).但是 类的实现者万一修改了 public 方法 show 的名字, 岂不是类的调用者仍然需要大量修改代码吗?这件事情确实如此, 但是一般很少会发生、一般类的设计都要求类提供的 public 方法能比较稳定, 不应该频繁发生大的改变、尤其是对于一些基础库中的类, 更是如此、每次接口的变动都要仔细考虑兼容性问题3.getter和setter方法 当我们使用 private 来修饰字段的时候 , 就无法直接使用这个字段了。此时如果需要获取或者修改这个 private 属性 , 就需要使用 getter / setter 方法 (1)getName 即为 getter 方法 , 表示获取这个成员的值 (2)setName 即为 setter 方法 , 表示设置这个成员的值
当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值、this 表示当前实例的引用不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法
在 IDEA 中可以使用 alt + insert (或者 alt + F12) 快速生成 setter / getter 方法、在 VSCode 中可以使用鼠标右键菜单 -> 源代码操作中自动生成 setter / getter 方法
class Person { private String name;//实例成员变量 private int age; public void setName(String name){ //name = name;//不能这样写,此时3个name都是同一个name,因为局部变量优先 this.name = name;//this引用,表示调用该方法的对象 } public String getName(){ return name; } public void show(){ System.out.println("name: "+name+" age: "+age); } } public static void main(String[] args) { Person person = new Person(); person.setName("xiaoxiao"); String name = person.getName(); System.out.println(name); person.show(); }
编译并运行该代码,输出如下:
xiaoxiao
name: xiaoxiao age: 0
四、构造方法 1.构造方法是一种特殊方法, 使用关键字 new 实例化新对象时会被自动调用 , 用于完成初始化操作 2.new的执行过程:①为对象分配内存空间 ②调用对象的构造方法 3.语法规则: (1)方法名称必须与类名称相同 (2)构造方法没有返回值类型声明 (3) 每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造) 4.注意: (1)如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造 (2)若类中定义了构造方法,则默认的无参构造将不再生成 (3)构造方法支持重载 、 规则和普通方法的重载一致 5.this关键字: this 表示当前对象引用 ( 注意不是当前对象 )、 可以借助 this 来访问对象的字段和方法。 我们在构造函数的内部,我们可以使用this关键字,构造函数是用来构造对象的,对象还没有构造好, 我们就使用了this,那this还代表当前对象吗?当然不是,this代表的是当前对象的引用 this一般可以这样用: (1)this.data 调用当前对象的属性 (2)this.func() 调用当前对象的方法 (3)this() 调用当前对象的其他构造方法 只能放在构造函数当中
class Person { private String name;//实例成员变量 private int age; private String sex; public Person() { //默认构造函数 构造对象 此处的public不能修改为private,一旦这样修改在类外就无法实例化对象了,不过在类内可以 this.name = "caocao"; this.age = 10; this.sex = "男"; } public Person(String name,int age,String sex) { //带有3个参数的构造函数 两个构造方法之间构成重载 this.name = name; this.age = age; this.sex = sex; } public void show(){ System.out.println("name: "+name+" age: "+age+" sex: "+sex); } } public class Main{ public static void main(String[] args) { Person p1 = new Person();//调用不带参数的构造函数 如果程序没有提供会调用不带参数的构造函数 p1.show(); Person p2 = new Person("zhangfei",80,"男");//调用带有3个参数的构造函数 p2.show(); } }
编译并运行该代码,输出如下:
name: caocao age: 10 sex: 男 name: zhangfei age: 80 sex: 男
class Person{ private String name; public Person(){ //this();//error 调用对象的其他构造方法,构成了无限递归 this("xiaoxiao");//调用带有一个参数的构造方法 必须放在第一行 System.out.println("Person()-->不带参数的构造方法"); } public Person(String name){ this.name = name; System.out.println("Person(String)-->带1个参数的构造方法"); } public void show() { System.out.println("name: "+name); } }public class Test{ public static void main(String[] args){ Person person = new Person(); person.show(); System.out.println(person); }}
编译并运行该代码,输出如下:
xiaoxiao
Person(String)-->带1个参数的构造方法
Person()-->不带参数的构造方法
五、代码块
1.字段的初始化方式有: (1)就地初始化 (2)使用构造方法初始化 (3)使用代码块初始化。前两种方式前面已经总结过了, 接下来我们介绍第三种方式, 使用代码块初始化
2.根据代码块定义的位置以及关键字,又可分为四种:本地代码块(普通代码块)、静态代码块、构造代码块、同步代码块
(1)本地代码块:定义在方法中的代码块
public class Main{ public static void main(String[] args) { { //直接使用{}定义,普通方法块 int x = 10 ; System.out.println("x1 = " +x); } int x = 100 ; System.out.println("x2 = " +x); } }
编译并运行该代码,输出如下:
x1 = 10
x2 = 100
这种用法比较少见
(2)构造代码块
构造块:定义在类中的代码块 ( 不加修饰符 ) 。也叫: 实例代码块 。构造代码块一般用于初始化实例成员变量 注意:实例代码块优先于构造函数执行class Person{ private String name;//实例成员变量 private int age; private String sex; public Person() { System.out.println("I am Person init()!"); } //实例代码块 { this.name = "bit"; this.age = 12; this.sex = "man"; System.out.println("I am instance init()!"); } public void show(){ System.out.println("name: "+name+" age: "+age+" sex: "+sex); } } public class Main { public static void main(String[] args) { Person p1 = new Person(); p1.show(); } }
(3)静态代码块 使用 static 定义的代码块。 一般用于初始化静态成员属性 注意: 静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。静态代码块执行完毕后 , 实例代码块(构造块)执行,再然后是构造函数执行。静态代码块不用实例化对象都可以被执行编译并运行该代码,输出如下:
I am instance init()! I am Person init()! name: bit age: 12 sex: man
class Person{ private String name;//实例成员变量 private int age; private String sex; private static int count = 0;//静态成员变量 由类共享数据 方法区 public Person(){ System.out.println("I am Person init()!"); } //实例代码块 { this.name = "bit"; this.age = 12; this.sex = "man"; System.out.println("I am instance init()!"); } //静态代码块 static { count = 10;//只能访问静态数据成员 System.out.println("I am static init()!"); } public void show(){ System.out.println("name: "+name+" age: "+age+" sex: "+sex); } } public class Main { public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person();//该静态代码块不会被执行 } }
编译并运行该代码,输出如下:
I am static init()!I am instance init()!
I am Person init()!
还有一个点:如果都是静态成员变量,那么其执行的顺序要看定义的顺序
class Person1{ public static int count1 = 10; static{ count1 = 99; }}class Person2{ static{ count2 = 99; } public static int count2 = 10;}class Person3{ static{ count3 = 99; } public static int count3;}class Person4{ public static int count4; static{ count4 = 99; }}public class Test{ public static void main(String[] args) { System.out.println(Person1.count1); System.out.println(Person2.count2); System.out.println(Person3.count3); System.out.println(Person4.count4); }}
编译并运行该代码,输出如下:
99
10
99
99
由这个结果可以看到有个例外,那就是当count没有被初始化时,默认值就是99
(4)同步代码块 后续笔记中将会总结六、补充 1.toString方法:
(1)toString 方法会在 println 的时候被自动调用.
(2)将对象转成字符串这样的操作我们称为 序列化
(3)toString 是 Object 类提供的方法, 我们自己创建的 Person 类默认继承自 Object 类, 可以重写 toString 方法实现我们自己版本的转换字符串方法
(4)IDEA快速生成Object的toString方法快捷键:alt+f12(insert)
不用快捷键也行,右键找到generate,然后找到toString(),最后点ok即可
class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); } } public class Main { public static void main(String[] args) { Person person = new Person("caocao",19); person.show(); //我们发现这里打印的是一个地址的哈希值 原因:调用的是Object的toString方法 System.out.println(person); } }
我们可以看一下println的源码:编译并运行该代码,输出如下:
name : caocao age : 19 Person@1c168e5
重写toString方法:
class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); } //重写Object的toString方法 @Override//@Override 在 Java 中称为 "注解", 此处的 @Override 表示下面实现的 toString 方法是重写了父类的方法 public String toString() { return "Person{" + "name='" + name + ''' + ", age=" + age + '}'; } } public class Main { public static void main(String[] args) { Person person = new Person("caocao",19); person.show(); System.out.println(person); } }
编译并运行该代码,输出如下:
name : caocao age : 19 Person{name='caocao', age=19}
2.匿名对象
(1)没有引用的对象称为匿名对象
(2)匿名对象只能在创建对象时使用
(3)如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象
class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); } } public class Main { public static void main(String[] args) { new Person("caocao",19).show();//通过匿名对象调用方法 } }
编译并运行该代码,输出如下:
name : caocao age : 19