大家好,欢迎来到IT知识分享网。
一、面向过程和面向对象思想
面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的方式去分析、设计和开发软件。早期先有面向过程思想,随着软件规模的扩大,问题复杂性的提高,面向过程的弊端越来越明显的显示出来,出现了面向对象思想并成为目前主流的方式。两者都贯穿于软件分析、设计和开发各个阶段,对应面向对象就分别称为面向对象分析(OOA)、面向对象设计(OOD)和面向对象编程(OOP)。C 语言是一种典型的面向过程语言,Java 是一种典型的面向对象语言。
面向过程思想思考问题时,我们首先思考“怎么按步骤实现?”并将步骤对应成方法,一步一步,最终完成。 这个适合简单任务,不需要过多协作的情况下。
面向过程适合简单、不需要协作的事务,重点关注如何执行。但是当我们思考比较复杂的设计任务时,此时面向对象思想就应运而生了。面向对象(Oriented-Object)思想更契合人的思维模式。我们首先思考的是“怎么设计这个事物?” 。
面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。 但是,具体到实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理。我们千万不要把面向过程和面向对象对立起来。他们是相辅相成的。面向对象离不开面向过程!
面向对象和面向过程思想的总结:都是解决问题的思维方式,都是代码组织的方式。面向过程是一种“执行者思维”,解决简单问题可以使用面向过程。面向对象是一种“设计者思维”,解决复杂、需要协作的问题可以使用面向对象。面向对象离不开面向过程:宏观上:通过面向对象进行整体设计;微观上:执行和处理数据,仍然是面向过程。
二、对象和类的详解
类:我们叫做 class。 对象:我们叫做 Object,instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思。
总结:类可以看成一类对象的模板,对象可以看成该类的一个具体实例。类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性、方法。
1.类的定义
// 每一个源文件必须有且只有一个public class,并且类名和文件名保持一致! public class Car { } class Tyre { // 一个Java文件可以同时定义多个class } class Engine { } class Seat { }
上面的类定义好后,没有任何的其他信息,就跟我们拿到一张张图纸,但是纸上没有任何信息,这是一个空类,没有任何实际意义。所以,我们需要定义类的具体信息。对于一个类来说,一般有三种常见的成员:属性 field、方法 method、构造器 constructor。这三种成员都可以定义零个或多个。
2.属性(field 成员变量)
属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。在定义成员变量时可以对其初始化,如果不对其初始化,Java 使用默认的值对其初始化。
成员变量的默认值 |
|
数据类型 |
默认值 |
整型 |
0 |
浮点型 |
0.0 |
字符型 |
‘\u0000’ |
布尔型 |
false |
所有引用类型 |
null |
属性定义的格式:
【修饰符】 属性类型 属性名=【默认值】;
3.方法
方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象。方法很类似于面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。
方法定义格式:
[修饰符] 方法返回值类型 方法名(形参列表) {
// n 条语句
}
方法的详细说明:
形式参数:在方法声明时用于接收外界传入的数据。
实参:调用方法时实际传给方法的数据。
返回值:方法在执行完毕后返还给调用它的环境的数据。
返回值类型:事先约定的返回值的数据类型,如无返回值,必须指定为 void。
注意事项:
1.实参的数目、数据类型和次序必须和所调用的方法声明的形式参数列表匹配。
2.return 语句终止方法的运行并指定要返回的数据。
3.Java 中进行方法调用中传递参数时,遵循值传递的原则(传递的都是数据的副本):基本类型传递的是该数据值的 copy 值。引用类型传递的是该对象引用的 copy 值,但指向的是同一个对象。
方法的重载(overload):
方法的重载是指一个类中可以定义多个方法名相同,但参数不同的方法。调用时,会根据不同的参数自动匹配对应的方法。重载的方法,实际是完全不同的方法,只是名称相同而已!
构成方法重载的条件:
不同的含义:形参类型、形参个数、形参顺序不同。
只有返回值不同不构成方法的重载,如:int a(String str){}与 void a(String str){}不构成方法重载。
只有形参的名称不同,不构成方法的重载,如:int a(String str){}与 int a(String s){}不构成方法重载。
package cn.pxy.test; public class OverloadTest { public static void main(String[] args) { System.out.println(add(3,5)); System.out.println(add(3,5,10)); System.out.println(add(3.0,5)); System.out.println(add(5,3.0)); } //求和方法 public static int add(int n1,int n2) { int sum=n1+n2; return sum; } //方法名相同,参数个数不同构成重载 public static int add(int n1,int n2,int n3) { int sum=n1+n2+n3; return sum; } //方法名相同,参数类型不同构成重载 public static double add(double n1,int n2) { double sum=n1+n2; return sum; } //方法名相同,参数顺序不同,构成重载 public static double add(int n1,double n2) { double sum=n1+n2; return sum; } }
运行结果:
4.构造方法(构造器constructor)
构造方法基础用法:
构造器也叫构造方法(constructor),用于对象的初始化。构造器是一个创建对象时被自动调用的特殊方法,目的是对象的初始化。构造器的名称应与类的名称一致。Java 通过new 关键字来调用构造器,从而返回该类的实例,是一种特殊的方法。
声明格式:
[修饰符] 类名(形参列表){
//n 条语句
}
构造器 4 个要点:
1.构造器通过 new 关键字调用!2.构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用 return 返回某个值。3.如果我们没有定义构造器,则编译器会自动定义一个无参的构造方法。如果已定义则编译器不会自动添加!4.构造器的方法名必须和类名一致!
对象的创建完全是由构造方法实现的吗?
不完全是。构造方法是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实返回了该类对象,但这个对象并不是完全由构造器负责创建的。创建一个对象分为如下四步:
1.分配对象空间,并将对象成员变量初始化为 0 或空
2.执行属性值的显式初始化
3.执行构造方法
4.返回对象的地址给相关的变量
构造方法的重载:
package cn.pxy.test; public class User{ int id; String name; String pwd; public User() { } public User(int id,String name) { this.id=id; this.name=name; } public User(int id,String name,String pwd) { this.id=id; this.name=name; this.pwd=pwd; } public static void main(String[] args) { User u1=new User(); User u2=new User(101,"李四"); User u3=new User(102,"张三",""); } }
如果方法构造中形参名与属性名相同时,需要使用 this 关键字区分属性与形参。如上例所示:this.id 表示属性 id;id 表示形参 id。
5.一个典型的学生类的定义与UML图
package cn.pxy.test; public class SxtStu { int id; String name; int age; Computer comp; void study() { System.out.println("我在使用我的"+comp.brand+"电脑学习!"); } SxtStu(){ } public static void main(String[] args) { SxtStu stu=new SxtStu(); stu.name="张三"; Computer comp=new Computer(); comp.brand="联想"; stu.comp=comp; stu.study(); } } class Computer{ String brand;//品牌 }
运行结果:
对应的UML图:
三、面向对象的内存分析
1.程序执行的内存分析过程
Java虚拟机内存模型:
为了分析程序执行的内存,Java 虚拟机的内存可以简单的分为三个区域:虚拟机栈 stack、堆 heap、方法区 method area。
虚拟机栈(简称:栈)的特点如下:
1.栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
2.JVM 为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
3.栈属于线程私有,不能实现线程间的共享!
4.栈的存储特性是“先进后出,后进先出”
5.栈是由系统自动分配,速度快!栈是一个连续的内存空间!
堆的特点如下:
1.堆用于存储创建好的对象和数组(数组也是对象)
2.JVM 只有一个堆,被所有线程共享
3.堆是一个不连续的内存空间,分配灵活,速度慢!
方法区(又叫静态区,也是堆)特点如下:
1.方法区是 JAVA 虚拟机规范,可以有不同的实现。
i.JDK7 以前是“永久代”
ii.JDK7 部分去除“永久代”,静态变量、字符串常量池都挪到了堆内存中
iii.JDK8 是“元数据空间”和堆结合起来。
2.JVM 只有一个方法区,被所有线程共享!
3.方法区实际也是堆,只是用于存储类、常量相关的信息!
4.用来存放程序中永远是不变或唯一的内容。(类信息、静态变量、字符串常量等)
示例:
创建Person类:
package cn.pxy.test; public class Person { String name; int age; public void show() { System.out.println("姓名:"+name+",年龄:"+age); } }
创建Person类对象并使用:
package cn.pxy.test; public class TestPerson { public static void main(String[] args) { //创建p1对象 Person p1=new Person(); p1.name="张三"; p1.age=18; p1.show(); //创建p2对象 Person p2=new Person(); p2.name="李四"; p2.age=22; p2.show(); } }
运行结果:
内存分配图:同一类的每个对象有不同的成员变量存储空间。同一类的每个对象共享该类的方法。
2.参数传值机制
Java 中,方法中所有参数都是“值传递”,也就是“传递的是值的副本”。 也就是说,我们得到的是“原参数的复印件,而不是原件”。因此,复印件改变不会影响原件。
基本数据类型参数的传值:传递的是值的副本。 副本改变不会影响原件。
引用类型参数的传值:传递的是值的副本。但是引用类型指的是“对象的地址”。因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变”。
示例:多个变量指向同一个对象
package cn.pxy.test; public class User{ int id; String name; String pwd; public User(int id,String name) { this.id=id; this.name=name; } public static void main(String[] args) { User u1=new User(101,"李四"); User u3=u1; System.out.println(u1.name); u3.name="张三"; //引用类型参数传递会改变原先的值 System.out.println(u1.name); } }
运行结果:
四、this、static 关键字
1.this关键字
对象创建的过程和 this 的本质:
构造方法是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实返回该类的对象,但这个对象并不是完全由构造器负责创建。创建一个对象分为如下四步:
1.分配对象空间,并将对象成员变量初始化为 0 或空
2.执行属性值的显式初始化
3.执行构造方法
4.返回对象的地址给相关的变量
this 的本质就是“创建好的对象的地址”! 由于在构造方法调用前,对象已经创建。因此,在构造方法中也可以使用 this 代表“当前对象”。
this 最常的用法:
在程序中产生二义性之处,应使用 this 来指明当前对象;普通方法中,this 总是指向调用该方法的对象。构造方法中,this 总是指向正要初始化的对象。
使用 this 关键字调用重载的构造方法,避免相同的初始化代码。但只能在构造方法中用,并且必须位于构造方法的第一句。
this 不能用于 static 方法中。
this关键字的使用:
package cn.pxy.test; public class User { int id; //id String name; //账户名 String pwd; //密码 public User() { } public User(int id, String name) { System.out.println("正在初始化已经创建好的对象:"+this); this.id = id; //不写this,无法区分局部变量id和成员变量id this.name = name; } public void login(){ System.out.println(this.name+",要登录!"); //不写this效果一样 } public static void main(String[ ] args) { User u3 = new User(101,"张三"); System.out.println("打印张三对象:"+u3); u3.login(); } }
运行结果:
this()调用重载构造方法:
package cn.pxy.test; public class TestThis { int a,b,c; TestThis(){ System.out.println("正要初始化一个Hello对象"); } TestThis(int a,int b){ //TestThis();//这样是无法调用构造方法的 this();//调用无参构造方法,并且必须位于第一行 a=a;//这里都是指的局部变量而不是成员变量 this.a=a;//这样就区分了局部变量和成员变量,这种情况占了this使用情况的大多数 this.b=b; System.out.println(a+b); } TestThis(int a,int b,int c){ this(a,b);//调用带参的构造方法,并且必须位于第一行 this.c=c; System.out.println(a+b+c); } void sing() { System.out.println("sing...."); } void eat() { this.sing();//调用本类的sing() System.out.println("回家吃饭!"); } public static void main(String[] args) { TestThis hi=new TestThis(2,3); hi.eat(); } }
运行结果:
2.static关键字
在类中,用 static 声明的成员变量为静态成员变量,也称为类变量。 类变量的生命周期和类相同,在整个应用程序执行期间都有效。它有如下特点:
为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化。
对于该类的所有对象来说,static 成员变量只有一份。被该类的所有对象共享!!
一般用“类名.类属性/方法”来调用。(也可以通过对象引用或类名(不需要实例化)访问静态成员。)
在 static 方法中不可直接访问非 static 的成员。
static关键字的使用:
package cn.pxy.test; public class User { int id; //id String name; //账户名 String pwd; //密码 static String company="头条号";//公司名 public User(int id, String name) { this.id = id; //不写this,无法区分局部变量id和成员变量id this.name = name; } public void login(){ System.out.println(this.name+",要登录!"); //不写this效果一样 } public static void printCompany() { //login();//调用非静态成员,编译就会报错 System.out.println(company); } public static void main(String[ ] args) { User u = new User(101,"张三"); User.printCompany(); User.company="阿里巴巴"; User.printCompany(); } }
运行结果:
静态初始化块:
构造方法用于对象的初始化!静态初始化块,用于类的初始化操作!在静态初始化块中不能直接访问非 static 成员。
静态初始化块执行顺序:
上溯到 Object 类,先执行 Object 的静态初始化块,再向下执行子类的静态初始化块,直到类的静态初始化块为止。构造方法执行顺序和上面顺序一样
package cn.pxy.test; public class User { int id; //id String name; //账户名 String pwd; //密码 static String company;//公司名 static { System.out.println("这里执行类的初始化工作"); company="头条号"; printCompany(); } public static void printCompany() { System.out.println(company); } public static void main(String[ ] args) { User u = new User(); } }
运行结果:
五、包机制(package、import)
包机制是 Java 中管理类的重要手段。 开发中,我们会遇到大量同名的类,通过包我们很容易对解决类重名的问题,也可以实现对类的有效管理。 包对于类,相当于文件夹对于文件的作用。
1.package
我们通过 package 实现对类的管理,package 的使用有两个要点:
1.通常是类的第一句非注释性语句。
2.包名:域名倒着写即可,再加上模块名,便于内部管理类。
写项目时都要加包,不要使用默认包。com.pxy 和 com.pxy.car,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者是前者的一部分。
JDK 中的主要包:
JDK 中的主要包 |
|
java中的常用包 |
说明 |
java.lang |
包含一些 Java 语言的核心类,如 String、Math、Integer、System 和 Thread,提供常用功能。 |
java.awt |
包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 |
java.net |
包含执行与网络相关的操作的类。 |
java.io |
包含能提供多种输入/输出功能的类。 |
java.util |
包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。 |
2.导入类import
如果我们要使用其他包的类,需要使用 import 导入,从而可以在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名。import 后,便于编写代码,提高可维护性。
注意要点:
Java 会默认导入 java.lang 包下所有的类,因此这些类我们可以直接使用。
如果导入两个同名的类,只能用包名+类名来显示调用相关类:
java.util.Date date = new java.util.Date();
示例:导入同名类
import java.sql.Date; import java.util.*;//导入该包下所有的类。会降低编译速度,但不会降低运行速度。 public class Test{ public static void main(String[ ] args) { //这里指的是java.sql.Date Date now; //java.util.Date因为和java.sql.Date类同名,需要完整路径 java.util.Date now2 = new java.util.Date(); System.out.println(now2); //java.util包的非同名类不需要完整路径 Scanner input = new Scanner(System.in); } }
静态导入:
静态导入(static import)是在 JDK1.5 新增加的功能,其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。
package cn.pxy.test; //以下两种静态导入的方式二选一即可 import static java.lang.Math.*;//导入Math类的所有静态属性 import static java.lang.Math.PI;//导入Math类的PI属性 public class Test{ public static void main(String [ ] args){ System.out.println(PI); System.out.println(random()); } }
运行结果:
六、Object类详解
1.Object 类基本特性
Object 类是所有 Java 类的根基类,也就意味着所有的 Java 对象都拥有 Object 类的属性和方法。如果在类的声明中未使用 extends 关键字指明其父类,则默认继承 Object 类。
2.toString 方法
Object 类中定义有 public String toString()方法,其返回值是 String 类型。Object类中 toString 方法的源码为:
public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
根据如上源码得知,默认会返回“类名+@+16 进制的 hashcode”。在打印输出或者用字符串连接对象时,会自动调用该对象的 toString()方法。
示例:重写toString方法
package cn.pxy.test; class Person1 { String name; int age; @Override public String toString() { return name+",年龄:"+age; } } public class Test { public static void main(String[ ] args) { Person1 p=new Person1(); p.age=20; p.name="李四"; System.out.println("info:"+p); Test t = new Test(); System.out.println(t); } }
运行结果:
3.==和 equals 方法
“==”代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。
Object 类中定义有:public boolean equals(Object obj)方法,提供定义“对象内容相等”的逻辑。比如,我们在学籍系统中认为学号相同的人就是同一个人。
Object 的 equals 方法默认就是比较两个对象的 hashcode,是同一个对象的引用时返回 true 否则返回 false。但是,我们可以根据我们自己的要求重写 equals 方法。
示例:重写 equals()方法
package cn.pxy.test; public class TestEquals { public static void main(String[] args) { Person2 p1=new Person2(100,"张三"); Person2 p2=new Person2(100,"李四"); System.out.println(p1==p2);//false,不是同一个对象 System.out.println(p1.equals(p2));//true,id相同则认为两个对象内容相同 String s1=new String("胖咸鱼"); String s2=new String("胖咸鱼"); System.out.println(s1==s2);//false,两个字符串不是同一个对象 System.out.println(s1.equals(s2));//true,两个字符串内容相同 } } class Person2{ int id; String name; public Person2(int id,String name) { this.id=id; this.name=name; } public boolean equals(Object obj) { if(obj==null) { return false; }else { if(obj instanceof Person2) { Person2 c=(Person2)obj; if(c.id==this.id) { return true; } } } return false; } }
运行结果:
JDK 提供的一些类,如 String、Date、包装类等,重写了 Object 的 equals 方法,调用这些类的 equals 方法, x.equals (y),当 x 和 y 所引用的对象是同一类对象且属性内容相等时(并不一定是相同对象),返回true 否则返回 false。
4.super关键字
super“可以看做”是直接父类对象的引用。可以通过 super 来访问父类中被子类覆盖的方法或属性。使用 super 调用普通方法,语句没有位置限制,可以在子类中随便调用。
在一个类中,若是构造方法的第一行代码没有显式的调用 super(…)或者 this(…);那么Java 默认都会调用 super(),含义是调用父类的无参数构造方法。这里的 super()可以省略。
package cn.pxy.test; public class TestSuper { public static void main(String[] args) { new ChildClass().f(); } } class FatherClass{ public int value; public void f() { value=100; System.out.println("FatherClass.value="+value); } } class ChildClass extends FatherClass{ public int value; public void f() { super.f();//调用父类的普通方法 value=200; System.out.println("ChildClass.value="+value); System.out.println(value); System.out.println(super.value);//调用父类的成员变量 } }
super的使用运行结果:
七、继承
继承是面向对象编程的三大特征之一,它让我们更加容易实现对于已有类的扩展、更加容易实现对于现实世界的建模。继承有两个主要作用:1.代码复用,更加容易实现类的扩展2.方便建模
1.继承的实现
继承让我们更加容易实现类的扩展。 比如,我们定义了人类,再定义 Boy 类就只需要扩展人类即可。子类是父类的扩展。继承的使用:
package cn.pxy.test; public class Test{ public static void main(String[] args) { Student s=new Student("李四",18,"java"); s.rest(); s.study(); } } class Person1{ String name; int age; public void rest() { System.out.println("我要休息一会!"); } } class Student extends Person1{ String major; public void study() { System.out.println("我在学习英语!"); } public Student(String name,int age,String major) { //拥有父类的属性 this.name=name; this.age=age; this.major=major; } }
运行结果:
2.instanceof运算符
instanceof 是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回 true;否则,返回 false。比如:在上例基础上测试:
public class Test{ public static void main(String[] args) { Student s=new Student("李四",18,"java"); System.out.println(s instanceof Person1); System.out.println(s instanceof Student); } } 两条输出语句都返回true。
3.继承使用的注意点
1.父类也称作超类、基类。子类:派生类等。
2.Java 中只有单继承,没有像 C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
3.Java 中类没有多继承,接口有多继承。
4.子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
5. 如果定义一个类时,没有调用 extends,则它的父类是:java.lang.Object。
4.方法的重写override
子类通过重写父类的方法,可以用自身的行为替换父类的行为。方法的重写是实现多态的必要条件。
方法的重写需要符合下面的三个要点:
1.“= =”:方法名、形参列表相同。
2.“≤”:返回值类型和声明异常类型,子类小于等于父类。
3.“≥”: 访问权限,子类大于等于父类
package cn.pxy.test; public class TestOverride { public static void main(String[] args) { Vehicle v1=new Vehicle(); Vehicle v2=new Plane(); v1.run(); v1.stop(); v2.run(); v2.stop(); } } class Vehicle{//交通工具类 public void run() { System.out.println("跑步。。。"); } public void stop() { System.out.println("停下来。。。"); } } class Plane extends Vehicle{ public void run() {//重写父类方法 System.out.println("天上飞~!!"); } public void stop() { System.out.println("停下来就坠机~!!"); } }
运行结果:
5.final关键字
final 关键字的作用:
修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
final int MAX_SPEED = 120;
修饰方法:该方法不可被子类重写。但是可以被重载!
final void study(){}
修饰类:修饰的类不能被继承。比如:Math、String 等。
final class A {}
6.继承树追溯
属性/方法查找顺序:(比如:查找变量 h)
1.查找当前类中有没有属性 h。
2.依次上溯每个父类,查看每个父类中是否有 h,直到 Object。
3.如果没找到,则出现编译错误。
4.上面步骤,只要找到 h 变量,则这个过程终止。
构造方法调用顺序:
构造方法第一句总是:super(…)来调用父类对应的构造方法。所以,流程就是:先向上追溯到 Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。
注:静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。
继承条件下构造方法执行过程:
package cn.pxy.test; public class TestSuper1 { public static void main(String[] args) { System.out.println("开始创建一个ChildClass对象。。。"); new ChildClass2(); } } class FatherClass2{ public FatherClass2() { System.out.println("创建FatherClass"); } } class ChildClass2 extends FatherClass2{ public ChildClass2() { System.out.println("创建ChildClass"); } }
运行结果:
7.继承和组合
我们可以通过继承方便的复用已经定义类的代码。还有一种方式,也可以方便的实现“代码复用”,那就是:“组合”。
“组合”不同于继承,更加灵活。“组合”的核心就是“将父类对象作为子类的属性”,然后,“子类通过调用这个属性来获得父类的属性和方法”。
package cn.pxy.test; public class Test{ public static void main(String[] args) { Student s=new Student("李四",18,"java"); s.person.rest(); s.study(); } } class Person1{ String name; int age; public void rest() { System.out.println("我要休息一会!"); } } class Student /*extends Person1*/{ Person1 person=new Person1(); String major; public void study() { System.out.println("我在学习英语!"); } public Student(String name,int age,String major) { //拥有父类的属性 this.person.name=name; this.person.age=age; this.person.rest(); this.major=major; } }
运行结果:
组合比较灵活。继承只能有一个父类,但是组合可以有多个属性。继承除了代码复用、也能方便我们对事物建模。所以,对于“is -a”关系建议使用继承,“has-a”关系建议使用组合。比如:上面的例子,Student is a Person 这个逻辑没问题,但是:Student has a Person就有问题了。这时候,显然继承关系比较合适。再比如:笔记本和芯片的关系显然是“has-a”关系,使用组合更好。
八、封装(encapsulation)
封装是面向对象三大特征之一。对于程序合理的封装让外部调用更加方便,更加利于写作。同时,对于实现者来说也更加容易修正和改版代码。
1.封装的作用和含义
封装就是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
就像我们要看电视,只需要按一下开关和换台就可以了,没有必要了解电视机内部的结构,制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。具体内部是怎么实现的,我们不需要操心。
编程中封装的具体优点:
提高代码的安全性。
提高代码的复用性。
“高内聚”:封装细节,便于修改内部代码,提高可维护性。
“低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。
2.封装的实现—使用访问控制符
Java 是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。 Java中 4 种“访问控制符”分别为 private、default、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。访问权限范围如表:
访问权限修饰符 |
||||
修饰符 |
同一个类 |
同一个包中 |
子类 |
所有类 |
private |
* |
|||
default |
* |
* |
||
protected |
* |
* |
* |
|
public |
* |
* |
* |
* |
1.private 表示私有,只有自己类能访问
2.default 表示没有修饰符修饰,只有同一个包的类能访问
3.protected 表示可以被同一个包的类以及其他包中的子类访问
4.public 表示可以被该项目的所有包中的所有类访问
3.封装的使用细节
开发中封装的简单规则:
属性一般使用 private 访问权限。
属性私有后, 提供相应的 get/set 方法来访问相关属性,这些方法通常是public 修饰的,以提供对属性的赋值与读取操作(注意:boolean 变量的 get方法是 is 开头!)。
方法:一些只用于本类的辅助性方法可以用 private 修饰,希望其他类调用的方法用 public 修饰。
封装的使用:
package cn.pxy.test; public class Test{ public static void main(String[] args) { Person1 p1=new Person1(); //p1.name="李四";//编译错误 p1.setName("李四"); p1.setAge(45); System.out.println(p1); Person1 p2=new Person1("张三",21); System.out.println(p2.getName()); } } class Person1{ private String name; private int age; public Person1() { } public Person1(String name,int age) { setName(name); setAge(age); } public void setName(String name) { this.name=name; } public String getName() { return name; } public void setAge(int age) { //在复制前先判断年龄是否合法 if(age>130||age<0) { this.age=18;//不合法赋值默认值18 }else { this.age=age; } } public int getAge() { return age; } public String toString() { return "Person1[name="+name+",age="+age+"]"; } }
运行结果:
九、多态(polymorphism)
1.多态概念和实现
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。 比如:同样是调用人的“休息”方法,张三是睡觉,李四是旅游。
多态的要点:
1.多态是方法的多态,不是属性的多态(多态与属性无关)。
2.多态的存在要有 3 个必要条件:继承,方法重写,父类引用指向子类对象。
3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
package cn.pxy.test; class Animal{ public void shout() { System.out.println("叫了一声。"); } } class Dog extends Animal{ public void shout() { System.out.println("汪汪汪!"); } public void Work(){ System.out.println("看门!"); } } class Cat extends Animal{ public void shout() { System.out.println("喵喵喵!"); } } public class TestPolym { public static void main(String[] args) { Animal a1=new Cat();//向上可以自动类型转换 //传的具体是哪一类就调用那一个类的方法。大大提高了程序的可扩展性 animalCry(a1); Animal a2=new Dog(); animalCry(a2); /* * 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换 * 否则通不过编译器的检查 */ Dog dog=(Dog)a2; dog.Work(); } //有了多态,只需要让增加的这个类继承Animal类就可以了 static void animalCry(Animal a) { a.shout(); } /** * 如果没有多态,这里需要写很多重载的方法 * 每增加一种动物,就需要重载一种动物的叫法,非常麻烦 * static void animalCry(Dog d){ * d.shout(); * } * static void animalCry(Cat c){ * c.shout; * } */ }
运行结果:
示例展示了多态最为多见的一种用法,即父类引用做方法的形参,实参可以是任意的子类对象,可以通过不同的子类对象实现不同的行为方式。
由此,可以看出多态的主要优势是提高了代码的可扩展性,符合开闭原则。但是多态也有弊端,就是无法调用子类特有的功能,比如,不能使用父类的引用变量调用 Dog类特有的 Work()方法。
2.对象的转型(casting)
父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型!
示例:对象的转型:
package cn.pxy.test; public class TestCasting { public static void main(String[] args) { Object obj=new String("胖咸鱼先生说");//向上可以自动转型 //obj.charAt(0);无法调用,编译器认为obj是Object类型而不是String类型 /* * 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换 */ String str=(String)obj;//向下转型 System.out.println(str.charAt(0));//位于0索引位置的字符 System.out.println(obj==str);//true,他们运行时是同一个对象 } }
运行结果:
示例:类型转换异常:
package cn.pxy.test; public class TestCasting { public static void main(String[] args) { Object obj=new String("胖咸鱼先生说"); //真实的子类类型是String,但是此处向下转型为StringBuffer StringBuffer str=(StringBuffer)obj; System.out.println(str.charAt(0)); } }
运行结果:
示例:向下转型中使用instanceof:
package cn.pxy.test; public class TestCasting { public static void main(String[] args) { Object obj=new String("胖咸鱼先生说"); if(obj instanceof String) { String str=(String)obj; System.out.println(str.charAt(0)); }else if(obj instanceof StringBuffer) { StringBuffer str=(StringBuffer)obj; System.out.println(str.charAt(0)); } } }
运行结果:
十、抽象类和接口
1.抽象方法和抽象类
抽象方法
使用 abstract 修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
抽象类
包含抽象方法的类就是抽象类。通过 abstract 方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
例如:
package cn.pxy.test; //抽象类 abstract class Animal1{ //抽象方法 abstract public void shout(); } class Pig extends Animal1{ //子类必须实现父类的抽象方法 public void shout() { System.out.println("哼哼哼"); } } public class TestAbstractClass { public static void main(String[] args) { Pig a=new Pig(); a.shout(); } }
抽象类的使用要点:
1.有抽象方法的类只能定义成抽象类
2.抽象类不能实例化,即不能用 new 来实例化抽象类。
3.抽象类可以包含属性、方法、构造方法。但是构造方法不能用来 new 实例,只能用来被子类调用。
4.抽象类只能用来被继承。
5.抽象方法必须被子类实现。
2.接口 interface
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。如果你是汽车,则必须能跑。
2.1接口的作用:
为什么需要接口?接口和抽象类的区别?
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。
抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类具有的公共方法规范。
从接口的实现者角度看,接口定义了可以向外部提供的服务。从接口的调用者角度看,接口定义了实现者能提供那些服务。
接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的模块之间的接口定义好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。接口和实现类不是父子关系,是实现规则的关系。即,普通类是具体实现;抽象类是具体实现、规范(抽象方法);接口是规范。
2.2如何定义和使用接口(JDK8 以前):
声明格式:
[访问修饰符] interface 接口名 [extends 父接口 1,父接口 2…]{
常量定义;
方法定义;
}
2.3定义接口的详细说明:
访问修饰符:只能是 public 或默认。
接口名:和类名采用相同命名机制。
extends:接口可以多继承。
常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
方法:接口中的方法只能是:public abstract。 省略的话,也是 public abstract。
2.4要点
子类通过 implements 来实现接口中的规范。
接口不能创建实例,但是可用于声明引用变量类型。
一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是 public 的。
JDK1.8(不含 8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。JDK1.8(含 8)后,接口中包含普通的静态方法、默认方法。
2.5接口中定义静态方法和默认方法(JDK8 以后)
JAVA8 之前,接口里的方法要求全部是抽象方法。JAVA8(含 8)之后,以后允许在接口里定义默认方法和类方法。
默认方法:
Java 8 及以上新版本,允许给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做默认方法(也称为扩展方法)。默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供默认方法的实现,所有这个接口的实现类都会通过继承得到这个方法。
静态方法:
JAVA8 以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是类,一种特殊的类),可以通过接口名调用。如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用。
2.6接口的多继承
接口完全支持多继承。和类的继承类似,子接口扩展某个父接口,将会获得父接口中所定义的一切。
interface A{ void testa(); } interface B{ void testb(); } /**接口可以多继承*/ interface C extends A,B{ void teatc(); } public class Test implements C{ public void testc(){ } public void testa(){ } public void testb(){ } }
2.7面向接口编程
面向接口编程是面向对象编程的一部分。接口就是规范,就是项目中最稳定的核心! 面向接口编程可以让我们把握住真正核心的东西,使实现复杂多变的需求成为可能。
通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩展性和和可维护性。面向接口编程的概念比接口本身的概念要大得多。设计阶段相对比较困难,在你没有写实现时就要想好接口,接口一变就乱套了,所以设计要比实现难!
十一、字符串String类详解
1.String基础
String 类又称作不可变字符序列。
String 位于 java.lang 包中,Java 程序默认导入 java.lang 包下的所有类。
Java 字符串就是 Unicode 字符序列,例如字符串“Java”就是 4 个 Unicode 字符’J’、’a’、’v’、’a’组成的。
Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是 String 类的一个实例。
Java 允许使用符号”+”把两个字符串连接起来。符号”+”把两个字符串按给定的顺序连接在一起,并且是完全按照给定的形式。当”+”运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串然后再进行连接。
2.字符串相等判断
equals 方法用来检测两个字符串内容是否相等。如果字符串 s 和 t 内容相等,则s.equals(t)返回 true,否则返回 false。
要测试两个字符串除了大小写区别外是否是相等的,需要使用 equalsIgnoreCase方法。
判断字符串是否相等不要使用”==”。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/53929.html