java面向对象的实例教程,Java基础面向对象
java面向对象的实例教程,Java基础面向对象属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。在定义成员变量时可以对其初始化,如果不对其初始化,Java 使用默认的值对其初始化。// 每一个源文件必须有且只有一个public class,并且类名和文件名保持一致! public class Car { } class Tyre { // 一个Java文件可以同时定义多个class } class Engine { } class Seat { }上面的类定义好后,没有任何的其他信息,就跟我们拿到一张张图纸,但是纸上没有任何信息,这是一个空类,没有任何实际意义。所以,我们需要定义类的具体信息。对于一个类来说,一般有三种常见的成员:属性 field、方法 method、构造器 constructor。这三种成员都可以定义零个或多个。面向对象和面向过程思想的总结:都是解决问题的思维方式,都是代码组织
一、面向过程和面向对象思想面向过程和面向对象都是对软件分析、设计和开发的一种思想 它指导着人们以不同的方式去分析、设计和开发软件。早期先有面向过程思想,随着软件规模的扩大,问题复杂性的提高,面向过程的弊端越来越明显的显示出来,出现了面向对象思想并成为目前主流的方式。两者都贯穿于软件分析、设计和开发各个阶段,对应面向对象就分别称为面向对象分析(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 "张三" "123456");
}
}
如果方法构造中形参名与属性名相同时,需要使用 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 |
包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。 |
如果我们要使用其他包的类,需要使用 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方法。
判断字符串是否相等不要使用"=="。