一、类与对象的初步认知
1.面向过程和面向对象
在了解类和对象之前我们先了解一下什么是面向过程和面向对象。
面向过程编程:
C语言就是面向过程编程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
面向对象编程:
JAVA是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
来举个例子,就比如洗衣服:
面向过程:1.拿桶 2.把衣服放桶里 3.接水 4.放洗衣服 5.搓 6.再接水 7.把衣服拧干 8.晒衣服
面向对象:1.把衣服和洗衣粉放进洗衣机 2.打开洗衣机 3.把衣服拿出来晾干
在这里洗衣机就是一个对象,所以只要操作洗衣机所具备的功能,都要定义在对象中。
(1) 面向过程和面向对象的区别
面向过程:注重的是过程,在整个过程中所涉及的行为,就是功能。
面向对象:注重的是对象,也就是参与过程所涉及到的主体。是通过逻辑将一个个功能实现连接起来
2.类和对象
类:类就是一类对象的统称
对象:对象就是这一类具体化的一个实例
这样说可能太过于抽象
上面说了java是面向对像编程,如果我们先要找对象编程,首先就得找对象而对象是通过类产生的。
我们来举个通俗易懂的例子:
就比如建房子,类就相当于一个模板也就是图纸
而对象就是房子,也就是一个实体。只有通过图纸才能建房子。
所以只有通过类才能产生对象,我们以后开发其实就是:
找对象,建对象,用对象,并维护对象之间的关系。
二、类和类的实例化
1.类的定义
在java当中我们用 class 关键字来创建一个新的类型,我们把它叫做类。
一个类是由方法和字段组成
这里我来举一个例子,我用class定义了一个 People 类,来描述一个人。
class People{
//成员变量/字段/属性
public String name ;
public String sex;
public int age;
//成员方法/行为
public void show(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:"+age);
}
}
class为定义类的关键字,People是类的名字,{}中为类的主体。
类中的元素称为:成员变量也就字段或者叫做成员属性。 这里面的name、age、sex 都是字段。
类中的方法称为:成员方法
字段(成员变量):定义在类的内部,方法的外部
2.实例化一个类
用类类型创建对象的过程,称为类的实例化
我们已经知道通过class关键字来定义一个类了,而我们知道类只是相当于一个模板,所以我们要通过类来实例化对象,由类产生对象的过程叫做 实例化一个对象。
代码如下(示例):
class People{//类名大驼峰
//成员变量/字段/属性
public String name ;
public String sex;
public int age;
//成员方法/行为
public void show(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:"+age);
}
}
public class Test {
public static void main(String[] args) {
People p1 = new People();//示例化一个对象
}
}
我们通过 new 关键字来实例化一个对象
一个类是可以实例化多个对象的,就比如我上面那个例子一个图纸是不是可以建很多房子。
通过 new 关键字我们是可以实例化多个对象的
public static void main(String[] args) {
People p1 = new People();
People p2 = new People();
People p3 = new People();
}
3.类在内存中的存储
我们知道了如何定义类和通过类实例化对象,那么一个类在内存到底是如何存储的呢?
和数组一样类也属于于引用,而引用指向的是一个对象,p1和p2这些变量都是局部变量它们属于引用,我在前面数组的博客也讲到过局部变量是在栈上开辟内存的,引用里面存的是对象的地址,这些变量里存的都是引用每实例化一个对象就在堆区开辟一块内存。对象里面存的就是字段。
注意:类里面的方法并不是是存在堆区的 ,方法本身是不占内存的,方法是存在方法区里的方法表中,在每个对象 new 好之后前面有几个字节存的就是方法表的地址,只有在调用这个方法时,会通过方法表的地址找到这个这个方法,然后在栈上为这个方法开辟栈帧。当然这里只是简单介绍一下,到后面的博客中会详细讲解。
引用不一定是在栈上的!如果它是一个示例成员变量,那么它就是在堆上的,就比如这里的 name和 sex。
4.访问类的成员
我们知道了如何定义个和实例化一个对象,那么怎么访问这些成员变量呢?
成员变量的访问:对象的引用 . 属性名
成员方法的访问:对象的引用 . 方法名
class People{
//成员变量/字段/属性
public String name ;
public String sex;
public int age;
//成员方法/行为
public void show(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:"+age);
}
}
public class Test {
public static void main(String[] args) {
People p1 = new People();
System.out.println(p1.name);
System.out.println(p1.sex);
System.out.println(p1.age);
p1.show();
}
}
运行结果
注意:这里的成员变量并没有初始化,不同于局部变量不初始化会报错。成员变量不初始化是其类型对应的0值。
三、类的成员
1.成员变量的初始化
我们把定义在类的内部,方法的外部的变量叫“字段”或者叫它成员变量有时候也叫它“属性"。这三种叫法并没有严格区分。
public static void main(String[] args) {
People p1 = new People();
p1.name = "java";
p1.sex = "nan";
p1.age = 50;
p1.show();
}
}
运行结果
还有一种就是在定义成员变量的时候赋值,但并不建议这样写,因为在实例化后还是能初始化,没必要多此一举。而且这个类是一个模板,把模板初始化也是不合适的。
class People{
//成员变量/字段/属性
public String name = "zhangsan" ;
public String sex = "lisi";
public int age = 22;
//成员方法/行为
public void show(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:"+age);
}
}
再来看一个代码,这个代码是什么意思呢?
public static void main(String[] args) {
People p1 = new People();
People p2 = p1;
}
这段代码的意思是 p2这个引用,引用了p1指向的这个对象
还有一个问题,一个类可以同时指向多个对象吗?
public static void main(String[] args) {
People p = new People();
p = new People();
p = new People();
}
答案是不能,这里的p只会指向最后一个对象,前面的都被覆盖掉了。
所以一个引用只能同时指向一个对象
2.方法
就是我们曾经讲过的方法,用于描述一个对象的行为.
上面我们已经介绍过了,这里就是一个显示人的信息的方法。
这样的 show 方法是和 p 实例相关联的. 如果创建了其他实例, 那么 show 的行为就会发生变化
class People{
//成员变量/字段/属性
public String name ;
public String sex;
public int age;
//成员方法/行为
public void show(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:"+age);
}
public void prt(){
System.out.println("爱java");
}
}
public class Test {
public static void main(String[] args) {
People p1 = new People();
p1.show();
p1.prt();
}
}
运行结果
成员方法也是通过 对象访问的!
3.toString方法
我们刚刚注意到,我们在把对象的属性进行打印的时候,都自己实现了show函数,其实,我们大可不必。
接下来我们看一段代码:
class People{
//成员变量/字段/属性
public String name ;
public String sex;
public int age;
//成员方法/行为
public void show(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:"+age);
}
}
public class Test {
public static void main(String[] args) {
People p1 = new People();
System.out.println(p1);
}
}
运行结果
我们发现这里打印的是一个地址,这里在打印的时候会默认调用Object 的 toString 方法来打印
默认调用Object 的 toString 方法,但是当前类如果重新实现 toString 这个方法后,就会调用当前类重新写的这个toString方法来打印。
IDEA快速生成Object的toString方法快捷键:alt+insert
再来看一段从写toString方法后的代码
class People{
//成员变量/字段/属性
public String name ;
public String sex;
public int age;
//成员方法/行为
public void show(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:"+age);
}
@Override//重写toString
public String toString() {
return "People{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
People p1 = new People();
System.out.println(p1);
}
}
运行结果
我们发现当我们重写toString方法后打印的就是我们重写的toString方法里的内容。
当然重写里双引号里面的内容是可以修改的
@Override
public String toString() {
return "People{" +
"姓名='" + name + "," +
", 性别='" + sex + '\'' +
", 年龄=" + age +
'}';
}
注意: 所以当我们没有重写toString方法的时候,就会默认调用Object 的 toString 方法来打印
@Override 在 Java 中称为 “注解”, 此处的 @Override 表示下面实现的 toString方法是重写了父类的方法.
(1)序列化和反序列化
序列化:把一个对象变成字符串
反序列化:把字符串变成对像
4.匿名对象
没有引用的对象称为匿名对象.
匿名对象只能在创建对象时使用.
如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象
用法如下
public class Test {
public static void main(String[] args) {
new People().name = "李四";
new People().sex = "男";
new People().age = 18;
new People().show();
}
}
每使用一次就得 new 一次
四、static 关键字
1.静态成员变量
我们成员变量分为 普通成员变量和静态成员变量
普通成员变量也叫做实例得成员变量,静态成员变量也叫做类变量。
我们来看下这段代码,我们用 static 关建键字创建了一个静态成员变量cont,我分别用引用和类名来访问 cont ,发现通过对象来访问得时候编译器给了一个警告。
运行结果
运行发现并没有报错,但对于整体的一个规范我们还是建议用 类名来访问静态成员变量。
所以静态成员变量的访问方式为 类名.静态成员变量
(1) 静态成员变量在内存中的存储
来看一个代码:
class People{
public String name ;
public int age;//普通成员变量
public static int cont;//静态成员变量
}
public class Test {
public static void main(String[] args) {
People p = new People();
p.age++;
People.cont++;
System.out.println(p.age);
System.out.println(People.cont);
People p2 = new People();
p2.age++;
People.cont++;
System.out.println(p.age);
System.out.println(People.cont);
}
}
运行结果
我们发现 age和cont的区别就是一个没有被 static修饰,一个被static 修饰,那是不是这cont就是同一个cont呢?
静态成员变量是存在方法区,所以它的访问是不依赖于对象的。
让我们再来看一个例子
class People{
public String name ;
public int age;//普通成员变量
public static int cont;//静态成员变量
}
public class Test {
public static void main(String[] args) {
People.cont++;
System.out.println(People.cont);
}
}
运行结果
和普通成员变量不同的是这里我们并没有实例化一个对象,直接通过类名来访问了cont,而普通成员变量不实例化是会报错的。
同样来看这个例子,这段代码运行会报错吗?
class People{
public String name ;
public int age;//普通成员变量
public static int cont;//静态成员变量
}
public class Test {
public static void main(String[] args) {
People p = null;
System.out.println(p.cont);
}
}
运行结果
这是为什么呢?我们前面说过 null.任何东西都会出现空引用异常,那这里为什么没有呢?
这里的 p代表p不指向任何对象,而我们已经说过静态成员变量的访问时不依赖于对象的,所以这里并没有发生异常。但如果通过 p访问普通成员变量的话就会报错。
总结:
1.静态成员变量的访问方式是 类名.静态成员变量
2.静态成员变量是不依赖于对象的。
2.静态成员方法
成员方法也分为两种,一种是普通的成员方法,一种是静态的成员方法。
普通的成员方法又叫做实例的成员方法,静态的成员方法也叫做类方法。
通过 static 关键字来创建静态成员方法,且也是通过 类名来访问的
class People{
public String name ;
public int age;//普通成员变量
public static int cont;//静态成员变量
public static void prt(){
System.out.println("这是一个静态的成员方法");
}
}
public class Test {
public static void main(String[] args) {
People.prt();
}
}
运行结果:
再来看一个例子:
为什么在普通成员方法里访问普通成员变量不报错,而在静态成员方法就报错呢?
因为静态的成员方法是通过类名来调用的,此时并没有示例化一个对象,我们知道普通的成员变量必须实例化对象后才能使用。
而在普通的成员方法里就可以访问是因为如果想调用普通成员方法就必须实例化对象,实例化对象后这里的 age和name都产生了,所以可以访问
同理可推到出,在静态的成员方法内部也是不可以调用普通成员方法的
所以在静态的成员方法内是不可以访问普通的成员变量的,也不能调用普通成员方法
注意:我们曾经写的方法为了简单, 都统一加上了 static. 但实际上一个方法具体要不要带 static, 都需要是情形而定.
静态的在类加载的时候就已经定义好了,具体在后面的JVM中会讲解到。
再来看一个final修饰的成员变量,这里的存储和final没关系,主要还是看static修饰。
五、封装
OOP 语言:面向对象编程,有一些重要的特征:继承 封装 多态
封装:在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了.
这样就降低了类使用者的学习和使用成本,从而降低了复杂程度,也保证了代码的安全性
举个例子:
我把类里的name和age都改一下
class People{
public String name ;
public int age;//普通成员变量
public static int cont;//静态成员变量
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
p.name = "张三";
p.age = 18;
}
}
那类的调用者调用这个类的时候发现这个类被修改,那么它就要大量修改代码。所以 这样的代码非常不安全。
为了避免这样的情况就得使用封装
1.private实现封装
private/ public 这两个关键字表示 “访问权限控制” .
被 public 修饰的成员变量或者成员方法, 可以直接被类的调用者使用.
被 private 修饰的成员变量或者成员方法,不能被类的调用者使用
这里我用private修饰了name,它只能在当前类里使用
2. getter和setter方法
当我们用private修饰字段后,这个字段就无法被直接使用了。
这个时候就用到了 get 和 set 方法了
class People{
private String name ;
private int age;//普通成员变量
public static int cont;//静态成员变量
public void setName(String myName){
name = myName;
}
public String getName(){
return name;
}
public void setAge(int myAge){
age = myAge;
}
public int getAge(){
return age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
p.setName("小明");//设置成员变量
System.out.println(p.getName());//获取成员变量
p.setAge(19);
System.out.println(p.getAge());
System.out.println(p);
}
}
getName 即为 getter 方法, 表示获取这个成员的值.
setName 即为 setter 方法, 表示设置这个成员的值
不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.
运行结果
总结:封装其实就是将一些需要的字段或者方法,使用private关键字进行修饰,同时提供对应的get和set方法提供给外部
(1) 在 IDEA中快速生成 get 和 set 方法
Alt+Insert 键
五、构造方法
构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作
构造方法没有返回值,且方法名和类名是相同的
构造方法是用来构造对象的(实例化对象)
new 执行过程
1.为对象分配内存空间
2.调用合适的构造方法
1.构造方法的基本使用
class People{
private String name ;
private int age;//普通成员变量
public static int cont;//静态成员变量
public People(){
System.out.println("这是一个不带有参数的构造方法");
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
}
}
运行结果:
那么为什么我们前面示例化对象时为什么没有构造方法呢?
如果我们没有写构造方法,系统默认会有一个没有参数且没有任何内容的构造方法
2.构造方法的重载
构造方法和方法一样是可以实现重载的,前面的博客中我们也讲到过重载
重载:
1.方法名要相同
2.参数列表不同(类型和个数)
3.返回值不做要求
代码如下:
class People{
private String name ;
private int age;//普通成员变量
public static int cont;//静态成员变量
public People(){
System.out.println("这是一个不带参的构造方法");
}
public People(String name){
System.out.println("这是一个带有一个参数的构造方法");
}
public People(String name,String sex){
System.out.println("这是一个带有两个参数的构造方法");
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
People p1 = new People("张三");
People p2 = new People("李四","男");
}
}
运行结果:
3.this关键字
this的三种用法:
1.this.data (访问当前对象的成员变量)
2.this.func() (访问当前对象的成员方法)
3.this.() (访问当前对象自己的构造方法)
在前面说封装的时候我们讲到 get 和 set 方法,当我们自己写构造方法的时候和编译器生成的构造方法有一个不一样的地方。
来看一段代码:
我们把形参的名字改成了name
class People{
private String name ;
private int age;//普通成员变量
public void setName(String name){
name = name;
}
public String getName(){
return name;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
p.setName("张三");
System.out.println(p.getName());
}
}
运行结果:
这是为什么呢?这里传过去的明明是张三
其实这里是局部变量优先了,相当于自己给自己赋值。
我们只需要加一个 this
public void setName(String name){
this.name = name;
}
运行结果
有的书上说 this 代表当前对象,其实这样的说法并不准确。
正确的说法应该是:this表示当前对象引用
注意:
1.this 是不可以访问静态的成员变量的
2.同样的静态方法的内部也是不能使用 this 的
3.我们以后要习惯使用 this ,避免出现上面的那种情况发生
同样 this 也可以访问成员方法
class People{
private String name ;
private int age;//普通成员变量
public void show(){
System.out.println("调用了这个方法");
}
public void setName(String name){
this.name = name;
this.show();
}
public String getName(){
return name;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
p.setName("张三");
System.out.println(p.getName());
}
}
运行结果:
最后就是通过 this()访问当前对象自己的构造方法
1.只能在构造方法内使用
2.只能使用一个this()
3.只能放在构造方法的第一行
代码如下:
class People{
private String name ;
private int age;//普通成员变量
public void show(){
System.out.println("调用了这个方法");
}
People(){
this("张三",18);
System.out.println("没有参数的构造方法");
}
People(String name,int age){
this.name = name;
this.age = age;
System.out.println("带有两个参数的构造方法");
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
System.out.println(p);
}
}
运行结果
六、代码块
1.本地代码块
方法内部的代码块,没什么意义了解即可。
2.静态代码块
创建静态代码块
static{
System.out.println("这是一个静态代码块");
}
注意:静态代码块只执行一次
class People{
private String name ;
private int age;//普通成员变量
People(){
System.out.println("没有参数的构造方法");
}
static{
System.out.println("这是一个静态代码块");
}
{
System.out.println("这是一个实例代码块");
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
System.out.println("--------------");
People p2 = new People();
}
}
运行结果:
这里可以看到静态代码块只会执行一次
注意:在静态代码块里也是一样的,不可以使用非静态的数据
。
3.实例代码块
实例代码块也叫构造代码块,在示例代码块中可以对属性进行赋值
class People{
private String name ;
private int age;//普通成员变量
private static int cont;//静态成员变量
{
this.name = "java";
this.age = 66;
cont = 100;
System.out.println("这是一个实例代码块");
}
}
4.执行顺序
如果静态代码块、示例代码块和构造方法同时存在,它们的执行顺序是什么呢?
1.静态代码块—>2.实例代码块—>3.构造方法
来看例子:
class People{
private String name ;
private int age;//普通成员变量
private static int cont;
People(){
System.out.println("没有参数的构造方法");
}
static{
System.out.println("这是一个静态代码块");
}
{
System.out.println("这是一个实例代码块");
}
}
public class Test {
public static void main(String[] args) {
People p = new People();
}
}
执行结果:
我们再来看一种情况:
我们知道 静态代码块是最先执行的,那我在定义静态成员变量的时候同时赋值。
class People{
private static int cont = 100;
static{
System.out.println("这是一个静态代码块");
}
public static int getCont() {
return cont;
}
public static void setCont(int cont) {
People.cont = cont;
}
static{
cont = 888;
System.out.println("这是第二个一个静态代码块");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(People.getCont());
}
}
运行结果:
我们再把静态代码交换顺序
class People{
static{
cont = 888;
System.out.println("这是第二个一个静态代码块");
}
private static int cont = 100;
static{
System.out.println("这是一个静态代码块");
}
public static int getCont() {
return cont;
}
public static void setCont(int cont) {
People.cont = cont;
}
}
public class Test {
public static void main(String[] args) {
System.out.println(People.getCont());
}
}
运行结果:
所以静态代码块是根据我们书写代码的顺序进行执行的,
注意:我们这里并没有 new 对象,可以知道静态代码块是在类加载的时候就已经执行了
总结
1.一个类可以实例化多个对象
2.所有静态有关的都是不依赖于对象的
3.和静态有关的方法或者是代码块里不能出现普通成员变量
4.IDEA Alt+Insert 可以生成toString方法和 get set方法