目录
1.面向对象
1.1.对象是什么?
1.2.对象在计算机中执行原理
1.3.类和对象的注意事项
2.面向对象基础
2.1.方法
2.1.1.定义方法
2.1.2.private方法
2.1.3.this变量
2.1java基础面向对象怎么学.4.方法参数
2.1.5.可变参数
2.1.6参数绑定
2.2.构造方法
2.2.1.默认构造方法
2.2.2多个构造方法
2.3.方法重载
2.4.继承
2.4.1.继承树
2.4.2.protected
2.4.3.super
2.4.4.阻止继承
2.4.5.向上转型
2.4.6.向下转型
2.4.7.区分继承和组合
1.面向对象
1.1.对象是什么?
对象可以简单理解为一种特殊的数据结构,对象是用类new出来的,有了类就可以创建出对象。
基本结构为:
使用对象:
按照上面的我举一个例子,我首先创建一个Student的类:定义三个变量和两个方法
下面我们去创造两个对象:
打印结果:
看完以上相信大家对面向对象有一个初步的了解。
1.2.对象在计算机中执行原理
我们需要明白这三个东西,方法区,堆内存,栈内存,先看图
首先在方法区中,会存入要执行的所有方法,在mian 方法中我们通过new 创建了对个对象,此时会保存着栈内存中,每个对象(s1,s2)有他们对应的地址,在堆内存中会把传入或者写入的值赋值给所对应对象地址中的变量,最后在方法区执行。
1.3.类和对象的注意事项
(1)类名建议用英文单词,首字母大写,满足驼峰模式,且要有意义,比如:Student、Car…
(2)类中定义的变量也称为成员变量(对象的属性),类中定义的方法也称为成员方法(对象的行为)。
(3)成员变量本身存在默认值,同学们在定义成员变量时一般来说不需要赋初始值(没有意义)
(4)一个代码文件中,可以写多个class类,但只能一个用public修饰,且public修饰的类名必须成为代码文件名。
我新增两个class 不会报错
这样写就会报错了,写了多个public
下面这种写法也会,public 修饰的不是与文件名相同的类
(5)对象与对象之间的数据不会相互影响,但多个变量指向同一个对象时就会相互影响了。
如果某个对象没有一个变量引用它,则该对象无法**作了,该对象会成为所谓的垃圾对象。
这样对象我们无法操作了,而且会空指针报错
2.面向对象基础
2.1.方法
一个可以包含多个field,例如,我们给类就定义了两个field:
但是,直接把用暴露给外部可能会破坏封装性。比如,代码可以这样写:
显然,直接操作,容易造成逻辑混乱。为了避免外部代码直接去访问,我们可以用修饰,拒绝外部访问:
试试修饰的有什么效果:
是不是编译报错?把访问的赋值语句去了就可以正常编译了。 把从改成,外部代码不能访问这些,那我们定义这些有什么用?怎么才能给它赋值?怎么才能读取它的值?
所以我们需要使用方法()来让外部代码可以间接修改:
虽然外部代码不能直接修改字段,但是,外部代码可以调用方法和来间接修改字段。在方法内部,我们就有机会检查参数对不对。比如,就会检查传入的参数,参数超出了范围,直接报错。这样,外部代码就没有任何机会把设置成不合理的值。对方法同样可以做检查,例如,不允许传入和空字符串:
同样,外部代码不能直接读取字段,但可以通过和间接获取字段的值。所以,一个类通过定义方法,就可以给外部代码暴露一些操作的接口,同时,内部自己保证逻辑一致性。调用方法的语法是。一个方法调用就是一个语句,所以不要忘了在末尾加。例如:。
2.1.1.定义方法
从上面的代码可以看出,定义方法的语法是:
方法返回值通过语句实现,如果没有返回值,返回类型设置为,可以省略。
2.1.2.private方法
有方法,自然就有方法。和字段一样,方法不允许外部调用,那我们定义方法有什么用?
定义方法的理由是内部方法是可以调用方法的。例如:
观察上述代码,是一个方法,外部代码无法调用,但是,内部方法可以调用它。
此外,我们还注意到,这个类只定义了字段,没有定义字段,获取时,通过方法返回的是一个实时计算的值,并非存储在某个字段的值。这说明方法可以封装一个类的对外接口,调用方不需要知道也不关心实例在内部到底有没有字段。
2.1.3.this变量
在方法内部,可以使用一个隐含的变量,它始终指向当前实例。因此,通过就可以访问当前实例的字段。
如果没有命名冲突,可以省略。例如:
但是,如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上:
2.1.4.方法参数
方法可以包含0个或任意个参数。方法参数用于接收传递给方法的变量值。调用方法时,必须严格按照参数的定义一一传递。例如:
调用这个方法时,必须有两个参数,且第一个参数必须为,第二个参数必须为:
2.1.5.可变参数
可变参数用定义,可变参数相当于数组类型:
上面的就定义了一个可变参数。调用时,可以这么写:
完全可以把可变参数改写为类型:
但是,调用方需要自己先构造,比较麻烦。例如:
另一个问题是,调用方可以传入:
而可变参数可以保证无法传入,因为传入0个参数时,接收到的实际值是一个空数组而不是。
2.1.6参数绑定
调用方把参数传递给实例方法时,调用时传递的值会按参数位置一一绑定。
那什么是参数绑定?
我们先观察一个基本类型参数的传递:
运行代码,从结果可知,修改外部的局部变量,不影响实例的字段,原因是方法获得的参数,复制了的值,因此,和局部变量互不影响。
结论:基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
我们再看一个传递引用参数的例子:
注意到的参数现在是一个数组。一开始,把数组传进去,然后,修改数组的内容,结果发现,实例的字段也被修改了!
结论
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。
有了上面的结论,我们再看一个例子:
不要怀疑引用参数绑定的机制,试解释为什么上面的代码两次输出都是。
2.2.构造方法
创建实例的时候,我们经常需要同时初始化这个实例的字段,例如:
初始化对象实例需要3行代码,而且,如果忘了调用或者,这个实例内部的状态就是不正确的。
能否在创建对象实例时就把内部字段全部初始化为合适的值?
完全可以。
这时,我们就需要构造方法。
创建实例的时候,实际上是通过构造方法来初始化实例的。我们先来定义一个构造方法,能在创建实例的时候,一次性传入和,完成初始化:
由于构造方法是如此特殊,所以构造方法的名称就是类名。构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有),调用构造方法,必须用操作符。
2.2.1.默认构造方法
是不是任何都有构造方法?是的。
那前面我们并没有为类编写构造方法,为什么可以调用?
原因是如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句,类似这样:
要特别注意的是,如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法:
如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来:
没有在构造方法中初始化字段时,引用类型的字段默认是,数值类型的字段用默认值,类型默认值是,布尔类型默认值是:
也可以对字段直接进行初始化:
那么问题来了:既对字段进行初始化,又在构造方法中对字段进行初始化:
当我们创建对象的时候,得到的对象实例,字段的初始值是啥?
在Java中,创建对象实例的时候,按照如下顺序进行初始化:
- 先初始化字段,例如,表示字段初始化为,表示字段默认初始化为,表示引用类型字段默认初始化为;
- 执行构造方法的代码进行初始化。
因此,构造方法的代码由于后运行,所以,的字段值最终由构造方法的代码确定。
2.2.2多个构造方法
可以定义多个构造方法,在通过操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分:
如果调用,会自动匹配到构造方法。
如果调用,会自动匹配到构造方法。
如果调用,会自动匹配到构造方法。
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是:
2.3.方法重载
在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在类中,定义多个方法:
这种方法名相同,但各自的参数不同,称为方法重载()。
注意:方法重载的返回值类型通常都是相同的。
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
举个例子,类提供了多个重载方法,可以查找子串:
- :根据字符的Unicode码查找;
- :根据字符串查找;
- :根据字符查找,但指定起始位置;
- 根据字符串查找,但指定起始位置。
试一试:
2.4.继承
在前面的章节中,我们已经定义了类:
现在,假设需要定义一个类,字段如下:
仔细观察,发现类包含了类已有的字段和方法,只是多出了一个字段和相应的、方法。
能不能在中不要写重复的代码?
这个时候,继承就派上用场了。
继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。当我们让从继承时,就获得了的所有功能,我们只需要为编写新增的功能。
Java使用关键字来实现继承:
可见,通过继承,只需要编写额外的功能,不再需要重复代码。
注意
子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
在OOP的术语中,我们把称为超类(super class),父类(parent class),基类(base class),把称为子类(subclass),扩展类(extended class)。
2.4.1.继承树
注意到我们在定义的时候,没有写。在Java中,没有明确写的类,编译器会自动加上。所以,任何类,除了,都会继承自某个类。下图是、的继承树:
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有特殊,它没有父类。
类似的,如果我们定义一个继承自的,它们的继承树关系如下:
2.4.2.protected
继承有个特点,就是子类无法访问父类的字段或者方法。例如,类就无法访问类的和字段:
这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把改为。用修饰的字段可以被子类访问:
因此,关键字可以把字段和方法的访问权限控制在继承树内部,一个字段和方法可以被其子类,以及子类的子类所访问,后面我们还会详细讲解。
2.4.3.super
关键字表示父类(超类)。子类引用父类的字段时,可以用。例如:
实际上,这里使用,或者,或者,效果都是一样的。编译器会自动定位到父类的字段。
但是,在某些时候,就必须使用。我们来看一个例子:
运行上面的代码,会得到一个编译错误,大意是在的构造方法中,无法调用的构造方法。
这是因为在Java中,任何的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句,所以,类的构造方法实际上是这样:
但是,类并没有无参数的构造方法,因此,编译失败。
解决方法是调用类存在的某个构造方法。例如:
这样就可以正常编译了!
因此我们得出结论:如果父类没有默认的构造方法,子类就必须显式调用并给出参数以便让编译器定位到父类的一个合适的构造方法。
这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
2.4.4.阻止继承
正常情况下,只要某个class没有修饰符,那么任何类都可以从该class继承。
从Java 15开始,允许使用修饰class,并通过明确写出能够从该class继承的子类名称。
例如,定义一个类:
上述类就是一个类,它只允许指定的3个类继承它。如果写:
是没问题的,因为出现在的列表中。但是,如果定义一个就会报错:
原因是并未出现在的列表中。这种类主要用于一些框架,防止继承被滥用。
类在Java 15中目前是预览状态,要启用它,必须使用参数和。
2.4.5.向上转型
如果一个引用变量的类型是,那么它可以指向一个类型的实例:
如果一个引用类型的变量是,那么它可以指向一个类型的实例:
现在问题来了:如果是从继承下来的,那么,一个引用类型为的变量,能否指向类型的实例?
测试一下就可以发现,这种指向是允许的!
这是因为继承自,因此,它拥有的全部功能。类型的变量,如果指向类型的实例,对它进行操作,是没有问题的!
这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。
向上转型实际上是把一个子类型安全地变为更加抽象的父类型:
注意到继承树是,所以,可以把类型转型为,或者更高层次的。
2.4.6.向下转型
和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。例如:
如果测试上面的代码,可以发现:
类型实际指向实例,类型变量实际指向实例。在向下转型的时候,把转型为会成功,因为确实指向实例,把转型为会失败,因为的实际类型是,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
因此,向下转型很可能会失败。失败的时候,Java虚拟机会报。
为了避免向下转型出错,Java提供了操作符,可以先判断一个实例究竟是不是某种类型:
实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为,那么对任何的判断都为。
利用,在向下转型前可以先判断:
从Java 14开始,判断后,可以直接转型为指定变量,避免再次强制转型。例如,对于以下代码:
可以改写如下:
这种使用的写法更加简洁。
2.4.7.区分继承和组合
在使用继承时,我们要注意逻辑一致性。
考察下面的类:
这个类也有字段,那么,我们能不能让继承自呢?
显然,从逻辑上讲,这是不合理的,不应该从继承,而应该从继承。
究其原因,是因为是的一种,它们是is关系,而并不是。实际上和的关系是has关系。
具有has关系不应该使用继承,而是使用组合,即可以持有一个实例:
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/19103.html