添砖加瓦(java)
java面向对象
前言:
大家好我是kdy丶
文章目录
一丶面向对象与面向过程:
面向对象学习的主线归纳总结起来主要有三条:
1.Java类及类的成员:属性、方法、构造器;代码块、内部类
2.面向对象的大特征:封装性、继承性、多态性、(抽象性)
3.其它关键字:this、super、static、final、abstract、interface等
但说到面向对象我们就不得不首先说说他和面向过程之间的区别
1丶面向对象与面向过程的区别:
1丶面向对象与面向过程的区别:
首先我在这里问大家一个问题:怎么打开电脑,并下载qq?
看似一个简单的问题,其实里面大有学问!
1)面向过程的解决办法:
1丶打开总电源的开关
2丶走到电脑前面,插上电源
3丶打开电脑的电源
4丶下载qq
2)面向对象的解决办法:
1丶人:插电源的方法,打开开关的方法,启动电脑电源的方法 ,操作电脑的方法。
2丶电脑:启动电脑的方法,下载qq的方法。
3丶人通过插电源的方法,打开开关的方法,启动电脑电源的方法。让电脑启动,之后再通过操作电脑的方法让电脑下载qq。
通过这个简单的例子我们可以看出,面向过程主要进行的是步骤,不再乎是谁做的。而面向对象主要看的是每个对象和类的功能,非常在乎是谁做的而是有哪些功能!
我们在看看一下网上一位大佬举的非常权威的例子:
1)可以拿生活中的实例来理解面向过程与面向对象,例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
2)如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
所以总结起来:
1.面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
2.面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
2丶面向对象与面向过程的优缺点:
我们还是用一个例子来说明问题,包饺子:
如果是面向对象,我们可以想把皮和馅分开,我想要什么样的皮和什么样的馅都可以。
如果是面向过程,那馅和皮就分不开,因为我只在意这个过程,我是否把饺子包完。什么馅的饺子和什么形状的饺子已经完成。
可是却在半路相到一个问题,我不想吃酸菜馅饺子,我想吃肉馅的!
这个时候面向对象的优点就充分体现出来了,我可以随时包什么馅的都可以。面向过程则还要把皮和馅分离才行。
也就是所面向对象耦合性比面向过程相对来说要低,面向过程性能方面
上要更加的好。
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
二丶类与对象:
我们在上面已经充分的体会到了什么是面向对象,设计面向对象的程序时候我们应该很清楚地意识到:
面向对象程序设计的重点是类的设计
设计类,就是设计类的成员。
1丶类和对象的思想概述:
类:对一类事物的描述,是抽象的、概念上的定义
对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)
二者的关系:
对象,是由类new出来的,派生出来的
(在这里我们常常会听见这样的一个名词:对象的实例化,实例化对象,将对象实例化等等,其实这一系列相同类型的说辞就是将类具体化,也就new出一个对象)
我们可以理解为:类 = 抽象概念的人;对象 = 具体的某个人
2丶类和对象的语法格式:
class 类名称{
属性名称;
返回值类型 方法名称(){}
}
对象的定义:
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
类名称 对象名称 = new 类名称() ;
如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式:
访问类中的属性:对象.属性 ;
调用类中的方法:对象.方法();
3丶类的成员
1)简单介绍类的属性和行为:
常见的类的成员有:
1丶属性:对应类中的成员变量
2丶行为:对应类中的成员方法
属性和行为就好比:
我是一个人(类),我的姓名和身体的特征等等都属于属性,我能敲代码,玩手机,做饭,就是人这个类的行为。
我们用代码实现这个程序:
public class demo01 {
public static void main(String[] args) {
//对象实例化
person p=new person();
//调用属性
System.out.println(p.name = "唐三");
System.out.println(p.age = 18);
System.out.println(p.sex = "男");
//调用方法
p.eat();
p.play();
p.see("电影");
}
}
class person{
//属性
String name;//姓名
int age;//年龄
String sex;//性别
//方法(行为)
public void eat(){
System.out.println("吃饭");
}
//方法(行为)
public void play(){
System.out.println("打篮球");
}
//方法(行为)
public void see(String s){
System.out.println("看"+s);
}
}
运行结果:
面向对象的程序设计主要是设计类,而设计类主要是设计类中的成员。
在这里我们就不得具体不说一说属性和方法了。
2)类的结构之属性:
对比:属性(成员变量) vs 局部变量
1丶相同点:
1 定义变量的格式:数据类型 变量名 = 变量值
2 先声明,后使用
3 变量都其对应的作用域
2丶不同点:
- 在类中声明的位置的不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部 的变量
- 关于权限修饰符的不同
1丶属性:可以在声明属性时,指明其权限,使用权限修饰符。
常用的权限修饰符:private、public、缺省、protected —>封装性
2丶局部变量:不可以使用权限修饰符。
- 默认初始化值的情况:
属性:类的属性,根据其类型,都默认初始化值。
整型(byte、short、int、long:0)
浮点型(float、double:0.0)
字符型(char:0 (或’\u0000’))
布尔型(boolean:false)
引用数据类型(类、数组、接口:null)
局部变量:没默认初始化值。
意味着,我们在调用局部变量之前,一定要显式赋值。
特别地:形参在调用时,我们赋值即可。
- 在内存中加载的位置:
属性:加载到堆空间中 (非static)
局部变量:加载到栈空间
3)类的结构之方法:
1丶方法的声明:
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
2丶权限修饰符:
权限修饰符:默认方法的权限修饰符先都使用public
Java规定的4种权限修饰符:private、public、缺省、protected -->封装性再细说
3丶返回值类型: 返回值 vs 没返回值
如果方法返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量:“return 数据”。
如果方法没返回值,则方法声明时,使用void来表示。通常,没返回值的方法中,就不需要使用return.但是,如果想使用的话,只能“return;”表示结束此方法的意思。
4丶 方法名和形参:
属于标识符,遵循标识符的规则和规范,“见名知意”
形参列表: 方法可以声明0个,1个,或多个形参。
格式:数据类型1 形参1,数据类型2 形参2,…
5丶 方法体
方法功能的体现
4)类的结构之构造器
1.构造器(或构造方法):Constructor
构造器的作用:
1.创建对象
2.初始化对象的信息
构造器可以创建对象,为什么可以?我们可以思考,在前面创建那么多的对象怎么没见到构造器?
我们看一下:
kid k1=new kid();
我创建了一个类叫kid,我们在进行创建对象的时候new kid();这个
就是创建对象,但是我们new 后面那个kid()是什么?是一个类吗?
其实不是如果吧括号去掉,只写kid那么他确实是一个类,但是他加上了括号就是一个构造器!
所以我们创建对象的时候都是new +构造器来创建出来的对象。
初始化对象的信息我们可以这么理解:
就像我们人类一样先天就要吃饭,我们可以将吃饭的代码加入人类的构造器中,让他就有这个特性,而不是要单独创建一个方法去让对象调用。
2.使用说明:
1.如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器
2.定义构造器的格式:权限修饰符 类名(形参列表){}
3.一个类中定义的多个构造器,彼此构成重载
4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
5.一个类中,至少会有一个构造器。
6.父类的构造器不可被子类继承
3丶构造器的格式:
修饰符 类名 (参数列表) {
初始化语句;
}
例子:
public class demo05 {
public static void main(String[] args) {
kid k1=new kid();
kid k2=new kid("kdy",21);
}
}
//类
class kid{
String name;
int age;
//构造器
public kid(){
System.out.println("我是一个孩子");
}
//就有形参的构造器
public kid(String name,int age){
System.out.println("我的名字是"+name);
System.out.println("我是一个"+age+"岁的孩子");
}
}
运行结果:
我们在这里已经理解了构造器的格式和使用。说到这里我们可能会想为什么我们kid k1=new kid();就会把输出的结果打印出来?其实这就是一个硬性规定。如果解释的话也能强行解释:
我们都知道,如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器。所以当我们平常不创建构造方法,默认系统给我们程序一个无参数构造器的的时候,创建对象是什么也输出不出来的 ;只有自己给构造器里面添加内容才会显示出来。
但构造器主要的作用并不是显不显示什么内容,他的主要作用是: 1.创建对象 2.初始化对象的信息。
4丶对象的内存解析:
我们看这一段代码:
public class demo01 {
public static void main(String[] args) {
person p1=new person();
System.out.println(p1.name="小明");
System.out.println(p1.age);
person p2=new person();
System.out.println(p2.sex);
person p3=p1;
p3.age = 10;
System.out.println(p1.age);
}
}
class person{
//属性
String name;//姓名
int age;//年龄
String sex="男";//性别
//方法
public void eat(){
System.out.println("吃饭");
}
public void play(){
System.out.println("打篮球");
}
public void see(String s){
System.out.println("看"+s);
}
}
运行结果:
下面我们就分析一下这对象在jvm中的内存:
1)我们首先看person类,里面的属性
String name;//姓名
int age;//年龄
String sex=“男”;//性别
我们可以看见sex是初始化为“男“。在person p1=new person();之后,堆中开辟一个地址栈中的p1地址相同,指针指向,由于只有sex被初始化,所以name和age分别为null和0。p1.name=“小明”,之后小明被赋值到p1的name中。p1.age依旧为0。
2) person p2=new person(); System.out.println(p2.sex);
与p1情况类似,只是sex初始化为“男”,所以谁输出“男”
3)person p3=p1;System.out.println(p3.age = 10);在这里p1和p3共用堆中的同一内存,由于在实例化对象的时候,p1和p3是两个不同的引用对象,在栈中都会指向堆中的同一地址。所以也就是说p1和p3是相等的。
这时p3.age = 10改变的其实就是p1中的属性,所以p1.age输出的就是10了。
.
5丶匿名对象
匿名对象,顾名思义就是没有名字的对象,这个很好理解。
我们来看这一段代码:
public class demo02 {
public static void main(String[] args) {
//对象引用
per p=new per();
p.name="jack";
//匿名对象
new per().name="tom";
new per().talk();
}
}
class per{
String name;
int age;
String gender;
public void talk(){
System.out.println(name+"在说话");
}
}
运行结果:
一般来说创建对象会用一个引用来接收对象,但是匿名对象不接需要接收,这也是匿名对象的一个缺点:只能使用一次。
所以当我们运行这一段程序的时候,输出的结果为null,因为匿名对象只能用一次,每次用一次都会new出个新的地址在堆中。
6丶方法的重载:
方法的重载和方法的重写,其实看似就差一个字,但是根本就不是一样的。
1.方法的重载的概念
定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
总结:“两同一不同”:
相同:同一个类、相同方法名
不同:参数列表不同:参数个数不同,参数类型不同
所以说我们看一个方法是否构成重载,就是要判断是否这些方法具有同样的方法名,形参个数和数据类型是否形同,跟方法的权限修饰符、返回值类型、形参变量名、方法体都没关系。
例1:
public class demo03 {
public static void main(String[] args) {
ol a=new ol();
a.sum(1,6);
}
}
class ol{
public void sum(int i,int j){
int s=i+j;
System.out.println(s);
}
public void sum(String i,int j){
String s=i+j;
System.out.println(s);
}
public double sum(double i,int j){
double s=i+j;
return s;
}
public void sum(double i,double j){
double s= (i+j);
System.out.println(s);
}
}
运行结果:
我们可以看到,在相同的方法名,不同的参数列表是就可以运行的,没有报错。
三丶:面向对象三大特性:
1丶 面向对象的特征一:封装性
1丶什么是封装性?
其实就是隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
2丶可是我们为什么要使用封装呢?我们可以这么想:
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。
可能你并不像明白我说的是什么意思,我用代代码讲解下。
例1:
public class demo01 {
public static void main(String[] args) {
person p1=new person();
p1.hair=1000;
System.out.println(p1.hair);
p1.talk();
p1.eat();
}
}
class person{
public int hair;//头发
public void eat(){
System.out.println("我正在吃饭");
}
public void talk(){
System.out.println("我正在讲话");
}
}
我们可以看到我们将hair(头发)公有化,但是hair要是为负数就不合理了,在这个时候我们无法通过属性,声明和赋值来改变,只能通过方法来进行限制。hair也不是谁都能使用的,这是我的头发,我应该保护他,让他私有化才行!
而java中通过将数据声明为私有的(private),再提供公共的(public)
方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
1丶隐藏一个类中不需要对外提供的实现细节;
2丶使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,
限制对属性的不合理操作;
3丶便于修改,增强代码的可维护性;
例:
public class demo01 {
public static void main(String[] args) {
person p1=new person();
// p1.hair=1000;
// System.out.println(p1.hair);
p1.setHair(1000);
System.out.println(p1.getHair());
p1.talk();
p1.eat();
}
}
class person{
//public int hair;//头发
private int hair;//头发
public void setHair(int hair) {
if (hair<0){
System.out.println("头发不能小于0");
}
this.hair=hair;
}
public int getHair() {
return hair;
}
public void eat(){
System.out.println("我正在吃饭");
}
public void talk(){
System.out.println("我正在讲话");
}
}
我们可以看见这样做,既能保护好hair不被外类使用,又能通过方法来进行赋值调用,这就是封装性的一种体现。
3丶封装性思想具体的代码体现:
体现一:将类的属性xxx私化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
体现二:不对外暴露的私有的方法
体现三:单例模式(将构造器私有化)
体现四:如果不希望类在包外被调用,可以将类设置为缺省的。
2丶 面向对象的特征一:继承性
1丶什么是继承性?
通俗的讲,子类继承父类可以使用父类中所有的属性和方法,这就是java中继承性的一种体现。
2丶为什么要有类的继承性?
1丶减少了代码的冗余,提高了代码的复用性
2丶便于功能的扩展
3丶为之后多态性的使用,提供了前提
这其实也就是继承性的好处。
3丶继承性的格式:
class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
4丶子类和父类不同点:
public class demo03 {
public static void main(String[] args) {
birds b=new birds();
b.setName("八哥");
b.setAge(5);
System.out.println(b.getName());
System.out.println(b.getAge()+"岁");
b.eat();
b.foot();
b.fly();
}
}
class birds extends animal{
public void fly(){
System.out.println("飞翔");
}
}
class animal{
String name;
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void eat(){
System.out.println("吃饭");
}
public void foot(){
System.out.println("走路");
}
}
运行结果:
我们可以看见即使子类没有声明属性和方法,子类也是可以调用的。 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
我们还是看上面这行代码:
public class demo04 {
public static void main(String[] args) {
birds1 b=new birds1();
b.age;
b.setName("八哥");
b.setAge(5);
System.out.println(b.getName());
System.out.println(b.getAge()+"岁");
b.eat();
b.foot();
b.fly();
}
}
}
class birds1 extends animal1 {
public void fly() {
System.out.println("飞翔");
}
}
class animal1{
String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void eat(){
System.out.println("吃饭");
}
public void foot(){
System.out.println("走路");
}
}
b.age是会报错的,所以这时候我们是不是会想,难道私有的属性没有被继承吗?
其实并不是,不允许子类的访问,如果这能访问成功,那岂不是破坏了java的封装性,但我们要访问
可以通过get set方法访问。
所以:
一旦子类继承父类以后,子类中就获取了父类中声明的所有的属性和方法。
特别的,父类 中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。 只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
注意:
子类和父类的关系,不同于子集和集合的关系。
5丶Java中关于继承性的规定:
1.一个类可以被多个子类继承。
2.Java中类的单继承性:一个类只能有一个父类
3.子父类是相对的概念。
4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
3丶 方法的重写:
1丶重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作(所以前提一定是子父类有继承关系)
2丶应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
我们用代码感受一下:
例1:
public class demo05 {
public static void main(String[] args) {
birds2 bb=new birds2();
bb.eat();//子类:重写的方法
animal2 an=new animal2();
an.eat();//父类:被重写的方法
}
}
class birds2 extends animal2 {
@Override
public void eat(){
System.out.println("小鸟 吃饭");
}
public void fly() {
System.out.println("飞翔");
}
}
class animal2{
String name;
private int age;
public void eat(){
System.out.println("吃饭");
}
public void foot(){
System.out.println("走路");
}
}
运行结果:
3丶 重写的规定:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
}
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
特殊情况:子类不能重写父类中声明为private权限的方法
我修改类父类eat()方法的权限,可以看到 @Override是没有报错的,这说明这个规定时间成立的。
在这里我们可以用这个例子形象的理解子类和父类的权限修饰符:
假如这是一个鸡蛋,我们要制作荷包蛋,我们打碎了之后会出现蛋清和蛋黄,因为我要是这么一个正常打碎的鸡蛋,只有蛋清比蛋黄"面积"更大才可以。所以蛋清蛋黄该有的“面积”覆盖。
其实就像是我们权限修饰符的关系一样,越大他的功能就越强。
③ 返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
我们看这样一张图。说明确实子类的返回值类型必须父小于等于父类;
而是基本数据类型的时候不可以是同级的,必须相同。
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
4丶 面向对象的特征一:多态性
首先我们要明确一点,要想用多态性这一特征,那么必须要具备两个前提条件:
① 类的继承关系 ② 方法的重写
为什么这么说,不着急我们慢慢来看。
1丶我们要清楚什么是多态?
可以理解为一个事物的多种形态。
对象的多态性:父类的引用指向子类的对象
例:
Object object=new String();
object是父类Object的引用,new string()则为子类对象,jvm中栈中的对象引用指向堆中的对象。
2丶多态性的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
我们用代码举例说明:
public class demo06 {
public static void main(String[] args) {
//父类方法都可以调用
Person person=new Person();
person.eat();
person.talk();
Person person1=new man();
person1.eat();
person1.talk();
person1.money();
Person person2=new woman();
person1.eat();
person1.talk();
person1.money();
}
}
class Person{
String name;
int age;
public void eat(){
System.out.println("吃饭");
}
public void talk(){
System.out.println("唠嗑");
}
}
class man extends Person{
public void eat(){
System.out.println("男人吃饭多");
}
public void talk(){
System.out.println("男人唠嗑");
}
public void money(){
System.out.println("男人挣钱");
}
}
class woman extends Person{
public void eat(){
System.out.println("女人吃饭少");
}
public void talk(){
System.out.println("女人唠嗑");
}
public void money(){
System.out.println("女人花钱");
}
}
这段代码有两处编译是不通过的:
我们会想看看,因为对象多态性之后,子类重写的方法是可以通过编译的,但是子类特有的方法是没办法通过编译的。
我们来看看运行结果:
可以看出来,运行时即使编译通过,类型的父类,但是调用方法运行出来的结果却是子类的!
所以:
总结:编译,看左边;运行,看右边。
我们还要注意一点,就是对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
3丶向上转型与向下转型:
向上转型:多态
向下转型:
Object o=new String();
String o1 = (String) o;
向下转型,就是将子类进行强制类型转换。
我么会可以进行一下类比:
其实向下转型和基本数据类型的强制类型转换非常相似,基本数据类型的强转是较高级的转换成较低级的;向下转型是父类向子类转换。
但是我们思考一个问题,为什么使用向下转型?
其实是这样的,我们在进行对象的多态性之后,子类的本身特有的方法是不可以调用的所以我们要进行向下转型,才可以调用。
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特的属性和方法?使用向下转型。
使用向下转型的时候要注意:强转的类型不是随便转的!
例:
//问题一:编译时通过,运行时不通过
//举例一:
Person1 p1 = new Woman();
Man1 m1 = (Man1)p3;
//举例二:
Person p4 = new Person();
Man m4 = (Man)p4;
//问题二:编译通过,运行时也通过
Object obj = new Woman();
Person p = (Person)obj;
Object obj = new Woman();
Person p = (Person)obj;
//问题三:编译不通过(毫不相干的两个类)
Man m5 = new Woman();
String str = new Date();
所以总结下,强转接收的类型必须是本类或者父类。
5丶权限修饰符
1丶Java规定的4种权限(从小到大排列):private、缺省、protected 、public
2丶4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
3丶具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
java封装性在java中极为常见和重要,而我们的权限修饰符可以很好的配合java中的类和类的内部结构,将我们的程序进行封装。
四丶:关键字的使用:
1丶this关键字:
1丶可以调用的结构:属性、方法;构造器
2丶this调用属性、方法:
this理解为:当前对象 或 当前正在创建的对象
1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,
通常情况下,我们都择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
3丶this调用构造器:
① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
② 构造器中不能通过"this(形参列表)“方式调用自己
③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)”
④ 规定:"this(形参列表)“必须声明在当前构造器的首行
⑤ 构造器内部,最多只能声明一个"this(形参列表)”,用来调用其他的构造器
2丶super关键字:
1丶super 关键字可以理解为:父类的
2丶可以用来调用的结构:
属性、方法、构造器
3丶super调用属性、方法:
1丶我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
2 丶特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
3 丶特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。(一般都写在构造方法中)
4丶super调用构造器:
1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
2 丶"super(形参列表)"的使用,必须声明在子类构造器的首行!
3丶 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)“只能二一,不能同时出现
4丶 在构造器的首行,没显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
5 丶在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)”,调用父类中的构造器
5丶this和super的区别:
3丶static关键字:
1丶static:静态的,主要用来修饰类的内部结构—>可以用来修饰:属性、方法、代码块、内部类
首先我们思考一个问题:
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们用不同的对象调用不同的属性就会有不同种结果,但是当我们只需要某些特定的数据在内存空间里只有一份的时候,我们应该怎么做?static能帮我们实现。
2丶使用static修饰属性:静态变量(或类变量)
例:
public class demo02 {
public static void main(String[] args) {
animal.food="食物";
animal an=new animal();
an.name="dog";
an.food="骨头";
System.out.println(an.name+"吃"+an.food);
animal an1=new animal();
an1.name="cat";
an1.food="鱼";
System.out.println(an.name+"吃"+an.food);
System.out.println(an.name+"吃"+an.food);
}
}
class animal{
String name;
int age;
static String food;
}
我们先看这样一段代码,如果按我们正常的理解,三个输出会接连输出:dog吃骨头,dog吃鱼,dog吃骨头。但是我们在讲food变为静态方法会发生什么呢?
这就是static的作用,当他修饰属性的时候他会将属性共享变成一个类变量。
我们举一个很形象的例子:
一个三室一厅的屋子,如果房子是一个类,那么屋子就是对象,三个对象,每个屋子中海油凳子椅子等属性,那么这个厕所就是其实就是一个共享数据,是一个类变量。三个屋子可以不同但是厕所只有一个必须一样。
3丶静态变量与实例变量:
属性,按是否使用static修饰,又分为:静态属性 和 非静态属性(实例变量)
1丶实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
2丶静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致 其他对象调用此静态变量时,是修改过了的。
因为是类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接
通过类名来访问它。静态变量在内存中只存在⼀份
实例变量就是每创建⼀个实例就会产⽣⼀个实例变量,它与该实例同⽣共死。
4丶静态变量内存解析:
有人可能会想问,为什么static可以共享数据?如果说真的,其实就是java的硬性规范,但是从jvm内存的角度来说还是可以解释的。
我们来看这一图片。类变量的内存是在方法区中的,而我们每修改一次就会改变一次,不会因为对象的不同去改变。
5丶使用static修饰方法:静态方法
① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
② 静态方法 非静态方法
类 yes no
对象 yes yes
③ 静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
6丶单例模式
首先我们要知道什么是设计模式?
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
单例模式就是23中设计模式中的一种:
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构 造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
这个逻辑是十分紧密的,可能有人会想为什么要用静态方法修饰改方法?
其实很简单,就是因为类可以直接调用static修饰的属性和方法。我们可以类.静态方法();
单例模式又分为两种:饿汉式和懒汉式(未涉及多线程)。
public class demo03 {
public static void main(String[] args) {
Bank bank = Bank.getBank();
Bank bank1 = Bank.getBank();
System.out.println(bank==bank1);
}
}
class Bank{
//饿汉式
// private Bank(){
//
// }
//
// private static Bank bank= new Bank();
//
// public static Bank getBank(){
// return bank;
// }
//懒汉式
private Bank(){
}
private static Bank bank=null;
public static Bank getBank(){
if (bank==null) {
bank = new Bank();
}
return bank;
}
}
运行结果:
饿汉式 和 懒汉式 的区别:
1丶 饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的
2丶懒汉式:
好处:延迟对象的创建。
目前的写法坏处:线程不安全。
4丶final关键字:
final:最终的
1.可以用来修饰:类、方法、变量
final 用来修饰一个类:此类不能被其他类所继承。
比如:String类、System类、StringBuffer类
2 final 用来修饰方法:
表明此方法不可以被重写
比如:Object类中getClass();
3 final 用来修饰变量:此时的"变量"就称为是一个常量
- final修饰属性:可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化
.2. final修饰局部变量:
尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
5丶abstract关键字:
abstract: 抽象的
1.可以用来修饰:类、方法
2.具体的:
abstract修饰类:抽象类
此类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 —>抽象的使用前提:继承性
abstract修饰方法:抽象方法
抽象方法只方法的声明,没方法体
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
若子类重写了父类中的所的抽象方法后,此子类方可实例化
若子类没重写父类中的所的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
3.注意点:
1.abstract不能用来修饰:属性、构造器等结构
2.abstract不能用来修饰私方法、静态方法、final的方法、final的类
6丶interface关键字:
1丶 接口使用interface来定义,Java中,接口和类是并列的两个结构
那我们为什么要使用接口?
Java类可以实现多个接口 —>弥补了Java单继承性的局限性
1丶一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
2丶另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有IS-A的关系,仅仅是具有相同的行为特征而已。接口更像是⼀种 LIKE-A 关系,它只是提供⼀种⽅法实现契约,并不要求接和口和实现接口的类具有 IS-A 关系
在这类大家可能不能理解什么事IS-A和LIKE-A的关系,我们举例说明:
医生IS-A人,就是医生是一类人,这是我们用来衡量继承性的一种关系;而LIKE-A,就是只是具备相同的特征而已,就像我们的医生和护士都会打针。就是说IS-A需要满足⾥式替换原则,即⼦类对象必须能够替换掉所有⽗类对象。;而LIKE-A只要实现一个这个特征就可以,发过来就是接口只要实现就必须拥有这个功能。
2丶接口中不能定义构造器的!意味着接口不可以实例化
接⼝是抽象类的延伸,在 Java 8 之前,它可以看成是⼀个完全抽象的类,也就是说它不能有任何的方法实现。
从 Java 8 开始,接⼝也可以拥有默认的⽅法实现,这是因为不⽀持默认⽅法的接⼝的维护成本太高了。在 Java 8 之前,如果⼀个接⼝想要添加新的方法,那么要修改所有实现了该接⼝的类,让它们都
实现新增的⽅法。(静态方法和默认方法)
接⼝的成员(字段 + ⽅法)默认都是 public 的,并且不允许定义为 private 或者 protected。从 Java 9
开始,允许将⽅法定义为 private,这样就能定义某些复⽤的代码⼜不会把⽅法暴露出去。接⼝的字段默认都是 static 和 final 的。也就是说:
全局常量:public static final的.但是书写时,可以省略不写
抽象方法:public abstract的
我们用一段代码体会下:
public class demo04 {
public static void main(String[] args) {
flash f=new flash();
computer com=new computer();
com.sah(f);
}
}
class computer{
public void sah(USB usb){//多态性的体现:USB usb=new flash();
usb.start();
System.out.println("执行中");
usb.end();
}
}
//实现接口
class flash implements USB{
@Override
public void start() {
System.out.println("开始");
}
@Override
public void end() {
System.out.println("结束");
}
}
//接口
interface USB{
void start();//默认是public static final修饰的
void end();
}
3丶接口规范
这是一个很抽象的概念其实接口就是规范,本质是一种契约,规范,标椎。在继承中我们假如定义一个医生类,那么我们继承强调的就是是不是的关系;而如果是实现接口就强调能不能的关系(你是不是个医生; 你能不能看病打针)。
五丶总结:
今天整理的java面向对象。希望大家能学到,同时也是自己重温知识的过程,我每天都会整理javase的基础知识分享给大家,完善自己。如果哪方面有问题,还请指正,期待大家的评论和关注,最后送给大家也送给自己一句话:真正的大师永远都怀着一颗学徒的心。
——我是kdy丶,一个未来的java程序员